#include "asterisk/logger.h"
#include "asterisk/devicestate.h"
#include "asterisk/pbx.h"
+#include "asterisk/app.h"
#include "asterisk/options.h"
/*! \brief Device state strings for printing */
/* 6 AST_DEVICE_RINGING */ "Ringing" /*!< Ring, ring, ring */
};
+/*! \brief A device state provider (not a channel) */
+struct devstate_prov {
+ char label[40];
+ ast_devstate_prov_cb_type callback;
+ AST_LIST_ENTRY(devstate_prov) list;
+};
+
+/*! \brief A list of providers */
+static AST_LIST_HEAD_STATIC(devstate_provs, devstate_prov);
+
/*! \brief A device state watcher (callback) */
struct devstate_cb {
void *data;
AST_LIST_ENTRY(devstate_cb) list;
};
+/*! \brief A device state watcher list */
static AST_LIST_HEAD_STATIC(devstate_cbs, devstate_cb);
struct state_change {
char device[1];
};
+/*! \brief The state change queue. State changes are queued
+ for processing by a separate thread */
static AST_LIST_HEAD_STATIC(state_changes, state_change);
+/*! \brief The device state change notification thread */
static pthread_t change_thread = AST_PTHREADT_NULL;
+
+/*! \brief Flag for the queue */
static ast_cond_t change_pending;
+/* Forward declarations */
+static int getproviderstate(const char *provider, const char *address);
+
/*! \brief Find devicestate as text message for output */
const char *devstate2str(int devstate)
{
}
/*! \brief Find out if device is active in a call or not
-\note find channels with the device's name in it
-This function is only used for channels that does not implement
-devicestate natively
+ \note find channels with the device's name in it
+ This function is only used for channels that does not implement
+ devicestate natively
*/
int ast_parse_device_state(const char *device)
{
int ast_device_state(const char *device)
{
char *buf;
- char *tech;
char *number;
const struct ast_channel_tech *chan_tech;
int res = 0;
+ /*! \brief Channel driver that provides device state */
+ char *tech;
+ /*! \brief Another provider of device state */
+ char *provider = NULL;
buf = ast_strdupa(device);
tech = strsep(&buf, "/");
number = buf;
- if (!number)
- return AST_DEVICE_INVALID;
-
+ if (!number) {
+ provider = strsep(&tech, ":");
+ if (!provider)
+ return AST_DEVICE_INVALID;
+ /* We have a provider */
+ number = tech;
+ tech = NULL;
+ }
+
+ if (provider) {
+ if(option_debug > 2)
+ ast_log(LOG_DEBUG, "Checking if I can find provider for \"%s\" - number: %s\n", provider, number);
+ return getproviderstate(provider, number);
+ }
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "No provider found, checking channel drivers for %s - %s\n", tech, number);
+
chan_tech = ast_get_channel_tech(tech);
if (!chan_tech)
return AST_DEVICE_INVALID;
}
}
+/*! \brief Add device state provider */
+int ast_devstate_prov_add(const char *label, ast_devstate_prov_cb_type callback)
+{
+ struct devstate_prov *devprov;
+
+ if (!callback || !(devprov = ast_calloc(1, sizeof(*devprov))))
+ return -1;
+
+ devprov->callback = callback;
+ ast_copy_string(devprov->label, label, sizeof(devprov->label));
+
+ AST_LIST_LOCK(&devstate_provs);
+ AST_LIST_INSERT_HEAD(&devstate_provs, devprov, list);
+ AST_LIST_UNLOCK(&devstate_provs);
+
+ return 0;
+}
+
+/*! \brief Remove device state provider */
+void ast_devstate_prov_del(const char *label)
+{
+ struct devstate_prov *devcb;
+
+ AST_LIST_LOCK(&devstate_provs);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_provs, devcb, list) {
+ if (!strcasecmp(devcb->label, label)) {
+ AST_LIST_REMOVE_CURRENT(&devstate_provs, list);
+ free(devcb);
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ AST_LIST_UNLOCK(&devstate_provs);
+}
+
+/*! \brief Get provider device state */
+static int getproviderstate(const char *provider, const char *address)
+{
+ struct devstate_prov *devprov;
+ int res = AST_DEVICE_INVALID;
+
+
+ AST_LIST_LOCK(&devstate_provs);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_provs, devprov, list) {
+ if(option_debug > 4)
+ ast_log(LOG_DEBUG, "Checking provider %s with %s\n", devprov->label, provider);
+
+ if (!strcasecmp(devprov->label, provider)) {
+ res = devprov->callback(address);
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ AST_LIST_UNLOCK(&devstate_provs);
+ return res;
+}
+
/*! \brief Add device state watcher */
int ast_devstate_add(ast_devstate_cb_type callback, void *data)
{
AST_LIST_UNLOCK(&devstate_cbs);
}
-/*! \brief Notify callback watchers of change, and notify PBX core for hint updates */
+/*! \brief Notify callback watchers of change, and notify PBX core for hint updates
+ Normally executed within a separate thread
+*/
static void do_state_change(const char *device)
{
int state;
char *device, *tmp;
struct state_change *change;
+ if (option_debug > 2)
+ ast_log(LOG_DEBUG, "Notification of state change to be queued on device/channel %s\n", buf);
+
device = buf;
if ((tmp = strrchr(device, '-')))
*tmp = '\0';
+
+
if (change_thread == AST_PTHREADT_NULL || !(change = ast_calloc(1, sizeof(*change) + strlen(device)))) {
/* we could not allocate a change struct, or */
/* there is no background thread, so process the change now */
#include "asterisk/manager.h"
#include "asterisk/utils.h"
#include "asterisk/adsi.h"
+#include "asterisk/devicestate.h"
#include "asterisk/monitor.h"
#ifdef __AST_DEBUG_MALLOC
static char *parkedcall = "ParkedCall";
+static int parkaddhints = 0; /*!< Add parking hints automatically */
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) */
"transfer to know the parking space). This application is always\n"
"registered internally and does not need to be explicitly added\n"
"into the dialplan, although you should include the 'parkedcalls'\n"
-"context.\n";
+"context (or the context specified in features.conf).\n\n"
+"If you set the PARKINGEXTEN variable to an extension in your\n"
+"parking context, park() will park the call on that extension, unless\n"
+"it already exists. In that case, execution will continue at next\n"
+"priority.\n" ;
static struct ast_app *monitor_app = NULL;
static int monitor_ok = 1;
struct ast_channel *chan; /*!< Parking channel */
struct timeval start; /*!< Time the parking started */
int parkingnum; /*!< Parking lot */
+ char parkingexten[AST_MAX_EXTENSION]; /*!< If set beforehand, parking extension used for this call */
char context[AST_MAX_CONTEXT]; /*!< Where to go if our parking time expires */
char exten[AST_MAX_EXTENSION];
int priority;
pthread_setschedparam(thread, SCHED_RR, &sched);
}
-static int adsi_announce_park(struct ast_channel *chan, int parkingnum)
+static int adsi_announce_park(struct ast_channel *chan, char *parkingexten)
{
int res;
int justify[5] = {ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT};
char tmp[256];
char *message[5] = {NULL, NULL, NULL, NULL, NULL};
- snprintf(tmp, sizeof(tmp), "Parked on %d", parkingnum);
+ snprintf(tmp, sizeof(tmp), "Parked on %s", parkingexten);
message[0] = tmp;
res = adsi_load_session(chan, NULL, 0, 1);
if (res == -1)
return adsi_print(chan, message, justify, 1);
}
+/*! \brief Notify metermaids that we've changed an extension */
+static void notify_metermaids(char *exten, char *context)
+{
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "Notification of state change to metermaids %s@%s\n", exten, context);
+
+ /* Send notification to devicestate subsystem */
+ ast_device_state_changed("park:%s@%s", exten, context);
+ return;
+}
+
+/*! \brief metermaids callback from devicestate.c */
+static int metermaidstate(const char *data)
+{
+ int res = AST_DEVICE_INVALID;
+ char *context = ast_strdupa(data);
+ char *exten;
+
+ exten = strsep(&context, "@");
+ if (!context)
+ return res;
+
+ if (option_debug > 3)
+ ast_log(LOG_DEBUG, "Checking state of exten %s in context %s\n", exten, context);
+
+ res = ast_exists_extension(NULL, context, exten, 1, NULL);
+
+ if (!res)
+ return AST_DEVICE_NOT_INUSE;
+ else
+ return AST_DEVICE_INUSE;
+}
+
/*! \brief Park a call
\note We put the user in the parking list, then wake up the parking thread to be sure it looks
after these channels too */
int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout)
{
struct parkeduser *pu, *cur;
- int i,x,parking_range;
- char exten[AST_MAX_EXTENSION];
+ int i, x = -1, parking_range;
struct ast_context *con;
+ const char *parkingexten;
+ /* Allocate memory for parking data */
if (!(pu = ast_calloc(1, sizeof(*pu))))
return -1;
+ /* Lock parking lot */
ast_mutex_lock(&parking_lock);
- parking_range = parking_stop - parking_start+1;
- for (i = 0; i < parking_range; i++) {
- x = (i + parking_offset) % parking_range + parking_start;
- cur = parkinglot;
- while(cur) {
- if (cur->parkingnum == x)
+ /* Check for channel variable PARKINGEXTEN */
+ parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN");
+ if (!ast_strlen_zero(parkingexten)) {
+ if (ast_exists_extension(NULL, parking_con, parkingexten, 1, NULL)) {
+ 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));
+ } else {
+ /* Select parking space within range */
+ parking_range = parking_stop - parking_start+1;
+ for (i = 0; i < parking_range; i++) {
+ x = (i + parking_offset) % parking_range + parking_start;
+ cur = parkinglot;
+ while(cur) {
+ if (cur->parkingnum == x)
+ break;
+ cur = cur->next;
+ }
+ if (!cur)
break;
- cur = cur->next;
}
- if (!cur)
- break;
- }
- if (!(i < parking_range)) {
- ast_log(LOG_WARNING, "No more parking spaces\n");
- free(pu);
- ast_mutex_unlock(&parking_lock);
- return -1;
+ if (!(i < parking_range)) {
+ ast_log(LOG_WARNING, "No more parking spaces\n");
+ free(pu);
+ ast_mutex_unlock(&parking_lock);
+ return -1;
+ }
+ /* Set pointer for next parking */
+ if (parkfindnext)
+ parking_offset = x - parking_start + 1;
}
- if (parkfindnext)
- parking_offset = x - parking_start + 1;
+
chan->appl = "Parked Call";
chan->data = NULL;
pu->chan = chan;
- /* Start music on hold */
+
+ /* Start music on hold if we have two different channels */
if (chan != peer) {
ast_indicate(pu->chan, AST_CONTROL_HOLD); /* Indicate to peer that we're on hold */
ast_moh_start(pu->chan, NULL);
pu->parkingtime = (timeout > 0) ? timeout : parkingtime;
if (extout)
*extout = x;
+
if (peer)
ast_copy_string(pu->peername, peer->name, sizeof(pu->peername));
pu->priority = chan->macropriority ? chan->macropriority : chan->priority;
pu->next = parkinglot;
parkinglot = pu;
+
/* If parking a channel directly, don't quiet yet get parking running on it */
if (peer == chan)
pu->notquiteyet = 1;
/* Wake up the (presumably select()ing) thread */
pthread_kill(parking_thread, SIGURG);
if (option_verbose > 1)
- ast_verbose(VERBOSE_PREFIX_2 "Parked %s on %d. Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000));
+ ast_verbose(VERBOSE_PREFIX_2 "Parked %s on %d@%s. Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, parking_con, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000));
+ if (pu->parkingnum != -1)
+ snprintf(pu->parkingexten, sizeof(pu->parkingexten), "%d", x);
manager_event(EVENT_FLAG_CALL, "ParkedCall",
- "Exten: %d\r\n"
+ "Exten: %s\r\n"
"Channel: %s\r\n"
"From: %s\r\n"
"Timeout: %ld\r\n"
"CallerID: %s\r\n"
"CallerIDName: %s\r\n",
- pu->parkingnum, pu->chan->name, peer ? peer->name : "",
+ pu->parkingexten, pu->chan->name, peer ? peer->name : "",
(long)pu->start.tv_sec + (long)(pu->parkingtime/1000) - (long)time(NULL),
S_OR(pu->chan->cid.cid_num, "<unknown>"),
S_OR(pu->chan->cid.cid_name, "<unknown>")
);
if (peer && adsipark && adsi_available(peer)) {
- adsi_announce_park(peer, pu->parkingnum);
+ adsi_announce_park(peer, pu->parkingexten); /* Only supports parking numbers */
adsi_unload_session(peer);
}
if (!con) /* Still no context? Bad */
ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
else { /* Add extension to context */
- snprintf(exten, sizeof(exten), "%d", x);
- ast_add_extension2(con, 1, exten, 1, NULL, NULL, parkedcall, strdup(exten), FREE, registrar);
+ if (!ast_add_extension2(con, 1, pu->parkingexten, 1, NULL, NULL, parkedcall, strdup(pu->parkingexten), FREE, registrar))
+ notify_metermaids(pu->parkingexten, parking_con);
}
/* Tell the peer channel the number of the parking space */
- if (peer)
+ if (peer && !pu->parkingnum == -1) /* Only say number if it's a number */
ast_say_digits(peer, pu->parkingnum, "", peer->language);
if (pu->notquiteyet) {
/* Wake up parking thread if we're really done */
#define FEATURE_SENSE_CHAN (1 << 0)
#define FEATURE_SENSE_PEER (1 << 1)
-/*
+/*! \brief
* set caller and callee according to the direction
*/
static void set_peers(struct ast_channel **caller, struct ast_channel **callee,
}
}
+/*! \brief support routing for one touch call parking */
static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
{
struct ast_channel *parker;
}
if (!monitor_app && !(monitor_app = pbx_findapp("Monitor"))) {
- monitor_ok=0;
+ monitor_ok = 0;
ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n");
return -1;
}
return res;
}
+/*! \brief Find the context for the transfer */
static const char *real_ctx(struct ast_channel *transferer, struct ast_channel *transferee)
{
const char *s = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT");
free(feature);
}
+/*! \brief Remove all features in the list */
static void ast_unregister_features(void)
{
struct ast_call_feature *feature;
return res;
}
-static void post_manager_event(const char *s, int num, struct ast_channel *chan)
+static void post_manager_event(const char *s, char *parkingexten, struct ast_channel *chan)
{
manager_event(EVENT_FLAG_CALL, s,
- "Exten: %d\r\n"
+ "Exten: %s\r\n"
"Channel: %s\r\n"
"CallerID: %s\r\n"
"CallerIDName: %s\r\n\r\n",
- num, chan->name,
+ parkingexten,
+ chan->name,
S_OR(chan->cid.cid_num, "<unknown>"),
S_OR(chan->cid.cid_name, "<unknown>")
);
pl = NULL;
pu = parkinglot;
/* navigate the list with prev-cur pointers to support removals */
- while(pu) {
+ while (pu) {
struct ast_channel *chan = pu->chan; /* shorthand */
int tms; /* timeout for this item */
int x; /* fd index in channel */
con = ast_context_find(parking_con_dial);
if (!con) {
con = ast_context_create(NULL, parking_con_dial, registrar);
- if (!con) {
+ if (!con)
ast_log(LOG_ERROR, "Parking dial context '%s' does not exist and unable to create\n", parking_con_dial);
- }
}
if (con) {
char returnexten[AST_MAX_EXTENSION];
set_c_e_p(chan, pu->context, pu->exten, pu->priority);
}
- post_manager_event("ParkedCallTimeOut", pu->parkingnum, chan);
+ post_manager_event("ParkedCallTimeOut", pu->parkingexten, chan);
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);
pu = pu->next;
con = ast_context_find(parking_con);
if (con) {
- char exten[AST_MAX_EXTENSION];
- snprintf(exten, sizeof(exten), "%d", pt->parkingnum);
- if (ast_context_remove_extension2(con, exten, 1, NULL))
+ if (ast_context_remove_extension2(con, pt->parkingexten, 1, NULL))
ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
+ else
+ notify_metermaids(pu->parkingexten, parking_con);
} else
ast_log(LOG_WARNING, "Whoa, no parking context?\n");
free(pt);
else
ast_clear_flag(chan, AST_FLAG_EXCEPTION);
chan->fdno = x;
+
/* See if they need servicing */
f = ast_read(chan);
if (!f || (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP)) {
if (f)
ast_frfree(f);
- post_manager_event("ParkedCallGiveUp", pu->parkingnum, chan);
+ post_manager_event("ParkedCallGiveUp", pu->parkingexten, chan);
/* There's a problem, hang them up*/
if (option_verbose > 1)
pu = pu->next;
con = ast_context_find(parking_con);
if (con) {
- char exten[AST_MAX_EXTENSION];
- snprintf(exten, sizeof(exten), "%d", pt->parkingnum);
- if (ast_context_remove_extension2(con, exten, 1, NULL))
+ 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
ast_log(LOG_WARNING, "Whoa, no parking context?\n");
free(pt);
return NULL; /* Never reached */
}
+/*! \brief Park a call */
static int park_call_exec(struct ast_channel *chan, void *data)
{
/* Data is unused at the moment but could contain a parking
where this call should return */
strcpy(chan->exten, "s");
chan->priority = 1;
+ /* Answer if call is not up */
if (chan->_state != AST_STATE_UP)
res = ast_answer(chan);
+ /* Sleep to allow VoIP streams to settle down */
if (!res)
res = ast_safe_sleep(chan, 1000);
+ /* Park the call */
if (!res)
res = ast_park_call(chan, chan, 0, NULL);
LOCAL_USER_REMOVE(u);
return res;
}
+/*! \brief Pickup parked call */
static int park_exec(struct ast_channel *chan, void *data)
{
int res=0;
struct ast_bridge_config config;
if (!data) {
- ast_log(LOG_WARNING, "Park requires an argument (extension number)\n");
+ ast_log(LOG_WARNING, "Parkedcall requires an argument (extension number)\n");
return -1;
}
LOCAL_USER_ADD(u);
peer = pu->chan;
con = ast_context_find(parking_con);
if (con) {
- char exten[AST_MAX_EXTENSION];
- snprintf(exten, sizeof(exten), "%d", pu->parkingnum);
- if (ast_context_remove_extension2(con, exten, 1, NULL))
+ if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL))
ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
+ else
+ notify_metermaids(pu->parkingexten, parking_con);
} else
ast_log(LOG_WARNING, "Whoa, no parking context?\n");
manager_event(EVENT_FLAG_CALL, "UnParkedCall",
- "Exten: %d\r\n"
+ "Exten: %s\r\n"
"Channel: %s\r\n"
"From: %s\r\n"
"CallerID: %s\r\n"
"CallerIDName: %s\r\n",
- pu->parkingnum, pu->chan->name, chan->name,
+ pu->parkingexten, pu->chan->name, chan->name,
S_OR(pu->chan->cid.cid_num, "<unknown>"),
S_OR(pu->chan->cid.cid_name, "<unknown>")
);
ast_mutex_lock(&parking_lock);
for (cur = parkinglot; cur; cur = cur->next) {
- ast_cli(fd, "%4d %25s (%-15s %-12s %-4d) %6lds\n"
- ,cur->parkingnum, cur->chan->name, cur->context, cur->exten
+ ast_cli(fd, "%-10.10s %25s (%-15s %-12s %-4d) %6lds\n"
+ ,cur->parkingexten, cur->chan->name, cur->context, cur->exten
,cur->priority, cur->start.tv_sec + (cur->parkingtime/1000) - time(NULL));
numparked++;
char idText[256] = "";
if (!ast_strlen_zero(id))
- snprintf(idText,256,"ActionID: %s\r\n",id);
+ snprintf(idText, 256, "ActionID: %s\r\n", id);
astman_send_ack(s, m, "Parked calls will follow");
return res;
}
+/*! \brief Add parking hints for all defined parking lots */
+static void park_add_hints(char *context, int start, int stop)
+{
+ int numext;
+ char device[AST_MAX_EXTENSION];
+ char exten[10];
+
+ for (numext = start; numext <= stop; numext++) {
+ snprintf(exten, sizeof(exten), "%d", numext);
+ snprintf(device, sizeof(device), "park:%s@%s", exten, context);
+ ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar);
+ }
+}
+
+
static int load_config(void)
{
int start = 0, end = 0;
+ int res;
struct ast_context *con = NULL;
struct ast_config *cfg = NULL;
struct ast_variable *var = NULL;
parking_stop = 750;
parkfindnext = 0;
adsipark = 0;
+ parkaddhints = 0;
transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
}
} else if (!strcasecmp(var->name, "findslot")) {
parkfindnext = (!strcasecmp(var->value, "next"));
+ } else if (!strcasecmp(var->name, "parkinghints")) {
+ parkaddhints = ast_true(var->value);
} else if (!strcasecmp(var->name, "adsipark")) {
adsipark = ast_true(var->value);
} else if (!strcasecmp(var->name, "transferdigittimeout")) {
/* Remove the old parking extension */
if (!ast_strlen_zero(old_parking_con) && (con = ast_context_find(old_parking_con))) {
- ast_context_remove_extension2(con, old_parking_ext, 1, registrar);
+ if(ast_context_remove_extension2(con, old_parking_ext, 1, registrar))
+ notify_metermaids(old_parking_ext, old_parking_con);
if (option_debug)
ast_log(LOG_DEBUG, "Removed old parking extension %s@%s\n", old_parking_ext, old_parking_con);
}
ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
return -1;
}
- return ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, strdup(""), FREE, registrar);
+ res = ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, strdup(""), FREE, registrar);
+ if (parkaddhints)
+ park_add_hints(parking_con, parking_start, parking_stop);
+ if (!res)
+ notify_metermaids(ast_parking_ext(), parking_con);
+ return res;
+
}
static int reload(void *mod)
ast_manager_register2("Park", EVENT_FLAG_CALL, manager_park,
"Park a channel", mandescr_park);
}
+
+ res |= ast_devstate_prov_add("Park", metermaidstate);
+
return res;
}
ast_cli_unregister(&showfeatures);
ast_cli_unregister(&showparked);
ast_unregister_application(parkcall);
+ ast_devstate_prov_del("Park");
return ast_unregister_application(parkedcall);
}