summaryrefslogtreecommitdiff
path: root/apps/app_queue.c
diff options
context:
space:
mode:
authorJonathan Rose <jrose@digium.com>2012-03-02 16:57:12 +0000
committerJonathan Rose <jrose@digium.com>2012-03-02 16:57:12 +0000
commitd3db6da254894be87052ceef0596a73cb401c274 (patch)
tree6fd2669b4a95fda39a2c3e32e6918f3fef60b9e3 /apps/app_queue.c
parent2ce386d198f46933e4b6f89a36a32fbe86cad409 (diff)
Adds a transfer callee on hangup option (like with Dial option F) to queues.
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
Diffstat (limited to 'apps/app_queue.c')
-rw-r--r--apps/app_queue.c262
1 files changed, 195 insertions, 67 deletions
diff --git a/apps/app_queue.c b/apps/app_queue.c
index b863f902b..ec04a2d52 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>
@@ -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;
}