diff options
author | markster <markster@f38db490-d61c-443f-a65b-d21fe96a405b> | 2002-09-02 23:15:40 +0000 |
---|---|---|
committer | markster <markster@f38db490-d61c-443f-a65b-d21fe96a405b> | 2002-09-02 23:15:40 +0000 |
commit | 3083c13a2823feea44f13981f7c7f434498b7bd1 (patch) | |
tree | b6391ffc2239c5e7ad3e5b3839eb56677f1e6dae | |
parent | bf3b17af69e47cde03441f1978bbe2d176640380 (diff) |
Version 0.2.0 from FTP
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@513 f38db490-d61c-443f-a65b-d21fe96a405b
-rwxr-xr-x | apps/app_queue.c | 915 | ||||
-rwxr-xr-x | configs/queues.conf.sample | 50 |
2 files changed, 965 insertions, 0 deletions
diff --git a/apps/app_queue.c b/apps/app_queue.c new file mode 100755 index 000000000..175f28df2 --- /dev/null +++ b/apps/app_queue.c @@ -0,0 +1,915 @@ +/* + * Asterisk -- A telephony toolkit for Linux. + * + * True call queues + * + * 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/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 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[|timeout[|options]]):\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"; + +/* 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; + int stillgoing; + int allowredirect; + int ringbackonly; + int musiconhold; + int dataquality; + int allowdisconnect; + 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]; /* Announcement to play */ + 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 */ + struct member *next; /* Next member */ +}; + +struct ast_call_queue { + pthread_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 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 */ + + 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 pthread_mutex_t qlock = AST_MUTEX_INITIALIZER; + + +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_pthread_mutex_lock(&qlock); + q = queues; + while(q) { + if (!strcasecmp(q->name, queuename)) { + /* This is our one */ + ast_pthread_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; + } + ast_pthread_mutex_unlock(&q->lock); + break; + } + q = q->next; + } + ast_pthread_mutex_unlock(&qlock); + return res; +} + +static void free_members(struct ast_call_queue *q) +{ + struct member *curm, *next; + curm = q->members; + while(curm) { + next = curm->next; + free(curm); + curm = next; + } + q->members = NULL; +} + +static void destroy_queue(struct ast_call_queue *q) +{ + struct ast_call_queue *cur, *prev = NULL; + ast_pthread_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_pthread_mutex_unlock(&qlock); + free_members(q); + 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_pthread_mutex_lock(&q->lock); + /* Take us out of the queue */ + prev = NULL; + cur = q->head; + while(cur) { + if (cur == qe) { + q->count--; + /* 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_pthread_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 != exception) + ast_hangup(outgoing->chan); + oo = outgoing; + outgoing=outgoing->next; + free(oo); + } +} + +#define MAX 256 + +static struct ast_channel *wait_for_answer(struct ast_channel *in, struct localuser *outgoing, int *to, int *allowredir, int *allowdisconnect, char *queue) +{ + struct localuser *o; + int found; + int numlines; + int sentringing = 0; + int numbusies = 0; + int orig = *to; + struct ast_frame *f; + struct ast_channel *peer = NULL; + struct ast_channel *watchers[MAX]; + int pos; + struct ast_channel *winner; + + while(*to && !peer) { + o = outgoing; + found = -1; + pos = 1; + numlines = 0; + watchers[0] = in; + while(o) { + /* Keep track of important channels */ + if (o->stillgoing) { + 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->_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->chan; + *allowredir = o->allowredirect; + *allowdisconnect = o->allowdisconnect; + } + } else if (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->chan; + *allowredir = o->allowredirect; + *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); + 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); + 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; + } + } + 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 (!*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 */ + pthread_mutex_lock(&qe->parent->lock); + ch = qe->parent->head; + pthread_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 try_calling(struct queue_ent *qe, char *options) +{ + struct member *cur; + struct localuser *outgoing=NULL, *tmp = NULL; + int to; + int allowredir=0; + int allowdisconnect=0; + char numsubst[AST_MAX_EXTENSION]; + char restofit[AST_MAX_EXTENSION]; + char *newnum; + struct ast_channel *peer; + int res = 0; + /* Hold the lock while we setup the outgoing calls */ + ast_pthread_mutex_lock(&qe->parent->lock); + cur = qe->parent->members; + 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)); + if (options) { + if (strchr(options, 't')) + tmp->allowredirect = 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; + } + + strncpy(numsubst, cur->loc, sizeof(numsubst)-1); + /* If we're dialing by extension, look at the extension to know what to dial */ + if ((newnum = strstr(numsubst, "BYEXTENSION"))) { + strncpy(restofit, newnum + strlen("BYEXTENSION"), sizeof(restofit)-1); + snprintf(newnum, sizeof(numsubst) - (newnum - numsubst), "%s%s", qe->chan->exten,restofit); + if (option_debug) + ast_log(LOG_DEBUG, "Dialing by extension %s\n", numsubst); + } + /* Request the peer */ + tmp->chan = ast_request(cur->tech, qe->chan->nativeformats, 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); + free(tmp); + cur = cur->next; + continue; + } +#if 0 + /* Don't honor call forwarding on a queue! */ + if (strlen(tmp->chan->call_forward)) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Forwarding call to '%s@%s'\n", tmp->chan->call_forward, tmp->chan->context); + /* Setup parameters */ + strncpy(chan->exten, tmp->chan->call_forward, sizeof(chan->exten)); + strncpy(chan->context, tmp->chan->context, sizeof(chan->context)); + chan->priority = 0; + to = 0; + ast_hangup(tmp->chan); + free(tmp); + cur = rest; + break; + } +#endif + 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, 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", numsubst); + ast_hangup(tmp->chan); + free(tmp); + cur = cur->next; + continue; + } else + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", numsubst); + /* 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->stillgoing = -1; + tmp->next = outgoing; + outgoing = tmp; + /* If this line is up, don't try anybody else */ + if (outgoing->chan->_state == AST_STATE_UP) + break; + + cur = cur->next; + } + if (qe->parent->timeout) + to = qe->parent->timeout * 1000; + else + to = -1; + ast_pthread_mutex_unlock(&qe->parent->lock); + + peer = wait_for_answer(qe->chan, outgoing, &to, &allowredir, &allowdisconnect, qe->parent->name); + if (!peer) { + if (to) + /* Musta gotten hung up */ + res = -1; + 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. */ + hanguptree(outgoing, peer); + /* Stop music on hold */ + ast_moh_stop(qe->chan); + outgoing = NULL; + if (strlen(qe->announce)) { + int res2; + res2 = ast_streamfile(peer, qe->announce, peer->language); + if (!res2) + res2 = ast_waitstream(peer, ""); + else + res2 = 0; + 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; + } + if (!strcmp(qe->chan->type,"Zap")) { + int x = 2; + if (tmp->dataquality) x = 0; + ast_channel_setoption(qe->chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0); + } + if (!strcmp(peer->type,"Zap")) { + int x = 2; + if (tmp->dataquality) x = 0; + ast_channel_setoption(peer,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0); + } + /* Drop out of the queue at this point, to prepare for next caller */ + leave_queue(qe); + res = ast_bridge_call(qe->chan, peer, allowredir, allowdisconnect); + ast_hangup(peer); + } +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_pthread_mutex_lock(&qe->parent->lock); + retrywait = qe->parent->retry * 1000; + ast_pthread_mutex_unlock(&qe->parent->lock); + return ast_waitfordigit(qe->chan, retrywait); +} + +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; +} + +static int queue_exec(struct ast_channel *chan, void *data) +{ + int res=-1; + struct localuser *u; + char *queuename; + char info[512]; + char *options = NULL; + + /* Our queue entry */ + struct queue_ent qe; + + if (!data) { + ast_log(LOG_WARNING, "Queue requires an argument (queuename|optional timeout)\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++; + } + } + + /* 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); + 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 = 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_pthread_mutex_lock(&qlock); + /* Mark all queues as dead for the moment */ + q = queues; + while(q) { + q->dead = 1; + 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)); + strncpy(q->name, cat, sizeof(q->name)); + new = 1; + } else new = 0; + } else + new = 0; + if (q) { + if (!new) + ast_pthread_mutex_lock(&q->lock); + /* Re-initialize the queue */ + q->dead = 0; + q->retry = 0; + q->timeout = -1; + q->maxlen = 0; + free_members(q); + strcpy(q->moh, ""); + strcpy(q->announce, ""); + strcpy(q->context, ""); + prev = NULL; + 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'; + if ((tmp = strchr(var->value, '/'))) { + tmp++; + strncpy(cur->loc, tmp, sizeof(cur->loc) - 1); + } 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 { + 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_pthread_mutex_unlock(&q->lock); + if (new) { + q->next = queues; + queues = q; + } + } + } + cat = ast_category_browse(cfg, cat); + } + 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_pthread_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]; + + time(&now); + if (argc != 2) + return RESULT_SHOWUSAGE; + q = queues; + if (!q) { + ast_cli(fd, "No queues.\n"); + return RESULT_SUCCESS; + } + while(q) { + ast_pthread_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)\n", q->name, q->count, max); + if (q->members) { + ast_cli(fd, " Members: \n"); + for (mem = q->members; mem; mem = mem->next) + ast_cli(fd, " %s/%s\n", mem->tech, mem->loc); + } 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_pthread_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); + 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); + 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; +} diff --git a/configs/queues.conf.sample b/configs/queues.conf.sample new file mode 100755 index 000000000..f6746e09f --- /dev/null +++ b/configs/queues.conf.sample @@ -0,0 +1,50 @@ +[general] +; +; Global settings for call queues +; (none exist currently) + +[default] +; +; Default settings for queues (currently unused) +; + +;[markq] +; +; A sample call queue +; +; Music sets which musiconhold applies for this particular +; call queue +; +;music = default +; +; An announcement may be specified which is played for the member as +; soon as they answer a call, typically to indicate to them which queue +; this call should be answered as, so that agents or members who are +; listening to more than one queue can differentiated how they should +; engage the customer +; +;announce = queue-markq +; +; A context may be specified, in which if the user types a SINGLE +; digit extension while they are in the queue, they will be taken out +; of the queue and sent to that extension in this context. +; +;context = qoutcon +; +; How long do we let the phone ring before we consider this a timeout... +; +;timeout = 15 +; +; How long do we wait before trying all the members again? +; +;retry = 5 +; +; Maximum number of people waiting in the queue (0 for unlimited) +; +;maxlen = 0 +; +; Each member of this call queue is listed on a separate line in +; the form technology/dialstring +; +;member => Zap/1 +;member => Zap/2 |