2 * Copyright (C) 2006, 2007, 2008 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., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
23 * SECTION:gupnp-service-info
24 * @short_description: Base abstract class for querying service information.
26 * The #GUPnPDeviceInfo base abstract class provides methods for querying
27 * service information.
30 #include <libsoup/soup.h>
33 #include "gupnp-service-info.h"
34 #include "gupnp-service-introspection-private.h"
35 #include "gupnp-context-private.h"
36 #include "gupnp-error.h"
37 #include "gupnp-error-private.h"
38 #include "gupnp-xml-doc.h"
39 #include "http-headers.h"
42 G_DEFINE_ABSTRACT_TYPE (GUPnPServiceInfo,
46 struct _GUPnPServiceInfoPrivate {
47 GUPnPContext *context;
59 /* For async downloads */
75 GUPnPServiceInfo *info;
77 GUPnPServiceIntrospectionCallback callback;
80 GCancellable *cancellable;
87 get_scpd_url_data_free (GetSCPDURLData *data)
89 if (data->cancellable)
90 g_object_unref (data->cancellable);
92 g_slice_free (GetSCPDURLData, data);
96 gupnp_service_info_init (GUPnPServiceInfo *info)
98 info->priv = G_TYPE_INSTANCE_GET_PRIVATE (info,
99 GUPNP_TYPE_SERVICE_INFO,
100 GUPnPServiceInfoPrivate);
104 gupnp_service_info_set_property (GObject *object,
109 GUPnPServiceInfo *info;
111 info = GUPNP_SERVICE_INFO (object);
113 switch (property_id) {
115 info->priv->context = g_object_ref (g_value_get_object (value));
118 info->priv->location = g_value_dup_string (value);
121 info->priv->udn = g_value_dup_string (value);
123 case PROP_SERVICE_TYPE:
124 info->priv->service_type = g_value_dup_string (value);
127 info->priv->url_base = g_value_dup_boxed (value);
130 info->priv->doc = g_value_dup_object (value);
133 info->priv->element = g_value_get_pointer (value);
136 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
142 gupnp_service_info_get_property (GObject *object,
147 GUPnPServiceInfo *info;
149 info = GUPNP_SERVICE_INFO (object);
151 switch (property_id) {
153 g_value_set_object (value,
154 info->priv->context);
157 g_value_set_string (value,
158 info->priv->location);
161 g_value_set_string (value,
164 case PROP_SERVICE_TYPE:
165 g_value_set_string (value,
166 gupnp_service_info_get_service_type (info));
169 g_value_set_boxed (value,
170 info->priv->url_base);
173 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
179 gupnp_service_info_dispose (GObject *object)
181 GUPnPServiceInfo *info;
183 info = GUPNP_SERVICE_INFO (object);
185 /* Cancel any pending SCPD GETs */
186 if (info->priv->context) {
187 SoupSession *session;
189 session = gupnp_context_get_session (info->priv->context);
191 while (info->priv->pending_gets) {
192 GetSCPDURLData *data;
194 data = info->priv->pending_gets->data;
196 if (data->cancellable)
197 g_cancellable_disconnect (data->cancellable,
200 soup_session_cancel_message (session,
202 SOUP_STATUS_CANCELLED);
204 get_scpd_url_data_free (data);
206 info->priv->pending_gets =
207 g_list_delete_link (info->priv->pending_gets,
208 info->priv->pending_gets);
212 g_object_unref (info->priv->context);
213 info->priv->context = NULL;
216 if (info->priv->doc) {
217 g_object_unref (info->priv->doc);
218 info->priv->doc = NULL;
221 G_OBJECT_CLASS (gupnp_service_info_parent_class)->dispose (object);
225 gupnp_service_info_finalize (GObject *object)
227 GUPnPServiceInfo *info;
229 info = GUPNP_SERVICE_INFO (object);
231 g_free (info->priv->location);
232 g_free (info->priv->udn);
233 g_free (info->priv->service_type);
235 soup_uri_free (info->priv->url_base);
237 G_OBJECT_CLASS (gupnp_service_info_parent_class)->finalize (object);
241 gupnp_service_info_class_init (GUPnPServiceInfoClass *klass)
243 GObjectClass *object_class;
245 object_class = G_OBJECT_CLASS (klass);
247 object_class->set_property = gupnp_service_info_set_property;
248 object_class->get_property = gupnp_service_info_get_property;
249 object_class->dispose = gupnp_service_info_dispose;
250 object_class->finalize = gupnp_service_info_finalize;
252 g_type_class_add_private (klass, sizeof (GUPnPServiceInfoPrivate));
255 * GUPnPServiceInfo:context:
257 * The #GUPnPContext to use.
259 g_object_class_install_property
262 g_param_spec_object ("context",
267 G_PARAM_CONSTRUCT_ONLY |
268 G_PARAM_STATIC_NAME |
269 G_PARAM_STATIC_NICK |
270 G_PARAM_STATIC_BLURB));
273 * GUPnPServiceInfo:location:
275 * The location of the device description file.
277 g_object_class_install_property
280 g_param_spec_string ("location",
282 "The location of the device description "
286 G_PARAM_CONSTRUCT_ONLY |
287 G_PARAM_STATIC_NAME |
288 G_PARAM_STATIC_NICK |
289 G_PARAM_STATIC_BLURB));
292 * GUPnPServiceInfo:udn:
294 * The UDN of the containing device.
296 g_object_class_install_property
299 g_param_spec_string ("udn",
301 "The UDN of the containing device",
304 G_PARAM_CONSTRUCT_ONLY |
305 G_PARAM_STATIC_NAME |
306 G_PARAM_STATIC_NICK |
307 G_PARAM_STATIC_BLURB));
310 * GUPnPServiceInfo:service-type:
314 g_object_class_install_property
317 g_param_spec_string ("service-type",
322 G_PARAM_CONSTRUCT_ONLY |
323 G_PARAM_STATIC_NAME |
324 G_PARAM_STATIC_NICK |
325 G_PARAM_STATIC_BLURB));
328 * GUPnPServiceInfo:url-base:
330 * The URL base (#SoupURI).
332 g_object_class_install_property
335 g_param_spec_boxed ("url-base",
340 G_PARAM_CONSTRUCT_ONLY |
341 G_PARAM_STATIC_NAME |
342 G_PARAM_STATIC_NICK |
343 G_PARAM_STATIC_BLURB));
346 * GUPnPServiceInfo:document:
352 g_object_class_install_property
355 g_param_spec_object ("document",
357 "The XML document related to this "
361 G_PARAM_CONSTRUCT_ONLY |
362 G_PARAM_STATIC_NAME |
363 G_PARAM_STATIC_NICK |
364 G_PARAM_STATIC_BLURB));
367 * GUPnPServiceInfo:element:
373 g_object_class_install_property
376 g_param_spec_pointer ("element",
378 "The XML element related to this "
381 G_PARAM_CONSTRUCT_ONLY |
382 G_PARAM_STATIC_NAME |
383 G_PARAM_STATIC_NICK |
384 G_PARAM_STATIC_BLURB));
388 * gupnp_service_info_get_context:
389 * @info: A #GUPnPServiceInfo
391 * Get the #GUPnPContext associated with @info.
393 * Returns: (transfer none): A #GUPnPContext.
396 gupnp_service_info_get_context (GUPnPServiceInfo *info)
398 g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
400 return info->priv->context;
404 * gupnp_service_info_get_location:
405 * @info: A #GUPnPServiceInfo
407 * Get the location of the device description file.
409 * Returns: A constant string.
412 gupnp_service_info_get_location (GUPnPServiceInfo *info)
414 g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
416 return info->priv->location;
420 * gupnp_service_info_get_url_base:
421 * @info: A #GUPnPServiceInfo
423 * Get the URL base of this service.
425 * Returns: A constant #SoupURI.
428 gupnp_service_info_get_url_base (GUPnPServiceInfo *info)
430 g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
432 return info->priv->url_base;
436 * gupnp_service_info_get_udn:
437 * @info: A #GUPnPServiceInfo
439 * Get the Unique Device Name of the containing device.
441 * Returns: A constant string.
444 gupnp_service_info_get_udn (GUPnPServiceInfo *info)
446 g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
448 return info->priv->udn;
452 * gupnp_service_info_get_service_type:
453 * @info: A #GUPnPServiceInfo
455 * Get the UPnP service type, or %NULL.
457 * Returns: A constant string.
460 gupnp_service_info_get_service_type (GUPnPServiceInfo *info)
462 g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
464 if (!info->priv->service_type) {
465 info->priv->service_type =
466 xml_util_get_child_element_content_glib
467 (info->priv->element, "serviceType");
470 return info->priv->service_type;
474 * gupnp_service_info_get_id:
475 * @info: A #GUPnPServiceInfo
477 * Get the ID of this service, or %NULL if there is no ID.
479 * Return value: A string. This string should be freed with g_free() after use.
482 gupnp_service_info_get_id (GUPnPServiceInfo *info)
484 g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
486 return xml_util_get_child_element_content_glib (info->priv->element,
491 * gupnp_service_info_get_scpd_url:
492 * @info: A #GUPnPServiceInfo
494 * Get the SCPD URL for this service, or %NULL if there is no SCPD.
496 * Return value: A string. This string should be freed with g_free() after use.
499 gupnp_service_info_get_scpd_url (GUPnPServiceInfo *info)
501 g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
503 return xml_util_get_child_element_content_url (info->priv->element,
505 info->priv->url_base);
509 * gupnp_service_info_get_control_url:
510 * @info: A #GUPnPServiceInfo
512 * Get the control URL for this service, or %NULL..
514 * Return value: A string. This string should be freed with g_free() after use.
517 gupnp_service_info_get_control_url (GUPnPServiceInfo *info)
519 g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
521 return xml_util_get_child_element_content_url (info->priv->element,
523 info->priv->url_base);
527 * gupnp_service_info_get_event_subscription_url:
528 * @info: A #GUPnPServiceInfo
530 * Get the event subscription URL for this service, or %NULL.
532 * Return value: A string. This string should be freed with g_free() after use.
535 gupnp_service_info_get_event_subscription_url (GUPnPServiceInfo *info)
537 g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
539 return xml_util_get_child_element_content_url (info->priv->element,
541 info->priv->url_base);
545 * gupnp_service_info_get_introspection:
546 * @info: A #GUPnPServiceInfo
547 * @error: return location for a #GError, or %NULL
549 * Note that introspection object is created from the information in service
550 * description document (SCPD) provided by the service so it can not be created
551 * if the service does not provide an SCPD.
553 * Warning: You should use gupnp_service_info_get_introspection_async()
554 * instead, this function re-enter the GMainloop before returning.
556 * Return value: (transfer full): A new #GUPnPServiceIntrospection for this
557 * service or %NULL. Unref after use.
559 GUPnPServiceIntrospection *
560 gupnp_service_info_get_introspection (GUPnPServiceInfo *info,
563 GUPnPServiceIntrospection *introspection;
564 SoupSession *session;
570 g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
572 introspection = NULL;
574 scpd_url = gupnp_service_info_get_scpd_url (info);
577 if (scpd_url != NULL) {
578 msg = soup_message_new (SOUP_METHOD_GET, scpd_url);
586 GUPNP_SERVER_ERROR_INVALID_URL,
587 "No valid SCPD URL defined");
592 /* Send off the message */
593 session = gupnp_context_get_session (info->priv->context);
595 status = soup_session_send_message (session, msg);
596 if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
597 _gupnp_error_set_server_error (error, msg);
599 g_object_unref (msg);
604 scpd = xmlRecoverMemory (msg->response_body->data,
605 msg->response_body->length);
607 g_object_unref (msg);
610 introspection = gupnp_service_introspection_new (scpd);
615 if (!introspection) {
618 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
619 "Could not parse SCPD");
622 return introspection;
626 * SCPD URL downloaded.
629 got_scpd_url (G_GNUC_UNUSED SoupSession *session,
631 GetSCPDURLData *data)
633 GUPnPServiceIntrospection *introspection;
636 introspection = NULL;
639 if (msg->status_code == SOUP_STATUS_CANCELLED)
642 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
645 scpd = xmlRecoverMemory (msg->response_body->data,
646 msg->response_body->length);
648 introspection = gupnp_service_introspection_new (scpd);
653 if (!introspection) {
656 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
657 "Could not parse SCPD");
660 error = _gupnp_error_new_server_error (msg);
662 /* prevent the callback from canceling the cancellable
663 * (and so freeing data just before we do) */
664 if (data->cancellable)
665 g_cancellable_disconnect (data->cancellable,
668 data->info->priv->pending_gets =
669 g_list_remove (data->info->priv->pending_gets, data);
671 data->callback (data->info,
677 g_error_free (error);
679 get_scpd_url_data_free (data);
683 cancellable_cancelled_cb (GCancellable *cancellable,
686 GUPnPServiceInfo *info;
687 GetSCPDURLData *data;
688 SoupSession *session;
694 session = gupnp_context_get_session (info->priv->context);
695 soup_session_cancel_message (session,
697 SOUP_STATUS_CANCELLED);
699 info->priv->pending_gets =
700 g_list_remove (info->priv->pending_gets, data);
702 error = g_error_new (G_IO_ERROR,
703 G_IO_ERROR_CANCELLED,
704 "The call was canceled");
706 data->callback (data->info,
711 get_scpd_url_data_free (data);
715 * gupnp_service_info_get_introspection_async:
716 * @info: A #GUPnPServiceInfo
717 * @callback: (scope async) : callback to be called when introspection object is ready.
718 * @user_data: user_data to be passed to the callback.
720 * Note that introspection object is created from the information in service
721 * description document (SCPD) provided by the service so it can not be created
722 * if the service does not provide an SCPD.
725 gupnp_service_info_get_introspection_async
726 (GUPnPServiceInfo *info,
727 GUPnPServiceIntrospectionCallback callback,
730 gupnp_service_info_get_introspection_async_full (info,
737 * gupnp_service_info_get_introspection_async_full:
738 * @info: A #GUPnPServiceInfo
739 * @callback: (scope async) : callback to be called when introspection object is ready.
740 * @cancellable: GCancellable that can be used to cancel the call, or %NULL.
741 * @user_data: user_data to be passed to the callback.
743 * Note that introspection object is created from the information in service
744 * description document (SCPD) provided by the service so it can not be created
745 * if the service does not provide an SCPD.
747 * If @cancellable is used to cancel the call, @callback will be called with
748 * error code %G_IO_ERROR_CANCELLED.
753 gupnp_service_info_get_introspection_async_full
754 (GUPnPServiceInfo *info,
755 GUPnPServiceIntrospectionCallback callback,
756 GCancellable *cancellable,
759 GetSCPDURLData *data;
761 SoupSession *session;
763 g_return_if_fail (GUPNP_IS_SERVICE_INFO (info));
764 g_return_if_fail (callback != NULL);
766 data = g_slice_new (GetSCPDURLData);
768 scpd_url = gupnp_service_info_get_scpd_url (info);
770 data->message = NULL;
771 if (scpd_url != NULL) {
772 data->message = soup_message_new (SOUP_METHOD_GET, scpd_url);
777 if (data->message == NULL) {
782 GUPNP_SERVER_ERROR_INVALID_URL,
783 "No valid SCPD URL defined");
785 callback (info, NULL, error, user_data);
787 g_error_free (error);
789 g_slice_free (GetSCPDURLData, data);
795 data->callback = callback;
796 data->user_data = user_data;
798 /* Send off the message */
799 info->priv->pending_gets =
800 g_list_prepend (info->priv->pending_gets,
803 session = gupnp_context_get_session (info->priv->context);
805 soup_session_queue_message (session,
807 (SoupSessionCallback) got_scpd_url,
810 data->cancellable = cancellable;
811 if (data->cancellable) {
812 g_object_ref (cancellable);
813 data->cancelled_id = g_cancellable_connect
815 G_CALLBACK (cancellable_cancelled_cb),