Merge "res_stasis: Add ability to switch applications." into 16
This commit is contained in:
commit
afe16021ef
14
CHANGES
14
CHANGES
|
@ -20,6 +20,20 @@ ARI
|
|||
types defined in the "disallowed" list are not sent to the application. Note
|
||||
that if a type is specified in both lists "disallowed" takes precedence.
|
||||
|
||||
* A new REST API call has been added: 'move'. It follows the format
|
||||
'channels/{channelId}/move' and can be used to move channels from one application
|
||||
to another without needing to exit back into the dialplan. An application must be
|
||||
specified, but the passing a list of arguments to the new application is optional.
|
||||
An example call would look like this:
|
||||
|
||||
client.channels.move(channelId=chan.id, app='ari-example', appArgs='a,b,c')
|
||||
|
||||
If the channel was inside of a bridge when switching applications, it will
|
||||
remain there. If the application specified cannot be moved to, then the channel
|
||||
will remain in the current application and an event will be triggered named
|
||||
"ApplicationMoveFailed", which will provide the destination application's name
|
||||
and the channel information.
|
||||
|
||||
res_pjsip
|
||||
------------------
|
||||
* A new configuration parameter "taskprocessor_overload_trigger" has been
|
||||
|
|
|
@ -501,6 +501,20 @@ void stasis_app_control_clear_roles(struct stasis_app_control *control);
|
|||
*/
|
||||
int stasis_app_control_continue(struct stasis_app_control *control, const char *context, const char *extension, int priority);
|
||||
|
||||
/*!
|
||||
* \brief Exit \c res_stasis and move to another Stasis application.
|
||||
*
|
||||
* If the channel is no longer in \c res_stasis, this function does nothing.
|
||||
*
|
||||
* \param control Control for \c res_stasis
|
||||
* \param app_name The name of the application to switch to
|
||||
* \param app_args The list of arguments to pass to the application
|
||||
*
|
||||
* \return 0 for success
|
||||
* \return -1 for error
|
||||
*/
|
||||
int stasis_app_control_move(struct stasis_app_control *control, const char *app_name, const char *app_args);
|
||||
|
||||
/*!
|
||||
* \brief Redirect a channel in \c res_stasis to a particular endpoint
|
||||
*
|
||||
|
|
|
@ -2044,6 +2044,127 @@ ari_validator ast_ari_validate_mailbox_fn(void)
|
|||
return ast_ari_validate_mailbox;
|
||||
}
|
||||
|
||||
int ast_ari_validate_application_move_failed(struct ast_json *json)
|
||||
{
|
||||
int res = 1;
|
||||
struct ast_json_iter *iter;
|
||||
int has_type = 0;
|
||||
int has_application = 0;
|
||||
int has_args = 0;
|
||||
int has_channel = 0;
|
||||
int has_destination = 0;
|
||||
|
||||
for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
|
||||
if (strcmp("asterisk_id", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
prop_is_valid = ast_ari_validate_string(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field asterisk_id failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
has_type = 1;
|
||||
prop_is_valid = ast_ari_validate_string(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field type failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
has_application = 1;
|
||||
prop_is_valid = ast_ari_validate_string(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field application failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
prop_is_valid = ast_ari_validate_date(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field timestamp failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("args", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
has_args = 1;
|
||||
prop_is_valid = ast_ari_validate_list(
|
||||
ast_json_object_iter_value(iter),
|
||||
ast_ari_validate_string);
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field args failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
has_channel = 1;
|
||||
prop_is_valid = ast_ari_validate_channel(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field channel failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
if (strcmp("destination", ast_json_object_iter_key(iter)) == 0) {
|
||||
int prop_is_valid;
|
||||
has_destination = 1;
|
||||
prop_is_valid = ast_ari_validate_string(
|
||||
ast_json_object_iter_value(iter));
|
||||
if (!prop_is_valid) {
|
||||
ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field destination failed validation\n");
|
||||
res = 0;
|
||||
}
|
||||
} else
|
||||
{
|
||||
ast_log(LOG_ERROR,
|
||||
"ARI ApplicationMoveFailed has undocumented field %s\n",
|
||||
ast_json_object_iter_key(iter));
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_type) {
|
||||
ast_log(LOG_ERROR, "ARI ApplicationMoveFailed missing required field type\n");
|
||||
res = 0;
|
||||
}
|
||||
|
||||
if (!has_application) {
|
||||
ast_log(LOG_ERROR, "ARI ApplicationMoveFailed missing required field application\n");
|
||||
res = 0;
|
||||
}
|
||||
|
||||
if (!has_args) {
|
||||
ast_log(LOG_ERROR, "ARI ApplicationMoveFailed missing required field args\n");
|
||||
res = 0;
|
||||
}
|
||||
|
||||
if (!has_channel) {
|
||||
ast_log(LOG_ERROR, "ARI ApplicationMoveFailed missing required field channel\n");
|
||||
res = 0;
|
||||
}
|
||||
|
||||
if (!has_destination) {
|
||||
ast_log(LOG_ERROR, "ARI ApplicationMoveFailed missing required field destination\n");
|
||||
res = 0;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ari_validator ast_ari_validate_application_move_failed_fn(void)
|
||||
{
|
||||
return ast_ari_validate_application_move_failed;
|
||||
}
|
||||
|
||||
int ast_ari_validate_application_replaced(struct ast_json *json)
|
||||
{
|
||||
int res = 1;
|
||||
|
@ -5111,6 +5232,9 @@ int ast_ari_validate_event(struct ast_json *json)
|
|||
if (strcmp("Event", discriminator) == 0) {
|
||||
/* Self type; fall through */
|
||||
} else
|
||||
if (strcmp("ApplicationMoveFailed", discriminator) == 0) {
|
||||
return ast_ari_validate_application_move_failed(json);
|
||||
} else
|
||||
if (strcmp("ApplicationReplaced", discriminator) == 0) {
|
||||
return ast_ari_validate_application_replaced(json);
|
||||
} else
|
||||
|
@ -5309,6 +5433,9 @@ int ast_ari_validate_message(struct ast_json *json)
|
|||
if (strcmp("Message", discriminator) == 0) {
|
||||
/* Self type; fall through */
|
||||
} else
|
||||
if (strcmp("ApplicationMoveFailed", discriminator) == 0) {
|
||||
return ast_ari_validate_application_move_failed(json);
|
||||
} else
|
||||
if (strcmp("ApplicationReplaced", discriminator) == 0) {
|
||||
return ast_ari_validate_application_replaced(json);
|
||||
} else
|
||||
|
|
|
@ -623,6 +623,24 @@ int ast_ari_validate_mailbox(struct ast_json *json);
|
|||
*/
|
||||
ari_validator ast_ari_validate_mailbox_fn(void);
|
||||
|
||||
/*!
|
||||
* \brief Validator for ApplicationMoveFailed.
|
||||
*
|
||||
* Notification that trying to move a channel to another Stasis application failed.
|
||||
*
|
||||
* \param json JSON object to validate.
|
||||
* \returns True (non-zero) if valid.
|
||||
* \returns False (zero) if invalid.
|
||||
*/
|
||||
int ast_ari_validate_application_move_failed(struct ast_json *json);
|
||||
|
||||
/*!
|
||||
* \brief Function pointer to ast_ari_validate_application_move_failed().
|
||||
*
|
||||
* See \ref ast_ari_model_validators.h for more details.
|
||||
*/
|
||||
ari_validator ast_ari_validate_application_move_failed_fn(void);
|
||||
|
||||
/*!
|
||||
* \brief Validator for ApplicationReplaced.
|
||||
*
|
||||
|
@ -1528,6 +1546,14 @@ ari_validator ast_ari_validate_application_fn(void);
|
|||
* - name: string (required)
|
||||
* - new_messages: int (required)
|
||||
* - old_messages: int (required)
|
||||
* ApplicationMoveFailed
|
||||
* - asterisk_id: string
|
||||
* - type: string (required)
|
||||
* - application: string (required)
|
||||
* - timestamp: Date
|
||||
* - args: List[string] (required)
|
||||
* - channel: Channel (required)
|
||||
* - destination: string (required)
|
||||
* ApplicationReplaced
|
||||
* - asterisk_id: string
|
||||
* - type: string (required)
|
||||
|
|
|
@ -217,6 +217,26 @@ void ast_ari_channels_continue_in_dialplan(
|
|||
ast_ari_response_no_content(response);
|
||||
}
|
||||
|
||||
void ast_ari_channels_move(struct ast_variable *headers,
|
||||
struct ast_ari_channels_move_args *args,
|
||||
struct ast_ari_response *response)
|
||||
{
|
||||
RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);
|
||||
|
||||
control = find_control(response, args->channel_id);
|
||||
if (!control) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stasis_app_control_move(control, args->app, args->app_args)) {
|
||||
ast_ari_response_error(response, 500, "Internal Server Error",
|
||||
"Failed to switch Stasis applications");
|
||||
return;
|
||||
}
|
||||
|
||||
ast_ari_response_no_content(response);
|
||||
}
|
||||
|
||||
void ast_ari_channels_redirect(struct ast_variable *headers,
|
||||
struct ast_ari_channels_redirect_args *args,
|
||||
struct ast_ari_response *response)
|
||||
|
|
|
@ -261,6 +261,34 @@ int ast_ari_channels_continue_in_dialplan_parse_body(
|
|||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_channels_continue_in_dialplan(struct ast_variable *headers, struct ast_ari_channels_continue_in_dialplan_args *args, struct ast_ari_response *response);
|
||||
/*! Argument struct for ast_ari_channels_move() */
|
||||
struct ast_ari_channels_move_args {
|
||||
/*! Channel's id */
|
||||
const char *channel_id;
|
||||
/*! The channel will be passed to this Stasis application. */
|
||||
const char *app;
|
||||
/*! The application arguments to pass to the Stasis application provided by 'app'. */
|
||||
const char *app_args;
|
||||
};
|
||||
/*!
|
||||
* \brief Body parsing function for /channels/{channelId}/move.
|
||||
* \param body The JSON body from which to parse parameters.
|
||||
* \param[out] args The args structure to parse into.
|
||||
* \retval zero on success
|
||||
* \retval non-zero on failure
|
||||
*/
|
||||
int ast_ari_channels_move_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_channels_move_args *args);
|
||||
|
||||
/*!
|
||||
* \brief Move the channel from one Stasis application to another.
|
||||
*
|
||||
* \param headers HTTP headers
|
||||
* \param args Swagger parameters
|
||||
* \param[out] response HTTP response
|
||||
*/
|
||||
void ast_ari_channels_move(struct ast_variable *headers, struct ast_ari_channels_move_args *args, struct ast_ari_response *response);
|
||||
/*! Argument struct for ast_ari_channels_redirect() */
|
||||
struct ast_ari_channels_redirect_args {
|
||||
/*! Channel's id */
|
||||
|
|
|
@ -775,6 +775,95 @@ static void ast_ari_channels_continue_in_dialplan_cb(
|
|||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
int ast_ari_channels_move_parse_body(
|
||||
struct ast_json *body,
|
||||
struct ast_ari_channels_move_args *args)
|
||||
{
|
||||
struct ast_json *field;
|
||||
/* Parse query parameters out of it */
|
||||
field = ast_json_object_get(body, "app");
|
||||
if (field) {
|
||||
args->app = ast_json_string_get(field);
|
||||
}
|
||||
field = ast_json_object_get(body, "appArgs");
|
||||
if (field) {
|
||||
args->app_args = ast_json_string_get(field);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Parameter parsing callback for /channels/{channelId}/move.
|
||||
* \param get_params GET parameters in the HTTP request.
|
||||
* \param path_vars Path variables extracted from the request.
|
||||
* \param headers HTTP headers.
|
||||
* \param[out] response Response to the HTTP request.
|
||||
*/
|
||||
static void ast_ari_channels_move_cb(
|
||||
struct ast_tcptls_session_instance *ser,
|
||||
struct ast_variable *get_params, struct ast_variable *path_vars,
|
||||
struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
|
||||
{
|
||||
struct ast_ari_channels_move_args args = {};
|
||||
struct ast_variable *i;
|
||||
#if defined(AST_DEVMODE)
|
||||
int is_valid;
|
||||
int code;
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
for (i = get_params; i; i = i->next) {
|
||||
if (strcmp(i->name, "app") == 0) {
|
||||
args.app = (i->value);
|
||||
} else
|
||||
if (strcmp(i->name, "appArgs") == 0) {
|
||||
args.app_args = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
for (i = path_vars; i; i = i->next) {
|
||||
if (strcmp(i->name, "channelId") == 0) {
|
||||
args.channel_id = (i->value);
|
||||
} else
|
||||
{}
|
||||
}
|
||||
if (ast_ari_channels_move_parse_body(body, &args)) {
|
||||
ast_ari_response_alloc_failed(response);
|
||||
goto fin;
|
||||
}
|
||||
ast_ari_channels_move(headers, &args, response);
|
||||
#if defined(AST_DEVMODE)
|
||||
code = response->response_code;
|
||||
|
||||
switch (code) {
|
||||
case 0: /* Implementation is still a stub, or the code wasn't set */
|
||||
is_valid = response->message == NULL;
|
||||
break;
|
||||
case 500: /* Internal Server Error */
|
||||
case 501: /* Not Implemented */
|
||||
case 404: /* Channel not found */
|
||||
case 409: /* Channel not in a Stasis application */
|
||||
is_valid = 1;
|
||||
break;
|
||||
default:
|
||||
if (200 <= code && code <= 299) {
|
||||
is_valid = ast_ari_validate_void(
|
||||
response->message);
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/move\n", code);
|
||||
is_valid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_valid) {
|
||||
ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/move\n");
|
||||
ast_ari_response_error(response, 500,
|
||||
"Internal Server Error", "Response validation failed");
|
||||
}
|
||||
#endif /* AST_DEVMODE */
|
||||
|
||||
fin: __attribute__((unused))
|
||||
return;
|
||||
}
|
||||
|
@ -2680,6 +2769,15 @@ static struct stasis_rest_handlers channels_channelId_continue = {
|
|||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/channels.json */
|
||||
static struct stasis_rest_handlers channels_channelId_move = {
|
||||
.path_segment = "move",
|
||||
.callbacks = {
|
||||
[AST_HTTP_POST] = ast_ari_channels_move_cb,
|
||||
},
|
||||
.num_children = 0,
|
||||
.children = { }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/channels.json */
|
||||
static struct stasis_rest_handlers channels_channelId_redirect = {
|
||||
.path_segment = "redirect",
|
||||
.callbacks = {
|
||||
|
@ -2831,8 +2929,8 @@ static struct stasis_rest_handlers channels_channelId = {
|
|||
[AST_HTTP_POST] = ast_ari_channels_originate_with_id_cb,
|
||||
[AST_HTTP_DELETE] = ast_ari_channels_hangup_cb,
|
||||
},
|
||||
.num_children = 14,
|
||||
.children = { &channels_channelId_continue,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop,&channels_channelId_dial, }
|
||||
.num_children = 15,
|
||||
.children = { &channels_channelId_continue,&channels_channelId_move,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop,&channels_channelId_dial, }
|
||||
};
|
||||
/*! \brief REST handler for /api-docs/channels.json */
|
||||
static struct stasis_rest_handlers channels = {
|
||||
|
|
153
res/res_stasis.c
153
res/res_stasis.c
|
@ -1324,7 +1324,17 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
|
|||
|
||||
control = control_create(chan, app);
|
||||
if (!control) {
|
||||
ast_log(LOG_ERROR, "Allocated failed\n");
|
||||
ast_log(LOG_ERROR, "Control allocation failed or Stasis app '%s' not registered\n", app_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!control_app(control)) {
|
||||
ast_log(LOG_ERROR, "Stasis app '%s' not registered\n", app_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!app_is_active(control_app(control))) {
|
||||
ast_log(LOG_ERROR, "Stasis app '%s' not active\n", app_name);
|
||||
return -1;
|
||||
}
|
||||
ao2_link(app_controls, control);
|
||||
|
@ -1334,7 +1344,7 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
|
|||
return -1;
|
||||
}
|
||||
|
||||
res = send_start_msg(app, chan, argc, argv);
|
||||
res = send_start_msg(control_app(control), chan, argc, argv);
|
||||
if (res != 0) {
|
||||
ast_log(LOG_ERROR,
|
||||
"Error sending start message to '%s'\n", app_name);
|
||||
|
@ -1357,15 +1367,138 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
|
|||
break;
|
||||
}
|
||||
|
||||
/* control->next_app is only modified within the control thread, so this is safe */
|
||||
if (control_next_app(control)) {
|
||||
struct stasis_app *next_app = ao2_find(apps_registry, control_next_app(control), OBJ_SEARCH_KEY);
|
||||
|
||||
if (next_app && app_is_active(next_app)) {
|
||||
int idx;
|
||||
int next_argc;
|
||||
char **next_argv;
|
||||
|
||||
/* If something goes wrong in this conditional, res will need to be non-zero
|
||||
* so that the code below the exec loop knows something went wrong during a move.
|
||||
*/
|
||||
if (!stasis_app_channel_is_stasis_end_published(chan)) {
|
||||
res = has_masquerade_store(chan) && app_send_end_msg(control_app(control), chan);
|
||||
if (res != 0) {
|
||||
ast_log(LOG_ERROR,
|
||||
"Error sending end message to %s\n", stasis_app_name(control_app(control)));
|
||||
control_mark_done(control);
|
||||
ao2_ref(next_app, -1);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
remove_stasis_end_published(chan);
|
||||
}
|
||||
|
||||
/* This will ao2_bump next_app, and unref the previous app by 1 */
|
||||
control_set_app(control, next_app);
|
||||
|
||||
/* There's a chance that the previous application is ready for clean up, so go ahead
|
||||
* and do that now.
|
||||
*/
|
||||
cleanup();
|
||||
|
||||
/* We need to add another masquerade store, otherwise the leave message will
|
||||
* not show up for the correct application.
|
||||
*/
|
||||
if (add_masquerade_store(chan)) {
|
||||
ast_log(LOG_ERROR, "Failed to attach masquerade detector\n");
|
||||
res = -1;
|
||||
control_mark_done(control);
|
||||
ao2_ref(next_app, -1);
|
||||
break;
|
||||
}
|
||||
|
||||
/* We MUST get the size before the list, as control_next_app_args steals the elements
|
||||
* from the string vector.
|
||||
*/
|
||||
next_argc = control_next_app_args_size(control);
|
||||
next_argv = control_next_app_args(control);
|
||||
|
||||
res = send_start_msg(control_app(control), chan, next_argc, next_argv);
|
||||
|
||||
/* Even if res != 0, we still need to free the memory we got from control_argv */
|
||||
if (next_argv) {
|
||||
for (idx = 0; idx < next_argc; idx++) {
|
||||
ast_free(next_argv[idx]);
|
||||
}
|
||||
ast_free(next_argv);
|
||||
}
|
||||
|
||||
if (res != 0) {
|
||||
ast_log(LOG_ERROR,
|
||||
"Error sending start message to '%s'\n", stasis_app_name(control_app(control)));
|
||||
remove_masquerade_store(chan);
|
||||
control_mark_done(control);
|
||||
ao2_ref(next_app, -1);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Done switching applications, free memory and clean up */
|
||||
control_move_cleanup(control);
|
||||
} else {
|
||||
/* If we can't switch applications, do nothing */
|
||||
struct ast_json *msg;
|
||||
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
|
||||
|
||||
if (!next_app) {
|
||||
ast_log(LOG_ERROR, "Could not move to Stasis app '%s' - not registered\n",
|
||||
control_next_app(control));
|
||||
} else {
|
||||
ast_log(LOG_ERROR, "Could not move to Stasis app '%s' - not active\n",
|
||||
control_next_app(control));
|
||||
}
|
||||
|
||||
snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan));
|
||||
if (!snapshot) {
|
||||
ast_log(LOG_ERROR, "Could not get channel shapshot for '%s'\n",
|
||||
ast_channel_name(chan));
|
||||
} else {
|
||||
struct ast_json *json_args;
|
||||
int next_argc = control_next_app_args_size(control);
|
||||
char **next_argv = control_next_app_args(control);
|
||||
|
||||
msg = ast_json_pack("{s: s, s: o, s: s, s: []}",
|
||||
"type", "ApplicationMoveFailed",
|
||||
"channel", ast_channel_snapshot_to_json(snapshot, NULL),
|
||||
"destination", control_next_app(control),
|
||||
"args");
|
||||
json_args = ast_json_object_get(msg, "args");
|
||||
if (!json_args) {
|
||||
ast_log(LOG_ERROR, "Could not get args json array");
|
||||
} else {
|
||||
int r = 0;
|
||||
int idx;
|
||||
for (idx = 0; idx < next_argc; ++idx) {
|
||||
r = ast_json_array_append(json_args,
|
||||
ast_json_string_create(next_argv[idx]));
|
||||
if (r != 0) {
|
||||
ast_log(LOG_ERROR, "Error appending to ApplicationMoveFailed message\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == 0) {
|
||||
app_send(control_app(control), msg);
|
||||
}
|
||||
}
|
||||
ast_json_unref(msg);
|
||||
}
|
||||
}
|
||||
control_move_cleanup(control);
|
||||
ao2_cleanup(next_app);
|
||||
}
|
||||
|
||||
last_bridge = bridge;
|
||||
bridge = ao2_bump(stasis_app_get_bridge(control));
|
||||
|
||||
if (bridge != last_bridge) {
|
||||
if (last_bridge) {
|
||||
app_unsubscribe_bridge(app, last_bridge);
|
||||
app_unsubscribe_bridge(control_app(control), last_bridge);
|
||||
}
|
||||
if (bridge) {
|
||||
app_subscribe_bridge(app, bridge);
|
||||
app_subscribe_bridge(control_app(control), bridge);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1425,18 +1558,18 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
|
|||
}
|
||||
|
||||
if (stasis_app_get_bridge(control)) {
|
||||
app_unsubscribe_bridge(app, stasis_app_get_bridge(control));
|
||||
app_unsubscribe_bridge(control_app(control), stasis_app_get_bridge(control));
|
||||
}
|
||||
ao2_cleanup(bridge);
|
||||
|
||||
/* Only publish a stasis_end event if it hasn't already been published */
|
||||
if (!stasis_app_channel_is_stasis_end_published(chan)) {
|
||||
if (!res && !stasis_app_channel_is_stasis_end_published(chan)) {
|
||||
/* A masquerade has occurred and this message will be wrong so it
|
||||
* has already been sent elsewhere. */
|
||||
res = has_masquerade_store(chan) && app_send_end_msg(app, chan);
|
||||
res = has_masquerade_store(chan) && app_send_end_msg(control_app(control), chan);
|
||||
if (res != 0) {
|
||||
ast_log(LOG_ERROR,
|
||||
"Error sending end message to %s\n", app_name);
|
||||
"Error sending end message to %s\n", stasis_app_name(control_app(control)));
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
|
@ -1456,12 +1589,10 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
|
|||
/* The control needs to be removed from the controls container in
|
||||
* case a new PBX is started and ends up coming back into Stasis.
|
||||
*/
|
||||
ao2_cleanup(app);
|
||||
app = NULL;
|
||||
control_unlink(control);
|
||||
control = NULL;
|
||||
|
||||
if (!ast_channel_pbx(chan)) {
|
||||
if (!res && !ast_channel_pbx(chan)) {
|
||||
int chan_hungup;
|
||||
|
||||
/* The ASYNCGOTO softhangup flag may have broken the channel out of
|
||||
|
|
|
@ -83,9 +83,19 @@ struct stasis_app_control {
|
|||
*/
|
||||
struct ast_silence_generator *silgen;
|
||||
/*!
|
||||
* The app for which this control was created
|
||||
* The app for which this control is currently controlling.
|
||||
* This can change through the use of the /channels/{channelId}/move
|
||||
* command.
|
||||
*/
|
||||
struct stasis_app *app;
|
||||
/*!
|
||||
* The name of the next Stasis application to move to.
|
||||
*/
|
||||
char *next_app;
|
||||
/*!
|
||||
* The list of arguments to pass to StasisStart when moving to another app.
|
||||
*/
|
||||
AST_VECTOR(, char *) next_app_args;
|
||||
/*!
|
||||
* When set, /c app_stasis should exit and continue in the dialplan.
|
||||
*/
|
||||
|
@ -101,6 +111,8 @@ static void control_dtor(void *obj)
|
|||
ast_channel_cleanup(control->channel);
|
||||
ao2_cleanup(control->app);
|
||||
|
||||
control_move_cleanup(control);
|
||||
|
||||
ast_cond_destroy(&control->wait_cond);
|
||||
AST_LIST_HEAD_DESTROY(&control->add_rules);
|
||||
AST_LIST_HEAD_DESTROY(&control->remove_rules);
|
||||
|
@ -141,6 +153,9 @@ struct stasis_app_control *control_create(struct ast_channel *channel, struct st
|
|||
return NULL;
|
||||
}
|
||||
|
||||
control->next_app = NULL;
|
||||
AST_VECTOR_INIT(&control->next_app_args, 0);
|
||||
|
||||
return control;
|
||||
}
|
||||
|
||||
|
@ -391,6 +406,73 @@ int stasis_app_control_continue(struct stasis_app_control *control, const char *
|
|||
return 0;
|
||||
}
|
||||
|
||||
struct stasis_app_control_move_data {
|
||||
char *app_name;
|
||||
char *app_args;
|
||||
};
|
||||
|
||||
static int app_control_move(struct stasis_app_control *control,
|
||||
struct ast_channel *chan, void *data)
|
||||
{
|
||||
struct stasis_app_control_move_data *move_data = data;
|
||||
|
||||
control->next_app = ast_strdup(move_data->app_name);
|
||||
if (!control->next_app) {
|
||||
ast_log(LOG_ERROR, "Allocation failed for next app\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (move_data->app_args) {
|
||||
char *token;
|
||||
|
||||
while ((token = strtok_r(move_data->app_args, ",", &move_data->app_args))) {
|
||||
int res;
|
||||
char *arg;
|
||||
|
||||
if (!(arg = ast_strdup(token))) {
|
||||
ast_log(LOG_ERROR, "Allocation failed for next app arg\n");
|
||||
control_move_cleanup(control);
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = AST_VECTOR_APPEND(&control->next_app_args, arg);
|
||||
if (res) {
|
||||
ast_log(LOG_ERROR, "Failed to append arg to next app args\n");
|
||||
ast_free(arg);
|
||||
control_move_cleanup(control);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int stasis_app_control_move(struct stasis_app_control *control, const char *app_name, const char *app_args)
|
||||
{
|
||||
struct stasis_app_control_move_data *move_data;
|
||||
size_t size;
|
||||
|
||||
size = sizeof(*move_data) + strlen(app_name) + strlen(app_args) + 2;
|
||||
if (!(move_data = ast_calloc(1, size))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
move_data->app_name = (char *)move_data + sizeof(*move_data);
|
||||
move_data->app_args = move_data->app_name + strlen(app_name) + 1;
|
||||
|
||||
strcpy(move_data->app_name, app_name); /* Safe */
|
||||
if (app_args) {
|
||||
strcpy(move_data->app_args, app_args); /* Safe */
|
||||
} else {
|
||||
move_data->app_args = NULL;
|
||||
}
|
||||
|
||||
stasis_app_send_command_async(control, app_control_move, move_data, ast_free_ptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int app_control_redirect(struct stasis_app_control *control,
|
||||
struct ast_channel *chan, void *data)
|
||||
{
|
||||
|
@ -1590,3 +1672,32 @@ void stasis_app_control_shutdown(void)
|
|||
}
|
||||
ast_mutex_unlock(&dial_bridge_lock);
|
||||
}
|
||||
|
||||
void control_set_app(struct stasis_app_control *control, struct stasis_app *app)
|
||||
{
|
||||
ao2_cleanup(control->app);
|
||||
control->app = ao2_bump(app);
|
||||
}
|
||||
|
||||
char *control_next_app(struct stasis_app_control *control)
|
||||
{
|
||||
return control->next_app;
|
||||
}
|
||||
|
||||
void control_move_cleanup(struct stasis_app_control *control)
|
||||
{
|
||||
ast_free(control->next_app);
|
||||
control->next_app = NULL;
|
||||
|
||||
AST_VECTOR_RESET(&control->next_app_args, ast_free_ptr);
|
||||
}
|
||||
|
||||
char **control_next_app_args(struct stasis_app_control *control)
|
||||
{
|
||||
return AST_VECTOR_STEAL_ELEMENTS(&control->next_app_args);
|
||||
}
|
||||
|
||||
int control_next_app_args_size(struct stasis_app_control *control)
|
||||
{
|
||||
return AST_VECTOR_SIZE(&control->next_app_args);
|
||||
}
|
||||
|
|
|
@ -107,6 +107,58 @@ int control_prestart_dispatch_all(struct stasis_app_control *control,
|
|||
*/
|
||||
struct stasis_app *control_app(struct stasis_app_control *control);
|
||||
|
||||
/*!
|
||||
* \brief Set the application the control object belongs to
|
||||
*
|
||||
* \param control The control for the channel
|
||||
* \param app The application this control will now belong to
|
||||
*
|
||||
* \note This will unref control's previous app by 1, and bump app by 1
|
||||
*/
|
||||
void control_set_app(struct stasis_app_control *control, struct stasis_app *app);
|
||||
|
||||
/*!
|
||||
* \brief Returns the name of the application we are moving to
|
||||
*
|
||||
* \param control The control for the channel
|
||||
*
|
||||
* \return The name of the application we are moving to
|
||||
*/
|
||||
char *control_next_app(struct stasis_app_control *control);
|
||||
|
||||
/*!
|
||||
* \brief Free any memory that was allocated for switching applications via
|
||||
* /channels/{channelId}/move
|
||||
*
|
||||
* \param control The control for the channel
|
||||
*/
|
||||
void control_move_cleanup(struct stasis_app_control *control);
|
||||
|
||||
/*!
|
||||
* \brief Returns the list of arguments to pass to the application we are moving to
|
||||
*
|
||||
* \note If you wish to get the size of the list, control_next_app_args_size should be
|
||||
* called before this, as this function will steal the elements from the string vector
|
||||
* and set the size to 0.
|
||||
*
|
||||
* \param control The control for the channel
|
||||
*
|
||||
* \return The arguments to pass to the application we are moving to
|
||||
*/
|
||||
char **control_next_app_args(struct stasis_app_control *control);
|
||||
|
||||
/*!
|
||||
* \brief Returns the number of arguments to be passed to the application we are moving to
|
||||
*
|
||||
* \note This should always be called before control_next_app_args, as calling that function
|
||||
* will steal all elements from the string vector and set the size to 0.
|
||||
*
|
||||
* \param control The control for the channel
|
||||
*
|
||||
* \return The number of arguments to be passed to the application we are moving to
|
||||
*/
|
||||
int control_next_app_args_size(struct stasis_app_control *control);
|
||||
|
||||
/*!
|
||||
* \brief Command callback for adding a channel to a bridge
|
||||
*
|
||||
|
|
|
@ -515,6 +515,54 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/channels/{channelId}/move",
|
||||
"description": "Move the channel from one Stasis application to another.",
|
||||
"operations": [
|
||||
{
|
||||
"httpMethod": "POST",
|
||||
"summary": "Move the channel from one Stasis application to another.",
|
||||
"nickname": "move",
|
||||
"responseClass": "void",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "channelId",
|
||||
"description": "Channel's id",
|
||||
"paramType": "path",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "app",
|
||||
"description": "The channel will be passed to this Stasis application.",
|
||||
"paramType": "query",
|
||||
"required": true,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
},
|
||||
{
|
||||
"name": "appArgs",
|
||||
"description": "The application arguments to pass to the Stasis application provided by 'app'.",
|
||||
"paramType": "query",
|
||||
"required": false,
|
||||
"allowMultiple": false,
|
||||
"dataType": "string"
|
||||
}
|
||||
],
|
||||
"errorResponses": [
|
||||
{
|
||||
"code": "404",
|
||||
"reason": "Channel not found"
|
||||
},
|
||||
{
|
||||
"code": "409",
|
||||
"reason": "Channel not in a Stasis application"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/channels/{channelId}/redirect",
|
||||
"description": "Inform the channel that it should redirect itself to a different location. Note that this will almost certainly cause the channel to exit the application.",
|
||||
|
|
|
@ -159,6 +159,7 @@
|
|||
"RecordingStarted",
|
||||
"RecordingFinished",
|
||||
"RecordingFailed",
|
||||
"ApplicationMoveFailed",
|
||||
"ApplicationReplaced",
|
||||
"BridgeCreated",
|
||||
"BridgeDestroyed",
|
||||
|
@ -335,6 +336,25 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ApplicationMoveFailed": {
|
||||
"id": "ApplicationMoveFailed",
|
||||
"description": "Notification that trying to move a channel to another Stasis application failed.",
|
||||
"properties": {
|
||||
"channel": {
|
||||
"required": true,
|
||||
"type": "Channel"
|
||||
},
|
||||
"destination": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"args": {
|
||||
"required": true,
|
||||
"type": "List[string]",
|
||||
"description": "Arguments to the application"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApplicationReplaced": {
|
||||
"id": "ApplicationReplaced",
|
||||
"description": "Notification that another WebSocket has taken over for an application.\n\nAn application may only be subscribed to by a single WebSocket at a time. If multiple WebSockets attempt to subscribe to the same application, the newer WebSocket wins, and the older one receives this event.",
|
||||
|
|
Loading…
Reference in New Issue