Skip to content
Snippets Groups Projects
app_queue.c 315 KiB
Newer Older
  • Learn to ignore specific revisions
  • #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";
    
    /* The maximum length of each persistent member queue database entry */
    
    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 events */
    static struct ast_event_sub *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 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;
    
    
    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)? */
    	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 */
    
    #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 eventwhencalled:2;
    
    	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 maskmemberstatus:1;
    	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;
    
    #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;
    }
    
    
    /*! \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;
    			}
    
    			if (conditions & QUEUE_EMPTY_UNAVAILABLE) {
    				ast_debug(4, "%s is unavailable because his device state is 'unavailable'\n", member->membername);
    				break;
    			}
    
    		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;
    			}
    
    		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;
    			}
    
    			goto default_case;
    
    		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;
    			}
    
    			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;
    
    				ao2_ref(member, -1);
    
    				ao2_iterator_destroy(&mem_iter);
    
    				ast_debug(4, "%s is available.\n", member->membername);
    				return 0;
    
    	ao2_iterator_destroy(&mem_iter);
    
    Mark Spencer's avatar
    Mark Spencer committed
    struct statechange {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int state;
    	char dev[0];
    };
    
    
    /*! \brief set a member's status based on device state of that member's state_interface.
    
     * 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)
    
    	m->status = status;
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (q->maskmemberstatus) {
    
    	/*** 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"
    
    		"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);
    
    	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));
    
    Olle Johansson's avatar
    Olle Johansson committed
    			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);
    
    	ao2_iterator_destroy(&qiter);
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (found) {
    
    		ast_debug(1, "Device '%s' changed to state '%d' (%s)\n", sc->dev, sc->state, ast_devstate2str(sc->state));
    
    Olle Johansson's avatar
    Olle Johansson committed
    	} else {
    
    		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));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    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;
    
    
    	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);
    	}
    
    /*! \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);
    
    	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);
    
    	}
    	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)
    
    	if ((cur = ao2_alloc(sizeof(*cur), NULL))) {
    
    		cur->penalty = penalty;
    		cur->paused = paused;
    		ast_copy_string(cur->interface, interface, sizeof(cur->interface));
    
    		if (!ast_strlen_zero(state_interface)) {
    
    			ast_copy_string(cur->state_interface, state_interface, sizeof(cur->state_interface));
    
    			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);
    
    static int compress_char(const char c)
    {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (c < 32) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	} else if (c > 96) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	} 
    	return c - 32;
    
    }
    
    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;
    
     * \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)
    
    	q->timeout = DEFAULT_TIMEOUT;
    
    	q->minannouncefrequency = DEFAULT_MIN_ANNOUNCE_FREQUENCY;
    
    	q->announcepositionlimit = 10; /* Default 10 positions */
    	q->announceposition = ANNOUNCEPOSITION_YES; /* Default yes */
    
    	q->roundingseconds = 0; /* Default - don't announce seconds */
    	q->servicelevel = 0;
    
    	q->setqueuevar = 0;
    	q->setqueueentryvar = 0;
    
    	q->reportholdtime = 0;
    	q->wrapuptime = 0;
    
    David Vossel's avatar
     
    David Vossel committed
    	q->penaltymemberslimit = 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;
    
    	q->timeoutpriority = TIMEOUT_PRIORITY_APP;
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    	q->autopausedelay = 0;
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (q->strategy == QUEUE_STRATEGY_LINEAR || q->strategy == QUEUE_STRATEGY_RRORDERED) {
    
    			/* 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);
    
    Olle Johansson's avatar
    Olle Johansson committed
    		} else {
    
    			q->members = ao2_container_alloc(37, member_hash_fn, member_cmp_fn);
    
    
    	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");
    
    	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++) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (q->sound_periodicannounce[i]) {
    
    			ast_str_set(&q->sound_periodicannounce[i], 0, "%s", "");
    
    Olle Johansson's avatar
    Olle Johansson committed
    	while ((pr_iter = AST_LIST_REMOVE_HEAD(&q->rules,list))) {
    
    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;
    
    		ao2_iterator_destroy(&mem_iter);
    
     * \brief Change queue penalty by adding rule.
     *
    
     * Check rule for errors with time or fomatting, see if rule is relative to rest
    
     * 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
    
    static int insert_penaltychange(const char *list_name, const char *content, const int linenum)
    
    {
    	char *timestr, *maxstr, *minstr, *contentdup;
    	struct penalty_rule *rule = NULL, *rule_iter;
    	struct rule_list *rl_iter;
    
    	int penaltychangetime, inserted = 0;
    
    
    	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;