Add a function doing a service rescan.
[profile/ivi/GSSDP.git] / libgssdp / gssdp-resource-browser.c
index 4023632..04b23fe 100644 (file)
  *
  * You should have received a copy of the GNU Library General Public
  * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
  */
 
 /**
  * SECTION:gssdp-resource-browser
  * @short_description: Class handling resource discovery.
  *
- * #GUPnPResourceBrowser handles resource discovery. After creating a browser
+ * #GSSDPResourceBrowser handles resource discovery. After creating a browser
  * and activating it, the ::resource-available and ::resource-unavailable
  * signals will be emitted whenever the availability of a resource matching the
  * specified discovery target changes. A discovery request is sent out
  */
 
 #include <config.h>
-#include <libsoup/soup-date.h>
+#include <libsoup/soup.h>
 #include <string.h>
 #include <stdio.h>
+#include <stdlib.h>
 
 #include "gssdp-resource-browser.h"
 #include "gssdp-client-private.h"
 #include "gssdp-protocol.h"
 #include "gssdp-marshal.h"
 
+#define RESCAN_TIMEOUT 5 /* 5 seconds */
 #define MAX_DISCOVERY_MESSAGES 3
 #define DISCOVERY_FREQUENCY    500 /* 500 ms */
 
@@ -51,6 +53,7 @@ struct _GSSDPResourceBrowserPrivate {
         GSSDPClient *client;
 
         char        *target;
+        GRegex      *target_regex;
 
         gushort      mx;
 
@@ -60,8 +63,12 @@ struct _GSSDPResourceBrowserPrivate {
 
         GHashTable  *resources;
                         
-        guint        timeout_id;
+        GSource     *timeout_src;
         guint        num_discovery;
+        guint        version;
+
+        GSource     *refresh_cache_src;
+        GHashTable  *fresh_resources;
 };
 
 enum {
@@ -83,7 +90,7 @@ static guint signals[LAST_SIGNAL];
 typedef struct {
         GSSDPResourceBrowser *resource_browser;
         char                 *usn;
-        guint                 timeout_id;
+        GSource              *timeout_src;
 } Resource;
 
 /* Function prototypes */
@@ -95,7 +102,7 @@ message_received_cb              (GSSDPClient          *client,
                                   const char           *from_ip,
                                   gushort               from_port,
                                   _GSSDPMessageType     type,
-                                  GHashTable           *headers,
+                                  SoupMessageHeaders   *headers,
                                   gpointer              user_data);
 static void
 resource_free                    (gpointer              data);
@@ -109,6 +116,8 @@ static void
 start_discovery                  (GSSDPResourceBrowser *resource_browser);
 static void
 stop_discovery                   (GSSDPResourceBrowser *resource_browser);
+static gboolean
+refresh_cache                    (gpointer data);
 
 static void
 gssdp_resource_browser_init (GSSDPResourceBrowser *resource_browser)
@@ -123,7 +132,7 @@ gssdp_resource_browser_init (GSSDPResourceBrowser *resource_browser)
         resource_browser->priv->resources =
                 g_hash_table_new_full (g_str_hash,
                                        g_str_equal,
-                                       NULL,
+                                       g_free,
                                        resource_free);
 }
 
@@ -220,6 +229,8 @@ gssdp_resource_browser_dispose (GObject *object)
         }
 
         clear_cache (resource_browser);
+
+        G_OBJECT_CLASS (gssdp_resource_browser_parent_class)->dispose (object);
 }
 
 static void
@@ -229,9 +240,14 @@ gssdp_resource_browser_finalize (GObject *object)
 
         resource_browser = GSSDP_RESOURCE_BROWSER (object);
 
+        if (resource_browser->priv->target_regex)
+                g_regex_unref (resource_browser->priv->target_regex);
+
         g_free (resource_browser->priv->target);
 
         g_hash_table_destroy (resource_browser->priv->resources);
+
+        G_OBJECT_CLASS (gssdp_resource_browser_parent_class)->finalize (object);
 }
 
 static void
@@ -239,17 +255,17 @@ gssdp_resource_browser_class_init (GSSDPResourceBrowserClass *klass)
 {
         GObjectClass *object_class;
 
-       object_class = G_OBJECT_CLASS (klass);
+        object_class = G_OBJECT_CLASS (klass);
 
-       object_class->set_property = gssdp_resource_browser_set_property;
-       object_class->get_property = gssdp_resource_browser_get_property;
-       object_class->dispose      = gssdp_resource_browser_dispose;
-       object_class->finalize     = gssdp_resource_browser_finalize;
+        object_class->set_property = gssdp_resource_browser_set_property;
+        object_class->get_property = gssdp_resource_browser_get_property;
+        object_class->dispose      = gssdp_resource_browser_dispose;
+        object_class->finalize     = gssdp_resource_browser_finalize;
 
         g_type_class_add_private (klass, sizeof (GSSDPResourceBrowserPrivate));
 
         /**
-         * GSSDPResourceBrowser:client
+         * GSSDPResourceBrowser:client:
          *
          * The #GSSDPClient to use.
          **/
@@ -266,7 +282,7 @@ gssdp_resource_browser_class_init (GSSDPResourceBrowserClass *klass)
                           G_PARAM_STATIC_BLURB));
 
         /**
-         * GSSDPResourceBrowser:target
+         * GSSDPResourceBrowser:target:
          *
          * The discovery target.
          **/
@@ -283,7 +299,7 @@ gssdp_resource_browser_class_init (GSSDPResourceBrowserClass *klass)
                           G_PARAM_STATIC_BLURB));
 
         /**
-         * GSSDPResourceBrowser:mx
+         * GSSDPResourceBrowser:mx:
          *
          * The maximum number of seconds in which to request other parties
          * to respond.
@@ -304,7 +320,7 @@ gssdp_resource_browser_class_init (GSSDPResourceBrowserClass *klass)
                           G_PARAM_STATIC_BLURB));
 
         /**
-         * GSSDPResourceBrowser:active
+         * GSSDPResourceBrowser:active:
          *
          * Whether this browser is active or not.
          **/
@@ -321,11 +337,11 @@ gssdp_resource_browser_class_init (GSSDPResourceBrowserClass *klass)
                           G_PARAM_STATIC_BLURB));
 
         /**
-         * GSSDPResourceBrowser::resource-available
+         * GSSDPResourceBrowser::resource-available:
          * @resource_browser: The #GSSDPResourceBrowser that received the
          * signal
          * @usn: The USN of the discovered resource
-         * @locations: A #GList of strings describing the locations of the
+         * @locations: (type GList*) (transfer none) (element-type utf8): A #GList of strings describing the locations of the
          * discovered resource.
          *
          * The ::resource-available signal is emitted whenever a new resource
@@ -345,7 +361,7 @@ gssdp_resource_browser_class_init (GSSDPResourceBrowserClass *klass)
                               G_TYPE_POINTER);
 
         /**
-         * GSSDPResourceBrowser::resource-unavailable
+         * GSSDPResourceBrowser::resource-unavailable:
          * @resource_browser: The #GSSDPResourceBrowser that received the
          * signal
          * @usn: The USN of the resource
@@ -367,7 +383,7 @@ gssdp_resource_browser_class_init (GSSDPResourceBrowserClass *klass)
 }
 
 /**
- * gssdp_resource_browser_new
+ * gssdp_resource_browser_new:
  * @client: The #GSSDPClient to associate with
  *
  * Return value: A new #GSSDPResourceBrowser object.
@@ -382,9 +398,9 @@ gssdp_resource_browser_new (GSSDPClient *client,
                              NULL);
 }
 
-/**
+/*
  * Sets the #GSSDPClient @resource_browser is associated with to @client
- **/
+ */
 static void
 gssdp_resource_browser_set_client (GSSDPResourceBrowser *resource_browser,
                                    GSSDPClient          *client)
@@ -405,10 +421,10 @@ gssdp_resource_browser_set_client (GSSDPResourceBrowser *resource_browser,
 }
 
 /**
- * gssdp_resource_browser_get_client
+ * gssdp_resource_browser_get_client:
  * @resource_browser: A #GSSDPResourceBrowser
  *
- * Return value: The #GSSDPClient @resource_browser is associated with.
+ * Returns: (transfer none): The #GSSDPClient @resource_browser is associated with.
  **/
 GSSDPClient *
 gssdp_resource_browser_get_client (GSSDPResourceBrowser *resource_browser)
@@ -420,7 +436,7 @@ gssdp_resource_browser_get_client (GSSDPResourceBrowser *resource_browser)
 }
 
 /**
- * gssdp_resource_browser_set_target
+ * gssdp_resource_browser_set_target:
  * @resource_browser: A #GSSDPResourceBrowser
  * @target: The browser target
  *
@@ -430,6 +446,11 @@ void
 gssdp_resource_browser_set_target (GSSDPResourceBrowser *resource_browser,
                                    const char           *target)
 {
+        char *pattern;
+        char *version;
+        char *version_pattern;
+        GError *error;
+
         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
         g_return_if_fail (target != NULL);
         g_return_if_fail (!resource_browser->priv->active);
@@ -437,11 +458,45 @@ gssdp_resource_browser_set_target (GSSDPResourceBrowser *resource_browser,
         g_free (resource_browser->priv->target);
         resource_browser->priv->target = g_strdup (target);
 
+        if (resource_browser->priv->target_regex)
+                g_regex_unref (resource_browser->priv->target_regex);
+
+        version_pattern = "([0-9]+)";
+        /* Make sure we have enough room for version pattern */
+        pattern = g_strndup (target,
+                             strlen (target) + strlen (version_pattern));
+
+        version = g_strrstr (pattern, ":");
+        if (version != NULL &&
+            (g_strstr_len (pattern, -1, "uuid:") != pattern ||
+             version != g_strstr_len (pattern, -1, ":")) &&
+            g_regex_match_simple (version_pattern,
+                                  version + 1,
+                                  G_REGEX_ANCHORED,
+                                  G_REGEX_MATCH_ANCHORED)) {
+                resource_browser->priv->version = atoi (version + 1);
+                strcpy (version + 1, version_pattern);
+        }
+
+        error = NULL;
+        resource_browser->priv->target_regex = g_regex_new (pattern,
+                                                            0,
+                                                            0,
+                                                            &error);
+        if (error) {
+                g_warning ("Error compiling regular expression '%s': %s",
+                           pattern,
+                           error->message);
+
+                g_error_free (error);
+        }
+
+        g_free (pattern);
         g_object_notify (G_OBJECT (resource_browser), "target");
 }
 
 /**
- * gssdp_resource_browser_get_target
+ * gssdp_resource_browser_get_target:
  * @resource_browser: A #GSSDPResourceBrowser
  *
  * Return value: The browser target.
@@ -456,7 +511,7 @@ gssdp_resource_browser_get_target (GSSDPResourceBrowser *resource_browser)
 }
 
 /**
- * gssdp_resource_browser_set_mx
+ * gssdp_resource_browser_set_mx:
  * @resource_browser: A #GSSDPResourceBrowser
  * @mx: The to be used MX value
  *
@@ -477,7 +532,7 @@ gssdp_resource_browser_set_mx (GSSDPResourceBrowser *resource_browser,
 }
 
 /**
- * gssdp_resource_browser_get_mx
+ * gssdp_resource_browser_get_mx:
  * @resource_browser: A #GSSDPResourceBrowser
  *
  * Return value: The used MX value.
@@ -491,7 +546,7 @@ gssdp_resource_browser_get_mx (GSSDPResourceBrowser *resource_browser)
 }
 
 /**
- * gssdp_resource_browser_set_active
+ * gssdp_resource_browser_set_active:
  * @resource_browser: A #GSSDPResourceBrowser
  * @active: TRUE to activate @resource_browser
  *
@@ -520,7 +575,7 @@ gssdp_resource_browser_set_active (GSSDPResourceBrowser *resource_browser,
 }
 
 /**
- * gssdp_resource_browser_get_active
+ * gssdp_resource_browser_get_active:
  * @resource_browser: A #GSSDPResourceBrowser
  *
  * Return value: TRUE if @resource_browser is active.
@@ -534,47 +589,112 @@ gssdp_resource_browser_get_active (GSSDPResourceBrowser *resource_browser)
 }
 
 /**
- * Resource expired: Remove
+ * gssdp_resource_browser_rescan:
+ * @resource_browser: A #GSSDPResourceBrowser
+ *
+ * Begins discovery if @resource_browser is active and no discovery is
+ * performed. Otherwise does nothing.
+ *
+ * Return value: %TRUE if rescaning has been started.
  **/
+gboolean
+gssdp_resource_browser_rescan (GSSDPResourceBrowser *resource_browser)
+{
+        g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser), 0);
+
+        if (resource_browser->priv->active &&
+            resource_browser->priv->timeout_src == NULL &&
+            resource_browser->priv->refresh_cache_src == NULL) {
+                start_discovery (resource_browser);
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+/*
+ * Resource expired: Remove
+ */
 static gboolean
 resource_expire (gpointer user_data)
 {
+        GSSDPResourceBrowser *resource_browser;
         Resource *resource;
+        char *usn;
+        char *canonical_usn;
 
         resource = user_data;
-        
-        g_signal_emit (resource->resource_browser,
-                       signals[RESOURCE_UNAVAILABLE],
-                       0,
-                       resource->usn);
+        resource_browser = resource->resource_browser;
+
+        /* Steal the USN pointer from the resource as we need it for the signal
+         * emission.
+         */
+        usn = resource->usn;
+        resource->usn = NULL;
+
+        if (resource_browser->priv->version > 0) {
+                char *version;
+
+                version = g_strrstr (usn, ":");
+                canonical_usn = g_strndup (usn, version - usn);
+        } else {
+                canonical_usn = g_strdup (usn);
+        }
 
         g_hash_table_remove (resource->resource_browser->priv->resources,
-                             resource->usn);
+                             canonical_usn);
+
+        g_signal_emit (resource_browser,
+                       signals[RESOURCE_UNAVAILABLE],
+                       0,
+                       usn);
+        g_free (usn);
+        g_free (canonical_usn);
 
         return FALSE;
 }
 
 static void
 resource_available (GSSDPResourceBrowser *resource_browser,
-                    GHashTable           *headers)
+                    SoupMessageHeaders   *headers)
 {
-        GSList *list;
         const char *usn;
+        const char *header;
         Resource *resource;
         gboolean was_cached;
         guint timeout;
         GList *locations;
+        char *canonical_usn;
 
-        list = g_hash_table_lookup (headers, "USN");
-        if (!list)
+        usn = soup_message_headers_get_one (headers, "USN");
+        if (!usn)
                 return; /* No USN specified */
-        usn = list->data;
+
+        if (resource_browser->priv->version > 0) {
+                char *version;
+
+                version = g_strrstr (usn, ":");
+                canonical_usn = g_strndup (usn, version - usn);
+        } else {
+                canonical_usn = g_strdup (usn);
+        }
 
         /* Get from cache, if possible */
-        resource = g_hash_table_lookup (resource_browser->priv->resources, usn);
+        resource = g_hash_table_lookup (resource_browser->priv->resources,
+                                        canonical_usn);
+        /* Put usn into fresh resources, so this resource will not be
+         * removed on cache refreshing. */
+        if (resource_browser->priv->fresh_resources != NULL) {
+                char *usn_copy = g_strdup (canonical_usn);
+
+                g_hash_table_insert (resource_browser->priv->fresh_resources,
+                                     usn_copy,
+                                     usn_copy);
+        }
+
         if (resource) {
                 /* Remove old timeout */
-                g_source_remove (resource->timeout_id);
+                g_source_destroy (resource->timeout_src);
 
                 was_cached = TRUE;
         } else {
@@ -585,22 +705,30 @@ resource_available (GSSDPResourceBrowser *resource_browser,
                 resource->usn              = g_strdup (usn);
                 
                 g_hash_table_insert (resource_browser->priv->resources,
-                                     resource->usn,
+                                     canonical_usn,
                                      resource);
                 
                 was_cached = FALSE;
+
+                /* hash-table takes ownership of this */
+                canonical_usn = NULL;
         }
 
+        if (canonical_usn != NULL)
+                g_free (canonical_usn);
+
         /* Calculate new timeout */
-        list = g_hash_table_lookup (headers, "Cache-Control");
-        if (list) {
-                GSList *l;
+        header = soup_message_headers_get_one (headers, "Cache-Control");
+        if (header) {
+                GSList *list;
                 int res;
 
                 res = 0;
 
-                for (l = list; l; l = l->next) {
-                        res = sscanf (l->data,
+                for (list = soup_header_parse_list (header);
+                     list;
+                     list = list->next) {
+                        res = sscanf (list->data,
                                       "max-age = %d",
                                       &timeout);
                         if (res == 1)
@@ -612,16 +740,24 @@ resource_available (GSSDPResourceBrowser *resource_browser,
                                    "default max-age of %d.\n"
                                    "Header was:\n%s",
                                    SSDP_DEFAULT_MAX_AGE,
-                                   (char *) list->data);
+                                   header);
 
                         timeout = SSDP_DEFAULT_MAX_AGE;
                 }
+
+                soup_header_free_list (list);
         } else {
-                list = g_hash_table_lookup (headers, "Expires");
-                if (list) {
+                const char *expires;
+
+                expires = soup_message_headers_get_one (headers, "Expires");
+                if (expires) {
+                        SoupDate *soup_exp_time;
                         time_t exp_time, cur_time;
 
-                        exp_time = soup_date_parse (list->data);
+                        soup_exp_time = soup_date_new_from_string (expires);
+                        exp_time = soup_date_to_time_t (soup_exp_time);
+                        soup_date_free (soup_exp_time);
+
                         cur_time = time (NULL);
 
                         if (exp_time > cur_time)
@@ -631,7 +767,7 @@ resource_available (GSSDPResourceBrowser *resource_browser,
                                            "default max-age of %d.\n"
                                            "Header was:\n%s",
                                            SSDP_DEFAULT_MAX_AGE,
-                                           (char *) list->data);
+                                           expires);
 
                                 timeout = SSDP_DEFAULT_MAX_AGE;
                         }
@@ -644,9 +780,15 @@ resource_available (GSSDPResourceBrowser *resource_browser,
                 }
         }
 
-        resource->timeout_id = g_timeout_add (timeout * 1000,
-                                              resource_expire,
-                                              resource);
+        resource->timeout_src = g_timeout_source_new_seconds (timeout);
+        g_source_set_callback (resource->timeout_src,
+                               resource_expire,
+                               resource, NULL);
+
+        g_source_attach (resource->timeout_src,
+                         g_main_context_get_thread_default ());
+
+        g_source_unref (resource->timeout_src);
 
         /* Only continue with signal emission if this resource was not
          * cached already */
@@ -656,17 +798,18 @@ resource_available (GSSDPResourceBrowser *resource_browser,
         /* Build list of locations */
         locations = NULL;
 
-        list = g_hash_table_lookup (headers, "Location");
-        if (list)
-                locations = g_list_append (locations, g_strdup (list->data));
+        header = soup_message_headers_get_one (headers, "Location");
+        if (header)
+                locations = g_list_append (locations, g_strdup (header));
 
-        list = g_hash_table_lookup (headers, "AL");
-        if (list) {
+        header = soup_message_headers_get_one (headers, "AL");
+        if (header) {
                 /* Parse AL header. The format is:
                  * <uri1><uri2>... */
-                char *start, *end, *uri;
+                const char *start, *end;
+                char *uri;
                 
-                start = list->data;
+                start = header;
                 while ((start = strchr (start, '<'))) {
                         start += 1;
                         if (!start || !*start)
@@ -700,40 +843,93 @@ resource_available (GSSDPResourceBrowser *resource_browser,
 
 static void
 resource_unavailable (GSSDPResourceBrowser *resource_browser,
-                      GHashTable           *headers)
+                      SoupMessageHeaders   *headers)
 {
-        GSList *list;
         const char *usn;
+        char *canonical_usn;
 
-        list = g_hash_table_lookup (headers, "USN");
-        if (!list)
+        usn = soup_message_headers_get_one (headers, "USN");
+        if (!usn)
                 return; /* No USN specified */
-        usn = list->data;
+
+        if (resource_browser->priv->version > 0) {
+                char *version;
+
+                version = g_strrstr (usn, ":");
+                canonical_usn = g_strndup (usn, version - usn);
+        } else {
+                canonical_usn = g_strdup (usn);
+        }
 
         /* Only process if we were cached */
-        if (!g_hash_table_lookup (resource_browser->priv->resources, usn))
-                return;
+        if (!g_hash_table_lookup (resource_browser->priv->resources,
+                                  canonical_usn))
+                goto out;
+
+        g_hash_table_remove (resource_browser->priv->resources,
+                             canonical_usn);
 
         g_signal_emit (resource_browser,
                        signals[RESOURCE_UNAVAILABLE],
                        0,
                        usn);
 
-        g_hash_table_remove (resource_browser->priv->resources, usn);
+out:
+        g_free (canonical_usn);
+}
+
+static gboolean
+check_target_compat (GSSDPResourceBrowser *resource_browser,
+                     const char           *st)
+{
+        GMatchInfo *info;
+        int         version;
+        char       *tmp;
+
+        if (strcmp (resource_browser->priv->target,
+                    GSSDP_ALL_RESOURCES) == 0)
+                return TRUE;
+
+        if (!g_regex_match (resource_browser->priv->target_regex,
+                            st,
+                            0,
+                            &info)) {
+                g_match_info_free (info);
+
+                return FALSE;
+        }
+
+        /* If there was no version to match, we're done */
+        if (resource_browser->priv->version == 0) {
+                g_match_info_free (info);
+
+                return TRUE;
+        }
+
+        if (g_match_info_get_match_count (info) != 2) {
+                g_match_info_free (info);
+
+                return FALSE;
+        }
+
+        version = atoi ((tmp = g_match_info_fetch (info, 1)));
+        g_free (tmp);
+        g_match_info_free (info);
+
+        return version >= resource_browser->priv->version;
 }
 
 static void
 received_discovery_response (GSSDPResourceBrowser *resource_browser,
-                             GHashTable           *headers)
+                             SoupMessageHeaders   *headers)
 {
-        GSList *list;
+        const char *st;
 
-        list = g_hash_table_lookup (headers, "ST");
-        if (!list)
+        st = soup_message_headers_get_one (headers, "ST");
+        if (!st)
                 return; /* No target specified */
 
-        if (strcmp (resource_browser->priv->target, GSSDP_ALL_RESOURCES) != 0 &&
-            strcmp (resource_browser->priv->target, list->data) != 0)
+        if (!check_target_compat (resource_browser, st))
                 return; /* Target doesn't match */
 
         resource_available (resource_browser, headers);
@@ -741,43 +937,42 @@ received_discovery_response (GSSDPResourceBrowser *resource_browser,
 
 static void
 received_announcement (GSSDPResourceBrowser *resource_browser,
-                       GHashTable           *headers)
+                       SoupMessageHeaders   *headers)
 {
-        GSList *list;
+        const char *header;
 
-        list = g_hash_table_lookup (headers, "NT");
-        if (!list)
+        header = soup_message_headers_get_one (headers, "NT");
+        if (!header)
                 return; /* No target specified */
 
-        if (strcmp (resource_browser->priv->target, GSSDP_ALL_RESOURCES) != 0 &&
-            strcmp (resource_browser->priv->target, list->data) != 0)
+        if (!check_target_compat (resource_browser, header))
                 return; /* Target doesn't match */
 
-        list = g_hash_table_lookup (headers, "NTS");
-        if (!list)
+        header = soup_message_headers_get_one (headers, "NTS");
+        if (!header)
                 return; /* No announcement type specified */
 
         /* Check announcement type */
-        if      (strncmp (list->data,
+        if      (strncmp (header,
                           SSDP_ALIVE_NTS,
                           strlen (SSDP_ALIVE_NTS)) == 0)
                 resource_available (resource_browser, headers);
-        else if (strncmp (list->data,
+        else if (strncmp (header,
                           SSDP_BYEBYE_NTS,
                           strlen (SSDP_BYEBYE_NTS)) == 0)
                 resource_unavailable (resource_browser, headers);
 }
 
-/**
+/*
  * Received a message
- **/
+ */
 static void
-message_received_cb (GSSDPClient      *client,
-                     const char       *from_ip,
-                     gushort           from_port,
-                     _GSSDPMessageType type,
-                     GHashTable       *headers,
-                     gpointer          user_data)
+message_received_cb (GSSDPClient        *client,
+                     const char         *from_ip,
+                     gushort             from_port,
+                     _GSSDPMessageType   type,
+                     SoupMessageHeaders *headers,
+                     gpointer            user_data)
 {
         GSSDPResourceBrowser *resource_browser;
 
@@ -798,9 +993,9 @@ message_received_cb (GSSDPClient      *client,
         }
 }
 
-/**
+/*
  * Free a Resource structure and its contained data
- **/
+ */
 static void
 resource_free (gpointer data)
 {
@@ -810,7 +1005,7 @@ resource_free (gpointer data)
 
         g_free (resource->usn);
 
-        g_source_remove (resource->timeout_id);
+        g_source_destroy (resource->timeout_src);
 
         g_slice_free (Resource, resource);
 }
@@ -830,9 +1025,9 @@ clear_cache_helper (gpointer key, gpointer value, gpointer data)
         return TRUE;
 }
 
-/**
+/*
  * Clears the cached resources hash
- **/
+ */
 static void
 clear_cache (GSSDPResourceBrowser *resource_browser)
 {
@@ -850,12 +1045,14 @@ send_discovery_request (GSSDPResourceBrowser *resource_browser)
 
         message = g_strdup_printf (SSDP_DISCOVERY_REQUEST,
                                    resource_browser->priv->target,
-                                   resource_browser->priv->mx);
+                                   resource_browser->priv->mx,
+                                   g_get_application_name () ?: "");
 
         _gssdp_client_send_message (resource_browser->priv->client,
                                     NULL,
                                     0,
-                                    message);
+                                    message,
+                                    _GSSDP_DISCOVERY_REQUEST);
 
         g_free (message);
 }
@@ -872,9 +1069,21 @@ discovery_timeout (gpointer data)
         resource_browser->priv->num_discovery += 1;
 
         if (resource_browser->priv->num_discovery >= MAX_DISCOVERY_MESSAGES) {
-                resource_browser->priv->timeout_id = 0;
+                resource_browser->priv->timeout_src = NULL;
                 resource_browser->priv->num_discovery = 0;
 
+                /* Setup cache refreshing */
+                resource_browser->priv->refresh_cache_src =
+                                  g_timeout_source_new_seconds (RESCAN_TIMEOUT);
+                g_source_set_callback
+                                     (resource_browser->priv->refresh_cache_src,
+                                      refresh_cache,
+                                      resource_browser,
+                                      NULL);
+                g_source_attach (resource_browser->priv->refresh_cache_src,
+                                 g_main_context_get_thread_default ());
+                g_source_unref (resource_browser->priv->refresh_cache_src);
+
                 return FALSE;
         } else
                 return TRUE;
@@ -889,19 +1098,78 @@ start_discovery (GSSDPResourceBrowser *resource_browser)
 
         /* And schedule the rest for later */
         resource_browser->priv->num_discovery = 1;
-        resource_browser->priv->timeout_id =
-                                g_timeout_add (DISCOVERY_FREQUENCY,
-                                               discovery_timeout,
-                                               resource_browser);
+        resource_browser->priv->timeout_src =
+                g_timeout_source_new (DISCOVERY_FREQUENCY);
+        g_source_set_callback (resource_browser->priv->timeout_src,
+                               discovery_timeout,
+                               resource_browser, NULL);
+
+        g_source_attach (resource_browser->priv->timeout_src,
+                         g_main_context_get_thread_default ());
+
+        g_source_unref (resource_browser->priv->timeout_src);
+
+        /* Setup a set of responsive resources for cache refreshing */
+        resource_browser->priv->fresh_resources = g_hash_table_new_full
+                                        (g_str_hash,
+                                         g_str_equal,
+                                         g_free,
+                                         NULL);
 }
 
 /* Stops the sending of discovery messages */
 static void
 stop_discovery (GSSDPResourceBrowser *resource_browser)
 {
-        if (resource_browser->priv->timeout_id) {
-                g_source_remove (resource_browser->priv->timeout_id);
-                resource_browser->priv->timeout_id = 0;
+        if (resource_browser->priv->timeout_src) {
+                g_source_destroy (resource_browser->priv->timeout_src);
+                resource_browser->priv->timeout_src = NULL;
                 resource_browser->priv->num_discovery = 0;
         }
+        if (resource_browser->priv->refresh_cache_src) {
+                g_source_destroy (resource_browser->priv->refresh_cache_src);
+                resource_browser->priv->refresh_cache_src = NULL;
+        }
+        if (resource_browser->priv->fresh_resources) {
+                g_hash_table_unref (resource_browser->priv->fresh_resources);
+                resource_browser->priv->fresh_resources = NULL;
+        }
+}
+
+static gboolean
+refresh_cache_helper (gpointer key, gpointer value, gpointer data)
+{
+        Resource *resource;
+        GHashTable *fresh_resources;
+
+        resource = value;
+        fresh_resources = data;
+
+        if (g_hash_table_lookup_extended (fresh_resources, key, NULL, NULL))
+                return FALSE;
+        else {
+                g_signal_emit (resource->resource_browser,
+                               signals[RESOURCE_UNAVAILABLE],
+                               0,
+                               resource->usn);
+
+                return TRUE;
+        }
+}
+
+/* Removes non-responsive resources */
+static gboolean
+refresh_cache (gpointer data)
+{
+        GSSDPResourceBrowser *resource_browser;
+
+        resource_browser = GSSDP_RESOURCE_BROWSER (data);
+        g_hash_table_foreach_remove (resource_browser->priv->resources,
+                                     refresh_cache_helper,
+                                     resource_browser->priv->fresh_resources);
+        g_hash_table_unref (resource_browser->priv->fresh_resources);
+        resource_browser->priv->fresh_resources = NULL;
+        resource_browser->priv->refresh_cache_src = NULL;
+
+        return FALSE;
 }