Index: apps/app_queue.c =================================================================== --- apps/app_queue.c (revision 262655) +++ 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 */ @@ -5949,6 +5963,293 @@ } } +/* Campon function - play file and wait for digit response + ret val: 1 = jump to another dialplan priority, 0 = continue */ +static int campon_message(struct queue_ent *qe, char *fname) +{ + int d; + + d = ast_streamfile(qe->chan, fname, qe->chan->language); + if ( !d ) { + d = ast_waitstream(qe->chan, AST_DIGIT_ANY); + } + if ( !d ) { + d = ast_waitfordigit(qe->chan, 2000); + } + ast_stopstream(qe->chan); + + if (d && valid_exit(qe, d)) { + return d; + } + + 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 periodicannouncefrequency; + int timeout; + int ringinuse; + char mohname[32]; + char context[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); + } + + 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 options from queues.conf [campon] */ + 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; + ringinuse = q->ringinuse; + strcpy(mohname, q->moh); + strcpy(context, q->context); + periodicannouncefrequency = q->periodicannouncefrequency; + } else { + /* use default values */ + timeout = 45; + ringinuse = 0; + strcpy(mohname, "default"); + strcpy(context, ""); + periodicannouncefrequency = 15; + } + + /* 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; + q->ringinuse = ringinuse; + ast_string_field_set(q, moh, mohname); + ast_string_field_set(q, announce, ""); + ast_string_field_set(q, context, context); + + q->periodicannouncefrequency = periodicannouncefrequency; + ast_str_set(&q->sound_periodicannounce[0], 0, "campon-repeat"); + + 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.expire = qe.start + 600; + 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; + } + + /* play Initial campon message, exit to single digit exten if exists */ + if (campon_message(&qe, "campon")) { + leave_queue(&qe); + return 0; /* jump to another dialplan priority */ + } + + /* Start music on hold */ + if (ringing) { + ast_indicate(qe.chan, AST_CONTROL_RINGING); + } else { + ast_moh_start(qe.chan, qe.moh, NULL); + } + + ast_log(LOG_NOTICE,"Waiting Turn Queue '%s' Timeout='%d'\n", queuename, qe.parent->timeout); + for (;;) { + res = wait_our_turn(&qe, ringing, &reason); + if (res < 0) { + /* They hungup, return immediately */ + if (option_verbose > 2) { + ast_verbose(VERBOSE_PREFIX_3 "User disconnected while waiting their turn\n"); + } + res = -1; + break; + } else if (!res) { + /* It's their turn */ + break; + } else { + /* They pressed a key */ + if (!ast_goto_if_exists(chan, qe.parent->context, qe.digits, 1)) { + break; + } else { + qe.digits[0] = '\0'; + } + } + } + + if (!res) { + qe.recall = time(NULL) + qe.parent->timeout; + ast_log(LOG_NOTICE,"Trying Queue '%s' Timeout='%d'\n", queuename, qe.parent->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) { + if (ringing) { + ast_indicate(qe.chan, -1); + } else { + ast_moh_stop(qe.chan); + } + ast_stopstream(qe.chan); + if (campon_message(&qe, "campon-repeat")) { + res = 1; /* jump to another dialplan priority */ + break; + } else { + /* Start music on hold */ + if (ringing) { + ast_indicate(qe.chan, AST_CONTROL_RINGING); + } else { + ast_moh_start(qe.chan, qe.moh, NULL); + } + qe.recall = time(NULL) + qe.parent->timeout; + ast_log(LOG_NOTICE,"Re-Trying Queue '%s' Timeout='%d'\n", queuename, qe.parent->timeout); + } + } + if (res < 0) { + if (option_verbose > 2) { + ast_verbose(VERBOSE_PREFIX_3 "User disconnected when they almost made it\n"); + } + res = -1; + break; + } else if (res && valid_exit(&qe, res)) { + break; + } + } + } + + /* Don't allow return code > 0 */ + if (res >= 0) { + res = 0; + if (ringing) { + ast_indicate(qe.chan, -1); + } else { + ast_moh_stop(qe.chan); + } + ast_stopstream(qe.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 @@ -7184,6 +7485,7 @@ res |= ast_manager_unregister("QueuePause"); res |= ast_manager_unregister("QueueLog"); res |= ast_manager_unregister("QueuePenalty"); + res |= ast_unregister_application(app_co); res |= ast_unregister_application(app_aqm); res |= ast_unregister_application(app_rqm); res |= ast_unregister_application(app_pqm); @@ -7246,6 +7548,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");