Fix typo in GSSDPResourceBroser docs
[profile/ivi/GSSDP.git] / libgssdp / gssdp-resource-browser.c
1 /* 
2  * Copyright (C) 2006, 2007, 2008 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  * #GSSDPResourceBrowser 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.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 #define MAX_DISCOVERY_MESSAGES 3
44 #define DISCOVERY_FREQUENCY    500 /* 500 ms */
45
46 G_DEFINE_TYPE (GSSDPResourceBrowser,
47                gssdp_resource_browser,
48                G_TYPE_OBJECT);
49
50 struct _GSSDPResourceBrowserPrivate {
51         GSSDPClient *client;
52
53         char        *target;
54         GRegex      *target_regex;
55
56         gushort      mx;
57
58         gboolean     active;
59
60         gulong       message_received_id;
61
62         GHashTable  *resources;
63                         
64         GSource     *timeout_src;
65         guint        num_discovery;
66 };
67
68 enum {
69         PROP_0,
70         PROP_CLIENT,
71         PROP_TARGET,
72         PROP_MX,
73         PROP_ACTIVE
74 };
75
76 enum {
77         RESOURCE_AVAILABLE,
78         RESOURCE_UNAVAILABLE,
79         LAST_SIGNAL
80 };
81
82 static guint signals[LAST_SIGNAL];
83
84 typedef struct {
85         GSSDPResourceBrowser *resource_browser;
86         char                 *usn;
87         GSource              *timeout_src;
88 } Resource;
89
90 /* Function prototypes */
91 static void
92 gssdp_resource_browser_set_client (GSSDPResourceBrowser *resource_browser,
93                                    GSSDPClient          *client);
94 static void
95 message_received_cb              (GSSDPClient          *client,
96                                   const char           *from_ip,
97                                   gushort               from_port,
98                                   _GSSDPMessageType     type,
99                                   SoupMessageHeaders   *headers,
100                                   gpointer              user_data);
101 static void
102 resource_free                    (gpointer              data);
103 static void
104 clear_cache                      (GSSDPResourceBrowser *resource_browser);
105 static void
106 send_discovery_request            (GSSDPResourceBrowser *resource_browser);
107 static gboolean
108 discovery_timeout                (gpointer              data);
109 static void
110 start_discovery                  (GSSDPResourceBrowser *resource_browser);
111 static void
112 stop_discovery                   (GSSDPResourceBrowser *resource_browser);
113
114 static void
115 gssdp_resource_browser_init (GSSDPResourceBrowser *resource_browser)
116 {
117         resource_browser->priv = G_TYPE_INSTANCE_GET_PRIVATE
118                                         (resource_browser,
119                                          GSSDP_TYPE_RESOURCE_BROWSER,
120                                          GSSDPResourceBrowserPrivate);
121
122         resource_browser->priv->mx = SSDP_DEFAULT_MX;
123
124         resource_browser->priv->resources =
125                 g_hash_table_new_full (g_str_hash,
126                                        g_str_equal,
127                                        NULL,
128                                        resource_free);
129 }
130
131 static void
132 gssdp_resource_browser_get_property (GObject    *object,
133                                      guint       property_id,
134                                      GValue     *value,
135                                      GParamSpec *pspec)
136 {
137         GSSDPResourceBrowser *resource_browser;
138
139         resource_browser = GSSDP_RESOURCE_BROWSER (object);
140
141         switch (property_id) {
142         case PROP_CLIENT:
143                 g_value_set_object
144                         (value,
145                          gssdp_resource_browser_get_client (resource_browser));
146                 break;
147         case PROP_TARGET:
148                 g_value_set_string
149                         (value,
150                          gssdp_resource_browser_get_target (resource_browser));
151                 break;
152         case PROP_MX:
153                 g_value_set_uint
154                         (value,
155                          gssdp_resource_browser_get_mx (resource_browser));
156                 break;
157         case PROP_ACTIVE:
158                 g_value_set_boolean
159                         (value,
160                          gssdp_resource_browser_get_active (resource_browser));
161                 break;
162         default:
163                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
164                 break;
165         }
166 }
167
168 static void
169 gssdp_resource_browser_set_property (GObject      *object,
170                                      guint         property_id,
171                                      const GValue *value,
172                                      GParamSpec   *pspec)
173 {
174         GSSDPResourceBrowser *resource_browser;
175
176         resource_browser = GSSDP_RESOURCE_BROWSER (object);
177
178         switch (property_id) {
179         case PROP_CLIENT:
180                 gssdp_resource_browser_set_client (resource_browser,
181                                                    g_value_get_object (value));
182                 break;
183         case PROP_TARGET:
184                 gssdp_resource_browser_set_target (resource_browser,
185                                                    g_value_get_string (value));
186                 break;
187         case PROP_MX:
188                 gssdp_resource_browser_set_mx (resource_browser,
189                                                g_value_get_uint (value));
190                 break;
191         case PROP_ACTIVE:
192                 gssdp_resource_browser_set_active (resource_browser,
193                                                    g_value_get_boolean (value));
194                 break;
195         default:
196                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
197                 break;
198         }
199 }
200
201 static void
202 gssdp_resource_browser_dispose (GObject *object)
203 {
204         GSSDPResourceBrowser *resource_browser;
205
206         resource_browser = GSSDP_RESOURCE_BROWSER (object);
207
208         if (resource_browser->priv->client) {
209                 if (g_signal_handler_is_connected
210                         (resource_browser->priv->client,
211                          resource_browser->priv->message_received_id)) {
212                         g_signal_handler_disconnect
213                                 (resource_browser->priv->client,
214                                  resource_browser->priv->message_received_id);
215                 }
216
217                 stop_discovery (resource_browser);
218
219                 g_object_unref (resource_browser->priv->client);
220                 resource_browser->priv->client = NULL;
221         }
222
223         clear_cache (resource_browser);
224
225         G_OBJECT_CLASS (gssdp_resource_browser_parent_class)->dispose (object);
226 }
227
228 static void
229 gssdp_resource_browser_finalize (GObject *object)
230 {
231         GSSDPResourceBrowser *resource_browser;
232
233         resource_browser = GSSDP_RESOURCE_BROWSER (object);
234
235         if (resource_browser->priv->target_regex)
236                 g_regex_unref (resource_browser->priv->target_regex);
237
238         g_free (resource_browser->priv->target);
239
240         g_hash_table_destroy (resource_browser->priv->resources);
241
242         G_OBJECT_CLASS (gssdp_resource_browser_parent_class)->finalize (object);
243 }
244
245 static void
246 gssdp_resource_browser_class_init (GSSDPResourceBrowserClass *klass)
247 {
248         GObjectClass *object_class;
249
250         object_class = G_OBJECT_CLASS (klass);
251
252         object_class->set_property = gssdp_resource_browser_set_property;
253         object_class->get_property = gssdp_resource_browser_get_property;
254         object_class->dispose      = gssdp_resource_browser_dispose;
255         object_class->finalize     = gssdp_resource_browser_finalize;
256
257         g_type_class_add_private (klass, sizeof (GSSDPResourceBrowserPrivate));
258
259         /**
260          * GSSDPResourceBrowser:client
261          *
262          * The #GSSDPClient to use.
263          **/
264         g_object_class_install_property
265                 (object_class,
266                  PROP_CLIENT,
267                  g_param_spec_object
268                          ("client",
269                           "Client",
270                           "The associated client.",
271                           GSSDP_TYPE_CLIENT,
272                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
273                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
274                           G_PARAM_STATIC_BLURB));
275
276         /**
277          * GSSDPResourceBrowser:target
278          *
279          * The discovery target.
280          **/
281         g_object_class_install_property
282                 (object_class,
283                  PROP_TARGET,
284                  g_param_spec_string
285                          ("target",
286                           "Target",
287                           "The discovery target.",
288                           NULL,
289                           G_PARAM_READWRITE |
290                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
291                           G_PARAM_STATIC_BLURB));
292
293         /**
294          * GSSDPResourceBrowser:mx
295          *
296          * The maximum number of seconds in which to request other parties
297          * to respond.
298          **/
299         g_object_class_install_property
300                 (object_class,
301                  PROP_MX,
302                  g_param_spec_uint
303                          ("mx",
304                           "MX",
305                           "The maximum number of seconds in which to request "
306                           "other parties to respond.",
307                           1,
308                           G_MAXUSHORT,
309                           SSDP_DEFAULT_MX,
310                           G_PARAM_READWRITE |
311                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
312                           G_PARAM_STATIC_BLURB));
313
314         /**
315          * GSSDPResourceBrowser:active
316          *
317          * Whether this browser is active or not.
318          **/
319         g_object_class_install_property
320                 (object_class,
321                  PROP_ACTIVE,
322                  g_param_spec_boolean
323                          ("active",
324                           "Active",
325                           "TRUE if the resource browser is active.",
326                           FALSE,
327                           G_PARAM_READWRITE |
328                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
329                           G_PARAM_STATIC_BLURB));
330
331         /**
332          * GSSDPResourceBrowser::resource-available
333          * @resource_browser: The #GSSDPResourceBrowser that received the
334          * signal
335          * @usn: The USN of the discovered resource
336          * @locations: (type GList*) (transfer none) (element-type utf8): A #GList of strings describing the locations of the
337          * discovered resource.
338          *
339          * The ::resource-available signal is emitted whenever a new resource
340          * has become available.
341          **/
342         signals[RESOURCE_AVAILABLE] =
343                 g_signal_new ("resource-available",
344                               GSSDP_TYPE_RESOURCE_BROWSER,
345                               G_SIGNAL_RUN_LAST,
346                               G_STRUCT_OFFSET (GSSDPResourceBrowserClass,
347                                                resource_available),
348                               NULL, NULL,
349                               gssdp_marshal_VOID__STRING_POINTER,
350                               G_TYPE_NONE,
351                               2,
352                               G_TYPE_STRING,
353                               G_TYPE_POINTER);
354
355         /**
356          * GSSDPResourceBrowser::resource-unavailable
357          * @resource_browser: The #GSSDPResourceBrowser that received the
358          * signal
359          * @usn: The USN of the resource
360          *
361          * The ::resource-unavailable signal is emitted whenever a resource
362          * is not available any more.
363          **/
364         signals[RESOURCE_UNAVAILABLE] =
365                 g_signal_new ("resource-unavailable",
366                               GSSDP_TYPE_RESOURCE_BROWSER,
367                               G_SIGNAL_RUN_LAST,
368                               G_STRUCT_OFFSET (GSSDPResourceBrowserClass,
369                                                resource_unavailable),
370                               NULL, NULL,
371                               gssdp_marshal_VOID__STRING,
372                               G_TYPE_NONE,
373                               1,
374                               G_TYPE_STRING);
375 }
376
377 /**
378  * gssdp_resource_browser_new
379  * @client: The #GSSDPClient to associate with
380  *
381  * Return value: A new #GSSDPResourceBrowser object.
382  **/
383 GSSDPResourceBrowser *
384 gssdp_resource_browser_new (GSSDPClient *client,
385                             const char  *target)
386 {
387         return g_object_new (GSSDP_TYPE_RESOURCE_BROWSER,
388                              "client", client,
389                              "target", target,
390                              NULL);
391 }
392
393 /*
394  * Sets the #GSSDPClient @resource_browser is associated with to @client
395  */
396 static void
397 gssdp_resource_browser_set_client (GSSDPResourceBrowser *resource_browser,
398                                    GSSDPClient          *client)
399 {
400         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
401         g_return_if_fail (GSSDP_IS_CLIENT (client));
402
403         resource_browser->priv->client = g_object_ref (client);
404
405         resource_browser->priv->message_received_id =
406                 g_signal_connect_object (resource_browser->priv->client,
407                                          "message-received",
408                                          G_CALLBACK (message_received_cb),
409                                          resource_browser,
410                                          0);
411
412         g_object_notify (G_OBJECT (resource_browser), "client");
413 }
414
415 /**
416  * gssdp_resource_browser_get_client
417  * @resource_browser: A #GSSDPResourceBrowser
418  *
419  * Returns: (transfer none): The #GSSDPClient @resource_browser is associated with.
420  **/
421 GSSDPClient *
422 gssdp_resource_browser_get_client (GSSDPResourceBrowser *resource_browser)
423 {
424         g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser),
425                               NULL);
426
427         return resource_browser->priv->client;
428 }
429
430 /**
431  * gssdp_resource_browser_set_target
432  * @resource_browser: A #GSSDPResourceBrowser
433  * @target: The browser target
434  *
435  * Sets the browser target of @resource_browser to @target.
436  **/
437 void
438 gssdp_resource_browser_set_target (GSSDPResourceBrowser *resource_browser,
439                                    const char           *target)
440 {
441         char *pattern;
442         char *version;
443         char *version_pattern;
444         GError *error;
445
446         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
447         g_return_if_fail (target != NULL);
448         g_return_if_fail (!resource_browser->priv->active);
449         
450         g_free (resource_browser->priv->target);
451         resource_browser->priv->target = g_strdup (target);
452
453         if (resource_browser->priv->target_regex)
454                 g_regex_unref (resource_browser->priv->target_regex);
455
456         version_pattern = "[0-9]+";
457         /* Make sure we have enough room for version pattern */
458         pattern = g_strndup (target,
459                              strlen (target) + strlen (version_pattern));
460
461         version = g_strrstr (pattern, ":");
462         if (version != NULL &&
463             (g_strstr_len (pattern, -1, "uuid:") != pattern ||
464              version != g_strstr_len (pattern, -1, ":")) &&
465             g_regex_match_simple (version_pattern,
466                                   version + 1,
467                                   G_REGEX_MATCH_ANCHORED,
468                                   0)) {
469                 strcpy (version + 1, version_pattern);
470         }
471
472         error = NULL;
473         resource_browser->priv->target_regex = g_regex_new (pattern,
474                                                             0,
475                                                             0,
476                                                             &error);
477         if (error) {
478                 g_warning ("Error compiling regular expression '%s': %s",
479                            pattern,
480                            error->message);
481
482                 g_error_free (error);
483         }
484
485         g_free (pattern);
486         g_object_notify (G_OBJECT (resource_browser), "target");
487 }
488
489 /**
490  * gssdp_resource_browser_get_target
491  * @resource_browser: A #GSSDPResourceBrowser
492  *
493  * Return value: The browser target.
494  **/
495 const char *
496 gssdp_resource_browser_get_target (GSSDPResourceBrowser *resource_browser)
497 {
498         g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser),
499                               NULL);
500
501         return resource_browser->priv->target;
502 }
503
504 /**
505  * gssdp_resource_browser_set_mx
506  * @resource_browser: A #GSSDPResourceBrowser
507  * @mx: The to be used MX value
508  *
509  * Sets the used MX value of @resource_browser to @mx.
510  **/
511 void
512 gssdp_resource_browser_set_mx (GSSDPResourceBrowser *resource_browser,
513                                gushort               mx)
514 {
515         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
516
517         if (resource_browser->priv->mx == mx)
518                 return;
519
520         resource_browser->priv->mx = mx;
521         
522         g_object_notify (G_OBJECT (resource_browser), "mx");
523 }
524
525 /**
526  * gssdp_resource_browser_get_mx
527  * @resource_browser: A #GSSDPResourceBrowser
528  *
529  * Return value: The used MX value.
530  **/
531 gushort
532 gssdp_resource_browser_get_mx (GSSDPResourceBrowser *resource_browser)
533 {
534         g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser), 0);
535
536         return resource_browser->priv->mx;
537 }
538
539 /**
540  * gssdp_resource_browser_set_active
541  * @resource_browser: A #GSSDPResourceBrowser
542  * @active: TRUE to activate @resource_browser
543  *
544  * (De)activates @resource_browser.
545  **/
546 void
547 gssdp_resource_browser_set_active (GSSDPResourceBrowser *resource_browser,
548                                    gboolean              active)
549 {
550         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
551
552         if (resource_browser->priv->active == active)
553                 return;
554
555         resource_browser->priv->active = active;
556
557         if (active) {
558                 start_discovery (resource_browser);
559         } else {
560                 stop_discovery (resource_browser);
561
562                 clear_cache (resource_browser);
563         }
564         
565         g_object_notify (G_OBJECT (resource_browser), "active");
566 }
567
568 /**
569  * gssdp_resource_browser_get_active
570  * @resource_browser: A #GSSDPResourceBrowser
571  *
572  * Return value: TRUE if @resource_browser is active.
573  **/
574 gboolean
575 gssdp_resource_browser_get_active (GSSDPResourceBrowser *resource_browser)
576 {
577         g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser), 0);
578
579         return resource_browser->priv->active;
580 }
581
582 /*
583  * Resource expired: Remove
584  */
585 static gboolean
586 resource_expire (gpointer user_data)
587 {
588         GSSDPResourceBrowser *resource_browser;
589         Resource *resource;
590         char *usn;
591
592         resource = user_data;
593         resource_browser = resource->resource_browser;
594
595         /* Steal the USN pointer from the resource as we need it for the signal
596          * emission.
597          */
598         usn = resource->usn;
599         resource->usn = NULL;
600
601         g_hash_table_remove (resource->resource_browser->priv->resources, usn);
602
603         g_signal_emit (resource_browser,
604                        signals[RESOURCE_UNAVAILABLE],
605                        0,
606                        usn);
607         g_free (usn);
608
609         return FALSE;
610 }
611
612 static void
613 resource_available (GSSDPResourceBrowser *resource_browser,
614                     SoupMessageHeaders   *headers)
615 {
616         const char *usn;
617         const char *header;
618         Resource *resource;
619         gboolean was_cached;
620         guint timeout;
621         GList *locations;
622
623         usn = soup_message_headers_get_one (headers, "USN");
624         if (!usn)
625                 return; /* No USN specified */
626
627         /* Get from cache, if possible */
628         resource = g_hash_table_lookup (resource_browser->priv->resources, usn);
629         if (resource) {
630                 /* Remove old timeout */
631                 g_source_destroy (resource->timeout_src);
632
633                 was_cached = TRUE;
634         } else {
635                 /* Create new Resource data structure */
636                 resource = g_slice_new (Resource);
637
638                 resource->resource_browser = resource_browser;
639                 resource->usn              = g_strdup (usn);
640                 
641                 g_hash_table_insert (resource_browser->priv->resources,
642                                      resource->usn,
643                                      resource);
644                 
645                 was_cached = FALSE;
646         }
647
648         /* Calculate new timeout */
649         header = soup_message_headers_get_one (headers, "Cache-Control");
650         if (header) {
651                 GSList *list;
652                 int res;
653
654                 res = 0;
655
656                 for (list = soup_header_parse_list (header);
657                      list;
658                      list = list->next) {
659                         res = sscanf (list->data,
660                                       "max-age = %d",
661                                       &timeout);
662                         if (res == 1)
663                                 break;
664                 }
665
666                 if (res != 1) {
667                         g_warning ("Invalid 'Cache-Control' header. Assuming "
668                                    "default max-age of %d.\n"
669                                    "Header was:\n%s",
670                                    SSDP_DEFAULT_MAX_AGE,
671                                    header);
672
673                         timeout = SSDP_DEFAULT_MAX_AGE;
674                 }
675
676                 soup_header_free_list (list);
677         } else {
678                 const char *expires;
679
680                 expires = soup_message_headers_get_one (headers, "Expires");
681                 if (expires) {
682                         SoupDate *soup_exp_time;
683                         time_t exp_time, cur_time;
684
685                         soup_exp_time = soup_date_new_from_string (expires);
686                         exp_time = soup_date_to_time_t (soup_exp_time);
687                         soup_date_free (soup_exp_time);
688
689                         cur_time = time (NULL);
690
691                         if (exp_time > cur_time)
692                                 timeout = exp_time - cur_time;
693                         else {
694                                 g_warning ("Invalid 'Expires' header. Assuming "
695                                            "default max-age of %d.\n"
696                                            "Header was:\n%s",
697                                            SSDP_DEFAULT_MAX_AGE,
698                                            expires);
699
700                                 timeout = SSDP_DEFAULT_MAX_AGE;
701                         }
702                 } else {
703                         g_warning ("No 'Cache-Control' nor any 'Expires' "
704                                    "header was specified. Assuming default "
705                                    "max-age of %d.", SSDP_DEFAULT_MAX_AGE);
706
707                         timeout = SSDP_DEFAULT_MAX_AGE;
708                 }
709         }
710
711         resource->timeout_src = g_timeout_source_new_seconds (timeout);
712         g_source_set_callback (resource->timeout_src,
713                                resource_expire,
714                                resource, NULL);
715
716         g_source_attach (resource->timeout_src,
717                          g_main_context_get_thread_default ());
718
719         g_source_unref (resource->timeout_src);
720
721         /* Only continue with signal emission if this resource was not
722          * cached already */
723         if (was_cached)
724                 return;
725
726         /* Build list of locations */
727         locations = NULL;
728
729         header = soup_message_headers_get_one (headers, "Location");
730         if (header)
731                 locations = g_list_append (locations, g_strdup (header));
732
733         header = soup_message_headers_get_one (headers, "AL");
734         if (header) {
735                 /* Parse AL header. The format is:
736                  * <uri1><uri2>... */
737                 const char *start, *end;
738                 char *uri;
739                 
740                 start = header;
741                 while ((start = strchr (start, '<'))) {
742                         start += 1;
743                         if (!start || !*start)
744                                 break;
745
746                         end = strchr (start, '>');
747                         if (!end || !*end)
748                                 break;
749
750                         uri = g_strndup (start, end - start);
751                         locations = g_list_append (locations, uri);
752
753                         start = end;
754                 }
755         }
756
757         /* Emit signal */
758         g_signal_emit (resource_browser,
759                        signals[RESOURCE_AVAILABLE],
760                        0,
761                        usn,
762                        locations);
763
764         /* Cleanup */
765         while (locations) {
766                 g_free (locations->data);
767
768                 locations = g_list_delete_link (locations, locations);
769         }
770 }
771
772 static void
773 resource_unavailable (GSSDPResourceBrowser *resource_browser,
774                       SoupMessageHeaders   *headers)
775 {
776         const char *usn;
777
778         usn = soup_message_headers_get_one (headers, "USN");
779         if (!usn)
780                 return; /* No USN specified */
781
782         /* Only process if we were cached */
783         if (!g_hash_table_lookup (resource_browser->priv->resources, usn))
784                 return;
785
786         g_hash_table_remove (resource_browser->priv->resources, usn);
787
788         g_signal_emit (resource_browser,
789                        signals[RESOURCE_UNAVAILABLE],
790                        0,
791                        usn);
792 }
793
794 static gboolean
795 check_target_compat (GSSDPResourceBrowser *resource_browser,
796                      const char           *st)
797 {
798         return strcmp (resource_browser->priv->target,
799                        GSSDP_ALL_RESOURCES) == 0 ||
800                g_regex_match (resource_browser->priv->target_regex,
801                               st,
802                               0,
803                               NULL);
804 }
805
806 static void
807 received_discovery_response (GSSDPResourceBrowser *resource_browser,
808                              SoupMessageHeaders   *headers)
809 {
810         const char *st;
811
812         st = soup_message_headers_get_one (headers, "ST");
813         if (!st)
814                 return; /* No target specified */
815
816         if (!check_target_compat (resource_browser, st))
817                 return; /* Target doesn't match */
818
819         resource_available (resource_browser, headers);
820 }
821
822 static void
823 received_announcement (GSSDPResourceBrowser *resource_browser,
824                        SoupMessageHeaders   *headers)
825 {
826         const char *header;
827
828         header = soup_message_headers_get_one (headers, "NT");
829         if (!header)
830                 return; /* No target specified */
831
832         if (!check_target_compat (resource_browser, header))
833                 return; /* Target doesn't match */
834
835         header = soup_message_headers_get_one (headers, "NTS");
836         if (!header)
837                 return; /* No announcement type specified */
838
839         /* Check announcement type */
840         if      (strncmp (header,
841                           SSDP_ALIVE_NTS,
842                           strlen (SSDP_ALIVE_NTS)) == 0)
843                 resource_available (resource_browser, headers);
844         else if (strncmp (header,
845                           SSDP_BYEBYE_NTS,
846                           strlen (SSDP_BYEBYE_NTS)) == 0)
847                 resource_unavailable (resource_browser, headers);
848 }
849
850 /*
851  * Received a message
852  */
853 static void
854 message_received_cb (GSSDPClient        *client,
855                      const char         *from_ip,
856                      gushort             from_port,
857                      _GSSDPMessageType   type,
858                      SoupMessageHeaders *headers,
859                      gpointer            user_data)
860 {
861         GSSDPResourceBrowser *resource_browser;
862
863         resource_browser = GSSDP_RESOURCE_BROWSER (user_data);
864
865         if (!resource_browser->priv->active)
866                 return;
867
868         switch (type) {
869         case _GSSDP_DISCOVERY_RESPONSE:
870                 received_discovery_response (resource_browser, headers);
871                 break;
872         case _GSSDP_ANNOUNCEMENT:
873                 received_announcement (resource_browser, headers);
874                 break;
875         default:
876                 break;
877         }
878 }
879
880 /*
881  * Free a Resource structure and its contained data
882  */
883 static void
884 resource_free (gpointer data)
885 {
886         Resource *resource;
887
888         resource = data;
889
890         g_free (resource->usn);
891
892         g_source_destroy (resource->timeout_src);
893
894         g_slice_free (Resource, resource);
895 }
896
897 static gboolean
898 clear_cache_helper (gpointer key, gpointer value, gpointer data)
899 {
900         Resource *resource;
901
902         resource = value;
903
904         g_signal_emit (resource->resource_browser,
905                        signals[RESOURCE_UNAVAILABLE],
906                        0,
907                        resource->usn);
908
909         return TRUE;
910 }
911
912 /*
913  * Clears the cached resources hash
914  */
915 static void
916 clear_cache (GSSDPResourceBrowser *resource_browser)
917 {
918         /* Clear cache */
919         g_hash_table_foreach_remove (resource_browser->priv->resources,
920                                      clear_cache_helper,
921                                      NULL);
922 }
923
924 /* Sends discovery request */
925 static void
926 send_discovery_request (GSSDPResourceBrowser *resource_browser)
927 {
928         char *message;
929
930         message = g_strdup_printf (SSDP_DISCOVERY_REQUEST,
931                                    resource_browser->priv->target,
932                                    resource_browser->priv->mx,
933                                    g_get_application_name () ?: "");
934
935         _gssdp_client_send_message (resource_browser->priv->client,
936                                     NULL,
937                                     0,
938                                     message,
939                                     _GSSDP_DISCOVERY_REQUEST);
940
941         g_free (message);
942 }
943
944 static gboolean
945 discovery_timeout (gpointer data)
946 {
947         GSSDPResourceBrowser *resource_browser;
948
949         resource_browser = GSSDP_RESOURCE_BROWSER (data);
950
951         send_discovery_request (resource_browser);
952
953         resource_browser->priv->num_discovery += 1;
954
955         if (resource_browser->priv->num_discovery >= MAX_DISCOVERY_MESSAGES) {
956                 resource_browser->priv->timeout_src = NULL;
957                 resource_browser->priv->num_discovery = 0;
958
959                 return FALSE;
960         } else
961                 return TRUE;
962 }
963
964 /* Starts sending discovery requests */
965 static void
966 start_discovery (GSSDPResourceBrowser *resource_browser)
967 {
968         /* Send one now */
969         send_discovery_request (resource_browser);
970
971         /* And schedule the rest for later */
972         resource_browser->priv->num_discovery = 1;
973         resource_browser->priv->timeout_src =
974                 g_timeout_source_new (DISCOVERY_FREQUENCY);
975         g_source_set_callback (resource_browser->priv->timeout_src,
976                                discovery_timeout,
977                                resource_browser, NULL);
978
979         g_source_attach (resource_browser->priv->timeout_src,
980                          g_main_context_get_thread_default ());
981
982         g_source_unref (resource_browser->priv->timeout_src);
983 }
984
985 /* Stops the sending of discovery messages */
986 static void
987 stop_discovery (GSSDPResourceBrowser *resource_browser)
988 {
989         if (resource_browser->priv->timeout_src) {
990                 g_source_destroy (resource_browser->priv->timeout_src);
991                 resource_browser->priv->timeout_src = NULL;
992                 resource_browser->priv->num_discovery = 0;
993         }
994 }