This patch adds a new message bus API to Asterisk.
[asterisk/asterisk.git] / main / manager.c
index fc0ec26..10a3a33 100644 (file)
@@ -91,6 +91,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/aoc.h"
 #include "asterisk/stringfields.h"
 #include "asterisk/presencestate.h"
+#include "asterisk/stasis.h"
 
 /*** DOCUMENTATION
        <manager name="Ping" language="en_US">
@@ -963,6 +964,73 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                         manager.conf will be present upon starting a new session.</para>
                </description>
        </manager>
+       <managerEvent language="en_US" name="Newchannel">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a new channel is created.</synopsis>
+                       <syntax>
+                               <parameter name="Channel">
+                               </parameter>
+                               <parameter name="ChannelState">
+                                       <para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
+                               </parameter>
+                               <parameter name="ChannelStateDesc">
+                                       <enumlist>
+                                               <enum name="Down"/>
+                                               <enum name="Rsrvd"/>
+                                               <enum name="OffHook"/>
+                                               <enum name="Dialing"/>
+                                               <enum name="Ring"/>
+                                               <enum name="Ringing"/>
+                                               <enum name="Up"/>
+                                               <enum name="Busy"/>
+                                               <enum name="Dialing Offhook"/>
+                                               <enum name="Pre-ring"/>
+                                               <enum name="Unknown"/>
+                                       </enumlist>
+                               </parameter>
+                               <parameter name="CallerIDNum">
+                               </parameter>
+                               <parameter name="CallerIDName">
+                               </parameter>
+                               <parameter name="ConnectedLineNum">
+                               </parameter>
+                               <parameter name="ConnectedLineName">
+                               </parameter>
+                               <parameter name="AccountCode">
+                               </parameter>
+                               <parameter name="Context">
+                               </parameter>
+                               <parameter name="Exten">
+                               </parameter>
+                               <parameter name="Priority">
+                               </parameter>
+                               <parameter name="Uniqueid">
+                               </parameter>
+                               <parameter name="Cause">
+                                       <para>A numeric cause code for why the channel was hung up.</para>
+                               </parameter>
+                               <parameter name="Cause-txt">
+                                       <para>A description of why the channel was hung up.</para>
+                               </parameter>
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="Newstate">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a channel's state changes.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="Hangup">
+               <managerEventInstance class="EVENT_FLAG_CALL">
+                       <synopsis>Raised when a channel is hung up.</synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" />
+                       </syntax>
+               </managerEventInstance>
+       </managerEvent>
  ***/
 
 /*! \addtogroup Group_AMI AMI functions
@@ -1060,6 +1128,8 @@ static const struct {
        {{ "restart", "gracefully", NULL }},
 };
 
+static struct stasis_subscription *channel_state_sub;
+
 static void acl_change_event_cb(const struct ast_event *event, void *userdata);
 
 static void acl_change_event_subscribe(void)
@@ -7376,6 +7446,127 @@ static void load_channelvars(struct ast_variable *var)
        AST_RWLIST_UNLOCK(&channelvars);
 }
 
+/*!
+ * \brief Generate the AMI message body from a channel snapshot
+ * \internal
+ *
+ * \param snapshot the channel snapshot for which to generate an AMI message body
+ *
+ * \retval NULL on error
+ * \retval ast_str* on success (must be ast_freed by caller)
+ */
+static struct ast_str *manager_build_channel_state_string(const struct ast_channel_snapshot *snapshot)
+{
+       struct ast_str *out = ast_str_create(1024);
+       int res = 0;
+       if (!out) {
+               return NULL;
+       }
+       res = ast_str_set(&out, 0,
+               "Channel: %s\r\n"
+               "ChannelState: %d\r\n"
+               "ChannelStateDesc: %s\r\n"
+               "CallerIDNum: %s\r\n"
+               "CallerIDName: %s\r\n"
+               "ConnectedLineNum: %s\r\n"
+               "ConnectedLineName: %s\r\n"
+               "AccountCode: %s\r\n"
+               "Context: %s\r\n"
+               "Exten: %s\r\n"
+               "Priority: %d\r\n"
+               "Uniqueid: %s\r\n"
+               "Cause: %d\r\n"
+               "Cause-txt: %s\r\n",
+               snapshot->name,
+               snapshot->state,
+               ast_state2str(snapshot->state),
+               snapshot->caller_number,
+               snapshot->caller_name,
+               snapshot->connected_number,
+               snapshot->connected_name,
+               snapshot->accountcode,
+               snapshot->context,
+               snapshot->exten,
+               snapshot->priority,
+               snapshot->uniqueid,
+               snapshot->hangupcause,
+               ast_cause2str(snapshot->hangupcause));
+
+       if (!res) {
+               return NULL;
+       }
+
+       return out;
+}
+
+static void channel_snapshot_update(struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot)
+{
+       int is_hungup;
+       char *manager_event = NULL;
+
+       if (!new_snapshot) {
+               /* Ignore cache clearing events; we'll see the hangup first */
+               return;
+       }
+
+       is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0;
+
+       if (!old_snapshot) {
+               manager_event = "Newchannel";
+       }
+
+       if (old_snapshot && old_snapshot->state != new_snapshot->state) {
+               manager_event = "Newstate";
+       }
+
+       if (old_snapshot && is_hungup) {
+               manager_event = "Hangup";
+       }
+
+       if (manager_event) {
+               RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
+
+               channel_event_string = manager_build_channel_state_string(new_snapshot);
+               if (channel_event_string) {
+                       manager_event(EVENT_FLAG_CALL, manager_event, "%s", ast_str_buffer(channel_event_string));
+               }
+       }
+}
+
+static void channel_varset(const char *channel_name, const char *uniqueid, const char *name, const char *value)
+{
+       /*** DOCUMENTATION
+               <managerEventInstance>
+                       <synopsis>Raised when a variable is set to a particular value.</synopsis>
+               </managerEventInstance>
+       ***/
+       manager_event(EVENT_FLAG_DIALPLAN, "VarSet",
+                     "Channel: %s\r\n"
+                     "Variable: %s\r\n"
+                     "Value: %s\r\n"
+                     "Uniqueid: %s\r\n",
+                     channel_name, name, value, uniqueid);
+}
+
+static void channel_event_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
+{
+       if (stasis_message_type(message) == stasis_cache_update()) {
+               struct stasis_cache_update *update = stasis_message_data(message);
+               if (ast_channel_snapshot() == update->type) {
+                       struct ast_channel_snapshot *old_snapshot =
+                               stasis_message_data(update->old_snapshot);
+                       struct ast_channel_snapshot *new_snapshot =
+                               stasis_message_data(update->new_snapshot);
+                       channel_snapshot_update(old_snapshot, new_snapshot);
+               }
+       } else if (stasis_message_type(message) == ast_channel_varset()) {
+               struct ast_channel_varset *varset = stasis_message_data(message);
+               const char *name = varset->snapshot ? varset->snapshot->name : "none";
+               const char *uniqueid = varset->snapshot ? varset->snapshot->uniqueid : "none";
+               channel_varset(name, uniqueid, varset->variable, varset->value);
+       }
+}
+
 /*! \internal \brief Free a user record.  Should already be removed from the list */
 static void manager_free_user(struct ast_manager_user *user)
 {
@@ -7399,6 +7590,9 @@ static void manager_shutdown(void)
 {
        struct ast_manager_user *user;
 
+       stasis_unsubscribe(channel_state_sub);
+       channel_state_sub = NULL;
+
        if (registered) {
                ast_manager_unregister("Ping");
                ast_manager_unregister("Events");
@@ -7490,6 +7684,12 @@ static int __init_manager(int reload, int by_external_config)
 
        manager_enabled = 0;
 
+       if (!channel_state_sub) {
+               channel_state_sub = stasis_subscribe(
+                       stasis_caching_get_topic(ast_channel_topic_all_cached()),
+                       channel_event_cb, NULL);
+       }
+
        if (!registered) {
                /* Register default actions */
                ast_manager_register_xml_core("Ping", 0, action_ping);