diff options
-rw-r--r-- | UPGRADE.txt | 7 | ||||
-rw-r--r-- | apps/app_disa.c | 15 | ||||
-rw-r--r-- | apps/app_playtones.c | 129 | ||||
-rw-r--r-- | apps/app_read.c | 8 | ||||
-rw-r--r-- | apps/app_readexten.c | 9 | ||||
-rw-r--r-- | channels/chan_misdn.c | 16 | ||||
-rw-r--r-- | channels/chan_skinny.c | 14 | ||||
-rw-r--r-- | channels/chan_unistim.c | 23 | ||||
-rw-r--r-- | configs/indications.conf.sample | 12 | ||||
-rw-r--r-- | funcs/func_channel.c | 13 | ||||
-rw-r--r-- | include/asterisk/_private.h | 2 | ||||
-rw-r--r-- | include/asterisk/channel.h | 2 | ||||
-rw-r--r-- | include/asterisk/indications.h | 292 | ||||
-rw-r--r-- | main/app.c | 13 | ||||
-rw-r--r-- | main/asterisk.c | 5 | ||||
-rw-r--r-- | main/channel.c | 11 | ||||
-rw-r--r-- | main/indications.c | 1111 | ||||
-rw-r--r-- | main/loader.c | 1 | ||||
-rw-r--r-- | main/pbx.c | 8 | ||||
-rw-r--r-- | res/res_indications.c | 452 | ||||
-rw-r--r-- | res/snmp/agent.c | 60 |
21 files changed, 1324 insertions, 879 deletions
diff --git a/UPGRADE.txt b/UPGRADE.txt index ccfe1c510..2479ed13f 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -18,6 +18,13 @@ === =========================================================== +From 1.6.1 to 1.6.2: + +* The res_indications module has been removed. Its functionality was important + enough that most of it has been moved into the Asterisk core. + Two applications previously provided by res_indications, PlayTones and + StopPlayTones, have been moved into a new module, app_playtones. + From 1.6.0.1 to 1.6.1: * The ast_agi_register_multiple() and ast_agi_unregister_multiple() diff --git a/apps/app_disa.c b/apps/app_disa.c index e5814dd3c..691fa94ab 100644 --- a/apps/app_disa.c +++ b/apps/app_disa.c @@ -124,15 +124,20 @@ AST_APP_OPTIONS(app_opts, { static void play_dialtone(struct ast_channel *chan, char *mailbox) { - const struct tone_zone_sound *ts = NULL; - if(ast_app_has_voicemail(mailbox, NULL)) + struct ast_tone_zone_sound *ts = NULL; + + if (ast_app_has_voicemail(mailbox, NULL)) { ts = ast_get_indication_tone(chan->zone, "dialrecall"); - else + } else { ts = ast_get_indication_tone(chan->zone, "dial"); - if (ts) + } + + if (ts) { ast_playtones_start(chan, 0, ts->data, 0); - else + ts = ast_tone_zone_sound_unref(ts); + } else { ast_tonepair_start(chan, 350, 440, 0, 0); + } } static int disa_exec(struct ast_channel *chan, void *data) diff --git a/apps/app_playtones.c b/apps/app_playtones.c new file mode 100644 index 000000000..6aff16860 --- /dev/null +++ b/apps/app_playtones.c @@ -0,0 +1,129 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2009, Digium, Inc. + * + * Russell Bryant <russell@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief Playtones application + * + * \author Russell Bryant <russell@digium.com> + * + * \ingroup applications + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/pbx.h" +#include "asterisk/channel.h" +#include "asterisk/indications.h" + +static const char playtones_app[] = "PlayTones"; +static const char stopplaytones_app[] = "StopPlayTones"; + +/*** DOCUMENTATION + <application name="PlayTones" language="en_US"> + <synopsis> + Play a tone list. + </synopsis> + <syntax> + <parameter name="arg" required="true"> + <para>Arg is either the tone name defined in the <filename>indications.conf</filename> + configuration file, or a directly specified list of frequencies and durations.</para> + </parameter> + </syntax> + <description> + <para>Plays a tone list. Execution will continue with the next step in the dialplan + immediately while the tones continue to play.</para> + <para>See the sample <filename>indications.conf</filename> for a description of the + specification of a tonelist.</para> + </description> + <see-also> + <ref type="application">StopPlayTones</ref> + </see-also> + </application> + <application name="StopPlayTones" language="en_US"> + <synopsis> + Stop playing a tone list. + </synopsis> + <syntax /> + <description> + <para>Stop playing a tone list, initiated by PlayTones().</para> + </description> + <see-also> + <ref type="application">PlayTones</ref> + </see-also> + </application> + ***/ + +static int handle_playtones(struct ast_channel *chan, void *data) +{ + struct ast_tone_zone_sound *ts; + int res; + const char *str = data; + + if (ast_strlen_zero(str)) { + ast_log(LOG_NOTICE,"Nothing to play\n"); + return -1; + } + + ts = ast_get_indication_tone(chan->zone, str); + + if (ts) { + res = ast_playtones_start(chan, 0, ts->data, 0); + ts = ast_tone_zone_sound_unref(ts); + } else { + res = ast_playtones_start(chan, 0, str, 0); + } + + if (res) { + ast_log(LOG_NOTICE, "Unable to start playtones\n"); + } + + return res; +} + +static int handle_stopplaytones(struct ast_channel *chan, void *data) +{ + ast_playtones_stop(chan); + + return 0; +} + +static int unload_module(void) +{ + int res; + + res = ast_unregister_application(playtones_app); + res |= ast_unregister_application(stopplaytones_app); + + return res; +} + +static int load_module(void) +{ + int res; + + res = ast_register_application_xml(playtones_app, handle_playtones); + res |= ast_register_application_xml(stopplaytones_app, handle_stopplaytones); + + return res ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Playtones Application"); diff --git a/apps/app_read.c b/apps/app_read.c index 6fb00b779..6b4088cb7 100644 --- a/apps/app_read.c +++ b/apps/app_read.c @@ -132,7 +132,7 @@ static int read_exec(struct ast_channel *chan, void *data) int tries = 1, to = 0, x = 0; double tosec; char *argcopy = NULL; - struct tone_zone_sound *ts = NULL; + struct ast_tone_zone_sound *ts = NULL; struct ast_flags flags = {0}; const char *status = "ERROR"; @@ -188,7 +188,7 @@ static int read_exec(struct ast_channel *chan, void *data) return 0; } if (ast_test_flag(&flags, OPT_INDICATION)) { - if (! ast_strlen_zero(arglist.filename)) { + if (!ast_strlen_zero(arglist.filename)) { ts = ast_get_indication_tone(chan->zone, arglist.filename); } } @@ -258,6 +258,10 @@ static int read_exec(struct ast_channel *chan, void *data) } } + if (ts) { + ts = ast_tone_zone_sound_unref(ts); + } + if (ast_check_hangup(chan)) status = "HANGUP"; pbx_builtin_setvar_helper(chan, "READSTATUS", status); diff --git a/apps/app_readexten.c b/apps/app_readexten.c index 5649a34a1..d5075ede8 100644 --- a/apps/app_readexten.c +++ b/apps/app_readexten.c @@ -132,7 +132,7 @@ static int readexten_exec(struct ast_channel *chan, void *data) int maxdigits = sizeof(exten) - 1; int timeout = 0, digit_timeout = 0, x = 0; char *argcopy = NULL, *status = ""; - struct tone_zone_sound *ts = NULL; + struct ast_tone_zone_sound *ts = NULL; struct ast_flags flags = {0}; AST_DECLARE_APP_ARGS(arglist, @@ -179,8 +179,9 @@ static int readexten_exec(struct ast_channel *chan, void *data) if (digit_timeout <= 0) digit_timeout = chan->pbx ? chan->pbx->dtimeoutms : 5000; - if (ast_test_flag(&flags, OPT_INDICATION) && !ast_strlen_zero(arglist.filename)) + if (ast_test_flag(&flags, OPT_INDICATION) && !ast_strlen_zero(arglist.filename)) { ts = ast_get_indication_tone(chan->zone, arglist.filename); + } do { if (chan->_state != AST_STATE_UP) { @@ -250,6 +251,10 @@ static int readexten_exec(struct ast_channel *chan, void *data) } } while (0); + if (ts) { + ts = ast_tone_zone_sound_unref(ts); + } + pbx_builtin_setvar_helper(chan, "READEXTENSTATUS", status); return status[0] == 'H' ? -1 : 0; diff --git a/channels/chan_misdn.c b/channels/chan_misdn.c index 7fab5ba0b..a1df2b713 100644 --- a/channels/chan_misdn.c +++ b/channels/chan_misdn.c @@ -379,7 +379,7 @@ struct chan_list { * \brief Tone zone sound used for dialtone generation. * \note Used as a boolean. Non-NULL to prod generation if enabled. */ - const struct tone_zone_sound *ts; + struct ast_tone_zone_sound *ts; /*! * \brief Enables overlap dialing for the set amount of seconds. (0 = Disabled) @@ -3357,7 +3357,6 @@ static enum ast_bridge_result misdn_bridge (struct ast_channel *c0, static int dialtone_indicate(struct chan_list *cl) { - const struct tone_zone_sound *ts = NULL; struct ast_channel *ast = cl->ast; int nd = 0; @@ -3374,14 +3373,14 @@ static int dialtone_indicate(struct chan_list *cl) } chan_misdn_log(3, cl->bc->port, " --> Dial\n"); - ts = ast_get_indication_tone(ast->zone, "dial"); - cl->ts = ts; + + cl->ts = ast_get_indication_tone(ast->zone, "dial"); - if (ts) { + if (cl->ts) { cl->notxtone = 0; cl->norxtone = 0; /* This prods us in misdn_write */ - ast_playtones_start(ast, 0, ts->data, 0); + ast_playtones_start(ast, 0, cl->ts->data, 0); } return 0; @@ -3406,8 +3405,9 @@ static int stop_indicate(struct chan_list *cl) misdn_lib_tone_generator_stop(cl->bc); ast_playtones_stop(ast); - cl->ts = NULL; - /*ast_deactivate_generator(ast);*/ + if (cl->ts) { + cl->ts = ast_tone_zone_sound_unref(cl->ts); + } return 0; } diff --git a/channels/chan_skinny.c b/channels/chan_skinny.c index fddd78170..d9e9163de 100644 --- a/channels/chan_skinny.c +++ b/channels/chan_skinny.c @@ -3751,7 +3751,7 @@ static int skinny_transfer(struct skinny_subchannel *sub) { struct skinny_subchannel *xferor; /* the sub doing the transferring */ struct skinny_subchannel *xferee; /* the sub being transferred */ - const struct tone_zone_sound *ts = NULL; + struct ast_tone_zone_sound *ts = NULL; if (ast_bridged_channel(sub->owner) || ast_bridged_channel(sub->related->owner)) { if (sub->xferor) { @@ -3774,8 +3774,10 @@ static int skinny_transfer(struct skinny_subchannel *sub) } if (xferor->owner->_state == AST_STATE_RING) { /* play ringing inband */ - ts = ast_get_indication_tone(xferor->owner->zone, "ring"); - ast_playtones_start(xferor->owner, 0, ts->data, 1); + if ((ts = ast_get_indication_tone(xferor->owner->zone, "ring"))) { + ast_playtones_start(xferor->owner, 0, ts->data, 1); + ts = ast_tone_zone_sound_unref(ts); + } } if (skinnydebug) ast_debug(1, "Transfer Masquerading %s to %s\n", @@ -3789,8 +3791,10 @@ static int skinny_transfer(struct skinny_subchannel *sub) ast_queue_control(xferee->owner, AST_CONTROL_UNHOLD); if (xferor->owner->_state == AST_STATE_RING) { /* play ringing inband */ - ts = ast_get_indication_tone(xferor->owner->zone, "ring"); - ast_playtones_start(xferor->owner, 0, ts->data, 1); + if ((ts = ast_get_indication_tone(xferor->owner->zone, "ring"))) { + ast_playtones_start(xferor->owner, 0, ts->data, 1); + ts = ast_tone_zone_sound_unref(ts); + } } if (skinnydebug) ast_debug(1, "Transfer Masquerading %s to %s\n", diff --git a/channels/chan_unistim.c b/channels/chan_unistim.c index 48d11e481..8229c9a32 100644 --- a/channels/chan_unistim.c +++ b/channels/chan_unistim.c @@ -439,7 +439,7 @@ static struct unistim_device { char datetimeformat; /*!< format used for displaying time/date */ char contrast; /*!< contrast */ char country[3]; /*!< country used for dial tone frequency */ - struct tone_zone *tz; /*!< Tone zone for res_indications (ring, busy, congestion) */ + struct ast_tone_zone *tz; /*!< Tone zone for res_indications (ring, busy, congestion) */ char ringvolume; /*!< Ring volume */ char ringstyle; /*!< Ring melody */ int rtp_port; /*!< RTP port used by the phone */ @@ -4057,17 +4057,17 @@ static char *control2str(int ind) return "UNKNOWN"; } -static void in_band_indication(struct ast_channel *ast, const struct tone_zone *tz, +static void in_band_indication(struct ast_channel *ast, const struct ast_tone_zone *tz, const char *indication) { - const struct tone_zone_sound *ts = NULL; + struct ast_tone_zone_sound *ts = NULL; - ts = ast_get_indication_tone(tz, indication); - - if (ts && ts->data[0]) + if ((ts = ast_get_indication_tone(tz, indication))) { ast_playtones_start(ast, 0, ts->data, 1); - else + ts = ast_tone_zone_sound_unref(ts); + } else { ast_log(LOG_WARNING, "Unable to get indication tone for %s\n", indication); + } } static int unistim_indicate(struct ast_channel *ast, int ind, const void *data, @@ -5223,6 +5223,9 @@ static struct unistim_device *build_device(const char *cat, const struct ast_var ast_log(LOG_ERROR, "An Unistim device must have at least one line!\n"); ast_mutex_destroy(&l->lock); ast_free(l); + if (d->tz) { + d->tz = ast_tone_zone_unref(d->tz); + } ast_free(d); return NULL; } @@ -5240,6 +5243,9 @@ static struct unistim_device *build_device(const char *cat, const struct ast_var ast_log(LOG_ERROR, "You must specify the mac address with device=\n"); ast_mutex_destroy(&l->lock); ast_free(l); + if (d->tz) { + d->tz = ast_tone_zone_unref(d->tz); + } ast_free(d); return NULL; } else @@ -5461,6 +5467,9 @@ static int reload_config(void) d2 = d2->next; } } + if (d->tz) { + d->tz = ast_tone_zone_unref(d->tz); + } ast_free(d); d = devices; continue; diff --git a/configs/indications.conf.sample b/configs/indications.conf.sample index d7088db7c..239dcd11c 100644 --- a/configs/indications.conf.sample +++ b/configs/indications.conf.sample @@ -1,6 +1,9 @@ +; ; indications.conf +; ; Configuration file for location specific tone indications -; used by the pbx_indications module. +; + ; ; NOTE: ; When adding countries to this file, please keep them in alphabetical @@ -9,7 +12,7 @@ ; The [general] category is for certain global variables. ; All other categories are interpreted as location specific indications ; -; + [general] country=us ; default location @@ -17,9 +20,6 @@ country=us ; default location ; [example] ; description = string ; The full name of your country, in English. -; alias = iso[,iso]* -; List of other countries 2-letter iso codes, which have the same -; tone indications. ; ringcadence = num[,num]* ; List of durations the physical bell rings. ; dial = tonelist @@ -56,8 +56,6 @@ country=us ; default location ; element = [!]freq[+|*freq2][/duration] ; tonelist = element[,element]* ; -; Please note that SPACES ARE NOT ALLOWED in tone lists! -; [at] description = Austria diff --git a/funcs/func_channel.c b/funcs/func_channel.c index 959f332d2..ff8eeb2e5 100644 --- a/funcs/func_channel.c +++ b/funcs/func_channel.c @@ -296,12 +296,19 @@ static int func_channel_write(struct ast_channel *chan, const char *function, } #endif else if (!strcasecmp(data, "tonezone")) { - struct tone_zone *new_zone; + struct ast_tone_zone *new_zone; if (!(new_zone = ast_get_indication_zone(value))) { ast_log(LOG_ERROR, "Unknown country code '%s' for tonezone. Check indications.conf for available country codes.\n", value); ret = -1; - } else - chan->zone = new_zone; + } else { + ast_channel_lock(chan); + if (chan->zone) { + chan->zone = ast_tone_zone_unref(chan->zone); + } + chan->zone = ast_tone_zone_ref(new_zone); + ast_channel_unlock(chan); + new_zone = ast_tone_zone_unref(new_zone); + } } else if (!strcasecmp(data, "callgroup")) chan->callgroup = ast_get_group(value); else if (!strcasecmp(data, "txgain")) { diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h index 98857025c..34c87c767 100644 --- a/include/asterisk/_private.h +++ b/include/asterisk/_private.h @@ -39,6 +39,8 @@ int ast_http_init(void); /*!< Provided by http.c */ int ast_http_reload(void); /*!< Provided by http.c */ int ast_tps_init(void); /*!< Provided by taskprocessor.c */ int ast_timing_init(void); /*!< Provided by timing.c */ +int ast_indications_init(void); /*!< Provided by indications.c */ +int ast_indications_reload(void);/*!< Provided by indications.c */ /*! * \brief Reload asterisk modules. diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h index 183a36bbf..04769e7c6 100644 --- a/include/asterisk/channel.h +++ b/include/asterisk/channel.h @@ -424,7 +424,7 @@ struct ast_channel { struct ast_trans_pvt *readtrans; /*!< Read translation path */ struct ast_audiohook_list *audiohooks; struct ast_cdr *cdr; /*!< Call Detail Record */ - struct tone_zone *zone; /*!< Tone zone as set in indications.conf or + struct ast_tone_zone *zone; /*!< Tone zone as set in indications.conf or in the CHANNEL dialplan function */ struct ast_channel_monitor *monitor; /*!< Channel monitoring */ #ifdef HAVE_EPOLL diff --git a/include/asterisk/indications.h b/include/asterisk/indications.h index b32fe500e..bf2f323b2 100644 --- a/include/asterisk/indications.h +++ b/include/asterisk/indications.h @@ -1,91 +1,251 @@ /* * Asterisk -- An open source telephony toolkit. * + * Copyright (C) 2002, Pauline Middelink + * Copyright (C) 2009, Digium, Inc. + * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. */ -/*! \file - * \brief BSD Telephony Of Mexico "Tormenta" Tone Zone Support 2/22/01 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * Primary Author: Pauline Middelink <middelink@polyware.nl> +/*! + * \file + * \brief Tone Indication Support * + * \author Pauline Middelink <middelink@polyware.nl> + * \author Russell Bryant <russell@digium.com> */ #ifndef _ASTERISK_INDICATIONS_H #define _ASTERISK_INDICATIONS_H -#include "asterisk/lock.h" - -/*! \brief Description is a series of tones of the format: - [!]freq1[+freq2][/duration] separated by commas. There - are no spaces. The sequence is repeated back to the - first tone description not preceeded by !. Duration is - specified in milliseconds */ -struct tone_zone_sound { - const char *name; /*!< Identifing name */ - const char *data; /*!< Actual zone description */ - AST_LIST_ENTRY(tone_zone_sound) list; +#include "asterisk/astobj2.h" + +/*! + * \brief Description of a tone + */ +struct ast_tone_zone_sound { + /*! \brief Name of the tone. For example, "busy". */ + const char *name; + /*! + * \brief Description of a tone + * + * The format is a comma separated list of tone parts in the following format: + * + * Format: [!][M]freq[<+|*>freq2][/duration] + * - '!' - means that the element is NOT repeated + * - 'M' - interpret the frequencies as midi notes instead of frequencies + * - freq - The first frequency + * - freq2 - The second frequency (optional) + * - '*' - modulate freq by freq2 at a fixed depth of 90% + * - '+' - combine the frequencies + * - duration - the length of the tone part (optional, forever if not specified) + */ + const char *data; + /*! \brief Linked list fields for including in the list on an ast_tone_zone */ + AST_LIST_ENTRY(ast_tone_zone_sound) entry; + /*! \brief Flags only used internally */ + union { + uint32_t __padding; + struct { + unsigned int killme:1; + }; + }; +}; + +/*! + * \brief A set of tones for a given locale + * + * \note If a reference to this tone zone is held, then the country + * is guaranteed not to change. It is safe to read it without + * locking the tone zone. This is not the case for any other + * field. + */ +struct ast_tone_zone { + /*! \brief Country code that this set of tones is for */ + char country[5]; + /*! + * \brief Text description of the given country. + * + * This is for nothing more than friendly display to a human. + */ + char description[40]; + /*! \brief Number of ring cadence elements in the ringcadence array */ + unsigned int nrringcadence; + /*! + * \brief Array of ring cadence parts + * + * Each element is an amount of time in milliseconds. The first element + * is for time on, and from there it alternates between on and off. + */ + int *ringcadence; + /*! \brief A list of tones for this locale */ + AST_LIST_HEAD_NOLOCK(, ast_tone_zone_sound) tones; + /*! \brief Flags only used internally */ + union { + uint32_t __padding; + struct { + unsigned int killme:1; + }; + }; }; -struct tone_zone { - AST_RWLIST_ENTRY(tone_zone) list; - char country[5]; /*!< Country code */ - char alias[5]; /*!< is this an alias? */ - char description[40]; /*!< Description */ - int nrringcadence; /*!< # registered ringcadence elements */ - int *ringcadence; /*!< Ring cadence */ - AST_LIST_HEAD_NOLOCK(, tone_zone_sound) tones; /*!< The known tones for this zone */ +/*! + * \brief A description of a part of a tone + * + * The elements in this structure map to the format described for the data + * part of the ast_tone_zone_sound struct. + */ +struct ast_tone_zone_part { + unsigned int freq1; + unsigned int freq2; + unsigned int time; + unsigned int modulate:1; + unsigned int midinote:1; }; -/*! \brief set the default tone country */ -int ast_set_indication_country(const char *country); - -/*! \brief locate tone_zone, given the country. if country == NULL, use the default country */ -struct tone_zone *ast_get_indication_zone(const char *country); -/*! \brief locate a tone_zone_sound, given the tone_zone. if tone_zone == NULL, use the default tone_zone */ -struct tone_zone_sound *ast_get_indication_tone(const struct tone_zone *zone, const char *indication); -/*! \brief deallocate the passed tone zone */ -void ast_destroy_indication_zone(struct tone_zone *zone); - -/*! \brief add a new country, if country exists, it will be replaced. */ -int ast_register_indication_country(struct tone_zone *country); -/*! \brief remove an existing country and all its indications, country must exist */ -int ast_unregister_indication_country(const char *country); -/*! \brief add a new indication to a tone_zone. tone_zone must exist. if the indication already - * exists, it will be replaced. */ -int ast_register_indication(struct tone_zone *zone, const char *indication, const char *tonelist); -/*! \brief remove an existing tone_zone's indication. tone_zone must exist */ -int ast_unregister_indication(struct tone_zone *zone, const char *indication); - -/*! \brief Start a tone-list going */ -int ast_playtones_start(struct ast_channel *chan, int vol, const char* tonelist, int interruptible); -/*! \brief Stop the tones from playing */ +/*! + * \brief Parse a tone part + * + * \param s The part of a tone to parse. This should be in the form described for + * the data part of ast_tone_zone_sound. '!' should be removed if present. + * \param tone_data An output parameter that contains the result of the parsing. + * + * \retval 0 success + * \retval -1 failure, and the contents of tone_data are undefined + */ +int ast_tone_zone_part_parse(const char *s, struct ast_tone_zone_part *tone_data); + +/*! + * \brief locate ast_tone_zone + * + * \param country country to find. If NULL is provided, get the default. + * + * \return a reference to the specified country if found or NULL if not found + */ +struct ast_tone_zone *ast_get_indication_zone(const char *country); + +/*! + * \brief Locate a tone zone sound + * + * \param zone Zone to look in for a sound, if NULL, the default will be used + * \param indication Sound to look for, such as "busy" + * + * \return a reference to the specified sound if it exists, NULL if not + */ +struct ast_tone_zone_sound *ast_get_indication_tone(const struct ast_tone_zone *zone, const char *indication); + +/*! + * \brief Start playing a list of tones on a channel + * + * \param chan the channel to play tones on + * \param vol volume + * \param tonelist the list of tones to play, comma separated + * \param interruptible whether or not this tone can be interrupted + * + * \retval 0 success + * \retval non-zero failure + */ +int ast_playtones_start(struct ast_channel *chan, int vol, const char *tonelist, int interruptible); + +/*! + * \brief Stop playing tones on a channel + * + * \param chan the channel to stop tones on + */ void ast_playtones_stop(struct ast_channel *chan); -/*! \brief support for walking through a list of indications */ -struct tone_zone *ast_walk_indications(const struct tone_zone *cur); +/*! + * \brief Get the number of registered tone zones + * + * \return the total number of registered tone zones + */ +int ast_tone_zone_count(void); + +/*! + * \brief Get an iterator for the available tone zones + * + * Use ao2_iterator_next() to iterate the tone zones. + * + * \return an initialized iterator + */ +struct ao2_iterator ast_tone_zone_iterator_init(void); + +extern struct ast_tone_zone __fake_tone_zone; +extern struct ast_tone_zone_sound __fake_tone_zone_sound; + +#define AST_CHECK_TONE_ZONE(tz) do { \ + (void) ((tz) == (&__fake_tone_zone)); \ +} while (0) + +#define AST_CHECK_TONE_ZONE_SOUND(ts) do { \ + (void) ((ts) == (&__fake_tone_zone_sound)); \ +} while (0) + +/*! + * \brief Lock an ast_tone_zone + */ +#define ast_tone_zone_lock(tz) ao2_lock(tz) + +/*! + * \brief Unlock an ast_tone_zone + */ +#define ast_tone_zone_unlock(tz) ao2_unlock(tz) -#if 0 -extern struct tone_zone *tone_zones; -extern ast_mutex_t tzlock; -#endif +/*! + * \brief Trylock an ast_tone_zone + */ +#define ast_tone_zone_trylock(tz) ao2_trylock(tz) + +/*! + * \brief Release a reference to an ast_tone_zone + * + * \return NULL + */ +#define ast_tone_zone_unref(tz) ({ \ + AST_CHECK_TONE_ZONE(tz); \ + ao2_ref(tz, -1); \ + (NULL); \ +}) + +/*! + * \brief Increase the reference count on an ast_tone_zone + * + * \return The tone zone provided as an argument + */ +#define ast_tone_zone_ref(tz) ({ \ + AST_CHECK_TONE_ZONE(tz); \ + ao2_ref(tz, +1); \ + (tz); \ +}) + +/*! + * \brief Release a reference to an ast_tone_zone_sound + * + * \return NULL + */ +#define ast_tone_zone_sound_unref(ts) ({ \ + AST_CHECK_TONE_ZONE_SOUND(ts); \ + ao2_ref(ts, -1); \ + (NULL); \ +}) + +/*! + * \brief Increase the reference count on an ast_tone_zone_sound + * + * \return The tone zone sound provided as an argument + */ +#define ast_tone_zone_sound_ref(ts) ({ \ + AST_CHECK_TONE_ZONE_SOUND(ts); \ + ao2_ref(ts, +1); \ + (ts); \ +}) #endif /* _ASTERISK_INDICATIONS_H */ diff --git a/main/app.c b/main/app.c index 789425fb7..83939c55b 100644 --- a/main/app.c +++ b/main/app.c @@ -75,21 +75,24 @@ static AST_RWLIST_HEAD_STATIC(groups, ast_group_info); */ int ast_app_dtget(struct ast_channel *chan, const char *context, char *collect, size_t size, int maxlen, int timeout) { - struct tone_zone_sound *ts; + struct ast_tone_zone_sound *ts; int res = 0, x = 0; if (maxlen > size) maxlen = size; - if (!timeout && chan->pbx) + if (!timeout && chan->pbx) { timeout = chan->pbx->dtimeoutms / 1000.0; - else if (!timeout) + } else if (!timeout) { timeout = 5; + } - if ((ts = ast_get_indication_tone(chan->zone, "dial")) && ts->data[0]) + if ((ts = ast_get_indication_tone(chan->zone, "dial"))) { res = ast_playtones_start(chan, 0, ts->data, 0); - else + ts = ast_tone_zone_sound_unref(ts); + } else { ast_log(LOG_NOTICE, "Huh....? no dial for indications?\n"); + } for (x = strlen(collect); x < maxlen; ) { res = ast_waitfordigit(chan, timeout); diff --git a/main/asterisk.c b/main/asterisk.c index 3567fbae5..d7808a6af 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -3566,6 +3566,11 @@ int main(int argc, char *argv[]) exit(1); } + if (ast_indications_init()) { + printf("%s", term_quit()); + exit(1); + } + ast_features_init(); if (init_framer()) { diff --git a/main/channel.c b/main/channel.c index f09433387..344b3a9f1 100644 --- a/main/channel.c +++ b/main/channel.c @@ -1403,7 +1403,11 @@ void ast_channel_free(struct ast_channel *chan) ast_cdr_discard(chan->cdr); chan->cdr = NULL; } - + + if (chan->zone) { + chan->zone = ast_tone_zone_unref(chan->zone); + } + ast_mutex_destroy(&chan->lock_dont_use); ast_string_field_free_memory(chan); @@ -2910,7 +2914,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition, /* By using an enum, we'll get compiler warnings for values not handled * in switch statements. */ enum ast_control_frame_type condition = _condition; - const struct tone_zone_sound *ts = NULL; + struct ast_tone_zone_sound *ts = NULL; int res = -1; ast_channel_lock(chan); @@ -2981,10 +2985,11 @@ int ast_indicate_data(struct ast_channel *chan, int _condition, break; } - if (ts && ts->data[0]) { + if (ts) { /* We have a tone to play, yay. */ ast_debug(1, "Driver for channel '%s' does not support indication %d, emulating it\n", chan->name, condition); ast_playtones_start(chan, 0, ts->data, 1); + ts = ast_tone_zone_sound_unref(ts); res = 0; chan->visible_indication = condition; } diff --git a/main/indications.c b/main/indications.c index c96dc41f1..1fe01f148 100644 --- a/main/indications.c +++ b/main/indications.c @@ -2,7 +2,7 @@ * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2002, Pauline Middelink - * + * Copyright (C) 2009, Digium, Inc. * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact @@ -15,17 +15,12 @@ * at the top of the source tree. */ -/*! \file +/*! + * \file + * \brief Indication Tone Handling * - * \brief Tone Management - * * \author Pauline Middelink <middelink@polyware.nl> - * - * This set of function allow us to play a list of tones on a channel. - * Each element has two frequencies, which are mixed together and a - * duration. For silence both frequencies can be set to 0. - * The playtones can be given as a comma separated string. - * + * \author Russell Bryant <russell@digium.com> */ #include "asterisk.h" @@ -40,22 +35,39 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/frame.h" #include "asterisk/channel.h" #include "asterisk/utils.h" +#include "asterisk/cli.h" +#include "asterisk/module.h" +#include "asterisk/astobj2.h" + +#include "asterisk/_private.h" /* _init(), _reload() */ + +/* Globals */ +static const char config[] = "indications.conf"; + +static const int midi_tohz[128] = { + 8, 8, 9, 9, 10, 10, 11, 12, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, + 25, 27, 29, 30, 32, 34, 36, 38, 41, 43, + 46, 48, 51, 55, 58, 61, 65, 69, 73, 77, + 82, 87, 92, 97, 103, 110, 116, 123, 130, 138, + 146, 155, 164, 174, 184, 195, 207, 220, 233, 246, + 261, 277, 293, 311, 329, 349, 369, 391, 415, 440, + 466, 493, 523, 554, 587, 622, 659, 698, 739, 783, + 830, 880, 932, 987, 1046, 1108, 1174, 1244, 1318, 1396, + 1479, 1567, 1661, 1760, 1864, 1975, 2093, 2217, 2349, 2489, + 2637, 2793, 2959, 3135, 3322, 3520, 3729, 3951, 4186, 4434, + 4698, 4978, 5274, 5587, 5919, 6271, 6644, 7040, 7458, 7902, + 8372, 8869, 9397, 9956, 10548, 11175, 11839, 12543 +}; + +static struct ao2_container *ast_tone_zones; -static int midi_tohz[128] = { - 8,8,9,9,10,10,11,12,12,13,14, - 15,16,17,18,19,20,21,23,24,25, - 27,29,30,32,34,36,38,41,43,46, - 48,51,55,58,61,65,69,73,77,82, - 87,92,97,103,110,116,123,130,138,146, - 155,164,174,184,195,207,220,233,246,261, - 277,293,311,329,349,369,391,415,440,466, - 493,523,554,587,622,659,698,739,783,830, - 880,932,987,1046,1108,1174,1244,1318,1396,1479, - 1567,1661,1760,1864,1975,2093,2217,2349,2489,2637, - 2793,2959,3135,3322,3520,3729,3951,4186,4434,4698, - 4978,5274,5587,5919,6271,6644,7040,7458,7902,8372, - 8869,9397,9956,10548,11175,11839,12543 - }; +#define NUM_TONE_ZONE_BUCKETS 53 + +/*! + * \note Access to this is protected by locking the ast_tone_zones container + */ +static struct ast_tone_zone *default_tone_zone; struct playtones_item { int fac1; @@ -100,21 +112,26 @@ static void playtones_release(struct ast_channel *chan, void *params) { struct playtones_state *ps = params; - if (chan) + if (chan) { ast_set_write_format(chan, ps->origwfmt); - if (ps->items) + } + + if (ps->items) { ast_free(ps->items); + ps->items = NULL; + } ast_free(ps); } -static void * playtones_alloc(struct ast_channel *chan, void *params) +static void *playtones_alloc(struct ast_channel *chan, void *params) { struct playtones_def *pd = params; struct playtones_state *ps = NULL; - if (!(ps = ast_calloc(1, sizeof(*ps)))) + if (!(ps = ast_calloc(1, sizeof(*ps)))) { return NULL; + } ps->origwfmt = chan->writeformat; @@ -131,10 +148,11 @@ static void * playtones_alloc(struct ast_channel *chan, void *params) } /* Let interrupts interrupt :) */ - if (pd->interruptible) + if (pd->interruptible) { ast_set_flag(chan, AST_FLAG_WRITE_INT); - else + } else { ast_clear_flag(chan, AST_FLAG_WRITE_INT); + } return ps; } @@ -144,17 +162,20 @@ static int playtones_generator(struct ast_channel *chan, void *data, int len, in struct playtones_state *ps = data; struct playtones_item *pi; int x; - /* we need to prepare a frame with 16 * timelen samples as we're - * generating SLIN audio - */ + + /* we need to prepare a frame with 16 * timelen samples as we're + * generating SLIN audio */ + len = samples * 2; if (len > sizeof(ps->data) / 2 - 1) { ast_log(LOG_WARNING, "Can't generate that much data!\n"); return -1; } + memset(&ps->f, 0, sizeof(ps->f)); pi = &ps->items[ps->npos]; + if (ps->oldnpos != ps->npos) { /* Load new parameters */ ps->v1_1 = 0; @@ -165,163 +186,199 @@ static int playtones_generator(struct ast_channel *chan, void *data, int len, in ps->v3_2 = pi->init_v3_2; ps->oldnpos = ps->npos; } - for (x = 0; x < len/2; x++) { + + for (x = 0; x < samples; x++) { ps->v1_1 = ps->v2_1; ps->v2_1 = ps->v3_1; ps->v3_1 = (pi->fac1 * ps->v2_1 >> 15) - ps->v1_1; - + ps->v1_2 = ps->v2_2; ps->v2_2 = ps->v3_2; ps->v3_2 = (pi->fac2 * ps->v2_2 >> 15) - ps->v1_2; if (pi->modulate) { int p; p = ps->v3_2 - 32768; - if (p < 0) p = -p; + if (p < 0) { + p = -p; + } p = ((p * 9) / 10) + 1; ps->data[x] = (ps->v3_1 * p) >> 15; - } else - ps->data[x] = ps->v3_1 + ps->v3_2; + } else { + ps->data[x] = ps->v3_1 + ps->v3_2; + } } - + ps->f.frametype = AST_FRAME_VOICE; ps->f.subclass = AST_FORMAT_SLINEAR; ps->f.datalen = len; ps->f.samples = samples; ps->f.offset = AST_FRIENDLY_OFFSET; ps->f.data.ptr = ps->data; - ps->f.delivery.tv_sec = 0; - ps->f.delivery.tv_usec = 0; - ast_write(chan, &ps->f); + + if (ast_write(chan, &ps->f)) { + return -1; + } ps->pos += x; + if (pi->duration && ps->pos >= pi->duration * 8) { /* item finished? */ ps->pos = 0; /* start new item */ ps->npos++; if (ps->npos >= ps->nitems) { /* last item? */ - if (ps->reppos == -1) /* repeat set? */ + if (ps->reppos == -1) { /* repeat set? */ return -1; + } ps->npos = ps->reppos; /* redo from top */ } } + return 0; } static struct ast_generator playtones = { - alloc: playtones_alloc, - release: playtones_release, - generate: playtones_generator, + .alloc = playtones_alloc, + .release = playtones_release, + .generate = playtones_generator, }; +int ast_tone_zone_part_parse(const char *s, struct ast_tone_zone_part *tone_data) +{ + if (sscanf(s, "%u+%u/%u", &tone_data->freq1, &tone_data->freq2, + &tone_data->time) == 3) { + /* f1+f2/time format */ + } else if (sscanf(s, "%u+%u", &tone_data->freq1, &tone_data->freq2) == 2) { + /* f1+f2 format */ + tone_data->time = 0; + } else if (sscanf(s, "%u*%u/%u", &tone_data->freq1, &tone_data->freq2, + &tone_data->time) == 3) { + /* f1*f2/time format */ + tone_data->modulate = 1; + } else if (sscanf(s, "%u*%u", &tone_data->freq1, &tone_data->freq2) == 2) { + /* f1*f2 format */ + tone_data->time = 0; + tone_data->modulate = 1; + } else if (sscanf(s, "%u/%u", &tone_data->freq1, &tone_data->time) == 2) { + /* f1/time format */ + tone_data->freq2 = 0; + } else if (sscanf(s, "%u", &tone_data->freq1) == 1) { + /* f1 format */ + tone_data->freq2 = 0; + tone_data->time = 0; + } else if (sscanf(s, "M%u+M%u/%u", &tone_data->freq1, &tone_data->freq2, + &tone_data->time) == 3) { + /* Mf1+Mf2/time format */ + tone_data->midinote = 1; + } else if (sscanf(s, "M%u+M%u", &tone_data->freq1, &tone_data->freq2) == 2) { + /* Mf1+Mf2 format */ + tone_data->time = 0; + tone_data->midinote = 1; + } else if (sscanf(s, "M%u*M%u/%u", &tone_data->freq1, &tone_data->freq2, + &tone_data->time) == 3) { + /* Mf1*Mf2/time format */ + tone_data->modulate = 1; + tone_data->midinote = 1; + } else if (sscanf(s, "M%u*M%u", &tone_data->freq1, &tone_data->freq2) == 2) { + /* Mf1*Mf2 format */ + tone_data->time = 0; + tone_data->modulate = 1; + tone_data->midinote = 1; + } else if (sscanf(s, "M%u/%u", &tone_data->freq1, &tone_data->time) == 2) { + /* Mf1/time format */ + tone_data->freq2 = -1; + tone_data->midinote = 1; + } else if (sscanf(s, "M%u", &tone_data->freq1) == 1) { + /* Mf1 format */ + tone_data->freq2 = -1; + tone_data->time = 0; + tone_data->midinote = 1; + } else { + return -1; + } + + return 0; +} + int ast_playtones_start(struct ast_channel *chan, int vol, const char *playlst, int interruptible) { - char *s, *data = ast_strdupa(playlst); /* cute */ - struct playtones_def d = { vol, -1, 0, 1, NULL}; + char *s, *data = ast_strdupa(playlst); + struct playtones_def d = { vol, -1, 0, 1, NULL }; char *stringp; char *separator; - - if (vol < 1) + static const float sample_rate = 8000.0; + static const float max_sample_val = 32768.0; + + if (vol < 1) { d.vol = 7219; /* Default to -8db */ + } d.interruptible = interruptible; - - stringp=data; - /* the stringp/data is not null here */ + + stringp = data; + /* check if the data is separated with '|' or with ',' by default */ - if (strchr(stringp,'|')) + if (strchr(stringp,'|')) { separator = "|"; - else + } else { separator = ","; - s = strsep(&stringp,separator); - while (s && *s) { - int freq1, freq2, duration, modulate = 0, midinote = 0; + } + + while ((s = strsep(&stringp, separator)) && !ast_strlen_zero(s)) { + struct ast_tone_zone_part tone_data = { + .time = 0, + }; - if (s[0]=='!') + s = ast_strip(s); + + if (s[0]=='!') { s++; - else if (d.reppos == -1) + } else if (d.reppos == -1) { d.reppos = d.nitems; - if (sscanf(s, "%d+%d/%d", &freq1, &freq2, &duration) == 3) { - /* f1+f2/time format */ - } else if (sscanf(s, "%d+%d", &freq1, &freq2) == 2) { - /* f1+f2 format */ - duration = 0; - } else if (sscanf(s, "%d*%d/%d", &freq1, &freq2, &duration) == 3) { - /* f1*f2/time format */ - modulate = 1; - } else if (sscanf(s, "%d*%d", &freq1, &freq2) == 2) { - /* f1*f2 format */ - duration = 0; - modulate = 1; - } else if (sscanf(s, "%d/%d", &freq1, &duration) == 2) { - /* f1/time format */ - freq2 = 0; - } else if (sscanf(s, "%d", &freq1) == 1) { - /* f1 format */ - freq2 = 0; - duration = 0; - } else if (sscanf(s, "M%d+M%d/%d", &freq1, &freq2, &duration) == 3) { - /* Mf1+Mf2/time format */ - midinote = 1; - } else if (sscanf(s, "M%d+M%d", &freq1, &freq2) == 2) { - /* Mf1+Mf2 format */ - duration = 0; - midinote = 1; - } else if (sscanf(s, "M%d*M%d/%d", &freq1, &freq2, &duration) == 3) { - /* Mf1*Mf2/time format */ - modulate = 1; - midinote = 1; - } else if (sscanf(s, "M%d*M%d", &freq1, &freq2) == 2) { - /* Mf1*Mf2 format */ - duration = 0; - modulate = 1; - midinote = 1; - } else if (sscanf(s, "M%d/%d", &freq1, &duration) == 2) { - /* Mf1/time format */ - freq2 = -1; - midinote = 1; - } else if (sscanf(s, "M%d", &freq1) == 1) { - /* Mf1 format */ - freq2 = -1; - duration = 0; - midinote = 1; - } else { - ast_log(LOG_WARNING,"%s: tone component '%s' of '%s' is no good\n",chan->name,s,playlst); - return -1; } - if (midinote) { + if (ast_tone_zone_part_parse(s, &tone_data)) { + ast_log(LOG_ERROR, "Failed to parse tone part '%s'\n", s); + continue; + } + + if (tone_data.midinote) { /* midi notes must be between 0 and 127 */ - if ((freq1 >= 0) && (freq1 <= 127)) - freq1 = midi_tohz[freq1]; - else - freq1 = 0; - if ((freq2 >= 0) && (freq2 <= 127)) - freq2 = midi_tohz[freq2]; - else - freq2 = 0; + if (tone_data.freq1 >= 0 && tone_data.freq1 <= 127) { + tone_data.freq1 = midi_tohz[tone_data.freq1]; + } else { + tone_data.freq1 = 0; + } + + if (tone_data.freq2 >= 0 && tone_data.freq2 <= 127) { + tone_data.freq2 = midi_tohz[tone_data.freq2]; + } else { + tone_data.freq2 = 0; + } } if (!(d.items = ast_realloc(d.items, (d.nitems + 1) * sizeof(*d.items)))) { return -1; } - d.items[d.nitems].fac1 = 2.0 * cos(2.0 * M_PI * (freq1 / 8000.0)) * 32768.0; - d.items[d.nitems].init_v2_1 = sin(-4.0 * M_PI * (freq1 / 8000.0)) * d.vol; - d.items[d.nitems].init_v3_1 = sin(-2.0 * M_PI * (freq1 / 8000.0)) * d.vol; - d.items[d.nitems].fac2 = 2.0 * cos(2.0 * M_PI * (freq2 / 8000.0)) * 32768.0; - d.items[d.nitems].init_v2_2 = sin(-4.0 * M_PI * (freq2 / 8000.0)) * d.vol; - d.items[d.nitems].init_v3_2 = sin(-2.0 * M_PI * (freq2 / 8000.0)) * d.vol; - d.items[d.nitems].duration = duration; - d.items[d.nitems].modulate = modulate; - d.nitems++; + d.items[d.nitems].fac1 = 2.0 * cos(2.0 * M_PI * (tone_data.freq1 / sample_rate)) * max_sample_val; + d.items[d.nitems].init_v2_1 = sin(-4.0 * M_PI * (tone_data.freq1 / sample_rate)) * d.vol; + d.items[d.nitems].init_v3_1 = sin(-2.0 * M_PI * (tone_data.freq1 / sample_rate)) * d.vol; + + d.items[d.nitems].fac2 = 2.0 * cos(2.0 * M_PI * (tone_data.freq2 / sample_rate)) * max_sample_val; + d.items[d.nitems].init_v2_2 = sin(-4.0 * M_PI * (tone_data.freq2 / sample_rate)) * d.vol; + d.items[d.nitems].init_v3_2 = sin(-2.0 * M_PI * (tone_data.freq2 / sample_rate)) * d.vol; - s = strsep(&stringp,separator); + d.items[d.nitems].duration = tone_data.time; + d.items[d.nitems].modulate = tone_data.modulate; + + d.nitems++; } if (ast_activate_generator(chan, &playtones, &d)) { ast_free(d.items); return -1; } + return 0; } @@ -330,265 +387,735 @@ void ast_playtones_stop(struct ast_channel *chan) ast_deactivate_generator(chan); } -/*--------------------------------------------*/ - -static AST_RWLIST_HEAD_STATIC(tone_zones, tone_zone); -static struct tone_zone *current_tonezone; - -struct tone_zone *ast_walk_indications(const struct tone_zone *cur) +int ast_tone_zone_count(void) { - struct tone_zone *tz = NULL; - - AST_RWLIST_RDLOCK(&tone_zones); - /* If cur is not NULL, then we have to iterate through - otherwise just return the first entry */ - if (cur) { - AST_RWLIST_TRAVERSE(&tone_zones, tz, list) { - if (tz == cur) - break; - } - tz = AST_RWLIST_NEXT(tz, list); - } else { - tz = AST_RWLIST_FIRST(&tone_zones); - } - AST_RWLIST_UNLOCK(&tone_zones); + return ao2_container_count(ast_tone_zones); +} - return tz; +struct ao2_iterator ast_tone_zone_iterator_init(void) +{ + return ao2_iterator_init(ast_tone_zones, 0); } /* Set global indication country */ -int ast_set_indication_country(const char *country) +static int ast_set_indication_country(const char *country) { - struct tone_zone *zone = NULL; + struct ast_tone_zone *zone = NULL; /* If no country is specified or we are unable to find the zone, then return not found */ - if (!country || !(zone = ast_get_indication_zone(country))) - return 1; - + if (ast_strlen_zero(country) || !(zone = ast_get_indication_zone(country))) { + return -1; + } + ast_verb(3, "Setting default indication country to '%s'\n", country); - /* Protect the current tonezone using the tone_zones lock as well */ - AST_RWLIST_WRLOCK(&tone_zones); - current_tonezone = zone; - AST_RWLIST_UNLOCK(&tone_zones); + ao2_lock(ast_tone_zones); + if (default_tone_zone) { + default_tone_zone = ast_tone_zone_unref(default_tone_zone); + } + default_tone_zone = ast_tone_zone_ref(zone); + ao2_unlock(ast_tone_zones); + + zone = ast_tone_zone_unref(zone); - /* Zone was found */ return 0; } -/* locate tone_zone, given the country. if country == NULL, use the default country */ -struct tone_zone *ast_get_indication_zone(const char *country) +/* locate ast_tone_zone, given the country. if country == NULL, use the default country */ +struct ast_tone_zone *ast_get_indication_zone(const char *country) { - struct tone_zone *tz = NULL; - int alias_loop = 0; - - AST_RWLIST_RDLOCK(&tone_zones); + struct ast_tone_zone *tz = NULL; + struct ast_tone_zone zone_arg = { + .nrringcadence = 0, + }; if (ast_strlen_zero(country)) { - tz = current_tonezone ? current_tonezone : AST_LIST_FIRST(&tone_zones); - } else { - do { - AST_RWLIST_TRAVERSE(&tone_zones, tz, list) { - if (!strcasecmp(tz->country, country)) - break; - } - if (!tz) - break; - /* If this is an alias then we have to search yet again otherwise we have found the zonezone */ - if (tz->alias && tz->alias[0]) - country = tz->alias; - else - break; - } while ((++alias_loop < 20) && tz); - } + ao2_lock(ast_tone_zones); + if (default_tone_zone) { + tz = ast_tone_zone_ref(default_tone_zone); + } + ao2_unlock(ast_tone_zones); - AST_RWLIST_UNLOCK(&tone_zones); + return tz; + } - /* If we reached the maximum loops to find the proper country via alias, print out a notice */ - if (alias_loop == 20) - ast_log(LOG_NOTICE, "Alias loop for '%s' is bonkers\n", country); + ast_copy_string(zone_arg.country, country, sizeof(zone_arg.country)); - return tz; + return ao2_find(ast_tone_zones, &zone_arg, OBJ_POINTER); } -/* locate a tone_zone_sound, given the tone_zone. if tone_zone == NULL, use the default tone_zone */ -struct tone_zone_sound *ast_get_indication_tone(const struct tone_zone *zone, const char *indication) +struct ast_tone_zone_sound *ast_get_indication_tone(const struct ast_tone_zone *_zone, const char *indication) { - struct tone_zone_sound *ts = NULL; + struct ast_tone_zone_sound *ts = NULL; + /* _zone is const to the users of the API */ + struct ast_tone_zone *zone = (struct ast_tone_zone *) _zone; - AST_RWLIST_RDLOCK(&tone_zones); - - /* If no zone is already specified we need to try to pick one */ + /* If no zone is specified, use the default */ if (!zone) { - if (current_tonezone) { - zone = current_tonezone; - } else if (!(zone = AST_LIST_FIRST(&tone_zones))) { - /* No zone has been found ;( */ - AST_RWLIST_UNLOCK(&tone_zones); + ao2_lock(ast_tone_zones); + if (default_tone_zone) { + zone = ast_tone_zone_ref(default_tone_zone); + } + ao2_unlock(ast_tone_zones); + + if (!zone) { return NULL; } } + ast_tone_zone_lock(zone); + /* Look through list of tones in the zone searching for the right one */ - AST_LIST_TRAVERSE(&zone->tones, ts, list) { - if (!strcasecmp(ts->name, indication)) + AST_LIST_TRAVERSE(&zone->tones, ts, entry) { + if (!strcasecmp(ts->name, indication)) { + /* Increase ref count for the reference we will return */ + ts = ast_tone_zone_sound_ref(ts); break; + } } - AST_RWLIST_UNLOCK(&tone_zones); + ast_tone_zone_unlock(zone); return ts; } -static inline void clear_zone_sound(struct tone_zone_sound *ts) +static void ast_tone_zone_sound_destructor(void *obj) { + struct ast_tone_zone_sound *ts = obj; + /* Deconstify the 'const char *'s so the compiler doesn't complain. (but it's safe) */ - ast_free((char *) ts->name); - ast_free((char *) ts->data); + if (ts->name) { + ast_free((char *) ts->name); + ts->name = NULL; + } + + if (ts->data) { + ast_free((char *) ts->data); + ts->data = NULL; + } } /*! \brief deallocate the passed tone zone */ -void ast_destroy_indication_zone(struct tone_zone *zone) +static void ast_tone_zone_destructor(void *obj) { - struct tone_zone_sound *current; + struct ast_tone_zone *zone = obj; + struct ast_tone_zone_sound *current; - while ((current = AST_LIST_REMOVE_HEAD(&zone->tones, list))) { - clear_zone_sound(current); - ast_free(current); + while ((current = AST_LIST_REMOVE_HEAD(&zone->tones, entry))) { + current = ast_tone_zone_sound_unref(current); } - if (zone->ringcadence) + if (zone->ringcadence) { ast_free(zone->ringcadence); + zone->ringcadence = NULL; + } +} + +/* add a new country, if country exists, it will be replaced. */ +static int ast_register_indication_country(struct ast_tone_zone *zone) +{ + ao2_lock(ast_tone_zones); + if (!default_tone_zone) { + default_tone_zone = ast_tone_zone_ref(zone); + } + ao2_unlock(ast_tone_zones); + + ao2_link(ast_tone_zones, zone); - ast_free(zone); + ast_verb(3, "Registered indication country '%s'\n", zone->country); + + return 0; } -/*--------------------------------------------*/ +/* remove an existing country and all its indications, country must exist. */ +static int ast_unregister_indication_country(const char *country) +{ + struct ast_tone_zone *tz = NULL; + struct ast_tone_zone zone_arg = { + .nrringcadence = 0, + }; -/* add a new country, if country exists, it will be replaced. */ -int ast_register_indication_country(struct tone_zone *zone) + ast_copy_string(zone_arg.country, country, sizeof(zone_arg.country)); + + if (!(tz = ao2_find(ast_tone_zones, &zone_arg, OBJ_POINTER))) { + return -1; + } + + ao2_lock(ast_tone_zones); + if (default_tone_zone == tz) { + ast_tone_zone_unref(default_tone_zone); + /* Get a new default, punt to the first one we find */ + default_tone_zone = ao2_callback(ast_tone_zones, 0, NULL, NULL); + } + ao2_unlock(ast_tone_zones); + + ao2_unlink(ast_tone_zones, tz); + + tz = ast_tone_zone_unref(tz); + + return 0; +} + +/*! + * \note called with the tone zone locked + */ +static int ast_register_indication(struct ast_tone_zone *zone, const char *indication, + const char *tonelist) { - struct tone_zone *tz = NULL; + struct ast_tone_zone_sound *ts; - AST_RWLIST_WRLOCK(&tone_zones); - AST_RWLIST_TRAVERSE_SAFE_BEGIN(&tone_zones, tz, list) { - /* If this is not the same zone, then just continue to the next entry */ - if (strcasecmp(zone->country, tz->country)) - continue; - /* If this zone we are going to remove is the current default then make the new zone the default */ - if (tz == current_tonezone) - current_tonezone = zone; - /* Remove from the linked list */ - AST_RWLIST_REMOVE_CURRENT(list); - /* Finally free the zone itself */ - ast_destroy_indication_zone(tz); - break; + if (ast_strlen_zero(indication) || ast_strlen_zero(tonelist)) { + return -1; } - AST_RWLIST_TRAVERSE_SAFE_END; - /* Add zone to the list */ - AST_RWLIST_INSERT_TAIL(&tone_zones, zone, list); + AST_LIST_TRAVERSE_SAFE_BEGIN(&zone->tones, ts, entry) { + if (!strcasecmp(indication, ts->name)) { + AST_LIST_REMOVE_CURRENT(entry); + ts = ast_tone_zone_sound_unref(ts); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; - /* It's all over. */ - AST_RWLIST_UNLOCK(&tone_zones); + if (!(ts = ao2_alloc(sizeof(*ts), ast_tone_zone_sound_destructor))) { + return -1; + } - ast_verb(3, "Registered indication country '%s'\n", zone->country); + if (!(ts->name = ast_strdup(indication)) || !(ts->data = ast_strdup(tonelist))) { + ts = ast_tone_zone_sound_unref(ts); + return -1; + } + + AST_LIST_INSERT_TAIL(&zone->tones, ts, entry); /* Inherit reference */ return 0; } -/* remove an existing country and all its indications, country must exist. - * Also, all countries which are an alias for the specified country are removed. */ -int ast_unregister_indication_country(const char *country) +/* remove an existing country's indication. Both country and indication must exist */ +static int ast_unregister_indication(struct ast_tone_zone *zone, const char *indication) { - struct tone_zone *tz = NULL; + struct ast_tone_zone_sound *ts; int res = -1; - AST_RWLIST_WRLOCK(&tone_zones); - AST_RWLIST_TRAVERSE_SAFE_BEGIN(&tone_zones, tz, list) { - if (country && (strcasecmp(country, tz->country) && strcasecmp(country, tz->alias))) - continue; - /* If this tonezone is the current default then unset it */ - if (tz == current_tonezone) { - ast_log(LOG_NOTICE,"Removed default indication country '%s'\n", tz->country); - current_tonezone = NULL; + ast_tone_zone_lock(zone); + + AST_LIST_TRAVERSE_SAFE_BEGIN(&zone->tones, ts, entry) { + if (!strcasecmp(indication, ts->name)) { + AST_LIST_REMOVE_CURRENT(entry); + ts = ast_tone_zone_sound_unref(ts); + res = 0; + break; } - /* Remove from the list */ - AST_RWLIST_REMOVE_CURRENT(list); - ast_verb(3, "Unregistered indication country '%s'\n", tz->country); - ast_destroy_indication_zone(tz); - res = 0; } - AST_RWLIST_TRAVERSE_SAFE_END; - AST_RWLIST_UNLOCK(&tone_zones); + AST_LIST_TRAVERSE_SAFE_END; + + ast_tone_zone_unlock(zone); return res; } -/* add a new indication to a tone_zone. tone_zone must exist. if the indication already - * exists, it will be replaced. */ -int ast_register_indication(struct tone_zone *zone, const char *indication, const char *tonelist) +static struct ast_tone_zone *ast_tone_zone_alloc(void) { - struct tone_zone_sound *ts; - int found = 0; + return ao2_alloc(sizeof(struct ast_tone_zone), ast_tone_zone_destructor); +} - /* is it an alias? stop */ - if (zone->alias[0]) - return -1; +static char *complete_country(struct ast_cli_args *a) +{ + char *res = NULL; + struct ao2_iterator i; + int which = 0; + size_t wordlen; + struct ast_tone_zone *tz; + + wordlen = strlen(a->word); + + i = ao2_iterator_init(ast_tone_zones, 0); + while ((tz = ao2_iterator_next(&i))) { + if (!strncasecmp(a->word, tz->country, wordlen) && ++which > a->n) { + res = ast_strdup(tz->country); + } + tz = ast_tone_zone_unref(tz); + if (res) { + break; + } + } - AST_RWLIST_WRLOCK(&tone_zones); + return res; +} - AST_LIST_TRAVERSE(&zone->tones, ts, list) { - if (!strcasecmp(indication, ts->name)) { - clear_zone_sound(ts); - found = 1; +static char *handle_cli_indication_add(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_tone_zone *tz; + int created_country = 0; + char *res = CLI_SUCCESS; + + switch (cmd) { + case CLI_INIT: + e->command = "indication add"; + e->usage = + "Usage: indication add <country> <indication> \"<tonelist>\"\n" + " Add the given indication to the country.\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 2) { + return complete_country(a); + } else { + return NULL; + } + } + + if (a->argc != 5) { + return CLI_SHOWUSAGE; + } + + if (!(tz = ast_get_indication_zone(a->argv[2]))) { + /* country does not exist, create it */ + ast_log(LOG_NOTICE, "Country '%s' does not exist, creating it.\n", a->argv[2]); + + if (!(tz = ast_tone_zone_alloc())) { + return CLI_FAILURE; + } + + ast_copy_string(tz->country, a->argv[2], sizeof(tz->country)); + + if (ast_register_indication_country(tz)) { + ast_log(LOG_WARNING, "Unable to register new country\n"); + tz = ast_tone_zone_unref(tz); + return CLI_FAILURE; + } + + created_country = 1; + } + + ast_tone_zone_lock(tz); + + if (ast_register_indication(tz, a->argv[3], a->argv[4])) { + ast_log(LOG_WARNING, "Unable to register indication %s/%s\n", a->argv[2], a->argv[3]); + if (created_country) { + ast_unregister_indication_country(a->argv[2]); + } + res = CLI_FAILURE; + } + + ast_tone_zone_unlock(tz); + + tz = ast_tone_zone_unref(tz); + + return res; +} + +static char *complete_indications(struct ast_cli_args *a) +{ + char *res = NULL; + int which = 0; + size_t wordlen; + struct ast_tone_zone_sound *ts; + struct ast_tone_zone *tz, tmp_tz = { + .nrringcadence = 0, + }; + + ast_copy_string(tmp_tz.country, a->argv[a->pos - 1], sizeof(tmp_tz.country)); + + if (!(tz = ao2_find(ast_tone_zones, &tmp_tz, OBJ_POINTER))) { + return NULL; + } + + wordlen = strlen(a->word); + + ast_tone_zone_lock(tz); + AST_LIST_TRAVERSE(&tz->tones, ts, entry) { + if (!strncasecmp(a->word, ts->name, wordlen) && ++which > a->n) { + res = ast_strdup(ts->name); break; } } - if (!ts) { - /* not there, we have to add */ - if (!(ts = ast_calloc(1, sizeof(*ts)))) { - AST_RWLIST_UNLOCK(&tone_zones); - return -2; + ast_tone_zone_unlock(tz); + + tz = ast_tone_zone_unref(tz); + + return res; +} + +static char *handle_cli_indication_remove(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_tone_zone *tz; + char *res = CLI_SUCCESS; + + switch (cmd) { + case CLI_INIT: + e->command = "indication remove"; + e->usage = + "Usage: indication remove <country> [indication]\n" + " Remove the given indication from the country.\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 2) { + return complete_country(a); + } else if (a->pos == 3) { + return complete_indications(a); } } - if (!(ts->name = ast_strdup(indication)) || !(ts->data = ast_strdup(tonelist))) { - ast_free(ts); - AST_RWLIST_UNLOCK(&tone_zones); - return -2; + + if (a->argc != 3 && a->argc != 4) { + return CLI_SHOWUSAGE; + } + + if (a->argc == 3) { + /* remove entire country */ + if (ast_unregister_indication_country(a->argv[2])) { + ast_log(LOG_WARNING, "Unable to unregister indication country %s\n", a->argv[2]); + return CLI_FAILURE; + } + + return CLI_SUCCESS; + } + + if (!(tz = ast_get_indication_zone(a->argv[2]))) { + ast_log(LOG_WARNING, "Unable to unregister indication %s/%s, country does not exists\n", a->argv[2], a->argv[3]); + return CLI_FAILURE; + } + + if (ast_unregister_indication(tz, a->argv[3])) { + ast_log(LOG_WARNING, "Unable to unregister indication %s/%s\n", a->argv[2], a->argv[3]); + res = CLI_FAILURE; + } + + tz = ast_tone_zone_unref(tz); + + return res; +} + +static char *handle_cli_indication_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ast_tone_zone *tz = NULL; + struct ast_str *buf; + int found_country = 0; + int i; + + switch (cmd) { + case CLI_INIT: + e->command = "indication show"; + e->usage = + "Usage: indication show [<country> ...]\n" + " Display either a condensed summary of all countries and indications, or a\n" + " more verbose list of indications for the specified countries.\n"; + return NULL; + case CLI_GENERATE: + return complete_country(a); + } + + if (a->argc == 2) { + struct ao2_iterator iter; + /* no arguments, show a list of countries */ + ast_cli(a->fd, "Country Description\n"); + ast_cli(a->fd, "===========================\n"); + iter = ast_tone_zone_iterator_init(); + while ((tz = ao2_iterator_next(&iter))) { + ast_tone_zone_lock(tz); + ast_cli(a->fd, "%-7.7s %s\n", tz->country, tz->description); + ast_tone_zone_unlock(tz); + tz = ast_tone_zone_unref(tz); + } + return CLI_SUCCESS; + } + + buf = ast_str_alloca(256); + + for (i = 2; i < a->argc; i++) { + struct ast_tone_zone zone_arg = { + .nrringcadence = 0, + }; + struct ast_tone_zone_sound *ts; + int j; + + ast_copy_string(zone_arg.country, a->argv[i], sizeof(zone_arg.country)); + + if (!(tz = ao2_find(ast_tone_zones, &zone_arg, OBJ_POINTER))) { + continue; + } + + if (!found_country) { + found_country = 1; + ast_cli(a->fd, "Country Indication PlayList\n"); + ast_cli(a->fd, "=====================================\n"); + } + + ast_tone_zone_lock(tz); + + ast_str_set(&buf, 0, "%-7.7s %-15.15s ", tz->country, "<ringcadence>"); + for (j = 0; j < tz->nrringcadence; j++) { + ast_str_append(&buf, 0, "%d%s", tz->ringcadence[j], + (j == tz->nrringcadence - 1) ? "" : ","); + } + ast_str_append(&buf, 0, "\n"); + ast_cli(a->fd, "%s", buf->str); + + AST_LIST_TRAVERSE(&tz->tones, ts, entry) { + ast_cli(a->fd, "%-7.7s %-15.15s %s\n", tz->country, ts->name, ts->data); + } + + ast_tone_zone_unlock(tz); + tz = ast_tone_zone_unref(tz); } - if (!found) { - AST_LIST_INSERT_TAIL(&zone->tones, ts, list); + if (!found_country) { + ast_cli(a->fd, "No countries matched your criteria.\n"); } - AST_RWLIST_UNLOCK(&tone_zones); + return CLI_SUCCESS; +} + +static int is_valid_tone_zone(struct ast_tone_zone *zone) +{ + int res; + + ast_tone_zone_lock(zone); + res = (!ast_strlen_zero(zone->description) && !AST_LIST_EMPTY(&zone->tones)); + ast_tone_zone_unlock(zone); + + return res; +} + +/*! + * \note This is called with the tone zone locked. + */ +static void store_tone_zone_ring_cadence(struct ast_tone_zone *zone, const char *val) +{ + char buf[1024]; + char *ring, *c = buf; + + ast_copy_string(buf, val, sizeof(buf)); + + while ((ring = strsep(&c, ","))) { + int *tmp, val; + + ring = ast_strip(ring); + + if (!isdigit(ring[0]) || (val = atoi(ring)) == -1) { + ast_log(LOG_WARNING, "Invalid ringcadence given '%s'.\n", ring); + continue; + } + + if (!(tmp = ast_realloc(zone->ringcadence, (zone->nrringcadence + 1) * sizeof(int)))) { + return; + } + + zone->ringcadence = tmp; + tmp[zone->nrringcadence] = val; + zone->nrringcadence++; + } +} + +static void store_config_tone_zone(struct ast_tone_zone *zone, const char *var, + const char *value) +{ + CV_START(var, value); + + CV_STR("description", zone->description); + CV_F("ringcadence", store_tone_zone_ring_cadence(zone, value)); + CV_F("ringcadance", store_tone_zone_ring_cadence(zone, value)); + + ast_register_indication(zone, var, value); + + CV_END; +} + +static void reset_tone_zone(struct ast_tone_zone *zone) +{ + ast_tone_zone_lock(zone); + + zone->killme = 0; + + if (zone->nrringcadence) { + zone->nrringcadence = 0; + ast_free(zone->ringcadence); + zone->ringcadence = NULL; + } + + ast_tone_zone_unlock(zone); +} + +static int parse_tone_zone(struct ast_config *cfg, const char *country) +{ + struct ast_variable *v; + struct ast_tone_zone *zone; + struct ast_tone_zone tmp_zone = { + .nrringcadence = 0, + }; + int allocd = 0; + + ast_copy_string(tmp_zone.country, country, sizeof(tmp_zone.country)); + + if ((zone = ao2_find(ast_tone_zones, &tmp_zone, OBJ_POINTER))) { + reset_tone_zone(zone); + } else if ((zone = ast_tone_zone_alloc())) { + allocd = 1; + ast_copy_string(zone->country, country, sizeof(zone->country)); + } else { + return -1; + } + + ast_tone_zone_lock(zone); + for (v = ast_variable_browse(cfg, country); v; v = v->next) { + store_config_tone_zone(zone, v->name, v->value); + } + ast_tone_zone_unlock(zone); + + if (allocd) { + if (!is_valid_tone_zone(zone)) { + ast_log(LOG_WARNING, "Indication country '%s' is invalid\n", country); + } else if (ast_register_indication_country(zone)) { + ast_log(LOG_WARNING, "Unable to register indication country '%s'.\n", + country); + } + } + + zone = ast_tone_zone_unref(zone); + return 0; } -/* remove an existing country's indication. Both country and indication must exist */ -int ast_unregister_indication(struct tone_zone *zone, const char *indication) +/*! + * Mark the zone and its tones before parsing configuration. We will use this + * to know what to remove after configuration is parsed. + */ +static int tone_zone_mark(void *obj, void *arg, int flags) +{ + struct ast_tone_zone *zone = obj; + struct ast_tone_zone_sound *s; + + ast_tone_zone_lock(zone); + + zone->killme = 1; + + AST_LIST_TRAVERSE(&zone->tones, s, entry) { + s->killme = 1; + } + + ast_tone_zone_unlock(zone); + + return 0; +} + +/*! + * Prune tones no longer in the configuration, and have the tone zone unlinked + * if it is no longer in the configuration at all. + */ +static int prune_tone_zone(void *obj, void *arg, int flags) +{ + struct ast_tone_zone *zone = obj; + struct ast_tone_zone_sound *s; + + ast_tone_zone_lock(zone); + + AST_LIST_TRAVERSE_SAFE_BEGIN(&zone->tones, s, entry) { + if (s->killme) { + AST_LIST_REMOVE_CURRENT(entry); + s = ast_tone_zone_sound_unref(s); + } + } + AST_LIST_TRAVERSE_SAFE_END; + + ast_tone_zone_unlock(zone); + + return zone->killme ? CMP_MATCH : 0; +} + +/*! \brief load indications module */ +static int load_indications(int reload) { - struct tone_zone_sound *ts; + struct ast_config *cfg; + const char *cxt = NULL; + const char *country = NULL; + struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; int res = -1; - /* is it an alias? stop */ - if (zone->alias[0]) + cfg = ast_config_load2(config, "indications", config_flags); + + if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) { return -1; + } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) { + return 0; + } - AST_RWLIST_WRLOCK(&tone_zones); + /* Lock the container to prevent multiple simultaneous reloads */ + ao2_lock(ast_tone_zones); - AST_LIST_TRAVERSE_SAFE_BEGIN(&zone->tones, ts, list) { - if (!strcasecmp(indication, ts->name)) { - AST_LIST_REMOVE_CURRENT(list); - clear_zone_sound(ts); - ast_free(ts); - res = 0; - break; + ao2_callback(ast_tone_zones, OBJ_NODATA, tone_zone_mark, NULL); + + /* Use existing config to populate the Indication table */ + while ((cxt = ast_category_browse(cfg, cxt))) { + /* All categories but "general" are considered countries */ + if (!strcasecmp(cxt, "general")) { + continue; + } + + if (parse_tone_zone(cfg, cxt)) { + goto return_cleanup; } } - AST_LIST_TRAVERSE_SAFE_END; - /* indication not found, goodbye */ - AST_RWLIST_UNLOCK(&tone_zones); + ao2_callback(ast_tone_zones, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, + prune_tone_zone, NULL); + + /* determine which country is the default */ + country = ast_variable_retrieve(cfg, "general", "country"); + if (ast_strlen_zero(country) || ast_set_indication_country(country)) { + ast_log(LOG_WARNING, "Unable to set the default country (for indication tones)\n"); + } + + res = 0; + +return_cleanup: + ao2_unlock(ast_tone_zones); + ast_config_destroy(cfg); + return res; } + +/*! \brief CLI entries for commands provided by this module */ +static struct ast_cli_entry cli_indications[] = { + AST_CLI_DEFINE(handle_cli_indication_add, "Add the given indication to the country"), + AST_CLI_DEFINE(handle_cli_indication_remove, "Remove the given indication from the country"), + AST_CLI_DEFINE(handle_cli_indication_show, "Display a list of all countries/indications") +}; + +static int ast_tone_zone_hash(const void *obj, const int flags) +{ + const struct ast_tone_zone *zone = obj; + + return ast_str_case_hash(zone->country); +} + +static int ast_tone_zone_cmp(void *obj, void *arg, int flags) +{ + struct ast_tone_zone *zone = obj; + struct ast_tone_zone *zone_arg = arg; + + return (!strcasecmp(zone->country, zone_arg->country)) ? + CMP_MATCH | CMP_STOP : 0; +} + +/*! \brief Load indications module */ +int ast_indications_init(void) +{ + if (!(ast_tone_zones = ao2_container_alloc(NUM_TONE_ZONE_BUCKETS, + ast_tone_zone_hash, ast_tone_zone_cmp))) { + return -1; + } + + if (load_indications(0)) { + return -1; + } + + ast_cli_register_multiple(cli_indications, ARRAY_LEN(cli_indications)); + + return 0; +} + +/*! \brief Reload indications module */ +int ast_indications_reload(void) +{ + return load_indications(1); +} + diff --git a/main/loader.c b/main/loader.c index e8a5662ef..05524fe08 100644 --- a/main/loader.c +++ b/main/loader.c @@ -249,6 +249,7 @@ static struct reload_classes { { "features", ast_features_reload }, { "dsp", ast_dsp_reload}, { "udptl", ast_udptl_reload }, + { "indications", ast_indications_reload }, { NULL, NULL } }; diff --git a/main/pbx.c b/main/pbx.c index 4c2b27366..0807edf72 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -8562,11 +8562,13 @@ static int pbx_builtin_waitexten(struct ast_channel *chan, void *data) } else if (ast_test_flag(&flags, WAITEXTEN_MOH)) { ast_indicate_data(chan, AST_CONTROL_HOLD, opts[0], strlen(opts[0])); } else if (ast_test_flag(&flags, WAITEXTEN_DIALTONE)) { - const struct tone_zone_sound *ts = ast_get_indication_tone(chan->zone, "dial"); - if (ts) + struct ast_tone_zone_sound *ts = ast_get_indication_tone(chan->zone, "dial"); + if (ts) { ast_playtones_start(chan, 0, ts->data, 0); - else + ts = ast_tone_zone_sound_unref(ts); + } else { ast_tonepair_start(chan, 350, 440, 0, 0); + } } /* Wait for "n" seconds */ if (args.timeout && (s = atof(args.timeout)) > 0) diff --git a/res/res_indications.c b/res/res_indications.c deleted file mode 100644 index b2ba5fcef..000000000 --- a/res/res_indications.c +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Asterisk -- An open source telephony toolkit. - * - * Copyright (C) 2002, Pauline Middelink - * - * - * See http://www.asterisk.org for more information about - * the Asterisk project. Please do not directly contact - * any of the maintainers of this project for assistance; - * the project provides a web site, mailing lists and IRC - * channels for your use. - * - * This program is free software, distributed under the terms of - * the GNU General Public License Version 2. See the LICENSE file - * at the top of the source tree. - */ - -/*! \file res_indications.c - * - * \brief Load the indications - * - * \author Pauline Middelink <middelink@polyware.nl> - * - * Load the country specific dialtones into the asterisk PBX. - */ - -#include "asterisk.h" - -ASTERISK_FILE_VERSION(__FILE__, "$Revision$") - -#include <ctype.h> -#include <sys/stat.h> - -#include "asterisk/lock.h" -#include "asterisk/file.h" -#include "asterisk/cli.h" -#include "asterisk/config.h" -#include "asterisk/channel.h" -#include "asterisk/pbx.h" -#include "asterisk/module.h" -#include "asterisk/translate.h" -#include "asterisk/indications.h" -#include "asterisk/utils.h" - -/*** DOCUMENTATION - <application name="PlayTones" language="en_US"> - <synopsis> - Play a tone list. - </synopsis> - <syntax> - <parameter name="arg" required="true"> - <para>Arg is either the tone name defined in the <filename>indications.conf</filename> - configuration file, or a directly specified list of frequencies and durations.</para> - </parameter> - </syntax> - <description> - <para>Plays a tone list. Execution will continue with the next step immediately, - while the tones continue to play.</para> - <para>See the sample <filename>indications.conf</filename> for a description of the - specification of a tonelist.</para> - </description> - <see-also> - <ref type="application">StopPlayTones</ref> - </see-also> - </application> - <application name="StopPlayTones" language="en_US"> - <synopsis> - Stop playing a tone list. - </synopsis> - <syntax /> - <description> - <para>Stop playing a tone list, initiated by PlayTones().</para> - </description> - <see-also> - <ref type="application">PlayTones</ref> - </see-also> - </application> - ***/ - -/* Globals */ -static const char config[] = "indications.conf"; - -/* - * Implementation of functions provided by this module - */ - -/*! - * \brief Add a country to indication - * \param e the ast_cli_entry for this CLI command - * \param cmd the reason we are being called - * \param a the arguments being passed to us - */ -static char *handle_cli_indication_add(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) -{ - struct tone_zone *tz; - int created_country = 0; - - switch (cmd) { - case CLI_INIT: - e->command = "indication add"; - e->usage = - "Usage: indication add <country> <indication> \"<tonelist>\"\n" - " Add the given indication to the country.\n"; - return NULL; - case CLI_GENERATE: - return NULL; - } - - if (a->argc != 5) - return CLI_SHOWUSAGE; - - tz = ast_get_indication_zone(a->argv[2]); - if (!tz) { - /* country does not exist, create it */ - ast_log(LOG_NOTICE, "Country '%s' does not exist, creating it.\n", a->argv[2]); - - if (!(tz = ast_calloc(1, sizeof(*tz)))) { - return CLI_FAILURE; - } - ast_copy_string(tz->country, a->argv[2], sizeof(tz->country)); - if (ast_register_indication_country(tz)) { - ast_log(LOG_WARNING, "Unable to register new country\n"); - ast_free(tz); - return CLI_FAILURE; - } - created_country = 1; - } - if (ast_register_indication(tz, a->argv[3], a->argv[4])) { - ast_log(LOG_WARNING, "Unable to register indication %s/%s\n", a->argv[2], a->argv[3]); - if (created_country) - ast_unregister_indication_country(a->argv[2]); - return CLI_FAILURE; - } - return CLI_SUCCESS; -} - -/*! - * \brief Remove a country from indication - * \param e the ast_cli_entry for this CLI command - * \param cmd the reason we are being called - * \param a the arguments being passed to us - */ -static char *handle_cli_indication_remove(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) -{ - struct tone_zone *tz; - - switch (cmd) { - case CLI_INIT: - e->command = "indication remove"; - e->usage = - "Usage: indication remove <country> <indication>\n" - " Remove the given indication from the country.\n"; - return NULL; - case CLI_GENERATE: - return NULL; - } - - if (a->argc != 3 && a->argc != 4) - return CLI_SHOWUSAGE; - - if (a->argc == 3) { - /* remove entiry country */ - if (ast_unregister_indication_country(a->argv[2])) { - ast_log(LOG_WARNING, "Unable to unregister indication country %s\n", a->argv[2]); - return CLI_FAILURE; - } - return CLI_SUCCESS; - } - - tz = ast_get_indication_zone(a->argv[2]); - if (!tz) { - ast_log(LOG_WARNING, "Unable to unregister indication %s/%s, country does not exists\n", a->argv[2], a->argv[3]); - return CLI_FAILURE; - } - if (ast_unregister_indication(tz, a->argv[3])) { - ast_log(LOG_WARNING, "Unable to unregister indication %s/%s\n", a->argv[2], a->argv[3]); - return CLI_FAILURE; - } - return CLI_SUCCESS; -} - -/*! - * \brief Show the current indications - * \param e the ast_cli_entry for this CLI command - * \param cmd the reason we are being called - * \param a the arguments being passed to us - */ -static char *handle_cli_indication_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) -{ - struct tone_zone *tz = NULL; - char buf[256]; - int found_country = 0; - - switch (cmd) { - case CLI_INIT: - e->command = "indication show"; - e->usage = - "Usage: indication show [<country> ...]\n" - " Display either a condensed for of all country/indications, or the\n" - " indications for the specified countries.\n"; - return NULL; - case CLI_GENERATE: - return NULL; - } - - if (a->argc == 2) { - /* no arguments, show a list of countries */ - ast_cli(a->fd, "Country Alias Description\n"); - ast_cli(a->fd, "===========================\n"); - while ((tz = ast_walk_indications(tz))) - ast_cli(a->fd, "%-7.7s %-7.7s %s\n", tz->country, tz->alias, tz->description); - return CLI_SUCCESS; - } - /* there was a request for specific country(ies), lets humor them */ - while ((tz = ast_walk_indications(tz))) { - int i, j; - for (i = 2; i < a->argc; i++) { - if (strcasecmp(tz->country, a->argv[i]) == 0 && !tz->alias[0]) { - struct tone_zone_sound *ts; - if (!found_country) { - found_country = 1; - ast_cli(a->fd, "Country Indication PlayList\n"); - ast_cli(a->fd, "=====================================\n"); - } - j = snprintf(buf, sizeof(buf), "%-7.7s %-15.15s ", tz->country, "<ringcadence>"); - for (i = 0; i < tz->nrringcadence; i++) { - j += snprintf(buf + j, sizeof(buf) - j, "%d,", tz->ringcadence[i]); - } - if (tz->nrringcadence) - j--; - ast_copy_string(buf + j, "\n", sizeof(buf) - j); - ast_cli(a->fd, "%s", buf); - AST_LIST_TRAVERSE(&tz->tones, ts, list) { - ast_cli(a->fd, "%-7.7s %-15.15s %s\n", tz->country, ts->name, ts->data); - } - break; - } - } - } - if (!found_country) - ast_cli(a->fd, "No countries matched your criteria.\n"); - return CLI_SUCCESS; -} - -/*! - * \brief play tone for indication country - * \param chan ast_channel to play the sounds back to - * \param data contains tone to play - */ -static int handle_playtones(struct ast_channel *chan, void *data) -{ - struct tone_zone_sound *ts; - int res; - - if (!data || !((char*)data)[0]) { - ast_log(LOG_NOTICE,"Nothing to play\n"); - return -1; - } - ts = ast_get_indication_tone(chan->zone, (const char*)data); - if (ts && ts->data[0]) - res = ast_playtones_start(chan, 0, ts->data, 0); - else - res = ast_playtones_start(chan, 0, (const char*)data, 0); - if (res) - ast_log(LOG_NOTICE,"Unable to start playtones\n"); - return res; -} - -/*! - * \brief Stop tones playing - * \param chan - * \param data - */ -static int handle_stopplaytones(struct ast_channel *chan, void *data) -{ - ast_playtones_stop(chan); - return 0; -} - -/*! \brief load indications module */ -static int ind_load_module(int reload) -{ - struct ast_config *cfg; - struct ast_variable *v; - char *cxt; - char *c; - struct tone_zone *tones; - const char *country = NULL; - struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; - - /* that the following cast is needed, is yuk! */ - /* yup, checked it out. It is NOT written to. */ - cfg = ast_config_load((char *)config, config_flags); - if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) { - return -1; - } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) { - return 0; - } - - if (reload) - ast_unregister_indication_country(NULL); - - /* Use existing config to populate the Indication table */ - cxt = ast_category_browse(cfg, NULL); - while(cxt) { - /* All categories but "general" are considered countries */ - if (!strcasecmp(cxt, "general")) { - cxt = ast_category_browse(cfg, cxt); - continue; - } - if (!(tones = ast_calloc(1, sizeof(*tones)))) { - ast_config_destroy(cfg); - return -1; - } - ast_copy_string(tones->country,cxt,sizeof(tones->country)); - - v = ast_variable_browse(cfg, cxt); - while(v) { - if (!strcasecmp(v->name, "description")) { - ast_copy_string(tones->description, v->value, sizeof(tones->description)); - } else if ((!strcasecmp(v->name,"ringcadence"))||(!strcasecmp(v->name,"ringcadance"))) { - char *ring,*rings = ast_strdupa(v->value); - c = rings; - ring = strsep(&c,","); - while (ring) { - int *tmp, val; - if (!isdigit(ring[0]) || (val=atoi(ring))==-1) { - ast_log(LOG_WARNING,"Invalid ringcadence given '%s' at line %d.\n",ring,v->lineno); - ring = strsep(&c,","); - continue; - } - if (!(tmp = ast_realloc(tones->ringcadence, (tones->nrringcadence + 1) * sizeof(int)))) { - ast_config_destroy(cfg); - ast_destroy_indication_zone(tones); - return -1; - } - tones->ringcadence = tmp; - tmp[tones->nrringcadence] = val; - tones->nrringcadence++; - /* next item */ - ring = strsep(&c,","); - } - } else if (!strcasecmp(v->name,"alias")) { - char *countries = ast_strdupa(v->value); - c = countries; - country = strsep(&c,","); - while (country) { - struct tone_zone* azone; - if (!(azone = ast_calloc(1, sizeof(*azone)))) { - ast_config_destroy(cfg); - ast_destroy_indication_zone(tones); - return -1; - } - ast_copy_string(azone->country, country, sizeof(azone->country)); - ast_copy_string(azone->alias, cxt, sizeof(azone->alias)); - if (ast_register_indication_country(azone)) { - ast_log(LOG_WARNING, "Unable to register indication alias at line %d.\n",v->lineno); - ast_destroy_indication_zone(tones); - } - /* next item */ - country = strsep(&c,","); - } - } else { - struct tone_zone_sound *ts; - - /* add tone to country */ - AST_LIST_TRAVERSE(&tones->tones, ts, list) { - if (!strcasecmp(v->name, ts->name)) { - /* already there */ - ast_log(LOG_NOTICE, "Duplicate entry '%s' skipped.\n", v->name); - goto out; - } - } - - /* not there, add it to the back */ - if (!(ts = ast_calloc(1, sizeof(*ts)))) { - ast_config_destroy(cfg); - return -1; - } - ts->name = ast_strdup(v->name); - ts->data = ast_strdup(v->value); - - AST_LIST_INSERT_TAIL(&tones->tones, ts, list); - } -out: v = v->next; - } - if (tones->description[0] || tones->alias[0] || !AST_LIST_EMPTY(&tones->tones)) { - if (ast_register_indication_country(tones)) { - ast_log(LOG_WARNING, "Unable to register indication at line %d.\n",v->lineno); - ast_destroy_indication_zone(tones); - } - } else { - ast_destroy_indication_zone(tones); - } - - cxt = ast_category_browse(cfg, cxt); - } - - /* determine which country is the default */ - country = ast_variable_retrieve(cfg,"general","country"); - if (ast_strlen_zero(country) || ast_set_indication_country(country)) { - ast_log(LOG_WARNING,"Unable to set the default country (for indication tones)\n"); - } - - ast_config_destroy(cfg); - return 0; -} - -/*! \brief CLI entries for commands provided by this module */ -static struct ast_cli_entry cli_indications[] = { - AST_CLI_DEFINE(handle_cli_indication_add, "Add the given indication to the country"), - AST_CLI_DEFINE(handle_cli_indication_remove, "Remove the given indication from the country"), - AST_CLI_DEFINE(handle_cli_indication_show, "Display a list of all countries/indications") -}; - -/*! \brief Unload indicators module */ -static int unload_module(void) -{ - /* remove the registed indications... */ - ast_unregister_indication_country(NULL); - - /* and the functions */ - ast_cli_unregister_multiple(cli_indications, ARRAY_LEN(cli_indications)); - ast_unregister_application("PlayTones"); - ast_unregister_application("StopPlayTones"); - return 0; -} - - -/*! \brief Load indications module */ -static int load_module(void) -{ - if (ind_load_module(0)) - return AST_MODULE_LOAD_DECLINE; - ast_cli_register_multiple(cli_indications, ARRAY_LEN(cli_indications)); - ast_register_application_xml("PlayTones", handle_playtones); - ast_register_application_xml("StopPlayTones", handle_stopplaytones); - - return AST_MODULE_LOAD_SUCCESS; -} - -/*! \brief Reload indications module */ -static int reload(void) -{ - return ind_load_module(1); -} - -AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Region-specific tones", - .load = load_module, - .unload = unload_module, - .reload = reload, - ); diff --git a/res/snmp/agent.c b/res/snmp/agent.c index 0a47879cf..f7e08220d 100644 --- a/res/snmp/agent.c +++ b/res/snmp/agent.c @@ -644,23 +644,34 @@ static u_char *ast_var_indications(struct variable *vp, oid *name, size_t *lengt int exact, size_t *var_len, WriteMethod **write_method) { static unsigned long long_ret; - struct tone_zone *tz = NULL; + static char ret_buf[128]; + struct ast_tone_zone *tz = NULL; if (header_generic(vp, name, length, exact, var_len, write_method)) return NULL; switch (vp->magic) { case ASTINDCOUNT: + { + struct ao2_iterator i; + long_ret = 0; - while ( (tz = ast_walk_indications(tz)) ) + + i = ast_tone_zone_iterator_init(); + while ((tz = ao2_iterator_next(&i))) { + tz = ast_tone_zone_unref(tz); long_ret++; + } - return (u_char *)&long_ret; + return (u_char *) &long_ret; + } case ASTINDCURRENT: tz = ast_get_indication_zone(NULL); if (tz) { - *var_len = strlen(tz->country); - return (u_char *)tz->country; + ast_copy_string(ret_buf, tz->country, sizeof(ret_buf)); + *var_len = strlen(ret_buf); + tz = ast_tone_zone_unref(tz); + return (u_char *) ret_buf; } *var_len = 0; return NULL; @@ -674,34 +685,47 @@ static u_char *ast_var_indications_table(struct variable *vp, oid *name, size_t int exact, size_t *var_len, WriteMethod **write_method) { static unsigned long long_ret; - struct tone_zone *tz = NULL; + static char ret_buf[256]; + struct ast_tone_zone *tz = NULL; int i; + struct ao2_iterator iter; - if (header_simple_table(vp, name, length, exact, var_len, write_method, -1)) + if (header_simple_table(vp, name, length, exact, var_len, write_method, -1)) { return NULL; + } i = name[*length - 1] - 1; - while ( (tz = ast_walk_indications(tz)) && i ) - i--; - if (tz == NULL) + + iter = ast_tone_zone_iterator_init(); + + while ((tz = ao2_iterator_next(&iter)) && i) { + tz = ast_tone_zone_unref(tz); + i--; + } + + if (tz == NULL) { return NULL; + } switch (vp->magic) { case ASTINDINDEX: long_ret = name[*length - 1]; return (u_char *)&long_ret; case ASTINDCOUNTRY: - *var_len = strlen(tz->country); - return (u_char *)tz->country; + ast_copy_string(ret_buf, tz->country, sizeof(ret_buf)); + tz = ast_tone_zone_unref(tz); + *var_len = strlen(ret_buf); + return (u_char *) ret_buf; case ASTINDALIAS: - if (tz->alias) { - *var_len = strlen(tz->alias); - return (u_char *)tz->alias; - } + /* No longer exists */ return NULL; case ASTINDDESCRIPTION: - *var_len = strlen(tz->description); - return (u_char *)tz->description; + ast_tone_zone_lock(tz); + ast_copy_string(ret_buf, tz->description, sizeof(ret_buf)); + ast_tone_zone_unlock(tz); + tz = ast_tone_zone_unref(tz); + *var_len = strlen(ret_buf); + return (u_char *) ret_buf; default: break; } |