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