Merge queue changes from Bug #214
authorMark Spencer <markster@digium.com>
Sat, 13 Mar 2004 06:00:41 +0000 (06:00 +0000)
committerMark Spencer <markster@digium.com>
Sat, 13 Mar 2004 06:00:41 +0000 (06:00 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@2415 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CHANGES
Makefile
apps/app_queue.c
configs/queues.conf.sample
sounds.txt
sounds/queue-callswaiting.gsm [new file with mode: 0755]
sounds/queue-holdtime.gsm [new file with mode: 0755]
sounds/queue-minutes.gsm [new file with mode: 0755]
sounds/queue-thankyou.gsm [new file with mode: 0755]
sounds/queue-thereare.gsm [new file with mode: 0755]
sounds/queue-youarenext.gsm [new file with mode: 0755]

diff --git a/CHANGES b/CHANGES
index 58728c7..f2a1371 100755 (executable)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,5 @@
+ -- ADPCM Standardization
+ -- Numerous bug fixes
  -- Add IAX2 Firmware Support
  -- Add G.726 support
  -- Add ices/icecast support
index da0aab9..e8fbe76 100755 (executable)
--- a/Makefile
+++ b/Makefile
@@ -269,7 +269,7 @@ datafiles: all
                        exit 1; \
                fi; \
        done
-       for x in sounds/vm-* sounds/transfer* sounds/pbx-* sounds/ss-* sounds/beep* sounds/dir-* sounds/conf-* sounds/agent-* sounds/invalid* sounds/tt-* sounds/auth-* sounds/privacy-*; do \
+       for x in sounds/vm-* sounds/transfer* sounds/pbx-* sounds/ss-* sounds/beep* sounds/dir-* sounds/conf-* sounds/agent-* sounds/invalid* sounds/tt-* sounds/auth-* sounds/privacy-* sounds/queue-*; do \
                if grep -q "^%`basename $$x`%" sounds.txt; then \
                        install -m 644 $$x $(DESTDIR)$(ASTVARLIBDIR)/sounds ; \
                else \
index 2f76249..8094904 100755 (executable)
@@ -7,6 +7,21 @@
  *
  * Mark Spencer <markster@linux-support.net>
  *
+ * These features added by David C. Troy <dave@toad.net>:
+ *    - Per-queue holdtime calculation
+ *    - Estimated holdtime announcement
+ *    - Position announcement
+ *    - Abandoned/completed call counters
+ *    - Failout timer passed as optional app parameter
+ *    - Optional monitoring of calls, started when call is answered
+ *
+ * Patch Version 1.07 2003-12-24 01
+ *
+ * Added servicelevel statistic by Michiel Betel <michiel@betel.nl>
+ *
+ * Fixed ot work with CVS as of 2004-02-25 and released as 1.07a
+ * by Matthew Enger <m.enger@xi.com.au>
+ *
  * This program is free software, distributed under the terms of
  * the GNU General Public License
  */
@@ -25,6 +40,7 @@
 #include <asterisk/cli.h>
 #include <asterisk/manager.h> /* JDG */
 #include <asterisk/config.h>
+#include <asterisk/monitor.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <unistd.h>
@@ -67,7 +83,7 @@ static char *app = "Queue";
 static char *synopsis = "Queue a call for a call queue";
 
 static char *descrip =
-"  Queue(queuename[|options[|URL][|announceoverride]]):\n"
+"  Queue(queuename[|options[|URL][|announceoverride][|timeout]]):\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"
@@ -81,7 +97,9 @@ static char *descrip =
 "  In addition to transferring the call, a call may be parked and then picked\n"
 "up by another user.\n"
 "  The optional URL will be sent to the called party if the channel supports\n"
-"it.\n";
+"it.\n"
+"  The timeout will cause the queue to fail out after a specified number of\n"
+"seconds, checked between each queues.conf 'timeout' and 'retry' cycle.\n";
 
 // [PHM 06/26/03]
 static char *app_aqm = "AddQueueMember" ;
@@ -127,12 +145,15 @@ 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 announce[80];              /* Announcement to play for member when call is answered */
        char context[80];               /* Context when user exits queue */
        int pos;                                        /* Where we are in the queue */
+       int last_pos_said;              /* Last position we told the user */
+       time_t last_pos;                /* Last time we told the user their position */
        int opos;                                       /* Where we started in the queue */
        int handled;                            /* Whether our call was handled */
        time_t start;                           /* When we started holding */
+       int queuetimeout;               /* How many seconds before timing out of queue */
        struct ast_channel *chan;       /* Our channel */
        struct queue_ent *next;         /* The next queue entry */
 };
@@ -151,10 +172,24 @@ 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 */
+       char announce[80];              /* Announcement to play when call is answered */
+       char context[80];               /* Context for this queue */
        int strategy;                   /* Queueing strategy */
-       int announcetimeout;    /* How often to announce their position */
+       int announcefrequency;          /* How often to announce their position */
+       int announceholdtime;           /* When to announce holdtime: 0 = never, -1 = every announcement, 1 = only once */
+       int holdtime;                   /* Current avg holdtime for this queue, based on recursive boxcar filter */
+       int callscompleted;             /* Number of queue calls completed */
+       int callsabandoned;             /* Number of queue calls abandoned */
+       int servicelevel;               /* seconds setting for servicelevel*/
+       int callscompletedinsl;         /* Number of queue calls answererd with servicelevel*/
+       char monfmt[8];                 /* Format to use when recording calls */
+       char sound_next[80];            /* Sound file: "Your call is now first in line" (def. queue-youarenext) */
+       char sound_thereare[80];        /* Sound file: "There are currently" (def. queue-thereare) */
+       char sound_calls[80];           /* Sound file: "calls waiting to speak to a representative." (def. queue-callswaiting)*/
+       char sound_holdtime[80];        /* Sound file: "The current estimated total holdtime is" (def. queue-holdtime) */
+       char sound_minutes[80];         /* Sound file: "minutes." (def. queue-minutes) */
+       char sound_thanks[80];          /* Sound file: "Thank you for your patience." (def. queue-thankyou) */
+
        int count;                              /* How many entries are in the queue */
        int maxlen;                             /* Max number of entries in queue */
 
@@ -288,6 +323,102 @@ static void destroy_queue(struct ast_call_queue *q)
        free(q);
 }
 
+static int play_file(struct ast_channel *chan, char *filename)
+{
+       int res;
+
+       ast_stopstream(chan);
+       res = ast_streamfile(chan, filename, chan->language);
+
+       if (!res)
+               res = ast_waitstream(chan, "");
+       else
+               res = 0;
+
+       if (res) {
+               ast_log(LOG_WARNING, "ast_streamfile failed on %s \n", chan->name);
+               res = 0;
+       }
+       ast_stopstream(chan);
+
+       return res;
+}
+
+static int say_position(struct queue_ent *qe)
+{
+       int res = 0, avgholdmins;
+       time_t now;
+
+       /* Check to see if this is ludicrous -- if we just announced position, don't do it again*/
+       time(&now);
+       if ( (now - qe->last_pos) < 15 )
+               return -1;
+
+       /* If either our position has changed, or we are over the freq timer, say position */
+       if ( (qe->last_pos_said == qe->pos) && ((now - qe->last_pos) < qe->parent->announcefrequency) )
+               return -1;
+
+       ast_moh_stop(qe->chan);
+       /* Say we're next, if we are */
+       if (qe->pos == 1) {
+               res += play_file(qe->chan, qe->parent->sound_next);
+               goto posout;
+       } else {
+               res += play_file(qe->chan, qe->parent->sound_thereare);
+               res += ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, qe->chan->language);
+               res += play_file(qe->chan, qe->parent->sound_calls);
+       }
+
+       /* Round hold time to nearest minute */
+       avgholdmins = ( (qe->parent->holdtime + 30) - (now - qe->start) ) / 60;
+       ast_verbose(VERBOSE_PREFIX_3 "Hold time for %s is %d minutes\n", qe->parent->name, avgholdmins);
+
+       /* If the hold time is >1 min, if it's enabled, and if it's not
+          supposed to be only once and we have already said it, say it */
+       if (avgholdmins > 1 && (qe->parent->announceholdtime) && (!(qe->parent->announceholdtime==1 && qe->last_pos)) ) {
+               res += play_file(qe->chan, qe->parent->sound_holdtime);
+               res += ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language);
+               res += play_file(qe->chan, qe->parent->sound_minutes);
+       }
+
+       posout:
+       /* Set our last_pos indicators */
+       qe->last_pos = now;
+       qe->last_pos_said = qe->pos;
+
+       ast_verbose(VERBOSE_PREFIX_3 "Told %s in %s their queue position (which was %d)\n", qe->chan->name, qe->parent->name, qe->pos);
+       res += play_file(qe->chan, qe->parent->sound_thanks);
+       ast_moh_start(qe->chan, qe->moh);
+
+       return (res>0);
+}
+
+static void record_abandoned(struct queue_ent *qe)
+{
+       ast_mutex_lock(&qe->parent->lock);
+       qe->parent->callsabandoned++;
+       ast_mutex_unlock(&qe->parent->lock);
+}
+
+static void recalc_holdtime(struct queue_ent *qe)
+{
+       int oldvalue, newvalue;
+
+       /* Calculate holdtime using a recursive boxcar filter */
+       /* Thanks to SRT for this contribution */
+       /* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */
+
+       newvalue = time(NULL) - qe->start;
+
+       ast_mutex_lock(&qe->parent->lock);
+       if (newvalue <= qe->parent->servicelevel)
+                       qe->parent->callscompletedinsl++;
+       oldvalue = qe->parent->holdtime;
+       qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newvalue) >> 2;
+       ast_mutex_unlock(&qe->parent->lock);
+}
+
+
 static void leave_queue(struct queue_ent *qe)
 {
        struct ast_call_queue *q;
@@ -317,6 +448,7 @@ ast_log(LOG_NOTICE, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name
                        else
                                q->head = cur->next;
                } else {
+                       /* Renumber the people after us in the queue based on a new count */
                        cur->pos = ++pos;
                        prev = cur;
                }
@@ -611,12 +743,29 @@ static int wait_our_turn(struct queue_ent *qe)
 {
        struct queue_ent *ch;
        int res = 0;
+       time_t now;
+
+       /* This is the holding pen for callers 2 through maxlen */
        for (;;) {
                /* Atomically read the parent head -- does not need a lock */
                ch = qe->parent->head;
+
                /* If we are now at the top of the head, break out */
-               if (qe->parent->head == qe)
+               if (qe == ch)
+                       break;
+
+               /* If we have timed out, break out */
+               if ( qe->queuetimeout ) {
+                       time(&now);
+                       if ( (now - qe->start) >= qe->queuetimeout )
                        break;
+               }
+
+               /* Make a position announcement, if enabled */
+               if (qe->parent->announcefrequency)
+                       say_position(qe);
+
+
                /* Wait a second before checking again */
                res = ast_waitfordigit(qe->chan, RECHECK * 1000);
                if (res)
@@ -640,6 +789,7 @@ static int update_queue(struct ast_call_queue *q, struct localuser *user)
                }
                cur = cur->next;
        }
+       q->callscompleted++;
        ast_mutex_unlock(&q->lock);
        return 0;
 }
@@ -793,6 +943,7 @@ static int try_calling(struct queue_ent *qe, char *options, char *announceoverri
        if (!peer) {
                if (to) {
                        /* Musta gotten hung up */
+                       record_abandoned(qe);
                        res = -1;
                } else {
                        if (digit && valid_exit(qe, digit))
@@ -817,6 +968,7 @@ static int try_calling(struct queue_ent *qe, char *options, char *announceoverri
                        ast_channel_setoption(peer,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0);
                }
                /* Update parameters for the queue */
+               recalc_holdtime(qe);
                update_queue(qe->parent, lpeer);
                hanguptree(outgoing, peer);
                /* Stop music on hold */
@@ -849,6 +1001,10 @@ static int try_calling(struct queue_ent *qe, char *options, char *announceoverri
                        ast_hangup(peer);
                        return -1;
                }
+               /* Begin Monitoring */
+               if (qe->parent->monfmt && *qe->parent->monfmt) {
+                       ast_monitor_start( peer, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1 );
+               }
                /* Drop out of the queue at this point, to prepare for next caller */
                leave_queue(qe);                        
                /* JDG: sendurl */
@@ -1120,6 +1276,7 @@ static int queue_exec(struct ast_channel *chan, void *data)
        char *options = NULL;
        char *url = NULL;
        char *announceoverride = NULL;
+       char *queuetimeoutstr = NULL;
        /* whether to exit Queue application after the timeout hits */
        int go_on = 0;
 
@@ -1134,6 +1291,9 @@ static int queue_exec(struct ast_channel *chan, void *data)
        }
        
        LOCAL_USER_ADD(u);
+
+       /* Setup our queue entry */
+       memset(&qe, 0, sizeof(qe));
        
        /* Parse our arguments XXX Check for failure XXX */
        strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
@@ -1151,25 +1311,39 @@ static int queue_exec(struct ast_channel *chan, void *data)
                                if (announceoverride) {
                                        *announceoverride = '\0';
                                        announceoverride++;
+                                       queuetimeoutstr = strchr(announceoverride, '|');
+                                       if (queuetimeoutstr) {
+                                               *queuetimeoutstr = '\0';
+                                               queuetimeoutstr++;
+                                               qe.queuetimeout = atoi(queuetimeoutstr);
+                                       } else {
+                                               qe.queuetimeout = 0;
                                }
                        }
                }
        }
-       if (option_debug)
-               ast_log(LOG_DEBUG, "queue: %s, options: %s, url: %s, announce: %s\n",
-                                       queuename, options, url, announceoverride);
-       /* Setup our queue entry */
-       memset(&qe, 0, sizeof(qe));
+       }
+
+       printf("queue: %s, options: %s, url: %s, announce: %s, timeout: %d\n",
+               queuename, options, url, announceoverride, qe.queuetimeout);
+
+
        qe.chan = chan;
        qe.start = time(NULL);
+       qe.last_pos_said = 0;
+       qe.last_pos = 0;
        if (!join_queue(queuename, &qe)) {
                ast_queue_log(queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", url ? url : "", chan->callerid ? chan->callerid : "");
                /* Start music on hold */
                ast_moh_start(chan, qe.moh);
                for (;;) {
+                       /* This is the wait loop for callers 2 through maxlen */
+
                        res = wait_our_turn(&qe);
                        /* If they hungup, return immediately */
                        if (res < 0) {
+                               /* Record this abandoned call */
+                               record_abandoned(&qe);
                                ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start);
                                if (option_verbose > 2) {
                                        ast_verbose(VERBOSE_PREFIX_3 "User disconnected while waiting their turn\n");
@@ -1186,6 +1360,22 @@ static int queue_exec(struct ast_channel *chan, void *data)
                }
                if (!res) {
                        for (;;) {
+                               /* This is the wait loop for the head caller*/
+                               /* To exit, they may get their call answered; */
+                               /* they may dial a digit from the queue context; */
+                               /* or, they may may timeout. */
+
+                               /* Leave if we have exceeded our queuetimeout */
+                               if (qe.queuetimeout && ( (time(NULL) - qe.start) >= qe.queuetimeout) ) {
+                                       res = 0;
+                                       break;
+                               }
+
+                               /* Make a position announcement, if enabled */
+                               if (qe.parent->announcefrequency)
+                                       say_position(&qe);
+
+                               /* Try calling all queue members for 'timeout' seconds */
                                res = try_calling(&qe, options, announceoverride, url, &go_on);
                                if (res) {
                                        if (res < 0) {
@@ -1195,6 +1385,14 @@ static int queue_exec(struct ast_channel *chan, void *data)
                                                ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
                                        break;
                                }
+
+                               /* Leave if we have exceeded our queuetimeout */
+                               if (qe.queuetimeout && ( (time(NULL) - qe.start) >= qe.queuetimeout) ) {
+                                       res = 0;
+                                       break;
+                               }
+
+                               /* OK, we didn't get anybody; wait for 'retry' seconds; may get a digit to exit with */
                                res = wait_a_bit(&qe);
                                if (res < 0) {
                                        ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start);
@@ -1208,18 +1406,20 @@ static int queue_exec(struct ast_channel *chan, void *data)
                                        ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
                                        break;
                                }
-                               /* exit after a timeout if 'n' option enabled */
+                               /* exit after 'timeout' cycle if 'n' option enabled */
                                if (go_on) {
                                        ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
                                        res = 0;
                                        break;
                                }
+
                        }
                }
                /* Don't allow return code > 0 */
                if (res > 0 && res != AST_PBX_KEEPALIVE) {
                        res = 0;        
                        ast_moh_stop(chan);
+                       ast_stopstream(chan);
                }
                leave_queue(&qe);
        } else {
@@ -1281,10 +1481,24 @@ static void reload_queues(void)
                                q->retry = 0;
                                q->timeout = -1;
                                q->maxlen = 0;
+                               q->announcefrequency = 0;
+                               q->announceholdtime = 0;
+                               q->holdtime = 0;
+                               q->callscompleted = 0;
+                               q->callsabandoned = 0;
+                               q->callscompletedinsl = 0;
+                               q->servicelevel = 0;
                                free_members(q, 0);
                                strcpy(q->moh, "");
                                strcpy(q->announce, "");
                                strcpy(q->context, "");
+                               strcpy(q->monfmt, "");
+                               strcpy(q->sound_next, "queue-youarenext");
+                               strcpy(q->sound_thereare, "queue-thereare");
+                               strcpy(q->sound_calls, "queue-callswaiting");
+                               strcpy(q->sound_holdtime, "queue-holdtime");
+                               strcpy(q->sound_minutes, "queue-minutes");
+                               strcpy(q->sound_thanks, "queue-thankyou");
                                prev = q->members;
                                if (prev) {
                                        /* find the end of any dynamic members */
@@ -1329,10 +1543,30 @@ static void reload_queues(void)
                                                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, "monitor-format")) {
+                                               strncpy(q->monfmt, var->value, sizeof(q->monfmt) - 1);
+                                       } else if (!strcasecmp(var->name, "queue-youarenext")) {
+                                               strncpy(q->sound_next, var->value, sizeof(q->sound_next) - 1);
+                                       } else if (!strcasecmp(var->name, "queue-thereare")) {
+                                               strncpy(q->sound_thereare, var->value, sizeof(q->sound_thereare) - 1);
+                                       } else if (!strcasecmp(var->name, "queue-callswaiting")) {
+                                               strncpy(q->sound_calls, var->value, sizeof(q->sound_calls) - 1);
+                                       } else if (!strcasecmp(var->name, "queue-holdtime")) {
+                                               strncpy(q->sound_holdtime, var->value, sizeof(q->sound_holdtime) - 1);
+                                       } else if (!strcasecmp(var->name, "queue-minutes")) {
+                                               strncpy(q->sound_minutes, var->value, sizeof(q->sound_minutes) - 1);
+                                       } else if (!strcasecmp(var->name, "queue-thankyou")) {
+                                               strncpy(q->sound_thanks, var->value, sizeof(q->sound_thanks) - 1);
+                                       } else if (!strcasecmp(var->name, "announce-frequency")) {
+                                               q->announcefrequency = atoi(var->value);
+                                       } else if (!strcasecmp(var->name, "announce-holdtime")) {
+                                               q->announceholdtime = (!strcasecmp(var->value,"once")) ? 1 : ast_true(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, "servicelevel")) {
+                                               q->servicelevel= atoi(var->value);
                                        } else if (!strcasecmp(var->name, "strategy")) {
                                                q->strategy = strat2int(var->value);
                                                if (q->strategy < 0) {
@@ -1390,6 +1624,8 @@ static int __queues_show(int fd, int argc, char **argv, int queue_show)
        time_t now;
        char max[80];
        char calls[80];
+       float sl = 0;
+
        time(&now);
        if ((!queue_show && argc != 2) || (queue_show && argc != 3))
                return RESULT_SHOWUSAGE;
@@ -1420,7 +1656,11 @@ static int __queues_show(int fd, int argc, char **argv, int queue_show)
                        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));
+               sl = 0;
+               if(q->callscompleted > 0)
+                       sl = 100*((float)q->callscompletedinsl/(float)q->callscompleted);
+               ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), C:%d, A:%d, SL:%2.1f%% within %ds\n",
+                       q->name, q->count, max, int2strat(q->strategy), q->holdtime, q->callscompleted, q->callsabandoned,sl,q->servicelevel);
                if (q->members) {
                        ast_cli(fd, "   Members: \n");
                        for (mem = q->members; mem; mem = mem->next) {
@@ -1502,6 +1742,8 @@ static int manager_queues_status( struct mansession *s, struct message *m )
        char idText[256] = "";
        struct ast_call_queue *q;
        struct queue_ent *qe;
+       float sl = 0;
+       struct member *mem;
        astman_send_ack(s, m, "Queue status will follow");
        time(&now);
        ast_mutex_lock(&qlock);
@@ -1511,21 +1753,43 @@ static int manager_queues_status( struct mansession *s, struct message *m )
        }
        while(q) {
                ast_mutex_lock(&q->lock);
+
+               /* List queue properties */
+               if(q->callscompleted > 0)
+                       sl = 100*((float)q->callscompletedinsl/(float)q->callscompleted);
                ast_cli(s->fd, "Event: QueueParams\r\n"
                                        "Queue: %s\r\n"
                                        "Max: %d\r\n"
                                        "Calls: %d\r\n"
+                                       "Holdtime: %d\r\n"
+                                       "Completed: %d\r\n"
+                                       "Abandoned: %d\r\n"
+                                       "ServiceLevel: %d\r\n"
+                                       "ServicelevelPerf: %2.1f\r\n"
                                        "%s"
                                        "\r\n",
-                                               q->name, q->maxlen, q->count,idText);
-#if 0
-               /* Do we care about queue members? */                                   
+                                               q->name, q->maxlen, q->count, q->holdtime, q->callscompleted,
+                                               q->callsabandoned, q->servicelevel, sl, idText);
+
+               /* List Queue Members */
                for (mem = q->members; mem; mem = mem->next) 
-                       ast_cli(fd, "      %s/%s\n", mem->tech, mem->loc);
-#endif                 
+                       ast_cli(s->fd, "Event: QueueMember\r\n"
+                               "Queue: %s\r\n"
+                               "Location: %s/%s\r\n"
+                               "Membership: %s\r\n"
+                               "Penalty: %d\r\n"
+                               "CallsTaken: %d\r\n"
+                               "LastCall: %ld\r\n"
+                               "%s"
+                               "\r\n",
+                                       q->name, mem->tech, mem->loc, mem->dynamic ? "dynamic" : "static",
+                                       mem->penalty, mem->calls, mem->lastcall, idText);
+
+               /* List Queue Entries */
+
                pos = 1;
                for (qe = q->head; qe; qe = qe->next) 
-                       ast_cli(s->fd, "Event: QueueMember\r\n"
+                       ast_cli(s->fd, "Event: QueueEntry\r\n"
                                "Queue: %s\r\n"
                                "Position: %d\r\n"
                                "Channel: %s\r\n"
index dff5da1..75dabab 100755 (executable)
@@ -2,6 +2,11 @@
 ;
 ; Global settings for call queues
 ;   (none exist currently)
+;
+; Note that a timeout to fail out of a queue may be passed as part of application call
+; from extensions.conf:
+; Queue(queuename|[options]|[optionalurl]|[announceoverride]|[timeout])
+; example: Queue(dave|t|||45)
 
 [default]
 ;
 ;
 ;maxlen = 0
 ;
+;
+; How often to announce queue position and/or estimated holdtime to caller (0=off)
+;
+;announce-frequency = 90 
+;
+; Should we include estimated hold time in position announcements?
+; Either yes, no, or only once; hold time will not be announced if <1 minute
+;
+;announce-holdtime = yes|no|once
+;
+; Use these sound files in making position/holdtime announcements.  The
+; defaults are as listed below -- change only if you need to.
+;
+;queue-youarenext = queue-youarenext           ("You are now first in line.")
+;queue-thereare        = queue-thereare                ("There are")
+;queue-callswaiting = queue-callswaiting       ("calls waiting.")
+;queue-holdtime = queue-holdtime               ("The current est. holdtime is")
+;queue-minutes = queue-minutes                 ("minutes.")
+;queue-thankyou = queue-thankyou               ("Thank you for your patience.")
+;
+; Calls may be recorded using Asterisk's monitor resource
+; This can be enabled from within the Queue application, starting recording
+; when the call is actually picked up; thus, only successful calls are
+; recorded, and you are not recording while people are listening to MOH.
+; To enable monitoring, simply specify "monitor-format";  it will be disabled
+; otherwise.
+;
+; monitor-format = gsm|wav|wav49
+;
 ; Each member of this call queue is listed on a separate line in
 ; the form technology/dialstring.  "member" means a normal member of a
 ; queue.  An optional penalty may be specified after a comma, such that
index dea1946..833f880 100755 (executable)
 
 %pbx-transfer.gsm%Transfer.
 
+%queue-callswaiting.gsm%Waiting to speak with a representative
+
+%queue-holdtime.gsm%The estimated hold time is currently
+
+%queue-minutes.gsm%Minutes
+
+%queue-thankyou.gsm%Thank you for your patience
+
+%queue-thereare.gsm%You are currently caller number
+
+%queue-youarenext.gsm%Your call is now first in line and will be answered by the next available representative.
+
 %ss-noservice.gsm%The number you have dialed is not in service. Please check the number and try again.
 
 %transfer.gsm%Please hold while I try that extension.
diff --git a/sounds/queue-callswaiting.gsm b/sounds/queue-callswaiting.gsm
new file mode 100755 (executable)
index 0000000..60596fd
Binary files /dev/null and b/sounds/queue-callswaiting.gsm differ
diff --git a/sounds/queue-holdtime.gsm b/sounds/queue-holdtime.gsm
new file mode 100755 (executable)
index 0000000..fd3e03c
Binary files /dev/null and b/sounds/queue-holdtime.gsm differ
diff --git a/sounds/queue-minutes.gsm b/sounds/queue-minutes.gsm
new file mode 100755 (executable)
index 0000000..e7053f7
Binary files /dev/null and b/sounds/queue-minutes.gsm differ
diff --git a/sounds/queue-thankyou.gsm b/sounds/queue-thankyou.gsm
new file mode 100755 (executable)
index 0000000..c611f5b
Binary files /dev/null and b/sounds/queue-thankyou.gsm differ
diff --git a/sounds/queue-thereare.gsm b/sounds/queue-thereare.gsm
new file mode 100755 (executable)
index 0000000..cb6cdd4
Binary files /dev/null and b/sounds/queue-thereare.gsm differ
diff --git a/sounds/queue-youarenext.gsm b/sounds/queue-youarenext.gsm
new file mode 100755 (executable)
index 0000000..3d54396
Binary files /dev/null and b/sounds/queue-youarenext.gsm differ