dect
/
asterisk
Archived
13
0
Fork 0
This repository has been archived on 2022-02-17. You can view files and clone it, but cannot push or open issues or pull requests.
asterisk/apps/app_queue.c

1508 lines
39 KiB
C
Executable File

/*
* Asterisk -- A telephony toolkit for Linux.
*
* True call queues with optional send URL on answer
*
* Copyright (C) 1999, Mark Spencer
*
* Mark Spencer <markster@linux-support.net>
*
* This program is free software, distributed under the terms of
* the GNU General Public License
*/
#include <asterisk/lock.h>
#include <asterisk/file.h>
#include <asterisk/logger.h>
#include <asterisk/channel.h>
#include <asterisk/pbx.h>
#include <asterisk/options.h>
#include <asterisk/module.h>
#include <asterisk/translate.h>
#include <asterisk/say.h>
#include <asterisk/parking.h>
#include <asterisk/musiconhold.h>
#include <asterisk/cli.h>
#include <asterisk/manager.h> /* JDG */
#include <asterisk/config.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/signal.h>
#include <netinet/in.h>
#include <pthread.h>
#define QUEUE_STRATEGY_RINGALL 0
#define QUEUE_STRATEGY_ROUNDROBIN 1
#define QUEUE_STRATEGY_LEASTRECENT 2
#define QUEUE_STRATEGY_FEWESTCALLS 3
#define QUEUE_STRATEGY_RANDOM 4
static struct strategy {
int strategy;
char *name;
} strategies[] = {
{ QUEUE_STRATEGY_RINGALL, "ringall" },
{ QUEUE_STRATEGY_ROUNDROBIN, "roundrobin" },
{ QUEUE_STRATEGY_LEASTRECENT, "leastrecent" },
{ QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" },
{ QUEUE_STRATEGY_RANDOM, "random" },
};
#define DEFAULT_RETRY 5
#define DEFAULT_TIMEOUT 15
#define RECHECK 1 /* Recheck every second to see we we're at the top yet */
static char *tdesc = "True Call Queueing";
static char *app = "Queue";
static char *synopsis = "Queue a call for a call queue";
static char *descrip =
" Queue(queuename[|options[|URL][|announceoverride]]):\n"
"Queues an incoming call in a particular call queue as defined in queues.conf.\n"
" This application returns -1 if the originating channel hangs up, or if the\n"
"call is bridged and either of the parties in the bridge terminate the call.\n"
"Returns 0 if the queue is full, nonexistant, or has no members.\n"
"The option string may contain zero or more of the following characters:\n"
" 't' -- allow the called user transfer the calling user\n"
" 'T' -- to allow the calling user to transfer the call.\n"
" 'd' -- data-quality (modem) call (minimum delay).\n"
" 'H' -- allow caller to hang up by hitting *.\n"
" In addition to transferring the call, a call may be parked and then picked\n"
"up by another user.\n"
" The optionnal URL will be sent to the called party if the channel supports\n"
"it.\n";
// [PHM 06/26/03]
static char *app_aqm = "AddQueueMember" ;
static char *app_aqm_synopsis = "Dynamically adds queue members" ;
static char *app_aqm_descrip =
" AddQueueMember(queuename[|interface]):\n"
"Dynamically adds interface to an existing queue\n"
"Returns -1 if there is an error.\n"
"Example: AddQueueMember(techsupport|SIP/3000)\n"
"";
static char *app_rqm = "RemoveQueueMember" ;
static char *app_rqm_synopsis = "Dynamically removes queue members" ;
static char *app_rqm_descrip =
" RemoveQueueMember(queuename[|interface]):\n"
"Dynamically removes interface to an existing queue\n"
"Returns -1 if there is an error.\n"
"Example: RemoveQueueMember(techsupport|SIP/3000)\n"
"";
/* We define a customer "local user" structure because we
use it not only for keeping track of what is in use but
also for keeping track of who we're dialing. */
struct localuser {
struct ast_channel *chan;
char numsubst[256];
char tech[40];
int stillgoing;
int metric;
int allowredirect_in;
int allowredirect_out;
int ringbackonly;
int musiconhold;
int dataquality;
int allowdisconnect;
struct member *member;
struct localuser *next;
};
LOCAL_USER_DECL;
struct queue_ent {
struct ast_call_queue *parent; /* What queue is our parent */
char moh[80]; /* Name of musiconhold to be used */
char announce[80]; /* Announcement to play */
char context[80]; /* Context when user exits queue */
int pos; /* Where we are in the queue */
time_t start; /* When we started holding */
struct ast_channel *chan; /* Our channel */
struct queue_ent *next; /* The next queue entry */
};
struct member {
char tech[80]; /* Technology */
char loc[256]; /* Location */
int penalty; /* Are we a last resort? */
int calls;
int dynamic; /* Are we dynamically added? */
time_t lastcall; /* When last successful call was hungup */
struct member *next; /* Next member */
};
struct ast_call_queue {
ast_mutex_t lock;
char name[80]; /* Name of the queue */
char moh[80]; /* Name of musiconhold to be used */
char announce[80]; /* Announcement to play */
char context[80]; /* Announcement to play */
int strategy; /* Queueing strategy */
int announcetimeout; /* How often to announce their position */
int count; /* How many entries are in the queue */
int maxlen; /* Max number of entries in queue */
int dead; /* Whether this queue is dead or not */
int retry; /* Retry calling everyone after this amount of time */
int timeout; /* How long to wait for an answer */
/* Queue strategy things */
int rrpos; /* Round Robin - position */
int wrapped; /* Round Robin - wrapped around? */
struct member *members; /* Member channels to be tried */
struct queue_ent *head; /* Start of the actual queue */
struct ast_call_queue *next; /* Next call queue */
};
static struct ast_call_queue *queues = NULL;
static ast_mutex_t qlock = AST_MUTEX_INITIALIZER;
static char *int2strat(int strategy)
{
int x;
for (x=0;x<sizeof(strategies) / sizeof(strategies[0]);x++) {
if (strategy == strategies[x].strategy)
return strategies[x].name;
}
return "<unknown>";
}
static int strat2int(char *strategy)
{
int x;
for (x=0;x<sizeof(strategies) / sizeof(strategies[0]);x++) {
if (!strcasecmp(strategy, strategies[x].name))
return strategies[x].strategy;
}
return -1;
}
static int join_queue(char *queuename, struct queue_ent *qe)
{
struct ast_call_queue *q;
struct queue_ent *cur, *prev = NULL;
int res = -1;
int pos = 0;
ast_mutex_lock(&qlock);
q = queues;
while(q) {
if (!strcasecmp(q->name, queuename)) {
/* This is our one */
ast_mutex_lock(&q->lock);
if (q->members && (!q->maxlen || (q->count < q->maxlen))) {
/* There's space for us, put us at the end */
prev = NULL;
cur = q->head;
while(cur) {
cur->pos = ++pos;
prev = cur;
cur = cur->next;
}
if (prev)
prev->next = qe;
else
q->head = qe;
/* Fix additional pointers and
information */
qe->next = NULL;
qe->parent = q;
qe->pos = ++pos;
strncpy(qe->moh, q->moh, sizeof(qe->moh));
strncpy(qe->announce, q->announce, sizeof(qe->announce));
strncpy(qe->context, q->context, sizeof(qe->context));
q->count++;
res = 0;
manager_event(EVENT_FLAG_CALL, "Join",
"Channel: %s\r\nCallerID: %s\r\nQueue: %s\r\nPosition: %d\r\nCount: %d\r\n",
qe->chan->name, (qe->chan->callerid ? qe->chan->callerid : "unknown"), q->name, qe->pos, q->count );
#if 0
ast_log(LOG_NOTICE, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos );
#endif
}
ast_mutex_unlock(&q->lock);
break;
}
q = q->next;
}
ast_mutex_unlock(&qlock);
return res;
}
static void free_members(struct ast_call_queue *q, int all)
{
/* Free non-dynamic members */
struct member *curm, *next, *prev;
curm = q->members;
prev = NULL;
while(curm) {
next = curm->next;
if (all || !curm->dynamic) {
if (prev)
prev->next = next;
else
q->members = next;
free(curm);
} else
prev = curm;
curm = next;
}
}
static void destroy_queue(struct ast_call_queue *q)
{
struct ast_call_queue *cur, *prev = NULL;
ast_mutex_lock(&qlock);
cur = queues;
while(cur) {
if (cur == q) {
if (prev)
prev->next = cur->next;
else
queues = cur->next;
} else {
prev = cur;
}
cur = cur->next;
}
ast_mutex_unlock(&qlock);
free_members(q, 1);
free(q);
}
static void leave_queue(struct queue_ent *qe)
{
struct ast_call_queue *q;
struct queue_ent *cur, *prev = NULL;
int pos = 0;
q = qe->parent;
if (!q)
return;
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\n",
qe->chan->name, q->name, q->count);
#if 0
ast_log(LOG_NOTICE, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name );
#endif
/* Take us out of the queue */
if (prev)
prev->next = cur->next;
else
q->head = cur->next;
} else {
cur->pos = ++pos;
prev = cur;
}
cur = cur->next;
}
ast_mutex_unlock(&q->lock);
if (q->dead && !q->count) {
/* It's dead and nobody is in it, so kill it */
destroy_queue(q);
}
}
static void hanguptree(struct localuser *outgoing, struct ast_channel *exception)
{
/* Hang up a tree of stuff */
struct localuser *oo;
while(outgoing) {
/* Hangup any existing lines we have open */
if (outgoing->chan && (outgoing->chan != exception))
ast_hangup(outgoing->chan);
oo = outgoing;
outgoing=outgoing->next;
free(oo);
}
}
static int ring_entry(struct queue_ent *qe, struct localuser *tmp)
{
int res;
/* Request the peer */
tmp->chan = ast_request(tmp->tech, qe->chan->nativeformats, tmp->numsubst);
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'\n", cur->tech);
#endif
if (qe->chan->cdr)
ast_cdr_busy(qe->chan->cdr);
tmp->stillgoing = 0;
return 0;
}
tmp->chan->appl = "AppQueue";
tmp->chan->data = "(Outgoing Line)";
tmp->chan->whentohangup = 0;
if (tmp->chan->callerid)
free(tmp->chan->callerid);
if (tmp->chan->ani)
free(tmp->chan->ani);
if (qe->chan->callerid)
tmp->chan->callerid = strdup(qe->chan->callerid);
else
tmp->chan->callerid = NULL;
if (qe->chan->ani)
tmp->chan->ani = strdup(qe->chan->ani);
else
tmp->chan->ani = NULL;
/* 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, tmp->numsubst, 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->numsubst);
ast_hangup(tmp->chan);
tmp->chan = NULL;
tmp->stillgoing = 0;
return 0;
} else
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", tmp->numsubst);
return 0;
}
static int ring_one(struct queue_ent *qe, struct localuser *outgoing)
{
struct localuser *cur;
struct localuser *best;
int bestmetric=0;
do {
best = NULL;
cur = outgoing;
while(cur) {
if (cur->stillgoing && /* Not already done */
!cur->chan && /* Isn't already going */
(!best || (cur->metric < bestmetric))) { /* We haven't found one yet, or it's better */
bestmetric = cur->metric;
best = cur;
}
cur = cur->next;
}
if (best) {
if (!qe->parent->strategy) {
/* Ring everyone who shares this best metric (for ringall) */
cur = outgoing;
while(cur) {
if (cur->stillgoing && !cur->chan && (cur->metric == bestmetric)) {
ast_log(LOG_DEBUG, "(Parallel) Trying '%s/%s' with metric %d\n", cur->tech, cur->numsubst, cur->metric);
ring_entry(qe, cur);
}
cur = cur->next;
}
} else {
/* Ring just the best channel */
ast_log(LOG_DEBUG, "Trying '%s/%s' with metric %d\n", best->tech, best->numsubst, best->metric);
ring_entry(qe, best);
}
}
} while (best && !best->chan);
if (!best) {
ast_log(LOG_DEBUG, "Nobody left to try ringing in queue\n");
return 0;
}
return 1;
}
static int valid_exit(struct queue_ent *qe, char digit)
{
char tmp[2];
if (!strlen(qe->context))
return 0;
tmp[0] = digit;
tmp[1] = '\0';
if (ast_exists_extension(qe->chan, qe->context, tmp, 1, qe->chan->callerid)) {
strncpy(qe->chan->context, qe->context, sizeof(qe->chan->context) - 1);
strncpy(qe->chan->exten, tmp, sizeof(qe->chan->exten) - 1);
qe->chan->priority = 0;
return 1;
}
return 0;
}
#define MAX 256
static struct localuser *wait_for_answer(struct queue_ent *qe, struct localuser *outgoing, int *to, int *allowredir_in, int *allowredir_out, int *allowdisconnect, char *digit)
{
char *queue = qe->parent->name;
struct localuser *o;
int found;
int numlines;
int sentringing = 0;
int numbusies = 0;
int orig = *to;
struct ast_frame *f;
struct localuser *peer = NULL;
struct ast_channel *watchers[MAX];
int pos;
struct ast_channel *winner;
struct ast_channel *in = qe->chan;
while(*to && !peer) {
o = outgoing;
found = -1;
pos = 1;
numlines = 0;
watchers[0] = in;
while(o) {
/* Keep track of important channels */
if (o->stillgoing && o->chan) {
watchers[pos++] = o->chan;
found = 1;
}
o = o->next;
numlines++;
}
if (found < 0) {
if (numlines == numbusies) {
ast_log(LOG_DEBUG, "Everyone is busy at this time\n");
} else {
ast_log(LOG_NOTICE, "No one is answered queue %s\n", queue);
}
*to = 0;
return NULL;
}
winner = ast_waitfor_n(watchers, pos, to);
o = outgoing;
while(o) {
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;
*allowredir_in = o->allowredirect_in;
*allowredir_out = o->allowredirect_out;
*allowdisconnect = o->allowdisconnect;
}
} else if (o->chan && (o->chan == winner)) {
f = ast_read(winner);
if (f) {
if (f->frametype == AST_FRAME_CONTROL) {
switch(f->subclass) {
case AST_CONTROL_ANSWER:
/* 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;
*allowredir_in = o->allowredirect_in;
*allowredir_out = o->allowredirect_out;
*allowdisconnect = o->allowdisconnect;
}
break;
case AST_CONTROL_BUSY:
if (option_verbose > 2)
ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", o->chan->name);
o->stillgoing = 0;
if (in->cdr)
ast_cdr_busy(in->cdr);
ast_hangup(o->chan);
o->chan = NULL;
if (qe->parent->strategy)
ring_one(qe, outgoing);
numbusies++;
break;
case AST_CONTROL_CONGESTION:
if (option_verbose > 2)
ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", o->chan->name);
o->stillgoing = 0;
if (in->cdr)
ast_cdr_busy(in->cdr);
ast_hangup(o->chan);
o->chan = NULL;
if (qe->parent->strategy)
ring_one(qe, outgoing);
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 {
o->stillgoing = 0;
ast_hangup(o->chan);
o->chan = NULL;
if (qe->parent->strategy)
ring_one(qe, outgoing);
}
}
o = o->next;
}
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);
#endif
if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) {
/* Got hung up */
*to=-1;
return NULL;
}
if (f && (f->frametype == AST_FRAME_DTMF) && allowdisconnect && (f->subclass == '*')) {
if (option_verbose > 3)
ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass);
*to=0;
return NULL;
}
if (f && (f->frametype == AST_FRAME_DTMF) && (f->subclass != '*') && valid_exit(qe, f->subclass)) {
if (option_verbose > 3)
ast_verbose(VERBOSE_PREFIX_3 "User pressed digit: %c", f->subclass);
*to=0;
*digit=f->subclass;
return NULL;
}
}
if (!*to && (option_verbose > 2))
ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", orig);
}
return peer;
}
static int wait_our_turn(struct queue_ent *qe)
{
struct queue_ent *ch;
int res = 0;
for (;;) {
/* Atomically read the parent head */
ast_mutex_lock(&qe->parent->lock);
ch = qe->parent->head;
ast_mutex_unlock(&qe->parent->lock);
/* If we are now at the top of the head, break out */
if (qe->parent->head == qe)
break;
/* Wait a second before checking again */
res = ast_waitfordigit(qe->chan, RECHECK * 1000);
if (res)
break;
}
return res;
}
static int update_queue(struct ast_call_queue *q, struct localuser *user)
{
struct member *cur;
/* 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);
cur = q->members;
while(cur) {
if (user->member == cur) {
time(&cur->lastcall);
cur->calls++;
break;
}
cur = cur->next;
}
ast_mutex_unlock(&q->lock);
return 0;
}
static int calc_metric(struct ast_call_queue *q, struct member *mem, int pos, struct queue_ent *qe, struct localuser *tmp)
{
switch (q->strategy) {
case QUEUE_STRATEGY_RINGALL:
/* Everyone equal, except for penalty */
tmp->metric = mem->penalty * 1000000;
break;
case QUEUE_STRATEGY_ROUNDROBIN:
if (!pos) {
if (!q->wrapped) {
/* No more channels, start over */
q->rrpos = 0;
} else {
/* Prioritize next entry */
q->rrpos++;
}
q->wrapped = 0;
}
if (pos < q->rrpos) {
tmp->metric = 1000 + pos;
} else {
if (pos > q->rrpos) {
/* Indicate there is another priority */
q->wrapped = 1;
}
tmp->metric = pos;
}
tmp->metric += mem->penalty * 1000000;
break;
case QUEUE_STRATEGY_RANDOM:
tmp->metric = rand() % 1000;
tmp->metric += mem->penalty * 1000000;
break;
case QUEUE_STRATEGY_FEWESTCALLS:
tmp->metric = mem->calls;
tmp->metric += mem->penalty * 1000000;
break;
case QUEUE_STRATEGY_LEASTRECENT:
if (!mem->lastcall)
tmp->metric = 0;
else
tmp->metric = 1000000 - (time(NULL) - mem->lastcall);
tmp->metric += mem->penalty * 1000000;
break;
default:
ast_log(LOG_WARNING, "Can't calculate metric for unknown strategy %d\n", q->strategy);
break;
}
return 0;
}
static int try_calling(struct queue_ent *qe, char *options, char *announceoverride, char *url)
{
struct member *cur;
struct localuser *outgoing=NULL, *tmp = NULL;
int to;
int allowredir_in=0;
int allowredir_out=0;
int allowdisconnect=0;
char restofit[AST_MAX_EXTENSION];
char *newnum;
struct ast_channel *peer;
struct localuser *lpeer;
int res = 0, bridge = 0;
int zapx = 2;
int x=0;
char *announce = NULL;
char digit = 0;
/* Hold the lock while we setup the outgoing calls */
ast_mutex_lock(&qe->parent->lock);
cur = qe->parent->members;
if (strlen(qe->announce))
announce = qe->announce;
if (announceoverride && strlen(announceoverride))
announce = announceoverride;
while(cur) {
/* Get a technology/[device:]number pair */
tmp = malloc(sizeof(struct localuser));
if (!tmp) {
ast_log(LOG_WARNING, "Out of memory\n");
goto out;
}
memset(tmp, 0, sizeof(struct localuser));
tmp->stillgoing = -1;
if (options) {
if (strchr(options, 't'))
tmp->allowredirect_in = 1;
if (strchr(options, 'T'))
tmp->allowredirect_out = 1;
if (strchr(options, 'r'))
tmp->ringbackonly = 1;
if (strchr(options, 'm'))
tmp->musiconhold = 1;
if (strchr(options, 'd'))
tmp->dataquality = 1;
if (strchr(options, 'H'))
tmp->allowdisconnect = 1;
}
if (url) {
ast_log(LOG_DEBUG, "Queue with URL=%s_\n", url);
} else
ast_log(LOG_DEBUG, "Simple queue (no URL)\n");
tmp->member = cur; /* Never directly dereference! Could change on reload */
strncpy(tmp->tech, cur->tech, sizeof(tmp->tech)-1);
strncpy(tmp->numsubst, cur->loc, sizeof(tmp->numsubst)-1);
/* If we're dialing by extension, look at the extension to know what to dial */
if ((newnum = strstr(tmp->numsubst, "BYEXTENSION"))) {
strncpy(restofit, newnum + strlen("BYEXTENSION"), sizeof(restofit)-1);
snprintf(newnum, sizeof(tmp->numsubst) - (newnum - tmp->numsubst), "%s%s", qe->chan->exten,restofit);
if (option_debug)
ast_log(LOG_DEBUG, "Dialing by extension %s\n", tmp->numsubst);
}
/* Special case: If we ring everyone, go ahead and ring them, otherwise
just calculate their metric for the appropriate strategy */
calc_metric(qe->parent, cur, x++, qe, tmp);
/* Put them in the list of outgoing thingies... We're ready now.
XXX If we're forcibly removed, these outgoing calls won't get
hung up XXX */
tmp->next = outgoing;
outgoing = tmp;
/* If this line is up, don't try anybody else */
if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP))
break;
cur = cur->next;
}
if (qe->parent->timeout)
to = qe->parent->timeout * 1000;
else
to = -1;
ring_one(qe, outgoing);
ast_mutex_unlock(&qe->parent->lock);
lpeer = wait_for_answer(qe, outgoing, &to, &allowredir_in, &allowredir_out, &allowdisconnect, &digit);
if (lpeer)
peer = lpeer->chan;
else
peer = NULL;
if (!peer) {
if (to) {
/* Musta gotten hung up */
res = -1;
} else {
if (digit && valid_exit(qe, digit))
res=digit;
else
/* Nobody answered, next please? */
res=0;
}
goto out;
}
if (peer) {
/* Ah ha! Someone answered within the desired timeframe. Of course after this
we will always return with -1 so that it is hung up properly after the
conversation. */
if (!strcmp(qe->chan->type,"Zap")) {
if (tmp->dataquality) zapx = 0;
ast_channel_setoption(qe->chan,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0);
}
if (!strcmp(peer->type,"Zap")) {
if (tmp->dataquality) zapx = 0;
ast_channel_setoption(peer,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0);
}
/* Update parameters for the queue */
update_queue(qe->parent, lpeer);
hanguptree(outgoing, peer);
/* Stop music on hold */
ast_moh_stop(qe->chan);
outgoing = NULL;
if (announce) {
int res2;
res2 = ast_autoservice_start(qe->chan);
if (!res2)
res2 = ast_streamfile(peer, announce, peer->language);
if (!res2)
res2 = ast_waitstream(peer, "");
res2 |= ast_autoservice_stop(qe->chan);
if (res2) {
/* Agent must have hung up */
ast_log(LOG_WARNING, "Agent on %s hungup on the customer. They're going to be pissed.\n", peer->name);
ast_hangup(peer);
return -1;
}
}
/* If appropriate, log that we have a destination channel */
if (qe->chan->cdr)
ast_cdr_setdestchan(qe->chan->cdr, peer->name);
/* Make sure channels are compatible */
res = ast_channel_make_compatible(qe->chan, peer);
if (res < 0) {
ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", qe->chan->name, peer->name);
ast_hangup(peer);
return -1;
}
/* Drop out of the queue at this point, to prepare for next caller */
leave_queue(qe);
/* JDG: sendurl */
if( url && strlen(url) && ast_channel_supports_html(peer) ) {
ast_log(LOG_DEBUG, "app_queue: sendurl=%s.\n", url);
ast_channel_sendurl( peer, url );
} /* /JDG */
bridge = ast_bridge_call(qe->chan, peer, allowredir_in, allowredir_out, allowdisconnect);
if(bridge != AST_PBX_NO_HANGUP_PEER)
ast_hangup(peer);
if( bridge == 0 ) res=1; /* JDG: bridge successfull, leave app_queue */
else res = bridge; /* bridge error, stay in the queue */
}
out:
hanguptree(outgoing, NULL);
return res;
}
static int wait_a_bit(struct queue_ent *qe)
{
int retrywait;
/* Hold the lock while we setup the outgoing calls */
ast_mutex_lock(&qe->parent->lock);
retrywait = qe->parent->retry * 1000;
ast_mutex_unlock(&qe->parent->lock);
return ast_waitfordigit(qe->chan, retrywait);
}
// [PHM 06/26/03]
static struct member * interface_exists( struct ast_call_queue * q, char * interface )
{
struct member * ret = NULL ;
struct member *mem;
char buf[500] ;
if( q != NULL )
{
mem = q->members ;
while( mem != NULL ) {
sprintf( buf, "%s/%s", mem->tech, mem->loc);
if( strcmp( buf, interface ) == 0 ) {
ret = mem ;
break ;
}
else
mem = mem->next ;
}
}
return( ret ) ;
}
static struct member * create_queue_node( char * interface )
{
struct member * cur ;
char * tmp ;
/* Add a new member */
cur = malloc(sizeof(struct member));
if (cur) {
memset(cur, 0, sizeof(struct member));
strncpy(cur->tech, interface, sizeof(cur->tech) - 1);
if ((tmp = strchr(cur->tech, '/')))
*tmp = '\0';
if ((tmp = strchr(interface, '/'))) {
tmp++;
strncpy(cur->loc, tmp, sizeof(cur->loc) - 1);
} else
ast_log(LOG_WARNING, "No location at interface '%s'\n", interface);
}
return( cur ) ;
}
static int rqm_exec(struct ast_channel *chan, void *data)
{
int res=-1;
struct localuser *u;
char *queuename;
struct member * node ;
struct member * look ;
char info[512];
char tmpchan[256]="";
char *interface=NULL;
struct ast_call_queue *q;
int found=0 ;
if (!data) {
ast_log(LOG_WARNING, "RemoveQueueMember requires an argument (queuename|optional interface)\n");
return -1;
}
LOCAL_USER_ADD(u); // not sure if we need this, but better be safe than sorry ;-)
/* Parse our arguments XXX Check for failure XXX */
strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
queuename = info;
if (queuename) {
interface = strchr(queuename, '|');
if (interface) {
*interface = '\0';
interface++;
}
else {
strncpy(tmpchan, chan->name, sizeof(tmpchan) - 1);
interface = strrchr(tmpchan, '-');
if (interface)
*interface = '\0';
interface = tmpchan;
}
}
if( ( q = queues) != NULL )
{
while( q && ( res != 0 ) && (!found) )
{
ast_mutex_lock(&q->lock);
if( strcmp( q->name, queuename) == 0 )
{
// found queue, try to remove interface
found=1 ;
if( ( node = interface_exists( q, interface ) ) != NULL )
{
if( ( look = q->members ) == node )
{
// 1st
q->members = node->next;
}
else
{
while( look != NULL )
if( look->next == node )
{
look->next = node->next ;
break ;
}
else
look = look->next ;
}
free( node ) ;
ast_log(LOG_NOTICE, "Removed interface '%s' to queue '%s'\n",
interface, queuename);
res = 0 ;
}
else
ast_log(LOG_WARNING, "Unable to remove interface '%s' from queue '%s': "
"Not there\n", interface, queuename);
}
ast_mutex_unlock(&q->lock);
q = q->next;
}
}
if( ! found )
ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': No such queue\n", queuename);
LOCAL_USER_REMOVE(u);
return res;
}
static int aqm_exec(struct ast_channel *chan, void *data)
{
int res=-1;
struct localuser *u;
char *queuename;
char info[512];
char tmpchan[512]="";
char *interface=NULL;
struct ast_call_queue *q;
struct member *save;
int found=0 ;
if (!data) {
ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename|optional interface)\n");
return -1;
}
LOCAL_USER_ADD(u); // not sure if we need this, but better be safe than sorry ;-)
/* Parse our arguments XXX Check for failure XXX */
strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
queuename = info;
if (queuename) {
interface = strchr(queuename, '|');
if (interface) {
*interface = '\0';
interface++;
}
else {
strncpy(tmpchan, chan->name, sizeof(tmpchan) - 1);
interface = strrchr(tmpchan, '-');
if (interface)
*interface = '\0';
interface = tmpchan;
}
}
if( ( q = queues) != NULL )
{
while( q && ( res != 0 ) && (!found) )
{
ast_mutex_lock(&q->lock);
if( strcmp( q->name, queuename) == 0 )
{
// found queue, try to enable interface
found=1 ;
if( interface_exists( q, interface ) == NULL )
{
save = q->members ;
q->members = create_queue_node( interface ) ;
if( q->members != NULL ) {
q->members->dynamic = 1;
q->members->next = save ;
} else
q->members = save ;
ast_log(LOG_NOTICE, "Added interface '%s' to queue '%s'\n", interface, queuename);
res = 0 ;
}
else
ast_log(LOG_WARNING, "Unable to add interface '%s' to queue '%s': "
"Already there\n", interface, queuename);
}
ast_mutex_unlock(&q->lock);
q = q->next;
}
}
if( ! found )
ast_log(LOG_WARNING, "Unable to add interface to queue '%s': No such queue\n", queuename);
LOCAL_USER_REMOVE(u);
return res;
}
static int queue_exec(struct ast_channel *chan, void *data)
{
int res=-1;
struct localuser *u;
char *queuename;
char info[512];
char *options = NULL;
char *url = NULL;
char *announceoverride = NULL;
/* Our queue entry */
struct queue_ent qe;
if (!data) {
ast_log(LOG_WARNING, "Queue requires an argument (queuename|optional timeout|optional URL)\n");
return -1;
}
LOCAL_USER_ADD(u);
/* Parse our arguments XXX Check for failure XXX */
strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
queuename = info;
if (queuename) {
options = strchr(queuename, '|');
if (options) {
*options = '\0';
options++;
url = strchr(options, '|');
if (url) {
*url = '\0';
url++;
announceoverride = strchr(url, '|');
if (announceoverride) {
*announceoverride = '\0';
announceoverride++;
}
}
}
}
printf("queue: %s, options: %s, url: %s, announce: %s\n",
queuename, options, url, announceoverride);
/* Setup our queue entry */
memset(&qe, 0, sizeof(qe));
qe.chan = chan;
qe.start = time(NULL);
if (!join_queue(queuename, &qe)) {
/* Start music on hold */
ast_moh_start(chan, qe.moh);
for (;;) {
res = wait_our_turn(&qe);
/* If they hungup, return immediately */
if (res < 0) {
if (option_verbose > 2) {
ast_verbose(VERBOSE_PREFIX_3 "User disconnected while waiting their turn\n");
res = -1;
}
break;
}
if (!res)
break;
if (valid_exit(&qe, res))
break;
}
if (!res) {
for (;;) {
res = try_calling(&qe, options, announceoverride, url);
if (res)
break;
res = wait_a_bit(&qe);
if (res < 0) {
if (option_verbose > 2) {
ast_verbose(VERBOSE_PREFIX_3 "User disconnected when they almost made it\n");
res = -1;
}
break;
}
if (res && valid_exit(&qe, res))
break;
}
}
/* Don't allow return code > 0 */
if (res > 0 && res != AST_PBX_KEEPALIVE) {
res = 0;
ast_moh_stop(chan);
}
leave_queue(&qe);
} else {
ast_log(LOG_WARNING, "Unable to join queue '%s'\n", queuename);
res = 0;
}
LOCAL_USER_REMOVE(u);
return res;
}
static void reload_queues(void)
{
struct ast_call_queue *q, *ql, *qn;
struct ast_config *cfg;
char *cat, *tmp;
struct ast_variable *var;
struct member *prev, *cur;
int new;
cfg = ast_load("queues.conf");
if (!cfg) {
ast_log(LOG_NOTICE, "No call queueing config file, so no call queues\n");
return;
}
ast_mutex_lock(&qlock);
/* Mark all queues as dead for the moment */
q = queues;
while(q) {
q = q->next;
}
/* Chug through config file */
cat = ast_category_browse(cfg, NULL);
while(cat) {
if (strcasecmp(cat, "general")) {
/* Look for an existing one */
q = queues;
while(q) {
if (!strcmp(q->name, cat))
break;
q = q->next;
}
if (!q) {
/* Make one then */
q = malloc(sizeof(struct ast_call_queue));
if (q) {
/* Initialize it */
memset(q, 0, sizeof(struct ast_call_queue));
ast_mutex_init(&q->lock);
strncpy(q->name, cat, sizeof(q->name));
new = 1;
} else new = 0;
} else
new = 0;
if (q) {
if (!new)
ast_mutex_lock(&q->lock);
/* Re-initialize the queue */
q->dead = 0;
q->retry = 0;
q->timeout = -1;
q->maxlen = 0;
free_members(q, 0);
strcpy(q->moh, "");
strcpy(q->announce, "");
strcpy(q->context, "");
prev = q->members;
if (prev) {
/* find the end of any dynamic members */
while(prev->next)
prev = prev->next;
}
var = ast_variable_browse(cfg, cat);
while(var) {
if (!strcasecmp(var->name, "member")) {
/* Add a new member */
cur = malloc(sizeof(struct member));
if (cur) {
memset(cur, 0, sizeof(struct member));
strncpy(cur->tech, var->value, sizeof(cur->tech) - 1);
if ((tmp = strchr(cur->tech, ','))) {
*tmp = '\0';
tmp++;
cur->penalty = atoi(tmp);
if (cur->penalty < 0)
cur->penalty = 0;
}
if ((tmp = strchr(cur->tech, '/')))
*tmp = '\0';
if ((tmp = strchr(var->value, '/'))) {
tmp++;
strncpy(cur->loc, tmp, sizeof(cur->loc) - 1);
if ((tmp = strchr(cur->loc, ',')))
*tmp = '\0';
} else
ast_log(LOG_WARNING, "No location at line %d of queue.conf\n", var->lineno);
if (prev)
prev->next = cur;
else
q->members = cur;
prev = cur;
}
} else if (!strcasecmp(var->name, "music")) {
strncpy(q->moh, var->value, sizeof(q->moh) - 1);
} else if (!strcasecmp(var->name, "announce")) {
strncpy(q->announce, var->value, sizeof(q->announce) - 1);
} else if (!strcasecmp(var->name, "context")) {
strncpy(q->context, var->value, sizeof(q->context) - 1);
} else if (!strcasecmp(var->name, "timeout")) {
q->timeout = atoi(var->value);
} else if (!strcasecmp(var->name, "retry")) {
q->retry = atoi(var->value);
} else if (!strcasecmp(var->name, "maxlen")) {
q->maxlen = atoi(var->value);
} else if (!strcasecmp(var->name, "strategy")) {
q->strategy = strat2int(var->value);
if (q->strategy < 0) {
ast_log(LOG_WARNING, "'%s' isn't a valid strategy, using ringall instead\n", var->value);
q->strategy = 0;
}
} else {
ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queue.conf\n", cat, var->name, var->lineno);
}
var = var->next;
}
if (q->retry < 1)
q->retry = DEFAULT_RETRY;
if (q->timeout < 0)
q->timeout = DEFAULT_TIMEOUT;
if (q->maxlen < 0)
q->maxlen = 0;
if (!new)
ast_mutex_unlock(&q->lock);
if (new) {
q->next = queues;
queues = q;
}
}
}
cat = ast_category_browse(cfg, cat);
}
ast_destroy(cfg);
q = queues;
ql = NULL;
while(q) {
qn = q->next;
if (q->dead) {
if (ql)
ql->next = q->next;
else
queues = q->next;
if (!q->count) {
free(q);
} else
ast_log(LOG_WARNING, "XXX Leaking a litttle memory :( XXX\n");
} else
ql = q;
q = qn;
}
ast_mutex_unlock(&qlock);
}
static int queues_show(int fd, int argc, char **argv)
{
struct ast_call_queue *q;
struct queue_ent *qe;
struct member *mem;
int pos;
time_t now;
char max[80];
char calls[80];
time(&now);
if (argc != 2)
return RESULT_SHOWUSAGE;
q = queues;
if (!q) {
ast_cli(fd, "No queues.\n");
return RESULT_SUCCESS;
}
while(q) {
ast_mutex_lock(&q->lock);
if (q->maxlen)
snprintf(max, sizeof(max), "%d", q->maxlen);
else
strcpy(max, "unlimited");
ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy\n", q->name, q->count, max, int2strat(q->strategy));
if (q->members) {
ast_cli(fd, " Members: \n");
for (mem = q->members; mem; mem = mem->next) {
if (mem->penalty)
snprintf(max, sizeof(max) - 20, " with penalty %d", mem->penalty);
else
strcpy(max, "");
if (mem->dynamic)
strcat(max, " (dynamic)");
if (mem->calls) {
snprintf(calls, sizeof(calls), " has taken %d calls (last was %ld secs ago)",
mem->calls, time(NULL) - mem->lastcall);
} else
strcpy(calls, " has taken no calls yet");
ast_cli(fd, " %s/%s%s%s\n", mem->tech, mem->loc, max, calls);
}
} else
ast_cli(fd, " No Members\n");
if (q->head) {
pos = 1;
ast_cli(fd, " Callers: \n");
for (qe = q->head; qe; qe = qe->next)
ast_cli(fd, " %d. %s (wait: %d:%02.2d)\n", pos++, qe->chan->name,
(now - qe->start) / 60, (now - qe->start) % 60);
} else
ast_cli(fd, " No Callers\n");
ast_cli(fd, "\n");
ast_mutex_unlock(&q->lock);
q = q->next;
}
return RESULT_SUCCESS;
}
/* JDG: callback to display queues status in manager */
static int manager_queues_show( struct mansession *s, struct message *m )
{
char *a[] = { "show", "queues" };
return queues_show( s->fd, 2, a );
} /* /JDG */
/* Dump queue status */
static int manager_queues_status( struct mansession *s, struct message *m )
{
time_t now;
int pos;
char *id = astman_get_header(m,"ActionID");
char idText[256] = "";
struct ast_call_queue *q;
struct queue_ent *qe;
astman_send_ack(s, m, "Queue status will follow");
time(&now);
q = queues;
if (id && &id) {
snprintf(idText,256,"ActionID: %s\r\n",id);
}
while(q) {
ast_mutex_lock(&q->lock);
ast_cli(s->fd, "Event: QueueParams\r\n"
"Queue: %s\r\n"
"Max: %d\r\n"
"Calls: %d\r\n"
"%s"
"\r\n",
q->name, q->maxlen, q->count,idText);
#if 0
/* Do we care about queue members? */
for (mem = q->members; mem; mem = mem->next)
ast_cli(fd, " %s/%s\n", mem->tech, mem->loc);
#endif
pos = 1;
for (qe = q->head; qe; qe = qe->next)
ast_cli(s->fd, "Event: QueueMember\r\n"
"Queue: %s\r\n"
"Position: %d\r\n"
"Channel: %s\r\n"
"CallerID: %s\r\n"
"Wait: %ld\r\n"
"%s"
"\r\n",
q->name, pos++, qe->chan->name, (qe->chan->callerid ? qe->chan->callerid : ""), now - qe->start), idText;
ast_mutex_unlock(&q->lock);
q = q->next;
}
return RESULT_SUCCESS;
}
static char show_queues_usage[] =
"Usage: show queues\n"
" Provides summary information on call queues.\n";
static struct ast_cli_entry cli_show_queues = {
{ "show", "queues", NULL }, queues_show,
"Show status of queues", show_queues_usage, NULL };
int unload_module(void)
{
STANDARD_HANGUP_LOCALUSERS;
ast_cli_unregister(&cli_show_queues);
ast_manager_unregister( "Queues" );
ast_manager_unregister( "QueueStatus" );
return ast_unregister_application(app);
}
int load_module(void)
{
int res;
res = ast_register_application(app, queue_exec, synopsis, descrip);
if (!res) {
ast_cli_register(&cli_show_queues);
ast_manager_register( "Queues", 0, manager_queues_show, "Queues" );
ast_manager_register( "QueueStatus", 0, manager_queues_status, "Queue Status" );
// [PHM 06/26/03]
ast_register_application(app_aqm, aqm_exec, app_aqm_synopsis, app_aqm_descrip) ;
ast_register_application(app_rqm, rqm_exec, app_rqm_synopsis, app_rqm_descrip) ;
}
reload_queues();
return res;
}
int reload(void)
{
reload_queues();
return 0;
}
char *description(void)
{
return tdesc;
}
int usecount(void)
{
int res;
STANDARD_USECOUNT(res);
return res;
}
char *key()
{
return ASTERISK_GPL_KEY;
}