Newer
Older
/* Check if queue is defined in realtime. */
if (!queue_vars) {
/* Delete queue from in-core list if it has been deleted in realtime. */
if (q) {
/*! \note Hmm, can't seem to distinguish a DB failure from a not
found condition... So we might delete an in-core queue
in case of DB failure. */
ast_log(LOG_DEBUG, "Queue %s not found in realtime.\n", queuename);
q->dead = 1;
/* Delete if unused (else will be deleted when last caller leaves). */
if (!q->count) {
/* Delete. */
ast_mutex_unlock(&q->lock);
Russell Bryant
committed
destroy_queue(q);
} else
ast_mutex_unlock(&q->lock);
}
return NULL;
}
/* Create a new queue if an in-core entry does not exist yet. */
if (!q) {
BJ Weschke
committed
if (!(q = alloc_queue(queuename)))
return NULL;
ast_mutex_lock(&q->lock);
clear_queue(q);
q->realtime = 1;
}
init_queue(q); /* Ensure defaults for all parameters not set explicitly. */
memset(tmpbuf, 0, sizeof(tmpbuf));
for (v = queue_vars; v; v = v->next) {
/* Convert to dashes `-' from underscores `_' as the latter are more SQL friendly. */
if ((tmp = strchr(v->name, '_'))) {
ast_copy_string(tmpbuf, v->name, sizeof(tmpbuf));
tmp_name = tmpbuf;
tmp = tmp_name;
while ((tmp = strchr(tmp, '_')))
*tmp++ = '-';
} else
tmp_name = v->name;
queue_set_param(q, tmp_name, v->value, -1, 0);
}
Kevin P. Fleming
committed
if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN)
rr_dep_warning();
/* Temporarily set non-dynamic members dead so we can detect deleted ones. */
for (m = q->members; m; m = m->next) {
if (!m->dynamic)
m->dead = 1;
}
Joshua Colp
committed
while ((interface = ast_category_browse(member_config, interface)))
rt_handle_member_record(q, interface, ast_variable_retrieve(member_config, interface, "penalty"));
/* Delete all realtime members that have been deleted in DB. */
m = q->members;
prev_m = NULL;
while (m) {
next_m = m->next;
if (m->dead) {
if (prev_m) {
prev_m->next = next_m;
} else {
q->members = next_m;
}
BJ Weschke
committed
remove_from_interfaces(m->interface);
free(m);
} else {
prev_m = m;
}
m = next_m;
}
Tilghman Lesher
committed
ast_mutex_unlock(&q->lock);
return q;
}
static struct call_queue *load_realtime_queue(char *queuename)
struct ast_variable *queue_vars;
struct ast_config *member_config = NULL;
Tilghman Lesher
committed
/* Find the queue in the in-core list first. */
AST_LIST_LOCK(&queues);
AST_LIST_TRAVERSE(&queues, q, list) {
Tilghman Lesher
committed
if (!strcasecmp(q->name, queuename)) {
break;
}
}
Tilghman Lesher
committed
Tilghman Lesher
committed
/*! \note Load from realtime before taking the global qlock, to avoid blocking all
queue operations while waiting for the DB.
This will be two separate database transactions, so we might
see queue parameters as they were before another process
changed the queue and member list as it was after the change.
Thus we might see an empty member list when a queue is
deleted. In practise, this is unlikely to cause a problem. */
queue_vars = ast_load_realtime("queues", "name", queuename, NULL);
if (queue_vars) {
member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", queuename, NULL);
if (!member_config) {
ast_log(LOG_ERROR, "no queue_members defined in your config (extconfig.conf).\n");
return NULL;
}
}
Tilghman Lesher
committed
q = find_queue_by_name_rt(queuename, queue_vars, member_config);
if (member_config)
ast_config_destroy(member_config);
if (queue_vars)
ast_variables_destroy(queue_vars);
Tilghman Lesher
committed
}
return q;
}
static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason)
{
struct queue_ent *cur, *prev = NULL;
int res = -1;
int pos = 0;
int inserted = 0;
enum queue_member_status stat;
if (!(q = load_realtime_queue(queuename)))
Tilghman Lesher
committed
return res;
Tilghman Lesher
committed
ast_mutex_lock(&q->lock);
/* This is our one */
Kevin P. Fleming
committed
stat = get_member_status(q, qe->max_penalty);
if (!q->joinempty && (stat == QUEUE_NO_MEMBERS))
*reason = QUEUE_JOINEMPTY;
else if ((q->joinempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS))
*reason = QUEUE_JOINUNAVAIL;
else if (q->maxlen && (q->count >= q->maxlen))
*reason = QUEUE_FULL;
else {
/* There's space for us, put us at the right position inside
* Take into account the priority of the calling user */
inserted = 0;
prev = NULL;
cur = q->head;
/* We have higher priority than the current user, enter
* before him, after all the other users with priority
* higher or equal to our priority. */
if ((!inserted) && (qe->prio > cur->prio)) {
insert_entry(q, prev, qe, &pos);
inserted = 1;
}
cur->pos = ++pos;
prev = cur;
cur = cur->next;
}
/* No luck, join at the end of the queue */
if (!inserted)
insert_entry(q, prev, qe, &pos);
ast_copy_string(qe->moh, q->moh, sizeof(qe->moh));
ast_copy_string(qe->announce, q->announce, sizeof(qe->announce));
ast_copy_string(qe->context, q->context, sizeof(qe->context));
q->count++;
res = 0;
manager_event(EVENT_FLAG_CALL, "Join",
"Channel: %s\r\nCallerID: %s\r\nCallerIDName: %s\r\nQueue: %s\r\nPosition: %d\r\nCount: %d\r\nUniqueid: %s\r\n",
qe->chan->name,
S_OR(qe->chan->cid.cid_num, "unknown"), /* XXX somewhere else it is <unknown> */
S_OR(qe->chan->cid.cid_name, "unknown"),
q->name, qe->pos, q->count, qe->chan->uniqueid );
BJ Weschke
committed
if (option_debug)
ast_log(LOG_DEBUG, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos );
ast_mutex_unlock(&q->lock);
static int play_file(struct ast_channel *chan, char *filename)
{
int res;
ast_stopstream(chan);
res = ast_streamfile(chan, filename, chan->language);
if (!res)
Kevin P. Fleming
committed
res = ast_waitstream(chan, AST_DIGIT_ANY);
ast_stopstream(chan);
return res;
}
Kevin P. Fleming
committed
static int valid_exit(struct queue_ent *qe, char digit)
{
Kevin P. Fleming
committed
int digitlen = strlen(qe->digits);
Kevin P. Fleming
committed
Kevin P. Fleming
committed
/* Prevent possible buffer overflow */
if (digitlen < sizeof(qe->digits) - 2) {
qe->digits[digitlen] = digit;
qe->digits[digitlen + 1] = '\0';
} else {
qe->digits[0] = '\0';
return 0;
}
Kevin P. Fleming
committed
if (ast_strlen_zero(qe->context))
return 0;
Kevin P. Fleming
committed
/* If the extension is bad, then reset the digits to blank */
if (!ast_canmatch_extension(qe->chan, qe->context, qe->digits, 1, qe->chan->cid.cid_num)) {
qe->digits[0] = '\0';
return 0;
}
/* We have an exact match */
if (!ast_goto_if_exists(qe->chan, qe->context, qe->digits, 1)) {
/* Return 1 on a successful goto */
Kevin P. Fleming
committed
return 1;
}
Kevin P. Fleming
committed
return 0;
}
static int say_position(struct queue_ent *qe)
{
int res = 0, avgholdmins, avgholdsecs;
time_t now;
/* Check to see if this is ludicrous -- if we just announced position, don't do it again*/
time(&now);
if ((now - qe->last_pos) < 15)
Kevin P. Fleming
committed
return 0;
/* If either our position has changed, or we are over the freq timer, say position */
if ((qe->last_pos_said == qe->pos) && ((now - qe->last_pos) < qe->parent->announcefrequency))
Kevin P. Fleming
committed
return 0;
ast_moh_stop(qe->chan);
/* Say we're next, if we are */
if (qe->pos == 1) {
Kevin P. Fleming
committed
res = play_file(qe->chan, qe->parent->sound_next);
if (res && valid_exit(qe, res))
goto playout;
else
goto posout;
Kevin P. Fleming
committed
res = play_file(qe->chan, qe->parent->sound_thereare);
if (res && valid_exit(qe, res))
Kevin P. Fleming
committed
goto playout;
res = ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, qe->chan->language, (char *) NULL); /* Needs gender */
if (res && valid_exit(qe, res))
goto playout;
res = play_file(qe->chan, qe->parent->sound_calls);
if (res && valid_exit(qe, res))
goto playout;
}
/* Round hold time to nearest minute */
avgholdmins = abs(((qe->parent->holdtime + 30) - (now - qe->start)) / 60);
/* If they have specified a rounding then round the seconds as well */
if (qe->parent->roundingseconds) {
avgholdsecs = (abs(((qe->parent->holdtime + 30) - (now - qe->start))) - 60 * avgholdmins) / qe->parent->roundingseconds;
avgholdsecs *= qe->parent->roundingseconds;
ast_verbose(VERBOSE_PREFIX_3 "Hold time for %s is %d minutes %d seconds\n", qe->parent->name, avgholdmins, avgholdsecs);
/* If the hold time is >1 min, if it's enabled, and if it's not
supposed to be only once and we have already said it, say it */
Mark Spencer
committed
if ((avgholdmins+avgholdsecs) > 0 && (qe->parent->announceholdtime) &&
(!(qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE) && qe->last_pos)) {
Kevin P. Fleming
committed
res = play_file(qe->chan, qe->parent->sound_holdtime);
if (res && valid_exit(qe, res))
goto playout;
Mark Spencer
committed
if (avgholdmins < 2) {
Kevin P. Fleming
committed
res = play_file(qe->chan, qe->parent->sound_lessthan);
if (res && valid_exit(qe, res))
goto playout;
res = ast_say_number(qe->chan, 2, AST_DIGIT_ANY, qe->chan->language, NULL);
Kevin P. Fleming
committed
if (res && valid_exit(qe, res))
goto playout;
} else {
res = ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language, NULL);
Kevin P. Fleming
committed
if (res && valid_exit(qe, res))
goto playout;
}
res = play_file(qe->chan, qe->parent->sound_minutes);
if (res && valid_exit(qe, res))
goto playout;
Kevin P. Fleming
committed
if (avgholdsecs>0) {
res = ast_say_number(qe->chan, avgholdsecs, AST_DIGIT_ANY, qe->chan->language, NULL);
Kevin P. Fleming
committed
if (res && valid_exit(qe, res))
goto playout;
res = play_file(qe->chan, qe->parent->sound_seconds);
if (res && valid_exit(qe, res))
goto playout;
Kevin P. Fleming
committed
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Told %s in %s their queue position (which was %d)\n",
Kevin P. Fleming
committed
res = play_file(qe->chan, qe->parent->sound_thanks);
if (res && !valid_exit(qe, res))
res = 0;
Kevin P. Fleming
committed
/* Don't restart music on hold if we're about to exit the caller from the queue */
Kevin P. Fleming
committed
ast_moh_start(qe->chan, qe->moh, NULL);
Kevin P. Fleming
committed
return res;
}
static void recalc_holdtime(struct queue_ent *qe)
{
int oldvalue, newvalue;
/* Calculate holdtime using a recursive boxcar filter */
/* Thanks to SRT for this contribution */
/* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */
newvalue = time(NULL) - qe->start;
ast_mutex_lock(&qe->parent->lock);
if (newvalue <= qe->parent->servicelevel)
oldvalue = qe->parent->holdtime;
qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newvalue) >> 2;
ast_mutex_unlock(&qe->parent->lock);
}
static void leave_queue(struct queue_ent *qe)
{
struct queue_ent *cur, *prev = NULL;
int pos = 0;
Mark Spencer
committed
ast_mutex_lock(&q->lock);
for (cur = q->head; cur; cur = cur->next) {
/* Take us out of the queue */
manager_event(EVENT_FLAG_CALL, "Leave",
"Channel: %s\r\nQueue: %s\r\nCount: %d\r\nUniqueid: %s\r\n",
qe->chan->name, q->name, q->count, qe->chan->uniqueid);
if (option_debug)
ast_log(LOG_DEBUG, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name );
/* Take us out of the queue */
if (prev)
prev->next = cur->next;
else
q->head = cur->next;
} else {
/* Renumber the people after us in the queue based on a new count */
ast_mutex_unlock(&q->lock);
Mark Spencer
committed
if (q->dead && !q->count) {
Russell Bryant
committed
AST_LIST_LOCK(&queues);
AST_LIST_REMOVE(&queues, q, list);
AST_LIST_UNLOCK(&queues);
Mark Spencer
committed
/* Hang up a list of outgoing calls */
static void hangupcalls(struct callattempt *outgoing, struct ast_channel *exception)
struct callattempt *oo;
Mark Spencer
committed
if (outgoing->chan && (outgoing->chan != exception))
outgoing = outgoing->q_next;
static int update_status(struct call_queue *q, struct member *member, int status)
Mark Spencer
committed
{
struct member *cur;
Mark Spencer
committed
Mark Spencer
committed
/* Since a reload could have taken place, we have to traverse the list to
be sure it's still valid */
ast_mutex_lock(&q->lock);
for (cur = q->members; cur; cur = cur->next) {
if (member != cur)
continue;
cur->status = status;
if (!q->maskmemberstatus) {
manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus",
"Queue: %s\r\n"
"Location: %s\r\n"
"Membership: %s\r\n"
"Penalty: %d\r\n"
"CallsTaken: %d\r\n"
"LastCall: %d\r\n"
"Status: %d\r\n"
"Paused: %d\r\n",
q->name, cur->interface, cur->dynamic ? "dynamic" : "static",
cur->penalty, cur->calls, (int)cur->lastcall, cur->status, cur->paused);
Mark Spencer
committed
}
}
ast_mutex_unlock(&q->lock);
return 0;
}
static int update_dial_status(struct call_queue *q, struct member *member, int status)
{
if (status == AST_CAUSE_BUSY)
status = AST_DEVICE_BUSY;
else if (status == AST_CAUSE_UNREGISTERED)
status = AST_DEVICE_UNAVAILABLE;
else if (status == AST_CAUSE_NOSUCHDRIVER)
status = AST_DEVICE_INVALID;
else
status = AST_DEVICE_UNKNOWN;
return update_status(q, member, status);
}
/* 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
{
/* &qlock and &rq->lock already set by try_calling()
* to solve deadlock */
if (q == rq) /* don't check myself, could deadlock */
ast_mutex_lock(&q->lock);
if (q->count && q->members) {
for (mem = q->members; mem; mem = mem->next) {
if (strcmp(mem->interface, member->interface))
continue;
ast_log(LOG_DEBUG, "Found matching member %s in queue '%s'\n", mem->interface, q->name);
if (q->weight > rq->weight) {
ast_log(LOG_DEBUG, "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);
found = 1;
break;
}
}
}
ast_mutex_unlock(&q->lock);
break;
}
return found;
}
/*! \brief common hangup actions */
static void do_hang(struct callattempt *o)
{
o->stillgoing = 0;
ast_hangup(o->chan);
o->chan = NULL;
}
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
static char *vars2manager(struct ast_channel *chan, char *vars, size_t len)
{
char *tmp = alloca(len);
if (pbx_builtin_serialize_variables(chan, tmp, len)) {
int i, j;
/* convert "\n" to "\nVariable: " */
strcpy(vars, "Variable: ");
for (i = 0, j = 10; (i < len - 1) && (j < len - 1); i++, j++) {
vars[j] = tmp[i];
if (tmp[i + 1] == '\0')
break;
if (tmp[i] == '\n') {
vars[j] = '\r';
vars[++j] = '\n';
ast_copy_string(&(vars[j]), "Variable: ", len - j);
j += 9;
}
}
if (j > len - 1)
j = len - 1;
vars[j - 2] = '\r';
vars[j - 1] = '\n';
vars[j] = '\0';
} else {
/* there are no channel variables; leave it blank */
*vars = '\0';
}
return vars;
}
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;
/* on entry here, we know that tmp->chan == NULL */
if (qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime)) {
if (option_debug)
ast_log(LOG_DEBUG, "Wrapuptime not yet expired for %s\n", tmp->interface);
if (qe->chan->cdr)
ast_cdr_busy(qe->chan->cdr);
tmp->stillgoing = 0;
(*busies)++;
Kevin P. Fleming
committed
if (!qe->parent->ringinuse && (tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) {
Kevin P. Fleming
committed
if (option_debug)
ast_log(LOG_DEBUG, "%s in use, can't receive call\n", tmp->interface);
if (qe->chan->cdr)
ast_cdr_busy(qe->chan->cdr);
tmp->stillgoing = 0;
return 0;
}
if (tmp->member->paused) {
if (option_debug)
ast_log(LOG_DEBUG, "%s paused, can't receive call\n", tmp->interface);
if (qe->chan->cdr)
ast_cdr_busy(qe->chan->cdr);
tmp->stillgoing = 0;
return 0;
}
if (use_weight && compare_weight(qe->parent,tmp->member)) {
ast_log(LOG_DEBUG, "Priority queue delaying call to %s:%s\n", qe->parent->name, tmp->interface);
if (qe->chan->cdr)
ast_cdr_busy(qe->chan->cdr);
tmp->stillgoing = 0;
(*busies)++;
return 0;
}
Kevin P. Fleming
committed
ast_copy_string(tech, tmp->interface, sizeof(tech));
if ((location = strchr(tech, '/')))
*location++ = '\0';
else
location = "";
/* Request the peer */
tmp->chan = ast_request(tech, qe->chan->nativeformats, location, &status);
if (!tmp->chan) { /* If we can't, just go on to the next call */
if (qe->chan->cdr)
ast_cdr_busy(qe->chan->cdr);
tmp->stillgoing = 0;
update_dial_status(qe->parent, tmp->member, status);
ast_mutex_lock(&qe->parent->lock);
qe->parent->rrpos++;
ast_mutex_unlock(&qe->parent->lock);
(*busies)++;
return 0;
update_dial_status(qe->parent, tmp->member, status);
Mark Spencer
committed
tmp->chan->appl = "AppQueue";
tmp->chan->data = "(Outgoing Line)";
tmp->chan->whentohangup = 0;
if (tmp->chan->cid.cid_num)
free(tmp->chan->cid.cid_num);
tmp->chan->cid.cid_num = ast_strdup(qe->chan->cid.cid_num);
if (tmp->chan->cid.cid_name)
free(tmp->chan->cid.cid_name);
tmp->chan->cid.cid_name = ast_strdup(qe->chan->cid.cid_name);
if (tmp->chan->cid.cid_ani)
free(tmp->chan->cid.cid_ani);
tmp->chan->cid.cid_ani = ast_strdup(qe->chan->cid.cid_ani);
/* Inherit specially named variables from parent channel */
ast_channel_inherit_variables(qe->chan, tmp->chan);
/* Presense of ADSI CPE on outgoing channel follows ours */
tmp->chan->adsicpe = qe->chan->adsicpe;
/* 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 */
if (option_debug)
ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res);
else if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", tmp->interface);
do_hang(tmp);
(*busies)++;
return 0;
} else if (qe->parent->eventwhencalled) {
char vars[2048];
manager_event(EVENT_FLAG_AGENT, "AgentCalled",
"AgentCalled: %s\r\n"
"ChannelCalling: %s\r\n"
"CallerID: %s\r\n"
"CallerIDName: %s\r\n"
"Context: %s\r\n"
"Extension: %s\r\n"
"Priority: %d\r\n"
"%s",
tmp->interface, qe->chan->name,
tmp->chan->cid.cid_num ? tmp->chan->cid.cid_num : "unknown",
tmp->chan->cid.cid_name ? tmp->chan->cid.cid_name : "unknown",
qe->chan->context, qe->chan->exten, qe->chan->priority,
qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", tmp->interface);
}
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;
}
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) {
if (option_debug)
ast_log(LOG_DEBUG, "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) {
if (option_debug)
ast_log(LOG_DEBUG, "(Parallel) Trying '%s' with metric %d\n", cur->interface, cur->metric);
ring_entry(qe, cur, busies);
}
}
} else {
/* Ring just the best channel */
if (option_debug)
ast_log(LOG_DEBUG, "Trying '%s' with metric %d\n", best->interface, best->metric);
ring_entry(qe, best, busies);
}
if (best->chan) /* break out with result = 1 */
ret = 1;
}
return ret;
}
static int store_next(struct queue_ent *qe, struct callattempt *outgoing)
struct callattempt *best = find_best(outgoing);
Mark Spencer
committed
if (best) {
/* Ring just the best channel */
if (option_debug)
ast_log(LOG_DEBUG, "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;
}
static int background_file(struct queue_ent *qe, struct ast_channel *chan, char *filename)
{
int res;
ast_stopstream(chan);
res = ast_streamfile(chan, filename, chan->language);
if (!res) {
/* Wait for a keypress */
res = ast_waitstream(chan, AST_DIGIT_ANY);
if (res < 0 || !valid_exit(qe, res))
res = 0;
/* Stop playback */
ast_stopstream(chan);
}
return res;
}
static int say_periodic_announcement(struct queue_ent *qe)
{
int res = 0;
time_t now;
/* Get the current time */
time(&now);
/* Check to see if it is time to announce */
Kevin P. Fleming
committed
if ((now - qe->last_periodic_announce_time) < qe->parent->periodicannouncefrequency)
return 0;
/* Stop the music on hold so we can play our own file */
ast_moh_stop(qe->chan);
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Playing periodic announcement\n");
/* Check to make sure we have a sound file. If not, reset to the first sound file */
if (qe->last_periodic_announce_sound >= MAX_PERIODIC_ANNOUNCEMENTS || !strlen(qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound])) {
qe->last_periodic_announce_sound = 0;
}
/* play the announcement */
res = background_file(qe, qe->chan, qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]);
/* Resume Music on Hold if the caller is going to stay in the queue */
if (!res)
Kevin P. Fleming
committed
ast_moh_start(qe->chan, qe->moh, NULL);
/* update last_periodic_announce_time */
qe->last_periodic_announce_time = now;
/* Update the current periodic announcement to the next announcement */
qe->last_periodic_announce_sound++;
Kevin P. Fleming
committed
return res;
}
static void record_abandoned(struct queue_ent *qe)
{
ast_mutex_lock(&qe->parent->lock);
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, qe->chan->uniqueid, qe->pos, qe->opos, (int)(time(NULL) - qe->start));
qe->parent->callsabandoned++;
ast_mutex_unlock(&qe->parent->lock);
}
/*! \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 *membername)
{
if (option_verbose > 2)
ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", rnatime);
ast_queue_log(qe->parent->name, qe->chan->uniqueid, membername, "RINGNOANSWER", "%d", rnatime);
if (qe->parent->autopause) {
if (!set_member_paused(qe->parent->name, membername, 1)) {
if (option_verbose > 2)
ast_verbose( VERBOSE_PREFIX_3 "Auto-Pausing Queue Member %s in queue %s since they failed to answer.\n", membername, qe->parent->name);
} else {
if (option_verbose > 2)
ast_verbose( VERBOSE_PREFIX_3 "Failed to pause Queue Member %s in queue %s!\n", membername, qe->parent->name);
}
}
Mark Spencer
committed
#define AST_MAX_WATCHERS 256
static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callattempt *outgoing, int *to, char *digit, int prebusies, int caller_disconnect)
{
char *queue = qe->parent->name;
struct callattempt *o;
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[256] = "";
long starttime = 0;
long endtime = 0;
starttime = (long) time(NULL);
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 (o->chan)
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_log(LOG_DEBUG, "Everyone is busy at this time\n");
} else {
ast_log(LOG_NOTICE, "No one is answering queue '%s' (%d/%d/%d)\n", queue, numlines, numbusies, numnochan);
}
*to = 0;
return NULL;
}
winner = ast_waitfor_n(watchers, pos, to);
for (o = outgoing; o; o = o->q_next) {
if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) {
if (!peer) {
if (option_verbose > 2)
ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
peer = o;
ast_copy_string(on, o->member->interface, sizeof(on));
if (!ast_strlen_zero(o->chan->call_forward)) {
char *stuff;
char *tech;
Kevin P. Fleming
committed
ast_copy_string(tmpchan, o->chan->call_forward, sizeof(tmpchan));
if ((stuff = strchr(tmpchan, '/'))) {
*stuff++ = '\0';
tech = tmpchan;
} else {
snprintf(tmpchan, sizeof(tmpchan), "%s@%s", o->chan->call_forward, o->chan->context);
stuff = tmpchan;
tech = "Local";
}
/* Before processing channel, go ahead and check for forwarding */
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Now forwarding %s to '%s/%s' (thanks to %s)\n", in->name, tech, stuff, o->chan->name);
/* Setup parameters */
Mark Spencer
committed
o->chan = ast_request(tech, in->nativeformats, stuff, &status);
update_dial_status(qe->parent, o->member, status);
if (!o->chan) {
ast_log(LOG_NOTICE, "Unable to create local channel for call forward to '%s/%s'\n", tech, stuff);
o->stillgoing = 0;
numnochan++;
} else {
ast_channel_inherit_variables(in, o->chan);
if (o->chan->cid.cid_num)
free(o->chan->cid.cid_num);
o->chan->cid.cid_num = ast_strdup(in->cid.cid_num);
if (o->chan->cid.cid_name)
free(o->chan->cid.cid_name);
o->chan->cid.cid_name = ast_strdup(in->cid.cid_name);
ast_string_field_set(o->chan, accountcode, in->accountcode);
o->chan->cdrflags = in->cdrflags;
if (in->cid.cid_ani) {
if (o->chan->cid.cid_ani)
free(o->chan->cid.cid_ani);
o->chan->cid.cid_ani = ast_strdup(in->cid.cid_ani);
}
free(o->chan->cid.cid_rdnis);
o->chan->cid.cid_rdnis = ast_strdup(S_OR(in->macroexten, in->exten));
if (ast_call(o->chan, tmpchan, 0)) {
ast_log(LOG_NOTICE, "Failed to dial on local channel for call forward to '%s'\n", tmpchan);
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) {
/* This is our guy if someone answered. */
if (!peer) {
if (option_verbose > 2)
ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
peer = o;
}
break;
case AST_CONTROL_BUSY:
if (option_verbose > 2)
ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", o->chan->name);
if (in->cdr)
ast_cdr_busy(in->cdr);
do_hang(o);
endtime = (long)time(NULL);
endtime -= starttime;
rna(endtime*1000, qe, on);
Russell Bryant
committed
if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
Mark Spencer
committed
if (qe->parent->timeoutrestart)
ring_one(qe, outgoing, &numbusies);
numbusies++;
break;
case AST_CONTROL_CONGESTION:
if (option_verbose > 2)
ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", o->chan->name);
if (in->cdr)
ast_cdr_busy(in->cdr);
endtime = (long)time(NULL);
endtime -= starttime;
rna(endtime*1000, qe, on);
do_hang(o);
Russell Bryant
committed
if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
Mark Spencer
committed
if (qe->parent->timeoutrestart)
ring_one(qe, outgoing, &numbusies);
numbusies++;
break;
case AST_CONTROL_RINGING:
if (option_verbose > 2)
ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name);
if (!sentringing) {
#if 0