Newer
Older
Kevin P. Fleming
committed
Mark Michelson
committed
if (avgholdmins == 1) {
res = play_file(qe->chan, qe->parent->sound_minute);
Kevin P. Fleming
committed
goto playout;
Kevin P. Fleming
committed
} else {
Mark Michelson
committed
res = play_file(qe->chan, qe->parent->sound_minutes);
Kevin P. Fleming
committed
goto playout;
Kevin P. Fleming
committed
}
Mark Michelson
committed
if (avgholdsecs >= 1) {
res = ast_say_number(qe->chan, avgholdsecs, AST_DIGIT_ANY, ast_channel_language(qe->chan), NULL);
Kevin P. Fleming
committed
goto playout;
Kevin P. Fleming
committed
res = play_file(qe->chan, qe->parent->sound_seconds);
Kevin P. Fleming
committed
goto playout;
} else if (qe->parent->announceholdtime && !qe->parent->announceposition) {
say_thanks = 0;
if (qe->parent->announceposition) {
ast_verb(3, "Told %s in %s their queue position (which was %d)\n",
ast_channel_name(qe->chan), qe->parent->name, qe->pos);
}
if (say_thanks) {
res = play_file(qe->chan, qe->parent->sound_thanks);
/* Don't restart music on hold if we're about to exit the caller from the queue */
Steve Murphy
committed
if (!res) {
if (ringing) {
ast_indicate(qe->chan, AST_CONTROL_RINGING);
} else {
ast_moh_start(qe->chan, qe->moh, NULL);
}
Steve Murphy
committed
}
Kevin P. Fleming
committed
return res;
static void recalc_holdtime(struct queue_ent *qe, int newholdtime)
/* Calculate holdtime using an exponential average */
/* Thanks to SRT for this contribution */
/* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */
Mark Michelson
committed
ao2_lock(qe->parent);
qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2;
Mark Michelson
committed
ao2_unlock(qe->parent);
Mark Michelson
committed
/*! \brief Caller leaving queue.
*
* Search the queue to find the leaving client, if found remove from queue
* create manager event, move others up the queue.
*/
static void leave_queue(struct queue_ent *qe)
{
struct queue_ent *current, *prev = NULL;
Mark Michelson
committed
struct penalty_rule *pr_iter;
Mark Spencer
committed
Tilghman Lesher
committed
queue_t_ref(q, "Copy queue pointer from queue entry");
Mark Michelson
committed
ao2_lock(q);
for (current = q->head; current; current = current->next) {
if (current == qe) {
char posstr[20];
/*** DOCUMENTATION
<managerEventInstance>
<synopsis>Raised when a channel leaves a Queue.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Join']/managerEventInstance/syntax/parameter[@name='Count'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Join']/managerEventInstance/syntax/parameter[@name='Position'])" />
</syntax>
<see-also>
<ref type="managerEvent">Join</ref>
</see-also>
</managerEventInstance>
***/
ast_manager_event(qe->chan, EVENT_FLAG_CALL, "Leave",
"Channel: %s\r\nQueue: %s\r\nCount: %d\r\nPosition: %d\r\nUniqueid: %s\r\n",
ast_channel_name(qe->chan), q->name, q->count, qe->pos, ast_channel_uniqueid(qe->chan));
ast_debug(1, "Queue '%s' Leave, Channel '%s'\n", q->name, ast_channel_name(qe->chan));
prev->next = current->next;
q->head = current->next;
Mark Michelson
committed
/* Free penalty rules */
while ((pr_iter = AST_LIST_REMOVE_HEAD(&qe->qe_rules, list))) {
Mark Michelson
committed
ast_free(pr_iter);
snprintf(posstr, sizeof(posstr), "%d", qe->pos);
pbx_builtin_setvar_helper(qe->chan, "QUEUEPOSITION", posstr);
/* Renumber the people after us in the queue based on a new count */
current->pos = ++pos;
prev = current;
Mark Michelson
committed
ao2_unlock(q);
/*If the queue is a realtime queue, check to see if it's still defined in real time*/
Mark Michelson
committed
if (q->realtime) {
struct ast_variable *var;
if (!(var = ast_load_realtime("queues", "name", q->name, SENTINEL))) {
Mark Michelson
committed
q->dead = 1;
} else {
ast_variables_destroy(var);
}
Mark Michelson
committed
}
Tilghman Lesher
committed
queues_t_unlink(queues, q, "Queue is now dead; remove it from the container");
/* unref the explicit ref earlier in the function */
Tilghman Lesher
committed
queue_t_unref(q, "Expire copied reference");
/*!
* \internal
* \brief Destroy the given callattempt structure and free it.
* \since 1.8
*
* \param doomed callattempt structure to destroy.
*
* \return Nothing
*/
static void callattempt_free(struct callattempt *doomed)
{
if (doomed->member) {
ao2_ref(doomed->member, -1);
}
ast_party_connected_line_free(&doomed->connected);
ast_free(doomed);
}
Mark Michelson
committed
/*! \brief Hang up a list of outgoing calls */
Olle Johansson
committed
static void hangupcalls(struct callattempt *outgoing, struct ast_channel *exception, int cancel_answered_elsewhere)
struct callattempt *oo;
Mark Spencer
committed
Olle Johansson
committed
/* If someone else answered the call we should indicate this in the CANCEL */
if (outgoing->chan && (outgoing->chan != exception)) {
ast_channel_hangupcause_set(outgoing->chan, AST_CAUSE_ANSWERED_ELSEWHERE);
Olle Johansson
committed
}
outgoing = outgoing->q_next;
ast_aoc_destroy_decoded(oo->aoc_s_rate_list);
callattempt_free(oo);
/*!
* \brief Get the number of members available to accept a call.
*
* \note The queue passed in should be locked prior to this function call
*
* \param[in] q The queue for which we are couting the number of available members
* \return Return the number of available members in queue q
*/
static int num_available_members(struct call_queue *q)
{
struct member *mem;
int avl = 0;
struct ao2_iterator mem_iter;
mem_iter = ao2_iterator_init(q->members, 0);
while ((mem = ao2_iterator_next(&mem_iter))) {
switch (mem->status) {
case AST_DEVICE_INVALID:
case AST_DEVICE_UNAVAILABLE:
break;
case AST_DEVICE_INUSE:
case AST_DEVICE_BUSY:
case AST_DEVICE_RINGING:
case AST_DEVICE_RINGINUSE:
case AST_DEVICE_ONHOLD:
if (!mem->ringinuse) {
break;
}
/* else fall through */
case AST_DEVICE_NOT_INUSE:
case AST_DEVICE_UNKNOWN:
if (!mem->paused) {
avl++;
}
break;
}
ao2_ref(mem, -1);
/* If autofill is not enabled or if the queue's strategy is ringall, then
* we really don't care about the number of available members so much as we
* do that there is at least one available.
*
* In fact, we purposely will return from this function stating that only
* one member is available if either of those conditions hold. That way,
* functions which determine what action to take based on the number of available
* members will operate properly. The reasoning is that even if multiple
* members are available, only the head caller can actually be serviced.
*/
if ((!q->autofill || q->strategy == QUEUE_STRATEGY_RINGALL) && avl) {
break;
}
}
ao2_iterator_destroy(&mem_iter);
return avl;
}
/* traverse all defined queues which have calls waiting and contain this member
return 0 if no other queue has precedence (higher weight) or 1 if found */
static int compare_weight(struct call_queue *rq, struct member *member)
Mark Spencer
committed
{
Mark Michelson
committed
struct ao2_iterator queue_iter;
Mark Michelson
committed
queue_iter = ao2_iterator_init(queues, 0);
Tilghman Lesher
committed
while ((q = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) {
Mark Michelson
committed
if (q == rq) { /* don't check myself, could deadlock */
Tilghman Lesher
committed
queue_t_unref(q, "Done with iterator");
Mark Michelson
committed
}
ao2_lock(q);
if ((mem = ao2_find(q->members, member, OBJ_POINTER))) {
ast_debug(1, "Found matching member %s in queue '%s'\n", mem->interface, q->name);
if (q->weight > rq->weight && q->count >= num_available_members(q)) {
ast_debug(1, "Queue '%s' (weight %d, calls %d) is preferred over '%s' (weight %d, calls %d)\n", q->name, q->weight, q->count, rq->name, rq->weight, rq->count);
Mark Michelson
committed
ao2_unlock(q);
Tilghman Lesher
committed
queue_t_unref(q, "Done with iterator");
Mark Michelson
committed
if (found) {
Mark Michelson
committed
}
ao2_iterator_destroy(&queue_iter);
/*! \brief common hangup actions */
static void do_hang(struct callattempt *o)
{
o->stillgoing = 0;
ast_hangup(o->chan);
o->chan = NULL;
}
Mark Michelson
committed
/*! \brief convert "\n" to "\nVariable: " ready for manager to use */
static char *vars2manager(struct ast_channel *chan, char *vars, size_t len)
{
struct ast_str *buf = ast_str_thread_get(&ast_str_thread_global_buf, len + 1);
const char *tmp;
if (pbx_builtin_serialize_variables(chan, &buf)) {
int i, j;
/* convert "\n" to "\nVariable: " */
strcpy(vars, "Variable: ");
tmp = ast_str_buffer(buf);
for (i = 0, j = 10; (i < len - 1) && (j < len - 1); i++, j++) {
vars[j] = tmp[i];
break;
if (tmp[i] == '\n') {
vars[j++] = '\r';
vars[j++] = '\n';
ast_copy_string(&(vars[j]), "Variable: ", len - j);
j += 9;
}
}
vars[j++] = '\r';
vars[j++] = '\n';
vars[j] = '\0';
} else {
/* there are no channel variables; leave it blank */
*vars = '\0';
}
return vars;
}
Mark Michelson
committed
/*!
* \brief Part 2 of ring_one
*
* Does error checking before attempting to request a channel and call a member.
* This function is only called from ring_one().
* Failure can occur if:
* - Agent on call
* - Agent is paused
* - Wrapup time not expired
* - Priority by another queue
Mark Michelson
committed
* \retval 1 on success to reach a free agent
* \retval 0 on failure to get agent.
static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies)
{
int res;
Mark Spencer
committed
int status;
char tech[256];
char *location;
const char *macrocontext, *macroexten;
enum ast_device_state newstate;
/* on entry here, we know that tmp->chan == NULL */
if (tmp->member->paused) {
ast_debug(1, "%s paused, can't receive call\n", tmp->interface);
if (ast_channel_cdr(qe->chan)) {
ast_cdr_busy(ast_channel_cdr(qe->chan));
tmp->stillgoing = 0;
return 0;
}
Kevin P. Fleming
committed
if ((tmp->lastqueue && tmp->lastqueue->wrapuptime && (time(NULL) - tmp->lastcall < tmp->lastqueue->wrapuptime)) ||
(!tmp->lastqueue && qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime))) {
ast_debug(1, "Wrapuptime not yet expired on queue %s for %s\n",
(tmp->lastqueue ? tmp->lastqueue->name : qe->parent->name), tmp->interface);
if (ast_channel_cdr(qe->chan)) {
ast_cdr_busy(ast_channel_cdr(qe->chan));
Kevin P. Fleming
committed
tmp->stillgoing = 0;
Kevin P. Fleming
committed
return 0;
}
if (!tmp->member->ringinuse) {
if (check_state_unknown && (tmp->member->status == AST_DEVICE_UNKNOWN)) {
newstate = ast_device_state(tmp->member->interface);
if (newstate != tmp->member->status) {
ast_log(LOG_WARNING, "Found a channel matching iterface %s while status was %s changed to %s\n",
tmp->member->interface, ast_devstate2str(tmp->member->status), ast_devstate2str(newstate));
ast_devstate_changed_literal(newstate, tmp->member->interface);
}
}
if ((tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) {
ast_debug(1, "%s in use, can't receive call\n", tmp->interface);
if (ast_channel_cdr(qe->chan)) {
ast_cdr_busy(ast_channel_cdr(qe->chan));
if (use_weight && compare_weight(qe->parent,tmp->member)) {
ast_debug(1, "Priority queue delaying call to %s:%s\n", qe->parent->name, tmp->interface);
if (ast_channel_cdr(qe->chan)) {
ast_cdr_busy(ast_channel_cdr(qe->chan));
tmp->stillgoing = 0;
(*busies)++;
return 0;
}
Kevin P. Fleming
committed
ast_copy_string(tech, tmp->interface, sizeof(tech));
*location++ = '\0';
/* Request the peer */
tmp->chan = ast_request(tech, ast_channel_nativeformats(qe->chan), qe->chan, location, &status);
if (!tmp->chan) { /* If we can't, just go on to the next call */
if (ast_channel_cdr(qe->chan)) {
ast_cdr_busy(ast_channel_cdr(qe->chan));
Mark Michelson
committed
ao2_lock(qe->parent);
update_status(qe->parent, tmp->member, get_queue_member_status(tmp->member));
qe->parent->rrpos++;
Mark Michelson
committed
qe->linpos++;
Mark Michelson
committed
ao2_unlock(qe->parent);
(*busies)++;
return 0;
Mark Michelson
committed
ast_channel_lock_both(tmp->chan, qe->chan);
Mark Michelson
committed
Olle Johansson
committed
if (qe->cancel_answered_elsewhere) {
ast_channel_hangupcause_set(tmp->chan, AST_CAUSE_ANSWERED_ELSEWHERE);
Olle Johansson
committed
}
ast_channel_appl_set(tmp->chan, "AppQueue");
ast_channel_data_set(tmp->chan, "(Outgoing Line)");
memset(ast_channel_whentohangup(tmp->chan), 0, sizeof(*ast_channel_whentohangup(tmp->chan)));
Mark Michelson
committed
/* If the new channel has no callerid, try to guess what it should be */
if (!ast_channel_caller(tmp->chan)->id.number.valid) {
if (ast_channel_connected(qe->chan)->id.number.valid) {
ast_party_caller_set_init(&caller, ast_channel_caller(tmp->chan));
caller.id = ast_channel_connected(qe->chan)->id;
caller.ani = ast_channel_connected(qe->chan)->ani;
ast_channel_set_caller_event(tmp->chan, &caller, NULL);
} else if (!ast_strlen_zero(ast_channel_dialed(qe->chan)->number.str)) {
ast_set_callerid(tmp->chan, ast_channel_dialed(qe->chan)->number.str, NULL, NULL);
} else if (!ast_strlen_zero(S_OR(ast_channel_macroexten(qe->chan), ast_channel_exten(qe->chan)))) {
ast_set_callerid(tmp->chan, S_OR(ast_channel_macroexten(qe->chan), ast_channel_exten(qe->chan)), NULL, NULL);
Mark Michelson
committed
}
tmp->dial_callerid_absent = 1;
Mark Michelson
committed
}
ast_party_redirecting_copy(ast_channel_redirecting(tmp->chan), ast_channel_redirecting(qe->chan));
Mark Michelson
committed
ast_channel_dialed(tmp->chan)->transit_network_select = ast_channel_dialed(qe->chan)->transit_network_select;
Mark Michelson
committed
ast_connected_line_copy_from_caller(ast_channel_connected(tmp->chan), ast_channel_caller(qe->chan));
/* Inherit specially named variables from parent channel */
ast_channel_inherit_variables(qe->chan, tmp->chan);
ast_channel_datastore_inherit(qe->chan, tmp->chan);
/* Presense of ADSI CPE on outgoing channel follows ours */
ast_channel_adsicpe_set(tmp->chan, ast_channel_adsicpe(qe->chan));
/* Inherit context and extension */
macrocontext = pbx_builtin_getvar_helper(qe->chan, "MACRO_CONTEXT");
ast_channel_dialcontext_set(tmp->chan, ast_strlen_zero(macrocontext) ? ast_channel_context(qe->chan) : macrocontext);
macroexten = pbx_builtin_getvar_helper(qe->chan, "MACRO_EXTEN");
ast_channel_exten_set(tmp->chan, macroexten);
ast_channel_exten_set(tmp->chan, ast_channel_exten(qe->chan));
if (ast_cdr_isset_unanswered()) {
/* they want to see the unanswered dial attempts! */
/* set up the CDR fields on all the CDRs to give sensical information */
ast_cdr_setdestchan(ast_channel_cdr(tmp->chan), ast_channel_name(tmp->chan));
strcpy(ast_channel_cdr(tmp->chan)->clid, ast_channel_cdr(qe->chan)->clid);
strcpy(ast_channel_cdr(tmp->chan)->channel, ast_channel_cdr(qe->chan)->channel);
strcpy(ast_channel_cdr(tmp->chan)->src, ast_channel_cdr(qe->chan)->src);
strcpy(ast_channel_cdr(tmp->chan)->dst, ast_channel_exten(qe->chan));
strcpy(ast_channel_cdr(tmp->chan)->dcontext, ast_channel_context(qe->chan));
strcpy(ast_channel_cdr(tmp->chan)->lastapp, ast_channel_cdr(qe->chan)->lastapp);
strcpy(ast_channel_cdr(tmp->chan)->lastdata, ast_channel_cdr(qe->chan)->lastdata);
ast_channel_cdr(tmp->chan)->amaflags = ast_channel_cdr(qe->chan)->amaflags;
strcpy(ast_channel_cdr(tmp->chan)->accountcode, ast_channel_cdr(qe->chan)->accountcode);
strcpy(ast_channel_cdr(tmp->chan)->userfield, ast_channel_cdr(qe->chan)->userfield);
ast_channel_unlock(tmp->chan);
ast_channel_unlock(qe->chan);
/* Place the call, but don't wait on the answer */
if ((res = ast_call(tmp->chan, location, 0))) {
/* Again, keep going even if there's an error */
ast_debug(1, "ast call on peer returned %d\n", res);
ast_verb(3, "Couldn't call %s\n", tmp->interface);
do_hang(tmp);
(*busies)++;
update_status(qe->parent, tmp->member, get_queue_member_status(tmp->member));
return 0;
} else if (qe->parent->eventwhencalled) {
char vars[2048];
ast_channel_lock_both(tmp->chan, qe->chan);
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
/*** DOCUMENTATION
<managerEventInstance>
<synopsis>Raised when an Agent is notified of a member in the queue.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<parameter name="AgentCalled">
<para>The agent's technology or location.</para>
</parameter>
<parameter name="AgentName">
<para>The name of the agent.</para>
</parameter>
<parameter name="Variable" required="no" multiple="yes">
<para>Optional channel variables from the ChannelCalling channel</para>
</parameter>
</syntax>
<see-also>
<ref type="managerEvent">AgentRingNoAnswer</ref>
<ref type="managerEvent">AgentComplete</ref>
<ref type="managerEvent">AgentConnect</ref>
</see-also>
</managerEventInstance>
***/
manager_event(EVENT_FLAG_AGENT, "AgentCalled",
"Queue: %s\r\n"
"AgentCalled: %s\r\n"
"AgentName: %s\r\n"
"ChannelCalling: %s\r\n"
"DestinationChannel: %s\r\n"
"CallerIDNum: %s\r\n"
"CallerIDName: %s\r\n"
"ConnectedLineNum: %s\r\n"
"ConnectedLineName: %s\r\n"
"Context: %s\r\n"
"Extension: %s\r\n"
"Priority: %d\r\n"
"Uniqueid: %s\r\n"
"%s",
qe->parent->name, tmp->interface, tmp->member->membername, ast_channel_name(qe->chan), ast_channel_name(tmp->chan),
S_COR(ast_channel_caller(qe->chan)->id.number.valid, ast_channel_caller(qe->chan)->id.number.str, "unknown"),
S_COR(ast_channel_caller(qe->chan)->id.name.valid, ast_channel_caller(qe->chan)->id.name.str, "unknown"),
S_COR(ast_channel_connected(qe->chan)->id.number.valid, ast_channel_connected(qe->chan)->id.number.str, "unknown"),
S_COR(ast_channel_connected(qe->chan)->id.name.valid, ast_channel_connected(qe->chan)->id.name.str, "unknown"),
ast_channel_context(qe->chan), ast_channel_exten(qe->chan), ast_channel_priority(qe->chan), ast_channel_uniqueid(qe->chan),
qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
ast_channel_unlock(tmp->chan);
ast_channel_unlock(qe->chan);
ast_verb(3, "Called %s\n", tmp->interface);
}
update_status(qe->parent, tmp->member, get_queue_member_status(tmp->member));
return 1;
}
/*! \brief find the entry with the best metric, or NULL */
static struct callattempt *find_best(struct callattempt *outgoing)
{
struct callattempt *best = NULL, *cur;
Mark Spencer
committed
for (cur = outgoing; cur; cur = cur->q_next) {
if (cur->stillgoing && /* Not already done */
!cur->chan && /* Isn't already going */
(!best || cur->metric < best->metric)) { /* We haven't found one yet, or it's better */
}
return best;
}
Mark Michelson
committed
/*!
* \brief Place a call to a queue member.
*
* Once metrics have been calculated for each member, this function is used
* to place a call to the appropriate member (or members). The low-level
* channel-handling and error detection is handled in ring_entry
*
Mark Michelson
committed
* \retval 1 if a member was called successfully
* \retval 0 otherwise
static int ring_one(struct queue_ent *qe, struct callattempt *outgoing, int *busies)
{
int ret = 0;
while (ret == 0) {
struct callattempt *best = find_best(outgoing);
if (!best) {
ast_debug(1, "Nobody left to try ringing in queue\n");
break;
}
Russell Bryant
committed
if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
struct callattempt *cur;
/* Ring everyone who shares this best metric (for ringall) */
for (cur = outgoing; cur; cur = cur->q_next) {
if (cur->stillgoing && !cur->chan && cur->metric <= best->metric) {
ast_debug(1, "(Parallel) Trying '%s' with metric %d\n", cur->interface, cur->metric);
ret |= ring_entry(qe, cur, busies);
}
}
} else {
/* Ring just the best channel */
ast_debug(1, "Trying '%s' with metric %d\n", best->interface, best->metric);
ret = ring_entry(qe, best, busies);
}
/* If we have timed out, break out */
if (qe->expire && (time(NULL) >= qe->expire)) {
ast_debug(1, "Queue timed out while ringing members.\n");
ret = 0;
break;
}
}
return ret;
}
Mark Michelson
committed
/*! \brief Search for best metric and add to Round Robbin queue */
Mark Michelson
committed
static int store_next_rr(struct queue_ent *qe, struct callattempt *outgoing)
struct callattempt *best = find_best(outgoing);
Mark Spencer
committed
if (best) {
/* Ring just the best channel */
ast_debug(1, "Next is '%s' with metric %d\n", best->interface, best->metric);
qe->parent->rrpos = best->metric % 1000;
} else {
/* Just increment rrpos */
Mark Spencer
committed
if (qe->parent->wrapped) {
/* No more channels, start over */
qe->parent->rrpos = 0;
} else {
/* Prioritize next entry */
qe->parent->rrpos++;
}
Mark Spencer
committed
qe->parent->wrapped = 0;
return 0;
}
Mark Michelson
committed
/*! \brief Search for best metric and add to Linear queue */
Mark Michelson
committed
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
static int store_next_lin(struct queue_ent *qe, struct callattempt *outgoing)
{
struct callattempt *best = find_best(outgoing);
if (best) {
/* Ring just the best channel */
ast_debug(1, "Next is '%s' with metric %d\n", best->interface, best->metric);
qe->linpos = best->metric % 1000;
} else {
/* Just increment rrpos */
if (qe->linwrapped) {
/* No more channels, start over */
qe->linpos = 0;
} else {
/* Prioritize next entry */
qe->linpos++;
}
}
qe->linwrapped = 0;
return 0;
}
/*! \brief Playback announcement to queued members if period has elapsed */
Steve Murphy
committed
static int say_periodic_announcement(struct queue_ent *qe, int ringing)
{
int res = 0;
time_t now;
/* Get the current time */
time(&now);
/* Check to see if it is time to announce */
if ((now - qe->last_periodic_announce_time) < qe->parent->periodicannouncefrequency) {
Kevin P. Fleming
committed
return 0;
/* Stop the music on hold so we can play our own file */
Steve Murphy
committed
ast_indicate(qe->chan,-1);
Steve Murphy
committed
ast_moh_stop(qe->chan);
ast_verb(3, "Playing periodic announcement\n");
if (qe->parent->randomperiodicannounce && qe->parent->numperiodicannounce) {
qe->last_periodic_announce_sound = ((unsigned long) ast_random()) % qe->parent->numperiodicannounce;
} else if (qe->last_periodic_announce_sound >= qe->parent->numperiodicannounce ||
ast_str_strlen(qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]) == 0) {
qe->last_periodic_announce_sound = 0;
}
/* play the announcement */
res = play_file(qe->chan, ast_str_buffer(qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]));
/* Resume Music on Hold if the caller is going to stay in the queue */
Steve Murphy
committed
if (!res) {
ast_indicate(qe->chan, AST_CONTROL_RINGING);
Steve Murphy
committed
}
/* update last_periodic_announce_time */
Matthew Nicholson
committed
time(&qe->last_periodic_announce_time);
Matthew Nicholson
committed
qe->last_periodic_announce_time = now;
/* Update the current periodic announcement to the next announcement */
if (!qe->parent->randomperiodicannounce) {
qe->last_periodic_announce_sound++;
}
Kevin P. Fleming
committed
return res;
Mark Michelson
committed
/*! \brief Record that a caller gave up on waiting in queue */
static void record_abandoned(struct queue_ent *qe)
{
Mark Michelson
committed
set_queue_variables(qe->parent, qe->chan);
/*** DOCUMENTATION
<managerEventInstance>
<synopsis>Raised when an caller abandons the queue.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Join']/managerEventInstance/syntax/parameter[@name='Position'])" />
<parameter name="OriginalPosition">
<para>The channel's original position in the queue.</para>
</parameter>
<parameter name="HoldTime">
<para>The time the channel was in the queue, expressed in seconds since 00:00, Jan 1, 1970 UTC.</para>
</parameter>
</syntax>
</managerEventInstance>
***/
manager_event(EVENT_FLAG_AGENT, "QueueCallerAbandon",
"Queue: %s\r\n"
"Uniqueid: %s\r\n"
"Position: %d\r\n"
"OriginalPosition: %d\r\n"
"HoldTime: %d\r\n",
qe->parent->name, ast_channel_uniqueid(qe->chan), qe->pos, qe->opos, (int)(time(NULL) - qe->start));
qe->parent->callsabandoned++;
Mark Michelson
committed
ao2_unlock(qe->parent);
/*! \brief RNA == Ring No Answer. Common code that is executed when we try a queue member and they don't answer. */
static void rna(int rnatime, struct queue_ent *qe, char *interface, char *membername, int autopause)
{
ast_verb(3, "Nobody picked up in %d ms\n", rnatime);
/* Stop ringing, and resume MOH if specified */
if (qe->ring_when_ringing) {
ast_indicate(qe->chan, -1);
ast_moh_start(qe->chan, qe->moh, NULL);
}
if (qe->parent->eventwhencalled) {
char vars[2048];
/*** DOCUMENTATION
<managerEventInstance>
<synopsis>Raised when an agent is notified of a member in the queue and fails to answer.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='MemberName'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='AgentCalled']/managerEventInstance/syntax/parameter[@name='Variable'])" />
<parameter name="Member">
<para>The queue member's channel technology or location.</para>
</parameter>
<parameter name="RingTime">
<para>The time the agent was rung, expressed in seconds since 00:00, Jan 1, 1970 UTC.</para>
</parameter>
</syntax>
<see-also>
<ref type="managerEvent">AgentCalled</ref>
</see-also>
</managerEventInstance>
***/
manager_event(EVENT_FLAG_AGENT, "AgentRingNoAnswer",
"Queue: %s\r\n"
"Uniqueid: %s\r\n"
"Channel: %s\r\n"
"Member: %s\r\n"
"MemberName: %s\r\n"
ast_channel_name(qe->chan),
interface,
membername,
rnatime,
qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
}
ast_queue_log(qe->parent->name, ast_channel_uniqueid(qe->chan), membername, "RINGNOANSWER", "%d", rnatime);
if (qe->parent->autopause != QUEUE_AUTOPAUSE_OFF && autopause) {
if (qe->parent->autopausedelay > 0) {
struct member *mem;
ao2_lock(qe->parent);
if ((mem = interface_exists(qe->parent, interface))) {
time_t idletime = time(&idletime)-mem->lastcall;
if ((mem->lastcall != 0) && (qe->parent->autopausedelay > idletime)) {
ao2_unlock(qe->parent);
Mark Michelson
committed
if (qe->parent->autopause == QUEUE_AUTOPAUSE_ON) {
if (!set_member_paused(qe->parent->name, interface, "Auto-Pause", 1)) {
ast_verb(3, "Auto-Pausing Queue Member %s in queue %s since they failed to answer.\n",
interface, qe->parent->name);
} else {
ast_verb(3, "Failed to pause Queue Member %s in queue %s!\n", interface, qe->parent->name);
}
} else {
Mark Michelson
committed
/* If queue autopause is mode all, just don't send any queue to stop.
* the function will stop in all queues */
if (!set_member_paused("", interface, "Auto-Pause", 1)) {
ast_verb(3, "Auto-Pausing Queue Member %s in all queues since they failed to answer on queue %s.\n",
interface, qe->parent->name);
} else {
ast_verb(3, "Failed to pause Queue Member %s in all queues!\n", interface);
}
}
}
Mark Spencer
committed
#define AST_MAX_WATCHERS 256
/*!
* \brief Wait for a member to answer the call
*
* \param[in] qe the queue_ent corresponding to the caller in the queue
* \param[in] outgoing the list of callattempts. Relevant ones will have their chan and stillgoing parameters non-zero
* \param[in] to the amount of time (in milliseconds) to wait for a response
* \param[out] digit if a user presses a digit to exit the queue, this is the digit the caller pressed
* \param[in] prebusies number of busy members calculated prior to calling wait_for_answer
* \param[in] caller_disconnect if the 'H' option is used when calling Queue(), this is used to detect if the caller pressed * to disconnect the call
* \param[in] forwardsallowed used to detect if we should allow call forwarding, based on the 'i' option to Queue()
*
* \todo eventually all call forward logic should be intergerated into and replaced by ast_call_forward()
static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callattempt *outgoing, int *to, char *digit, int prebusies, int caller_disconnect, int forwardsallowed)
{
const char *queue = qe->parent->name;
struct callattempt *o, *start = NULL, *prev = NULL;
Mark Spencer
committed
int status;
int numbusies = prebusies;
int numnochan = 0;
int stillgoing = 0;
struct callattempt *peer = NULL;
struct ast_channel *in = qe->chan;
char on[80] = "";
char membername[80] = "";
long starttime = 0;
Mark Michelson
committed
long endtime = 0;
#ifdef HAVE_EPOLL
struct callattempt *epollo;
#endif
Mark Michelson
committed
struct ast_party_connected_line connected_caller;
char *inchan_name;
ast_party_connected_line_init(&connected_caller);
Mark Michelson
committed
ast_channel_lock(qe->chan);
inchan_name = ast_strdupa(ast_channel_name(qe->chan));
Mark Michelson
committed
ast_channel_unlock(qe->chan);
starttime = (long) time(NULL);
Mark Michelson
committed
#ifdef HAVE_EPOLL
for (epollo = outgoing; epollo; epollo = epollo->q_next) {
ast_poll_channel_add(in, epollo->chan);
}
Mark Michelson
committed
#endif
int numlines, retry, pos = 1;
struct ast_channel *watchers[AST_MAX_WATCHERS];
watchers[0] = in;
for (retry = 0; retry < 2; retry++) {
numlines = 0;
for (o = outgoing; o; o = o->q_next) { /* Keep track of important channels */
if (o->stillgoing) { /* Keep track of important channels */
stillgoing = 1;
if (pos < AST_MAX_WATCHERS) {
watchers[pos++] = o->chan;
}
}
numlines++;
}
if (pos > 1 /* found */ || !stillgoing /* nobody listening */ ||
(qe->parent->strategy != QUEUE_STRATEGY_RINGALL) /* ring would not be delivered */) {
break;
/* On "ringall" strategy we only move to the next penalty level
when *all* ringing phones are done in the current penalty level */
ring_one(qe, outgoing, &numbusies);
/* and retry... */
if (pos == 1 /* not found */) {
if (numlines == (numbusies + numnochan)) {
ast_debug(1, "Everyone is busy at this time\n");
Mark Michelson
committed
ast_debug(3, "No one is answering queue '%s' (%d numlines / %d busies / %d failed channels)\n", queue, numlines, numbusies, numnochan);
/* Poll for events from both the incoming channel as well as any outgoing channels */
/* Service all of the outgoing channels */
for (o = start; o; o = o->call_next) {
/* We go with a fixed buffer here instead of using ast_strdupa. Using
Mark Michelson
committed
* ast_strdupa in a loop like this one can cause a stack overflow
*/
char ochan_name[AST_CHANNEL_NAME];
if (o->chan) {
ast_channel_lock(o->chan);
ast_copy_string(ochan_name, ast_channel_name(o->chan), sizeof(ochan_name));
ast_channel_unlock(o->chan);
}
if (o->stillgoing && (o->chan) && (ast_channel_state(o->chan) == AST_STATE_UP)) {
Mark Michelson
committed
ast_verb(3, "%s answered %s\n", ochan_name, inchan_name);
if (!o->block_connected_update) {
if (o->pending_connected_update) {
if (ast_channel_connected_line_sub(o->chan, in, &o->connected, 0) &&
ast_channel_connected_line_macro(o->chan, in, &o->connected, 1, 0)) {
ast_channel_update_connected_line(in, &o->connected, NULL);
} else if (!o->dial_callerid_absent) {
Mark Michelson
committed
ast_channel_lock(o->chan);
ast_connected_line_copy_from_caller(&connected_caller, ast_channel_caller(o->chan));
Mark Michelson
committed
ast_channel_unlock(o->chan);
connected_caller.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
Richard Mudgett
committed
if (ast_channel_connected_line_sub(o->chan, in, &connected_caller, 0) &&
ast_channel_connected_line_macro(o->chan, in, &connected_caller, 1, 0)) {
ast_channel_update_connected_line(in, &connected_caller, NULL);
}
Mark Michelson
committed
ast_party_connected_line_free(&connected_caller);
}
}
if (o->aoc_s_rate_list) {
size_t encoded_size;
struct ast_aoc_encoded *encoded;
if ((encoded = ast_aoc_encode(o->aoc_s_rate_list, &encoded_size, o->chan))) {
ast_indicate_data(in, AST_CONTROL_AOC, encoded, encoded_size);
ast_aoc_destroy_encoded(encoded);
}
}
peer = o;
ast_copy_string(on, o->member->interface, sizeof(on));