/*
* Asterisk -- A telephony toolkit for Linux.
*
* Distribute incoming calls to an agent pool.
*
* Copyright (C) 2002, James Sharp
*
* James Sharp <jsharp@psychoses.org>
*
* This program is free software, distributed under the terms of
* the GNU General Public License
*/
#include <asterisk/lock.h>
#include <asterisk/file.h>
#include <asterisk/frame.h>
#include <asterisk/logger.h>
#include <asterisk/channel.h>
#include <asterisk/pbx.h>
#include <asterisk/module.h>
#include <asterisk/translate.h>
#include <asterisk/config.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
#include <netinet/in.h>
static char *tdesc = "Automatic call distribution system";
static char *app = "ACD";
static char *synopsis = "Distribute an incoming call to a group of Asterisk devices";
static char *descrip = "Takes an incoming call and connects it to an Asterisk channel in a group of channels based on either \"Next available\" or \"Most idle\"";
#define ACD_MAX_GROUPS 16
#define ACD_MAX_AGENTS_PERGROUP 48
#define ACD_CONFIG "acd.conf"
STANDARD_LOCAL_USER;
LOCAL_USER_DECL;
static int acd_connect_to_acd_host(struct ast_config *);
static int route_by_nextavail(struct ast_channel *, int);
static int route_by_mostidle(struct ast_channel *, int);
struct acd_agent {
char *agent_device;
time_t last_call_time;
};
struct acd_agent_grouping {
struct acd_agent agent[ACD_MAX_AGENTS_PERGROUP];
char *group_name;
int defined_agents;
char *routing_type;
};
struct acd_agent_grouping acd_grouping[ACD_MAX_GROUPS];
static int acd_groups_defined;
static char *acdgroups[ACD_MAX_GROUPS];
static int acd_exec(struct ast_channel *chan, void *data)
{
int dest_group, ret;
dest_group = atoi((char *)data);
ast_log(LOG_DEBUG,"Executing ACD acd_exec. Source channel %s. Destination group %i\n",chan->name,dest_group);
/* Okay. Now we figure out what channel we need to route this call down to. */
/* One of two ways..."Next available" sends it down to the next non-busy channel */
/* "Most Idle" sends it down the channel for the agent that has been idle (non-busy) longest */
/* This is the "Next available" code */
if (!strcasecmp(acd_grouping[dest_group].routing_type,"nextavail"))
ret = route_by_nextavail(chan, dest_group);
/* if (!strcasecmp(acd_grouping[dest_group].routing_type,"mostidle"))
ret = route_by_mostidle(chan, dest_group);
*/
return 0;
}
static int route_by_nextavail(struct ast_channel *chan, int dest_group)
{
struct ast_channel *destchan;
struct ast_frame *f;
int i, res;
char *tech, *number;
time_t t;
/* Go through the list of agents in the destination group. Do an ast_request on each of them, if it returns NULL then the channel is definitely busy. */
/* If ast_request returns a channel, then it is probably not busy...but we'll have to check for that since it could possibly be busy */
while (chan->state == AST_STATE_UP) {
for (i=0; i<acd_grouping[dest_group].defined_agents; i++) {
tech = strdup(acd_grouping[dest_group].agent[i].agent_device);
ast_log(LOG_DEBUG,"ast_request for %s\n",tech);
number = strchr(tech,'/');
*number = '\0';
number++;
destchan = ast_request(tech, chan->nativeformats, number);
if (destchan == NULL) {
ast_log(LOG_DEBUG,"ast_request for %s/%s returned NULL. Probably busy.\n",tech,number);
free(tech);
continue;
}
/* ast_request has given us a destination channel. Attempt a call down it. */
res = ast_call(destchan, number, 0);
if (res) {
/* If it returns an error, log it and go on to the next one */
ast_log(LOG_DEBUG, "ast call on acd peer returned %d\n", res);
ast_hangup(destchan);
continue;
}
f = ast_read(destchan);
if (f->frametype == AST_FRAME_CONTROL) {
switch(f->subclass) {
case AST_CONTROL_ANSWER:
case AST_CONTROL_RINGING:
/* A valid channel. Connect the two */
time(&t);
acd_grouping[dest_group].agent[i].last_call_time = t;
res = ast_channel_make_compatible(chan, destchan);
if (res < 0) {
ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", chan->name, destchan->name);
ast_hangup(destchan);
return -1;
}
ast_bridge_call(chan,destchan,0);
ast_hangup(destchan);
return 0;
break;
default:
/* Its busy or something's up. Go onto the next one */
continue;
break;
}
}
}
ast_streamfile(chan,"acd-agent-busy",chan->language);
ast_safe_sleep(chan,10000);
}
return -1;
}
int load_module(void)
{
struct ast_config *cfg;
struct ast_variable *v;
char *acdgroup;
int i=0;
int j=0;
cfg = ast_load(ACD_CONFIG);
if (!cfg) {
ast_log(LOG_WARNING, "No such configuration file %s\n", ACD_CONFIG);
return -1;
}
if (ast_variable_retrieve(cfg, "network", "hostname")) {
acd_connect_to_acd_host(cfg);
}
acdgroup = ast_category_browse(cfg, NULL);
/* Get all the ACD group names */
while(acdgroup) {
if (strstr(acdgroup,"acdgroup")) {
acdgroups[i] = strdup(acdgroup);
i++;
ast_log(LOG_DEBUG,"Registered ACD Group name %s\n",acdgroup);
}
acdgroup = ast_category_browse(cfg,acdgroup);
}
acd_groups_defined = i;
/* Now that we've got the group names, browse through the "agent" IDs under each group context */
for (i=0;i<acd_groups_defined;i++)
{
j=0;
acd_grouping[i].group_name = ast_variable_retrieve(cfg,acdgroups[i],"name");
acd_grouping[i].routing_type = ast_variable_retrieve(cfg,acdgroups[i],"routing");
if (acd_grouping[i].group_name == NULL)
acd_grouping[i].group_name = strdup(acdgroups[i]);
if (acd_grouping[i].routing_type == NULL)
{
ast_log(LOG_WARNING,"ACD Group \"%s\": No routing type defined. Should be \"nextavail\" or \"mostidle\"\n",acd_grouping[i].group_name);
return -1;
}
if (strcasecmp(acd_grouping[i].routing_type,"nextavail") && strcasecmp(acd_grouping[i].routing_type,"mostidle"))
{
ast_log(LOG_WARNING,"ACD Group \"%s\": Routing type \"%s\". Should be \"nextavail\" or \"mostidle\"\n",acd_grouping[i].group_name,acd_grouping[i].routing_type);
return -1;
}
v = ast_variable_browse(cfg,acdgroups[i]);
while (v) {
if (!strcasecmp(v->name,"agent"))
{
acd_grouping[i].agent[j].agent_device = strdup(v->value);
ast_log(LOG_DEBUG,"Registered agent %s as part of ACD Group %s using Routing Type %s\n",acd_grouping[i].agent[j].agent_device, acd_grouping[i].group_name,acd_grouping[i].routing_type);
j++;
}
v=v->next;
}
acd_grouping[i].defined_agents = j;
}
return ast_register_application(app, acd_exec, synopsis, descrip);
}
static int acd_connect_to_acd_host(struct ast_config *cfg)
{
return 0;
}
int unload_module(void)
{
int res;
res = ast_unregister_application(app);
return res;
}
int usecount(void)
{
return 0;
}
char *key()
{
return ASTERISK_GPL_KEY;
}
char *description(void)
{
return tdesc;
}