Newer
Older
* Asterisk -- An open source telephony toolkit.
* Copyright (C) 1999 - 2006, 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.
*/
* \brief True call queues with optional send URL on answer
* \author Mark Spencer <markster@digium.com>
*
* \note 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.
* \note 2004-06-04: Priorities in queues added by inAccess Networks (work funded by Hellas On Line (HOL) www.hol.gr).
* \note 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>
*
Kevin P. Fleming
committed
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#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>
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/app.h"
#include "asterisk/linkedlists.h"
Kevin P. Fleming
committed
#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"
#include "asterisk/stringfields.h"
Russell Bryant
committed
enum {
QUEUE_STRATEGY_RINGALL = 0,
QUEUE_STRATEGY_ROUNDROBIN,
QUEUE_STRATEGY_LEASTRECENT,
QUEUE_STRATEGY_FEWESTCALLS,
QUEUE_STRATEGY_RANDOM,
QUEUE_STRATEGY_RRMEMORY
};
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 MAX_PERIODIC_ANNOUNCEMENTS 10 /* The maximum periodic announcements we can have */
#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 *app = "Queue";
static char *synopsis = "Queue a call for a call queue";
static char *descrip =
" Queue(queuename[|options[|URL][|announceoverride][|timeout][|AGI]):\n"
"Queues an incoming call in a particular call queue as defined in queues.conf.\n"
"This application will return to the dialplan if the queue does not exist, or\n"
"any of the join options cause the caller to not enter the queue.\n"
"The option string may contain zero or more of the following characters:\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"
" 'r' -- ring instead of playing MOH\n"
" 't' -- allow the called user transfer the calling user\n"
" 'T' -- to allow the calling user to transfer the call.\n"
" 'w' -- allow the called user to write the conversation to disk via Monitor\n"
" 'W' -- allow the calling user to write the conversation to disk via Monitor\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"
" The optional AGI parameter will setup an AGI script to be executed on the \n"
"calling party's channel once they are connected to a queue member.\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[|options]]]):\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"
"The option string may contain zero or more of the following characters:\n"
" 'j' -- jump to +101 priority when appropriate.\n"
" This application sets the following channel variable upon completion:\n"
" AQMSTATUS The status of the attempt to add a queue member as a \n"
" text string, one of\n"
" ADDED | MEMBERALREADY | NOSUCHQUEUE \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[|options]]):\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"
"The option string may contain zero or more of the following characters:\n"
" 'j' -- jump to +101 priority when appropriate.\n"
" This application sets the following channel variable upon completion:\n"
" RQMSTATUS The status of the attempt to remove a queue member as a\n"
" text string, one of\n"
" REMOVED | NOTINQUEUE | NOSUCHQUEUE \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[|options]):\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 and the appropriate options are set.\n"
"The application will fail if the interface is not found and no extension\n"
"to jump to exists.\n"
"The option string may contain zero or more of the following characters:\n"
" 'j' -- jump to +101 priority when appropriate.\n"
" This application sets the following channel variable upon completion:\n"
" PQMSTATUS The status of the attempt to pause a queue member as a\n"
" text string, one of\n"
" PAUSED | NOTFOUND\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[|options]):\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"
"The option string may contain zero or more of the following characters:\n"
" 'j' -- jump to +101 priority when appropriate.\n"
" This application sets the following channel variable upon completion:\n"
" UPQMSTATUS The status of the attempt to unpause a queue \n"
" member as a text string, one of\n"
" UNPAUSED | NOTFOUND\n"
"Example: UnpauseQueueMember(|SIP/3000)\n";
static char *app_ql = "QueueLog" ;
static char *app_ql_synopsis = "Writes to the queue_log" ;
static char *app_ql_descrip =
" QueueLog(queuename|uniqueid|agent|event[|additionalinfo]):\n"
"Allows you to write your own events into the queue log\n"
"Example: QueueLog(101|${UNIQUEID}|${AGENT}|WENTONBREAK|600)\n";
static const char *pm_family = "/Queue/PersistentMembers";
/* The maximum length of each persistent member queue database entry */
BJ Weschke
committed
#define PM_MAX_LEN 8192
Mark Spencer
committed
static int queue_persistent_members = 0;
Mark Spencer
committed
/*! \brief queues.conf [general] option */
static int autofill_default = 0;
/*! \brief queues.conf [general] option */
static int montype_default = 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
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" },
};
/*! \brief 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 callattempt {
struct callattempt *q_next;
char interface[256];
Mark Spencer
committed
int oldstatus;
struct member *member;
struct 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 */
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 announcement */
int last_periodic_announce_sound; /*!< The last periodic announcement we made */
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 */
int max_penalty; /*!< Limit the members that can take this call to this penalty or lower */
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 */
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 */
unsigned int dead:1; /*!< Used to detect members deleted in realtime */
unsigned int delme:1; /*!< Flag to delete entry on reload */
struct member *next; /*!< Next member */
BJ Weschke
committed
char interface[80];
AST_LIST_ENTRY(member_interface) list; /*!< Next call queue */
BJ Weschke
committed
};
static AST_LIST_HEAD_STATIC(interfaces, member_interface);
BJ Weschke
committed
/* values used in multi-bit flags in call_queue */
Mark Spencer
committed
#define QUEUE_EMPTY_NORMAL 1
#define QUEUE_EMPTY_STRICT 2
#define ANNOUNCEHOLDTIME_ALWAYS 1
#define ANNOUNCEHOLDTIME_ONCE 2
#define QUEUE_EVENT_VARIABLES 3
Mark Spencer
committed
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 */
unsigned int monjoin:1;
unsigned int dead:1;
unsigned int joinempty:2;
unsigned int eventwhencalled:2;
unsigned int leavewhenempty:2;
Kevin P. Fleming
committed
unsigned int ringinuse:1;
unsigned int setinterfacevar:1;
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;
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
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? */
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*/
int callscompletedinsl; /*!< Number of calls answered with servicelevel*/
char monfmt[8]; /*!< Format to use when recording calls */
int montype; /*!< Monitor type Monitor vs. MixMonitor */
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_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[MAX_PERIODIC_ANNOUNCEMENTS][80];/*!< Sound files: Custom announce, no default */
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 */
int timeout; /*!< How long to wait for an answer */
int weight; /*!< Respective weight */
int autopause; /*!< Auto pause queue members if they fail to answer */
/* Queue strategy things */
int rrpos; /*!< Round Robin - position */
int memberdelay; /*!< Seconds to delay connecting member to caller */
int autofill; /*!< Ignore the head call status and ring an available agent */
struct member *members; /*!< Head of the list of members */
struct queue_ent *head; /*!< Head of the list of callers */
AST_LIST_ENTRY(call_queue) list; /*!< Next call queue */
static AST_LIST_HEAD_STATIC(queues, call_queue);
static int set_member_paused(char *queuename, char *interface, int paused);
Kevin P. Fleming
committed
static void rr_dep_warning(void)
{
static unsigned int warned = 0;
if (!warned) {
ast_log(LOG_NOTICE, "The 'roundrobin' queue strategy is deprecated. Please use the 'rrmemory' strategy instead.\n");
warned = 1;
}
}
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;
}
/*! \brief Insert the 'new' entry after the 'prev' entry of queue 'q' */
static inline void insert_entry(struct 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 call_queue *q, int max_penalty)
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) {
Kevin P. Fleming
committed
if (max_penalty && (member->penalty > max_penalty))
continue;
if (member->paused) continue;
Mark Spencer
committed
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 statechange *sc = data;
struct member *cur;
struct member_interface *curint;
technology = ast_strdupa(sc->dev);
loc = strchr(technology, '/');
BJ Weschke
committed
AST_LIST_LOCK(&interfaces);
AST_LIST_TRAVERSE(&interfaces, curint, list) {
if (!strcasecmp(curint->interface, sc->dev))
BJ Weschke
committed
}
AST_LIST_UNLOCK(&interfaces);
BJ Weschke
committed
if (option_debug)
ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d' (%s) but we don't care because they're not a member of any queue.\n", technology, loc, sc->state, devstate2str(sc->state));
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_LIST_LOCK(&queues);
AST_LIST_TRAVERSE(&queues, q, list) {
ast_mutex_lock(&q->lock);
for (cur = q->members; cur; cur = cur->next) {
if (strcasecmp(sc->dev, cur->interface))
continue;
if (cur->status != sc->state) {
cur->status = sc->state;
if (q->maskmemberstatus)
continue;
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: %d\r\n"
"Status: %d\r\n"
"Paused: %d\r\n",
q->name, cur->interface, cur->dynamic ? "dynamic" : "static",
cur->penalty, cur->calls, (int)cur->lastcall, cur->status, cur->paused);
ast_mutex_unlock(&q->lock);
AST_LIST_UNLOCK(&queues);
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;
if (!(sc = ast_calloc(1, sizeof(*sc) + strlen(dev) + 1)))
return 0;
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);
static struct member *create_queue_member(char *interface, int penalty, int paused)
{
struct member *cur;
BJ Weschke
committed
if ((cur = ast_calloc(1, sizeof(*cur)))) {
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 call_queue *alloc_queue(const char *queuename)
{
BJ Weschke
committed
if ((q = ast_calloc(1, sizeof(*q)))) {
ast_mutex_init(&q->lock);
ast_copy_string(q->name, queuename, sizeof(q->name));
}
return q;
}
static void init_queue(struct 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;
Kevin P. Fleming
committed
q->ringinuse = 1;
q->setinterfacevar = 0;
q->autofill = autofill_default;
q->montype = montype_default;
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[0], "queue-periodic-announce", sizeof(q->sound_periodicannounce[0]));
for (i = 1; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) {
}
static void clear_queue(struct call_queue *q)
{
q->holdtime = 0;
q->callscompleted = 0;
q->callsabandoned = 0;
q->callscompletedinsl = 0;
q->wrapuptime = 0;
}
BJ Weschke
committed
{
struct member_interface *curint;
BJ Weschke
committed
AST_LIST_LOCK(&interfaces);
AST_LIST_TRAVERSE(&interfaces, curint, list) {
if (!strcasecmp(curint->interface, interface))
BJ Weschke
committed
}
if (curint) {
AST_LIST_UNLOCK(&interfaces);
return 0;
}
BJ Weschke
committed
if (option_debug)
ast_log(LOG_DEBUG, "Adding %s to the list of interfaces that make up all of our queue members.\n", interface);
if ((curint = ast_calloc(1, sizeof(*curint)))) {
ast_copy_string(curint->interface, interface, sizeof(curint->interface));
AST_LIST_INSERT_HEAD(&interfaces, curint, list);
BJ Weschke
committed
}
AST_LIST_UNLOCK(&interfaces);
BJ Weschke
committed
}
static int interface_exists_global(char *interface)
{
BJ Weschke
committed
struct member *mem;
int ret = 0;
AST_LIST_LOCK(&queues);
AST_LIST_TRAVERSE(&queues, q, list) {
ast_mutex_lock(&q->lock);
for (mem = q->members; mem && !ret; mem = mem->next) {
if (!strcasecmp(interface, mem->interface))
BJ Weschke
committed
ret = 1;
BJ Weschke
committed
ast_mutex_unlock(&q->lock);
BJ Weschke
committed
}
AST_LIST_UNLOCK(&queues);
return ret;
}
static int remove_from_interfaces(char *interface)
{
struct member_interface *curint;
BJ Weschke
committed
AST_LIST_LOCK(&interfaces);
AST_LIST_TRAVERSE_SAFE_BEGIN(&interfaces, curint, list) {
if (!strcasecmp(curint->interface, interface)) {
if (!interface_exists_global(interface)) {
if (option_debug)
ast_log(LOG_DEBUG, "Removing %s from the list of interfaces that make up all of our queue members.\n", interface);
AST_LIST_REMOVE_CURRENT(&interfaces, list);
free(curint);
}
break;
BJ Weschke
committed
}
}
AST_LIST_TRAVERSE_SAFE_END;
AST_LIST_UNLOCK(&interfaces);
BJ Weschke
committed
}
static void clear_and_free_interfaces(void)
{
struct member_interface *curint;
BJ Weschke
committed
AST_LIST_LOCK(&interfaces);
while ((curint = AST_LIST_REMOVE_HEAD(&interfaces, list)))
BJ Weschke
committed
free(curint);
AST_LIST_UNLOCK(&interfaces);
}
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 call_queue *q, const char *param, const char *val, int linenum, int failunknown)
{
int i = 0;
char *c, *lastc;
char buff[80];
Kevin P. Fleming
committed
if (!strcasecmp(param, "musicclass") ||
!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;
Kevin P. Fleming
committed
} else if (!strcasecmp(param, "ringinuse")) {
q->ringinuse = ast_true(val);
} else if (!strcasecmp(param, "setinterfacevar")) {
q->setinterfacevar = ast_true(val);
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
} 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")) {
if (strchr(val,'|')) {
lastc = (char *)val;
while ((c = strchr(lastc,'|'))) {
if (i > MAX_PERIODIC_ANNOUNCEMENTS)
break;
strncpy(buff, lastc, abs(lastc - c));
buff[abs(lastc - c)] = '\0';
ast_copy_string(q->sound_periodicannounce[i], buff, sizeof(q->sound_periodicannounce[i]));
lastc = (c + 1);
i++;
}
if (strlen(lastc)) {
ast_copy_string(q->sound_periodicannounce[i], lastc, sizeof(q->sound_periodicannounce[i]));
}
} else {
ast_copy_string(q->sound_periodicannounce[i], val, sizeof(q->sound_periodicannounce[i]));
}
} else if (!strcasecmp(param, "periodic-announce-frequency")) {
q->periodicannouncefrequency = atoi(val);
} else if (!strcasecmp(param, "retry")) {
q->retry = atoi(val);
q->retry = DEFAULT_RETRY;
} else if (!strcasecmp(param, "wrapuptime")) {
q->wrapuptime = atoi(val);
} else if (!strcasecmp(param, "autofill")) {
q->autofill = ast_true(val);
} else if (!strcasecmp(param, "monitor-type")) {
if (!strcasecmp(val, "mixmonitor"))
q->montype = 1;
} else if (!strcasecmp(param, "autopause")) {
q->autopause = ast_true(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);
Russell Bryant
committed
q->strategy = QUEUE_STRATEGY_RINGALL;
}
} 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 = QUEUE_EVENT_VARIABLES;
} else {
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);
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 call_queue *q, char *interface, const char *penalty_str)
{
struct member *m, *prev_m;
int penalty = 0;
penalty = atoi(penalty_str);
penalty = 0;
}
/* Find the member, or the place to put a new one. */
for (m = q->members, prev_m = NULL;
m && strcmp(m->interface, interface);
prev_m = m, m = m->next);
/* Create a new one if not found, else update penalty */
if (!m) {
if ((m = create_queue_member(interface, penalty, 0))) {
m->dead = 0;
BJ Weschke
committed
add_to_interfaces(interface);
if (prev_m) {
prev_m->next = m;
} else {
q->members = m;
}
}
} else {
m->dead = 0; /* Do not delete this one. */
m->penalty = penalty;
}
}
static void free_members(struct call_queue *q, int all)
Russell Bryant
committed
{
/* Free non-dynamic members */
struct member *curm, *next, *prev = NULL;
for (curm = q->members; curm; curm = next) {
next = curm->next;
if (all || !curm->dynamic) {
if (prev)
prev->next = next;
else
q->members = next;
BJ Weschke
committed
remove_from_interfaces(curm->interface);
Russell Bryant
committed
free(curm);
Russell Bryant
committed
prev = curm;
}
}
static void destroy_queue(struct call_queue *q)
Russell Bryant
committed
{
free_members(q, 1);
ast_mutex_destroy(&q->lock);
free(q);
}
/*!\brief Reload a single queue via realtime.
\return Return the queue, or NULL if it doesn't exist.
Tilghman Lesher
committed
\note Should be called with the global qlock locked. */
static struct call_queue *find_queue_by_name_rt(const char *queuename, struct ast_variable *queue_vars, struct ast_config *member_config)
{
struct ast_variable *v;
struct member *m, *prev_m, *next_m;
Joshua Colp
committed
char *interface = NULL;
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). */
if (!strcasecmp(q->name, queuename))
break;
}
/* 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 {
Tilghman Lesher
committed
ast_mutex_unlock(&q->lock);
return q;
}
}
} else if (!member_config)
/* Not found in the list, and it's not realtime ... */