diff options
Diffstat (limited to 'res')
-rw-r--r-- | res/ari/resource_bridges.c | 43 | ||||
-rw-r--r-- | res/ari/resource_bridges.h | 30 | ||||
-rw-r--r-- | res/res_ari_bridges.c | 144 | ||||
-rw-r--r-- | res/res_stasis.c | 246 |
4 files changed, 461 insertions, 2 deletions
diff --git a/res/ari/resource_bridges.c b/res/ari/resource_bridges.c index 348cf972b..670792fc1 100644 --- a/res/ari/resource_bridges.c +++ b/res/ari/resource_bridges.c @@ -43,6 +43,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/bridge.h" #include "asterisk/format_cap.h" #include "asterisk/file.h" +#include "asterisk/musiconhold.h" /*! * \brief Finds a bridge, filling the response with an error, if appropriate. @@ -498,6 +499,48 @@ void ast_ari_record_bridge(struct ast_variable *headers, struct ast_record_bridg ast_ari_response_created(response, recording_url, json); } +void ast_ari_moh_start_bridge(struct ast_variable *headers, struct ast_moh_start_bridge_args *args, struct ast_ari_response *response) +{ + RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup); + struct ast_channel *moh_channel; + const char *moh_class = args->moh_class; + + if (!bridge) { + /* The response is provided by find_bridge() */ + return; + } + + moh_channel = stasis_app_bridge_moh_channel(bridge); + if (!moh_channel) { + ast_ari_response_alloc_failed(response); + return; + } + + ast_moh_start(moh_channel, moh_class, NULL); + + ast_ari_response_no_content(response); + +} + +void ast_ari_moh_stop_bridge(struct ast_variable *headers, struct ast_moh_stop_bridge_args *args, struct ast_ari_response *response) +{ + RAII_VAR(struct ast_bridge *, bridge, find_bridge(response, args->bridge_id), ao2_cleanup); + + if (!bridge) { + /* the response is provided by find_bridge() */ + return; + } + + if (stasis_app_bridge_moh_stop(bridge)) { + ast_ari_response_error( + response, 409, "Conflict", + "Bridge isn't playing music"); + return; + } + + ast_ari_response_no_content(response); +} + void ast_ari_get_bridge(struct ast_variable *headers, struct ast_get_bridge_args *args, struct ast_ari_response *response) { RAII_VAR(struct ast_bridge_snapshot *, snapshot, ast_bridge_snapshot_get_latest(args->bridge_id), ao2_cleanup); diff --git a/res/ari/resource_bridges.h b/res/ari/resource_bridges.h index d82cb6f8b..dd4575a73 100644 --- a/res/ari/resource_bridges.h +++ b/res/ari/resource_bridges.h @@ -133,6 +133,36 @@ struct ast_remove_channel_from_bridge_args { * \param[out] response HTTP response */ void ast_ari_remove_channel_from_bridge(struct ast_variable *headers, struct ast_remove_channel_from_bridge_args *args, struct ast_ari_response *response); +/*! \brief Argument struct for ast_ari_moh_start_bridge() */ +struct ast_moh_start_bridge_args { + /*! \brief Bridge's id */ + const char *bridge_id; + /*! \brief Channel's id */ + const char *moh_class; +}; +/*! + * \brief Play music on hold to a bridge or change the MOH class that is playing. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void ast_ari_moh_start_bridge(struct ast_variable *headers, struct ast_moh_start_bridge_args *args, struct ast_ari_response *response); +/*! \brief Argument struct for ast_ari_moh_stop_bridge() */ +struct ast_moh_stop_bridge_args { + /*! \brief Bridge's id */ + const char *bridge_id; +}; +/*! + * \brief Stop playing music on hold to a bridge. + * + * This will only stop music on hold being played via bridges/{bridgeId}/mohStart. + * + * \param headers HTTP headers + * \param args Swagger parameters + * \param[out] response HTTP response + */ +void ast_ari_moh_stop_bridge(struct ast_variable *headers, struct ast_moh_stop_bridge_args *args, struct ast_ari_response *response); /*! \brief Argument struct for ast_ari_play_on_bridge() */ struct ast_play_on_bridge_args { /*! \brief Bridge's id */ diff --git a/res/res_ari_bridges.c b/res/res_ari_bridges.c index 05ea12e6a..fe72df714 100644 --- a/res/res_ari_bridges.c +++ b/res/res_ari_bridges.c @@ -469,6 +469,128 @@ fin: __attribute__((unused)) return; } /*! + * \brief Parameter parsing callback for /bridges/{bridgeId}/mohStart. + * \param get_params GET parameters in the HTTP request. + * \param path_vars Path variables extracted from the request. + * \param headers HTTP headers. + * \param[out] response Response to the HTTP request. + */ +static void ast_ari_moh_start_bridge_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct ast_ari_response *response) +{ + struct ast_moh_start_bridge_args args = {}; + struct ast_variable *i; +#if defined(AST_DEVMODE) + int is_valid; + int code; +#endif /* AST_DEVMODE */ + + for (i = get_params; i; i = i->next) { + if (strcmp(i->name, "mohClass") == 0) { + args.moh_class = (i->value); + } else + {} + } + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "bridgeId") == 0) { + args.bridge_id = (i->value); + } else + {} + } + ast_ari_moh_start_bridge(headers, &args, response); +#if defined(AST_DEVMODE) + code = response->response_code; + + switch (code) { + case 0: /* Implementation is still a stub, or the code wasn't set */ + is_valid = response->message == NULL; + break; + case 500: /* Internal Server Error */ + case 501: /* Not Implemented */ + case 404: /* Bridge not found */ + case 409: /* Bridge not in Stasis application */ + is_valid = 1; + break; + default: + if (200 <= code && code <= 299) { + is_valid = ast_ari_validate_void( + response->message); + } else { + ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/mohStart\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/mohStart\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + +fin: __attribute__((unused)) + return; +} +/*! + * \brief Parameter parsing callback for /bridges/{bridgeId}/mohStop. + * \param get_params GET parameters in the HTTP request. + * \param path_vars Path variables extracted from the request. + * \param headers HTTP headers. + * \param[out] response Response to the HTTP request. + */ +static void ast_ari_moh_stop_bridge_cb( + struct ast_variable *get_params, struct ast_variable *path_vars, + struct ast_variable *headers, struct ast_ari_response *response) +{ + struct ast_moh_stop_bridge_args args = {}; + struct ast_variable *i; +#if defined(AST_DEVMODE) + int is_valid; + int code; +#endif /* AST_DEVMODE */ + + for (i = path_vars; i; i = i->next) { + if (strcmp(i->name, "bridgeId") == 0) { + args.bridge_id = (i->value); + } else + {} + } + ast_ari_moh_stop_bridge(headers, &args, response); +#if defined(AST_DEVMODE) + code = response->response_code; + + switch (code) { + case 0: /* Implementation is still a stub, or the code wasn't set */ + is_valid = response->message == NULL; + break; + case 500: /* Internal Server Error */ + case 501: /* Not Implemented */ + case 404: /* Bridge not found */ + case 409: /* Bridge not in Stasis application */ + is_valid = 1; + break; + default: + if (200 <= code && code <= 299) { + is_valid = ast_ari_validate_void( + response->message); + } else { + ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/mohStop\n", code); + is_valid = 0; + } + } + + if (!is_valid) { + ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/mohStop\n"); + ast_ari_response_error(response, 500, + "Internal Server Error", "Response validation failed"); + } +#endif /* AST_DEVMODE */ + +fin: __attribute__((unused)) + return; +} +/*! * \brief Parameter parsing callback for /bridges/{bridgeId}/play. * \param get_params GET parameters in the HTTP request. * \param path_vars Path variables extracted from the request. @@ -641,6 +763,24 @@ static struct stasis_rest_handlers bridges_bridgeId_removeChannel = { .children = { } }; /*! \brief REST handler for /api-docs/bridges.{format} */ +static struct stasis_rest_handlers bridges_bridgeId_mohStart = { + .path_segment = "mohStart", + .callbacks = { + [AST_HTTP_POST] = ast_ari_moh_start_bridge_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/bridges.{format} */ +static struct stasis_rest_handlers bridges_bridgeId_mohStop = { + .path_segment = "mohStop", + .callbacks = { + [AST_HTTP_POST] = ast_ari_moh_stop_bridge_cb, + }, + .num_children = 0, + .children = { } +}; +/*! \brief REST handler for /api-docs/bridges.{format} */ static struct stasis_rest_handlers bridges_bridgeId_play = { .path_segment = "play", .callbacks = { @@ -666,8 +806,8 @@ static struct stasis_rest_handlers bridges_bridgeId = { [AST_HTTP_GET] = ast_ari_get_bridge_cb, [AST_HTTP_DELETE] = ast_ari_delete_bridge_cb, }, - .num_children = 4, - .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_play,&bridges_bridgeId_record, } + .num_children = 6, + .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_mohStart,&bridges_bridgeId_mohStop,&bridges_bridgeId_play,&bridges_bridgeId_record, } }; /*! \brief REST handler for /api-docs/bridges.{format} */ static struct stasis_rest_handlers bridges = { diff --git a/res/res_stasis.c b/res/res_stasis.c index 963e70d9e..35c1847bd 100644 --- a/res/res_stasis.c +++ b/res/res_stasis.c @@ -65,6 +65,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/strings.h" #include "stasis/app.h" #include "stasis/control.h" +#include "asterisk/core_unreal.h" +#include "asterisk/musiconhold.h" +#include "asterisk/causes.h" +#include "asterisk/stringfields.h" +#include "asterisk/bridge_after.h" /*! Time to wait for a frame in the application */ #define MAX_WAIT_MS 200 @@ -90,6 +95,8 @@ struct ao2_container *app_controls; struct ao2_container *app_bridges; +struct ao2_container *app_bridges_moh; + /*! \brief Message router for the channel caching topic */ struct stasis_message_router *channel_router; @@ -194,6 +201,234 @@ static int bridges_compare(void *lhs, void *rhs, int flags) } } +/*! + * Used with app_bridges_moh, provides links between bridges and existing music + * on hold channels that are being used with them. + */ +struct stasis_app_bridge_moh_wrapper { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(channel_id); + AST_STRING_FIELD(bridge_id); + ); +}; + +static void stasis_app_bridge_moh_wrapper_destructor(void *obj) +{ + struct stasis_app_bridge_moh_wrapper *wrapper = obj; + ast_string_field_free_memory(wrapper); +} + +/*! AO2 hash function for the bridges moh container */ +static int bridges_moh_hash_fn(const void *obj, const int flags) +{ + const struct stasis_app_bridge_moh_wrapper *wrapper; + const char *key; + + switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + case OBJ_KEY: + key = obj; + return ast_str_hash(key); + case OBJ_POINTER: + wrapper = obj; + return ast_str_hash(wrapper->bridge_id); + default: + /* Hash can only work on something with a full key. */ + ast_assert(0); + return 0; + } +} + +static int bridges_moh_sort_fn(const void *obj_left, const void *obj_right, const int flags) +{ + const struct stasis_app_bridge_moh_wrapper *left = obj_left; + const struct stasis_app_bridge_moh_wrapper *right = obj_right; + const char *right_key = obj_right; + int cmp; + + switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) { + case OBJ_POINTER: + right_key = right->bridge_id; + /* Fall through */ + case OBJ_KEY: + cmp = strcmp(left->bridge_id, right_key); + break; + case OBJ_PARTIAL_KEY: + cmp = strncmp(left->bridge_id, right_key, strlen(right_key)); + break; + default: + /* Sort can only work on something with a full or partial key. */ + ast_assert(0); + cmp = 0; + break; + } + return cmp; +} + +/*! Removes the bridge to music on hold channel link */ +static void remove_bridge_moh(char *bridge_id) +{ + RAII_VAR(struct stasis_app_bridge_moh_wrapper *, moh_wrapper, ao2_find(app_bridges_moh, bridge_id, OBJ_KEY), ao2_cleanup); + + if (moh_wrapper) { + ao2_unlink_flags(app_bridges_moh, moh_wrapper, OBJ_NOLOCK); + } + ast_free(bridge_id); +} + +/*! After bridge failure callback for moh channels */ +static void moh_after_bridge_cb_failed(enum ast_bridge_after_cb_reason reason, void *data) +{ + char *bridge_id = data; + + remove_bridge_moh(bridge_id); +} + +/*! After bridge callback for moh channels */ +static void moh_after_bridge_cb(struct ast_channel *chan, void *data) +{ + char *bridge_id = data; + + remove_bridge_moh(bridge_id); +} + +/*! Request a bridge MOH channel */ +static struct ast_channel *prepare_bridge_moh_channel(void) +{ + RAII_VAR(struct ast_format_cap *, cap, NULL, ast_format_cap_destroy); + struct ast_format format; + + cap = ast_format_cap_alloc_nolock(); + if (!cap) { + return NULL; + } + + ast_format_cap_add(cap, ast_format_set(&format, AST_FORMAT_SLINEAR, 0)); + + return ast_request("Announcer", cap, NULL, "ARI_MOH", NULL); +} + +/*! Provides the moh channel with a thread so it can actually play its music */ +static void *moh_channel_thread(void *data) +{ + struct ast_channel *moh_channel = data; + + while (!ast_safe_sleep(moh_channel, 1000)); + + ast_moh_stop(moh_channel); + ast_hangup(moh_channel); + + return NULL; +} + +/*! + * \internal + * \brief Creates, pushes, and links a channel for playing music on hold to bridge + * + * \param bridge Which bridge this moh channel exists for + * + * \retval NULL if the channel could not be created, pushed, or linked + * \retval Reference to the channel on success + */ +static struct ast_channel *bridge_moh_create(struct ast_bridge *bridge) +{ + RAII_VAR(struct stasis_app_bridge_moh_wrapper *, new_wrapper, NULL, ao2_cleanup); + RAII_VAR(char *, bridge_id, ast_strdup(bridge->uniqueid), ast_free); + struct ast_channel *chan; + pthread_t threadid; + + if (!bridge_id) { + return NULL; + } + + chan = prepare_bridge_moh_channel(); + + if (!chan) { + return NULL; + } + + /* The after bridge callback assumes responsibility of the bridge_id. */ + ast_bridge_set_after_callback(chan, moh_after_bridge_cb, moh_after_bridge_cb_failed, bridge_id); + + bridge_id = NULL; + + if (ast_unreal_channel_push_to_bridge(chan, bridge, + AST_BRIDGE_CHANNEL_FLAG_IMMOVABLE | AST_BRIDGE_CHANNEL_FLAG_LONELY)) { + ast_hangup(chan); + return NULL; + } + + new_wrapper = ao2_alloc_options(sizeof(*new_wrapper), stasis_app_bridge_moh_wrapper_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!new_wrapper) { + ast_hangup(chan); + return NULL; + } + + if (ast_string_field_init(new_wrapper, 32)) { + ast_hangup(chan); + return NULL; + } + + ast_string_field_set(new_wrapper, bridge_id, bridge->uniqueid); + ast_string_field_set(new_wrapper, channel_id, ast_channel_uniqueid(chan)); + + if (!ao2_link(app_bridges_moh, new_wrapper)) { + ast_hangup(chan); + return NULL; + } + + if (ast_pthread_create_detached(&threadid, NULL, moh_channel_thread, chan)) { + ast_log(LOG_ERROR, "Failed to create channel thread. Abandoning MOH channel creation.\n"); + ao2_unlink_flags(app_bridges_moh, new_wrapper, OBJ_NOLOCK); + ast_hangup(chan); + return NULL; + } + + return chan; +} + +struct ast_channel *stasis_app_bridge_moh_channel(struct ast_bridge *bridge) +{ + RAII_VAR(struct stasis_app_bridge_moh_wrapper *, moh_wrapper, NULL, ao2_cleanup); + + SCOPED_AO2LOCK(lock, app_bridges_moh); + + moh_wrapper = ao2_find(app_bridges_moh, bridge->uniqueid, OBJ_KEY | OBJ_NOLOCK); + + if (!moh_wrapper) { + struct ast_channel *bridge_moh_channel = bridge_moh_create(bridge); + return bridge_moh_channel; + } + + return ast_channel_get_by_name(moh_wrapper->channel_id); +} + +int stasis_app_bridge_moh_stop(struct ast_bridge *bridge) +{ + RAII_VAR(struct stasis_app_bridge_moh_wrapper *, moh_wrapper, NULL, ao2_cleanup); + struct ast_channel *chan; + + SCOPED_AO2LOCK(lock, app_bridges_moh); + + moh_wrapper = ao2_find(app_bridges_moh, bridge->uniqueid, OBJ_KEY | OBJ_NOLOCK); + + if (!moh_wrapper) { + return -1; + } + + chan = ast_channel_get_by_name(moh_wrapper->channel_id); + if (!chan) { + return -1; + } + + ast_moh_stop(chan); + ast_softhangup(chan, AST_CAUSE_NORMAL_CLEARING); + ao2_cleanup(chan); + + ao2_unlink_flags(app_bridges_moh, moh_wrapper, OBJ_NOLOCK); + + return 0; +} + struct ast_bridge *stasis_app_bridge_find_by_id( const char *bridge_id) { @@ -1003,6 +1238,14 @@ static int load_module(void) return AST_MODULE_LOAD_FAILURE; } + app_bridges_moh = ao2_container_alloc_hash( + AO2_ALLOC_OPT_LOCK_MUTEX, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, + 37, bridges_moh_hash_fn, bridges_moh_sort_fn, NULL); + + if (!app_bridges_moh) { + return AST_MODULE_LOAD_FAILURE; + } + channel_router = stasis_message_router_create(ast_channel_topic_all_cached()); if (!channel_router) { return AST_MODULE_LOAD_FAILURE; @@ -1058,6 +1301,9 @@ static int unload_module(void) ao2_cleanup(app_bridges); app_bridges = NULL; + ao2_cleanup(app_bridges_moh); + app_bridges_moh = NULL; + return r; } |