Version 0.2.0 from FTP
authorMark Spencer <markster@digium.com>
Mon, 2 Sep 2002 23:15:40 +0000 (23:15 +0000)
committerMark Spencer <markster@digium.com>
Mon, 2 Sep 2002 23:15:40 +0000 (23:15 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@513 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_queue.c [new file with mode: 0755]
configs/queues.conf.sample [new file with mode: 0755]

diff --git a/apps/app_queue.c b/apps/app_queue.c
new file mode 100755 (executable)
index 0000000..175f28d
--- /dev/null
@@ -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 (executable)
index 0000000..f6746e0
--- /dev/null
@@ -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