Newer
Older
Mark Michelson
committed
#define RES_NOT_DYNAMIC (-4) /*!< Member is not dynamic */
static char *app_aqm = "AddQueueMember" ;
static char *app_rqm = "RemoveQueueMember" ;
static char *app_pqm = "PauseQueueMember" ;
static char *app_upqm = "UnpauseQueueMember" ;
static char *app_ql = "QueueLog" ;
static const char * const 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 = 1;
/*! \brief queues.conf [general] option */
static int montype_default = 0;
Mark Michelson
committed
/*! \brief queues.conf [general] option */
static int shared_lastcall = 1;
Mark Michelson
committed
Russell Bryant
committed
/*! \brief Subscription to device state change events */
static struct ast_event_sub *device_state_sub;
/*! \brief queues.conf [general] option */
static int update_cdr = 0;
/*! \brief queues.conf [general] option */
static int negative_penalty_invalid = 0;
Kinsey Moore
committed
/*! \brief queues.conf [general] option */
static int log_membername_as_agent = 0;
/*! \brief queues.conf [general] option */
static int check_state_unknown = 0;
/*! \brief name of the ringinuse field in the realtime database */
static char *realtime_ringinuse_field;
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,
Jason Parker
committed
QUEUE_CONTINUE = 7,
Mark Spencer
committed
};
static const struct {
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" },
Jason Parker
committed
{ QUEUE_CONTINUE, "CONTINUE" },
Mark Spencer
committed
};
enum queue_timeout_priority {
TIMEOUT_PRIORITY_APP,
TIMEOUT_PRIORITY_CONF,
};
/*! \brief We define a custom "local user" structure because we
Mark Michelson
committed
* use it not only for keeping track of what is in use but
* also for keeping track of who we're dialing.
*
* There are two "links" defined in this structure, q_next and call_next.
* q_next links ALL defined callattempt structures into a linked list. call_next is
* a link which allows for a subset of the callattempts to be traversed. This subset
* is used in wait_for_answer so that irrelevant callattempts are not traversed. This
* also is helpful so that queue logs are always accurate in the case where a call to
* a member times out, especially if using the ringall strategy.
*/
struct callattempt {
struct callattempt *q_next;
struct callattempt *call_next;
char interface[256]; /*!< An Asterisk dial string (not a channel name) */
Mark Michelson
committed
struct call_queue *lastqueue;
struct member *member;
/*! Saved connected party info from an AST_CONTROL_CONNECTED_LINE. */
Mark Michelson
committed
struct ast_party_connected_line connected;
/*! TRUE if an AST_CONTROL_CONNECTED_LINE update was saved to the connected element. */
unsigned int pending_connected_update:1;
/*! TRUE if the connected line update is blocked. */
unsigned int block_connected_update:1;
/*! TRUE if caller id is not available for connected line */
unsigned int dial_callerid_absent:1;
/*! TRUE if the call is still active */
unsigned int stillgoing:1;
Mark Michelson
committed
struct call_queue *parent; /*!< What queue is our parent */
char moh[MAX_MUSICCLASS]; /*!< Name of musiconhold to be used */
char announce[PATH_MAX]; /*!< Announcement to play for member when call is answered */
Mark Michelson
committed
char context[AST_MAX_CONTEXT]; /*!< Context when user exits queue */
char digits[AST_MAX_EXTENSION]; /*!< Digits entered while in queue */
Mark Michelson
committed
int valid_digits; /*!< Digits entered correspond to valid extension. Exited */
Mark Michelson
committed
int pos; /*!< Where we are in the queue */
int prio; /*!< Our priority */
int last_pos_said; /*!< Last position we told the user */
int ring_when_ringing; /*!< Should we only use ring indication when a channel is ringing? */
Mark Michelson
committed
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 pending; /*!< Non-zero if we are attempting to call a member */
int max_penalty; /*!< Limit the members that can take this call to this penalty or lower */
int min_penalty; /*!< Limit the members that can take this call to this penalty or higher */
Mark Michelson
committed
int linpos; /*!< If using linear strategy, what position are we at? */
int linwrapped; /*!< Is the linpos wrapped? */
Mark Michelson
committed
time_t start; /*!< When we started holding */
time_t expire; /*!< When this entry should expire (time out of queue) */
Olle Johansson
committed
int cancel_answered_elsewhere; /*!< Whether we should force the CAE flag on this call (C) option*/
Mark Michelson
committed
struct ast_channel *chan; /*!< Our channel */
AST_LIST_HEAD_NOLOCK(,penalty_rule) qe_rules; /*!< Local copy of the queue's penalty rules */
struct penalty_rule *pr; /*!< Pointer to the next penalty rule to implement */
struct queue_ent *next; /*!< The next queue entry */
char interface[AST_CHANNEL_NAME]; /*!< Technology/Location to dial to reach this member*/
char state_exten[AST_MAX_EXTENSION]; /*!< Extension to get state from (if using hint) */
char state_context[AST_MAX_CONTEXT]; /*!< Context to use when getting state (if using hint) */
char state_interface[AST_CHANNEL_NAME]; /*!< Technology/Location from which to read devicestate changes */
char membername[80]; /*!< Member name to use in queue logs */
int penalty; /*!< Are we a last resort? */
int calls; /*!< Number of calls serviced by this member */
int dynamic; /*!< Are we dynamically added? */
int realtime; /*!< Is this member realtime? */
int status; /*!< Status of queue member */
int paused; /*!< Are we paused (not accepting calls)? */
time_t lastcall; /*!< When last successful call was hungup */
struct call_queue *lastqueue; /*!< Last queue we received a call */
unsigned int dead:1; /*!< Used to detect members deleted in realtime */
unsigned int delme:1; /*!< Flag to delete entry on reload */
char rt_uniqueid[80]; /*!< Unique id of realtime member entry */
unsigned int ringinuse:1; /*!< Flag to ring queue members even if their status is 'inuse' */
enum empty_conditions {
QUEUE_EMPTY_PENALTY = (1 << 0),
QUEUE_EMPTY_PAUSED = (1 << 1),
QUEUE_EMPTY_INUSE = (1 << 2),
QUEUE_EMPTY_RINGING = (1 << 3),
QUEUE_EMPTY_UNAVAILABLE = (1 << 4),
QUEUE_EMPTY_INVALID = (1 << 5),
QUEUE_EMPTY_UNKNOWN = (1 << 6),
QUEUE_EMPTY_WRAPUP = (1 << 7),
};
enum member_properties {
MEMBER_PENALTY = 0,
MEMBER_RINGINUSE = 1,
};
/* values used in multi-bit flags in call_queue */
Mark Spencer
committed
#define ANNOUNCEHOLDTIME_ALWAYS 1
#define ANNOUNCEHOLDTIME_ONCE 2
#define QUEUE_EVENT_VARIABLES 3
Mark Spencer
committed
Mark Michelson
committed
struct penalty_rule {
int time; /*!< Number of seconds that need to pass before applying this rule */
int max_value; /*!< The amount specified in the penalty rule for max penalty */
int min_value; /*!< The amount specified in the penalty rule for min penalty */
int max_relative; /*!< Is the max adjustment relative? 1 for relative, 0 for absolute */
int min_relative; /*!< Is the min adjustment relative? 1 for relative, 0 for absolute */
AST_LIST_ENTRY(penalty_rule) list; /*!< Next penalty_rule */
};
#define ANNOUNCEPOSITION_YES 1 /*!< We announce position */
#define ANNOUNCEPOSITION_NO 2 /*!< We don't announce position */
#define ANNOUNCEPOSITION_MORE_THAN 3 /*!< We say "Currently there are more than <limit>" */
#define ANNOUNCEPOSITION_LIMIT 4 /*!< We not announce position more than <limit> */
AST_DECLARE_STRING_FIELDS(
/*! Queue name */
AST_STRING_FIELD(name);
/*! Music on Hold class */
AST_STRING_FIELD(moh);
/*! Announcement to play when call is answered */
AST_STRING_FIELD(announce);
/*! Exit context */
AST_STRING_FIELD(context);
/*! Macro to run upon member connection */
AST_STRING_FIELD(membermacro);
/*! Gosub to run upon member connection */
AST_STRING_FIELD(membergosub);
Mark Michelson
committed
/*! Default rule to use if none specified in call to Queue() */
AST_STRING_FIELD(defaultrule);
/*! Sound file: "Your call is now first in line" (def. queue-youarenext) */
AST_STRING_FIELD(sound_next);
/*! Sound file: "There are currently" (def. queue-thereare) */
AST_STRING_FIELD(sound_thereare);
/*! Sound file: "calls waiting to speak to a representative." (def. queue-callswaiting) */
AST_STRING_FIELD(sound_calls);
/*! Sound file: "Currently there are more than" (def. queue-quantity1) */
AST_STRING_FIELD(queue_quantity1);
/*! Sound file: "callers waiting to speak with a representative" (def. queue-quantity2) */
AST_STRING_FIELD(queue_quantity2);
/*! Sound file: "The current estimated total holdtime is" (def. queue-holdtime) */
AST_STRING_FIELD(sound_holdtime);
/*! Sound file: "minutes." (def. queue-minutes) */
AST_STRING_FIELD(sound_minutes);
Mark Michelson
committed
/*! Sound file: "minute." (def. queue-minute) */
AST_STRING_FIELD(sound_minute);
/*! Sound file: "seconds." (def. queue-seconds) */
AST_STRING_FIELD(sound_seconds);
/*! Sound file: "Thank you for your patience." (def. queue-thankyou) */
AST_STRING_FIELD(sound_thanks);
/*! Sound file: Custom announce for caller, no default */
AST_STRING_FIELD(sound_callerannounce);
/*! Sound file: "Hold time" (def. queue-reporthold) */
AST_STRING_FIELD(sound_reporthold);
);
/*! Sound files: Custom announce, no default */
struct ast_str *sound_periodicannounce[MAX_PERIODIC_ANNOUNCEMENTS];
unsigned int eventwhencalled:2;
Kevin P. Fleming
committed
unsigned int ringinuse:1;
unsigned int setinterfacevar:1;
unsigned int setqueuevar:1;
unsigned int setqueueentryvar:1;
unsigned int reportholdtime:1;
unsigned int wrapped:1;
unsigned int timeoutrestart:1;
unsigned int announceholdtime:2;
unsigned int announceposition:3;
unsigned int maskmemberstatus:1;
unsigned int realtime:1;
Matthew Nicholson
committed
unsigned int relativeperiodicannounce:1;
unsigned int autopausebusy:1;
unsigned int autopauseunavail:1;
enum empty_conditions joinempty;
enum empty_conditions leavewhenempty;
int announcepositionlimit; /*!< How many positions we announce? */
int announcefrequency; /*!< How often to announce their position */
Russell Bryant
committed
int minannouncefrequency; /*!< The minimum number of seconds between position announcements (def. 15) */
int periodicannouncefrequency; /*!< How often to play periodic announcement */
int numperiodicannounce; /*!< The number of periodic announcements configured */
int randomperiodicannounce; /*!< Are periodic announcments randomly chosen */
int roundingseconds; /*!< How many seconds do we round to? */
int holdtime; /*!< Current avg holdtime, based on an exponential average */
int talktime; /*!< Current avg talktime, based on the same exponential average */
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 */
int count; /*!< How many entries */
int maxlen; /*!< Max number of entries */
int wrapuptime; /*!< Wrapup Time */
int penaltymemberslimit; /*!< Disregard penalty when queue has fewer than this many members */
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 */
int autopausedelay; /*!< Delay auto pause for autopausedelay seconds since last call */
int timeoutpriority; /*!< Do we allow a fraction of the timeout to occur for a ring? */
/* 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 ao2_container *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 */
Mark Michelson
committed
AST_LIST_HEAD_NOLOCK(, penalty_rule) rules; /*!< The list of penalty rules to invoke */
Mark Michelson
committed
struct rule_list {
char name[80];
AST_LIST_HEAD_NOLOCK(,penalty_rule) rules;
AST_LIST_ENTRY(rule_list) list;
};
static AST_LIST_HEAD_STATIC(rule_lists, rule_list);
Mark Michelson
committed
Mark Michelson
committed
static struct ao2_container *queues;
static void update_realtime_members(struct call_queue *q);
static struct member *interface_exists(struct call_queue *q, const char *interface);
static int set_member_paused(const char *queuename, const char *interface, const char *reason, int paused);
static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
Kinsey Moore
committed
static struct member *find_member_by_queuename_and_interface(const char *queuename, const char *interface);
/*! \brief sets the QUEUESTATUS channel variable */
Mark Spencer
committed
static void set_queue_result(struct ast_channel *chan, enum queue_result res)
{
int i;
for (i = 0; i < ARRAY_LEN(queue_results); i++) {
Mark Spencer
committed
if (queue_results[i].id == res) {
pbx_builtin_setvar_helper(chan, "QUEUESTATUS", queue_results[i].text);
return;
}
}
}
static const char *int2strat(int strategy)
{
int x;
for (x = 0; x < ARRAY_LEN(strategies); x++) {
return strategies[x].name;
}
return "<unknown>";
}
static int strat2int(const char *strategy)
{
int x;
for (x = 0; x < ARRAY_LEN(strategies); x++) {
if (!strcasecmp(strategy, strategies[x].name)) {
return strategies[x].strategy;
}
return -1;
}
Mark Michelson
committed
static int autopause2int(const char *autopause)
{
int x;
/*This 'double check' that default value is OFF */
Mark Michelson
committed
return QUEUE_AUTOPAUSE_OFF;
Mark Michelson
committed
/*This 'double check' is to ensure old values works */
Mark Michelson
committed
return QUEUE_AUTOPAUSE_ON;
Mark Michelson
committed
for (x = 0; x < ARRAY_LEN(autopausesmodes); x++) {
if (!strcasecmp(autopause, autopausesmodes[x].name)) {
Mark Michelson
committed
return autopausesmodes[x].autopause;
Mark Michelson
committed
}
/*This 'double check' that default value is OFF */
return QUEUE_AUTOPAUSE_OFF;
}
Mark Michelson
committed
static int queue_hash_cb(const void *obj, const int flags)
{
const struct call_queue *q = obj;
return ast_str_case_hash(q->name);
Mark Michelson
committed
}
static int queue_cmp_cb(void *obj, void *arg, int flags)
Mark Michelson
committed
{
struct call_queue *q = obj, *q2 = arg;
return !strcasecmp(q->name, q2->name) ? CMP_MATCH | CMP_STOP : 0;
Mark Michelson
committed
}
Tilghman Lesher
committed
#ifdef REF_DEBUG_ONLY_QUEUES
#define queue_ref(q) _queue_ref(q, "", __FILE__, __LINE__, __PRETTY_FUNCTION__)
#define queue_unref(q) _queue_unref(q, "", __FILE__, __LINE__, __PRETTY_FUNCTION__)
#define queue_t_ref(q, tag) _queue_ref(q, tag, __FILE__, __LINE__, __PRETTY_FUNCTION__)
#define queue_t_unref(q, tag) _queue_unref(q, tag, __FILE__, __LINE__, __PRETTY_FUNCTION__)
#define queues_t_link(c, q, tag) __ao2_link_debug(c, q, 0, tag, __FILE__, __LINE__, __PRETTY_FUNCTION__)
#define queues_t_unlink(c, q, tag) __ao2_unlink_debug(c, q, 0, tag, __FILE__, __LINE__, __PRETTY_FUNCTION__)
static inline struct call_queue *_queue_ref(struct call_queue *q, const char *tag, const char *file, int line, const char *filename)
{
__ao2_ref_debug(q, 1, tag, file, line, filename);
return q;
}
static inline struct call_queue *_queue_unref(struct call_queue *q, const char *tag, const char *file, int line, const char *filename)
{
__ao2_ref_debug(q, -1, tag, file, line, filename);
return NULL;
}
Tilghman Lesher
committed
#else
#define queue_t_ref(q, tag) queue_ref(q)
#define queue_t_unref(q, tag) queue_unref(q)
#define queues_t_link(c, q, tag) ao2_t_link(c, q, tag)
#define queues_t_unlink(c, q, tag) ao2_t_unlink(c, q, tag)
Mark Michelson
committed
static inline struct call_queue *queue_ref(struct call_queue *q)
{
ao2_ref(q, 1);
return q;
}
static inline struct call_queue *queue_unref(struct call_queue *q)
{
ao2_ref(q, -1);
Mark Michelson
committed
}
Tilghman Lesher
committed
#endif
Mark Michelson
committed
Mark Michelson
committed
/*! \brief Set variables of queue */
Mark Michelson
committed
static void set_queue_variables(struct call_queue *q, struct ast_channel *chan)
{
char interfacevar[256]="";
float sl = 0;
Mark Michelson
committed
if (q->setqueuevar) {
sl = 0;
Mark Michelson
committed
sl = 100 * ((float) q->callscompletedinsl / (float) q->callscompleted);
snprintf(interfacevar, sizeof(interfacevar),
"QUEUENAME=%s,QUEUEMAX=%d,QUEUESTRATEGY=%s,QUEUECALLS=%d,QUEUEHOLDTIME=%d,QUEUETALKTIME=%d,QUEUECOMPLETED=%d,QUEUEABANDONED=%d,QUEUESRVLEVEL=%d,QUEUESRVLEVELPERF=%2.1f",
Mark Michelson
committed
q->name, q->maxlen, int2strat(q->strategy), q->count, q->holdtime, q->talktime, q->callscompleted, q->callsabandoned, q->servicelevel, sl);
Mark Michelson
committed
pbx_builtin_setvar_multiple(chan, interfacevar);
}
}
/*! \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;
/* every queue_ent must have a reference to it's parent call_queue, this
* reference does not go away until the end of the queue_ent's life, meaning
* that even when the queue_ent leaves the call_queue this ref must remain. */
queue_ref(q);
new->parent = q;
new->pos = ++(*pos);
new->opos = *pos;
}
/*! \brief Check if members are available
*
* This function checks to see if members are available to be called. If any member
* is available, the function immediately returns 0. If no members are available,
* then -1 is returned.
static int get_member_status(struct call_queue *q, int max_penalty, int min_penalty, enum empty_conditions conditions)
Mark Spencer
committed
{
struct member *member;
struct ao2_iterator mem_iter;
Mark Spencer
committed
Mark Michelson
committed
ao2_lock(q);
mem_iter = ao2_iterator_init(q->members, 0);
Russell Bryant
committed
for (; (member = ao2_iterator_next(&mem_iter)); ao2_ref(member, -1)) {
if ((max_penalty && (member->penalty > max_penalty)) || (min_penalty && (member->penalty < min_penalty))) {
if (conditions & QUEUE_EMPTY_PENALTY) {
ast_debug(4, "%s is unavailable because his penalty is not between %d and %d\n", member->membername, min_penalty, max_penalty);
continue;
}
}
Kevin P. Fleming
committed
Mark Spencer
committed
switch (member->status) {
case AST_DEVICE_INVALID:
if (conditions & QUEUE_EMPTY_INVALID) {
ast_debug(4, "%s is unavailable because his device state is 'invalid'\n", member->membername);
break;
}
Tilghman Lesher
committed
goto default_case;
Mark Spencer
committed
case AST_DEVICE_UNAVAILABLE:
if (conditions & QUEUE_EMPTY_UNAVAILABLE) {
ast_debug(4, "%s is unavailable because his device state is 'unavailable'\n", member->membername);
break;
}
Tilghman Lesher
committed
goto default_case;
case AST_DEVICE_INUSE:
if (conditions & QUEUE_EMPTY_INUSE) {
ast_debug(4, "%s is unavailable because his device state is 'inuse'\n", member->membername);
break;
}
Tilghman Lesher
committed
goto default_case;
case AST_DEVICE_RINGING:
if (conditions & QUEUE_EMPTY_RINGING) {
ast_debug(4, "%s is unavailable because his device state is 'ringing'\n", member->membername);
break;
}
case AST_DEVICE_UNKNOWN:
if (conditions & QUEUE_EMPTY_UNKNOWN) {
ast_debug(4, "%s is unavailable because his device state is 'unknown'\n", member->membername);
break;
}
Tilghman Lesher
committed
/* Fall-through */
Mark Spencer
committed
default:
Tilghman Lesher
committed
default_case:
if (member->paused && (conditions & QUEUE_EMPTY_PAUSED)) {
ast_debug(4, "%s is unavailable because he is paused'\n", member->membername);
break;
} else if ((conditions & QUEUE_EMPTY_WRAPUP) && member->lastcall && q->wrapuptime && (time(NULL) - q->wrapuptime < member->lastcall)) {
ast_debug(4, "%s is unavailable because it has only been %d seconds since his last call (wrapup time is %d)\n", member->membername, (int) (time(NULL) - member->lastcall), q->wrapuptime);
break;
} else {
ao2_iterator_destroy(&mem_iter);
ast_debug(4, "%s is available.\n", member->membername);
return 0;
}
break;
Mark Spencer
committed
}
}
ao2_iterator_destroy(&mem_iter);
Mark Michelson
committed
ao2_unlock(q);
Mark Spencer
committed
}
Russell Bryant
committed
AST_LIST_ENTRY(statechange) entry;
Mark Michelson
committed
/*! \brief set a member's status based on device state of that member's state_interface.
Mark Michelson
committed
* Lock interface list find sc, iterate through each queues queue_member list for member to
* update state inside queues
*/
static int update_status(struct call_queue *q, struct member *m, const int status)
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
/*** DOCUMENTATION
<managerEventInstance>
<synopsis>Raised when a Queue member's status has changed.</synopsis>
<syntax>
<parameter name="Queue">
<para>The name of the queue.</para>
</parameter>
<parameter name="Location">
<para>The queue member's channel technology or location.</para>
</parameter>
<parameter name="MemberName">
<para>The name of the queue member.</para>
</parameter>
<parameter name="StateInterface">
<para>Channel technology or location from which to read device state changes.</para>
</parameter>
<parameter name="Membership">
<enumlist>
<enum name="dynamic"/>
<enum name="realtime"/>
<enum name="static"/>
</enumlist>
</parameter>
<parameter name="Penalty">
<para>The penalty associated with the queue member.</para>
</parameter>
<parameter name="CallsTaken">
<para>The number of calls this queue member has serviced.</para>
</parameter>
<parameter name="LastCall">
<para>The time this member last took call, expressed in seconds since 00:00, Jan 1, 1970 UTC.</para>
</parameter>
<parameter name="Status">
<para>The status of the queue member. This will be a device state value.</para>
</parameter>
<parameter name="Paused">
<enumlist>
<enum name="0"/>
<enum name="1"/>
</enumlist>
</parameter>
</syntax>
</managerEventInstance>
***/
manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus",
"Queue: %s\r\n"
"Location: %s\r\n"
"MemberName: %s\r\n"
"StateInterface: %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, m->interface, m->membername, m->state_interface, m->dynamic ? "dynamic" : m->realtime ? "realtime" : "static",
m->penalty, m->calls, (int)m->lastcall, m->status, m->paused
);
return 0;
}
/*! \brief set a member's status based on device state of that member's interface*/
static int handle_statechange(void *datap)
struct statechange *sc = datap;
struct ao2_iterator miter, qiter;
struct member *m;
struct call_queue *q;
char interface[80], *slash_pos;
int found = 0;
qiter = ao2_iterator_init(queues, 0);
Tilghman Lesher
committed
while ((q = ao2_t_iterator_next(&qiter, "Iterate over queues"))) {
miter = ao2_iterator_init(q->members, 0);
for (; (m = ao2_iterator_next(&miter)); ao2_ref(m, -1)) {
ast_copy_string(interface, m->state_interface, sizeof(interface));
if ((slash_pos = strchr(interface, '/'))) {
if (!strncasecmp(interface, "Local/", 6) && (slash_pos = strchr(slash_pos + 1, '/'))) {
if (!strcasecmp(interface, sc->dev)) {
found = 1;
update_status(q, m, sc->state);
ao2_ref(m, -1);
break;
}
}
ao2_iterator_destroy(&miter);
Tilghman Lesher
committed
queue_t_unref(q, "Done with iterator");
ao2_iterator_destroy(&qiter);
Tilghman Lesher
committed
ast_debug(1, "Device '%s' changed to state '%d' (%s)\n", sc->dev, sc->state, ast_devstate2str(sc->state));
Tilghman Lesher
committed
ast_debug(3, "Device '%s' changed to state '%d' (%s) but we don't care because they're not a member of any queue.\n", sc->dev, sc->state, ast_devstate2str(sc->state));
ast_free(sc);
Mark Michelson
committed
Russell Bryant
committed
static void device_state_cb(const struct ast_event *event, void *unused)
{
enum ast_device_state state;
const char *device;
struct statechange *sc;
size_t datapsize;
Russell Bryant
committed
state = ast_event_get_ie_uint(event, AST_EVENT_IE_STATE);
device = ast_event_get_ie_str(event, AST_EVENT_IE_DEVICE);
if (ast_strlen_zero(device)) {
ast_log(LOG_ERROR, "Received invalid event that had no device IE\n");
return;
}
datapsize = sizeof(*sc) + strlen(device) + 1;
if (!(sc = ast_calloc(1, datapsize))) {
ast_log(LOG_ERROR, "failed to calloc a state change struct\n");
return;
}
sc->state = state;
strcpy(sc->dev, device);
if (ast_taskprocessor_push(devicestate_tps, handle_statechange, sc) < 0) {
ast_free(sc);
}
Russell Bryant
committed
}
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
/*! \brief Helper function which converts from extension state to device state values */
static int extensionstate2devicestate(int state)
{
switch (state) {
case AST_EXTENSION_NOT_INUSE:
state = AST_DEVICE_NOT_INUSE;
break;
case AST_EXTENSION_INUSE:
state = AST_DEVICE_INUSE;
break;
case AST_EXTENSION_BUSY:
state = AST_DEVICE_BUSY;
break;
case AST_EXTENSION_RINGING:
state = AST_DEVICE_RINGING;
break;
case AST_EXTENSION_ONHOLD:
state = AST_DEVICE_ONHOLD;
break;
case AST_EXTENSION_UNAVAILABLE:
state = AST_DEVICE_UNAVAILABLE;
break;
case AST_EXTENSION_REMOVED:
case AST_EXTENSION_DEACTIVATED:
default:
state = AST_DEVICE_INVALID;
break;
}
return state;
}
static int extension_state_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data)
{
struct ao2_iterator miter, qiter;
struct member *m;
struct call_queue *q;
int state = info->exten_state;
int found = 0, device_state = extensionstate2devicestate(state);
/* only interested in extension state updates involving device states */
if (info->reason != AST_HINT_UPDATE_DEVICE) {
return 0;
}
qiter = ao2_iterator_init(queues, 0);
Tilghman Lesher
committed
while ((q = ao2_t_iterator_next(&qiter, "Iterate through queues"))) {
ao2_lock(q);
miter = ao2_iterator_init(q->members, 0);
for (; (m = ao2_iterator_next(&miter)); ao2_ref(m, -1)) {
if (!strcmp(m->state_context, context) && !strcmp(m->state_exten, exten)) {
update_status(q, m, device_state);
ao2_ref(m, -1);
found = 1;
break;
}
}
ao2_iterator_destroy(&miter);
ao2_unlock(q);
Tilghman Lesher
committed
queue_t_unref(q, "Done with iterator");
}
ao2_iterator_destroy(&qiter);
if (found) {
ast_debug(1, "Extension '%s@%s' changed to state '%d' (%s)\n", exten, context, device_state, ast_devstate2str(device_state));
} else {
ast_debug(3, "Extension '%s@%s' changed to state '%d' (%s) but we don't care because they're not a member of any queue.\n",
exten, context, device_state, ast_devstate2str(device_state));
}
return 0;
}
/*! \brief Return the current state of a member */
static int get_queue_member_status(struct member *cur)
{
return ast_strlen_zero(cur->state_exten) ? ast_device_state(cur->state_interface) : extensionstate2devicestate(ast_extension_state(NULL, cur->state_context, cur->state_exten));
}
/*! \brief allocate space for new queue member and set fields based on parameters passed */
static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused, const char *state_interface, int ringinuse)
{
struct member *cur;
if ((cur = ao2_alloc(sizeof(*cur), NULL))) {
cur->ringinuse = ringinuse;
cur->penalty = penalty;
cur->paused = paused;
ast_copy_string(cur->interface, interface, sizeof(cur->interface));
if (!ast_strlen_zero(state_interface)) {
Mark Michelson
committed
ast_copy_string(cur->state_interface, state_interface, sizeof(cur->state_interface));
Mark Michelson
committed
ast_copy_string(cur->state_interface, interface, sizeof(cur->state_interface));
}
if (!ast_strlen_zero(membername)) {
ast_copy_string(cur->membername, membername, sizeof(cur->membername));
ast_copy_string(cur->membername, interface, sizeof(cur->membername));
}
if (!strchr(cur->interface, '/')) {
ast_log(LOG_WARNING, "No location at interface '%s'\n", interface);
if (!strncmp(cur->state_interface, "hint:", 5)) {
char *tmp = ast_strdupa(cur->state_interface), *context = tmp;
char *exten = strsep(&context, "@") + 5;
ast_copy_string(cur->state_exten, exten, sizeof(cur->state_exten));
ast_copy_string(cur->state_context, S_OR(context, "default"), sizeof(cur->state_context));
}
cur->status = get_queue_member_status(cur);
}
return cur;
}
static int compress_char(const char c)
{
}
static int member_hash_fn(const void *obj, const int flags)
{
const struct member *mem = obj;
const char *interface = (flags & OBJ_KEY) ? obj : mem->interface;
const char *chname = strchr(interface, '/');
if (!chname) {
chname = interface;
}
for (i = 0; i < 5 && chname[i]; i++) {
ret += compress_char(chname[i]) << (i * 6);
static int member_cmp_fn(void *obj1, void *obj2, int flags)
struct member *mem1 = obj1;
struct member *mem2 = obj2;
const char *interface = (flags & OBJ_KEY) ? obj2 : mem2->interface;
return strcasecmp(mem1->interface, interface) ? 0 : CMP_MATCH | CMP_STOP;
Mark Michelson
committed
* \brief Initialize Queue default values.
* \note the queue's lock must be held before executing this function
*/
static void init_queue(struct call_queue *q)
{
Mark Michelson
committed
struct penalty_rule *pr_iter;
q->dead = 0;
q->retry = DEFAULT_RETRY;
q->timeout = DEFAULT_TIMEOUT;
q->maxlen = 0;
q->announcefrequency = 0;
Russell Bryant
committed
q->minannouncefrequency = DEFAULT_MIN_ANNOUNCE_FREQUENCY;
q->announceholdtime = 1;
q->announcepositionlimit = 10; /* Default 10 positions */
q->announceposition = ANNOUNCEPOSITION_YES; /* Default yes */
q->roundingseconds = 0; /* Default - don't announce seconds */
q->servicelevel = 0;
Kevin P. Fleming
committed
q->ringinuse = 1;
q->setinterfacevar = 0;
q->setqueuevar = 0;
q->setqueueentryvar = 0;
q->autofill = autofill_default;
q->montype = montype_default;
q->monfmt[0] = '\0';
q->reportholdtime = 0;
q->wrapuptime = 0;
q->joinempty = 0;
q->leavewhenempty = 0;
q->memberdelay = 0;
q->maskmemberstatus = 0;
q->eventwhencalled = 0;
q->weight = 0;
q->timeoutrestart = 0;
q->periodicannouncefrequency = 0;
q->randomperiodicannounce = 0;
q->numperiodicannounce = 0;
Mark Michelson
committed
q->autopause = QUEUE_AUTOPAUSE_OFF;
q->timeoutpriority = TIMEOUT_PRIORITY_APP;
Mark Michelson
committed
if (!q->members) {
if (q->strategy == QUEUE_STRATEGY_LINEAR || q->strategy == QUEUE_STRATEGY_RRORDERED) {
Mark Michelson
committed
/* linear strategy depends on order, so we have to place all members in a single bucket */
q->members = ao2_container_alloc(1, member_hash_fn, member_cmp_fn);
Mark Michelson
committed
q->members = ao2_container_alloc(37, member_hash_fn, member_cmp_fn);
Mark Michelson
committed
}
ast_string_field_set(q, sound_next, "queue-youarenext");
ast_string_field_set(q, sound_thereare, "queue-thereare");
ast_string_field_set(q, sound_calls, "queue-callswaiting");
ast_string_field_set(q, queue_quantity1, "queue-quantity1");
ast_string_field_set(q, queue_quantity2, "queue-quantity2");
ast_string_field_set(q, sound_holdtime, "queue-holdtime");
ast_string_field_set(q, sound_minutes, "queue-minutes");
Mark Michelson
committed
ast_string_field_set(q, sound_minute, "queue-minute");
ast_string_field_set(q, sound_seconds, "queue-seconds");
ast_string_field_set(q, sound_thanks, "queue-thankyou");
ast_string_field_set(q, sound_reporthold, "queue-reporthold");
if (!q->sound_periodicannounce[0]) {
q->sound_periodicannounce[0] = ast_str_create(32);
}
if (q->sound_periodicannounce[0]) {
ast_str_set(&q->sound_periodicannounce[0], 0, "queue-periodic-announce");
for (i = 1; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) {
ast_str_set(&q->sound_periodicannounce[i], 0, "%s", "");
}
Mark Michelson
committed
while ((pr_iter = AST_LIST_REMOVE_HEAD(&q->rules,list))) {
Mark Michelson
committed
ast_free(pr_iter);
}
static void clear_queue(struct call_queue *q)
{
q->holdtime = 0;
q->callscompleted = 0;
q->callsabandoned = 0;
q->callscompletedinsl = 0;
q->talktime = 0;
if (q->members) {
struct member *mem;
struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0);
while ((mem = ao2_iterator_next(&mem_iter))) {
mem->calls = 0;
mem->lastcall = 0;
ao2_ref(mem, -1);
}
ao2_iterator_destroy(&mem_iter);
}
Mark Michelson
committed
* \brief Change queue penalty by adding rule.
*
* Check rule for errors with time or fomatting, see if rule is relative to rest
Mark Michelson
committed
* of queue, iterate list of rules to find correct insertion point, insert and return.
* \retval -1 on failure
* \retval 0 on success
* \note Call this with the rule_lists locked
Mark Michelson
committed
*/
static int insert_penaltychange(const char *list_name, const char *content, const int linenum)
Mark Michelson
committed
{
char *timestr, *maxstr, *minstr, *contentdup;
struct penalty_rule *rule = NULL, *rule_iter;
struct rule_list *rl_iter;
int penaltychangetime, inserted = 0;
Mark Michelson
committed
if (!(rule = ast_calloc(1, sizeof(*rule)))) {
return -1;
}
contentdup = ast_strdupa(content);
if (!(maxstr = strchr(contentdup, ','))) {
ast_log(LOG_WARNING, "Improperly formatted penaltychange rule at line %d. Ignoring.\n", linenum);
ast_free(rule);
return -1;