Newer
Older
q->dead = 1;
/* Delete if unused (else will be deleted when last caller leaves). */
Tilghman Lesher
committed
queues_t_unlink(queues, q, "Unused; removing from container");
Mark Michelson
committed
ao2_unlock(q);
Tilghman Lesher
committed
queue_t_unref(q, "Queue is dead; can't return it");
}
return NULL;
}
/* Create a new queue if an in-core entry does not exist yet. */
if (!q) {
Mark Michelson
committed
struct ast_variable *tmpvar = NULL;
return NULL;
Mark Michelson
committed
ao2_lock(q);
clear_queue(q);
q->realtime = 1;
Mark Michelson
committed
/*Before we initialize the queue, we need to set the strategy, so that linear strategy
* will allocate the members properly
*/
for (tmpvar = queue_vars; tmpvar; tmpvar = tmpvar->next) {
Mark Michelson
committed
q->strategy = strat2int(tmpvar->value);
if (q->strategy < 0) {
ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n",
tmpvar->value, q->name);
q->strategy = QUEUE_STRATEGY_RINGALL;
}
break;
}
}
/* We traversed all variables and didn't find a strategy */
Mark Michelson
committed
q->strategy = QUEUE_STRATEGY_RINGALL;
Tilghman Lesher
committed
queues_t_link(queues, q, "Add queue to container");
}
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 (strchr(v->name, '_')) {
ast_copy_string(tmpbuf, v->name, sizeof(tmpbuf));
tmp_name = tmpbuf;
tmp = tmpbuf;
*tmp++ = '-';
tmp_name = v->name;
/* NULL values don't get returned from realtime; blank values should
* still get set. If someone doesn't want a value to be set, they
* should set the realtime column to NULL, not blank. */
queue_set_param(q, tmp_name, v->value, -1, 0);
}
/* Temporarily set realtime members dead so we can detect deleted ones. */
mem_iter = ao2_iterator_init(q->members, 0);
while ((m = ao2_iterator_next(&mem_iter))) {
}
ao2_iterator_destroy(&mem_iter);
while ((interface = ast_category_browse(member_config, interface))) {
rt_handle_member_record(q, interface, member_config);
/* Delete all realtime members that have been deleted in DB. */
mem_iter = ao2_iterator_init(q->members, 0);
while ((m = ao2_iterator_next(&mem_iter))) {
if (m->dead) {
Kinsey Moore
committed
if (ast_strlen_zero(m->membername) || !log_membername_as_agent) {
ast_queue_log(q->name, "REALTIME", m->interface, "REMOVEMEMBER", "%s", "");
} else {
ast_queue_log(q->name, "REALTIME", m->membername, "REMOVEMEMBER", "%s", "");
}
Jonathan Rose
committed
member_remove_from_queue(q, m);
}
}
ao2_iterator_destroy(&mem_iter);
Mark Michelson
committed
ao2_unlock(q);
Tilghman Lesher
committed
return q;
}
Jonathan Rose
committed
/*!
* note */
/*!
* \internal
* \brief Returns reference to the named queue. If the queue is realtime, it will load the queue as well.
* \param queuename - name of the desired queue
*
* \retval the queue
* \retval NULL if it doesn't exist
*/
static struct call_queue *find_load_queue_rt_friendly(const char *queuename)
struct ast_variable *queue_vars;
struct ast_config *member_config = NULL;
struct call_queue *q = NULL, tmpq = {
};
Tilghman Lesher
committed
/* Find the queue in the in-core list first. */
Tilghman Lesher
committed
q = ao2_t_find(queues, &tmpq, OBJ_POINTER, "Look for queue in memory first");
Tilghman Lesher
committed
Mark Michelson
committed
/*! \note Load from realtime before taking the "queues" container lock, to avoid blocking all
Tilghman Lesher
committed
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, SENTINEL);
Tilghman Lesher
committed
if (queue_vars) {
member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", queuename, SENTINEL);
Tilghman Lesher
committed
if (!member_config) {
Matthew Jordan
committed
ast_debug(1, "No queue_members defined in config extconfig.conf\n");
member_config = ast_config_new();
Tilghman Lesher
committed
}
}
if (q) {
prev_weight = q->weight ? 1 : 0;
queue_t_unref(q, "Need to find realtime queue");
Tilghman Lesher
committed
q = find_queue_by_name_rt(queuename, queue_vars, member_config);
ast_config_destroy(member_config);
ast_variables_destroy(queue_vars);
/* update the use_weight value if the queue's has gained or lost a weight */
if (q) {
if (!q->weight && prev_weight) {
ast_atomic_fetchadd_int(&use_weight, -1);
}
if (q->weight && !prev_weight) {
ast_atomic_fetchadd_int(&use_weight, +1);
}
}
/* Other cases will end up with the proper value for use_weight */
} else {
update_realtime_members(q);
Tilghman Lesher
committed
}
return q;
}
static int update_realtime_member_field(struct member *mem, const char *queue_name, const char *field, const char *value)
{
int ret = -1;
return ret;
if ((ast_update_realtime("queue_members", "uniqueid", mem->rt_uniqueid, field, value, SENTINEL)) > 0) {
ret = 0;
static void update_realtime_members(struct call_queue *q)
{
struct ast_config *member_config = NULL;
struct ao2_iterator mem_iter;
if (!(member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", q->name , SENTINEL))) {
/* This queue doesn't have realtime members. If the queue still has any realtime
* members in memory, they need to be removed.
*/
ao2_lock(q);
mem_iter = ao2_iterator_init(q->members, 0);
while ((m = ao2_iterator_next(&mem_iter))) {
if (m->realtime) {
Jonathan Rose
committed
member_remove_from_queue(q, m);
}
ao2_ref(m, -1);
}
ast_debug(3, "Queue %s has no realtime members defined. No need for update\n", q->name);
ao2_unlock(q);
Mark Michelson
committed
ao2_lock(q);
/* Temporarily set realtime members dead so we can detect deleted ones.*/
mem_iter = ao2_iterator_init(q->members, 0);
while ((m = ao2_iterator_next(&mem_iter))) {
ao2_iterator_destroy(&mem_iter);
while ((interface = ast_category_browse(member_config, interface))) {
rt_handle_member_record(q, interface, member_config);
}
/* Delete all realtime members that have been deleted in DB. */
mem_iter = ao2_iterator_init(q->members, 0);
while ((m = ao2_iterator_next(&mem_iter))) {
Kinsey Moore
committed
if (ast_strlen_zero(m->membername) || !log_membername_as_agent) {
ast_queue_log(q->name, "REALTIME", m->interface, "REMOVEMEMBER", "%s", "");
} else {
ast_queue_log(q->name, "REALTIME", m->membername, "REMOVEMEMBER", "%s", "");
}
Jonathan Rose
committed
member_remove_from_queue(q, m);
ao2_iterator_destroy(&mem_iter);
Mark Michelson
committed
ao2_unlock(q);
ast_config_destroy(member_config);
static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason, int position)
Tilghman Lesher
committed
{
struct queue_ent *cur, *prev = NULL;
int res = -1;
int pos = 0;
int inserted = 0;
Jonathan Rose
committed
if (!(q = find_load_queue_rt_friendly(queuename))) {
Tilghman Lesher
committed
return res;
Mark Michelson
committed
ao2_lock(q);
/* This is our one */
if (q->joinempty) {
int status = 0;
if ((status = get_member_status(q, qe->max_penalty, qe->min_penalty, q->joinempty))) {
*reason = QUEUE_JOINEMPTY;
queue_t_unref(q, "Done with realtime queue");
return res;
}
if (*reason == QUEUE_UNKNOWN && q->maxlen && (q->count >= q->maxlen)) {
*reason = QUEUE_FULL;
Jason Parker
committed
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
/* 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;
}
/* <= is necessary for the position comparison because it may not be possible to enter
* at our desired position since higher-priority callers may have taken the position we want
*/
if (!inserted && (qe->prio >= cur->prio) && position && (position <= pos + 1)) {
insert_entry(q, prev, qe, &pos);
inserted = 1;
/*pos is incremented inside insert_entry, so don't need to add 1 here*/
if (position < pos) {
ast_log(LOG_NOTICE, "Asked to be inserted at position %d but forced into position %d due to higher priority callers\n", position, pos);
}
}
cur->pos = ++pos;
prev = cur;
cur = cur->next;
}
/* No luck, join at the end of the queue */
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++;
ast_devstate_changed(AST_DEVICE_RINGING, AST_DEVSTATE_CACHABLE, "Queue:%s", q->name);
res = 0;
Jason Parker
committed
blob = ast_json_pack("{s: s, s: i, s: i}",
"Queue", q->name,
"Position", qe->pos,
"Count", q->count);
ast_channel_publish_blob(qe->chan, queue_caller_join_type(), blob);
ast_debug(1, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, ast_channel_name(qe->chan), qe->pos );
Mark Michelson
committed
ao2_unlock(q);
queue_t_unref(q, "Done with realtime queue");
static int play_file(struct ast_channel *chan, const char *filename)
if (ast_strlen_zero(filename)) {
return 0;
}
if (!ast_fileexists(filename, NULL, ast_channel_language(chan))) {
res = ast_streamfile(chan, filename, ast_channel_language(chan));
Kevin P. Fleming
committed
res = ast_waitstream(chan, AST_DIGIT_ANY);
ast_stopstream(chan);
return res;
}
Mark Michelson
committed
/*!
* \brief Check for valid exit from queue via goto
* \retval 0 if failure
* \retval 1 if successful
*/
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
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,
S_COR(ast_channel_caller(qe->chan)->id.number.valid, ast_channel_caller(qe->chan)->id.number.str, NULL))) {
Kevin P. Fleming
committed
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;
}
Steve Murphy
committed
static int say_position(struct queue_ent *qe, int ringing)
int res = 0, avgholdmins, avgholdsecs, announceposition = 0;
int say_thanks = 1;
Russell Bryant
committed
/* Let minannouncefrequency seconds pass between the start of each position announcement */
if ((now - qe->last_pos) < qe->parent->minannouncefrequency) {
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;
Steve Murphy
committed
if (ringing) {
ast_indicate(qe->chan,-1);
} else {
ast_moh_stop(qe->chan);
}
if (qe->parent->announceposition == ANNOUNCEPOSITION_YES ||
qe->parent->announceposition == ANNOUNCEPOSITION_MORE_THAN ||
(qe->parent->announceposition == ANNOUNCEPOSITION_LIMIT &&
qe->pos <= qe->parent->announcepositionlimit)) {
announceposition = 1;
if (announceposition == 1) {
/* Say we're next, if we are */
if (qe->pos == 1) {
res = play_file(qe->chan, qe->parent->sound_next);
goto playout;
} else {
if (qe->parent->announceposition == ANNOUNCEPOSITION_MORE_THAN && qe->pos > qe->parent->announcepositionlimit){
/* More than Case*/
res = play_file(qe->chan, qe->parent->queue_quantity1);
goto playout;
res = ast_say_number(qe->chan, qe->parent->announcepositionlimit, AST_DIGIT_ANY, ast_channel_language(qe->chan), NULL); /* Needs gender */
goto playout;
} else {
/* Normal Case */
res = play_file(qe->chan, qe->parent->sound_thereare);
goto playout;
res = ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, ast_channel_language(qe->chan), NULL); /* Needs gender */
goto playout;
}
if (qe->parent->announceposition == ANNOUNCEPOSITION_MORE_THAN && qe->pos > qe->parent->announcepositionlimit){
/* More than Case*/
res = play_file(qe->chan, qe->parent->queue_quantity2);
goto playout;
} else {
res = play_file(qe->chan, qe->parent->sound_calls);
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;
Mark Michelson
committed
ast_verb(3, "Hold time for %s is %d minute(s) %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 */
if ((avgholdmins+avgholdsecs) > 0 && qe->parent->announceholdtime &&
((qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE && !qe->last_pos) ||
!(qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE))) {
Kevin P. Fleming
committed
res = play_file(qe->chan, qe->parent->sound_holdtime);
Kevin P. Fleming
committed
goto playout;
Kevin P. Fleming
committed
Mark Michelson
committed
if (avgholdmins >= 1) {
res = ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, ast_channel_language(qe->chan), NULL);
Mark Michelson
committed
goto playout;
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.
Mark Michelson
committed
* 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) {
Jason Parker
committed
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
char posstr[20];
ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Queue:%s", q->name);
Jason Parker
committed
blob = ast_json_pack("{s: s, s: i, s: i}",
"Queue", q->name,
"Position", qe->pos,
"Count", q->count);
ast_channel_publish_blob(qe->chan, queue_caller_leave_type(), blob);
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);
}
static void publish_dial_end_event(struct ast_channel *in, struct callattempt *outgoing, struct ast_channel *exception, const char *status)
{
struct callattempt *cur;
for (cur = outgoing; cur; cur = cur->q_next) {
if (cur->chan && cur->chan != exception) {
ast_channel_publish_dial(in, cur->chan, NULL, status);
Mark Michelson
committed
/*! \brief Hang up a list of outgoing calls */
static void hangupcalls(struct queue_ent *qe, 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);
ast_channel_publish_dial(qe->chan, outgoing->chan, outgoing->interface, "CANCEL");
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))) {
avl += is_member_available(mem);
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;
}
Richard Mudgett
committed
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
/*!
* \internal
* \brief Check if the member status is available.
*
* \param status Member status to check if available.
*
* \retval non-zero if the member status is available.
*/
static int member_status_available(int status)
{
return status == AST_DEVICE_NOT_INUSE || status == AST_DEVICE_UNKNOWN;
}
/*!
* \internal
* \brief Clear the member call pending flag.
*
* \param mem Queue member.
*
* \return Nothing
*/
static void member_call_pending_clear(struct member *mem)
{
ao2_lock(mem);
mem->call_pending = 0;
ao2_unlock(mem);
}
/*!
* \internal
* \brief Set the member call pending flag.
*
* \param mem Queue member.
*
* \retval non-zero if call pending flag was already set.
*/
static int member_call_pending_set(struct member *mem)
{
int old_pending;
ao2_lock(mem);
old_pending = mem->call_pending;
mem->call_pending = 1;
ao2_unlock(mem);
return old_pending;
}
/*!
* \internal
* \brief Determine if can ring a queue entry.
*
* \param qe Queue entry to check.
* \param call Member call attempt.
*
* \retval non-zero if an entry can be called.
*/
static int can_ring_entry(struct queue_ent *qe, struct callattempt *call)
{
if (call->member->paused) {
ast_debug(1, "%s paused, can't receive call\n", call->interface);
return 0;
}
if (!call->member->ringinuse && !member_status_available(call->member->status)) {
ast_debug(1, "%s not available, can't receive call\n", call->interface);
return 0;
}
if ((call->lastqueue && call->lastqueue->wrapuptime && (time(NULL) - call->lastcall < call->lastqueue->wrapuptime))
|| (!call->lastqueue && qe->parent->wrapuptime && (time(NULL) - call->lastcall < qe->parent->wrapuptime))) {
ast_debug(1, "Wrapuptime not yet expired on queue %s for %s\n",
(call->lastqueue ? call->lastqueue->name : qe->parent->name),
call->interface);
return 0;
}
if (use_weight && compare_weight(qe->parent, call->member)) {
ast_debug(1, "Priority queue delaying call to %s:%s\n",
qe->parent->name, call->interface);
return 0;
}
if (!call->member->ringinuse) {
if (member_call_pending_set(call->member)) {
ast_debug(1, "%s has another call pending, can't receive call\n",
call->interface);
return 0;
}
/*
* The queue member is available. Get current status to be sure
* because the device state and extension state callbacks may
* not have updated the status yet.
*/
if (!member_status_available(get_queue_member_status(call->member))) {
ast_debug(1, "%s actually not available, can't receive call\n",
call->interface);
member_call_pending_clear(call->member);
return 0;
}
}
return 1;
}
/*!
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().
Mark Michelson
committed
* 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;
Jason Parker
committed
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
/* on entry here, we know that tmp->chan == NULL */
Richard Mudgett
committed
if (!can_ring_entry(qe, tmp)) {
Richard Mudgett
committed
++*busies;
ast_assert(tmp->member->ringinuse || tmp->member->call_pending);
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 */
Mark Michelson
committed
ao2_lock(qe->parent);
qe->parent->rrpos++;
Mark Michelson
committed
qe->linpos++;
Mark Michelson
committed
ao2_unlock(qe->parent);
Richard Mudgett
committed
member_call_pending_clear(tmp->member);
/* BUGBUG: Raise a BUSY dial end message here */
Richard Mudgett
committed
tmp->stillgoing = 0;
++*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));
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_verb(3, "Couldn't call %s\n", tmp->interface);
do_hang(tmp);
Richard Mudgett
committed
member_call_pending_clear(tmp->member);
++*busies;
return 0;
Richard Mudgett
committed
}
ast_channel_lock_both(tmp->chan, qe->chan);
Jason Parker
committed
blob = ast_json_pack("{s: s, s: s, s: s}",
"Queue", qe->parent->name,
"Interface", tmp->interface,
"MemberName", tmp->member->membername);
queue_publish_multi_channel_blob(qe->chan, tmp->chan, queue_agent_called_type(), blob);
ast_channel_publish_dial(qe->chan, tmp->chan, tmp->interface, NULL);
ast_channel_unlock(tmp->chan);
ast_channel_unlock(qe->chan);
ast_verb(3, "Called %s\n", tmp->interface);
Richard Mudgett
committed
member_call_pending_clear(tmp->member);