Imported Upstream version 0.20.12
[profile/ivi/GUPnP.git] / libgupnp / gupnp-root-device.c
1 /*
2  * Copyright (C) 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-root-device
24  * @short_description: Class for root device implementations.
25  *
26  * #GUPnPRootDevice allows for implementing root devices.
27  */
28
29 #include <string.h>
30
31 #include <libgssdp/gssdp-resource-group.h>
32
33 #include "gupnp-root-device.h"
34 #include "gupnp-context-private.h"
35 #include "http-headers.h"
36 #include "xml-util.h"
37
38 G_DEFINE_TYPE (GUPnPRootDevice,
39                gupnp_root_device,
40                GUPNP_TYPE_DEVICE);
41
42 struct _GUPnPRootDevicePrivate {
43         GUPnPXMLDoc *description_doc;
44
45         GSSDPResourceGroup *group;
46
47         char  *description_path;
48         char  *description_dir;
49         char  *relative_location;
50 };
51
52 enum {
53         PROP_0,
54         PROP_DESCRIPTION_DOC,
55         PROP_DESCRIPTION_PATH,
56         PROP_DESCRIPTION_DIR,
57         PROP_AVAILABLE
58 };
59
60 static void
61 gupnp_root_device_finalize (GObject *object)
62 {
63         GUPnPRootDevice *device;
64         GObjectClass *object_class;
65
66         device = GUPNP_ROOT_DEVICE (object);
67
68         g_object_unref (device->priv->description_doc);
69         g_free (device->priv->description_path);
70         g_free (device->priv->description_dir);
71         g_free (device->priv->relative_location);
72
73         /* Call super */
74         object_class = G_OBJECT_CLASS (gupnp_root_device_parent_class);
75         object_class->finalize (object);
76 }
77
78 static void
79 gupnp_root_device_dispose (GObject *object)
80 {
81         GUPnPRootDevice *device;
82         GObjectClass *object_class;
83
84         device = GUPNP_ROOT_DEVICE (object);
85
86         if (device->priv->group) {
87                 g_object_unref (device->priv->group);
88                 device->priv->group = NULL;
89         }
90
91         /* Call super */
92         object_class = G_OBJECT_CLASS (gupnp_root_device_parent_class);
93         object_class->dispose (object);
94 }
95
96 static void
97 gupnp_root_device_init (GUPnPRootDevice *device)
98 {
99         device->priv = G_TYPE_INSTANCE_GET_PRIVATE (device,
100                                                     GUPNP_TYPE_ROOT_DEVICE,
101                                                     GUPnPRootDevicePrivate);
102 }
103
104 static void
105 gupnp_root_device_set_property (GObject      *object,
106                                 guint         property_id,
107                                 const GValue *value,
108                                 GParamSpec   *pspec)
109 {
110         GUPnPRootDevice *device;
111
112         device = GUPNP_ROOT_DEVICE (object);
113
114         switch (property_id) {
115         case PROP_DESCRIPTION_DOC:
116                 device->priv->description_doc = g_value_dup_object (value);
117                 break;
118         case PROP_DESCRIPTION_PATH:
119                 device->priv->description_path = g_value_dup_string (value);
120                 break;
121         case PROP_DESCRIPTION_DIR:
122                 device->priv->description_dir = g_value_dup_string (value);
123                 break;
124         case PROP_AVAILABLE:
125                 gupnp_root_device_set_available
126                                         (device, g_value_get_boolean (value));
127                 break;
128         default:
129                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
130                 break;
131         }
132 }
133
134 static void
135 gupnp_root_device_get_property (GObject    *object,
136                                 guint       property_id,
137                                 GValue     *value,
138                                 GParamSpec *pspec)
139 {
140         GUPnPRootDevice *device;
141
142         device = GUPNP_ROOT_DEVICE (object);
143
144         switch (property_id) {
145         case PROP_DESCRIPTION_PATH:
146                 g_value_set_string (value,
147                                     device->priv->description_path);
148                 break;
149         case PROP_DESCRIPTION_DIR:
150                 g_value_set_string (value,
151                                     device->priv->description_dir);
152                 break;
153         case PROP_AVAILABLE:
154                 g_value_set_boolean (value,
155                                      gupnp_root_device_get_available (device));
156                 break;
157         default:
158                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
159                 break;
160         }
161 }
162
163 static void
164 fill_resource_group (xmlNode            *element,
165                      const char         *location,
166                      GSSDPResourceGroup *group)
167 {
168         xmlNode *child;
169         xmlChar *udn, *device_type;
170         char *usn;
171
172         /* Add device */
173         udn = xml_util_get_child_element_content (element, "UDN");
174         if (!udn) {
175                 g_warning ("No UDN specified.");
176
177                 return;
178         }
179
180         device_type = xml_util_get_child_element_content (element,
181                                                           "deviceType");
182         if (!device_type) {
183                 g_warning ("No deviceType specified.");
184
185                 return;
186         }
187
188         gssdp_resource_group_add_resource_simple (group,
189                                                   (const char *) udn,
190                                                   (const char *) udn,
191                                                   location);
192
193         usn = g_strdup_printf ("%s::%s", (char *) udn, (char *) device_type);
194         gssdp_resource_group_add_resource_simple (group,
195                                                   (const char *) device_type,
196                                                   (const char *) usn,
197                                                   location);
198         g_free (usn);
199
200         xmlFree (device_type);
201
202         /* Add embedded services */
203         child = xml_util_get_element (element,
204                                       "serviceList",
205                                       NULL);
206         if (child) {
207                 for (child = child->children; child; child = child->next) {
208                         xmlChar *service_type;
209
210                         if (strcmp ("service", (char *) child->name))
211                                 continue;
212
213                         service_type = xml_util_get_child_element_content
214                                                 (child, "serviceType");
215                         if (!service_type)
216                                 continue;
217
218                         usn = g_strdup_printf ("%s::%s",
219                                                (char *) udn,
220                                                (char *) service_type);
221                         gssdp_resource_group_add_resource_simple
222                                                 (group,
223                                                  (const char *) service_type,
224                                                  (const char *) usn,
225                                                  location);
226                         g_free (usn);
227
228                         xmlFree (service_type);
229                 }
230         }
231
232         /* Cleanup */
233         xmlFree (udn);
234
235         /* Add embedded devices */
236         child = xml_util_get_element (element,
237                                       "deviceList",
238                                       NULL);
239         if (child) {
240                 for (child = child->children; child; child = child->next)
241                         if (!strcmp ("device", (char *) child->name))
242                                 fill_resource_group (child, location, group);
243         }
244 }
245
246 /* Load and parse @description_path as an XML document, synchronously. */
247 static GUPnPXMLDoc *
248 load_and_parse (const char *description_path)
249 {
250         GUPnPXMLDoc *description_doc;
251         GError *error = NULL;
252
253         /* We don't, so load and parse it */
254         description_doc = gupnp_xml_doc_new_from_path (description_path,
255                                                        &error);
256         if (description_doc == NULL) {
257                 g_critical ("Error loading description: %s\n", error->message);
258
259                 g_error_free (error);
260         }
261
262         return description_doc;
263 }
264
265 static GObject *
266 gupnp_root_device_constructor (GType                  type,
267                                guint                  n_construct_params,
268                                GObjectConstructParam *construct_params)
269 {
270         GObjectClass *object_class;
271         GObject *object;
272         GUPnPRootDevice *device;
273         GUPnPContext *context;
274         const char *description_path, *description_dir, *udn;
275         char *desc_path, *location, *usn, *relative_location;
276         unsigned int i;
277         GUPnPXMLDoc *description_doc;
278         xmlNode *root_element, *element;
279         SoupURI *url_base;
280
281         object = NULL;
282         location = NULL;
283
284         /* Get 'description-doc', 'context', 'description-dir' and
285          * 'description-path' property values */
286         description_doc   = NULL;
287         context           = NULL;
288         description_path  = NULL;
289         description_dir   = NULL;
290
291         for (i = 0; i < n_construct_params; i++) {
292                 const char *par_name;
293
294                 par_name = construct_params[i].pspec->name;
295
296                 if (strcmp (par_name, "description-doc") == 0) {
297                         description_doc =
298                                 g_value_get_object (construct_params[i].value);
299
300                         continue;
301                 } 
302
303                 if (strcmp (par_name, "context") == 0) {
304                         context =
305                                 g_value_get_object (construct_params[i].value);
306
307                         continue;
308                 }
309
310                 if (strcmp (par_name, "description-path") == 0) {
311                         description_path =
312                                 g_value_get_string (construct_params[i].value);
313
314                         continue;
315                 }
316
317                 if (strcmp (par_name, "description-dir") == 0) {
318                         description_dir =
319                                 g_value_get_string (construct_params[i].value);
320
321                         continue;
322                 }
323         }
324
325         if (!context) {
326                 g_warning ("No context specified.");
327
328                 return NULL;
329         }
330
331         if (!description_path) {
332                 g_warning ("Path to description document not specified.");
333
334                 return NULL;
335         }
336
337         if (!description_dir) {
338                 g_warning ("Path to description directory not specified.");
339
340                 return NULL;
341         }
342
343         if (g_path_is_absolute (description_path))
344                 desc_path = g_strdup (description_path);
345         else
346                 desc_path = g_build_filename (description_dir,
347                                               description_path,
348                                               NULL);
349
350         /* Check whether we have a parsed description document */
351         if (!description_doc) {
352                 /* We don't, so load and parse it */
353                 description_doc = load_and_parse (desc_path);
354                 if (description_doc == NULL)
355                         goto DONE;
356         }
357
358         /* Find correct element */
359         root_element = xml_util_get_element ((xmlNode *) description_doc->doc,
360                                              "root",
361                                              NULL);
362         if (!root_element) {
363                 g_warning ("\"/root\" element not found.");
364
365                 goto DONE;
366         }
367
368         element = xml_util_get_element (root_element,
369                                         "device",
370                                         NULL);
371         if (!element) {
372                 g_warning ("\"/root/device\" element not found.");
373
374                 goto DONE;
375         }
376
377         /* Set 'element' and 'description-doc' properties */
378         for (i = 0; i < n_construct_params; i++) {
379                 const char *par_name;
380
381                 par_name = construct_params[i].pspec->name;
382
383                 if (strcmp (par_name, "element") == 0) {
384                         g_value_set_pointer (construct_params[i].value,
385                                              element);
386
387                         continue;
388                 }
389
390                 if (strcmp (par_name, "description-doc") == 0) {
391                         g_value_set_object (construct_params[i].value,
392                                             description_doc);
393
394                         continue;
395                 }
396         }
397
398         /* Create object */
399         object_class = G_OBJECT_CLASS (gupnp_root_device_parent_class);
400
401         object = object_class->constructor (type,
402                                             n_construct_params,
403                                             construct_params);
404         device = GUPNP_ROOT_DEVICE (object);
405
406         /* Generate location relative to HTTP root */
407         udn = gupnp_device_info_get_udn (GUPNP_DEVICE_INFO (device));
408         if (udn && strstr (udn, "uuid:") == udn)
409                 device->priv->relative_location = g_strdup_printf ("%s.xml", udn + 5);
410         else
411                 device->priv->relative_location = g_strdup_printf ("RootDevice%p.xml", device);
412
413         relative_location = g_strjoin (NULL,
414                                        "/",
415                                        device->priv->relative_location,
416                                        NULL);
417
418         /* Host the description file and dir */
419         gupnp_context_host_path (context, desc_path, relative_location);
420         gupnp_context_host_path (context, device->priv->description_dir, "");
421
422         /* Generate full location */
423         location = g_strjoin (NULL,
424                               _gupnp_context_get_server_url (context),
425                               relative_location,
426                               NULL);
427         g_free (relative_location);
428
429         /* Save the URL base, if any */
430         url_base = xml_util_get_child_element_content_uri (root_element,
431                                                            "URLBase",
432                                                            NULL);
433         if (!url_base)
434                 url_base = soup_uri_new (location);
435
436         /* Set additional properties */
437         g_object_set (object,
438                       "location", location,
439                       "url-base", url_base,
440                       NULL);
441
442         soup_uri_free (url_base);
443
444         /* Create resource group */
445         device->priv->group = gssdp_resource_group_new (GSSDP_CLIENT (context));
446
447         /* Add services and devices to resource group */
448         usn = g_strdup_printf ("%s::upnp:rootdevice", (const char *) udn);
449         gssdp_resource_group_add_resource_simple (device->priv->group,
450                                                   "upnp:rootdevice",
451                                                   usn,
452                                                   location);
453         g_free (usn);
454
455         fill_resource_group (element, location, device->priv->group);
456
457  DONE:
458         /* Cleanup */
459         g_free (desc_path);
460         g_free (location);
461
462         return object;
463 }
464
465 static void
466 gupnp_root_device_class_init (GUPnPRootDeviceClass *klass)
467 {
468         GObjectClass *object_class;
469
470         object_class = G_OBJECT_CLASS (klass);
471
472         object_class->set_property = gupnp_root_device_set_property;
473         object_class->get_property = gupnp_root_device_get_property;
474         object_class->constructor  = gupnp_root_device_constructor;
475         object_class->dispose      = gupnp_root_device_dispose;
476         object_class->finalize     = gupnp_root_device_finalize;
477
478         g_type_class_add_private (klass, sizeof (GUPnPRootDevicePrivate));
479
480         /**
481          * GUPnPRootDevice:description-doc:
482          *
483          * Device description document. Constructor property.
484          *
485          * Since: 0.13.0
486          **/
487         g_object_class_install_property
488                 (object_class,
489                  PROP_DESCRIPTION_DOC,
490                  g_param_spec_object ("description-doc",
491                                       "Description document",
492                                       "Device description document",
493                                       GUPNP_TYPE_XML_DOC,
494                                       G_PARAM_WRITABLE |
495                                       G_PARAM_CONSTRUCT_ONLY |
496                                       G_PARAM_STATIC_NAME |
497                                       G_PARAM_STATIC_NICK |
498                                       G_PARAM_STATIC_BLURB));
499
500         /**
501          * GUPnPRootDevice:description-path:
502          *
503          * The path to device description document. This could either be an
504          * absolute path or path relative to GUPnPRootDevice:description-dir.
505          *
506          * Since: 0.13.0
507          **/
508         g_object_class_install_property
509                 (object_class,
510                  PROP_DESCRIPTION_PATH,
511                  g_param_spec_string ("description-path",
512                                       "Description Path",
513                                       "The path to device descrition document",
514                                       NULL,
515                                       G_PARAM_READWRITE |
516                                       G_PARAM_CONSTRUCT_ONLY |
517                                       G_PARAM_STATIC_NAME |
518                                       G_PARAM_STATIC_NICK |
519                                       G_PARAM_STATIC_BLURB));
520
521         /**
522          * GUPnPRootDevice:description-dir:
523          *
524          * The path to directory where description documents are provided.
525          **/
526         g_object_class_install_property
527                 (object_class,
528                  PROP_DESCRIPTION_DIR,
529                  g_param_spec_string ("description-dir",
530                                       "Description Directory",
531                                       "The path to directory where "
532                                       "description documents are provided",
533                                       NULL,
534                                       G_PARAM_READWRITE |
535                                       G_PARAM_CONSTRUCT_ONLY |
536                                       G_PARAM_STATIC_NAME |
537                                       G_PARAM_STATIC_NICK |
538                                       G_PARAM_STATIC_BLURB));
539
540         /**
541          * GUPnPRootDevice:available:
542          *
543          * TRUE if this device is available.
544          **/
545         g_object_class_install_property
546                 (object_class,
547                  PROP_AVAILABLE,
548                  g_param_spec_boolean ("available",
549                                        "Available",
550                                        "Whether this device is available",
551                                        FALSE,
552                                        G_PARAM_READWRITE |
553                                        G_PARAM_STATIC_NAME |
554                                        G_PARAM_STATIC_NICK |
555                                        G_PARAM_STATIC_BLURB));
556 }
557
558 /**
559  * gupnp_root_device_new:
560  * @context: The #GUPnPContext
561  * @description_path: Path to device description document. This could either
562  * be an absolute path or path relative to @description_dir.
563  * @description_dir: Path to directory where description documents are provided.
564  *
565  * Create a new #GUPnPRootDevice object, automatically loading and parsing
566  * device description document from @description_path.
567  *
568  * Return value: A new @GUPnPRootDevice object.
569  **/
570 GUPnPRootDevice *
571 gupnp_root_device_new (GUPnPContext *context,
572                        const char   *description_path,
573                        const char   *description_dir)
574 {
575         GUPnPResourceFactory *factory;
576
577         factory = gupnp_resource_factory_get_default ();
578
579         return gupnp_root_device_new_full (context,
580                                            factory,
581                                            NULL,
582                                            description_path,
583                                            description_dir);
584 }
585
586 /**
587  * gupnp_root_device_new_full:
588  * @context: A #GUPnPContext
589  * @factory: A #GUPnPResourceFactory
590  * @description_doc: Device description document, or %NULL
591  * @description_path: Path to device description document. This could either
592  * be an absolute path or path relative to @description_dir.
593  * @description_dir: Path to directory where description documents are provided.
594  *
595  * Create a new #GUPnPRootDevice, automatically loading and parsing
596  * device description document from @description_path if @description_doc is
597  * %NULL.
598  *
599  * Return value: A new #GUPnPRootDevice object.
600  **/
601 GUPnPRootDevice *
602 gupnp_root_device_new_full (GUPnPContext         *context,
603                             GUPnPResourceFactory *factory,
604                             GUPnPXMLDoc          *description_doc,
605                             const char           *description_path,
606                             const char           *description_dir)
607 {
608         g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
609         g_return_val_if_fail (GUPNP_IS_RESOURCE_FACTORY (factory), NULL);
610
611         return g_object_new (GUPNP_TYPE_ROOT_DEVICE,
612                              "context", context,
613                              "resource-factory", factory,
614                              "root-device", NULL,
615                              "description-doc", description_doc,
616                              "description-path", description_path,
617                              "description-dir", description_dir,
618                              NULL);
619 }
620
621 /**
622  * gupnp_root_device_set_available:
623  * @root_device: A #GUPnPRootDevice
624  * @available: %TRUE if @root_device should be available
625  *
626  * Controls whether or not @root_device is available (announcing
627  * its presence).
628  **/
629 void
630 gupnp_root_device_set_available (GUPnPRootDevice *root_device,
631                                  gboolean         available)
632 {
633         g_return_if_fail (GUPNP_IS_ROOT_DEVICE (root_device));
634
635         gssdp_resource_group_set_available (root_device->priv->group,
636                                             available);
637
638         g_object_notify (G_OBJECT (root_device), "available");
639 }
640
641 /**
642  * gupnp_root_device_get_available:
643  * @root_device: A #GUPnPRootDevice
644  *
645  * Get whether or not @root_device is available (announcing its presence).
646  *
647  * Return value: %TRUE if @root_device is available, %FALSE otherwise.
648  **/
649 gboolean
650 gupnp_root_device_get_available (GUPnPRootDevice *root_device)
651 {
652         g_return_val_if_fail (GUPNP_IS_ROOT_DEVICE (root_device), FALSE);
653
654         return gssdp_resource_group_get_available (root_device->priv->group);
655 }
656
657 /**
658  * gupnp_root_device_get_relative_location:
659  * @root_device: A #GUPnPRootDevice
660  *
661  * Get the relative location of @root_device.
662  *
663  * Return value: The relative location of @root_device.
664  **/
665 const char *
666 gupnp_root_device_get_relative_location (GUPnPRootDevice *root_device)
667 {
668         g_return_val_if_fail (GUPNP_IS_ROOT_DEVICE (root_device), NULL);
669
670         return root_device->priv->relative_location;
671 }
672
673 /**
674  * gupnp_root_device_get_description_path:
675  * @root_device: A #GUPnPRootDevice
676  *
677  * Get the path to the device description document of @root_device.
678  *
679  * Return value: The path to device description document of @root_device.
680  **/
681 const char *
682 gupnp_root_device_get_description_path (GUPnPRootDevice *root_device)
683 {
684         g_return_val_if_fail (GUPNP_IS_ROOT_DEVICE (root_device), NULL);
685
686         return root_device->priv->description_path;
687 }
688
689 /**
690  * gupnp_root_device_get_description_dir:
691  * @root_device: A #GUPnPRootDevice
692  *
693  * Get the path to the directory containing description documents related to
694  * @root_device.
695  *
696  * Return value: The path to description document directory of @root_device.
697  **/
698 const char *
699 gupnp_root_device_get_description_dir (GUPnPRootDevice *root_device)
700 {
701         g_return_val_if_fail (GUPNP_IS_ROOT_DEVICE (root_device), NULL);
702
703         return root_device->priv->description_dir;
704 }
705
706 /**
707  * gupnp_root_device_get_ssdp_resource_group:
708  * @root_device: A #GUPnPRootDevice
709  *
710  * Get the #GSSDPResourceGroup used by @root_device.
711  *
712  * Returns: (transfer none): The #GSSDPResourceGroup of @root_device.
713  *
714  * Since: 0.19.2
715  **/
716 GSSDPResourceGroup *
717 gupnp_root_device_get_ssdp_resource_group (GUPnPRootDevice *root_device)
718 {
719         g_return_val_if_fail (GUPNP_IS_ROOT_DEVICE (root_device), NULL);
720
721         return root_device->priv->group;
722 }