diff options
Diffstat (limited to 'res/res_stasis.c')
-rw-r--r-- | res/res_stasis.c | 429 |
1 files changed, 242 insertions, 187 deletions
diff --git a/res/res_stasis.c b/res/res_stasis.c index e21941210..691462722 100644 --- a/res/res_stasis.c +++ b/res/res_stasis.c @@ -103,10 +103,15 @@ struct ao2_container *app_bridges; struct ao2_container *app_bridges_moh; +const char *stasis_app_name(const struct stasis_app *app) +{ + return app_name(app); +} + /*! AO2 hash function for \ref app */ static int app_hash(const void *obj, const int flags) { - const struct app *app; + const struct stasis_app *app; const char *key; switch (flags & OBJ_SEARCH_MASK) { @@ -115,7 +120,7 @@ static int app_hash(const void *obj, const int flags) break; case OBJ_SEARCH_OBJECT: app = obj; - key = app_name(app); + key = stasis_app_name(app); break; default: /* Hash can only work on something with a full key. */ @@ -128,24 +133,24 @@ static int app_hash(const void *obj, const int flags) /*! AO2 comparison function for \ref app */ static int app_compare(void *obj, void *arg, int flags) { - const struct app *object_left = obj; - const struct app *object_right = arg; + const struct stasis_app *object_left = obj; + const struct stasis_app *object_right = arg; const char *right_key = arg; int cmp; switch (flags & OBJ_SEARCH_MASK) { case OBJ_SEARCH_OBJECT: - right_key = app_name(object_right); + right_key = stasis_app_name(object_right); /* Fall through */ case OBJ_SEARCH_KEY: - cmp = strcmp(app_name(object_left), right_key); + cmp = strcmp(stasis_app_name(object_left), right_key); break; case OBJ_SEARCH_PARTIAL_KEY: /* * We could also use a partial key struct containing a length * so strlen() does not get called for every comparison instead. */ - cmp = strncmp(app_name(object_left), right_key, strlen(right_key)); + cmp = strncmp(stasis_app_name(object_left), right_key, strlen(right_key)); break; default: /* @@ -229,13 +234,13 @@ static int control_compare(void *obj, void *arg, int flags) static int cleanup_cb(void *obj, void *arg, int flags) { - struct app *app = obj; + struct stasis_app *app = obj; if (!app_is_finished(app)) { return 0; } - ast_verb(1, "Shutting down application '%s'\n", app_name(app)); + ast_verb(1, "Shutting down application '%s'\n", stasis_app_name(app)); app_shutdown(app); return CMP_MATCH; @@ -619,7 +624,7 @@ void stasis_app_bridge_destroy(const char *bridge_id) ast_bridge_destroy(bridge, 0); } -static int send_start_msg(struct app *app, struct ast_channel *chan, +static int send_start_msg(struct stasis_app *app, struct ast_channel *chan, int argc, char *argv[]) { RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); @@ -667,7 +672,7 @@ static int send_start_msg(struct app *app, struct ast_channel *chan, return 0; } -static int send_end_msg(struct app *app, struct ast_channel *chan) +static int send_end_msg(struct stasis_app *app, struct ast_channel *chan) { RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref); RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup); @@ -714,7 +719,7 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, { SCOPED_MODULE_USE(ast_module_info->self); - RAII_VAR(struct app *, app, NULL, ao2_cleanup); + RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup); RAII_VAR(struct stasis_app_control *, control, NULL, control_unlink); struct ast_bridge *last_bridge = NULL; int res = 0; @@ -838,7 +843,7 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc, int stasis_app_send(const char *app_name, struct ast_json *message) { - RAII_VAR(struct app *, app, NULL, ao2_cleanup); + RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup); app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY); if (!app) { @@ -849,17 +854,31 @@ int stasis_app_send(const char *app_name, struct ast_json *message) "Stasis app '%s' not registered\n", app_name); return -1; } - app_send(app, message); return 0; } +static struct stasis_app *find_app_by_name(const char *app_name) +{ + struct stasis_app *res = NULL; + + if (!ast_strlen_zero(app_name)) { + res = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY); + } + + if (!res) { + ast_log(LOG_WARNING, "Could not find app '%s'\n", + app_name ? : "(null)"); + } + return res; +} + static int append_name(void *obj, void *arg, int flags) { - struct app *app = obj; + struct stasis_app *app = obj; struct ao2_container *apps = arg; - ast_str_container_add(apps, app_name(app)); + ast_str_container_add(apps, stasis_app_name(app)); return 0; } @@ -879,7 +898,7 @@ struct ao2_container *stasis_app_get_all(void) int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data) { - RAII_VAR(struct app *, app, NULL, ao2_cleanup); + RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup); SCOPED_LOCK(apps_lock, apps_registry, ao2_lock, ao2_unlock); @@ -904,7 +923,7 @@ int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data) void stasis_app_unregister(const char *app_name) { - RAII_VAR(struct app *, app, NULL, ao2_cleanup); + RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup); if (!app_name) { return; @@ -925,217 +944,249 @@ void stasis_app_unregister(const char *app_name) cleanup(); } -struct ast_json *stasis_app_to_json(const char *app_name) +/*! + * \internal \brief List of registered event sources. + */ +AST_RWLIST_HEAD_STATIC(event_sources, stasis_app_event_source); + +void stasis_app_register_event_source(struct stasis_app_event_source *obj) { - RAII_VAR(struct app *, app, NULL, ao2_cleanup); + SCOPED_LOCK(lock, &event_sources, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK); + AST_LIST_INSERT_TAIL(&event_sources, obj, next); + /* only need to bump the module ref on non-core sources because the + core ones are [un]registered by this module. */ + if (!stasis_app_is_core_event_source(obj)) { + ast_module_ref(ast_module_info->self); + } +} + +void stasis_app_unregister_event_source(struct stasis_app_event_source *obj) +{ + struct stasis_app_event_source *source; + SCOPED_LOCK(lock, &event_sources, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK); + AST_RWLIST_TRAVERSE_SAFE_BEGIN(&event_sources, source, next) { + if (source == obj) { + AST_RWLIST_REMOVE_CURRENT(next); + if (!stasis_app_is_core_event_source(obj)) { + ast_module_unref(ast_module_info->self); + } + break; + } + } + AST_RWLIST_TRAVERSE_SAFE_END; +} - if (app_name) { - app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY); +/*! + * \internal + * \brief Convert event source data to JSON. + * + * Calls each event source that has a "to_json" handler allowing each + * source to add data to the given JSON object. + * + * \param app application associated with the event source + * \param json a json object to "fill" + * + * \retval The given json object. + */ +static struct ast_json *app_event_sources_to_json( + const struct stasis_app *app, struct ast_json *json) +{ + struct stasis_app_event_source *source; + SCOPED_LOCK(lock, &event_sources, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK); + AST_LIST_TRAVERSE(&event_sources, source, next) { + if (source->to_json) { + source->to_json(app, json); + } } + return json; +} +static struct ast_json *stasis_app_object_to_json(struct stasis_app *app) +{ if (!app) { return NULL; } - return app_to_json(app); + return app_event_sources_to_json(app, app_to_json(app)); } -#define CHANNEL_SCHEME "channel:" -#define BRIDGE_SCHEME "bridge:" -#define ENDPOINT_SCHEME "endpoint:" - -/*! Struct for capturing event source information */ -struct event_source { - enum { - EVENT_SOURCE_CHANNEL, - EVENT_SOURCE_BRIDGE, - EVENT_SOURCE_ENDPOINT, - } event_source_type; - union { - struct ast_channel *channel; - struct ast_bridge *bridge; - struct ast_endpoint *endpoint; - }; -}; - -enum stasis_app_subscribe_res stasis_app_subscribe(const char *app_name, - const char **event_source_uris, int event_sources_count, - struct ast_json **json) +struct ast_json *stasis_app_to_json(const char *app_name) { - RAII_VAR(struct app *, app, NULL, ao2_cleanup); - RAII_VAR(struct event_source *, event_sources, NULL, ast_free); - enum stasis_app_subscribe_res res = STASIS_ASR_OK; - int i; + RAII_VAR(struct stasis_app *, app, find_app_by_name(app_name), ao2_cleanup); - if (app_name) { - app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY); + return stasis_app_object_to_json(app); +} + +/*! + * \internal + * \brief Finds an event source that matches a uri scheme. + * + * Uri(s) should begin with a particular scheme that can be matched + * against an event source. + * + * \param uri uri containing a scheme to match + * + * \retval an event source if found, NULL otherwise. + */ +static struct stasis_app_event_source *app_event_source_find(const char *uri) +{ + struct stasis_app_event_source *source; + SCOPED_LOCK(lock, &event_sources, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK); + AST_LIST_TRAVERSE(&event_sources, source, next) { + if (ast_begins_with(uri, source->scheme)) { + return source; + } } + return NULL; +} + +/*! + * \internal + * \brief Callback for subscription handling + * + * \param app [un]subscribing application + * \param uri scheme:id of an event source + * \param event_source being [un]subscribed [from]to + * + * \retval stasis_app_subscribe_res return code. + */ +typedef enum stasis_app_subscribe_res (*app_subscription_handler)( + struct stasis_app *app, const char *uri, + struct stasis_app_event_source *event_source); + +/*! + * \internal + * \brief Subscriptions handler for application [un]subscribing. + * + * \param app_name Name of the application to subscribe. + * \param event_source_uris URIs for the event sources to subscribe to. + * \param event_sources_count Array size of event_source_uris. + * \param json Optional output pointer for JSON representation of the app + * after adding the subscription. + * \param handler [un]subscribe handler + * + * \retval stasis_app_subscribe_res return code. + */ +static enum stasis_app_subscribe_res app_handle_subscriptions( + const char *app_name, const char **event_source_uris, + int event_sources_count, struct ast_json **json, + app_subscription_handler handler) +{ + RAII_VAR(struct stasis_app *, app, find_app_by_name(app_name), ao2_cleanup); + int i; if (!app) { - ast_log(LOG_WARNING, "Could not find app '%s'\n", - app_name ? : "(null)"); return STASIS_ASR_APP_NOT_FOUND; } - event_sources = ast_calloc(event_sources_count, sizeof(*event_sources)); - if (!event_sources) { - return STASIS_ASR_INTERNAL_ERROR; - } - - for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) { + for (i = 0; i < event_sources_count; ++i) { const char *uri = event_source_uris[i]; - ast_debug(3, "%s: Checking %s\n", app_name, - uri); - if (ast_begins_with(uri, CHANNEL_SCHEME)) { - event_sources[i].event_source_type = - EVENT_SOURCE_CHANNEL; - event_sources[i].channel = ast_channel_get_by_name( - uri + strlen(CHANNEL_SCHEME)); - if (!event_sources[i].channel) { - ast_log(LOG_WARNING, "Channel not found: %s\n", uri); - res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND; - } - } else if (ast_begins_with(uri, BRIDGE_SCHEME)) { - event_sources[i].event_source_type = - EVENT_SOURCE_BRIDGE; - event_sources[i].bridge = stasis_app_bridge_find_by_id( - uri + strlen(BRIDGE_SCHEME)); - if (!event_sources[i].bridge) { - ast_log(LOG_WARNING, "Bridge not found: %s\n", uri); - res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND; - } - } else if (ast_begins_with(uri, ENDPOINT_SCHEME)) { - event_sources[i].event_source_type = - EVENT_SOURCE_ENDPOINT; - event_sources[i].endpoint = ast_endpoint_find_by_id( - uri + strlen(ENDPOINT_SCHEME)); - if (!event_sources[i].endpoint) { - ast_log(LOG_WARNING, "Endpoint not found: %s\n", uri); - res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND; - } - } else { + enum stasis_app_subscribe_res res = STASIS_ASR_INTERNAL_ERROR; + struct stasis_app_event_source *event_source; + + if (!(event_source = app_event_source_find(uri))) { ast_log(LOG_WARNING, "Invalid scheme: %s\n", uri); - res = STASIS_ASR_EVENT_SOURCE_BAD_SCHEME; + return STASIS_ASR_EVENT_SOURCE_BAD_SCHEME; } - } - for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) { - int sub_res = -1; - ast_debug(1, "%s: Subscribing to %s\n", app_name, - event_source_uris[i]); - - switch (event_sources[i].event_source_type) { - case EVENT_SOURCE_CHANNEL: - sub_res = app_subscribe_channel(app, - event_sources[i].channel); - break; - case EVENT_SOURCE_BRIDGE: - sub_res = app_subscribe_bridge(app, - event_sources[i].bridge); - break; - case EVENT_SOURCE_ENDPOINT: - sub_res = app_subscribe_endpoint(app, - event_sources[i].endpoint); - break; + if (handler && + ((res = handler(app, uri, event_source)))) { + return res; } + } - if (sub_res != 0) { - ast_log(LOG_WARNING, - "Error subscribing app '%s' to '%s'\n", - app_name, event_source_uris[i]); - res = STASIS_ASR_INTERNAL_ERROR; - } + if (json) { + ast_debug(3, "%s: Successful; setting results\n", app_name); + *json = stasis_app_object_to_json(app); } - if (res == STASIS_ASR_OK && json) { - ast_debug(1, "%s: Successful; setting results\n", app_name); - *json = app_to_json(app); + return STASIS_ASR_OK; +} + +/*! + * \internal + * \brief Subscribe an app to an event source. + * + * \param app subscribing application + * \param uri scheme:id of an event source + * \param event_source being subscribed to + * + * \retval stasis_app_subscribe_res return code. + */ +static enum stasis_app_subscribe_res app_subscribe( + struct stasis_app *app, const char *uri, + struct stasis_app_event_source *event_source) +{ + const char *app_name = stasis_app_name(app); + RAII_VAR(void *, obj, NULL, ao2_cleanup); + + ast_debug(3, "%s: Checking %s\n", app_name, uri); + + if (!event_source->find || + (!(obj = event_source->find(app, uri + strlen(event_source->scheme))))) { + ast_log(LOG_WARNING, "Event source not found: %s\n", uri); + return STASIS_ASR_EVENT_SOURCE_NOT_FOUND; } - for (i = 0; i < event_sources_count; ++i) { - switch (event_sources[i].event_source_type) { - case EVENT_SOURCE_CHANNEL: - event_sources[i].channel = - ast_channel_cleanup(event_sources[i].channel); - break; - case EVENT_SOURCE_BRIDGE: - ao2_cleanup(event_sources[i].bridge); - event_sources[i].bridge = NULL; - break; - case EVENT_SOURCE_ENDPOINT: - ao2_cleanup(event_sources[i].endpoint); - event_sources[i].endpoint = NULL; - break; - } + ast_debug(3, "%s: Subscribing to %s\n", app_name, uri); + + if (!event_source->subscribe || (event_source->subscribe(app, obj))) { + ast_log(LOG_WARNING, "Error subscribing app '%s' to '%s'\n", + app_name, uri); + return STASIS_ASR_INTERNAL_ERROR; } - return res; + return STASIS_ASR_OK; } -enum stasis_app_subscribe_res stasis_app_unsubscribe(const char *app_name, +enum stasis_app_subscribe_res stasis_app_subscribe(const char *app_name, const char **event_source_uris, int event_sources_count, struct ast_json **json) { - RAII_VAR(struct app *, app, NULL, ao2_cleanup); - enum stasis_app_subscribe_res res = STASIS_ASR_OK; - int i; + return app_handle_subscriptions( + app_name, event_source_uris, event_sources_count, + json, app_subscribe); +} - if (app_name) { - app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY); - } +/*! + * \internal + * \brief Unsubscribe an app from an event source. + * + * \param app application to unsubscribe + * \param uri scheme:id of an event source + * \param event_source being unsubscribed from + * + * \retval stasis_app_subscribe_res return code. + */ +static enum stasis_app_subscribe_res app_unsubscribe( + struct stasis_app *app, const char *uri, + struct stasis_app_event_source *event_source) +{ + const char *app_name = stasis_app_name(app); + const char *id = uri + strlen(event_source->scheme); - if (!app) { - ast_log(LOG_WARNING, "Could not find app '%s'\n", - app_name ? : "(null)"); - return STASIS_ASR_APP_NOT_FOUND; + if (!event_source->is_subscribed || + (!event_source->is_subscribed(app, id))) { + return STASIS_ASR_EVENT_SOURCE_NOT_FOUND; } - /* Validate the input */ - for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) { - if (ast_begins_with(event_source_uris[i], CHANNEL_SCHEME)) { - const char *channel_id = event_source_uris[i] + - strlen(CHANNEL_SCHEME); - if (!app_is_subscribed_channel_id(app, channel_id)) { - res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND; - } - } else if (ast_begins_with(event_source_uris[i], BRIDGE_SCHEME)) { - const char *bridge_id = event_source_uris[i] + - strlen(BRIDGE_SCHEME); - if (!app_is_subscribed_bridge_id(app, bridge_id)) { - res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND; - } - } else if (ast_begins_with(event_source_uris[i], ENDPOINT_SCHEME)) { - const char *endpoint_id = event_source_uris[i] + - strlen(ENDPOINT_SCHEME); - if (!app_is_subscribed_endpoint_id(app, endpoint_id)) { - res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND; - } - } else { - res = STASIS_ASR_EVENT_SOURCE_BAD_SCHEME; - } - } + ast_debug(3, "%s: Unsubscribing from %s\n", app_name, uri); - for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) { - if (ast_begins_with(event_source_uris[i], CHANNEL_SCHEME)) { - const char *channel_id = event_source_uris[i] + - strlen(CHANNEL_SCHEME); - app_unsubscribe_channel_id(app, channel_id); - } else if (ast_begins_with(event_source_uris[i], BRIDGE_SCHEME)) { - const char *bridge_id = event_source_uris[i] + - strlen(BRIDGE_SCHEME); - app_unsubscribe_bridge_id(app, bridge_id); - } else if (ast_begins_with(event_source_uris[i], ENDPOINT_SCHEME)) { - const char *endpoint_id = event_source_uris[i] + - strlen(ENDPOINT_SCHEME); - app_unsubscribe_endpoint_id(app, endpoint_id); - } - } - - if (res == STASIS_ASR_OK && json) { - *json = app_to_json(app); + if (!event_source->unsubscribe || (event_source->unsubscribe(app, id))) { + ast_log(LOG_WARNING, "Error unsubscribing app '%s' to '%s'\n", + app_name, uri); + return -1; } + return 0; +} - return res; +enum stasis_app_subscribe_res stasis_app_unsubscribe(const char *app_name, + const char **event_source_uris, int event_sources_count, + struct ast_json **json) +{ + return app_handle_subscriptions( + app_name, event_source_uris, event_sources_count, + json, app_unsubscribe); } void stasis_app_ref(void) @@ -1150,6 +1201,8 @@ void stasis_app_unref(void) static int unload_module(void) { + stasis_app_unregister_event_sources(); + ao2_cleanup(apps_registry); apps_registry = NULL; @@ -1206,6 +1259,8 @@ static int load_module(void) return AST_MODULE_LOAD_FAILURE; } + stasis_app_register_event_sources(); + return AST_MODULE_LOAD_SUCCESS; } |