mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-04 20:04:50 +00:00
features: Add FEATURE() and FEATUREMAP() functions.
Add two new dialplan functions: FEATURE() and FEATUREMAP(). FEATURE() lets you set some of the configuration options from the [general] section of features.conf on a per-channel basis. FEATUREMAP() lets you customize the key sequence used to activate built-in features, such as blindxfer, and automon. See the built-in documentation for details. Review: https://reviewboard.asterisk.org/r/1871/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@364437 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
5
CHANGES
5
CHANGES
@@ -209,6 +209,11 @@ Dialplan functions
|
|||||||
VM_INFO.
|
VM_INFO.
|
||||||
* The REDIRECTING function now supports the redirecting original party id
|
* The REDIRECTING function now supports the redirecting original party id
|
||||||
and reason.
|
and reason.
|
||||||
|
* Two new functions have been added: FEATURE() and FEATUREMAP(). FEATURE()
|
||||||
|
lets you set some of the configuration options from the [general] section
|
||||||
|
of features.conf on a per-channel basis. FEATUREMAP() lets you customize
|
||||||
|
the key sequence used to activate built-in features, such as blindxfer,
|
||||||
|
and automon. See the built-in documentation for details.
|
||||||
|
|
||||||
Followme changes
|
Followme changes
|
||||||
-------------
|
-------------
|
||||||
|
416
main/features.c
416
main/features.c
@@ -1,7 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* Asterisk -- An open source telephony toolkit.
|
* Asterisk -- An open source telephony toolkit.
|
||||||
*
|
*
|
||||||
* Copyright (C) 1999 - 2008, Digium, Inc.
|
* Copyright (C) 1999 - 2012, Digium, Inc.
|
||||||
|
* Copyright (C) 2012, Russell Bryant
|
||||||
*
|
*
|
||||||
* Mark Spencer <markster@digium.com>
|
* Mark Spencer <markster@digium.com>
|
||||||
*
|
*
|
||||||
@@ -408,6 +409,57 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||||||
<para>Bridge together two channels already in the PBX.</para>
|
<para>Bridge together two channels already in the PBX.</para>
|
||||||
</description>
|
</description>
|
||||||
</manager>
|
</manager>
|
||||||
|
<function name="FEATURE" language="en_US">
|
||||||
|
<synopsis>
|
||||||
|
Get or set a feature option on a channel.
|
||||||
|
</synopsis>
|
||||||
|
<syntax>
|
||||||
|
<parameter name="option_name" required="true">
|
||||||
|
<para>The allowed values are:</para>
|
||||||
|
<enumlist>
|
||||||
|
<enum name="parkingtime"><para>Specified in seconds.</para></enum>
|
||||||
|
</enumlist>
|
||||||
|
</parameter>
|
||||||
|
</syntax>
|
||||||
|
<description>
|
||||||
|
<para>When this function is used as a read, it will get the current
|
||||||
|
value of the specified feature option for this channel. It will be
|
||||||
|
the value of this option configured in features.conf if a channel specific
|
||||||
|
value has not been set. This function can also be used to set a channel
|
||||||
|
specific value for the supported feature options.</para>
|
||||||
|
</description>
|
||||||
|
<see-also>
|
||||||
|
<ref type="function">FEATUREMAP</ref>
|
||||||
|
</see-also>
|
||||||
|
</function>
|
||||||
|
<function name="FEATUREMAP" language="en_US">
|
||||||
|
<synopsis>
|
||||||
|
Get or set a feature map to a given value on a specific channel.
|
||||||
|
</synopsis>
|
||||||
|
<syntax>
|
||||||
|
<parameter name="feature_name" required="true">
|
||||||
|
<para>The allowed values are:</para>
|
||||||
|
<enumlist>
|
||||||
|
<enum name="atxfer"><para>Attended Transfer</para></enum>
|
||||||
|
<enum name="blindxfer"><para>Blind Transfer</para></enum>
|
||||||
|
<enum name="automon"><para>Auto Monitor</para></enum>
|
||||||
|
<enum name="disconnect"><para>Call Disconnect</para></enum>
|
||||||
|
<enum name="parkcall"><para>Park Call</para></enum>
|
||||||
|
<enum name="automixmon"><para>Auto MixMonitor</para></enum>
|
||||||
|
</enumlist>
|
||||||
|
</parameter>
|
||||||
|
</syntax>
|
||||||
|
<description>
|
||||||
|
<para>When this function is used as a read, it will get the current
|
||||||
|
digit sequence mapped to the specified feature for this channel. This
|
||||||
|
value will be the one configured in features.conf if a channel specific
|
||||||
|
value has not been set. This function can also be used to set a channel
|
||||||
|
specific value for a feature mapping.</para>
|
||||||
|
</description>
|
||||||
|
<see-also>
|
||||||
|
<ref type="function">FEATURE</ref>
|
||||||
|
</see-also>
|
||||||
|
</function>
|
||||||
***/
|
***/
|
||||||
|
|
||||||
#define DEFAULT_PARK_TIME 45000 /*!< ms */
|
#define DEFAULT_PARK_TIME 45000 /*!< ms */
|
||||||
@@ -508,7 +560,7 @@ struct parkeduser {
|
|||||||
char context[AST_MAX_CONTEXT]; /*!< Where to go if our parking time expires */
|
char context[AST_MAX_CONTEXT]; /*!< Where to go if our parking time expires */
|
||||||
char exten[AST_MAX_EXTENSION];
|
char exten[AST_MAX_EXTENSION];
|
||||||
int priority;
|
int priority;
|
||||||
int parkingtime; /*!< Maximum length in parking lot before return */
|
unsigned int parkingtime; /*!< Maximum length in parking lot before return */
|
||||||
/*! Method to entertain the caller when parked: AST_CONTROL_RINGING, AST_CONTROL_HOLD, or 0(none) */
|
/*! Method to entertain the caller when parked: AST_CONTROL_RINGING, AST_CONTROL_HOLD, or 0(none) */
|
||||||
enum ast_control_frame_type hold_method;
|
enum ast_control_frame_type hold_method;
|
||||||
unsigned int notquiteyet:1;
|
unsigned int notquiteyet:1;
|
||||||
@@ -535,7 +587,7 @@ struct parkinglot_cfg {
|
|||||||
/*! Last available extension for parking */
|
/*! Last available extension for parking */
|
||||||
int parking_stop;
|
int parking_stop;
|
||||||
/*! Default parking time in ms. */
|
/*! Default parking time in ms. */
|
||||||
int parkingtime;
|
unsigned int parkingtime;
|
||||||
/*!
|
/*!
|
||||||
* \brief Enable DTMF based transfers on bridge when picking up parked calls.
|
* \brief Enable DTMF based transfers on bridge when picking up parked calls.
|
||||||
*
|
*
|
||||||
@@ -1482,6 +1534,8 @@ static struct parkeduser *park_space_reserve(struct ast_channel *park_me, struct
|
|||||||
return pu;
|
return pu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static unsigned int get_parkingtime(struct ast_channel *chan, struct ast_parkinglot *parkinglot);
|
||||||
|
|
||||||
/* Park a call */
|
/* Park a call */
|
||||||
static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, struct ast_park_call_args *args)
|
static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, struct ast_park_call_args *args)
|
||||||
{
|
{
|
||||||
@@ -1515,7 +1569,7 @@ static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, st
|
|||||||
}
|
}
|
||||||
|
|
||||||
pu->start = ast_tvnow();
|
pu->start = ast_tvnow();
|
||||||
pu->parkingtime = (args->timeout > 0) ? args->timeout : pu->parkinglot->cfg.parkingtime;
|
pu->parkingtime = (args->timeout > 0) ? args->timeout : get_parkingtime(chan, pu->parkinglot);
|
||||||
if (args->extout)
|
if (args->extout)
|
||||||
*(args->extout) = pu->parkingnum;
|
*(args->extout) = pu->parkingnum;
|
||||||
|
|
||||||
@@ -1588,7 +1642,7 @@ static int park_call_full(struct ast_channel *chan, struct ast_channel *peer, st
|
|||||||
|
|
||||||
/* Wake up the (presumably select()ing) thread */
|
/* Wake up the (presumably select()ing) thread */
|
||||||
pthread_kill(parking_thread, SIGURG);
|
pthread_kill(parking_thread, SIGURG);
|
||||||
ast_verb(2, "Parked %s on %d (lot %s). Will timeout back to extension [%s] %s, %d in %d seconds\n",
|
ast_verb(2, "Parked %s on %d (lot %s). Will timeout back to extension [%s] %s, %d in %u seconds\n",
|
||||||
ast_channel_name(chan), pu->parkingnum, pu->parkinglot->name,
|
ast_channel_name(chan), pu->parkingnum, pu->parkinglot->name,
|
||||||
pu->context, pu->exten, pu->priority, (pu->parkingtime / 1000));
|
pu->context, pu->exten, pu->priority, (pu->parkingtime / 1000));
|
||||||
|
|
||||||
@@ -3158,6 +3212,10 @@ void ast_unlock_call_features(void)
|
|||||||
ast_rwlock_unlock(&features_lock);
|
ast_rwlock_unlock(&features_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \internal
|
||||||
|
* \pre Expects feature_lock to be readlocked
|
||||||
|
*/
|
||||||
struct ast_call_feature *ast_find_call_feature(const char *name)
|
struct ast_call_feature *ast_find_call_feature(const char *name)
|
||||||
{
|
{
|
||||||
int x;
|
int x;
|
||||||
@@ -3169,6 +3227,143 @@ struct ast_call_feature *ast_find_call_feature(const char *name)
|
|||||||
return find_dynamic_feature(name);
|
return find_dynamic_feature(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct feature_exten {
|
||||||
|
char sname[FEATURE_SNAME_LEN];
|
||||||
|
char exten[FEATURE_MAX_LEN];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct feature_ds {
|
||||||
|
struct ao2_container *feature_map;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief specified in seconds, stored in milliseconds
|
||||||
|
*
|
||||||
|
* \todo XXX This isn't pretty. At some point it would be nice to have all
|
||||||
|
* of the global / [general] options in a config object that we store here
|
||||||
|
* instead of handling each one manually.
|
||||||
|
* */
|
||||||
|
unsigned int parkingtime;
|
||||||
|
unsigned int parkingtime_is_set:1;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void feature_ds_destroy(void *data)
|
||||||
|
{
|
||||||
|
struct feature_ds *feature_ds = data;
|
||||||
|
|
||||||
|
if (feature_ds->feature_map) {
|
||||||
|
ao2_ref(feature_ds->feature_map, -1);
|
||||||
|
feature_ds->feature_map = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_free(feature_ds);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct ast_datastore_info feature_ds_info = {
|
||||||
|
.type = "FEATURE",
|
||||||
|
.destroy = feature_ds_destroy,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int feature_exten_hash(const void *obj, int flags)
|
||||||
|
{
|
||||||
|
const struct feature_exten *fe = obj;
|
||||||
|
const char *sname = obj;
|
||||||
|
|
||||||
|
return ast_str_hash(flags & OBJ_KEY ? sname : fe->sname);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int feature_exten_cmp(void *obj, void *arg, int flags)
|
||||||
|
{
|
||||||
|
const struct feature_exten *fe = obj, *fe2 = arg;
|
||||||
|
const char *sname = arg;
|
||||||
|
|
||||||
|
return !strcmp(fe->sname, flags & OBJ_KEY ? sname : fe2->sname) ?
|
||||||
|
CMP_MATCH | CMP_STOP : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \internal
|
||||||
|
* \brief Find or create feature datastore on a channel
|
||||||
|
*
|
||||||
|
* \pre chan is locked
|
||||||
|
*
|
||||||
|
* \return the data on the FEATURE datastore, or NULL on error
|
||||||
|
*/
|
||||||
|
static struct feature_ds *get_feature_ds(struct ast_channel *chan)
|
||||||
|
{
|
||||||
|
struct feature_ds *feature_ds;
|
||||||
|
struct ast_datastore *ds;
|
||||||
|
|
||||||
|
if ((ds = ast_channel_datastore_find(chan, &feature_ds_info, NULL))) {
|
||||||
|
feature_ds = ds->data;
|
||||||
|
return feature_ds;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(feature_ds = ast_calloc(1, sizeof(*feature_ds)))) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(feature_ds->feature_map = ao2_container_alloc(7, feature_exten_hash, feature_exten_cmp))) {
|
||||||
|
feature_ds_destroy(feature_ds);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(ds = ast_datastore_alloc(&feature_ds_info, NULL))) {
|
||||||
|
feature_ds_destroy(feature_ds);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ds->data = feature_ds;
|
||||||
|
|
||||||
|
ast_channel_datastore_add(chan, ds);
|
||||||
|
|
||||||
|
return feature_ds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \internal
|
||||||
|
* \brief Get the extension for a given builtin feature
|
||||||
|
*
|
||||||
|
* \pre expects feature_lock to be readlocked
|
||||||
|
*
|
||||||
|
* \retval 0 success
|
||||||
|
* \retval non-zero failiure
|
||||||
|
*/
|
||||||
|
static int builtin_feature_get_exten(struct ast_channel *chan, const char *feature_name,
|
||||||
|
char *buf, size_t len)
|
||||||
|
{
|
||||||
|
struct ast_call_feature *feature;
|
||||||
|
struct feature_ds *feature_ds;
|
||||||
|
struct feature_exten *fe = NULL;
|
||||||
|
|
||||||
|
*buf = '\0';
|
||||||
|
|
||||||
|
if (!(feature = ast_find_call_feature(feature_name))) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_copy_string(buf, feature->exten, len);
|
||||||
|
|
||||||
|
ast_unlock_call_features();
|
||||||
|
|
||||||
|
ast_channel_lock(chan);
|
||||||
|
if ((feature_ds = get_feature_ds(chan))) {
|
||||||
|
fe = ao2_find(feature_ds->feature_map, feature_name, OBJ_KEY);
|
||||||
|
}
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
|
||||||
|
ast_rdlock_call_features();
|
||||||
|
|
||||||
|
if (fe) {
|
||||||
|
ao2_lock(fe);
|
||||||
|
ast_copy_string(buf, fe->exten, len);
|
||||||
|
ao2_unlock(fe);
|
||||||
|
ao2_ref(fe, -1);
|
||||||
|
fe = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief exec an app by feature
|
* \brief exec an app by feature
|
||||||
* \param chan,peer,config,code,sense,data
|
* \param chan,peer,config,code,sense,data
|
||||||
@@ -3299,25 +3494,33 @@ static int feature_interpret_helper(struct ast_channel *chan, struct ast_channel
|
|||||||
|
|
||||||
ast_rwlock_rdlock(&features_lock);
|
ast_rwlock_rdlock(&features_lock);
|
||||||
for (x = 0; x < FEATURES_COUNT; x++) {
|
for (x = 0; x < FEATURES_COUNT; x++) {
|
||||||
if ((ast_test_flag(features, builtin_features[x].feature_mask)) &&
|
char feature_exten[FEATURE_MAX_LEN] = "";
|
||||||
!ast_strlen_zero(builtin_features[x].exten)) {
|
|
||||||
/* Feature is up for consideration */
|
if (!ast_test_flag(features, builtin_features[x].feature_mask)) {
|
||||||
if (!strcmp(builtin_features[x].exten, code)) {
|
continue;
|
||||||
ast_debug(3, "Feature detected: fname=%s sname=%s exten=%s\n", builtin_features[x].fname, builtin_features[x].sname, builtin_features[x].exten);
|
}
|
||||||
if (operation == FEATURE_INTERPRET_CHECK) {
|
|
||||||
res = AST_FEATURE_RETURN_SUCCESS; /* We found something */
|
if (builtin_feature_get_exten(chan, builtin_features[x].sname, feature_exten, sizeof(feature_exten))) {
|
||||||
} else if (operation == FEATURE_INTERPRET_DO) {
|
continue;
|
||||||
res = builtin_features[x].operation(chan, peer, config, code, sense, NULL);
|
}
|
||||||
}
|
|
||||||
if (feature) {
|
/* Feature is up for consideration */
|
||||||
memcpy(feature, &builtin_features[x], sizeof(*feature));
|
|
||||||
}
|
if (!strcmp(feature_exten, code)) {
|
||||||
feature_detected = 1;
|
ast_debug(3, "Feature detected: fname=%s sname=%s exten=%s\n", builtin_features[x].fname, builtin_features[x].sname, feature_exten);
|
||||||
break;
|
if (operation == FEATURE_INTERPRET_CHECK) {
|
||||||
} else if (!strncmp(builtin_features[x].exten, code, strlen(code))) {
|
res = AST_FEATURE_RETURN_SUCCESS; /* We found something */
|
||||||
if (res == AST_FEATURE_RETURN_PASSDIGITS) {
|
} else if (operation == FEATURE_INTERPRET_DO) {
|
||||||
res = AST_FEATURE_RETURN_STOREDIGITS;
|
res = builtin_features[x].operation(chan, peer, config, code, sense, NULL);
|
||||||
}
|
}
|
||||||
|
if (feature) {
|
||||||
|
memcpy(feature, &builtin_features[x], sizeof(*feature));
|
||||||
|
}
|
||||||
|
feature_detected = 1;
|
||||||
|
break;
|
||||||
|
} else if (!strncmp(feature_exten, code, strlen(code))) {
|
||||||
|
if (res == AST_FEATURE_RETURN_PASSDIGITS) {
|
||||||
|
res = AST_FEATURE_RETURN_STOREDIGITS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5539,9 +5742,9 @@ static int parkinglot_config_read(const char *pl_name, struct parkinglot_cfg *cf
|
|||||||
} else if (!strcasecmp(var->name, "parkedmusicclass")) {
|
} else if (!strcasecmp(var->name, "parkedmusicclass")) {
|
||||||
ast_copy_string(cfg->mohclass, var->value, sizeof(cfg->mohclass));
|
ast_copy_string(cfg->mohclass, var->value, sizeof(cfg->mohclass));
|
||||||
} else if (!strcasecmp(var->name, "parkingtime")) {
|
} else if (!strcasecmp(var->name, "parkingtime")) {
|
||||||
int parkingtime = 0;
|
unsigned int parkingtime = 0;
|
||||||
|
|
||||||
if ((sscanf(var->value, "%30d", &parkingtime) != 1) || parkingtime < 1) {
|
if ((sscanf(var->value, "%30u", &parkingtime) != 1) || parkingtime < 1) {
|
||||||
ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value);
|
ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value);
|
||||||
error = -1;
|
error = -1;
|
||||||
} else {
|
} else {
|
||||||
@@ -6851,7 +7054,7 @@ static char *handle_feature_show(struct ast_cli_entry *e, int cmd, struct ast_cl
|
|||||||
ast_cli(a->fd,"%-22s: %s\n", "Parking context", curlot->cfg.parking_con);
|
ast_cli(a->fd,"%-22s: %s\n", "Parking context", curlot->cfg.parking_con);
|
||||||
ast_cli(a->fd,"%-22s: %d-%d\n", "Parked call extensions",
|
ast_cli(a->fd,"%-22s: %d-%d\n", "Parked call extensions",
|
||||||
curlot->cfg.parking_start, curlot->cfg.parking_stop);
|
curlot->cfg.parking_start, curlot->cfg.parking_stop);
|
||||||
ast_cli(a->fd,"%-22s: %d ms\n", "Parkingtime", curlot->cfg.parkingtime);
|
ast_cli(a->fd,"%-22s: %u ms\n", "Parkingtime", curlot->cfg.parkingtime);
|
||||||
ast_cli(a->fd,"%-22s: %s\n", "Comeback to origin",
|
ast_cli(a->fd,"%-22s: %s\n", "Comeback to origin",
|
||||||
(curlot->cfg.comebacktoorigin ? "yes" : "no"));
|
(curlot->cfg.comebacktoorigin ? "yes" : "no"));
|
||||||
ast_cli(a->fd,"%-22s: %s%s\n", "Comeback context",
|
ast_cli(a->fd,"%-22s: %s%s\n", "Comeback context",
|
||||||
@@ -8282,6 +8485,163 @@ exit_features_test:
|
|||||||
}
|
}
|
||||||
#endif /* defined(TEST_FRAMEWORK) */
|
#endif /* defined(TEST_FRAMEWORK) */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \internal
|
||||||
|
* \brief Get parkingtime for a channel
|
||||||
|
*/
|
||||||
|
static unsigned int get_parkingtime(struct ast_channel *chan, struct ast_parkinglot *parkinglot)
|
||||||
|
{
|
||||||
|
const char *parkinglot_name;
|
||||||
|
struct feature_ds *feature_ds;
|
||||||
|
unsigned int parkingtime;
|
||||||
|
|
||||||
|
ast_channel_lock(chan);
|
||||||
|
|
||||||
|
feature_ds = get_feature_ds(chan);
|
||||||
|
if (feature_ds && feature_ds->parkingtime_is_set) {
|
||||||
|
parkingtime = feature_ds->parkingtime;
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
return parkingtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
parkinglot_name = ast_strdupa(S_OR(ast_channel_parkinglot(chan), ""));
|
||||||
|
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
|
||||||
|
if (!parkinglot) {
|
||||||
|
if (!ast_strlen_zero(parkinglot_name)) {
|
||||||
|
parkinglot = find_parkinglot(parkinglot_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parkinglot) {
|
||||||
|
parkinglot = parkinglot_addref(default_parkinglot);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Just to balance the unref below */
|
||||||
|
parkinglot_addref(parkinglot);
|
||||||
|
}
|
||||||
|
|
||||||
|
parkingtime = parkinglot->cfg.parkingtime;
|
||||||
|
|
||||||
|
parkinglot_unref(parkinglot);
|
||||||
|
|
||||||
|
return parkingtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int feature_read(struct ast_channel *chan, const char *cmd, char *data,
|
||||||
|
char *buf, size_t len)
|
||||||
|
{
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
if (!strcasecmp(data, "parkingtime")) {
|
||||||
|
snprintf(buf, len, "%u", get_parkingtime(chan, NULL) / 1000);
|
||||||
|
} else {
|
||||||
|
ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data);
|
||||||
|
res = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int feature_write(struct ast_channel *chan, const char *cmd, char *data,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
int res = 0;
|
||||||
|
struct feature_ds *feature_ds;
|
||||||
|
|
||||||
|
ast_channel_lock(chan);
|
||||||
|
|
||||||
|
if (!(feature_ds = get_feature_ds(chan))) {
|
||||||
|
res = -1;
|
||||||
|
goto return_cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcasecmp(data, "parkingtime")) {
|
||||||
|
feature_ds->parkingtime_is_set = 1;
|
||||||
|
if (sscanf(value, "%30u", &feature_ds->parkingtime) == 1) {
|
||||||
|
feature_ds->parkingtime *= 1000; /* stored in ms */
|
||||||
|
} else {
|
||||||
|
ast_log(LOG_WARNING, "'%s' is not a valid parkingtime\n", value);
|
||||||
|
feature_ds->parkingtime_is_set = 0;
|
||||||
|
res = -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ast_log(LOG_WARNING, "Invalid argument '%s' to FEATURE()\n", data);
|
||||||
|
res = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return_cleanup:
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int featuremap_read(struct ast_channel *chan, const char *cmd, char *data,
|
||||||
|
char *buf, size_t len)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
|
||||||
|
ast_rdlock_call_features();
|
||||||
|
|
||||||
|
if ((res = builtin_feature_get_exten(chan, data, buf, len))) {
|
||||||
|
ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_unlock_call_features();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int featuremap_write(struct ast_channel *chan, const char *cmd, char *data,
|
||||||
|
const char *value)
|
||||||
|
{
|
||||||
|
struct feature_ds *feature_ds;
|
||||||
|
struct feature_exten *fe;
|
||||||
|
|
||||||
|
if (!ast_find_call_feature(data)) {
|
||||||
|
ast_log(LOG_WARNING, "Invalid argument '%s' to FEATUREMAP()\n", data);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_channel_lock(chan);
|
||||||
|
|
||||||
|
if (!(feature_ds = get_feature_ds(chan))) {
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(fe = ao2_find(feature_ds->feature_map, data, OBJ_KEY))) {
|
||||||
|
if (!(fe = ao2_alloc(sizeof(*fe), NULL))) {
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ast_copy_string(fe->sname, data, sizeof(fe->sname));
|
||||||
|
ao2_link(feature_ds->feature_map, fe);
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_channel_unlock(chan);
|
||||||
|
|
||||||
|
ao2_lock(fe);
|
||||||
|
ast_copy_string(fe->exten, value, sizeof(fe->exten));
|
||||||
|
ao2_unlock(fe);
|
||||||
|
ao2_ref(fe, -1);
|
||||||
|
fe = NULL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct ast_custom_function feature_function = {
|
||||||
|
.name = "FEATURE",
|
||||||
|
.read = feature_read,
|
||||||
|
.write = feature_write
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct ast_custom_function featuremap_function = {
|
||||||
|
.name = "FEATUREMAP",
|
||||||
|
.read = featuremap_read,
|
||||||
|
.write = featuremap_write
|
||||||
|
};
|
||||||
|
|
||||||
int ast_features_init(void)
|
int ast_features_init(void)
|
||||||
{
|
{
|
||||||
int res;
|
int res;
|
||||||
@@ -8306,6 +8666,8 @@ int ast_features_init(void)
|
|||||||
ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park);
|
ast_manager_register_xml_core("Park", EVENT_FLAG_CALL, manager_park);
|
||||||
ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge);
|
ast_manager_register_xml_core("Bridge", EVENT_FLAG_CALL, action_bridge);
|
||||||
}
|
}
|
||||||
|
res |= __ast_custom_function_register(&feature_function, NULL);
|
||||||
|
res |= __ast_custom_function_register(&featuremap_function, NULL);
|
||||||
|
|
||||||
res |= ast_devstate_prov_add("Park", metermaidstate);
|
res |= ast_devstate_prov_add("Park", metermaidstate);
|
||||||
#if defined(TEST_FRAMEWORK)
|
#if defined(TEST_FRAMEWORK)
|
||||||
|
Reference in New Issue
Block a user