Use the same delimited character as the FILTER function in FIELDQTY and CUT.
[asterisk/asterisk.git] / main / devicestate.c
index 06386aa..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[] = {
@@ -144,21 +146,11 @@ static const char *devstatestring[] = {
 struct devstate_prov {
        char label[40];
        ast_devstate_prov_cb_type callback;
-       AST_LIST_ENTRY(devstate_prov) list;
+       AST_RWLIST_ENTRY(devstate_prov) list;
 };
 
 /*! \brief A list of providers */
-static AST_LIST_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_LIST_ENTRY(devstate_cb) list;
-};
-
-/*! \brief A device state watcher list */
-static AST_LIST_HEAD_STATIC(devstate_cbs, devstate_cb);
+static AST_RWLIST_HEAD_STATIC(devstate_provs, devstate_prov);
 
 struct state_change {
        AST_LIST_ENTRY(state_change) list;
@@ -175,25 +167,94 @@ 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);
 
 /*! \brief Find devicestate as text message for output */
-const char *devstate2str(int devstate) 
+const char *devstate2str(enum ast_device_state devstate) 
 {
        return devstatestring[devstate];
 }
 
+const char *ast_devstate_str(enum ast_device_state state)
+{
+       const char *res = "UNKNOWN";
+
+       switch (state) {
+       case AST_DEVICE_UNKNOWN:
+               break;
+       case AST_DEVICE_NOT_INUSE:
+               res = "NOT_INUSE";
+               break;
+       case AST_DEVICE_INUSE:
+               res = "INUSE";
+               break;
+       case AST_DEVICE_BUSY:
+               res = "BUSY";
+               break;
+       case AST_DEVICE_INVALID:
+               res = "INVALID";
+               break;
+       case AST_DEVICE_UNAVAILABLE:
+               res = "UNAVAILABLE";
+               break;
+       case AST_DEVICE_RINGING:
+               res = "RINGING";
+               break;
+       case AST_DEVICE_RINGINUSE:
+               res = "RINGINUSE";
+               break;
+       case AST_DEVICE_ONHOLD:
+               res = "ONHOLD";
+               break;
+       }
+
+       return res;
+}
+
+enum ast_device_state ast_devstate_val(const char *val)
+{
+       if (!strcasecmp(val, "NOT_INUSE"))
+               return AST_DEVICE_NOT_INUSE;
+       else if (!strcasecmp(val, "INUSE"))
+               return AST_DEVICE_INUSE;
+       else if (!strcasecmp(val, "BUSY"))
+               return AST_DEVICE_BUSY;
+       else if (!strcasecmp(val, "INVALID"))
+               return AST_DEVICE_INVALID;
+       else if (!strcasecmp(val, "UNAVAILABLE"))
+               return AST_DEVICE_UNAVAILABLE;
+       else if (!strcasecmp(val, "RINGING"))
+               return AST_DEVICE_RINGING;
+       else if (!strcasecmp(val, "RINGINUSE"))
+               return AST_DEVICE_RINGINUSE;
+       else if (!strcasecmp(val, "ONHOLD"))
+               return AST_DEVICE_ONHOLD;
+
+       return AST_DEVICE_UNKNOWN;
+}
+
 /*! \brief Find out if device is active in a call or not 
        \note find channels with the device's name in it
        This function is only used for channels that does not implement 
        devicestate natively
 */
-int ast_parse_device_state(const char *device)
+enum ast_device_state ast_parse_device_state(const char *device)
 {
        struct ast_channel *chan;
        char match[AST_CHANNEL_NAME];
-       int res;
+       enum ast_device_state res;
 
        ast_copy_string(match, device, sizeof(match)-1);
        strcat(match, "-");
@@ -212,24 +273,46 @@ int 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 */
-int ast_device_state(const char *device)
+enum ast_device_state ast_device_state(const char *device)
 {
        char *buf;
        char *number;
        const struct ast_channel_tech *chan_tech;
-       int res = 0;
+       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, "/");
-       number = buf;
-       if (!number) {
-               provider = strsep(&tech, ":");
-               if (!provider)
+       if (!(number = buf)) {
+               if (!(provider = strsep(&tech, ":")))
                        return AST_DEVICE_INVALID;
                /* We have a provider */
                number = tech;
@@ -237,33 +320,29 @@ int 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);
 
-       chan_tech = ast_get_channel_tech(tech);
-       if (!chan_tech)
+       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;
 
-       if (!chan_tech->devicestate)    /* Does the channel driver support device state notification? */
-               return ast_parse_device_state(device);  /* No, try the generic function */
-       else {
-               res = chan_tech->devicestate(number);   /* Ask the channel driver for device state */
-               if (res == AST_DEVICE_UNKNOWN) {
-                       res = ast_parse_device_state(device);
-                       /* at this point we know the device exists, but the channel driver
-                          could not give us a state; if there is no channel state available,
-                          it must be 'not in use'
-                       */
-                       if (res == AST_DEVICE_UNKNOWN)
-                               res = AST_DEVICE_NOT_INUSE;
-                       return res;
-               } else
-                       return res;
-       }
+       if (!(chan_tech->devicestate)) /* Does the channel driver support device state notification? */
+               return ast_parse_device_state(device); /* No, try the generic function */
+
+       res = chan_tech->devicestate(number);
+
+       if (res != AST_DEVICE_UNKNOWN)
+               return res;
+
+       res = ast_parse_device_state(device);
+
+       if (res == AST_DEVICE_UNKNOWN)
+               return AST_DEVICE_NOT_INUSE;
+
+       return res;
 }
 
 /*! \brief Add device state provider */
@@ -277,28 +356,32 @@ int ast_devstate_prov_add(const char *label, ast_devstate_prov_cb_type callback)
        devprov->callback = callback;
        ast_copy_string(devprov->label, label, sizeof(devprov->label));
 
-       AST_LIST_LOCK(&devstate_provs);
-       AST_LIST_INSERT_HEAD(&devstate_provs, devprov, list);
-       AST_LIST_UNLOCK(&devstate_provs);
+       AST_RWLIST_WRLOCK(&devstate_provs);
+       AST_RWLIST_INSERT_HEAD(&devstate_provs, devprov, list);
+       AST_RWLIST_UNLOCK(&devstate_provs);
 
        return 0;
 }
 
 /*! \brief Remove device state provider */
-void ast_devstate_prov_del(const char *label)
+int ast_devstate_prov_del(const char *label)
 {
        struct devstate_prov *devcb;
+       int res = -1;
 
-       AST_LIST_LOCK(&devstate_provs);
-       AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_provs, devcb, list) {
+       AST_RWLIST_WRLOCK(&devstate_provs);
+       AST_RWLIST_TRAVERSE_SAFE_BEGIN(&devstate_provs, devcb, list) {
                if (!strcasecmp(devcb->label, label)) {
-                       AST_LIST_REMOVE_CURRENT(&devstate_provs, list);
-                       free(devcb);
+                       AST_RWLIST_REMOVE_CURRENT(&devstate_provs, list);
+                       ast_free(devcb);
+                       res = 0;
                        break;
                }
        }
-       AST_LIST_TRAVERSE_SAFE_END;
-       AST_LIST_UNLOCK(&devstate_provs);
+       AST_RWLIST_TRAVERSE_SAFE_END;
+       AST_RWLIST_UNLOCK(&devstate_provs);
+
+       return res;
 }
 
 /*! \brief Get provider device state */
@@ -308,91 +391,71 @@ static int getproviderstate(const char *provider, const char *address)
        int res = AST_DEVICE_INVALID;
 
 
-       AST_LIST_LOCK(&devstate_provs);
-       AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_provs, devprov, list) {
-               if (option_debug > 4)
-                       ast_log(LOG_DEBUG, "Checking provider %s with %s\n", devprov->label, provider);
+       AST_RWLIST_RDLOCK(&devstate_provs);
+       AST_RWLIST_TRAVERSE(&devstate_provs, devprov, list) {
+               ast_debug(5, "Checking provider %s with %s\n", devprov->label, provider);
 
                if (!strcasecmp(devprov->label, provider)) {
                        res = devprov->callback(address);
                        break;
                }
        }
-       AST_LIST_TRAVERSE_SAFE_END;
-       AST_LIST_UNLOCK(&devstate_provs);
+       AST_RWLIST_UNLOCK(&devstate_provs);
        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;
+       struct ast_event *event;
 
-       if (!callback || !(devcb = ast_calloc(1, sizeof(*devcb))))
-               return -1;
-
-       devcb->data = data;
-       devcb->callback = callback;
-
-       AST_LIST_LOCK(&devstate_cbs);
-       AST_LIST_INSERT_HEAD(&devstate_cbs, devcb, list);
-       AST_LIST_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_LIST_LOCK(&devstate_cbs);
-       AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_cbs, devcb, list) {
-               if ((devcb->callback == callback) && (devcb->data == data)) {
-                       AST_LIST_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_LIST_TRAVERSE_SAFE_END;
-       AST_LIST_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_LIST_LOCK(&devstate_cbs);
-       AST_LIST_TRAVERSE(&devstate_cbs, devcb, list)
-               devcb->callback(device, state, devcb->data);
-       AST_LIST_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, *tmp;
+       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 ((tmp = strrchr(device, '-')))
-               *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);
@@ -401,20 +464,41 @@ static int __ast_device_state_changed_literal(char *buf)
                strcpy(change->device, device);
                AST_LIST_LOCK(&state_changes);
                AST_LIST_INSERT_TAIL(&state_changes, change, list);
-               if (AST_LIST_FIRST(&state_changes) == change)
-                       /* the list was empty, signal the thread */
-                       ast_cond_signal(&change_pending);
+               ast_cond_signal(&change_pending);
                AST_LIST_UNLOCK(&state_changes);
        }
 
        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 */
@@ -426,28 +510,29 @@ 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 */
 static void *do_devstate_changes(void *data)
 {
-       struct state_change *cur;
+       struct state_change *next, *current;
 
-       AST_LIST_LOCK(&state_changes);
        for (;;) {
-               /* the list lock will _always_ be held at this point in the loop */
-               cur = AST_LIST_REMOVE_HEAD(&state_changes, list);
-               if (cur) {
-                       /* we got an entry, so unlock the list while we process it */
-                       AST_LIST_UNLOCK(&state_changes);
-                       do_state_change(cur->device);
-                       free(cur);
-                       AST_LIST_LOCK(&state_changes);
-               } else {
-                       /* there was no entry, so atomically unlock the list and wait for
-                          the condition to be signalled (returns with the lock held) */
+               /* This basically pops off any state change entries, resets the list back to NULL, unlocks, and processes each state change */
+               AST_LIST_LOCK(&state_changes);
+               if (AST_LIST_EMPTY(&state_changes))
                        ast_cond_wait(&change_pending, &state_changes.lock);
+               next = AST_LIST_FIRST(&state_changes);
+               AST_LIST_HEAD_INIT_NOLOCK(&state_changes);
+               AST_LIST_UNLOCK(&state_changes);
+
+               /* Process each state change */
+               while ((current = next)) {
+                       next = AST_LIST_NEXT(current, list);
+                       do_state_change(current->device);
+                       ast_free(current);
                }
        }