Fix the lib path
[external/gssdp.git] / libgssdp / gssdp-resource-group.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-group
24  * @short_description: Class for controlling resource announcement.
25  *
26  * A #GSSDPResourceGroup is a group of SSDP resources whose availability can
27  * be controlled as one. This is useful when one needs to announce a single
28  * service as multiple SSDP resources (UPnP does this for example).
29  */
30
31 #include <config.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <time.h>
35
36 #include <libsoup/soup.h>
37
38 #include "gssdp-resource-group.h"
39 #include "gssdp-resource-browser.h"
40 #include "gssdp-client-private.h"
41 #include "gssdp-protocol.h"
42
43 G_DEFINE_TYPE (GSSDPResourceGroup,
44                gssdp_resource_group,
45                G_TYPE_OBJECT);
46
47 struct _GSSDPResourceGroupPrivate {
48         GSSDPClient *client;
49
50         guint        max_age;
51
52         gboolean     available;
53
54         GList       *resources;
55
56         gulong       message_received_id;
57
58         GSource     *timeout_src;
59
60         guint        last_resource_id;
61         
62         guint        message_delay;
63         GQueue      *message_queue;
64         GSource     *message_src;
65 };
66
67 enum {
68         PROP_0,
69         PROP_CLIENT,
70         PROP_MAX_AGE,
71         PROP_AVAILABLE,
72         PROP_MESSAGE_DELAY
73 };
74
75 typedef struct {
76         GSSDPResourceGroup *resource_group;
77
78         GRegex              *target_regex;
79         char                *target;
80         char                *usn;
81         GList               *locations;
82
83         GList               *responses;
84
85         guint                id;
86
87         gboolean             initial_alive_sent;
88 } Resource;
89
90 typedef struct {
91         char     *dest_ip;
92         gushort   dest_port;
93         char     *target;
94         Resource *resource;
95
96         GSource  *timeout_src;
97 } DiscoveryResponse;
98
99 #define DEFAULT_MESSAGE_DELAY 20 
100
101 /* Function prototypes */
102 static void
103 gssdp_resource_group_set_client (GSSDPResourceGroup *resource_group,
104                                  GSSDPClient        *client);
105 static gboolean
106 resource_group_timeout          (gpointer            user_data);
107 static void
108 message_received_cb             (GSSDPClient        *client,
109                                  const char         *from_ip,
110                                  gushort             from_port,
111                                  _GSSDPMessageType   type,
112                                  SoupMessageHeaders *headers,
113                                  gpointer            user_data);
114 static void
115 resource_alive                  (Resource           *resource);
116 static void
117 resource_byebye                 (Resource           *resource);
118 static void
119 resource_free                   (Resource           *resource);
120 static gboolean
121 discovery_response_timeout      (gpointer            user_data);
122 static void
123 discovery_response_free         (DiscoveryResponse  *response);
124 static gboolean
125 process_queue                   (gpointer            data);
126 static GRegex *
127 create_target_regex             (const char         *target,
128                                  GError            **error);
129
130 static void
131 gssdp_resource_group_init (GSSDPResourceGroup *resource_group)
132 {
133         resource_group->priv = G_TYPE_INSTANCE_GET_PRIVATE
134                                         (resource_group,
135                                          GSSDP_TYPE_RESOURCE_GROUP,
136                                          GSSDPResourceGroupPrivate);
137
138         resource_group->priv->max_age = SSDP_DEFAULT_MAX_AGE;
139         resource_group->priv->message_delay = DEFAULT_MESSAGE_DELAY;
140
141         resource_group->priv->message_queue = g_queue_new ();
142 }
143
144 static void
145 gssdp_resource_group_get_property (GObject    *object,
146                                    guint       property_id,
147                                    GValue     *value,
148                                    GParamSpec *pspec)
149 {
150         GSSDPResourceGroup *resource_group;
151
152         resource_group = GSSDP_RESOURCE_GROUP (object);
153
154         switch (property_id) {
155         case PROP_CLIENT:
156                 g_value_set_object
157                         (value,
158                          gssdp_resource_group_get_client (resource_group));
159                 break;
160         case PROP_MAX_AGE:
161                 g_value_set_uint
162                         (value,
163                          gssdp_resource_group_get_max_age (resource_group));
164                 break;
165         case PROP_AVAILABLE:
166                 g_value_set_boolean
167                         (value,
168                          gssdp_resource_group_get_available (resource_group));
169                 break;
170         case PROP_MESSAGE_DELAY:
171                 g_value_set_uint
172                         (value,
173                          gssdp_resource_group_get_message_delay 
174                                 (resource_group));
175                 break;
176         default:
177                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
178                 break;
179         }
180 }
181
182 static void
183 gssdp_resource_group_set_property (GObject      *object,
184                                    guint         property_id,
185                                    const GValue *value,
186                                    GParamSpec   *pspec)
187 {
188         GSSDPResourceGroup *resource_group;
189
190         resource_group = GSSDP_RESOURCE_GROUP (object);
191
192         switch (property_id) {
193         case PROP_CLIENT:
194                 gssdp_resource_group_set_client (resource_group,
195                                                  g_value_get_object (value));
196                 break;
197         case PROP_MAX_AGE:
198                 gssdp_resource_group_set_max_age (resource_group,
199                                                   g_value_get_long (value));
200                 break;
201         case PROP_AVAILABLE:
202                 gssdp_resource_group_set_available
203                         (resource_group, g_value_get_boolean (value));
204                 break;
205         case PROP_MESSAGE_DELAY:
206                 gssdp_resource_group_set_message_delay
207                         (resource_group, g_value_get_uint (value));
208                 break;
209         default:
210                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
211                 break;
212         }
213 }
214
215 static void
216 gssdp_resource_group_dispose (GObject *object)
217 {
218         GSSDPResourceGroup *resource_group;
219         GSSDPResourceGroupPrivate *priv;
220
221         resource_group = GSSDP_RESOURCE_GROUP (object);
222         priv = resource_group->priv;
223
224         while (priv->resources) {
225                 resource_free (priv->resources->data);
226                 priv->resources =
227                         g_list_delete_link (priv->resources,
228                                             priv->resources);
229         }
230
231         if (priv->message_queue) {
232                 /* send messages without usual delay */
233                 while (!g_queue_is_empty (priv->message_queue)) {
234                         if (priv->available)
235                                 process_queue (resource_group);
236                         else
237                                 g_free (g_queue_pop_head
238                                         (priv->message_queue));
239                 }
240
241                 g_queue_free (priv->message_queue);
242                 priv->message_queue = NULL;
243         }
244
245         if (priv->message_src) {
246                 g_source_destroy (priv->message_src);
247                 priv->message_src = NULL;
248         }
249
250         if (priv->timeout_src) {
251                 g_source_destroy (priv->timeout_src);
252                 priv->timeout_src = NULL;
253         }
254
255         if (priv->client) {
256                 if (g_signal_handler_is_connected
257                         (priv->client,
258                          priv->message_received_id)) {
259                         g_signal_handler_disconnect
260                                 (priv->client,
261                                  priv->message_received_id);
262                 }
263                                                    
264                 g_object_unref (priv->client);
265                 priv->client = NULL;
266         }
267
268         G_OBJECT_CLASS (gssdp_resource_group_parent_class)->dispose (object);
269 }
270
271 static void
272 gssdp_resource_group_class_init (GSSDPResourceGroupClass *klass)
273 {
274         GObjectClass *object_class;
275
276         object_class = G_OBJECT_CLASS (klass);
277
278         object_class->set_property = gssdp_resource_group_set_property;
279         object_class->get_property = gssdp_resource_group_get_property;
280         object_class->dispose      = gssdp_resource_group_dispose;
281
282         g_type_class_add_private (klass, sizeof (GSSDPResourceGroupPrivate));
283
284         /**
285          * GSSDPResourceGroup:client
286          *
287          * The #GSSDPClient to use.
288          **/
289         g_object_class_install_property
290                 (object_class,
291                  PROP_CLIENT,
292                  g_param_spec_object
293                          ("client",
294                           "Client",
295                           "The associated client.",
296                           GSSDP_TYPE_CLIENT,
297                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
298                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
299                           G_PARAM_STATIC_BLURB));
300
301         /**
302          * GSSDPResourceGroup:max-age
303          *
304          * The number of seconds our advertisements are valid.
305          **/
306         g_object_class_install_property
307                 (object_class,
308                  PROP_MAX_AGE,
309                  g_param_spec_uint
310                          ("max-age",
311                           "Max age",
312                           "The number of seconds advertisements are valid.",
313                           0,
314                           G_MAXUINT,
315                           SSDP_DEFAULT_MAX_AGE,
316                           G_PARAM_READWRITE |
317                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
318                           G_PARAM_STATIC_BLURB));
319
320         /**
321          * GSSDPResourceGroup:available
322          *
323          * Whether this group of resources is available or not.
324          **/
325         g_object_class_install_property
326                 (object_class,
327                  PROP_AVAILABLE,
328                  g_param_spec_boolean
329                          ("available",
330                           "Available",
331                           "Whether this group of resources is available or "
332                           "not.",
333                           FALSE,
334                           G_PARAM_READWRITE |
335                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
336                           G_PARAM_STATIC_BLURB));
337
338         /**
339          * GSSDPResourceGroup:message-delay
340          *
341          * The minimum number of milliseconds between SSDP messages.
342          * The default is 20 based on DLNA specification.
343          **/
344         g_object_class_install_property
345                 (object_class,
346                  PROP_MESSAGE_DELAY,
347                  g_param_spec_uint
348                          ("message-delay",
349                           "Message delay",
350                           "The minimum number of milliseconds between SSDP "
351                           "messages.",
352                           0,
353                           G_MAXUINT,
354                           DEFAULT_MESSAGE_DELAY,
355                           G_PARAM_READWRITE |
356                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
357                           G_PARAM_STATIC_BLURB));
358 }
359
360 /**
361  * gssdp_resource_group_new
362  * @client: The #GSSDPClient to associate with
363  *
364  * Return value: A new #GSSDPResourceGroup object.
365  **/
366 GSSDPResourceGroup *
367 gssdp_resource_group_new (GSSDPClient *client)
368 {
369         return g_object_new (GSSDP_TYPE_RESOURCE_GROUP,
370                              "client", client,
371                              NULL);
372 }
373
374 /**
375  * Sets the #GSSDPClient @resource_group is associated with @client
376  **/
377 static void
378 gssdp_resource_group_set_client (GSSDPResourceGroup *resource_group,
379                                  GSSDPClient        *client)
380 {
381         g_return_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group));
382         g_return_if_fail (GSSDP_IS_CLIENT (client));
383
384         resource_group->priv->client = g_object_ref (client);
385
386         resource_group->priv->message_received_id =
387                 g_signal_connect_object (resource_group->priv->client,
388                                          "message-received",
389                                          G_CALLBACK (message_received_cb),
390                                          resource_group,
391                                          0);
392
393         g_object_notify (G_OBJECT (resource_group), "client");
394 }
395
396 /**
397  * gssdp_resource_group_get_client
398  * @resource_group: A #GSSDPResourceGroup
399  *
400  * Return value: The #GSSDPClient @resource_group is associated with.
401  **/
402 GSSDPClient *
403 gssdp_resource_group_get_client (GSSDPResourceGroup *resource_group)
404 {
405         g_return_val_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group), NULL);
406
407         return resource_group->priv->client;
408 }
409
410 /**
411  * gssdp_resource_group_set_max_age
412  * @resource_group: A #GSSDPResourceGroup
413  * @max_age: The number of seconds advertisements are valid
414  *
415  * Sets the number of seconds advertisements are valid to @max_age.
416  **/
417 void
418 gssdp_resource_group_set_max_age (GSSDPResourceGroup *resource_group,
419                                   guint               max_age)
420 {
421         g_return_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group));
422
423         if (resource_group->priv->max_age == max_age)
424                 return;
425
426         resource_group->priv->max_age = max_age;
427         
428         g_object_notify (G_OBJECT (resource_group), "max-age");
429 }
430
431 /**
432  * gssdp_resource_group_get_max_age
433  * @resource_group: A #GSSDPResourceGroup
434  *
435  * Return value: The number of seconds advertisements are valid.
436  **/
437 guint
438 gssdp_resource_group_get_max_age (GSSDPResourceGroup *resource_group)
439 {
440         g_return_val_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group), 0);
441
442         return resource_group->priv->max_age;
443 }
444
445 /**
446  * gssdp_resource_group_set_message_delay
447  * @resource_group: A #GSSDPResourceGroup
448  * @message_delay: The message delay in ms.
449  *
450  * Sets the minimum time between each SSDP message.
451  **/
452 void
453 gssdp_resource_group_set_message_delay (GSSDPResourceGroup *resource_group,
454                                         guint               message_delay)
455 {
456         g_return_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group));
457
458         if (resource_group->priv->message_delay == message_delay)
459                 return;
460
461         resource_group->priv->message_delay = message_delay;
462         
463         g_object_notify (G_OBJECT (resource_group), "message-delay");
464 }
465
466 /**
467  * gssdp_resource_group_get_message_delay
468  * @resource_group: A #GSSDPResourceGroup
469  *
470  * Return value: the minimum time between each SSDP message in ms.
471  **/
472 guint
473 gssdp_resource_group_get_message_delay (GSSDPResourceGroup *resource_group)
474 {
475         g_return_val_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group), 0);
476
477         return resource_group->priv->message_delay;
478 }
479
480 /**
481  * gssdp_resource_group_set_available
482  * @resource_group: A #GSSDPResourceGroup
483  * @available: TRUE if @resource_group should be available (advertised)
484  *
485  * Sets @resource_group<!-- -->s availability to @available. Changing
486  * @resource_group<!-- -->s availability causes it to announce its new state
487  * to listening SSDP clients.
488  **/
489 void
490 gssdp_resource_group_set_available (GSSDPResourceGroup *resource_group,
491                                     gboolean            available)
492 {
493         GList *l;
494
495         g_return_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group));
496
497         if (resource_group->priv->available == available)
498                 return;
499
500         resource_group->priv->available = available;
501
502         if (available) {
503                 GMainContext *context;
504                 int timeout;
505
506                 /* We want to re-announce before actually timing out */
507                 timeout = resource_group->priv->max_age;
508                 if (timeout > 2)
509                         timeout = timeout / 2 - 1;
510
511                 /* Add re-announcement timer */
512                 resource_group->priv->timeout_src =
513                         g_timeout_source_new_seconds (timeout);
514                 g_source_set_callback (resource_group->priv->timeout_src,
515                                        resource_group_timeout,
516                                        resource_group, NULL);
517
518                 context = gssdp_client_get_main_context
519                         (resource_group->priv->client);
520                 g_source_attach (resource_group->priv->timeout_src, context);
521
522                 g_source_unref (resource_group->priv->timeout_src);
523
524                 /* Announce all resources */
525                 for (l = resource_group->priv->resources; l; l = l->next)
526                         resource_alive (l->data);
527         } else {
528                 /* Unannounce all resources */
529                 for (l = resource_group->priv->resources; l; l = l->next)
530                         resource_byebye (l->data);
531
532                 /* Remove re-announcement timer */
533                 g_source_destroy (resource_group->priv->timeout_src);
534                 resource_group->priv->timeout_src = NULL;
535         }
536         
537         g_object_notify (G_OBJECT (resource_group), "available");
538 }
539
540 /**
541  * gssdp_resource_group_get_available
542  * @resource_group: A #GSSDPResourceGroup
543  *
544  * Return value: TRUE if @resource_group is available (advertised).
545  **/
546 gboolean
547 gssdp_resource_group_get_available (GSSDPResourceGroup *resource_group)
548 {
549         g_return_val_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group), FALSE);
550
551         return resource_group->priv->available;
552 }
553
554 /**
555  * gssdp_resource_group_add_resource
556  * @resource_group: An @GSSDPResourceGroup
557  * @target: The resource's target
558  * @usn: The resource's USN
559  * @locations: A #GList of the resource's locations
560  *
561  * Adds a resource with target @target, USN @usn, and locations @locations
562  * to @resource_group.
563  *
564  * Return value: The ID of the added resource.
565  **/
566 guint
567 gssdp_resource_group_add_resource (GSSDPResourceGroup *resource_group,
568                                    const char         *target,
569                                    const char         *usn,
570                                    GList              *locations)
571 {
572         Resource *resource;
573         GList *l;
574         GError *error;
575
576         g_return_val_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group), 0);
577         g_return_val_if_fail (target != NULL, 0);
578         g_return_val_if_fail (usn != NULL, 0);
579         g_return_val_if_fail (locations != NULL, 0);
580
581         resource = g_slice_new0 (Resource);
582
583         resource->resource_group = resource_group;
584
585         resource->target = g_strdup (target);
586         resource->usn    = g_strdup (usn);
587
588         error = NULL;
589         resource->target_regex = create_target_regex (target, &error);
590         if (error) {
591                 g_warning ("Error compiling regular expression for '%s': %s",
592                            target,
593                            error->message);
594
595                 g_error_free (error);
596                 resource_free (resource);
597
598                 return 0;
599         }
600
601         resource->initial_alive_sent = FALSE;
602
603         for (l = locations; l; l = l->next) {
604                 resource->locations = g_list_append (resource->locations,
605                                                     g_strdup (l->data));
606         }
607
608         resource_group->priv->resources =
609                 g_list_prepend (resource_group->priv->resources, resource);
610
611         resource->id = ++resource_group->priv->last_resource_id;
612
613         if (resource_group->priv->available)
614                 resource_alive (resource);
615
616         return resource->id;
617 }
618
619 /**
620  * gssdp_resource_group_add_resource_simple
621  * @resource_group: An @GSSDPResourceGroup
622  * @target: The resource's target
623  * @usn: The resource's USN
624  * @location: The resource's location
625  *
626  * Adds a resource with target @target, USN @usn, and location @location
627  * to @resource_group.
628  *
629  * Return value: The ID of the added resource.
630  **/
631 guint
632 gssdp_resource_group_add_resource_simple (GSSDPResourceGroup *resource_group,
633                                           const char         *target,
634                                           const char         *usn,
635                                           const char         *location)
636 {
637         Resource *resource;
638         GError   *error;
639
640         g_return_val_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group), 0);
641         g_return_val_if_fail (target != NULL, 0);
642         g_return_val_if_fail (usn != NULL, 0);
643         g_return_val_if_fail (location != NULL, 0);
644
645         resource = g_slice_new0 (Resource);
646
647         resource->resource_group = resource_group;
648
649         resource->target = g_strdup (target);
650         resource->usn    = g_strdup (usn);
651
652         error = NULL;
653         resource->target_regex = create_target_regex (target, &error);
654         if (error) {
655                 g_warning ("Error compiling regular expression for '%s': %s",
656                            target,
657                            error->message);
658
659                 g_error_free (error);
660                 resource_free (resource);
661
662                 return 0;
663         }
664
665         resource->locations = g_list_append (resource->locations,
666                                              g_strdup (location));
667
668         resource_group->priv->resources =
669                 g_list_prepend (resource_group->priv->resources, resource);
670
671         resource->id = ++resource_group->priv->last_resource_id;
672
673         if (resource_group->priv->available)
674                 resource_alive (resource);
675
676         return resource->id;
677 }
678
679 /**
680  * gssdp_resource_group_remove_resource
681  * @resource_group: An @GSSDPResourceGroup
682  * @resource_id: The ID of the resource to remove
683  *
684  * Removes the resource with ID @resource_id from @resource_group.
685  **/
686 void
687 gssdp_resource_group_remove_resource (GSSDPResourceGroup *resource_group,
688                                       guint               resource_id)
689 {
690         GList *l;
691
692         g_return_if_fail (GSSDP_IS_RESOURCE_GROUP (resource_group));
693         g_return_if_fail (resource_id > 0);
694
695         for (l = resource_group->priv->resources; l; l = l->next) {
696                 Resource *resource;
697
698                 resource = l->data;
699
700                 if (resource->id == resource_id) {
701                         resource_group->priv->resources = 
702                                 g_list_remove (resource_group->priv->resources,
703                                                resource);
704                         
705                         resource_free (resource);
706
707                         return;
708                 }
709         }
710 }
711
712 /**
713  * Called to re-announce all resources periodically
714  **/
715 static gboolean
716 resource_group_timeout (gpointer user_data)
717 {
718         GSSDPResourceGroup *resource_group;
719         GList *l;
720
721         resource_group = GSSDP_RESOURCE_GROUP (user_data);
722
723         /* Re-announce all resources */
724         for (l = resource_group->priv->resources; l; l = l->next)
725                 resource_alive (l->data);
726
727         return TRUE;
728 }
729
730 /**
731  * Received a message
732  **/
733 static void
734 message_received_cb (GSSDPClient        *client,
735                      const char         *from_ip,
736                      gushort             from_port,
737                      _GSSDPMessageType   type,
738                      SoupMessageHeaders *headers,
739                      gpointer            user_data)
740 {
741         GSSDPResourceGroup *resource_group;
742         const char *target, *mx_str;
743         gboolean want_all;
744         int mx;
745         GList *l;
746
747         resource_group = GSSDP_RESOURCE_GROUP (user_data);
748
749         /* Only process if we are available */
750         if (!resource_group->priv->available)
751                 return;
752
753         /* We only handle discovery requests */
754         if (type != _GSSDP_DISCOVERY_REQUEST)
755                 return;
756
757         /* Extract target */
758         target = soup_message_headers_get_one (headers, "ST");
759         if (!target) {
760                 g_warning ("Discovery request did not have an ST header");
761
762                 return;
763         }
764
765         /* Is this the "ssdp:all" target? */
766         want_all = (strcmp (target, GSSDP_ALL_RESOURCES) == 0);
767
768         /* Extract MX */
769         mx_str = soup_message_headers_get_one (headers, "MX");
770         if (mx_str)
771                 mx = atoi (mx_str);
772         else
773                 mx = SSDP_DEFAULT_MX;
774
775         /* Find matching resource */
776         for (l = resource_group->priv->resources; l; l = l->next) {
777                 Resource *resource;
778
779                 resource = l->data;
780
781                 if (want_all ||
782                     g_regex_match (resource->target_regex,
783                                    target,
784                                    0,
785                                    NULL)) {
786                         /* Match. */
787                         guint timeout;
788                         DiscoveryResponse *response;
789                         GMainContext *context;
790
791                         /* Get a random timeout from the interval [0, mx] */
792                         timeout = g_random_int_range (0, mx * 1000);
793
794                         /* Prepare response */
795                         response = g_slice_new (DiscoveryResponse);
796                         
797                         response->dest_ip   = g_strdup (from_ip);
798                         response->dest_port = from_port;
799                         response->resource  = resource;
800
801                         if (want_all)
802                                 response->target = g_strdup (resource->target);
803                         else
804                                 response->target = g_strdup (target);
805
806                         /* Add timeout */
807                         response->timeout_src = g_timeout_source_new (timeout);
808                         g_source_set_callback (response->timeout_src,
809                                                discovery_response_timeout,
810                                                response, NULL);
811
812                         context = gssdp_client_get_main_context (client);
813                         g_source_attach (response->timeout_src, context);
814
815                         g_source_unref (response->timeout_src);
816                         
817                         /* Add to resource */
818                         resource->responses =
819                                 g_list_prepend (resource->responses, response);
820                 }
821         }
822 }
823
824 /**
825  * Construct the AL (Alternative Locations) header for @resource
826  **/
827 static char *
828 construct_al (Resource *resource)
829 {
830        if (resource->locations->next) {
831                 GString *al_string;
832                 GList *l;
833
834                 al_string = g_string_new ("AL: ");
835
836                 for (l = resource->locations->next; l; l = l->next) {
837                         g_string_append_c (al_string, '<');
838                         g_string_append (al_string, l->data);
839                         g_string_append_c (al_string, '>');
840                 }
841
842                 g_string_append (al_string, "\r\n");
843
844                 return g_string_free (al_string, FALSE);
845         } else
846                 return NULL; 
847 }
848
849 /**
850  * Send a discovery response
851  **/
852 static gboolean
853 discovery_response_timeout (gpointer user_data)
854 {
855         DiscoveryResponse *response;
856         GSSDPClient *client;
857         SoupDate *date;
858         char *al, *date_str, *message;
859         guint max_age;
860
861         response = user_data;
862
863         /* Send message */
864         client = response->resource->resource_group->priv->client;
865
866         max_age = response->resource->resource_group->priv->max_age;
867
868         al = construct_al (response->resource);
869
870         date = soup_date_new_from_now (0);
871         date_str = soup_date_to_string (date, SOUP_DATE_HTTP);
872         soup_date_free (date);
873
874         message = g_strdup_printf (SSDP_DISCOVERY_RESPONSE,
875                                    (char *) response->resource->locations->data,
876                                    al ? al : "",
877                                    response->resource->usn,
878                                    gssdp_client_get_server_id (client),
879                                    max_age,
880                                    response->target,
881                                    date_str);
882
883         _gssdp_client_send_message (client,
884                                     response->dest_ip,
885                                     response->dest_port,
886                                     message);
887
888         g_free (message);
889         g_free (date_str);
890         g_free (al);
891
892         discovery_response_free (response);
893
894         return FALSE;
895 }
896
897 /**
898  * Free a DiscoveryResponse structure and its contained data
899  **/
900 static void
901 discovery_response_free (DiscoveryResponse *response)
902 {
903         response->resource->responses =
904                 g_list_remove (response->resource->responses, response);
905
906         g_source_destroy (response->timeout_src);
907         
908         g_free (response->dest_ip);
909         g_free (response->target);
910
911         g_slice_free (DiscoveryResponse, response);
912 }
913
914 /**
915  * Send the next queued message, if any
916  **/
917 static gboolean
918 process_queue (gpointer data)
919 {
920         GSSDPResourceGroup *resource_group;
921
922         resource_group = GSSDP_RESOURCE_GROUP (data);
923
924         if (g_queue_is_empty (resource_group->priv->message_queue)) {
925                 /* this is the timeout after last message in queue */
926                 resource_group->priv->message_src = NULL;
927
928                 return FALSE;
929         } else {
930                 GSSDPClient *client;
931                 char *message;
932
933                 client = resource_group->priv->client;
934                 message = g_queue_pop_head
935                         (resource_group->priv->message_queue);
936
937                 _gssdp_client_send_message (client,
938                                             NULL,
939                                             0,
940                                             message);
941                 g_free (message);
942
943                 return TRUE;
944         }
945 }
946
947 /**
948  * Add a message to sending queue
949  * 
950  * Do not free @message.
951  **/
952 static void
953 queue_message (GSSDPResourceGroup *resource_group,
954                char               *message)
955 {
956         g_queue_push_tail (resource_group->priv->message_queue, 
957                            message);
958
959         if (resource_group->priv->message_src == NULL) {
960                 /* nothing in the queue: process message immediately 
961                    and add a timeout for (possible) next message */
962                 GMainContext *context;
963
964                 process_queue (resource_group);
965                 resource_group->priv->message_src = g_timeout_source_new (
966                     resource_group->priv->message_delay);
967                 g_source_set_callback (resource_group->priv->message_src,
968                     process_queue, resource_group, NULL);
969                 context = gssdp_client_get_main_context (
970                     resource_group->priv->client);
971                 g_source_attach (resource_group->priv->message_src, context);
972                 g_source_unref (resource_group->priv->message_src);
973         }
974 }
975
976 /**
977  * Send ssdp:alive message for @resource
978  **/
979 static void
980 resource_alive (Resource *resource)
981 {
982         GSSDPClient *client;
983         guint max_age;
984         char *al, *message;
985
986         if (!resource->initial_alive_sent) {
987                 /* Unannounce before first announce. This is done to
988                    minimize the possibility of control points thinking
989                    that this is just a reannouncement. */
990                 resource_byebye (resource);
991
992                 resource->initial_alive_sent = TRUE;
993         }
994
995         /* Send message */
996         client = resource->resource_group->priv->client;
997
998         max_age = resource->resource_group->priv->max_age;
999
1000         al = construct_al (resource);
1001
1002         message = g_strdup_printf (SSDP_ALIVE_MESSAGE,
1003                                    max_age,
1004                                    (char *) resource->locations->data,
1005                                    al ? al : "",
1006                                    gssdp_client_get_server_id (client),
1007                                    resource->target,
1008                                    resource->usn);
1009
1010         queue_message (resource->resource_group, message);
1011
1012         g_free (al);
1013 }
1014
1015 /**
1016  * Send ssdp:byebye message for @resource
1017  **/
1018 static void
1019 resource_byebye (Resource *resource)
1020 {
1021         char *message;
1022
1023         /* Queue message */
1024         message = g_strdup_printf (SSDP_BYEBYE_MESSAGE,
1025                                    resource->target,
1026                                    resource->usn);
1027         
1028         queue_message (resource->resource_group, message);
1029 }
1030
1031 /**
1032  * Free a Resource structure and its contained data
1033  **/
1034 static void
1035 resource_free (Resource *resource)
1036 {
1037         while (resource->responses)
1038                 discovery_response_free (resource->responses->data);
1039
1040         if (resource->resource_group->priv->available)
1041                 resource_byebye (resource);
1042
1043         g_free (resource->usn);
1044         g_free (resource->target);
1045
1046         if (resource->target_regex)
1047                 g_regex_unref (resource->target_regex);
1048
1049         while (resource->locations) {
1050                 g_free (resource->locations->data);
1051                 resource->locations = g_list_delete_link (resource->locations,
1052                                                          resource->locations);
1053         }
1054
1055         g_slice_free (Resource, resource);
1056 }
1057
1058 static GRegex *
1059 create_target_regex (const char *target, GError **error)
1060 {
1061         GRegex *regex;
1062         char *pattern;
1063         char *version;
1064         char *version_pattern;
1065
1066         if (strncmp (target, "urn:", 4) != 0) {
1067                 /* target is not a URN, No need to deal with version. */
1068                 return g_regex_new (target, 0, 0, error);
1069         }
1070
1071         version_pattern = "[0-9]+$";
1072         /* Make sure we have enough room for version pattern */
1073         pattern = g_strndup (target,
1074                              strlen (target) + strlen (version_pattern));
1075
1076         version = g_strrstr (pattern, ":") + 1;
1077         if (version != NULL &&
1078             g_regex_match_simple (version_pattern, version, 0, 0)) {
1079                 strcpy (version, version_pattern);
1080         }
1081
1082         regex = g_regex_new (pattern, 0, 0, error);
1083
1084         g_free (pattern);
1085
1086         return regex;
1087 }
1088