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