d6aca04554841fa34c40fc124a718511f322987b
[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         g_object_class_install_property
486                 (object_class,
487                  PROP_DESCRIPTION_DOC,
488                  g_param_spec_object ("description-doc",
489                                       "Description document",
490                                       "Device description document",
491                                       GUPNP_TYPE_XML_DOC,
492                                       G_PARAM_WRITABLE |
493                                       G_PARAM_CONSTRUCT_ONLY |
494                                       G_PARAM_STATIC_NAME |
495                                       G_PARAM_STATIC_NICK |
496                                       G_PARAM_STATIC_BLURB));
497
498         /**
499          * GUPnPRootDevice:description-path:
500          *
501          * The path to device description document. This could either be an
502          * absolute path or path relative to GUPnPRootDevice:description-dir.
503          **/
504         g_object_class_install_property
505                 (object_class,
506                  PROP_DESCRIPTION_PATH,
507                  g_param_spec_string ("description-path",
508                                       "Description Path",
509                                       "The path to device descrition document",
510                                       NULL,
511                                       G_PARAM_READWRITE |
512                                       G_PARAM_CONSTRUCT_ONLY |
513                                       G_PARAM_STATIC_NAME |
514                                       G_PARAM_STATIC_NICK |
515                                       G_PARAM_STATIC_BLURB));
516
517         /**
518          * GUPnPRootDevice:description-dir:
519          *
520          * The path to directory where description documents are provided.
521          **/
522         g_object_class_install_property
523                 (object_class,
524                  PROP_DESCRIPTION_DIR,
525                  g_param_spec_string ("description-dir",
526                                       "Description Directory",
527                                       "The path to directory where "
528                                       "description documents are provided",
529                                       NULL,
530                                       G_PARAM_READWRITE |
531                                       G_PARAM_CONSTRUCT_ONLY |
532                                       G_PARAM_STATIC_NAME |
533                                       G_PARAM_STATIC_NICK |
534                                       G_PARAM_STATIC_BLURB));
535
536         /**
537          * GUPnPRootDevice:available:
538          *
539          * TRUE if this device is available.
540          **/
541         g_object_class_install_property
542                 (object_class,
543                  PROP_AVAILABLE,
544                  g_param_spec_boolean ("available",
545                                        "Available",
546                                        "Whether this device is available",
547                                        FALSE,
548                                        G_PARAM_READWRITE |
549                                        G_PARAM_STATIC_NAME |
550                                        G_PARAM_STATIC_NICK |
551                                        G_PARAM_STATIC_BLURB));
552 }
553
554 /**
555  * gupnp_root_device_new:
556  * @context: The #GUPnPContext
557  * @description_path: Path to device description document. This could either
558  * be an absolute path or path relative to @description_dir.
559  * @description_dir: Path to directory where description documents are provided.
560  *
561  * Create a new #GUPnPRootDevice object, automatically loading and parsing
562  * device description document from @description_path.
563  *
564  * Return value: A new @GUPnPRootDevice object.
565  **/
566 GUPnPRootDevice *
567 gupnp_root_device_new (GUPnPContext *context,
568                        const char   *description_path,
569                        const char   *description_dir)
570 {
571         GUPnPResourceFactory *factory;
572
573         factory = gupnp_resource_factory_get_default ();
574
575         return gupnp_root_device_new_full (context,
576                                            factory,
577                                            NULL,
578                                            description_path,
579                                            description_dir);
580 }
581
582 /**
583  * gupnp_root_device_new_full:
584  * @context: A #GUPnPContext
585  * @factory: A #GUPnPResourceFactory
586  * @description_doc: Device description document, or %NULL
587  * @description_path: Path to device description document. This could either
588  * be an absolute path or path relative to @description_dir.
589  * @description_dir: Path to directory where description documents are provided.
590  *
591  * Create a new #GUPnPRootDevice, automatically loading and parsing
592  * device description document from @description_path if @description_doc is
593  * %NULL.
594  *
595  * Return value: A new #GUPnPRootDevice object.
596  **/
597 GUPnPRootDevice *
598 gupnp_root_device_new_full (GUPnPContext         *context,
599                             GUPnPResourceFactory *factory,
600                             GUPnPXMLDoc          *description_doc,
601                             const char           *description_path,
602                             const char           *description_dir)
603 {
604         g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
605         g_return_val_if_fail (GUPNP_IS_RESOURCE_FACTORY (factory), NULL);
606
607         return g_object_new (GUPNP_TYPE_ROOT_DEVICE,
608                              "context", context,
609                              "resource-factory", factory,
610                              "root-device", NULL,
611                              "description-doc", description_doc,
612                              "description-path", description_path,
613                              "description-dir", description_dir,
614                              NULL);
615 }
616
617 /**
618  * gupnp_root_device_set_available:
619  * @root_device: A #GUPnPRootDevice
620  * @available: %TRUE if @root_device should be available
621  *
622  * Controls whether or not @root_device is available (announcing
623  * its presence).
624  **/
625 void
626 gupnp_root_device_set_available (GUPnPRootDevice *root_device,
627                                  gboolean         available)
628 {
629         g_return_if_fail (GUPNP_IS_ROOT_DEVICE (root_device));
630
631         gssdp_resource_group_set_available (root_device->priv->group,
632                                             available);
633
634         g_object_notify (G_OBJECT (root_device), "available");
635 }
636
637 /**
638  * gupnp_root_device_get_available:
639  * @root_device: A #GUPnPRootDevice
640  *
641  * Get whether or not @root_device is available (announcing its presence).
642  *
643  * Return value: %TRUE if @root_device is available, %FALSE otherwise.
644  **/
645 gboolean
646 gupnp_root_device_get_available (GUPnPRootDevice *root_device)
647 {
648         g_return_val_if_fail (GUPNP_IS_ROOT_DEVICE (root_device), FALSE);
649
650         return gssdp_resource_group_get_available (root_device->priv->group);
651 }
652
653 /**
654  * gupnp_root_device_get_relative_location:
655  * @root_device: A #GUPnPRootDevice
656  *
657  * Get the relative location of @root_device.
658  *
659  * Return value: The relative location of @root_device.
660  **/
661 const char *
662 gupnp_root_device_get_relative_location (GUPnPRootDevice *root_device)
663 {
664         g_return_val_if_fail (GUPNP_IS_ROOT_DEVICE (root_device), NULL);
665
666         return root_device->priv->relative_location;
667 }
668
669 /**
670  * gupnp_root_device_get_description_path:
671  * @root_device: A #GUPnPRootDevice
672  *
673  * Get the path to the device description document of @root_device.
674  *
675  * Return value: The path to device description document of @root_device.
676  **/
677 const char *
678 gupnp_root_device_get_description_path (GUPnPRootDevice *root_device)
679 {
680         g_return_val_if_fail (GUPNP_IS_ROOT_DEVICE (root_device), NULL);
681
682         return root_device->priv->description_path;
683 }
684
685 /**
686  * gupnp_root_device_get_description_dir:
687  * @root_device: A #GUPnPRootDevice
688  *
689  * Get the path to the directory containing description documents related to
690  * @root_device.
691  *
692  * Return value: The path to description document directory of @root_device.
693  **/
694 const char *
695 gupnp_root_device_get_description_dir (GUPnPRootDevice *root_device)
696 {
697         g_return_val_if_fail (GUPNP_IS_ROOT_DEVICE (root_device), NULL);
698
699         return root_device->priv->description_dir;
700 }