summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/asterisk/devicestate.h9
-rw-r--r--include/asterisk/stasis_app.h108
-rw-r--r--include/asterisk/stasis_app_device_state.h95
-rw-r--r--main/devicestate.c16
-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
-rw-r--r--rest-api-templates/ari.make.mustache4
-rw-r--r--rest-api/api-docs/applications.json9
-rw-r--r--rest-api/api-docs/deviceStates.json151
-rw-r--r--rest-api/api-docs/events.json12
-rw-r--r--rest-api/resources.json4
21 files changed, 1935 insertions, 247 deletions
diff --git a/include/asterisk/devicestate.h b/include/asterisk/devicestate.h
index a3267387f..565e01341 100644
--- a/include/asterisk/devicestate.h
+++ b/include/asterisk/devicestate.h
@@ -325,6 +325,15 @@ struct stasis_cache *ast_device_state_cache(void);
struct stasis_message_type *ast_device_state_message_type(void);
/*!
+ * \brief Clear the device from the stasis cache.
+ * \param The device to clear
+ * \retval 0 if successful
+ * \retval -1 nothing to clear
+ * \since 12
+ */
+int ast_device_state_clear_cache(const char *device);
+
+/*!
* \brief Initialize the device state core
* \retval 0 Success
* \retval -1 Failure
diff --git a/include/asterisk/stasis_app.h b/include/asterisk/stasis_app.h
index 4ef55b193..0c22a6c30 100644
--- a/include/asterisk/stasis_app.h
+++ b/include/asterisk/stasis_app.h
@@ -111,6 +111,18 @@ void stasis_app_unregister(const char *app_name);
*/
int stasis_app_send(const char *app_name, struct ast_json *message);
+/*! \brief Forward declare app */
+struct stasis_app;
+
+/*!
+ * \brief Retrieve an application's name
+ *
+ * \param app An application
+ *
+ * \return The name of the application.
+ */
+const char *stasis_app_name(const struct stasis_app *app);
+
/*!
* \brief Return the JSON representation of a Stasis application.
*
@@ -121,6 +133,102 @@ int stasis_app_send(const char *app_name, struct ast_json *message);
*/
struct ast_json *stasis_app_to_json(const char *app_name);
+/*!
+ * \brief Event source information and callbacks.
+ */
+struct stasis_app_event_source {
+ /*! \brief The scheme to match against on [un]subscribes */
+ const char *scheme;
+
+ /*!
+ * \brief Find an event source data object by the given id/name.
+ *
+ * \param app Application
+ * \param id A unique identifier to search on
+ *
+ * \return The data object associated with the id/name.
+ */
+ void *(*find)(const struct stasis_app *app, const char *id);
+
+ /*!
+ * \brief Subscribe an application to an event source.
+ *
+ * \param app Application
+ * \param obj an event source data object
+ *
+ * \return 0 on success, failure code otherwise
+ */
+ int (*subscribe)(struct stasis_app *app, void *obj);
+
+ /*!
+ * \brief Cancel the subscription an app has to an event source.
+ *
+ * \param app Application
+ * \param id a previously subscribed object id
+ *
+ * \return 0 on success, failure code otherwise
+ */
+ int (*unsubscribe)(struct stasis_app *app, const char *id);
+
+ /*!
+ * \brief Find an event source by the given id/name.
+ *
+ * \param app Application
+ * \param id A unique identifier to check
+ *
+ * \return true if id is subscribed, false otherwise.
+ */
+ int (*is_subscribed)(struct stasis_app *app, const char *id);
+
+ /*!
+ * \brief Convert event source data to json
+ *
+ * \param app Application
+ * \param id json object to fill
+ */
+ void (*to_json)(const struct stasis_app *app, struct ast_json *json);
+
+ /*! Next item in the list */
+ AST_LIST_ENTRY(stasis_app_event_source) next;
+};
+
+/*!
+ * \brief Register an application event source.
+ *
+ * \param obj the event source to register
+ */
+void stasis_app_register_event_source(struct stasis_app_event_source *obj);
+
+/*!
+ * \brief Register core event sources.
+ */
+void stasis_app_register_event_sources(void);
+
+/*!
+ * \brief Checks to see if the given object is a core event source
+ *
+ * \note core event sources are currently only endpoint, bridge, and channel.
+ *
+ * \param obj event source object to check
+ *
+ * \return non-zero if core event source, otherwise 0 (false)
+
+ */
+int stasis_app_is_core_event_source(struct stasis_app_event_source *obj);
+
+/*!
+ * \brief Unregister an application event source.
+ *
+ * \param obj the event source to unregister
+ */
+void stasis_app_unregister_event_source(struct stasis_app_event_source *obj);
+
+/*!
+ * \brief Unregister core event sources.
+ */
+void stasis_app_unregister_event_sources(void);
+
+
/*! \brief Return code for stasis_app_[un]subscribe */
enum stasis_app_subscribe_res {
STASIS_ASR_OK,
diff --git a/include/asterisk/stasis_app_device_state.h b/include/asterisk/stasis_app_device_state.h
new file mode 100644
index 000000000..2bc521a04
--- /dev/null
+++ b/include/asterisk/stasis_app_device_state.h
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+#ifndef _ASTERISK_STASIS_APP_DEVICE_STATE_H
+#define _ASTERISK_STASIS_APP_DEVICE_STATE_H
+
+/*! \file
+ *
+ * \brief Stasis Application Device State API. See \ref res_stasis "Stasis
+ * Application API" for detailed documentation.
+ *
+ * \author Kevin Harwell <kharwell@digium.com>
+ * \since 12
+ */
+
+#include "asterisk/app.h"
+#include "asterisk/stasis_app.h"
+
+/*! @{ */
+
+/*!
+ * \brief Convert device state to json.
+ *
+ * \param name the name of the device
+ * \param state the device state
+ * \return JSON representation.
+ * \return \c NULL on error.
+ */
+struct ast_json *stasis_app_device_state_to_json(
+ const char *name, enum ast_device_state state);
+
+/*!
+ * \brief Convert device states to json array.
+ *
+ * \return JSON representation.
+ * \return \c NULL on error.
+ */
+struct ast_json *stasis_app_device_states_to_json(void);
+
+/*! Stasis device state application result codes */
+enum stasis_device_state_result {
+ /*! Application controlled device state is okay */
+ STASIS_DEVICE_STATE_OK,
+ /*! The device name is not application controlled */
+ STASIS_DEVICE_STATE_NOT_CONTROLLED,
+ /*! The application controlled device name is missing */
+ STASIS_DEVICE_STATE_MISSING,
+ /*! The application controlled device is unknown */
+ STASIS_DEVICE_STATE_UNKNOWN,
+ /*! The application controlled device has subscribers */
+ STASIS_DEVICE_STATE_SUBSCRIBERS
+};
+
+/*!
+ * \brief Changes the state of a device controlled by ARI.
+ *
+ * \note The controlled device must be prefixed with 'Stasis:'.
+ * \note Implicitly creates the device state.
+ *
+ * \param name the name of the ARI controlled device
+ * \param value a valid device state value
+ *
+ * \return a stasis device state application result.
+ */
+enum stasis_device_state_result stasis_app_device_state_update(
+ const char *name, const char *value);
+
+/*!
+ * \brief Delete a device controlled by ARI.
+ *
+ * \param name the name of the ARI controlled device
+ *
+ * \returna stasis device state application result.
+ */
+enum stasis_device_state_result stasis_app_device_state_delete(
+ const char *name);
+
+/*! @} */
+
+#endif /* _ASTERISK_STASIS_APP_DEVICE_STATE_H */
diff --git a/main/devicestate.c b/main/devicestate.c
index 158d1f817..7b52f00af 100644
--- a/main/devicestate.c
+++ b/main/devicestate.c
@@ -734,6 +734,22 @@ struct stasis_topic *ast_device_state_topic(const char *device)
return stasis_topic_pool_get_topic(device_state_topic_pool, device);
}
+int ast_device_state_clear_cache(const char *device)
+{
+ RAII_VAR(struct stasis_message *, cached_msg, NULL, ao2_cleanup);
+ RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+ if (!(cached_msg = stasis_cache_get(ast_device_state_cache(),
+ ast_device_state_message_type(), device))) {
+ /* nothing to clear */
+ return -1;
+ }
+
+ msg = stasis_cache_clear_create(cached_msg);
+ stasis_publish(ast_device_state_topic(device), msg);
+ return 0;
+}
+
int ast_publish_device_state_full(
const char *device,
enum ast_device_state state,
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 */
diff --git a/rest-api-templates/ari.make.mustache b/rest-api-templates/ari.make.mustache
index 37f09c179..50293cf11 100644
--- a/rest-api-templates/ari.make.mustache
+++ b/rest-api-templates/ari.make.mustache
@@ -19,8 +19,8 @@
#
{{#apis}}
-res_ari_{{name}}.so: ari/resource_{{name}}.o
+res_ari_{{c_name}}.so: ari/resource_{{c_name}}.o
-ari/resource_{{name}}.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_{{name}})
+ari/resource_{{c_name}}.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_{{c_name}})
{{/apis}}
diff --git a/rest-api/api-docs/applications.json b/rest-api/api-docs/applications.json
index 494088dc5..cd27d586e 100644
--- a/rest-api/api-docs/applications.json
+++ b/rest-api/api-docs/applications.json
@@ -68,7 +68,7 @@
},
{
"name": "eventSource",
- "description": "URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}",
+ "description": "URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, deviceState:{deviceName}",
"paramType": "query",
"required": true,
"allowMultiple": true,
@@ -107,7 +107,7 @@
},
{
"name": "eventSource",
- "description": "URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}",
+ "description": "URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, device_state:{deviceName}",
"paramType": "query",
"required": true,
"allowMultiple": true,
@@ -160,6 +160,11 @@
"type": "List[string]",
"description": "{tech}/{resource} for endpoints subscribed to.",
"required": true
+ },
+ "device_names": {
+ "type": "List[string]",
+ "description": "Names of the devices subscribed to.",
+ "required": true
}
}
}
diff --git a/rest-api/api-docs/deviceStates.json b/rest-api/api-docs/deviceStates.json
new file mode 100644
index 000000000..529bc70da
--- /dev/null
+++ b/rest-api/api-docs/deviceStates.json
@@ -0,0 +1,151 @@
+{
+ "_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.",
+ "_author": "Kevin Harwell <kharwell@digium.com>",
+ "_svn_revision": "$Revision$",
+ "apiVersion": "0.0.1",
+ "swaggerVersion": "1.1",
+ "basePath": "http://localhost:8088/stasis",
+ "resourcePath": "/api-docs/deviceStates.{format}",
+ "apis": [
+ {
+ "path": "/deviceStates",
+ "description": "Device states",
+ "operations": [
+ {
+ "httpMethod": "GET",
+ "summary": "List all ARI controlled device states.",
+ "nickname": "list",
+ "responseClass": "List[DeviceState]"
+ }
+ ]
+ },
+ {
+ "path": "/deviceStates/{deviceName}",
+ "description": "Device state",
+ "operations": [
+ {
+ "httpMethod": "GET",
+ "summary": "Retrieve the current state of a device.",
+ "nickname": "get",
+ "responseClass": "DeviceState",
+ "parameters": [
+ {
+ "name": "deviceName",
+ "description": "Name of the device",
+ "paramType": "path",
+ "required": true,
+ "allowMultiple": false,
+ "dataType": "string"
+ }
+ ]
+ },
+ {
+ "httpMethod": "PUT",
+ "summary": "Change the state of a device controlled by ARI. (Note - implicitly creates the device state).",
+ "nickname": "update",
+ "responseClass": "void",
+ "parameters": [
+ {
+ "name": "deviceName",
+ "description": "Name of the device",
+ "paramType": "path",
+ "required": true,
+ "allowMultiple": false,
+ "dataType": "string"
+ },
+ {
+ "name": "deviceState",
+ "description": "Device state value",
+ "paramType": "query",
+ "required": true,
+ "allowMultiple": false,
+ "dataType": "string",
+ "allowableValues": {
+ "valueType": "LIST",
+ "values": [
+ "NOT_INUSE",
+ "INUSE",
+ "BUSY",
+ "INVALID",
+ "UNAVAILABLE",
+ "RINGING",
+ "RINGINUSE",
+ "ONHOLD"
+ ]
+ }
+
+ }
+ ],
+ "errorResponses": [
+ {
+ "code": 404,
+ "reason": "Device name is missing"
+ },
+ {
+ "code": 409,
+ "reason": "Uncontrolled device specified"
+ }
+ ]
+ },
+ {
+ "httpMethod": "DELETE",
+ "summary": "Destroy a device-state controlled by ARI.",
+ "nickname": "delete",
+ "responseClass": "void",
+ "parameters": [
+ {
+ "name": "deviceName",
+ "description": "Name of the device",
+ "paramType": "path",
+ "required": true,
+ "allowMultiple": false,
+ "dataType": "string"
+ }
+ ],
+ "errorResponses": [
+ {
+ "code": 404,
+ "reason": "Device name is missing"
+ },
+ {
+ "code": 409,
+ "reason": "Uncontrolled device specified"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "models": {
+ "DeviceState": {
+ "id": "DeviceState",
+ "description": "Represents the state of a device.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name of the device.",
+ "required": true
+ },
+ "state": {
+ "type": "string",
+ "description": "Device's state",
+ "required": true,
+ "allowableValues": {
+ "valueType": "LIST",
+ "values": [
+ "UNKNOWN",
+ "NOT_INUSE",
+ "INUSE",
+ "BUSY",
+ "INVALID",
+ "UNAVAILABLE",
+ "RINGING",
+ "RINGINUSE",
+ "ONHOLD"
+ ]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json
index e30a193c1..a342099a8 100644
--- a/rest-api/api-docs/events.json
+++ b/rest-api/api-docs/events.json
@@ -76,6 +76,7 @@
}
},
"subTypes": [
+ "DeviceStateChanged",
"PlaybackStarted",
"PlaybackFinished",
"ApplicationReplaced",
@@ -98,6 +99,17 @@
"StasisStart"
]
},
+ "DeviceStateChanged": {
+ "id": "DeviceStateChanged",
+ "description": "Notification that a device state has changed.",
+ "properties": {
+ "device_state": {
+ "type": "DeviceState",
+ "description": "Device state object",
+ "required": true
+ }
+ }
+ },
"PlaybackStarted": {
"id": "PlaybackStarted",
"description": "Event showing the start of a media playback operation.",
diff --git a/rest-api/resources.json b/rest-api/resources.json
index 7e0b793fb..495348a21 100644
--- a/rest-api/resources.json
+++ b/rest-api/resources.json
@@ -35,6 +35,10 @@
"description": "Playback control resources"
},
{
+ "path": "/api-docs/deviceStates.{format}",
+ "description": "Device state resources"
+ },
+ {
"path": "/api-docs/events.{format}",
"description": "WebSocket resource"
},