diff --git a/apps/app_queue.c b/apps/app_queue.c index b863f902b4448f75da95f334646117184fd6487c..ec04a2d525829e688b0e5ab18e430d7e5b6ffd95 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -135,6 +135,28 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <option name="d"> <para>data-quality (modem) call (minimum delay).</para> </option> + <option name="F" argsep="^"> + <argument name="context" required="false" /> + <argument name="exten" required="false" /> + <argument name="priority" required="true" /> + <para>When the caller hangs up, transfer the <emphasis>called member</emphasis> + to the specified destination and <emphasis>start</emphasis> execution at that location.</para> + <note> + <para>Any channel variables you want the called channel to inherit from the caller channel must be + prefixed with one or two underbars ('_').</para> + </note> + </option> + <option name="F"> + <para>When the caller hangs up, transfer the <emphasis>called member</emphasis> to the next priority of + the current extension and <emphasis>start</emphasis> execution at that location.</para> + <note> + <para>Any channel variables you want the called channel to inherit from the caller channel must be + prefixed with one or two underbars ('_').</para> + </note> + <note> + <para>Using this option from a Macro() or GoSub() might not make sense as there would be no return points.</para> + </note> + </option> <option name="h"> <para>Allow <emphasis>callee</emphasis> to hang up by pressing <literal>*</literal>.</para> </option> @@ -848,6 +870,56 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") </manager> ***/ +enum { + OPT_MARK_AS_ANSWERED = (1 << 0), + OPT_GO_ON = (1 << 1), + OPT_DATA_QUALITY = (1 << 2), + OPT_CALLEE_GO_ON = (1 << 3), + OPT_CALLEE_HANGUP = (1 << 4), + OPT_CALLER_HANGUP = (1 << 5), + OPT_IGNORE_CALL_FW = (1 << 6), + OPT_UPDATE_CONNECTED = (1 << 7), + OPT_CALLEE_PARK = (1 << 8), + OPT_CALLER_PARK = (1 << 9), + OPT_NO_RETRY = (1 << 10), + OPT_RINGING = (1 << 11), + OPT_RING_WHEN_RINGING = (1 << 12), + OPT_CALLEE_TRANSFER = (1 << 13), + OPT_CALLER_TRANSFER = (1 << 14), + OPT_CALLEE_AUTOMIXMON = (1 << 15), + OPT_CALLER_AUTOMIXMON = (1 << 16), + OPT_CALLEE_AUTOMON = (1 << 17), + OPT_CALLER_AUTOMON = (1 << 18), +}; + +enum { + OPT_ARG_CALLEE_GO_ON = 0, + /* note: this entry _MUST_ be the last one in the enum */ + OPT_ARG_ARRAY_SIZE +}; + +AST_APP_OPTIONS(queue_exec_options, BEGIN_OPTIONS + AST_APP_OPTION('C', OPT_MARK_AS_ANSWERED), + AST_APP_OPTION('c', OPT_GO_ON), + AST_APP_OPTION('d', OPT_DATA_QUALITY), + AST_APP_OPTION_ARG('F', OPT_CALLEE_GO_ON, OPT_ARG_CALLEE_GO_ON), + AST_APP_OPTION('h', OPT_CALLEE_HANGUP), + AST_APP_OPTION('H', OPT_CALLER_HANGUP), + AST_APP_OPTION('i', OPT_IGNORE_CALL_FW), + AST_APP_OPTION('I', OPT_UPDATE_CONNECTED), + AST_APP_OPTION('k', OPT_CALLEE_PARK), + AST_APP_OPTION('K', OPT_CALLER_PARK), + AST_APP_OPTION('n', OPT_NO_RETRY), + AST_APP_OPTION('r', OPT_RINGING), + AST_APP_OPTION('R', OPT_RING_WHEN_RINGING), + AST_APP_OPTION('t', OPT_CALLEE_TRANSFER), + AST_APP_OPTION('T', OPT_CALLER_TRANSFER), + AST_APP_OPTION('x', OPT_CALLEE_AUTOMIXMON), + AST_APP_OPTION('X', OPT_CALLER_AUTOMIXMON), + AST_APP_OPTION('w', OPT_CALLEE_AUTOMON), + AST_APP_OPTION('W', OPT_CALLER_AUTOMON), +END_OPTIONS); + enum { QUEUE_STRATEGY_RINGALL = 0, QUEUE_STRATEGY_LEASTRECENT, @@ -1233,6 +1305,21 @@ static void set_queue_result(struct ast_channel *chan, enum queue_result res) } } +/*! + * \internal + * \brief Converts delimited '^' characters in a target priority/extension/context string + * to commas so that they can be used with ast_parseable_goto. + * \param s string that '^' characters are being replaced in. + */ +static void replace_macro_delimiter(char *s) +{ + for (; *s; s++) { + if (*s == '^') { + *s = ','; + } + } +} + static const char *int2strat(int strategy) { int x; @@ -4413,7 +4500,9 @@ static void end_bridge_callback(void *data) } } -/*! \brief A large function which calls members, updates statistics, and bridges the caller and a member +/*! + * \internal + * \brief A large function which calls members, updates statistics, and bridges the caller and a member * * Here is the process of this function * 1. Process any options passed to the Queue() application. Options here mean the third argument to Queue() @@ -4430,7 +4519,8 @@ static void end_bridge_callback(void *data) * 9. Do any post processing after the call has disconnected. * * \param[in] qe the queue_ent structure which corresponds to the caller attempting to reach members - * \param[in] options the options passed as the third parameter to the Queue() application + * \param[in] opts the options passed as the third parameter to the Queue() application + * \param[in] opt_args the options passed as the third parameter to the Queue() application * \param[in] announceoverride filename to play to user when waiting * \param[in] url the url passed as the fourth parameter to the Queue() application * \param[in,out] tries the number of times we have tried calling queue members @@ -4440,7 +4530,7 @@ static void end_bridge_callback(void *data) * \param[in] gosub the gosub passed as the seventh parameter to the Queue() application * \param[in] ringing 1 if the 'r' option is set, otherwise 0 */ -static int try_calling(struct queue_ent *qe, const char *options, char *announceoverride, const char *url, int *tries, int *noption, const char *agi, const char *macro, const char *gosub, int ringing) +static int try_calling(struct queue_ent *qe, const struct ast_flags opts, char **opt_args, char *announceoverride, const char *url, int *tries, int *noption, const char *agi, const char *macro, const char *gosub, int ringing) { struct member *cur; struct callattempt *outgoing = NULL; /* the list of calls we are building */ @@ -4499,62 +4589,59 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce res = 0; goto out; } - - for (; options && *options; options++) - switch (*options) { - case 't': - ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_REDIRECT); - break; - case 'T': - ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_REDIRECT); - break; - case 'w': - ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_AUTOMON); - break; - case 'W': - ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMON); - break; - case 'c': - ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_NO_H_EXTEN); - break; - case 'd': - nondataquality = 0; - break; - case 'h': - ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_DISCONNECT); - break; - case 'H': - ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT); - break; - case 'k': - ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_PARKCALL); - break; - case 'K': - ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_PARKCALL); - break; - case 'n': - if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY || qe->parent->strategy == QUEUE_STRATEGY_LINEAR || qe->parent->strategy == QUEUE_STRATEGY_RRORDERED) - (*tries)++; - else - *tries = ao2_container_count(qe->parent->members); - *noption = 1; - break; - case 'i': - forwardsallowed = 0; - break; - case 'I': - update_connectedline = 0; - break; - case 'x': - ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_AUTOMIXMON); - break; - case 'X': - ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMIXMON); - break; - case 'C': - qe->cancel_answered_elsewhere = 1; - break; - } + + if (ast_test_flag(&opts, OPT_CALLEE_TRANSFER)) { + ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_REDIRECT); + } + if (ast_test_flag(&opts, OPT_CALLER_TRANSFER)) { + ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_REDIRECT); + } + if (ast_test_flag(&opts, OPT_CALLEE_AUTOMON)) { + ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_AUTOMON); + } + if (ast_test_flag(&opts, OPT_CALLER_AUTOMON)) { + ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMON); + } + if (ast_test_flag(&opts, OPT_GO_ON)) { + ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_NO_H_EXTEN); + } + if (ast_test_flag(&opts, OPT_DATA_QUALITY)) { + nondataquality = 0; + } + if (ast_test_flag(&opts, OPT_CALLEE_HANGUP)) { + ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_DISCONNECT); + } + if (ast_test_flag(&opts, OPT_CALLER_HANGUP)) { + ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT); + } + if (ast_test_flag(&opts, OPT_CALLEE_PARK)) { + ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_PARKCALL); + } + if (ast_test_flag(&opts, OPT_CALLER_PARK)) { + ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_PARKCALL); + } + if (ast_test_flag(&opts, OPT_NO_RETRY)) { + if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY || qe->parent->strategy == QUEUE_STRATEGY_LINEAR || qe->parent->strategy == QUEUE_STRATEGY_RRORDERED) + (*tries)++; + else + *tries = ao2_container_count(qe->parent->members); + *noption = 1; + } + if (ast_test_flag(&opts, OPT_IGNORE_CALL_FW)) { + forwardsallowed = 0; + } + if (ast_test_flag(&opts, OPT_UPDATE_CONNECTED)) { + update_connectedline = 0; + } + if (ast_test_flag(&opts, OPT_CALLEE_AUTOMIXMON)) { + ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_AUTOMIXMON); + } + if (ast_test_flag(&opts, OPT_CALLER_AUTOMIXMON)) { + ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMIXMON); + } + if (ast_test_flag(&opts, OPT_MARK_AS_ANSWERED)) { + qe->cancel_answered_elsewhere = 1; + } /* if the calling channel has the ANSWERED_ELSEWHERE flag set, make sure this is inherited. (this is mainly to support chan_local) @@ -4743,6 +4830,11 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce } } } else { /* peer is valid */ + /* These variables are used with the F option without arguments (callee jumps to next priority after queue) */ + char *caller_context; + char *caller_extension; + int caller_priority; + /* Ah ha! Someone answered within the desired timeframe. Of course after this we will always return with -1 so that it is hung up properly after the conversation. */ @@ -4870,12 +4962,17 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce /* try to set queue variables if configured to do so*/ set_queue_variables(qe->parent, qe->chan); set_queue_variables(qe->parent, peer); - + ast_channel_lock(qe->chan); + /* Copy next destination data for 'F' option (no args) */ + caller_context = ast_strdupa(ast_channel_context(qe->chan)); + caller_extension = ast_strdupa(ast_channel_exten(qe->chan)); + caller_priority = ast_channel_priority(qe->chan); if ((monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME"))) { monitorfilename = ast_strdupa(monitorfilename); } ast_channel_unlock(qe->chan); + /* Begin Monitoring */ if (qe->parent->monfmt && *qe->parent->monfmt) { if (!qe->parent->montype) { @@ -5220,7 +5317,30 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce if (transfer_ds) { ast_datastore_free(transfer_ds); } - ast_hangup(peer); + + if (!ast_check_hangup(peer) && ast_test_flag(&opts, OPT_CALLEE_GO_ON)) { + if (!ast_strlen_zero(opt_args[OPT_ARG_CALLEE_GO_ON])) { + int res; + replace_macro_delimiter(opt_args[OPT_ARG_CALLEE_GO_ON]); + res = ast_parseable_goto(peer, opt_args[OPT_ARG_CALLEE_GO_ON]); + if (res == AST_PBX_SUCCESS) { + ast_pbx_start(peer); + } else { + ast_hangup(peer); + } + } else { /* F() */ + int res; + res = ast_goto_if_exists(peer, caller_context, caller_extension, caller_priority + 1); + if (res == AST_PBX_GOTO_FAILED) { + ast_hangup(peer); + } else { + ast_pbx_start(peer); + } + } + } else { + ast_hangup(peer); + } + res = bridge ? bridge : 1; ao2_ref(member, -1); } @@ -6037,15 +6157,21 @@ static int queue_exec(struct ast_channel *chan, const char *data) ); /* Our queue entry */ struct queue_ent qe = { 0 }; - + struct ast_flags opts = { 0, }; + char *opt_args[OPT_ARG_ARRAY_SIZE]; + if (ast_strlen_zero(data)) { ast_log(LOG_WARNING, "Queue requires an argument: queuename[,options[,URL[,announceoverride[,timeout[,agi[,macro[,gosub[,rule[,position]]]]]]]]]\n"); return -1; } - + parse = ast_strdupa(data); AST_STANDARD_APP_ARGS(args, parse); + if (!ast_strlen_zero(args.options)) { + ast_app_parse_options(queue_exec_options, &opts, opt_args, args.options); + } + /* Setup our queue entry */ qe.start = time(NULL); @@ -6098,15 +6224,17 @@ static int queue_exec(struct ast_channel *chan, const char *data) } ast_channel_unlock(chan); - if (args.options && (strchr(args.options, 'r'))) + if (ast_test_flag(&opts, OPT_RINGING)) { ringing = 1; + } - if (ringing != 1 && args.options && (strchr(args.options, 'R'))) { + if (ringing != 1 && ast_test_flag(&opts, OPT_RING_WHEN_RINGING)) { qe.ring_when_ringing = 1; } - if (args.options && (strchr(args.options, 'c'))) + if (ast_test_flag(&opts, OPT_GO_ON)) { qcontinue = 1; + } if (args.position) { position = atoi(args.position); @@ -6198,7 +6326,7 @@ check_turns: } /* Try calling all queue members for 'timeout' seconds */ - res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi, args.macro, args.gosub, ringing); + res = try_calling(&qe, opts, opt_args, args.announceoverride, args.url, &tries, &noption, args.agi, args.macro, args.gosub, ringing); if (res) { goto stop; }