2013-05-14 21:45:08 +00:00
|
|
|
/*
|
|
|
|
* Asterisk -- An open source telephony toolkit.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2013, Digium, Inc.
|
|
|
|
*
|
|
|
|
* David M. Lee, II <dlee@digium.com>
|
|
|
|
*
|
|
|
|
* See http://www.asterisk.org for more information about
|
|
|
|
* the Asterisk project. Please do not directly contact
|
|
|
|
* any of the maintainers of this project for assistance;
|
|
|
|
* the project provides a web site, mailing lists and IRC
|
|
|
|
* channels for your use.
|
|
|
|
*
|
|
|
|
* This program is free software, distributed under the terms of
|
|
|
|
* the GNU General Public License Version 2. See the LICENSE file
|
|
|
|
* at the top of the source tree.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*! \file
|
|
|
|
*
|
|
|
|
* \brief Stasis application control support.
|
|
|
|
*
|
|
|
|
* \author David M. Lee, II <dlee@digium.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "asterisk.h"
|
|
|
|
|
|
|
|
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|
|
|
|
|
|
|
#include "asterisk/stasis_channels.h"
|
|
|
|
|
|
|
|
#include "command.h"
|
|
|
|
#include "control.h"
|
2013-07-01 18:19:15 +00:00
|
|
|
#include "asterisk/dial.h"
|
2013-07-25 04:06:32 +00:00
|
|
|
#include "asterisk/bridge.h"
|
2013-08-13 15:27:32 +00:00
|
|
|
#include "asterisk/bridge_after.h"
|
2013-07-25 04:06:32 +00:00
|
|
|
#include "asterisk/bridge_basic.h"
|
2013-07-18 16:03:12 +00:00
|
|
|
#include "asterisk/frame.h"
|
2013-06-26 19:29:57 +00:00
|
|
|
#include "asterisk/pbx.h"
|
2013-07-19 19:40:27 +00:00
|
|
|
#include "asterisk/musiconhold.h"
|
2013-11-01 14:38:21 +00:00
|
|
|
#include "asterisk/app.h"
|
2013-05-14 21:45:08 +00:00
|
|
|
|
|
|
|
struct stasis_app_control {
|
2013-08-13 15:27:32 +00:00
|
|
|
ast_cond_t wait_cond;
|
2013-05-14 21:45:08 +00:00
|
|
|
/*! Queue of commands to dispatch on the channel */
|
|
|
|
struct ao2_container *command_queue;
|
|
|
|
/*!
|
|
|
|
* The associated channel.
|
|
|
|
* Be very careful with the threading associated w/ manipulating
|
|
|
|
* the channel.
|
|
|
|
*/
|
|
|
|
struct ast_channel *channel;
|
2013-08-13 15:27:32 +00:00
|
|
|
/*!
|
|
|
|
* When a channel is in a bridge, the bridge that it is in.
|
|
|
|
*/
|
|
|
|
struct ast_bridge *bridge;
|
|
|
|
/*!
|
|
|
|
* Holding place for channel's PBX while imparted to a bridge.
|
|
|
|
*/
|
|
|
|
struct ast_pbx *pbx;
|
|
|
|
/*!
|
|
|
|
* When set, /c app_stasis should exit and continue in the dialplan.
|
|
|
|
*/
|
|
|
|
int is_done:1;
|
2013-05-14 21:45:08 +00:00
|
|
|
};
|
|
|
|
|
2013-08-13 15:27:32 +00:00
|
|
|
static void control_dtor(void *obj)
|
|
|
|
{
|
|
|
|
struct stasis_app_control *control = obj;
|
|
|
|
|
|
|
|
ao2_cleanup(control->command_queue);
|
|
|
|
ast_cond_destroy(&control->wait_cond);
|
|
|
|
}
|
|
|
|
|
2013-05-14 21:45:08 +00:00
|
|
|
struct stasis_app_control *control_create(struct ast_channel *channel)
|
|
|
|
{
|
2013-08-13 15:27:32 +00:00
|
|
|
RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
|
|
|
|
int res;
|
2013-05-14 21:45:08 +00:00
|
|
|
|
2013-08-13 15:27:32 +00:00
|
|
|
control = ao2_alloc(sizeof(*control), control_dtor);
|
2013-05-14 21:45:08 +00:00
|
|
|
if (!control) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-08-13 15:27:32 +00:00
|
|
|
res = ast_cond_init(&control->wait_cond, NULL);
|
|
|
|
if (res != 0) {
|
|
|
|
ast_log(LOG_ERROR, "Error initializing ast_cond_t: %s\n",
|
|
|
|
strerror(errno));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-05-23 20:11:35 +00:00
|
|
|
control->command_queue = ao2_container_alloc_list(
|
|
|
|
AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, NULL);
|
2013-05-14 21:45:08 +00:00
|
|
|
|
2013-07-19 19:35:21 +00:00
|
|
|
if (!control->command_queue) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-05-14 21:45:08 +00:00
|
|
|
control->channel = channel;
|
|
|
|
|
2013-08-13 15:27:32 +00:00
|
|
|
ao2_ref(control, +1);
|
2013-05-14 21:45:08 +00:00
|
|
|
return control;
|
|
|
|
}
|
|
|
|
|
2013-08-13 15:27:32 +00:00
|
|
|
static void *noop_cb(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-05-14 21:45:08 +00:00
|
|
|
static struct stasis_app_command *exec_command(
|
|
|
|
struct stasis_app_control *control, stasis_app_command_cb command_fn,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
RAII_VAR(struct stasis_app_command *, command, NULL, ao2_cleanup);
|
|
|
|
|
2013-08-13 15:27:32 +00:00
|
|
|
command_fn = command_fn ? : noop_cb;
|
|
|
|
|
2013-05-14 21:45:08 +00:00
|
|
|
command = command_create(command_fn, data);
|
|
|
|
if (!command) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-08-13 15:27:32 +00:00
|
|
|
ao2_lock(control->command_queue);
|
|
|
|
ao2_link_flags(control->command_queue, command, OBJ_NOLOCK);
|
|
|
|
ast_cond_signal(&control->wait_cond);
|
|
|
|
ao2_unlock(control->command_queue);
|
2013-05-14 21:45:08 +00:00
|
|
|
|
|
|
|
ao2_ref(command, +1);
|
|
|
|
return command;
|
|
|
|
}
|
|
|
|
|
2013-07-01 18:19:15 +00:00
|
|
|
struct stasis_app_control_dial_data {
|
|
|
|
char endpoint[AST_CHANNEL_NAME];
|
|
|
|
int timeout;
|
|
|
|
};
|
|
|
|
|
2013-10-02 15:24:16 +00:00
|
|
|
static void *app_control_add_channel_to_bridge(
|
|
|
|
struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data);
|
|
|
|
|
2013-07-01 18:19:15 +00:00
|
|
|
static void *app_control_dial(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
RAII_VAR(struct ast_dial *, dial, ast_dial_create(), ast_dial_destroy);
|
|
|
|
RAII_VAR(struct stasis_app_control_dial_data *, dial_data, data, ast_free);
|
|
|
|
enum ast_dial_result res;
|
|
|
|
char *tech, *resource;
|
|
|
|
struct ast_channel *new_chan;
|
2013-10-02 15:24:16 +00:00
|
|
|
RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
|
2013-07-01 18:19:15 +00:00
|
|
|
|
|
|
|
tech = dial_data->endpoint;
|
|
|
|
if (!(resource = strchr(tech, '/'))) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
*resource++ = '\0';
|
|
|
|
|
|
|
|
if (!dial) {
|
|
|
|
ast_log(LOG_ERROR, "Failed to create dialing structure.\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ast_dial_append(dial, tech, resource) < 0) {
|
|
|
|
ast_log(LOG_ERROR, "Failed to add %s/%s to dialing structure.\n", tech, resource);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ast_dial_set_global_timeout(dial, dial_data->timeout);
|
|
|
|
|
|
|
|
res = ast_dial_run(dial, NULL, 0);
|
|
|
|
if (res != AST_DIAL_RESULT_ANSWERED || !(new_chan = ast_dial_answered_steal(dial))) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(bridge = ast_bridge_basic_new())) {
|
|
|
|
ast_log(LOG_ERROR, "Failed to create basic bridge.\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-09-13 22:19:23 +00:00
|
|
|
if (ast_bridge_impart(bridge, new_chan, NULL, NULL,
|
|
|
|
AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
|
|
|
|
ast_hangup(new_chan);
|
|
|
|
} else {
|
2013-10-02 15:24:16 +00:00
|
|
|
app_control_add_channel_to_bridge(control, chan, bridge);
|
2013-09-13 22:19:23 +00:00
|
|
|
}
|
2013-07-01 18:19:15 +00:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-10-02 15:24:16 +00:00
|
|
|
int stasis_app_control_dial(struct stasis_app_control *control, const char *endpoint, const char *exten, const char *context,
|
|
|
|
int timeout)
|
2013-07-01 18:19:15 +00:00
|
|
|
{
|
|
|
|
struct stasis_app_control_dial_data *dial_data;
|
|
|
|
|
|
|
|
if (!(dial_data = ast_calloc(1, sizeof(*dial_data)))) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-10-02 15:24:16 +00:00
|
|
|
if (!ast_strlen_zero(endpoint)) {
|
|
|
|
ast_copy_string(dial_data->endpoint, endpoint, sizeof(dial_data->endpoint));
|
|
|
|
} else if (!ast_strlen_zero(exten) && !ast_strlen_zero(context)) {
|
|
|
|
snprintf(dial_data->endpoint, sizeof(dial_data->endpoint), "Local/%s@%s", exten, context);
|
|
|
|
} else {
|
|
|
|
return -1;
|
|
|
|
}
|
2013-07-01 18:19:15 +00:00
|
|
|
|
|
|
|
if (timeout > 0) {
|
|
|
|
dial_data->timeout = timeout * 1000;
|
|
|
|
} else if (timeout == -1) {
|
|
|
|
dial_data->timeout = -1;
|
|
|
|
} else {
|
|
|
|
dial_data->timeout = 30000;
|
|
|
|
}
|
|
|
|
|
|
|
|
stasis_app_send_command_async(control, app_control_dial, dial_data);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-08-05 16:59:13 +00:00
|
|
|
int stasis_app_control_add_role(struct stasis_app_control *control, const char *role)
|
|
|
|
{
|
|
|
|
return ast_channel_add_bridge_role(control->channel, role);
|
|
|
|
}
|
|
|
|
|
|
|
|
void stasis_app_control_clear_roles(struct stasis_app_control *control)
|
|
|
|
{
|
|
|
|
ast_channel_clear_bridge_roles(control->channel);
|
|
|
|
}
|
|
|
|
|
2013-05-14 21:45:08 +00:00
|
|
|
int control_is_done(struct stasis_app_control *control)
|
|
|
|
{
|
|
|
|
/* Called from stasis_app_exec thread; no lock needed */
|
|
|
|
return control->is_done;
|
|
|
|
}
|
|
|
|
|
2013-06-26 19:29:57 +00:00
|
|
|
struct stasis_app_control_continue_data {
|
|
|
|
char context[AST_MAX_CONTEXT];
|
|
|
|
char extension[AST_MAX_EXTENSION];
|
|
|
|
int priority;
|
|
|
|
};
|
|
|
|
|
2013-05-14 21:45:08 +00:00
|
|
|
static void *app_control_continue(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
2013-06-26 19:29:57 +00:00
|
|
|
RAII_VAR(struct stasis_app_control_continue_data *, continue_data, data, ast_free);
|
|
|
|
|
2013-08-13 15:27:32 +00:00
|
|
|
ast_assert(control->channel != NULL);
|
|
|
|
|
|
|
|
/* If we're in a Stasis bridge, depart it before going back to the
|
|
|
|
* dialplan */
|
|
|
|
if (stasis_app_get_bridge(control)) {
|
|
|
|
ast_bridge_depart(control->channel);
|
|
|
|
}
|
|
|
|
|
2013-05-14 21:45:08 +00:00
|
|
|
/* Called from stasis_app_exec thread; no lock needed */
|
2013-06-26 19:29:57 +00:00
|
|
|
ast_explicit_goto(control->channel, continue_data->context, continue_data->extension, continue_data->priority);
|
|
|
|
|
2013-05-14 21:45:08 +00:00
|
|
|
control->is_done = 1;
|
2013-06-26 19:29:57 +00:00
|
|
|
|
2013-05-14 21:45:08 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-06-26 19:29:57 +00:00
|
|
|
int stasis_app_control_continue(struct stasis_app_control *control, const char *context, const char *extension, int priority)
|
2013-05-14 21:45:08 +00:00
|
|
|
{
|
2013-06-26 19:29:57 +00:00
|
|
|
struct stasis_app_control_continue_data *continue_data;
|
|
|
|
|
|
|
|
if (!(continue_data = ast_calloc(1, sizeof(*continue_data)))) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
ast_copy_string(continue_data->context, S_OR(context, ""), sizeof(continue_data->context));
|
|
|
|
ast_copy_string(continue_data->extension, S_OR(extension, ""), sizeof(continue_data->extension));
|
|
|
|
if (priority > 0) {
|
|
|
|
continue_data->priority = priority;
|
|
|
|
} else {
|
|
|
|
continue_data->priority = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
stasis_app_send_command_async(control, app_control_continue, continue_data);
|
|
|
|
|
|
|
|
return 0;
|
2013-05-14 21:45:08 +00:00
|
|
|
}
|
|
|
|
|
2013-11-01 14:38:21 +00:00
|
|
|
struct stasis_app_control_dtmf_data {
|
|
|
|
int before;
|
|
|
|
int between;
|
|
|
|
unsigned int duration;
|
|
|
|
int after;
|
|
|
|
char dtmf[];
|
|
|
|
};
|
|
|
|
|
|
|
|
static void *app_control_dtmf(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
RAII_VAR(struct stasis_app_control_dtmf_data *, dtmf_data, data, ast_free);
|
|
|
|
|
|
|
|
if (dtmf_data->before) {
|
|
|
|
ast_safe_sleep(chan, dtmf_data->before);
|
|
|
|
}
|
|
|
|
|
|
|
|
ast_dtmf_stream(chan, NULL, dtmf_data->dtmf, dtmf_data->between, dtmf_data->duration);
|
|
|
|
|
|
|
|
if (dtmf_data->after) {
|
|
|
|
ast_safe_sleep(chan, dtmf_data->after);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int stasis_app_control_dtmf(struct stasis_app_control *control, const char *dtmf, int before, int between, unsigned int duration, int after)
|
|
|
|
{
|
|
|
|
struct stasis_app_control_dtmf_data *dtmf_data;
|
|
|
|
|
|
|
|
if (!(dtmf_data = ast_calloc(1, sizeof(*dtmf_data) + strlen(dtmf) + 1))) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
dtmf_data->before = before;
|
|
|
|
dtmf_data->between = between;
|
|
|
|
dtmf_data->duration = duration;
|
|
|
|
dtmf_data->after = after;
|
|
|
|
strcpy(dtmf_data->dtmf, dtmf);
|
|
|
|
|
|
|
|
stasis_app_send_command_async(control, app_control_dtmf, dtmf_data);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *app_control_ring(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
ast_indicate(control->channel, AST_CONTROL_RINGING);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int stasis_app_control_ring(struct stasis_app_control *control)
|
|
|
|
{
|
|
|
|
stasis_app_send_command_async(control, app_control_ring, NULL);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-11-13 23:11:32 +00:00
|
|
|
static void *app_control_ring_stop(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
ast_indicate(control->channel, -1);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int stasis_app_control_ring_stop(struct stasis_app_control *control)
|
|
|
|
{
|
|
|
|
stasis_app_send_command_async(control, app_control_ring_stop, NULL);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-07-18 16:03:12 +00:00
|
|
|
struct stasis_app_control_mute_data {
|
|
|
|
enum ast_frame_type frametype;
|
|
|
|
unsigned int direction;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void *app_control_mute(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
RAII_VAR(struct stasis_app_control_mute_data *, mute_data, data, ast_free);
|
|
|
|
SCOPED_CHANNELLOCK(lockvar, chan);
|
|
|
|
|
|
|
|
ast_channel_suppress(control->channel, mute_data->direction, mute_data->frametype);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int stasis_app_control_mute(struct stasis_app_control *control, unsigned int direction, enum ast_frame_type frametype)
|
|
|
|
{
|
|
|
|
struct stasis_app_control_mute_data *mute_data;
|
|
|
|
|
|
|
|
if (!(mute_data = ast_calloc(1, sizeof(*mute_data)))) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
mute_data->direction = direction;
|
|
|
|
mute_data->frametype = frametype;
|
|
|
|
|
|
|
|
stasis_app_send_command_async(control, app_control_mute, mute_data);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *app_control_unmute(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
RAII_VAR(struct stasis_app_control_mute_data *, mute_data, data, ast_free);
|
|
|
|
SCOPED_CHANNELLOCK(lockvar, chan);
|
|
|
|
|
|
|
|
ast_channel_unsuppress(control->channel, mute_data->direction, mute_data->frametype);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int stasis_app_control_unmute(struct stasis_app_control *control, unsigned int direction, enum ast_frame_type frametype)
|
|
|
|
{
|
|
|
|
struct stasis_app_control_mute_data *mute_data;
|
|
|
|
|
|
|
|
if (!(mute_data = ast_calloc(1, sizeof(*mute_data)))) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
mute_data->direction = direction;
|
|
|
|
mute_data->frametype = frametype;
|
|
|
|
|
|
|
|
stasis_app_send_command_async(control, app_control_unmute, mute_data);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-07-08 14:46:20 +00:00
|
|
|
char *stasis_app_control_get_channel_var(struct stasis_app_control *control, const char *variable)
|
|
|
|
{
|
|
|
|
RAII_VAR(struct ast_str *, tmp, ast_str_create(32), ast_free);
|
2013-07-08 15:11:07 +00:00
|
|
|
SCOPED_CHANNELLOCK(lockvar, control->channel);
|
2013-07-08 14:46:20 +00:00
|
|
|
|
|
|
|
if (!tmp) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (variable[strlen(variable) - 1] == ')') {
|
|
|
|
if (ast_func_read2(control->channel, variable, &tmp, 0)) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!ast_str_retrieve_variable(&tmp, 0, control->channel, NULL, variable)) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ast_strdup(ast_str_buffer(tmp));
|
|
|
|
}
|
|
|
|
|
|
|
|
int stasis_app_control_set_channel_var(struct stasis_app_control *control, const char *variable, const char *value)
|
|
|
|
{
|
|
|
|
return pbx_builtin_setvar_helper(control->channel, variable, value);
|
|
|
|
}
|
|
|
|
|
2013-07-01 18:56:21 +00:00
|
|
|
static void *app_control_hold(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
ast_indicate(control->channel, AST_CONTROL_HOLD);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void stasis_app_control_hold(struct stasis_app_control *control)
|
|
|
|
{
|
|
|
|
stasis_app_send_command_async(control, app_control_hold, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *app_control_unhold(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
ast_indicate(control->channel, AST_CONTROL_UNHOLD);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void stasis_app_control_unhold(struct stasis_app_control *control)
|
|
|
|
{
|
|
|
|
stasis_app_send_command_async(control, app_control_unhold, NULL);
|
|
|
|
}
|
|
|
|
|
2013-07-19 19:40:27 +00:00
|
|
|
static void *app_control_moh_start(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
char *moh_class = data;
|
|
|
|
|
|
|
|
ast_moh_start(chan, moh_class, NULL);
|
|
|
|
|
|
|
|
ast_free(moh_class);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void stasis_app_control_moh_start(struct stasis_app_control *control, const char *moh_class)
|
|
|
|
{
|
|
|
|
char *data = NULL;
|
|
|
|
|
|
|
|
if (!ast_strlen_zero(moh_class)) {
|
|
|
|
data = ast_strdup(moh_class);
|
|
|
|
}
|
|
|
|
|
|
|
|
stasis_app_send_command_async(control, app_control_moh_start, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *app_control_moh_stop(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
ast_moh_stop(chan);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void stasis_app_control_moh_stop(struct stasis_app_control *control)
|
|
|
|
{
|
|
|
|
stasis_app_send_command_async(control, app_control_moh_stop, NULL);
|
|
|
|
}
|
|
|
|
|
2013-05-14 21:45:08 +00:00
|
|
|
struct ast_channel_snapshot *stasis_app_control_get_snapshot(
|
|
|
|
const struct stasis_app_control *control)
|
|
|
|
{
|
|
|
|
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
|
|
|
|
struct ast_channel_snapshot *snapshot;
|
|
|
|
|
2013-08-01 13:49:34 +00:00
|
|
|
msg = stasis_cache_get(ast_channel_cache(), ast_channel_snapshot_type(),
|
2013-05-14 21:45:08 +00:00
|
|
|
stasis_app_control_get_channel_id(control));
|
|
|
|
if (!msg) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
snapshot = stasis_message_data(msg);
|
|
|
|
ast_assert(snapshot != NULL);
|
|
|
|
|
|
|
|
ao2_ref(snapshot, +1);
|
|
|
|
return snapshot;
|
|
|
|
}
|
|
|
|
|
|
|
|
void *stasis_app_send_command(struct stasis_app_control *control,
|
|
|
|
stasis_app_command_cb command_fn, void *data)
|
|
|
|
{
|
|
|
|
RAII_VAR(struct stasis_app_command *, command, NULL, ao2_cleanup);
|
|
|
|
|
|
|
|
if (control == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
command = exec_command(control, command_fn, data);
|
|
|
|
if (!command) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return command_join(command);
|
|
|
|
}
|
|
|
|
|
|
|
|
int stasis_app_send_command_async(struct stasis_app_control *control,
|
|
|
|
stasis_app_command_cb command_fn, void *data)
|
|
|
|
{
|
|
|
|
RAII_VAR(struct stasis_app_command *, command, NULL, ao2_cleanup);
|
|
|
|
|
|
|
|
if (control == NULL) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
command = exec_command(control, command_fn, data);
|
|
|
|
if (!command) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-08-13 15:27:32 +00:00
|
|
|
struct ast_bridge *stasis_app_get_bridge(struct stasis_app_control *control)
|
|
|
|
{
|
|
|
|
if (!control) {
|
|
|
|
return NULL;
|
|
|
|
} else {
|
|
|
|
SCOPED_AO2LOCK(lock, control);
|
|
|
|
return control->bridge;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-01 12:13:09 +00:00
|
|
|
static void *bridge_channel_depart(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
RAII_VAR(struct ast_bridge_channel *, bridge_channel, data, ao2_cleanup);
|
|
|
|
|
|
|
|
{
|
|
|
|
SCOPED_CHANNELLOCK(lock, chan);
|
|
|
|
|
|
|
|
if (bridge_channel != ast_channel_internal_bridge_channel(chan)) {
|
|
|
|
ast_debug(3, "%s: Channel is no longer in departable state\n",
|
|
|
|
ast_channel_uniqueid(chan));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ast_debug(3, "%s: Channel departing bridge\n",
|
|
|
|
ast_channel_uniqueid(chan));
|
|
|
|
|
|
|
|
ast_bridge_depart(chan);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
2013-08-13 15:27:32 +00:00
|
|
|
|
|
|
|
static void bridge_after_cb(struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
struct stasis_app_control *control = data;
|
|
|
|
SCOPED_AO2LOCK(lock, control);
|
2013-11-01 12:13:09 +00:00
|
|
|
struct ast_bridge_channel *bridge_channel;
|
2013-08-13 15:27:32 +00:00
|
|
|
|
|
|
|
ast_debug(3, "%s, %s: Channel leaving bridge\n",
|
|
|
|
ast_channel_uniqueid(chan), control->bridge->uniqueid);
|
|
|
|
|
|
|
|
ast_assert(chan == control->channel);
|
|
|
|
|
|
|
|
/* Restore the channel's PBX */
|
|
|
|
ast_channel_pbx_set(control->channel, control->pbx);
|
|
|
|
control->pbx = NULL;
|
|
|
|
|
|
|
|
/* No longer in the bridge */
|
|
|
|
control->bridge = NULL;
|
|
|
|
|
2013-11-01 12:13:09 +00:00
|
|
|
/* Get the bridge channel so we don't depart from the wrong bridge */
|
|
|
|
ast_channel_lock(chan);
|
|
|
|
bridge_channel = ast_channel_get_bridge_channel(chan);
|
|
|
|
ast_channel_unlock(chan);
|
|
|
|
|
|
|
|
/* Depart this channel from the bridge using the command queue if possible */
|
|
|
|
if (stasis_app_send_command_async(control, bridge_channel_depart, bridge_channel)) {
|
|
|
|
ao2_cleanup(bridge_channel);
|
|
|
|
}
|
2013-08-13 15:27:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void bridge_after_cb_failed(enum ast_bridge_after_cb_reason reason,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct stasis_app_control *control = data;
|
|
|
|
|
|
|
|
bridge_after_cb(control->channel, data);
|
|
|
|
|
|
|
|
ast_debug(3, " reason: %s\n",
|
|
|
|
ast_bridge_after_cb_reason_string(reason));
|
|
|
|
}
|
|
|
|
|
2013-08-23 17:19:02 +00:00
|
|
|
static int OK = 0;
|
|
|
|
static int FAIL = -1;
|
|
|
|
|
2013-08-13 15:27:32 +00:00
|
|
|
static void *app_control_add_channel_to_bridge(
|
|
|
|
struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
struct ast_bridge *bridge = data;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
if (!control || !bridge) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ast_debug(3, "%s: Adding to bridge %s\n",
|
|
|
|
stasis_app_control_get_channel_id(control),
|
|
|
|
bridge->uniqueid);
|
|
|
|
|
|
|
|
ast_assert(chan != NULL);
|
|
|
|
|
|
|
|
/* Depart whatever Stasis bridge we're currently in. */
|
|
|
|
if (stasis_app_get_bridge(control)) {
|
|
|
|
/* Note that it looks like there's a race condition here, since
|
|
|
|
* we don't have control locked. But this happens from the
|
|
|
|
* control callback thread, so there won't be any other
|
|
|
|
* concurrent attempts to bridge.
|
|
|
|
*/
|
|
|
|
ast_bridge_depart(chan);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res = ast_bridge_set_after_callback(chan, bridge_after_cb,
|
|
|
|
bridge_after_cb_failed, control);
|
|
|
|
if (res != 0) {
|
|
|
|
ast_log(LOG_ERROR, "Error setting after-bridge callback\n");
|
2013-08-23 17:19:02 +00:00
|
|
|
return &FAIL;
|
2013-08-13 15:27:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
/* pbx and bridge are modified by the bridging impart thread.
|
|
|
|
* It shouldn't happen concurrently, but we still need to lock
|
|
|
|
* for the memory fence.
|
|
|
|
*/
|
|
|
|
SCOPED_AO2LOCK(lock, control);
|
|
|
|
|
|
|
|
/* Save off the channel's PBX */
|
|
|
|
ast_assert(control->pbx == NULL);
|
|
|
|
if (!control->pbx) {
|
|
|
|
control->pbx = ast_channel_pbx(chan);
|
|
|
|
ast_channel_pbx_set(chan, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
res = ast_bridge_impart(bridge,
|
|
|
|
chan,
|
|
|
|
NULL, /* swap channel */
|
|
|
|
NULL, /* features */
|
2013-09-13 22:19:23 +00:00
|
|
|
AST_BRIDGE_IMPART_CHAN_DEPARTABLE);
|
2013-08-13 15:27:32 +00:00
|
|
|
if (res != 0) {
|
|
|
|
ast_log(LOG_ERROR, "Error adding channel to bridge\n");
|
|
|
|
ast_channel_pbx_set(chan, control->pbx);
|
|
|
|
control->pbx = NULL;
|
2013-08-23 17:19:02 +00:00
|
|
|
return &FAIL;
|
2013-08-13 15:27:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ast_assert(stasis_app_get_bridge(control) == NULL);
|
|
|
|
control->bridge = bridge;
|
|
|
|
}
|
2013-08-23 17:19:02 +00:00
|
|
|
return &OK;
|
2013-08-13 15:27:32 +00:00
|
|
|
}
|
|
|
|
|
2013-08-23 17:19:02 +00:00
|
|
|
int stasis_app_control_add_channel_to_bridge(
|
2013-08-13 15:27:32 +00:00
|
|
|
struct stasis_app_control *control, struct ast_bridge *bridge)
|
|
|
|
{
|
2013-08-23 17:19:02 +00:00
|
|
|
int *res;
|
2013-08-13 15:27:32 +00:00
|
|
|
ast_debug(3, "%s: Sending channel add_to_bridge command\n",
|
|
|
|
stasis_app_control_get_channel_id(control));
|
2013-08-23 17:19:02 +00:00
|
|
|
res = stasis_app_send_command(control,
|
2013-08-13 15:27:32 +00:00
|
|
|
app_control_add_channel_to_bridge, bridge);
|
2013-08-23 17:19:02 +00:00
|
|
|
return *res;
|
2013-08-13 15:27:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void *app_control_remove_channel_from_bridge(
|
|
|
|
struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan, void *data)
|
|
|
|
{
|
|
|
|
struct ast_bridge *bridge = data;
|
|
|
|
|
|
|
|
if (!control) {
|
2013-08-23 17:19:02 +00:00
|
|
|
return &FAIL;
|
2013-08-13 15:27:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* We should only depart from our own bridge */
|
|
|
|
ast_debug(3, "%s: Departing bridge %s\n",
|
|
|
|
stasis_app_control_get_channel_id(control),
|
|
|
|
bridge->uniqueid);
|
|
|
|
|
|
|
|
if (bridge != stasis_app_get_bridge(control)) {
|
|
|
|
ast_log(LOG_WARNING, "%s: Not in bridge %s; not removing\n",
|
|
|
|
stasis_app_control_get_channel_id(control),
|
|
|
|
bridge->uniqueid);
|
2013-08-23 17:19:02 +00:00
|
|
|
return &FAIL;
|
2013-08-13 15:27:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ast_bridge_depart(chan);
|
2013-08-23 17:19:02 +00:00
|
|
|
return &OK;
|
2013-08-13 15:27:32 +00:00
|
|
|
}
|
|
|
|
|
2013-08-23 17:19:02 +00:00
|
|
|
int stasis_app_control_remove_channel_from_bridge(
|
2013-08-13 15:27:32 +00:00
|
|
|
struct stasis_app_control *control, struct ast_bridge *bridge)
|
|
|
|
{
|
2013-08-23 17:19:02 +00:00
|
|
|
int *res;
|
2013-08-13 15:27:32 +00:00
|
|
|
ast_debug(3, "%s: Sending channel remove_from_bridge command\n",
|
|
|
|
stasis_app_control_get_channel_id(control));
|
2013-08-23 17:19:02 +00:00
|
|
|
res = stasis_app_send_command(control,
|
2013-08-13 15:27:32 +00:00
|
|
|
app_control_remove_channel_from_bridge, bridge);
|
2013-08-23 17:19:02 +00:00
|
|
|
return *res;
|
2013-08-13 15:27:32 +00:00
|
|
|
}
|
|
|
|
|
2013-05-14 21:45:08 +00:00
|
|
|
const char *stasis_app_control_get_channel_id(
|
|
|
|
const struct stasis_app_control *control)
|
|
|
|
{
|
|
|
|
return ast_channel_uniqueid(control->channel);
|
|
|
|
}
|
|
|
|
|
|
|
|
void stasis_app_control_publish(
|
|
|
|
struct stasis_app_control *control, struct stasis_message *message)
|
|
|
|
{
|
|
|
|
if (!control || !control->channel || !message) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
stasis_publish(ast_channel_topic(control->channel), message);
|
|
|
|
}
|
|
|
|
|
2013-05-23 20:21:16 +00:00
|
|
|
int stasis_app_control_queue_control(struct stasis_app_control *control,
|
|
|
|
enum ast_control_frame_type frame_type)
|
|
|
|
{
|
|
|
|
return ast_queue_control(control->channel, frame_type);
|
|
|
|
}
|
|
|
|
|
2013-05-14 21:45:08 +00:00
|
|
|
int control_dispatch_all(struct stasis_app_control *control,
|
|
|
|
struct ast_channel *chan)
|
|
|
|
{
|
|
|
|
int count = 0;
|
|
|
|
struct ao2_iterator i;
|
|
|
|
void *obj;
|
|
|
|
|
|
|
|
ast_assert(control->channel == chan);
|
|
|
|
|
|
|
|
i = ao2_iterator_init(control->command_queue, AO2_ITERATOR_UNLINK);
|
|
|
|
|
|
|
|
while ((obj = ao2_iterator_next(&i))) {
|
|
|
|
RAII_VAR(struct stasis_app_command *, command, obj, ao2_cleanup);
|
|
|
|
command_invoke(command, control, chan);
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
|
|
|
|
ao2_iterator_destroy(&i);
|
|
|
|
return count;
|
|
|
|
}
|
2013-06-10 13:07:11 +00:00
|
|
|
|
2013-08-13 15:27:32 +00:00
|
|
|
void control_wait(struct stasis_app_control *control)
|
2013-06-10 13:07:11 +00:00
|
|
|
{
|
2013-08-13 15:27:32 +00:00
|
|
|
if (!control) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ast_assert(control->command_queue != NULL);
|
|
|
|
|
|
|
|
ao2_lock(control->command_queue);
|
|
|
|
while (ao2_container_count(control->command_queue) == 0) {
|
|
|
|
int res = ast_cond_wait(&control->wait_cond,
|
|
|
|
ao2_object_get_lockaddr(control->command_queue));
|
|
|
|
if (res < 0) {
|
|
|
|
ast_log(LOG_ERROR, "Error waiting on command queue\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ao2_unlock(control->command_queue);
|
2013-06-10 13:07:11 +00:00
|
|
|
}
|