func_periodic_hook.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2014, Russell Bryant
00005  *
00006  * Russell Bryant <russell@russellbryant.net>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief Periodic dialplan hooks.
00022  *
00023  * \author Russell Bryant <russell@russellbryant.net>
00024  *
00025  * \ingroup functions
00026  */
00027 
00028 /*** MODULEINFO
00029    <support_level>core</support_level>
00030    <depend>app_chanspy</depend>
00031    <depend>func_cut</depend>
00032    <depend>func_groupcount</depend>
00033    <depend>func_uri</depend>
00034  ***/
00035 
00036 #include "asterisk.h"
00037 
00038 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 419592 $")
00039 
00040 #include "asterisk/module.h"
00041 #include "asterisk/channel.h"
00042 #include "asterisk/pbx.h"
00043 #include "asterisk/app.h"
00044 #include "asterisk/audiohook.h"
00045 #define AST_API_MODULE
00046 #include "asterisk/beep.h"
00047 
00048 /*** DOCUMENTATION
00049    <function name="PERIODIC_HOOK" language="en_US">
00050       <synopsis>
00051          Execute a periodic dialplan hook into the audio of a call.
00052       </synopsis>
00053       <syntax>
00054          <parameter name="context" required="true">
00055             <para>(On Read Only) Context for the hook extension.</para>
00056          </parameter>
00057          <parameter name="extension" required="true">
00058             <para>(On Read Only) The hook extension.</para>
00059          </parameter>
00060          <parameter name="interval" required="true">
00061             <para>(On Read Only) Number of seconds in between hook runs.
00062             Whole seconds only.</para>
00063          </parameter>
00064          <parameter name="hook_id" required="true">
00065             <para>(On Write Only) The hook ID.</para>
00066          </parameter>
00067       </syntax>
00068       <description>
00069          <para>For example, you could use this function to enable playing
00070          a periodic <literal>beep</literal> sound in a call.</para>
00071          <para/>
00072          <para>To turn on:</para>
00073          <para>  Set(BEEPID=${PERIODIC_HOOK(hooks,beep,180)})</para>
00074          <para/>
00075          <para>To turn off:</para>
00076          <para>  Set(PERIODIC_HOOK(${BEEPID})=off)</para>
00077          <para/>
00078          <para>To turn back on again later:</para>
00079          <para>Set(PERIODIC_HOOK(${BEEPID})=on)</para>
00080          <para/>
00081          <para>It is important to note that the hook does not actually
00082          run on the channel itself.  It runs asynchronously on a new channel.
00083          Any audio generated by the hook gets injected into the call for
00084          the channel PERIODIC_HOOK() was set on.</para>
00085          <para/>
00086          <para>The hook dialplan will have two variables available.
00087          <variable>HOOK_CHANNEL</variable> is the channel the hook is
00088          enabled on.  <variable>HOOK_ID</variable> is the hook ID for
00089          enabling or disabling the hook.</para>
00090       </description>
00091    </function>
00092  ***/
00093 
00094 static const char context_name[] = "__func_periodic_hook_context__";
00095 static const char exten_name[] = "hook";
00096 static const char full_exten_name[] = "hook@__func_periodic_hook_context__";
00097 
00098 static const char beep_exten[] = "beep";
00099 
00100 /*!
00101  * \brief Last used hook ID
00102  *
00103  * This is incremented each time a hook is created to give each hook a unique
00104  * ID.
00105  */
00106 static unsigned int global_hook_id;
00107 
00108 /*! State put in a datastore to track the state of the hook */
00109 struct hook_state {
00110    /*!
00111     * \brief audiohook used as a callback into this module
00112     *
00113     * \note The code assumes this is the first element in the struct
00114     */
00115    struct ast_audiohook audiohook;
00116    /*! Seconds between each hook run */
00117    unsigned int interval;
00118    /*! The last time the hook ran */
00119    struct timeval last_hook;
00120    /*! Dialplan context for the hook */
00121    char *context;
00122    /*! Dialplan extension for the hook */
00123    char *exten;
00124    /*! Hook ID */
00125    unsigned int hook_id;
00126    /*! Non-zero if the hook is currently disabled */
00127    unsigned char disabled;
00128 };
00129 
00130 static void hook_datastore_destroy_callback(void *data)
00131 {
00132    struct hook_state *state = data;
00133 
00134    ast_audiohook_lock(&state->audiohook);
00135    ast_audiohook_detach(&state->audiohook);
00136    ast_audiohook_unlock(&state->audiohook);
00137    ast_audiohook_destroy(&state->audiohook);
00138 
00139    ast_free(state->context);
00140    ast_free(state->exten);
00141    ast_free(state);
00142 
00143    ast_module_unref(ast_module_info->self);
00144 }
00145 
00146 static const struct ast_datastore_info hook_datastore = {
00147    .type = AST_MODULE,
00148    .destroy = hook_datastore_destroy_callback,
00149 };
00150 
00151 /*! Arguments to the thread that launches the hook */
00152 struct hook_thread_arg {
00153    /*! Hook ID */
00154    char *hook_id;
00155    /*! Name of the channel the hook was set on */
00156    char *chan_name;
00157    /*! Dialplan context for the hook */
00158    char *context;
00159    /*! Dialplan extension for the hook */
00160    char *exten;
00161 };
00162 
00163 static void hook_thread_arg_destroy(struct hook_thread_arg *arg)
00164 {
00165    ast_free(arg->hook_id);
00166    ast_free(arg->chan_name);
00167    ast_free(arg->context);
00168    ast_free(arg->exten);
00169    ast_free(arg);
00170 }
00171 
00172 static void *hook_launch_thread(void *data)
00173 {
00174    struct hook_thread_arg *arg = data;
00175    struct ast_variable hook_id = {
00176       .name = "HOOK_ID",
00177       .value = arg->hook_id,
00178    };
00179    struct ast_variable chan_name_var = {
00180       .name = "HOOK_CHANNEL",
00181       .value = arg->chan_name,
00182       .next = &hook_id,
00183    };
00184 
00185    ast_pbx_outgoing_exten("Local", NULL, full_exten_name, 60,
00186          arg->context, arg->exten, 1, NULL, 0, NULL, NULL, &chan_name_var,
00187          NULL, NULL, 1, NULL);
00188 
00189    hook_thread_arg_destroy(arg);
00190 
00191    return NULL;
00192 }
00193 
00194 static struct hook_thread_arg *hook_thread_arg_alloc(struct ast_channel *chan,
00195       struct hook_state *state)
00196 {
00197    struct hook_thread_arg *arg;
00198 
00199    if (!(arg = ast_calloc(1, sizeof(*arg)))) {
00200       return NULL;
00201    }
00202 
00203    ast_channel_lock(chan);
00204    arg->chan_name = ast_strdup(ast_channel_name(chan));
00205    ast_channel_unlock(chan);
00206    if (!arg->chan_name) {
00207       hook_thread_arg_destroy(arg);
00208       return NULL;
00209    }
00210 
00211    if (ast_asprintf(&arg->hook_id, "%u", state->hook_id) == -1) {
00212       hook_thread_arg_destroy(arg);
00213       return NULL;
00214    }
00215 
00216    if (!(arg->context = ast_strdup(state->context))) {
00217       hook_thread_arg_destroy(arg);
00218       return NULL;
00219    }
00220 
00221    if (!(arg->exten = ast_strdup(state->exten))) {
00222       hook_thread_arg_destroy(arg);
00223       return NULL;
00224    }
00225 
00226    return arg;
00227 }
00228 
00229 static int do_hook(struct ast_channel *chan, struct hook_state *state)
00230 {
00231    pthread_t t;
00232    struct hook_thread_arg *arg;
00233    int res;
00234 
00235    if (!(arg = hook_thread_arg_alloc(chan, state))) {
00236       return -1;
00237    }
00238 
00239    /*
00240     * We don't want to block normal frame processing *at all* while we kick
00241     * this off, so do it in a new thread.
00242     */
00243    res = ast_pthread_create_detached_background(&t, NULL, hook_launch_thread, arg);
00244    if (res != 0) {
00245       hook_thread_arg_destroy(arg);
00246    }
00247 
00248    return res;
00249 }
00250 
00251 static int hook_callback(struct ast_audiohook *audiohook, struct ast_channel *chan,
00252       struct ast_frame *frame, enum ast_audiohook_direction direction)
00253 {
00254    struct hook_state *state = (struct hook_state *) audiohook; /* trust me. */
00255    struct timeval now;
00256    int res = 0;
00257 
00258    if (audiohook->status == AST_AUDIOHOOK_STATUS_DONE || state->disabled) {
00259       return 0;
00260    }
00261 
00262    now = ast_tvnow();
00263    if (ast_tvdiff_ms(now, state->last_hook) > state->interval * 1000) {
00264       if ((res = do_hook(chan, state))) {
00265          const char *name;
00266          ast_channel_lock(chan);
00267          name = ast_strdupa(ast_channel_name(chan));
00268          ast_channel_unlock(chan);
00269          ast_log(LOG_WARNING, "Failed to run hook on '%s'\n", name);
00270       }
00271       state->last_hook = now;
00272    }
00273 
00274    return res;
00275 }
00276 
00277 static struct hook_state *hook_state_alloc(const char *context, const char *exten,
00278       unsigned int interval, unsigned int hook_id)
00279 {
00280    struct hook_state *state;
00281 
00282    if (!(state = ast_calloc(1, sizeof(*state)))) {
00283       return NULL;
00284    }
00285 
00286    state->context = ast_strdup(context);
00287    state->exten = ast_strdup(exten);
00288    state->interval = interval;
00289    state->hook_id = hook_id;
00290 
00291    ast_audiohook_init(&state->audiohook, AST_AUDIOHOOK_TYPE_MANIPULATE,
00292          AST_MODULE, AST_AUDIOHOOK_MANIPULATE_ALL_RATES);
00293    state->audiohook.manipulate_callback = hook_callback;
00294 
00295    return state;
00296 }
00297 
00298 static int init_hook(struct ast_channel *chan, const char *context, const char *exten,
00299       unsigned int interval, unsigned int hook_id)
00300 {
00301    struct hook_state *state;
00302    struct ast_datastore *datastore;
00303    char uid[32];
00304 
00305    snprintf(uid, sizeof(uid), "%u", hook_id);
00306 
00307    if (!(datastore = ast_datastore_alloc(&hook_datastore, uid))) {
00308       return -1;
00309    }
00310    ast_module_ref(ast_module_info->self);
00311    if (!(state = hook_state_alloc(context, exten, interval, hook_id))) {
00312       ast_datastore_free(datastore);
00313       return -1;
00314    }
00315    datastore->data = state;
00316 
00317    ast_channel_lock(chan);
00318    ast_channel_datastore_add(chan, datastore);
00319    ast_audiohook_attach(chan, &state->audiohook);
00320    ast_channel_unlock(chan);
00321 
00322    return 0;
00323 }
00324 
00325 static int hook_on(struct ast_channel *chan, const char *data, unsigned int hook_id)
00326 {
00327    char *parse = ast_strdupa(S_OR(data, ""));
00328    AST_DECLARE_APP_ARGS(args,
00329       AST_APP_ARG(context);
00330       AST_APP_ARG(exten);
00331       AST_APP_ARG(interval);
00332    );
00333    unsigned int interval;
00334 
00335    AST_STANDARD_APP_ARGS(args, parse);
00336 
00337    if (ast_strlen_zero(args.interval) ||
00338          sscanf(args.interval, "%30u", &interval) != 1 || interval == 0) {
00339       ast_log(LOG_WARNING, "Invalid hook interval: '%s'\n", S_OR(args.interval, ""));
00340       return -1;
00341    }
00342 
00343    if (ast_strlen_zero(args.context) || ast_strlen_zero(args.exten)) {
00344       ast_log(LOG_WARNING, "A context and extension are required for PERIODIC_HOOK().\n");
00345       return -1;
00346    }
00347 
00348    ast_debug(1, "hook to %s@%s enabled on %s with interval of %u seconds\n",
00349          args.exten, args.context, ast_channel_name(chan), interval);
00350 
00351    return init_hook(chan, args.context, args.exten, interval, hook_id);
00352 }
00353 
00354 static int hook_off(struct ast_channel *chan, const char *hook_id)
00355 {
00356    struct ast_datastore *datastore;
00357    struct hook_state *state;
00358 
00359    if (ast_strlen_zero(hook_id)) {
00360       return -1;
00361    }
00362 
00363    ast_channel_lock(chan);
00364 
00365    if (!(datastore = ast_channel_datastore_find(chan, &hook_datastore, hook_id))) {
00366       ast_log(LOG_WARNING, "Hook with ID '%s' not found on channel '%s'\n", hook_id,
00367             ast_channel_name(chan));
00368       ast_channel_unlock(chan);
00369       return -1;
00370    }
00371 
00372    state = datastore->data;
00373    state->disabled = 1;
00374 
00375    ast_channel_unlock(chan);
00376 
00377    return 0;
00378 }
00379 
00380 static int hook_read(struct ast_channel *chan, const char *cmd, char *data,
00381           char *buf, size_t len)
00382 {
00383    unsigned int hook_id;
00384 
00385    if (!chan) {
00386       return -1;
00387    }
00388 
00389    hook_id = (unsigned int) ast_atomic_fetchadd_int((int *) &global_hook_id, 1);
00390 
00391    snprintf(buf, len, "%u", hook_id);
00392 
00393    return hook_on(chan, data, hook_id);
00394 }
00395 
00396 static int hook_re_enable(struct ast_channel *chan, const char *uid)
00397 {
00398    struct ast_datastore *datastore;
00399    struct hook_state *state;
00400 
00401    if (ast_strlen_zero(uid)) {
00402       return -1;
00403    }
00404 
00405    ast_channel_lock(chan);
00406 
00407    if (!(datastore = ast_channel_datastore_find(chan, &hook_datastore, uid))) {
00408       ast_log(LOG_WARNING, "Hook with ID '%s' not found on '%s'\n",
00409             uid, ast_channel_name(chan));
00410       ast_channel_unlock(chan);
00411       return -1;
00412    }
00413 
00414    state = datastore->data;
00415    state->disabled = 0;
00416 
00417    ast_channel_unlock(chan);
00418 
00419    return 0;
00420 }
00421 
00422 static int hook_write(struct ast_channel *chan, const char *cmd, char *data,
00423       const char *value)
00424 {
00425    int res;
00426 
00427    if (!chan) {
00428       return -1;
00429    }
00430 
00431    if (ast_false(value)) {
00432       res = hook_off(chan, data);
00433    } else if (ast_true(value)) {
00434       res = hook_re_enable(chan, data);
00435    } else {
00436       ast_log(LOG_WARNING, "Invalid value for PERIODIC_HOOK function: '%s'\n", value);
00437       res = -1;
00438    }
00439 
00440    return res;
00441 }
00442 
00443 static struct ast_custom_function hook_function = {
00444    .name = "PERIODIC_HOOK",
00445    .read = hook_read,
00446    .write = hook_write,
00447 };
00448 
00449 static struct ast_context *func_periodic_hook_context;
00450 
00451 static int unload_module(void)
00452 {
00453    if (func_periodic_hook_context) {
00454       ast_context_destroy(func_periodic_hook_context, AST_MODULE);
00455    }
00456 
00457    return ast_custom_function_unregister(&hook_function);
00458 }
00459 
00460 static int load_module(void)
00461 {
00462    int res;
00463 
00464    func_periodic_hook_context = ast_context_find_or_create(NULL, NULL,
00465          context_name, AST_MODULE);
00466    if (!func_periodic_hook_context) {
00467       ast_log(LOG_ERROR, "Failed to create %s dialplan context.\n", context_name);
00468       return AST_MODULE_LOAD_DECLINE;
00469    }
00470 
00471    /*
00472     * Based on a handy recipe from the Asterisk Cookbook.
00473     */
00474    ast_add_extension(context_name, 1, exten_name, 1, "", "",
00475          "Set", "EncodedChannel=${CUT(HOOK_CHANNEL,-,1-2)}",
00476          NULL, AST_MODULE);
00477    ast_add_extension(context_name, 1, exten_name, 2, "", "",
00478          "Set", "GROUP_NAME=${EncodedChannel}${HOOK_ID}",
00479          NULL, AST_MODULE);
00480    ast_add_extension(context_name, 1, exten_name, 3, "", "",
00481          "Set", "GROUP(periodic-hook)=${GROUP_NAME}",
00482          NULL, AST_MODULE);
00483    ast_add_extension(context_name, 1, exten_name, 4, "", "", "ExecIf",
00484          "$[${GROUP_COUNT(${GROUP_NAME}@periodic-hook)} > 1]?Hangup()",
00485          NULL, AST_MODULE);
00486    ast_add_extension(context_name, 1, exten_name, 5, "", "",
00487          "Set", "ChannelToSpy=${URIDECODE(${EncodedChannel})}",
00488          NULL, AST_MODULE);
00489    ast_add_extension(context_name, 1, exten_name, 6, "", "",
00490          "ChanSpy", "${ChannelToSpy},qEB", NULL, AST_MODULE);
00491 
00492    res = ast_add_extension(context_name, 1, beep_exten, 1, "", "",
00493          "Answer", "", NULL, AST_MODULE);
00494    res |= ast_add_extension(context_name, 1, beep_exten, 2, "", "",
00495          "Playback", "beep", NULL, AST_MODULE);
00496 
00497    res = ast_custom_function_register_escalating(&hook_function, AST_CFE_BOTH);
00498 
00499    return res ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
00500 }
00501 
00502 int AST_OPTIONAL_API_NAME(ast_beep_start)(struct ast_channel *chan,
00503       unsigned int interval, char *beep_id, size_t len)
00504 {
00505    char args[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 32];
00506 
00507    snprintf(args, sizeof(args), "%s,%s,%u",
00508          context_name, beep_exten, interval);
00509 
00510    if (hook_read(chan, NULL, args, beep_id, len)) {
00511       ast_log(LOG_WARNING, "Failed to enable periodic beep.\n");
00512       return -1;
00513    }
00514 
00515    return 0;
00516 }
00517 
00518 int AST_OPTIONAL_API_NAME(ast_beep_stop)(struct ast_channel *chan, const char *beep_id)
00519 {
00520    return hook_write(chan, NULL, (char *) beep_id, "off");
00521 }
00522 
00523 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Periodic dialplan hooks.",
00524       .support_level = AST_MODULE_SUPPORT_CORE,
00525       .load = load_module,
00526       .unload = unload_module,
00527       );

Generated on Thu Apr 16 06:27:36 2015 for Asterisk - The Open Source Telephony Project by  doxygen 1.5.6