Skip to content
Snippets Groups Projects
app_queue.c 107 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    	/* Hold the lock while we setup the outgoing calls */
    
    	if (use_weight) 
    		ast_mutex_lock(&qlock);
    
    	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;
    
    	if (announceoverride && !ast_strlen_zero(announceoverride))
    
    Mark Spencer's avatar
    Mark Spencer committed
    		announce = announceoverride;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	while(cur) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (!tmp) {
    
    			ast_mutex_unlock(&qe->parent->lock);
    
    			if (use_weight) 
    				ast_mutex_unlock(&qlock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ast_log(LOG_WARNING, "Out of memory\n");
    			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));
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* If we're dialing by extension, look at the extension to know what to dial */
    
    		if ((newnum = strstr(tmp->interface, "/BYEXTENSION"))) {
    			newnum++;
    			strncpy(restofit, newnum + strlen("BYEXTENSION"), sizeof(restofit) - 1);
    			snprintf(newnum, sizeof(tmp->interface) - (newnum - tmp->interface), "%s%s", qe->chan->exten, restofit);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (option_debug)
    
    				ast_log(LOG_DEBUG, "Dialing by extension %s\n", tmp->interface);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    		/* Special case: If we ring everyone, go ahead and ring them, otherwise
    		   just calculate their metric for the appropriate strategy */
    
    		calc_metric(qe->parent, cur, x++, qe, tmp);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* 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 */
    		tmp->next = outgoing;
    		outgoing = tmp;		
    		/* If this line is up, don't try anybody else */
    
    		if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP))
    
    Mark Spencer's avatar
    Mark Spencer committed
    			break;
    
    		cur = cur->next;
    	}
    	if (qe->parent->timeout)
    		to = qe->parent->timeout * 1000;
    	else
    		to = -1;
    
    	ring_one(qe, outgoing, &numbusies);
    
    	ast_mutex_unlock(&qe->parent->lock);
    
    	if (use_weight) 
    		ast_mutex_unlock(&qlock);
    
    	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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!peer) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* Musta gotten hung up */
    
    			record_abandoned(qe);
    
    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
    		goto out;
    	}
    	if (peer) {
    		/* 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->type,"Zap"))
    			ast_channel_setoption(qe->chan, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0);
    		if (!strcmp(peer->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", "");
    
    					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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ast_hangup(peer);
    			return -1;
    		}
    
    		/* Begin Monitoring */
    		if (qe->parent->monfmt && *qe->parent->monfmt) {
    
    			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 );
    
    				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);			
    
     		if (url && !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 successfull, 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 databse
     *
    
     * <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_mutex_lock(&qlock);
    	for (q = queues ; q ; q = q->next) {
    		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)
    				    dump_queue_members(q);
    
    
    				res = RES_OKAY;
    			} else {
    				res = RES_EXISTS;
    			}
    			ast_mutex_unlock(&q->lock);
    			break;
    		}
    		ast_mutex_unlock(&q->lock);
    	}
    	ast_mutex_unlock(&qlock);
    	return res;
    }
    
    
    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;
    
    	ast_mutex_lock(&qlock);
    	for (q = queues ; q ; q = q->next) {
    		ast_mutex_lock(&q->lock);
    		if (!strcmp(q->name, queuename)) {
    			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",
    						"Queue: %s\r\n"
    
    						"Membership: %s\r\n"
    						"Penalty: %d\r\n"
    						"CallsTaken: %d\r\n"
    						"LastCall: %ld\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, new_member->lastcall, new_member->status, new_member->paused);
    
    						dump_queue_members(q);
    
    					res = RES_OKAY;
    				} else {
    					res = RES_OUTOFMEMORY;
    				}
    			} else {
    				res = RES_EXISTS;
    			}
    			ast_mutex_unlock(&q->lock);
    			break;
    		}
    		ast_mutex_unlock(&q->lock);
    	}
    	ast_mutex_unlock(&qlock);
    	return res;
    }
    
    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_mutex_lock(&qlock);
    	for (q = queues ; q ; q = q->next) {
    		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)
    				    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_mutex_unlock(&qlock);
    
    	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_mutex_lock(&qlock);
    
    
    	/* 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;
    
    
    		cur_queue = queues;
    		while (cur_queue) {
    			ast_mutex_lock(&cur_queue->lock);
    
    			if (!strcmp(queue_name, cur_queue->name))
    
    			ast_mutex_unlock(&cur_queue->lock);
    			cur_queue = cur_queue->next;
    		}
    
    		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 persisent 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;
    
    	if (db_tree) {
    		ast_log(LOG_NOTICE, "Queue members sucessfully reloaded from database.\n");
    		ast_db_freetree(db_tree);
    
    static int pqm_exec(struct ast_channel *chan, void *data)
    {
    	struct localuser *u;
    	char *queuename, *interface;
    
    	if (!data) {
    		ast_log(LOG_WARNING, "PauseQueueMember requires an argument ([queuename]|interface])\n");
    		return -1;
    	}
    
    	queuename = ast_strdupa((char *)data);
    	if (!queuename) {
    		ast_log(LOG_ERROR, "Out of memory\n");
    		return -1;
    	}
    
    	interface = strchr(queuename, '|');
    	if (!interface) {
    		ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface])\n");
    		return -1;
    	}
    
    	LOCAL_USER_ADD(u);
    
    	*interface = '\0';
    	interface++;
    
    	if (set_member_paused(queuename, interface, 1)) {
    		ast_log(LOG_WARNING, "Attempt to pause interface %s, not found\n", interface);
    
    		if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
    
    			LOCAL_USER_REMOVE(u);
    			return 0;
    		}
    		return -1;
    	}
    
    	LOCAL_USER_REMOVE(u);
    
    	return 0;
    }
    
    static int upqm_exec(struct ast_channel *chan, void *data)
    {
    	struct localuser *u;
    	char *queuename, *interface;
    
    	if (!data) {
    		ast_log(LOG_WARNING, "UnpauseQueueMember requires an argument ([queuename]|interface])\n");
    		return -1;
    	}
    
    	queuename = ast_strdupa((char *)data);
    	if (!queuename) {
    		ast_log(LOG_ERROR, "Out of memory\n");
    		return -1;
    	}
    
    	interface = strchr(queuename, '|');
    	if (!interface) {
    		ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface])\n");
    		return -1;
    	}
    
    	LOCAL_USER_ADD(u);
    
    	*interface = '\0';
    	interface++;
    
    	if (set_member_paused(queuename, interface, 0)) {
    		ast_log(LOG_WARNING, "Attempt to unpause interface %s, not found\n", interface);
    
    		if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
    
    			LOCAL_USER_REMOVE(u);
    			return 0;
    		}
    		return -1;
    	}
    
    	LOCAL_USER_REMOVE(u);
    
    	return 0;
    }
    
    
    static int rqm_exec(struct ast_channel *chan, void *data)
    {
    	int res=-1;
    	struct localuser *u;
    
    	char tmpchan[256]="";
    
    
    	if (!data) {
    
    		ast_log(LOG_WARNING, "RemoveQueueMember requires an argument (queuename[|interface])\n");
    
    		return -1;
    	}
    
    
    	info = ast_strdupa((char *)data);
    	if (!info) {
    		ast_log(LOG_ERROR, "Out of memory\n");
    		return -1;
    	}
    
    	LOCAL_USER_ADD(u);
    
    
    	queuename = info;
    	if (queuename) {
    		interface = strchr(queuename, '|');
    		if (interface) {
    			*interface = '\0';
    			interface++;
    		}
    
    			interface = strrchr(tmpchan, '-');
    			if (interface)
    				*interface = '\0';
    			interface = tmpchan;
    		}
    
    	switch (remove_from_queue(queuename, interface)) {
    	case RES_OKAY:
    		ast_log(LOG_NOTICE, "Removed interface '%s' from queue '%s'\n", interface, queuename);
    		res = 0;
    		break;
    	case RES_EXISTS:
    		ast_log(LOG_WARNING, "Unable to remove interface '%s' from queue '%s': Not there\n", interface, queuename);
    
    		ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
    
    		res = 0;
    		break;
    	case RES_NOSUCHQUEUE:
    
    		ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': No such queue\n", queuename);
    
    		res = 0;
    		break;
    	case RES_OUTOFMEMORY:
    		ast_log(LOG_ERROR, "Out of memory\n");
    		break;
    	}
    
    
    	LOCAL_USER_REMOVE(u);
    	return res;
    }
    
    static int aqm_exec(struct ast_channel *chan, void *data)
    {
    	int res=-1;
    	struct localuser *u;
    	char *queuename;
    
    	char tmpchan[512]="";
    
    	char *interface=NULL;
    
    	char *penaltys=NULL;
    	int penalty = 0;
    
    
    	if (!data) {
    
    		ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename[|[interface][|penalty]])\n");
    
    		return -1;
    	}
    
    
    	info = ast_strdupa((char *)data);
    	if (!info) {
    		ast_log(LOG_ERROR, "Out of memory\n");
    		return -1;
    	}
    	LOCAL_USER_ADD(u);
    
    
    	queuename = info;
    	if (queuename) {
    		interface = strchr(queuename, '|');
    		if (interface) {
    			*interface = '\0';
    			interface++;
    		}
    
    		if (interface) {
    			penaltys = strchr(interface, '|');
    			if (penaltys) {
    
    		if (!interface || ast_strlen_zero(interface)) {
    
    			interface = strrchr(tmpchan, '-');
    			if (interface)
    				*interface = '\0';
    			interface = tmpchan;
    		}
    
    		if (penaltys && !ast_strlen_zero(penaltys)) {
    
    			if ((sscanf(penaltys, "%d", &penalty) != 1) || penalty < 0) {
    				ast_log(LOG_WARNING, "Penalty '%s' is invalid, must be an integer >= 0\n", penaltys);
    				penalty = 0;
    			}
    		}
    
    	switch (add_to_queue(queuename, interface, penalty, 0, queue_persistent_members)) {
    
    	case RES_OKAY:
    		ast_log(LOG_NOTICE, "Added interface '%s' to queue '%s'\n", interface, queuename);
    		res = 0;
    		break;
    	case RES_EXISTS:
    		ast_log(LOG_WARNING, "Unable to add interface '%s' to queue '%s': Already there\n", interface, queuename);
    
    		ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
    
    		res = 0;
    		break;
    	case RES_NOSUCHQUEUE:
    
    		ast_log(LOG_WARNING, "Unable to add interface to queue '%s': No such queue\n", queuename);
    
    		res = 0;
    		break;
    	case RES_OUTOFMEMORY:
    
    		ast_log(LOG_ERROR, "Out of memory adding member %s to queue %s\n", interface, queuename);
    
    
    	LOCAL_USER_REMOVE(u);
    	return res;
    }
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int queue_exec(struct ast_channel *chan, void *data)
    {
    	int res=-1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct localuser *u;
    	char *queuename;
    	char info[512];
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *options = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *url = NULL;
    	char *announceoverride = NULL;
    
    	char *user_priority;
    	int prio;
    
    	char *queuetimeoutstr = NULL;
    
    	/* whether to exit Queue application after the timeout hits */
    	int go_on = 0;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Our queue entry */
    	struct queue_ent qe;
    	
    
    	if (!data || 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;
    	}
    	
    	LOCAL_USER_ADD(u);
    
    
    	/* Setup our queue entry */
    	memset(&qe, 0, sizeof(qe));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	
    	/* Parse our arguments XXX Check for failure XXX */
    
    	queuename = strsep(&info_ptr, "|");
    	options = strsep(&info_ptr, "|");
    	url = strsep(&info_ptr, "|");
    	announceoverride = strsep(&info_ptr, "|");
    	queuetimeoutstr = info_ptr;
    
    	/* set the expire time based on the supplied timeout; */
    	if (queuetimeoutstr)
    		qe.expire = qe.start + atoi(queuetimeoutstr);
    	else
    		qe.expire = 0;
    
    
    	/* 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;
    
    	if (options && (strchr(options, 'r')))
    		ringing = 1;
    
    	if (option_debug)  
    
    		ast_log(LOG_DEBUG, "queue: %s, options: %s, url: %s, announce: %s, expires: %ld, priority: %d\n",
    
    			queuename, options, url, announceoverride, (long)qe.expire, (int)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);
    
    	if (!join_queue(queuename, &qe, &reason)) {
    		ast_queue_log(queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", url ? url : "",
    			      chan->cid.cid_num ? chan->cid.cid_num : "");
    
    		if (ringing) {
    			ast_indicate(chan, AST_CONTROL_RINGING);
    		} 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(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", queuename);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					res = -1;
    				}
    				break;
    			}
    
    Mark Spencer's avatar
    Mark Spencer committed
    				break;
    
    			if (valid_exit(&qe, res)) {
    
    				ast_queue_log(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)) {
    
    				if (makeannouncement) {
    					/* Make a position announcement, if enabled */
    					if (qe.parent->announcefrequency && !ringing)
    
    						ast_queue_log(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(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
    					break;
    				}
    
    
    				/* Try calling all queue members for 'timeout' seconds */
    
    				res = try_calling(&qe, options, announceoverride, url, &go_on);
    
    				if (res) {
    					if (res < 0) {
    
    							ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start);
    
    					} else if (res > 0)
    
    						ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%s|%d", qe.digits, qe.pos);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					break;
    
    				/* leave the queue if no agents, if enabled */
    
    				if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
    
    					reason = QUEUE_LEAVEEMPTY;
    					res = 0;
    					break;
    				}
    
    				/* leave the queue if no reachable agents, if enabled */
    
    				if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
    
    				/* Leave if we have exceeded our queuetimeout */
    
    				if (qe.expire && (time(NULL) > qe.expire)) {
    
    					res = 0;
    					break;
    				}
    
    				/* OK, we didn't get anybody; wait for 'retry' seconds; may get a digit to exit with */
    
    Mark Spencer's avatar
    Mark Spencer committed
    				res = wait_a_bit(&qe);
    				if (res < 0) {
    
    					ast_queue_log(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 when they almost made it\n", queuename);
    
    Mark Spencer's avatar
    Mark Spencer committed
    						res = -1;
    					}
    					break;
    				}
    
    				if (res && valid_exit(&qe, res)) {
    
    					ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%s|%d", qe.digits, qe.pos);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					break;
    
    				/* exit after 'timeout' cycle if 'n' option enabled */
    
    				if (go_on) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    					if (option_verbose > 2) {
    						ast_verbose(VERBOSE_PREFIX_3 "Exiting on time-out cycle\n");
    						res = -1;
    					}
    
    					ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
    
    					res = 0;
    					break;
    				}
    
    				/* 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)) {
    
    					if (option_debug)
    						ast_log(LOG_DEBUG, "Darn priorities, going back in queue (%s)!\n",
    
    								qe.chan->name);
    					goto check_turns;
    				}
    
    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", queuename);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    	LOCAL_USER_REMOVE(u);
    	return res;
    }