2007-06-30 Jorn Baayen <jorn@openedhand.com>
[profile/ivi/GSSDP.git] / libgssdp / gssdp-resource-browser.c
1 /* 
2  * Copyright (C) 2006, 2007 OpenedHand Ltd.
3  *
4  * Author: Jorn Baayen <jorn@openedhand.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 /**
23  * SECTION:gssdp-resource-browser
24  * @short_description: Class handling resource discovery.
25  *
26  * #GUPnPResourceBrowser handles resource discovery. After creating a browser
27  * and activating it, the ::resource-available and ::resource-unavailable
28  * signals will be emitted whenever the availability of a resource matching the
29  * specified discovery target changes. A discovery request is sent out
30  * automatically when activating the browser.
31  */
32
33 #include <config.h>
34 #include <libsoup/soup-date.h>
35 #include <string.h>
36 #include <stdio.h>
37
38 #include "gssdp-resource-browser.h"
39 #include "gssdp-client-private.h"
40 #include "gssdp-protocol.h"
41 #include "gssdp-marshal.h"
42
43 G_DEFINE_TYPE (GSSDPResourceBrowser,
44                gssdp_resource_browser,
45                G_TYPE_OBJECT);
46
47 struct _GSSDPResourceBrowserPrivate {
48         GSSDPClient *client;
49
50         char        *target;
51
52         gushort      mx;
53
54         gboolean     active;
55
56         gulong       message_received_id;
57
58         GHashTable  *resources;
59 };
60
61 enum {
62         PROP_0,
63         PROP_CLIENT,
64         PROP_TARGET,
65         PROP_MX,
66         PROP_ACTIVE
67 };
68
69 enum {
70         RESOURCE_AVAILABLE,
71         RESOURCE_UNAVAILABLE,
72         LAST_SIGNAL
73 };
74
75 static guint signals[LAST_SIGNAL];
76
77 typedef struct {
78         GSSDPResourceBrowser *resource_browser;
79         char                 *usn;
80         guint                 timeout_id;
81 } Resource;
82
83 /* Function prototypes */
84 static void
85 gssdp_resource_browser_set_client (GSSDPResourceBrowser *resource_browser,
86                                    GSSDPClient          *client);
87 static void
88 message_received_cb              (GSSDPClient          *client,
89                                   const char           *from_ip,
90                                   _GSSDPMessageType     type,
91                                   GHashTable           *headers,
92                                   gpointer              user_data);
93 static void
94 resource_free                    (gpointer              data);
95 static void
96 clear_cache                      (GSSDPResourceBrowser *resource_browser);
97
98 static void
99 gssdp_resource_browser_init (GSSDPResourceBrowser *resource_browser)
100 {
101         resource_browser->priv = G_TYPE_INSTANCE_GET_PRIVATE
102                                         (resource_browser,
103                                          GSSDP_TYPE_RESOURCE_BROWSER,
104                                          GSSDPResourceBrowserPrivate);
105
106         resource_browser->priv->mx = SSDP_DEFAULT_MX;
107
108         resource_browser->priv->resources =
109                 g_hash_table_new_full (g_str_hash,
110                                        g_str_equal,
111                                        NULL,
112                                        resource_free);
113 }
114
115 static void
116 gssdp_resource_browser_get_property (GObject    *object,
117                                      guint       property_id,
118                                      GValue     *value,
119                                      GParamSpec *pspec)
120 {
121         GSSDPResourceBrowser *resource_browser;
122
123         resource_browser = GSSDP_RESOURCE_BROWSER (object);
124
125         switch (property_id) {
126         case PROP_CLIENT:
127                 g_value_set_object
128                         (value,
129                          gssdp_resource_browser_get_client (resource_browser));
130                 break;
131         case PROP_TARGET:
132                 g_value_set_string
133                         (value,
134                          gssdp_resource_browser_get_target (resource_browser));
135                 break;
136         case PROP_MX:
137                 g_value_set_uint
138                         (value,
139                          gssdp_resource_browser_get_mx (resource_browser));
140                 break;
141         case PROP_ACTIVE:
142                 g_value_set_boolean
143                         (value,
144                          gssdp_resource_browser_get_active (resource_browser));
145                 break;
146         default:
147                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
148                 break;
149         }
150 }
151
152 static void
153 gssdp_resource_browser_set_property (GObject      *object,
154                                      guint         property_id,
155                                      const GValue *value,
156                                      GParamSpec   *pspec)
157 {
158         GSSDPResourceBrowser *resource_browser;
159
160         resource_browser = GSSDP_RESOURCE_BROWSER (object);
161
162         switch (property_id) {
163         case PROP_CLIENT:
164                 gssdp_resource_browser_set_client (resource_browser,
165                                                    g_value_get_object (value));
166                 break;
167         case PROP_TARGET:
168                 gssdp_resource_browser_set_target (resource_browser,
169                                                    g_value_get_string (value));
170                 break;
171         case PROP_MX:
172                 gssdp_resource_browser_set_mx (resource_browser,
173                                                g_value_get_uint (value));
174                 break;
175         case PROP_ACTIVE:
176                 gssdp_resource_browser_set_active (resource_browser,
177                                                    g_value_get_boolean (value));
178                 break;
179         default:
180                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
181                 break;
182         }
183 }
184
185 static void
186 gssdp_resource_browser_dispose (GObject *object)
187 {
188         GSSDPResourceBrowser *resource_browser;
189
190         resource_browser = GSSDP_RESOURCE_BROWSER (object);
191
192         if (resource_browser->priv->client) {
193                 if (g_signal_handler_is_connected
194                         (resource_browser->priv->client,
195                          resource_browser->priv->message_received_id)) {
196                         g_signal_handler_disconnect
197                                 (resource_browser->priv->client,
198                                  resource_browser->priv->message_received_id);
199                 }
200                                                    
201                 g_object_unref (resource_browser->priv->client);
202                 resource_browser->priv->client = NULL;
203         }
204
205         clear_cache (resource_browser);
206 }
207
208 static void
209 gssdp_resource_browser_finalize (GObject *object)
210 {
211         GSSDPResourceBrowser *resource_browser;
212
213         resource_browser = GSSDP_RESOURCE_BROWSER (object);
214
215         g_free (resource_browser->priv->target);
216
217         g_hash_table_destroy (resource_browser->priv->resources);
218 }
219
220 static void
221 gssdp_resource_browser_class_init (GSSDPResourceBrowserClass *klass)
222 {
223         GObjectClass *object_class;
224
225         object_class = G_OBJECT_CLASS (klass);
226
227         object_class->set_property = gssdp_resource_browser_set_property;
228         object_class->get_property = gssdp_resource_browser_get_property;
229         object_class->dispose      = gssdp_resource_browser_dispose;
230         object_class->finalize     = gssdp_resource_browser_finalize;
231
232         g_type_class_add_private (klass, sizeof (GSSDPResourceBrowserPrivate));
233
234         /**
235          * GSSDPResourceBrowser:client
236          *
237          * The #GSSDPClient to use.
238          **/
239         g_object_class_install_property
240                 (object_class,
241                  PROP_CLIENT,
242                  g_param_spec_object
243                          ("client",
244                           "Client",
245                           "The associated client.",
246                           GSSDP_TYPE_CLIENT,
247                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
248                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
249                           G_PARAM_STATIC_BLURB));
250
251         /**
252          * GSSDPResourceBrowser:target
253          *
254          * The discovery target.
255          **/
256         g_object_class_install_property
257                 (object_class,
258                  PROP_TARGET,
259                  g_param_spec_string
260                          ("target",
261                           "Target",
262                           "The discovery target.",
263                           NULL,
264                           G_PARAM_READWRITE |
265                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
266                           G_PARAM_STATIC_BLURB));
267
268         /**
269          * GSSDPResourceBrowser:mx
270          *
271          * The maximum number of seconds in which to request other parties
272          * to respond.
273          **/
274         g_object_class_install_property
275                 (object_class,
276                  PROP_MX,
277                  g_param_spec_uint
278                          ("mx",
279                           "MX",
280                           "The maximum number of seconds in which to request "
281                           "other parties to respond.",
282                           1,
283                           G_MAXUSHORT,
284                           SSDP_DEFAULT_MX,
285                           G_PARAM_READWRITE |
286                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
287                           G_PARAM_STATIC_BLURB));
288
289         /**
290          * GSSDPResourceBrowser:active
291          *
292          * Whether this browser is active or not.
293          **/
294         g_object_class_install_property
295                 (object_class,
296                  PROP_ACTIVE,
297                  g_param_spec_boolean
298                          ("active",
299                           "Active",
300                           "TRUE if the resource browser is active.",
301                           FALSE,
302                           G_PARAM_READWRITE |
303                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
304                           G_PARAM_STATIC_BLURB));
305
306         /**
307          * GSSDPResourceBrowser::resource-available
308          * @resource_browser: The #GSSDPResourceBrowser that received the
309          * signal
310          * @usn: The USN of the discovered resource
311          * @locations: A #GList of strings describing the locations of the
312          * discovered resource.
313          *
314          * The ::resource-available signal is emitted whenever a new resource
315          * has become available.
316          **/
317         signals[RESOURCE_AVAILABLE] =
318                 g_signal_new ("resource-available",
319                               GSSDP_TYPE_RESOURCE_BROWSER,
320                               G_SIGNAL_RUN_LAST,
321                               G_STRUCT_OFFSET (GSSDPResourceBrowserClass,
322                                                resource_available),
323                               NULL, NULL,
324                               gssdp_marshal_VOID__STRING_POINTER,
325                               G_TYPE_NONE,
326                               2,
327                               G_TYPE_STRING,
328                               G_TYPE_POINTER);
329
330         /**
331          * GSSDPResourceBrowser::resource-unavailable
332          * @resource_browser: The #GSSDPResourceBrowser that received the
333          * signal
334          * @usn: The USN of the resource
335          *
336          * The ::resource-unavailable signal is emitted whenever a resource
337          * is not available any more.
338          **/
339         signals[RESOURCE_UNAVAILABLE] =
340                 g_signal_new ("resource-unavailable",
341                               GSSDP_TYPE_RESOURCE_BROWSER,
342                               G_SIGNAL_RUN_LAST,
343                               G_STRUCT_OFFSET (GSSDPResourceBrowserClass,
344                                                resource_unavailable),
345                               NULL, NULL,
346                               gssdp_marshal_VOID__STRING,
347                               G_TYPE_NONE,
348                               1,
349                               G_TYPE_STRING);
350 }
351
352 /**
353  * gssdp_resource_browser_new
354  * @client: The #GSSDPClient to associate with
355  *
356  * Return value: A new #GSSDPResourceBrowser object.
357  **/
358 GSSDPResourceBrowser *
359 gssdp_resource_browser_new (GSSDPClient *client,
360                             const char  *target)
361 {
362         return g_object_new (GSSDP_TYPE_RESOURCE_BROWSER,
363                              "client", client,
364                              "target", target,
365                              NULL);
366 }
367
368 /**
369  * Sets the #GSSDPClient @resource_browser is associated with to @client
370  **/
371 static void
372 gssdp_resource_browser_set_client (GSSDPResourceBrowser *resource_browser,
373                                    GSSDPClient          *client)
374 {
375         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
376         g_return_if_fail (GSSDP_IS_CLIENT (client));
377
378         resource_browser->priv->client = g_object_ref (client);
379
380         resource_browser->priv->message_received_id =
381                 g_signal_connect_object (resource_browser->priv->client,
382                                          "message-received",
383                                          G_CALLBACK (message_received_cb),
384                                          resource_browser,
385                                          0);
386
387         g_object_notify (G_OBJECT (resource_browser), "client");
388 }
389
390 /**
391  * gssdp_resource_browser_get_client
392  * @resource_browser: A #GSSDPResourceBrowser
393  *
394  * Return value: The #GSSDPClient @resource_browser is associated with.
395  **/
396 GSSDPClient *
397 gssdp_resource_browser_get_client (GSSDPResourceBrowser *resource_browser)
398 {
399         g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser),
400                               NULL);
401
402         return resource_browser->priv->client;
403 }
404
405 /**
406  * gssdp_resource_browser_set_target
407  * @resource_browser: A #GSSDPResourceBrowser
408  * @target: The browser target
409  *
410  * Sets the browser target of @resource_browser to @target.
411  **/
412 void
413 gssdp_resource_browser_set_target (GSSDPResourceBrowser *resource_browser,
414                                    const char           *target)
415 {
416         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
417         g_return_if_fail (target != NULL);
418         g_return_if_fail (!resource_browser->priv->active);
419         
420         g_free (resource_browser->priv->target);
421         resource_browser->priv->target = g_strdup (target);
422
423         g_object_notify (G_OBJECT (resource_browser), "target");
424 }
425
426 /**
427  * gssdp_resource_browser_get_target
428  * @resource_browser: A #GSSDPResourceBrowser
429  *
430  * Return value: The browser target.
431  **/
432 const char *
433 gssdp_resource_browser_get_target (GSSDPResourceBrowser *resource_browser)
434 {
435         g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser),
436                               NULL);
437
438         return resource_browser->priv->target;
439 }
440
441 /**
442  * gssdp_resource_browser_set_mx
443  * @resource_browser: A #GSSDPResourceBrowser
444  * @mx: The to be used MX value
445  *
446  * Sets the used MX value of @resource_browser to @mx.
447  **/
448 void
449 gssdp_resource_browser_set_mx (GSSDPResourceBrowser *resource_browser,
450                                gushort               mx)
451 {
452         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
453
454         if (resource_browser->priv->mx == mx)
455                 return;
456
457         resource_browser->priv->mx = mx;
458         
459         g_object_notify (G_OBJECT (resource_browser), "mx");
460 }
461
462 /**
463  * gssdp_resource_browser_get_mx
464  * @resource_browser: A #GSSDPResourceBrowser
465  *
466  * Return value: The used MX value.
467  **/
468 gushort
469 gssdp_resource_browser_get_mx (GSSDPResourceBrowser *resource_browser)
470 {
471         g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser), 0);
472
473         return resource_browser->priv->mx;
474 }
475
476 /**
477  * gssdp_resource_browser_set_active
478  * @resource_browser: A #GSSDPResourceBrowser
479  * @active: TRUE to activate @resource_browser
480  *
481  * (De)activates @resource_browser.
482  **/
483 void
484 gssdp_resource_browser_set_active (GSSDPResourceBrowser *resource_browser,
485                                    gboolean              active)
486 {
487         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
488
489         if (resource_browser->priv->active == active)
490                 return;
491
492         resource_browser->priv->active = active;
493
494         if (active) {
495                 /* Emit discovery message */
496                 char *message;
497
498                 message = g_strdup_printf (SSDP_DISCOVERY_REQUEST,
499                                            resource_browser->priv->target,
500                                            resource_browser->priv->mx);
501
502                 _gssdp_client_send_message (resource_browser->priv->client,
503                                             NULL,
504                                             message);
505
506                 g_free (message);
507         } else
508                 clear_cache (resource_browser);
509         
510         g_object_notify (G_OBJECT (resource_browser), "active");
511 }
512
513 /**
514  * gssdp_resource_browser_get_active
515  * @resource_browser: A #GSSDPResourceBrowser
516  *
517  * Return value: TRUE if @resource_browser is active.
518  **/
519 gboolean
520 gssdp_resource_browser_get_active (GSSDPResourceBrowser *resource_browser)
521 {
522         g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser), 0);
523
524         return resource_browser->priv->active;
525 }
526
527 /**
528  * Resource expired: Remove
529  **/
530 static gboolean
531 resource_expire (gpointer user_data)
532 {
533         Resource *resource;
534
535         resource = user_data;
536         
537         g_signal_emit (resource->resource_browser,
538                        signals[RESOURCE_UNAVAILABLE],
539                        0,
540                        resource->usn);
541
542         g_hash_table_remove (resource->resource_browser->priv->resources,
543                              resource->usn);
544
545         return FALSE;
546 }
547
548 static void
549 resource_available (GSSDPResourceBrowser *resource_browser,
550                     GHashTable           *headers)
551 {
552         GSList *list;
553         const char *usn;
554         Resource *resource;
555         gboolean was_cached;
556         guint timeout;
557         GList *locations;
558
559         list = g_hash_table_lookup (headers, "USN");
560         if (!list)
561                 return; /* No USN specified */
562         usn = list->data;
563
564         /* Get from cache, if possible */
565         resource = g_hash_table_lookup (resource_browser->priv->resources, usn);
566         if (resource) {
567                 /* Remove old timeout */
568                 g_source_remove (resource->timeout_id);
569
570                 was_cached = TRUE;
571         } else {
572                 /* Create new Resource data structure */
573                 resource = g_slice_new (Resource);
574
575                 resource->resource_browser = resource_browser;
576                 resource->usn              = g_strdup (usn);
577                 
578                 g_hash_table_insert (resource_browser->priv->resources,
579                                      resource->usn,
580                                      resource);
581                 
582                 was_cached = FALSE;
583         }
584
585         /* Calculate new timeout */
586         list = g_hash_table_lookup (headers, "Cache-Control");
587         if (list) {
588                 GSList *l;
589                 int res;
590
591                 res = 0;
592
593                 for (l = list; l; l = l->next) {
594                         res = sscanf (l->data,
595                                       "max-age = %d",
596                                       &timeout);
597                         if (res == 1)
598                                 break;
599                 }
600
601                 if (res != 1) {
602                         g_warning ("Invalid 'Cache-Control' header. Assuming "
603                                    "default max-age of %d.\n"
604                                    "Header was:\n%s",
605                                    SSDP_DEFAULT_MAX_AGE,
606                                    (char *) list->data);
607
608                         timeout = SSDP_DEFAULT_MAX_AGE;
609                 }
610         } else {
611                 list = g_hash_table_lookup (headers, "Expires");
612                 if (list) {
613                         time_t exp_time, cur_time;
614
615                         exp_time = soup_date_parse (list->data);
616                         cur_time = time (NULL);
617
618                         if (exp_time > cur_time)
619                                 timeout = exp_time - cur_time;
620                         else {
621                                 g_warning ("Invalid 'Expires' header. Assuming "
622                                            "default max-age of %d.\n"
623                                            "Header was:\n%s",
624                                            SSDP_DEFAULT_MAX_AGE,
625                                            (char *) list->data);
626
627                                 timeout = SSDP_DEFAULT_MAX_AGE;
628                         }
629                 } else {
630                         g_warning ("No 'Cache-Control' nor any 'Expires' "
631                                    "header was specified. Assuming default "
632                                    "max-age of %d.", SSDP_DEFAULT_MAX_AGE);
633
634                         timeout = SSDP_DEFAULT_MAX_AGE;
635                 }
636         }
637
638         resource->timeout_id = g_timeout_add (timeout * 1000,
639                                               resource_expire,
640                                               resource);
641
642         /* Only continue with signal emission if this resource was not
643          * cached already */
644         if (was_cached)
645                 return;
646
647         /* Build list of locations */
648         locations = NULL;
649
650         list = g_hash_table_lookup (headers, "Location");
651         if (list)
652                 locations = g_list_append (locations, g_strdup (list->data));
653
654         list = g_hash_table_lookup (headers, "AL");
655         if (list) {
656                 /* Parse AL header. The format is:
657                  * <uri1><uri2>... */
658                 char *start, *end, *uri;
659                 
660                 start = list->data;
661                 while ((start = strchr (start, '<'))) {
662                         start += 1;
663                         if (!start || !*start)
664                                 break;
665
666                         end = strchr (start, '>');
667                         if (!end || !*end)
668                                 break;
669
670                         uri = g_strndup (start, end - start);
671                         locations = g_list_append (locations, uri);
672
673                         start = end;
674                 }
675         }
676
677         /* Emit signal */
678         g_signal_emit (resource_browser,
679                        signals[RESOURCE_AVAILABLE],
680                        0,
681                        usn,
682                        locations);
683
684         /* Cleanup */
685         while (locations) {
686                 g_free (locations->data);
687
688                 locations = g_list_delete_link (locations, locations);
689         }
690 }
691
692 static void
693 resource_unavailable (GSSDPResourceBrowser *resource_browser,
694                       GHashTable           *headers)
695 {
696         GSList *list;
697         const char *usn;
698
699         list = g_hash_table_lookup (headers, "USN");
700         if (!list)
701                 return; /* No USN specified */
702         usn = list->data;
703
704         /* Only process if we were cached */
705         if (!g_hash_table_lookup (resource_browser->priv->resources, usn))
706                 return;
707
708         g_signal_emit (resource_browser,
709                        signals[RESOURCE_UNAVAILABLE],
710                        0,
711                        usn);
712
713         g_hash_table_remove (resource_browser->priv->resources, usn);
714 }
715
716 static void
717 received_discovery_response (GSSDPResourceBrowser *resource_browser,
718                              GHashTable           *headers)
719 {
720         GSList *list;
721
722         list = g_hash_table_lookup (headers, "ST");
723         if (!list)
724                 return; /* No target specified */
725
726         if (strcmp (resource_browser->priv->target, GSSDP_ALL_RESOURCES) != 0 &&
727             strcmp (resource_browser->priv->target, list->data) != 0)
728                 return; /* Target doesn't match */
729
730         resource_available (resource_browser, headers);
731 }
732
733 static void
734 received_announcement (GSSDPResourceBrowser *resource_browser,
735                        GHashTable           *headers)
736 {
737         GSList *list;
738
739         list = g_hash_table_lookup (headers, "NT");
740         if (!list)
741                 return; /* No target specified */
742
743         if (strcmp (resource_browser->priv->target, GSSDP_ALL_RESOURCES) != 0 &&
744             strcmp (resource_browser->priv->target, list->data) != 0)
745                 return; /* Target doesn't match */
746
747         list = g_hash_table_lookup (headers, "NTS");
748         if (!list)
749                 return; /* No announcement type specified */
750
751         /* Check announcement type */
752         if      (strncmp (list->data,
753                           SSDP_ALIVE_NTS,
754                           strlen (SSDP_ALIVE_NTS)) == 0)
755                 resource_available (resource_browser, headers);
756         else if (strncmp (list->data,
757                           SSDP_BYEBYE_NTS,
758                           strlen (SSDP_BYEBYE_NTS)) == 0)
759                 resource_unavailable (resource_browser, headers);
760 }
761
762 /**
763  * Received a message
764  **/
765 static void
766 message_received_cb (GSSDPClient      *client,
767                      const char       *from_ip,
768                      _GSSDPMessageType type,
769                      GHashTable       *headers,
770                      gpointer          user_data)
771 {
772         GSSDPResourceBrowser *resource_browser;
773
774         resource_browser = GSSDP_RESOURCE_BROWSER (user_data);
775
776         if (!resource_browser->priv->active)
777                 return;
778
779         switch (type) {
780         case _GSSDP_DISCOVERY_RESPONSE:
781                 received_discovery_response (resource_browser, headers);
782                 break;
783         case _GSSDP_ANNOUNCEMENT:
784                 received_announcement (resource_browser, headers);
785                 break;
786         default:
787                 break;
788         }
789 }
790
791 /**
792  * Free a Resource structure and its contained data
793  **/
794 static void
795 resource_free (gpointer data)
796 {
797         Resource *resource;
798
799         resource = data;
800
801         g_free (resource->usn);
802
803         g_source_remove (resource->timeout_id);
804
805         g_slice_free (Resource, resource);
806 }
807
808 static gboolean
809 clear_cache_helper (gpointer key, gpointer value, gpointer data)
810 {
811         Resource *resource;
812
813         resource = value;
814
815         g_signal_emit (resource->resource_browser,
816                        signals[RESOURCE_UNAVAILABLE],
817                        0,
818                        resource->usn);
819
820         return TRUE;
821 }
822
823 /**
824  * Clears the cached resources hash
825  **/
826 static void
827 clear_cache (GSSDPResourceBrowser *resource_browser)
828 {
829         /* Clear cache */
830         g_hash_table_foreach_remove (resource_browser->priv->resources,
831                                      clear_cache_helper,
832                                      NULL);
833 }