Imported Upstream version 0.20.12
[profile/ivi/GUPnP.git] / libgupnp / gupnp-device-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-device-info
24  * @short_description: Base abstract class for querying device information.
25  *
26  * The #GUPnPDeviceInfo base abstract class provides methods for querying
27  * device information.
28  */
29
30 #include <string.h>
31
32 #include "gupnp-device-info.h"
33 #include "gupnp-device-info-private.h"
34 #include "gupnp-resource-factory-private.h"
35 #include "xml-util.h"
36
37 G_DEFINE_ABSTRACT_TYPE (GUPnPDeviceInfo,
38                         gupnp_device_info,
39                         G_TYPE_OBJECT);
40
41 struct _GUPnPDeviceInfoPrivate {
42         GUPnPResourceFactory *factory;
43         GUPnPContext         *context;
44
45         char *location;
46         char *udn;
47         char *device_type;
48
49         SoupURI *url_base;
50
51         GUPnPXMLDoc *doc;
52
53         xmlNode *element;
54 };
55
56 enum {
57         PROP_0,
58         PROP_RESOURCE_FACTORY,
59         PROP_CONTEXT,
60         PROP_LOCATION,
61         PROP_UDN,
62         PROP_DEVICE_TYPE,
63         PROP_URL_BASE,
64         PROP_DOCUMENT,
65         PROP_ELEMENT
66 };
67
68 static void
69 gupnp_device_info_init (GUPnPDeviceInfo *info)
70 {
71         info->priv = G_TYPE_INSTANCE_GET_PRIVATE (info,
72                                                   GUPNP_TYPE_DEVICE_INFO,
73                                                   GUPnPDeviceInfoPrivate);
74 }
75
76 static void
77 gupnp_device_info_set_property (GObject      *object,
78                                 guint         property_id,
79                                 const GValue *value,
80                                 GParamSpec   *pspec)
81 {
82         GUPnPDeviceInfo *info;
83
84         info = GUPNP_DEVICE_INFO (object);
85
86         switch (property_id) {
87         case PROP_RESOURCE_FACTORY:
88                 info->priv->factory =
89                         GUPNP_RESOURCE_FACTORY (g_value_dup_object (value));
90                 break;
91         case PROP_CONTEXT:
92                 info->priv->context = g_object_ref (g_value_get_object (value));
93                 break;
94         case PROP_LOCATION:
95                 info->priv->location = g_value_dup_string (value);
96                 break;
97         case PROP_UDN:
98                 info->priv->udn = g_value_dup_string (value);
99                 break;
100         case PROP_DEVICE_TYPE:
101                 info->priv->device_type = g_value_dup_string (value);
102                 break;
103         case PROP_URL_BASE:
104                 info->priv->url_base = g_value_dup_boxed (value);
105                 break;
106         case PROP_DOCUMENT:
107                 info->priv->doc = g_value_dup_object (value);
108                 break;
109         case PROP_ELEMENT:
110                 info->priv->element = g_value_get_pointer (value);
111                 break;
112         default:
113                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
114                 break;
115         }
116 }
117
118 static void
119 gupnp_device_info_get_property (GObject    *object,
120                                 guint       property_id,
121                                 GValue     *value,
122                                 GParamSpec *pspec)
123 {
124         GUPnPDeviceInfo *info;
125
126         info = GUPNP_DEVICE_INFO (object);
127
128         switch (property_id) {
129         case PROP_RESOURCE_FACTORY:
130                 g_value_set_object (value,
131                                     info->priv->factory);
132                 break;
133         case PROP_CONTEXT:
134                 g_value_set_object (value,
135                                     info->priv->context);
136                 break;
137         case PROP_LOCATION:
138                 g_value_set_string (value,
139                                     info->priv->location);
140                 break;
141         case PROP_UDN:
142                 g_value_set_string (value,
143                                     gupnp_device_info_get_udn (info));
144                 break;
145         case PROP_DEVICE_TYPE:
146                 g_value_set_string (value,
147                                     gupnp_device_info_get_device_type (info));
148                 break;
149         case PROP_URL_BASE:
150                 g_value_set_boxed (value, info->priv->url_base);
151                 break;
152         default:
153                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
154                 break;
155         }
156 }
157
158 static void
159 gupnp_device_info_dispose (GObject *object)
160 {
161         GUPnPDeviceInfo *info;
162
163         info = GUPNP_DEVICE_INFO (object);
164
165         if (info->priv->factory) {
166                 g_object_unref (info->priv->factory);
167                 info->priv->factory = NULL;
168         }
169
170         if (info->priv->context) {
171                 g_object_unref (info->priv->context);
172                 info->priv->context = NULL;
173         }
174
175         if (info->priv->doc) {
176                 g_object_unref (info->priv->doc);
177                 info->priv->doc = NULL;
178         }
179
180         G_OBJECT_CLASS (gupnp_device_info_parent_class)->dispose (object);
181 }
182
183 static void
184 gupnp_device_info_finalize (GObject *object)
185 {
186         GUPnPDeviceInfo *info;
187
188         info = GUPNP_DEVICE_INFO (object);
189
190         g_free (info->priv->location);
191         g_free (info->priv->udn);
192         g_free (info->priv->device_type);
193
194         soup_uri_free (info->priv->url_base);
195
196         G_OBJECT_CLASS (gupnp_device_info_parent_class)->finalize (object);
197 }
198
199 static void
200 gupnp_device_info_class_init (GUPnPDeviceInfoClass *klass)
201 {
202         GObjectClass *object_class;
203
204         object_class = G_OBJECT_CLASS (klass);
205
206         object_class->set_property = gupnp_device_info_set_property;
207         object_class->get_property = gupnp_device_info_get_property;
208         object_class->dispose      = gupnp_device_info_dispose;
209         object_class->finalize     = gupnp_device_info_finalize;
210
211         g_type_class_add_private (klass, sizeof (GUPnPDeviceInfoPrivate));
212
213         /**
214          * GUPnPDeviceInfo:resource-factory:
215          *
216          * The resource factory to use. Set to NULL for default factory.
217          **/
218         g_object_class_install_property
219                 (object_class,
220                  PROP_RESOURCE_FACTORY,
221                  g_param_spec_object ("resource-factory",
222                                       "Resource Factory",
223                                       "The resource factory to use",
224                                       GUPNP_TYPE_RESOURCE_FACTORY,
225                                       G_PARAM_READWRITE |
226                                       G_PARAM_CONSTRUCT_ONLY |
227                                       G_PARAM_STATIC_NAME |
228                                       G_PARAM_STATIC_NICK |
229                                       G_PARAM_STATIC_BLURB));
230
231         /**
232          * GUPnPDeviceInfo:context:
233          *
234          * The #GUPnPContext to use.
235          **/
236         g_object_class_install_property
237                 (object_class,
238                  PROP_CONTEXT,
239                  g_param_spec_object ("context",
240                                       "Context",
241                                       "The GUPnPContext",
242                                       GUPNP_TYPE_CONTEXT,
243                                       G_PARAM_READWRITE |
244                                       G_PARAM_CONSTRUCT_ONLY |
245                                       G_PARAM_STATIC_NAME |
246                                       G_PARAM_STATIC_NICK |
247                                       G_PARAM_STATIC_BLURB));
248
249         /**
250          * GUPnPDeviceInfo:location:
251          *
252          * The location of the device description file.
253          **/
254         g_object_class_install_property
255                 (object_class,
256                  PROP_LOCATION,
257                  g_param_spec_string ("location",
258                                       "Location",
259                                       "The location of the device description "
260                                       "file",
261                                       NULL,
262                                       G_PARAM_READWRITE |
263                                       G_PARAM_CONSTRUCT_ONLY |
264                                       G_PARAM_STATIC_NAME |
265                                       G_PARAM_STATIC_NICK |
266                                       G_PARAM_STATIC_BLURB));
267
268         /**
269          * GUPnPDeviceInfo:udn:
270          *
271          * The UDN of this device.
272          **/
273         g_object_class_install_property
274                 (object_class,
275                  PROP_UDN,
276                  g_param_spec_string ("udn",
277                                       "UDN",
278                                       "The UDN",
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          * GUPnPDeviceInfo:device-type:
288          *
289          * The device type.
290          **/
291         g_object_class_install_property
292                 (object_class,
293                  PROP_DEVICE_TYPE,
294                  g_param_spec_string ("device-type",
295                                       "Device type",
296                                       "The device type",
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          * GUPnPDeviceInfo:url-base:
306          *
307          * The URL base (#SoupURI).
308          **/
309         g_object_class_install_property
310                 (object_class,
311                  PROP_URL_BASE,
312                  g_param_spec_boxed ("url-base",
313                                      "URL base",
314                                      "The URL base",
315                                      SOUP_TYPE_URI,
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          * GUPnPDeviceInfo:document:
324          *
325          * Private property.
326          *
327          * Stability: Private
328          **/
329         g_object_class_install_property
330                 (object_class,
331                  PROP_DOCUMENT,
332                  g_param_spec_object ("document",
333                                       "Document",
334                                       "The XML document related to this "
335                                       "device",
336                                       GUPNP_TYPE_XML_DOC,
337                                       G_PARAM_READWRITE |
338                                       G_PARAM_CONSTRUCT_ONLY |
339                                       G_PARAM_STATIC_NAME |
340                                       G_PARAM_STATIC_NICK |
341                                       G_PARAM_STATIC_BLURB));
342
343         /**
344          * GUPnPDeviceInfo:element:
345          *
346          * Private property.
347          *
348          * Stability: Private
349          **/
350         g_object_class_install_property
351                 (object_class,
352                  PROP_ELEMENT,
353                  g_param_spec_pointer ("element",
354                                        "Element",
355                                        "The XML element related to this "
356                                        "device",
357                                        G_PARAM_WRITABLE |
358                                        G_PARAM_CONSTRUCT_ONLY |
359                                        G_PARAM_STATIC_NAME |
360                                        G_PARAM_STATIC_NICK |
361                                        G_PARAM_STATIC_BLURB));
362 }
363
364 /**
365  * gupnp_device_info_get_resource_factory:
366  * @device_info: A #GUPnPDeviceInfo
367  *
368  * Get the #GUPnPResourceFactory used by the @device_info.
369  *
370  * Returns: (transfer none): A #GUPnPResourceFactory.
371  **/
372 GUPnPResourceFactory *
373 gupnp_device_info_get_resource_factory (GUPnPDeviceInfo *info)
374 {
375         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
376
377         return info->priv->factory;
378 }
379
380 /**
381  * gupnp_device_info_get_context:
382  * @info: A #GUPnPDeviceInfo
383  *
384  * Get the associated #GUPnPContext.
385  *
386  * Returns: (transfer none): A #GUPnPContext.
387  **/
388 GUPnPContext *
389 gupnp_device_info_get_context (GUPnPDeviceInfo *info)
390 {
391         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
392
393         return info->priv->context;
394 }
395
396 /**
397  * gupnp_device_info_get_location:
398  * @info: A #GUPnPDeviceInfo
399  *
400  * Get the location of the device description file.
401  *
402  * Returns: A constant string.
403  **/
404 const char *
405 gupnp_device_info_get_location (GUPnPDeviceInfo *info)
406 {
407         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
408
409         return info->priv->location;
410 }
411
412 /**
413  * gupnp_device_info_get_url_base:
414  * @info: A #GUPnPDeviceInfo
415  *
416  * Get the URL base of this device.
417  *
418  * Returns: A #SoupURI.
419  **/
420 const SoupURI *
421 gupnp_device_info_get_url_base (GUPnPDeviceInfo *info)
422 {
423         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
424
425         return info->priv->url_base;
426 }
427
428 /**
429  * gupnp_device_info_get_udn:
430  * @info: A #GUPnPDeviceInfo
431  *
432  * Get the Unique Device Name of the device.
433  *
434  * Returns: A constant string.
435  **/
436 const char *
437 gupnp_device_info_get_udn (GUPnPDeviceInfo *info)
438 {
439         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
440
441         if (!info->priv->udn) {
442                 info->priv->udn =
443                         xml_util_get_child_element_content_glib
444                                 (info->priv->element, "UDN");
445         }
446
447         return info->priv->udn;
448 }
449
450 /**
451  * gupnp_device_info_get_device_type:
452  * @info: A #GUPnPDeviceInfo
453  *
454  * Get the UPnP device type.
455  *
456  * Returns: A constant string, or %NULL.
457  **/
458 const char *
459 gupnp_device_info_get_device_type (GUPnPDeviceInfo *info)
460 {
461         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
462
463         if (!info->priv->device_type) {
464                 info->priv->device_type =
465                         xml_util_get_child_element_content_glib
466                                 (info->priv->element, "deviceType");
467         }
468
469         return info->priv->device_type;
470 }
471
472 /**
473  * gupnp_device_info_get_friendly_name:
474  * @info: A #GUPnPDeviceInfo
475  *
476  * Get the friendly name of the device.
477  *
478  * Return value: A string, or %NULL. g_free() after use.
479  **/
480 char *
481 gupnp_device_info_get_friendly_name (GUPnPDeviceInfo *info)
482 {
483         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
484
485         return xml_util_get_child_element_content_glib (info->priv->element,
486                                                         "friendlyName");
487 }
488
489 /**
490  * gupnp_device_info_get_manufacturer:
491  * @info: A #GUPnPDeviceInfo
492  *
493  * Get the manufacturer of the device.
494  *
495  * Return value: A string, or %NULL. g_free() after use.
496  **/
497 char *
498 gupnp_device_info_get_manufacturer (GUPnPDeviceInfo *info)
499 {
500         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
501
502         return xml_util_get_child_element_content_glib (info->priv->element,
503                                                         "manufacturer");
504 }
505
506 /**
507  * gupnp_device_info_get_manufacturer_url:
508  * @info: A #GUPnPDeviceInfo
509  *
510  * Get a URL pointing to the manufacturer's website.
511  *
512  * Return value: A string, or %NULL. g_free() after use.
513  **/
514 char *
515 gupnp_device_info_get_manufacturer_url (GUPnPDeviceInfo *info)
516 {
517         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
518
519         return xml_util_get_child_element_content_url (info->priv->element,
520                                                        "manufacturerURL",
521                                                        info->priv->url_base);
522 }
523
524 /**
525  * gupnp_device_info_get_model_description:
526  * @info: A #GUPnPDeviceInfo
527  *
528  * Get the description of the device model.
529  *
530  * Return value: A string, or %NULL. g_free() after use.
531  **/
532 char *
533 gupnp_device_info_get_model_description (GUPnPDeviceInfo *info)
534 {
535         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
536
537         return xml_util_get_child_element_content_glib (info->priv->element,
538                                                         "modelDescription");
539 }
540
541 /**
542  * gupnp_device_info_get_model_name:
543  * @info: A #GUPnPDeviceInfo
544  *
545  * Get the model name of the device.
546  *
547  * Return value: A string, or %NULL. g_free() after use.
548  **/
549 char *
550 gupnp_device_info_get_model_name (GUPnPDeviceInfo *info)
551 {
552         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
553
554         return xml_util_get_child_element_content_glib (info->priv->element,
555                                                         "modelName");
556 }
557
558 /**
559  * gupnp_device_info_get_model_number:
560  * @info: A #GUPnPDeviceInfo
561  *
562  * Get the model number of the device.
563  *
564  * Return value: A string, or %NULL. g_free() after use.
565  **/
566 char *
567 gupnp_device_info_get_model_number (GUPnPDeviceInfo *info)
568 {
569         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
570
571         return xml_util_get_child_element_content_glib (info->priv->element,
572                                                         "modelNumber");
573 }
574
575 /**
576  * gupnp_device_info_get_model_url:
577  * @info: A #GUPnPDeviceInfo
578  *
579  * Get a URL pointing to the device model's website.
580  *
581  * Return value: A string, or %NULL. g_free() after use.
582  **/
583 char *
584 gupnp_device_info_get_model_url (GUPnPDeviceInfo *info)
585 {
586         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
587
588         return xml_util_get_child_element_content_url (info->priv->element,
589                                                        "modelURL",
590                                                        info->priv->url_base);
591 }
592
593 /**
594  * gupnp_device_info_get_serial_number:
595  * @info: A #GUPnPDeviceInfo
596  *
597  * Get the serial number of the device.
598  *
599  * Return value: A string, or %NULL. g_free() after use.
600  **/
601 char *
602 gupnp_device_info_get_serial_number (GUPnPDeviceInfo *info)
603 {
604         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
605
606         return xml_util_get_child_element_content_glib (info->priv->element,
607                                                         "serialNumber");
608 }
609
610 /**
611  * gupnp_device_info_get_upc:
612  * @info: A #GUPnPDeviceInfo
613  *
614  * Get the Universal Product Code of the device.
615  *
616  * Return value: A string, or %NULL. g_free() after use.
617  **/
618 char *
619 gupnp_device_info_get_upc (GUPnPDeviceInfo *info)
620 {
621         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
622
623         return xml_util_get_child_element_content_glib (info->priv->element,
624                                                         "UPC");
625 }
626
627 /**
628  * gupnp_device_info_get_presentation_url:
629  * @info: A #GUPnPDeviceInfo
630  *
631  * Get a URL pointing to the device's presentation page, for web-based
632  * administration.
633  *
634  * Return value: A string, or %NULL. g_free() after use.
635  **/
636 char *
637 gupnp_device_info_get_presentation_url (GUPnPDeviceInfo *info)
638 {
639         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
640
641         return xml_util_get_child_element_content_url (info->priv->element,
642                                                        "presentationURL",
643                                                        info->priv->url_base);
644 }
645
646 typedef struct {
647         xmlChar *mime_type;
648         int      width;
649         int      height;
650         int      depth;
651         xmlChar *url;
652
653         int      weight;
654 } Icon;
655
656 static Icon *
657 icon_parse (G_GNUC_UNUSED GUPnPDeviceInfo *info, xmlNode *element)
658 {
659         Icon *icon;
660
661         icon = g_slice_new0 (Icon);
662
663         icon->mime_type = xml_util_get_child_element_content     (element,
664                                                                   "mimetype");
665         icon->width     = xml_util_get_child_element_content_int (element,
666                                                                   "width");
667         icon->height    = xml_util_get_child_element_content_int (element,
668                                                                   "height");
669         icon->depth     = xml_util_get_child_element_content_int (element,
670                                                                   "depth");
671         icon->url       = xml_util_get_child_element_content     (element,
672                                                                   "url");
673
674         return icon;
675 }
676
677 static void
678 icon_free (Icon *icon)
679 {
680         if (icon->mime_type)
681                 xmlFree (icon->mime_type);
682
683         if (icon->url)
684                 xmlFree (icon->url);
685
686         g_slice_free (Icon, icon);
687 }
688
689 /**
690  * gupnp_device_info_get_icon_url:
691  * @info: A #GUPnPDeviceInfo
692  * @requested_mime_type: (allow-none) (transfer none): The requested file
693  * format, or %NULL for any
694  * @requested_depth: The requested color depth, or -1 for any
695  * @requested_width: The requested width, or -1 for any
696  * @requested_height: The requested height, or -1 for any
697  * @prefer_bigger: %TRUE if a bigger, rather than a smaller icon should be
698  * returned if no exact match could be found
699  * @mime_type: (out) (allow-none): The location where to store the the format
700  * of the returned icon, or %NULL. The returned string should be freed after
701  * use
702  * @depth: (out) (allow-none) :  The location where to store the depth of the
703  * returned icon, or %NULL
704  * @width: (out) (allow-none) : The location where to store the width of the
705  * returned icon, or %NULL
706  * @height: (out) (allow-none) : The location where to store the height of the
707  * returned icon, or %NULL
708  *
709  * Get a URL pointing to the icon most closely matching the
710  * given criteria, or %NULL. If @requested_mime_type is set, only icons with
711  * this mime type will be returned. If @requested_depth is set, only icons with
712  * this or lower depth will be returned. If @requested_width and/or
713  * @requested_height are set, only icons that are this size or smaller are
714  * returned, unless @prefer_bigger is set, in which case the next biggest icon
715  * will be returned. The returned strings should be freed.
716  *
717  * Return value: (transfer full): a string, or %NULL.  g_free() after use.
718  **/
719 char *
720 gupnp_device_info_get_icon_url (GUPnPDeviceInfo *info,
721                                 const char      *requested_mime_type,
722                                 int              requested_depth,
723                                 int              requested_width,
724                                 int              requested_height,
725                                 gboolean         prefer_bigger,
726                                 char           **mime_type,
727                                 int             *depth,
728                                 int             *width,
729                                 int             *height)
730 {
731         GList *icons, *l;
732         xmlNode *element;
733         Icon *icon, *closest;
734         char *ret;
735
736         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
737
738         /* List available icons */
739         icons = NULL;
740
741         element = xml_util_get_element (info->priv->element,
742                                         "iconList",
743                                         NULL);
744         if (!element)
745                 return NULL;
746
747         for (element = element->children; element; element = element->next) {
748                 if (!strcmp ("icon", (char *) element->name)) {
749                         gboolean mime_type_ok;
750
751                         icon = icon_parse (info, element);
752
753                         if (requested_mime_type) {
754                                 if (icon->mime_type)
755                                         mime_type_ok = !strcmp (
756                                                 requested_mime_type,
757                                                 (char *) icon->mime_type);
758                                 else
759                                         mime_type_ok = FALSE;
760                         } else
761                                 mime_type_ok = TRUE;
762
763                         if (requested_depth >= 0)
764                                 icon->weight = requested_depth - icon->depth;
765
766                         /* Filter out icons with incorrect mime type or
767                          * incorrect depth. */
768                         if (mime_type_ok && icon->weight >= 0) {
769                                 if (requested_width >= 0) {
770                                         if (prefer_bigger) {
771                                                 icon->weight +=
772                                                         icon->width -
773                                                         requested_width;
774                                         } else {
775                                                 icon->weight +=
776                                                         requested_width -
777                                                         icon->width;
778                                         }
779                                 }
780
781                                 if (requested_height >= 0) {
782                                         if (prefer_bigger) {
783                                                 icon->weight +=
784                                                         icon->height -
785                                                         requested_height;
786                                         } else {
787                                                 icon->weight +=
788                                                         requested_height -
789                                                         icon->height;
790                                         }
791                                 }
792
793                                 icons = g_list_prepend (icons, icon);
794                         } else
795                                 icon_free (icon);
796                 }
797         }
798
799         if (icons == NULL)
800                 return NULL;
801
802         /* Find closest match */
803         closest = NULL;
804         for (l = icons; l; l = l->next) {
805                 icon = l->data;
806
807                 /* Look between icons with positive weight first */
808                 if (icon->weight >= 0) {
809                         if (!closest || icon->weight < closest->weight)
810                                 closest = icon;
811                 }
812         }
813
814         if (!closest) {
815                 for (l = icons; l; l = l->next) {
816                         icon = l->data;
817
818                         /* No icons with positive weight, look at ones with
819                          * negative weight */
820                         if (!closest || icon->weight > closest->weight)
821                                 closest = icon;
822                 }
823         }
824
825         /* Fill in return values */
826         if (closest) {
827                 icon = closest;
828
829                 if (mime_type) {
830                         if (icon->mime_type) {
831                                 *mime_type = g_strdup
832                                                 ((char *) icon->mime_type);
833                         } else
834                                 *mime_type = NULL;
835                 }
836
837                 if (depth)
838                         *depth = icon->depth;
839                 if (width)
840                         *width = icon->width;
841                 if (height)
842                         *height = icon->height;
843
844                 if (icon->url) {
845                         SoupURI *uri;
846
847                         uri = soup_uri_new_with_base (info->priv->url_base,
848                                                       (const char *) icon->url);
849                         ret = soup_uri_to_string (uri, FALSE);
850                         soup_uri_free (uri);
851                 } else
852                         ret = NULL;
853         } else {
854                 if (mime_type)
855                         *mime_type = NULL;
856                 if (depth)
857                         *depth = -1;
858                 if (width)
859                         *width = -1;
860                 if (height)
861                         *height = -1;
862
863                 ret = NULL;
864         }
865
866         /* Cleanup */
867         g_list_free_full (icons, (GDestroyNotify) icon_free);
868
869         return ret;
870 }
871
872 /* Returns TRUE if @query matches against @base.
873  * - If @query does not specify a version, it matches any version specified
874  *   in @base.
875  * - If @query specifies a version, it matches any version specified in @base
876  *   that is greater or equal.
877  * */
878 static gboolean
879 resource_type_match (const char *query,
880                      const char *base)
881 {
882         gboolean match;
883         guint type_len;
884         char *colon;
885         guint query_ver, base_ver;
886
887         /* Inspect last colon (if any!) on @base */
888         colon = strrchr (base, ':');
889         if (G_UNLIKELY (colon == NULL))
890                 return !strcmp (query, base); /* No colon */
891
892         /* Length of type until last colon */
893         type_len = strlen (base) - strlen (colon);
894
895         /* Match initial portions */
896         match = (strncmp (query, base, type_len) == 0);
897         if (match == FALSE)
898                 return FALSE;
899
900         /* Initial portions matched. Try to position pointers after
901          * last colons of both @query and @base. */
902         colon += 1;
903         if (G_UNLIKELY (*colon == 0))
904                 return TRUE;
905
906         query += type_len;
907         switch (*query) {
908         case 0:
909                 return TRUE; /* @query does not specify a version */
910         case ':':
911                 query += 1;
912                 if (G_UNLIKELY (*query == 0))
913                         return TRUE;
914
915                 break;
916         default:
917                 return FALSE; /* Hmm, not EOS, nor colon.. bad */
918         }
919
920         /* Parse versions */
921         query_ver = atoi (query);
922         base_ver  = atoi (colon);
923
924         /* Compare versions */
925         return (query_ver <= base_ver);
926 }
927
928 /**
929  * gupnp_device_info_list_dlna_device_class_identifier:
930  * @info: A #GUPnPDeviceInfo
931  *
932  * Get a #GList of strings that represent the device class and version as
933  * announced in the device description file using the &lt;dlna:X_DLNADOC&gt;
934  * element.
935  * Returns: (transfer full) (element-type utf8): a #GList of newly allocated strings or
936  * %NULL if the device description doesn't contain the &lt;dlna:X_DLNADOC&gt;
937  * element.
938  *
939  * Since: 0.20.4
940  **/
941 GList *
942 gupnp_device_info_list_dlna_device_class_identifier (GUPnPDeviceInfo *info)
943 {
944         xmlNode *element;
945         GList *list  = NULL;
946
947         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
948
949         element = info->priv->element;
950
951         for (element = element->children; element; element = element->next) {
952                 /* No early exit since the node explicitly may appear multiple
953                  * times: 7.2.10.3 */
954                 if (!strcmp ("X_DLNADOC", (char *) element->name)) {
955                         xmlChar *content = NULL;
956
957                         content = xmlNodeGetContent (element);
958
959                         if (content == NULL)
960                                 continue;
961
962                         list = g_list_prepend (list,
963                                                g_strdup ((char *) content));
964                         xmlFree (content);
965                 }
966         }
967
968         /* Return in order of appearance in document */
969         return g_list_reverse (list);
970 }
971
972 /**
973  * gupnp_device_info_list_dlna_capabilities:
974  * @info: A #GUPnPDeviceInfo
975  *
976  * Get a #GList of strings that represent the device capabilities as announced
977  * in the device description file using the &lt;dlna:X_DLNACAP&gt; element.
978  *
979  * Returns: (transfer full) (element-type utf8): a #GList of newly allocated strings or
980  * %NULL if the device description doesn't contain the &lt;dlna:X_DLNACAP&gt;
981  * element.
982  *
983  * Since: 0.13.0
984  **/
985 GList *
986 gupnp_device_info_list_dlna_capabilities (GUPnPDeviceInfo *info)
987 {
988         xmlChar *caps;
989
990         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
991
992         caps = xml_util_get_child_element_content (info->priv->element,
993                                                    "X_DLNACAP");
994
995         if (caps) {
996                 GList         *list  = NULL;
997                 const xmlChar *start = caps;
998
999                 while (*start) {
1000                         const xmlChar *end = start;
1001
1002                         while (*end && *end != ',')
1003                                 end++;
1004
1005                         if (end > start) {
1006                                 gchar *value;
1007
1008                                 value = g_strndup ((const gchar *) start,
1009                                                    end - start);
1010
1011                                 list = g_list_prepend (list, value);
1012                         }
1013
1014                         if (*end)
1015                                 start = end + 1;
1016                         else
1017                                 break;
1018                 }
1019
1020                 xmlFree (caps);
1021
1022                 return g_list_reverse (list);
1023         }
1024
1025         return NULL;
1026 }
1027
1028 /**
1029  * gupnp_device_info_get_description_value:
1030  * @info:    A #GUPnPDeviceInfo
1031  * @element: Name of the description element to retrieve
1032  *
1033  * This function provides generic access to the contents of arbitrary elements
1034  * in the device description file.
1035  *
1036  * Return value: a newly allocated string or %NULL if the device
1037  *               description doesn't contain the given @element
1038  *
1039  * Since: 0.13.0
1040  **/
1041 char *
1042 gupnp_device_info_get_description_value (GUPnPDeviceInfo *info,
1043                                          const char      *element)
1044 {
1045         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1046         g_return_val_if_fail (element != NULL, NULL);
1047
1048         return xml_util_get_child_element_content_glib (info->priv->element,
1049                                                         element);
1050 }
1051
1052 /**
1053  * gupnp_device_info_list_devices:
1054  * @info: A #GUPnPDeviceInfo
1055  *
1056  * Get a #GList of new objects implementing #GUPnPDeviceInfo
1057  * representing the devices directly contained in @info. The returned list
1058  * should be g_list_free()'d and the elements should be g_object_unref()'d.
1059  *
1060  * Note that devices are not cached internally, so that every time you
1061  * call this function new objects are created. The application
1062  * must cache any used devices if it wishes to keep them around and re-use
1063  * them.
1064  *
1065  * Return value: (element-type GUPnP.DeviceInfo) (transfer full): a #GList of
1066  * new #GUPnPDeviceInfo objects.
1067  **/
1068 GList *
1069 gupnp_device_info_list_devices (GUPnPDeviceInfo *info)
1070 {
1071         GUPnPDeviceInfoClass *class;
1072         GList *devices;
1073         xmlNode *element;
1074
1075         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1076
1077         class = GUPNP_DEVICE_INFO_GET_CLASS (info);
1078
1079         g_return_val_if_fail (class->get_device, NULL);
1080
1081         devices = NULL;
1082
1083         element = xml_util_get_element (info->priv->element,
1084                                         "deviceList",
1085                                         NULL);
1086         if (!element)
1087                 return NULL;
1088
1089         for (element = element->children; element; element = element->next) {
1090                 if (!strcmp ("device", (char *) element->name)) {
1091                         GUPnPDeviceInfo *child;
1092
1093                         child = class->get_device (info, element);
1094                         devices = g_list_prepend (devices, child);
1095                 }
1096         }
1097
1098         return devices;
1099 }
1100
1101 /**
1102  * gupnp_device_info_list_device_types:
1103  * @info: A #GUPnPDeviceInfo
1104  *
1105  * Get a #GList of strings representing the types of the devices
1106  * directly contained in @info.
1107  *
1108  * Return value: (element-type utf8) (transfer full): A #GList of strings. The
1109  * elements should be g_free()'d and the list should be g_list_free()'d.
1110  **/
1111 GList *
1112 gupnp_device_info_list_device_types (GUPnPDeviceInfo *info)
1113 {
1114         GList *device_types;
1115         xmlNode *element;
1116
1117         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1118
1119         device_types = NULL;
1120
1121         element = xml_util_get_element (info->priv->element,
1122                                         "deviceList",
1123                                         NULL);
1124         if (!element)
1125                 return NULL;
1126
1127         for (element = element->children; element; element = element->next) {
1128                 if (!strcmp ("device", (char *) element->name)) {
1129                         char *type;
1130
1131                         type = xml_util_get_child_element_content_glib
1132                                                    (element, "deviceType");
1133                         if (!type)
1134                                 continue;
1135
1136                         device_types = g_list_prepend (device_types, type);
1137                 }
1138         }
1139
1140         return device_types;
1141 }
1142
1143 /**
1144  * gupnp_device_info_get_device:
1145  * @info: A #GUPnPDeviceInfo
1146  * @type: The type of the device to be retrieved.
1147  *
1148  * Get the service with type @type directly contained in @info as
1149  * a new object implementing #GUPnPDeviceInfo, or %NULL if no such device
1150  * was found. The returned object should be unreffed when done.
1151  *
1152  * Note that devices are not cached internally, so that every time you call
1153  * this function a new object is created. The application must cache any used
1154  * devices if it wishes to keep them around and re-use them.
1155  *
1156  * Returns: (transfer full)(allow-none): A new #GUPnPDeviceInfo.
1157  **/
1158 GUPnPDeviceInfo *
1159 gupnp_device_info_get_device (GUPnPDeviceInfo *info,
1160                               const char      *type)
1161 {
1162         GUPnPDeviceInfoClass *class;
1163         GUPnPDeviceInfo *device;
1164         xmlNode *element;
1165
1166         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1167         g_return_val_if_fail (type != NULL, NULL);
1168
1169         class = GUPNP_DEVICE_INFO_GET_CLASS (info);
1170
1171         g_return_val_if_fail (class->get_device, NULL);
1172
1173         device = NULL;
1174
1175         element = xml_util_get_element (info->priv->element,
1176                                         "deviceList",
1177                                         NULL);
1178         if (!element)
1179                 return NULL;
1180
1181         for (element = element->children; element; element = element->next) {
1182                 if (!strcmp ("device", (char *) element->name)) {
1183                         xmlNode *type_element;
1184                         xmlChar *type_str;
1185
1186                         type_element = xml_util_get_element (element,
1187                                                              "deviceType",
1188                                                              NULL);
1189                         if (!type_element)
1190                                 continue;
1191
1192                         type_str = xmlNodeGetContent (type_element);
1193                         if (!type_str)
1194                                 continue;
1195
1196                         if (resource_type_match (type, (char *) type_str))
1197                                 device = class->get_device (info, element);
1198
1199                         xmlFree (type_str);
1200
1201                         if (device)
1202                                 break;
1203                 }
1204         }
1205
1206         return device;
1207 }
1208 /**
1209  * gupnp_device_info_list_services:
1210  * @info: A #GUPnPDeviceInfo
1211  *
1212  * Get a #GList of new objects implementing #GUPnPServiceInfo representing the
1213  * services directly contained in @info. The returned list should be
1214  * g_list_free()'d and the elements should be g_object_unref()'d.
1215  *
1216  * Note that services are not cached internally, so that every time you call
1217  * function new objects are created. The application must cache any used
1218  * services if it wishes to keep them around and re-use them.
1219  *
1220  * Return value: (element-type GUPnP.ServiceInfo) (transfer full) : A #GList of
1221  * new #GUPnPServiceInfo objects.
1222  */
1223 GList *
1224 gupnp_device_info_list_services (GUPnPDeviceInfo *info)
1225 {
1226         GUPnPDeviceInfoClass *class;
1227         GList *services;
1228         xmlNode *element;
1229
1230         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1231
1232         class = GUPNP_DEVICE_INFO_GET_CLASS (info);
1233
1234         g_return_val_if_fail (class->get_service, NULL);
1235
1236         services = NULL;
1237
1238         element = xml_util_get_element (info->priv->element,
1239                                         "serviceList",
1240                                         NULL);
1241         if (!element)
1242                 return NULL;
1243
1244         for (element = element->children; element; element = element->next) {
1245                 if (!strcmp ("service", (char *) element->name)) {
1246                         GUPnPServiceInfo *service;
1247
1248                         service = class->get_service (info, element);
1249                         services = g_list_prepend (services, service);
1250                 }
1251         }
1252
1253         return services;
1254 }
1255
1256 /**
1257  * gupnp_device_info_list_service_types:
1258  * @info: A #GUPnPDeviceInfo
1259  *
1260  * Get a #GList of strings representing the types of the services
1261  * directly contained in @info.
1262  *
1263  * Return value: (element-type utf8) (transfer full): A #GList of strings. The
1264  * elements should be g_free()'d and the list should be g_list_free()'d.
1265  **/
1266 GList *
1267 gupnp_device_info_list_service_types (GUPnPDeviceInfo *info)
1268 {
1269         GList *service_types;
1270         xmlNode *element;
1271
1272         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1273
1274         service_types = NULL;
1275
1276         element = xml_util_get_element (info->priv->element,
1277                                         "serviceList",
1278                                         NULL);
1279         if (!element)
1280                 return NULL;
1281
1282         for (element = element->children; element; element = element->next) {
1283                 if (!strcmp ("service", (char *) element->name)) {
1284                         char *type;
1285
1286                         type = xml_util_get_child_element_content_glib
1287                                                    (element, "serviceType");
1288                         if (!type)
1289                                 continue;
1290
1291                         service_types = g_list_prepend (service_types, type);
1292                 }
1293         }
1294
1295         return service_types;
1296 }
1297
1298 /**
1299  * gupnp_device_info_get_service:
1300  * @info: A #GUPnPDeviceInfo
1301  * @type: The type of the service to be retrieved.
1302  *
1303  * Get the service with type @type directly contained in @info as a new object
1304  * implementing #GUPnPServiceInfo, or %NULL if no such device was found. The
1305  * returned object should be unreffed when done.
1306  *
1307  * Note that services are not cached internally, so that every time you call
1308  * this function a new object is created. The application must cache any used
1309  * services if it wishes to keep them around and re-use them.
1310  *
1311  * Returns: (transfer full): A #GUPnPServiceInfo.
1312  **/
1313 GUPnPServiceInfo *
1314 gupnp_device_info_get_service (GUPnPDeviceInfo *info,
1315                                const char      *type)
1316 {
1317         GUPnPDeviceInfoClass *class;
1318         GUPnPServiceInfo *service;
1319         xmlNode *element;
1320
1321         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1322         g_return_val_if_fail (type != NULL, NULL);
1323
1324         class = GUPNP_DEVICE_INFO_GET_CLASS (info);
1325
1326         g_return_val_if_fail (class->get_service, NULL);
1327
1328         service = NULL;
1329
1330         element = xml_util_get_element (info->priv->element,
1331                                         "serviceList",
1332                                         NULL);
1333         if (!element)
1334                 return NULL;
1335
1336         for (element = element->children; element; element = element->next) {
1337                 if (!strcmp ("service", (char *) element->name)) {
1338                         xmlNode *type_element;
1339                         xmlChar *type_str;
1340
1341                         type_element = xml_util_get_element (element,
1342                                                              "serviceType",
1343                                                              NULL);
1344                         if (!type_element)
1345                                 continue;
1346
1347                         type_str = xmlNodeGetContent (type_element);
1348                         if (!type_str)
1349                                 continue;
1350
1351                         if (resource_type_match (type, (char *) type_str))
1352                                 service = class->get_service (info, element);
1353
1354                         xmlFree (type_str);
1355
1356                         if (service)
1357                                 break;
1358                 }
1359         }
1360
1361         return service;
1362 }
1363
1364 /* Return associated xmlDoc */
1365 GUPnPXMLDoc *
1366 _gupnp_device_info_get_document (GUPnPDeviceInfo *info)
1367 {
1368         return info->priv->doc;
1369 }