1da6c1988ca770851abb2bbb2e82f9b3e82463be
[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         SoupMessage                      *message;
81 } GetSCPDURLData;
82
83 static void
84 get_scpd_url_data_free (GetSCPDURLData *data)
85 {
86         g_slice_free (GetSCPDURLData, data);
87 }
88
89 static void
90 gupnp_service_info_init (GUPnPServiceInfo *info)
91 {
92         info->priv = G_TYPE_INSTANCE_GET_PRIVATE (info,
93                                                   GUPNP_TYPE_SERVICE_INFO,
94                                                   GUPnPServiceInfoPrivate);
95 }
96
97 static void
98 gupnp_service_info_set_property (GObject      *object,
99                                  guint         property_id,
100                                  const GValue *value,
101                                  GParamSpec   *pspec)
102 {
103         GUPnPServiceInfo *info;
104
105         info = GUPNP_SERVICE_INFO (object);
106
107         switch (property_id) {
108         case PROP_CONTEXT:
109                 info->priv->context = g_object_ref (g_value_get_object (value));
110                 break;
111         case PROP_LOCATION:
112                 info->priv->location = g_value_dup_string (value);
113                 break;
114         case PROP_UDN:
115                 info->priv->udn = g_value_dup_string (value);
116                 break;
117         case PROP_SERVICE_TYPE:
118                 info->priv->service_type = g_value_dup_string (value);
119                 break;
120         case PROP_URL_BASE:
121                 info->priv->url_base = g_value_dup_boxed (value);
122                 break;
123         case PROP_DOCUMENT:
124                 info->priv->doc = g_value_dup_object (value);
125                 break;
126         case PROP_ELEMENT:
127                 info->priv->element = g_value_get_pointer (value);
128                 break;
129         default:
130                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
131                 break;
132         }
133 }
134
135 static void
136 gupnp_service_info_get_property (GObject    *object,
137                                  guint       property_id,
138                                  GValue     *value,
139                                  GParamSpec *pspec)
140 {
141         GUPnPServiceInfo *info;
142
143         info = GUPNP_SERVICE_INFO (object);
144
145         switch (property_id) {
146         case PROP_CONTEXT:
147                 g_value_set_object (value,
148                                     info->priv->context);
149                 break;
150         case PROP_LOCATION:
151                 g_value_set_string (value,
152                                     info->priv->location);
153                 break;
154         case PROP_UDN:
155                 g_value_set_string (value,
156                                     info->priv->udn);
157                 break;
158         case PROP_SERVICE_TYPE:
159                 g_value_set_string (value,
160                                     gupnp_service_info_get_service_type (info));
161                 break;
162         case PROP_URL_BASE:
163                 g_value_set_boxed (value,
164                                    info->priv->url_base);
165                 break;
166         default:
167                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
168                 break;
169         }
170 }
171
172 static void
173 gupnp_service_info_dispose (GObject *object)
174 {
175         GUPnPServiceInfo *info;
176
177         info = GUPNP_SERVICE_INFO (object);
178
179         /* Cancel any pending SCPD GETs */
180         if (info->priv->context) {
181                 SoupSession *session;
182
183                 session = gupnp_context_get_session (info->priv->context);
184
185                 while (info->priv->pending_gets) {
186                         GetSCPDURLData *data;
187
188                         data = info->priv->pending_gets->data;
189
190                         soup_session_cancel_message (session,
191                                                      data->message,
192                                                      SOUP_STATUS_CANCELLED);
193
194                         get_scpd_url_data_free (data);
195
196                         info->priv->pending_gets =
197                                 g_list_delete_link (info->priv->pending_gets,
198                                                     info->priv->pending_gets);
199                 }
200
201                 /* Unref context */
202                 g_object_unref (info->priv->context);
203                 info->priv->context = NULL;
204         }
205
206         if (info->priv->doc) {
207                 g_object_unref (info->priv->doc);
208                 info->priv->doc = NULL;
209         }
210
211         G_OBJECT_CLASS (gupnp_service_info_parent_class)->dispose (object);
212 }
213
214 static void
215 gupnp_service_info_finalize (GObject *object)
216 {
217         GUPnPServiceInfo *info;
218
219         info = GUPNP_SERVICE_INFO (object);
220
221         g_free (info->priv->location);
222         g_free (info->priv->udn);
223         g_free (info->priv->service_type);
224
225         soup_uri_free (info->priv->url_base);
226
227         G_OBJECT_CLASS (gupnp_service_info_parent_class)->finalize (object);
228 }
229
230 static void
231 gupnp_service_info_class_init (GUPnPServiceInfoClass *klass)
232 {
233         GObjectClass *object_class;
234
235         object_class = G_OBJECT_CLASS (klass);
236
237         object_class->set_property = gupnp_service_info_set_property;
238         object_class->get_property = gupnp_service_info_get_property;
239         object_class->dispose      = gupnp_service_info_dispose;
240         object_class->finalize     = gupnp_service_info_finalize;
241
242         g_type_class_add_private (klass, sizeof (GUPnPServiceInfoPrivate));
243
244         /**
245          * GUPnPServiceInfo:context:
246          *
247          * The #GUPnPContext to use.
248          **/
249         g_object_class_install_property
250                 (object_class,
251                  PROP_CONTEXT,
252                  g_param_spec_object ("context",
253                                       "Context",
254                                       "The GUPnPContext.",
255                                       GUPNP_TYPE_CONTEXT,
256                                       G_PARAM_READWRITE |
257                                       G_PARAM_CONSTRUCT_ONLY |
258                                       G_PARAM_STATIC_NAME |
259                                       G_PARAM_STATIC_NICK |
260                                       G_PARAM_STATIC_BLURB));
261
262         /**
263          * GUPnPServiceInfo:location:
264          *
265          * The location of the device description file.
266          **/
267         g_object_class_install_property
268                 (object_class,
269                  PROP_LOCATION,
270                  g_param_spec_string ("location",
271                                       "Location",
272                                       "The location of the device description "
273                                       "file",
274                                       NULL,
275                                       G_PARAM_READWRITE |
276                                       G_PARAM_CONSTRUCT_ONLY |
277                                       G_PARAM_STATIC_NAME |
278                                       G_PARAM_STATIC_NICK |
279                                       G_PARAM_STATIC_BLURB));
280
281         /**
282          * GUPnPServiceInfo:udn:
283          *
284          * The UDN of the containing device.
285          **/
286         g_object_class_install_property
287                 (object_class,
288                  PROP_UDN,
289                  g_param_spec_string ("udn",
290                                       "UDN",
291                                       "The UDN of the containing device",
292                                       NULL,
293                                       G_PARAM_READWRITE |
294                                       G_PARAM_CONSTRUCT_ONLY |
295                                       G_PARAM_STATIC_NAME |
296                                       G_PARAM_STATIC_NICK |
297                                       G_PARAM_STATIC_BLURB));
298
299         /**
300          * GUPnPServiceInfo:service-type:
301          *
302          * The service type.
303          **/
304         g_object_class_install_property
305                 (object_class,
306                  PROP_SERVICE_TYPE,
307                  g_param_spec_string ("service-type",
308                                       "Service type",
309                                       "The service type",
310                                       NULL,
311                                       G_PARAM_READWRITE |
312                                       G_PARAM_CONSTRUCT_ONLY |
313                                       G_PARAM_STATIC_NAME |
314                                       G_PARAM_STATIC_NICK |
315                                       G_PARAM_STATIC_BLURB));
316
317         /**
318          * GUPnPServiceInfo:url-base:
319          *
320          * The URL base (#SoupURI).
321          **/
322         g_object_class_install_property
323                 (object_class,
324                  PROP_URL_BASE,
325                  g_param_spec_boxed ("url-base",
326                                      "URL base",
327                                      "The URL base",
328                                      SOUP_TYPE_URI,
329                                      G_PARAM_READWRITE |
330                                      G_PARAM_CONSTRUCT_ONLY |
331                                      G_PARAM_STATIC_NAME |
332                                      G_PARAM_STATIC_NICK |
333                                      G_PARAM_STATIC_BLURB));
334
335         /**
336          * GUPnPServiceInfo:document:
337          *
338          * Private property.
339          *
340          * Stability: Private
341          **/
342         g_object_class_install_property
343                 (object_class,
344                  PROP_DOCUMENT,
345                  g_param_spec_object ("document",
346                                       "Document",
347                                       "The XML document related to this "
348                                       "service",
349                                       GUPNP_TYPE_XML_DOC,
350                                       G_PARAM_WRITABLE |
351                                       G_PARAM_CONSTRUCT_ONLY |
352                                       G_PARAM_STATIC_NAME |
353                                       G_PARAM_STATIC_NICK |
354                                       G_PARAM_STATIC_BLURB));
355
356         /**
357          * GUPnPServiceInfo:element:
358          *
359          * Private property.
360          *
361          * Stability: Private
362          **/
363         g_object_class_install_property
364                 (object_class,
365                  PROP_ELEMENT,
366                  g_param_spec_pointer ("element",
367                                        "Element",
368                                        "The XML element related to this "
369                                        "device",
370                                        G_PARAM_WRITABLE |
371                                        G_PARAM_CONSTRUCT_ONLY |
372                                        G_PARAM_STATIC_NAME |
373                                        G_PARAM_STATIC_NICK |
374                                        G_PARAM_STATIC_BLURB));
375 }
376
377 /**
378  * gupnp_service_info_get_context:
379  * @info: A #GUPnPServiceInfo
380  *
381  * Get the #GUPnPContext associated with @info.
382  *
383  * Returns: (transfer none): A #GUPnPContext.
384  **/
385 GUPnPContext *
386 gupnp_service_info_get_context (GUPnPServiceInfo *info)
387 {
388         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
389
390         return info->priv->context;
391 }
392
393 /**
394  * gupnp_service_info_get_location:
395  * @info: A #GUPnPServiceInfo
396  *
397  * Get the location of the device description file.
398  *
399  * Returns: A constant string.
400  **/
401 const char *
402 gupnp_service_info_get_location (GUPnPServiceInfo *info)
403 {
404         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
405
406         return info->priv->location;
407 }
408
409 /**
410  * gupnp_service_info_get_url_base:
411  * @info: A #GUPnPServiceInfo
412  *
413  * Get the URL base of this service.
414  *
415  * Returns: A constant #SoupURI.
416  **/
417 const SoupURI *
418 gupnp_service_info_get_url_base (GUPnPServiceInfo *info)
419 {
420         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
421
422         return info->priv->url_base;
423 }
424
425 /**
426  * gupnp_service_info_get_udn:
427  * @info: A #GUPnPServiceInfo
428  *
429  * Get the Unique Device Name of the containing device.
430  *
431  * Returns: A constant string.
432  **/
433 const char *
434 gupnp_service_info_get_udn (GUPnPServiceInfo *info)
435 {
436         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
437
438         return info->priv->udn;
439 }
440
441 /**
442  * gupnp_service_info_get_service_type:
443  * @info: A #GUPnPServiceInfo
444  *
445  * Get the UPnP service type, or %NULL.
446  *
447  * Returns: A constant string.
448  **/
449 const char *
450 gupnp_service_info_get_service_type (GUPnPServiceInfo *info)
451 {
452         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
453
454         if (!info->priv->service_type) {
455                 info->priv->service_type =
456                         xml_util_get_child_element_content_glib
457                                 (info->priv->element, "serviceType");
458         }
459
460         return info->priv->service_type;
461 }
462
463 /**
464  * gupnp_service_info_get_id:
465  * @info: A #GUPnPServiceInfo
466  *
467  * Get the ID of this service, or %NULL if there is no ID.
468  *
469  * Return value: A string. This string should be freed with g_free() after use.
470  **/
471 char *
472 gupnp_service_info_get_id (GUPnPServiceInfo *info)
473 {
474         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
475
476         return xml_util_get_child_element_content_glib (info->priv->element,
477                                                         "serviceId");
478 }
479
480 /**
481  * gupnp_service_info_get_scpd_url:
482  * @info: A #GUPnPServiceInfo
483  *
484  * Get the SCPD URL for this service, or %NULL if there is no SCPD.
485  *
486  * Return value: A string. This string should be freed with g_free() after use.
487  **/
488 char *
489 gupnp_service_info_get_scpd_url (GUPnPServiceInfo *info)
490 {
491         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
492
493         return xml_util_get_child_element_content_url (info->priv->element,
494                                                        "SCPDURL",
495                                                        info->priv->url_base);
496 }
497
498 /**
499  * gupnp_service_info_get_control_url:
500  * @info: A #GUPnPServiceInfo
501  *
502  * Get the control URL for this service, or %NULL..
503  *
504  * Return value: A string. This string should be freed with g_free() after use.
505  **/
506 char *
507 gupnp_service_info_get_control_url (GUPnPServiceInfo *info)
508 {
509         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
510
511         return xml_util_get_child_element_content_url (info->priv->element,
512                                                        "controlURL",
513                                                        info->priv->url_base);
514 }
515
516 /**
517  * gupnp_service_info_get_event_subscription_url:
518  * @info: A #GUPnPServiceInfo
519  *
520  * Get the event subscription URL for this service, or %NULL.
521  *
522  * Return value: A string. This string should be freed with g_free() after use.
523  **/
524 char *
525 gupnp_service_info_get_event_subscription_url (GUPnPServiceInfo *info)
526 {
527         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
528
529         return xml_util_get_child_element_content_url (info->priv->element,
530                                                        "eventSubURL",
531                                                        info->priv->url_base);
532 }
533
534 /**
535  * gupnp_service_info_get_introspection:
536  * @info: A #GUPnPServiceInfo
537  * @error: return location for a #GError, or %NULL
538  *
539  * Note that introspection object is created from the information in service
540  * description document (SCPD) provided by the service so it can not be created
541  * if the service does not provide an SCPD.
542  *
543  * Warning: You  should use gupnp_service_info_get_introspection_async()
544  * instead, this function re-enter the GMainloop before returning.
545  *
546  * Return value: (transfer full):  A new #GUPnPServiceIntrospection for this
547  * service or %NULL. Unref after use.
548  **/
549 GUPnPServiceIntrospection *
550 gupnp_service_info_get_introspection (GUPnPServiceInfo *info,
551                                       GError          **error)
552 {
553         GUPnPServiceIntrospection *introspection;
554         SoupSession *session;
555         SoupMessage *msg;
556         int status;
557         char *scpd_url;
558         xmlDoc *scpd;
559
560         g_return_val_if_fail (GUPNP_IS_SERVICE_INFO (info), NULL);
561
562         introspection = NULL;
563
564         scpd_url = gupnp_service_info_get_scpd_url (info);
565
566         msg = NULL;
567         if (scpd_url != NULL) {
568                 msg = soup_message_new (SOUP_METHOD_GET, scpd_url);
569
570                 g_free (scpd_url);
571         }
572
573         if (msg == NULL) {
574                 g_set_error (error,
575                              GUPNP_SERVER_ERROR,
576                              GUPNP_SERVER_ERROR_INVALID_URL,
577                              "No valid SCPD URL defined");
578
579                 return NULL;
580         }
581
582         /* Send off the message */
583         session = gupnp_context_get_session (info->priv->context);
584
585         status = soup_session_send_message (session, msg);
586         if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
587                 _gupnp_error_set_server_error (error, msg);
588
589                 g_object_unref (msg);
590
591                 return NULL;
592         }
593
594         scpd = xmlRecoverMemory (msg->response_body->data,
595                                  msg->response_body->length);
596
597         g_object_unref (msg);
598
599         if (scpd) {
600                 introspection = gupnp_service_introspection_new (scpd);
601
602                 xmlFreeDoc (scpd);
603         }
604
605         if (!introspection) {
606                 g_set_error (error,
607                              GUPNP_SERVER_ERROR,
608                              GUPNP_SERVER_ERROR_INVALID_RESPONSE,
609                              "Could not parse SCPD");
610         }
611
612         return introspection;
613 }
614
615 /*
616  * SCPD URL downloaded.
617  */
618 static void
619 got_scpd_url (G_GNUC_UNUSED SoupSession *session,
620               SoupMessage               *msg,
621               GetSCPDURLData            *data)
622 {
623         GUPnPServiceIntrospection *introspection;
624         GError *error;
625
626         introspection = NULL;
627         error = NULL;
628
629         if (msg->status_code == SOUP_STATUS_CANCELLED)
630                 return;
631
632         if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
633                 xmlDoc *scpd;
634
635                 scpd = xmlRecoverMemory (msg->response_body->data,
636                                          msg->response_body->length);
637                 if (scpd) {
638                         introspection = gupnp_service_introspection_new (scpd);
639
640                         xmlFreeDoc (scpd);
641                 }
642
643                 if (!introspection) {
644                         error = g_error_new
645                                         (GUPNP_SERVER_ERROR,
646                                          GUPNP_SERVER_ERROR_INVALID_RESPONSE,
647                                          "Could not parse SCPD");
648                 }
649         } else
650                 error = _gupnp_error_new_server_error (msg);
651
652         data->info->priv->pending_gets =
653                 g_list_remove (data->info->priv->pending_gets, data);
654
655         data->callback (data->info,
656                         introspection,
657                         error,
658                         data->user_data);
659
660         if (error)
661                 g_error_free (error);
662
663         get_scpd_url_data_free (data);
664 }
665
666 /**
667  * gupnp_service_info_get_introspection_async:
668  * @info: A #GUPnPServiceInfo
669  * @callback: (scope async) : callback to be called when introspection object is ready.
670  * @user_data: user_data to be passed to the callback.
671  *
672  * Note that introspection object is created from the information in service
673  * description document (SCPD) provided by the service so it can not be created
674  * if the service does not provide an SCPD.
675  **/
676 void
677 gupnp_service_info_get_introspection_async
678                                 (GUPnPServiceInfo                 *info,
679                                  GUPnPServiceIntrospectionCallback callback,
680                                  gpointer                          user_data)
681 {
682         GetSCPDURLData *data;
683         char *scpd_url;
684         SoupSession *session;
685
686         g_return_if_fail (GUPNP_IS_SERVICE_INFO (info));
687         g_return_if_fail (callback != NULL);
688
689         data = g_slice_new (GetSCPDURLData);
690
691         scpd_url = gupnp_service_info_get_scpd_url (info);
692
693         data->message = NULL;
694         if (scpd_url != NULL) {
695                 data->message = soup_message_new (SOUP_METHOD_GET, scpd_url);
696
697                 g_free (scpd_url);
698         }
699
700         if (data->message == NULL) {
701                 GError *error;
702
703                 error = g_error_new
704                                 (GUPNP_SERVER_ERROR,
705                                  GUPNP_SERVER_ERROR_INVALID_URL,
706                                  "No valid SCPD URL defined");
707
708                 callback (info, NULL, error, user_data);
709
710                 g_error_free (error);
711
712                 g_slice_free (GetSCPDURLData, data);
713
714                 return;
715         }
716
717         data->info      = info;
718         data->callback  = callback;
719         data->user_data = user_data;
720
721         /* Send off the message */
722         info->priv->pending_gets =
723                 g_list_prepend (info->priv->pending_gets,
724                                 data);
725
726         session = gupnp_context_get_session (info->priv->context);
727
728         soup_session_queue_message (session,
729                                     data->message,
730                                     (SoupSessionCallback) got_scpd_url,
731                                     data);
732 }