<p>Benjamin Keith Ford has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/10960">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">res_stasis: Add ability to switch applications.<br><br>Added the ability to move between Stasis applications within Stasis.<br>This can be done by calling 'move' in an application, providing (at<br>minimum) the channel's id and the application to switch to. If the<br>application is not registered or active, nothing will happen and the<br>channel will remain in the current application, and an event will be<br>triggered to let the application know that the move failed. The event<br>name is "ApplicationMoveFailed", and provides the "destination" that the<br>channel was attempting to move to, as well as the usual channel<br>information. Optionally, a list of arguments can be passed to the<br>function call for the receiving application. A full example of a 'move'<br>call would look like this:<br><br>client.channels.move(channelId, app, appArgs)<br><br>The control object used to control the channel in Stasis can now switch<br>which application it belongs to, rather than belonging to one Stasis<br>application for its lifetime. This allows us to use the same control<br>object instead of having to tear down the current one and create<br>another.<br><br>ASTERISK-28267 #close<br><br>Change-Id: I43d12b10045a98a8d42541889b85695be26f288a<br>---<br>M CHANGES<br>M include/asterisk/stasis_app.h<br>M res/ari/ari_model_validators.c<br>M res/ari/ari_model_validators.h<br>M res/ari/resource_channels.c<br>M res/ari/resource_channels.h<br>M res/res_ari_channels.c<br>M res/res_stasis.c<br>M res/stasis/control.c<br>M res/stasis/control.h<br>M rest-api/api-docs/channels.json<br>M rest-api/api-docs/events.json<br>12 files changed, 707 insertions(+), 27 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/60/10960/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/CHANGES b/CHANGES</span><br><span>index f5deca9..3c3b39d 100644</span><br><span>--- a/CHANGES</span><br><span>+++ b/CHANGES</span><br><span>@@ -20,6 +20,20 @@</span><br><span> extension, and the 'h' extension. Any phone that registers under this context</span><br><span> will place all calls to the corresponding Stasis application.</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ * A new REST API call has been added: 'move'. It follows the format</span><br><span style="color: hsl(120, 100%, 40%);">+ 'channels/{channelId}/move' and can be used to move channels from one application</span><br><span style="color: hsl(120, 100%, 40%);">+ to another without needing to exit back into the dialplan. An application must be</span><br><span style="color: hsl(120, 100%, 40%);">+ specified, but the passing a list of arguments to the new application is optional.</span><br><span style="color: hsl(120, 100%, 40%);">+ An example call would look like this:</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ client.channels.move(channelId=chan.id, app='ari-example', appArgs='a,b,c')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ If the channel was inside of a bridge when switching applications, it will</span><br><span style="color: hsl(120, 100%, 40%);">+ remain there. If the application specified cannot be moved to, then the channel</span><br><span style="color: hsl(120, 100%, 40%);">+ will remain in the current application and an event will be triggered named</span><br><span style="color: hsl(120, 100%, 40%);">+ "ApplicationMoveFailed", which will provide the destination application's name</span><br><span style="color: hsl(120, 100%, 40%);">+ and the channel information.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> res_pjsip</span><br><span> ------------------</span><br><span> * Added "send_contact_status_on_update_registration" global configuration option</span><br><span>diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h</span><br><span>index b0829ab..3dd23db 100644</span><br><span>--- a/include/asterisk/stasis_app.h</span><br><span>+++ b/include/asterisk/stasis_app.h</span><br><span>@@ -491,6 +491,20 @@</span><br><span> int stasis_app_control_continue(struct stasis_app_control *control, const char *context, const char *extension, int priority);</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Exit \c res_stasis and move to another Stasis application.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * If the channel is no longer in \c res_stasis, this function does nothing.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param control Control for \c res_stasis</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param app_name The name of the application to switch to</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param app_args The list of arguments to pass to the application</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return 0 for success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return -1 for error</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int stasis_app_control_move(struct stasis_app_control *control, const char *app_name, const char *app_args);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span> * \brief Redirect a channel in \c res_stasis to a particular endpoint</span><br><span> *</span><br><span> * \param control Control for \c res_stasis</span><br><span>diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c</span><br><span>index a06a1f5..6aff552 100644</span><br><span>--- a/res/ari/ari_model_validators.c</span><br><span>+++ b/res/ari/ari_model_validators.c</span><br><span>@@ -2028,6 +2028,127 @@</span><br><span> return ast_ari_validate_mailbox;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+int ast_ari_validate_application_move_failed(struct ast_json *json)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ int res = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json_iter *iter;</span><br><span style="color: hsl(120, 100%, 40%);">+ int has_type = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ int has_application = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ int has_args = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ int has_channel = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ int has_destination = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp("asterisk_id", ast_json_object_iter_key(iter)) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ int prop_is_valid;</span><br><span style="color: hsl(120, 100%, 40%);">+ prop_is_valid = ast_ari_validate_string(</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_iter_value(iter));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!prop_is_valid) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field asterisk_id failed validation\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ int prop_is_valid;</span><br><span style="color: hsl(120, 100%, 40%);">+ has_type = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ prop_is_valid = ast_ari_validate_string(</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_iter_value(iter));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!prop_is_valid) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field type failed validation\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ int prop_is_valid;</span><br><span style="color: hsl(120, 100%, 40%);">+ has_application = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ prop_is_valid = ast_ari_validate_string(</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_iter_value(iter));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!prop_is_valid) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field application failed validation\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ int prop_is_valid;</span><br><span style="color: hsl(120, 100%, 40%);">+ prop_is_valid = ast_ari_validate_date(</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_iter_value(iter));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!prop_is_valid) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field timestamp failed validation\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp("args", ast_json_object_iter_key(iter)) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ int prop_is_valid;</span><br><span style="color: hsl(120, 100%, 40%);">+ has_args = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ prop_is_valid = ast_ari_validate_list(</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_iter_value(iter),</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_validate_string);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!prop_is_valid) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field args failed validation\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ int prop_is_valid;</span><br><span style="color: hsl(120, 100%, 40%);">+ has_channel = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ prop_is_valid = ast_ari_validate_channel(</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_iter_value(iter));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!prop_is_valid) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field channel failed validation\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp("destination", ast_json_object_iter_key(iter)) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ int prop_is_valid;</span><br><span style="color: hsl(120, 100%, 40%);">+ has_destination = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ prop_is_valid = ast_ari_validate_string(</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_iter_value(iter));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!prop_is_valid) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI ApplicationMoveFailed field destination failed validation\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+ "ARI ApplicationMoveFailed has undocumented field %s\n",</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_object_iter_key(iter));</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!has_type) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI ApplicationMoveFailed missing required field type\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!has_application) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI ApplicationMoveFailed missing required field application\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!has_args) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI ApplicationMoveFailed missing required field args\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!has_channel) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI ApplicationMoveFailed missing required field channel\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!has_destination) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "ARI ApplicationMoveFailed missing required field destination\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return res;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ari_validator ast_ari_validate_application_move_failed_fn(void)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ return ast_ari_validate_application_move_failed;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> int ast_ari_validate_application_replaced(struct ast_json *json)</span><br><span> {</span><br><span> int res = 1;</span><br><span>@@ -5095,6 +5216,9 @@</span><br><span> if (strcmp("Event", discriminator) == 0) {</span><br><span> /* Self type; fall through */</span><br><span> } else</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp("ApplicationMoveFailed", discriminator) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return ast_ari_validate_application_move_failed(json);</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span> if (strcmp("ApplicationReplaced", discriminator) == 0) {</span><br><span> return ast_ari_validate_application_replaced(json);</span><br><span> } else</span><br><span>@@ -5293,6 +5417,9 @@</span><br><span> if (strcmp("Message", discriminator) == 0) {</span><br><span> /* Self type; fall through */</span><br><span> } else</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp("ApplicationMoveFailed", discriminator) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return ast_ari_validate_application_move_failed(json);</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span> if (strcmp("ApplicationReplaced", discriminator) == 0) {</span><br><span> return ast_ari_validate_application_replaced(json);</span><br><span> } else</span><br><span>diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h</span><br><span>index ab0b2f6..0471631 100644</span><br><span>--- a/res/ari/ari_model_validators.h</span><br><span>+++ b/res/ari/ari_model_validators.h</span><br><span>@@ -624,6 +624,24 @@</span><br><span> ari_validator ast_ari_validate_mailbox_fn(void);</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Validator for ApplicationMoveFailed.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * Notification that trying to move a channel to another Stasis application failed.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param json JSON object to validate.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \returns True (non-zero) if valid.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \returns False (zero) if invalid.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int ast_ari_validate_application_move_failed(struct ast_json *json);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Function pointer to ast_ari_validate_application_move_failed().</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * See \ref ast_ari_model_validators.h for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ari_validator ast_ari_validate_application_move_failed_fn(void);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span> * \brief Validator for ApplicationReplaced.</span><br><span> *</span><br><span> * Notification that another WebSocket has taken over for an application.</span><br><span>@@ -1527,6 +1545,14 @@</span><br><span> * - name: string (required)</span><br><span> * - new_messages: int (required)</span><br><span> * - old_messages: int (required)</span><br><span style="color: hsl(120, 100%, 40%);">+ * ApplicationMoveFailed</span><br><span style="color: hsl(120, 100%, 40%);">+ * - asterisk_id: string</span><br><span style="color: hsl(120, 100%, 40%);">+ * - type: string (required)</span><br><span style="color: hsl(120, 100%, 40%);">+ * - application: string (required)</span><br><span style="color: hsl(120, 100%, 40%);">+ * - timestamp: Date</span><br><span style="color: hsl(120, 100%, 40%);">+ * - args: List[string] (required)</span><br><span style="color: hsl(120, 100%, 40%);">+ * - channel: Channel (required)</span><br><span style="color: hsl(120, 100%, 40%);">+ * - destination: string (required)</span><br><span> * ApplicationReplaced</span><br><span> * - asterisk_id: string</span><br><span> * - type: string (required)</span><br><span>diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c</span><br><span>index 8aeb096..c256050 100644</span><br><span>--- a/res/ari/resource_channels.c</span><br><span>+++ b/res/ari/resource_channels.c</span><br><span>@@ -217,6 +217,26 @@</span><br><span> ast_ari_response_no_content(response);</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+void ast_ari_channels_move(struct ast_variable *headers,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_ari_channels_move_args *args,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_ari_response *response)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ RAII_VAR(struct stasis_app_control *, control, NULL, ao2_cleanup);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ control = find_control(response, args->channel_id);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!control) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (stasis_app_control_move(control, args->app, args->app_args)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_response_error(response, 500, "Internal Server Error",</span><br><span style="color: hsl(120, 100%, 40%);">+ "Failed to switch Stasis applications");</span><br><span style="color: hsl(120, 100%, 40%);">+ return;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_response_no_content(response);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> void ast_ari_channels_redirect(struct ast_variable *headers,</span><br><span> struct ast_ari_channels_redirect_args *args,</span><br><span> struct ast_ari_response *response)</span><br><span>diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h</span><br><span>index b071d08..fdd7a6b 100644</span><br><span>--- a/res/ari/resource_channels.h</span><br><span>+++ b/res/ari/resource_channels.h</span><br><span>@@ -261,6 +261,34 @@</span><br><span> * \param[out] response HTTP response</span><br><span> */</span><br><span> 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);</span><br><span style="color: hsl(120, 100%, 40%);">+/*! Argument struct for ast_ari_channels_move() */</span><br><span style="color: hsl(120, 100%, 40%);">+struct ast_ari_channels_move_args {</span><br><span style="color: hsl(120, 100%, 40%);">+ /*! Channel's id */</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *channel_id;</span><br><span style="color: hsl(120, 100%, 40%);">+ /*! The channel will be passed to this Stasis application. */</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *app;</span><br><span style="color: hsl(120, 100%, 40%);">+ /*! The application arguments to pass to the Stasis application provided by 'app'. */</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *app_args;</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Body parsing function for /channels/{channelId}/move.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param body The JSON body from which to parse parameters.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[out] args The args structure to parse into.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval zero on success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval non-zero on failure</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int ast_ari_channels_move_parse_body(</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *body,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_ari_channels_move_args *args);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Move the channel from one Stasis application to another.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param headers HTTP headers</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param args Swagger parameters</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[out] response HTTP response</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+void ast_ari_channels_move(struct ast_variable *headers, struct ast_ari_channels_move_args *args, struct ast_ari_response *response);</span><br><span> /*! Argument struct for ast_ari_channels_redirect() */</span><br><span> struct ast_ari_channels_redirect_args {</span><br><span> /*! Channel's id */</span><br><span>diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c</span><br><span>index dae146c..3d96d60 100644</span><br><span>--- a/res/res_ari_channels.c</span><br><span>+++ b/res/res_ari_channels.c</span><br><span>@@ -778,6 +778,95 @@</span><br><span> fin: __attribute__((unused))</span><br><span> return;</span><br><span> }</span><br><span style="color: hsl(120, 100%, 40%);">+int ast_ari_channels_move_parse_body(</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *body,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_ari_channels_move_args *args)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *field;</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Parse query parameters out of it */</span><br><span style="color: hsl(120, 100%, 40%);">+ field = ast_json_object_get(body, "app");</span><br><span style="color: hsl(120, 100%, 40%);">+ if (field) {</span><br><span style="color: hsl(120, 100%, 40%);">+ args->app = ast_json_string_get(field);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ field = ast_json_object_get(body, "appArgs");</span><br><span style="color: hsl(120, 100%, 40%);">+ if (field) {</span><br><span style="color: hsl(120, 100%, 40%);">+ args->app_args = ast_json_string_get(field);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Parameter parsing callback for /channels/{channelId}/move.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param get_params GET parameters in the HTTP request.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param path_vars Path variables extracted from the request.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param headers HTTP headers.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[out] response Response to the HTTP request.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+static void ast_ari_channels_move_cb(</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_tcptls_session_instance *ser,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_variable *get_params, struct ast_variable *path_vars,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_ari_channels_move_args args = {};</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_variable *i;</span><br><span style="color: hsl(120, 100%, 40%);">+#if defined(AST_DEVMODE)</span><br><span style="color: hsl(120, 100%, 40%);">+ int is_valid;</span><br><span style="color: hsl(120, 100%, 40%);">+ int code;</span><br><span style="color: hsl(120, 100%, 40%);">+#endif /* AST_DEVMODE */</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ for (i = get_params; i; i = i->next) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp(i->name, "app") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ args.app = (i->value);</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp(i->name, "appArgs") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ args.app_args = (i->value);</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span style="color: hsl(120, 100%, 40%);">+ {}</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ for (i = path_vars; i; i = i->next) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (strcmp(i->name, "channelId") == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ args.channel_id = (i->value);</span><br><span style="color: hsl(120, 100%, 40%);">+ } else</span><br><span style="color: hsl(120, 100%, 40%);">+ {}</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_ari_channels_move_parse_body(body, &args)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_response_alloc_failed(response);</span><br><span style="color: hsl(120, 100%, 40%);">+ goto fin;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_channels_move(headers, &args, response);</span><br><span style="color: hsl(120, 100%, 40%);">+#if defined(AST_DEVMODE)</span><br><span style="color: hsl(120, 100%, 40%);">+ code = response->response_code;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ switch (code) {</span><br><span style="color: hsl(120, 100%, 40%);">+ case 0: /* Implementation is still a stub, or the code wasn't set */</span><br><span style="color: hsl(120, 100%, 40%);">+ is_valid = response->message == NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ case 500: /* Internal Server Error */</span><br><span style="color: hsl(120, 100%, 40%);">+ case 501: /* Not Implemented */</span><br><span style="color: hsl(120, 100%, 40%);">+ case 404: /* Channel not found */</span><br><span style="color: hsl(120, 100%, 40%);">+ case 409: /* Channel not in a Stasis application */</span><br><span style="color: hsl(120, 100%, 40%);">+ is_valid = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ default:</span><br><span style="color: hsl(120, 100%, 40%);">+ if (200 <= code && code <= 299) {</span><br><span style="color: hsl(120, 100%, 40%);">+ is_valid = ast_ari_validate_void(</span><br><span style="color: hsl(120, 100%, 40%);">+ response->message);</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Invalid error response %d for /channels/{channelId}/move\n", code);</span><br><span style="color: hsl(120, 100%, 40%);">+ is_valid = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!is_valid) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Response validation failed for /channels/{channelId}/move\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_ari_response_error(response, 500,</span><br><span style="color: hsl(120, 100%, 40%);">+ "Internal Server Error", "Response validation failed");</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+#endif /* AST_DEVMODE */</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+fin: __attribute__((unused))</span><br><span style="color: hsl(120, 100%, 40%);">+ return;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span> int ast_ari_channels_redirect_parse_body(</span><br><span> struct ast_json *body,</span><br><span> struct ast_ari_channels_redirect_args *args)</span><br><span>@@ -2680,6 +2769,15 @@</span><br><span> .children = { }</span><br><span> };</span><br><span> /*! \brief REST handler for /api-docs/channels.json */</span><br><span style="color: hsl(120, 100%, 40%);">+static struct stasis_rest_handlers channels_channelId_move = {</span><br><span style="color: hsl(120, 100%, 40%);">+ .path_segment = "move",</span><br><span style="color: hsl(120, 100%, 40%);">+ .callbacks = {</span><br><span style="color: hsl(120, 100%, 40%);">+ [AST_HTTP_POST] = ast_ari_channels_move_cb,</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ .num_children = 0,</span><br><span style="color: hsl(120, 100%, 40%);">+ .children = { }</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+/*! \brief REST handler for /api-docs/channels.json */</span><br><span> static struct stasis_rest_handlers channels_channelId_redirect = {</span><br><span> .path_segment = "redirect",</span><br><span> .callbacks = {</span><br><span>@@ -2831,8 +2929,8 @@</span><br><span> [AST_HTTP_POST] = ast_ari_channels_originate_with_id_cb,</span><br><span> [AST_HTTP_DELETE] = ast_ari_channels_hangup_cb,</span><br><span> },</span><br><span style="color: hsl(0, 100%, 40%);">- .num_children = 14,</span><br><span style="color: hsl(0, 100%, 40%);">- .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, }</span><br><span style="color: hsl(120, 100%, 40%);">+ .num_children = 15,</span><br><span style="color: hsl(120, 100%, 40%);">+ .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, }</span><br><span> };</span><br><span> /*! \brief REST handler for /api-docs/channels.json */</span><br><span> static struct stasis_rest_handlers channels = {</span><br><span>diff --git a/res/res_stasis.c b/res/res_stasis.c</span><br><span>index 704d779..def0cb3 100644</span><br><span>--- a/res/res_stasis.c</span><br><span>+++ b/res/res_stasis.c</span><br><span>@@ -1293,7 +1293,8 @@</span><br><span> int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,</span><br><span> char *argv[])</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">- RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);</span><br><span style="color: hsl(120, 100%, 40%);">+ SCOPED_MODULE_USE(ast_module_info->self);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> RAII_VAR(struct stasis_app_control *, control, NULL, control_unlink);</span><br><span> struct ast_bridge *bridge = NULL;</span><br><span> int res = 0;</span><br><span>@@ -1310,21 +1311,19 @@</span><br><span> return -1;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);</span><br><span style="color: hsl(0, 100%, 40%);">- if (!app) {</span><br><span style="color: hsl(0, 100%, 40%);">- ast_log(LOG_ERROR,</span><br><span style="color: hsl(0, 100%, 40%);">- "Stasis app '%s' not registered\n", app_name);</span><br><span style="color: hsl(0, 100%, 40%);">- return -1;</span><br><span style="color: hsl(0, 100%, 40%);">- }</span><br><span style="color: hsl(0, 100%, 40%);">- if (!app_is_active(app)) {</span><br><span style="color: hsl(0, 100%, 40%);">- ast_log(LOG_ERROR,</span><br><span style="color: hsl(0, 100%, 40%);">- "Stasis app '%s' not active\n", app_name);</span><br><span style="color: hsl(120, 100%, 40%);">+ control = control_create(chan, ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!control) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Control allocation failed or Stasis app '%s' not registered\n", app_name);</span><br><span> return -1;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- control = control_create(chan, app);</span><br><span style="color: hsl(0, 100%, 40%);">- if (!control) {</span><br><span style="color: hsl(0, 100%, 40%);">- ast_log(LOG_ERROR, "Allocated failed\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!control_app(control)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Stasis app '%s' not registered\n", app_name);</span><br><span style="color: hsl(120, 100%, 40%);">+ return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!app_is_active(control_app(control))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Stasis app '%s' not active\n", app_name);</span><br><span> return -1;</span><br><span> }</span><br><span> ao2_link(app_controls, control);</span><br><span>@@ -1334,7 +1333,7 @@</span><br><span> return -1;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- res = send_start_msg(app, chan, argc, argv);</span><br><span style="color: hsl(120, 100%, 40%);">+ res = send_start_msg(control_app(control), chan, argc, argv);</span><br><span> if (res != 0) {</span><br><span> ast_log(LOG_ERROR,</span><br><span> "Error sending start message to '%s'\n", app_name);</span><br><span>@@ -1357,15 +1356,114 @@</span><br><span> break;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ /* control->next_app is only modified within the control thread, so this is safe */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (control_next_app(control)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ struct stasis_app *next_app = ao2_find(apps_registry, control_next_app(control), OBJ_SEARCH_KEY);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (next_app && app_is_active(next_app)) {</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* If something goes wrong in this conditional, res will need to be non-zero</span><br><span style="color: hsl(120, 100%, 40%);">+ * so that the code below the exec loop knows something went wrong during a move.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!stasis_app_channel_is_stasis_end_published(chan)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ res = has_masquerade_store(chan) && app_send_end_msg(control_app(control), chan);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (res != 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+ "Error sending end message to %s\n", stasis_app_name(control_app(control)));</span><br><span style="color: hsl(120, 100%, 40%);">+ control_mark_done(control);</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ remove_stasis_end_published(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* This will ao2_bump next_app, and unref the previous app by 1 */</span><br><span style="color: hsl(120, 100%, 40%);">+ control_set_app(control, next_app);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* There's a chance that the previous application is ready for clean up, so go ahead</span><br><span style="color: hsl(120, 100%, 40%);">+ * and do that now.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ cleanup();</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* We need to add another masquerade store, otherwise the leave message will</span><br><span style="color: hsl(120, 100%, 40%);">+ * not show up for the correct application.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (add_masquerade_store(chan)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Failed to attach masquerade detector\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ res = -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ control_mark_done(control);</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ res = send_start_msg(control_app(control), chan, control_argc(control), control_argv(control));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (res != 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+ "Error sending start message to '%s'\n", stasis_app_name(control_app(control)));</span><br><span style="color: hsl(120, 100%, 40%);">+ remove_masquerade_store(chan);</span><br><span style="color: hsl(120, 100%, 40%);">+ control_mark_done(control);</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Done switching applications, free memory and clean up */</span><br><span style="color: hsl(120, 100%, 40%);">+ control_move_cleanup(control);</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* If we can't switch applications, do nothing */</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *msg;</span><br><span style="color: hsl(120, 100%, 40%);">+ RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!next_app) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Could not move to Stasis app '%s' - not registered\n",</span><br><span style="color: hsl(120, 100%, 40%);">+ control_next_app(control));</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Could not move to Stasis app '%s' - not active\n",</span><br><span style="color: hsl(120, 100%, 40%);">+ control_next_app(control));</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!snapshot) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Could not get channel shapshot for '%s'\n",</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_channel_name(chan));</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_json *json_args;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ msg = ast_json_pack("{s: s, s: o, s: s, s: []}",</span><br><span style="color: hsl(120, 100%, 40%);">+ "type", "ApplicationMoveFailed",</span><br><span style="color: hsl(120, 100%, 40%);">+ "channel", ast_channel_snapshot_to_json(snapshot, NULL),</span><br><span style="color: hsl(120, 100%, 40%);">+ "destination", control_next_app(control),</span><br><span style="color: hsl(120, 100%, 40%);">+ "args");</span><br><span style="color: hsl(120, 100%, 40%);">+ json_args = ast_json_object_get(msg, "args");</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!json_args) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Could not get args json array");</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ int r = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ int idx;</span><br><span style="color: hsl(120, 100%, 40%);">+ for (idx = 0; idx < control_argc(control); ++idx) {</span><br><span style="color: hsl(120, 100%, 40%);">+ r = ast_json_array_append(json_args,</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_string_create(control_argv(control)[idx]));</span><br><span style="color: hsl(120, 100%, 40%);">+ if (r != 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Error appending to ApplicationMoveFailed message\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ if (r == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ app_send(control_app(control), msg);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_json_unref(msg);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ control_move_cleanup(control);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> last_bridge = bridge;</span><br><span> bridge = ao2_bump(stasis_app_get_bridge(control));</span><br><span> </span><br><span> if (bridge != last_bridge) {</span><br><span> if (last_bridge) {</span><br><span style="color: hsl(0, 100%, 40%);">- app_unsubscribe_bridge(app, last_bridge);</span><br><span style="color: hsl(120, 100%, 40%);">+ app_unsubscribe_bridge(control_app(control), last_bridge);</span><br><span> }</span><br><span> if (bridge) {</span><br><span style="color: hsl(0, 100%, 40%);">- app_subscribe_bridge(app, bridge);</span><br><span style="color: hsl(120, 100%, 40%);">+ app_subscribe_bridge(control_app(control), bridge);</span><br><span> }</span><br><span> }</span><br><span> </span><br><span>@@ -1425,18 +1523,18 @@</span><br><span> }</span><br><span> </span><br><span> if (stasis_app_get_bridge(control)) {</span><br><span style="color: hsl(0, 100%, 40%);">- app_unsubscribe_bridge(app, stasis_app_get_bridge(control));</span><br><span style="color: hsl(120, 100%, 40%);">+ app_unsubscribe_bridge(control_app(control), stasis_app_get_bridge(control));</span><br><span> }</span><br><span> ao2_cleanup(bridge);</span><br><span> </span><br><span> /* Only publish a stasis_end event if it hasn't already been published */</span><br><span style="color: hsl(0, 100%, 40%);">- if (!stasis_app_channel_is_stasis_end_published(chan)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!res && !stasis_app_channel_is_stasis_end_published(chan)) {</span><br><span> /* A masquerade has occurred and this message will be wrong so it</span><br><span> * has already been sent elsewhere. */</span><br><span style="color: hsl(0, 100%, 40%);">- res = has_masquerade_store(chan) && app_send_end_msg(app, chan);</span><br><span style="color: hsl(120, 100%, 40%);">+ res = has_masquerade_store(chan) && app_send_end_msg(control_app(control), chan);</span><br><span> if (res != 0) {</span><br><span> ast_log(LOG_ERROR,</span><br><span style="color: hsl(0, 100%, 40%);">- "Error sending end message to %s\n", app_name);</span><br><span style="color: hsl(120, 100%, 40%);">+ "Error sending end message to %s\n", stasis_app_name(control_app(control)));</span><br><span> return res;</span><br><span> }</span><br><span> } else {</span><br><span>@@ -1456,12 +1554,10 @@</span><br><span> /* The control needs to be removed from the controls container in</span><br><span> * case a new PBX is started and ends up coming back into Stasis.</span><br><span> */</span><br><span style="color: hsl(0, 100%, 40%);">- ao2_cleanup(app);</span><br><span style="color: hsl(0, 100%, 40%);">- app = NULL;</span><br><span> control_unlink(control);</span><br><span> control = NULL;</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- if (!ast_channel_pbx(chan)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!res && !ast_channel_pbx(chan)) {</span><br><span> int chan_hungup;</span><br><span> </span><br><span> /* The ASYNCGOTO softhangup flag may have broken the channel out of</span><br><span>diff --git a/res/stasis/control.c b/res/stasis/control.c</span><br><span>index e4d007c..e897aec 100644</span><br><span>--- a/res/stasis/control.c</span><br><span>+++ b/res/stasis/control.c</span><br><span>@@ -83,10 +83,24 @@</span><br><span> */</span><br><span> struct ast_silence_generator *silgen;</span><br><span> /*!</span><br><span style="color: hsl(0, 100%, 40%);">- * The app for which this control was created</span><br><span style="color: hsl(120, 100%, 40%);">+ * The app for which this control is currently controlling.</span><br><span style="color: hsl(120, 100%, 40%);">+ * This can change through the use of the /channels/{channelId}/move</span><br><span style="color: hsl(120, 100%, 40%);">+ * command.</span><br><span> */</span><br><span> struct stasis_app *app;</span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * The name of the next Stasis application to move to.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ char *next_app;</span><br><span style="color: hsl(120, 100%, 40%);">+ /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * The list of arguments to pass to StasisStart when moving to another app.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ char **argv;</span><br><span style="color: hsl(120, 100%, 40%);">+ /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * The number of arguments argv contains.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ int argc;</span><br><span style="color: hsl(120, 100%, 40%);">+ /*!</span><br><span> * When set, /c app_stasis should exit and continue in the dialplan.</span><br><span> */</span><br><span> int is_done:1;</span><br><span>@@ -101,6 +115,8 @@</span><br><span> ast_channel_cleanup(control->channel);</span><br><span> ao2_cleanup(control->app);</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ control_move_cleanup(control);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> ast_cond_destroy(&control->wait_cond);</span><br><span> AST_LIST_HEAD_DESTROY(&control->add_rules);</span><br><span> AST_LIST_HEAD_DESTROY(&control->remove_rules);</span><br><span>@@ -125,11 +141,12 @@</span><br><span> if (res != 0) {</span><br><span> ast_log(LOG_ERROR, "Error initializing ast_cond_t: %s\n",</span><br><span> strerror(errno));</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_cleanup(app);</span><br><span> ao2_ref(control, -1);</span><br><span> return NULL;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- control->app = ao2_bump(app);</span><br><span style="color: hsl(120, 100%, 40%);">+ control->app = app;</span><br><span> </span><br><span> ast_channel_ref(channel);</span><br><span> control->channel = channel;</span><br><span>@@ -141,6 +158,10 @@</span><br><span> return NULL;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ control->next_app = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ control->argv = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ control->argc = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> return control;</span><br><span> }</span><br><span> </span><br><span>@@ -391,6 +412,103 @@</span><br><span> return 0;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+struct stasis_app_control_move_data {</span><br><span style="color: hsl(120, 100%, 40%);">+ char *app_name;</span><br><span style="color: hsl(120, 100%, 40%);">+ char **argv;</span><br><span style="color: hsl(120, 100%, 40%);">+ int argc;</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static int app_control_move(struct stasis_app_control *control,</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_channel *chan, void *data)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct stasis_app_control_move_data *move_data = data;</span><br><span style="color: hsl(120, 100%, 40%);">+ size_t size;</span><br><span style="color: hsl(120, 100%, 40%);">+ int idx;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ size = strlen(move_data->app_name) * sizeof(*move_data->app_name) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!(control->next_app = ast_calloc(1, size))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Allocation failed for next app\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_copy_string(control->next_app, S_OR(move_data->app_name, ""), size);</span><br><span style="color: hsl(120, 100%, 40%);">+ control->argc = move_data->argc;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (move_data->argv) {</span><br><span style="color: hsl(120, 100%, 40%);">+ size = sizeof(*move_data->argv) * control->argc;</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!(control->argv = ast_calloc(1, size))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Allocation failed for argument list\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ control_move_cleanup(control);</span><br><span style="color: hsl(120, 100%, 40%);">+ return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ for (idx = 0; idx < control->argc; idx++) {</span><br><span style="color: hsl(120, 100%, 40%);">+ size = strlen(move_data->argv[idx]) * sizeof(*move_data->argv[idx]) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!(control->argv[idx] = ast_calloc(1, size))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_log(LOG_ERROR, "Allocation failed for argument\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ control_move_cleanup(control);</span><br><span style="color: hsl(120, 100%, 40%);">+ return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_copy_string(control->argv[idx], S_OR(move_data->argv[idx], ""), size);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ control->argv = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+int stasis_app_control_move(struct stasis_app_control *control, const char *app_name, const char *app_args)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct stasis_app_control_move_data *move_data;</span><br><span style="color: hsl(120, 100%, 40%);">+ size_t size;</span><br><span style="color: hsl(120, 100%, 40%);">+ int argc = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ char *iter;</span><br><span style="color: hsl(120, 100%, 40%);">+ char *token;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!(move_data = ast_calloc(1, sizeof(*move_data)))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ size = strlen(app_name) * sizeof(*app_name) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!(move_data->app_name = ast_calloc(1, size))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_copy_string(move_data->app_name, S_OR(app_name, ""), size);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (app_args) {</span><br><span style="color: hsl(120, 100%, 40%);">+ size = strlen(app_args) * sizeof(*app_args) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!(iter = ast_calloc(1, size))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_copy_string(iter, S_OR(app_args, ""), size);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!(move_data->argv = ast_calloc(1, sizeof(*move_data->argv)))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ while ((token = strtok_r(iter, ",", &iter))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!(move_data->argv = ast_realloc(move_data->argv, sizeof(*move_data->argv) * (argc + 1)))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ size = strlen(token) * sizeof(*token) + 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ move_data->argv[argc] = ast_calloc(1, size);</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_copy_string(move_data->argv[argc], S_OR(token, ""), size);</span><br><span style="color: hsl(120, 100%, 40%);">+ argc++;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ move_data->argv = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ move_data->argc = argc;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ stasis_app_send_command_async(control, app_control_move, move_data, ast_free_ptr);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static int app_control_redirect(struct stasis_app_control *control,</span><br><span> struct ast_channel *chan, void *data)</span><br><span> {</span><br><span>@@ -1590,3 +1708,47 @@</span><br><span> }</span><br><span> ast_mutex_unlock(&dial_bridge_lock);</span><br><span> }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+void control_set_app(struct stasis_app_control *control, struct stasis_app *app)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_cleanup(control->app);</span><br><span style="color: hsl(120, 100%, 40%);">+ control->app = ao2_bump(app);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+char *control_next_app(struct stasis_app_control *control)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ return control->next_app;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+void control_move_cleanup(struct stasis_app_control *control)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ if (control->next_app) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_free(control->next_app);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ control->next_app = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (control->argv) {</span><br><span style="color: hsl(120, 100%, 40%);">+ int idx;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ for (idx = 0; idx < control->argc; idx++) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (control->argv[idx]) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_free(control->argv[idx]);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_free(control->argv);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ control->argv = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ control->argc = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+char **control_argv(struct stasis_app_control *control)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ return control->argv;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+int control_argc(struct stasis_app_control *control)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ return control->argc;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span>diff --git a/res/stasis/control.h b/res/stasis/control.h</span><br><span>index 868a809..86b0c4a 100644</span><br><span>--- a/res/stasis/control.h</span><br><span>+++ b/res/stasis/control.h</span><br><span>@@ -37,6 +37,9 @@</span><br><span> *</span><br><span> * \return New control object.</span><br><span> * \return \c NULL on error.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note This function inherits app's ref rather than bumping app!</span><br><span style="color: hsl(120, 100%, 40%);">+ * If something goes wrong, ao2_cleanup is called on the app.</span><br><span> */</span><br><span> struct stasis_app_control *control_create(struct ast_channel *channel, struct stasis_app *app);</span><br><span> </span><br><span>@@ -108,6 +111,30 @@</span><br><span> struct stasis_app *control_app(struct stasis_app_control *control);</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Set the application the control object belongs to</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param control The control for the channel</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param app The application this control will now belong to</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note This will unref the previous app by 1, and bump app by 1</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+void control_set_app(struct stasis_app_control *control, struct stasis_app *app);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+char *control_next_app(struct stasis_app_control *control);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Free any memory that was allocated for switching applications via</span><br><span style="color: hsl(120, 100%, 40%);">+ * /channels/{channelId}/move</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param control The control for the channel</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+void control_move_cleanup(struct stasis_app_control *control);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+char **control_argv(struct stasis_app_control *control);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+int control_argc(struct stasis_app_control *control);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span> * \brief Command callback for adding a channel to a bridge</span><br><span> *</span><br><span> * \param control The control for chan</span><br><span>diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json</span><br><span>index 08db224..6161934 100644</span><br><span>--- a/rest-api/api-docs/channels.json</span><br><span>+++ b/rest-api/api-docs/channels.json</span><br><span>@@ -516,6 +516,54 @@</span><br><span> ]</span><br><span> },</span><br><span> {</span><br><span style="color: hsl(120, 100%, 40%);">+ "path": "/channels/{channelId}/move",</span><br><span style="color: hsl(120, 100%, 40%);">+ "description": "Move the channel from one Stasis application to another.",</span><br><span style="color: hsl(120, 100%, 40%);">+ "operations": [</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ "httpMethod": "POST",</span><br><span style="color: hsl(120, 100%, 40%);">+ "summary": "Move the channel from one Stasis application to another.",</span><br><span style="color: hsl(120, 100%, 40%);">+ "nickname": "move",</span><br><span style="color: hsl(120, 100%, 40%);">+ "responseClass": "void",</span><br><span style="color: hsl(120, 100%, 40%);">+ "parameters": [</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ "name": "channelId",</span><br><span style="color: hsl(120, 100%, 40%);">+ "description": "Channel's id",</span><br><span style="color: hsl(120, 100%, 40%);">+ "paramType": "path",</span><br><span style="color: hsl(120, 100%, 40%);">+ "required": true,</span><br><span style="color: hsl(120, 100%, 40%);">+ "allowMultiple": false,</span><br><span style="color: hsl(120, 100%, 40%);">+ "dataType": "string"</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ "name": "app",</span><br><span style="color: hsl(120, 100%, 40%);">+ "description": "The channel will be passed to this Stasis application.",</span><br><span style="color: hsl(120, 100%, 40%);">+ "paramType": "query",</span><br><span style="color: hsl(120, 100%, 40%);">+ "required": true,</span><br><span style="color: hsl(120, 100%, 40%);">+ "allowMultiple": false,</span><br><span style="color: hsl(120, 100%, 40%);">+ "dataType": "string"</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ "name": "appArgs",</span><br><span style="color: hsl(120, 100%, 40%);">+ "description": "The application arguments to pass to the Stasis application provided by 'app'.",</span><br><span style="color: hsl(120, 100%, 40%);">+ "paramType": "query",</span><br><span style="color: hsl(120, 100%, 40%);">+ "required": false,</span><br><span style="color: hsl(120, 100%, 40%);">+ "allowMultiple": false,</span><br><span style="color: hsl(120, 100%, 40%);">+ "dataType": "string"</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ ],</span><br><span style="color: hsl(120, 100%, 40%);">+ "errorResponses": [</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ "code": "404",</span><br><span style="color: hsl(120, 100%, 40%);">+ "reason": "Channel not found"</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span style="color: hsl(120, 100%, 40%);">+ "code": "409",</span><br><span style="color: hsl(120, 100%, 40%);">+ "reason": "Channel not in a Stasis application"</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ ]</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ ]</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ {</span><br><span> "path": "/channels/{channelId}/redirect",</span><br><span> "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.",</span><br><span> "operations": [</span><br><span>diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json</span><br><span>index d85d8d9..c9f4b6a 100644</span><br><span>--- a/rest-api/api-docs/events.json</span><br><span>+++ b/rest-api/api-docs/events.json</span><br><span>@@ -159,6 +159,7 @@</span><br><span> "RecordingStarted",</span><br><span> "RecordingFinished",</span><br><span> "RecordingFailed",</span><br><span style="color: hsl(120, 100%, 40%);">+ "ApplicationMoveFailed",</span><br><span> "ApplicationReplaced",</span><br><span> "BridgeCreated",</span><br><span> "BridgeDestroyed",</span><br><span>@@ -335,6 +336,25 @@</span><br><span> }</span><br><span> }</span><br><span> },</span><br><span style="color: hsl(120, 100%, 40%);">+ "ApplicationMoveFailed": {</span><br><span style="color: hsl(120, 100%, 40%);">+ "id": "ApplicationMoveFailed",</span><br><span style="color: hsl(120, 100%, 40%);">+ "description": "Notification that trying to move a channel to another Stasis application failed.",</span><br><span style="color: hsl(120, 100%, 40%);">+ "properties": {</span><br><span style="color: hsl(120, 100%, 40%);">+ "channel": {</span><br><span style="color: hsl(120, 100%, 40%);">+ "required": true,</span><br><span style="color: hsl(120, 100%, 40%);">+ "type": "Channel"</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ "destination": {</span><br><span style="color: hsl(120, 100%, 40%);">+ "required": true,</span><br><span style="color: hsl(120, 100%, 40%);">+ "type": "string"</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span style="color: hsl(120, 100%, 40%);">+ "args": {</span><br><span style="color: hsl(120, 100%, 40%);">+ "required": true,</span><br><span style="color: hsl(120, 100%, 40%);">+ "type": "List[string]",</span><br><span style="color: hsl(120, 100%, 40%);">+ "description": "Arguments to the application"</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ },</span><br><span> "ApplicationReplaced": {</span><br><span> "id": "ApplicationReplaced",</span><br><span> "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.",</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/10960">change 10960</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/10960"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 16 </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>
<div style="display:none"> Gerrit-Change-Id: I43d12b10045a98a8d42541889b85695be26f288a </div>
<div style="display:none"> Gerrit-Change-Number: 10960 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Benjamin Keith Ford <bford@digium.com> </div>