Imported Upstream version 0.20.12
[profile/ivi/GUPnP.git] / libgupnp / gupnp-service-info.c
1 /*
2  * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
3  *
4  * Author: Jorn Baayen <jorn@openedhand.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 /**
23  * SECTION:gupnp-service-info
24  * @short_description: Base abstract class for querying service information.
25  *
26  * The #GUPnPDeviceInfo base abstract class provides methods for querying
27  * service information.
28  */
29
30 #include <libsoup/soup.h>
31 #include <string.h>
32
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"
40 #include "xml-util.h"
41
42 G_DEFINE_ABSTRACT_TYPE (GUPnPServiceInfo,
43                         gupnp_service_info,
44                         G_TYPE_OBJECT);
45
46 struct _GUPnPServiceInfoPrivate {
47         GUPnPContext *context;
48
49         char *location;
50         char *udn;
51         char *service_type;
52
53         SoupURI *url_base;
54
55         GUPnPXMLDoc *doc;
56
57         xmlNode *element;
58
59         /* For async downloads */
60         GList *pending_gets;
61 };
62
63 enum {
64         PROP_0,
65         PROP_CONTEXT,
66         PROP_LOCATION,
67         PROP_UDN,
68         PROP_SERVICE_TYPE,
69         PROP_URL_BASE,
70         PROP_DOCUMENT,
71         PROP_ELEMENT
72 };
73
74 typedef struct {
75         GUPnPServiceInfo                 *info;
76
77         GUPnPServiceIntrospectionCallback callback;
78         gpointer                          user_data;
79
80         GCancellable                     *cancellable;
81         gulong                            cancelled_id;
82
83         SoupMessage                      *message;
84 } GetSCPDURLData;
85
86 static void
87 get_scpd_url_data_free (GetSCPDURLData *data)
88 {
89         if (data->cancellable)
90                 g_object_unref (data->cancellable);
91
92         g_slice_free (GetSCPDURLData, data);
93 }
94
95 static void
96 gupnp_service_info_init (GUPnPServiceInfo *info)
97 {
98         info->priv = G_TYPE_INSTANCE_GET_PRIVATE (info,
99                                                   GUPNP_TYPE_SERVICE_INFO,
100                                                   GUPnPServiceInfoPrivate);
101 }
102
103 static void
104 gupnp_service_info_set_property (GObject      *object,
105                                  guint         property_id,
106                                  const GValue *value,
107                                  GParamSpec   *pspec)
108 {
109         GUPnPServiceInfo *info;
110
111         info = GUPNP_SERVICE_INFO (object);
112
113         switch (property_id) {
114         case PROP_CONTEXT:
115                 info->priv->context = g_object_ref (g_value_get_object (value));
116                 break;
117         case PROP_LOCATION:
118                 info->priv->location = g_value_dup_string (value);
119                 break;
120         case PROP_UDN:
121                 info->priv->udn = g_value_dup_string (value);
122                 break;
123         case PROP_SERVICE_TYPE:
124                 info->priv->service_type = g_value_dup_string (value);
125                 break;
126         case PROP_URL_BASE:
127                 info->priv->url_base = g_value_dup_boxed (value);
128                 break;
129         case PROP_DOCUMENT:
130                 info->priv->doc = g_value_dup_object (value);
131                 break;
132         case PROP_ELEMENT:
133                 info->priv->element = g_value_get_pointer (value);
134                 break;
135         default:
136                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
137                 break;
138         }
139 }
140
141 static void
142 gupnp_service_info_get_property (GObject    *object,
143                                  guint       property_id,
144                                  GValue     *value,
145                                  GParamSpec *pspec)
146 {
147         GUPnPServiceInfo *info;
148
149         info = GUPNP_SERVICE_INFO (object);
150
151         switch (property_id) {
152         case PROP_CONTEXT:
153                 g_value_set_object (value,
154                                     info->priv->context);
155                 break;
156         case PROP_LOCATION:
157                 g_value_set_string (value,
158                                     info->priv->location);
159                 break;
160         case PROP_UDN:
161                 g_value_set_string (value,
162                                     info->priv->udn);
163                 break;
164         case PROP_SERVICE_TYPE:
165                 g_value_set_string (value,
166                                     gupnp_service_info_get_service_type (info));
167                 break;
168         case PROP_URL_BASE:
169                 g_value_set_boxed (value,
170                                    info->priv->url_base);
171                 break;
172         default:
173                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
174                 break;
175         }
176 }
177
178 static void
179 gupnp_service_info_dispose (GObject *object)
180 {
181         GUPnPServiceInfo *info;
182
183         info = GUPNP_SERVICE_INFO (object);
184
185         /* Cancel any pending SCPD GETs */
186         if (info->priv->context) {
187                 SoupSession *session;
188
189                 session = gupnp_context_get_session (info->priv->context);
190
191                 while (info->priv->pending_gets) {
192                         GetSCPDURLData *data;
193
194                         data = info->priv->pending_gets->data;
195
196                         if (data->cancellable)
197                                 g_cancellable_disconnect (data->cancellable,
198                                                           data->cancelled_id);
199
200                         soup_session_cancel_message (session,
201                                                      data->message,
202                                                      SOUP_STATUS_CANCELLED);
203
204                         get_scpd_url_data_free (data);
205
206                         info->priv->pending_gets =
207                                 g_list_delete_link (info->priv->pending_gets,
208                                                     info->priv->pending_gets);
209                 }
210
211                 /* Unref context */
212                 g_object_unref (info->priv->context);
213                 info->priv->context = NULL;
214         }
215
216         if (info->priv->doc) {
217                 g_object_unref (info->priv->doc);
218                 info->priv->doc = NULL;
219         }
220
221         G_OBJECT_CLASS (gupnp_service_info_parent_class)->dispose (object);
222 }
223
224 static void
225 gupnp_service_info_finalize (GObject *object)
226 {
227         GUPnPServiceInfo *info;
228
229         info = GUPNP_SERVICE_INFO (object);
230
231         g_free (info->priv->location);
232         g_free (info->priv->udn);
233         g_free (info->priv->service_type);
234
235         soup_uri_free (info->priv->url_base);
236
237         G_OBJECT_CLASS (gupnp_service_info_parent_class)->finalize (object);
238 }
239
240 static void
241 gupnp_service_info_class_init (GUPnPServiceInfoClass *klass)
242 {
243         GObjectClass *object_class;
244
245         object_class = G_OBJECT_CLASS (klass);
246
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;
251
252         g_type_class_add_private (klass, sizeof (GUPnPServiceInfoPrivate));
253
254         /**
255          * GUPnPServiceInfo:context:
256          *
257          * The #GUPnPContext to use.
258          **/
259         g_object_class_install_property
260                 (object_class,
261                  PROP_CONTEXT,
262                  g_param_spec_object ("context",
263                                       "Context",
264                                       "The GUPnPContext.",
265                                       GUPNP_TYPE_CONTEXT,
266                                       G_PARAM_READWRITE |
267                                       G_PARAM_CONSTRUCT_ONLY |
268                                       G_PARAM_STATIC_NAME |
269                                       G_PARAM_STATIC_NICK |
270                                       G_PARAM_STATIC_BLURB));
271
272         /**
273          * GUPnPServiceInfo:location:
274          *
275          * The location of the device description file.
276          **/
277         g_object_class_install_property
278                 (object_class,
279                  PROP_LOCATION,
280                  g_param_spec_string ("location",
281                                       "Location",
282                                       "The location of the device description "
283                                       "file",
284                                       NULL,
285                                       G_PARAM_READWRITE |
286                                       G_PARAM_CONSTRUCT_ONLY |
287                                       G_PARAM_STATIC_NAME |
288                                       G_PARAM_STATIC_NICK |
289                                       G_PARAM_STATIC_BLURB));
290
291         /**
292          * GUPnPServiceInfo:udn:
293          *
294          * The UDN of the containing device.
295          **/
296         g_object_class_install_property
297                 (object_class,
298                  PROP_UDN,
299                  g_param_spec_string ("udn",
300                                       "UDN",
301                                       "The UDN of the containing device",
302                                       NULL,
303                                       G_PARAM_READWRITE |
304                                       G_PARAM_CONSTRUCT_ONLY |
305                                       G_PARAM_STATIC_NAME |
306                                       G_PARAM_STATIC_NICK |
307                                       G_PARAM_STATIC_BLURB));
308
309         /**
310          * GUPnPServiceInfo:service-type:
311          *
312          * The service type.
313          **/
314         g_object_class_install_property
315                 (object_class,
316                  PROP_SERVICE_TYPE,
317                  g_param_spec_string ("service-type",
318                                       "Service type",
319                                       "The service type",
320                                       NULL,
321                                       G_PARAM_READWRITE |
322                                       G_PARAM_CONSTRUCT_ONLY |
323                                       G_PARAM_STATIC_NAME |
324                                       G_PARAM_STATIC_NICK |
325                                       G_PARAM_STATIC_BLURB));
326
327         /**
328          * GUPnPServiceInfo:url-base:
329          *
330          * The URL base (#SoupURI).
331          **/
332         g_object_class_install_property
333                 (object_class,
334                  PROP_URL_BASE,
335                  g_param_spec_boxed ("url-base",
336                                      "URL base",
337                                      "The URL base",
338                                      SOUP_TYPE_URI,
339                                      G_PARAM_READWRITE |
340                                      G_PARAM_CONSTRUCT_ONLY |
341                                      G_PARAM_STATIC_NAME |
342                                      G_PARAM_STATIC_NICK |
343                                      G_PARAM_STATIC_BLURB));
344
345         /**
346          * GUPnPServiceInfo:document:
347          *
348          * Private property.
349          *
350          * Stability: Private
351          **/
352         g_object_class_install_property
353                 (object_class,
354                  PROP_DOCUMENT,
355                  g_param_spec_object ("document",
356                                       "Document",
357                                       "The XML document related to this "
358                                       "service",
359                                       GUPNP_TYPE_XML_DOC,
360                                       G_PARAM_WRITABLE |
361                                       G_PARAM_CONSTRUCT_ONLY |
362                                       G_PARAM_STATIC_NAME |
363                                       G_PARAM_STATIC_NICK |
364                                       G_PARAM_STATIC_BLURB));
365
366         /**
367          * GUPnPServiceInfo:element:
368          *
369          * Private property.
370          *
371          * Stability: Private
372          **/
373         g_object_class_install_property
374                 (object_class,
375                  PROP_ELEMENT,
376                  g_param_spec_pointer ("element",
377                                        "Element",
378                                        "The XML element related to this "
379                                        "device",
380                                        G_PARAM_WRITABLE |
381                                        G_PARAM_CONSTRUCT_ONLY |
382                                        G_PARAM_STATIC_NAME |
383                                        G_PARAM_STATIC_NICK |
384                                        G_PARAM_STATIC_BLURB));
385 }
386
387 /**
388  * gupnp_service_info_get_context:
389  * @info: A #GUPnPServiceInfo
390  *
391  * Get the #GUPnPContext associated with @info.
392  *
393  * Returns: (transfer none): A #GUPnPContext.
394  **/
395 GUPnPContext *
396 gupnp_service_info_get_context (GUPnPServiceInfo *info)
397 {
398         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
399
400         return info->priv->context;
401 }
402
403 /**
404  * gupnp_service_info_get_location:
405  * @info: A #GUPnPServiceInfo
406  *
407  * Get the location of the device description file.
408  *
409  * Returns: A constant string.
410  **/
411 const char *
412 gupnp_service_info_get_location (GUPnPServiceInfo *info)
413 {
414         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
415
416         return info->priv->location;
417 }
418
419 /**
420  * gupnp_service_info_get_url_base:
421  * @info: A #GUPnPServiceInfo
422  *
423  * Get the URL base of this service.
424  *
425  * Returns: A constant #SoupURI.
426  **/
427 const SoupURI *
428 gupnp_service_info_get_url_base (GUPnPServiceInfo *info)
429 {
430         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
431
432         return info->priv->url_base;
433 }
434
435 /**
436  * gupnp_service_info_get_udn:
437  * @info: A #GUPnPServiceInfo
438  *
439  * Get the Unique Device Name of the containing device.
440  *
441  * Returns: A constant string.
442  **/
443 const char *
444 gupnp_service_info_get_udn (GUPnPServiceInfo *info)
445 {
446         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
447
448         return info->priv->udn;
449 }
450
451 /**
452  * gupnp_service_info_get_service_type:
453  * @info: A #GUPnPServiceInfo
454  *
455  * Get the UPnP service type, or %NULL.
456  *
457  * Returns: A constant string.
458  **/
459 const char *
460 gupnp_service_info_get_service_type (GUPnPServiceInfo *info)
461 {
462         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
463
464         if (!info->priv->service_type) {
465                 info->priv->service_type =
466                         xml_util_get_child_element_content_glib
467                                 (info->priv->element, "serviceType");
468         }
469
470         return info->priv->service_type;
471 }
472
473 /**
474  * gupnp_service_info_get_id:
475  * @info: A #GUPnPServiceInfo
476  *
477  * Get the ID of this service, or %NULL if there is no ID.
478  *
479  * Return value: A string. This string should be freed with g_free() after use.
480  **/
481 char *
482 gupnp_service_info_get_id (GUPnPServiceInfo *info)
483 {
484         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
485
486         return xml_util_get_child_element_content_glib (info->priv->element,
487                                                         "serviceId");
488 }
489
490 /**
491  * gupnp_service_info_get_scpd_url:
492  * @info: A #GUPnPServiceInfo
493  *
494  * Get the SCPD URL for this service, or %NULL if there is no SCPD.
495  *
496  * Return value: A string. This string should be freed with g_free() after use.
497  **/
498 char *
499 gupnp_service_info_get_scpd_url (GUPnPServiceInfo *info)
500 {
501         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
502
503         return xml_util_get_child_element_content_url (info->priv->element,
504                                                        "SCPDURL",
505                                                        info->priv->url_base);
506 }
507
508 /**
509  * gupnp_service_info_get_control_url:
510  * @info: A #GUPnPServiceInfo
511  *
512  * Get the control URL for this service, or %NULL..
513  *
514  * Return value: A string. This string should be freed with g_free() after use.
515  **/
516 char *
517 gupnp_service_info_get_control_url (GUPnPServiceInfo *info)
518 {
519         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
520
521         return xml_util_get_child_element_content_url (info->priv->element,
522                                                        "controlURL",
523                                                        info->priv->url_base);
524 }
525
526 /**
527  * gupnp_service_info_get_event_subscription_url:
528  * @info: A #GUPnPServiceInfo
529  *
530  * Get the event subscription URL for this service, or %NULL.
531  *
532  * Return value: A string. This string should be freed with g_free() after use.
533  **/
534 char *
535 gupnp_service_info_get_event_subscription_url (GUPnPServiceInfo *info)
536 {
537         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
538
539         return xml_util_get_child_element_content_url (info->priv->element,
540                                                        "eventSubURL",
541                                                        info->priv->url_base);
542 }
543
544 /**
545  * gupnp_service_info_get_introspection:
546  * @info: A #GUPnPServiceInfo
547  * @error: return location for a #GError, or %NULL
548  *
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.
552  *
553  * Warning: You  should use gupnp_service_info_get_introspection_async()
554  * instead, this function re-enter the GMainloop before returning.
555  *
556  * Return value: (transfer full):  A new #GUPnPServiceIntrospection for this
557  * service or %NULL. Unref after use.
558  **/
559 GUPnPServiceIntrospection *
560 gupnp_service_info_get_introspection (GUPnPServiceInfo *info,
561                                       GError          **error)
562 {
563         GUPnPServiceIntrospection *introspection;
564         SoupSession *session;
565         SoupMessage *msg;
566         int status;
567         char *scpd_url;
568         xmlDoc *scpd;
569
570         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
571
572         introspection = NULL;
573
574         scpd_url = gupnp_service_info_get_scpd_url (info);
575
576         msg = NULL;
577         if (scpd_url != NULL) {
578                 msg = soup_message_new (SOUP_METHOD_GET, scpd_url);
579
580                 g_free (scpd_url);
581         }
582
583         if (msg == NULL) {
584                 g_set_error (error,
585                              GUPNP_SERVER_ERROR,
586                              GUPNP_SERVER_ERROR_INVALID_URL,
587                              "No valid SCPD URL defined");
588
589                 return NULL;
590         }
591
592         /* Send off the message */
593         session = gupnp_context_get_session (info->priv->context);
594
595         status = soup_session_send_message (session, msg);
596         if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
597                 _gupnp_error_set_server_error (error, msg);
598
599                 g_object_unref (msg);
600
601                 return NULL;
602         }
603
604         scpd = xmlRecoverMemory (msg->response_body->data,
605                                  msg->response_body->length);
606
607         g_object_unref (msg);
608
609         if (scpd) {
610                 introspection = gupnp_service_introspection_new (scpd);
611
612                 xmlFreeDoc (scpd);
613         }
614
615         if (!introspection) {
616                 g_set_error (error,
617                              GUPNP_SERVER_ERROR,
618                              GUPNP_SERVER_ERROR_INVALID_RESPONSE,
619                              "Could not parse SCPD");
620         }
621
622         return introspection;
623 }
624
625 /*
626  * SCPD URL downloaded.
627  */
628 static void
629 got_scpd_url (G_GNUC_UNUSED SoupSession *session,
630               SoupMessage               *msg,
631               GetSCPDURLData            *data)
632 {
633         GUPnPServiceIntrospection *introspection;
634         GError *error;
635
636         introspection = NULL;
637         error = NULL;
638
639         if (msg->status_code == SOUP_STATUS_CANCELLED)
640                 return;
641
642         if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
643                 xmlDoc *scpd;
644
645                 scpd = xmlRecoverMemory (msg->response_body->data,
646                                          msg->response_body->length);
647                 if (scpd) {
648                         introspection = gupnp_service_introspection_new (scpd);
649
650                         xmlFreeDoc (scpd);
651                 }
652
653                 if (!introspection) {
654                         error = g_error_new
655                                         (GUPNP_SERVER_ERROR,
656                                          GUPNP_SERVER_ERROR_INVALID_RESPONSE,
657                                          "Could not parse SCPD");
658                 }
659         } else
660                 error = _gupnp_error_new_server_error (msg);
661
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,
666                                           data->cancelled_id);
667
668         data->info->priv->pending_gets =
669                 g_list_remove (data->info->priv->pending_gets, data);
670
671         data->callback (data->info,
672                         introspection,
673                         error,
674                         data->user_data);
675
676         if (error)
677                 g_error_free (error);
678
679         get_scpd_url_data_free (data);
680 }
681
682 static void
683 cancellable_cancelled_cb (GCancellable *cancellable,
684                           gpointer user_data)
685 {
686         GUPnPServiceInfo *info;
687         GetSCPDURLData *data;
688         SoupSession *session;
689         GError *error;
690
691         data = user_data;
692         info = data->info;
693
694         session = gupnp_context_get_session (info->priv->context);
695         soup_session_cancel_message (session,
696                                      data->message,
697                                      SOUP_STATUS_CANCELLED);
698
699         info->priv->pending_gets =
700                 g_list_remove (info->priv->pending_gets, data);
701
702         error = g_error_new (G_IO_ERROR,
703                              G_IO_ERROR_CANCELLED,
704                              "The call was canceled");
705
706         data->callback (data->info,
707                         NULL,
708                         error,
709                         data->user_data);
710
711         get_scpd_url_data_free (data);
712 }
713
714 /**
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.
719  *
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.
723  **/
724 void
725 gupnp_service_info_get_introspection_async
726                                 (GUPnPServiceInfo                 *info,
727                                  GUPnPServiceIntrospectionCallback callback,
728                                  gpointer                          user_data)
729 {
730         gupnp_service_info_get_introspection_async_full (info,
731                                                          callback,
732                                                          NULL,
733                                                          user_data);
734 }
735
736 /**
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.
742  *
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.
746  *
747  * If @cancellable is used to cancel the call, @callback will be called with
748  * error code %G_IO_ERROR_CANCELLED.
749  *
750  * Since: 0.20.9.
751  **/
752 void
753 gupnp_service_info_get_introspection_async_full
754                                 (GUPnPServiceInfo                 *info,
755                                  GUPnPServiceIntrospectionCallback callback,
756                                  GCancellable                     *cancellable,
757                                  gpointer                          user_data)
758 {
759         GetSCPDURLData *data;
760         char *scpd_url;
761         SoupSession *session;
762
763         g_return_if_fail (GUPNP_IS_SERVICE_INFO (info));
764         g_return_if_fail (callback != NULL);
765
766         data = g_slice_new (GetSCPDURLData);
767
768         scpd_url = gupnp_service_info_get_scpd_url (info);
769
770         data->message = NULL;
771         if (scpd_url != NULL) {
772                 data->message = soup_message_new (SOUP_METHOD_GET, scpd_url);
773
774                 g_free (scpd_url);
775         }
776
777         if (data->message == NULL) {
778                 GError *error;
779
780                 error = g_error_new
781                                 (GUPNP_SERVER_ERROR,
782                                  GUPNP_SERVER_ERROR_INVALID_URL,
783                                  "No valid SCPD URL defined");
784
785                 callback (info, NULL, error, user_data);
786
787                 g_error_free (error);
788
789                 g_slice_free (GetSCPDURLData, data);
790
791                 return;
792         }
793
794         data->info      = info;
795         data->callback  = callback;
796         data->user_data = user_data;
797
798         /* Send off the message */
799         info->priv->pending_gets =
800                 g_list_prepend (info->priv->pending_gets,
801                                 data);
802
803         session = gupnp_context_get_session (info->priv->context);
804
805         soup_session_queue_message (session,
806                                     data->message,
807                                     (SoupSessionCallback) got_scpd_url,
808                                     data);
809
810         data->cancellable = cancellable;
811         if (data->cancellable) {
812                 g_object_ref (cancellable);
813                 data->cancelled_id = g_cancellable_connect
814                                 (data->cancellable,
815                                  G_CALLBACK (cancellable_cancelled_cb),
816                                  data,
817                                  NULL);
818         }
819 }