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");