2 * Copyright (C) 2006, 2007 OpenedHand Ltd.
4 * Author: Jorn Baayen <jorn@openedhand.com>
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.
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.
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.
23 * SECTION:gssdp-resource-browser
24 * @short_description: Class handling resource discovery.
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.
34 #include <libsoup/soup-date.h>
38 #include "gssdp-resource-browser.h"
39 #include "gssdp-client-private.h"
40 #include "gssdp-protocol.h"
41 #include "gssdp-marshal.h"
43 G_DEFINE_TYPE (GSSDPResourceBrowser,
44 gssdp_resource_browser,
47 struct _GSSDPResourceBrowserPrivate {
56 gulong message_received_id;
58 GHashTable *resources;
75 static guint signals[LAST_SIGNAL];
78 GSSDPResourceBrowser *resource_browser;
83 /* Function prototypes */
85 gssdp_resource_browser_set_client (GSSDPResourceBrowser *resource_browser,
88 message_received_cb (GSSDPClient *client,
90 _GSSDPMessageType type,
94 resource_free (gpointer data);
96 clear_cache (GSSDPResourceBrowser *resource_browser);
99 gssdp_resource_browser_init (GSSDPResourceBrowser *resource_browser)
101 resource_browser->priv = G_TYPE_INSTANCE_GET_PRIVATE
103 GSSDP_TYPE_RESOURCE_BROWSER,
104 GSSDPResourceBrowserPrivate);
106 resource_browser->priv->mx = SSDP_DEFAULT_MX;
108 resource_browser->priv->resources =
109 g_hash_table_new_full (g_str_hash,
116 gssdp_resource_browser_get_property (GObject *object,
121 GSSDPResourceBrowser *resource_browser;
123 resource_browser = GSSDP_RESOURCE_BROWSER (object);
125 switch (property_id) {
129 gssdp_resource_browser_get_client (resource_browser));
134 gssdp_resource_browser_get_target (resource_browser));
139 gssdp_resource_browser_get_mx (resource_browser));
144 gssdp_resource_browser_get_active (resource_browser));
147 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
153 gssdp_resource_browser_set_property (GObject *object,
158 GSSDPResourceBrowser *resource_browser;
160 resource_browser = GSSDP_RESOURCE_BROWSER (object);
162 switch (property_id) {
164 gssdp_resource_browser_set_client (resource_browser,
165 g_value_get_object (value));
168 gssdp_resource_browser_set_target (resource_browser,
169 g_value_get_string (value));
172 gssdp_resource_browser_set_mx (resource_browser,
173 g_value_get_uint (value));
176 gssdp_resource_browser_set_active (resource_browser,
177 g_value_get_boolean (value));
180 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
186 gssdp_resource_browser_dispose (GObject *object)
188 GSSDPResourceBrowser *resource_browser;
190 resource_browser = GSSDP_RESOURCE_BROWSER (object);
192 if (resource_browser->priv->client) {
193 if (g_signal_handler_is_connected
194 (resource_browser->priv->client,
195 resource_browser->priv->message_received_id)) {
196 g_signal_handler_disconnect
197 (resource_browser->priv->client,
198 resource_browser->priv->message_received_id);
201 g_object_unref (resource_browser->priv->client);
202 resource_browser->priv->client = NULL;
205 clear_cache (resource_browser);
209 gssdp_resource_browser_finalize (GObject *object)
211 GSSDPResourceBrowser *resource_browser;
213 resource_browser = GSSDP_RESOURCE_BROWSER (object);
215 g_free (resource_browser->priv->target);
217 g_hash_table_destroy (resource_browser->priv->resources);
221 gssdp_resource_browser_class_init (GSSDPResourceBrowserClass *klass)
223 GObjectClass *object_class;
225 object_class = G_OBJECT_CLASS (klass);
227 object_class->set_property = gssdp_resource_browser_set_property;
228 object_class->get_property = gssdp_resource_browser_get_property;
229 object_class->dispose = gssdp_resource_browser_dispose;
230 object_class->finalize = gssdp_resource_browser_finalize;
232 g_type_class_add_private (klass, sizeof (GSSDPResourceBrowserPrivate));
235 * GSSDPResourceBrowser:client
237 * The #GSSDPClient to use.
239 g_object_class_install_property
245 "The associated client.",
247 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
248 G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
249 G_PARAM_STATIC_BLURB));
252 * GSSDPResourceBrowser:target
254 * The discovery target.
256 g_object_class_install_property
262 "The discovery target.",
265 G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
266 G_PARAM_STATIC_BLURB));
269 * GSSDPResourceBrowser:mx
271 * The maximum number of seconds in which to request other parties
274 g_object_class_install_property
280 "The maximum number of seconds in which to request "
281 "other parties to respond.",
286 G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
287 G_PARAM_STATIC_BLURB));
290 * GSSDPResourceBrowser:active
292 * Whether this browser is active or not.
294 g_object_class_install_property
300 "TRUE if the resource browser is active.",
303 G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
304 G_PARAM_STATIC_BLURB));
307 * GSSDPResourceBrowser::resource-available
308 * @resource_browser: The #GSSDPResourceBrowser that received the
310 * @usn: The USN of the discovered resource
311 * @locations: A #GList of strings describing the locations of the
312 * discovered resource.
314 * The ::resource-available signal is emitted whenever a new resource
315 * has become available.
317 signals[RESOURCE_AVAILABLE] =
318 g_signal_new ("resource-available",
319 GSSDP_TYPE_RESOURCE_BROWSER,
321 G_STRUCT_OFFSET (GSSDPResourceBrowserClass,
324 gssdp_marshal_VOID__STRING_POINTER,
331 * GSSDPResourceBrowser::resource-unavailable
332 * @resource_browser: The #GSSDPResourceBrowser that received the
334 * @usn: The USN of the resource
336 * The ::resource-unavailable signal is emitted whenever a resource
337 * is not available any more.
339 signals[RESOURCE_UNAVAILABLE] =
340 g_signal_new ("resource-unavailable",
341 GSSDP_TYPE_RESOURCE_BROWSER,
343 G_STRUCT_OFFSET (GSSDPResourceBrowserClass,
344 resource_unavailable),
346 gssdp_marshal_VOID__STRING,
353 * gssdp_resource_browser_new
354 * @client: The #GSSDPClient to associate with
356 * Return value: A new #GSSDPResourceBrowser object.
358 GSSDPResourceBrowser *
359 gssdp_resource_browser_new (GSSDPClient *client,
362 return g_object_new (GSSDP_TYPE_RESOURCE_BROWSER,
369 * Sets the #GSSDPClient @resource_browser is associated with to @client
372 gssdp_resource_browser_set_client (GSSDPResourceBrowser *resource_browser,
375 g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
376 g_return_if_fail (GSSDP_IS_CLIENT (client));
378 resource_browser->priv->client = g_object_ref (client);
380 resource_browser->priv->message_received_id =
381 g_signal_connect_object (resource_browser->priv->client,
383 G_CALLBACK (message_received_cb),
387 g_object_notify (G_OBJECT (resource_browser), "client");
391 * gssdp_resource_browser_get_client
392 * @resource_browser: A #GSSDPResourceBrowser
394 * Return value: The #GSSDPClient @resource_browser is associated with.
397 gssdp_resource_browser_get_client (GSSDPResourceBrowser *resource_browser)
399 g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser),
402 return resource_browser->priv->client;
406 * gssdp_resource_browser_set_target
407 * @resource_browser: A #GSSDPResourceBrowser
408 * @target: The browser target
410 * Sets the browser target of @resource_browser to @target.
413 gssdp_resource_browser_set_target (GSSDPResourceBrowser *resource_browser,
416 g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
417 g_return_if_fail (target != NULL);
418 g_return_if_fail (!resource_browser->priv->active);
420 g_free (resource_browser->priv->target);
421 resource_browser->priv->target = g_strdup (target);
423 g_object_notify (G_OBJECT (resource_browser), "target");
427 * gssdp_resource_browser_get_target
428 * @resource_browser: A #GSSDPResourceBrowser
430 * Return value: The browser target.
433 gssdp_resource_browser_get_target (GSSDPResourceBrowser *resource_browser)
435 g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser),
438 return resource_browser->priv->target;
442 * gssdp_resource_browser_set_mx
443 * @resource_browser: A #GSSDPResourceBrowser
444 * @mx: The to be used MX value
446 * Sets the used MX value of @resource_browser to @mx.
449 gssdp_resource_browser_set_mx (GSSDPResourceBrowser *resource_browser,
452 g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
454 if (resource_browser->priv->mx == mx)
457 resource_browser->priv->mx = mx;
459 g_object_notify (G_OBJECT (resource_browser), "mx");
463 * gssdp_resource_browser_get_mx
464 * @resource_browser: A #GSSDPResourceBrowser
466 * Return value: The used MX value.
469 gssdp_resource_browser_get_mx (GSSDPResourceBrowser *resource_browser)
471 g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser), 0);
473 return resource_browser->priv->mx;
477 * gssdp_resource_browser_set_active
478 * @resource_browser: A #GSSDPResourceBrowser
479 * @active: TRUE to activate @resource_browser
481 * (De)activates @resource_browser.
484 gssdp_resource_browser_set_active (GSSDPResourceBrowser *resource_browser,
487 g_return_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser));
489 if (resource_browser->priv->active == active)
492 resource_browser->priv->active = active;
495 /* Emit discovery message */
498 message = g_strdup_printf (SSDP_DISCOVERY_REQUEST,
499 resource_browser->priv->target,
500 resource_browser->priv->mx);
502 _gssdp_client_send_message (resource_browser->priv->client,
508 clear_cache (resource_browser);
510 g_object_notify (G_OBJECT (resource_browser), "active");
514 * gssdp_resource_browser_get_active
515 * @resource_browser: A #GSSDPResourceBrowser
517 * Return value: TRUE if @resource_browser is active.
520 gssdp_resource_browser_get_active (GSSDPResourceBrowser *resource_browser)
522 g_return_val_if_fail (GSSDP_IS_RESOURCE_BROWSER (resource_browser), 0);
524 return resource_browser->priv->active;
528 * Resource expired: Remove
531 resource_expire (gpointer user_data)
535 resource = user_data;
537 g_signal_emit (resource->resource_browser,
538 signals[RESOURCE_UNAVAILABLE],
542 g_hash_table_remove (resource->resource_browser->priv->resources,
549 resource_available (GSSDPResourceBrowser *resource_browser,
559 list = g_hash_table_lookup (headers, "USN");
561 return; /* No USN specified */
564 /* Get from cache, if possible */
565 resource = g_hash_table_lookup (resource_browser->priv->resources, usn);
567 /* Remove old timeout */
568 g_source_remove (resource->timeout_id);
572 /* Create new Resource data structure */
573 resource = g_slice_new (Resource);
575 resource->resource_browser = resource_browser;
576 resource->usn = g_strdup (usn);
578 g_hash_table_insert (resource_browser->priv->resources,
585 /* Calculate new timeout */
586 list = g_hash_table_lookup (headers, "Cache-Control");
593 for (l = list; l; l = l->next) {
594 res = sscanf (l->data,
602 g_warning ("Invalid 'Cache-Control' header. Assuming "
603 "default max-age of %d.\n"
605 SSDP_DEFAULT_MAX_AGE,
606 (char *) list->data);
608 timeout = SSDP_DEFAULT_MAX_AGE;
611 list = g_hash_table_lookup (headers, "Expires");
613 time_t exp_time, cur_time;
615 exp_time = soup_date_parse (list->data);
616 cur_time = time (NULL);
618 if (exp_time > cur_time)
619 timeout = exp_time - cur_time;
621 g_warning ("Invalid 'Expires' header. Assuming "
622 "default max-age of %d.\n"
624 SSDP_DEFAULT_MAX_AGE,
625 (char *) list->data);
627 timeout = SSDP_DEFAULT_MAX_AGE;
630 g_warning ("No 'Cache-Control' nor any 'Expires' "
631 "header was specified. Assuming default "
632 "max-age of %d.", SSDP_DEFAULT_MAX_AGE);
634 timeout = SSDP_DEFAULT_MAX_AGE;
638 resource->timeout_id = g_timeout_add (timeout * 1000,
642 /* Only continue with signal emission if this resource was not
647 /* Build list of locations */
650 list = g_hash_table_lookup (headers, "Location");
652 locations = g_list_append (locations, g_strdup (list->data));
654 list = g_hash_table_lookup (headers, "AL");
656 /* Parse AL header. The format is:
658 char *start, *end, *uri;
661 while ((start = strchr (start, '<'))) {
663 if (!start || !*start)
666 end = strchr (start, '>');
670 uri = g_strndup (start, end - start);
671 locations = g_list_append (locations, uri);
678 g_signal_emit (resource_browser,
679 signals[RESOURCE_AVAILABLE],
686 g_free (locations->data);
688 locations = g_list_delete_link (locations, locations);
693 resource_unavailable (GSSDPResourceBrowser *resource_browser,
699 list = g_hash_table_lookup (headers, "USN");
701 return; /* No USN specified */
704 /* Only process if we were cached */
705 if (!g_hash_table_lookup (resource_browser->priv->resources, usn))
708 g_signal_emit (resource_browser,
709 signals[RESOURCE_UNAVAILABLE],
713 g_hash_table_remove (resource_browser->priv->resources, usn);
717 received_discovery_response (GSSDPResourceBrowser *resource_browser,
722 list = g_hash_table_lookup (headers, "ST");
724 return; /* No target specified */
726 if (strcmp (resource_browser->priv->target, GSSDP_ALL_RESOURCES) != 0 &&
727 strcmp (resource_browser->priv->target, list->data) != 0)
728 return; /* Target doesn't match */
730 resource_available (resource_browser, headers);
734 received_announcement (GSSDPResourceBrowser *resource_browser,
739 list = g_hash_table_lookup (headers, "NT");
741 return; /* No target specified */
743 if (strcmp (resource_browser->priv->target, GSSDP_ALL_RESOURCES) != 0 &&
744 strcmp (resource_browser->priv->target, list->data) != 0)
745 return; /* Target doesn't match */
747 list = g_hash_table_lookup (headers, "NTS");
749 return; /* No announcement type specified */
751 /* Check announcement type */
752 if (strncmp (list->data,
754 strlen (SSDP_ALIVE_NTS)) == 0)
755 resource_available (resource_browser, headers);
756 else if (strncmp (list->data,
758 strlen (SSDP_BYEBYE_NTS)) == 0)
759 resource_unavailable (resource_browser, headers);
766 message_received_cb (GSSDPClient *client,
768 _GSSDPMessageType type,
772 GSSDPResourceBrowser *resource_browser;
774 resource_browser = GSSDP_RESOURCE_BROWSER (user_data);
776 if (!resource_browser->priv->active)
780 case _GSSDP_DISCOVERY_RESPONSE:
781 received_discovery_response (resource_browser, headers);
783 case _GSSDP_ANNOUNCEMENT:
784 received_announcement (resource_browser, headers);
792 * Free a Resource structure and its contained data
795 resource_free (gpointer data)
801 g_free (resource->usn);
803 g_source_remove (resource->timeout_id);
805 g_slice_free (Resource, resource);
809 clear_cache_helper (gpointer key, gpointer value, gpointer data)
815 g_signal_emit (resource->resource_browser,
816 signals[RESOURCE_UNAVAILABLE],
824 * Clears the cached resources hash
827 clear_cache (GSSDPResourceBrowser *resource_browser)
830 g_hash_table_foreach_remove (resource_browser->priv->resources,