#define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000
#define DEFAULT_FEATURE_DIGIT_TIMEOUT 500
#define DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER 15000
+#define DEFAULT_ATXFER_DROP_CALL 0
+#define DEFAULT_ATXFER_LOOP_DELAY 10000
+#define DEFAULT_ATXFER_CALLBACK_RETRIES 2
#define AST_MAX_WATCHERS 256
AST_FEATURE_FLAG_BYBOTH = (3 << 3),
};
+struct feature_group_exten {
+ AST_LIST_ENTRY(feature_group_exten) entry;
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(exten);
+ );
+ struct ast_call_feature *feature;
+};
+
+struct feature_group {
+ AST_LIST_ENTRY(feature_group) entry;
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(gname);
+ );
+ AST_LIST_HEAD_NOLOCK(, feature_group_exten) features;
+};
+
+static AST_RWLIST_HEAD_STATIC(feature_groups, feature_group);
+
static char *parkedcall = "ParkedCall";
static int parkaddhints = 0; /*!< Add parking hints automatically */
+static int parkedcalltransfers = 0; /*!< Enable DTMF based transfers on bridge when picking up parked calls */
+static int parkedcallreparking = 0; /*!< Enable DTMF based parking on bridge when picking up parked calls */
static int parkingtime = DEFAULT_PARK_TIME; /*!< No more than 45 seconds parked before you do something with them */
static char parking_con[AST_MAX_EXTENSION]; /*!< Context for which parking is made accessible */
static char parking_con_dial[AST_MAX_EXTENSION]; /*!< Context for dialback for parking (KLUDGE) */
static int comebacktoorigin = 1;
static int atxfernoanswertimeout;
+static unsigned int atxferdropcall;
+static unsigned int atxferloopdelay;
+static unsigned int atxfercallbackretries;
static char *registrar = "res_features"; /*!< Registrar for operations */
struct ast_bridge_config bconfig;
struct ast_channel *chan;
struct ast_channel *peer;
+ unsigned int return_to_pbx:1;
};
+
+
/*! \brief store context, priority and extension */
static void set_c_e_p(struct ast_channel *chan, const char *context, const char *ext, int pri)
{
goto_on_transfer = ast_strdupa(val);
- if (!(xferchan = ast_channel_alloc(0)))
+ if (!(xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, chan->name)))
return;
for (x = goto_on_transfer; x && *x; x++) {
if (*x == '^')
*x = '|';
}
- ast_string_field_set(xferchan, name, chan->name);
/* Make formats okay */
xferchan->readformat = chan->readformat;
xferchan->writeformat = chan->writeformat;
}
}
-static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name);
+static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, struct ast_channel *transferee, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, int igncallerstate);
static void *ast_bridge_call_thread(void *data)
{
struct ast_bridge_thread_obj *tobj = data;
+ int res;
- tobj->chan->appl = "Transferred Call";
+ tobj->chan->appl = !tobj->return_to_pbx ? "Transferred Call" : "ManagerBridge";
tobj->chan->data = tobj->peer->name;
- tobj->peer->appl = "Transferred Call";
+ tobj->peer->appl = !tobj->return_to_pbx ? "Transferred Call" : "ManagerBridge";
tobj->peer->data = tobj->chan->name;
+
if (tobj->chan->cdr) {
ast_cdr_reset(tobj->chan->cdr, NULL);
ast_cdr_setdestchan(tobj->chan->cdr, tobj->peer->name);
}
ast_bridge_call(tobj->peer, tobj->chan, &tobj->bconfig);
- ast_hangup(tobj->chan);
- ast_hangup(tobj->peer);
- bzero(tobj, sizeof(*tobj)); /*! \todo XXX for safety */
+
+ if (tobj->return_to_pbx) {
+ if (!ast_check_hangup(tobj->peer)) {
+ ast_log(LOG_VERBOSE, "putting peer %s into PBX again\n", tobj->peer->name);
+ res = ast_pbx_start(tobj->peer);
+ if (res != AST_PBX_SUCCESS)
+ ast_log(LOG_WARNING, "FAILED continuing PBX on peer %s\n", tobj->peer->name);
+ } else
+ ast_hangup(tobj->peer);
+ if (!ast_check_hangup(tobj->chan)) {
+ ast_log(LOG_VERBOSE, "putting chan %s into PBX again\n", tobj->chan->name);
+ res = ast_pbx_start(tobj->chan);
+ if (res != AST_PBX_SUCCESS)
+ ast_log(LOG_WARNING, "FAILED continuing PBX on chan %s\n", tobj->chan->name);
+ } else
+ ast_hangup(tobj->chan);
+ } else {
+ ast_hangup(tobj->chan);
+ ast_hangup(tobj->peer);
+ }
+
free(tobj);
+
return NULL;
}
}
/*! \brief metermaids callback from devicestate.c */
-static int metermaidstate(const char *data)
+static enum ast_device_state metermaidstate(const char *data)
{
- int res = AST_DEVICE_INVALID;
+ enum ast_device_state res = AST_DEVICE_INVALID;
char *context = ast_strdupa(data);
char *exten;
res = ast_exists_extension(NULL, context, exten, 1, NULL);
- if (!res)
+ if (res == AST_DEVICE_UNKNOWN)
return AST_DEVICE_NOT_INUSE;
else
return AST_DEVICE_INUSE;
parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN");
if (!ast_strlen_zero(parkingexten)) {
if (ast_exists_extension(NULL, parking_con, parkingexten, 1, NULL)) {
+ ast_mutex_unlock(&parking_lock);
+ free(pu);
ast_log(LOG_WARNING, "Requested parking extension already exists: %s@%s\n", parkingexten, parking_con);
return 0; /* Continue execution if possible */
}
ast_copy_string(pu->parkingexten, parkingexten, sizeof(pu->parkingexten));
+ x = atoi(parkingexten);
} else {
/* Select parking space within range */
parking_range = parking_stop - parking_start+1;
struct ast_frame *f;
/* Make a new, fake channel that we'll use to masquerade in the real one */
- if (!(chan = ast_channel_alloc(0))) {
+ if (!(chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, rchan->accountcode, rchan->exten, rchan->context, rchan->amaflags, "Parked/%s",rchan->name))) {
ast_log(LOG_WARNING, "Unable to create parked channel\n");
return -1;
}
- /* Let us keep track of the channel name */
- ast_string_field_build(chan, name, "Parked/%s",rchan->name);
/* Make formats okay */
chan->readformat = rchan->readformat;
if (!ast_strlen_zero(courtesytone)) {
if (ast_autoservice_start(callee_chan))
return -1;
- if (ast_stream_and_wait(caller_chan, courtesytone, caller_chan->language, "")) {
+ if (ast_stream_and_wait(caller_chan, courtesytone, "")) {
ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
ast_autoservice_stop(callee_chan);
return -1;
snprintf(args, len, "%s|%s|m", S_OR(touch_format, "wav"), touch_filename);
}
- for( x = 0; x < strlen(args); x++) {
+ for(x = 0; x < strlen(args); x++) {
if (args[x] == '/')
args[x] = '-';
}
memset(xferto, 0, sizeof(xferto));
/* Transfer */
- res = ast_stream_and_wait(transferer, "pbx-transfer", transferer->language, AST_DIGIT_ANY);
+ res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY);
if (res < 0) {
finishup(transferee);
return -1; /* error ? */
}
/*! \todo XXX Maybe we should have another message here instead of invalid extension XXX */
} else if (ast_exists_extension(transferee, transferer_real_context, xferto, 1, transferer->cid.cid_num)) {
- pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", chan->name);
+ pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", transferee->name);
pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", peer->name);
res=finishup(transferee);
+ if (!transferer->cdr) {
+ transferer->cdr=ast_cdr_alloc();
+ if (transferer) {
+ ast_cdr_init(transferer->cdr, transferer); /* initilize our channel's cdr */
+ ast_cdr_start(transferer->cdr);
+ }
+ }
+ if (transferer->cdr) {
+ ast_cdr_setdestchan(transferer->cdr, transferee->name);
+ ast_cdr_setapp(transferer->cdr, "BLINDTRANSFER","");
+ }
if (!transferee->pbx) {
/* Doh! Use our handy async_goto functions */
if (option_verbose > 2)
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Unable to find extension '%s' in context '%s'\n", xferto, transferer_real_context);
}
- if (ast_stream_and_wait(transferer, xferfailsound, transferer->language, AST_DIGIT_ANY) < 0 ) {
+ if (ast_stream_and_wait(transferer, xferfailsound, AST_DIGIT_ANY) < 0) {
finishup(transferee);
return -1;
}
struct ast_channel *transferee;
const char *transferer_real_context;
char xferto[256] = "";
+ char callbackto[256] = "";
int res;
int outstate=0;
struct ast_channel *newchan;
ast_indicate(transferee, AST_CONTROL_HOLD);
/* Transfer */
- res = ast_stream_and_wait(transferer, "pbx-transfer", transferer->language, AST_DIGIT_ANY);
+ res = ast_stream_and_wait(transferer, "pbx-transfer", AST_DIGIT_ANY);
if (res < 0) {
finishup(transferee);
return res;
if (res == 0) {
ast_log(LOG_WARNING, "Did not read data.\n");
finishup(transferee);
- if (ast_stream_and_wait(transferer, "beeperr", transferer->language, ""))
+ if (ast_stream_and_wait(transferer, "beeperr", ""))
return -1;
return FEATURE_RETURN_SUCCESS;
}
if (!ast_exists_extension(transferer, transferer_real_context, xferto, 1, transferer->cid.cid_num)) {
ast_log(LOG_WARNING, "Extension %s does not exist in context %s\n",xferto,transferer_real_context);
finishup(transferee);
- if (ast_stream_and_wait(transferer, "beeperr", transferer->language, ""))
+ if (ast_stream_and_wait(transferer, "beeperr", ""))
return -1;
return FEATURE_RETURN_SUCCESS;
}
l = strlen(xferto);
snprintf(xferto + l, sizeof(xferto) - l, "@%s/n", transferer_real_context); /* append context */
- newchan = ast_feature_request_and_dial(transferer, "Local", ast_best_codec(transferer->nativeformats),
- xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name);
- ast_indicate(transferer, -1);
- if (!newchan) {
- finishup(transferee);
- /* any reason besides user requested cancel and busy triggers the failed sound */
- if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY &&
- ast_stream_and_wait(transferer, xferfailsound, transferer->language, ""))
+ newchan = ast_feature_request_and_dial(transferer, transferee, "Local", ast_best_codec(transferer->nativeformats),
+ xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name, 1);
+
+ if (!ast_check_hangup(transferer)) {
+ /* Transferer is up - old behaviour */
+ ast_indicate(transferer, -1);
+ if (!newchan) {
+ finishup(transferee);
+ /* any reason besides user requested cancel and busy triggers the failed sound */
+ if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY &&
+ ast_stream_and_wait(transferer, xferfailsound, ""))
+ return -1;
+ if (ast_stream_and_wait(transferer, xfersound, ""))
+ ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+ return FEATURE_RETURN_SUCCESS;
+ }
+
+ if (check_compat(transferer, newchan))
return -1;
- return FEATURE_RETURN_SUCCESS;
- }
+ memset(&bconfig,0,sizeof(struct ast_bridge_config));
+ ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
+ ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
+ res = ast_bridge_call(transferer, newchan, &bconfig);
+ if (newchan->_softhangup || !transferer->_softhangup) {
+ ast_hangup(newchan);
+ if (ast_stream_and_wait(transferer, xfersound, ""))
+ ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+ finishup(transferee);
+ transferer->_softhangup = 0;
+ return FEATURE_RETURN_SUCCESS;
+ }
+ if (check_compat(transferee, newchan))
+ return -1;
+ ast_indicate(transferee, AST_CONTROL_UNHOLD);
+
+ if ((ast_autoservice_stop(transferee) < 0)
+ || (ast_waitfordigit(transferee, 100) < 0)
+ || (ast_waitfordigit(newchan, 100) < 0)
+ || ast_check_hangup(transferee)
+ || ast_check_hangup(newchan)) {
+ ast_hangup(newchan);
+ return -1;
+ }
+ xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
+ if (!xferchan) {
+ ast_hangup(newchan);
+ return -1;
+ }
+ /* Make formats okay */
+ xferchan->readformat = transferee->readformat;
+ xferchan->writeformat = transferee->writeformat;
+ ast_channel_masquerade(xferchan, transferee);
+ ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
+ xferchan->_state = AST_STATE_UP;
+ ast_clear_flag(xferchan, AST_FLAGS_ALL);
+ xferchan->_softhangup = 0;
+ if ((f = ast_read(xferchan)))
+ ast_frfree(f);
+ newchan->_state = AST_STATE_UP;
+ ast_clear_flag(newchan, AST_FLAGS_ALL);
+ newchan->_softhangup = 0;
+ if (!(tobj = ast_calloc(1, sizeof(*tobj)))) {
+ ast_hangup(xferchan);
+ ast_hangup(newchan);
+ return -1;
+ }
+ tobj->chan = xferchan;
+ tobj->peer = newchan;
+ tobj->bconfig = *config;
- if (check_compat(transferer, newchan))
- return -1;
- memset(&bconfig,0,sizeof(struct ast_bridge_config));
- ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
- ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
- res = ast_bridge_call(transferer, newchan, &bconfig);
- if (newchan->_softhangup || newchan->_state != AST_STATE_UP || !transferer->_softhangup) {
- ast_hangup(newchan);
- if (ast_stream_and_wait(transferer, xfersound, transferer->language, ""))
+ if (ast_stream_and_wait(newchan, xfersound, ""))
ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
- finishup(transferee);
- transferer->_softhangup = 0;
- return FEATURE_RETURN_SUCCESS;
- }
-
- if (check_compat(transferee, newchan))
- return -1;
+ ast_bridge_call_thread_launch(tobj);
+ return -1; /* XXX meaning the channel is bridged ? */
+ } else if (!ast_check_hangup(transferee)) {
+ /* act as blind transfer */
+ if (ast_autoservice_stop(transferee) < 0) {
+ ast_hangup(newchan);
+ return -1;
+ }
- ast_indicate(transferee, AST_CONTROL_UNHOLD);
-
- if ((ast_autoservice_stop(transferee) < 0)
- || (ast_waitfordigit(transferee, 100) < 0)
- || (ast_waitfordigit(newchan, 100) < 0)
- || ast_check_hangup(transferee)
- || ast_check_hangup(newchan)) {
- ast_hangup(newchan);
- return -1;
- }
+ if (!newchan) {
+ unsigned int tries = 0;
- xferchan = ast_channel_alloc(0);
- if (!xferchan) {
- ast_hangup(newchan);
- return -1;
- }
- ast_string_field_build(xferchan, name, "Transfered/%s", transferee->name);
- /* Make formats okay */
- xferchan->readformat = transferee->readformat;
- xferchan->writeformat = transferee->writeformat;
- ast_channel_masquerade(xferchan, transferee);
- ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
- xferchan->_state = AST_STATE_UP;
- ast_clear_flag(xferchan, AST_FLAGS_ALL);
- xferchan->_softhangup = 0;
+ /* newchan wasn't created - we should callback to transferer */
+ if (!ast_exists_extension(transferer, transferer_real_context, transferer->cid.cid_num, 1, transferee->cid.cid_num)) {
+ ast_log(LOG_WARNING, "Extension %s does not exist in context %s - callback failed\n",transferer->cid.cid_num,transferer_real_context);
+ if (ast_stream_and_wait(transferee, "beeperr", ""))
+ return -1;
+ return FEATURE_RETURN_SUCCESS;
+ }
+ snprintf(callbackto, sizeof(callbackto), "%s@%s/n", transferer->cid.cid_num, transferer_real_context); /* append context */
+
+ newchan = ast_feature_request_and_dial(transferee, NULL, "Local", ast_best_codec(transferee->nativeformats),
+ callbackto, atxfernoanswertimeout, &outstate, transferee->cid.cid_num, transferee->cid.cid_name, 0);
+ while (!newchan && !atxferdropcall && tries < atxfercallbackretries) {
+ /* Trying to transfer again */
+ ast_autoservice_start(transferee);
+ ast_indicate(transferee, AST_CONTROL_HOLD);
+
+ newchan = ast_feature_request_and_dial(transferer, transferee, "Local", ast_best_codec(transferer->nativeformats),
+ xferto, atxfernoanswertimeout, &outstate, transferer->cid.cid_num, transferer->cid.cid_name, 1);
+ if (ast_autoservice_stop(transferee) < 0) {
+ ast_hangup(newchan);
+ return -1;
+ }
+ if (!newchan) {
+ /* Transfer failed, sleeping */
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Sleeping for %d ms before callback.\n", atxferloopdelay);
+ ast_safe_sleep(transferee, atxferloopdelay);
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Trying to callback...\n");
+ newchan = ast_feature_request_and_dial(transferee, NULL, "Local", ast_best_codec(transferee->nativeformats),
+ callbackto, atxfernoanswertimeout, &outstate, transferee->cid.cid_num, transferee->cid.cid_name, 0);
+ }
+ tries++;
+ }
+ }
+ if (!newchan)
+ return -1;
- if ((f = ast_read(xferchan)))
- ast_frfree(f);
+ /* newchan is up, we should prepare transferee and bridge them */
+ if (check_compat(transferee, newchan))
+ return -1;
+ ast_indicate(transferee, AST_CONTROL_UNHOLD);
+
+ if ((ast_waitfordigit(transferee, 100) < 0)
+ || (ast_waitfordigit(newchan, 100) < 0)
+ || ast_check_hangup(transferee)
+ || ast_check_hangup(newchan)) {
+ ast_hangup(newchan);
+ return -1;
+ }
- newchan->_state = AST_STATE_UP;
- ast_clear_flag(newchan, AST_FLAGS_ALL);
- newchan->_softhangup = 0;
+ xferchan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "Transfered/%s", transferee->name);
+ if (!xferchan) {
+ ast_hangup(newchan);
+ return -1;
+ }
+ /* Make formats okay */
+ xferchan->readformat = transferee->readformat;
+ xferchan->writeformat = transferee->writeformat;
+ ast_channel_masquerade(xferchan, transferee);
+ ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
+ xferchan->_state = AST_STATE_UP;
+ ast_clear_flag(xferchan, AST_FLAGS_ALL);
+ xferchan->_softhangup = 0;
+ if ((f = ast_read(xferchan)))
+ ast_frfree(f);
+ newchan->_state = AST_STATE_UP;
+ ast_clear_flag(newchan, AST_FLAGS_ALL);
+ newchan->_softhangup = 0;
+ if (!(tobj = ast_calloc(1, sizeof(*tobj)))) {
+ ast_hangup(xferchan);
+ ast_hangup(newchan);
+ return -1;
+ }
+ tobj->chan = xferchan;
+ tobj->peer = newchan;
+ tobj->bconfig = *config;
- tobj = ast_calloc(1, sizeof(struct ast_bridge_thread_obj));
- if (!tobj) {
- ast_hangup(xferchan);
- ast_hangup(newchan);
+ if (ast_stream_and_wait(newchan, xfersound, ""))
+ ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
+ ast_bridge_call_thread_launch(tobj);
+ return -1; /* XXX meaning the channel is bridged ? */
+ } else {
+ /* Transferee hung up */
+ finishup(transferee);
return -1;
}
- tobj->chan = xferchan;
- tobj->peer = newchan;
- tobj->bconfig = *config;
-
- if (ast_stream_and_wait(newchan, xfersound, newchan->language, ""))
- ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
- ast_bridge_call_thread_launch(tobj);
- return -1; /* XXX meaning the channel is bridged ? */
}
-
/* add atxfer and automon as undefined so you can only use em if you configure them */
-#define FEATURES_COUNT (sizeof(builtin_features) / sizeof(builtin_features[0]))
+#define FEATURES_COUNT ARRAY_LEN(builtin_features)
+
+AST_RWLOCK_DEFINE_STATIC(features_lock);
-struct ast_call_feature builtin_features[] =
- {
+static struct ast_call_feature builtin_features[] =
+{
{ AST_FEATURE_REDIRECT, "Blind Transfer", "blindxfer", "#", "#", builtin_blindtransfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
{ AST_FEATURE_REDIRECT, "Attended Transfer", "atxfer", "", "", builtin_atxfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
{ AST_FEATURE_AUTOMON, "One Touch Monitor", "automon", "", "", builtin_automonitor, AST_FEATURE_FLAG_NEEDSDTMF, "" },
ast_verbose(VERBOSE_PREFIX_2 "Registered Feature '%s'\n",feature->sname);
}
+/*! \brief This function must be called while feature_groups is locked... */
+static struct feature_group* register_group(const char *fgname)
+{
+ struct feature_group *fg;
+
+ if (!fgname) {
+ ast_log(LOG_NOTICE, "You didn't pass a new group name!\n");
+ return NULL;
+ }
+
+ if (!(fg = ast_calloc(1, sizeof(*fg))))
+ return NULL;
+
+ if (ast_string_field_init(fg, 128)) {
+ free(fg);
+ return NULL;
+ }
+
+ ast_string_field_set(fg, gname, fgname);
+
+ AST_LIST_INSERT_HEAD(&feature_groups, fg, entry);
+
+ if (option_verbose >= 2)
+ ast_verbose(VERBOSE_PREFIX_2 "Registered group '%s'\n", fg->gname);
+
+ return fg;
+}
+
+/*! \brief This function must be called while feature_groups is locked... */
+
+static void register_group_feature(struct feature_group *fg, const char *exten, struct ast_call_feature *feature)
+{
+ struct feature_group_exten *fge;
+
+ if (!(fge = ast_calloc(1, sizeof(*fge))))
+ return;
+
+ if (ast_string_field_init(fge, 128)) {
+ free(fge);
+ return;
+ }
+
+ if (!fg) {
+ ast_log(LOG_NOTICE, "You didn't pass a group!\n");
+ return;
+ }
+
+ if (!feature) {
+ ast_log(LOG_NOTICE, "You didn't pass a feature!\n");
+ return;
+ }
+
+ ast_string_field_set(fge, exten, (ast_strlen_zero(exten) ? feature->exten : exten));
+
+ fge->feature = feature;
+
+ AST_LIST_INSERT_HEAD(&fg->features, fge, entry);
+
+ if (option_verbose >= 2)
+ ast_verbose(VERBOSE_PREFIX_2 "Registered feature '%s' for group '%s' at exten '%s'\n",
+ feature->sname, fg->gname, exten);
+}
+
/*! \brief unregister feature from feature_list */
void ast_unregister_feature(struct ast_call_feature *feature)
{
AST_LIST_UNLOCK(&feature_list);
}
-/*! \brief find a feature by name */
-static struct ast_call_feature *find_feature(char *name)
+/*! \brief find a call feature by name */
+static struct ast_call_feature *find_dynamic_feature(const char *name)
{
struct ast_call_feature *tmp;
- AST_LIST_LOCK(&feature_list);
AST_LIST_TRAVERSE(&feature_list, tmp, feature_entry) {
if (!strcasecmp(tmp->sname, name))
break;
}
- AST_LIST_UNLOCK(&feature_list);
return tmp;
}
+/*! \brief Remove all groups in the list */
+static void ast_unregister_groups(void)
+{
+ struct feature_group *fg;
+ struct feature_group_exten *fge;
+
+ AST_RWLIST_WRLOCK(&feature_groups);
+ while ((fg = AST_LIST_REMOVE_HEAD(&feature_groups, entry))) {
+ while ((fge = AST_LIST_REMOVE_HEAD(&fg->features, entry))) {
+ ast_string_field_free_all(fge);
+ free(fge);
+ }
+
+ ast_string_field_free_all(fg);
+ free(fg);
+ }
+ AST_RWLIST_UNLOCK(&feature_groups);
+}
+
+/*! \brief Find a group by name */
+static struct feature_group *find_group(const char *name) {
+ struct feature_group *fg = NULL;
+
+ AST_LIST_TRAVERSE(&feature_groups, fg, entry) {
+ if (!strcasecmp(fg->gname, name))
+ break;
+ }
+
+ return fg;
+}
+
+static struct feature_group_exten *find_group_exten(struct feature_group *fg, const char *code) {
+ struct feature_group_exten *fge = NULL;
+
+ AST_LIST_TRAVERSE(&fg->features, fge, entry) {
+ if(!strcasecmp(fge->exten, code))
+ break;
+ }
+
+ return fge;
+}
+
+void ast_rdlock_call_features(void)
+{
+ ast_rwlock_rdlock(&features_lock);
+}
+
+void ast_unlock_call_features(void)
+{
+ ast_rwlock_unlock(&features_lock);
+}
+
+/*! \brief find a call feature by name */
+struct ast_call_feature *ast_find_call_feature(const char *name)
+{
+ int x;
+ for (x = 0; x < FEATURES_COUNT; x++) {
+ if (!strcasecmp(name, builtin_features[x].sname))
+ return &builtin_features[x];
+ }
+ return NULL;
+}
+
/*! \brief exec an app by feature */
static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
{
static void unmap_features(void)
{
int x;
+
+ ast_rwlock_wrlock(&features_lock);
for (x = 0; x < FEATURES_COUNT; x++)
strcpy(builtin_features[x].exten, builtin_features[x].default_exten);
+ ast_rwlock_unlock(&features_lock);
}
static int remap_feature(const char *name, const char *value)
{
- int x;
- int res = -1;
+ int x, res = -1;
+
+ ast_rwlock_wrlock(&features_lock);
for (x = 0; x < FEATURES_COUNT; x++) {
- if (!strcasecmp(name, builtin_features[x].sname)) {
- ast_copy_string(builtin_features[x].exten, value, sizeof(builtin_features[x].exten));
- if (option_verbose > 1)
- ast_verbose(VERBOSE_PREFIX_2 "Remapping feature %s (%s) to sequence '%s'\n", builtin_features[x].fname, builtin_features[x].sname, builtin_features[x].exten);
- res = 0;
- } else if (!strcmp(value, builtin_features[x].exten))
- ast_log(LOG_WARNING, "Sequence '%s' already mapped to function %s (%s) while assigning to %s\n", value, builtin_features[x].fname, builtin_features[x].sname, name);
+ if (strcasecmp(builtin_features[x].sname, name))
+ continue;
+
+ ast_copy_string(builtin_features[x].exten, value, sizeof(builtin_features[x].exten));
+ res = 0;
+ break;
}
+ ast_rwlock_unlock(&features_lock);
+
return res;
}
struct ast_flags features;
int res = FEATURE_RETURN_PASSDIGITS;
struct ast_call_feature *feature;
+ struct feature_group *fg = NULL;
+ struct feature_group_exten *fge;
const char *dynamic_features=pbx_builtin_getvar_helper(chan,"DYNAMIC_FEATURES");
+ char *tmp, *tok;
if (sense == FEATURE_SENSE_CHAN)
- ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);
+ ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);
else
- ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL);
+ ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL);
if (option_debug > 2)
ast_log(LOG_DEBUG, "Feature interpret: chan=%s, peer=%s, sense=%d, features=%d\n", chan->name, peer->name, sense, features.flags);
- for (x=0; x < FEATURES_COUNT; x++) {
+ ast_rwlock_rdlock(&features_lock);
+ for (x = 0; x < FEATURES_COUNT; x++) {
if ((ast_test_flag(&features, builtin_features[x].feature_mask)) &&
!ast_strlen_zero(builtin_features[x].exten)) {
/* Feature is up for consideration */
}
}
}
+ ast_rwlock_unlock(&features_lock);
+ if (ast_strlen_zero(dynamic_features))
+ return res;
- if (!ast_strlen_zero(dynamic_features)) {
- char *tmp = ast_strdupa(dynamic_features);
- char *tok;
+ tmp = ast_strdupa(dynamic_features);
- while ((tok = strsep(&tmp, "#")) != NULL) {
- feature = find_feature(tok);
-
- if (feature) {
- /* Feature is up for consideration */
- if (!strcmp(feature->exten, code)) {
- if (option_verbose > 2)
- ast_verbose(VERBOSE_PREFIX_3 " Feature Found: %s exten: %s\n",feature->sname, tok);
- res = feature->operation(chan, peer, config, code, sense);
- break;
- } else if (!strncmp(feature->exten, code, strlen(code))) {
- res = FEATURE_RETURN_STOREDIGITS;
- }
- }
+ while ((tok = strsep(&tmp, "#"))) {
+ AST_RWLIST_RDLOCK(&feature_groups);
+
+ fg = find_group(tok);
+
+ if (fg && (fge = find_group_exten(fg, code))) {
+ res = fge->feature->operation(chan, peer, config, code, sense);
+ AST_RWLIST_UNLOCK(&feature_groups);
+ continue;
}
+
+ AST_RWLIST_UNLOCK(&feature_groups);
+ AST_LIST_LOCK(&feature_list);
+
+ if(!(feature = find_dynamic_feature(tok))) {
+ AST_LIST_UNLOCK(&feature_list);
+ continue;
+ }
+
+ /* Feature is up for consideration */
+ if (!strcmp(feature->exten, code)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 " Feature Found: %s exten: %s\n",feature->sname, tok);
+ res = feature->operation(chan, peer, config, code, sense);
+ AST_LIST_UNLOCK(&feature_list);
+ break;
+ } else if (!strncmp(feature->exten, code, strlen(code)))
+ res = FEATURE_RETURN_STOREDIGITS;
+
+ AST_LIST_UNLOCK(&feature_list);
}
return res;
{
int x;
- ast_clear_flag(config, AST_FLAGS_ALL);
+ ast_clear_flag(config, AST_FLAGS_ALL);
+
+ ast_rwlock_rdlock(&features_lock);
for (x = 0; x < FEATURES_COUNT; x++) {
- if (ast_test_flag(builtin_features + x, AST_FEATURE_FLAG_NEEDSDTMF)) {
- if (ast_test_flag(&(config->features_caller), builtin_features[x].feature_mask))
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
+ if (!ast_test_flag(builtin_features + x, AST_FEATURE_FLAG_NEEDSDTMF))
+ continue;
- if (ast_test_flag(&(config->features_callee), builtin_features[x].feature_mask))
- ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
- }
+ if (ast_test_flag(&(config->features_caller), builtin_features[x].feature_mask))
+ ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
+
+ if (ast_test_flag(&(config->features_callee), builtin_features[x].feature_mask))
+ ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
}
+ ast_rwlock_unlock(&features_lock);
if (chan && peer && !(ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_0) && ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_1))) {
const char *dynamic_features = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
/* while we have a feature */
while ((tok = strsep(&tmp, "#"))) {
- if ((feature = find_feature(tok)) && ast_test_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF)) {
+ AST_LIST_LOCK(&feature_list);
+ if ((feature = find_dynamic_feature(tok)) && ast_test_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF)) {
if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLER))
ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLEE))
ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
}
+ AST_LIST_UNLOCK(&feature_list);
}
}
}
}
/*! \todo XXX Check - this is very similar to the code in channel.c */
-static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name)
+static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, struct ast_channel *transferee, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name, int igncallerstate)
{
int state = 0;
int cause = 0;
ast_set_callerid(chan, cid_num, cid_name, cid_num);
ast_channel_inherit_variables(caller, chan);
pbx_builtin_setvar_helper(chan, "TRANSFERERNAME", caller->name);
+ if (!chan->cdr) {
+ chan->cdr=ast_cdr_alloc();
+ if (chan->cdr) {
+ ast_cdr_init(chan->cdr, chan); /* initilize our channel's cdr */
+ ast_cdr_start(chan->cdr);
+ }
+ }
+
if (!ast_call(chan, data, timeout)) {
struct timeval started;
int x, len = 0;
ast_indicate(caller, AST_CONTROL_RINGING);
/* support dialing of the featuremap disconnect code while performing an attended tranfer */
- for (x=0; x < FEATURES_COUNT; x++) {
+ ast_rwlock_rdlock(&features_lock);
+ for (x = 0; x < FEATURES_COUNT; x++) {
if (strcasecmp(builtin_features[x].sname, "disconnect"))
continue;
memset(dialed_code, 0, len);
break;
}
+ ast_rwlock_unlock(&features_lock);
x = 0;
started = ast_tvnow();
to = timeout;
- while (!ast_check_hangup(caller) && timeout && (chan->_state != AST_STATE_UP)) {
+ while (!((transferee && transferee->_softhangup) && (!igncallerstate && ast_check_hangup(caller))) && timeout && (chan->_state != AST_STATE_UP)) {
struct ast_frame *f = NULL;
monitor_chans[0] = caller;
if (f->subclass == AST_CONTROL_RINGING) {
state = f->subclass;
if (option_verbose > 2)
- ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", chan->name);
+ ast_verbose(VERBOSE_PREFIX_3 "%s is ringing\n", chan->name);
ast_indicate(caller, AST_CONTROL_RINGING);
} else if ((f->subclass == AST_CONTROL_BUSY) || (f->subclass == AST_CONTROL_CONGESTION)) {
state = f->subclass;
if (option_verbose > 2)
- ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", chan->name);
+ ast_verbose(VERBOSE_PREFIX_3 "%s is busy\n", chan->name);
ast_indicate(caller, AST_CONTROL_BUSY);
ast_frfree(f);
f = NULL;
} else if (caller && (active_channel == caller)) {
f = ast_read(caller);
if (f == NULL) { /*doh! where'd he go?*/
- if (caller->_softhangup && !chan->_softhangup) {
- /* make this a blind transfer */
- ready = 1;
+ if (!igncallerstate) {
+ if (caller->_softhangup && !chan->_softhangup) {
+ /* make this a blind transfer */
+ ready = 1;
+ break;
+ }
+ state = AST_CONTROL_HANGUP;
+ res = 0;
break;
}
- state = AST_CONTROL_HANGUP;
- res = 0;
- break;
- }
+ } else {
- if (f->frametype == AST_FRAME_DTMF) {
- dialed_code[x++] = f->subclass;
- dialed_code[x] = '\0';
- if (strlen(dialed_code) == len) {
- x = 0;
- } else if (x && strncmp(dialed_code, disconnect_code, x)) {
- x = 0;
+ if (f->frametype == AST_FRAME_DTMF) {
+ dialed_code[x++] = f->subclass;
dialed_code[x] = '\0';
- }
- if (*dialed_code && !strcmp(dialed_code, disconnect_code)) {
- /* Caller Canceled the call */
- state = AST_CONTROL_UNHOLD;
- ast_frfree(f);
- f = NULL;
- break;
+ if (strlen(dialed_code) == len) {
+ x = 0;
+ } else if (x && strncmp(dialed_code, disconnect_code, x)) {
+ x = 0;
+ dialed_code[x] = '\0';
+ }
+ if (*dialed_code && !strcmp(dialed_code, disconnect_code)) {
+ /* Caller Canceled the call */
+ state = AST_CONTROL_UNHOLD;
+ ast_frfree(f);
+ f = NULL;
+ break;
+ }
}
}
}
int hadfeatures=0;
struct ast_option_header *aoh;
struct ast_bridge_config backup_config;
+ struct ast_cdr *bridge_cdr;
memset(&backup_config, 0, sizeof(backup_config));
free(peer->cdr);
peer->cdr = NULL;
}
+
for (;;) {
struct ast_channel *other; /* used later */
if (!f || (f->frametype == AST_FRAME_CONTROL &&
(f->subclass == AST_CONTROL_HANGUP || f->subclass == AST_CONTROL_BUSY ||
- f->subclass == AST_CONTROL_CONGESTION ) ) ) {
+ f->subclass == AST_CONTROL_CONGESTION))) {
res = -1;
break;
}
}
if (f)
ast_frfree(f);
+
+ }
+ /* arrange the cdrs */
+ bridge_cdr = ast_cdr_alloc();
+ if (bridge_cdr) {
+ if (chan->cdr && peer->cdr) { /* both of them? merge */
+ ast_cdr_init(bridge_cdr,chan); /* seems more logicaller to use the destination as a base, but, really, it's random */
+ ast_cdr_start(bridge_cdr); /* now is the time to start */
+
+ /* absorb the channel cdr */
+ ast_cdr_merge(bridge_cdr, chan->cdr);
+ ast_cdr_discard(chan->cdr); /* no posting these guys */
+
+ /* absorb the peer cdr */
+ ast_cdr_merge(bridge_cdr, peer->cdr);
+ ast_cdr_discard(peer->cdr); /* no posting these guys */
+ peer->cdr = NULL;
+ chan->cdr = bridge_cdr; /* make this available to the rest of the world via the chan while the call is in progress */
+ } else if (chan->cdr) {
+ /* take the cdr from the channel - literally */
+ ast_cdr_init(bridge_cdr,chan);
+ /* absorb this data */
+ ast_cdr_merge(bridge_cdr, chan->cdr);
+ ast_cdr_discard(chan->cdr); /* no posting these guys */
+ chan->cdr = bridge_cdr; /* make this available to the rest of the world via the chan while the call is in progress */
+ } else if (peer->cdr) {
+ /* take the cdr from the peer - literally */
+ ast_cdr_init(bridge_cdr,peer);
+ /* absorb this data */
+ ast_cdr_merge(bridge_cdr, peer->cdr);
+ ast_cdr_discard(peer->cdr); /* no posting these guys */
+ peer->cdr = NULL;
+ peer->cdr = bridge_cdr; /* make this available to the rest of the world via the chan while the call is in progress */
+ } else {
+ /* make up a new cdr */
+ ast_cdr_init(bridge_cdr,chan); /* eh, just pick one of them */
+ chan->cdr = bridge_cdr; /* */
+ }
+ if (ast_strlen_zero(bridge_cdr->dstchannel)) {
+ if (strcmp(bridge_cdr->channel, peer->name) != 0)
+ ast_cdr_setdestchan(bridge_cdr, peer->name);
+ else
+ ast_cdr_setdestchan(bridge_cdr, chan->name);
+ }
}
return res;
}
-static void post_manager_event(const char *s, char *parkingexten, struct ast_channel *chan)
+/*! \brief Output parking event to manager */
+static void post_manager_event(const char *s, struct parkeduser *pu)
{
manager_event(EVENT_FLAG_CALL, s,
"Exten: %s\r\n"
"Channel: %s\r\n"
"CallerIDNum: %s\r\n"
"CallerIDName: %s\r\n\r\n",
- parkingexten,
- chan->name,
- S_OR(chan->cid.cid_num, "<unknown>"),
- S_OR(chan->cid.cid_name, "<unknown>")
+ pu->parkingexten,
+ pu->chan->name,
+ S_OR(pu->chan->cid.cid_num, "<unknown>"),
+ S_OR(pu->chan->cid.cid_name, "<unknown>")
);
}
/*! \brief Take care of parked calls and unpark them if needed */
static void *do_parking_thread(void *ignore)
{
+ char parkingslot[AST_MAX_EXTENSION];
fd_set rfds, efds; /* results from previous select, to be preserved across loops. */
+
FD_ZERO(&rfds);
FD_ZERO(&efds);
- char parkingslot[AST_MAX_EXTENSION];
for (;;) {
struct parkeduser *pu, *pl, *pt = NULL;
ast_add_extension2(con, 1, peername, 1, NULL, NULL, "Dial", strdup(returnexten), ast_free, registrar);
}
if (comebacktoorigin) {
- set_c_e_p(chan, parking_con_dial, peername, 1);
+ set_c_e_p(chan, parking_con_dial, peername, 1);
} else {
- ast_log(LOG_WARNING, "now going to parkedcallstimeout,s,1 | ps is %d\n",pu->parkingnum);
- snprintf(parkingslot, sizeof(parkingslot), "%d", pu->parkingnum);
- pbx_builtin_setvar_helper(pu->chan, "PARKINGSLOT", parkingslot);
- set_c_e_p(chan, "parkedcallstimeout", peername, 1);
- }
+ ast_log(LOG_WARNING, "now going to parkedcallstimeout,s,1 | ps is %d\n",pu->parkingnum);
+ snprintf(parkingslot, sizeof(parkingslot), "%d", pu->parkingnum);
+ pbx_builtin_setvar_helper(pu->chan, "PARKINGSLOT", parkingslot);
+ set_c_e_p(chan, "parkedcallstimeout", peername, 1);
+ }
} else {
/* They've been waiting too long, send them back to where they came. Theoretically they
should have their original extensions and such, but we copy to be on the safe side */
set_c_e_p(chan, pu->context, pu->exten, pu->priority);
}
- post_manager_event("ParkedCallTimeOut", pu->parkingexten, chan);
+ post_manager_event("ParkedCallTimeOut", pu);
if (option_verbose > 1)
ast_verbose(VERBOSE_PREFIX_2 "Timeout for %s parked on %d. Returning to %s,%s,%d\n", chan->name, pu->parkingnum, chan->context, chan->exten, chan->priority);
if (!f || (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP)) {
if (f)
ast_frfree(f);
- post_manager_event("ParkedCallGiveUp", pu->parkingexten, chan);
+ post_manager_event("ParkedCallGiveUp", pu);
/* There's a problem, hang them up*/
if (option_verbose > 1)
if (con) {
if (ast_context_remove_extension2(con, pt->parkingexten, 1, NULL))
ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
- else
- notify_metermaids(pt->parkingexten, parking_con);
+ else
+ notify_metermaids(pt->parkingexten, parking_con);
} else
ast_log(LOG_WARNING, "Whoa, no parking context?\n");
free(pt);
if (pu->moh_trys < 3 && !chan->generatordata) {
if (option_debug)
ast_log(LOG_DEBUG, "MOH on parked call stopped by outside source. Restarting.\n");
- ast_indicate_data(pu->chan, AST_CONTROL_HOLD,
+ ast_indicate_data(chan, AST_CONTROL_HOLD,
S_OR(parkmohclass, NULL),
!ast_strlen_zero(parkmohclass) ? strlen(parkmohclass) + 1 : 0);
pu->moh_trys++;
struct ast_channel *peer=NULL;
struct parkeduser *pu, *pl=NULL;
struct ast_context *con;
+
int park;
struct ast_bridge_config config;
int error = 0;
ast_indicate(peer, AST_CONTROL_UNHOLD);
if (parkedplay == 0) {
- error = ast_stream_and_wait(chan, courtesytone, chan->language, "");
+ error = ast_stream_and_wait(chan, courtesytone, "");
} else if (parkedplay == 1) {
- error = ast_stream_and_wait(peer, courtesytone, chan->language, "");
+ error = ast_stream_and_wait(peer, courtesytone, "");
} else if (parkedplay == 2) {
if (!ast_streamfile(chan, courtesytone, chan->language) &&
!ast_streamfile(peer, courtesytone, chan->language)) {
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Channel %s connected to parked call %d\n", chan->name, park);
+ pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", peer->name);
+ ast_cdr_setdestchan(chan->cdr, peer->name);
memset(&config, 0, sizeof(struct ast_bridge_config));
- ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
- ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT);
- config.timelimit = 0;
- config.play_warning = 0;
- config.warning_freq = 0;
- config.warning_sound=NULL;
+ if ((parkedcalltransfers == AST_FEATURE_FLAG_BYCALLEE) || (parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH))
+ ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
+ if ((parkedcalltransfers == AST_FEATURE_FLAG_BYCALLER) || (parkedcalltransfers == AST_FEATURE_FLAG_BYBOTH))
+ ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT);
+ if ((parkedcallreparking == AST_FEATURE_FLAG_BYCALLEE) || (parkedcallreparking == AST_FEATURE_FLAG_BYBOTH))
+ ast_set_flag(&(config.features_callee), AST_FEATURE_PARKCALL);
+ if ((parkedcallreparking == AST_FEATURE_FLAG_BYCALLER) || (parkedcallreparking == AST_FEATURE_FLAG_BYBOTH))
+ ast_set_flag(&(config.features_caller), AST_FEATURE_PARKCALL);
res = ast_bridge_call(chan, peer, &config);
+ pbx_builtin_setvar_helper(chan, "PARKEDCHANNEL", peer->name);
+ ast_cdr_setdestchan(chan->cdr, peer->name);
+
/* Simulate the PBX hanging up */
if (res != AST_PBX_NO_HANGUP_PEER)
ast_hangup(peer);
return res;
} else {
/*! \todo XXX Play a message XXX */
- if (ast_stream_and_wait(chan, "pbx-invalidpark", chan->language, ""))
+ if (ast_stream_and_wait(chan, "pbx-invalidpark", ""))
ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark", chan->name);
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "Channel %s tried to talk to nonexistent parked call %d\n", chan->name, park);
static int handle_showfeatures(int fd, int argc, char *argv[])
{
int i;
- int fcount;
struct ast_call_feature *feature;
char format[] = "%-25s %-7s %-7s\n";
ast_cli(fd, format, "Pickup", "*8", ast_pickup_ext()); /* default hardcoded above, so we'll hardcode it here */
- fcount = sizeof(builtin_features) / sizeof(builtin_features[0]);
-
- for (i = 0; i < fcount; i++)
- {
+ ast_rwlock_rdlock(&features_lock);
+ for (i = 0; i < FEATURES_COUNT; i++)
ast_cli(fd, format, builtin_features[i].fname, builtin_features[i].default_exten, builtin_features[i].exten);
- }
+ ast_rwlock_unlock(&features_lock);
+
ast_cli(fd, "\n");
ast_cli(fd, format, "Dynamic Feature", "Default", "Current");
ast_cli(fd, format, "---------------", "-------", "-------");
- if (AST_LIST_EMPTY(&feature_list)) {
+ if (AST_LIST_EMPTY(&feature_list))
ast_cli(fd, "(none)\n");
- }
else {
AST_LIST_LOCK(&feature_list);
- AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) {
+ AST_LIST_TRAVERSE(&feature_list, feature, feature_entry)
ast_cli(fd, format, feature->sname, "no def", feature->exten);
- }
AST_LIST_UNLOCK(&feature_list);
}
ast_cli(fd, "\nCall parking\n");
return RESULT_SUCCESS;
}
+static char mandescr_bridge[] =
+"Description: Bridge together two channels already in the PBX\n"
+"Variables: ( Headers marked with * are required )\n"
+" *Channel1: Channel to Bridge to Channel2\n"
+" *Channel2: Channel to Bridge to Channel1\n"
+" Tone: (Yes|No) Play courtesy tone to Channel 2\n"
+"\n";
+
+static void do_bridge_masquerade(struct ast_channel *chan, struct ast_channel *tmpchan)
+{
+ ast_moh_stop(chan);
+ ast_mutex_lock(&chan->lock);
+ ast_setstate(tmpchan, chan->_state);
+ tmpchan->readformat = chan->readformat;
+ tmpchan->writeformat = chan->writeformat;
+ ast_channel_masquerade(tmpchan, chan);
+ ast_mutex_lock(&tmpchan->lock);
+ ast_do_masquerade(tmpchan);
+ /* when returning from bridge, the channel will continue at the next priority */
+ ast_explicit_goto(tmpchan, chan->context, chan->exten, chan->priority + 1);
+ ast_mutex_unlock(&tmpchan->lock);
+ ast_mutex_unlock(&chan->lock);
+}
+
+static int action_bridge(struct mansession *s, const struct message *m)
+{
+ const char *channela = astman_get_header(m, "Channel1");
+ const char *channelb = astman_get_header(m, "Channel2");
+ const char *playtone = astman_get_header(m, "Tone");
+ struct ast_channel *chana = NULL, *chanb = NULL;
+ struct ast_channel *tmpchana = NULL, *tmpchanb = NULL;
+ struct ast_bridge_thread_obj *tobj = NULL;
+
+ /* make sure valid channels were specified */
+ if (!ast_strlen_zero(channela) && !ast_strlen_zero(channelb)) {
+ chana = ast_get_channel_by_name_prefix_locked(channela, strlen(channela));
+ chanb = ast_get_channel_by_name_prefix_locked(channelb, strlen(channelb));
+ if (chana)
+ ast_mutex_unlock(&chana->lock);
+ if (chanb)
+ ast_mutex_unlock(&chanb->lock);
+
+ /* send errors if any of the channels could not be found/locked */
+ if (!chana) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Channel1 does not exists: %s", channela);
+ astman_send_error(s, m, buf);
+ return 0;
+ }
+ if (!chanb) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "Channel2 does not exists: %s", channelb);
+ astman_send_error(s, m, buf);
+ return 0;
+ }
+ } else {
+ astman_send_error(s, m, "Missing channel parameter in request");
+ return 0;
+ }
+
+ /* Answer the channels if needed */
+ if (chana->_state != AST_STATE_UP)
+ ast_answer(chana);
+ if (chanb->_state != AST_STATE_UP)
+ ast_answer(chanb);
+
+ /* create the placeholder channels and grab the other channels */
+ if (!(tmpchana = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL,
+ NULL, NULL, 0, "Bridge/%s", chana->name))) {
+ astman_send_error(s, m, "Unable to create temporary channel!");
+ return 1;
+ }
+
+ if (!(tmpchanb = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL,
+ NULL, NULL, 0, "Bridge/%s", chanb->name))) {
+ astman_send_error(s, m, "Unable to create temporary channels!");
+ ast_channel_free(tmpchana);
+ return 1;
+ }
+
+ do_bridge_masquerade(chana, tmpchana);
+ do_bridge_masquerade(chanb, tmpchanb);
+
+ /* make the channels compatible, send error if we fail doing so */
+ if (ast_channel_make_compatible(tmpchana, tmpchanb)) {
+ ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for manager bridge\n", tmpchana->name, tmpchanb->name);
+ astman_send_error(s, m, "Could not make channels compatible for manager bridge");
+ ast_hangup(tmpchana);
+ ast_hangup(tmpchanb);
+ return 1;
+ }
+
+ /* setup the bridge thread object and start the bridge */
+ if (!(tobj = ast_calloc(1, sizeof(*tobj)))) {
+ ast_log(LOG_WARNING, "Unable to spawn a new bridge thread on %s and %s: %s\n", tmpchana->name, tmpchanb->name, strerror(errno));
+ astman_send_error(s, m, "Unable to spawn a new bridge thread");
+ ast_hangup(tmpchana);
+ ast_hangup(tmpchanb);
+ return 1;
+ }
+
+ tobj->chan = tmpchana;
+ tobj->peer = tmpchanb;
+ tobj->return_to_pbx = 1;
+
+ if (ast_true(playtone)) {
+ if (!ast_strlen_zero(xfersound) && !ast_streamfile(tmpchanb, xfersound, tmpchanb->language)) {
+ if (ast_waitstream(tmpchanb, "") < 0)
+ ast_log(LOG_WARNING, "Failed to play a courtesy tone on chan %s\n", tmpchanb->name);
+ }
+ }
+
+ ast_bridge_call_thread_launch(tobj);
+
+ astman_send_ack(s, m, "Launched bridge thread with success");
+
+ return 0;
+}
+
static char showfeatures_help[] =
"Usage: feature list\n"
" Lists currently configured features.\n";
numparked++;
}
ast_mutex_unlock(&parking_lock);
- ast_cli(fd, "%d parked call%s.\n", numparked, (numparked != 1) ? "s" : "");
+ ast_cli(fd, "%d parked call%s.\n", numparked, ESS(numparked));
return RESULT_SUCCESS;
" Lists currently parked calls.\n";
static struct ast_cli_entry cli_features[] = {
- { { "feature", "list", NULL },
+ { { "feature", "show", NULL },
handle_showfeatures, "Lists configured features",
showfeatures_help },
};
/*! \brief Dump lot status */
-static int manager_parking_status( struct mansession *s, struct message *m )
+static int manager_parking_status(struct mansession *s, const struct message *m)
{
struct parkeduser *cur;
- char *id = astman_get_header(m,"ActionID");
+ const char *id = astman_get_header(m, "ActionID");
char idText[256] = "";
if (!ast_strlen_zero(id))
- snprintf(idText, 256, "ActionID: %s\r\n", id);
+ snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
astman_send_ack(s, m, "Parked calls will follow");
- ast_mutex_lock(&parking_lock);
+ ast_mutex_lock(&parking_lock);
- for (cur=parkinglot; cur; cur = cur->next) {
+ for (cur = parkinglot; cur; cur = cur->next) {
astman_append(s, "Event: ParkedCall\r\n"
"Exten: %d\r\n"
"Channel: %s\r\n"
"CallerIDName: %s\r\n"
"%s"
"\r\n",
- cur->parkingnum, cur->chan->name, cur->peername,
- (long)cur->start.tv_sec + (long)(cur->parkingtime/1000) - (long)time(NULL),
+ cur->parkingnum, cur->chan->name, cur->peername,
+ (long) cur->start.tv_sec + (long) (cur->parkingtime / 1000) - (long) time(NULL),
S_OR(cur->chan->cid.cid_num, ""), /* XXX in other places it is <unknown> */
S_OR(cur->chan->cid.cid_name, ""),
idText);
- }
+ }
astman_append(s,
"Event: ParkedCallsComplete\r\n"
"%s"
"\r\n",idText);
- ast_mutex_unlock(&parking_lock);
+ ast_mutex_unlock(&parking_lock);
- return RESULT_SUCCESS;
+ return RESULT_SUCCESS;
}
static char mandescr_park[] =
" *Channel2: Channel to announce park info to (and return to if timeout)\n"
" Timeout: Number of milliseconds to wait before callback.\n";
-static int manager_park(struct mansession *s, struct message *m)
+static int manager_park(struct mansession *s, const struct message *m)
{
- char *channel = astman_get_header(m, "Channel");
- char *channel2 = astman_get_header(m, "Channel2");
- char *timeout = astman_get_header(m, "Timeout");
+ const char *channel = astman_get_header(m, "Channel");
+ const char *channel2 = astman_get_header(m, "Channel2");
+ const char *timeout = astman_get_header(m, "Timeout");
char buf[BUFSIZ];
int to = 0;
int res = 0;
struct ast_channel *cur = NULL;
int res = -1;
- while ( (cur = ast_channel_walk_locked(cur)) != NULL) {
+ while ((cur = ast_channel_walk_locked(cur)) != NULL) {
if (!cur->pbx &&
(cur != chan) &&
(chan->pickupgroup & cur->callgroup) &&
{
int start = 0, end = 0;
int res;
+ int i;
struct ast_context *con = NULL;
struct ast_config *cfg = NULL;
struct ast_variable *var = NULL;
+ struct feature_group *fg = NULL;
char old_parking_ext[AST_MAX_EXTENSION];
char old_parking_con[AST_MAX_EXTENSION] = "";
+ char *ctg;
+ static const char *categories[] = {
+ /* Categories in features.conf that are not
+ * to be parsed as group categories
+ */
+ "general",
+ "featuremap",
+ "applicationmap"
+ };
if (!ast_strlen_zero(parking_con)) {
strcpy(old_parking_ext, parking_ext);
adsipark = 0;
comebacktoorigin = 1;
parkaddhints = 0;
+ parkedcalltransfers = 0;
+ parkedcallreparking = 0;
transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
+ atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
+ atxferdropcall = DEFAULT_ATXFER_DROP_CALL;
+ atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
cfg = ast_config_load("features.conf");
if (!cfg) {
parkfindnext = (!strcasecmp(var->value, "next"));
} else if (!strcasecmp(var->name, "parkinghints")) {
parkaddhints = ast_true(var->value);
+ } else if (!strcasecmp(var->name, "parkedcalltransfers")) {
+ if (!strcasecmp(var->value, "both"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH;
+ else if (!strcasecmp(var->value, "caller"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER;
+ else if (!strcasecmp(var->value, "callee"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE;
+ } else if (!strcasecmp(var->name, "parkedcallreparking")) {
+ if (!strcasecmp(var->value, "both"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYBOTH;
+ else if (!strcasecmp(var->value, "caller"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYCALLER;
+ else if (!strcasecmp(var->value, "callee"))
+ parkedcalltransfers = AST_FEATURE_FLAG_BYCALLEE;
} else if (!strcasecmp(var->name, "adsipark")) {
adsipark = ast_true(var->value);
} else if (!strcasecmp(var->name, "transferdigittimeout")) {
atxfernoanswertimeout = DEFAULT_NOANSWER_TIMEOUT_ATTENDED_TRANSFER;
} else
atxfernoanswertimeout = atxfernoanswertimeout * 1000;
+ } else if (!strcasecmp(var->name, "atxferloopdelay")) {
+ if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) {
+ ast_log(LOG_WARNING, "%s is not a valid atxferloopdelay\n", var->value);
+ atxferloopdelay = DEFAULT_ATXFER_LOOP_DELAY;
+ } else
+ atxferloopdelay *= 1000;
+ } else if (!strcasecmp(var->name, "atxferdropcall")) {
+ atxferdropcall = ast_true(var->value);
+ } else if (!strcasecmp(var->name, "atxfercallbackretries")) {
+ if ((sscanf(var->value, "%u", &atxferloopdelay) != 1)) {
+ ast_log(LOG_WARNING, "%s is not a valid atxfercallbackretries\n", var->value);
+ atxfercallbackretries = DEFAULT_ATXFER_CALLBACK_RETRIES;
+ }
} else if (!strcasecmp(var->name, "courtesytone")) {
ast_copy_string(courtesytone, var->value, sizeof(courtesytone));
} else if (!strcasecmp(var->name, "parkedplay")) {
continue;
}
- if ((feature = find_feature(var->name))) {
+ AST_LIST_LOCK(&feature_list);
+ if ((feature = find_dynamic_feature(var->name))) {
+ AST_LIST_UNLOCK(&feature_list);
ast_log(LOG_WARNING, "Dynamic Feature '%s' specified more than once!\n", var->name);
continue;
}
+ AST_LIST_UNLOCK(&feature_list);
if (!(feature = ast_calloc(1, sizeof(*feature))))
continue;
if (option_verbose >= 1)
ast_verbose(VERBOSE_PREFIX_2 "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n", var->name, app, app_args, exten);
- }
+ }
+
+ ast_unregister_groups();
+ AST_RWLIST_WRLOCK(&feature_groups);
+
+ ctg = NULL;
+ struct ast_call_feature *feature;
+ while ((ctg = ast_category_browse(cfg, ctg))) {
+ for (i = 0; i < ARRAY_LEN(categories); i++) {
+ if (!strcasecmp(categories[i], ctg))
+ break;
+ }
+
+ if (i < ARRAY_LEN(categories))
+ continue;
+
+ if (!(fg = register_group(ctg)))
+ continue;
+
+ for (var = ast_variable_browse(cfg, ctg); var; var = var->next) {
+ AST_LIST_LOCK(&feature_list);
+ if(!(feature = find_dynamic_feature(var->name)) &&
+ !(feature = ast_find_call_feature(var->name))) {
+ AST_LIST_UNLOCK(&feature_list);
+ ast_log(LOG_WARNING, "Feature '%s' was not found.\n", var->name);
+ continue;
+ }
+ AST_LIST_UNLOCK(&feature_list);
+
+ register_group_feature(fg, var->value, feature);
+ }
+ }
+
+ AST_RWLIST_UNLOCK(&feature_groups);
+
ast_config_destroy(cfg);
/* Remove the old parking extension */
ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
return -1;
}
- res = ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, strdup(""), ast_free, registrar);
+ res = ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, NULL, NULL, registrar);
if (parkaddhints)
park_add_hints(parking_con, parking_start, parking_stop);
if (!res)
}
+static char *app_bridge = "Bridge";
+static char *bridge_synopsis = "Bridge two channels";
+static char *bridge_descrip =
+"Usage: Bridge(channel[|options])\n"
+" Allows the ability to bridge two channels via the dialplan.\n"
+"The current channel is bridged to the specified 'channel'.\n"
+"The following options are supported:\n"
+" p - Play a courtesy tone to 'channel'.\n"
+"BRIDGERESULT dial plan variable will contain SUCCESS, FAILURE, LOOP, NONEXISTENT or INCOMPATIBLE.\n";
+
+enum {
+ BRIDGE_OPT_PLAYTONE = (1 << 0),
+};
+
+AST_APP_OPTIONS(bridge_exec_options, BEGIN_OPTIONS
+ AST_APP_OPTION('p', BRIDGE_OPT_PLAYTONE)
+END_OPTIONS );
+
+static int bridge_exec(struct ast_channel *chan, void *data)
+{
+ struct ast_module_user *u;
+ struct ast_channel *current_dest_chan, *final_dest_chan;
+ char *tmp_data = NULL;
+ struct ast_flags opts = { 0, };
+ struct ast_bridge_config bconfig = { { 0, }, };
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(dest_chan);
+ AST_APP_ARG(options);
+ );
+
+ if (ast_strlen_zero(data)) {
+ ast_log(LOG_WARNING, "Bridge require at least 1 argument specifying the other end of the bridge\n");
+ return -1;
+ }
+
+ u = ast_module_user_add(chan);
+
+ tmp_data = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, tmp_data);
+ if (!ast_strlen_zero(args.options))
+ ast_app_parse_options(bridge_exec_options, &opts, NULL, args.options);
+
+ /* avoid bridge with ourselves */
+ if (!strncmp(chan->name, args.dest_chan,
+ strlen(chan->name) < strlen(args.dest_chan) ?
+ strlen(chan->name) : strlen(args.dest_chan))) {
+ ast_log(LOG_WARNING, "Unable to bridge channel %s with itself\n", chan->name);
+ manager_event(EVENT_FLAG_CALL, "BridgeExec",
+ "Response: Failed\r\n"
+ "Reason: Unable to bridge channel to itself\r\n"
+ "Channel1: %s\r\n"
+ "Channel2: %s\r\n",
+ chan->name, args.dest_chan);
+ pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "LOOP");
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ /* make sure we have a valid end point */
+ if (!(current_dest_chan = ast_get_channel_by_name_prefix_locked(args.dest_chan,
+ strlen(args.dest_chan)))) {
+ ast_log(LOG_WARNING, "Bridge failed because channel %s does not exists or we "
+ "cannot get its lock\n", args.dest_chan);
+ manager_event(EVENT_FLAG_CALL, "BridgeExec",
+ "Response: Failed\r\n"
+ "Reason: Cannot grab end point\r\n"
+ "Channel1: %s\r\n"
+ "Channel2: %s\r\n", chan->name, args.dest_chan);
+ pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "NONEXISTENT");
+ ast_module_user_remove(u);
+ return 0;
+ }
+ ast_mutex_unlock(¤t_dest_chan->lock);
+
+ /* answer the channel if needed */
+ if (current_dest_chan->_state != AST_STATE_UP)
+ ast_answer(current_dest_chan);
+
+ /* try to allocate a place holder where current_dest_chan will be placed */
+ if (!(final_dest_chan = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL,
+ NULL, NULL, 0, "Bridge/%s", current_dest_chan->name))) {
+ ast_log(LOG_WARNING, "Cannot create placeholder channel for chan %s\n", args.dest_chan);
+ manager_event(EVENT_FLAG_CALL, "BridgeExec",
+ "Response: Failed\r\n"
+ "Reason: cannot create placeholder\r\n"
+ "Channel1: %s\r\n"
+ "Channel2: %s\r\n", chan->name, args.dest_chan);
+ }
+ do_bridge_masquerade(current_dest_chan, final_dest_chan);
+
+ /* now current_dest_chan is a ZOMBIE and with softhangup set to 1 and final_dest_chan is our end point */
+ /* try to make compatible, send error if we fail */
+ if (ast_channel_make_compatible(chan, final_dest_chan) < 0) {
+ ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", chan->name, final_dest_chan->name);
+ manager_event(EVENT_FLAG_CALL, "BridgeExec",
+ "Response: Failed\r\n"
+ "Reason: Could not make channels compatible for bridge\r\n"
+ "Channel1: %s\r\n"
+ "Channel2: %s\r\n", chan->name, final_dest_chan->name);
+ ast_hangup(final_dest_chan); /* may be we should return this channel to the PBX? */
+ pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "INCOMPATIBLE");
+ ast_module_user_remove(u);
+ return 0;
+ }
+
+ /* Report that the bridge will be successfull */
+ manager_event(EVENT_FLAG_CALL, "BridgeExec",
+ "Response: Success\r\n"
+ "Channel1: %s\r\n"
+ "Channel2: %s\r\n", chan->name, final_dest_chan->name);
+
+ /* we have 2 valid channels to bridge, now it is just a matter of setting up the bridge config and starting the bridge */
+ if (ast_test_flag(&opts, BRIDGE_OPT_PLAYTONE) && !ast_strlen_zero(xfersound)) {
+ if (!ast_streamfile(final_dest_chan, xfersound, final_dest_chan->language)) {
+ if (ast_waitstream(final_dest_chan, "") < 0)
+ ast_log(LOG_WARNING, "Failed to play courtesy tone on %s\n", final_dest_chan->name);
+ }
+ }
+
+ /* do the bridge */
+ ast_bridge_call(chan, final_dest_chan, &bconfig);
+
+ /* the bridge has ended, set BRIDGERESULT to SUCCESS. If the other channel has not been hung up, return it to the PBX */
+ pbx_builtin_setvar_helper(chan, "BRIDGERESULT", "SUCCESS");
+ if (!ast_check_hangup(final_dest_chan)) {
+ if (option_debug) {
+ ast_log(LOG_DEBUG, "starting new PBX in %s,%s,%d for chan %s\n",
+ final_dest_chan->context, final_dest_chan->exten,
+ final_dest_chan->priority, final_dest_chan->name);
+ }
+
+ if (ast_pbx_start(final_dest_chan) != AST_PBX_SUCCESS) {
+ ast_log(LOG_WARNING, "FAILED continuing PBX on dest chan %s\n", final_dest_chan->name);
+ ast_hangup(final_dest_chan);
+ } else if (option_debug)
+ ast_log(LOG_DEBUG, "SUCCESS continuing PBX on chan %s\n", final_dest_chan->name);
+ } else {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "hangup chan %s since the other endpoint has hung up\n", final_dest_chan->name);
+ ast_hangup(final_dest_chan);
+ }
+
+ ast_module_user_remove(u);
+
+ return 0;
+}
+
static int reload(void)
{
return load_config();
static int load_module(void)
{
int res;
-
+
+ ast_register_application(app_bridge, bridge_exec, bridge_synopsis, bridge_descrip);
+
memset(parking_ext, 0, sizeof(parking_ext));
memset(parking_con, 0, sizeof(parking_con));
if (!res)
res = ast_register_application(parkcall, park_call_exec, synopsis2, descrip2);
if (!res) {
- ast_manager_register("ParkedCalls", 0, manager_parking_status, "List parked calls" );
+ ast_manager_register("ParkedCalls", 0, manager_parking_status, "List parked calls");
ast_manager_register2("Park", EVENT_FLAG_CALL, manager_park,
"Park a channel", mandescr_park);
+ ast_manager_register2("Bridge", EVENT_FLAG_COMMAND, action_bridge, "Bridge two channels already in the PBX", mandescr_bridge);
}
res |= ast_devstate_prov_add("Park", metermaidstate);
ast_module_user_hangup_all();
ast_manager_unregister("ParkedCalls");
+ ast_manager_unregister("Bridge");
ast_manager_unregister("Park");
ast_cli_unregister_multiple(cli_features, sizeof(cli_features) / sizeof(struct ast_cli_entry));
ast_unregister_application(parkcall);
+ ast_unregister_application(app_bridge);
ast_devstate_prov_del("Park");
return ast_unregister_application(parkedcall);
}
.load = load_module,
.unload = unload_module,
.reload = reload,
- );
+ );