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