Adds a transfer callee on hangup option (like with Dial option F) to queues.
authorJonathan Rose <jrose@digium.com>
Fri, 2 Mar 2012 16:57:12 +0000 (16:57 +0000)
committerJonathan Rose <jrose@digium.com>
Fri, 2 Mar 2012 16:57:12 +0000 (16:57 +0000)
This should (and does in my testing) act just like the Dial option of the same name.
This allows a queue member to be transfered to the next priority (no args), or to
a context/extension/priority similar to goto (with args context^extension^priority)
when a caller hangs up on them.

(closes issue ASTERISK-19283)
Reported by: To
Patches:
queue_f-v3.diff uploaded by To (license 6347)
Review: https://reviewboard.asterisk.org/r/1785/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@357861 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_queue.c

index b863f90..ec04a2d 100644 (file)
@@ -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>
@@ -849,6 +871,56 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  ***/
 
 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,
        QUEUE_STRATEGY_FEWESTCALLS,
@@ -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;
                }