2 * Asterisk -- A telephony toolkit for Linux.
4 * True call queues with optional send URL on answer
6 * Copyright (C) 1999, Mark Spencer
8 * Mark Spencer <markster@linux-support.net>
10 * These features added by David C. Troy <dave@toad.net>:
11 * - Per-queue holdtime calculation
12 * - Estimated holdtime announcement
13 * - Position announcement
14 * - Abandoned/completed call counters
15 * - Failout timer passed as optional app parameter
16 * - Optional monitoring of calls, started when call is answered
18 * Patch Version 1.07 2003-12-24 01
20 * Added servicelevel statistic by Michiel Betel <michiel@betel.nl>
21 * Added Priority jumping code for adding and removing queue members by Jonathan Stanton <asterisk@doilooklikeicare.com>
23 * Fixed ot work with CVS as of 2004-02-25 and released as 1.07a
24 * by Matthew Enger <m.enger@xi.com.au>
26 * This program is free software, distributed under the terms of
27 * the GNU General Public License
30 #include <asterisk/lock.h>
31 #include <asterisk/file.h>
32 #include <asterisk/logger.h>
33 #include <asterisk/channel.h>
34 #include <asterisk/pbx.h>
35 #include <asterisk/options.h>
36 #include <asterisk/module.h>
37 #include <asterisk/translate.h>
38 #include <asterisk/say.h>
39 #include <asterisk/parking.h>
40 #include <asterisk/musiconhold.h>
41 #include <asterisk/cli.h>
42 #include <asterisk/manager.h>
43 #include <asterisk/config.h>
44 #include <asterisk/monitor.h>
45 #include <asterisk/utils.h>
53 #include <sys/signal.h>
54 #include <netinet/in.h>
56 #include "../astconf.h"
60 #define QUEUE_STRATEGY_RINGALL 0
61 #define QUEUE_STRATEGY_ROUNDROBIN 1
62 #define QUEUE_STRATEGY_LEASTRECENT 2
63 #define QUEUE_STRATEGY_FEWESTCALLS 3
64 #define QUEUE_STRATEGY_RANDOM 4
65 #define QUEUE_STRATEGY_RRMEMORY 5
67 static struct strategy {
71 { QUEUE_STRATEGY_RINGALL, "ringall" },
72 { QUEUE_STRATEGY_ROUNDROBIN, "roundrobin" },
73 { QUEUE_STRATEGY_LEASTRECENT, "leastrecent" },
74 { QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" },
75 { QUEUE_STRATEGY_RANDOM, "random" },
76 { QUEUE_STRATEGY_RRMEMORY, "rrmemory" },
79 #define DEFAULT_RETRY 5
80 #define DEFAULT_TIMEOUT 15
81 #define RECHECK 1 /* Recheck every second to see we we're at the top yet */
83 static char *tdesc = "True Call Queueing";
85 static char *app = "Queue";
87 static char *synopsis = "Queue a call for a call queue";
89 static char *descrip =
90 " Queue(queuename[|options[|URL][|announceoverride][|timeout]]):\n"
91 "Queues an incoming call in a particular call queue as defined in queues.conf.\n"
92 " This application returns -1 if the originating channel hangs up, or if the\n"
93 "call is bridged and either of the parties in the bridge terminate the call.\n"
94 "Returns 0 if the queue is full, nonexistant, or has no members.\n"
95 "The option string may contain zero or more of the following characters:\n"
96 " 't' -- allow the called user transfer the calling user\n"
97 " 'T' -- to allow the calling user to transfer the call.\n"
98 " 'd' -- data-quality (modem) call (minimum delay).\n"
99 " 'H' -- allow caller to hang up by hitting *.\n"
100 " 'n' -- no retries on the timeout; will exit this application and go to the next step.\n"
101 " 'r' -- ring instead of playing MOH\n"
102 " In addition to transferring the call, a call may be parked and then picked\n"
103 "up by another user.\n"
104 " The optional URL will be sent to the called party if the channel supports\n"
106 " The timeout will cause the queue to fail out after a specified number of\n"
107 "seconds, checked between each queues.conf 'timeout' and 'retry' cycle.\n";
110 static char *app_aqm = "AddQueueMember" ;
111 static char *app_aqm_synopsis = "Dynamically adds queue members" ;
112 static char *app_aqm_descrip =
113 " AddQueueMember(queuename[|interface]):\n"
114 "Dynamically adds interface to an existing queue.\n"
115 "If the interface is already in the queue and there exists an n+101 priority\n"
116 "then it will then jump to this priority. Otherwise it will return an error\n"
117 "Returns -1 if there is an error.\n"
118 "Example: AddQueueMember(techsupport|SIP/3000)\n"
121 static char *app_rqm = "RemoveQueueMember" ;
122 static char *app_rqm_synopsis = "Dynamically removes queue members" ;
123 static char *app_rqm_descrip =
124 " RemoveQueueMember(queuename[|interface]):\n"
125 "Dynamically removes interface to an existing queue\n"
126 "If the interface is NOT in the queue and there exists an n+101 priority\n"
127 "then it will then jump to this priority. Otherwise it will return an error\n"
128 "Returns -1 if there is an error.\n"
129 "Example: RemoveQueueMember(techsupport|SIP/3000)\n"
132 /* We define a customer "local user" structure because we
133 use it not only for keeping track of what is in use but
134 also for keeping track of who we're dialing. */
137 struct ast_channel *chan;
142 int allowredirect_in;
143 int allowredirect_out;
148 struct member *member;
149 struct localuser *next;
155 struct ast_call_queue *parent; /* What queue is our parent */
156 char moh[80]; /* Name of musiconhold to be used */
157 char announce[80]; /* Announcement to play for member when call is answered */
158 char context[80]; /* Context when user exits queue */
159 int pos; /* Where we are in the queue */
160 int last_pos_said; /* Last position we told the user */
161 time_t last_pos; /* Last time we told the user their position */
162 int opos; /* Where we started in the queue */
163 int handled; /* Whether our call was handled */
164 time_t start; /* When we started holding */
165 int queuetimeout; /* How many seconds before timing out of queue */
166 struct ast_channel *chan; /* Our channel */
167 struct queue_ent *next; /* The next queue entry */
171 char tech[80]; /* Technology */
172 char loc[256]; /* Location */
173 int penalty; /* Are we a last resort? */
175 int dynamic; /* Are we dynamically added? */
176 time_t lastcall; /* When last successful call was hungup */
177 struct member *next; /* Next member */
180 struct ast_call_queue {
182 char name[80]; /* Name of the queue */
183 char moh[80]; /* Name of musiconhold to be used */
184 char announce[80]; /* Announcement to play when call is answered */
185 char context[80]; /* Context for this queue */
186 int strategy; /* Queueing strategy */
187 int announcefrequency; /* How often to announce their position */
188 int announceholdtime; /* When to announce holdtime: 0 = never, -1 = every announcement, 1 = only once */
189 int holdtime; /* Current avg holdtime for this queue, based on recursive boxcar filter */
190 int callscompleted; /* Number of queue calls completed */
191 int callsabandoned; /* Number of queue calls abandoned */
192 int servicelevel; /* seconds setting for servicelevel*/
193 int callscompletedinsl; /* Number of queue calls answererd with servicelevel*/
194 char monfmt[8]; /* Format to use when recording calls */
195 int monjoin; /* Should we join the two files when we are done with the call */
196 char sound_next[80]; /* Sound file: "Your call is now first in line" (def. queue-youarenext) */
197 char sound_thereare[80]; /* Sound file: "There are currently" (def. queue-thereare) */
198 char sound_calls[80]; /* Sound file: "calls waiting to speak to a representative." (def. queue-callswaiting)*/
199 char sound_holdtime[80]; /* Sound file: "The current estimated total holdtime is" (def. queue-holdtime) */
200 char sound_minutes[80]; /* Sound file: "minutes." (def. queue-minutes) */
201 char sound_thanks[80]; /* Sound file: "Thank you for your patience." (def. queue-thankyou) */
203 int count; /* How many entries are in the queue */
204 int maxlen; /* Max number of entries in queue */
206 int dead; /* Whether this queue is dead or not */
207 int retry; /* Retry calling everyone after this amount of time */
208 int timeout; /* How long to wait for an answer */
210 /* Queue strategy things */
212 int rrpos; /* Round Robin - position */
213 int wrapped; /* Round Robin - wrapped around? */
215 struct member *members; /* Member channels to be tried */
216 struct queue_ent *head; /* Start of the actual queue */
217 struct ast_call_queue *next; /* Next call queue */
220 static struct ast_call_queue *queues = NULL;
221 AST_MUTEX_DEFINE_STATIC(qlock);
223 static char *int2strat(int strategy)
226 for (x=0;x<sizeof(strategies) / sizeof(strategies[0]);x++) {
227 if (strategy == strategies[x].strategy)
228 return strategies[x].name;
233 static int strat2int(char *strategy)
236 for (x=0;x<sizeof(strategies) / sizeof(strategies[0]);x++) {
237 if (!strcasecmp(strategy, strategies[x].name))
238 return strategies[x].strategy;
243 static int join_queue(char *queuename, struct queue_ent *qe)
245 struct ast_call_queue *q;
246 struct queue_ent *cur, *prev = NULL;
249 ast_mutex_lock(&qlock);
252 if (!strcasecmp(q->name, queuename)) {
253 /* This is our one */
254 ast_mutex_lock(&q->lock);
255 if (q->members && (!q->maxlen || (q->count < q->maxlen))) {
256 /* There's space for us, put us at the end */
268 /* Fix additional pointers and
274 strncpy(qe->moh, q->moh, sizeof(qe->moh));
275 strncpy(qe->announce, q->announce, sizeof(qe->announce));
276 strncpy(qe->context, q->context, sizeof(qe->context));
279 manager_event(EVENT_FLAG_CALL, "Join",
280 "Channel: %s\r\nCallerID: %s\r\nQueue: %s\r\nPosition: %d\r\nCount: %d\r\n",
281 qe->chan->name, (qe->chan->callerid ? qe->chan->callerid : "unknown"), q->name, qe->pos, q->count );
283 ast_log(LOG_NOTICE, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos );
286 ast_mutex_unlock(&q->lock);
291 ast_mutex_unlock(&qlock);
295 static void free_members(struct ast_call_queue *q, int all)
297 /* Free non-dynamic members */
298 struct member *curm, *next, *prev;
303 if (all || !curm->dynamic) {
315 static void destroy_queue(struct ast_call_queue *q)
317 struct ast_call_queue *cur, *prev = NULL;
318 ast_mutex_lock(&qlock);
323 prev->next = cur->next;
331 ast_mutex_unlock(&qlock);
336 static int play_file(struct ast_channel *chan, char *filename)
340 ast_stopstream(chan);
341 res = ast_streamfile(chan, filename, chan->language);
344 res = ast_waitstream(chan, "");
349 ast_log(LOG_WARNING, "ast_streamfile failed on %s \n", chan->name);
352 ast_stopstream(chan);
357 static int say_position(struct queue_ent *qe)
359 int res = 0, avgholdmins;
362 /* Check to see if this is ludicrous -- if we just announced position, don't do it again*/
364 if ( (now - qe->last_pos) < 15 )
367 /* If either our position has changed, or we are over the freq timer, say position */
368 if ( (qe->last_pos_said == qe->pos) && ((now - qe->last_pos) < qe->parent->announcefrequency) )
371 ast_moh_stop(qe->chan);
372 /* Say we're next, if we are */
374 res += play_file(qe->chan, qe->parent->sound_next);
377 res += play_file(qe->chan, qe->parent->sound_thereare);
378 res += ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, qe->chan->language, (char *) NULL); /* Needs gender */
379 res += play_file(qe->chan, qe->parent->sound_calls);
382 /* Round hold time to nearest minute */
383 avgholdmins = ( (qe->parent->holdtime + 30) - (now - qe->start) ) / 60;
384 if (option_verbose > 2)
385 ast_verbose(VERBOSE_PREFIX_3 "Hold time for %s is %d minutes\n", qe->parent->name, avgholdmins);
387 /* If the hold time is >1 min, if it's enabled, and if it's not
388 supposed to be only once and we have already said it, say it */
389 if (avgholdmins > 1 && (qe->parent->announceholdtime) && (!(qe->parent->announceholdtime==1 && qe->last_pos)) ) {
390 res += play_file(qe->chan, qe->parent->sound_holdtime);
391 res += ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language, (char*) NULL);
392 res += play_file(qe->chan, qe->parent->sound_minutes);
396 /* Set our last_pos indicators */
398 qe->last_pos_said = qe->pos;
400 if (option_verbose > 2)
401 ast_verbose(VERBOSE_PREFIX_3 "Told %s in %s their queue position (which was %d)\n", qe->chan->name, qe->parent->name, qe->pos);
402 res += play_file(qe->chan, qe->parent->sound_thanks);
403 ast_moh_start(qe->chan, qe->moh);
408 static void record_abandoned(struct queue_ent *qe)
410 ast_mutex_lock(&qe->parent->lock);
411 qe->parent->callsabandoned++;
412 ast_mutex_unlock(&qe->parent->lock);
415 static void recalc_holdtime(struct queue_ent *qe)
417 int oldvalue, newvalue;
419 /* Calculate holdtime using a recursive boxcar filter */
420 /* Thanks to SRT for this contribution */
421 /* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */
423 newvalue = time(NULL) - qe->start;
425 ast_mutex_lock(&qe->parent->lock);
426 if (newvalue <= qe->parent->servicelevel)
427 qe->parent->callscompletedinsl++;
428 oldvalue = qe->parent->holdtime;
429 qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newvalue) >> 2;
430 ast_mutex_unlock(&qe->parent->lock);
434 static void leave_queue(struct queue_ent *qe)
436 struct ast_call_queue *q;
437 struct queue_ent *cur, *prev = NULL;
442 ast_mutex_lock(&q->lock);
450 /* Take us out of the queue */
451 manager_event(EVENT_FLAG_CALL, "Leave",
452 "Channel: %s\r\nQueue: %s\r\nCount: %d\r\n",
453 qe->chan->name, q->name, q->count);
455 ast_log(LOG_NOTICE, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name );
457 /* Take us out of the queue */
459 prev->next = cur->next;
463 /* Renumber the people after us in the queue based on a new count */
469 ast_mutex_unlock(&q->lock);
470 if (q->dead && !q->count) {
471 /* It's dead and nobody is in it, so kill it */
476 static void hanguptree(struct localuser *outgoing, struct ast_channel *exception)
478 /* Hang up a tree of stuff */
479 struct localuser *oo;
481 /* Hangup any existing lines we have open */
482 if (outgoing->chan && (outgoing->chan != exception))
483 ast_hangup(outgoing->chan);
485 outgoing=outgoing->next;
490 static int ring_entry(struct queue_ent *qe, struct localuser *tmp)
493 /* Request the peer */
494 tmp->chan = ast_request(tmp->tech, qe->chan->nativeformats, tmp->numsubst);
495 if (!tmp->chan) { /* If we can't, just go on to the next call */
497 ast_log(LOG_NOTICE, "Unable to create channel of type '%s'\n", cur->tech);
500 ast_cdr_busy(qe->chan->cdr);
504 tmp->chan->appl = "AppQueue";
505 tmp->chan->data = "(Outgoing Line)";
506 tmp->chan->whentohangup = 0;
507 if (tmp->chan->callerid)
508 free(tmp->chan->callerid);
510 free(tmp->chan->ani);
511 if (qe->chan->callerid)
512 tmp->chan->callerid = strdup(qe->chan->callerid);
514 tmp->chan->callerid = NULL;
516 tmp->chan->ani = strdup(qe->chan->ani);
518 tmp->chan->ani = NULL;
519 /* Presense of ADSI CPE on outgoing channel follows ours */
520 tmp->chan->adsicpe = qe->chan->adsicpe;
521 /* Place the call, but don't wait on the answer */
522 res = ast_call(tmp->chan, tmp->numsubst, 0);
524 /* Again, keep going even if there's an error */
526 ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res);
527 else if (option_verbose > 2)
528 ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", tmp->numsubst);
529 ast_hangup(tmp->chan);
534 if (option_verbose > 2)
535 ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", tmp->numsubst);
539 static int ring_one(struct queue_ent *qe, struct localuser *outgoing)
541 struct localuser *cur;
542 struct localuser *best;
548 if (cur->stillgoing && /* Not already done */
549 !cur->chan && /* Isn't already going */
550 (!best || (cur->metric < bestmetric))) { /* We haven't found one yet, or it's better */
551 bestmetric = cur->metric;
557 if (!qe->parent->strategy) {
558 /* Ring everyone who shares this best metric (for ringall) */
561 if (cur->stillgoing && !cur->chan && (cur->metric == bestmetric)) {
562 ast_log(LOG_DEBUG, "(Parallel) Trying '%s/%s' with metric %d\n", cur->tech, cur->numsubst, cur->metric);
568 /* Ring just the best channel */
569 ast_log(LOG_DEBUG, "Trying '%s/%s' with metric %d\n", best->tech, best->numsubst, best->metric);
570 ring_entry(qe, best);
573 } while (best && !best->chan);
575 ast_log(LOG_DEBUG, "Nobody left to try ringing in queue\n");
581 static int store_next(struct queue_ent *qe, struct localuser *outgoing)
583 struct localuser *cur;
584 struct localuser *best;
589 if (cur->stillgoing && /* Not already done */
590 !cur->chan && /* Isn't already going */
591 (!best || (cur->metric < bestmetric))) { /* We haven't found one yet, or it's better */
592 bestmetric = cur->metric;
598 /* Ring just the best channel */
599 ast_log(LOG_DEBUG, "Next is '%s/%s' with metric %d\n", best->tech, best->numsubst, best->metric);
600 qe->parent->rrpos = best->metric % 1000;
602 /* Just increment rrpos */
603 if (!qe->parent->wrapped) {
604 /* No more channels, start over */
605 qe->parent->rrpos = 0;
607 /* Prioritize next entry */
611 qe->parent->wrapped = 0;
615 static int valid_exit(struct queue_ent *qe, char digit)
618 if (ast_strlen_zero(qe->context))
622 if (ast_exists_extension(qe->chan, qe->context, tmp, 1, qe->chan->callerid)) {
623 strncpy(qe->chan->context, qe->context, sizeof(qe->chan->context) - 1);
624 strncpy(qe->chan->exten, tmp, sizeof(qe->chan->exten) - 1);
625 qe->chan->priority = 0;
633 static struct localuser *wait_for_answer(struct queue_ent *qe, struct localuser *outgoing, int *to, int *allowredir_in, int *allowredir_out, int *allowdisconnect, char *digit)
635 char *queue = qe->parent->name;
643 struct localuser *peer = NULL;
644 struct ast_channel *watchers[MAX];
646 struct ast_channel *winner;
647 struct ast_channel *in = qe->chan;
649 while(*to && !peer) {
656 /* Keep track of important channels */
657 if (o->stillgoing && o->chan) {
658 watchers[pos++] = o->chan;
665 if (numlines == numbusies) {
666 ast_log(LOG_DEBUG, "Everyone is busy at this time\n");
668 ast_log(LOG_NOTICE, "No one is answering queue '%s'\n", queue);
673 winner = ast_waitfor_n(watchers, pos, to);
676 if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) {
678 if (option_verbose > 2)
679 ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
681 *allowredir_in = o->allowredirect_in;
682 *allowredir_out = o->allowredirect_out;
683 *allowdisconnect = o->allowdisconnect;
685 } else if (o->chan && (o->chan == winner)) {
686 f = ast_read(winner);
688 if (f->frametype == AST_FRAME_CONTROL) {
689 switch(f->subclass) {
690 case AST_CONTROL_ANSWER:
691 /* This is our guy if someone answered. */
693 if (option_verbose > 2)
694 ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
696 *allowredir_in = o->allowredirect_in;
697 *allowredir_out = o->allowredirect_out;
698 *allowdisconnect = o->allowdisconnect;
701 case AST_CONTROL_BUSY:
702 if (option_verbose > 2)
703 ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", o->chan->name);
706 ast_cdr_busy(in->cdr);
709 if (qe->parent->strategy)
710 ring_one(qe, outgoing);
713 case AST_CONTROL_CONGESTION:
714 if (option_verbose > 2)
715 ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", o->chan->name);
718 ast_cdr_busy(in->cdr);
721 if (qe->parent->strategy)
722 ring_one(qe, outgoing);
725 case AST_CONTROL_RINGING:
726 if (option_verbose > 2)
727 ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name);
730 ast_indicate(in, AST_CONTROL_RINGING);
735 case AST_CONTROL_OFFHOOK:
736 /* Ignore going off hook */
739 ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass);
747 if (qe->parent->strategy)
748 ring_one(qe, outgoing);
756 if (f && (f->frametype != AST_FRAME_VOICE))
757 printf("Frame type: %d, %d\n", f->frametype, f->subclass);
758 else if (!f || (f->frametype != AST_FRAME_VOICE))
759 printf("Hangup received on %s\n", in->name);
761 if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) {
766 if (f && (f->frametype == AST_FRAME_DTMF) && allowdisconnect && (f->subclass == '*')) {
767 if (option_verbose > 3)
768 ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass);
772 if (f && (f->frametype == AST_FRAME_DTMF) && (f->subclass != '*') && valid_exit(qe, f->subclass)) {
773 if (option_verbose > 3)
774 ast_verbose(VERBOSE_PREFIX_3 "User pressed digit: %c", f->subclass);
780 if (!*to && (option_verbose > 2))
781 ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", orig);
788 static int wait_our_turn(struct queue_ent *qe, int ringing)
790 struct queue_ent *ch;
794 /* This is the holding pen for callers 2 through maxlen */
796 /* Atomically read the parent head -- does not need a lock */
797 ch = qe->parent->head;
799 /* If we are now at the top of the head, break out */
803 /* If we have timed out, break out */
804 if ( qe->queuetimeout ) {
806 if ( (now - qe->start) >= qe->queuetimeout )
810 /* Make a position announcement, if enabled */
811 if (qe->parent->announcefrequency && !ringing)
815 /* Wait a second before checking again */
816 res = ast_waitfordigit(qe->chan, RECHECK * 1000);
823 static int update_queue(struct ast_call_queue *q, struct member *member)
826 /* Since a reload could have taken place, we have to traverse the list to
827 be sure it's still valid */
828 ast_mutex_lock(&q->lock);
832 time(&cur->lastcall);
839 ast_mutex_unlock(&q->lock);
843 static int calc_metric(struct ast_call_queue *q, struct member *mem, int pos, struct queue_ent *qe, struct localuser *tmp)
845 switch (q->strategy) {
846 case QUEUE_STRATEGY_RINGALL:
847 /* Everyone equal, except for penalty */
848 tmp->metric = mem->penalty * 1000000;
850 case QUEUE_STRATEGY_ROUNDROBIN:
853 /* No more channels, start over */
856 /* Prioritize next entry */
862 case QUEUE_STRATEGY_RRMEMORY:
863 if (pos < q->rrpos) {
864 tmp->metric = 1000 + pos;
866 if (pos > q->rrpos) {
867 /* Indicate there is another priority */
872 tmp->metric += mem->penalty * 1000000;
874 case QUEUE_STRATEGY_RANDOM:
875 tmp->metric = rand() % 1000;
876 tmp->metric += mem->penalty * 1000000;
878 case QUEUE_STRATEGY_FEWESTCALLS:
879 tmp->metric = mem->calls;
880 tmp->metric += mem->penalty * 1000000;
882 case QUEUE_STRATEGY_LEASTRECENT:
886 tmp->metric = 1000000 - (time(NULL) - mem->lastcall);
887 tmp->metric += mem->penalty * 1000000;
890 ast_log(LOG_WARNING, "Can't calculate metric for unknown strategy %d\n", q->strategy);
896 static int try_calling(struct queue_ent *qe, char *options, char *announceoverride, char *url, int *go_on)
899 struct localuser *outgoing=NULL, *tmp = NULL;
902 int allowredir_out=0;
903 int allowdisconnect=0;
904 char restofit[AST_MAX_EXTENSION];
905 char oldexten[AST_MAX_EXTENSION]="";
906 char oldcontext[AST_MAX_EXTENSION]="";
907 char queuename[256]="";
909 char *monitorfilename;
910 struct ast_channel *peer;
911 struct localuser *lpeer;
912 struct member *member;
913 int res = 0, bridge = 0;
916 char *announce = NULL;
920 struct ast_bridge_config config;
921 /* Hold the lock while we setup the outgoing calls */
922 ast_mutex_lock(&qe->parent->lock);
923 strncpy(queuename, qe->parent->name, sizeof(queuename) - 1);
925 cur = qe->parent->members;
926 if (!ast_strlen_zero(qe->announce))
927 announce = qe->announce;
928 if (announceoverride && !ast_strlen_zero(announceoverride))
929 announce = announceoverride;
931 /* Get a technology/[device:]number pair */
932 tmp = malloc(sizeof(struct localuser));
934 ast_mutex_unlock(&qe->parent->lock);
935 ast_log(LOG_WARNING, "Out of memory\n");
938 memset(tmp, 0, sizeof(struct localuser));
939 tmp->stillgoing = -1;
941 if (strchr(options, 't'))
942 tmp->allowredirect_in = 1;
943 if (strchr(options, 'T'))
944 tmp->allowredirect_out = 1;
945 if (strchr(options, 'r'))
946 tmp->ringbackonly = 1;
947 if (strchr(options, 'm'))
948 tmp->musiconhold = 1;
949 if (strchr(options, 'd'))
950 tmp->dataquality = 1;
951 if (strchr(options, 'H'))
952 tmp->allowdisconnect = 1;
953 if ((strchr(options, 'n')) && (now - qe->start >= qe->parent->timeout))
957 ast_log(LOG_DEBUG, "Queue with URL=%s_\n", url);
959 ast_log(LOG_DEBUG, "Simple queue (no URL)\n");
961 tmp->member = cur; /* Never directly dereference! Could change on reload */
962 strncpy(tmp->tech, cur->tech, sizeof(tmp->tech)-1);
963 strncpy(tmp->numsubst, cur->loc, sizeof(tmp->numsubst)-1);
964 /* If we're dialing by extension, look at the extension to know what to dial */
965 if ((newnum = strstr(tmp->numsubst, "BYEXTENSION"))) {
966 strncpy(restofit, newnum + strlen("BYEXTENSION"), sizeof(restofit)-1);
967 snprintf(newnum, sizeof(tmp->numsubst) - (newnum - tmp->numsubst), "%s%s", qe->chan->exten,restofit);
969 ast_log(LOG_DEBUG, "Dialing by extension %s\n", tmp->numsubst);
971 /* Special case: If we ring everyone, go ahead and ring them, otherwise
972 just calculate their metric for the appropriate strategy */
973 calc_metric(qe->parent, cur, x++, qe, tmp);
974 /* Put them in the list of outgoing thingies... We're ready now.
975 XXX If we're forcibly removed, these outgoing calls won't get
977 tmp->next = outgoing;
979 /* If this line is up, don't try anybody else */
980 if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP))
985 if (qe->parent->timeout)
986 to = qe->parent->timeout * 1000;
989 ring_one(qe, outgoing);
990 ast_mutex_unlock(&qe->parent->lock);
991 lpeer = wait_for_answer(qe, outgoing, &to, &allowredir_in, &allowredir_out, &allowdisconnect, &digit);
992 ast_mutex_lock(&qe->parent->lock);
993 if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) {
994 store_next(qe, outgoing);
996 ast_mutex_unlock(&qe->parent->lock);
1003 /* Musta gotten hung up */
1004 record_abandoned(qe);
1007 if (digit && valid_exit(qe, digit))
1010 /* Nobody answered, next please? */
1016 /* Ah ha! Someone answered within the desired timeframe. Of course after this
1017 we will always return with -1 so that it is hung up properly after the
1020 if (!strcmp(qe->chan->type,"Zap")) {
1021 if (tmp->dataquality) zapx = 0;
1022 ast_channel_setoption(qe->chan,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0);
1024 if (!strcmp(peer->type,"Zap")) {
1025 if (tmp->dataquality) zapx = 0;
1026 ast_channel_setoption(peer,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0);
1028 /* Update parameters for the queue */
1029 recalc_holdtime(qe);
1030 member = lpeer->member;
1031 hanguptree(outgoing, peer);
1035 res2 = ast_autoservice_start(qe->chan);
1037 res2 = ast_streamfile(peer, announce, peer->language);
1039 res2 = ast_waitstream(peer, "");
1041 ast_log(LOG_WARNING, "Announcement file '%s' is unavailable, continuing anyway...\n", announce);
1045 res2 |= ast_autoservice_stop(qe->chan);
1047 /* Agent must have hung up */
1048 ast_log(LOG_WARNING, "Agent on %s hungup on the customer. They're going to be pissed.\n", peer->name);
1049 ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "AGENTDUMP", "%s", "");
1054 /* Stop music on hold */
1055 ast_moh_stop(qe->chan);
1056 /* If appropriate, log that we have a destination channel */
1058 ast_cdr_setdestchan(qe->chan->cdr, peer->name);
1059 /* Make sure channels are compatible */
1060 res = ast_channel_make_compatible(qe->chan, peer);
1062 ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "SYSCOMPAT", "%s", "");
1063 ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", qe->chan->name, peer->name);
1067 /* Begin Monitoring */
1068 if (qe->parent->monfmt && *qe->parent->monfmt) {
1069 monitorfilename = pbx_builtin_getvar_helper( qe->chan, "MONITOR_FILENAME");
1070 if(monitorfilename) {
1071 ast_monitor_start( peer, qe->parent->monfmt, monitorfilename, 1 );
1073 ast_monitor_start( peer, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1 );
1075 if(qe->parent->monjoin) {
1076 ast_monitor_setjoinfiles( peer, 1);
1079 /* Drop out of the queue at this point, to prepare for next caller */
1081 if( url && !ast_strlen_zero(url) && ast_channel_supports_html(peer) ) {
1082 ast_log(LOG_DEBUG, "app_queue: sendurl=%s.\n", url);
1083 ast_channel_sendurl( peer, url );
1085 ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "CONNECT", "%ld", (long)time(NULL) - qe->start);
1086 strncpy(oldcontext, qe->chan->context, sizeof(oldcontext) - 1);
1087 strncpy(oldexten, qe->chan->exten, sizeof(oldexten) - 1);
1090 memset(&config,0,sizeof(struct ast_bridge_config));
1091 config.allowredirect_in = allowredir_in;
1092 config.allowredirect_out = allowredir_out;
1093 config.allowdisconnect = allowdisconnect;
1094 bridge = ast_bridge_call(qe->chan,peer,&config);
1096 if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) {
1097 ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "TRANSFER", "%s|%s", qe->chan->exten, qe->chan->context);
1098 } else if (qe->chan->_softhangup) {
1099 ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "COMPLETECALLER", "%ld|%ld", (long)(callstart - qe->start), (long)(time(NULL) - callstart));
1101 ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "COMPLETEAGENT", "%ld|%ld", (long)(callstart - qe->start), (long)(time(NULL) - callstart));
1104 if(bridge != AST_PBX_NO_HANGUP_PEER)
1106 update_queue(qe->parent, member);
1107 if( bridge == 0 ) res=1; /* JDG: bridge successfull, leave app_queue */
1108 else res = bridge; /* bridge error, stay in the queue */
1111 hanguptree(outgoing, NULL);
1115 static int wait_a_bit(struct queue_ent *qe)
1117 /* Don't need to hold the lock while we setup the outgoing calls */
1118 int retrywait = qe->parent->retry * 1000;
1119 return ast_waitfordigit(qe->chan, retrywait);
1124 static struct member * interface_exists( struct ast_call_queue * q, char * interface )
1126 struct member * ret = NULL ;
1134 while( mem != NULL ) {
1135 sprintf( buf, "%s/%s", mem->tech, mem->loc);
1137 if( strcmp( buf, interface ) == 0 ) {
1150 static struct member * create_queue_node( char * interface )
1152 struct member * cur ;
1155 /* Add a new member */
1157 cur = malloc(sizeof(struct member));
1160 memset(cur, 0, sizeof(struct member));
1161 strncpy(cur->tech, interface, sizeof(cur->tech) - 1);
1162 if ((tmp = strchr(cur->tech, '/')))
1164 if ((tmp = strchr(interface, '/'))) {
1166 strncpy(cur->loc, tmp, sizeof(cur->loc) - 1);
1168 ast_log(LOG_WARNING, "No location at interface '%s'\n", interface);
1175 static int rqm_exec(struct ast_channel *chan, void *data)
1178 struct localuser *u;
1180 struct member * node ;
1181 struct member * look ;
1183 char tmpchan[256]="";
1184 char *interface=NULL;
1185 struct ast_call_queue *q;
1189 ast_log(LOG_WARNING, "RemoveQueueMember requires an argument (queuename|optional interface)\n");
1193 LOCAL_USER_ADD(u); // not sure if we need this, but better be safe than sorry ;-)
1195 /* Parse our arguments XXX Check for failure XXX */
1196 strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
1199 interface = strchr(queuename, '|');
1205 strncpy(tmpchan, chan->name, sizeof(tmpchan) - 1);
1206 interface = strrchr(tmpchan, '-');
1209 interface = tmpchan;
1213 if( ( q = queues) != NULL )
1215 while( q && ( res != 0 ) && (!found) )
1217 ast_mutex_lock(&q->lock);
1218 if( strcmp( q->name, queuename) == 0 )
1220 // found queue, try to remove interface
1223 if( ( node = interface_exists( q, interface ) ) != NULL )
1225 if( ( look = q->members ) == node )
1228 q->members = node->next;
1232 while( look != NULL )
1233 if( look->next == node )
1235 look->next = node->next ;
1244 ast_log(LOG_NOTICE, "Removed interface '%s' to queue '%s'\n",
1245 interface, queuename);
1250 ast_log(LOG_WARNING, "Unable to remove interface '%s' from queue '%s': "
1251 "Not there\n", interface, queuename);
1252 if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + 101, chan->callerid))
1254 chan->priority += 100;
1260 ast_mutex_unlock(&q->lock);
1266 ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': No such queue\n", queuename);
1268 LOCAL_USER_REMOVE(u);
1274 static int aqm_exec(struct ast_channel *chan, void *data)
1277 struct localuser *u;
1280 char tmpchan[512]="";
1281 char *interface=NULL;
1282 struct ast_call_queue *q;
1283 struct member *save;
1287 ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename|optional interface)\n");
1291 LOCAL_USER_ADD(u); // not sure if we need this, but better be safe than sorry ;-)
1293 /* Parse our arguments XXX Check for failure XXX */
1294 strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
1297 interface = strchr(queuename, '|');
1303 strncpy(tmpchan, chan->name, sizeof(tmpchan) - 1);
1304 interface = strrchr(tmpchan, '-');
1307 interface = tmpchan;
1311 if( ( q = queues) != NULL )
1313 while( q && ( res != 0 ) && (!found) )
1315 ast_mutex_lock(&q->lock);
1316 if( strcmp( q->name, queuename) == 0 )
1318 // found queue, try to enable interface
1321 if( interface_exists( q, interface ) == NULL )
1324 q->members = create_queue_node( interface ) ;
1326 if( q->members != NULL ) {
1327 q->members->dynamic = 1;
1328 q->members->next = save ;
1332 ast_log(LOG_NOTICE, "Added interface '%s' to queue '%s'\n", interface, queuename);
1337 ast_log(LOG_WARNING, "Unable to add interface '%s' to queue '%s': "
1338 "Already there\n", interface, queuename);
1339 if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + 101, chan->callerid))
1341 chan->priority += 100;
1347 ast_mutex_unlock(&q->lock);
1353 ast_log(LOG_WARNING, "Unable to add interface to queue '%s': No such queue\n", queuename);
1355 LOCAL_USER_REMOVE(u);
1360 static int queue_exec(struct ast_channel *chan, void *data)
1364 struct localuser *u;
1367 char *options = NULL;
1369 char *announceoverride = NULL;
1370 char *queuetimeoutstr = NULL;
1371 /* whether to exit Queue application after the timeout hits */
1376 /* Our queue entry */
1377 struct queue_ent qe;
1380 ast_log(LOG_WARNING, "Queue requires an argument (queuename|optional timeout|optional URL)\n");
1386 /* Setup our queue entry */
1387 memset(&qe, 0, sizeof(qe));
1389 /* Parse our arguments XXX Check for failure XXX */
1390 strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
1393 options = strchr(queuename, '|');
1397 url = strchr(options, '|');
1401 announceoverride = strchr(url, '|');
1402 if (announceoverride) {
1403 *announceoverride = '\0';
1405 queuetimeoutstr = strchr(announceoverride, '|');
1406 if (queuetimeoutstr) {
1407 *queuetimeoutstr = '\0';
1409 qe.queuetimeout = atoi(queuetimeoutstr);
1411 qe.queuetimeout = 0;
1419 if (strchr(options, 'r')) {
1424 printf("queue: %s, options: %s, url: %s, announce: %s, timeout: %d\n",
1425 queuename, options, url, announceoverride, qe.queuetimeout);
1429 qe.start = time(NULL);
1430 qe.last_pos_said = 0;
1432 if (!join_queue(queuename, &qe)) {
1433 ast_queue_log(queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", url ? url : "", chan->callerid ? chan->callerid : "");
1434 /* Start music on hold */
1436 ast_indicate(chan, AST_CONTROL_RINGING);
1438 ast_moh_start(chan, qe.moh);
1441 /* This is the wait loop for callers 2 through maxlen */
1443 res = wait_our_turn(&qe, ringing);
1444 /* If they hungup, return immediately */
1446 /* Record this abandoned call */
1447 record_abandoned(&qe);
1448 ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start);
1449 if (option_verbose > 2) {
1450 ast_verbose(VERBOSE_PREFIX_3 "User disconnected while waiting their turn\n");
1457 if (valid_exit(&qe, res)) {
1458 ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
1464 /* This is the wait loop for the head caller*/
1465 /* To exit, they may get their call answered; */
1466 /* they may dial a digit from the queue context; */
1467 /* or, they may may timeout. */
1469 /* Leave if we have exceeded our queuetimeout */
1470 if (qe.queuetimeout && ( (time(NULL) - qe.start) >= qe.queuetimeout) ) {
1475 /* Make a position announcement, if enabled */
1476 if (qe.parent->announcefrequency && !ringing)
1479 /* Try calling all queue members for 'timeout' seconds */
1480 res = try_calling(&qe, options, announceoverride, url, &go_on);
1484 ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start);
1486 ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
1490 /* Leave if we have exceeded our queuetimeout */
1491 if (qe.queuetimeout && ( (time(NULL) - qe.start) >= qe.queuetimeout) ) {
1496 /* OK, we didn't get anybody; wait for 'retry' seconds; may get a digit to exit with */
1497 res = wait_a_bit(&qe);
1499 ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start);
1500 if (option_verbose > 2) {
1501 ast_verbose(VERBOSE_PREFIX_3 "User disconnected when they almost made it\n");
1506 if (res && valid_exit(&qe, res)) {
1507 ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
1510 /* exit after 'timeout' cycle if 'n' option enabled */
1512 if (option_verbose > 2) {
1513 ast_verbose(VERBOSE_PREFIX_3 "Exiting on time-out cycle\n");
1516 ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
1523 /* Don't allow return code > 0 */
1524 if (res > 0 && res != AST_PBX_KEEPALIVE) {
1527 ast_indicate(chan, -1);
1531 ast_stopstream(chan);
1535 ast_log(LOG_WARNING, "Unable to join queue '%s'\n", queuename);
1538 LOCAL_USER_REMOVE(u);
1542 static void reload_queues(void)
1544 struct ast_call_queue *q, *ql, *qn;
1545 struct ast_config *cfg;
1547 struct ast_variable *var;
1548 struct member *prev, *cur;
1550 cfg = ast_load("queues.conf");
1552 ast_log(LOG_NOTICE, "No call queueing config file, so no call queues\n");
1555 ast_mutex_lock(&qlock);
1556 /* Mark all queues as dead for the moment */
1562 /* Chug through config file */
1563 cat = ast_category_browse(cfg, NULL);
1565 if (strcasecmp(cat, "general")) {
1566 /* Look for an existing one */
1569 if (!strcmp(q->name, cat))
1575 q = malloc(sizeof(struct ast_call_queue));
1578 memset(q, 0, sizeof(struct ast_call_queue));
1579 ast_mutex_init(&q->lock);
1580 strncpy(q->name, cat, sizeof(q->name));
1587 ast_mutex_lock(&q->lock);
1588 /* Re-initialize the queue */
1593 q->announcefrequency = 0;
1594 q->announceholdtime = 0;
1596 q->callscompleted = 0;
1597 q->callsabandoned = 0;
1598 q->callscompletedinsl = 0;
1599 q->servicelevel = 0;
1602 strcpy(q->announce, "");
1603 strcpy(q->context, "");
1604 strcpy(q->monfmt, "");
1605 strcpy(q->sound_next, "queue-youarenext");
1606 strcpy(q->sound_thereare, "queue-thereare");
1607 strcpy(q->sound_calls, "queue-callswaiting");
1608 strcpy(q->sound_holdtime, "queue-holdtime");
1609 strcpy(q->sound_minutes, "queue-minutes");
1610 strcpy(q->sound_thanks, "queue-thankyou");
1613 /* find the end of any dynamic members */
1617 var = ast_variable_browse(cfg, cat);
1619 if (!strcasecmp(var->name, "member")) {
1620 /* Add a new member */
1621 cur = malloc(sizeof(struct member));
1623 memset(cur, 0, sizeof(struct member));
1624 strncpy(cur->tech, var->value, sizeof(cur->tech) - 1);
1625 if ((tmp = strchr(cur->tech, ','))) {
1628 cur->penalty = atoi(tmp);
1629 if (cur->penalty < 0)
1632 if ((tmp = strchr(cur->tech, '/')))
1634 if ((tmp = strchr(var->value, '/'))) {
1636 strncpy(cur->loc, tmp, sizeof(cur->loc) - 1);
1637 if ((tmp = strchr(cur->loc, ',')))
1640 ast_log(LOG_WARNING, "No location at line %d of queue.conf\n", var->lineno);
1647 } else if (!strcasecmp(var->name, "music")) {
1648 strncpy(q->moh, var->value, sizeof(q->moh) - 1);
1649 } else if (!strcasecmp(var->name, "announce")) {
1650 strncpy(q->announce, var->value, sizeof(q->announce) - 1);
1651 } else if (!strcasecmp(var->name, "context")) {
1652 strncpy(q->context, var->value, sizeof(q->context) - 1);
1653 } else if (!strcasecmp(var->name, "timeout")) {
1654 q->timeout = atoi(var->value);
1655 } else if (!strcasecmp(var->name, "monitor-join")) {
1656 q->monjoin = ast_true(var->value);
1657 } else if (!strcasecmp(var->name, "monitor-format")) {
1658 strncpy(q->monfmt, var->value, sizeof(q->monfmt) - 1);
1659 } else if (!strcasecmp(var->name, "queue-youarenext")) {
1660 strncpy(q->sound_next, var->value, sizeof(q->sound_next) - 1);
1661 } else if (!strcasecmp(var->name, "queue-thereare")) {
1662 strncpy(q->sound_thereare, var->value, sizeof(q->sound_thereare) - 1);
1663 } else if (!strcasecmp(var->name, "queue-callswaiting")) {
1664 strncpy(q->sound_calls, var->value, sizeof(q->sound_calls) - 1);
1665 } else if (!strcasecmp(var->name, "queue-holdtime")) {
1666 strncpy(q->sound_holdtime, var->value, sizeof(q->sound_holdtime) - 1);
1667 } else if (!strcasecmp(var->name, "queue-minutes")) {
1668 strncpy(q->sound_minutes, var->value, sizeof(q->sound_minutes) - 1);
1669 } else if (!strcasecmp(var->name, "queue-thankyou")) {
1670 strncpy(q->sound_thanks, var->value, sizeof(q->sound_thanks) - 1);
1671 } else if (!strcasecmp(var->name, "announce-frequency")) {
1672 q->announcefrequency = atoi(var->value);
1673 } else if (!strcasecmp(var->name, "announce-holdtime")) {
1674 q->announceholdtime = (!strcasecmp(var->value,"once")) ? 1 : ast_true(var->value);
1675 } else if (!strcasecmp(var->name, "retry")) {
1676 q->retry = atoi(var->value);
1677 } else if (!strcasecmp(var->name, "maxlen")) {
1678 q->maxlen = atoi(var->value);
1679 } else if (!strcasecmp(var->name, "servicelevel")) {
1680 q->servicelevel= atoi(var->value);
1681 } else if (!strcasecmp(var->name, "strategy")) {
1682 q->strategy = strat2int(var->value);
1683 if (q->strategy < 0) {
1684 ast_log(LOG_WARNING, "'%s' isn't a valid strategy, using ringall instead\n", var->value);
1688 ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queue.conf\n", cat, var->name, var->lineno);
1693 q->retry = DEFAULT_RETRY;
1695 q->timeout = DEFAULT_TIMEOUT;
1699 ast_mutex_unlock(&q->lock);
1706 cat = ast_category_browse(cfg, cat);
1721 ast_log(LOG_WARNING, "XXX Leaking a litttle memory :( XXX\n");
1726 ast_mutex_unlock(&qlock);
1729 static int __queues_show(int fd, int argc, char **argv, int queue_show)
1731 struct ast_call_queue *q;
1732 struct queue_ent *qe;
1741 if ((!queue_show && argc != 2) || (queue_show && argc != 3))
1742 return RESULT_SHOWUSAGE;
1743 ast_mutex_lock(&qlock);
1746 ast_mutex_unlock(&qlock);
1748 ast_cli(fd, "No such queue: %s.\n",argv[2]);
1750 ast_cli(fd, "No queues.\n");
1751 return RESULT_SUCCESS;
1754 ast_mutex_lock(&q->lock);
1756 if (strcasecmp(q->name, argv[2]) != 0) {
1757 ast_mutex_unlock(&q->lock);
1760 ast_cli(fd, "No such queue: %s.\n",argv[2]);
1767 snprintf(max, sizeof(max), "%d", q->maxlen);
1769 strcpy(max, "unlimited");
1771 if(q->callscompleted > 0)
1772 sl = 100*((float)q->callscompletedinsl/(float)q->callscompleted);
1773 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",
1774 q->name, q->count, max, int2strat(q->strategy), q->holdtime, q->callscompleted, q->callsabandoned,sl,q->servicelevel);
1776 ast_cli(fd, " Members: \n");
1777 for (mem = q->members; mem; mem = mem->next) {
1779 snprintf(max, sizeof(max) - 20, " with penalty %d", mem->penalty);
1783 strcat(max, " (dynamic)");
1785 snprintf(calls, sizeof(calls), " has taken %d calls (last was %ld secs ago)",
1786 mem->calls, (long)(time(NULL) - mem->lastcall));
1788 strcpy(calls, " has taken no calls yet");
1789 ast_cli(fd, " %s/%s%s%s\n", mem->tech, mem->loc, max, calls);
1792 ast_cli(fd, " No Members\n");
1795 ast_cli(fd, " Callers: \n");
1796 for (qe = q->head; qe; qe = qe->next)
1797 ast_cli(fd, " %d. %s (wait: %ld:%2.2ld)\n", pos++, qe->chan->name,
1798 (long)(now - qe->start) / 60, (long)(now - qe->start) % 60);
1800 ast_cli(fd, " No Callers\n");
1802 ast_mutex_unlock(&q->lock);
1807 ast_mutex_unlock(&qlock);
1808 return RESULT_SUCCESS;
1811 static int queues_show(int fd, int argc, char **argv)
1813 return __queues_show(fd, argc, argv, 0);
1816 static int queue_show(int fd, int argc, char **argv)
1818 return __queues_show(fd, argc, argv, 1);
1821 static char *complete_queue(char *line, char *word, int pos, int state)
1823 struct ast_call_queue *q;
1826 ast_mutex_lock(&qlock);
1829 if (!strncasecmp(word, q->name, strlen(word))) {
1830 if (++which > state)
1835 ast_mutex_unlock(&qlock);
1836 return q ? strdup(q->name) : NULL;
1839 /* JDG: callback to display queues status in manager */
1840 static int manager_queues_show( struct mansession *s, struct message *m )
1842 char *a[] = { "show", "queues" };
1843 return queues_show( s->fd, 2, a );
1847 /* Dump queue status */
1848 static int manager_queues_status( struct mansession *s, struct message *m )
1852 char *id = astman_get_header(m,"ActionID");
1853 char idText[256] = "";
1854 struct ast_call_queue *q;
1855 struct queue_ent *qe;
1858 astman_send_ack(s, m, "Queue status will follow");
1860 ast_mutex_lock(&qlock);
1862 if (id && !ast_strlen_zero(id)) {
1863 snprintf(idText,256,"ActionID: %s\r\n",id);
1866 ast_mutex_lock(&q->lock);
1868 /* List queue properties */
1869 if(q->callscompleted > 0)
1870 sl = 100*((float)q->callscompletedinsl/(float)q->callscompleted);
1871 ast_cli(s->fd, "Event: QueueParams\r\n"
1878 "ServiceLevel: %d\r\n"
1879 "ServicelevelPerf: %2.1f\r\n"
1882 q->name, q->maxlen, q->count, q->holdtime, q->callscompleted,
1883 q->callsabandoned, q->servicelevel, sl, idText);
1885 /* List Queue Members */
1886 for (mem = q->members; mem; mem = mem->next)
1887 ast_cli(s->fd, "Event: QueueMember\r\n"
1889 "Location: %s/%s\r\n"
1890 "Membership: %s\r\n"
1892 "CallsTaken: %d\r\n"
1896 q->name, mem->tech, mem->loc, mem->dynamic ? "dynamic" : "static",
1897 mem->penalty, mem->calls, mem->lastcall, idText);
1899 /* List Queue Entries */
1902 for (qe = q->head; qe; qe = qe->next)
1903 ast_cli(s->fd, "Event: QueueEntry\r\n"
1911 q->name, pos++, qe->chan->name, (qe->chan->callerid ? qe->chan->callerid : ""), (long)(now - qe->start), idText);
1912 ast_mutex_unlock(&q->lock);
1915 ast_mutex_unlock(&qlock);
1916 return RESULT_SUCCESS;
1919 static char show_queues_usage[] =
1920 "Usage: show queues\n"
1921 " Provides summary information on call queues.\n";
1923 static struct ast_cli_entry cli_show_queues = {
1924 { "show", "queues", NULL }, queues_show,
1925 "Show status of queues", show_queues_usage, NULL };
1927 static char show_queue_usage[] =
1928 "Usage: show queue\n"
1929 " Provides summary information on a specified queue.\n";
1931 static struct ast_cli_entry cli_show_queue = {
1932 { "show", "queue", NULL }, queue_show,
1933 "Show status of a specified queue", show_queue_usage, complete_queue };
1935 int unload_module(void)
1937 STANDARD_HANGUP_LOCALUSERS;
1938 ast_cli_unregister(&cli_show_queue);
1939 ast_cli_unregister(&cli_show_queues);
1940 ast_manager_unregister( "Queues" );
1941 ast_manager_unregister( "QueueStatus" );
1942 ast_unregister_application(app_aqm);
1943 ast_unregister_application(app_rqm);
1944 return ast_unregister_application(app);
1947 int load_module(void)
1950 res = ast_register_application(app, queue_exec, synopsis, descrip);
1952 ast_cli_register(&cli_show_queue);
1953 ast_cli_register(&cli_show_queues);
1954 ast_manager_register( "Queues", 0, manager_queues_show, "Queues" );
1955 ast_manager_register( "QueueStatus", 0, manager_queues_status, "Queue Status" );
1958 ast_register_application(app_aqm, aqm_exec, app_aqm_synopsis, app_aqm_descrip) ;
1959 ast_register_application(app_rqm, rqm_exec, app_rqm_synopsis, app_rqm_descrip) ;
1972 char *description(void)
1980 STANDARD_USECOUNT(res);
1986 return ASTERISK_GPL_KEY;