Newer
Older
Jason Parker
committed
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
</syntax>
<see-also>
<ref type="function">QUEUE_MEMBER</ref>
</see-also>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="QueueMemberRinginuse">
<managerEventInstance class="EVENT_FLAG_AGENT">
<synopsis>Raised when a member's ringinuse setting is changed.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter)" />
</syntax>
<see-also>
<ref type="function">QUEUE_MEMBER</ref>
</see-also>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="QueueCallerJoin">
<managerEventInstance class="EVENT_FLAG_AGENT">
<synopsis>Raised when a caller joins a Queue.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<parameter name="Position">
<para>This channel's current position in the queue.</para>
</parameter>
<parameter name="Count">
<para>The total number of channels in the queue.</para>
</parameter>
</syntax>
<see-also>
<ref type="managerEvent">QueueCallerLeave</ref>
<ref type="application">Queue</ref>
</see-also>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="QueueCallerLeave">
<managerEventInstance class="EVENT_FLAG_AGENT">
<synopsis>Raised when a caller leaves a Queue.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueCallerJoin']/managerEventInstance/syntax/parameter[@name='Count'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueCallerJoin']/managerEventInstance/syntax/parameter[@name='Position'])" />
</syntax>
<see-also>
<ref type="managerEvent">QueueCallerJoin</ref>
</see-also>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="QueueCallerAbandon">
<managerEventInstance class="EVENT_FLAG_AGENT">
<synopsis>Raised when a caller abandons the queue.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueCallerJoin']/managerEventInstance/syntax/parameter[@name='Position'])" />
<parameter name="OriginalPosition">
<para>The channel's original position in the queue.</para>
</parameter>
<parameter name="HoldTime">
<para>The time the channel was in the queue, expressed in seconds since 00:00, Jan 1, 1970 UTC.</para>
</parameter>
</syntax>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="AgentCalled">
<managerEventInstance class="EVENT_FLAG_AGENT">
<synopsis>Raised when an queue member is notified of a caller in the queue.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='DialBegin']/managerEventInstance/syntax/parameter[contains(@name, 'Dest')])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='MemberName'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Interface'])" />
</syntax>
<see-also>
<ref type="managerEvent">AgentRingNoAnswer</ref>
<ref type="managerEvent">AgentComplete</ref>
<ref type="managerEvent">AgentConnect</ref>
</see-also>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="AgentRingNoAnswer">
<managerEventInstance class="EVENT_FLAG_AGENT">
<synopsis>Raised when a queue member is notified of a caller in the queue and fails to answer.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='DialBegin']/managerEventInstance/syntax/parameter[contains(@name, 'Dest')])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='MemberName'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Interface'])" />
<parameter name="RingTime">
<para>The time the queue member was rung, expressed in seconds since 00:00, Jan 1, 1970 UTC.</para>
</parameter>
</syntax>
<see-also>
<ref type="managerEvent">AgentCalled</ref>
</see-also>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="AgentComplete">
<managerEventInstance class="EVENT_FLAG_AGENT">
<synopsis>Raised when a queue member has finished servicing a caller in the queue.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='DialBegin']/managerEventInstance/syntax/parameter[contains(@name, 'Dest')])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='MemberName'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Interface'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueCallerAbandon']/managerEventInstance/syntax/parameter[@name='HoldTime'])" />
<parameter name="TalkTime">
<para>The time the queue member talked with the caller in the queue, expressed in seconds since 00:00, Jan 1, 1970 UTC.</para>
</parameter>
<parameter name="Reason">
<enumlist>
<enum name="caller"/>
<enum name="agent"/>
<enum name="transfer"/>
</enumlist>
</parameter>
</syntax>
<see-also>
<ref type="managerEvent">AgentCalled</ref>
<ref type="managerEvent">AgentConnect</ref>
</see-also>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="AgentDump">
<managerEventInstance class="EVENT_FLAG_AGENT">
<synopsis>Raised when a queue member hangs up on a caller in the queue.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='DialBegin']/managerEventInstance/syntax/parameter[contains(@name, 'Dest')])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='MemberName'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Interface'])" />
</syntax>
<see-also>
<ref type="managerEvent">AgentCalled</ref>
<ref type="managerEvent">AgentConnect</ref>
</see-also>
</managerEventInstance>
</managerEvent>
<managerEvent language="en_US" name="AgentConnect">
<managerEventInstance class="EVENT_FLAG_AGENT">
<synopsis>Raised when a queue member answers and is bridged to a caller in the queue.</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='DialBegin']/managerEventInstance/syntax/parameter[contains(@name, 'Dest')])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='MemberName'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Interface'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='AgentRingNoAnswer']/managerEventInstance/syntax/parameter[@name='RingTime'])" />
<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueCallerAbandon']/managerEventInstance/syntax/parameter[@name='HoldTime'])" />
</syntax>
<see-also>
<ref type="managerEvent">AgentCalled</ref>
<ref type="managerEvent">AgentComplete</ref>
<ref type="managerEvent">AgentDump</ref>
</see-also>
</managerEventInstance>
</managerEvent>
Jonathan Rose
committed
enum {
OPT_MARK_AS_ANSWERED = (1 << 0),
OPT_GO_ON = (1 << 1),
OPT_DATA_QUALITY = (1 << 2),
OPT_CALLEE_GO_ON = (1 << 3),
OPT_CALLEE_HANGUP = (1 << 4),
OPT_CALLER_HANGUP = (1 << 5),
OPT_IGNORE_CALL_FW = (1 << 6),
OPT_IGNORE_CONNECTEDLINE = (1 << 7),
Jonathan Rose
committed
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
OPT_CALLEE_PARK = (1 << 8),
OPT_CALLER_PARK = (1 << 9),
OPT_NO_RETRY = (1 << 10),
OPT_RINGING = (1 << 11),
OPT_RING_WHEN_RINGING = (1 << 12),
OPT_CALLEE_TRANSFER = (1 << 13),
OPT_CALLER_TRANSFER = (1 << 14),
OPT_CALLEE_AUTOMIXMON = (1 << 15),
OPT_CALLER_AUTOMIXMON = (1 << 16),
OPT_CALLEE_AUTOMON = (1 << 17),
OPT_CALLER_AUTOMON = (1 << 18),
};
enum {
OPT_ARG_CALLEE_GO_ON = 0,
/* note: this entry _MUST_ be the last one in the enum */
OPT_ARG_ARRAY_SIZE
};
AST_APP_OPTIONS(queue_exec_options, BEGIN_OPTIONS
AST_APP_OPTION('C', OPT_MARK_AS_ANSWERED),
AST_APP_OPTION('c', OPT_GO_ON),
AST_APP_OPTION('d', OPT_DATA_QUALITY),
AST_APP_OPTION_ARG('F', OPT_CALLEE_GO_ON, OPT_ARG_CALLEE_GO_ON),
AST_APP_OPTION('h', OPT_CALLEE_HANGUP),
AST_APP_OPTION('H', OPT_CALLER_HANGUP),
AST_APP_OPTION('i', OPT_IGNORE_CALL_FW),
AST_APP_OPTION('I', OPT_IGNORE_CONNECTEDLINE),
Jonathan Rose
committed
AST_APP_OPTION('k', OPT_CALLEE_PARK),
AST_APP_OPTION('K', OPT_CALLER_PARK),
AST_APP_OPTION('n', OPT_NO_RETRY),
AST_APP_OPTION('r', OPT_RINGING),
AST_APP_OPTION('R', OPT_RING_WHEN_RINGING),
AST_APP_OPTION('t', OPT_CALLEE_TRANSFER),
AST_APP_OPTION('T', OPT_CALLER_TRANSFER),
AST_APP_OPTION('x', OPT_CALLEE_AUTOMIXMON),
AST_APP_OPTION('X', OPT_CALLER_AUTOMIXMON),
AST_APP_OPTION('w', OPT_CALLEE_AUTOMON),
AST_APP_OPTION('W', OPT_CALLER_AUTOMON),
END_OPTIONS);
Russell Bryant
committed
enum {
QUEUE_STRATEGY_RINGALL = 0,
QUEUE_STRATEGY_LEASTRECENT,
QUEUE_STRATEGY_FEWESTCALLS,
QUEUE_STRATEGY_RANDOM,
Mark Michelson
committed
QUEUE_STRATEGY_RRMEMORY,
QUEUE_STRATEGY_WRANDOM,
QUEUE_STRATEGY_RRORDERED,
Russell Bryant
committed
};
Mark Michelson
committed
enum {
QUEUE_AUTOPAUSE_OFF = 0,
QUEUE_AUTOPAUSE_ON,
QUEUE_AUTOPAUSE_ALL
};
enum queue_reload_mask {
QUEUE_RELOAD_PARAMETERS = (1 << 0),
QUEUE_RELOAD_MEMBER = (1 << 1),
QUEUE_RELOAD_RULES = (1 << 2),
QUEUE_RESET_STATS = (1 << 3),
};
static const struct strategy {
int strategy;
} strategies[] = {
{ QUEUE_STRATEGY_RINGALL, "ringall" },
{ QUEUE_STRATEGY_LEASTRECENT, "leastrecent" },
{ QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" },
{ QUEUE_STRATEGY_RANDOM, "random" },
{ QUEUE_STRATEGY_RRMEMORY, "rrmemory" },
Tilghman Lesher
committed
{ QUEUE_STRATEGY_RRMEMORY, "roundrobin" },
Mark Michelson
committed
{ QUEUE_STRATEGY_LINEAR, "linear" },
{ QUEUE_STRATEGY_WRANDOM, "wrandom"},
{ QUEUE_STRATEGY_RRORDERED, "rrordered"},
};
Mark Michelson
committed
static const struct autopause {
int autopause;
const char *name;
} autopausesmodes [] = {
{ QUEUE_AUTOPAUSE_OFF,"no" },
{ QUEUE_AUTOPAUSE_ON, "yes" },
{ QUEUE_AUTOPAUSE_ALL,"all" },
};
#define DEFAULT_RETRY 5
#define DEFAULT_TIMEOUT 15
Mark Michelson
committed
#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 DEFAULT_MIN_ANNOUNCE_FREQUENCY 15 /*!< The minimum number of seconds between position announcements \
Mark Michelson
committed
The default value of 15 provides backwards compatibility */
Mark Michelson
committed
#define MAX_QUEUE_BUCKETS 53
Mark Michelson
committed
#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 */
#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";
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
/*! \brief Subscription to device state change messages */
static struct stasis_subscription *device_state_sub;
Russell Bryant
committed
/*! \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 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.
Mark Michelson
committed
*/
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)? */
Jonathan Rose
committed
int queuepos; /*!< In what order (pertains to certain strategies) should this member be called? */
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 */
Richard Mudgett
committed
unsigned int call_pending:1; /*!< TRUE if the Q is attempting to place a call to the member. */
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];
Kevin P. Fleming
committed
unsigned int ringinuse:1;
unsigned int announce_to_first_user:1; /*!< Whether or not we announce to the first user in a queue */
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 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);
#if 0 // BUGBUG
static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
#endif // BUGBUG
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
}
Jonathan Rose
committed
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
/*! \internal
* \brief ao2_callback, Decreases queuepos of all followers with a queuepos greater than arg.
* \param obj the member being acted on
* \param arg pointer to an integer containing the position value that was removed and requires reduction for anything above
*/
static int queue_member_decrement_followers(void *obj, void *arg, int flag)
{
struct member *mem = obj;
int *decrement_followers_after = arg;
if (mem->queuepos > *decrement_followers_after) {
mem->queuepos--;
}
return 0;
}
/*! \internal
* \brief ao2_callback, finds members in a queue marked for deletion and in a cascading fashion runs queue_member_decrement_followers
* on them. This callback should always be ran before performing mass unlinking of delmarked members from queues.
* \param obj member being acted on
* \param arg pointer to the queue members are being removed from
*/
static int queue_delme_members_decrement_followers(void *obj, void *arg, int flag)
{
struct member *mem = obj;
struct call_queue *queue = arg;
int rrpos = mem->queuepos;
if (mem->delme) {
ao2_callback(queue->members, OBJ_NODATA | OBJ_MULTIPLE, queue_member_decrement_followers, &rrpos);
}
return 0;
}
/*! \internal
* \brief Use this to decrement followers during removal of a member
* \param queue which queue the member is being removed from
* \param mem which member is being removed from the queue
*/
static void queue_member_follower_removal(struct call_queue *queue, struct member *mem)
{
int pos = mem->queuepos;
/* If the position being removed is less than the current place in the queue, reduce the queue position by one so that we don't skip the member
* who would have been next otherwise. */
if (pos < queue->rrpos) {
queue->rrpos--;
}
ao2_callback(queue->members, OBJ_NODATA | OBJ_MULTIPLE, queue_member_decrement_followers, &pos);
}
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);
Jason Parker
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;
}
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_caller_join_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_caller_leave_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_caller_abandon_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_member_status_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_member_added_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_member_removed_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_member_pause_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_member_penalty_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_member_ringinuse_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_agent_called_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_agent_connect_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_agent_complete_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_agent_dump_type);
STASIS_MESSAGE_TYPE_DEFN_LOCAL(queue_agent_ringnoanswer_type);
Jason Parker
committed
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
static void queue_channel_manager_event(void *data,
struct stasis_subscription *sub, struct stasis_topic *topic,
struct stasis_message *message)
{
const char *type = data;
struct ast_channel_blob *obj = stasis_message_data(message);
RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
RAII_VAR(struct ast_str *, event_string, NULL, ast_free);
channel_event_string = ast_manager_build_channel_state_string(obj->snapshot);
if (!channel_event_string) {
return;
}
event_string = ast_manager_str_from_json_object(obj->blob, NULL);
if (!event_string) {
return;
}
manager_event(EVENT_FLAG_AGENT, type,
"%s"
"%s",
ast_str_buffer(channel_event_string),
ast_str_buffer(event_string));
}
static void queue_multi_channel_manager_event(void *data,
struct stasis_subscription *sub, struct stasis_topic *topic,
struct stasis_message *message)
{
const char *type = data;
struct ast_multi_channel_blob *obj = stasis_message_data(message);
struct ast_channel_snapshot *caller;
struct ast_channel_snapshot *agent;
RAII_VAR(struct ast_str *, caller_event_string, NULL, ast_free);
RAII_VAR(struct ast_str *, agent_event_string, NULL, ast_free);
RAII_VAR(struct ast_str *, event_string, NULL, ast_free);
caller = ast_multi_channel_blob_get_channel(obj, "caller");
agent = ast_multi_channel_blob_get_channel(obj, "agent");
caller_event_string = ast_manager_build_channel_state_string(caller);
agent_event_string = ast_manager_build_channel_state_string_prefix(agent, "Dest");
if (!caller_event_string || !agent_event_string) {
return;
}
event_string = ast_manager_str_from_json_object(ast_multi_channel_blob_get_json(obj), NULL);
if (!event_string) {
return;
}
manager_event(EVENT_FLAG_AGENT, type,
"%s"
"%s"
"%s",
ast_str_buffer(caller_event_string),
ast_str_buffer(agent_event_string),
ast_str_buffer(event_string));
}
static void queue_member_manager_event(void *data,
struct stasis_subscription *sub, struct stasis_topic *topic,
struct stasis_message *message)
{
const char *type = data;
struct ast_json_payload *payload = stasis_message_data(message);
struct ast_json *event = payload->json;
RAII_VAR(struct ast_str *, event_string, NULL, ast_free);
event_string = ast_manager_str_from_json_object(event, NULL);
if (!event_string) {
return;
}
manager_event(EVENT_FLAG_AGENT, type,
"%s", ast_str_buffer(event_string));
}
static void queue_publish_multi_channel_blob(struct ast_channel *caller, struct ast_channel *agent, struct stasis_message_type *type, struct ast_json *blob)
{
RAII_VAR(struct ast_multi_channel_blob *, payload, NULL, ao2_cleanup);
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
struct ast_channel_snapshot *caller_snapshot;
struct ast_channel_snapshot *agent_snapshot;
payload = ast_multi_channel_blob_create(blob);
if (!payload) {
return;
}
caller_snapshot = ast_channel_snapshot_create(caller);
agent_snapshot = ast_channel_snapshot_create(agent);
if (!caller_snapshot || !agent_snapshot) {
return;
}
ast_multi_channel_blob_add_channel(payload, "caller", caller_snapshot);
ast_multi_channel_blob_add_channel(payload, "agent", agent_snapshot);
msg = stasis_message_create(type, payload);
if (!msg) {
return;
}
stasis_publish(ast_channel_topic(caller), msg);
}
static void queue_publish_member_blob(struct stasis_message_type *type, struct ast_json *blob)
{
RAII_VAR(struct ast_json_payload *, payload, NULL, ao2_cleanup);
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
payload = ast_json_payload_create(blob);
if (!payload) {
return;
}
msg = stasis_message_create(type, payload);
if (!msg) {
return;
}
stasis_publish(ast_manager_get_topic(), msg);
}
static struct ast_json *queue_member_blob_create(struct call_queue *q, struct member *mem)
{
return ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: i, s: i, s: i, s: i, s: i, s: i}",
"Queue", q->name,
"MemberName", mem->membername,
"Interface", mem->interface,
"StateInterface", mem->state_interface,
"Membership", (mem->dynamic ? "dynamic" : (mem->realtime ? "realtime" : "static")),
"Penalty", mem->penalty,
"CallsTaken", mem->calls,
"LastCall", (int)mem->lastcall,
"Status", mem->status,
"Paused", mem->paused,
"Ringinuse", mem->ringinuse);
}
/*! \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;