Use the same delimited character as the FILTER function in FIELDQTY and CUT.
[asterisk/asterisk.git] / main / devicestate.c
index d0b9722..aef21f7 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Asterisk -- An open source telephony toolkit.
  *
- * Copyright (C) 1999 - 2006, Digium, Inc.
+ * Copyright (C) 1999 - 2007, Digium, Inc.
  *
  * Mark Spencer <markster@digium.com>
  *
@@ -25,6 +25,7 @@
  *
  *     \arg \ref AstExtState
  */
+
 /*! \page AstExtState Extension and device states in Asterisk
  *
  *     Asterisk has an internal system that reports states
@@ -48,7 +49,7 @@
  *     or just a specific extensions.
  *
  *     For non-device related states, there's an API called
- *     devicestateproviders. This is an extendable system for
+ *     devicestate providers. This is an extendible system for
  *     delivering state information from outside sources or
  *     functions within Asterisk. Currently we have providers
  *     for app_meetme.c - the conference bridge - and call
@@ -63,7 +64,7 @@
  *
  *     The CLI command "show hints" show last known state
  *
- *     \note None of these handle user states, like an IM presense
+ *     \note None of these handle user states, like an IM presence
  *     system. res_jabber.c can subscribe and watch such states
  *     in jabber/xmpp based systems.
  *
  *     and reported back.
  *
  *     - Extension states
- *             \arg \ref enum ast_extension_states
+ *             \arg \ref AstENUM ast_extension_states
  *             \arg \ref pbx.c 
  *             \arg \ref pbx.h 
  *     - Structures
- *             - \ref struct ast_state_cb  Callbacks for watchers
+ *             - \ref ast_state_cb struct.  Callbacks for watchers
  *             - Callback ast_state_cb_type
- *             - \ref struct ast_hint
+ *             - \ref ast_hint struct.
  *     - Functions
  *             - ast_extension_state_add()
  *             - ast_extension_state_del()
@@ -126,6 +127,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/pbx.h"
 #include "asterisk/app.h"
 #include "asterisk/options.h"
+#include "asterisk/event.h"
 
 /*! \brief Device state strings for printing */
 static const char *devstatestring[] = {
@@ -150,16 +152,6 @@ struct devstate_prov {
 /*! \brief A list of providers */
 static AST_RWLIST_HEAD_STATIC(devstate_provs, devstate_prov);
 
-/*! \brief  A device state watcher (callback) */
-struct devstate_cb {
-       void *data;
-       ast_devstate_cb_type callback;  /*!< Where to report when state changes */
-       AST_RWLIST_ENTRY(devstate_cb) list;
-};
-
-/*! \brief A device state watcher list */
-static AST_RWLIST_HEAD_STATIC(devstate_cbs, devstate_cb);
-
 struct state_change {
        AST_LIST_ENTRY(state_change) list;
        char device[1];
@@ -175,6 +167,17 @@ static pthread_t change_thread = AST_PTHREADT_NULL;
 /*! \brief Flag for the queue */
 static ast_cond_t change_pending;
 
+/*! \brief Whether or not to cache this device state value */
+enum devstate_cache {
+       /*! Cache this value as it is coming from a device state provider which is
+        *  pushing up state change events to us as they happen */
+       CACHE_ON,
+       /*! Don't cache this result, since it was pulled from the device state provider.
+        *  We only want to cache results from device state providers that are being nice
+        *  and pushing state change events up to us as they happen. */
+       CACHE_OFF,
+};
+
 /* Forward declarations */
 static int getproviderstate(const char *provider, const char *address);
 
@@ -270,18 +273,42 @@ enum ast_device_state ast_parse_device_state(const char *device)
        return res;
 }
 
+static enum ast_device_state devstate_cached(const char *device)
+{
+       enum ast_device_state res = AST_DEVICE_UNKNOWN;
+       struct ast_event *event;
+
+       event = ast_event_get_cached(AST_EVENT_DEVICE_STATE,
+               AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, device,
+               AST_EVENT_IE_END);
+
+       if (!event)
+               return res;
+
+       res = ast_event_get_ie_uint(event, AST_EVENT_IE_STATE);
+
+       ast_event_destroy(event);
+
+       return res;
+}
+
 /*! \brief Check device state through channel specific function or generic function */
 enum ast_device_state ast_device_state(const char *device)
 {
        char *buf;
        char *number;
        const struct ast_channel_tech *chan_tech;
-       enum ast_device_state res = AST_DEVICE_UNKNOWN;
+       enum ast_device_state res;
        /*! \brief Channel driver that provides device state */
        char *tech;
        /*! \brief Another provider of device state */
        char *provider = NULL;
-       
+
+       /* If the last known state is cached, just return that */
+       res = devstate_cached(device);
+       if (res != AST_DEVICE_UNKNOWN)
+               return res;
+
        buf = ast_strdupa(device);
        tech = strsep(&buf, "/");
        if (!(number = buf)) {
@@ -293,13 +320,11 @@ enum ast_device_state ast_device_state(const char *device)
        }
 
        if (provider)  {
-               if (option_debug > 2)
-                       ast_log(LOG_DEBUG, "Checking if I can find provider for \"%s\" - number: %s\n", provider, number);
+               ast_debug(3, "Checking if I can find provider for \"%s\" - number: %s\n", provider, number);
                return getproviderstate(provider, number);
        }
 
-       if (option_debug > 3)
-               ast_log(LOG_DEBUG, "No provider found, checking channel drivers for %s - %s\n", tech, number);
+       ast_debug(4, "No provider found, checking channel drivers for %s - %s\n", tech, number);
 
        if (!(chan_tech = ast_get_channel_tech(tech)))
                return AST_DEVICE_INVALID;
@@ -348,7 +373,7 @@ int ast_devstate_prov_del(const char *label)
        AST_RWLIST_TRAVERSE_SAFE_BEGIN(&devstate_provs, devcb, list) {
                if (!strcasecmp(devcb->label, label)) {
                        AST_RWLIST_REMOVE_CURRENT(&devstate_provs, list);
-                       free(devcb);
+                       ast_free(devcb);
                        res = 0;
                        break;
                }
@@ -368,8 +393,7 @@ static int getproviderstate(const char *provider, const char *address)
 
        AST_RWLIST_RDLOCK(&devstate_provs);
        AST_RWLIST_TRAVERSE(&devstate_provs, devprov, list) {
-               if (option_debug > 4)
-                       ast_log(LOG_DEBUG, "Checking provider %s with %s\n", devprov->label, provider);
+               ast_debug(5, "Checking provider %s with %s\n", devprov->label, provider);
 
                if (!strcasecmp(devprov->label, provider)) {
                        res = devprov->callback(address);
@@ -380,78 +404,58 @@ static int getproviderstate(const char *provider, const char *address)
        return res;
 }
 
-/*! \brief Add device state watcher */
-int ast_devstate_add(ast_devstate_cb_type callback, void *data)
+static void devstate_event(const char *device, enum ast_device_state state, enum devstate_cache cache)
 {
-       struct devstate_cb *devcb;
-
-       if (!callback || !(devcb = ast_calloc(1, sizeof(*devcb))))
-               return -1;
-
-       devcb->data = data;
-       devcb->callback = callback;
+       struct ast_event *event;
 
-       AST_RWLIST_WRLOCK(&devstate_cbs);
-       AST_RWLIST_INSERT_HEAD(&devstate_cbs, devcb, list);
-       AST_RWLIST_UNLOCK(&devstate_cbs);
-
-       return 0;
-}
-
-/*! \brief Remove device state watcher */
-void ast_devstate_del(ast_devstate_cb_type callback, void *data)
-{
-       struct devstate_cb *devcb;
+       if (!(event = ast_event_new(AST_EVENT_DEVICE_STATE,
+                       AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, device,
+                       AST_EVENT_IE_STATE, AST_EVENT_IE_PLTYPE_UINT, state,
+                       AST_EVENT_IE_END))) {
+               return;
+       }
 
-       AST_RWLIST_WRLOCK(&devstate_cbs);
-       AST_RWLIST_TRAVERSE_SAFE_BEGIN(&devstate_cbs, devcb, list) {
-               if ((devcb->callback == callback) && (devcb->data == data)) {
-                       AST_RWLIST_REMOVE_CURRENT(&devstate_cbs, list);
-                       free(devcb);
-                       break;
-               }
+       if (cache == CACHE_ON) {
+               /* Cache this event, replacing an event in the cache with the same
+                * device name if it exists. */
+               ast_event_queue_and_cache(event,
+                       AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR,
+                       AST_EVENT_IE_END);
+       } else {
+               ast_event_queue(event);
        }
-       AST_RWLIST_TRAVERSE_SAFE_END;
-       AST_RWLIST_UNLOCK(&devstate_cbs);
 }
 
-/*! \brief Notify callback watchers of change, and notify PBX core for hint updates
-       Normally executed within a separate thread
-*/
+/*! Called by the state change thread to find out what the state is, and then
+ *  to queue up the state change event */
 static void do_state_change(const char *device)
 {
-       int state;
-       struct devstate_cb *devcb;
+       enum ast_device_state state;
 
        state = ast_device_state(device);
-       if (option_debug > 2)
-               ast_log(LOG_DEBUG, "Changing state for %s - state %d (%s)\n", device, state, devstate2str(state));
 
-       AST_RWLIST_RDLOCK(&devstate_cbs);
-       AST_RWLIST_TRAVERSE(&devstate_cbs, devcb, list)
-               devcb->callback(device, state, devcb->data);
-       AST_RWLIST_UNLOCK(&devstate_cbs);
+       ast_debug(3, "Changing state for %s - state %d (%s)\n", device, state, devstate2str(state));
 
-       ast_hint_state_changed(device);
+       devstate_event(device, state, CACHE_OFF);
 }
 
-static int __ast_device_state_changed_literal(char *buf)
+static int __ast_devstate_changed_literal(enum ast_device_state state, char *buf)
 {
        char *device;
        struct state_change *change;
+       char *tmp = NULL;
 
-       if (option_debug > 2)
-               ast_log(LOG_DEBUG, "Notification of state change to be queued on device/channel %s\n", buf);
+       ast_debug(3, "Notification of state change to be queued on device/channel %s\n", buf);
 
        device = buf;
 
-       if (!strncasecmp(device, "Zap", 3)) {
-               char *tmp = strrchr(device, '-');
-               if (tmp)
-                       *tmp = '\0';
-       }
+       tmp = strrchr(device, '-');
+       if (tmp)
+               *tmp = '\0';
 
-       if (change_thread == AST_PTHREADT_NULL || !(change = ast_calloc(1, sizeof(*change) + strlen(device)))) {
+       if (state != AST_DEVICE_UNKNOWN) {
+               devstate_event(device, state, CACHE_ON);
+       } else if (change_thread == AST_PTHREADT_NULL || !(change = ast_calloc(1, sizeof(*change) + strlen(device)))) {
                /* we could not allocate a change struct, or */
                /* there is no background thread, so process the change now */
                do_state_change(device);
@@ -467,11 +471,34 @@ static int __ast_device_state_changed_literal(char *buf)
        return 1;
 }
 
+int ast_devstate_changed_literal(enum ast_device_state state, const char *dev)
+{
+       char *buf;
+
+       buf = ast_strdupa(dev);
+
+       return __ast_devstate_changed_literal(state, buf);
+}
+
 int ast_device_state_changed_literal(const char *dev)
 {
        char *buf;
+
        buf = ast_strdupa(dev);
-       return __ast_device_state_changed_literal(buf);
+
+       return __ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, buf);
+}
+
+int ast_devstate_changed(enum ast_device_state state, const char *fmt, ...) 
+{
+       char buf[AST_MAX_EXTENSION];
+       va_list ap;
+
+       va_start(ap, fmt);
+       vsnprintf(buf, sizeof(buf), fmt, ap);
+       va_end(ap);
+
+       return __ast_devstate_changed_literal(state, buf);
 }
 
 /*! \brief Accept change notification, add it to change queue */
@@ -483,7 +510,8 @@ int ast_device_state_changed(const char *fmt, ...)
        va_start(ap, fmt);
        vsnprintf(buf, sizeof(buf), fmt, ap);
        va_end(ap);
-       return __ast_device_state_changed_literal(buf);
+
+       return __ast_devstate_changed_literal(AST_DEVICE_UNKNOWN, buf);
 }
 
 /*! \brief Go through the dev state change queue and update changes in the dev state thread */
@@ -504,7 +532,7 @@ static void *do_devstate_changes(void *data)
                while ((current = next)) {
                        next = AST_LIST_NEXT(current, list);
                        do_state_change(current->device);
-                       free(current);
+                       ast_free(current);
                }
        }