Index: apps/app_queue.c
===================================================================
--- apps/app_queue.c (revision 290970)
+++ apps/app_queue.c (working copy)
@@ -372,6 +372,17 @@
[Queue]
+
+
+ Camp a call for an extension.
+
+
+
+
+
+ Camps an incoming call onto a particular extension.
+
+
Return Queue information in variables.
@@ -549,6 +560,8 @@
static char *app_ql = "QueueLog" ;
+static char *app_co = "CampOn" ;
+
/*! \brief Persistent Members astdb family */
static const char *pm_family = "Queue/PersistentMembers";
/* The maximum length of each persistent member queue database entry */
@@ -652,6 +665,7 @@
int linpos; /*!< If using linear strategy, what position are we at? */
int linwrapped; /*!< Is the linpos wrapped? */
time_t start; /*!< When we started holding */
+ time_t recall; /*!< Recall time for campon message */
time_t expire; /*!< When this entry should expire (time out of queue) */
int cancel_answered_elsewhere; /*!< Whether we should force the CAE flag on this call (C) option*/
struct ast_channel *chan; /*!< Our channel */
@@ -1984,9 +1998,9 @@
return res;
}
}
- if (*reason == QUEUE_UNKNOWN && q->maxlen && (q->count >= q->maxlen))
+ if (*reason == QUEUE_UNKNOWN && q->maxlen && (q->count >= q->maxlen)) {
*reason = QUEUE_FULL;
- else if (*reason == QUEUE_UNKNOWN) {
+ } else if (*reason == QUEUE_UNKNOWN) {
/* There's space for us, put us at the right position inside
* the queue.
* Take into account the priority of the calling user */
@@ -3753,13 +3767,14 @@
ast_channel_lock(qe->chan);
ast_channel_datastore_add(qe->chan, datastore);
ast_channel_unlock(qe->chan);
- } else
+ } else {
dialed_interfaces = datastore->data;
+ }
AST_LIST_LOCK(dialed_interfaces);
AST_LIST_TRAVERSE(dialed_interfaces, di, list) {
if (!strcasecmp(cur->interface, di->interface)) {
- ast_debug(1, "Skipping dialing interface '%s' since it has already been dialed\n",
+ ast_debug(1, "Skipping dialing interface '%s' since it has already been dialed\n",
di->interface);
break;
}
@@ -3805,10 +3820,11 @@
XXX If we're forcibly removed, these outgoing calls won't get
hung up XXX */
tmp->q_next = outgoing;
- outgoing = tmp;
+ outgoing = tmp;
/* If this line is up, don't try anybody else */
- if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP))
+ if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP)) {
break;
+ }
} else {
ao2_ref(cur, -1);
ast_free(tmp);
@@ -5954,6 +5970,280 @@
}
}
+static int goto_exten(struct ast_channel *chan, const char *dialcontext, char *ext)
+{
+ if (!ast_goto_if_exists(chan, dialcontext, ext, 1) ||
+ (!ast_strlen_zero(chan->macrocontext) &&
+ !ast_goto_if_exists(chan, chan->macrocontext, ext, 1))) {
+ return 0;
+ } else {
+ ast_log(LOG_WARNING, "Can't find extension '%s' in current context. "
+ "Not Exiting CampOn!\n", ext);
+ return -1;
+ }
+}
+
+/* Campon function - play file and wait for digit response
+ ret val: 1 = jump to another dialplan priority, 0 = continue */
+static int campon_message(struct ast_channel *chan, char *fname)
+{
+ int d;
+
+ d = ast_streamfile(chan, fname, chan->language);
+ if ( !d ) {
+ d = ast_waitstream(chan, AST_DIGIT_ANY);
+ }
+ if ( !d ) {
+ d = ast_waitfordigit(chan, 5000);
+ }
+ ast_stopstream(chan);
+
+ if ( d == '0' && !goto_exten(chan, chan->context, "o")) { /* operator selected */
+ return 1;
+ } else if ( d == '1' && !goto_exten(chan, chan->context, "v")) { /* Leave Voice Mail */
+ return 1;
+ } else if ( d == '2' && !goto_exten(chan, chan->context, "d")) { /* Directory */
+ return 1;
+ } else if ( d == '*' && !goto_exten(chan, chan->context, "a")) { /* abort */
+ return 1;
+ } else if ( d == '#' && !goto_exten(chan, chan->context, "h")) { /* hangup */
+ return 1;
+ }
+ return 0;
+}
+
+/* Campon on exec - called from dialplan, copied from queue_exec and
+ reload_queue. Uses the queue function to campon the call, with recall
+ every 45 seconds to replay another message to allow caller to exit
+ by pressing 1 or 2. Dynamically creates a queue if one does not exist */
+
+/*! \brief CampOn application */
+static int co_exec(struct ast_channel *chan, void *data)
+{
+ int res = -1;
+ char *parse;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(interface);
+ AST_APP_ARG(penalty);
+ AST_APP_ARG(options);
+ AST_APP_ARG(membername);
+ AST_APP_ARG(state_interface);
+ );
+
+ char *queuename = NULL;
+ char *options = NULL;
+ struct call_queue *q;
+ int timeout;
+ char mohname[32];
+ time_t curr_time;
+
+ int prio = 0;
+ int min_penalty = 0;
+ int max_penalty = 0;
+
+ int paused = 0;
+ int penalty = 0;
+
+ enum queue_result reason = QUEUE_UNKNOWN;
+ int tries = 0;
+ int noption = 0;
+
+ int ringing = 0;
+
+ /* Our queue entry */
+ struct queue_ent qe = { 0 };
+ struct ao2_iterator queue_iter;
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "CAMPON requires an argument (technology/number|optional timeout|optional URL)\n");
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ /* Parse our arguments XXX Check for failure XXX */
+ if (ast_strlen_zero(args.interface)) {
+ ast_log(LOG_WARNING, "CAMPON requires an interface argument (technology/number))\n");
+ return -1;
+ }
+
+ if (!ast_strlen_zero(args.penalty)) {
+ if ((sscanf(args.penalty, "%30d", &penalty) != 1) || penalty < 0) {
+ ast_log(LOG_WARNING, "Penalty '%s' is invalid, must be an integer >= 0\n", args.penalty);
+ penalty = 0;
+ }
+ }
+
+ /* Answer the Channel if it's not already up */
+ if ( chan->_state != AST_STATE_UP ) {
+ ast_answer(chan);
+ }
+
+ /* play campon message, exit to single digit exten if exists */
+ if (campon_message(chan, "campon")) {
+ return 0; /* jump to another dialplan priority */
+ }
+
+ queuename = args.interface;
+
+ /* make sure queue entry for this phone/extension exists otherwise create */
+ queue_iter = ao2_iterator_init(queues, 0);
+ while ((q = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) {
+ if ( !strcmp(q->name, queuename) ) {
+ queue_t_unref(q, "Done with iterator");
+ break;
+ }
+ queue_t_unref(q, "Done with iterator");
+ }
+ ao2_iterator_destroy(&queue_iter);
+
+ if (!q) {
+ /* get campon options for timeout and moh */
+ queue_iter = ao2_iterator_init(queues, 0);
+ while ((q = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) {
+ if ( !strcmp(q->name, "campon") ) {
+ queue_t_unref(q, "Done with iterator");
+ break;
+ }
+ queue_t_unref(q, "Done with iterator");
+ }
+ ao2_iterator_destroy(&queue_iter);
+
+ if (q) {
+ /* found an entry for [campon] in queues.conf */
+ timeout = q->timeout;
+ strcpy(mohname, q->moh);
+ } else {
+ /* use default values */
+ timeout = 45;
+ strcpy(mohname, "default");
+ }
+
+ /* Make a new queue for the phone/extensions */
+ if (!(q = alloc_queue(queuename))) {
+ return -1;
+ }
+
+ /* initialize the queue */
+ q->strategy = QUEUE_STRATEGY_RINGALL;
+ init_queue(q);
+ q->timeout = timeout;
+ strcpy(q->moh, mohname);
+ strcpy(q->announce, "");
+ strcpy(q->context, "");
+ queues_t_link(queues, q, "Add queue to container");
+
+ free_members(q, 1);
+ queue_t_unref(q, "Expiring creation reference");
+
+ switch (add_to_queue(queuename, args.interface, args.membername, penalty, paused, 0, args.state_interface)) {
+ case RES_OKAY:
+ ast_queue_log(queuename, chan->uniqueid, args.interface, "ADDMEMBER", "%s", "");
+ ast_log(LOG_NOTICE, "Added interface '%s' to queue '%s'\n", args.interface, queuename);
+ break;
+ case RES_EXISTS:
+ ast_log(LOG_WARNING, "Unable to add interface '%s' to queue '%s': Already there\n", args.interface, queuename);
+ break;
+ case RES_NOSUCHQUEUE:
+ ast_log(LOG_WARNING, "Unable to add interface to queue '%s': No such queue\n", queuename);
+ break;
+ case RES_OUTOFMEMORY:
+ ast_log(LOG_ERROR, "Out of memory adding member %s to queue %s\n", args.interface, queuename);
+ break;
+ }
+ }
+
+ /* Setup our queue entry */
+ qe.start = time(NULL);
+ qe.chan = chan;
+ qe.prio = prio;
+ qe.max_penalty = max_penalty;
+ qe.min_penalty = min_penalty;
+ qe.last_pos_said = 0;
+ qe.last_pos = 0;
+ qe.last_periodic_announce_time = time(NULL);
+ qe.last_periodic_announce_sound = 0;
+ qe.valid_digits = 0;
+
+ if (join_queue(queuename, &qe, &reason)) {
+ ast_log(LOG_WARNING, "Unable to join queue '%s'\n", queuename);
+ set_queue_result(chan, reason);
+ pbx_builtin_setvar_helper(chan, "CAMPONSTATUS", "JOINUNAVIAL");
+ return 0;
+ }
+
+ /* Start music on hold */
+ if (ringing) {
+ ast_indicate(qe.chan, AST_CONTROL_RINGING);
+ } else {
+ ast_moh_start(qe.chan, qe.moh, NULL);
+ }
+
+ for (;;) {
+ res = wait_our_turn(&qe, ringing, &reason);
+ /* If they hungup, return immediately */
+ if (res < 0) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "User disconnected while waiting their turn\n");
+ res = -1;
+ }
+ break;
+ }
+ if (!res) {
+ break;
+ }
+ if (valid_exit(&qe, res)) {
+ break;
+ }
+ }
+ if (!res) {
+ qe.recall = time(NULL) + timeout;
+ for (;;) {
+ res = try_calling(&qe, options, NULL, NULL, &tries, &noption, NULL, NULL, NULL, ringing);
+ if (res)
+ break;
+ res = wait_a_bit(&qe);
+ curr_time = time(NULL);
+ if (curr_time >= qe.recall) {
+ ast_moh_stop(chan);
+ if (campon_message(chan, "campon-repeat")) {
+ res = 1; /* jump to another dialplan priority */
+ break;
+ } else {
+ ast_moh_start(chan, qe.moh, NULL);
+ qe.recall = time(NULL) + 45;
+ }
+ }
+ if (res < 0) {
+ if (option_verbose > 2) {
+ ast_verbose(VERBOSE_PREFIX_3 "User disconnected when they almost made it\n");
+ res = -1;
+ }
+ break;
+ }
+ if (res && valid_exit(&qe, res)) {
+ break;
+ }
+ }
+ }
+
+ /* Don't allow return code > 0 */
+ if (res >= 0) {
+ res = 0;
+ if (ringing) {
+ ast_indicate(chan, -1);
+ } else {
+ ast_moh_stop(chan);
+ }
+ ast_stopstream(chan);
+ }
+ leave_queue(&qe);
+ return res;
+}
+
+
/*! \brief reload the queues.conf file
*
* This function reloads the information in the general section of the queues.conf
@@ -7194,6 +7484,7 @@
res |= ast_unregister_application(app_pqm);
res |= ast_unregister_application(app_upqm);
res |= ast_unregister_application(app_ql);
+ res |= ast_unregister_application(app_co);
res |= ast_unregister_application(app);
res |= ast_custom_function_unregister(&queuevar_function);
res |= ast_custom_function_unregister(&queuemembercount_function);
@@ -7251,6 +7542,7 @@
res |= ast_register_application_xml(app_pqm, pqm_exec);
res |= ast_register_application_xml(app_upqm, upqm_exec);
res |= ast_register_application_xml(app_ql, ql_exec);
+ res |= ast_register_application_xml(app_co, co_exec);
res |= ast_manager_register("Queues", 0, manager_queues_show, "Queues");
res |= ast_manager_register("QueueStatus", 0, manager_queues_status, "Queue Status");
res |= ast_manager_register("QueueSummary", 0, manager_queues_summary, "Queue Summary");