Skip to content
Snippets Groups Projects
app_queue.c 331 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			</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>
    
    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),
    
    	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),
    
    	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);
    
    
    enum {
    	QUEUE_STRATEGY_RINGALL = 0,
    	QUEUE_STRATEGY_LEASTRECENT,
    	QUEUE_STRATEGY_FEWESTCALLS,
    	QUEUE_STRATEGY_RANDOM,
    
    	QUEUE_STRATEGY_LINEAR,
    
    	QUEUE_STRATEGY_WRANDOM,
    	QUEUE_STRATEGY_RRORDERED,
    
    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 {
    
    } strategies[] = {
    	{ QUEUE_STRATEGY_RINGALL, "ringall" },
    	{ QUEUE_STRATEGY_LEASTRECENT, "leastrecent" },
    	{ QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" },
    	{ QUEUE_STRATEGY_RANDOM, "random" },
    
    	{ QUEUE_STRATEGY_RRMEMORY, "rrmemory" },
    
    	{ QUEUE_STRATEGY_RRMEMORY, "roundrobin" },
    
    	{ QUEUE_STRATEGY_WRANDOM, "wrandom"},
    
    	{ QUEUE_STRATEGY_RRORDERED, "rrordered"},
    
    static const struct autopause {
    	int autopause;
    	const char *name;
    } autopausesmodes [] = {
    	{ QUEUE_AUTOPAUSE_OFF,"no" },
    	{ QUEUE_AUTOPAUSE_ON, "yes" },
    	{ QUEUE_AUTOPAUSE_ALL,"all" },
    };
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    #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 */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    #define DEFAULT_MIN_ANNOUNCE_FREQUENCY 15       /*!< The minimum number of seconds between position announcements \
    
                                                         The default value of 15 provides backwards compatibility */
    
    #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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *app = "Queue";
    
    
    static char *app_aqm = "AddQueueMember" ;
    
    static char *app_rqm = "RemoveQueueMember" ;
    
    
    static char *app_pqm = "PauseQueueMember" ;
    
    static char *app_upqm = "UnpauseQueueMember" ;
    
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*! \brief Persistent Members astdb family */
    
    static const char * const pm_family = "Queue/PersistentMembers";
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*! \brief queues.conf [general] option */
    
    static int queue_persistent_members = 0;
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*! \brief queues.conf per-queue weight option */
    
    static int use_weight = 0;
    
    
    /*! \brief queues.conf [general] option */
    
    static int autofill_default = 1;
    
    /*! \brief queues.conf [general] option */
    static int montype_default = 0;
    
    
    static int shared_lastcall = 1;
    
    /*! \brief Subscription to device state change messages */
    static struct stasis_subscription *device_state_sub;
    
    Jason Parker's avatar
    Jason Parker committed
    /*! \brief queues.conf [general] option */
    static int update_cdr = 0;
    
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    /*! \brief queues.conf [general] option */
    static int negative_penalty_invalid = 0;
    
    
    /*! \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;
    
    
    enum queue_result {
    	QUEUE_UNKNOWN = 0,
    	QUEUE_TIMEOUT = 1,
    	QUEUE_JOINEMPTY = 2,
    	QUEUE_LEAVEEMPTY = 3,
    	QUEUE_JOINUNAVAIL = 4,
    	QUEUE_LEAVEUNAVAIL = 5,
    	QUEUE_FULL = 6,
    
    	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" },
    
    enum queue_timeout_priority {
    	TIMEOUT_PRIORITY_APP,
    	TIMEOUT_PRIORITY_CONF,
    };
    
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*! \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.
     *
     *  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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_channel *chan;
    
    Olle Johansson's avatar
    Olle Johansson committed
    	char interface[256];			/*!< An Asterisk dial string (not a channel name) */
    
    	int metric;
    
    	time_t lastcall;
    
    	/*! Saved connected party info from an AST_CONTROL_CONNECTED_LINE. */
    
    	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;
    
    	struct ast_aoc_decoded *aoc_s_rate_list;
    
    Mark Spencer's avatar
    Mark Spencer committed
    struct queue_ent {
    
    	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 */
    
    	char context[AST_MAX_CONTEXT];         /*!< Context when user exits queue */
    	char digits[AST_MAX_EXTENSION];        /*!< Digits entered while in queue */
    
    	int valid_digits;                      /*!< Digits entered correspond to valid extension. Exited */
    
    	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? */
    
    	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 */
    
    	int linpos;                            /*!< If using linear strategy, what position are we at? */
    	int linwrapped;                        /*!< Is the linpos wrapped? */
    
    	time_t start;                          /*!< When we started holding */
    	time_t expire;                         /*!< When this entry should expire (time out of queue) */
    
    	int cancel_answered_elsewhere;	       /*!< Whether we should force the CAE flag on this call (C) option*/
    
    	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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    };
    
    struct member {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	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) */
    
    Olle Johansson's avatar
    Olle Johansson committed
    	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)? */
    
    	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 */
    
    	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 */
    
    #define ANNOUNCEHOLDTIME_ALWAYS 1
    #define ANNOUNCEHOLDTIME_ONCE 2
    
    #define QUEUE_EVENT_VARIABLES 3
    
    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> */
    
    
    struct call_queue {
    
    	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);
    
    		/*! 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);
    
    		/*! 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 dead:1;
    
    	unsigned int announce_to_first_user:1; /*!< Whether or not we announce to the first user in a queue */
    
    	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 realtime:1;
    
    	unsigned int found:1;
    
    	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? */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	int announcefrequency;              /*!< How often to announce their position */
    
    	int minannouncefrequency;           /*!< The minimum number of seconds between position announcements (def. 15) */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	int periodicannouncefrequency;      /*!< How often to play periodic announcement */
    
    	int numperiodicannounce;            /*!< The number of periodic announcements configured */
    	int randomperiodicannounce;         /*!< Are periodic announcments randomly chosen */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	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 */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	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 */
    
    David Vossel's avatar
     
    David Vossel committed
    	int penaltymemberslimit;            /*!< Disregard penalty when queue has fewer than this many members */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    
    	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 */
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    	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? */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	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 */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	struct queue_ent *head;             /*!< Head of the list of callers */
    	AST_LIST_ENTRY(call_queue) list;    /*!< Next call queue */
    
    	AST_LIST_HEAD_NOLOCK(, penalty_rule) rules; /*!< The list of penalty rules to invoke */
    
    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);
    
    static void update_realtime_members(struct call_queue *q);
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    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);
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
    
    
    static struct member *find_member_by_queuename_and_interface(const char *queuename, const char *interface);
    
    /*! \brief sets the QUEUESTATUS channel variable */
    
    static void set_queue_result(struct ast_channel *chan, enum queue_result res)
    {
    	int i;
    
    
    	for (i = 0; i < ARRAY_LEN(queue_results); i++) {
    
    		if (queue_results[i].id == res) {
    			pbx_builtin_setvar_helper(chan, "QUEUESTATUS", queue_results[i].text);
    			return;
    		}
    	}
    }
    
    static const char *int2strat(int strategy)
    
    	for (x = 0; x < ARRAY_LEN(strategies); x++) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (strategy == strategies[x].strategy) {
    
    static int strat2int(const char *strategy)
    
    	for (x = 0; x < ARRAY_LEN(strategies); x++) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (!strcasecmp(strategy, strategies[x].name)) {
    
    static int autopause2int(const char *autopause)
    {
    	int x;
    	/*This 'double check' that default value is OFF */
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (ast_strlen_zero(autopause)) {
    
    
    	/*This 'double check' is to ensure old values works */
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if(ast_true(autopause)) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (!strcasecmp(autopause, autopausesmodes[x].name)) {
    
    	}
    
    	/*This 'double check' that default value is OFF */
    	return QUEUE_AUTOPAUSE_OFF;
    }
    
    
    static int queue_hash_cb(const void *obj, const int flags)
    {
    	const struct call_queue *q = obj;
    
    
    	return ast_str_case_hash(q->name);
    
    static int queue_cmp_cb(void *obj, void *arg, int flags)
    
    	return !strcasecmp(q->name, q2->name) ? CMP_MATCH | CMP_STOP : 0;
    
    /*! \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);
    }
    
    
    #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;
    }
    
    
    
    #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)
    
    
    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);
    
    static void set_queue_variables(struct call_queue *q, struct ast_channel *chan)
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (q->callscompleted > 0) {
    
    			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",
    
    			q->name, q->maxlen, int2strat(q->strategy), q->count, q->holdtime, q->talktime, q->callscompleted, q->callsabandoned,  q->servicelevel, sl);
    
    		pbx_builtin_setvar_multiple(chan, interfacevar);
    
    	} else {
    		ao2_unlock(q);
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*! \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);
    
    
    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)
    
    	struct ao2_iterator mem_iter;
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    
    	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;
    			}
    		}
    
    			if (conditions & QUEUE_EMPTY_INVALID) {
    				ast_debug(4, "%s is unavailable because his device state is 'invalid'\n", member->membername);
    				break;