Skip to content
Snippets Groups Projects
app_queue.c 118 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			tmp->metric = 0;
    		else
    			tmp->metric = 1000000 - (time(NULL) - mem->lastcall);
    		tmp->metric += mem->penalty * 1000000;
    
    		break;
    	default:
    		ast_log(LOG_WARNING, "Can't calculate metric for unknown strategy %d\n", q->strategy);
    		break;
    
    static int try_calling(struct queue_ent *qe, const char *options, char *announceoverride, const char *url, int *go_on)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	struct member *cur;
    
    	struct callattempt *outgoing=NULL; /* the queue we are building */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int to;
    
    	char oldexten[AST_MAX_EXTENSION]="";
    
    	char oldcontext[AST_MAX_CONTEXT]="";
    
    	char queuename[256]="";
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_channel *peer;
    
    	struct member *member;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int res = 0, bridge = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *announce = NULL;
    
    	time_t callstart;
    
    	struct ast_bridge_config bridge_config;
    	char nondataquality = 1;
    
    	memset(&bridge_config, 0, sizeof(bridge_config));
    
    		
    	for (; options && *options; options++)
    		switch (*options) {
    		case 't':
    			ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_REDIRECT);
    			break;
    		case 'T':
    			ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_REDIRECT);
    			break;
    
    		case 'w':
    			ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_AUTOMON);
    			break;
    		case 'W':
    			ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMON);
    			break;
    
    		case 'd':
    			nondataquality = 0;
    			break;
    		case 'h':
    			ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_DISCONNECT);
    			break;
    		case 'H':
    			ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT);
    			break;
    		case 'n':
    			if ((now - qe->start >= qe->parent->timeout))
    				*go_on = 1;
    			break;
    		}
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Hold the lock while we setup the outgoing calls */
    
    	if (use_weight) 
    
    		AST_LIST_LOCK(&queues);
    
    	if (option_debug)
    		ast_log(LOG_DEBUG, "%s is trying to call a queue member.\n", 
    							qe->chan->name);
    
    	ast_copy_string(queuename, qe->parent->name, sizeof(queuename));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	cur = qe->parent->members;
    
    	if (!ast_strlen_zero(qe->announce))
    
    Mark Spencer's avatar
    Mark Spencer committed
    		announce = qe->announce;
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	if (!ast_strlen_zero(announceoverride))
    
    Mark Spencer's avatar
    Mark Spencer committed
    		announce = announceoverride;
    
    	for (;cur; cur = cur->next) {
    		struct callattempt *tmp = ast_calloc(1, sizeof(*tmp));
    
    			ast_mutex_unlock(&qe->parent->lock);
    
    			if (use_weight) 
    
    				AST_LIST_UNLOCK(&queues);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			goto out;
    		}
    
    		tmp->stillgoing = -1;
    
    		if (option_debug) {
    			if (url)
    				ast_log(LOG_DEBUG, "Queue with URL=%s_\n", url);
    			else 
    				ast_log(LOG_DEBUG, "Simple queue (no URL)\n");
    		}
    
    		tmp->member = cur;		/* Never directly dereference!  Could change on reload */
    
    		tmp->lastcall = cur->lastcall;
    
    		ast_copy_string(tmp->interface, cur->interface, sizeof(tmp->interface));
    
    		/* Special case: If we ring everyone, go ahead and ring them, otherwise
    		   just calculate their metric for the appropriate strategy */
    
    		if (!calc_metric(qe->parent, cur, x++, qe, tmp)) {
    			/* Put them in the list of outgoing thingies...  We're ready now. 
    			   XXX If we're forcibly removed, these outgoing calls won't get
    			   hung up XXX */
    
    			outgoing = tmp;		
    			/* If this line is up, don't try anybody else */
    			if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP))
    				break;
    		} else {
    			free(tmp);
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	to = (qe->parent->timeout) ? qe->parent->timeout * 1000 : -1;
    
    	ring_one(qe, outgoing, &numbusies);
    
    	ast_mutex_unlock(&qe->parent->lock);
    
    	if (use_weight) 
    
    		AST_LIST_UNLOCK(&queues);
    
    	lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT));
    
    	ast_mutex_lock(&qe->parent->lock);
    	if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) {
    		store_next(qe, outgoing);
    	}
    	ast_mutex_unlock(&qe->parent->lock);
    
    	peer = lpeer ? lpeer->chan : NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!peer) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			res = -1;
    
    		if (option_debug)
    			ast_log(LOG_DEBUG, "%s: Nobody answered.\n", qe->chan->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Ah ha!  Someone answered within the desired timeframe.  Of course after this
    		   we will always return with -1 so that it is hung up properly after the 
    		   conversation.  */
    
    		if (!strcmp(qe->chan->tech->type, "Zap"))
    
    			ast_channel_setoption(qe->chan, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0);
    
    		if (!strcmp(peer->tech->type, "Zap"))
    
    			ast_channel_setoption(peer, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0);
    
    		/* Update parameters for the queue */
    
    		recalc_holdtime(qe);
    
    		member = lpeer->member;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		outgoing = NULL;
    
    		if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			int res2;
    
    			res2 = ast_autoservice_start(qe->chan);
    
    				if (qe->parent->memberdelay) {
    					ast_log(LOG_NOTICE, "Delaying member connect for %d seconds\n", qe->parent->memberdelay);
    					res2 |= ast_safe_sleep(peer, qe->parent->memberdelay * 1000);
    				}
    				if (!res2 && announce) {
    
    					if (play_file(peer, announce))
    						ast_log(LOG_WARNING, "Announcement file '%s' is unavailable, continuing anyway...\n", announce);
    				}
    
    					if (!play_file(peer, qe->parent->sound_reporthold)) {
    						int holdtime;
    
    						time(&now);
    						holdtime = abs((now - qe->start) / 60);
    						if (holdtime < 2) {
    							play_file(peer, qe->parent->sound_lessthan);
    							ast_say_number(peer, 2, AST_DIGIT_ANY, peer->language, NULL);
    						} else 
    							ast_say_number(peer, holdtime, AST_DIGIT_ANY, peer->language, NULL);
    						play_file(peer, qe->parent->sound_minutes);
    					}
    
    			res2 |= ast_autoservice_stop(qe->chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				/* Agent must have hung up */
    				ast_log(LOG_WARNING, "Agent on %s hungup on the customer.  They're going to be pissed.\n", peer->name);
    
    				ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "AGENTDUMP", "%s", "");
    
    Olle Johansson's avatar
    Olle Johansson committed
    				record_abandoned(qe);
    
    					manager_event(EVENT_FLAG_AGENT, "AgentDump",
    
    						      "Queue: %s\r\n"
    						      "Uniqueid: %s\r\n"
    						      "Channel: %s\r\n"
    						      "Member: %s\r\n",
    						      queuename, qe->chan->uniqueid, peer->name, member->interface);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				ast_hangup(peer);
    
    				goto out;
    			} else if (res2) {
    				/* Caller must have hung up just before being connected*/
    				ast_log(LOG_NOTICE, "Caller was about to talk to agent on %s but the caller hungup.\n", peer->name);
    				ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "ABANDON", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start);
    				record_abandoned(qe);
    				ast_hangup(peer);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				return -1;
    			}
    		}
    
    		/* Stop music on hold */
    		ast_moh_stop(qe->chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* If appropriate, log that we have a destination channel */
    		if (qe->chan->cdr)
    			ast_cdr_setdestchan(qe->chan->cdr, peer->name);
    		/* Make sure channels are compatible */
    		res = ast_channel_make_compatible(qe->chan, peer);
    		if (res < 0) {
    
    			ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "SYSCOMPAT", "%s", "");
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", qe->chan->name, peer->name);
    
    Olle Johansson's avatar
    Olle Johansson committed
    		record_abandoned(qe);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ast_hangup(peer);
    			return -1;
    		}
    
    		/* Begin Monitoring */
    		if (qe->parent->monfmt && *qe->parent->monfmt) {
    
    			const char *monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME");
    
    			if (pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC") || pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC_ARGS"))
    
    				ast_monitor_start(which, qe->parent->monfmt, monitorfilename, 1 );
    
    				ast_monitor_start(which, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1 );
    
    			else {
    				/* Last ditch effort -- no CDR, make up something */
    				char tmpid[256];
    
    				snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
    
    				ast_monitor_start(which, qe->parent->monfmt, tmpid, 1 );
    			}
    
    				ast_monitor_setjoinfiles(which, 1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Drop out of the queue at this point, to prepare for next caller */
    		leave_queue(qe);			
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
     		if (!ast_strlen_zero(url) && ast_channel_supports_html(peer)) {
    
    			if (option_debug)
    	 			ast_log(LOG_DEBUG, "app_queue: sendurl=%s.\n", url);
    
    		ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "CONNECT", "%ld", (long)time(NULL) - qe->start);
    
    			manager_event(EVENT_FLAG_AGENT, "AgentConnect",
    
    				      "Queue: %s\r\n"
    				      "Uniqueid: %s\r\n"
    				      "Channel: %s\r\n"
    				      "Member: %s\r\n"
    				      "Holdtime: %ld\r\n",
    				      queuename, qe->chan->uniqueid, peer->name, member->interface,
    				      (long)time(NULL) - qe->start);
    
    		ast_copy_string(oldcontext, qe->chan->context, sizeof(oldcontext));
    		ast_copy_string(oldexten, qe->chan->exten, sizeof(oldexten));
    
    		time(&callstart);
    
    		bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
    
    		if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) {
    			ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "TRANSFER", "%s|%s", qe->chan->exten, qe->chan->context);
    		} else if (qe->chan->_softhangup) {
    
    			ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "COMPLETECALLER", "%ld|%ld",
    				      (long)(callstart - qe->start), (long)(time(NULL) - callstart));
    			if (qe->parent->eventwhencalled)
    
    				manager_event(EVENT_FLAG_AGENT, "AgentComplete",
    
    					      "Queue: %s\r\n"
    					      "Uniqueid: %s\r\n"
    					      "Channel: %s\r\n"
    					      "Member: %s\r\n"
    					      "HoldTime: %ld\r\n"
    					      "TalkTime: %ld\r\n"
    					      "Reason: caller\r\n",
    					      queuename, qe->chan->uniqueid, peer->name, member->interface,
    					      (long)(callstart - qe->start), (long)(time(NULL) - callstart));
    
    		} else {
    			ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "COMPLETEAGENT", "%ld|%ld", (long)(callstart - qe->start), (long)(time(NULL) - callstart));
    
    				manager_event(EVENT_FLAG_AGENT, "AgentComplete",
    
    					      "Queue: %s\r\n"
    					      "Uniqueid: %s\r\n"
    					      "Channel: %s\r\n"
    					      "HoldTime: %ld\r\n"
    					      "TalkTime: %ld\r\n"
    					      "Reason: agent\r\n",
    					      queuename, qe->chan->uniqueid, peer->name, (long)(callstart - qe->start),
    					      (long)(time(NULL) - callstart));
    
    
    		if(bridge != AST_PBX_NO_HANGUP_PEER)
    			ast_hangup(peer);
    
    		update_queue(qe->parent, member);
    
    		if (bridge == 0) 
    
    			res = 1; /* JDG: bridge successfully, leave app_queue */
    
    		else 
    			res = bridge; /* bridge error, stay in the queue */
    
    Mark Spencer's avatar
    Mark Spencer committed
    out:
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    static int wait_a_bit(struct queue_ent *qe)
    {
    
    	/* Don't need to hold the lock while we setup the outgoing calls */
    	int retrywait = qe->parent->retry * 1000;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return ast_waitfordigit(qe->chan, retrywait);
    }
    
    
    static struct member * interface_exists(struct ast_call_queue *q, char *interface)
    
    {
    	struct member *mem;
    
    
    	if (q)
    		for (mem = q->members; mem; mem = mem->next)
    
    			if (!strcasecmp(interface, mem->interface))
    
    /* Dump all members in a specific queue to the database
    
     * <pm_family>/<queuename> = <interface>;<penalty>;<paused>[|...]
    
     *
     */
    static void dump_queue_members(struct ast_call_queue *pm_queue)
    {
    
    	struct member *cur_member;
    
    	char value[PM_MAX_LEN];
    	int value_len = 0;
    	int res;
    
    	memset(value, 0, sizeof(value));
    
    
    	if (!pm_queue)
    		return;
    
    	for (cur_member = pm_queue->members; cur_member; cur_member = cur_member->next) {
    		if (!cur_member->dynamic)
    			continue;
    
    		res = snprintf(value + value_len, sizeof(value) - value_len, "%s;%d;%d%s",
    			       cur_member->interface, cur_member->penalty, cur_member->paused,
    			       cur_member->next ? "|" : "");
    		if (res != strlen(value + value_len)) {
    			ast_log(LOG_WARNING, "Could not create persistent member string, out of space\n");
    			break;
    		}
    		value_len += res;
    
    	
    	if (value_len && !cur_member) {
    		if (ast_db_put(pm_family, pm_queue->name, value))
    			ast_log(LOG_WARNING, "failed to create persistent dynamic entry!\n");
    	} else
    		/* Delete the entry if the queue is empty or there is an error */
    		ast_db_del(pm_family, pm_queue->name);
    
    static int remove_from_queue(char *queuename, char *interface)
    {
    	struct ast_call_queue *q;
    	struct member *last_member, *look;
    	int res = RES_NOSUCHQUEUE;
    
    
    	AST_LIST_LOCK(&queues);
    	AST_LIST_TRAVERSE(&queues, q, list) {
    
    		ast_mutex_lock(&q->lock);
    		if (!strcmp(q->name, queuename)) {
    			if ((last_member = interface_exists(q, interface))) {
    				if ((look = q->members) == last_member) {
    					q->members = last_member->next;
    				} else {
    					while (look != NULL) {
    						if (look->next == last_member) {
    							look->next = last_member->next;
    							break;
    						} else {
    							 look = look->next;
    						}
    					}
    				}
    
    				manager_event(EVENT_FLAG_AGENT, "QueueMemberRemoved",
    						"Queue: %s\r\n"
    
    						"Location: %s\r\n",
    					q->name, last_member->interface);
    
    
    				if (queue_persistent_members)
    
    Olle Johansson's avatar
    Olle Johansson committed
    					dump_queue_members(q);
    
    				res = RES_OKAY;
    			} else {
    				res = RES_EXISTS;
    			}
    			ast_mutex_unlock(&q->lock);
    			break;
    		}
    		ast_mutex_unlock(&q->lock);
    	}
    
    	AST_LIST_UNLOCK(&queues);
    
    static int add_to_queue(char *queuename, char *interface, int penalty, int paused, int dump)
    
    {
    	struct ast_call_queue *q;
    	struct member *new_member;
    	int res = RES_NOSUCHQUEUE;
    
    
    	/* \note Ensure the appropriate realtime queue is loaded.  Note that this
    	 * short-circuits if the queue is already in memory. */
    	q = load_realtime_queue(queuename);
    
    
    	AST_LIST_LOCK(&queues);
    
    		if (interface_exists(q, interface) == NULL) {
    			new_member = create_queue_member(interface, penalty, paused);
    
    			if (new_member != NULL) {
    				new_member->dynamic = 1;
    				new_member->next = q->members;
    				q->members = new_member;
    				manager_event(EVENT_FLAG_AGENT, "QueueMemberAdded",
    
    Olle Johansson's avatar
    Olle Johansson committed
    						"Queue: %s\r\n"
    						"Location: %s\r\n"
    						"Membership: %s\r\n"
    						"Penalty: %d\r\n"
    						"CallsTaken: %d\r\n"
    						"LastCall: %d\r\n"
    						"Status: %d\r\n"
    						"Paused: %d\r\n",
    						q->name, new_member->interface, new_member->dynamic ? "dynamic" : "static",
    						new_member->penalty, new_member->calls, (int)new_member->lastcall, new_member->status, new_member->paused);
    
    
    	AST_LIST_UNLOCK(&queues);
    
    static int set_member_paused(char *queuename, char *interface, int paused)
    {
    	int found = 0;
    	struct ast_call_queue *q;
    	struct member *mem;
    
    	/* Special event for when all queues are paused - individual events still generated */
    
    	if (ast_strlen_zero(queuename))
    		ast_queue_log("NONE", "NONE", interface, (paused ? "PAUSEALL" : "UNPAUSEALL"), "%s", "");
    
    
    	AST_LIST_LOCK(&queues);
    	AST_LIST_TRAVERSE(&queues, q, list) {
    
    		ast_mutex_lock(&q->lock);
    
    		if (ast_strlen_zero(queuename) || !strcasecmp(q->name, queuename)) {
    
    			if ((mem = interface_exists(q, interface))) {
    				found++;
    				if (mem->paused == paused)
    					ast_log(LOG_DEBUG, "%spausing already-%spaused queue member %s:%s\n", (paused ? "" : "un"), (paused ? "" : "un"), q->name, interface);
    				mem->paused = paused;
    
    				if (queue_persistent_members)
    
    Olle Johansson's avatar
    Olle Johansson committed
    					dump_queue_members(q);
    
    
    				ast_queue_log(q->name, "NONE", interface, (paused ? "PAUSE" : "UNPAUSE"), "%s", "");
    
    				manager_event(EVENT_FLAG_AGENT, "QueueMemberPaused",
    					"Queue: %s\r\n"
    					"Location: %s\r\n"
    					"Paused: %d\r\n",
    						q->name, mem->interface, paused);
    			}
    		}
    		ast_mutex_unlock(&q->lock);
    	}
    
    	AST_LIST_UNLOCK(&queues);
    
    
    	if (found)
    		return RESULT_SUCCESS;
    	else
    		return RESULT_FAILURE;
    }
    
    
    /* Reload dynamic queue members persisted into the astdb */
    
    static void reload_queue_members(void)
    {
    
    	char *cur_ptr;	
    	char *queue_name;
    	char *member;
    	char *interface;
    	char *penalty_tok;
    	int penalty = 0;
    	char *paused_tok;
    	int paused = 0;
    	struct ast_db_entry *db_tree;
    	struct ast_db_entry *entry;
    	struct ast_call_queue *cur_queue;
    
    	char queue_data[PM_MAX_LEN];
    
    
    	AST_LIST_LOCK(&queues);
    
    
    	/* Each key in 'pm_family' is the name of a queue */
    	db_tree = ast_db_gettree(pm_family, NULL);
    	for (entry = db_tree; entry; entry = entry->next) {
    
    		queue_name = entry->key + strlen(pm_family) + 2;
    
    		AST_LIST_TRAVERSE(&queues, cur_queue, list) {
    
    			ast_mutex_lock(&cur_queue->lock);
    
    			if (!strcmp(queue_name, cur_queue->name))
    
    			ast_mutex_unlock(&cur_queue->lock);
    		}
    
    		if (!cur_queue) {
    			/* If the queue no longer exists, remove it from the
    			 * database */
    
    			ast_db_del(pm_family, queue_name);
    
    			ast_mutex_unlock(&cur_queue->lock);
    
    		if (ast_db_get(pm_family, queue_name, queue_data, PM_MAX_LEN))
    			continue;
    
    		cur_ptr = queue_data;
    		while ((member = strsep(&cur_ptr, "|"))) {
    			if (ast_strlen_zero(member))
    				continue;
    
    			interface = strsep(&member, ";");
    			penalty_tok = strsep(&member, ";");
    			paused_tok = strsep(&member, ";");
    
    			if (!penalty_tok) {
    
    				ast_log(LOG_WARNING, "Error parsing persistent member string for '%s' (penalty)\n", queue_name);
    
    				break;
    			}
    			penalty = strtol(penalty_tok, NULL, 10);
    			if (errno == ERANGE) {
    				ast_log(LOG_WARNING, "Error converting penalty: %s: Out of range.\n", penalty_tok);
    				break;
    			}
    			
    			if (!paused_tok) {
    				ast_log(LOG_WARNING, "Error parsing persistent member string for '%s' (paused)\n", queue_name);
    				break;
    			}
    			paused = strtol(paused_tok, NULL, 10);
    			if ((errno == ERANGE) || paused < 0 || paused > 1) {
    				ast_log(LOG_WARNING, "Error converting paused: %s: Expected 0 or 1.\n", paused_tok);
    				break;
    			}
    
    			if (option_debug)
    				ast_log(LOG_DEBUG, "Reload Members: Queue: %s  Member: %s  Penalty: %d  Paused: %d\n", queue_name, interface, penalty, paused);
    			
    			if (add_to_queue(queue_name, interface, penalty, paused, 0) == RES_OUTOFMEMORY) {
    				ast_log(LOG_ERROR, "Out of Memory when reloading persistent queue member\n");
    				break;
    
    	AST_LIST_UNLOCK(&queues);
    
    		ast_log(LOG_NOTICE, "Queue members successfully reloaded from database.\n");
    
    		ast_db_freetree(db_tree);
    
    static int pqm_exec(struct ast_channel *chan, void *data)
    {
    
    	char *parse;
    	int priority_jump = 0;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(interface);
    		AST_APP_ARG(options);
    	);
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_WARNING, "PauseQueueMember requires an argument ([queuename]|interface[|options])\n");
    
    	if (!(parse = ast_strdupa(data)))
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    
    	if (args.options) {
    		if (strchr(args.options, 'j'))
    			priority_jump = 1;
    	}
    
    	if (ast_strlen_zero(args.interface)) {
    		ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options])\n");
    
    	if (set_member_paused(args.queuename, args.interface, 1)) {
    		ast_log(LOG_WARNING, "Attempt to pause interface %s, not found\n", args.interface);
    
    		if (priority_jump || ast_opt_priority_jumping) {
    
    			if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
    				pbx_builtin_setvar_helper(chan, "PQMSTATUS", "NOTFOUND");
    
    		pbx_builtin_setvar_helper(chan, "PQMSTATUS", "NOTFOUND");
    
    	pbx_builtin_setvar_helper(chan, "PQMSTATUS", "PAUSED");
    
    	return 0;
    }
    
    static int upqm_exec(struct ast_channel *chan, void *data)
    {
    
    	char *parse;
    	int priority_jump = 0;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(interface);
    		AST_APP_ARG(options);
    	);
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_WARNING, "UnpauseQueueMember requires an argument ([queuename]|interface[|options])\n");
    
    	if (!(parse = ast_strdupa(data))) 
    
    		return -1;
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    
    	if (args.options) {
    		if (strchr(args.options, 'j'))
    			priority_jump = 1;
    	}
    
    	if (ast_strlen_zero(args.interface)) {
    		ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options])\n");
    
    	if (set_member_paused(args.queuename, args.interface, 0)) {
    		ast_log(LOG_WARNING, "Attempt to unpause interface %s, not found\n", args.interface);
    
    		if (priority_jump || ast_opt_priority_jumping) {
    
    			if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
    				pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND");
    
    		pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND");
    
    	pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "UNPAUSED");
    
    static int rqm_exec(struct ast_channel *chan, void *data)
    {
    	int res=-1;
    
    	char *parse, *temppos = NULL;
    	int priority_jump = 0;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(interface);
    		AST_APP_ARG(options);
    	);
    
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_WARNING, "RemoveQueueMember requires an argument (queuename[|interface[|options]])\n");
    
    		return -1;
    	}
    
    	if (!(parse = ast_strdupa(data)))
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    
    	if (ast_strlen_zero(args.interface)) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		args.interface = ast_strdupa(chan->name);
    
    		temppos = strrchr(args.interface, '-');
    		if (temppos)
    			*temppos = '\0';
    
    	if (args.options) {
    		if (strchr(args.options, 'j'))
    			priority_jump = 1;
    	}
    
    	switch (remove_from_queue(args.queuename, args.interface)) {
    
    		ast_log(LOG_NOTICE, "Removed interface '%s' from queue '%s'\n", args.interface, args.queuename);
    		pbx_builtin_setvar_helper(chan, "RQMSTATUS", "REMOVED");
    
    		ast_log(LOG_WARNING, "Unable to remove interface '%s' from queue '%s': Not there\n", args.interface, args.queuename);
    
    		if (priority_jump || ast_opt_priority_jumping) 
    
    			ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
    		pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOTINQUEUE");
    
    		res = 0;
    		break;
    	case RES_NOSUCHQUEUE:
    
    		ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': No such queue\n", args.queuename);
    		pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOSUCHQUEUE");
    
    		res = 0;
    		break;
    	case RES_OUTOFMEMORY:
    		ast_log(LOG_ERROR, "Out of memory\n");
    		break;
    	}
    
    	return res;
    }
    
    static int aqm_exec(struct ast_channel *chan, void *data)
    {
    	int res=-1;
    
    	char *parse, *temppos = NULL;
    	int priority_jump = 0;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(interface);
    		AST_APP_ARG(penalty);
    		AST_APP_ARG(options);
    	);
    
    	int penalty = 0;
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename[|[interface]|[penalty][|options]])\n");
    
    		return -1;
    	}
    
    	if (!(parse = ast_strdupa(data)))
    		return -1;
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    
    	if (ast_strlen_zero(args.interface)) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		args.interface = ast_strdupa(chan->name);
    
    		temppos = strrchr(args.interface, '-');
    		if (temppos)
    			*temppos = '\0';
    	}
    
    	if (!ast_strlen_zero(args.penalty)) {
    		if ((sscanf(args.penalty, "%d", &penalty) != 1) || penalty < 0) {
    			ast_log(LOG_WARNING, "Penalty '%s' is invalid, must be an integer >= 0\n", args.penalty);
    			penalty = 0;
    
    	
    	if (args.options) {
    		if (strchr(args.options, 'j'))
    			priority_jump = 1;
    	}
    
    
    	switch (add_to_queue(args.queuename, args.interface, penalty, 0, queue_persistent_members)) {
    
    		ast_log(LOG_NOTICE, "Added interface '%s' to queue '%s'\n", args.interface, args.queuename);
    		pbx_builtin_setvar_helper(chan, "AQMSTATUS", "ADDED");
    
    		ast_log(LOG_WARNING, "Unable to add interface '%s' to queue '%s': Already there\n", args.interface, args.queuename);
    
    		if (priority_jump || ast_opt_priority_jumping) 
    
    			ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
    		pbx_builtin_setvar_helper(chan, "AQMSTATUS", "MEMBERALREADY");
    
    		res = 0;
    		break;
    	case RES_NOSUCHQUEUE:
    
    		ast_log(LOG_WARNING, "Unable to add interface to queue '%s': No such queue\n", args.queuename);
    		pbx_builtin_setvar_helper(chan, "AQMSTATUS", "NOSUCHQUEUE");
    
    		res = 0;
    		break;
    	case RES_OUTOFMEMORY:
    
    		ast_log(LOG_ERROR, "Out of memory adding member %s to queue %s\n", args.interface, args.queuename);
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int queue_exec(struct ast_channel *chan, void *data)
    {
    	int res=-1;
    
    	/* whether to exit Queue application after the timeout hits */
    	int go_on = 0;
    
    
    	char *parse;
    	AST_DECLARE_APP_ARGS(args,
    		 AST_APP_ARG(queuename);
    		 AST_APP_ARG(options);
    		 AST_APP_ARG(url);
    		 AST_APP_ARG(announceoverride);
    		 AST_APP_ARG(queuetimeoutstr);
    	);
    	
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Our queue entry */
    	struct queue_ent qe;
    	
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_WARNING, "Queue requires an argument: queuename[|options[|URL][|announceoverride][|timeout]]\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return -1;
    	}
    
    	
    	parse = ast_strdupa(data);
    	if (!parse) {
    		ast_log(LOG_ERROR, "Out of memory!\n");
    		return -1;
    	}
    	AST_STANDARD_APP_ARGS(args, parse);
    
    
    	/* Setup our queue entry */
    	memset(&qe, 0, sizeof(qe));
    
    	qe.start = time(NULL);
    
    	/* set the expire time based on the supplied timeout; */
    
    	if (args.queuetimeoutstr)
    		qe.expire = qe.start + atoi(args.queuetimeoutstr);
    
    
    	/* Get the priority from the variable ${QUEUE_PRIO} */
    	user_priority = pbx_builtin_getvar_helper(chan, "QUEUE_PRIO");
    	if (user_priority) {
    		if (sscanf(user_priority, "%d", &prio) == 1) {
    			if (option_debug)
    				ast_log(LOG_DEBUG, "%s: Got priority %d from ${QUEUE_PRIO}.\n",
    
    		} else {
    			ast_log(LOG_WARNING, "${QUEUE_PRIO}: Invalid value (%s), channel %s.\n",
    
    		if (option_debug > 2)
    
    			ast_log(LOG_DEBUG, "NO QUEUE_PRIO variable found. Using default.\n");
    		prio = 0;
    
    	/* Get the maximum penalty from the variable ${QUEUE_MAX_PENALTY} */
    	if ((max_penalty_str = pbx_builtin_getvar_helper(chan, "QUEUE_MAX_PENALTY"))) {
    		if (sscanf(max_penalty_str, "%d", &max_penalty) == 1) {
    			if (option_debug)
    				ast_log(LOG_DEBUG, "%s: Got max penalty %d from ${QUEUE_MAX_PENALTY}.\n",
    					chan->name, max_penalty);
    		} else {
    			ast_log(LOG_WARNING, "${QUEUE_MAX_PENALTY}: Invalid value (%s), channel %s.\n",
    				max_penalty_str, chan->name);
    			max_penalty = 0;
    		}
    	} else {
    		max_penalty = 0;
    	}
    
    
    	if (args.options && (strchr(args.options, 'r')))
    
    	if (option_debug)  
    
    		ast_log(LOG_DEBUG, "queue: %s, options: %s, url: %s, announce: %s, expires: %ld, priority: %d\n",
    
    			args.queuename, args.options, args.url, args.announceoverride, (long)qe.expire, prio);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	qe.chan = chan;
    
    	qe.last_pos_said = 0;
    	qe.last_pos = 0;
    
    	qe.last_periodic_announce_time = time(NULL);
    
    	qe.last_periodic_announce_sound = 0;
    
    	if (!join_queue(args.queuename, &qe, &reason)) {
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    		ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", S_OR(args.url, ""),
    			      S_OR(chan->cid.cid_num, ""));
    
    		if (ringing) {
    			ast_indicate(chan, AST_CONTROL_RINGING);
    
    Olle Johansson's avatar
    Olle Johansson committed
    		} else {
    
    			ast_moh_start(chan, qe.moh);
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		for (;;) {
    
    			/* This is the wait loop for callers 2 through maxlen */
    
    
    			res = wait_our_turn(&qe, ringing, &reason);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* If they hungup, return immediately */
    			if (res < 0) {
    
    				/* Record this abandoned call */
    				record_abandoned(&qe);
    
    				 ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (option_verbose > 2) {
    
    					ast_verbose(VERBOSE_PREFIX_3 "User disconnected from queue %s while waiting their turn\n", args.queuename);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				}
    
    Olle Johansson's avatar
    Olle Johansson committed
    				res = -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    				break;
    			}
    
    Mark Spencer's avatar
    Mark Spencer committed
    				break;
    
    			if (valid_exit(&qe, res)) {
    
    				ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%s|%d", qe.digits, qe.pos);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				break;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    		if (!res) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			for (;;) {
    
    				/* This is the wait loop for the head caller*/
    				/* To exit, they may get their call answered; */
    				/* they may dial a digit from the queue context; */
    
    				/* Leave if we have exceeded our queuetimeout */
    
    				if (qe.expire && (time(NULL) > qe.expire)) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    					record_abandoned(&qe);
    
    					ast_queue_log(args.queuename, chan->uniqueid,"NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
    
    				if (makeannouncement) {
    					/* Make a position announcement, if enabled */
    					if (qe.parent->announcefrequency && !ringing)
    
    						 ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%s|%d", qe.digits, qe.pos);
    
    				/* Make a periodic announcement, if enabled */
    				if (qe.parent->periodicannouncefrequency && !ringing)
    					res = say_periodic_announcement(&qe);
    
    				if (res && valid_exit(&qe, res)) {
    
    					ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
    
    				/* Try calling all queue members for 'timeout' seconds */
    
    				res = try_calling(&qe, args.options, args.announceoverride, args.url, &go_on);