Fix all warnings from g-ir-scanner
[profile/ivi/GSSDP.git] / libgssdp / gssdp-resource-browser.c
1 /* 
2  * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
3  *
4  * Author: Jorn Baayen <jorn@openedhand.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 /**
23  * SECTION:gssdp-resource-browser
24  * @short_description: Class handling resource discovery.
25  *
26  * #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: (type GList*) (transfer none) (element-type utf8): A #GList of strings describing the locations of the
337          * discovered resource.
338          *
339          * The ::resource-available signal is emitted whenever a new resource
340          * has become available.
341          **/
342         signals[RESOURCE_AVAILABLE] =
343                 g_signal_new ("resource-available",
344                               GSSDP_TYPE_RESOURCE_BROWSER,
345                               G_SIGNAL_RUN_LAST,
346                               G_STRUCT_OFFSET (GSSDPResourceBrowserClass,
347                                                resource_available),
348                               NULL, NULL,
349                               gssdp_marshal_VOID__STRING_POINTER,
350                               G_TYPE_NONE,
351                               2,
352                               G_TYPE_STRING,
353                               G_TYPE_POINTER);
354
355         /**
356          * GSSDPResourceBrowser::resource-unavailable
357          * @resource_browser: The #GSSDPResourceBrowser that received the
358          * signal
359          * @usn: The USN of the resource
360          *
361          * The ::resource-unavailable signal is emitted whenever a resource
362          * is not available any more.
363          **/
364         signals[RESOURCE_UNAVAILABLE] =
365                 g_signal_new ("resource-unavailable",
366                               GSSDP_TYPE_RESOURCE_BROWSER,
367                               G_SIGNAL_RUN_LAST,
368                               G_STRUCT_OFFSET (GSSDPResourceBrowserClass,
369                                                resource_unavailable),
370                               NULL, NULL,
371                               gssdp_marshal_VOID__STRING,
372                               G_TYPE_NONE,
373                               1,
374                               G_TYPE_STRING);
375 }
376
377 /**
378  * gssdp_resource_browser_new
379  * @client: The #GSSDPClient to associate with
380  *
381  * Return value: A new #GSSDPResourceBrowser object.
382  **/
383 GSSDPResourceBrowser *
384 gssdp_resource_browser_new (GSSDPClient *client,
385                             const char  *target)
386 {
387         return g_object_new (GSSDP_TYPE_RESOURCE_BROWSER,
388                              "client", client,
389                              "target", target,
390                              NULL);
391 }
392
393 /**
394  * Sets the #GSSDPClient @resource_browser is associated with to @client
395  **/
396 static void
397 gssdp_resource_browser_set_client (GSSDPResourceBrowser *resource_browser,
398                                    GSSDPClient          *client)
399 {
400         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
401         g_return_if_fail (GSSDP_IS_CLIENT (client));
402
403         resource_browser->priv->client = g_object_ref (client);
404
405         resource_browser->priv->message_received_id =
406                 g_signal_connect_object (resource_browser->priv->client,
407                                          "message-received",
408                                          G_CALLBACK (message_received_cb),
409                                          resource_browser,
410                                          0);
411
412         g_object_notify (G_OBJECT (resource_browser), "client");
413 }
414
415 /**
416  * gssdp_resource_browser_get_client
417  * @resource_browser: A #GSSDPResourceBrowser
418  *
419  * Returns: (transfer none): The #GSSDPClient @resource_browser is associated with.
420  **/
421 GSSDPClient *
422 gssdp_resource_browser_get_client (GSSDPResourceBrowser *resource_browser)
423 {
424         g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser),
425                               NULL);
426
427         return resource_browser->priv->client;
428 }
429
430 /**
431  * gssdp_resource_browser_set_target
432  * @resource_browser: A #GSSDPResourceBrowser
433  * @target: The browser target
434  *
435  * Sets the browser target of @resource_browser to @target.
436  **/
437 void
438 gssdp_resource_browser_set_target (GSSDPResourceBrowser *resource_browser,
439                                    const char           *target)
440 {
441         char *pattern;
442         char *version;
443         char *version_pattern;
444         GError *error;
445
446         g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
447         g_return_if_fail (target != NULL);
448         g_return_if_fail (!resource_browser->priv->active);
449         
450         g_free (resource_browser->priv->target);
451         resource_browser->priv->target = g_strdup (target);
452
453         if (resource_browser->priv->target_regex)
454                 g_regex_unref (resource_browser->priv->target_regex);
455
456         version_pattern = "[0-9]+";
457         /* Make sure we have enough room for version pattern */
458         pattern = g_strndup (target,
459                              strlen (target) + strlen (version_pattern));
460
461         version = g_strrstr (pattern, ":");
462         if (version != NULL &&
463             g_regex_match_simple (version_pattern, version + 1, 0, 0)) {
464                 strcpy (version + 1, 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         GSSDPResourceBrowser *resource_browser;
584         Resource *resource;
585         char *usn;
586
587         resource = user_data;
588         resource_browser = resource->resource_browser;
589
590         /* Steal the USN pointer from the resource as we need it for the signal
591          * emission.
592          */
593         usn = resource->usn;
594         resource->usn = NULL;
595
596         g_hash_table_remove (resource->resource_browser->priv->resources, usn);
597
598         g_signal_emit (resource_browser,
599                        signals[RESOURCE_UNAVAILABLE],
600                        0,
601                        usn);
602         g_free (usn);
603
604         return FALSE;
605 }
606
607 static void
608 resource_available (GSSDPResourceBrowser *resource_browser,
609                     SoupMessageHeaders   *headers)
610 {
611         const char *usn;
612         const char *header;
613         Resource *resource;
614         gboolean was_cached;
615         guint timeout;
616         GList *locations;
617         GMainContext *context;
618
619         usn = soup_message_headers_get_one (headers, "USN");
620         if (!usn)
621                 return; /* No USN specified */
622
623         /* Get from cache, if possible */
624         resource = g_hash_table_lookup (resource_browser->priv->resources, usn);
625         if (resource) {
626                 /* Remove old timeout */
627                 g_source_destroy (resource->timeout_src);
628
629                 was_cached = TRUE;
630         } else {
631                 /* Create new Resource data structure */
632                 resource = g_slice_new (Resource);
633
634                 resource->resource_browser = resource_browser;
635                 resource->usn              = g_strdup (usn);
636                 
637                 g_hash_table_insert (resource_browser->priv->resources,
638                                      resource->usn,
639                                      resource);
640                 
641                 was_cached = FALSE;
642         }
643
644         /* Calculate new timeout */
645         header = soup_message_headers_get_one (headers, "Cache-Control");
646         if (header) {
647                 GSList *list;
648                 int res;
649
650                 res = 0;
651
652                 for (list = soup_header_parse_list (header);
653                      list;
654                      list = list->next) {
655                         res = sscanf (list->data,
656                                       "max-age = %d",
657                                       &timeout);
658                         if (res == 1)
659                                 break;
660                 }
661
662                 if (res != 1) {
663                         g_warning ("Invalid 'Cache-Control' header. Assuming "
664                                    "default max-age of %d.\n"
665                                    "Header was:\n%s",
666                                    SSDP_DEFAULT_MAX_AGE,
667                                    header);
668
669                         timeout = SSDP_DEFAULT_MAX_AGE;
670                 }
671
672                 soup_header_free_list (list);
673         } else {
674                 const char *expires;
675
676                 expires = soup_message_headers_get_one (headers, "Expires");
677                 if (expires) {
678                         SoupDate *soup_exp_time;
679                         time_t exp_time, cur_time;
680
681                         soup_exp_time = soup_date_new_from_string (expires);
682                         exp_time = soup_date_to_time_t (soup_exp_time);
683                         soup_date_free (soup_exp_time);
684
685                         cur_time = time (NULL);
686
687                         if (exp_time > cur_time)
688                                 timeout = exp_time - cur_time;
689                         else {
690                                 g_warning ("Invalid 'Expires' header. Assuming "
691                                            "default max-age of %d.\n"
692                                            "Header was:\n%s",
693                                            SSDP_DEFAULT_MAX_AGE,
694                                            expires);
695
696                                 timeout = SSDP_DEFAULT_MAX_AGE;
697                         }
698                 } else {
699                         g_warning ("No 'Cache-Control' nor any 'Expires' "
700                                    "header was specified. Assuming default "
701                                    "max-age of %d.", SSDP_DEFAULT_MAX_AGE);
702
703                         timeout = SSDP_DEFAULT_MAX_AGE;
704                 }
705         }
706
707         resource->timeout_src = g_timeout_source_new_seconds (timeout);
708         g_source_set_callback (resource->timeout_src,
709                                resource_expire,
710                                resource, NULL);
711
712         context = gssdp_client_get_main_context
713                 (resource_browser->priv->client);
714         g_source_attach (resource->timeout_src, context);
715
716         g_source_unref (resource->timeout_src);
717
718         /* Only continue with signal emission if this resource was not
719          * cached already */
720         if (was_cached)
721                 return;
722
723         /* Build list of locations */
724         locations = NULL;
725
726         header = soup_message_headers_get_one (headers, "Location");
727         if (header)
728                 locations = g_list_append (locations, g_strdup (header));
729
730         header = soup_message_headers_get_one (headers, "AL");
731         if (header) {
732                 /* Parse AL header. The format is:
733                  * <uri1><uri2>... */
734                 const char *start, *end;
735                 char *uri;
736                 
737                 start = header;
738                 while ((start = strchr (start, '<'))) {
739                         start += 1;
740                         if (!start || !*start)
741                                 break;
742
743                         end = strchr (start, '>');
744                         if (!end || !*end)
745                                 break;
746
747                         uri = g_strndup (start, end - start);
748                         locations = g_list_append (locations, uri);
749
750                         start = end;
751                 }
752         }
753
754         /* Emit signal */
755         g_signal_emit (resource_browser,
756                        signals[RESOURCE_AVAILABLE],
757                        0,
758                        usn,
759                        locations);
760
761         /* Cleanup */
762         while (locations) {
763                 g_free (locations->data);
764
765                 locations = g_list_delete_link (locations, locations);
766         }
767 }
768
769 static void
770 resource_unavailable (GSSDPResourceBrowser *resource_browser,
771                       SoupMessageHeaders   *headers)
772 {
773         const char *usn;
774
775         usn = soup_message_headers_get_one (headers, "USN");
776         if (!usn)
777                 return; /* No USN specified */
778
779         /* Only process if we were cached */
780         if (!g_hash_table_lookup (resource_browser->priv->resources, usn))
781                 return;
782
783         g_hash_table_remove (resource_browser->priv->resources, usn);
784
785         g_signal_emit (resource_browser,
786                        signals[RESOURCE_UNAVAILABLE],
787                        0,
788                        usn);
789 }
790
791 static gboolean
792 check_target_compat (GSSDPResourceBrowser *resource_browser,
793                      const char           *st)
794 {
795         return strcmp (resource_browser->priv->target,
796                        GSSDP_ALL_RESOURCES) == 0 ||
797                g_regex_match (resource_browser->priv->target_regex,
798                               st,
799                               0,
800                               NULL);
801 }
802
803 static void
804 received_discovery_response (GSSDPResourceBrowser *resource_browser,
805                              SoupMessageHeaders   *headers)
806 {
807         const char *st;
808
809         st = soup_message_headers_get_one (headers, "ST");
810         if (!st)
811                 return; /* No target specified */
812
813         if (!check_target_compat (resource_browser, st))
814                 return; /* Target doesn't match */
815
816         resource_available (resource_browser, headers);
817 }
818
819 static void
820 received_announcement (GSSDPResourceBrowser *resource_browser,
821                        SoupMessageHeaders   *headers)
822 {
823         const char *header;
824
825         header = soup_message_headers_get_one (headers, "NT");
826         if (!header)
827                 return; /* No target specified */
828
829         if (!check_target_compat (resource_browser, header))
830                 return; /* Target doesn't match */
831
832         header = soup_message_headers_get_one (headers, "NTS");
833         if (!header)
834                 return; /* No announcement type specified */
835
836         /* Check announcement type */
837         if      (strncmp (header,
838                           SSDP_ALIVE_NTS,
839                           strlen (SSDP_ALIVE_NTS)) == 0)
840                 resource_available (resource_browser, headers);
841         else if (strncmp (header,
842                           SSDP_BYEBYE_NTS,
843                           strlen (SSDP_BYEBYE_NTS)) == 0)
844                 resource_unavailable (resource_browser, headers);
845 }
846
847 /**
848  * Received a message
849  **/
850 static void
851 message_received_cb (GSSDPClient        *client,
852                      const char         *from_ip,
853                      gushort             from_port,
854                      _GSSDPMessageType   type,
855                      SoupMessageHeaders *headers,
856                      gpointer            user_data)
857 {
858         GSSDPResourceBrowser *resource_browser;
859
860         resource_browser = GSSDP_RESOURCE_BROWSER (user_data);
861
862         if (!resource_browser->priv->active)
863                 return;
864
865         switch (type) {
866         case _GSSDP_DISCOVERY_RESPONSE:
867                 received_discovery_response (resource_browser, headers);
868                 break;
869         case _GSSDP_ANNOUNCEMENT:
870                 received_announcement (resource_browser, headers);
871                 break;
872         default:
873                 break;
874         }
875 }
876
877 /**
878  * Free a Resource structure and its contained data
879  **/
880 static void
881 resource_free (gpointer data)
882 {
883         Resource *resource;
884
885         resource = data;
886
887         g_free (resource->usn);
888
889         g_source_destroy (resource->timeout_src);
890
891         g_slice_free (Resource, resource);
892 }
893
894 static gboolean
895 clear_cache_helper (gpointer key, gpointer value, gpointer data)
896 {
897         Resource *resource;
898
899         resource = value;
900
901         g_signal_emit (resource->resource_browser,
902                        signals[RESOURCE_UNAVAILABLE],
903                        0,
904                        resource->usn);
905
906         return TRUE;
907 }
908
909 /**
910  * Clears the cached resources hash
911  **/
912 static void
913 clear_cache (GSSDPResourceBrowser *resource_browser)
914 {
915         /* Clear cache */
916         g_hash_table_foreach_remove (resource_browser->priv->resources,
917                                      clear_cache_helper,
918                                      NULL);
919 }
920
921 /* Sends discovery request */
922 static void
923 send_discovery_request (GSSDPResourceBrowser *resource_browser)
924 {
925         char *message;
926
927         message = g_strdup_printf (SSDP_DISCOVERY_REQUEST,
928                                    resource_browser->priv->target,
929                                    resource_browser->priv->mx,
930                                    g_get_application_name () ?: "");
931
932         _gssdp_client_send_message (resource_browser->priv->client,
933                                     NULL,
934                                     0,
935                                     message);
936
937         g_free (message);
938 }
939
940 static gboolean
941 discovery_timeout (gpointer data)
942 {
943         GSSDPResourceBrowser *resource_browser;
944
945         resource_browser = GSSDP_RESOURCE_BROWSER (data);
946
947         send_discovery_request (resource_browser);
948
949         resource_browser->priv->num_discovery += 1;
950
951         if (resource_browser->priv->num_discovery >= MAX_DISCOVERY_MESSAGES) {
952                 resource_browser->priv->timeout_src = NULL;
953                 resource_browser->priv->num_discovery = 0;
954
955                 return FALSE;
956         } else
957                 return TRUE;
958 }
959
960 /* Starts sending discovery requests */
961 static void
962 start_discovery (GSSDPResourceBrowser *resource_browser)
963 {
964         GMainContext *context;
965
966         /* Send one now */
967         send_discovery_request (resource_browser);
968
969         /* And schedule the rest for later */
970         resource_browser->priv->num_discovery = 1;
971         resource_browser->priv->timeout_src =
972                 g_timeout_source_new (DISCOVERY_FREQUENCY);
973         g_source_set_callback (resource_browser->priv->timeout_src,
974                                discovery_timeout,
975                                resource_browser, NULL);
976
977         context = gssdp_client_get_main_context
978                 (resource_browser->priv->client);
979         g_source_attach (resource_browser->priv->timeout_src, context);
980
981         g_source_unref (resource_browser->priv->timeout_src);
982 }
983
984 /* Stops the sending of discovery messages */
985 static void
986 stop_discovery (GSSDPResourceBrowser *resource_browser)
987 {
988         if (resource_browser->priv->timeout_src) {
989                 g_source_destroy (resource_browser->priv->timeout_src);
990                 resource_browser->priv->timeout_src = NULL;
991                 resource_browser->priv->num_discovery = 0;
992         }
993 }