Skip to content
Snippets Groups Projects
app_queue.c 86.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • 				ast_log(LOG_NOTICE, "No one is answering queue '%s'\n", queue);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    			*to = 0;
    			return NULL;
    		}
    		winner = ast_waitfor_n(watchers, pos, to);
    		o = outgoing;
    		while(o) {
    
    			if (o->stillgoing && (o->chan) &&  (o->chan->_state == AST_STATE_UP)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (!peer) {
    					if (option_verbose > 2)
    						ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
    
    					ast_copy_flags(flags, o, QUEUE_FLAG_REDIR_IN & QUEUE_FLAG_REDIR_OUT & QUEUE_FLAG_DISCON_IN & QUEUE_FLAG_DISCON_OUT);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				}
    
    Mark Spencer's avatar
    Mark Spencer committed
    			} else if (o->chan && (o->chan == winner)) {
    
    				if (!ast_strlen_zero(o->chan->call_forward)) {
    					char tmpchan[256]="";
    					char *stuff;
    					char *tech;
    					strncpy(tmpchan, o->chan->call_forward, sizeof(tmpchan) - 1);
    					if ((stuff = strchr(tmpchan, '/'))) {
    						*stuff = '\0';
    						stuff++;
    						tech = tmpchan;
    					} else {
    						snprintf(tmpchan, sizeof(tmpchan), "%s@%s", o->chan->call_forward, o->chan->context);
    						stuff = tmpchan;
    						tech = "Local";
    					}
    					/* Before processing channel, go ahead and check for forwarding */
    					if (option_verbose > 2)
    						ast_verbose(VERBOSE_PREFIX_3 "Now forwarding %s to '%s/%s' (thanks to %s)\n", in->name, tech, stuff, o->chan->name);
    					/* Setup parameters */
    
    					o->chan = ast_request(tech, in->nativeformats, stuff, &status);
    					if (status != o->oldstatus) 
    
    Mark Spencer's avatar
    Mark Spencer committed
    						update_dial_status(qe->parent, o->member, status);						
    
    					if (!o->chan) {
    						ast_log(LOG_NOTICE, "Unable to create local channel for call forward to '%s/%s'\n", tech, stuff);
    						o->stillgoing = 0;
    						numnochan++;
    					} else {
    						if (o->chan->cid.cid_num)
    							free(o->chan->cid.cid_num);
    						o->chan->cid.cid_num = NULL;
    						if (o->chan->cid.cid_name)
    							free(o->chan->cid.cid_name);
    						o->chan->cid.cid_name = NULL;
    
    						if (in->cid.cid_num) {
    							o->chan->cid.cid_num = strdup(in->cid.cid_num);
    							if (!o->chan->cid.cid_num)
    								ast_log(LOG_WARNING, "Out of memory\n");	
    						}
    						if (in->cid.cid_name) {
    							o->chan->cid.cid_name = strdup(in->cid.cid_name);
    							if (!o->chan->cid.cid_name)
    								ast_log(LOG_WARNING, "Out of memory\n");	
    						}
    						strncpy(o->chan->accountcode, in->accountcode, sizeof(o->chan->accountcode) - 1);
    						o->chan->cdrflags = in->cdrflags;
    
    						if (in->cid.cid_ani) {
    							if (o->chan->cid.cid_ani)
    								free(o->chan->cid.cid_ani);
    							o->chan->cid.cid_ani = malloc(strlen(in->cid.cid_ani) + 1);
    							if (o->chan->cid.cid_ani)
    								strncpy(o->chan->cid.cid_ani, in->cid.cid_ani, strlen(in->cid.cid_ani) + 1);
    							else
    								ast_log(LOG_WARNING, "Out of memory\n");
    						}
    						if (o->chan->cid.cid_rdnis) 
    							free(o->chan->cid.cid_rdnis);
    						if (!ast_strlen_zero(in->macroexten))
    							o->chan->cid.cid_rdnis = strdup(in->macroexten);
    						else
    							o->chan->cid.cid_rdnis = strdup(in->exten);
    						if (ast_call(o->chan, tmpchan, 0)) {
    							ast_log(LOG_NOTICE, "Failed to dial on local channel for call forward to '%s'\n", tmpchan);
    							o->stillgoing = 0;
    							ast_hangup(o->chan);
    							o->chan = NULL;
    							numnochan++;
    						}
    					}
    					/* Hangup the original channel now, in case we needed it */
    					ast_hangup(winner);
    					continue;
    				}
    
    Mark Spencer's avatar
    Mark Spencer committed
    				f = ast_read(winner);
    				if (f) {
    					if (f->frametype == AST_FRAME_CONTROL) {
    						switch(f->subclass) {
    
    		    				case AST_CONTROL_ANSWER:
    
    Mark Spencer's avatar
    Mark Spencer committed
    							/* This is our guy if someone answered. */
    							if (!peer) {
    								if (option_verbose > 2)
    									ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
    
    								ast_copy_flags(flags, o, QUEUE_FLAG_REDIR_IN & QUEUE_FLAG_REDIR_OUT & QUEUE_FLAG_DISCON_IN & QUEUE_FLAG_DISCON_OUT);
    
    Mark Spencer's avatar
    Mark Spencer committed
    							}
    							break;
    						case AST_CONTROL_BUSY:
    							if (option_verbose > 2)
    								ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", o->chan->name);
    							o->stillgoing = 0;
    							if (in->cdr)
    								ast_cdr_busy(in->cdr);
    
    							ast_hangup(o->chan);
    							o->chan = NULL;
    							if (qe->parent->strategy)
    								ring_one(qe, outgoing);
    
    Mark Spencer's avatar
    Mark Spencer committed
    							numbusies++;
    							break;
    						case AST_CONTROL_CONGESTION:
    							if (option_verbose > 2)
    								ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", o->chan->name);
    							o->stillgoing = 0;
    							if (in->cdr)
    								ast_cdr_busy(in->cdr);
    
    							ast_hangup(o->chan);
    							o->chan = NULL;
    							if (qe->parent->strategy)
    								ring_one(qe, outgoing);
    
    Mark Spencer's avatar
    Mark Spencer committed
    							numbusies++;
    							break;
    						case AST_CONTROL_RINGING:
    							if (option_verbose > 2)
    								ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name);
    							if (!sentringing) {
    #if 0
    								ast_indicate(in, AST_CONTROL_RINGING);
    #endif								
    								sentringing++;
    							}
    							break;
    						case AST_CONTROL_OFFHOOK:
    							/* Ignore going off hook */
    							break;
    						default:
    							ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass);
    						}
    					}
    					ast_frfree(f);
    				} else {
    					o->stillgoing = 0;
    
    					ast_hangup(o->chan);
    					o->chan = NULL;
    					if (qe->parent->strategy)
    						ring_one(qe, outgoing);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				}
    			}
    			o = o->next;
    		}
    		if (winner == in) {
    			f = ast_read(in);
    #if 0
    			if (f && (f->frametype != AST_FRAME_VOICE))
    					printf("Frame type: %d, %d\n", f->frametype, f->subclass);
    			else if (!f || (f->frametype != AST_FRAME_VOICE))
    				printf("Hangup received on %s\n", in->name);
    #endif
    			if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) {
    				/* Got hung up */
    				*to=-1;
    				return NULL;
    			}
    
    			if (f && (f->frametype == AST_FRAME_DTMF) && ast_test_flag(flags, QUEUE_FLAG_DISCON_OUT) && (f->subclass == '*')) {
    
    				if (option_verbose > 3)
    
    Mark Spencer's avatar
    Mark Spencer committed
    					ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				*to=0;
    				return NULL;
    			}
    
    			if (f && (f->frametype == AST_FRAME_DTMF) && (f->subclass != '*') && valid_exit(qe, f->subclass)) {
    				if (option_verbose > 3)
    					ast_verbose(VERBOSE_PREFIX_3 "User pressed digit: %c", f->subclass);
    				*to=0;
    				*digit=f->subclass;
    				return NULL;
    			}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    		if (!*to && (option_verbose > 2))
    			ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", orig);
    	}
    
    	return peer;
    	
    }
    
    
    static int is_our_turn(struct queue_ent *qe)
    {
    	struct queue_ent *ch;
    	int res;
    
    	/* Atomically read the parent head -- does not need a lock */
    	ch = qe->parent->head;
    	/* If we are now at the top of the head, break out */
    	if (ch == qe) {
    		if (option_debug)
    			ast_log(LOG_DEBUG, "It's our turn (%s).\n", qe->chan->name);
    		res = 1;
    	} else {
    		if (option_debug)
    			ast_log(LOG_DEBUG, "It's not our turn (%s).\n", qe->chan->name);
    		res = 0;
    	}
    	return res;
    }
    
    
    static int wait_our_turn(struct queue_ent *qe, int ringing)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	int res = 0;
    
    
    	/* This is the holding pen for callers 2 through maxlen */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	for (;;) {
    
    			break;
    
    		/* If we have timed out, break out */
    
    		if (qe->expire && (time(NULL) > qe->expire))
    
    Mark Spencer's avatar
    Mark Spencer committed
    			break;
    
    		/* leave the queue if no agents, if enabled */
    
    		if (ast_test_flag(qe->parent, QUEUE_FLAG_LEAVEWHENEMPTY) && has_no_members(qe->parent)) {
    
    		/* Make a position announcement, if enabled */
    
    		if (qe->parent->announcefrequency && !ringing)
    
    			say_position(qe);
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Wait a second before checking again */
    		res = ast_waitfordigit(qe->chan, RECHECK * 1000);
    		if (res)
    			break;
    	}
    	return res;
    }
    
    
    static int update_queue(struct ast_call_queue *q, struct member *member)
    
    {
    	struct member *cur;
    	/* Since a reload could have taken place, we have to traverse the list to
    		be sure it's still valid */
    
    		if (member == cur) {
    
    			time(&cur->lastcall);
    			cur->calls++;
    			break;
    		}
    		cur = cur->next;
    	}
    
    	q->callscompleted++;
    
    	return 0;
    }
    
    static int calc_metric(struct ast_call_queue *q, struct member *mem, int pos, struct queue_ent *qe, struct localuser *tmp)
    
    {
    	switch (q->strategy) {
    	case QUEUE_STRATEGY_RINGALL:
    
    		/* Everyone equal, except for penalty */
    		tmp->metric = mem->penalty * 1000000;
    
    		break;
    
    	case QUEUE_STRATEGY_ROUNDROBIN:
    		if (!pos) {
    
    			if (!ast_test_flag(q, QUEUE_FLAG_WRAPPED)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				/* No more channels, start over */
    				q->rrpos = 0;
    			} else {
    				/* Prioritize next entry */
    
    			ast_clear_flag(q, QUEUE_FLAG_WRAPPED);
    
    		/* Fall through */
    	case QUEUE_STRATEGY_RRMEMORY:
    
    		if (pos < q->rrpos) {
    			tmp->metric = 1000 + pos;
    		} else {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (pos > q->rrpos) {
    				/* Indicate there is another priority */
    
    				ast_set_flag(q, QUEUE_FLAG_WRAPPED);
    
    		tmp->metric += mem->penalty * 1000000;
    
    		break;
    	case QUEUE_STRATEGY_RANDOM:
    		tmp->metric = rand() % 1000;
    
    		tmp->metric += mem->penalty * 1000000;
    		break;
    	case QUEUE_STRATEGY_FEWESTCALLS:
    		tmp->metric = mem->calls;
    		tmp->metric += mem->penalty * 1000000;
    		break;
    	case QUEUE_STRATEGY_LEASTRECENT:
    		if (!mem->lastcall)
    			tmp->metric = 0;
    		else
    			tmp->metric = 1000000 - (time(NULL) - mem->lastcall);
    		tmp->metric += mem->penalty * 1000000;
    
    		break;
    	default:
    		ast_log(LOG_WARNING, "Can't calculate metric for unknown strategy %d\n", q->strategy);
    		break;
    
    static int try_calling(struct queue_ent *qe, char *options, char *announceoverride, char *url, int *go_on)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	struct member *cur;
    	struct localuser *outgoing=NULL, *tmp = NULL;
    	int to;
    
    	struct ast_flags flags;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char restofit[AST_MAX_EXTENSION];
    
    	char oldexten[AST_MAX_EXTENSION]="";
    	char oldcontext[AST_MAX_EXTENSION]="";
    	char queuename[256]="";
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *newnum;
    
    	char *monitorfilename;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_channel *peer;
    
    	struct member *member;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int res = 0, bridge = 0;
    
    	int zapx = 2;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *announce = NULL;
    
    	time_t callstart;
    
    	struct ast_bridge_config config;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Hold the lock while we setup the outgoing calls */
    
    	if (option_debug)
    		ast_log(LOG_DEBUG, "%s is trying to call a queue member.\n", 
    							qe->chan->name);
    
    	strncpy(queuename, qe->parent->name, sizeof(queuename) - 1);
    
    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) {
    		/* Get a technology/[device:]number pair */
    		tmp = malloc(sizeof(struct localuser));
    		if (!tmp) {
    
    			ast_mutex_unlock(&qe->parent->lock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ast_log(LOG_WARNING, "Out of memory\n");
    			goto out;
    		}
    		memset(tmp, 0, sizeof(struct localuser));
    
    		tmp->stillgoing = -1;
    
    		for (; options && *options; options++)
    			switch (*options) {
    			case 't':
    
    				ast_set_flag(tmp, QUEUE_FLAG_REDIR_IN);
    
    				ast_set_flag(tmp, QUEUE_FLAG_REDIR_OUT);
    
    				ast_set_flag(tmp, QUEUE_FLAG_RINGBACKONLY);
    
    				ast_set_flag(tmp, QUEUE_FLAG_MUSICONHOLD);
    
    				ast_set_flag(tmp, QUEUE_FLAG_DATAQUALITY);
    
    				ast_set_flag(tmp, QUEUE_FLAG_DISCON_IN);
    
    				ast_set_flag(tmp, QUEUE_FLAG_DISCON_OUT);
    
    				break;
    			case 'n':
    			        if ((now - qe->start >= qe->parent->timeout))
    					*go_on = 1;
    				break;
    			}
    
    		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;
    
    		strncpy(tmp->interface, cur->interface, sizeof(tmp->interface)-1);
    
    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;
    
    	ast_mutex_unlock(&qe->parent->lock);
    
    	lpeer = wait_for_answer(qe, outgoing, &to, &flags, &digit);
    
    	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;
    
    		} else {
    			if (digit && valid_exit(qe, digit))
    				res=digit;
    			else
    				/* Nobody answered, next please? */
    				res=0;
    		}
    
    		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")) {
    
    			zapx = !ast_test_flag(tmp, QUEUE_FLAG_DATAQUALITY);
    
    			ast_channel_setoption(qe->chan,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0);
    		}			
    		if (!strcmp(peer->type,"Zap")) {
    
    			zapx = !ast_test_flag(tmp, QUEUE_FLAG_DATAQUALITY);
    
    			ast_channel_setoption(peer,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0);
    		}
    
    		/* Update parameters for the queue */
    
    		recalc_holdtime(qe);
    
    		member = lpeer->member;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		hanguptree(outgoing, peer);
    		outgoing = NULL;
    
    		if (announce || ast_test_flag(qe->parent, QUEUE_FLAG_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 (!res2 && ast_test_flag(qe->parent, QUEUE_FLAG_REPORTHOLDTIME)) {
    
    					if (!play_file(peer, qe->parent->sound_reporthold)) {
    						int holdtime;
    						time_t now;
    
    						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
    			if (res2) {
    				/* 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", "");
    
    Mark Spencer's avatar
    Mark Spencer committed
    				ast_hangup(peer);
    				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);
    			ast_hangup(peer);
    			return -1;
    		}
    
    		/* Begin Monitoring */
    		if (qe->parent->monfmt && *qe->parent->monfmt) {
    
    			monitorfilename = pbx_builtin_getvar_helper( qe->chan, "MONITOR_FILENAME");
    
    			if(monitorfilename) {
    				ast_monitor_start( peer, qe->parent->monfmt, monitorfilename, 1 );
    			} else {
    				ast_monitor_start( peer, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1 );
    			}
    
    			if(ast_test_flag(qe->parent, QUEUE_FLAG_MONJOIN)) {
    
    				ast_monitor_setjoinfiles( peer, 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);
    
    Mark Spencer's avatar
    Mark Spencer committed
     			ast_channel_sendurl( peer, url );
    
    		ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "CONNECT", "%ld", (long)time(NULL) - qe->start);
    		strncpy(oldcontext, qe->chan->context, sizeof(oldcontext) - 1);
    		strncpy(oldexten, qe->chan->exten, sizeof(oldexten) - 1);
    		time(&callstart);
    
    
    		memset(&config,0,sizeof(struct ast_bridge_config));
    
    		if (ast_test_flag(&flags, QUEUE_FLAG_REDIR_IN))
    
    			ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
    
    		if (ast_test_flag(&flags, QUEUE_FLAG_REDIR_OUT))
    
    			ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT);
    
    		if (ast_test_flag(&flags, QUEUE_FLAG_DISCON_IN))
    
    			ast_set_flag(&(config.features_callee), AST_FEATURE_DISCONNECT);
    
    		if (ast_test_flag(&flags, QUEUE_FLAG_DISCON_OUT))
    
    			ast_set_flag(&(config.features_caller), AST_FEATURE_DISCONNECT);
    
    		bridge = ast_bridge_call(qe->chan,peer,&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));
    		} else {
    			ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "COMPLETEAGENT", "%ld|%ld", (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:
    	hanguptree(outgoing, NULL);
    	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);
    }
    
    
    /* [PHM 06/26/03] */
    
    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 (!strcmp(interface, mem->interface))
    				return mem;
    
    static struct member *create_queue_node(char *interface, int penalty)
    
    	
    	/* Add a new member */
    
    	cur = malloc(sizeof(struct member));
    
    	if (cur) {
    		memset(cur, 0, sizeof(struct member));
    
    		cur->penalty = penalty;
    
    		strncpy(cur->interface, interface, sizeof(cur->interface) - 1);
    		if (!strchr(cur->interface, '/'))
    
    			ast_log(LOG_WARNING, "No location at interface '%s'\n", interface);
    
    		cur->status = ast_device_state(interface);
    
    /* Dump all members in a specific queue to the databse
     *
     * <pm_family>/<queuename> = <interface>;<penalty>;...
     *
     */
    static void dump_queue_members(struct ast_call_queue *pm_queue)
    {
    	struct member *cur_member = NULL;
    	char value[PM_MAX_LEN];
    	int value_len = 0;
    	int res;
    
    	memset(value, 0, sizeof(value));
    
    	if (pm_queue) {
    		cur_member = pm_queue->members;
    		while (cur_member) {
    			if (cur_member->dynamic) {
    				value_len = strlen(value);
    
    				res = snprintf(value+value_len, sizeof(value)-value_len, "%s;%d;", cur_member->interface, cur_member->penalty);
    
    				if (res != strlen(value + value_len)) {
    					ast_log(LOG_WARNING, "Could not create persistent member string, out of space\n");
    					break;
    				}
    			}					
    			cur_member = cur_member->next;
    		}
    
    		if (!ast_strlen_zero(value) && !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)
    {
    	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_node(interface, penalty);
    
    				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",
    
    					q->name, new_member->interface, new_member->dynamic ? "dynamic" : "static",
    
    					new_member->penalty, new_member->calls, new_member->lastcall, new_member->status);
    
    					
    					if (queue_persistent_members)
    
    						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;
    }
    
    /* Add members saved in the queue members DB file saves
     * created by dump_queue_members(), back into the queues */
    static void reload_queue_members(void)
    {
    	char *cur_pm_ptr;	
    	char *pm_queue_name;
    	char *pm_interface;
    	char *pm_penalty_tok;
    	int pm_penalty = 0;
    	struct ast_db_entry *pm_db_tree = NULL;
    	int pm_family_len = 0;
    	struct ast_call_queue *cur_queue = NULL;
    	char queue_data[PM_MAX_LEN];
    
    	pm_db_tree = ast_db_gettree(pm_family, NULL);
    
    	pm_family_len = strlen(pm_family);
    	ast_mutex_lock(&qlock);
    	/* Each key in 'pm_family' is the name of a specific queue in which
    	 * we will reload members into. */
    	while (pm_db_tree) {
    		pm_queue_name = pm_db_tree->key+pm_family_len+2;
    
    		cur_queue = queues;
    		while (cur_queue) {
    			ast_mutex_lock(&cur_queue->lock);
    			
    			if (strcmp(pm_queue_name, cur_queue->name) == 0)
    
    			
    			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, pm_queue_name);
    			pm_db_tree = pm_db_tree->next;
    			continue;
    		} else
    
    			ast_mutex_unlock(&cur_queue->lock);
    
    
    		if (!ast_db_get(pm_family, pm_queue_name, queue_data, PM_MAX_LEN)) {
    			/* Parse each <interface>;<penalty>; from the value of the
    			 * queuename key and add it to the respective queue */
    			cur_pm_ptr = queue_data;
    			while ((pm_interface = strsep(&cur_pm_ptr, ";"))) {
    				if (!(pm_penalty_tok = strsep(&cur_pm_ptr, ";"))) {
    					ast_log(LOG_WARNING, "Error parsing corrupted Queue DB string for '%s'\n", pm_queue_name);
    					break;
    				}
    				pm_penalty = strtol(pm_penalty_tok, NULL, 10);
    				if (errno == ERANGE) {
    					ast_log(LOG_WARNING, "Error converting penalty: %s: Out of range.\n", pm_penalty_tok);
    					break;
    				}
    	
    				if (option_debug)
    
    					ast_log(LOG_DEBUG, "Reload Members: Queue: %s  Member: %s  Penalty: %d\n", pm_queue_name, pm_interface, pm_penalty);
    
    	
    				if (add_to_queue(pm_queue_name, pm_interface, pm_penalty) == RES_OUTOFMEMORY) {
    
    					ast_log(LOG_ERROR, "Out of Memory when loading queue member from astdb\n");
    
    					break;
    				}
    			}
    		}
    		
    		pm_db_tree = pm_db_tree->next;
    	}
    
    	ast_log(LOG_NOTICE, "Queue members sucessfully reloaded from database.\n");
    	ast_mutex_unlock(&qlock);
    	if (pm_db_tree) {
    		ast_db_freetree(pm_db_tree);
    		pm_db_tree = NULL;
    	}
    }
    
    
    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++;
    		}
    
    		else {
    			strncpy(tmpchan, chan->name, sizeof(tmpchan) - 1);
    			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);
    
    		if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + 101, chan->cid.cid_num)) {
    
    		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)) {
    
    			strncpy(tmpchan, chan->name, sizeof(tmpchan) - 1);
    			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)) {
    	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);
    
    		if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + 101, chan->cid.cid_num)) {
    
    		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));