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