Newer
Older
ast_copy_string(membername, o->member->membername, sizeof(membername));
/* Before processing channel, go ahead and check for forwarding */
if (!ast_strlen_zero(ast_channel_call_forward(o->chan)) && !forwardsallowed) {
ast_verb(3, "Forwarding %s to '%s' prevented.\n", inchan_name, ast_channel_call_forward(o->chan));
numnochan++;
do_hang(o);
winner = NULL;
continue;
} else if (!ast_strlen_zero(ast_channel_call_forward(o->chan))) {
Mark Michelson
committed
struct ast_channel *original = o->chan;
char *stuff;
char *tech;
ast_copy_string(tmpchan, ast_channel_call_forward(o->chan), sizeof(tmpchan));
if ((stuff = strchr(tmpchan, '/'))) {
*stuff++ = '\0';
tech = tmpchan;
} else {
snprintf(tmpchan, sizeof(tmpchan), "%s@%s", ast_channel_call_forward(o->chan), ast_channel_context(o->chan));
stuff = tmpchan;
tech = "Local";
}
if (!strcasecmp(tech, "Local")) {
/*
* Drop the connected line update block for local channels since
* this is going to run dialplan and the user can change his
* mind about what connected line information he wants to send.
*/
o->block_connected_update = 0;
}
ast_cel_report_event(in, AST_CEL_FORWARD, NULL, ast_channel_call_forward(o->chan), NULL);
Mark Michelson
committed
ast_verb(3, "Now forwarding %s to '%s/%s' (thanks to %s)\n", inchan_name, tech, stuff, ochan_name);
/* Setup parameters */
o->chan = ast_request(tech, ast_channel_nativeformats(in), in, stuff, &status);
if (!o->chan) {
ast_log(LOG_NOTICE,
"Forwarding failed to create channel to dial '%s/%s'\n",
tech, stuff);
o->stillgoing = 0;
numnochan++;
} else {
ast_channel_lock_both(o->chan, original);
ast_party_redirecting_copy(ast_channel_redirecting(o->chan),
ast_channel_redirecting(original));
ast_channel_unlock(o->chan);
ast_channel_unlock(original);
ast_channel_lock_both(o->chan, in);
ast_channel_inherit_variables(in, o->chan);
ast_channel_datastore_inherit(in, o->chan);
if (o->pending_connected_update) {
/*
* Re-seed the callattempt's connected line information with
* previously acquired connected line info from the queued
* channel. The previously acquired connected line info could
* have been set through the CONNECTED_LINE dialplan function.
*/
o->pending_connected_update = 0;
ast_party_connected_line_copy(&o->connected, ast_channel_connected(in));
}
ast_channel_accountcode_set(o->chan, ast_channel_accountcode(in));
if (!ast_channel_redirecting(o->chan)->from.number.valid
|| ast_strlen_zero(ast_channel_redirecting(o->chan)->from.number.str)) {
Richard Mudgett
committed
/*
* The call was not previously redirected so it is
* now redirected from this number.
*/
ast_party_number_free(&ast_channel_redirecting(o->chan)->from.number);
ast_party_number_init(&ast_channel_redirecting(o->chan)->from.number);
ast_channel_redirecting(o->chan)->from.number.valid = 1;
ast_channel_redirecting(o->chan)->from.number.str =
ast_strdup(S_OR(ast_channel_macroexten(in), ast_channel_exten(in)));
Richard Mudgett
committed
}
Mark Michelson
committed
ast_channel_dialed(o->chan)->transit_network_select = ast_channel_dialed(in)->transit_network_select;
Mark Michelson
committed
o->dial_callerid_absent = !ast_channel_caller(o->chan)->id.number.valid
|| ast_strlen_zero(ast_channel_caller(o->chan)->id.number.str);
ast_connected_line_copy_from_caller(ast_channel_connected(o->chan),
ast_channel_caller(in));
Mark Michelson
committed
ast_channel_unlock(in);
if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL
&& !o->block_connected_update) {
struct ast_party_redirecting redirecting;
Mark Michelson
committed
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
/*
* Redirecting updates to the caller make sense only on single
* call at a time strategies.
*
* We must unlock o->chan before calling
* ast_channel_redirecting_macro, because we put o->chan into
* autoservice there. That is pretty much a guaranteed
* deadlock. This is why the handling of o->chan's lock may
* seem a bit unusual here.
*/
ast_party_redirecting_init(&redirecting);
ast_party_redirecting_copy(&redirecting, ast_channel_redirecting(o->chan));
ast_channel_unlock(o->chan);
if (ast_channel_redirecting_sub(o->chan, in, &redirecting, 0) &&
ast_channel_redirecting_macro(o->chan, in, &redirecting, 1, 0)) {
ast_channel_update_redirecting(in, &redirecting, NULL);
}
ast_party_redirecting_free(&redirecting);
} else {
ast_channel_unlock(o->chan);
}
Mark Michelson
committed
if (ast_call(o->chan, stuff, 0)) {
ast_log(LOG_NOTICE, "Forwarding failed to dial '%s/%s'\n",
tech, stuff);
do_hang(o);
numnochan++;
}
}
/* Hangup the original channel now, in case we needed it */
ast_hangup(winner);
continue;
}
f = ast_read(winner);
if (f) {
if (f->frametype == AST_FRAME_CONTROL) {
switch (f->subclass.integer) {
/* This is our guy if someone answered. */
if (!peer) {
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;
Mark Michelson
committed
ast_verb(3, "%s is busy\n", ochan_name);
ast_cdr_busy(ast_channel_cdr(in));
do_hang(o);
Russell Bryant
committed
endtime = (long) time(NULL);
endtime -= starttime;
rna(endtime * 1000, qe, on, membername, qe->parent->autopausebusy);
Russell Bryant
committed
if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
/* Have enough time for a queue member to answer? */
if (*to > 500) {
ring_one(qe, outgoing, &numbusies);
starttime = (long) time(NULL);
}
numbusies++;
break;
case AST_CONTROL_CONGESTION:
Mark Michelson
committed
ast_verb(3, "%s is circuit-busy\n", ochan_name);
ast_cdr_busy(ast_channel_cdr(in));
Russell Bryant
committed
endtime = (long) time(NULL);
endtime -= starttime;
rna(endtime * 1000, qe, on, membername, qe->parent->autopauseunavail);
do_hang(o);
Russell Bryant
committed
if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
if (*to > 500) {
ring_one(qe, outgoing, &numbusies);
starttime = (long) time(NULL);
}
numbusies++;
break;
case AST_CONTROL_RINGING:
Mark Michelson
committed
ast_verb(3, "%s is ringing\n", ochan_name);
/* Start ring indication when the channel is ringing, if specified */
if (qe->ring_when_ringing) {
ast_moh_stop(qe->chan);
ast_indicate(qe->chan, AST_CONTROL_RINGING);
}
break;
case AST_CONTROL_OFFHOOK:
/* Ignore going off hook */
break;
Mark Michelson
committed
case AST_CONTROL_CONNECTED_LINE:
if (o->block_connected_update) {
Mark Michelson
committed
ast_verb(3, "Connected line update to %s prevented.\n", inchan_name);
break;
}
if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
Mark Michelson
committed
struct ast_party_connected_line connected;
Mark Michelson
committed
ast_verb(3, "%s connected line has changed. Saving it until answer for %s\n", ochan_name, inchan_name);
ast_party_connected_line_set_init(&connected, &o->connected);
ast_connected_line_parse_data(f->data.ptr, f->datalen, &connected);
ast_party_connected_line_set(&o->connected, &connected, NULL);
Mark Michelson
committed
ast_party_connected_line_free(&connected);
o->pending_connected_update = 1;
break;
}
/*
* Prevent using the CallerID from the outgoing channel since we
* got a connected line update from it.
*/
o->dial_callerid_absent = 1;
if (ast_channel_connected_line_sub(o->chan, in, f, 1) &&
ast_channel_connected_line_macro(o->chan, in, f, 1, 1)) {
ast_indicate_data(in, AST_CONTROL_CONNECTED_LINE, f->data.ptr, f->datalen);
Mark Michelson
committed
}
break;
case AST_CONTROL_AOC:
{
struct ast_aoc_decoded *decoded = ast_aoc_decode(f->data.ptr, f->datalen, o->chan);
if (decoded && (ast_aoc_get_msg_type(decoded) == AST_AOC_S)) {
ast_aoc_destroy_decoded(o->aoc_s_rate_list);
o->aoc_s_rate_list = decoded;
} else {
ast_aoc_destroy_decoded(decoded);
}
}
break;
Mark Michelson
committed
case AST_CONTROL_REDIRECTING:
if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
/*
* Redirecting updates to the caller make sense only on single
* call at a time strategies.
*/
break;
}
if (o->block_connected_update) {
ast_verb(3, "Redirecting update to %s prevented\n",
inchan_name);
break;
}
ast_verb(3, "%s redirecting info has changed, passing it to %s\n",
ochan_name, inchan_name);
if (ast_channel_redirecting_sub(o->chan, in, f, 1) &&
ast_channel_redirecting_macro(o->chan, in, f, 1, 1)) {
ast_indicate_data(in, AST_CONTROL_REDIRECTING, f->data.ptr, f->datalen);
Mark Michelson
committed
}
break;
case AST_CONTROL_PVT_CAUSE_CODE:
ast_indicate_data(in, AST_CONTROL_PVT_CAUSE_CODE, f->data.ptr, f->datalen);
break;
ast_debug(1, "Dunno what to do with control type %d\n", f->subclass.integer);
Mark Michelson
committed
break;
} else { /* ast_read() returned NULL */
endtime = (long) time(NULL) - starttime;
rna(endtime * 1000, qe, on, membername, 1);
do_hang(o);
Russell Bryant
committed
if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
if (*to > 500) {
ring_one(qe, outgoing, &numbusies);
starttime = (long) time(NULL);
}
/* If we received an event from the caller, deal with it. */
if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass.integer == AST_CONTROL_HANGUP))) {
Mark Michelson
committed
if (f) {
Michiel van Baak
committed
if (f->data.uint32) {
ast_channel_hangupcause_set(in, f->data.uint32);
Michiel van Baak
committed
}
ast_frfree(f);
Mark Michelson
committed
}
/*!
* \todo
* XXX Queue like Dial really should send any connected line
* updates (AST_CONTROL_CONNECTED_LINE) from the caller to each
* ringing queue member.
*/
if ((f->frametype == AST_FRAME_DTMF) && caller_disconnect && (f->subclass.integer == '*')) {
ast_verb(3, "User hit %c to disconnect call.\n", f->subclass.integer);
ast_frfree(f);
if ((f->frametype == AST_FRAME_DTMF) && valid_exit(qe, f->subclass.integer)) {
ast_verb(3, "User pressed digit: %c\n", f->subclass.integer);
*digit = f->subclass.integer;
ast_frfree(f);
return NULL;
}
ast_frfree(f);
rna(orig, qe, o->interface, o->member->membername, 1);
Mark Michelson
committed
#ifdef HAVE_EPOLL
Mark Michelson
committed
for (epollo = outgoing; epollo; epollo = epollo->q_next) {
ast_poll_channel_del(in, epollo->chan);
}
Mark Michelson
committed
#endif
Mark Michelson
committed
/*!
* \brief Check if we should start attempting to call queue members.
* A simple process, really. Count the number of members who are available
* to take our call and then see if we are in a position in the queue at
* which a member could accept our call.
*
* \param[in] qe The caller who wants to know if it is his turn
* \retval 0 It is not our turn
* \retval 1 It is our turn
static int is_our_turn(struct queue_ent *qe)
{
struct queue_ent *ch;
int res;
int avl;
int idx = 0;
/* This needs a lock. How many members are available to be served? */
ao2_lock(qe->parent);
avl = num_available_members(qe->parent);
ast_debug(1, "There %s %d available %s.\n", avl != 1 ? "are" : "is", avl, avl != 1 ? "members" : "member");
while ((idx < avl) && (ch) && (ch != qe)) {
ch = ch->next;
}
ao2_unlock(qe->parent);
/* If the queue entry is within avl [the number of available members] calls from the top ...
* Autofill and position check added to support autofill=no (as only calls
* from the front of the queue are valid when autofill is disabled)
*/
if (ch && idx < avl && (qe->parent->autofill || qe->pos == 1)) {
ast_debug(1, "It's our turn (%s).\n", ast_channel_name(qe->chan));
ast_debug(1, "It's not our turn (%s).\n", ast_channel_name(qe->chan));
return res;
}
Mark Michelson
committed
/*!
* \brief update rules for queues
*
* Calculate min/max penalties making sure if relative they stay within bounds.
* Update queues penalty and set dialplan vars, goto next list entry.
*/
Mark Michelson
committed
static void update_qe_rule(struct queue_ent *qe)
{
int max_penalty = qe->pr->max_relative ? qe->max_penalty + qe->pr->max_value : qe->pr->max_value;
int min_penalty = qe->pr->min_relative ? qe->min_penalty + qe->pr->min_value : qe->pr->min_value;
char max_penalty_str[20], min_penalty_str[20];
/* a relative change to the penalty could put it below 0 */
Mark Michelson
committed
max_penalty = 0;
Mark Michelson
committed
min_penalty = 0;
Mark Michelson
committed
min_penalty = max_penalty;
snprintf(max_penalty_str, sizeof(max_penalty_str), "%d", max_penalty);
snprintf(min_penalty_str, sizeof(min_penalty_str), "%d", min_penalty);
Mark Michelson
committed
pbx_builtin_setvar_helper(qe->chan, "QUEUE_MAX_PENALTY", max_penalty_str);
pbx_builtin_setvar_helper(qe->chan, "QUEUE_MIN_PENALTY", min_penalty_str);
qe->max_penalty = max_penalty;
qe->min_penalty = min_penalty;
ast_debug(3, "Setting max penalty to %d and min penalty to %d for caller %s since %d seconds have elapsed\n", qe->max_penalty, qe->min_penalty, ast_channel_name(qe->chan), qe->pr->time);
Mark Michelson
committed
qe->pr = AST_LIST_NEXT(qe->pr, list);
}
/*! \brief The waiting areas for callers who are not actively calling members
*
* This function is one large loop. This function will return if a caller
* either exits the queue or it becomes that caller's turn to attempt calling
* queue members. Inside the loop, we service the caller with periodic announcements,
* holdtime announcements, etc. as configured in queues.conf
*
* \retval 0 if the caller's turn has arrived
* \retval -1 if the caller should exit the queue.
Mark Spencer
committed
static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *reason)
/* This is the holding pen for callers 2 through maxlen */
Mark Spencer
committed
/* If we have timed out, break out */
if (qe->expire && (time(NULL) >= qe->expire)) {
Mark Spencer
committed
*reason = QUEUE_TIMEOUT;
Mark Spencer
committed
}
if (qe->parent->leavewhenempty) {
int status = 0;
if ((status = get_member_status(qe->parent, qe->max_penalty, qe->min_penalty, qe->parent->leavewhenempty))) {
*reason = QUEUE_LEAVEEMPTY;
ast_queue_log(qe->parent->name, ast_channel_uniqueid(qe->chan), "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long) time(NULL) - qe->start);
leave_queue(qe);
break;
}
}
/* Make a position announcement, if enabled */
Steve Murphy
committed
if (qe->parent->announcefrequency &&
Kevin P. Fleming
committed
break;
/* If we have timed out, break out */
if (qe->expire && (time(NULL) >= qe->expire)) {
*reason = QUEUE_TIMEOUT;
break;
}
/* Make a periodic announcement, if enabled */
Steve Murphy
committed
if (qe->parent->periodicannouncefrequency &&
(res = say_periodic_announcement(qe,ringing)))
Mark Michelson
committed
/* see if we need to move to the next penalty level for this queue */
while (qe->pr && ((time(NULL) - qe->start) >= qe->pr->time)) {
Mark Michelson
committed
update_qe_rule(qe);
}
/* If we have timed out, break out */
if (qe->expire && (time(NULL) >= qe->expire)) {
*reason = QUEUE_TIMEOUT;
break;
}
if ((res = ast_waitfordigit(qe->chan, RECHECK * 1000))) {
/* If we have timed out, break out */
if (qe->expire && (time(NULL) >= qe->expire)) {
*reason = QUEUE_TIMEOUT;
break;
}
Mark Michelson
committed
/*!
* \brief update the queue status
* \retval Always 0
*/
static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl, int newtalktime)
{
Mark Michelson
committed
struct member *mem;
struct call_queue *qtmp;
Mark Michelson
committed
if (shared_lastcall) {
queue_iter = ao2_iterator_init(queues, 0);
Tilghman Lesher
committed
while ((qtmp = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) {
Mark Michelson
committed
ao2_lock(qtmp);
if ((mem = ao2_find(qtmp->members, member, OBJ_POINTER))) {
Mark Michelson
committed
time(&mem->lastcall);
mem->calls++;
mem->lastqueue = q;
ao2_ref(mem, -1);
}
ao2_unlock(qtmp);
Tilghman Lesher
committed
queue_t_unref(qtmp, "Done with iterator");
Mark Michelson
committed
}
ao2_iterator_destroy(&queue_iter);
Mark Michelson
committed
} else {
ao2_lock(q);
time(&member->lastcall);
member->calls++;
member->lastqueue = q;
ao2_unlock(q);
}
Mark Michelson
committed
ao2_lock(q);
/* Calculate talktime using the same exponential average as holdtime code*/
oldtalktime = q->talktime;
q->talktime = (((oldtalktime << 2) - oldtalktime) + newtalktime) >> 2;
Mark Michelson
committed
ao2_unlock(q);
return 0;
}
/*! \brief Calculate the metric of each member in the outgoing callattempts
*
* A numeric metric is given to each member depending on the ring strategy used
* by the queue. Members with lower metrics will be called before members with
* higher metrics
Mark Michelson
committed
* \retval -1 if penalties are exceeded
* \retval 0 otherwise
static int calc_metric(struct call_queue *q, struct member *mem, int pos, struct queue_ent *qe, struct callattempt *tmp)
int membercount = ao2_container_count(q->members);
unsigned char usepenalty = (membercount <= q->penaltymemberslimit) ? 0 : 1;
if (usepenalty) {
if ((qe->max_penalty && (mem->penalty > qe->max_penalty)) ||
(qe->min_penalty && (mem->penalty < qe->min_penalty))) {
return -1;
}
} else {
ast_debug(1, "Disregarding penalty, %d members and %d in penaltymemberslimit.\n",
Kevin P. Fleming
committed
switch (q->strategy) {
case QUEUE_STRATEGY_RINGALL:
/* Everyone equal, except for penalty */
Mark Michelson
committed
case QUEUE_STRATEGY_LINEAR:
if (pos < qe->linpos) {
tmp->metric = 1000 + pos;
} else {
Mark Michelson
committed
/* Indicate there is another priority */
qe->linwrapped = 1;
Mark Michelson
committed
tmp->metric = pos;
}
Mark Michelson
committed
break;
case QUEUE_STRATEGY_RRMEMORY:
if (pos < q->rrpos) {
tmp->metric = 1000 + pos;
} else {
Mark Spencer
committed
q->wrapped = 1;
tmp->metric = pos;
}
break;
case QUEUE_STRATEGY_RANDOM:
Tilghman Lesher
committed
tmp->metric = ast_random() % 1000;
break;
case QUEUE_STRATEGY_WRANDOM:
tmp->metric = ast_random() % ((1 + mem->penalty) * 1000);
break;
case QUEUE_STRATEGY_FEWESTCALLS:
tmp->metric = mem->calls;
break;
case QUEUE_STRATEGY_LEASTRECENT:
tmp->metric = 0;
tmp->metric = 1000000 - (time(NULL) - mem->lastcall);
break;
default:
ast_log(LOG_WARNING, "Can't calculate metric for unknown strategy %d\n", q->strategy);
break;
enum agent_complete_reason {
CALLER,
AGENT,
TRANSFER
};
Mark Michelson
committed
/*! \brief Send out AMI message with member call completion status information */
static void send_agent_complete(const struct queue_ent *qe, const char *queuename,
const struct ast_channel *peer, const struct member *member, time_t callstart,
char *vars, size_t vars_len, enum agent_complete_reason rsn)
{
const char *reason = NULL; /* silence dumb compilers */
return;
switch (rsn) {
case CALLER:
reason = "caller";
break;
case AGENT:
reason = "agent";
break;
case TRANSFER:
reason = "transfer";
break;
}
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
/*** DOCUMENTATION
<managerEventInstance>
<synopsis>Raised when an agent has finished servicing a member in the queue.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='AgentRingNoAnswer']/managerEventInstance/syntax/parameter[@name='Member'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='MemberName'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueCallerAbandon']/managerEventInstance/syntax/parameter[@name='HoldTime'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='AgentCalled']/managerEventInstance/syntax/parameter[@name='Variable'])" />
<parameter name="TalkTime">
<para>The time the agent talked with the member in the queue, expressed in seconds since 00:00, Jan 1, 1970 UTC.</para>
</parameter>
<parameter name="Reason">
<enumlist>
<enum name="caller"/>
<enum name="agent"/>
<enum name="transfer"/>
</enumlist>
</parameter>
</syntax>
<see-also>
<ref type="managerEvent">AgentCalled</ref>
<ref type="managerEvent">AgentConnect</ref>
</see-also>
</managerEventInstance>
***/
manager_event(EVENT_FLAG_AGENT, "AgentComplete",
"Queue: %s\r\n"
"Uniqueid: %s\r\n"
"Channel: %s\r\n"
"Member: %s\r\n"
"MemberName: %s\r\n"
"HoldTime: %ld\r\n"
"TalkTime: %ld\r\n"
"Reason: %s\r\n"
"%s",
queuename, ast_channel_uniqueid(qe->chan), ast_channel_name(peer), member->interface, member->membername,
(long)(callstart - qe->start), (long)(time(NULL) - callstart), reason,
qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, vars_len) : "");
}
Mark Michelson
committed
struct queue_transfer_ds {
struct queue_ent *qe;
struct member *member;
static void queue_transfer_destroy(void *data)
{
struct queue_transfer_ds *qtds = data;
ast_free(qtds);
}
/*! \brief a datastore used to help correctly log attended transfers of queue callers
*/
static const struct ast_datastore_info queue_transfer_info = {
.type = "queue_transfer",
.chan_fixup = queue_transfer_fixup,
.destroy = queue_transfer_destroy,
};
/*! \brief Log an attended transfer when a queue caller channel is masqueraded
*
* When a caller is masqueraded, we want to log a transfer. Fixup time is the closest we can come to when
* the actual transfer occurs. This happens during the masquerade after datastores are moved from old_chan
* to new_chan. This is why new_chan is referenced for exten, context, and datastore information.
*
* At the end of this, we want to remove the datastore so that this fixup function is not called on any
* future masquerades of the caller during the current call.
*/
static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
{
struct queue_transfer_ds *qtds = data;
struct queue_ent *qe = qtds->qe;
struct member *member = qtds->member;
time_t callstart = qtds->starttime;
int callcompletedinsl = qtds->callcompletedinsl;
struct ast_datastore *datastore;
ast_queue_log(qe->parent->name, ast_channel_uniqueid(qe->chan), member->membername, "TRANSFER", "%s|%s|%ld|%ld|%d",
ast_channel_exten(new_chan), ast_channel_context(new_chan), (long) (callstart - qe->start),
(long) (time(NULL) - callstart), qe->opos);
update_queue(qe->parent, member, callcompletedinsl, (time(NULL) - callstart));
/* No need to lock the channels because they are already locked in ast_do_masquerade */
if ((datastore = ast_channel_datastore_find(old_chan, &queue_transfer_info, NULL))) {
ast_channel_datastore_remove(old_chan, datastore);
ast_log(LOG_WARNING, "Can't find the queue_transfer datastore.\n");
}
}
/*! \brief mechanism to tell if a queue caller was atxferred by a queue member.
*
* When a caller is atxferred, then the queue_transfer_info datastore
* is removed from the channel. If it's still there after the bridge is
* broken, then the caller was not atxferred.
*
* \note Only call this with chan locked
*/
static int attended_transfer_occurred(struct ast_channel *chan)
{
return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1;
}
/*! \brief create a datastore for storing relevant info to log attended transfers in the queue_log
*/
static struct ast_datastore *setup_transfer_datastore(struct queue_ent *qe, struct member *member, time_t starttime, int callcompletedinsl)
{
struct ast_datastore *ds;
struct queue_transfer_ds *qtds = ast_calloc(1, sizeof(*qtds));
if (!qtds) {
ast_log(LOG_WARNING, "Memory allocation error!\n");
ast_channel_lock(qe->chan);
Kevin P. Fleming
committed
if (!(ds = ast_datastore_alloc(&queue_transfer_info, NULL))) {
ast_channel_unlock(qe->chan);
ast_log(LOG_WARNING, "Unable to create transfer datastore. queue_log will not show attended transfer\n");
qtds->qe = qe;
/* This member is refcounted in try_calling, so no need to add it here, too */
qtds->member = member;
qtds->starttime = starttime;
qtds->callcompletedinsl = callcompletedinsl;
ds->data = qtds;
ast_channel_datastore_add(qe->chan, ds);
ast_channel_unlock(qe->chan);
Mark Michelson
committed
struct queue_end_bridge {
struct call_queue *q;
struct ast_channel *chan;
};
static void end_bridge_callback_data_fixup(struct ast_bridge_config *bconfig, struct ast_channel *originator, struct ast_channel *terminator)
{
struct queue_end_bridge *qeb = bconfig->end_bridge_callback_data;
ao2_ref(qeb, +1);
qeb->chan = originator;
}
static void end_bridge_callback(void *data)
{
Mark Michelson
committed
struct queue_end_bridge *qeb = data;
struct call_queue *q = qeb->q;
struct ast_channel *chan = qeb->chan;
Mark Michelson
committed
if (ao2_ref(qeb, -1) == 1) {
set_queue_variables(q, chan);
/* This unrefs the reference we made in try_calling when we allocated qeb */
Tilghman Lesher
committed
queue_t_unref(q, "Expire bridge_config reference");
Mark Michelson
committed
}
Jonathan Rose
committed
/*!
* \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()
* 2. Iterate trough the members of the queue, creating a callattempt corresponding to each member. During this
* iteration, we also check the dialed_interfaces datastore to see if we have already attempted calling this
* member. If we have, we do not create a callattempt. This is in place to prevent call forwarding loops. Also
* during each iteration, we call calc_metric to determine which members should be rung when.
* 3. Call ring_one to place a call to the appropriate member(s)
* 4. Call wait_for_answer to wait for an answer. If no one answers, return.
* 5. Take care of any holdtime announcements, member delays, or other options which occur after a call has been answered.
* 6. Start the monitor or mixmonitor if the option is set
* 7. Remove the caller from the queue to allow other callers to advance
* 8. Bridge the call.
* 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
Jonathan Rose
committed
* \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
* \param[out] noption set if the call to Queue() has the 'n' option set.
* \param[in] agi the agi passed as the fifth parameter to the Queue() application
* \param[in] macro the macro passed as the sixth parameter to the Queue() application
* \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
*/
Jonathan Rose
committed
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 callattempt *outgoing = NULL; /* the list of calls we are building */
Jason Parker
committed
int to, orig;
char oldexten[AST_MAX_EXTENSION]="";
char oldcontext[AST_MAX_CONTEXT]="";
char interfacevar[256]="";
struct ast_channel *which;
struct callattempt *lpeer;
struct member *member;
struct ast_app *application;
int numbusies = 0;
int x=0;
char digit = 0;
Russell Bryant
committed
time_t now = time(NULL);
Mark Spencer
committed
struct ast_bridge_config bridge_config;
char nondataquality = 1;
char *agiexec = NULL;
char *macroexec = NULL;
Steve Murphy
committed
char *gosubexec = NULL;
const char *monitorfilename;
const char *monitor_exec;
const char *monitor_options;
char tmpid[256], tmpid2[256];
char meid[1024], meid2[1024];
char mixmonargs[1512];
struct ast_app *mixmonapp = NULL;
char *p;
char vars[2048];
int forwardsallowed = 1;
int block_connected_line = 0;
struct ast_datastore *datastore, *transfer_ds;
Mark Michelson
committed
struct queue_end_bridge *queue_end_bridge = NULL;
ast_channel_lock(qe->chan);
datastore = ast_channel_datastore_find(qe->chan, &dialed_interface_info, NULL);
ast_channel_unlock(qe->chan);
Mark Spencer
committed
memset(&bridge_config, 0, sizeof(bridge_config));
Steve Murphy
committed
tmpid[0] = 0;
meid[0] = 0;
/* If we've already exceeded our timeout, then just stop
* This should be extremely rare. queue_exec will take care
* of removing the caller and reporting the timeout as the reason.
*/
if (qe->expire && now >= qe->expire) {
res = 0;
goto out;
}
Jonathan Rose
committed
4928
4929
4930
4931
4932
4933
4934
4935
4936
4937
4938
4939
4940
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
4958
4959
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) {
Jonathan Rose
committed
(*tries)++;
Jonathan Rose
committed
*tries = ao2_container_count(qe->parent->members);
Jonathan Rose
committed
*noption = 1;
}
if (ast_test_flag(&opts, OPT_IGNORE_CALL_FW)) {
forwardsallowed = 0;
}
if (ast_test_flag(&opts, OPT_IGNORE_CONNECTEDLINE)) {
block_connected_line = 1;
Jonathan Rose
committed
}
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;
}
Mark Spencer
committed
/* if the calling channel has AST_CAUSE_ANSWERED_ELSEWHERE set, make sure this is inherited.
Olle Johansson
committed
(this is mainly to support chan_local)
*/
if (ast_channel_hangupcause(qe->chan) == AST_CAUSE_ANSWERED_ELSEWHERE) {
Olle Johansson
committed
qe->cancel_answered_elsewhere = 1;
}
Mark Michelson
committed
ao2_lock(qe->parent);
ast_debug(1, "%s is trying to call a queue member.\n",
ast_channel_name(qe->chan));
Kevin P. Fleming
committed
ast_copy_string(queuename, qe->parent->name, sizeof(queuename));
}
if (!ast_strlen_zero(announceoverride)) {