ARI: Adding a channel to a bridge while a live recording is active blocks
authorKevin Harwell <kharwell@digium.com>
Fri, 13 Dec 2013 16:38:57 +0000 (16:38 +0000)
committerKevin Harwell <kharwell@digium.com>
Fri, 13 Dec 2013 16:38:57 +0000 (16:38 +0000)
Added the ability to have rules that are checked when adding and/or removing
channels to/from a bridge.  In this case, if a channel is currently recording
and someone attempts to add it to a bridge an "is recording" rule is checked,
fails, and a 409 conflict is returned.

Also command functions now return an integer value that can be descriptive of
what kind of problems, if any, occurred before or during execution.

(closes issue ASTERISK-22624)
Reported by: Joshua Colp
Review: https://reviewboard.asterisk.org/r/2947/
........

Merged revisions 403749 from http://svn.asterisk.org/svn/asterisk/branches/12

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@403750 65c4cc65-6c06-0410-ace0-fbb531ad65f3

include/asterisk/stasis_app.h
include/asterisk/stasis_app_impl.h
res/ari/resource_bridges.c
res/res_ari_bridges.c
res/res_stasis_answer.c
res/res_stasis_playback.c
res/res_stasis_recording.c
res/stasis/command.c
res/stasis/command.h
res/stasis/control.c
rest-api/api-docs/bridges.json

index 0c22a6c..56e039b 100644 (file)
@@ -277,6 +277,60 @@ enum stasis_app_subscribe_res stasis_app_unsubscribe(const char *app_name,
 /*! \brief Handler for controlling a channel that's in a Stasis application */
 struct stasis_app_control;
 
+/*! \brief Rule to check to see if an operation is allowed */
+struct stasis_app_control_rule {
+       /*!
+        * \brief Checks to see if an operation is allowed on the control
+        *
+        * \param control Control object to check
+        * \return 0 on success, otherwise a failure code
+        */
+       enum stasis_app_control_channel_result (*check_rule)(
+               const struct stasis_app_control *control);
+       /*! Next item in the list */
+       AST_LIST_ENTRY(stasis_app_control_rule) next;
+};
+
+/*!
+ * \brief Registers an add channel to bridge rule.
+ *
+ * \param control Control object
+ * \param rule The rule to register
+ */
+void stasis_app_control_register_add_rule(
+       struct stasis_app_control *control,
+       struct stasis_app_control_rule *rule);
+
+/*!
+ * \brief UnRegister an add channel to bridge rule.
+ *
+ * \param control Control object
+ * \param rule The rule to unregister
+ */
+void stasis_app_control_unregister_add_rule(
+       struct stasis_app_control *control,
+       struct stasis_app_control_rule *rule);
+
+/*!
+ * \brief Registers a remove channel from bridge rule.
+ *
+ * \param control Control object
+ * \param rule The rule to register
+ */
+void stasis_app_control_register_remove_rule(
+       struct stasis_app_control *control,
+       struct stasis_app_control_rule *rule);
+
+/*!
+ * \brief Unregisters a remove channel from bridge rule.
+ *
+ * \param control Control object
+ * \param rule The rule to unregister
+ */
+void stasis_app_control_unregister_remove_rule(
+       struct stasis_app_control *control,
+       struct stasis_app_control_rule *rule);
+
 /*!
  * \brief Returns the handler for the given channel.
  * \param chan Channel to handle.
@@ -582,6 +636,16 @@ int stasis_app_bridge_moh_stop(
        struct ast_bridge *bridge);
 
 /*!
+ * \brief Result codes used when adding/removing channels to/from bridges.
+ */
+enum stasis_app_control_channel_result {
+       /*! The channel is okay to be added/removed */
+       STASIS_APP_CHANNEL_OKAY = 0,
+       /*! The channel is currently recording */
+       STASIS_APP_CHANNEL_RECORDING
+};
+
+/*!
  * \brief Add a channel to the bridge.
  *
  * \param control Control whose channel should be added to the bridge
index d4b4677..a8c8c05 100644 (file)
@@ -49,7 +49,7 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
        char *argv[]);
 
 /*! Callback type for stasis app commands */
-typedef void *(*stasis_app_command_cb)(struct stasis_app_control *control,
+typedef int (*stasis_app_command_cb)(struct stasis_app_control *control,
        struct ast_channel *chan, void *data);
 
 /*!
@@ -63,10 +63,11 @@ typedef void *(*stasis_app_command_cb)(struct stasis_app_control *control,
  * \param control Control object for the channel to send the command to.
  * \param command Command function to execute.
  * \param data Optional data to pass along with the control function.
- * \return Return value from \a command.
- * \return \c NULL on error.
+ *
+ * \return zero on success.
+ * \return error code otherwise.
  */
-void *stasis_app_send_command(struct stasis_app_control *control,
+int stasis_app_send_command(struct stasis_app_control *control,
        stasis_app_command_cb command, void *data);
 
 /*!
index e09bea6..c074718 100644 (file)
@@ -172,6 +172,22 @@ static struct control_list *control_list_create(struct ast_ari_response *respons
        return list;
 }
 
+static int check_add_remove_channel(struct ast_ari_response *response,
+                                   struct stasis_app_control *control,
+                                   enum stasis_app_control_channel_result result)
+{
+       switch (result) {
+       case STASIS_APP_CHANNEL_RECORDING :
+               ast_ari_response_error(
+                       response, 409, "Conflict", "Channel %s currently recording",
+                       stasis_app_control_get_channel_id(control));
+               return -1;
+       case STASIS_APP_CHANNEL_OKAY:
+               return 0;
+       }
+       return 0;
+}
+
 void ast_ari_bridges_add_channel(struct ast_variable *headers,
        struct ast_ari_bridges_add_channel_args *args,
        struct ast_ari_response *response)
@@ -179,6 +195,7 @@ void ast_ari_bridges_add_channel(struct ast_variable *headers,
        RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup);
        RAII_VAR(struct control_list *, list, NULL, ao2_cleanup);
        size_t i;
+       int has_error = 0;
 
        if (!bridge) {
                /* Response filled in by find_bridge() */
@@ -202,10 +219,16 @@ void ast_ari_bridges_add_channel(struct ast_variable *headers,
        }
 
        for (i = 0; i < list->count; ++i) {
-               stasis_app_control_add_channel_to_bridge(list->controls[i], bridge);
+               if ((has_error = check_add_remove_channel(response, list->controls[i],
+                            stasis_app_control_add_channel_to_bridge(
+                                   list->controls[i], bridge)))) {
+                       break;
+               }
        }
 
-       ast_ari_response_no_content(response);
+       if (!has_error) {
+               ast_ari_response_no_content(response);
+       }
 }
 
 void ast_ari_bridges_remove_channel(struct ast_variable *headers,
index f72a728..a68fbf6 100644 (file)
@@ -438,7 +438,7 @@ static void ast_ari_bridges_add_channel_cb(
        case 501: /* Not Implemented */
        case 400: /* Channel not found */
        case 404: /* Bridge not found */
-       case 409: /* Bridge not in Stasis application */
+       case 409: /* Bridge not in Stasis application; Channel currently recording */
        case 422: /* Channel not in Stasis application */
                is_valid = 1;
                break;
index 257afb5..bc7d2fb 100644 (file)
@@ -35,28 +35,25 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/module.h"
 #include "asterisk/stasis_app_impl.h"
 
-static int OK = 0;
-static int FAIL = -1;
-
-static void *app_control_answer(struct stasis_app_control *control,
+static int app_control_answer(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        const int delay = 0;
        ast_debug(3, "%s: Answering",
                stasis_app_control_get_channel_id(control));
-       return __ast_answer(chan, delay) == 0 ? &OK : &FAIL;
+       return __ast_answer(chan, delay);
 }
 
 int stasis_app_control_answer(struct stasis_app_control *control)
 {
-       int *retval;
+       int retval;
 
        ast_debug(3, "%s: Sending answer command\n",
                stasis_app_control_get_channel_id(control));
 
        retval = stasis_app_send_command(control, app_control_answer, NULL);
 
-       if (retval == NULL || *retval != 0) {
+       if (retval != 0) {
                ast_log(LOG_WARNING, "%s: Failed to answer channel",
                        stasis_app_control_get_channel_id(control));
                return -1;
index ce29de1..f6cd540 100644 (file)
@@ -389,7 +389,7 @@ static void remove_from_playbacks(struct stasis_app_playback *playback)
                OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
 }
 
-static void *play_uri(struct stasis_app_control *control,
+static int play_uri(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        RAII_VAR(struct stasis_app_playback *, playback, NULL,
@@ -400,7 +400,7 @@ static void *play_uri(struct stasis_app_control *control,
        playback = data;
 
        if (!control) {
-               return NULL;
+               return -1;
        }
 
        bridge = stasis_app_get_bridge(control);
@@ -435,7 +435,7 @@ static void *play_uri(struct stasis_app_control *control,
                play_on_channel(playback, chan);
        }
 
-       return NULL;
+       return 0;
 }
 
 static void set_target_uri(
index ecf0cfa..f0fa1e5 100644 (file)
@@ -244,20 +244,43 @@ static void recording_publish(struct stasis_app_recording *recording, const char
        stasis_app_control_publish(recording->control, message);
 }
 
-static void recording_fail(struct stasis_app_recording *recording, const char *cause)
+
+static void recording_set_state(struct stasis_app_recording *recording,
+                               enum stasis_app_recording_state state,
+                               const char *cause)
 {
        SCOPED_AO2LOCK(lock, recording);
-       recording->state = STASIS_APP_RECORDING_STATE_FAILED;
+       recording->state = state;
        recording_publish(recording, cause);
 }
 
+static enum stasis_app_control_channel_result check_rule_recording(
+       const struct stasis_app_control *control)
+{
+       return STASIS_APP_CHANNEL_RECORDING;
+}
+
+struct stasis_app_control_rule rule_recording = {
+       .check_rule = check_rule_recording
+};
+
+static void recording_fail(struct stasis_app_control *control,
+                          struct stasis_app_recording *recording,
+                          const char *cause)
+{
+       stasis_app_control_unregister_add_rule(control, &rule_recording);
+
+       recording_set_state(
+               recording, STASIS_APP_RECORDING_STATE_FAILED, cause);
+}
+
 static void recording_cleanup(struct stasis_app_recording *recording)
 {
        ao2_unlink_flags(recordings, recording,
                OBJ_POINTER | OBJ_UNLINK | OBJ_NODATA);
 }
 
-static void *record_file(struct stasis_app_control *control,
+static int record_file(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        RAII_VAR(struct stasis_app_recording *, recording,
@@ -271,8 +294,8 @@ static void *record_file(struct stasis_app_control *control,
 
        if (stasis_app_get_bridge(control)) {
                ast_log(LOG_ERROR, "Cannot record channel while in bridge\n");
-               recording_fail(recording, "Cannot record channel while in bridge");
-               return NULL;
+               recording_fail(control, recording, "Cannot record channel while in bridge");
+               return -1;
        }
 
        switch (recording->options->terminate_on) {
@@ -293,15 +316,12 @@ static void *record_file(struct stasis_app_control *control,
        if (res != 0) {
                ast_debug(3, "%s: Failed to answer\n",
                        ast_channel_uniqueid(chan));
-               recording_fail(recording, "Failed to answer channel");
-               return NULL;
+               recording_fail(control, recording, "Failed to answer channel");
+               return -1;
        }
 
-       ao2_lock(recording);
-       recording->state = STASIS_APP_RECORDING_STATE_RECORDING;
-       recording_publish(recording, NULL);
-       ao2_unlock(recording);
-
+       recording_set_state(
+               recording, STASIS_APP_RECORDING_STATE_RECORDING, NULL);
        ast_play_and_record_full(chan,
                NULL, /* playfile */
                recording->absolute_name,
@@ -320,12 +340,12 @@ static void *record_file(struct stasis_app_control *control,
 
        ast_debug(3, "%s: Recording complete\n", ast_channel_uniqueid(chan));
 
-       ao2_lock(recording);
-       recording->state = STASIS_APP_RECORDING_STATE_COMPLETE;
-       recording_publish(recording, NULL);
-       ao2_unlock(recording);
+       recording_set_state(
+               recording, STASIS_APP_RECORDING_STATE_COMPLETE, NULL);
+
+       stasis_app_control_unregister_add_rule(control, &rule_recording);
 
-       return NULL;
+       return 0;
 }
 
 static void recording_dtor(void *obj)
@@ -412,6 +432,8 @@ struct stasis_app_recording *stasis_app_control_record(
                ao2_link(recordings, recording);
        }
 
+       stasis_app_control_register_add_rule(control, &rule_recording);
+
        /* A ref is kept in the recordings container; no need to bump */
        stasis_app_send_command_async(control, record_file, recording);
 
index f1f7f8f..a9e53af 100644 (file)
@@ -37,7 +37,7 @@ struct stasis_app_command {
        ast_cond_t condition;
        stasis_app_command_cb callback;
        void *data;
-       void *retval;
+       int retval;
        int is_done:1;
 };
 
@@ -67,7 +67,7 @@ struct stasis_app_command *command_create(
        return command;
 }
 
-static void command_complete(struct stasis_app_command *command, void *retval)
+void command_complete(struct stasis_app_command *command, int retval)
 {
        SCOPED_MUTEX(lock, &command->lock);
 
@@ -76,7 +76,7 @@ static void command_complete(struct stasis_app_command *command, void *retval)
        ast_cond_signal(&command->condition);
 }
 
-void *command_join(struct stasis_app_command *command)
+int command_join(struct stasis_app_command *command)
 {
        SCOPED_MUTEX(lock, &command->lock);
        while (!command->is_done) {
@@ -89,7 +89,7 @@ void *command_join(struct stasis_app_command *command)
 void command_invoke(struct stasis_app_command *command,
        struct stasis_app_control *control, struct ast_channel *chan)
 {
-       void *retval = command->callback(control, chan, command->data);
+       int retval = command->callback(control, chan, command->data);
        command_complete(command, retval);
 }
 
index 21f4df0..a99d40d 100644 (file)
@@ -34,9 +34,11 @@ struct stasis_app_command;
 struct stasis_app_command *command_create(
        stasis_app_command_cb callback, void *data);
 
+void command_complete(struct stasis_app_command *command, int retval);
+
 void command_invoke(struct stasis_app_command *command,
        struct stasis_app_control *control, struct ast_channel *chan);
 
-void *command_join(struct stasis_app_command *command);
+int command_join(struct stasis_app_command *command);
 
 #endif /* _ASTERISK_RES_STASIS_CONTROL_H */
index 14c9f57..6b09291 100644 (file)
@@ -40,6 +40,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/musiconhold.h"
 #include "asterisk/app.h"
 
+AST_LIST_HEAD(app_control_rules, stasis_app_control_rule);
+
 struct stasis_app_control {
        ast_cond_t wait_cond;
        /*! Queue of commands to dispatch on the channel */
@@ -59,6 +61,14 @@ struct stasis_app_control {
         */
        struct ast_pbx *pbx;
        /*!
+        * A list of rules to check before adding a channel to a bridge.
+        */
+       struct app_control_rules add_rules;
+       /*!
+        * A list of rules to check before removing a channel from a bridge.
+        */
+       struct app_control_rules remove_rules;
+       /*!
         * Silence generator, when silence is being generated.
         */
        struct ast_silence_generator *silgen;
@@ -72,6 +82,9 @@ static void control_dtor(void *obj)
 {
        struct stasis_app_control *control = obj;
 
+       AST_LIST_HEAD_DESTROY(&control->add_rules);
+       AST_LIST_HEAD_DESTROY(&control->remove_rules);
+
        /* We may have a lingering silence generator; free it */
        ast_channel_stop_silence_generator(control->channel, control->silgen);
        control->silgen = NULL;
@@ -106,22 +119,121 @@ struct stasis_app_control *control_create(struct ast_channel *channel)
 
        control->channel = channel;
 
+       AST_LIST_HEAD_INIT(&control->add_rules);
+       AST_LIST_HEAD_INIT(&control->remove_rules);
+
        ao2_ref(control, +1);
        return control;
 }
 
-static void *noop_cb(struct stasis_app_control *control,
+static void app_control_register_rule(
+       const struct stasis_app_control *control,
+       struct app_control_rules *list, struct stasis_app_control_rule *obj)
+{
+       SCOPED_AO2LOCK(lock, control->command_queue);
+       AST_LIST_INSERT_TAIL(list, obj, next);
+}
+
+static void app_control_unregister_rule(
+       const struct stasis_app_control *control,
+       struct app_control_rules *list, struct stasis_app_control_rule *obj)
+{
+       struct stasis_app_control_rule *rule;
+       SCOPED_AO2LOCK(lock, control->command_queue);
+       AST_RWLIST_TRAVERSE_SAFE_BEGIN(list, rule, next) {
+               if (rule == obj) {
+                       AST_RWLIST_REMOVE_CURRENT(next);
+                       break;
+               }
+       }
+       AST_RWLIST_TRAVERSE_SAFE_END;
+}
+
+/*!
+ * \internal
+ * \brief Checks to make sure each rule in the given list passes.
+ *
+ * \details Loops over a list of rules checking for rejections or failures.
+ *          If one rule fails its resulting error code is returned.
+ *
+ * \note Command queue should be locked before calling this function.
+ *
+ * \param control The stasis application control
+ * \param list The list of rules to check
+ *
+ * \retval 0 if all rules pass
+ * \retval non-zero error code if a rule fails
+ */
+static enum stasis_app_control_channel_result app_control_check_rules(
+       const struct stasis_app_control *control,
+       struct app_control_rules *list)
+{
+       int res = 0;
+       struct stasis_app_control_rule *rule;
+       AST_LIST_TRAVERSE(list, rule, next) {
+               if ((res = rule->check_rule(control))) {
+                       return res;
+               }
+       }
+       return res;
+}
+
+void stasis_app_control_register_add_rule(
+       struct stasis_app_control *control,
+       struct stasis_app_control_rule *rule)
+{
+       return app_control_register_rule(control, &control->add_rules, rule);
+}
+
+void stasis_app_control_unregister_add_rule(
+       struct stasis_app_control *control,
+       struct stasis_app_control_rule *rule)
+{
+       app_control_unregister_rule(control, &control->add_rules, rule);
+}
+
+void stasis_app_control_register_remove_rule(
+       struct stasis_app_control *control,
+       struct stasis_app_control_rule *rule)
+{
+       return app_control_register_rule(control, &control->remove_rules, rule);
+}
+
+void stasis_app_control_unregister_remove_rule(
+       struct stasis_app_control *control,
+       struct stasis_app_control_rule *rule)
+{
+       app_control_unregister_rule(control, &control->remove_rules, rule);
+}
+
+static int app_control_can_add_channel_to_bridge(
+       struct stasis_app_control *control)
+{
+       return app_control_check_rules(control, &control->add_rules);
+}
+
+static int app_control_can_remove_channel_from_bridge(
+       struct stasis_app_control *control)
+{
+       return app_control_check_rules(control, &control->remove_rules);
+}
+
+static int noop_cb(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
-       return NULL;
+       return 0;
 }
 
+/*! Callback type to see if the command can execute
+    note: command_queue is locked during callback */
+typedef int (*app_command_can_exec_cb)(struct stasis_app_control *control);
 
-static struct stasis_app_command *exec_command(
+static struct stasis_app_command *exec_command_on_condition(
        struct stasis_app_control *control, stasis_app_command_cb command_fn,
-       void *data)
+       void *data, app_command_can_exec_cb can_exec_fn)
 {
-       RAII_VAR(struct stasis_app_command *, command, NULL, ao2_cleanup);
+       int retval;
+       struct stasis_app_command *command;
 
        command_fn = command_fn ? : noop_cb;
 
@@ -131,24 +243,36 @@ static struct stasis_app_command *exec_command(
        }
 
        ao2_lock(control->command_queue);
+       if (can_exec_fn && (retval = can_exec_fn(control))) {
+               ao2_unlock(control->command_queue);
+               command_complete(command, retval);
+               return command;
+       }
+
        ao2_link_flags(control->command_queue, command, OBJ_NOLOCK);
        ast_cond_signal(&control->wait_cond);
        ao2_unlock(control->command_queue);
 
-       ao2_ref(command, +1);
        return command;
 }
 
+static struct stasis_app_command *exec_command(
+       struct stasis_app_control *control, stasis_app_command_cb command_fn,
+       void *data)
+{
+       return exec_command_on_condition(control, command_fn, data, NULL);
+}
+
 struct stasis_app_control_dial_data {
        char endpoint[AST_CHANNEL_NAME];
        int timeout;
 };
 
-static void *app_control_add_channel_to_bridge(
+static int app_control_add_channel_to_bridge(
         struct stasis_app_control *control,
         struct ast_channel *chan, void *data);
 
-static void *app_control_dial(struct stasis_app_control *control,
+static int app_control_dial(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        RAII_VAR(struct ast_dial *, dial, ast_dial_create(), ast_dial_destroy);
@@ -160,30 +284,30 @@ static void *app_control_dial(struct stasis_app_control *control,
 
        tech = dial_data->endpoint;
        if (!(resource = strchr(tech, '/'))) {
-               return NULL;
+               return -1;
        }
        *resource++ = '\0';
 
        if (!dial) {
                ast_log(LOG_ERROR, "Failed to create dialing structure.\n");
-               return NULL;
+               return -1;
        }
 
        if (ast_dial_append(dial, tech, resource) < 0) {
                ast_log(LOG_ERROR, "Failed to add %s/%s to dialing structure.\n", tech, resource);
-               return NULL;
+               return -1;
        }
 
        ast_dial_set_global_timeout(dial, dial_data->timeout);
 
        res = ast_dial_run(dial, NULL, 0);
        if (res != AST_DIAL_RESULT_ANSWERED || !(new_chan = ast_dial_answered_steal(dial))) {
-               return NULL;
+               return -1;
        }
 
        if (!(bridge = ast_bridge_basic_new())) {
                ast_log(LOG_ERROR, "Failed to create basic bridge.\n");
-               return NULL;
+               return -1;
        }
 
        if (ast_bridge_impart(bridge, new_chan, NULL, NULL,
@@ -193,7 +317,7 @@ static void *app_control_dial(struct stasis_app_control *control,
                app_control_add_channel_to_bridge(control, chan, bridge);
        }
 
-       return NULL;
+       return 0;
 }
 
 int stasis_app_control_dial(struct stasis_app_control *control, const char *endpoint, const char *exten, const char *context,
@@ -248,7 +372,7 @@ struct stasis_app_control_continue_data {
        int priority;
 };
 
-static void *app_control_continue(struct stasis_app_control *control,
+static int app_control_continue(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        RAII_VAR(struct stasis_app_control_continue_data *, continue_data, data, ast_free);
@@ -266,7 +390,7 @@ static void *app_control_continue(struct stasis_app_control *control,
 
        control->is_done = 1;
 
-       return NULL;
+       return 0;
 }
 
 int stasis_app_control_continue(struct stasis_app_control *control, const char *context, const char *extension, int priority)
@@ -297,7 +421,7 @@ struct stasis_app_control_dtmf_data {
        char dtmf[];
 };
 
-static void *app_control_dtmf(struct stasis_app_control *control,
+static int app_control_dtmf(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        RAII_VAR(struct stasis_app_control_dtmf_data *, dtmf_data, data, ast_free);
@@ -312,7 +436,7 @@ static void *app_control_dtmf(struct stasis_app_control *control,
                ast_safe_sleep(chan, dtmf_data->after);
        }
 
-       return NULL;
+       return 0;
 }
 
 int stasis_app_control_dtmf(struct stasis_app_control *control, const char *dtmf, int before, int between, unsigned int duration, int after)
@@ -334,12 +458,12 @@ int stasis_app_control_dtmf(struct stasis_app_control *control, const char *dtmf
        return 0;
 }
 
-static void *app_control_ring(struct stasis_app_control *control,
+static int app_control_ring(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        ast_indicate(control->channel, AST_CONTROL_RINGING);
 
-       return NULL;
+       return 0;
 }
 
 int stasis_app_control_ring(struct stasis_app_control *control)
@@ -349,12 +473,12 @@ int stasis_app_control_ring(struct stasis_app_control *control)
        return 0;
 }
 
-static void *app_control_ring_stop(struct stasis_app_control *control,
+static int app_control_ring_stop(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        ast_indicate(control->channel, -1);
 
-       return NULL;
+       return 0;
 }
 
 int stasis_app_control_ring_stop(struct stasis_app_control *control)
@@ -369,7 +493,7 @@ struct stasis_app_control_mute_data {
        unsigned int direction;
 };
 
-static void *app_control_mute(struct stasis_app_control *control,
+static int app_control_mute(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        RAII_VAR(struct stasis_app_control_mute_data *, mute_data, data, ast_free);
@@ -377,7 +501,7 @@ static void *app_control_mute(struct stasis_app_control *control,
 
        ast_channel_suppress(control->channel, mute_data->direction, mute_data->frametype);
 
-       return NULL;
+       return 0;
 }
 
 int stasis_app_control_mute(struct stasis_app_control *control, unsigned int direction, enum ast_frame_type frametype)
@@ -396,7 +520,7 @@ int stasis_app_control_mute(struct stasis_app_control *control, unsigned int dir
        return 0;
 }
 
-static void *app_control_unmute(struct stasis_app_control *control,
+static int app_control_unmute(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        RAII_VAR(struct stasis_app_control_mute_data *, mute_data, data, ast_free);
@@ -404,7 +528,7 @@ static void *app_control_unmute(struct stasis_app_control *control,
 
        ast_channel_unsuppress(control->channel, mute_data->direction, mute_data->frametype);
 
-       return NULL;
+       return 0;
 }
 
 int stasis_app_control_unmute(struct stasis_app_control *control, unsigned int direction, enum ast_frame_type frametype)
@@ -456,12 +580,12 @@ int stasis_app_control_set_channel_var(struct stasis_app_control *control, const
        return pbx_builtin_setvar_helper(control->channel, variable, value);
 }
 
-static void *app_control_hold(struct stasis_app_control *control,
+static int app_control_hold(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        ast_indicate(control->channel, AST_CONTROL_HOLD);
 
-       return NULL;
+       return 0;
 }
 
 void stasis_app_control_hold(struct stasis_app_control *control)
@@ -469,12 +593,12 @@ void stasis_app_control_hold(struct stasis_app_control *control)
        stasis_app_send_command_async(control, app_control_hold, NULL);
 }
 
-static void *app_control_unhold(struct stasis_app_control *control,
+static int app_control_unhold(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        ast_indicate(control->channel, AST_CONTROL_UNHOLD);
 
-       return NULL;
+       return 0;
 }
 
 void stasis_app_control_unhold(struct stasis_app_control *control)
@@ -482,7 +606,7 @@ void stasis_app_control_unhold(struct stasis_app_control *control)
        stasis_app_send_command_async(control, app_control_unhold, NULL);
 }
 
-static void *app_control_moh_start(struct stasis_app_control *control,
+static int app_control_moh_start(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        char *moh_class = data;
@@ -490,7 +614,7 @@ static void *app_control_moh_start(struct stasis_app_control *control,
        ast_moh_start(chan, moh_class, NULL);
 
        ast_free(moh_class);
-       return NULL;
+       return 0;
 }
 
 void stasis_app_control_moh_start(struct stasis_app_control *control, const char *moh_class)
@@ -504,11 +628,11 @@ void stasis_app_control_moh_start(struct stasis_app_control *control, const char
        stasis_app_send_command_async(control, app_control_moh_start, data);
 }
 
-static void *app_control_moh_stop(struct stasis_app_control *control,
+static int app_control_moh_stop(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        ast_moh_stop(chan);
-       return NULL;
+       return 0;
 }
 
 void stasis_app_control_moh_stop(struct stasis_app_control *control)
@@ -516,7 +640,7 @@ void stasis_app_control_moh_stop(struct stasis_app_control *control)
        stasis_app_send_command_async(control, app_control_moh_stop, NULL);
 }
 
-static void *app_control_silence_start(struct stasis_app_control *control,
+static int app_control_silence_start(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        if (control->silgen) {
@@ -538,7 +662,7 @@ static void *app_control_silence_start(struct stasis_app_control *control,
                        stasis_app_control_get_channel_id(control));
        }
 
-       return NULL;
+       return 0;
 }
 
 void stasis_app_control_silence_start(struct stasis_app_control *control)
@@ -546,7 +670,7 @@ void stasis_app_control_silence_start(struct stasis_app_control *control)
        stasis_app_send_command_async(control, app_control_silence_start, NULL);
 }
 
-static void *app_control_silence_stop(struct stasis_app_control *control,
+static int app_control_silence_stop(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        if (control->silgen) {
@@ -557,7 +681,7 @@ static void *app_control_silence_stop(struct stasis_app_control *control,
                control->silgen = NULL;
        }
 
-       return NULL;
+       return 0;
 }
 
 void stasis_app_control_silence_stop(struct stasis_app_control *control)
@@ -584,23 +708,31 @@ struct ast_channel_snapshot *stasis_app_control_get_snapshot(
        return snapshot;
 }
 
-void *stasis_app_send_command(struct stasis_app_control *control,
-       stasis_app_command_cb command_fn, void *data)
+static int app_send_command_on_condition(struct stasis_app_control *control,
+                                        stasis_app_command_cb command_fn, void *data,
+                                        app_command_can_exec_cb can_exec_fn)
 {
        RAII_VAR(struct stasis_app_command *, command, NULL, ao2_cleanup);
 
        if (control == NULL) {
-               return NULL;
+               return -1;
        }
 
-       command = exec_command(control, command_fn, data);
+       command = exec_command_on_condition(
+               control, command_fn, data, can_exec_fn);
        if (!command) {
-               return NULL;
+               return -1;
        }
 
        return command_join(command);
 }
 
+int stasis_app_send_command(struct stasis_app_control *control,
+       stasis_app_command_cb command_fn, void *data)
+{
+       return app_send_command_on_condition(control, command_fn, data, NULL);
+}
+
 int stasis_app_send_command_async(struct stasis_app_control *control,
        stasis_app_command_cb command_fn, void *data)
 {
@@ -628,7 +760,7 @@ struct ast_bridge *stasis_app_get_bridge(struct stasis_app_control *control)
        }
 }
 
-static void *bridge_channel_depart(struct stasis_app_control *control,
+static int bridge_channel_depart(struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        RAII_VAR(struct ast_bridge_channel *, bridge_channel, data, ao2_cleanup);
@@ -639,7 +771,7 @@ static void *bridge_channel_depart(struct stasis_app_control *control,
                if (bridge_channel != ast_channel_internal_bridge_channel(chan)) {
                        ast_debug(3, "%s: Channel is no longer in departable state\n",
                                ast_channel_uniqueid(chan));
-                       return NULL;
+                       return -1;
                }
        }
 
@@ -648,7 +780,7 @@ static void *bridge_channel_depart(struct stasis_app_control *control,
 
        ast_bridge_depart(chan);
 
-       return NULL;
+       return 0;
 }
 
 static void bridge_after_cb(struct ast_channel *chan, void *data)
@@ -691,10 +823,7 @@ static void bridge_after_cb_failed(enum ast_bridge_after_cb_reason reason,
                ast_bridge_after_cb_reason_string(reason));
 }
 
-static int OK = 0;
-static int FAIL = -1;
-
-static void *app_control_add_channel_to_bridge(
+static int app_control_add_channel_to_bridge(
        struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
@@ -702,7 +831,7 @@ static void *app_control_add_channel_to_bridge(
        int res;
 
        if (!control || !bridge) {
-               return NULL;
+               return -1;
        }
 
        ast_debug(3, "%s: Adding to bridge %s\n",
@@ -726,7 +855,7 @@ static void *app_control_add_channel_to_bridge(
                bridge_after_cb_failed, control);
        if (res != 0) {
                ast_log(LOG_ERROR, "Error setting after-bridge callback\n");
-               return &FAIL;
+               return -1;
        }
 
        {
@@ -752,34 +881,34 @@ static void *app_control_add_channel_to_bridge(
                        ast_log(LOG_ERROR, "Error adding channel to bridge\n");
                        ast_channel_pbx_set(chan, control->pbx);
                        control->pbx = NULL;
-                       return &FAIL;
+                       return -1;
                }
 
                ast_assert(stasis_app_get_bridge(control) == NULL);
                control->bridge = bridge;
        }
-       return &OK;
+       return 0;
 }
 
 int stasis_app_control_add_channel_to_bridge(
        struct stasis_app_control *control, struct ast_bridge *bridge)
 {
-       int *res;
        ast_debug(3, "%s: Sending channel add_to_bridge command\n",
                        stasis_app_control_get_channel_id(control));
-       res = stasis_app_send_command(control,
-               app_control_add_channel_to_bridge, bridge);
-       return *res;
+
+       return app_send_command_on_condition(
+               control, app_control_add_channel_to_bridge, bridge,
+               app_control_can_add_channel_to_bridge);
 }
 
-static void *app_control_remove_channel_from_bridge(
+static int app_control_remove_channel_from_bridge(
        struct stasis_app_control *control,
        struct ast_channel *chan, void *data)
 {
        struct ast_bridge *bridge = data;
 
        if (!control) {
-               return &FAIL;
+               return -1;
        }
 
        /* We should only depart from our own bridge */
@@ -791,22 +920,21 @@ static void *app_control_remove_channel_from_bridge(
                ast_log(LOG_WARNING, "%s: Not in bridge %s; not removing\n",
                        stasis_app_control_get_channel_id(control),
                        bridge->uniqueid);
-               return &FAIL;
+               return -1;
        }
 
        ast_bridge_depart(chan);
-       return &OK;
+       return 0;
 }
 
 int stasis_app_control_remove_channel_from_bridge(
        struct stasis_app_control *control, struct ast_bridge *bridge)
 {
-       int *res;
        ast_debug(3, "%s: Sending channel remove_from_bridge command\n",
                        stasis_app_control_get_channel_id(control));
-       res = stasis_app_send_command(control,
-               app_control_remove_channel_from_bridge, bridge);
-       return *res;
+       return app_send_command_on_condition(
+               control, app_control_remove_channel_from_bridge, bridge,
+               app_control_can_remove_channel_from_bridge);
 }
 
 const char *stasis_app_control_get_channel_id(
index 2339a96..d7f63ad 100644 (file)
                                                },
                                                {
                                                        "code": 409,
-                                                       "reason": "Bridge not in Stasis application"
+                                                       "reason": "Bridge not in Stasis application; Channel currently recording"
                                                },
                                                {
                                                        "code": 422,