mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-22 21:36:28 +00:00
Add channel events for res_stasis apps
This change adds a framework in res_stasis for handling events from channel topics. JSON event generation and validation code is created from event documentation in rest-api/api-docs/events.json to assist in JSON event generation, ensure consistency, and ensure that accurate documentation is available for ALL events that are received by res_stasis applications. The userevent application has been refactored along with the code that handles userevent channel blob events to pass the headers as key/value pairs in the JSON blob. As a side-effect, app_userevent now handles duplicate keys by overwriting the previous value. Review: https://reviewboard.asterisk.org/r/2428/ (closes issue ASTERISK-21180) Patch-By: Kinsey Moore <kmoore@digium.com> git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@388275 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
6
CHANGES
6
CHANGES
@@ -153,6 +153,12 @@ Sorcery
|
|||||||
column named "id" within their schema when using the Sorcery realtime module.
|
column named "id" within their schema when using the Sorcery realtime module.
|
||||||
This column must be able to contain a string of up to 128 characters in length.
|
This column must be able to contain a string of up to 128 characters in length.
|
||||||
|
|
||||||
|
app_userevent
|
||||||
|
------------------
|
||||||
|
* UserEvent will now handle duplicate keys by overwriting the previous value
|
||||||
|
assigned to the key. UserEvent invocations will also be distributed to any
|
||||||
|
interested res_stasis applications.
|
||||||
|
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
--- Functionality changes from Asterisk 10 to Asterisk 11 --------------------
|
--- Functionality changes from Asterisk 10 to Asterisk 11 --------------------
|
||||||
------------------------------------------------------------------------------
|
------------------------------------------------------------------------------
|
||||||
|
@@ -39,23 +39,28 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||||||
/*** DOCUMENTATION
|
/*** DOCUMENTATION
|
||||||
<application name="UserEvent" language="en_US">
|
<application name="UserEvent" language="en_US">
|
||||||
<synopsis>
|
<synopsis>
|
||||||
Send an arbitrary event to the manager interface.
|
Send an arbitrary user-defined event to parties interested in a channel (AMI users and relevant res_stasis applications).
|
||||||
</synopsis>
|
</synopsis>
|
||||||
<syntax>
|
<syntax>
|
||||||
<parameter name="eventname" required="true" />
|
<parameter name="eventname" required="true" />
|
||||||
<parameter name="body" />
|
<parameter name="body" />
|
||||||
</syntax>
|
</syntax>
|
||||||
<description>
|
<description>
|
||||||
<para>Sends an arbitrary event to the manager interface, with an optional
|
<para>Sends an arbitrary event to interested parties, with an optional
|
||||||
<replaceable>body</replaceable> representing additional arguments. The
|
<replaceable>body</replaceable> representing additional arguments. The
|
||||||
<replaceable>body</replaceable> may be specified as
|
<replaceable>body</replaceable> may be specified as
|
||||||
a <literal>,</literal> delimited list of headers. Each additional
|
a <literal>,</literal> delimited list of key:value pairs.</para>
|
||||||
argument will be placed on a new line in the event. The format of the
|
<para>For AMI, each additional argument will be placed on a new line in
|
||||||
event will be:</para>
|
the event and the format of the event will be:</para>
|
||||||
<para> Event: UserEvent</para>
|
<para> Event: UserEvent</para>
|
||||||
<para> UserEvent: <specified event name></para>
|
<para> UserEvent: <specified event name></para>
|
||||||
<para> [body]</para>
|
<para> [body]</para>
|
||||||
<para>If no <replaceable>body</replaceable> is specified, only Event and UserEvent headers will be present.</para>
|
<para>If no <replaceable>body</replaceable> is specified, only Event and
|
||||||
|
UserEvent headers will be present.</para>
|
||||||
|
<para>For res_stasis applications, the event will be provided as a JSON
|
||||||
|
blob with additional arguments appearing as keys in the object and the
|
||||||
|
<replaceable>eventname</replaceable> under the
|
||||||
|
<literal>eventname</literal> key.</para>
|
||||||
</description>
|
</description>
|
||||||
</application>
|
</application>
|
||||||
***/
|
***/
|
||||||
@@ -70,7 +75,6 @@ static int userevent_exec(struct ast_channel *chan, const char *data)
|
|||||||
AST_APP_ARG(eventname);
|
AST_APP_ARG(eventname);
|
||||||
AST_APP_ARG(extra)[100];
|
AST_APP_ARG(extra)[100];
|
||||||
);
|
);
|
||||||
RAII_VAR(struct ast_str *, body, ast_str_create(16), ast_free);
|
|
||||||
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
|
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
|
||||||
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
|
RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
|
||||||
|
|
||||||
@@ -79,25 +83,37 @@ static int userevent_exec(struct ast_channel *chan, const char *data)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!body) {
|
|
||||||
ast_log(LOG_WARNING, "Unable to allocate buffer\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
parse = ast_strdupa(data);
|
parse = ast_strdupa(data);
|
||||||
|
|
||||||
AST_STANDARD_APP_ARGS(args, parse);
|
AST_STANDARD_APP_ARGS(args, parse);
|
||||||
|
|
||||||
for (x = 0; x < args.argc - 1; x++) {
|
blob = ast_json_pack("{s: s, s: s}",
|
||||||
ast_str_append(&body, 0, "%s\r\n", args.extra[x]);
|
"type", "userevent",
|
||||||
|
"eventname", args.eventname);
|
||||||
|
if (!blob) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
blob = ast_json_pack("{s: s, s: s}",
|
for (x = 0; x < args.argc - 1; x++) {
|
||||||
"eventname", args.eventname,
|
char *key, *value = args.extra[x];
|
||||||
"body", ast_str_buffer(body));
|
struct ast_json *json_value;
|
||||||
if (!blob) {
|
|
||||||
ast_log(LOG_WARNING, "Unable to create message buffer\n");
|
key = strsep(&value, ":");
|
||||||
return -1;
|
if (!value) {
|
||||||
|
/* no ':' in string? */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = ast_strip(value);
|
||||||
|
json_value = ast_json_string_create(value);
|
||||||
|
if (!json_value) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ref stolen by ast_json_object_set */
|
||||||
|
if (ast_json_object_set(blob, key, json_value)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = ast_channel_blob_create(
|
msg = ast_channel_blob_create(
|
||||||
|
@@ -306,6 +306,34 @@ void ast_channel_publish_dial(struct ast_channel *caller,
|
|||||||
*/
|
*/
|
||||||
struct ast_json *ast_channel_snapshot_to_json(const struct ast_channel_snapshot *snapshot);
|
struct ast_json *ast_channel_snapshot_to_json(const struct ast_channel_snapshot *snapshot);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Compares the context, exten and priority of two snapshots.
|
||||||
|
* \since 12
|
||||||
|
*
|
||||||
|
* \param old_snapshot Old snapshot
|
||||||
|
* \param new_snapshot New snapshot
|
||||||
|
*
|
||||||
|
* \return True (non-zero) if context, exten or priority are identical.
|
||||||
|
* \return False (zero) if context, exten and priority changed.
|
||||||
|
*/
|
||||||
|
int ast_channel_snapshot_cep_equal(
|
||||||
|
const struct ast_channel_snapshot *old_snapshot,
|
||||||
|
const struct ast_channel_snapshot *new_snapshot);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Compares the callerid info of two snapshots.
|
||||||
|
* \since 12
|
||||||
|
*
|
||||||
|
* \param old_snapshot Old snapshot
|
||||||
|
* \param new_snapshot New snapshot
|
||||||
|
*
|
||||||
|
* \return True (non-zero) if callerid are identical.
|
||||||
|
* \return False (zero) if callerid changed.
|
||||||
|
*/
|
||||||
|
int ast_channel_snapshot_caller_id_equal(
|
||||||
|
const struct ast_channel_snapshot *old_snapshot,
|
||||||
|
const struct ast_channel_snapshot *new_snapshot);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Dispose of the stasis channel topics and message types
|
* \brief Dispose of the stasis channel topics and message types
|
||||||
*/
|
*/
|
||||||
|
@@ -402,34 +402,6 @@ static struct snapshot_manager_event *channel_state_change(
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief Compares the context, exten and priority of two snapshots.
|
|
||||||
* \param old_snapshot Old snapshot
|
|
||||||
* \param new_snapshot New snapshot
|
|
||||||
* \return True (non-zero) if context, exten or priority are identical.
|
|
||||||
* \return False (zero) if context, exten and priority changed.
|
|
||||||
*/
|
|
||||||
static inline int cep_equal(
|
|
||||||
const struct ast_channel_snapshot *old_snapshot,
|
|
||||||
const struct ast_channel_snapshot *new_snapshot)
|
|
||||||
{
|
|
||||||
ast_assert(old_snapshot != NULL);
|
|
||||||
ast_assert(new_snapshot != NULL);
|
|
||||||
|
|
||||||
/* We actually get some snapshots with CEP set, but before the
|
|
||||||
* application is set. Since empty application is invalid, we treat
|
|
||||||
* setting the application from nothing as a CEP change.
|
|
||||||
*/
|
|
||||||
if (ast_strlen_zero(old_snapshot->appl) &&
|
|
||||||
!ast_strlen_zero(new_snapshot->appl)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return old_snapshot->priority == new_snapshot->priority &&
|
|
||||||
strcmp(old_snapshot->context, new_snapshot->context) == 0 &&
|
|
||||||
strcmp(old_snapshot->exten, new_snapshot->exten) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct snapshot_manager_event *channel_newexten(
|
static struct snapshot_manager_event *channel_newexten(
|
||||||
struct ast_channel_snapshot *old_snapshot,
|
struct ast_channel_snapshot *old_snapshot,
|
||||||
struct ast_channel_snapshot *new_snapshot)
|
struct ast_channel_snapshot *new_snapshot)
|
||||||
@@ -444,7 +416,7 @@ static struct snapshot_manager_event *channel_newexten(
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (old_snapshot && cep_equal(old_snapshot, new_snapshot)) {
|
if (old_snapshot && ast_channel_snapshot_cep_equal(old_snapshot, new_snapshot)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,23 +431,6 @@ static struct snapshot_manager_event *channel_newexten(
|
|||||||
new_snapshot->data);
|
new_snapshot->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief Compares the callerid info of two snapshots.
|
|
||||||
* \param old_snapshot Old snapshot
|
|
||||||
* \param new_snapshot New snapshot
|
|
||||||
* \return True (non-zero) if callerid are identical.
|
|
||||||
* \return False (zero) if callerid changed.
|
|
||||||
*/
|
|
||||||
static inline int caller_id_equal(
|
|
||||||
const struct ast_channel_snapshot *old_snapshot,
|
|
||||||
const struct ast_channel_snapshot *new_snapshot)
|
|
||||||
{
|
|
||||||
ast_assert(old_snapshot != NULL);
|
|
||||||
ast_assert(new_snapshot != NULL);
|
|
||||||
return strcmp(old_snapshot->caller_number, new_snapshot->caller_number) == 0 &&
|
|
||||||
strcmp(old_snapshot->caller_name, new_snapshot->caller_name) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct snapshot_manager_event *channel_new_callerid(
|
static struct snapshot_manager_event *channel_new_callerid(
|
||||||
struct ast_channel_snapshot *old_snapshot,
|
struct ast_channel_snapshot *old_snapshot,
|
||||||
struct ast_channel_snapshot *new_snapshot)
|
struct ast_channel_snapshot *new_snapshot)
|
||||||
@@ -485,7 +440,7 @@ static struct snapshot_manager_event *channel_new_callerid(
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (caller_id_equal(old_snapshot, new_snapshot)) {
|
if (ast_channel_snapshot_caller_id_equal(old_snapshot, new_snapshot)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,19 +542,62 @@ static void channel_varset_cb(void *data, struct stasis_subscription *sub,
|
|||||||
variable, value);
|
variable, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Callback used to determine whether a key should be skipped when converting a JSON object to a manager blob
|
||||||
|
* \param key Key from JSON blob to be evaluated
|
||||||
|
* \retval non-zero if the key should be excluded
|
||||||
|
* \retval zero if the key should not be excluded
|
||||||
|
*/
|
||||||
|
typedef int (*key_exclusion_cb)(const char *key);
|
||||||
|
|
||||||
|
static struct ast_str *manager_str_from_json_object(struct ast_json *blob, key_exclusion_cb exclusion_cb)
|
||||||
|
{
|
||||||
|
struct ast_str *output_str = ast_str_create(32);
|
||||||
|
struct ast_json_iter *blob_iter = ast_json_object_iter(blob);
|
||||||
|
if (!output_str || !blob_iter) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
const char *key = ast_json_object_iter_key(blob_iter);
|
||||||
|
const char *value = ast_json_string_get(ast_json_object_iter_value(blob_iter));
|
||||||
|
if (exclusion_cb && exclusion_cb(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ast_str_append(&output_str, 0, "%s: %s\r\n", key, value);
|
||||||
|
if (!output_str) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
} while ((blob_iter = ast_json_object_iter_next(blob, blob_iter)));
|
||||||
|
|
||||||
|
return output_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int userevent_exclusion_cb(const char *key)
|
||||||
|
{
|
||||||
|
if (!strcmp("type", key)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (!strcmp("eventname", key)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void channel_user_event_cb(void *data, struct stasis_subscription *sub,
|
static void channel_user_event_cb(void *data, struct stasis_subscription *sub,
|
||||||
struct stasis_topic *topic, struct stasis_message *message)
|
struct stasis_topic *topic, struct stasis_message *message)
|
||||||
{
|
{
|
||||||
struct ast_channel_blob *obj = stasis_message_data(message);
|
struct ast_channel_blob *obj = stasis_message_data(message);
|
||||||
RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
|
RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
|
||||||
|
RAII_VAR(struct ast_str *, body, NULL, ast_free);
|
||||||
const char *eventname;
|
const char *eventname;
|
||||||
const char *body;
|
|
||||||
|
|
||||||
eventname = ast_json_string_get(ast_json_object_get(obj->blob, "eventname"));
|
eventname = ast_json_string_get(ast_json_object_get(obj->blob, "eventname"));
|
||||||
body = ast_json_string_get(ast_json_object_get(obj->blob, "body"));
|
body = manager_str_from_json_object(obj->blob, userevent_exclusion_cb);
|
||||||
channel_event_string = ast_manager_build_channel_state_string(obj->snapshot);
|
channel_event_string = ast_manager_build_channel_state_string(obj->snapshot);
|
||||||
|
|
||||||
if (!channel_event_string) {
|
if (!channel_event_string || !body) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,7 +619,7 @@ static void channel_user_event_cb(void *data, struct stasis_subscription *sub,
|
|||||||
"%s"
|
"%s"
|
||||||
"UserEvent: %s\r\n"
|
"UserEvent: %s\r\n"
|
||||||
"%s",
|
"%s",
|
||||||
ast_str_buffer(channel_event_string), eventname, body);
|
ast_str_buffer(channel_event_string), eventname, ast_str_buffer(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void channel_hangup_request_cb(void *data,
|
static void channel_hangup_request_cb(void *data,
|
||||||
|
@@ -448,6 +448,37 @@ struct ast_json *ast_channel_snapshot_to_json(const struct ast_channel_snapshot
|
|||||||
return ast_json_ref(json_chan);
|
return ast_json_ref(json_chan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ast_channel_snapshot_cep_equal(
|
||||||
|
const struct ast_channel_snapshot *old_snapshot,
|
||||||
|
const struct ast_channel_snapshot *new_snapshot)
|
||||||
|
{
|
||||||
|
ast_assert(old_snapshot != NULL);
|
||||||
|
ast_assert(new_snapshot != NULL);
|
||||||
|
|
||||||
|
/* We actually get some snapshots with CEP set, but before the
|
||||||
|
* application is set. Since empty application is invalid, we treat
|
||||||
|
* setting the application from nothing as a CEP change.
|
||||||
|
*/
|
||||||
|
if (ast_strlen_zero(old_snapshot->appl) &&
|
||||||
|
!ast_strlen_zero(new_snapshot->appl)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return old_snapshot->priority == new_snapshot->priority &&
|
||||||
|
strcmp(old_snapshot->context, new_snapshot->context) == 0 &&
|
||||||
|
strcmp(old_snapshot->exten, new_snapshot->exten) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ast_channel_snapshot_caller_id_equal(
|
||||||
|
const struct ast_channel_snapshot *old_snapshot,
|
||||||
|
const struct ast_channel_snapshot *new_snapshot)
|
||||||
|
{
|
||||||
|
ast_assert(old_snapshot != NULL);
|
||||||
|
ast_assert(new_snapshot != NULL);
|
||||||
|
return strcmp(old_snapshot->caller_number, new_snapshot->caller_number) == 0 &&
|
||||||
|
strcmp(old_snapshot->caller_name, new_snapshot->caller_name) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
void ast_stasis_channels_shutdown(void)
|
void ast_stasis_channels_shutdown(void)
|
||||||
{
|
{
|
||||||
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_snapshot_type);
|
STASIS_MESSAGE_TYPE_CLEANUP(ast_channel_snapshot_type);
|
||||||
|
455
res/res_stasis.c
455
res/res_stasis.c
@@ -39,6 +39,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||||||
#include "asterisk/stasis_app.h"
|
#include "asterisk/stasis_app.h"
|
||||||
#include "asterisk/stasis_channels.h"
|
#include "asterisk/stasis_channels.h"
|
||||||
#include "asterisk/strings.h"
|
#include "asterisk/strings.h"
|
||||||
|
#include "asterisk/stasis_message_router.h"
|
||||||
|
#include "asterisk/callerid.h"
|
||||||
|
#include "stasis_http/resource_events.h"
|
||||||
|
|
||||||
/*! Time to wait for a frame in the application */
|
/*! Time to wait for a frame in the application */
|
||||||
#define MAX_WAIT_MS 200
|
#define MAX_WAIT_MS 200
|
||||||
@@ -55,6 +58,18 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||||||
*/
|
*/
|
||||||
#define CONTROLS_NUM_BUCKETS 127
|
#define CONTROLS_NUM_BUCKETS 127
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Number of buckets for the channels container for app instances. Remember
|
||||||
|
* to keep it a prime number!
|
||||||
|
*/
|
||||||
|
#define APP_CHANNELS_BUCKETS 7
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Number of buckets for the blob_handlers container. Remember to keep
|
||||||
|
* it a prime number!
|
||||||
|
*/
|
||||||
|
#define BLOB_HANDLER_BUCKETS 7
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Stasis application container. Please call apps_registry() instead of
|
* \brief Stasis application container. Please call apps_registry() instead of
|
||||||
* directly accessing.
|
* directly accessing.
|
||||||
@@ -63,6 +78,9 @@ struct ao2_container *__apps_registry;
|
|||||||
|
|
||||||
struct ao2_container *__app_controls;
|
struct ao2_container *__app_controls;
|
||||||
|
|
||||||
|
/*! \brief Message router for the channel caching topic */
|
||||||
|
struct stasis_message_router *channel_router;
|
||||||
|
|
||||||
/*! Ref-counting accessor for the stasis applications container */
|
/*! Ref-counting accessor for the stasis applications container */
|
||||||
static struct ao2_container *apps_registry(void)
|
static struct ao2_container *apps_registry(void)
|
||||||
{
|
{
|
||||||
@@ -81,6 +99,8 @@ struct app {
|
|||||||
stasis_app_cb handler;
|
stasis_app_cb handler;
|
||||||
/*! Opaque data to hand to callback function. */
|
/*! Opaque data to hand to callback function. */
|
||||||
void *data;
|
void *data;
|
||||||
|
/*! List of channel identifiers this app instance is interested in */
|
||||||
|
struct ao2_container *channels;
|
||||||
/*! Name of the Stasis application */
|
/*! Name of the Stasis application */
|
||||||
char name[];
|
char name[];
|
||||||
};
|
};
|
||||||
@@ -91,12 +111,14 @@ static void app_dtor(void *obj)
|
|||||||
|
|
||||||
ao2_cleanup(app->data);
|
ao2_cleanup(app->data);
|
||||||
app->data = NULL;
|
app->data = NULL;
|
||||||
|
ao2_cleanup(app->channels);
|
||||||
|
app->channels = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Constructor for \ref app. */
|
/*! Constructor for \ref app. */
|
||||||
static struct app *app_create(const char *name, stasis_app_cb handler, void *data)
|
static struct app *app_create(const char *name, stasis_app_cb handler, void *data)
|
||||||
{
|
{
|
||||||
struct app *app;
|
RAII_VAR(struct app *, app, NULL, ao2_cleanup);
|
||||||
size_t size;
|
size_t size;
|
||||||
|
|
||||||
ast_assert(name != NULL);
|
ast_assert(name != NULL);
|
||||||
@@ -114,6 +136,12 @@ static struct app *app_create(const char *name, stasis_app_cb handler, void *dat
|
|||||||
ao2_ref(data, +1);
|
ao2_ref(data, +1);
|
||||||
app->data = data;
|
app->data = data;
|
||||||
|
|
||||||
|
app->channels = ast_str_container_alloc(APP_CHANNELS_BUCKETS);
|
||||||
|
if (!app->channels) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ao2_ref(app, +1);
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,6 +168,27 @@ static int app_compare(void *lhs, void *rhs, int flags)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int app_add_channel(struct app* app, const struct ast_channel *chan)
|
||||||
|
{
|
||||||
|
const char *uniqueid;
|
||||||
|
ast_assert(chan != NULL);
|
||||||
|
ast_assert(app != NULL);
|
||||||
|
|
||||||
|
uniqueid = ast_channel_uniqueid(chan);
|
||||||
|
if (!ast_str_container_add(app->channels, uniqueid)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void app_remove_channel(struct app* app, const struct ast_channel *chan)
|
||||||
|
{
|
||||||
|
ast_assert(chan != NULL);
|
||||||
|
ast_assert(app != NULL);
|
||||||
|
|
||||||
|
ao2_find(app->channels, ast_channel_uniqueid(chan), OBJ_KEY | OBJ_NODATA | OBJ_UNLINK);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Send a message to the given application.
|
* \brief Send a message to the given application.
|
||||||
* \param app App to send the message to.
|
* \param app App to send the message to.
|
||||||
@@ -316,6 +365,9 @@ void stasis_app_control_continue(struct stasis_app_control *control)
|
|||||||
control->continue_to_dialplan = 1;
|
control->continue_to_dialplan = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! \brief Typedef for blob handler callbacks */
|
||||||
|
typedef struct ast_json *(*channel_blob_handler_cb)(struct ast_channel_blob *);
|
||||||
|
|
||||||
static int OK = 0;
|
static int OK = 0;
|
||||||
static int FAIL = -1;
|
static int FAIL = -1;
|
||||||
|
|
||||||
@@ -343,43 +395,11 @@ int stasis_app_control_answer(struct stasis_app_control *control)
|
|||||||
return *retval;
|
return *retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct ast_json *app_event_create(
|
|
||||||
const char *event_name,
|
|
||||||
const struct ast_channel_snapshot *snapshot,
|
|
||||||
const struct ast_json *extra_info)
|
|
||||||
{
|
|
||||||
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
|
||||||
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
|
||||||
|
|
||||||
if (extra_info) {
|
|
||||||
event = ast_json_deep_copy(extra_info);
|
|
||||||
} else {
|
|
||||||
event = ast_json_object_create();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (snapshot) {
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
/* Mustn't already have a channel field */
|
|
||||||
ast_assert(ast_json_object_get(event, "channel") == NULL);
|
|
||||||
|
|
||||||
ret = ast_json_object_set(
|
|
||||||
event,
|
|
||||||
"channel", ast_channel_snapshot_to_json(snapshot));
|
|
||||||
if (ret != 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message = ast_json_pack("{s: o}", event_name, ast_json_ref(event));
|
|
||||||
|
|
||||||
return ast_json_ref(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int send_start_msg(struct app *app, struct ast_channel *chan,
|
static int send_start_msg(struct app *app, struct ast_channel *chan,
|
||||||
int argc, char *argv[])
|
int argc, char *argv[])
|
||||||
{
|
{
|
||||||
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
|
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
|
||||||
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
|
RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
|
||||||
|
|
||||||
struct ast_json *json_args;
|
struct ast_json *json_args;
|
||||||
@@ -393,19 +413,13 @@ static int send_start_msg(struct app *app, struct ast_channel *chan,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = ast_json_pack("{s: {s: [], s: o}}",
|
blob = ast_json_pack("{s: []}", "args");
|
||||||
"stasis-start",
|
if (!blob) {
|
||||||
"args",
|
|
||||||
"channel", ast_channel_snapshot_to_json(snapshot));
|
|
||||||
|
|
||||||
if (!msg) {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Append arguments to args array */
|
/* Append arguments to args array */
|
||||||
json_args = ast_json_object_get(
|
json_args = ast_json_object_get(blob, "args");
|
||||||
ast_json_object_get(msg, "stasis-start"),
|
|
||||||
"args");
|
|
||||||
ast_assert(json_args != NULL);
|
ast_assert(json_args != NULL);
|
||||||
for (i = 0; i < argc; ++i) {
|
for (i = 0; i < argc; ++i) {
|
||||||
int r = ast_json_array_append(json_args,
|
int r = ast_json_array_append(json_args,
|
||||||
@@ -416,6 +430,11 @@ static int send_start_msg(struct app *app, struct ast_channel *chan,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg = stasis_json_event_stasis_start_create(snapshot, blob);
|
||||||
|
if (!msg) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
app_send(app, msg);
|
app_send(app, msg);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -432,7 +451,8 @@ static int send_end_msg(struct app *app, struct ast_channel *chan)
|
|||||||
if (snapshot == NULL) {
|
if (snapshot == NULL) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
msg = app_event_create("stasis-end", snapshot, NULL);
|
|
||||||
|
msg = stasis_json_event_stasis_end_create(snapshot);
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -441,62 +461,201 @@ static int send_end_msg(struct app *app, struct ast_channel *chan)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dtmf_handler(struct app *app, struct ast_channel_blob *obj)
|
static int app_watching_channel_cb(void *obj, void *arg, int flags)
|
||||||
|
{
|
||||||
|
RAII_VAR(char *, uniqueid, NULL, ao2_cleanup);
|
||||||
|
struct app *app = obj;
|
||||||
|
char *chan_uniqueid = arg;
|
||||||
|
|
||||||
|
uniqueid = ao2_find(app->channels, chan_uniqueid, OBJ_KEY);
|
||||||
|
return uniqueid ? CMP_MATCH : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct ao2_container *get_watching_apps(const char *uniqueid)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ao2_container *, apps, apps_registry(), ao2_cleanup);
|
||||||
|
struct ao2_container *watching_apps;
|
||||||
|
char *uniqueid_dup;
|
||||||
|
RAII_VAR(struct ao2_iterator *,watching_apps_iter, NULL, ao2_iterator_destroy);
|
||||||
|
ast_assert(uniqueid != NULL);
|
||||||
|
ast_assert(apps != NULL);
|
||||||
|
|
||||||
|
uniqueid_dup = ast_strdupa(uniqueid);
|
||||||
|
|
||||||
|
watching_apps_iter = ao2_callback(apps, OBJ_MULTIPLE, app_watching_channel_cb, uniqueid_dup);
|
||||||
|
watching_apps = watching_apps_iter->c;
|
||||||
|
|
||||||
|
if (!ao2_container_count(watching_apps)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ao2_ref(watching_apps, +1);
|
||||||
|
return watching_apps_iter->c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Typedef for callbacks that get called on channel snapshot updates */
|
||||||
|
typedef struct ast_json *(*channel_snapshot_monitor)(
|
||||||
|
struct ast_channel_snapshot *old_snapshot,
|
||||||
|
struct ast_channel_snapshot *new_snapshot);
|
||||||
|
|
||||||
|
/*! \brief Handle channel state changes */
|
||||||
|
static struct ast_json *channel_state(
|
||||||
|
struct ast_channel_snapshot *old_snapshot,
|
||||||
|
struct ast_channel_snapshot *new_snapshot)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||||
|
struct ast_channel_snapshot *snapshot = new_snapshot ? new_snapshot : old_snapshot;
|
||||||
|
|
||||||
|
if (!old_snapshot) {
|
||||||
|
return stasis_json_event_channel_created_create(snapshot);
|
||||||
|
} else if (!new_snapshot) {
|
||||||
|
json = ast_json_pack("{s: i, s: s}",
|
||||||
|
"cause", snapshot->hangupcause,
|
||||||
|
"cause_txt", ast_cause2str(snapshot->hangupcause));
|
||||||
|
if (!json) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return stasis_json_event_channel_destroyed_create(snapshot, json);
|
||||||
|
} else if (old_snapshot->state != new_snapshot->state) {
|
||||||
|
return stasis_json_event_channel_state_change_create(snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct ast_json *channel_dialplan(
|
||||||
|
struct ast_channel_snapshot *old_snapshot,
|
||||||
|
struct ast_channel_snapshot *new_snapshot)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||||
|
|
||||||
|
/* No Newexten event on cache clear */
|
||||||
|
if (!new_snapshot) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty application is not valid for a Newexten event */
|
||||||
|
if (ast_strlen_zero(new_snapshot->appl)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_snapshot && ast_channel_snapshot_cep_equal(old_snapshot, new_snapshot)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
json = ast_json_pack("{s: s, s: s}",
|
||||||
|
"application", new_snapshot->appl,
|
||||||
|
"application_data", new_snapshot->data);
|
||||||
|
if (!json) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stasis_json_event_channel_dialplan_create(new_snapshot, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct ast_json *channel_callerid(
|
||||||
|
struct ast_channel_snapshot *old_snapshot,
|
||||||
|
struct ast_channel_snapshot *new_snapshot)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
|
||||||
|
|
||||||
|
/* No NewCallerid event on cache clear or first event */
|
||||||
|
if (!old_snapshot || !new_snapshot) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ast_channel_snapshot_caller_id_equal(old_snapshot, new_snapshot)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
json = ast_json_pack("{s: i, s: s}",
|
||||||
|
"caller_presentation", new_snapshot->caller_pres,
|
||||||
|
"caller_presentation_txt", ast_describe_caller_presentation(new_snapshot->caller_pres));
|
||||||
|
if (!json) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stasis_json_event_channel_caller_id_create(new_snapshot, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct ast_json *channel_snapshot(
|
||||||
|
struct ast_channel_snapshot *old_snapshot,
|
||||||
|
struct ast_channel_snapshot *new_snapshot)
|
||||||
|
{
|
||||||
|
if (!new_snapshot) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stasis_json_event_channel_snapshot_create(new_snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
channel_snapshot_monitor channel_monitors[] = {
|
||||||
|
channel_snapshot,
|
||||||
|
channel_state,
|
||||||
|
channel_dialplan,
|
||||||
|
channel_callerid
|
||||||
|
};
|
||||||
|
|
||||||
|
static int app_send_cb(void *obj, void *arg, int flags)
|
||||||
|
{
|
||||||
|
struct app *app = obj;
|
||||||
|
struct ast_json *msg = arg;
|
||||||
|
|
||||||
|
app_send(app, msg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sub_snapshot_handler(void *data,
|
||||||
|
struct stasis_subscription *sub,
|
||||||
|
struct stasis_topic *topic,
|
||||||
|
struct stasis_message *message)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ao2_container *, watching_apps, NULL, ao2_cleanup);
|
||||||
|
struct stasis_cache_update *update = stasis_message_data(message);
|
||||||
|
struct ast_channel_snapshot *new_snapshot = stasis_message_data(update->new_snapshot);
|
||||||
|
struct ast_channel_snapshot *old_snapshot = stasis_message_data(update->old_snapshot);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
watching_apps = get_watching_apps(new_snapshot ? new_snapshot->uniqueid : old_snapshot->uniqueid);
|
||||||
|
if (!watching_apps) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_LEN(channel_monitors); ++i) {
|
||||||
|
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
|
||||||
|
|
||||||
|
msg = channel_monitors[i](old_snapshot, new_snapshot);
|
||||||
|
if (msg) {
|
||||||
|
ao2_callback(watching_apps, OBJ_NODATA, app_send_cb, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void distribute_message(struct ao2_container *apps, struct ast_json *msg)
|
||||||
|
{
|
||||||
|
ao2_callback(apps, OBJ_NODATA, app_send_cb, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void generic_blob_handler(struct ast_channel_blob *obj, channel_blob_handler_cb handler_cb)
|
||||||
{
|
{
|
||||||
RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref);
|
|
||||||
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
|
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
|
||||||
const char *direction;
|
RAII_VAR(struct ao2_container *, watching_apps, NULL, ao2_cleanup);
|
||||||
|
|
||||||
/* To simplify events, we'll only generate on receive */
|
if (!obj->snapshot) {
|
||||||
direction = ast_json_string_get(
|
|
||||||
ast_json_object_get(obj->blob, "direction"));
|
|
||||||
|
|
||||||
if (strcmp("Received", direction) != 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
extra = ast_json_pack(
|
watching_apps = get_watching_apps(obj->snapshot->uniqueid);
|
||||||
"{s: o}",
|
if (!watching_apps) {
|
||||||
"digit", ast_json_ref(ast_json_object_get(obj->blob, "digit")));
|
|
||||||
if (!extra) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = app_event_create("dtmf-received", obj->snapshot, extra);
|
msg = handler_cb(obj);
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
app_send(app, msg);
|
distribute_message(watching_apps, msg);
|
||||||
}
|
|
||||||
|
|
||||||
static void sub_handler(void *data, struct stasis_subscription *sub,
|
|
||||||
struct stasis_topic *topic,
|
|
||||||
struct stasis_message *message)
|
|
||||||
{
|
|
||||||
struct app *app = data;
|
|
||||||
|
|
||||||
if (stasis_subscription_final_message(sub, message)) {
|
|
||||||
ao2_cleanup(data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ast_channel_snapshot_type() == stasis_message_type(message)) {
|
|
||||||
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
|
|
||||||
struct ast_channel_snapshot *snapshot =
|
|
||||||
stasis_message_data(message);
|
|
||||||
|
|
||||||
msg = app_event_create("channel-state-change", snapshot, NULL);
|
|
||||||
if (!msg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
app_send(app, msg);
|
|
||||||
} else if (ast_channel_dtmf_end_type() == stasis_message_type(message)) {
|
|
||||||
/* To simplify events, we'll only generate on DTMF end */
|
|
||||||
struct ast_channel_blob *blob = stasis_message_data(message);
|
|
||||||
dtmf_handler(app, blob);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -544,8 +703,6 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
|
|||||||
RAII_VAR(struct ao2_container *, apps, apps_registry(), ao2_cleanup);
|
RAII_VAR(struct ao2_container *, apps, apps_registry(), ao2_cleanup);
|
||||||
RAII_VAR(struct app *, app, NULL, ao2_cleanup);
|
RAII_VAR(struct app *, app, NULL, ao2_cleanup);
|
||||||
RAII_VAR(struct stasis_app_control *, control, NULL, control_unlink);
|
RAII_VAR(struct stasis_app_control *, control, NULL, control_unlink);
|
||||||
RAII_VAR(struct stasis_subscription *, subscription, NULL,
|
|
||||||
stasis_unsubscribe);
|
|
||||||
int res = 0;
|
int res = 0;
|
||||||
int hungup = 0;
|
int hungup = 0;
|
||||||
|
|
||||||
@@ -570,21 +727,17 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
|
|||||||
ao2_link(controls, control);
|
ao2_link(controls, control);
|
||||||
}
|
}
|
||||||
|
|
||||||
subscription =
|
|
||||||
stasis_subscribe(ast_channel_topic(chan), sub_handler, app);
|
|
||||||
if (subscription == NULL) {
|
|
||||||
ast_log(LOG_ERROR, "Error subscribing app %s to channel %s\n",
|
|
||||||
app_name, ast_channel_name(chan));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
ao2_ref(app, +1); /* subscription now has a reference */
|
|
||||||
|
|
||||||
res = send_start_msg(app, chan, argc, argv);
|
res = send_start_msg(app, chan, argc, argv);
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
ast_log(LOG_ERROR, "Error sending start message to %s\n", app_name);
|
ast_log(LOG_ERROR, "Error sending start message to %s\n", app_name);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (app_add_channel(app, chan)) {
|
||||||
|
ast_log(LOG_ERROR, "Error adding listener for channel %s to app %s\n", ast_channel_name(chan), app_name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
RAII_VAR(struct ast_frame *, f, NULL, ast_frame_dtor);
|
RAII_VAR(struct ast_frame *, f, NULL, ast_frame_dtor);
|
||||||
int r;
|
int r;
|
||||||
@@ -634,6 +787,7 @@ int stasis_app_exec(struct ast_channel *chan, const char *app_name, int argc,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app_remove_channel(app, chan);
|
||||||
res = send_end_msg(app, chan);
|
res = send_end_msg(app, chan);
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
ast_log(LOG_ERROR,
|
ast_log(LOG_ERROR,
|
||||||
@@ -675,10 +829,16 @@ int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data)
|
|||||||
|
|
||||||
if (app) {
|
if (app) {
|
||||||
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
|
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
|
||||||
SCOPED_LOCK(app_lock, app, ao2_lock, ao2_unlock);
|
SCOPED_LOCK(app_lock, app, ao2_lock, ao2_unlock);
|
||||||
|
|
||||||
msg = app_event_create("application-replaced", NULL, NULL);
|
blob = ast_json_pack("{s: s}", "application", app_name);
|
||||||
app->handler(app->data, app_name, msg);
|
if (blob) {
|
||||||
|
msg = stasis_json_event_application_replaced_create(blob);
|
||||||
|
if (msg) {
|
||||||
|
app->handler(app->data, app_name, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
app->handler = handler;
|
app->handler = handler;
|
||||||
ao2_cleanup(app->data);
|
ao2_cleanup(app->data);
|
||||||
@@ -706,6 +866,82 @@ void stasis_app_unregister(const char *app_name)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct ast_json *handle_blob_dtmf(struct ast_channel_blob *obj)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, extra, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
|
||||||
|
const char *direction;
|
||||||
|
|
||||||
|
/* To simplify events, we'll only generate on receive */
|
||||||
|
direction = ast_json_string_get(
|
||||||
|
ast_json_object_get(obj->blob, "direction"));
|
||||||
|
|
||||||
|
if (strcmp("Received", direction) != 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
extra = ast_json_pack(
|
||||||
|
"{s: o}",
|
||||||
|
"digit", ast_json_ref(ast_json_object_get(obj->blob, "digit")));
|
||||||
|
if (!extra) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stasis_json_event_channel_dtmf_received_create(obj->snapshot, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To simplify events, we'll only generate on DTMF end (dtmf_end type) */
|
||||||
|
static void sub_dtmf_handler(void *data,
|
||||||
|
struct stasis_subscription *sub,
|
||||||
|
struct stasis_topic *topic,
|
||||||
|
struct stasis_message *message)
|
||||||
|
{
|
||||||
|
struct ast_channel_blob *obj = stasis_message_data(message);
|
||||||
|
generic_blob_handler(obj, handle_blob_dtmf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct ast_json *handle_blob_userevent(struct ast_channel_blob *obj)
|
||||||
|
{
|
||||||
|
return stasis_json_event_channel_userevent_create(obj->snapshot, obj->blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sub_userevent_handler(void *data,
|
||||||
|
struct stasis_subscription *sub,
|
||||||
|
struct stasis_topic *topic,
|
||||||
|
struct stasis_message *message)
|
||||||
|
{
|
||||||
|
struct ast_channel_blob *obj = stasis_message_data(message);
|
||||||
|
generic_blob_handler(obj, handle_blob_userevent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct ast_json *handle_blob_hangup_request(struct ast_channel_blob *obj)
|
||||||
|
{
|
||||||
|
return stasis_json_event_channel_hangup_request_create(obj->snapshot, obj->blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sub_hangup_request_handler(void *data,
|
||||||
|
struct stasis_subscription *sub,
|
||||||
|
struct stasis_topic *topic,
|
||||||
|
struct stasis_message *message)
|
||||||
|
{
|
||||||
|
struct ast_channel_blob *obj = stasis_message_data(message);
|
||||||
|
generic_blob_handler(obj, handle_blob_hangup_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct ast_json *handle_blob_varset(struct ast_channel_blob *obj)
|
||||||
|
{
|
||||||
|
return stasis_json_event_channel_varset_create(obj->snapshot, obj->blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sub_varset_handler(void *data,
|
||||||
|
struct stasis_subscription *sub,
|
||||||
|
struct stasis_topic *topic,
|
||||||
|
struct stasis_message *message)
|
||||||
|
{
|
||||||
|
struct ast_channel_blob *obj = stasis_message_data(message);
|
||||||
|
generic_blob_handler(obj, handle_blob_varset);
|
||||||
|
}
|
||||||
|
|
||||||
static int load_module(void)
|
static int load_module(void)
|
||||||
{
|
{
|
||||||
int r = 0;
|
int r = 0;
|
||||||
@@ -722,13 +958,30 @@ static int load_module(void)
|
|||||||
return AST_MODULE_LOAD_FAILURE;
|
return AST_MODULE_LOAD_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return r;
|
channel_router = stasis_message_router_create(stasis_caching_get_topic(ast_channel_topic_all_cached()));
|
||||||
|
if (!channel_router) {
|
||||||
|
return AST_MODULE_LOAD_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
r |= stasis_message_router_add(channel_router, stasis_cache_update_type(), sub_snapshot_handler, NULL);
|
||||||
|
r |= stasis_message_router_add(channel_router, ast_channel_user_event_type(), sub_userevent_handler, NULL);
|
||||||
|
r |= stasis_message_router_add(channel_router, ast_channel_varset_type(), sub_varset_handler, NULL);
|
||||||
|
r |= stasis_message_router_add(channel_router, ast_channel_dtmf_begin_type(), sub_dtmf_handler, NULL);
|
||||||
|
r |= stasis_message_router_add(channel_router, ast_channel_hangup_request_type(), sub_hangup_request_handler, NULL);
|
||||||
|
if (r) {
|
||||||
|
return AST_MODULE_LOAD_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AST_MODULE_LOAD_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int unload_module(void)
|
static int unload_module(void)
|
||||||
{
|
{
|
||||||
int r = 0;
|
int r = 0;
|
||||||
|
|
||||||
|
stasis_message_router_unsubscribe(channel_router);
|
||||||
|
channel_router = NULL;
|
||||||
|
|
||||||
ao2_cleanup(__apps_registry);
|
ao2_cleanup(__apps_registry);
|
||||||
__apps_registry = NULL;
|
__apps_registry = NULL;
|
||||||
|
|
||||||
|
@@ -42,6 +42,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||||||
|
|
||||||
#include "asterisk/module.h"
|
#include "asterisk/module.h"
|
||||||
#include "stasis_http/resource_events.h"
|
#include "stasis_http/resource_events.h"
|
||||||
|
#include "asterisk/stasis_channels.h"
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Parameter parsing callback for /events.
|
* \brief Parameter parsing callback for /events.
|
||||||
@@ -76,6 +77,524 @@ static struct stasis_rest_handlers events = {
|
|||||||
.children = { }
|
.children = { }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_channel_snapshot_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
|
||||||
|
event = ast_json_object_create();
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "channel_snapshot", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_channel_destroyed_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
struct ast_json *validator;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
ast_assert(blob != NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "channel") == NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "type") == NULL);
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "cause");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "cause_txt");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
event = ast_json_deep_copy(blob);
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "channel_destroyed", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_channel_caller_id_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
struct ast_json *validator;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
ast_assert(blob != NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "channel") == NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "type") == NULL);
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "caller_presentation_txt");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "caller_presentation");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
event = ast_json_deep_copy(blob);
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "channel_caller_id", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_channel_hangup_request_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
struct ast_json *validator;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
ast_assert(blob != NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "channel") == NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "type") == NULL);
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "soft");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
}
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "cause");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
}
|
||||||
|
|
||||||
|
event = ast_json_deep_copy(blob);
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "channel_hangup_request", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_application_replaced_create(
|
||||||
|
struct ast_json *blob
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
struct ast_json *validator;
|
||||||
|
|
||||||
|
ast_assert(blob != NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "type") == NULL);
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "application");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
event = ast_json_deep_copy(blob);
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "application_replaced", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_channel_varset_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
struct ast_json *validator;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
ast_assert(blob != NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "channel") == NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "type") == NULL);
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "variable");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "value");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
event = ast_json_deep_copy(blob);
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "channel_varset", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_channel_userevent_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
struct ast_json *validator;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
ast_assert(blob != NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "channel") == NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "type") == NULL);
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "eventname");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
event = ast_json_deep_copy(blob);
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "channel_userevent", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_channel_created_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
|
||||||
|
event = ast_json_object_create();
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "channel_created", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_stasis_start_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
struct ast_json *validator;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
ast_assert(blob != NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "channel") == NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "type") == NULL);
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "args");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
event = ast_json_deep_copy(blob);
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "stasis_start", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_channel_dialplan_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
struct ast_json *validator;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
ast_assert(blob != NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "channel") == NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "type") == NULL);
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "application");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "application_data");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
event = ast_json_deep_copy(blob);
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "channel_dialplan", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_channel_state_change_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
|
||||||
|
event = ast_json_object_create();
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "channel_state_change", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_channel_dtmf_received_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
struct ast_json *validator;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
ast_assert(blob != NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "channel") == NULL);
|
||||||
|
ast_assert(ast_json_object_get(blob, "type") == NULL);
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "digit");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
event = ast_json_deep_copy(blob);
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "channel_dtmf_received", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ast_json *stasis_json_event_stasis_end_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
|
||||||
|
event = ast_json_object_create();
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = ast_json_pack("{s: o}", "stasis_end", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
static int load_module(void)
|
static int load_module(void)
|
||||||
{
|
{
|
||||||
return stasis_http_add_handler(&events);
|
return stasis_http_add_handler(&events);
|
||||||
|
@@ -52,10 +52,6 @@ static struct ast_json *oom_json;
|
|||||||
*/
|
*/
|
||||||
#define APPS_NUM_BUCKETS 7
|
#define APPS_NUM_BUCKETS 7
|
||||||
|
|
||||||
struct websocket_app {
|
|
||||||
char *name;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \internal
|
* \internal
|
||||||
* \brief Helper to write a JSON object to a WebSocket.
|
* \brief Helper to write a JSON object to a WebSocket.
|
||||||
@@ -78,35 +74,6 @@ static int websocket_write_json(struct ast_websocket *session,
|
|||||||
strlen(str));
|
strlen(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Hash function for websocket_app */
|
|
||||||
static int hash_app(const void *obj, const int flags)
|
|
||||||
{
|
|
||||||
const struct websocket_app *app = obj;
|
|
||||||
const char *name = flags & OBJ_KEY ? obj : app->name;
|
|
||||||
|
|
||||||
return ast_str_hash(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Comparison function for websocket_app */
|
|
||||||
static int compare_app(void *lhs, void *rhs, int flags)
|
|
||||||
{
|
|
||||||
const struct websocket_app *lhs_app = lhs;
|
|
||||||
const struct websocket_app *rhs_app = rhs;
|
|
||||||
const char *rhs_name = flags & OBJ_KEY ? rhs : rhs_app->name;
|
|
||||||
|
|
||||||
if (strcmp(lhs_app->name, rhs_name) == 0) {
|
|
||||||
return CMP_MATCH;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void app_dtor(void *obj)
|
|
||||||
{
|
|
||||||
struct websocket_app *app = obj;
|
|
||||||
ast_free(app->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct stasis_ws_session_info {
|
struct stasis_ws_session_info {
|
||||||
struct ast_websocket *ws_session;
|
struct ast_websocket *ws_session;
|
||||||
struct ao2_container *websocket_apps;
|
struct ao2_container *websocket_apps;
|
||||||
@@ -132,7 +99,7 @@ static struct stasis_ws_session_info *session_create(
|
|||||||
|
|
||||||
session->ws_session = ws_session;
|
session->ws_session = ws_session;
|
||||||
session->websocket_apps =
|
session->websocket_apps =
|
||||||
ao2_container_alloc(APPS_NUM_BUCKETS, hash_app, compare_app);
|
ast_str_container_alloc(APPS_NUM_BUCKETS);
|
||||||
|
|
||||||
if (!session->websocket_apps) {
|
if (!session->websocket_apps) {
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -154,12 +121,12 @@ static struct stasis_ws_session_info *session_create(
|
|||||||
static void session_shutdown(struct stasis_ws_session_info *session)
|
static void session_shutdown(struct stasis_ws_session_info *session)
|
||||||
{
|
{
|
||||||
struct ao2_iterator i;
|
struct ao2_iterator i;
|
||||||
struct websocket_app *app;
|
char *app;
|
||||||
SCOPED_AO2LOCK(lock, session);
|
SCOPED_AO2LOCK(lock, session);
|
||||||
|
|
||||||
i = ao2_iterator_init(session->websocket_apps, 0);
|
i = ao2_iterator_init(session->websocket_apps, 0);
|
||||||
while ((app = ao2_iterator_next(&i))) {
|
while ((app = ao2_iterator_next(&i))) {
|
||||||
stasis_app_unregister(app->name);
|
stasis_app_unregister(app);
|
||||||
ao2_cleanup(app);
|
ao2_cleanup(app);
|
||||||
}
|
}
|
||||||
ao2_iterator_destroy(&i);
|
ao2_iterator_destroy(&i);
|
||||||
@@ -212,15 +179,10 @@ static int session_register_apps(struct stasis_ws_session_info *session,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
while ((app_name = strsep(&apps, ","))) {
|
while ((app_name = strsep(&apps, ","))) {
|
||||||
RAII_VAR(struct websocket_app *, app, NULL, ao2_cleanup);
|
if (ast_str_container_add(session->websocket_apps, app_name)) {
|
||||||
|
|
||||||
app = ao2_alloc(sizeof(*app), app_dtor);
|
|
||||||
if (!app) {
|
|
||||||
websocket_write_json(session->ws_session, oom_json);
|
websocket_write_json(session->ws_session, oom_json);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
app->name = ast_strdup(app_name);
|
|
||||||
ao2_link(session->websocket_apps, app);
|
|
||||||
|
|
||||||
stasis_app_register(app_name, app_handler, session);
|
stasis_app_register(app_name, app_handler, session);
|
||||||
}
|
}
|
||||||
|
@@ -83,8 +83,8 @@ void stasis_http_get_endpoint(struct ast_variable *headers, struct ast_get_endpo
|
|||||||
* JSON models
|
* JSON models
|
||||||
*
|
*
|
||||||
* Endpoint
|
* Endpoint
|
||||||
|
* - resource: string (required)
|
||||||
* - technology: string (required)
|
* - technology: string (required)
|
||||||
* - name: string (required)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#endif /* _ASTERISK_RESOURCE_ENDPOINTS_H */
|
#endif /* _ASTERISK_RESOURCE_ENDPOINTS_H */
|
||||||
|
@@ -55,42 +55,240 @@ struct ast_event_websocket_args {
|
|||||||
*/
|
*/
|
||||||
void stasis_http_event_websocket(struct ast_variable *headers, struct ast_event_websocket_args *args, struct stasis_http_response *response);
|
void stasis_http_event_websocket(struct ast_variable *headers, struct ast_event_websocket_args *args, struct stasis_http_response *response);
|
||||||
|
|
||||||
|
struct ast_channel_snapshot;
|
||||||
|
struct ast_bridge_snapshot;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Some part of channel state changed.
|
||||||
|
*
|
||||||
|
* \param channel The channel to be used to generate this event
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_channel_snapshot_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Notification that a channel has been destroyed.
|
||||||
|
*
|
||||||
|
* \param channel The channel to be used to generate this event
|
||||||
|
* \param blob JSON blob containing the following parameters:
|
||||||
|
* - cause: integer - Integer representation of the cause of the hangup (required)
|
||||||
|
* - cause_txt: string - Text representation of the cause of the hangup (required)
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_channel_destroyed_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Channel changed Caller ID.
|
||||||
|
*
|
||||||
|
* \param channel The channel that changed Caller ID.
|
||||||
|
* \param blob JSON blob containing the following parameters:
|
||||||
|
* - caller_presentation_txt: string - The text representation of the Caller Presentation value. (required)
|
||||||
|
* - caller_presentation: integer - The integer representation of the Caller Presentation value. (required)
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_channel_caller_id_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief A hangup was requested on the channel.
|
||||||
|
*
|
||||||
|
* \param channel The channel on which the hangup was requested.
|
||||||
|
* \param blob JSON blob containing the following parameters:
|
||||||
|
* - soft: boolean - Whether the hangup request was a soft hangup request.
|
||||||
|
* - cause: integer - Integer representation of the cause of the hangup.
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_channel_hangup_request_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Notification that another WebSocket has taken over for an application.
|
||||||
|
*
|
||||||
|
* \param blob JSON blob containing the following parameters:
|
||||||
|
* - application: string (required)
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_application_replaced_create(
|
||||||
|
struct ast_json *blob
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Channel variable changed.
|
||||||
|
*
|
||||||
|
* \param channel The channel on which the variable was set.
|
||||||
|
* \param blob JSON blob containing the following parameters:
|
||||||
|
* - variable: string - The variable that changed. (required)
|
||||||
|
* - value: string - The new value of the variable. (required)
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_channel_varset_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief User-generated event with additional user-defined fields in the object.
|
||||||
|
*
|
||||||
|
* \param channel The channel that signaled the user event.
|
||||||
|
* \param blob JSON blob containing the following parameters:
|
||||||
|
* - eventname: string - The name of the user event. (required)
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_channel_userevent_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Notification that a channel has been created.
|
||||||
|
*
|
||||||
|
* \param channel The channel to be used to generate this event
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_channel_created_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Notification that a channel has entered a Stasis appliction.
|
||||||
|
*
|
||||||
|
* \param channel The channel to be used to generate this event
|
||||||
|
* \param blob JSON blob containing the following parameters:
|
||||||
|
* - args: List[string] - Arguments to the application (required)
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_stasis_start_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Channel changed location in the dialplan.
|
||||||
|
*
|
||||||
|
* \param channel The channel that changed dialplan location.
|
||||||
|
* \param blob JSON blob containing the following parameters:
|
||||||
|
* - application: string - The application that the channel is currently in. (required)
|
||||||
|
* - application_data: string - The data that was passed to the application when it was invoked. (required)
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_channel_dialplan_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Notification of a channel's state change.
|
||||||
|
*
|
||||||
|
* \param channel The channel to be used to generate this event
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_channel_state_change_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief DTMF received on a channel.
|
||||||
|
*
|
||||||
|
* \param channel The channel on which DTMF was received
|
||||||
|
* \param blob JSON blob containing the following parameters:
|
||||||
|
* - digit: string - DTMF digit received (0-9, A-E, # or *) (required)
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_channel_dtmf_received_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot,
|
||||||
|
struct ast_json *blob
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Notification that a channel has left a Stasis appliction.
|
||||||
|
*
|
||||||
|
* \param channel The channel to be used to generate this event
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
struct ast_json *stasis_json_event_stasis_end_create(
|
||||||
|
struct ast_channel_snapshot *channel_snapshot
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* JSON models
|
* JSON models
|
||||||
*
|
*
|
||||||
* DtmfReceived
|
* ChannelSnapshot
|
||||||
* - digit: string
|
* ChannelDestroyed
|
||||||
* - channel: Channel
|
* - cause: integer (required)
|
||||||
* BridgeCreated
|
* - cause_txt: string (required)
|
||||||
* - bridge: Bridge
|
* ChannelCallerId
|
||||||
* BridgeDestroyed
|
* - caller_presentation_txt: string (required)
|
||||||
* - bridge: Bridge
|
* - caller_presentation: integer (required)
|
||||||
|
* ChannelHangupRequest
|
||||||
|
* - soft: boolean
|
||||||
|
* - cause: integer
|
||||||
* ApplicationReplaced
|
* ApplicationReplaced
|
||||||
* - application: string
|
* - application: string (required)
|
||||||
* ChannelLeftBridge
|
* ChannelVarset
|
||||||
* - bridge: Bridge
|
* - variable: string (required)
|
||||||
* - channel: Channel
|
* - value: string (required)
|
||||||
|
* ChannelUserevent
|
||||||
|
* - eventname: string (required)
|
||||||
|
* ChannelCreated
|
||||||
* StasisStart
|
* StasisStart
|
||||||
* - args: List[string]
|
* - args: List[string] (required)
|
||||||
* - channel_info: Channel
|
* ChannelDialplan
|
||||||
* StasisEnd
|
* - application: string (required)
|
||||||
* - channel_info: Channel
|
* - application_data: string (required)
|
||||||
* ChannelStateChange
|
* ChannelStateChange
|
||||||
* - channel_info: Channel
|
* ChannelDtmfReceived
|
||||||
* ChannelEnteredBridge
|
* - digit: string (required)
|
||||||
* - bridge: Bridge
|
|
||||||
* - channel: Channel
|
|
||||||
* Event
|
* Event
|
||||||
* - stasis_start: StasisStart
|
* - channel_created: ChannelCreated
|
||||||
* - channel_entered_bridge: ChannelEnteredBridge
|
* - channel_destroyed: ChannelDestroyed
|
||||||
* - channel_left_bridge: ChannelLeftBridge
|
* - channel_dialplan: ChannelDialplan
|
||||||
|
* - channel_varset: ChannelVarset
|
||||||
* - application_replaced: ApplicationReplaced
|
* - application_replaced: ApplicationReplaced
|
||||||
* - channel_state_change: ChannelStateChange
|
* - channel_state_change: ChannelStateChange
|
||||||
* - bridge_created: BridgeCreated
|
* - stasis_start: StasisStart
|
||||||
* - application: string (required)
|
* - application: string (required)
|
||||||
|
* - channel_hangup_request: ChannelHangupRequest
|
||||||
|
* - channel_userevent: ChannelUserevent
|
||||||
|
* - channel_snapshot: ChannelSnapshot
|
||||||
|
* - channel_dtmf_received: ChannelDtmfReceived
|
||||||
|
* - channel_caller_id: ChannelCallerId
|
||||||
* - stasis_end: StasisEnd
|
* - stasis_end: StasisEnd
|
||||||
* - dtmf_received: DtmfReceived
|
* StasisEnd
|
||||||
* - bridge_destroyed: BridgeDestroyed
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#endif /* _ASTERISK_RESOURCE_EVENTS_H */
|
#endif /* _ASTERISK_RESOURCE_EVENTS_H */
|
||||||
|
@@ -107,7 +107,6 @@ class PathSegment(Stringify):
|
|||||||
"""
|
"""
|
||||||
return len(self.__children)
|
return len(self.__children)
|
||||||
|
|
||||||
|
|
||||||
class AsteriskProcessor(SwaggerPostProcessor):
|
class AsteriskProcessor(SwaggerPostProcessor):
|
||||||
"""A SwaggerPostProcessor which adds fields needed to generate Asterisk
|
"""A SwaggerPostProcessor which adds fields needed to generate Asterisk
|
||||||
RESTful HTTP binding code.
|
RESTful HTTP binding code.
|
||||||
@@ -145,6 +144,18 @@ class AsteriskProcessor(SwaggerPostProcessor):
|
|||||||
segment = resource_api.root_path.get_child(api.path.split('/'))
|
segment = resource_api.root_path.get_child(api.path.split('/'))
|
||||||
for operation in api.operations:
|
for operation in api.operations:
|
||||||
segment.operations.append(operation)
|
segment.operations.append(operation)
|
||||||
|
resource_api.api_declaration.has_events = False
|
||||||
|
for model in resource_api.api_declaration.models:
|
||||||
|
if model.id == "Event":
|
||||||
|
resource_api.api_declaration.has_events = True
|
||||||
|
break
|
||||||
|
if resource_api.api_declaration.has_events:
|
||||||
|
resource_api.api_declaration.events = \
|
||||||
|
[self.process_model(model, context) for model in \
|
||||||
|
resource_api.api_declaration.models if model.id != "Event"]
|
||||||
|
else:
|
||||||
|
resource_api.api_declaration.events = []
|
||||||
|
|
||||||
# Since every API path should start with /[resource], root should
|
# Since every API path should start with /[resource], root should
|
||||||
# have exactly one child.
|
# have exactly one child.
|
||||||
if resource_api.root_path.num_children() != 1:
|
if resource_api.root_path.num_children() != 1:
|
||||||
@@ -177,3 +188,43 @@ class AsteriskProcessor(SwaggerPostProcessor):
|
|||||||
parameter.c_space = ''
|
parameter.c_space = ''
|
||||||
else:
|
else:
|
||||||
parameter.c_space = ' '
|
parameter.c_space = ' '
|
||||||
|
|
||||||
|
def process_model(self, model, context):
|
||||||
|
model.c_id = snakify(model.id)
|
||||||
|
model.channel = False
|
||||||
|
model.channel_desc = ""
|
||||||
|
model.bridge = False
|
||||||
|
model.bridge_desc = ""
|
||||||
|
model.properties = [self.process_property(model, prop, context) for prop in model.properties]
|
||||||
|
model.properties = [prop for prop in model.properties if prop]
|
||||||
|
model.has_properties = (len(model.properties) != 0)
|
||||||
|
return model
|
||||||
|
|
||||||
|
def process_property(self, model, prop, context):
|
||||||
|
# process channel separately since it will be pulled out
|
||||||
|
if prop.name == 'channel' and prop.type == 'Channel':
|
||||||
|
model.channel = True
|
||||||
|
model.channel_desc = prop.description or ""
|
||||||
|
return None
|
||||||
|
|
||||||
|
# process bridge separately since it will be pulled out
|
||||||
|
if prop.name == 'bridge' and prop.type == 'Bridge':
|
||||||
|
model.bridge = True
|
||||||
|
model.bridge_desc = prop.description or ""
|
||||||
|
return None
|
||||||
|
|
||||||
|
prop.c_name = snakify(prop.name)
|
||||||
|
if prop.type in self.type_mapping:
|
||||||
|
prop.c_type = self.type_mapping[prop.type]
|
||||||
|
prop.c_convert = self.convert_mapping[prop.c_type]
|
||||||
|
else:
|
||||||
|
prop.c_type = "Property type %s not mappable to a C type" % (prop.type)
|
||||||
|
prop.c_convert = "Property type %s not mappable to a C conversion" % (prop.type)
|
||||||
|
#raise SwaggerError(
|
||||||
|
# "Invalid property type %s" % prop.type, context)
|
||||||
|
# You shouldn't put a space between 'char *' and the variable
|
||||||
|
if prop.c_type.endswith('*'):
|
||||||
|
prop.c_space = ''
|
||||||
|
else:
|
||||||
|
prop.c_space = ' '
|
||||||
|
return prop
|
||||||
|
10
rest-api-templates/event_function_decl.mustache
Normal file
10
rest-api-templates/event_function_decl.mustache
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
struct ast_json *stasis_json_event_{{c_id}}_create(
|
||||||
|
{{#bridge}}
|
||||||
|
struct ast_bridge_snapshot *bridge_snapshot{{#channel}},{{/channel}}{{^channel}}{{#has_properties}},{{/has_properties}}{{/channel}}
|
||||||
|
{{/bridge}}
|
||||||
|
{{#channel}}
|
||||||
|
struct ast_channel_snapshot *channel_snapshot{{#has_properties}},{{/has_properties}}
|
||||||
|
{{/channel}}
|
||||||
|
{{#has_properties}}
|
||||||
|
struct ast_json *blob
|
||||||
|
{{/has_properties}}
|
@@ -47,6 +47,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
|||||||
|
|
||||||
#include "asterisk/module.h"
|
#include "asterisk/module.h"
|
||||||
#include "stasis_http/resource_{{name}}.h"
|
#include "stasis_http/resource_{{name}}.h"
|
||||||
|
{{#has_events}}
|
||||||
|
#include "asterisk/stasis_channels.h"
|
||||||
|
{{/has_events}}
|
||||||
|
|
||||||
{{#apis}}
|
{{#apis}}
|
||||||
{{#operations}}
|
{{#operations}}
|
||||||
@@ -96,6 +99,89 @@ static void stasis_http_{{c_nickname}}_cb(
|
|||||||
{{> rest_handler}}
|
{{> rest_handler}}
|
||||||
{{/root_path}}
|
{{/root_path}}
|
||||||
|
|
||||||
|
{{#has_events}}
|
||||||
|
{{#events}}
|
||||||
|
{{> event_function_decl}}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RAII_VAR(struct ast_json *, message, NULL, ast_json_unref);
|
||||||
|
RAII_VAR(struct ast_json *, event, NULL, ast_json_unref);
|
||||||
|
{{#has_properties}}
|
||||||
|
struct ast_json *validator;
|
||||||
|
{{/has_properties}}
|
||||||
|
{{#channel}}
|
||||||
|
int ret;
|
||||||
|
{{/channel}}
|
||||||
|
{{#bridge}}
|
||||||
|
{{^channel}}
|
||||||
|
int ret;
|
||||||
|
{{/channel}}
|
||||||
|
{{/bridge}}
|
||||||
|
|
||||||
|
{{#channel}}
|
||||||
|
ast_assert(channel_snapshot != NULL);
|
||||||
|
{{/channel}}
|
||||||
|
{{#bridge}}
|
||||||
|
ast_assert(bridge_snapshot != NULL);
|
||||||
|
{{/bridge}}
|
||||||
|
{{#has_properties}}
|
||||||
|
ast_assert(blob != NULL);
|
||||||
|
{{#channel}}
|
||||||
|
ast_assert(ast_json_object_get(blob, "channel") == NULL);
|
||||||
|
{{/channel}}
|
||||||
|
{{#bridge}}
|
||||||
|
ast_assert(ast_json_object_get(blob, "bridge") == NULL);
|
||||||
|
{{/bridge}}
|
||||||
|
ast_assert(ast_json_object_get(blob, "type") == NULL);
|
||||||
|
{{#properties}}
|
||||||
|
|
||||||
|
validator = ast_json_object_get(blob, "{{name}}");
|
||||||
|
if (validator) {
|
||||||
|
/* do validation? XXX */
|
||||||
|
{{#required}}
|
||||||
|
} else {
|
||||||
|
/* fail message generation if the required parameter doesn't exist */
|
||||||
|
return NULL;
|
||||||
|
{{/required}}
|
||||||
|
}
|
||||||
|
{{/properties}}
|
||||||
|
|
||||||
|
event = ast_json_deep_copy(blob);
|
||||||
|
{{/has_properties}}
|
||||||
|
{{^has_properties}}
|
||||||
|
|
||||||
|
event = ast_json_object_create();
|
||||||
|
{{/has_properties}}
|
||||||
|
if (!event) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
{{#channel}}
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"channel", ast_channel_snapshot_to_json(channel_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/channel}}
|
||||||
|
{{#bridge}}
|
||||||
|
ret = ast_json_object_set(event,
|
||||||
|
"bridge", ast_bridge_snapshot_to_json(bridge_snapshot));
|
||||||
|
if (ret) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/bridge}}
|
||||||
|
message = ast_json_pack("{s: o}", "{{c_id}}", ast_json_ref(event));
|
||||||
|
if (!message) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ast_json_ref(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
{{/events}}
|
||||||
|
{{/has_events}}
|
||||||
static int load_module(void)
|
static int load_module(void)
|
||||||
{
|
{
|
||||||
return stasis_http_add_handler(&{{root_full_name}});
|
return stasis_http_add_handler(&{{root_full_name}});
|
||||||
|
@@ -64,16 +64,48 @@ void stasis_http_{{c_nickname}}(struct ast_variable *headers, struct ast_{{c_nic
|
|||||||
{{/operations}}
|
{{/operations}}
|
||||||
{{/apis}}
|
{{/apis}}
|
||||||
|
|
||||||
|
{{#has_events}}
|
||||||
|
struct ast_channel_snapshot;
|
||||||
|
struct ast_bridge_snapshot;
|
||||||
|
|
||||||
|
{{#events}}
|
||||||
|
/*!
|
||||||
|
* \brief {{description}}
|
||||||
|
{{#notes}}
|
||||||
|
*
|
||||||
|
* {{{notes}}}
|
||||||
|
{{/notes}}
|
||||||
|
*
|
||||||
|
{{#channel}}
|
||||||
|
* \param channel {{#channel_desc}}{{channel_desc}}{{/channel_desc}}{{^channel_desc}}The channel to be used to generate this event{{/channel_desc}}
|
||||||
|
{{/channel}}
|
||||||
|
{{#bridge}}
|
||||||
|
* \param bridge {{#bridge_desc}}{{bridge_desc}}{{/bridge_desc}}{{^bridge_desc}}The bridge to be used to generate this event{{/bridge_desc}}
|
||||||
|
{{/bridge}}
|
||||||
|
{{#has_properties}}
|
||||||
|
* \param blob JSON blob containing the following parameters:
|
||||||
|
{{/has_properties}}
|
||||||
|
{{#properties}}
|
||||||
|
* - {{name}}: {{type}} {{#description}}- {{description}}{{/description}}{{#required}} (required){{/required}}
|
||||||
|
{{/properties}}
|
||||||
|
*
|
||||||
|
* \retval NULL on error
|
||||||
|
* \retval JSON (ast_json) describing the event
|
||||||
|
*/
|
||||||
|
{{> event_function_decl}}
|
||||||
|
);
|
||||||
|
|
||||||
|
{{/events}}
|
||||||
|
{{/has_events}}
|
||||||
/*
|
/*
|
||||||
* JSON models
|
* JSON models
|
||||||
*
|
*
|
||||||
{{#models}}
|
{{#models}}
|
||||||
* {{id}}
|
* {{id}}
|
||||||
{{#properties}}
|
{{#properties}}
|
||||||
* - {{name}}: {{type}} {{#required}}(required){{/required}}
|
* - {{name}}: {{type}}{{#required}} (required){{/required}}
|
||||||
{{/properties}}
|
{{/properties}}
|
||||||
{{/models}}
|
{{/models}} */
|
||||||
*/
|
|
||||||
|
|
||||||
#endif /* _ASTERISK_RESOURCE_{{name_caps}}_H */
|
#endif /* _ASTERISK_RESOURCE_{{name_caps}}_H */
|
||||||
{{/api_declaration}}
|
{{/api_declaration}}
|
||||||
|
@@ -295,13 +295,17 @@ class Model(Stringify):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.id = None
|
self.id = None
|
||||||
|
self.notes = None
|
||||||
|
self.description = None
|
||||||
self.properties = None
|
self.properties = None
|
||||||
|
|
||||||
def load(self, id, model_json, processor, context):
|
def load(self, id, model_json, processor, context):
|
||||||
context = add_context(context, model_json, 'id')
|
context = add_context(context, model_json, 'id')
|
||||||
|
# This arrangement is required by the Swagger API spec
|
||||||
self.id = model_json.get('id')
|
self.id = model_json.get('id')
|
||||||
if id != self.id:
|
if id != self.id:
|
||||||
raise SwaggerError("Model id doesn't match name", c)
|
raise SwaggerError("Model id doesn't match name", c)
|
||||||
|
self.description = model_json.get('description')
|
||||||
props = model_json.get('properties').items() or []
|
props = model_json.get('properties').items() or []
|
||||||
self.properties = [
|
self.properties = [
|
||||||
Property(k).load(j, processor, context) for (k, j) in props]
|
Property(k).load(j, processor, context) for (k, j) in props]
|
||||||
|
@@ -54,12 +54,16 @@
|
|||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"application_replaced": { "type": "ApplicationReplaced" },
|
"application_replaced": { "type": "ApplicationReplaced" },
|
||||||
"bridge_created": { "type": "BridgeCreated" },
|
"channel_created": { "type": "ChannelCreated" },
|
||||||
"bridge_destroyed": { "type": "BridgeDestroyed" },
|
"channel_destroyed": { "type": "ChannelDestroyed" },
|
||||||
"channel_entered_bridge": { "type": "ChannelEnteredBridge" },
|
"channel_snapshot": { "type": "ChannelSnapshot" },
|
||||||
"channel_left_bridge": { "type": "ChannelLeftBridge" },
|
|
||||||
"channel_state_change": { "type": "ChannelStateChange" },
|
"channel_state_change": { "type": "ChannelStateChange" },
|
||||||
"dtmf_received": { "type": "DtmfReceived" },
|
"channel_dtmf_received": { "type": "ChannelDtmfReceived" },
|
||||||
|
"channel_dialplan": { "type": "ChannelDialplan" },
|
||||||
|
"channel_caller_id": { "type": "ChannelCallerId" },
|
||||||
|
"channel_userevent": { "type": "ChannelUserevent" },
|
||||||
|
"channel_hangup_request": { "type": "ChannelHangupRequest" },
|
||||||
|
"channel_varset": { "type": "ChannelVarset" },
|
||||||
"stasis_end": { "type": "StasisEnd" },
|
"stasis_end": { "type": "StasisEnd" },
|
||||||
"stasis_start": { "type": "StasisStart" }
|
"stasis_start": { "type": "StasisStart" }
|
||||||
}
|
}
|
||||||
@@ -70,48 +74,47 @@
|
|||||||
"notes": "An application may only be subscribed to by a single WebSocket at a time. If multiple WebSockets attempt to subscribe to the same application, the newer WebSocket wins, and the older one receives this event.",
|
"notes": "An application may only be subscribed to by a single WebSocket at a time. If multiple WebSockets attempt to subscribe to the same application, the newer WebSocket wins, and the older one receives this event.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"application": {
|
"application": {
|
||||||
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"BridgeCreated": {
|
"ChannelCreated": {
|
||||||
"id": "BridgeCreated",
|
"id": "ChannelCreated",
|
||||||
"description": "Notification that a bridge has been created.",
|
"description": "Notification that a channel has been created.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"bridge": {
|
|
||||||
"type": "Bridge"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"BridgeDestroyed": {
|
|
||||||
"id": "BridgeDestroyed",
|
|
||||||
"description": "Notification that a bridge has been destroyed.",
|
|
||||||
"properties": {
|
|
||||||
"bridge": {
|
|
||||||
"type": "Bridge"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ChannelEnteredBridge": {
|
|
||||||
"id": "ChannelEnteredBridge",
|
|
||||||
"description": "Notification that a channel has entered a bridge.",
|
|
||||||
"properties": {
|
|
||||||
"bridge": {
|
|
||||||
"type": "Bridge"
|
|
||||||
},
|
|
||||||
"channel": {
|
"channel": {
|
||||||
|
"required": true,
|
||||||
"type": "Channel"
|
"type": "Channel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ChannelLeftBridge": {
|
"ChannelSnapshot": {
|
||||||
"id": "ChannelLeftBridge",
|
"id": "ChannelSnapshot",
|
||||||
"description": "Notification that a channel has left a bridge.",
|
"description": "Some part of channel state changed.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"bridge": {
|
"channel": {
|
||||||
"type": "Bridge"
|
"required": true,
|
||||||
|
"type": "Channel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ChannelDestroyed": {
|
||||||
|
"id": "ChannelDestroyed",
|
||||||
|
"description": "Notification that a channel has been destroyed.",
|
||||||
|
"properties": {
|
||||||
|
"cause": {
|
||||||
|
"required": true,
|
||||||
|
"description": "Integer representation of the cause of the hangup",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"cause_txt": {
|
||||||
|
"required": true,
|
||||||
|
"description": "Text representation of the cause of the hangup",
|
||||||
|
"type": "string"
|
||||||
},
|
},
|
||||||
"channel": {
|
"channel": {
|
||||||
|
"required": true,
|
||||||
"type": "Channel"
|
"type": "Channel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,31 +123,133 @@
|
|||||||
"id": "ChannelStateChange",
|
"id": "ChannelStateChange",
|
||||||
"description": "Notification of a channel's state change.",
|
"description": "Notification of a channel's state change.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"channel_info": {
|
"channel": {
|
||||||
|
"required": true,
|
||||||
"type": "Channel"
|
"type": "Channel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"DtmfReceived": {
|
"ChannelDtmfReceived": {
|
||||||
"id": "DtmfReceived",
|
"id": "ChannelDtmfReceived",
|
||||||
"description": "DTMF received on a channel.",
|
"description": "DTMF received on a channel.",
|
||||||
"notes": "This event is sent when the DTMF ends. There is no notification about the start of DTMF",
|
"notes": "This event is sent when the DTMF ends. There is no notification about the start of DTMF",
|
||||||
"properties": {
|
"properties": {
|
||||||
"digit": {
|
"digit": {
|
||||||
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "DTMF digit received (0-9, A-E, # or *)"
|
"description": "DTMF digit received (0-9, A-E, # or *)"
|
||||||
},
|
},
|
||||||
"channel": {
|
"channel": {
|
||||||
|
"required": true,
|
||||||
"type": "Channel",
|
"type": "Channel",
|
||||||
"description": "The channel on which DTMF was received"
|
"description": "The channel on which DTMF was received"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ChannelDialplan": {
|
||||||
|
"id": "ChannelDialplan",
|
||||||
|
"description": "Channel changed location in the dialplan.",
|
||||||
|
"properties": {
|
||||||
|
"application": {
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"description": "The application that the channel is currently in."
|
||||||
|
},
|
||||||
|
"application_data": {
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"description": "The data that was passed to the application when it was invoked."
|
||||||
|
},
|
||||||
|
"channel": {
|
||||||
|
"required": true,
|
||||||
|
"type": "Channel",
|
||||||
|
"description": "The channel that changed dialplan location."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ChannelCallerId": {
|
||||||
|
"id": "ChannelCallerId",
|
||||||
|
"description": "Channel changed Caller ID.",
|
||||||
|
"properties": {
|
||||||
|
"caller_presentation": {
|
||||||
|
"required": true,
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The integer representation of the Caller Presentation value."
|
||||||
|
},
|
||||||
|
"caller_presentation_txt": {
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"description": "The text representation of the Caller Presentation value."
|
||||||
|
},
|
||||||
|
"channel": {
|
||||||
|
"required": true,
|
||||||
|
"type": "Channel",
|
||||||
|
"description": "The channel that changed Caller ID."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ChannelUserevent": {
|
||||||
|
"id": "ChannelUserevent",
|
||||||
|
"description": "User-generated event with additional user-defined fields in the object.",
|
||||||
|
"properties": {
|
||||||
|
"eventname": {
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the user event."
|
||||||
|
},
|
||||||
|
"channel": {
|
||||||
|
"required": true,
|
||||||
|
"type": "Channel",
|
||||||
|
"description": "The channel that signaled the user event."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ChannelHangupRequest": {
|
||||||
|
"id": "ChannelHangupRequest",
|
||||||
|
"description": "A hangup was requested on the channel.",
|
||||||
|
"properties": {
|
||||||
|
"cause": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Integer representation of the cause of the hangup."
|
||||||
|
},
|
||||||
|
"soft": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the hangup request was a soft hangup request."
|
||||||
|
},
|
||||||
|
"channel": {
|
||||||
|
"required": true,
|
||||||
|
"type": "Channel",
|
||||||
|
"description": "The channel on which the hangup was requested."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ChannelVarset": {
|
||||||
|
"id": "ChannelVarset",
|
||||||
|
"description": "Channel variable changed.",
|
||||||
|
"properties": {
|
||||||
|
"variable": {
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"description": "The variable that changed."
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"description": "The new value of the variable."
|
||||||
|
},
|
||||||
|
"channel": {
|
||||||
|
"required": true,
|
||||||
|
"type": "Channel",
|
||||||
|
"description": "The channel on which the variable was set."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"StasisEnd": {
|
"StasisEnd": {
|
||||||
"id": "StasisEnd",
|
"id": "StasisEnd",
|
||||||
"description": "Notification that a channel has left a Stasis appliction.",
|
"description": "Notification that a channel has left a Stasis appliction.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"channel_info": {
|
"channel": {
|
||||||
|
"required": true,
|
||||||
"type": "Channel"
|
"type": "Channel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,10 +259,12 @@
|
|||||||
"description": "Notification that a channel has entered a Stasis appliction.",
|
"description": "Notification that a channel has entered a Stasis appliction.",
|
||||||
"properties": {
|
"properties": {
|
||||||
"args": {
|
"args": {
|
||||||
|
"required": true,
|
||||||
"type": "List[string]",
|
"type": "List[string]",
|
||||||
"description": "Arguments to the application"
|
"description": "Arguments to the application"
|
||||||
},
|
},
|
||||||
"channel_info": {
|
"channel": {
|
||||||
|
"required": true,
|
||||||
"type": "Channel"
|
"type": "Channel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user