Newer
Older
* Asterisk -- An open source telephony toolkit.
* Copyright (C) 1999 - 2005, Digium, Inc.
* Mark Spencer <markster@digium.com>
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*
*
* True call queues with optional send URL on answer
*
*
* 2004-11-25: Persistent Dynamic Members added by:
* NetNation Communications (www.netnation.com)
* Kevin Lindsay <kevinl@netnation.com>
*
* Each dynamic agent in each queue is now stored in the astdb.
* When asterisk is restarted, each agent will be automatically
* readded into their recorded queues. This feature can be
Mark Spencer
committed
* configured with the 'persistent_members=<1|0>' setting in the
* '[general]' category in queues.conf. The default is on.
* 2004-06-04: Priorities in queues added by inAccess Networks (work funded by Hellas On Line (HOL) www.hol.gr).
*
* 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>
James Golovich
committed
* Added Priority jumping code for adding and removing queue members by Jonathan Stanton <asterisk@doilooklikeicare.com>
* Fixed to 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
*/
#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 "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
Kevin P. Fleming
committed
#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/features.h"
#include "asterisk/musiconhold.h"
#include "asterisk/cli.h"
#include "asterisk/manager.h"
#include "asterisk/config.h"
#include "asterisk/monitor.h"
#include "asterisk/utils.h"
#include "asterisk/causes.h"
#include "asterisk/astdb.h"
Kevin P. Fleming
committed
#include "asterisk/devicestate.h"
#define QUEUE_STRATEGY_RINGALL 0
#define QUEUE_STRATEGY_ROUNDROBIN 1
#define QUEUE_STRATEGY_LEASTRECENT 2
#define QUEUE_STRATEGY_FEWESTCALLS 3
#define QUEUE_STRATEGY_RANDOM 4
#define QUEUE_STRATEGY_RRMEMORY 5
static struct strategy {
int strategy;
char *name;
} strategies[] = {
{ QUEUE_STRATEGY_RINGALL, "ringall" },
{ QUEUE_STRATEGY_ROUNDROBIN, "roundrobin" },
{ QUEUE_STRATEGY_LEASTRECENT, "leastrecent" },
{ QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" },
{ QUEUE_STRATEGY_RANDOM, "random" },
{ QUEUE_STRATEGY_RRMEMORY, "rrmemory" },
};
#define DEFAULT_RETRY 5
#define DEFAULT_TIMEOUT 15
#define RECHECK 1 /* Recheck every second to see we we're at the top yet */
#define RES_OKAY 0 /* Action completed */
#define RES_EXISTS (-1) /* Entry already exists */
#define RES_OUTOFMEMORY (-2) /* Out of memory */
#define RES_NOSUCHQUEUE (-3) /* No such queue */
static char *tdesc = "True Call Queueing";
static char *app = "Queue";
static char *synopsis = "Queue a call for a call queue";
static char *descrip =
" Queue(queuename[|options[|URL][|announceoverride][|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"
"Returns 0 if the queue is full, nonexistent, 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 callee to hang up by hitting *.\n"
" 'n' -- no retries on the timeout; will exit this application and \n"
" go to the next step.\n"
" 'r' -- ring instead of playing MOH\n"
" In addition to transferring the call, a call may be parked and then picked\n"
" The optional URL will be sent to the called party if the channel supports\n"
"it.\n"
" The timeout will cause the queue to fail out after a specified number of\n"
Mark Spencer
committed
"seconds, checked between each queues.conf 'timeout' and 'retry' cycle.\n"
" This application sets the following channel variable upon completion:\n"
" QUEUESTATUS The status of the call as a text string, one of\n"
" TIMEOUT | FULL | JOINEMPTY | LEAVEEMPTY | JOINUNAVAIL | LEAVEUNAVAIL\n";
static char *app_aqm = "AddQueueMember" ;
static char *app_aqm_synopsis = "Dynamically adds queue members" ;
static char *app_aqm_descrip =
" AddQueueMember(queuename[|interface[|penalty]]):\n"
James Golovich
committed
"Dynamically adds interface to an existing queue.\n"
"If the interface is already in the queue and there exists an n+101 priority\n"
"then it will then jump to this priority. Otherwise it will return an error\n"
"Returns -1 if there is an error.\n"
"Example: AddQueueMember(techsupport|SIP/3000)\n"
"";
static char *app_rqm = "RemoveQueueMember" ;
static char *app_rqm_synopsis = "Dynamically removes queue members" ;
static char *app_rqm_descrip =
" RemoveQueueMember(queuename[|interface]):\n"
"Dynamically removes interface to an existing queue\n"
James Golovich
committed
"If the interface is NOT in the queue and there exists an n+101 priority\n"
"then it will then jump to this priority. Otherwise it will return an error\n"
"Returns -1 if there is an error.\n"
"Example: RemoveQueueMember(techsupport|SIP/3000)\n"
"";
static char *app_pqm = "PauseQueueMember" ;
static char *app_pqm_synopsis = "Pauses a queue member" ;
static char *app_pqm_descrip =
" PauseQueueMember([queuename]|interface):\n"
"Pauses (blocks calls for) a queue member.\n"
"The given interface will be paused in the given queue. This prevents\n"
"any calls from being sent from the queue to the interface until it is\n"
"unpaused with UnpauseQueueMember or the manager interface. If no\n"
"queuename is given, the interface is paused in every queue it is a\n"
"member of. If the interface is not in the named queue, or if no queue\n"
"is given and the interface is not in any queue, it will jump to\n"
" priority n+101, if it exists. Returns -1 if the interface is not\n"
"found and no extension to jump to exists, 0 otherwise.\n"
"Example: PauseQueueMember(|SIP/3000)\n";
static char *app_upqm = "UnpauseQueueMember" ;
static char *app_upqm_synopsis = "Unpauses a queue member" ;
static char *app_upqm_descrip =
" UnpauseQueueMember([queuename]|interface):\n"
"Unpauses (resumes calls to) a queue member.\n"
"This is the counterpart to PauseQueueMember and operates exactly the\n"
"same way, except it unpauses instead of pausing the given interface.\n"
"Example: UnpauseQueueMember(|SIP/3000)\n";
/* Persistent Members astdb family */
static const char *pm_family = "/Queue/PersistentMembers";
/* The maximum lengh of each persistent member queue database entry */
#define PM_MAX_LEN 2048
Mark Spencer
committed
/* queues.conf [general] option */
static int queue_persistent_members = 0;
Mark Spencer
committed
/* queues.conf per-queue weight option */
static int use_weight = 0;
Mark Spencer
committed
enum queue_result {
QUEUE_UNKNOWN = 0,
QUEUE_TIMEOUT = 1,
QUEUE_JOINEMPTY = 2,
QUEUE_LEAVEEMPTY = 3,
QUEUE_JOINUNAVAIL = 4,
QUEUE_LEAVEUNAVAIL = 5,
QUEUE_FULL = 6,
};
Mark Spencer
committed
const struct {
enum queue_result id;
char *text;
} queue_results[] = {
{ QUEUE_UNKNOWN, "UNKNOWN" },
{ QUEUE_TIMEOUT, "TIMEOUT" },
{ QUEUE_JOINEMPTY,"JOINEMPTY" },
{ QUEUE_LEAVEEMPTY, "LEAVEEMPTY" },
{ QUEUE_JOINUNAVAIL, "JOINUNAVAIL" },
{ QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" },
{ QUEUE_FULL, "FULL" },
};
/* We define a custom "local user" structure because we
use it not only for keeping track of what is in use but
also for keeping track of who we're dialing. */
struct localuser {
struct ast_channel *chan;
char interface[256];
Mark Spencer
committed
int oldstatus;
struct member *member;
struct localuser *next;
};
LOCAL_USER_DECL;
struct queue_ent {
struct ast_call_queue *parent; /* What queue is our parent */
char moh[80]; /* Name of musiconhold to be used */
char announce[80]; /* Announcement to play for member when call is answered */
char context[AST_MAX_CONTEXT]; /* Context when user exits queue */
Kevin P. Fleming
committed
char digits[AST_MAX_EXTENSION]; /* Digits entered while in queue */
int pos; /* Where we are in the queue */
int prio; /* Our priority */
int last_pos_said; /* Last position we told the user */
time_t last_periodic_announce_time; /* The last time we played a periodic anouncement */
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 */
time_t expire; /* When this entry should expire (time out of queue) */
struct ast_channel *chan; /* Our channel */
struct queue_ent *next; /* The next queue entry */
};
struct member {
char interface[80]; /* Technology/Location */
int penalty; /* Are we a last resort? */
int calls; /* Number of calls serviced by this member */
int dynamic; /* Are we dynamically added? */
int status; /* Status of queue member */
int paused; /* Are we paused (not accepting calls)? */
time_t lastcall; /* When last successful call was hungup */
int dead; /* Used to detect members deleted in realtime */
Mark Spencer
committed
/* values used in multi-bit flags in ast_call_queue */
#define QUEUE_EMPTY_NORMAL 1
#define QUEUE_EMPTY_STRICT 2
#define ANNOUNCEHOLDTIME_ALWAYS 1
#define ANNOUNCEHOLDTIME_ONCE 2
Mark Spencer
committed
ast_mutex_t lock;
char name[80]; /* Name */
char moh[80]; /* Music On Hold class to be used */
char announce[80]; /* Announcement to play when call is answered */
char context[AST_MAX_CONTEXT]; /* Exit context */
Mark Spencer
committed
unsigned int monjoin:1;
unsigned int dead:1;
unsigned int joinempty:2;
unsigned int eventwhencalled:1;
unsigned int leavewhenempty:2;
unsigned int reportholdtime:1;
unsigned int wrapped:1;
unsigned int timeoutrestart:1;
unsigned int announceholdtime:2;
unsigned int strategy:3;
unsigned int maskmemberstatus:1;
unsigned int realtime:1;
int announcefrequency; /* How often to announce their position */
int periodicannouncefrequency; /* How often to play periodic announcement */
int roundingseconds; /* How many seconds do we round to? */
Mark Spencer
committed
int holdtime; /* Current avg holdtime, 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*/
Mark Spencer
committed
int callscompletedinsl; /* Number of calls answered 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) */
Mark Spencer
committed
char sound_lessthan[80]; /* Sound file: "less-than" (def. queue-lessthan) */
char sound_seconds[80]; /* Sound file: "seconds." (def. queue-seconds) */
char sound_thanks[80]; /* Sound file: "Thank you for your patience." (def. queue-thankyou) */
char sound_reporthold[80]; /* Sound file: "Hold time" (def. queue-reporthold) */
char sound_periodicannounce[80];/* Sound file: Custom announce, no default */
Mark Spencer
committed
int count; /* How many entries */
int maxlen; /* Max number of entries */
int wrapuptime; /* Wrapup Time */
int retry; /* Retry calling everyone after this amount of time */
Mark Spencer
committed
int weight; /* Respective weight */
/* Queue strategy things */
int rrpos; /* Round Robin - position */
int memberdelay; /* Seconds to delay connecting member to caller */
Mark Spencer
committed
struct member *members; /* Head of the list of members */
struct queue_ent *head; /* Head of the list of callers */
struct ast_call_queue *next; /* Next call queue */
};
static struct ast_call_queue *queues = NULL;
Mark Spencer
committed
static void set_queue_result(struct ast_channel *chan, enum queue_result res)
{
int i;
for (i = 0; i < sizeof(queue_results) / sizeof(queue_results[0]); i++) {
if (queue_results[i].id == res) {
pbx_builtin_setvar_helper(chan, "QUEUESTATUS", queue_results[i].text);
return;
}
}
}
static char *int2strat(int strategy)
{
int x;
for (x=0;x<sizeof(strategies) / sizeof(strategies[0]);x++) {
if (strategy == strategies[x].strategy)
return strategies[x].name;
}
return "<unknown>";
}
static int strat2int(const char *strategy)
{
int x;
for (x=0;x<sizeof(strategies) / sizeof(strategies[0]);x++) {
if (!strcasecmp(strategy, strategies[x].name))
return strategies[x].strategy;
}
return -1;
}
/* Insert the 'new' entry after the 'prev' entry of queue 'q' */
Mark Spencer
committed
static inline void insert_entry(struct ast_call_queue *q, struct queue_ent *prev, struct queue_ent *new, int *pos)
{
struct queue_ent *cur;
if (!q || !new)
return;
if (prev) {
cur = prev->next;
prev->next = new;
} else {
cur = q->head;
q->head = new;
}
new->next = cur;
new->parent = q;
new->pos = ++(*pos);
new->opos = *pos;
}
Mark Spencer
committed
enum queue_member_status {
QUEUE_NO_MEMBERS,
QUEUE_NO_REACHABLE_MEMBERS,
QUEUE_NORMAL
};
static enum queue_member_status get_member_status(const struct ast_call_queue *q)
Mark Spencer
committed
{
struct member *member;
Mark Spencer
committed
enum queue_member_status result = QUEUE_NO_MEMBERS;
for (member = q->members; member; member = member->next) {
switch (member->status) {
case AST_DEVICE_INVALID:
Mark Spencer
committed
/* nothing to do */
break;
case AST_DEVICE_UNAVAILABLE:
result = QUEUE_NO_REACHABLE_MEMBERS;
Mark Spencer
committed
break;
default:
Mark Spencer
committed
return QUEUE_NORMAL;
Mark Spencer
committed
}
}
Mark Spencer
committed
return result;
Mark Spencer
committed
}
struct statechange {
int state;
char dev[0];
};
static void *changethread(void *data)
{
struct ast_call_queue *q;
struct statechange *sc = data;
struct member *cur;
char *loc;
technology = ast_strdupa(sc->dev);
loc = strchr(technology, '/');
if (loc) {
*loc = '\0';
loc++;
} else {
free(sc);
return NULL;
}
if (option_debug)
ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d' (%s)\n", technology, loc, sc->state, devstate2str(sc->state));
ast_mutex_lock(&qlock);
for (q = queues; q; q = q->next) {
ast_mutex_lock(&q->lock);
cur = q->members;
while(cur) {
if (!strcasecmp(sc->dev, cur->interface)) {
if (cur->status != sc->state) {
cur->status = sc->state;
if (!q->maskmemberstatus) {
manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus",
"Queue: %s\r\n"
"Location: %s\r\n"
"Membership: %s\r\n"
"Penalty: %d\r\n"
"CallsTaken: %d\r\n"
"LastCall: %ld\r\n"
"Status: %d\r\n"
"Paused: %d\r\n",
q->name, cur->interface, cur->dynamic ? "dynamic" : "static",
cur->penalty, cur->calls, cur->lastcall, cur->status, cur->paused);
}
}
}
cur = cur->next;
}
ast_mutex_unlock(&q->lock);
}
ast_mutex_unlock(&qlock);
free(sc);
return NULL;
}
static int statechange_queue(const char *dev, int state, void *ign)
{
/* Avoid potential for deadlocks by spawning a new thread to handle
the event */
struct statechange *sc;
pthread_t t;
pthread_attr_t attr;
sc = malloc(sizeof(struct statechange) + strlen(dev) + 1);
if (sc) {
sc->state = state;
strcpy(sc->dev, dev);
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (ast_pthread_create(&t, &attr, changethread, sc)) {
ast_log(LOG_WARNING, "Failed to create update thread!\n");
free(sc);
}
}
return 0;
}
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
static struct member *create_queue_member(char *interface, int penalty, int paused)
{
struct member *cur;
/* Add a new member */
cur = malloc(sizeof(struct member));
if (cur) {
memset(cur, 0, sizeof(struct member));
cur->penalty = penalty;
cur->paused = paused;
ast_copy_string(cur->interface, interface, sizeof(cur->interface));
if (!strchr(cur->interface, '/'))
ast_log(LOG_WARNING, "No location at interface '%s'\n", interface);
cur->status = ast_device_state(interface);
}
return cur;
}
static struct ast_call_queue *alloc_queue(const char *queuename)
{
struct ast_call_queue *q;
q = malloc(sizeof(*q));
if (q) {
memset(q, 0, sizeof(*q));
ast_mutex_init(&q->lock);
ast_copy_string(q->name, queuename, sizeof(q->name));
}
return q;
}
static void init_queue(struct ast_call_queue *q)
{
q->dead = 0;
q->retry = DEFAULT_RETRY;
q->timeout = -1;
q->maxlen = 0;
q->announcefrequency = 0;
q->announceholdtime = 0;
q->roundingseconds = 0; /* Default - don't announce seconds */
q->servicelevel = 0;
q->moh[0] = '\0';
q->announce[0] = '\0';
q->context[0] = '\0';
q->monfmt[0] = '\0';
q->periodicannouncefrequency = 0;
ast_copy_string(q->sound_next, "queue-youarenext", sizeof(q->sound_next));
ast_copy_string(q->sound_thereare, "queue-thereare", sizeof(q->sound_thereare));
ast_copy_string(q->sound_calls, "queue-callswaiting", sizeof(q->sound_calls));
ast_copy_string(q->sound_holdtime, "queue-holdtime", sizeof(q->sound_holdtime));
ast_copy_string(q->sound_minutes, "queue-minutes", sizeof(q->sound_minutes));
ast_copy_string(q->sound_seconds, "queue-seconds", sizeof(q->sound_seconds));
ast_copy_string(q->sound_thanks, "queue-thankyou", sizeof(q->sound_thanks));
ast_copy_string(q->sound_lessthan, "queue-less-than", sizeof(q->sound_lessthan));
ast_copy_string(q->sound_reporthold, "queue-reporthold", sizeof(q->sound_reporthold));
ast_copy_string(q->sound_periodicannounce, "queue-periodic-announce", sizeof(q->sound_periodicannounce));
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
}
static void clear_queue(struct ast_call_queue *q)
{
q->holdtime = 0;
q->callscompleted = 0;
q->callsabandoned = 0;
q->callscompletedinsl = 0;
q->wrapuptime = 0;
}
/* Configure a queue parameter.
For error reporting, line number is passed for .conf static configuration.
For Realtime queues, linenum is -1.
The failunknown flag is set for config files (and static realtime) to show
errors for unknown parameters. It is cleared for dynamic realtime to allow
extra fields in the tables. */
static void queue_set_param(struct ast_call_queue *q, const char *param, const char *val, int linenum, int failunknown)
{
if (!strcasecmp(param, "music") || !strcasecmp(param, "musiconhold")) {
ast_copy_string(q->moh, val, sizeof(q->moh));
} else if (!strcasecmp(param, "announce")) {
ast_copy_string(q->announce, val, sizeof(q->announce));
} else if (!strcasecmp(param, "context")) {
ast_copy_string(q->context, val, sizeof(q->context));
} else if (!strcasecmp(param, "timeout")) {
q->timeout = atoi(val);
if (q->timeout < 0)
q->timeout = DEFAULT_TIMEOUT;
} else if (!strcasecmp(param, "monitor-join")) {
q->monjoin = ast_true(val);
} else if (!strcasecmp(param, "monitor-format")) {
ast_copy_string(q->monfmt, val, sizeof(q->monfmt));
} else if (!strcasecmp(param, "queue-youarenext")) {
ast_copy_string(q->sound_next, val, sizeof(q->sound_next));
} else if (!strcasecmp(param, "queue-thereare")) {
ast_copy_string(q->sound_thereare, val, sizeof(q->sound_thereare));
} else if (!strcasecmp(param, "queue-callswaiting")) {
ast_copy_string(q->sound_calls, val, sizeof(q->sound_calls));
} else if (!strcasecmp(param, "queue-holdtime")) {
ast_copy_string(q->sound_holdtime, val, sizeof(q->sound_holdtime));
} else if (!strcasecmp(param, "queue-minutes")) {
ast_copy_string(q->sound_minutes, val, sizeof(q->sound_minutes));
} else if (!strcasecmp(param, "queue-seconds")) {
ast_copy_string(q->sound_seconds, val, sizeof(q->sound_seconds));
} else if (!strcasecmp(param, "queue-lessthan")) {
ast_copy_string(q->sound_lessthan, val, sizeof(q->sound_lessthan));
} else if (!strcasecmp(param, "queue-thankyou")) {
ast_copy_string(q->sound_thanks, val, sizeof(q->sound_thanks));
} else if (!strcasecmp(param, "queue-reporthold")) {
ast_copy_string(q->sound_reporthold, val, sizeof(q->sound_reporthold));
} else if (!strcasecmp(param, "announce-frequency")) {
q->announcefrequency = atoi(val);
} else if (!strcasecmp(param, "announce-round-seconds")) {
q->roundingseconds = atoi(val);
if (q->roundingseconds>60 || q->roundingseconds<0) {
if (linenum >= 0) {
ast_log(LOG_WARNING, "'%s' isn't a valid value for %s "
"using 0 instead for queue '%s' at line %d of queues.conf\n",
val, param, q->name, linenum);
} else {
ast_log(LOG_WARNING, "'%s' isn't a valid value for %s "
"using 0 instead for queue '%s'\n", val, param, q->name);
}
q->roundingseconds=0;
}
} else if (!strcasecmp(param, "announce-holdtime")) {
if (!strcasecmp(val, "once"))
q->announceholdtime = ANNOUNCEHOLDTIME_ONCE;
else if (ast_true(val))
q->announceholdtime = ANNOUNCEHOLDTIME_ALWAYS;
else
q->announceholdtime = 0;
} else if (!strcasecmp(param, "periodic-announce")) {
ast_copy_string(q->sound_periodicannounce, val, sizeof(q->sound_periodicannounce));
} else if (!strcasecmp(param, "periodic-announce-frequency")) {
q->periodicannouncefrequency = atoi(val);
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
} else if (!strcasecmp(param, "retry")) {
q->retry = atoi(val);
if (q->retry < 0)
q->retry = DEFAULT_RETRY;
} else if (!strcasecmp(param, "wrapuptime")) {
q->wrapuptime = atoi(val);
} else if (!strcasecmp(param, "maxlen")) {
q->maxlen = atoi(val);
if (q->maxlen < 0)
q->maxlen = 0;
} else if (!strcasecmp(param, "servicelevel")) {
q->servicelevel= atoi(val);
} else if (!strcasecmp(param, "strategy")) {
q->strategy = strat2int(val);
if (q->strategy < 0) {
ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n",
val, q->name);
q->strategy = 0;
}
} else if (!strcasecmp(param, "joinempty")) {
if (!strcasecmp(val, "strict"))
q->joinempty = QUEUE_EMPTY_STRICT;
else if (ast_true(val))
q->joinempty = QUEUE_EMPTY_NORMAL;
else
q->joinempty = 0;
} else if (!strcasecmp(param, "leavewhenempty")) {
if (!strcasecmp(val, "strict"))
q->leavewhenempty = QUEUE_EMPTY_STRICT;
else if (ast_true(val))
q->leavewhenempty = QUEUE_EMPTY_NORMAL;
else
q->leavewhenempty = 0;
} else if (!strcasecmp(param, "eventmemberstatus")) {
q->maskmemberstatus = !ast_true(val);
} else if (!strcasecmp(param, "eventwhencalled")) {
q->eventwhencalled = ast_true(val);
} else if (!strcasecmp(param, "reportholdtime")) {
q->reportholdtime = ast_true(val);
} else if (!strcasecmp(param, "memberdelay")) {
q->memberdelay = atoi(val);
} else if (!strcasecmp(param, "weight")) {
q->weight = atoi(val);
if (q->weight)
use_weight++;
/* With Realtime queues, if the last queue using weights is deleted in realtime,
we will not see any effect on use_weight until next reload. */
} else if (!strcasecmp(param, "timeoutrestart")) {
q->timeoutrestart = ast_true(val);
} else if(failunknown) {
if (linenum >= 0) {
ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queues.conf\n",
q->name, param, linenum);
} else {
ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s\n", q->name, param);
}
}
}
static void rt_handle_member_record(struct ast_call_queue *q, char *interface, const char *penalty_str)
{
struct member *m, *prev_m;
int penalty = 0;
if(penalty_str) {
penalty = atoi(penalty_str);
if(penalty < 0)
penalty = 0;
}
/* Find the member, or the place to put a new one. */
prev_m = NULL;
m = q->members;
while (m && strcmp(m->interface, interface)) {
prev_m = m;
m = m->next;
}
/* Create a new one if not found, else update penalty */
if (!m) {
m = create_queue_member(interface, penalty, 0);
if (m) {
m->dead = 0;
if (prev_m) {
prev_m->next = m;
} else {
q->members = m;
}
}
} else {
m->dead = 0; /* Do not delete this one. */
m->penalty = penalty;
}
}
/* Reload a single queue via realtime. Return the queue, or NULL if it doesn't exist.
Should be called with the global qlock locked.
When found, the queue is returned with q->lock locked. */
static struct ast_call_queue *reload_queue_rt(const char *queuename, struct ast_variable *queue_vars, struct ast_config *member_config)
{
struct ast_variable *v;
struct ast_call_queue *q, *prev_q;
struct member *m, *prev_m, *next_m;
char *interface;
char *tmp, *tmp_name;
char tmpbuf[64]; /* Must be longer than the longest queue param name. */
/* Find the queue in the in-core list (we will create a new one if not found). */
q = queues;
prev_q = NULL;
while (q) {
if (!strcasecmp(q->name, queuename)) {
break;
}
q = q->next;
prev_q = q;
}
/* Static queues override realtime. */
if (q) {
ast_mutex_lock(&q->lock);
if (!q->realtime) {
if (q->dead) {
ast_mutex_unlock(&q->lock);
return NULL;
} else {
return q;
}
}
}
/* Check if queue is defined in realtime. */
if (!queue_vars) {
/* Delete queue from in-core list if it has been deleted in realtime. */
if (q) {
/* Hmm, can't seem to distinguish a DB failure from a not
found condition... So we might delete an in-core queue
in case of DB failure. */
ast_log(LOG_DEBUG, "Queue %s not found in realtime.\n", queuename);
q->dead = 1;
/* Delete if unused (else will be deleted when last caller leaves). */
if (!q->count) {
/* Delete. */
if (!prev_q) {
queues = q->next;
} else {
prev_q->next = q->next;
}
ast_mutex_unlock(&q->lock);
free(q);
} else
ast_mutex_unlock(&q->lock);
}
return NULL;
}
/* Create a new queue if an in-core entry does not exist yet. */
if (!q) {
q = alloc_queue(queuename);
if (!q)
return NULL;
ast_mutex_lock(&q->lock);
clear_queue(q);
q->realtime = 1;
q->next = queues;
queues = q;
}
init_queue(q); /* Ensure defaults for all parameters not set explicitly. */
v = queue_vars;
memset(tmpbuf, 0, sizeof(tmpbuf));
while(v) {
/* Convert to dashes `-' from underscores `_' as the latter are more SQL friendly. */
if((tmp = strchr(v->name, '_')) != NULL) {
ast_copy_string(tmpbuf, v->name, sizeof(tmpbuf));
tmp_name = tmpbuf;
tmp = tmp_name;
while((tmp = strchr(tmp, '_')) != NULL)
*tmp++ = '-';
} else
tmp_name = v->name;
queue_set_param(q, tmp_name, v->value, -1, 0);
v = v->next;
}
/* Temporarily set members dead so we can detect deleted ones. */
m = q->members;
while (m) {
m->dead = 1;
m = m->next;
}
interface = ast_category_browse(member_config, NULL);
while (interface) {
rt_handle_member_record(q, interface, ast_variable_retrieve(member_config, interface, "penalty"));
interface = ast_category_browse(member_config, interface);
}
/* Delete all realtime members that have been deleted in DB. */
m = q->members;
prev_m = NULL;
while (m) {
next_m = m->next;
if (m->dead) {
if (prev_m) {
prev_m->next = next_m;
} else {
q->members = next_m;
}
free(m);
} else {
prev_m = m;
}
m = next_m;
}
return q;
}
Mark Spencer
committed
static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason)
struct ast_variable *queue_vars = NULL;
struct ast_config *member_config = NULL;
struct ast_call_queue *q;
struct queue_ent *cur, *prev = NULL;
int res = -1;
int pos = 0;
int inserted = 0;
enum queue_member_status stat;
/* Load from realtime before taking the global qlock, to avoid blocking all
queue operations while waiting for the DB.
This will be two separate database transactions, so we might
see queue parameters as they were before another process
changed the queue and member list as it was after the change.
Thus we might see an empty member list when a queue is
deleted. In practise, this is unlikely to cause a problem. */
queue_vars = ast_load_realtime("queues", "name", queuename, NULL);
if(queue_vars)
member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", queuename, NULL);
Kevin P. Fleming
committed
if (!member_config) {
ast_log(LOG_ERROR, "no queue_members defined in your config (extconfig.conf).\n");
return res;
}
ast_mutex_lock(&qlock);
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
q = reload_queue_rt(queuename, queue_vars, member_config);
/* Note: If found, reload_queue_rt() returns with q->lock locked. */
if(member_config)
ast_config_destroy(member_config);
if(queue_vars)
ast_variables_destroy(queue_vars);
if (!q) {
ast_mutex_unlock(&qlock);
return res;
}
/* This is our one */
stat = get_member_status(q);
if (!q->joinempty && (stat == QUEUE_NO_MEMBERS))
*reason = QUEUE_JOINEMPTY;
else if ((q->joinempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS))
*reason = QUEUE_JOINUNAVAIL;
else if (q->maxlen && (q->count >= q->maxlen))
*reason = QUEUE_FULL;
else {
/* There's space for us, put us at the right position inside
* the queue.
* Take into account the priority of the calling user */
inserted = 0;
prev = NULL;
cur = q->head;
while(cur) {
/* We have higher priority than the current user, enter
* before him, after all the other users with priority
* higher or equal to our priority. */
if ((!inserted) && (qe->prio > cur->prio)) {
insert_entry(q, prev, qe, &pos);
inserted = 1;
}
cur->pos = ++pos;
prev = cur;
cur = cur->next;
}
/* No luck, join at the end of the queue */
if (!inserted)
insert_entry(q, prev, qe, &pos);
ast_copy_string(qe->moh, q->moh, sizeof(qe->moh));
ast_copy_string(qe->announce, q->announce, sizeof(qe->announce));
ast_copy_string(qe->context, q->context, sizeof(qe->context));
q->count++;
res = 0;
manager_event(EVENT_FLAG_CALL, "Join",
"Channel: %s\r\nCallerID: %s\r\nCallerIDName: %s\r\nQueue: %s\r\nPosition: %d\r\nCount: %d\r\n",
qe->chan->name,
qe->chan->cid.cid_num ? qe->chan->cid.cid_num : "unknown",
qe->chan->cid.cid_name ? qe->chan->cid.cid_name : "unknown",
q->name, qe->pos, q->count );
#if 0
ast_log(LOG_NOTICE, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos );
#endif
ast_mutex_unlock(&q->lock);
ast_mutex_unlock(&qlock);
static void free_members(struct ast_call_queue *q, int all)
/* Free non-dynamic members */
struct member *curm, *next, *prev;
Mark Spencer
committed
if (all || !curm->dynamic) {
if (prev)
prev->next = next;
else
q->members = next;
free(curm);
} else
prev = curm;
curm = next;
}
}
static void destroy_queue(struct ast_call_queue *q)
{
struct ast_call_queue *cur, *prev = NULL;
Mark Spencer
committed
ast_mutex_lock(&qlock);
for (cur = queues; cur; cur = cur->next) {
if (cur == q) {
if (prev)
prev->next = cur->next;
else
queues = cur->next;
} else {
prev = cur;
}
}
ast_mutex_unlock(&qlock);
Mark Spencer
committed
ast_mutex_destroy(&q->lock);
static int play_file(struct ast_channel *chan, char *filename)
{
int res;
ast_stopstream(chan);
res = ast_streamfile(chan, filename, chan->language);
if (!res)
Kevin P. Fleming
committed
res = ast_waitstream(chan, AST_DIGIT_ANY);
else
res = 0;
ast_stopstream(chan);