summaryrefslogtreecommitdiff
path: root/res
diff options
context:
space:
mode:
authorKevin Harwell <kharwell@digium.com>2013-11-23 17:48:28 +0000
committerKevin Harwell <kharwell@digium.com>2013-11-23 17:48:28 +0000
commited483779946553e2ec42472c6b665b86dcb07066 (patch)
tree954ae85065f670f111954e36e5bd77d4f271778a /res
parent05cbf8df9b2ea0b41e049698b9f51ee4365ceab0 (diff)
ARI: Implement device state API
Created a data model and implemented functionality for an ARI device state resource. The following operations have been added that allow a user to manipulate an ARI controlled device: Create/Change the state of an ARI controlled device PUT /deviceStates/{deviceName}&{deviceState} Retrieve all ARI controlled devices GET /deviceStates Retrieve the current state of a device GET /deviceStates/{deviceName} Destroy a device-state controlled by ARI DELETE /deviceStates/{deviceName} The ARI controlled device must begin with 'Stasis:'. An example controlled device name would be Stasis:Example. A 'DeviceStateChanged' event has also been added so that an application can subscribe and receive device change events. Any device state, ARI controlled or not, can be subscribed to. While adding the event, the underlying subscription control mechanism was refactored so that all current and future resource subscriptions would be the same. Each event resource must now register itself in order to be able to properly handle [un]subscribes. (issue ASTERISK-22838) Reported by: Matt Jordan Review: https://reviewboard.asterisk.org/r/3025/ ........ Merged revisions 403134 from http://svn.asterisk.org/svn/asterisk/branches/12 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@403135 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'res')
-rw-r--r--res/ari.make4
-rw-r--r--res/ari/ari_model_validators.c156
-rw-r--r--res/ari/ari_model_validators.h45
-rw-r--r--res/ari/resource_applications.h4
-rw-r--r--res/ari/resource_device_states.c111
-rw-r--r--res/ari/resource_device_states.h95
-rw-r--r--res/res_ari_device_states.c323
-rw-r--r--res/res_stasis.c429
-rw-r--r--res/res_stasis_device_state.c416
-rw-r--r--res/res_stasis_device_state.exports.in6
-rw-r--r--res/stasis/app.c143
-rw-r--r--res/stasis/app.h42
12 files changed, 1531 insertions, 243 deletions
diff --git a/res/ari.make b/res/ari.make
index 0257f81a9..c5dac2d3c 100644
--- a/res/ari.make
+++ b/res/ari.make
@@ -45,6 +45,10 @@ res_ari_playbacks.so: ari/resource_playbacks.o
ari/resource_playbacks.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_playbacks)
+res_ari_device_states.so: ari/resource_device_states.o
+
+ari/resource_device_states.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_device_states)
+
res_ari_events.so: ari/resource_events.o
ari/resource_events.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_events)
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index 9ea2f94cd..f84a92ed7 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -1333,6 +1333,60 @@ ari_validator ast_ari_validate_playback_fn(void)
return ast_ari_validate_playback;
}
+int ast_ari_validate_device_state(struct ast_json *json)
+{
+ int res = 1;
+ struct ast_json_iter *iter;
+ int has_name = 0;
+ int has_state = 0;
+
+ for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+ if (strcmp("name", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_name = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI DeviceState field name failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("state", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_state = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI DeviceState field state failed validation\n");
+ res = 0;
+ }
+ } else
+ {
+ ast_log(LOG_ERROR,
+ "ARI DeviceState has undocumented field %s\n",
+ ast_json_object_iter_key(iter));
+ res = 0;
+ }
+ }
+
+ if (!has_name) {
+ ast_log(LOG_ERROR, "ARI DeviceState missing required field name\n");
+ res = 0;
+ }
+
+ if (!has_state) {
+ ast_log(LOG_ERROR, "ARI DeviceState missing required field state\n");
+ res = 0;
+ }
+
+ return res;
+}
+
+ari_validator ast_ari_validate_device_state_fn(void)
+{
+ return ast_ari_validate_device_state;
+}
+
int ast_ari_validate_application_replaced(struct ast_json *json)
{
int res = 1;
@@ -2746,6 +2800,85 @@ ari_validator ast_ari_validate_channel_varset_fn(void)
return ast_ari_validate_channel_varset;
}
+int ast_ari_validate_device_state_changed(struct ast_json *json)
+{
+ int res = 1;
+ struct ast_json_iter *iter;
+ int has_type = 0;
+ int has_application = 0;
+ int has_device_state = 0;
+
+ for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+ if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_type = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI DeviceStateChanged field type failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_application = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI DeviceStateChanged field application failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_date(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI DeviceStateChanged field timestamp failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("device_state", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_device_state = 1;
+ prop_is_valid = ast_ari_validate_device_state(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI DeviceStateChanged field device_state failed validation\n");
+ res = 0;
+ }
+ } else
+ {
+ ast_log(LOG_ERROR,
+ "ARI DeviceStateChanged has undocumented field %s\n",
+ ast_json_object_iter_key(iter));
+ res = 0;
+ }
+ }
+
+ if (!has_type) {
+ ast_log(LOG_ERROR, "ARI DeviceStateChanged missing required field type\n");
+ res = 0;
+ }
+
+ if (!has_application) {
+ ast_log(LOG_ERROR, "ARI DeviceStateChanged missing required field application\n");
+ res = 0;
+ }
+
+ if (!has_device_state) {
+ ast_log(LOG_ERROR, "ARI DeviceStateChanged missing required field device_state\n");
+ res = 0;
+ }
+
+ return res;
+}
+
+ari_validator ast_ari_validate_device_state_changed_fn(void)
+{
+ return ast_ari_validate_device_state_changed;
+}
+
int ast_ari_validate_endpoint_state_change(struct ast_json *json)
{
int res = 1;
@@ -2887,6 +3020,9 @@ int ast_ari_validate_event(struct ast_json *json)
if (strcmp("ChannelVarset", discriminator) == 0) {
return ast_ari_validate_channel_varset(json);
} else
+ if (strcmp("DeviceStateChanged", discriminator) == 0) {
+ return ast_ari_validate_device_state_changed(json);
+ } else
if (strcmp("EndpointStateChange", discriminator) == 0) {
return ast_ari_validate_endpoint_state_change(json);
} else
@@ -3025,6 +3161,9 @@ int ast_ari_validate_message(struct ast_json *json)
if (strcmp("ChannelVarset", discriminator) == 0) {
return ast_ari_validate_channel_varset(json);
} else
+ if (strcmp("DeviceStateChanged", discriminator) == 0) {
+ return ast_ari_validate_device_state_changed(json);
+ } else
if (strcmp("EndpointStateChange", discriminator) == 0) {
return ast_ari_validate_endpoint_state_change(json);
} else
@@ -3592,6 +3731,7 @@ int ast_ari_validate_application(struct ast_json *json)
struct ast_json_iter *iter;
int has_bridge_ids = 0;
int has_channel_ids = 0;
+ int has_device_names = 0;
int has_endpoint_ids = 0;
int has_name = 0;
@@ -3618,6 +3758,17 @@ int ast_ari_validate_application(struct ast_json *json)
res = 0;
}
} else
+ if (strcmp("device_names", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_device_names = 1;
+ prop_is_valid = ast_ari_validate_list(
+ ast_json_object_iter_value(iter),
+ ast_ari_validate_string);
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI Application field device_names failed validation\n");
+ res = 0;
+ }
+ } else
if (strcmp("endpoint_ids", ast_json_object_iter_key(iter)) == 0) {
int prop_is_valid;
has_endpoint_ids = 1;
@@ -3657,6 +3808,11 @@ int ast_ari_validate_application(struct ast_json *json)
res = 0;
}
+ if (!has_device_names) {
+ ast_log(LOG_ERROR, "ARI Application missing required field device_names\n");
+ res = 0;
+ }
+
if (!has_endpoint_ids) {
ast_log(LOG_ERROR, "ARI Application missing required field endpoint_ids\n");
res = 0;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index a1e5b099e..979fa4a47 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -481,6 +481,24 @@ int ast_ari_validate_playback(struct ast_json *json);
ari_validator ast_ari_validate_playback_fn(void);
/*!
+ * \brief Validator for DeviceState.
+ *
+ * Represents the state of a device.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_device_state(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_device_state().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_device_state_fn(void);
+
+/*!
* \brief Validator for ApplicationReplaced.
*
* Notification that another WebSocket has taken over for an application.
@@ -755,6 +773,24 @@ int ast_ari_validate_channel_varset(struct ast_json *json);
ari_validator ast_ari_validate_channel_varset_fn(void);
/*!
+ * \brief Validator for DeviceStateChanged.
+ *
+ * Notification that a device state has changed.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_device_state_changed(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_device_state_changed().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_device_state_changed_fn(void);
+
+/*!
* \brief Validator for EndpointStateChange.
*
* Endpoint state changed.
@@ -1052,6 +1088,9 @@ ari_validator ast_ari_validate_application_fn(void);
* - media_uri: string (required)
* - state: string (required)
* - target_uri: string (required)
+ * DeviceState
+ * - name: string (required)
+ * - state: string (required)
* ApplicationReplaced
* - type: string (required)
* - application: string (required)
@@ -1143,6 +1182,11 @@ ari_validator ast_ari_validate_application_fn(void);
* - channel: Channel
* - value: string (required)
* - variable: string (required)
+ * DeviceStateChanged
+ * - type: string (required)
+ * - application: string (required)
+ * - timestamp: Date
+ * - device_state: DeviceState (required)
* EndpointStateChange
* - type: string (required)
* - application: string (required)
@@ -1187,6 +1231,7 @@ ari_validator ast_ari_validate_application_fn(void);
* Application
* - bridge_ids: List[string] (required)
* - channel_ids: List[string] (required)
+ * - device_names: List[string] (required)
* - endpoint_ids: List[string] (required)
* - name: string (required)
*/
diff --git a/res/ari/resource_applications.h b/res/ari/resource_applications.h
index 12aacb330..6aebd07fd 100644
--- a/res/ari/resource_applications.h
+++ b/res/ari/resource_applications.h
@@ -67,7 +67,7 @@ void ast_ari_applications_get(struct ast_variable *headers, struct ast_ari_appli
struct ast_ari_applications_subscribe_args {
/*! \brief Application's name */
const char *application_name;
- /*! \brief Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource} */
+ /*! \brief Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, deviceState:{deviceName} */
const char **event_source;
/*! \brief Length of event_source array. */
size_t event_source_count;
@@ -88,7 +88,7 @@ void ast_ari_applications_subscribe(struct ast_variable *headers, struct ast_ari
struct ast_ari_applications_unsubscribe_args {
/*! \brief Application's name */
const char *application_name;
- /*! \brief Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource} */
+ /*! \brief Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, device_state:{deviceName} */
const char **event_source;
/*! \brief Length of event_source array. */
size_t event_source_count;
diff --git a/res/ari/resource_device_states.c b/res/ari/resource_device_states.c
new file mode 100644
index 000000000..50b876740
--- /dev/null
+++ b/res/ari/resource_device_states.c
@@ -0,0 +1,111 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * Kevin Harwell <kharwell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief /api-docs/deviceStates.{format} implementation- Device state resources
+ *
+ * \author Kevin Harwell <kharwell@digium.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "resource_device_states.h"
+#include "asterisk/stasis_app_device_state.h"
+
+void ast_ari_device_states_list(
+ struct ast_variable *headers,
+ struct ast_ari_device_states_list_args *args,
+ struct ast_ari_response *response)
+{
+ RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+
+ if (!(json = stasis_app_device_states_to_json())) {
+ ast_ari_response_error(response, 500,
+ "Internal Server Error", "Error building response");
+ return;
+ }
+
+ ast_ari_response_ok(response, json);
+}
+
+void ast_ari_device_states_get(struct ast_variable *headers,
+ struct ast_ari_device_states_get_args *args,
+ struct ast_ari_response *response)
+{
+ RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+
+ if (!(json = stasis_app_device_state_to_json(
+ args->device_name, ast_device_state(args->device_name)))) {
+ ast_ari_response_error(response, 500,
+ "Internal Server Error", "Error building response");
+ return;
+ }
+
+ ast_ari_response_ok(response, json);
+}
+
+void ast_ari_device_states_update(struct ast_variable *headers,
+ struct ast_ari_device_states_update_args *args,
+ struct ast_ari_response *response)
+{
+ switch (stasis_app_device_state_update(
+ args->device_name, args->device_state)) {
+ case STASIS_DEVICE_STATE_NOT_CONTROLLED:
+ ast_ari_response_error(response, 409,
+ "Conflict", "Uncontrolled device specified");
+ return;
+ case STASIS_DEVICE_STATE_MISSING:
+ ast_ari_response_error(response, 404,
+ "Not Found", "Device name is missing");
+ return;
+ case STASIS_DEVICE_STATE_UNKNOWN:
+ ast_ari_response_error(response, 500, "Internal Server Error",
+ "Unknown device");
+ return;
+ case STASIS_DEVICE_STATE_OK:
+ case STASIS_DEVICE_STATE_SUBSCRIBERS: /* shouldn't be returned for update */
+ ast_ari_response_no_content(response);
+ }
+}
+
+void ast_ari_device_states_delete(struct ast_variable *headers,
+ struct ast_ari_device_states_delete_args *args,
+ struct ast_ari_response *response)
+{
+ switch (stasis_app_device_state_delete(args->device_name)) {
+ case STASIS_DEVICE_STATE_NOT_CONTROLLED:
+ ast_ari_response_error(response, 409,
+ "Conflict", "Uncontrolled device specified");
+ return;
+ case STASIS_DEVICE_STATE_MISSING:
+ ast_ari_response_error(response, 404,
+ "Not Found", "Device name is missing");
+ return;
+ case STASIS_DEVICE_STATE_SUBSCRIBERS:
+ ast_ari_response_error(response, 500,
+ "Internal Server Error",
+ "Cannot delete device with subscribers");
+ return;
+ case STASIS_DEVICE_STATE_OK:
+ case STASIS_DEVICE_STATE_UNKNOWN:
+ ast_ari_response_no_content(response);
+ }
+}
diff --git a/res/ari/resource_device_states.h b/res/ari/resource_device_states.h
new file mode 100644
index 000000000..a3bac999c
--- /dev/null
+++ b/res/ari/resource_device_states.h
@@ -0,0 +1,95 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * Kevin Harwell <kharwell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Generated file - declares stubs to be implemented in
+ * res/ari/resource_deviceStates.c
+ *
+ * Device state resources
+ *
+ * \author Kevin Harwell <kharwell@digium.com>
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!! DO NOT EDIT !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/ari_resource.h.mustache
+ */
+
+#ifndef _ASTERISK_RESOURCE_DEVICESTATES_H
+#define _ASTERISK_RESOURCE_DEVICESTATES_H
+
+#include "asterisk/ari.h"
+
+/*! \brief Argument struct for ast_ari_device_states_list() */
+struct ast_ari_device_states_list_args {
+};
+/*!
+ * \brief List all ARI controlled device states.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_device_states_list(struct ast_variable *headers, struct ast_ari_device_states_list_args *args, struct ast_ari_response *response);
+/*! \brief Argument struct for ast_ari_device_states_get() */
+struct ast_ari_device_states_get_args {
+ /*! \brief Name of the device */
+ const char *device_name;
+};
+/*!
+ * \brief Retrieve the current state of a device.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_device_states_get(struct ast_variable *headers, struct ast_ari_device_states_get_args *args, struct ast_ari_response *response);
+/*! \brief Argument struct for ast_ari_device_states_update() */
+struct ast_ari_device_states_update_args {
+ /*! \brief Name of the device */
+ const char *device_name;
+ /*! \brief Device state value */
+ const char *device_state;
+};
+/*!
+ * \brief Change the state of a device controlled by ARI. (Note - implicitly creates the device state).
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_device_states_update(struct ast_variable *headers, struct ast_ari_device_states_update_args *args, struct ast_ari_response *response);
+/*! \brief Argument struct for ast_ari_device_states_delete() */
+struct ast_ari_device_states_delete_args {
+ /*! \brief Name of the device */
+ const char *device_name;
+};
+/*!
+ * \brief Destroy a device-state controlled by ARI.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_device_states_delete(struct ast_variable *headers, struct ast_ari_device_states_delete_args *args, struct ast_ari_response *response);
+
+#endif /* _ASTERISK_RESOURCE_DEVICESTATES_H */
diff --git a/res/res_ari_device_states.c b/res/res_ari_device_states.c
new file mode 100644
index 000000000..07f51ba3e
--- /dev/null
+++ b/res/res_ari_device_states.c
@@ -0,0 +1,323 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012 - 2013, Digium, Inc.
+ *
+ * Kevin Harwell <kharwell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * !!!!! DO NOT EDIT !!!!!
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * This file is generated by a mustache template. Please see the original
+ * template in rest-api-templates/res_ari_resource.c.mustache
+ */
+
+/*! \file
+ *
+ * \brief Device state resources
+ *
+ * \author Kevin Harwell <kharwell@digium.com>
+ */
+
+/*** MODULEINFO
+ <depend type="module">res_ari</depend>
+ <depend type="module">res_stasis</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/app.h"
+#include "asterisk/module.h"
+#include "asterisk/stasis_app.h"
+#include "ari/resource_device_states.h"
+#if defined(AST_DEVMODE)
+#include "ari/ari_model_validators.h"
+#endif
+
+#define MAX_VALS 128
+
+/*!
+ * \brief Parameter parsing callback for /deviceStates.
+ * \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_device_states_list_cb(
+ struct ast_variable *get_params, struct ast_variable *path_vars,
+ struct ast_variable *headers, struct ast_ari_response *response)
+{
+ struct ast_ari_device_states_list_args args = {};
+#if defined(AST_DEVMODE)
+ int is_valid;
+ int code;
+#endif /* AST_DEVMODE */
+
+ ast_ari_device_states_list(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 */
+ is_valid = 1;
+ break;
+ default:
+ if (200 <= code && code <= 299) {
+ is_valid = ast_ari_validate_list(response->message,
+ ast_ari_validate_device_state_fn());
+ } else {
+ ast_log(LOG_ERROR, "Invalid error response %d for /deviceStates\n", code);
+ is_valid = 0;
+ }
+ }
+
+ if (!is_valid) {
+ ast_log(LOG_ERROR, "Response validation failed for /deviceStates\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 /deviceStates/{deviceName}.
+ * \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_device_states_get_cb(
+ struct ast_variable *get_params, struct ast_variable *path_vars,
+ struct ast_variable *headers, struct ast_ari_response *response)
+{
+ struct ast_ari_device_states_get_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, "deviceName") == 0) {
+ args.device_name = (i->value);
+ } else
+ {}
+ }
+ ast_ari_device_states_get(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 */
+ is_valid = 1;
+ break;
+ default:
+ if (200 <= code && code <= 299) {
+ is_valid = ast_ari_validate_device_state(
+ response->message);
+ } else {
+ ast_log(LOG_ERROR, "Invalid error response %d for /deviceStates/{deviceName}\n", code);
+ is_valid = 0;
+ }
+ }
+
+ if (!is_valid) {
+ ast_log(LOG_ERROR, "Response validation failed for /deviceStates/{deviceName}\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 /deviceStates/{deviceName}.
+ * \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_device_states_update_cb(
+ struct ast_variable *get_params, struct ast_variable *path_vars,
+ struct ast_variable *headers, struct ast_ari_response *response)
+{
+ struct ast_ari_device_states_update_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, "deviceState") == 0) {
+ args.device_state = (i->value);
+ } else
+ {}
+ }
+ for (i = path_vars; i; i = i->next) {
+ if (strcmp(i->name, "deviceName") == 0) {
+ args.device_name = (i->value);
+ } else
+ {}
+ }
+ ast_ari_device_states_update(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: /* Device name is missing */
+ case 409: /* Uncontrolled device specified */
+ 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 /deviceStates/{deviceName}\n", code);
+ is_valid = 0;
+ }
+ }
+
+ if (!is_valid) {
+ ast_log(LOG_ERROR, "Response validation failed for /deviceStates/{deviceName}\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 /deviceStates/{deviceName}.
+ * \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_device_states_delete_cb(
+ struct ast_variable *get_params, struct ast_variable *path_vars,
+ struct ast_variable *headers, struct ast_ari_response *response)
+{
+ struct ast_ari_device_states_delete_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, "deviceName") == 0) {
+ args.device_name = (i->value);
+ } else
+ {}
+ }
+ ast_ari_device_states_delete(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: /* Device name is missing */
+ case 409: /* Uncontrolled device specified */
+ 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 /deviceStates/{deviceName}\n", code);
+ is_valid = 0;
+ }
+ }
+
+ if (!is_valid) {
+ ast_log(LOG_ERROR, "Response validation failed for /deviceStates/{deviceName}\n");
+ ast_ari_response_error(response, 500,
+ "Internal Server Error", "Response validation failed");
+ }
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+ return;
+}
+
+/*! \brief REST handler for /api-docs/deviceStates.{format} */
+static struct stasis_rest_handlers deviceStates_deviceName = {
+ .path_segment = "deviceName",
+ .is_wildcard = 1,
+ .callbacks = {
+ [AST_HTTP_GET] = ast_ari_device_states_get_cb,
+ [AST_HTTP_PUT] = ast_ari_device_states_update_cb,
+ [AST_HTTP_DELETE] = ast_ari_device_states_delete_cb,
+ },
+ .num_children = 0,
+ .children = { }
+};
+/*! \brief REST handler for /api-docs/deviceStates.{format} */
+static struct stasis_rest_handlers deviceStates = {
+ .path_segment = "deviceStates",
+ .callbacks = {
+ [AST_HTTP_GET] = ast_ari_device_states_list_cb,
+ },
+ .num_children = 1,
+ .children = { &deviceStates_deviceName, }
+};
+
+static int load_module(void)
+{
+ int res = 0;
+ stasis_app_ref();
+ res |= ast_ari_add_handler(&deviceStates);
+ return res;
+}
+
+static int unload_module(void)
+{
+ ast_ari_remove_handler(&deviceStates);
+ stasis_app_unref();
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "RESTful API module - Device state resources",
+ .load = load_module,
+ .unload = unload_module,
+ .nonoptreq = "res_ari,res_stasis",
+ );
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;
}
diff --git a/res/res_stasis_device_state.c b/res/res_stasis_device_state.c
new file mode 100644
index 000000000..e6d339107
--- /dev/null
+++ b/res/res_stasis_device_state.c
@@ -0,0 +1,416 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Kevin Harwell <kharwell@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*** MODULEINFO
+ <depend type="module">res_stasis</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/astdb.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/module.h"
+#include "asterisk/stasis_app_impl.h"
+#include "asterisk/stasis_app_device_state.h"
+
+#define DEVICE_STATE_SIZE 64
+/*! astdb family name */
+#define DEVICE_STATE_FAMILY "StasisDeviceState"
+/*! Stasis device state provider */
+#define DEVICE_STATE_PROVIDER_STASIS "Stasis"
+/*! Scheme for custom device states */
+#define DEVICE_STATE_SCHEME_STASIS "Stasis:"
+/*! Scheme for device state subscriptions */
+#define DEVICE_STATE_SCHEME_SUB "device_state:"
+
+/*! Number of hash buckets for device state subscriptions */
+#define DEVICE_STATE_BUCKETS 37
+
+/*! Container for subscribed device states */
+static struct ao2_container *device_state_subscriptions;
+
+/*!
+ * \brief Device state subscription object.
+ */
+struct device_state_subscription {
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(app_name);
+ AST_STRING_FIELD(device_name);
+ );
+ /*! The subscription object */
+ struct stasis_subscription *sub;
+};
+
+static int device_state_subscriptions_hash(const void *obj, const int flags)
+{
+ const struct device_state_subscription *object;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ object = obj;
+ return ast_str_hash(object->device_name);
+ case OBJ_SEARCH_KEY:
+ default:
+ /* Hash can only work on something with a full key. */
+ ast_assert(0);
+ return 0;
+ }
+}
+
+static int device_state_subscriptions_cmp(void *obj, void *arg, int flags)
+{
+ const struct device_state_subscription *object_left = obj;
+ const struct device_state_subscription *object_right = arg;
+ int cmp;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ /* find objects matching both device and app names */
+ if (strcmp(object_left->device_name,
+ object_right->device_name)) {
+ return 0;
+ }
+ cmp = strcmp(object_left->app_name, object_right->app_name);
+ break;
+ case OBJ_SEARCH_KEY:
+ case OBJ_SEARCH_PARTIAL_KEY:
+ ast_assert(0); /* not supported by container */
+ /* fall through */
+ default:
+ cmp = 0;
+ break;
+ }
+
+ return cmp ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static void device_state_subscription_destroy(void *obj)
+{
+ struct device_state_subscription *sub = obj;
+ sub->sub = stasis_unsubscribe(sub->sub);
+ ast_string_field_free_memory(sub);
+}
+
+static struct device_state_subscription *device_state_subscription_create(
+ const struct stasis_app *app, const char *device_name)
+{
+ struct device_state_subscription *sub = ao2_alloc(
+ sizeof(*sub), device_state_subscription_destroy);
+ const char *app_name = stasis_app_name(app);
+ size_t size = strlen(device_name) + strlen(app_name) + 2;
+
+ if (!sub) {
+ return NULL;
+ }
+
+ if (ast_string_field_init(sub, size)) {
+ ao2_ref(sub, -1);
+ return NULL;
+ }
+
+ ast_string_field_set(sub, app_name, app_name);
+ ast_string_field_set(sub, device_name, device_name);
+ return sub;
+}
+
+static struct device_state_subscription *find_device_state_subscription(
+ struct stasis_app *app, const char *name)
+{
+ struct device_state_subscription dummy_sub = {
+ .app_name = stasis_app_name(app),
+ .device_name = name
+ };
+
+ return ao2_find(device_state_subscriptions, &dummy_sub, OBJ_SEARCH_OBJECT);
+}
+
+static void remove_device_state_subscription(
+ struct device_state_subscription *sub)
+{
+ ao2_unlink(device_state_subscriptions, sub);
+}
+
+struct ast_json *stasis_app_device_state_to_json(
+ const char *name, enum ast_device_state state)
+{
+ return ast_json_pack("{s: s, s: s}",
+ "name", name,
+ "state", ast_devstate_str(state));
+}
+
+struct ast_json *stasis_app_device_states_to_json(void)
+{
+ struct ast_json *array = ast_json_array_create();
+ RAII_VAR(struct ast_db_entry *, tree,
+ ast_db_gettree(DEVICE_STATE_FAMILY, NULL), ast_db_freetree);
+ struct ast_db_entry *entry;
+
+ for (entry = tree; entry; entry = entry->next) {
+ const char *name = strrchr(entry->key, '/');
+ if (!ast_strlen_zero(name)) {
+ struct ast_str *device = ast_str_alloca(DEVICE_STATE_SIZE);
+ ast_str_set(&device, 0, "%s%s",
+ DEVICE_STATE_SCHEME_STASIS, ++name);
+ ast_json_array_append(
+ array, stasis_app_device_state_to_json(
+ ast_str_buffer(device),
+ ast_device_state(ast_str_buffer(device))));
+ }
+ }
+
+ return array;
+}
+
+static void send_device_state(struct device_state_subscription *sub,
+ const char *name, enum ast_device_state state)
+{
+ RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
+
+ json = ast_json_pack("{s:s, s:s, s:o, s:o}",
+ "type", "DeviceStateChanged",
+ "application", sub->app_name,
+ "timestamp", ast_json_timeval(ast_tvnow(), NULL),
+ "device_state", stasis_app_device_state_to_json(
+ name, state));
+
+ if (!json) {
+ ast_log(LOG_ERROR, "Unable to create device state json object\n");
+ return;
+ }
+
+ stasis_app_send(sub->app_name, json);
+}
+
+enum stasis_device_state_result stasis_app_device_state_update(
+ const char *name, const char *value)
+{
+ size_t size = strlen(DEVICE_STATE_SCHEME_STASIS);
+ enum ast_device_state state;
+
+ ast_debug(3, "Updating device name = %s, value = %s", name, value);
+
+ if (strncasecmp(name, DEVICE_STATE_SCHEME_STASIS, size)) {
+ ast_log(LOG_ERROR, "Update can only be used to set "
+ "'%s' device state!\n", DEVICE_STATE_SCHEME_STASIS);
+ return STASIS_DEVICE_STATE_NOT_CONTROLLED;
+ }
+
+ name += size;
+ if (ast_strlen_zero(name)) {
+ ast_log(LOG_ERROR, "Update requires custom device name!\n");
+ return STASIS_DEVICE_STATE_MISSING;
+ }
+
+ if (!value || (state = ast_devstate_val(value)) == AST_DEVICE_UNKNOWN) {
+ ast_log(LOG_ERROR, "Unknown device state "
+ "value '%s'\n", value);
+ return STASIS_DEVICE_STATE_UNKNOWN;
+ }
+
+ ast_db_put(DEVICE_STATE_FAMILY, name, value);
+ ast_devstate_changed(state, AST_DEVSTATE_CACHABLE, "%s%s",
+ DEVICE_STATE_SCHEME_STASIS, name);
+
+ return STASIS_DEVICE_STATE_OK;
+}
+
+enum stasis_device_state_result stasis_app_device_state_delete(const char *name)
+{
+ const char *full_name = name;
+ size_t size = strlen(DEVICE_STATE_SCHEME_STASIS);
+
+ if (strncasecmp(name, DEVICE_STATE_SCHEME_STASIS, size)) {
+ ast_log(LOG_ERROR, "Can only delete '%s' device states!\n",
+ DEVICE_STATE_SCHEME_STASIS);
+ return STASIS_DEVICE_STATE_NOT_CONTROLLED;
+ }
+
+ name += size;
+ if (ast_strlen_zero(name)) {
+ ast_log(LOG_ERROR, "Delete requires a device name!\n");
+ return STASIS_DEVICE_STATE_MISSING;
+ }
+
+ if (ast_device_state_clear_cache(full_name)) {
+ return STASIS_DEVICE_STATE_UNKNOWN;
+ }
+
+ ast_db_del(DEVICE_STATE_FAMILY, name);
+
+ /* send state change for delete */
+ ast_devstate_changed(
+ AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "%s%s",
+ DEVICE_STATE_SCHEME_STASIS, name);
+
+ return STASIS_DEVICE_STATE_OK;
+}
+
+static void populate_cache(void)
+{
+ RAII_VAR(struct ast_db_entry *, tree,
+ ast_db_gettree(DEVICE_STATE_FAMILY, NULL), ast_db_freetree);
+ struct ast_db_entry *entry;
+
+ for (entry = tree; entry; entry = entry->next) {
+ const char *name = strrchr(entry->key, '/');
+ if (!ast_strlen_zero(name)) {
+ ast_devstate_changed(
+ ast_devstate_val(entry->data),
+ AST_DEVSTATE_CACHABLE, "%s%s\n",
+ DEVICE_STATE_SCHEME_STASIS, name + 1);
+ }
+ }
+}
+
+static enum ast_device_state stasis_device_state_cb(const char *data)
+{
+ char buf[DEVICE_STATE_SIZE] = "";
+
+ ast_db_get(DEVICE_STATE_FAMILY, data, buf, sizeof(buf));
+
+ return ast_devstate_val(buf);
+}
+
+static void device_state_cb(void *data, struct stasis_subscription *sub,
+ struct stasis_message *msg)
+{
+ struct ast_device_state_message *device_state;
+
+ if (ast_device_state_message_type() != stasis_message_type(msg)) {
+ return;
+ }
+
+ device_state = stasis_message_data(msg);
+ if (device_state->eid) {
+ /* ignore non-aggregate states */
+ return;
+ }
+
+ send_device_state(data, device_state->device, device_state->state);
+}
+
+static void *find_device_state(const struct stasis_app *app, const char *name)
+{
+ return device_state_subscription_create(app, name);
+}
+
+static int is_subscribed_device_state(struct stasis_app *app, const char *name)
+{
+ RAII_VAR(struct device_state_subscription *, sub,
+ find_device_state_subscription(app, name), ao2_cleanup);
+ return sub != NULL;
+}
+
+static int subscribe_device_state(struct stasis_app *app, void *obj)
+{
+ struct device_state_subscription *sub = obj;
+
+ ast_debug(3, "Subscribing to device %s", sub->device_name);
+
+ if (is_subscribed_device_state(app, sub->device_name)) {
+ ast_log(LOG_WARNING, "Already subscribed to %s\n", sub->device_name);
+ return -1;
+ }
+
+ if (!(sub->sub = stasis_subscribe(
+ ast_device_state_topic(sub->device_name),
+ device_state_cb, sub))) {
+ ast_log(LOG_ERROR, "Unable to subscribe to device %s\n",
+ sub->device_name);
+ return -1;
+ }
+
+ ao2_link(device_state_subscriptions, sub);
+ return 0;
+}
+
+static int unsubscribe_device_state(struct stasis_app *app, const char *name)
+{
+ RAII_VAR(struct device_state_subscription *, sub,
+ find_device_state_subscription(app, name), ao2_cleanup);
+ remove_device_state_subscription(sub);
+ return 0;
+}
+
+static int device_to_json_cb(void *obj, void *arg, void *data, int flags)
+{
+ struct device_state_subscription *sub = obj;
+ const char *app_name = arg;
+ struct ast_json *array = data;
+
+ if (strcmp(sub->app_name, app_name)) {
+ return 0;
+ }
+
+ ast_json_array_append(
+ array, ast_json_string_create(sub->device_name));
+ return 0;
+
+}
+
+static void devices_to_json(const struct stasis_app *app, struct ast_json *json)
+{
+ struct ast_json *array = ast_json_array_create();
+ ao2_callback_data(device_state_subscriptions, OBJ_NODATA,
+ device_to_json_cb, (void *)stasis_app_name(app), array);
+ ast_json_object_set(json, "device_names", array);
+}
+
+struct stasis_app_event_source device_state_event_source = {
+ .scheme = DEVICE_STATE_SCHEME_SUB,
+ .find = find_device_state,
+ .subscribe = subscribe_device_state,
+ .unsubscribe = unsubscribe_device_state,
+ .is_subscribed = is_subscribed_device_state,
+ .to_json = devices_to_json
+};
+
+static int load_module(void)
+{
+ populate_cache();
+ if (ast_devstate_prov_add(DEVICE_STATE_PROVIDER_STASIS,
+ stasis_device_state_cb)) {
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ if (!(device_state_subscriptions = ao2_container_alloc(
+ DEVICE_STATE_BUCKETS, device_state_subscriptions_hash,
+ device_state_subscriptions_cmp))) {
+ return AST_MODULE_LOAD_FAILURE;
+ }
+
+ stasis_app_register_event_source(&device_state_event_source);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ ast_devstate_prov_del(DEVICE_STATE_PROVIDER_STASIS);
+ stasis_app_unregister_event_source(&device_state_event_source);
+ ao2_cleanup(device_state_subscriptions);
+ device_state_subscriptions = NULL;
+ return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application device state support",
+ .load = load_module,
+ .unload = unload_module,
+ .nonoptreq = "res_stasis");
diff --git a/res/res_stasis_device_state.exports.in b/res/res_stasis_device_state.exports.in
new file mode 100644
index 000000000..0ad493c49
--- /dev/null
+++ b/res/res_stasis_device_state.exports.in
@@ -0,0 +1,6 @@
+{
+ global:
+ LINKER_SYMBOL_PREFIXstasis_app_*;
+ local:
+ *;
+};
diff --git a/res/stasis/app.c b/res/stasis/app.c
index 433d3adb5..8ad41e565 100644
--- a/res/stasis/app.c
+++ b/res/stasis/app.c
@@ -36,7 +36,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/stasis_endpoints.h"
#include "asterisk/stasis_message_router.h"
-struct app {
+struct stasis_app {
/*! Aggregation topic for this application. */
struct stasis_topic *topic;
/*! Router for handling messages forwarded to \a topic. */
@@ -93,7 +93,7 @@ static void forwards_unsubscribe(struct app_forwards *forwards)
forwards->topic_cached_forward = NULL;
}
-static struct app_forwards *forwards_create(struct app *app,
+static struct app_forwards *forwards_create(struct stasis_app *app,
const char *id)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
@@ -114,7 +114,7 @@ static struct app_forwards *forwards_create(struct app *app,
}
/*! Forward a channel's topics to an app */
-static struct app_forwards *forwards_create_channel(struct app *app,
+static struct app_forwards *forwards_create_channel(struct stasis_app *app,
struct ast_channel *chan)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
@@ -149,7 +149,7 @@ static struct app_forwards *forwards_create_channel(struct app *app,
}
/*! Forward a bridge's topics to an app */
-static struct app_forwards *forwards_create_bridge(struct app *app,
+static struct app_forwards *forwards_create_bridge(struct stasis_app *app,
struct ast_bridge *bridge)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
@@ -184,7 +184,7 @@ static struct app_forwards *forwards_create_bridge(struct app *app,
}
/*! Forward a endpoint's topics to an app */
-static struct app_forwards *forwards_create_endpoint(struct app *app,
+static struct app_forwards *forwards_create_endpoint(struct stasis_app *app,
struct ast_endpoint *endpoint)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
@@ -250,7 +250,7 @@ static int forwards_sort(const void *obj_left, const void *obj_right, int flags)
static void app_dtor(void *obj)
{
- struct app *app = obj;
+ struct stasis_app *app = obj;
ast_verb(1, "Destroying Stasis app %s\n", app->name);
@@ -268,7 +268,7 @@ static void app_dtor(void *obj)
static void sub_default_handler(void *data, struct stasis_subscription *sub,
struct stasis_message *message)
{
- struct app *app = data;
+ struct stasis_app *app = data;
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
if (stasis_subscription_final_message(sub, message)) {
@@ -435,7 +435,7 @@ static void sub_channel_update_handler(void *data,
struct stasis_subscription *sub,
struct stasis_message *message)
{
- struct app *app = data;
+ struct stasis_app *app = data;
struct stasis_cache_update *update;
struct ast_channel_snapshot *new_snapshot;
struct ast_channel_snapshot *old_snapshot;
@@ -489,7 +489,7 @@ static void sub_endpoint_update_handler(void *data,
struct stasis_message *message)
{
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
- struct app *app = data;
+ struct stasis_app *app = data;
struct stasis_cache_update *update;
struct ast_endpoint_snapshot *new_snapshot;
const struct timeval *tv;
@@ -535,7 +535,7 @@ static void sub_bridge_update_handler(void *data,
struct stasis_message *message)
{
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
- struct app *app = data;
+ struct stasis_app *app = data;
struct stasis_cache_update *update;
struct ast_bridge_snapshot *new_snapshot;
struct ast_bridge_snapshot *old_snapshot;
@@ -569,7 +569,7 @@ static void sub_bridge_update_handler(void *data,
static void bridge_merge_handler(void *data, struct stasis_subscription *sub,
struct stasis_message *message)
{
- struct app *app = data;
+ struct stasis_app *app = data;
struct ast_bridge_merge_message *merge;
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
@@ -599,9 +599,9 @@ static void bridge_merge_handler(void *data, struct stasis_subscription *sub,
stasis_publish(app->topic, message);
}
-struct app *app_create(const char *name, stasis_app_cb handler, void *data)
+struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *data)
{
- RAII_VAR(struct app *, app, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
size_t size;
int res = 0;
@@ -674,7 +674,7 @@ struct app *app_create(const char *name, stasis_app_cb handler, void *data)
* \param app App to send the message to.
* \param message Message to send.
*/
-void app_send(struct app *app, struct ast_json *message)
+void app_send(struct stasis_app *app, struct ast_json *message)
{
stasis_app_cb handler;
RAII_VAR(void *, data, NULL, ao2_cleanup);
@@ -699,7 +699,7 @@ void app_send(struct app *app, struct ast_json *message)
handler(data, app->name, message);
}
-void app_deactivate(struct app *app)
+void app_deactivate(struct stasis_app *app)
{
SCOPED_AO2LOCK(lock, app);
ast_verb(1, "Deactivating Stasis app '%s'\n", app->name);
@@ -708,7 +708,7 @@ void app_deactivate(struct app *app)
app->data = NULL;
}
-void app_shutdown(struct app *app)
+void app_shutdown(struct stasis_app *app)
{
SCOPED_AO2LOCK(lock, app);
@@ -720,20 +720,20 @@ void app_shutdown(struct app *app)
app->bridge_merge_sub = NULL;
}
-int app_is_active(struct app *app)
+int app_is_active(struct stasis_app *app)
{
SCOPED_AO2LOCK(lock, app);
return app->handler != NULL;
}
-int app_is_finished(struct app *app)
+int app_is_finished(struct stasis_app *app)
{
SCOPED_AO2LOCK(lock, app);
return app->handler == NULL && ao2_container_count(app->forwards) == 0;
}
-void app_update(struct app *app, stasis_app_cb handler, void *data)
+void app_update(struct stasis_app *app, stasis_app_cb handler, void *data)
{
SCOPED_AO2LOCK(lock, app);
@@ -760,12 +760,12 @@ void app_update(struct app *app, stasis_app_cb handler, void *data)
app->data = data;
}
-const char *app_name(const struct app *app)
+const char *app_name(const struct stasis_app *app)
{
return app->name;
}
-struct ast_json *app_to_json(const struct app *app)
+struct ast_json *app_to_json(const struct stasis_app *app)
{
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
struct ast_json *channels;
@@ -815,7 +815,7 @@ struct ast_json *app_to_json(const struct app *app)
return ast_json_ref(json);
}
-int app_subscribe_channel(struct app *app, struct ast_channel *chan)
+int app_subscribe_channel(struct stasis_app *app, struct ast_channel *chan)
{
int res;
@@ -846,7 +846,12 @@ int app_subscribe_channel(struct app *app, struct ast_channel *chan)
}
}
-static int unsubscribe(struct app *app, const char *kind, const char *id)
+static int subscribe_channel(struct stasis_app *app, void *obj)
+{
+ return app_subscribe_channel(app, obj);
+}
+
+static int unsubscribe(struct stasis_app *app, const char *kind, const char *id)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
SCOPED_AO2LOCK(lock, app->forwards);
@@ -870,7 +875,7 @@ static int unsubscribe(struct app *app, const char *kind, const char *id)
return 0;
}
-int app_unsubscribe_channel(struct app *app, struct ast_channel *chan)
+int app_unsubscribe_channel(struct stasis_app *app, struct ast_channel *chan)
{
if (!app || !chan) {
return -1;
@@ -879,7 +884,7 @@ int app_unsubscribe_channel(struct app *app, struct ast_channel *chan)
return app_unsubscribe_channel_id(app, ast_channel_uniqueid(chan));
}
-int app_unsubscribe_channel_id(struct app *app, const char *channel_id)
+int app_unsubscribe_channel_id(struct stasis_app *app, const char *channel_id)
{
if (!app || !channel_id) {
return -1;
@@ -888,14 +893,27 @@ int app_unsubscribe_channel_id(struct app *app, const char *channel_id)
return unsubscribe(app, "channel", channel_id);
}
-int app_is_subscribed_channel_id(struct app *app, const char *channel_id)
+int app_is_subscribed_channel_id(struct stasis_app *app, const char *channel_id)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
forwards = ao2_find(app->forwards, channel_id, OBJ_SEARCH_KEY);
return forwards != NULL;
}
-int app_subscribe_bridge(struct app *app, struct ast_bridge *bridge)
+static void *channel_find(const struct stasis_app *app, const char *id)
+{
+ return ast_channel_get_by_name(id);
+}
+
+struct stasis_app_event_source channel_event_source = {
+ .scheme = "channel:",
+ .find = channel_find,
+ .subscribe = subscribe_channel,
+ .unsubscribe = app_unsubscribe_channel_id,
+ .is_subscribed = app_is_subscribed_channel_id
+};
+
+int app_subscribe_bridge(struct stasis_app *app, struct ast_bridge *bridge)
{
if (!app || !bridge) {
return -1;
@@ -920,7 +938,12 @@ int app_subscribe_bridge(struct app *app, struct ast_bridge *bridge)
}
}
-int app_unsubscribe_bridge(struct app *app, struct ast_bridge *bridge)
+static int subscribe_bridge(struct stasis_app *app, void *obj)
+{
+ return app_subscribe_bridge(app, obj);
+}
+
+int app_unsubscribe_bridge(struct stasis_app *app, struct ast_bridge *bridge)
{
if (!app || !bridge) {
return -1;
@@ -929,7 +952,7 @@ int app_unsubscribe_bridge(struct app *app, struct ast_bridge *bridge)
return app_unsubscribe_bridge_id(app, bridge->uniqueid);
}
-int app_unsubscribe_bridge_id(struct app *app, const char *bridge_id)
+int app_unsubscribe_bridge_id(struct stasis_app *app, const char *bridge_id)
{
if (!app || !bridge_id) {
return -1;
@@ -938,14 +961,27 @@ int app_unsubscribe_bridge_id(struct app *app, const char *bridge_id)
return unsubscribe(app, "bridge", bridge_id);
}
-int app_is_subscribed_bridge_id(struct app *app, const char *bridge_id)
+int app_is_subscribed_bridge_id(struct stasis_app *app, const char *bridge_id)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
forwards = ao2_find(app->forwards, bridge_id, OBJ_SEARCH_KEY);
return forwards != NULL;
}
-int app_subscribe_endpoint(struct app *app, struct ast_endpoint *endpoint)
+static void *bridge_find(const struct stasis_app *app, const char *id)
+{
+ return stasis_app_bridge_find_by_id(id);
+}
+
+struct stasis_app_event_source bridge_event_source = {
+ .scheme = "bridge:",
+ .find = bridge_find,
+ .subscribe = subscribe_bridge,
+ .unsubscribe = app_unsubscribe_bridge_id,
+ .is_subscribed = app_is_subscribed_bridge_id
+};
+
+int app_subscribe_endpoint(struct stasis_app *app, struct ast_endpoint *endpoint)
{
if (!app || !endpoint) {
return -1;
@@ -970,7 +1006,12 @@ int app_subscribe_endpoint(struct app *app, struct ast_endpoint *endpoint)
}
}
-int app_unsubscribe_endpoint_id(struct app *app, const char *endpoint_id)
+static int subscribe_endpoint(struct stasis_app *app, void *obj)
+{
+ return app_subscribe_endpoint(app, obj);
+}
+
+int app_unsubscribe_endpoint_id(struct stasis_app *app, const char *endpoint_id)
{
if (!app || !endpoint_id) {
return -1;
@@ -979,9 +1020,45 @@ int app_unsubscribe_endpoint_id(struct app *app, const char *endpoint_id)
return unsubscribe(app, "endpoint", endpoint_id);
}
-int app_is_subscribed_endpoint_id(struct app *app, const char *endpoint_id)
+int app_is_subscribed_endpoint_id(struct stasis_app *app, const char *endpoint_id)
{
RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
forwards = ao2_find(app->forwards, endpoint_id, OBJ_SEARCH_KEY);
return forwards != NULL;
}
+
+static void *endpoint_find(const struct stasis_app *app, const char *id)
+{
+ return ast_endpoint_find_by_id(id);
+}
+
+struct stasis_app_event_source endpoint_event_source = {
+ .scheme = "endpoint:",
+ .find = endpoint_find,
+ .subscribe = subscribe_endpoint,
+ .unsubscribe = app_unsubscribe_endpoint_id,
+ .is_subscribed = app_is_subscribed_endpoint_id
+};
+
+void stasis_app_register_event_sources(void)
+{
+ stasis_app_register_event_source(&channel_event_source);
+ stasis_app_register_event_source(&bridge_event_source);
+ stasis_app_register_event_source(&endpoint_event_source);
+}
+
+int stasis_app_is_core_event_source(struct stasis_app_event_source *obj)
+{
+ return obj == &endpoint_event_source ||
+ obj == &bridge_event_source ||
+ obj == &channel_event_source;
+}
+
+void stasis_app_unregister_event_sources(void)
+{
+ stasis_app_unregister_event_source(&endpoint_event_source);
+ stasis_app_unregister_event_source(&bridge_event_source);
+ stasis_app_unregister_event_source(&channel_event_source);
+}
+
+
diff --git a/res/stasis/app.h b/res/stasis/app.h
index 4db9db97d..419ec54a8 100644
--- a/res/stasis/app.h
+++ b/res/stasis/app.h
@@ -34,7 +34,7 @@
/*!
* \brief Opaque pointer to \c res_stasis app structure.
*/
-struct app;
+struct stasis_app;
/*!
* \brief Create a res_stasis application.
@@ -45,7 +45,7 @@ struct app;
* \return New \c res_stasis application.
* \return \c NULL on error.
*/
-struct app *app_create(const char *name, stasis_app_cb handler, void *data);
+struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *data);
/*!
* \brief Tears down an application.
@@ -54,7 +54,7 @@ struct app *app_create(const char *name, stasis_app_cb handler, void *data);
*
* \param app Application to unsubscribe.
*/
-void app_shutdown(struct app *app);
+void app_shutdown(struct stasis_app *app);
/*!
* \brief Deactivates an application.
@@ -64,7 +64,7 @@ void app_shutdown(struct app *app);
*
* \param app Application to deactivate.
*/
-void app_deactivate(struct app *app);
+void app_deactivate(struct stasis_app *app);
/*!
* \brief Checks whether an app is active.
@@ -73,7 +73,7 @@ void app_deactivate(struct app *app);
* \return True (non-zero) if app is active.
* \return False (zero) if app has been deactivated.
*/
-int app_is_active(struct app *app);
+int app_is_active(struct stasis_app *app);
/*!
* \brief Checks whether a deactivated app has no channels.
@@ -82,7 +82,7 @@ int app_is_active(struct app *app);
* \param True (non-zero) if app is deactivated, and has no associated channels.
* \param False (zero) otherwise.
*/
-int app_is_finished(struct app *app);
+int app_is_finished(struct stasis_app *app);
/*!
* \brief Update the handler and data for a \c res_stasis application.
@@ -93,7 +93,7 @@ int app_is_finished(struct app *app);
* \param handler New application callback.
* \param data New data pointer for the callback.
*/
-void app_update(struct app *app, stasis_app_cb handler, void *data);
+void app_update(struct stasis_app *app, stasis_app_cb handler, void *data);
/*!
* \brief Return an application's name.
@@ -102,7 +102,7 @@ void app_update(struct app *app, stasis_app_cb handler, void *data);
* \return Name of the application.
* \return \c NULL is \a app is \c NULL.
*/
-const char *app_name(const struct app *app);
+const char *app_name(const struct stasis_app *app);
/*!
* \brief Send a message to an application.
@@ -110,11 +110,11 @@ const char *app_name(const struct app *app);
* \param app Application.
* \param message Message to send.
*/
-void app_send(struct app *app, struct ast_json *message);
+void app_send(struct stasis_app *app, struct ast_json *message);
struct app_forwards;
-struct ast_json *app_to_json(const struct app *app);
+struct ast_json *app_to_json(const struct stasis_app *app);
/*!
* \brief Subscribes an application to a channel.
@@ -124,7 +124,7 @@ struct ast_json *app_to_json(const struct app *app);
* \return 0 on success.
* \return Non-zero on error.
*/
-int app_subscribe_channel(struct app *app, struct ast_channel *chan);
+int app_subscribe_channel(struct stasis_app *app, struct ast_channel *chan);
/*!
* \brief Cancel the subscription an app has for a channel.
@@ -134,7 +134,7 @@ int app_subscribe_channel(struct app *app, struct ast_channel *chan);
* \return 0 on success.
* \return Non-zero on error.
*/
-int app_unsubscribe_channel(struct app *app, struct ast_channel *chan);
+int app_unsubscribe_channel(struct stasis_app *app, struct ast_channel *chan);
/*!
* \brief Cancel the subscription an app has for a channel.
@@ -144,7 +144,7 @@ int app_unsubscribe_channel(struct app *app, struct ast_channel *chan);
* \return 0 on success.
* \return Non-zero on error.
*/
-int app_unsubscribe_channel_id(struct app *app, const char *channel_id);
+int app_unsubscribe_channel_id(struct stasis_app *app, const char *channel_id);
/*!
* \brief Test if an app is subscribed to a channel.
@@ -154,7 +154,7 @@ int app_unsubscribe_channel_id(struct app *app, const char *channel_id);
* \return True (non-zero) if channel is subscribed to \a app.
* \return False (zero) if channel is not subscribed.
*/
-int app_is_subscribed_channel_id(struct app *app, const char *channel_id);
+int app_is_subscribed_channel_id(struct stasis_app *app, const char *channel_id);
/*!
* \brief Add a bridge subscription to an existing channel subscription.
@@ -164,7 +164,7 @@ int app_is_subscribed_channel_id(struct app *app, const char *channel_id);
* \return 0 on success.
* \return Non-zero on error.
*/
-int app_subscribe_bridge(struct app *app, struct ast_bridge *bridge);
+int app_subscribe_bridge(struct stasis_app *app, struct ast_bridge *bridge);
/*!
* \brief Cancel the bridge subscription for an application.
@@ -174,7 +174,7 @@ int app_subscribe_bridge(struct app *app, struct ast_bridge *bridge);
* \return 0 on success.
* \return Non-zero on error.
*/
-int app_unsubscribe_bridge(struct app *app, struct ast_bridge *bridge);
+int app_unsubscribe_bridge(struct stasis_app *app, struct ast_bridge *bridge);
/*!
* \brief Cancel the subscription an app has for a bridge.
@@ -184,7 +184,7 @@ int app_unsubscribe_bridge(struct app *app, struct ast_bridge *bridge);
* \return 0 on success.
* \return Non-zero on error.
*/
-int app_unsubscribe_bridge_id(struct app *app, const char *bridge_id);
+int app_unsubscribe_bridge_id(struct stasis_app *app, const char *bridge_id);
/*!
* \brief Test if an app is subscribed to a bridge.
@@ -194,7 +194,7 @@ int app_unsubscribe_bridge_id(struct app *app, const char *bridge_id);
* \return True (non-zero) if bridge is subscribed to \a app.
* \return False (zero) if bridge is not subscribed.
*/
-int app_is_subscribed_bridge_id(struct app *app, const char *bridge_id);
+int app_is_subscribed_bridge_id(struct stasis_app *app, const char *bridge_id);
/*!
* \brief Subscribes an application to a endpoint.
@@ -204,7 +204,7 @@ int app_is_subscribed_bridge_id(struct app *app, const char *bridge_id);
* \return 0 on success.
* \return Non-zero on error.
*/
-int app_subscribe_endpoint(struct app *app, struct ast_endpoint *endpoint);
+int app_subscribe_endpoint(struct stasis_app *app, struct ast_endpoint *endpoint);
/*!
* \brief Cancel the subscription an app has for a endpoint.
@@ -214,7 +214,7 @@ int app_subscribe_endpoint(struct app *app, struct ast_endpoint *endpoint);
* \return 0 on success.
* \return Non-zero on error.
*/
-int app_unsubscribe_endpoint_id(struct app *app, const char *endpoint_id);
+int app_unsubscribe_endpoint_id(struct stasis_app *app, const char *endpoint_id);
/*!
* \brief Test if an app is subscribed to a endpoint.
@@ -224,6 +224,6 @@ int app_unsubscribe_endpoint_id(struct app *app, const char *endpoint_id);
* \return True (non-zero) if endpoint is subscribed to \a app.
* \return False (zero) if endpoint is not subscribed.
*/
-int app_is_subscribed_endpoint_id(struct app *app, const char *endpoint_id);
+int app_is_subscribed_endpoint_id(struct stasis_app *app, const char *endpoint_id);
#endif /* _ASTERISK_RES_STASIS_APP_H */