Skip to content
Snippets Groups Projects
app_queue.c 170 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    				ast_hangup(peer);
    
    				ao2_ref(member, -1);
    
    				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, member->membername, "ABANDON", "%d|%d|%ld", qe->pos, qe->opos, (long) time(NULL) - qe->start);
    
    				record_abandoned(qe);
    				ast_hangup(peer);
    
    				ao2_ref(member, -1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				return -1;
    			}
    		}
    
    		/* Stop music on hold */
    
    		if (ringing)
    			ast_indicate(qe->chan,-1);
    		else
    			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, member->membername, "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);
    
    			record_abandoned(qe);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ast_hangup(peer);
    
    			ao2_ref(member, -1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			return -1;
    		}
    
    
    		/* Play announcement to the caller telling it's his turn if defined */
    		if (!ast_strlen_zero(qe->parent->sound_callerannounce)) {
    			if (play_file(qe->chan, qe->parent->sound_callerannounce))
    				ast_log(LOG_WARNING, "Announcement file '%s' is unavailable, continuing anyway...\n", qe->parent->sound_callerannounce);
    		}
    
    
    		/* if setinterfacevar is defined, make member variables available to the channel */
    		/* use  pbx_builtin_setvar to set a load of variables with one call */
    		if (qe->parent->setinterfacevar) {
    			snprintf(interfacevar,sizeof(interfacevar), "MEMBERINTERFACE=%s|MEMBERNAME=%s|MEMBERCALLS=%d|MEMBERLASTCALL=%ld|MEMBERPENALTY=%d|MEMBERDYNAMIC=%d|MEMBERREALTIME=%d",
    				member->interface, member->membername, member->calls, (long)member->lastcall, member->penalty, member->dynamic, member->realtime);
    		 	pbx_builtin_setvar(qe->chan, interfacevar);
    		}
    		
    		/* if setqueueentryvar is defined, make queue entry (i.e. the caller) variables available to the channel */
    		/* use  pbx_builtin_setvar to set a load of variables with one call */
    		if (qe->parent->setqueueentryvar) {
    			snprintf(interfacevar,sizeof(interfacevar), "QEHOLDTIME=%ld|QEORIGINALPOS=%d",
    				(long) time(NULL) - qe->start, qe->opos);
    			pbx_builtin_setvar(qe->chan, interfacevar);
    		}
    	
    		/* try to set queue variables if configured to do so*/
    		set_queue_variables(qe);
    
    		/* Begin Monitoring */
    		if (qe->parent->monfmt && *qe->parent->monfmt) {
    
    				ast_debug(1, "Starting Monitor as requested.\n");
    
    				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"))
    					which = qe->chan;
    				else
    					which = peer;
    				if (monitorfilename)
    
    					ast_monitor_start(which, qe->parent->monfmt, monitorfilename, 1, X_REC_IN | X_REC_OUT);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				else if (qe->chan->cdr)
    
    					ast_monitor_start(which, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1, X_REC_IN | X_REC_OUT);
    
    				else {
    					/* Last ditch effort -- no CDR, make up something */
    					snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
    
    					ast_monitor_start(which, qe->parent->monfmt, tmpid, 1, X_REC_IN | X_REC_OUT);
    
    				ast_debug(1, "Starting MixMonitor as requested.\n");
    
    				monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME");
    				if (!monitorfilename) {
    					if (qe->chan->cdr)
    
    						ast_copy_string(tmpid, qe->chan->cdr->uniqueid, sizeof(tmpid));
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    					else
    
    						snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
    				} else {
    
    					const char *m = monitorfilename;
    					for (p = tmpid2; p < tmpid2 + sizeof(tmpid2) - 1; p++, m++) {
    						switch (*m) {
    						case '^':
    							if (*(m + 1) == '{')
    								*p = '$';
    							break;
    						case ',':
    							*p++ = '\\';
    							/* Fall through */
    						default:
    							*p = *m;
    
    					if (p == tmpid2 + sizeof(tmpid2))
    						tmpid2[sizeof(tmpid2) - 1] = '\0';
    
    
    					pbx_substitute_variables_helper(qe->chan, tmpid2, tmpid, sizeof(tmpid) - 1);
    				}
    
    				monitor_exec = pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC");
    				monitor_options = pbx_builtin_getvar_helper(qe->chan, "MONITOR_OPTIONS");
    
    				if (monitor_exec) {
    
    					const char *m = monitor_exec;
    					for (p = meid2; p < meid2 + sizeof(meid2) - 1; p++, m++) {
    						switch (*m) {
    						case '^':
    							if (*(m + 1) == '{')
    								*p = '$';
    							break;
    						case ',':
    							*p++ = '\\';
    							/* Fall through */
    						default:
    							*p = *m;
    
    					if (p == meid2 + sizeof(meid2))
    						meid2[sizeof(meid2) - 1] = '\0';
    
    					pbx_substitute_variables_helper(qe->chan, meid2, meid, sizeof(meid) - 1);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				}
    
    				snprintf(tmpid2, sizeof(tmpid2), "%s.%s", tmpid, qe->parent->monfmt);
    
    					monitor_options = "";
    
    					if (!ast_strlen_zero(monitor_exec))
    
    						snprintf(mixmonargs, sizeof(mixmonargs), "%s,b%s,%s", tmpid2, monitor_options, monitor_exec);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    					else
    
    						snprintf(mixmonargs, sizeof(mixmonargs), "%s,b%s", tmpid2, monitor_options);
    
    
    					ast_debug(1, "Arguments being passed to MixMonitor: %s\n", mixmonargs);
    
    					/* We purposely lock the CDR so that pbx_exec does not update the application data */
    					if (qe->chan->cdr)
    						ast_set_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED);
    
    					ret = pbx_exec(qe->chan, mixmonapp, mixmonargs);
    
    					if (qe->chan->cdr)
    						ast_clear_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED);
    
    
    				} else
    					ast_log(LOG_WARNING, "Asked to run MixMonitor on this call, but cannot find the MixMonitor app!\n");
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Drop out of the queue at this point, to prepare for next caller */
    		leave_queue(qe);			
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		if (!ast_strlen_zero(url) && ast_channel_supports_html(peer)) {
    
    			ast_debug(1, "app_queue: sendurl=%s.\n", url);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			ast_channel_sendurl(peer, url);
    		}
    
    		
    		/* run a macro for this connection if defined. The macro simply returns, no action is taken on the result */
    		/* use macro from dialplan if passed as a option, otherwise use the default queue macro */
    		if (!ast_strlen_zero(macro)) {
    				macroexec = ast_strdupa(macro);
    		} else {
    			if (qe->parent->membermacro)
    				macroexec = ast_strdupa(qe->parent->membermacro);
    		}
    
    		if (!ast_strlen_zero(macroexec)) {
    
    			ast_debug(1, "app_queue: macro=%s.\n", macroexec);
    
    			
    			res = ast_autoservice_start(qe->chan);
    			if (res) {
    				ast_log(LOG_ERROR, "Unable to start autoservice on calling channel\n");
    				res = -1;
    			}
    			
    			app = pbx_findapp("Macro");
    
    			if (app) {
    				res = pbx_exec(qe->chan, app, macroexec);
    
    				ast_debug(1, "Macro exited with status %d\n", res);
    
    				res = 0;
    			} else {
    				ast_log(LOG_ERROR, "Could not find application Macro\n");
    				res = -1;
    			}
    
    			if (ast_autoservice_stop(qe->chan) < 0) {
    				ast_log(LOG_ERROR, "Could not stop autoservice on calling channel\n");
    				res = -1;
    			}
    		}
    
    
    		/* run a gosub for this connection if defined. The gosub simply returns, no action is taken on the result */
    		/* use gosub from dialplan if passed as a option, otherwise use the default queue gosub */
    		if (!ast_strlen_zero(gosub)) {
    				gosubexec = ast_strdupa(gosub);
    		} else {
    			if (qe->parent->membergosub)
    				gosubexec = ast_strdupa(qe->parent->membergosub);
    		}
    
    		if (!ast_strlen_zero(gosubexec)) {
    			if (option_debug)
    				ast_log(LOG_DEBUG, "app_queue: gosub=%s.\n", gosubexec);
    			
    			res = ast_autoservice_start(qe->chan);
    			if (res) {
    				ast_log(LOG_ERROR, "Unable to start autoservice on calling channel\n");
    				res = -1;
    			}
    			
    			app = pbx_findapp("Gosub");
    			
    			if (app) {
    
    				char *gosub_args, *gosub_argstart;
    
    				/* Set where we came from */
    				ast_copy_string(qe->chan->context, "app_dial_gosub_virtual_context", sizeof(qe->chan->context));
    				ast_copy_string(qe->chan->exten, "s", sizeof(qe->chan->exten));
    				qe->chan->priority = 0;
    
    
    				if (gosub_argstart) {
    					*gosub_argstart = 0;
    
    					asprintf(&gosub_args, "%s,s,1(%s)", gosubexec, gosub_argstart + 1);
    
    				}
    				if (gosub_args) {
    					res = pbx_exec(qe->chan, app, gosub_args);
    					ast_pbx_run(qe->chan);
    					free(gosub_args);
    					if (option_debug)
    						ast_log(LOG_DEBUG, "Gosub exited with status %d\n", res);
    				} else
    					ast_log(LOG_ERROR, "Could not Allocate string for Gosub arguments -- Gosub Call Aborted!\n");
    				
    
    				res = 0;
    			} else {
    				ast_log(LOG_ERROR, "Could not find application Gosub\n");
    				res = -1;
    			}
    		
    			if (ast_autoservice_stop(qe->chan) < 0) {
    				ast_log(LOG_ERROR, "Could not stop autoservice on calling channel\n");
    				res = -1;
    			}
    		}
    
    
    			ast_debug(1, "app_queue: agi=%s.\n", agi);
    
    			app = pbx_findapp("agi");
    			if (app) {
    				agiexec = ast_strdupa(agi);
    				ret = pbx_exec(qe->chan, app, agiexec);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			} else
    
    				ast_log(LOG_WARNING, "Asked to execute an AGI on this channel, but could not find application (agi)!\n");
    		}
    
    		ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "CONNECT", "%ld|%s|%ld", (long) time(NULL) - qe->start, peer->uniqueid,
    													(long)(orig - to > 0 ? (orig - to) / 1000 : 0));
    
    Jason Parker's avatar
    Jason Parker committed
    		if (update_cdr && qe->chan->cdr) 
    			ast_copy_string(qe->chan->cdr->dstchannel, member->membername, sizeof(qe->chan->cdr->dstchannel));
    
    			manager_event(EVENT_FLAG_AGENT, "AgentConnect",
    
    					"Queue: %s\r\n"
    					"Uniqueid: %s\r\n"
    					"Channel: %s\r\n"
    					"Member: %s\r\n"
    
    					"MemberName: %s\r\n"
    
    					"Holdtime: %ld\r\n"
    					"BridgedChannel: %s\r\n"
    
    					queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername,
    
    					(long) time(NULL) - qe->start, peer->uniqueid, (long)(orig - to > 0 ? (orig - to) / 1000 : 0),
    
    					qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
    
    		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, member->membername, "TRANSFER", "%s|%s|%ld|%ld",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				qe->chan->exten, qe->chan->context, (long) (callstart - qe->start),
    				(long) (time(NULL) - callstart));
    
    			send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), TRANSFER);
    
    			ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETECALLER", "%ld|%ld|%d",
    
    				(long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos);
    
    			send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), CALLER);
    
    			ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETEAGENT", "%ld|%ld|%d",
    				(long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos);
    
    			send_agent_complete(qe, queuename, peer, member, callstart, vars, sizeof(vars), AGENT);
    
    		if (bridge != AST_PBX_NO_HANGUP_PEER)
    
    			ast_hangup(peer);
    
    		update_queue(qe->parent, member, callcompletedinsl);
    
    		res = bridge ? bridge : 1;
    
    		ao2_ref(member, -1);
    
    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;
    
    	int res = ast_waitfordigit(qe->chan, retrywait);
    	if (res > 0 && !valid_exit(qe, res))
    		res = 0;
    
    	return res;
    
    static struct member *interface_exists(struct call_queue *q, const char *interface)
    
    {
    	struct member *mem;
    
    	struct ao2_iterator mem_iter;
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    	while ((mem = ao2_iterator_next(&mem_iter))) {
    
    		if (!strcasecmp(interface, mem->interface))
    			return mem;
    
    		ao2_ref(mem, -1);
    
    /* Dump all members in a specific queue to the database
    
     * <pm_family>/<queuename> = <interface>;<penalty>;<paused>[|...]
    
    static void dump_queue_members(struct call_queue *pm_queue)
    
    	struct member *cur_member;
    
    	char value[PM_MAX_LEN];
    	int value_len = 0;
    	int res;
    
    	struct ao2_iterator mem_iter;
    
    	if (!pm_queue)
    		return;
    
    	mem_iter = ao2_iterator_init(pm_queue->members, 0);
    	while ((cur_member = ao2_iterator_next(&mem_iter))) {
    
    		if (!cur_member->dynamic) {
    			ao2_ref(cur_member, -1);
    
    		}
    
    		res = snprintf(value + value_len, sizeof(value) - value_len, "%s%s;%d;%d;%s",
    			value_len ? "|" : "", cur_member->interface, cur_member->penalty, cur_member->paused, cur_member->membername);
    
    		ao2_ref(cur_member, -1);
    
    		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(const char *queuename, const char *interface)
    
    	struct call_queue *q, tmpq = {
    		.name = queuename,	
    	};
    
    	struct member *mem, tmpmem;
    
    	ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
    
    	if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) {
    
    		if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) {
    			/* XXX future changes should beware of this assumption!! */
    			if(!mem->dynamic) {
    				ao2_ref(mem, -1);
    
    Mark Michelson's avatar
    Mark Michelson committed
    				ao2_unlock(q);
    				return RES_NOT_DYNAMIC;
    
    			q->membercount--;
    
    			manager_event(EVENT_FLAG_AGENT, "QueueMemberRemoved",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				"Queue: %s\r\n"
    
    				"Location: %s\r\n"
    				"MemberName: %s\r\n",
    
    				q->name, mem->interface, mem->membername);
    
    			ao2_unlink(q->members, mem);
    
    			if (queue_persistent_members)
    				dump_queue_members(q);
    			
    			res = RES_OKAY;
    		} else {
    			res = RES_EXISTS;
    
    static int add_to_queue(const char *queuename, const char *interface, const char *membername, int penalty, int paused, int dump)
    
    	struct call_queue *q;
    
    	struct member *new_member, *old_member;
    
    	/* \note Ensure the appropriate realtime queue is loaded.  Note that this
    	 * short-circuits if the queue is already in memory. */
    
    	if (!(q = load_realtime_queue(queuename)))
    		return res;
    
    	if ((old_member = interface_exists(q, interface)) == NULL) {
    
    		add_to_interfaces(interface);
    
    		if ((new_member = create_queue_member(interface, membername, penalty, paused))) {
    
    			new_member->dynamic = 1;
    
    			ao2_link(q->members, new_member);
    
    			q->membercount++;
    
    			manager_event(EVENT_FLAG_AGENT, "QueueMemberAdded",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				"Queue: %s\r\n"
    				"Location: %s\r\n"
    
    				"MemberName: %s\r\n"
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				"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->membername,
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				new_member->penalty, new_member->calls, (int) new_member->lastcall,
    				new_member->status, new_member->paused);
    
    			ao2_ref(new_member, -1);
    			new_member = NULL;
    
    
    			if (dump)
    				dump_queue_members(q);
    			
    			res = RES_OKAY;
    
    			res = RES_OUTOFMEMORY;
    
    		ao2_ref(old_member, -1);
    
    static int set_member_paused(const char *queuename, const char *interface, const char *reason, int paused)
    
    	struct call_queue *q;
    
    	struct member *mem;
    
    
    	/* Special event for when all queues are paused - individual events still generated */
    
    	/* XXX In all other cases, we use the membername, but since this affects all queues, we cannot */
    
    	if (ast_strlen_zero(queuename))
    		ast_queue_log("NONE", "NONE", interface, (paused ? "PAUSEALL" : "UNPAUSEALL"), "%s", "");
    
    
    	queue_iter = ao2_iterator_init(queues, 0);
    	while((q = ao2_iterator_next(&queue_iter))) {
    		ao2_lock(q);
    
    		if (ast_strlen_zero(queuename) || !strcasecmp(q->name, queuename)) {
    
    			if ((mem = interface_exists(q, interface))) {
    				found++;
    
    					ast_debug(1, "%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);
    
    				if(mem->realtime)
    
    					update_realtime_member_field(mem, q->name, "paused", paused ? "1" : "0");
    
    				ast_queue_log(q->name, "NONE", mem->membername, (paused ? "PAUSE" : "UNPAUSE"), "%s", S_OR(reason, ""));
    				
    				if (!ast_strlen_zero(reason)) {
    					manager_event(EVENT_FLAG_AGENT, "QueueMemberPaused",
    						"Queue: %s\r\n"
    						"Location: %s\r\n"
    						"MemberName: %s\r\n"
    						"Paused: %d\r\n"
    						"Reason: %s\r\n",
    							q->name, mem->interface, mem->membername, paused, reason);
    				} else {
    					manager_event(EVENT_FLAG_AGENT, "QueueMemberPaused",
    						"Queue: %s\r\n"
    						"Location: %s\r\n"
    						"MemberName: %s\r\n"
    						"Paused: %d\r\n",
    							q->name, mem->interface, mem->membername, paused);
    				}
    
    				ao2_ref(mem, -1);
    
    	return found ? RESULT_SUCCESS : RESULT_FAILURE;
    
    /* \brief Sets members penalty, if queuename=NULL we set member penalty in all the queues. */
    static int set_member_penalty(char *queuename, char *interface, int penalty)
    {
    	int foundinterface = 0, foundqueue = 0;
    	struct call_queue *q;
    	struct member *mem;
    	struct ao2_iterator queue_iter;
    
    	queue_iter = ao2_iterator_init(queues, 0);
    	while ((q = ao2_iterator_next(&queue_iter))) {
    		ao2_lock(q);
    		if (ast_strlen_zero(queuename) || !strcasecmp(q->name, queuename)) {
    			foundqueue++;
    			if ((mem = interface_exists(q, interface))) {
    				foundinterface++;
    				mem->penalty = penalty;
    				
    				ast_queue_log(q->name, "NONE", interface, "PENALTY", "%d", penalty);
    				manager_event(EVENT_FLAG_AGENT, "QueueMemberPenalty",
    					"Queue: %s\r\n"
    					"Location: %s\r\n"
    					"Penalty: %d\r\n",
    					q->name, mem->interface, penalty);
    
    			}
    		}
    		ao2_unlock(q);
    		queue_unref(q);
    	}
    
    	if (foundinterface) {
    		return RESULT_SUCCESS;
    	} else if (foundqueue) {
    		ast_log (LOG_ERROR, "Invalid queuename\n"); 
    	} else {
    		ast_log (LOG_ERROR, "Invalid interface\n");
    	}	
    
    	return RESULT_FAILURE;
    }
    
    /* \brief Gets members penalty. 
     * 
     * \return Return the members penalty or RESULT_FAILURE on error. */
    static int get_member_penalty(char *queuename, char *interface)
    {
    	int foundqueue = 0, penalty;
    
    	struct call_queue *q, tmpq = {
    		.name = queuename,	
    	};
    
    	if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) {
    
    		foundqueue = 1;
    		ao2_lock(q);
    		if ((mem = interface_exists(q, interface))) {
    			penalty = mem->penalty;
    			ao2_unlock(q);
    			queue_unref(q);
    			return penalty;
    		}
    		ao2_unlock(q);
    		queue_unref(q);
    	}
    
    	/* some useful debuging */
    	if (foundqueue) 
    		ast_log (LOG_ERROR, "Invalid queuename\n");
    	else 
    		ast_log (LOG_ERROR, "Invalid interface\n");
    
    	return RESULT_FAILURE;
    }
    
    
    /* Reload dynamic queue members persisted into the astdb */
    
    static void reload_queue_members(void)
    {
    
    	char *member;
    	char *interface;
    
    	char *membername = NULL;
    
    	char *penalty_tok;
    	int penalty = 0;
    	char *paused_tok;
    	int paused = 0;
    	struct ast_db_entry *db_tree;
    	struct ast_db_entry *entry;
    
    	char queue_data[PM_MAX_LEN];
    
    
    
    	/* 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;
    
    		{
    			struct call_queue tmpq = {
    				.name = queue_name,
    			};
    			cur_queue = ao2_find(queues, &tmpq, OBJ_POINTER);
    		}	
    
    
    		if (!cur_queue)
    			cur_queue = load_realtime_queue(queue_name);
    
    
    		if (!cur_queue) {
    			/* If the queue no longer exists, remove it from the
    			 * database */
    
    			ast_log(LOG_WARNING, "Error loading persistent queue: '%s': it does not exist\n", queue_name);
    
    			ast_db_del(pm_family, queue_name);
    
    		if (ast_db_get(pm_family, queue_name, queue_data, PM_MAX_LEN)) {
    			queue_unref(cur_queue);
    
    		cur_ptr = queue_data;
    
    			if (ast_strlen_zero(member))
    				continue;
    
    			interface = strsep(&member, ";");
    			penalty_tok = strsep(&member, ";");
    			paused_tok = strsep(&member, ";");
    
    			membername = strsep(&member, ";");
    
    				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 (ast_strlen_zero(membername))
    				membername = interface;
    
    			ast_debug(1, "Reload Members: Queue: %s  Member: %s  Name: %s  Penalty: %d  Paused: %d\n", queue_name, interface, membername, penalty, paused);
    
    			if (add_to_queue(queue_name, interface, membername, penalty, paused, 0) == RES_OUTOFMEMORY) {
    
    				ast_log(LOG_ERROR, "Out of Memory when reloading persistent queue member\n");
    				break;
    
    		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;
    	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][|reason])\n");
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    	if (ast_strlen_zero(args.interface)) {
    
    		ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options[|reason]])\n");
    
    	if (set_member_paused(args.queuename, args.interface, args.reason, 1)) {
    
    		ast_log(LOG_WARNING, "Attempt to pause interface %s, not found\n", args.interface);
    		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;
    	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[|reason]])\n");
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    	if (ast_strlen_zero(args.interface)) {
    
    		ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options[|reason]])\n");
    
    	if (set_member_paused(args.queuename, args.interface, args.reason, 0)) {
    
    		ast_log(LOG_WARNING, "Attempt to unpause interface %s, not found\n", args.interface);
    		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;
    	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;
    	}
    
    	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';
    
    	switch (remove_from_queue(args.queuename, args.interface)) {
    
    		ast_queue_log(args.queuename, chan->uniqueid, args.interface, "REMOVEMEMBER", "%s", "");
    
    		ast_log(LOG_NOTICE, "Removed interface '%s' from queue '%s'\n", args.interface, args.queuename);
    
    		pbx_builtin_setvar_helper(chan, "RQMSTATUS", "REMOVED");
    
    		ast_debug(1, "Unable to remove interface '%s' from queue '%s': Not there\n", args.interface, args.queuename);
    
    		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");
    
    	case RES_NOT_DYNAMIC:
    		ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': '%s' is not a dynamic member\n", args.queuename, args.interface);
    		pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOTDYNAMIC");
    		res = 0;
    		break;
    
    
    	return res;
    }
    
    static int aqm_exec(struct ast_channel *chan, void *data)
    {
    	int res=-1;
    
    	char *parse, *temppos = NULL;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(interface);
    		AST_APP_ARG(penalty);
    		AST_APP_ARG(options);
    
    		AST_APP_ARG(membername);
    
    	int penalty = 0;
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename[|[interface]|[penalty][|options][|membername]])\n");
    
    		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;
    
    	switch (add_to_queue(args.queuename, args.interface, args.membername, penalty, 0, queue_persistent_members)) {
    
    		ast_queue_log(args.queuename, chan->uniqueid, args.interface, "ADDMEMBER", "%s", "");
    
    		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);
    		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);
    
    static int ql_exec(struct ast_channel *chan, void *data)
    {
    	char *parse;
    
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(uniqueid);
    
    		AST_APP_ARG(membername);
    
    		AST_APP_ARG(event);
    		AST_APP_ARG(params);
    	);
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_WARNING, "QueueLog requires arguments (queuename|uniqueid|membername|event[|additionalinfo]\n");
    
    		return -1;
    	}
    
    	parse = ast_strdupa(data);
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    	if (ast_strlen_zero(args.queuename) || ast_strlen_zero(args.uniqueid)
    
    	    || ast_strlen_zero(args.membername) || ast_strlen_zero(args.event)) {
    		ast_log(LOG_WARNING, "QueueLog requires arguments (queuename|uniqueid|membername|event[|additionalinfo])\n");
    
    	ast_queue_log(args.queuename, args.uniqueid, args.membername, args.event, 
    
    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 tries = 0;
    	int noption = 0;
    
    	AST_DECLARE_APP_ARGS(args,
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(options);
    		AST_APP_ARG(url);
    		AST_APP_ARG(announceoverride);
    		AST_APP_ARG(queuetimeoutstr);
    		AST_APP_ARG(agi);
    
    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[|agi]]]]]\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return -1;
    	}
    
    	
    	parse = ast_strdupa(data);
    	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) {
    
    			ast_debug(1, "%s: Got priority %d from ${QUEUE_PRIO}.\n", chan->name, prio);
    
    		} else {
    			ast_log(LOG_WARNING, "${QUEUE_PRIO}: Invalid value (%s), channel %s.\n",
    
    		ast_debug(3, "NO QUEUE_PRIO variable found. Using default.\n");