Newer
Older
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. */
v = queue_vars;
memset(tmpbuf, 0, sizeof(tmpbuf));
while(v) {
/* Convert to dashes `-' from underscores `_' as the latter are more SQL friendly. */
if((tmp = strchr(v->name, '_')) != NULL) {
ast_copy_string(tmpbuf, v->name, sizeof(tmpbuf));
tmp_name = tmpbuf;
tmp = tmp_name;
while((tmp = strchr(tmp, '_')) != NULL)
*tmp++ = '-';
} else
tmp_name = v->name;
queue_set_param(q, tmp_name, v->value, -1, 0);
v = v->next;
}
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. */
m = q->members;
while (m) {
if (!m->dynamic)
m->dead = 1;
m = m->next;
}
interface = ast_category_browse(member_config, NULL);
while (interface) {
rt_handle_member_record(q, interface, ast_variable_retrieve(member_config, interface, "penalty"));
interface = ast_category_browse(member_config, interface);
}
/* 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;
}
Tilghman Lesher
committed
static struct ast_call_queue *load_realtime_queue(char *queuename)
struct ast_variable *queue_vars = NULL;
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 ast_call_queue *q;
struct queue_ent *cur, *prev = NULL;
int res = -1;
int pos = 0;
int inserted = 0;
enum queue_member_status stat;
Tilghman Lesher
committed
q = load_realtime_queue(queuename);
if (!q)
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);
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
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
* the queue.
* Take into account the priority of the calling user */
inserted = 0;
prev = NULL;
cur = q->head;
while(cur) {
/* 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);
else
res = 0;
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;
}
/* If there's no context to goto, short-circuit */
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;
}
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;
} else {
avgholdsecs=0;
}
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;
if (avgholdmins>0) {
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, (char *)NULL);
if (res && valid_exit(qe, res))
goto playout;
} else {
res = ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language, (char*) NULL);
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, (char*) NULL);
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
posout:
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Told %s in %s their queue position (which was %d)\n",
qe->chan->name, qe->parent->name, qe->pos);
res = play_file(qe->chan, qe->parent->sound_thanks);
if (res && !valid_exit(qe, res))
res = 0;
Kevin P. Fleming
committed
playout:
/* Set our last_pos indicators */
qe->last_pos = now;
qe->last_pos_said = qe->pos;
/* Don't restart music on hold if we're about to exit the caller from the queue */
ast_moh_start(qe->chan, qe->moh);
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 ast_call_queue *q;
struct queue_ent *cur, *prev = NULL;
int pos = 0;
Mark Spencer
committed
ast_mutex_lock(&q->lock);
prev = NULL;
cur = q->head;
while(cur) {
if (cur == qe) {
q->count--;
/* 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 */
cur->pos = ++pos;
prev = cur;
}
cur = cur->next;
}
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
while(outgoing) {
/* Hangup any existing lines we have open */
if (outgoing->chan && (outgoing->chan != exception))
outgoing=outgoing->q_next;
Mark Spencer
committed
static int update_status(struct ast_call_queue *q, struct member *member, int status)
{
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) {
Mark Spencer
committed
if (member == cur) {
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"
"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
break;
}
}
ast_mutex_unlock(&q->lock);
return 0;
}
static int update_dial_status(struct ast_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 */
Mark Spencer
committed
static int compare_weight(struct ast_call_queue *rq, struct member *member)
{
struct ast_call_queue *q;
struct member *mem;
/* &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) {
Russell Bryant
committed
if (!strcmp(mem->interface, member->interface)) {
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);
}
}
}
}
ast_mutex_unlock(&q->lock);
if (found)
break;
}
return found;
}
/*! \brief common hangup actions */
static void do_hang(struct callattempt *o)
{
o->stillgoing = 0;
ast_hangup(o->chan);
o->chan = NULL;
}
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_INUSE)) {
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 0
ast_log(LOG_NOTICE, "Unable to create channel of type '%s' for Queue\n", cur->tech);
#endif
if (qe->chan->cdr)
ast_cdr_busy(qe->chan->cdr);
tmp->stillgoing = 0;
update_dial_status(qe->parent, tmp->member, status);
(*busies)++;
return 0;
Mark Spencer
committed
} else if (status != tmp->oldstatus)
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 */
res = ast_call(tmp->chan, location, 0);
if (res) {
/* 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 {
Mark Spencer
committed
if (qe->parent->eventwhencalled) {
manager_event(EVENT_FLAG_AGENT, "AgentCalled",
"AgentCalled: %s\r\n"
"ChannelCalling: %s\r\n"
"CallerID: %s\r\n"
"Context: %s\r\n"
"Extension: %s\r\n"
"Priority: %d\r\n",
tmp->interface, qe->chan->name,
S_OR(tmp->chan->cid.cid_num, "unknown"),
S_OR(tmp->chan->cid.cid_name, "unknown"),
qe->chan->context, qe->chan->exten, qe->chan->priority);
}
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 */
best = cur;
}
}
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))
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
res = 0;
/* Stop playback */
ast_stopstream(chan);
} else {
res = 0;
}
/*if (res) {
ast_log(LOG_WARNING, "ast_streamfile failed on %s \n", chan->name);
res = 0;
}*/
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)
ast_moh_start(qe->chan, qe->moh);
/* 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);
}
}
return;
}
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 */ ||
Russell Bryant
committed
(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 tmpchan[256]="";
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);
if (status != o->oldstatus)
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 {
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);
}
if (o->chan->cid.cid_rdnis)
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) {
switch(f->subclass) {
/* 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);
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
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
ast_indicate(in, AST_CONTROL_RINGING);
#endif
sentringing++;
}
break;
case AST_CONTROL_OFFHOOK:
/* Ignore going off hook */
break;
default:
ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass);
}
}
ast_frfree(f);
} else {
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);
}
}
}
if (winner == in) {
f = ast_read(in);
#if 0
if (f && (f->frametype != AST_FRAME_VOICE))
printf("Frame type: %d, %d\n", f->frametype, f->subclass);
else if (!f || (f->frametype != AST_FRAME_VOICE))
printf("Hangup received on %s\n", in->name);