/* * Asterisk -- A telephony toolkit for Linux. * * True call queues with optional send URL on answer * * Copyright (C) 1999, Mark Spencer * * Mark Spencer * * This program is free software, distributed under the terms of * the GNU General Public License */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* JDG */ #include #include #include #include #include #include #include #include #include #include #include #define QUEUE_STRATEGY_RINGALL 0 #define QUEUE_STRATEGY_ROUNDROBIN 1 #define QUEUE_STRATEGY_LEASTRECENT 2 #define QUEUE_STRATEGY_FEWESTCALLS 3 #define QUEUE_STRATEGY_RANDOM 4 static struct strategy { int strategy; char *name; } strategies[] = { { QUEUE_STRATEGY_RINGALL, "ringall" }, { QUEUE_STRATEGY_ROUNDROBIN, "roundrobin" }, { QUEUE_STRATEGY_LEASTRECENT, "leastrecent" }, { QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" }, { QUEUE_STRATEGY_RANDOM, "random" }, }; #define DEFAULT_RETRY 5 #define DEFAULT_TIMEOUT 15 #define RECHECK 1 /* Recheck every second to see we we're at the top yet */ static char *tdesc = "True Call Queueing"; static char *app = "Queue"; static char *synopsis = "Queue a call for a call queue"; static char *descrip = " Queue(queuename[|options[|URL][|announceoverride]]):\n" "Queues an incoming call in a particular call queue as defined in queues.conf.\n" " This application returns -1 if the originating channel hangs up, or if the\n" "call is bridged and either of the parties in the bridge terminate the call.\n" "Returns 0 if the queue is full, nonexistant, or has no members.\n" "The option string may contain zero or more of the following characters:\n" " 't' -- allow the called user transfer the calling user\n" " 'T' -- to allow the calling user to transfer the call.\n" " 'd' -- data-quality (modem) call (minimum delay).\n" " 'H' -- allow caller to hang up by hitting *.\n" " 'n' -- no retries on the timeout; will exit this application and go to the next step.\n" " In addition to transferring the call, a call may be parked and then picked\n" "up by another user.\n" " The optionnal URL will be sent to the called party if the channel supports\n" "it.\n"; // [PHM 06/26/03] static char *app_aqm = "AddQueueMember" ; static char *app_aqm_synopsis = "Dynamically adds queue members" ; static char *app_aqm_descrip = " AddQueueMember(queuename[|interface]):\n" "Dynamically adds interface to an existing queue\n" "Returns -1 if there is an error.\n" "Example: AddQueueMember(techsupport|SIP/3000)\n" ""; static char *app_rqm = "RemoveQueueMember" ; static char *app_rqm_synopsis = "Dynamically removes queue members" ; static char *app_rqm_descrip = " RemoveQueueMember(queuename[|interface]):\n" "Dynamically removes interface to an existing queue\n" "Returns -1 if there is an error.\n" "Example: RemoveQueueMember(techsupport|SIP/3000)\n" ""; /* whether to exit Queue application after the timeout hits */ static int go_on = 0; /* We define a customer "local user" structure because we use it not only for keeping track of what is in use but also for keeping track of who we're dialing. */ struct localuser { struct ast_channel *chan; char numsubst[256]; char tech[40]; int stillgoing; int metric; int allowredirect_in; int allowredirect_out; int ringbackonly; int musiconhold; int dataquality; int allowdisconnect; struct member *member; struct localuser *next; }; LOCAL_USER_DECL; struct queue_ent { struct ast_call_queue *parent; /* What queue is our parent */ char moh[80]; /* Name of musiconhold to be used */ char announce[80]; /* Announcement to play */ char context[80]; /* Context when user exits queue */ int pos; /* Where we are in the queue */ time_t start; /* When we started holding */ struct ast_channel *chan; /* Our channel */ struct queue_ent *next; /* The next queue entry */ }; struct member { char tech[80]; /* Technology */ char loc[256]; /* Location */ int penalty; /* Are we a last resort? */ int calls; int dynamic; /* Are we dynamically added? */ time_t lastcall; /* When last successful call was hungup */ struct member *next; /* Next member */ }; struct ast_call_queue { ast_mutex_t lock; char name[80]; /* Name of the queue */ char moh[80]; /* Name of musiconhold to be used */ char announce[80]; /* Announcement to play */ char context[80]; /* Announcement to play */ int strategy; /* Queueing strategy */ int announcetimeout; /* How often to announce their position */ int count; /* How many entries are in the queue */ int maxlen; /* Max number of entries in queue */ int dead; /* Whether this queue is dead or not */ int retry; /* Retry calling everyone after this amount of time */ int timeout; /* How long to wait for an answer */ /* Queue strategy things */ int rrpos; /* Round Robin - position */ int wrapped; /* Round Robin - wrapped around? */ struct member *members; /* Member channels to be tried */ struct queue_ent *head; /* Start of the actual queue */ struct ast_call_queue *next; /* Next call queue */ }; static struct ast_call_queue *queues = NULL; static ast_mutex_t qlock = AST_MUTEX_INITIALIZER; static char *int2strat(int strategy) { int x; for (x=0;x"; } static int strat2int(char *strategy) { int x; for (x=0;xname, queuename)) { /* This is our one */ ast_mutex_lock(&q->lock); if (q->members && (!q->maxlen || (q->count < q->maxlen))) { /* There's space for us, put us at the end */ prev = NULL; cur = q->head; while(cur) { cur->pos = ++pos; prev = cur; cur = cur->next; } if (prev) prev->next = qe; else q->head = qe; /* Fix additional pointers and information */ qe->next = NULL; qe->parent = q; qe->pos = ++pos; strncpy(qe->moh, q->moh, sizeof(qe->moh)); strncpy(qe->announce, q->announce, sizeof(qe->announce)); strncpy(qe->context, q->context, sizeof(qe->context)); q->count++; res = 0; manager_event(EVENT_FLAG_CALL, "Join", "Channel: %s\r\nCallerID: %s\r\nQueue: %s\r\nPosition: %d\r\nCount: %d\r\n", qe->chan->name, (qe->chan->callerid ? qe->chan->callerid : "unknown"), q->name, qe->pos, q->count ); #if 0 ast_log(LOG_NOTICE, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos ); #endif } ast_mutex_unlock(&q->lock); break; } q = q->next; } ast_mutex_unlock(&qlock); return res; } static void free_members(struct ast_call_queue *q, int all) { /* Free non-dynamic members */ struct member *curm, *next, *prev; curm = q->members; prev = NULL; while(curm) { next = curm->next; if (all || !curm->dynamic) { if (prev) prev->next = next; else q->members = next; free(curm); } else prev = curm; curm = next; } } static void destroy_queue(struct ast_call_queue *q) { struct ast_call_queue *cur, *prev = NULL; ast_mutex_lock(&qlock); cur = queues; while(cur) { if (cur == q) { if (prev) prev->next = cur->next; else queues = cur->next; } else { prev = cur; } cur = cur->next; } ast_mutex_unlock(&qlock); free_members(q, 1); free(q); } static void leave_queue(struct queue_ent *qe) { struct ast_call_queue *q; struct queue_ent *cur, *prev = NULL; int pos = 0; q = qe->parent; if (!q) return; ast_mutex_lock(&q->lock); prev = NULL; cur = q->head; while(cur) { if (cur == qe) { q->count--; /* Take us out of the queue */ manager_event(EVENT_FLAG_CALL, "Leave", "Channel: %s\r\nQueue: %s\r\nCount: %d\r\n", qe->chan->name, q->name, q->count); #if 0 ast_log(LOG_NOTICE, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name ); #endif /* Take us out of the queue */ if (prev) prev->next = cur->next; else q->head = cur->next; } else { cur->pos = ++pos; prev = cur; } cur = cur->next; } ast_mutex_unlock(&q->lock); if (q->dead && !q->count) { /* It's dead and nobody is in it, so kill it */ destroy_queue(q); } } static void hanguptree(struct localuser *outgoing, struct ast_channel *exception) { /* Hang up a tree of stuff */ struct localuser *oo; while(outgoing) { /* Hangup any existing lines we have open */ if (outgoing->chan && (outgoing->chan != exception)) ast_hangup(outgoing->chan); oo = outgoing; outgoing=outgoing->next; free(oo); } } static int ring_entry(struct queue_ent *qe, struct localuser *tmp) { int res; /* Request the peer */ tmp->chan = ast_request(tmp->tech, qe->chan->nativeformats, tmp->numsubst); if (!tmp->chan) { /* If we can't, just go on to the next call */ #if 0 ast_log(LOG_NOTICE, "Unable to create channel of type '%s'\n", cur->tech); #endif if (qe->chan->cdr) ast_cdr_busy(qe->chan->cdr); tmp->stillgoing = 0; return 0; } tmp->chan->appl = "AppQueue"; tmp->chan->data = "(Outgoing Line)"; tmp->chan->whentohangup = 0; if (tmp->chan->callerid) free(tmp->chan->callerid); if (tmp->chan->ani) free(tmp->chan->ani); if (qe->chan->callerid) tmp->chan->callerid = strdup(qe->chan->callerid); else tmp->chan->callerid = NULL; if (qe->chan->ani) tmp->chan->ani = strdup(qe->chan->ani); else tmp->chan->ani = NULL; /* Presense of ADSI CPE on outgoing channel follows ours */ tmp->chan->adsicpe = qe->chan->adsicpe; /* Place the call, but don't wait on the answer */ res = ast_call(tmp->chan, tmp->numsubst, 0); if (res) { /* Again, keep going even if there's an error */ if (option_debug) ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res); else if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", tmp->numsubst); ast_hangup(tmp->chan); tmp->chan = NULL; tmp->stillgoing = 0; return 0; } else if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", tmp->numsubst); return 0; } static int ring_one(struct queue_ent *qe, struct localuser *outgoing) { struct localuser *cur; struct localuser *best; int bestmetric=0; do { best = NULL; cur = outgoing; while(cur) { if (cur->stillgoing && /* Not already done */ !cur->chan && /* Isn't already going */ (!best || (cur->metric < bestmetric))) { /* We haven't found one yet, or it's better */ bestmetric = cur->metric; best = cur; } cur = cur->next; } if (best) { if (!qe->parent->strategy) { /* Ring everyone who shares this best metric (for ringall) */ cur = outgoing; while(cur) { if (cur->stillgoing && !cur->chan && (cur->metric == bestmetric)) { ast_log(LOG_DEBUG, "(Parallel) Trying '%s/%s' with metric %d\n", cur->tech, cur->numsubst, cur->metric); ring_entry(qe, cur); } cur = cur->next; } } else { /* Ring just the best channel */ ast_log(LOG_DEBUG, "Trying '%s/%s' with metric %d\n", best->tech, best->numsubst, best->metric); ring_entry(qe, best); } } } while (best && !best->chan); if (!best) { ast_log(LOG_DEBUG, "Nobody left to try ringing in queue\n"); return 0; } return 1; } static int valid_exit(struct queue_ent *qe, char digit) { char tmp[2]; if (!strlen(qe->context)) return 0; tmp[0] = digit; tmp[1] = '\0'; if (ast_exists_extension(qe->chan, qe->context, tmp, 1, qe->chan->callerid)) { strncpy(qe->chan->context, qe->context, sizeof(qe->chan->context) - 1); strncpy(qe->chan->exten, tmp, sizeof(qe->chan->exten) - 1); qe->chan->priority = 0; return 1; } return 0; } #define MAX 256 static struct localuser *wait_for_answer(struct queue_ent *qe, struct localuser *outgoing, int *to, int *allowredir_in, int *allowredir_out, int *allowdisconnect, char *digit) { char *queue = qe->parent->name; struct localuser *o; int found; int numlines; int sentringing = 0; int numbusies = 0; int orig = *to; struct ast_frame *f; struct localuser *peer = NULL; struct ast_channel *watchers[MAX]; int pos; struct ast_channel *winner; struct ast_channel *in = qe->chan; while(*to && !peer) { o = outgoing; found = -1; pos = 1; numlines = 0; watchers[0] = in; while(o) { /* Keep track of important channels */ if (o->stillgoing && o->chan) { watchers[pos++] = o->chan; found = 1; } o = o->next; numlines++; } if (found < 0) { if (numlines == numbusies) { ast_log(LOG_DEBUG, "Everyone is busy at this time\n"); } else { ast_log(LOG_NOTICE, "No one is answered queue %s\n", queue); } *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)) { if (!peer) { if (option_verbose > 2) ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name); peer = o; *allowredir_in = o->allowredirect_in; *allowredir_out = o->allowredirect_out; *allowdisconnect = o->allowdisconnect; } } else if (o->chan && (o->chan == winner)) { f = ast_read(winner); if (f) { if (f->frametype == AST_FRAME_CONTROL) { switch(f->subclass) { case AST_CONTROL_ANSWER: /* 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); peer = o; *allowredir_in = o->allowredirect_in; *allowredir_out = o->allowredirect_out; *allowdisconnect = o->allowdisconnect; } 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); 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); 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); } } 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) && allowdisconnect && (f->subclass == '*')) { if (option_verbose > 3) ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass); *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; } } if (!*to && (option_verbose > 2)) ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", orig); } return peer; } static int wait_our_turn(struct queue_ent *qe) { struct queue_ent *ch; int res = 0; for (;;) { /* 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 (qe->parent->head == qe) break; /* 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 localuser *user) { struct member *cur; /* Since a reload could have taken place, we have to traverse the list to be sure it's still valid */ ast_mutex_lock(&q->lock); cur = q->members; while(cur) { if (user->member == cur) { time(&cur->lastcall); cur->calls++; break; } cur = cur->next; } ast_mutex_unlock(&q->lock); 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 (!q->wrapped) { /* No more channels, start over */ q->rrpos = 0; } else { /* Prioritize next entry */ q->rrpos++; } q->wrapped = 0; } if (pos < q->rrpos) { tmp->metric = 1000 + pos; } else { if (pos > q->rrpos) { /* Indicate there is another priority */ q->wrapped = 1; } tmp->metric = pos; } 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; } return 0; } static int try_calling(struct queue_ent *qe, char *options, char *announceoverride, char *url) { struct member *cur; struct localuser *outgoing=NULL, *tmp = NULL; int to; int allowredir_in=0; int allowredir_out=0; int allowdisconnect=0; char restofit[AST_MAX_EXTENSION]; char *newnum; struct ast_channel *peer; struct localuser *lpeer; int res = 0, bridge = 0; int zapx = 2; int x=0; char *announce = NULL; char digit = 0; /* Hold the lock while we setup the outgoing calls */ ast_mutex_lock(&qe->parent->lock); cur = qe->parent->members; if (strlen(qe->announce)) announce = qe->announce; if (announceoverride && strlen(announceoverride)) announce = announceoverride; while(cur) { /* Get a technology/[device:]number pair */ tmp = malloc(sizeof(struct localuser)); if (!tmp) { ast_mutex_unlock(&qe->parent->lock); ast_log(LOG_WARNING, "Out of memory\n"); goto out; } memset(tmp, 0, sizeof(struct localuser)); tmp->stillgoing = -1; if (options) { if (strchr(options, 't')) tmp->allowredirect_in = 1; if (strchr(options, 'T')) tmp->allowredirect_out = 1; if (strchr(options, 'r')) tmp->ringbackonly = 1; if (strchr(options, 'm')) tmp->musiconhold = 1; if (strchr(options, 'd')) tmp->dataquality = 1; if (strchr(options, 'H')) tmp->allowdisconnect = 1; if (strchr(options, 'n')) go_on = 1; } 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 */ strncpy(tmp->tech, cur->tech, sizeof(tmp->tech)-1); strncpy(tmp->numsubst, cur->loc, sizeof(tmp->numsubst)-1); /* If we're dialing by extension, look at the extension to know what to dial */ if ((newnum = strstr(tmp->numsubst, "BYEXTENSION"))) { strncpy(restofit, newnum + strlen("BYEXTENSION"), sizeof(restofit)-1); snprintf(newnum, sizeof(tmp->numsubst) - (newnum - tmp->numsubst), "%s%s", qe->chan->exten,restofit); if (option_debug) ast_log(LOG_DEBUG, "Dialing by extension %s\n", tmp->numsubst); } /* 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); /* 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)) break; cur = cur->next; } if (qe->parent->timeout) to = qe->parent->timeout * 1000; else to = -1; ring_one(qe, outgoing); ast_mutex_unlock(&qe->parent->lock); lpeer = wait_for_answer(qe, outgoing, &to, &allowredir_in, &allowredir_out, &allowdisconnect, &digit); if (lpeer) peer = lpeer->chan; else peer = NULL; if (!peer) { if (to) { /* Musta gotten hung up */ res = -1; } else { if (digit && valid_exit(qe, digit)) res=digit; else /* Nobody answered, next please? */ res=0; } 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")) { if (tmp->dataquality) zapx = 0; ast_channel_setoption(qe->chan,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0); } if (!strcmp(peer->type,"Zap")) { if (tmp->dataquality) zapx = 0; ast_channel_setoption(peer,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0); } /* Update parameters for the queue */ update_queue(qe->parent, lpeer); hanguptree(outgoing, peer); /* Stop music on hold */ ast_moh_stop(qe->chan); outgoing = NULL; if (announce) { int res2; res2 = ast_autoservice_start(qe->chan); if (!res2) res2 = ast_streamfile(peer, announce, peer->language); if (!res2) res2 = ast_waitstream(peer, ""); res2 |= ast_autoservice_stop(qe->chan); 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_hangup(peer); return -1; } } /* 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_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; } /* Drop out of the queue at this point, to prepare for next caller */ leave_queue(qe); /* JDG: sendurl */ if( url && strlen(url) && ast_channel_supports_html(peer) ) { ast_log(LOG_DEBUG, "app_queue: sendurl=%s.\n", url); ast_channel_sendurl( peer, url ); } /* /JDG */ bridge = ast_bridge_call(qe->chan, peer, allowredir_in, allowredir_out, allowdisconnect); if(bridge != AST_PBX_NO_HANGUP_PEER) ast_hangup(peer); if( bridge == 0 ) res=1; /* JDG: bridge successfull, leave app_queue */ else res = bridge; /* bridge error, stay in the queue */ } 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; return ast_waitfordigit(qe->chan, retrywait); } // [PHM 06/26/03] static struct member * interface_exists( struct ast_call_queue * q, char * interface ) { struct member * ret = NULL ; struct member *mem; char buf[500] ; if( q != NULL ) { mem = q->members ; while( mem != NULL ) { sprintf( buf, "%s/%s", mem->tech, mem->loc); if( strcmp( buf, interface ) == 0 ) { ret = mem ; break ; } else mem = mem->next ; } } return( ret ) ; } static struct member * create_queue_node( char * interface ) { struct member * cur ; char * tmp ; /* Add a new member */ cur = malloc(sizeof(struct member)); if (cur) { memset(cur, 0, sizeof(struct member)); strncpy(cur->tech, interface, sizeof(cur->tech) - 1); if ((tmp = strchr(cur->tech, '/'))) *tmp = '\0'; if ((tmp = strchr(interface, '/'))) { tmp++; strncpy(cur->loc, tmp, sizeof(cur->loc) - 1); } else ast_log(LOG_WARNING, "No location at interface '%s'\n", interface); } return( cur ) ; } static int rqm_exec(struct ast_channel *chan, void *data) { int res=-1; struct localuser *u; char *queuename; struct member * node ; struct member * look ; char info[512]; char tmpchan[256]=""; char *interface=NULL; struct ast_call_queue *q; int found=0 ; if (!data) { ast_log(LOG_WARNING, "RemoveQueueMember requires an argument (queuename|optional interface)\n"); return -1; } LOCAL_USER_ADD(u); // not sure if we need this, but better be safe than sorry ;-) /* Parse our arguments XXX Check for failure XXX */ strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1); 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; } } if( ( q = queues) != NULL ) { while( q && ( res != 0 ) && (!found) ) { ast_mutex_lock(&q->lock); if( strcmp( q->name, queuename) == 0 ) { // found queue, try to remove interface found=1 ; if( ( node = interface_exists( q, interface ) ) != NULL ) { if( ( look = q->members ) == node ) { // 1st q->members = node->next; } else { while( look != NULL ) if( look->next == node ) { look->next = node->next ; break ; } else look = look->next ; } free( node ) ; ast_log(LOG_NOTICE, "Removed interface '%s' to queue '%s'\n", interface, queuename); res = 0 ; } else ast_log(LOG_WARNING, "Unable to remove interface '%s' from queue '%s': " "Not there\n", interface, queuename); } ast_mutex_unlock(&q->lock); q = q->next; } } if( ! found ) ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': No such queue\n", queuename); 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 info[512]; char tmpchan[512]=""; char *interface=NULL; struct ast_call_queue *q; struct member *save; int found=0 ; if (!data) { ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename|optional interface)\n"); return -1; } LOCAL_USER_ADD(u); // not sure if we need this, but better be safe than sorry ;-) /* Parse our arguments XXX Check for failure XXX */ strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1); 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; } } if( ( q = queues) != NULL ) { while( q && ( res != 0 ) && (!found) ) { ast_mutex_lock(&q->lock); if( strcmp( q->name, queuename) == 0 ) { // found queue, try to enable interface found=1 ; if( interface_exists( q, interface ) == NULL ) { save = q->members ; q->members = create_queue_node( interface ) ; if( q->members != NULL ) { q->members->dynamic = 1; q->members->next = save ; } else q->members = save ; ast_log(LOG_NOTICE, "Added interface '%s' to queue '%s'\n", interface, queuename); res = 0 ; } else ast_log(LOG_WARNING, "Unable to add interface '%s' to queue '%s': " "Already there\n", interface, queuename); } ast_mutex_unlock(&q->lock); q = q->next; } } if( ! found ) ast_log(LOG_WARNING, "Unable to add interface to queue '%s': No such queue\n", queuename); LOCAL_USER_REMOVE(u); return res; } static int queue_exec(struct ast_channel *chan, void *data) { int res=-1; struct localuser *u; char *queuename; char info[512]; char *options = NULL; char *url = NULL; char *announceoverride = NULL; /* Our queue entry */ struct queue_ent qe; if (!data) { ast_log(LOG_WARNING, "Queue requires an argument (queuename|optional timeout|optional URL)\n"); return -1; } LOCAL_USER_ADD(u); /* Parse our arguments XXX Check for failure XXX */ strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1); queuename = info; if (queuename) { options = strchr(queuename, '|'); if (options) { *options = '\0'; options++; url = strchr(options, '|'); if (url) { *url = '\0'; url++; announceoverride = strchr(url, '|'); if (announceoverride) { *announceoverride = '\0'; announceoverride++; } } } } printf("queue: %s, options: %s, url: %s, announce: %s\n", queuename, options, url, announceoverride); /* Setup our queue entry */ memset(&qe, 0, sizeof(qe)); qe.chan = chan; qe.start = time(NULL); if (!join_queue(queuename, &qe)) { /* Start music on hold */ ast_moh_start(chan, qe.moh); for (;;) { res = wait_our_turn(&qe); /* If they hungup, return immediately */ if (res < 0) { if (option_verbose > 2) { ast_verbose(VERBOSE_PREFIX_3 "User disconnected while waiting their turn\n"); res = -1; } break; } if (!res) break; if (valid_exit(&qe, res)) break; } if (!res) { for (;;) { res = try_calling(&qe, options, announceoverride, url); if (res) break; /* exit after a timeout if 'n' option enabled */ if (go_on) { res = 0; break; } res = wait_a_bit(&qe); if (res < 0) { if (option_verbose > 2) { ast_verbose(VERBOSE_PREFIX_3 "User disconnected when they almost made it\n"); res = -1; } break; } if (res && valid_exit(&qe, res)) break; } } /* Don't allow return code > 0 */ if (res > 0 && res != AST_PBX_KEEPALIVE) { res = 0; ast_moh_stop(chan); } leave_queue(&qe); } else { ast_log(LOG_WARNING, "Unable to join queue '%s'\n", queuename); res = 0; } LOCAL_USER_REMOVE(u); return res; } static void reload_queues(void) { struct ast_call_queue *q, *ql, *qn; struct ast_config *cfg; char *cat, *tmp; struct ast_variable *var; struct member *prev, *cur; int new; cfg = ast_load("queues.conf"); if (!cfg) { ast_log(LOG_NOTICE, "No call queueing config file, so no call queues\n"); return; } ast_mutex_lock(&qlock); /* Mark all queues as dead for the moment */ q = queues; while(q) { q->dead = 1; q = q->next; } /* Chug through config file */ cat = ast_category_browse(cfg, NULL); while(cat) { if (strcasecmp(cat, "general")) { /* Look for an existing one */ q = queues; while(q) { if (!strcmp(q->name, cat)) break; q = q->next; } if (!q) { /* Make one then */ q = malloc(sizeof(struct ast_call_queue)); if (q) { /* Initialize it */ memset(q, 0, sizeof(struct ast_call_queue)); ast_mutex_init(&q->lock); strncpy(q->name, cat, sizeof(q->name)); new = 1; } else new = 0; } else new = 0; if (q) { if (!new) ast_mutex_lock(&q->lock); /* Re-initialize the queue */ q->dead = 0; q->retry = 0; q->timeout = -1; q->maxlen = 0; free_members(q, 0); strcpy(q->moh, ""); strcpy(q->announce, ""); strcpy(q->context, ""); prev = q->members; if (prev) { /* find the end of any dynamic members */ while(prev->next) prev = prev->next; } var = ast_variable_browse(cfg, cat); while(var) { if (!strcasecmp(var->name, "member")) { /* Add a new member */ cur = malloc(sizeof(struct member)); if (cur) { memset(cur, 0, sizeof(struct member)); strncpy(cur->tech, var->value, sizeof(cur->tech) - 1); if ((tmp = strchr(cur->tech, ','))) { *tmp = '\0'; tmp++; cur->penalty = atoi(tmp); if (cur->penalty < 0) cur->penalty = 0; } if ((tmp = strchr(cur->tech, '/'))) *tmp = '\0'; if ((tmp = strchr(var->value, '/'))) { tmp++; strncpy(cur->loc, tmp, sizeof(cur->loc) - 1); if ((tmp = strchr(cur->loc, ','))) *tmp = '\0'; } else ast_log(LOG_WARNING, "No location at line %d of queue.conf\n", var->lineno); if (prev) prev->next = cur; else q->members = cur; prev = cur; } } else if (!strcasecmp(var->name, "music")) { strncpy(q->moh, var->value, sizeof(q->moh) - 1); } else if (!strcasecmp(var->name, "announce")) { strncpy(q->announce, var->value, sizeof(q->announce) - 1); } else if (!strcasecmp(var->name, "context")) { strncpy(q->context, var->value, sizeof(q->context) - 1); } else if (!strcasecmp(var->name, "timeout")) { q->timeout = atoi(var->value); } else if (!strcasecmp(var->name, "retry")) { q->retry = atoi(var->value); } else if (!strcasecmp(var->name, "maxlen")) { q->maxlen = atoi(var->value); } else if (!strcasecmp(var->name, "strategy")) { q->strategy = strat2int(var->value); if (q->strategy < 0) { ast_log(LOG_WARNING, "'%s' isn't a valid strategy, using ringall instead\n", var->value); q->strategy = 0; } } else { ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queue.conf\n", cat, var->name, var->lineno); } var = var->next; } if (q->retry < 1) q->retry = DEFAULT_RETRY; if (q->timeout < 0) q->timeout = DEFAULT_TIMEOUT; if (q->maxlen < 0) q->maxlen = 0; if (!new) ast_mutex_unlock(&q->lock); if (new) { q->next = queues; queues = q; } } } cat = ast_category_browse(cfg, cat); } ast_destroy(cfg); q = queues; ql = NULL; while(q) { qn = q->next; if (q->dead) { if (ql) ql->next = q->next; else queues = q->next; if (!q->count) { free(q); } else ast_log(LOG_WARNING, "XXX Leaking a litttle memory :( XXX\n"); } else ql = q; q = qn; } ast_mutex_unlock(&qlock); } static int queues_show(int fd, int argc, char **argv) { struct ast_call_queue *q; struct queue_ent *qe; struct member *mem; int pos; time_t now; char max[80]; char calls[80]; time(&now); if (argc != 2) return RESULT_SHOWUSAGE; ast_mutex_lock(&qlock); q = queues; if (!q) { ast_mutex_unlock(&qlock); ast_cli(fd, "No queues.\n"); return RESULT_SUCCESS; } while(q) { ast_mutex_lock(&q->lock); if (q->maxlen) snprintf(max, sizeof(max), "%d", q->maxlen); else strcpy(max, "unlimited"); ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy\n", q->name, q->count, max, int2strat(q->strategy)); if (q->members) { ast_cli(fd, " Members: \n"); for (mem = q->members; mem; mem = mem->next) { if (mem->penalty) snprintf(max, sizeof(max) - 20, " with penalty %d", mem->penalty); else strcpy(max, ""); if (mem->dynamic) strcat(max, " (dynamic)"); if (mem->calls) { snprintf(calls, sizeof(calls), " has taken %d calls (last was %ld secs ago)", mem->calls, (long)(time(NULL) - mem->lastcall)); } else strcpy(calls, " has taken no calls yet"); ast_cli(fd, " %s/%s%s%s\n", mem->tech, mem->loc, max, calls); } } else ast_cli(fd, " No Members\n"); if (q->head) { pos = 1; ast_cli(fd, " Callers: \n"); for (qe = q->head; qe; qe = qe->next) ast_cli(fd, " %d. %s (wait: %ld:%2.2ld)\n", pos++, qe->chan->name, (long)(now - qe->start) / 60, (long)(now - qe->start) % 60); } else ast_cli(fd, " No Callers\n"); ast_cli(fd, "\n"); ast_mutex_unlock(&q->lock); q = q->next; } ast_mutex_unlock(&qlock); return RESULT_SUCCESS; } /* JDG: callback to display queues status in manager */ static int manager_queues_show( struct mansession *s, struct message *m ) { char *a[] = { "show", "queues" }; return queues_show( s->fd, 2, a ); } /* /JDG */ /* Dump queue status */ static int manager_queues_status( struct mansession *s, struct message *m ) { time_t now; int pos; char *id = astman_get_header(m,"ActionID"); char idText[256] = ""; struct ast_call_queue *q; struct queue_ent *qe; astman_send_ack(s, m, "Queue status will follow"); time(&now); ast_mutex_lock(&qlock); q = queues; if (id && &id) { snprintf(idText,256,"ActionID: %s\r\n",id); } while(q) { ast_mutex_lock(&q->lock); ast_cli(s->fd, "Event: QueueParams\r\n" "Queue: %s\r\n" "Max: %d\r\n" "Calls: %d\r\n" "%s" "\r\n", q->name, q->maxlen, q->count,idText); #if 0 /* Do we care about queue members? */ for (mem = q->members; mem; mem = mem->next) ast_cli(fd, " %s/%s\n", mem->tech, mem->loc); #endif pos = 1; for (qe = q->head; qe; qe = qe->next) ast_cli(s->fd, "Event: QueueMember\r\n" "Queue: %s\r\n" "Position: %d\r\n" "Channel: %s\r\n" "CallerID: %s\r\n" "Wait: %ld\r\n" "%s" "\r\n", q->name, pos++, qe->chan->name, (qe->chan->callerid ? qe->chan->callerid : ""), (long)(now - qe->start), idText); ast_mutex_unlock(&q->lock); q = q->next; } ast_mutex_unlock(&qlock); return RESULT_SUCCESS; } static char show_queues_usage[] = "Usage: show queues\n" " Provides summary information on call queues.\n"; static struct ast_cli_entry cli_show_queues = { { "show", "queues", NULL }, queues_show, "Show status of queues", show_queues_usage, NULL }; int unload_module(void) { STANDARD_HANGUP_LOCALUSERS; ast_cli_unregister(&cli_show_queues); ast_manager_unregister( "Queues" ); ast_manager_unregister( "QueueStatus" ); return ast_unregister_application(app); } int load_module(void) { int res; res = ast_register_application(app, queue_exec, synopsis, descrip); if (!res) { ast_cli_register(&cli_show_queues); ast_manager_register( "Queues", 0, manager_queues_show, "Queues" ); ast_manager_register( "QueueStatus", 0, manager_queues_status, "Queue Status" ); // [PHM 06/26/03] ast_register_application(app_aqm, aqm_exec, app_aqm_synopsis, app_aqm_descrip) ; ast_register_application(app_rqm, rqm_exec, app_rqm_synopsis, app_rqm_descrip) ; } reload_queues(); return res; } int reload(void) { reload_queues(); return 0; } char *description(void) { return tdesc; } int usecount(void) { int res; STANDARD_USECOUNT(res); return res; } char *key() { return ASTERISK_GPL_KEY; }