Skip to content
Snippets Groups Projects
app_queue.c 161 KiB
Newer Older
  • Learn to ignore specific revisions
  • 					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;
    
    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 member *mem, tmpmem;
    
    	ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
    
    	ast_copy_string(tmpq.name, queuename, sizeof(tmpq.name));
    	if((q = ao2_find(queues, &tmpq, OBJ_POINTER))) {
    		ao2_lock(q);
    
    		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);
    
    			
    			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;
    
    /* 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 *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;
    
    		ast_copy_string(tmpq.name, queue_name, sizeof(tmpq.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");
    
    	/* 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) {
    
    			ast_debug(1, "%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 (args.options && (strchr(args.options, 'c')))
    		qcontinue = 1;
    
    
    	ast_debug(1, "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;
    
    	qe.valid_digits = 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, ""),
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			S_OR(chan->cid.cid_num, ""));
    
    		if (ringing) {
    			ast_indicate(chan, AST_CONTROL_RINGING);
    
    Olle Johansson's avatar
    Olle Johansson committed
    		} else {
    
    		/* This is the wait loop for callers 2 through maxlen */
    		res = wait_our_turn(&qe, ringing, &reason);
    
    		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; */
    			/* or, they may timeout. */
    
    			enum queue_member_status stat;
    
    			/* Leave if we have exceeded our queuetimeout */
    			if (qe.expire && (time(NULL) > qe.expire)) {
    				record_abandoned(&qe);
    				reason = QUEUE_TIMEOUT;
    				res = 0;
    				ast_queue_log(args.queuename, chan->uniqueid,"NONE", "EXITWITHTIMEOUT", "%d|%d|%ld", 
    					qe.pos, qe.opos, (long) time(NULL) - qe.start);
    				break;
    			}
    
    			if (makeannouncement) {
    				/* Make a position announcement, if enabled */
    
    				if (qe.parent->announcefrequency)
    					if ((res = say_position(&qe,ringing)))
    
    			}
    			makeannouncement = 1;
    
    			/* Make a periodic announcement, if enabled */
    
    			if (qe.parent->periodicannouncefrequency)
    				if ((res = say_periodic_announcement(&qe,ringing)))
    
    			/* Try calling all queue members for 'timeout' seconds */
    
    			res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi, args.macro, args.gosub, ringing);
    
    			stat = get_member_status(qe.parent, qe.max_penalty);
    
    			/* exit after 'timeout' cycle if 'n' option enabled */
    
    			if (noption && tries >= qe.parent->membercount) {
    
    				ast_verb(3, "Exiting on time-out cycle\n");
    
    				ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
    				record_abandoned(&qe);
    				reason = QUEUE_TIMEOUT;
    				res = 0;
    				break;
    			}
    
    			/* leave the queue if no agents, if enabled */
    			if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
    				record_abandoned(&qe);
    				reason = QUEUE_LEAVEEMPTY;
    				ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
    				res = 0;
    				break;
    			}
    
    			/* leave the queue if no reachable agents, if enabled */
    			if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS)) {
    				record_abandoned(&qe);
    				reason = QUEUE_LEAVEUNAVAIL;
    				ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
    				res = 0;
    				break;
    			}
    			if ((qe.parent->leavewhenempty == QUEUE_EMPTY_LOOSE) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
    				record_abandoned(&qe);
    				reason = QUEUE_LEAVEUNAVAIL;
    				res = 0;
    				break;
    			}
    
    			/* Leave if we have exceeded our queuetimeout */
    			if (qe.expire && (time(NULL) > qe.expire)) {
    				record_abandoned(&qe);
    				reason = QUEUE_TIMEOUT;
    				res = 0;
    				ast_queue_log(qe.parent->name, qe.chan->uniqueid,"NONE", "EXITWITHTIMEOUT", "%d|%d|%ld", qe.pos, qe.opos, (long) time(NULL) - qe.start);
    				break;
    			}
    
    			/* If using dynamic realtime members, we should regenerate the member list for this queue */
    			update_realtime_members(qe.parent);
    
    
    			/* OK, we didn't get anybody; wait for 'retry' seconds; may get a digit to exit with */
    			res = wait_a_bit(&qe);
    			if (res)
    				goto stop;
    
    			/* Since this is a priority queue and
    			 * it is not sure that we are still at the head
    			 * of the queue, go and check for our turn again.
    			 */
    			if (!is_our_turn(&qe)) {
    				ast_debug(1, "Darn priorities, going back in queue (%s)!\n", qe.chan->name);
    				goto check_turns;
    
    
    stop:
    		if (res) {
    			if (res < 0) {
    				if (!qe.handled) {
    					record_abandoned(&qe);
    					ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ABANDON",
    						"%d|%d|%ld", qe.pos, qe.opos,
    						(long) time(NULL) - qe.start);
    				}
    				res = -1;
    			} else if (qe.valid_digits) {
    				ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY",
    					"%s|%d", qe.digits, qe.pos);
    			}
    		}
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Don't allow return code > 0 */
    
    		if (res >= 0 && res != AST_PBX_KEEPALIVE) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			res = 0;	
    
    			if (ringing) {
    				ast_indicate(chan, -1);
    			} else {
    				ast_moh_stop(chan);
    			}			
    
    			ast_stopstream(chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		leave_queue(&qe);
    
    		if (reason != QUEUE_UNKNOWN)
    			set_queue_result(chan, reason);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else {
    
    		ast_log(LOG_WARNING, "Unable to join queue '%s'\n", args.queuename);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    static int queue_function_var(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    
    
    	char interfacevar[256]="";
            float sl = 0;
    
    	buf[0] = '\0';
    	
    	if (ast_strlen_zero(data)) {
    		ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd);
    		return -1;
    	}
    	
    
    	ast_copy_string(tmpq.name, data, sizeof(tmpq.name));