Fix the lib path
[external/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  * #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.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: 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  * Return value: 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, ":") + 1;
462         if (version != NULL &&
463             g_regex_match_simple (version_pattern, version, 0, 0)) {
464                 strcpy (version, version_pattern);
465         }
466
467         error = NULL;
468         resource_browser->priv->target_regex = g_regex_new (pattern,
469                                                             0,
470                                                             0,
471                                                             &error);
472         if (error) {
473                 g_warning ("Error compiling regular expression '%s': %s",
474                            pattern,
475                            error->message);
476
477                 g_error_free (error);
478         }
479
480         g_free (pattern);
481         g_object_notify (G_OBJECT (resource_browser), "target");
482 }
483
484 /**
485  * gssdp_resource_browser_get_target
486  * @resource_browser: A #GSSDPResourceBrowser
487  *
488  * Return value: The browser target.
489  **/
490 const char *
491 gssdp_resource_browser_get_target (GSSDPResourceBrowser *resource_browser)
492 {
493         g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser),
494                               NULL);
495
496         return resource_browser->priv->target;
497 }
498
499 /**
500  * gssdp_resource_browser_set_mx
501  * @resource_browser: A #GSSDPResourceBrowser
502  * @mx: The to be used MX value
503  *
504  * Sets the used MX value of @resource_browser to @mx.
505  **/
506 void
507 gssdp_resource_browser_set_mx (GSSDPResourceBrowser *resource_browser,
508                                gushort               mx)
509 {
510         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
511
512         if (resource_browser->priv->mx == mx)
513                 return;
514
515         resource_browser->priv->mx = mx;
516         
517         g_object_notify (G_OBJECT (resource_browser), "mx");
518 }
519
520 /**
521  * gssdp_resource_browser_get_mx
522  * @resource_browser: A #GSSDPResourceBrowser
523  *
524  * Return value: The used MX value.
525  **/
526 gushort
527 gssdp_resource_browser_get_mx (GSSDPResourceBrowser *resource_browser)
528 {
529         g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser), 0);
530
531         return resource_browser->priv->mx;
532 }
533
534 /**
535  * gssdp_resource_browser_set_active
536  * @resource_browser: A #GSSDPResourceBrowser
537  * @active: TRUE to activate @resource_browser
538  *
539  * (De)activates @resource_browser.
540  **/
541 void
542 gssdp_resource_browser_set_active (GSSDPResourceBrowser *resource_browser,
543                                    gboolean              active)
544 {
545         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
546
547         if (resource_browser->priv->active == active)
548                 return;
549
550         resource_browser->priv->active = active;
551
552         if (active) {
553                 start_discovery (resource_browser);
554         } else {
555                 stop_discovery (resource_browser);
556
557                 clear_cache (resource_browser);
558         }
559         
560         g_object_notify (G_OBJECT (resource_browser), "active");
561 }
562
563 /**
564  * gssdp_resource_browser_get_active
565  * @resource_browser: A #GSSDPResourceBrowser
566  *
567  * Return value: TRUE if @resource_browser is active.
568  **/
569 gboolean
570 gssdp_resource_browser_get_active (GSSDPResourceBrowser *resource_browser)
571 {
572         g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser), 0);
573
574         return resource_browser->priv->active;
575 }
576
577 /**
578  * Resource expired: Remove
579  **/
580 static gboolean
581 resource_expire (gpointer user_data)
582 {
583         Resource *resource;
584         char *usn;
585
586         resource = user_data;
587
588         /* Steal the USN pointer from the resource as we need it for the signal
589          * emission.
590          */
591         usn = resource->usn;
592         resource->usn = NULL;
593
594         g_hash_table_remove (resource->resource_browser->priv->resources, usn);
595
596         g_signal_emit (resource->resource_browser,
597                        signals[RESOURCE_UNAVAILABLE],
598                        0,
599                        usn);
600         g_free (usn);
601
602         return FALSE;
603 }
604
605 static void
606 resource_available (GSSDPResourceBrowser *resource_browser,
607                     SoupMessageHeaders   *headers)
608 {
609         const char *usn;
610         const char *header;
611         Resource *resource;
612         gboolean was_cached;
613         guint timeout;
614         GList *locations;
615         GMainContext *context;
616
617         usn = soup_message_headers_get_one (headers, "USN");
618         if (!usn)
619                 return; /* No USN specified */
620
621         /* Get from cache, if possible */
622         resource = g_hash_table_lookup (resource_browser->priv->resources, usn);
623         if (resource) {
624                 /* Remove old timeout */
625                 g_source_destroy (resource->timeout_src);
626
627                 was_cached = TRUE;
628         } else {
629                 /* Create new Resource data structure */
630                 resource = g_slice_new (Resource);
631
632                 resource->resource_browser = resource_browser;
633                 resource->usn              = g_strdup (usn);
634                 
635                 g_hash_table_insert (resource_browser->priv->resources,
636                                      resource->usn,
637                                      resource);
638                 
639                 was_cached = FALSE;
640         }
641
642         /* Calculate new timeout */
643         header = soup_message_headers_get_one (headers, "Cache-Control");
644         if (header) {
645                 GSList *list;
646                 int res;
647
648                 res = 0;
649
650                 for (list = soup_header_parse_list (header);
651                      list;
652                      list = list->next) {
653                         res = sscanf (list->data,
654                                       "max-age = %d",
655                                       &timeout);
656                         if (res == 1)
657                                 break;
658                 }
659
660                 if (res != 1) {
661                         g_warning ("Invalid 'Cache-Control' header. Assuming "
662                                    "default max-age of %d.\n"
663                                    "Header was:\n%s",
664                                    SSDP_DEFAULT_MAX_AGE,
665                                    header);
666
667                         timeout = SSDP_DEFAULT_MAX_AGE;
668                 }
669
670                 soup_header_free_list (list);
671         } else {
672                 const char *expires;
673
674                 expires = soup_message_headers_get_one (headers, "Expires");
675                 if (expires) {
676                         SoupDate *soup_exp_time;
677                         time_t exp_time, cur_time;
678
679                         soup_exp_time = soup_date_new_from_string (expires);
680                         exp_time = soup_date_to_time_t (soup_exp_time);
681                         soup_date_free (soup_exp_time);
682
683                         cur_time = time (NULL);
684
685                         if (exp_time > cur_time)
686                                 timeout = exp_time - cur_time;
687                         else {
688                                 g_warning ("Invalid 'Expires' header. Assuming "
689                                            "default max-age of %d.\n"
690                                            "Header was:\n%s",
691                                            SSDP_DEFAULT_MAX_AGE,
692                                            expires);
693
694                                 timeout = SSDP_DEFAULT_MAX_AGE;
695                         }
696                 } else {
697                         g_warning ("No 'Cache-Control' nor any 'Expires' "
698                                    "header was specified. Assuming default "
699                                    "max-age of %d.", SSDP_DEFAULT_MAX_AGE);
700
701                         timeout = SSDP_DEFAULT_MAX_AGE;
702                 }
703         }
704
705         resource->timeout_src = g_timeout_source_new_seconds (timeout);
706         g_source_set_callback (resource->timeout_src,
707                                resource_expire,
708                                resource, NULL);
709
710         context = gssdp_client_get_main_context
711                 (resource_browser->priv->client);
712         g_source_attach (resource->timeout_src, context);
713
714         g_source_unref (resource->timeout_src);
715
716         /* Only continue with signal emission if this resource was not
717          * cached already */
718         if (was_cached)
719                 return;
720
721         /* Build list of locations */
722         locations = NULL;
723
724         header = soup_message_headers_get_one (headers, "Location");
725         if (header)
726                 locations = g_list_append (locations, g_strdup (header));
727
728         header = soup_message_headers_get_one (headers, "AL");
729         if (header) {
730                 /* Parse AL header. The format is:
731                  * <uri1><uri2>... */
732                 const char *start, *end;
733                 char *uri;
734                 
735                 start = header;
736                 while ((start = strchr (start, '<'))) {
737                         start += 1;
738                         if (!start || !*start)
739                                 break;
740
741                         end = strchr (start, '>');
742                         if (!end || !*end)
743                                 break;
744
745                         uri = g_strndup (start, end - start);
746                         locations = g_list_append (locations, uri);
747
748                         start = end;
749                 }
750         }
751
752         /* Emit signal */
753         g_signal_emit (resource_browser,
754                        signals[RESOURCE_AVAILABLE],
755                        0,
756                        usn,
757                        locations);
758
759         /* Cleanup */
760         while (locations) {
761                 g_free (locations->data);
762
763                 locations = g_list_delete_link (locations, locations);
764         }
765 }
766
767 static void
768 resource_unavailable (GSSDPResourceBrowser *resource_browser,
769                       SoupMessageHeaders   *headers)
770 {
771         const char *usn;
772
773         usn = soup_message_headers_get_one (headers, "USN");
774         if (!usn)
775                 return; /* No USN specified */
776
777         /* Only process if we were cached */
778         if (!g_hash_table_lookup (resource_browser->priv->resources, usn))
779                 return;
780
781         g_hash_table_remove (resource_browser->priv->resources, usn);
782
783         g_signal_emit (resource_browser,
784                        signals[RESOURCE_UNAVAILABLE],
785                        0,
786                        usn);
787 }
788
789 static gboolean
790 check_target_compat (GSSDPResourceBrowser *resource_browser,
791                      const char           *st)
792 {
793         return strcmp (resource_browser->priv->target,
794                        GSSDP_ALL_RESOURCES) == 0 ||
795                g_regex_match (resource_browser->priv->target_regex,
796                               st,
797                               0,
798                               NULL);
799 }
800
801 static void
802 received_discovery_response (GSSDPResourceBrowser *resource_browser,
803                              SoupMessageHeaders   *headers)
804 {
805         const char *st;
806
807         st = soup_message_headers_get_one (headers, "ST");
808         if (!st)
809                 return; /* No target specified */
810
811         if (!check_target_compat (resource_browser, st))
812                 return; /* Target doesn't match */
813
814         resource_available (resource_browser, headers);
815 }
816
817 static void
818 received_announcement (GSSDPResourceBrowser *resource_browser,
819                        SoupMessageHeaders   *headers)
820 {
821         const char *header;
822
823         header = soup_message_headers_get_one (headers, "NT");
824         if (!header)
825                 return; /* No target specified */
826
827         if (!check_target_compat (resource_browser, header))
828                 return; /* Target doesn't match */
829
830         header = soup_message_headers_get_one (headers, "NTS");
831         if (!header)
832                 return; /* No announcement type specified */
833
834         /* Check announcement type */
835         if      (strncmp (header,
836                           SSDP_ALIVE_NTS,
837                           strlen (SSDP_ALIVE_NTS)) == 0)
838                 resource_available (resource_browser, headers);
839         else if (strncmp (header,
840                           SSDP_BYEBYE_NTS,
841                           strlen (SSDP_BYEBYE_NTS)) == 0)
842                 resource_unavailable (resource_browser, headers);
843 }
844
845 /**
846  * Received a message
847  **/
848 static void
849 message_received_cb (GSSDPClient        *client,
850                      const char         *from_ip,
851                      gushort             from_port,
852                      _GSSDPMessageType   type,
853                      SoupMessageHeaders *headers,
854                      gpointer            user_data)
855 {
856         GSSDPResourceBrowser *resource_browser;
857
858         resource_browser = GSSDP_RESOURCE_BROWSER (user_data);
859
860         if (!resource_browser->priv->active)
861                 return;
862
863         switch (type) {
864         case _GSSDP_DISCOVERY_RESPONSE:
865                 received_discovery_response (resource_browser, headers);
866                 break;
867         case _GSSDP_ANNOUNCEMENT:
868                 received_announcement (resource_browser, headers);
869                 break;
870         default:
871                 break;
872         }
873 }
874
875 /**
876  * Free a Resource structure and its contained data
877  **/
878 static void
879 resource_free (gpointer data)
880 {
881         Resource *resource;
882
883         resource = data;
884
885         g_free (resource->usn);
886
887         g_source_destroy (resource->timeout_src);
888
889         g_slice_free (Resource, resource);
890 }
891
892 static gboolean
893 clear_cache_helper (gpointer key, gpointer value, gpointer data)
894 {
895         Resource *resource;
896
897         resource = value;
898
899         g_signal_emit (resource->resource_browser,
900                        signals[RESOURCE_UNAVAILABLE],
901                        0,
902                        resource->usn);
903
904         return TRUE;
905 }
906
907 /**
908  * Clears the cached resources hash
909  **/
910 static void
911 clear_cache (GSSDPResourceBrowser *resource_browser)
912 {
913         /* Clear cache */
914         g_hash_table_foreach_remove (resource_browser->priv->resources,
915                                      clear_cache_helper,
916                                      NULL);
917 }
918
919 /* Sends discovery request */
920 static void
921 send_discovery_request (GSSDPResourceBrowser *resource_browser)
922 {
923         char *message;
924
925         message = g_strdup_printf (SSDP_DISCOVERY_REQUEST,
926                                    resource_browser->priv->target,
927                                    resource_browser->priv->mx,
928                                    g_get_application_name () ?: "");
929
930         _gssdp_client_send_message (resource_browser->priv->client,
931                                     NULL,
932                                     0,
933                                     message);
934
935         g_free (message);
936 }
937
938 static gboolean
939 discovery_timeout (gpointer data)
940 {
941         GSSDPResourceBrowser *resource_browser;
942
943         resource_browser = GSSDP_RESOURCE_BROWSER (data);
944
945         send_discovery_request (resource_browser);
946
947         resource_browser->priv->num_discovery += 1;
948
949         if (resource_browser->priv->num_discovery >= MAX_DISCOVERY_MESSAGES) {
950                 resource_browser->priv->timeout_src = NULL;
951                 resource_browser->priv->num_discovery = 0;
952
953                 return FALSE;
954         } else
955                 return TRUE;
956 }
957
958 /* Starts sending discovery requests */
959 static void
960 start_discovery (GSSDPResourceBrowser *resource_browser)
961 {
962         GMainContext *context;
963
964         /* Send one now */
965         send_discovery_request (resource_browser);
966
967         /* And schedule the rest for later */
968         resource_browser->priv->num_discovery = 1;
969         resource_browser->priv->timeout_src =
970                 g_timeout_source_new (DISCOVERY_FREQUENCY);
971         g_source_set_callback (resource_browser->priv->timeout_src,
972                                discovery_timeout,
973                                resource_browser, NULL);
974
975         context = gssdp_client_get_main_context
976                 (resource_browser->priv->client);
977         g_source_attach (resource_browser->priv->timeout_src, context);
978
979         g_source_unref (resource_browser->priv->timeout_src);
980 }
981
982 /* Stops the sending of discovery messages */
983 static void
984 stop_discovery (GSSDPResourceBrowser *resource_browser)
985 {
986         if (resource_browser->priv->timeout_src) {
987                 g_source_destroy (resource_browser->priv->timeout_src);
988                 resource_browser->priv->timeout_src = NULL;
989                 resource_browser->priv->num_discovery = 0;
990         }
991 }