Add gobject introspection
[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_WRITABLE |
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         while (icons) {
868                 icon_free (icons->data);
869                 icons = g_list_delete_link (icons, icons);
870         }
871
872         return ret;
873 }
874
875 /* Returns TRUE if @query matches against @base.
876  * - If @query does not specify a version, it matches any version specified
877  *   in @base.
878  * - If @query specifies a version, it matches any version specified in @base
879  *   that is greater or equal.
880  * */
881 static gboolean
882 resource_type_match (const char *query,
883                      const char *base)
884 {
885         gboolean match;
886         guint type_len;
887         char *colon;
888         guint query_ver, base_ver;
889
890         /* Inspect last colon (if any!) on @base */
891         colon = strrchr (base, ':');
892         if (G_UNLIKELY (colon == NULL))
893                 return !strcmp (query, base); /* No colon */
894
895         /* Length of type until last colon */
896         type_len = strlen (base) - strlen (colon);
897
898         /* Match initial portions */
899         match = (strncmp (query, base, type_len) == 0);
900         if (match == FALSE)
901                 return FALSE;
902
903         /* Initial portions matched. Try to position pointers after
904          * last colons of both @query and @base. */
905         colon += 1;
906         if (G_UNLIKELY (*colon == 0))
907                 return TRUE;
908
909         query += type_len;
910         switch (*query) {
911         case 0:
912                 return TRUE; /* @query does not specify a version */
913         case ':':
914                 query += 1;
915                 if (G_UNLIKELY (*query == 0))
916                         return TRUE;
917
918                 break;
919         default:
920                 return FALSE; /* Hmm, not EOS, nor colon.. bad */
921         }
922
923         /* Parse versions */
924         query_ver = atoi (query);
925         base_ver  = atoi (colon);
926
927         /* Compare versions */
928         return (query_ver <= base_ver);
929 }
930
931 /**
932  * gupnp_device_info_list_dlna_device_class_identifier:
933  * @info: A #GUPnPDeviceInfo
934  *
935  * Get a #GList of strings that represent the device class and version as
936  * announced in the device description file using the &lt;dlna:X_DLNADOC&gt;
937  * element.
938  * Returns: (transfer full) (element-type utf8): a #GList of newly allocated strings or
939  * %NULL if the device description doesn't contain the &lt;dlna:X_DLNADOC&gt;
940  * element.
941  **/
942 GList *
943 gupnp_device_info_list_dlna_device_class_identifier (GUPnPDeviceInfo *info)
944 {
945         xmlNode *element;
946         GList *list  = NULL;
947
948         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
949
950         element = info->priv->element;
951
952         for (element = element->children; element; element = element->next) {
953                 /* No early exit since the node explicitly may appear multiple
954                  * times: 7.2.10.3 */
955                 if (!strcmp ("X_DLNADOC", (char *) element->name))
956                         list = g_list_prepend (list,
957                                                xmlNodeGetContent(element));
958         }
959
960         /* Return in order of appearance in document */
961         return g_list_reverse (list);
962 }
963
964 /**
965  * gupnp_device_info_list_dlna_capabilities:
966  * @info: A #GUPnPDeviceInfo
967  *
968  * Get a #GList of strings that represent the device capabilities as announced
969  * in the device description file using the &lt;dlna:X_DLNACAP&gt; element.
970  *
971  * Returns: (transfer full) (element-type utf8): a #GList of newly allocated strings or
972  * %NULL if the device description doesn't contain the &lt;dlna:X_DLNACAP&gt;
973  * element.
974  **/
975 GList *
976 gupnp_device_info_list_dlna_capabilities (GUPnPDeviceInfo *info)
977 {
978         xmlChar *caps;
979
980         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
981
982         caps = xml_util_get_child_element_content (info->priv->element,
983                                                    "X_DLNACAP");
984
985         if (caps) {
986                 GList         *list  = NULL;
987                 const xmlChar *start = caps;
988
989                 while (*start) {
990                         const xmlChar *end = start;
991
992                         while (*end && *end != ',')
993                                 end++;
994
995                         if (end > start) {
996                                 gchar *value;
997
998                                 value = g_strndup ((const gchar *) start,
999                                                    end - start);
1000
1001                                 list = g_list_prepend (list, value);
1002                         }
1003
1004                         if (*end)
1005                                 start = end + 1;
1006                         else
1007                                 break;
1008                 }
1009
1010                 xmlFree (caps);
1011
1012                 return g_list_reverse (list);
1013         }
1014
1015         return NULL;
1016 }
1017
1018 /**
1019  * gupnp_device_info_get_description_value:
1020  * @info:    A #GUPnPDeviceInfo
1021  * @element: Name of the description element to retrieve
1022  *
1023  * This function provides generic access to the contents of arbitrary elements
1024  * in the device description file.
1025  *
1026  * Return value: a newly allocated string or %NULL if the device
1027  *               description doesn't contain the given @element
1028  **/
1029 char *
1030 gupnp_device_info_get_description_value (GUPnPDeviceInfo *info,
1031                                          const char      *element)
1032 {
1033         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1034         g_return_val_if_fail (element != NULL, NULL);
1035
1036         return xml_util_get_child_element_content_glib (info->priv->element,
1037                                                         element);
1038 }
1039
1040 /**
1041  * gupnp_device_info_list_devices:
1042  * @info: A #GUPnPDeviceInfo
1043  *
1044  * Get a #GList of new objects implementing #GUPnPDeviceInfo
1045  * representing the devices directly contained in @info. The returned list
1046  * should be g_list_free()'d and the elements should be g_object_unref()'d.
1047  *
1048  * Note that devices are not cached internally, so that every time you
1049  * call this function new objects are created. The application
1050  * must cache any used devices if it wishes to keep them around and re-use
1051  * them.
1052  *
1053  * Return value: (element-type GUPnP.DeviceInfo) (transfer full): a #GList of
1054  * new #GUPnPDeviceInfo objects.
1055  **/
1056 GList *
1057 gupnp_device_info_list_devices (GUPnPDeviceInfo *info)
1058 {
1059         GUPnPDeviceInfoClass *class;
1060         GList *devices;
1061         xmlNode *element;
1062
1063         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1064
1065         class = GUPNP_DEVICE_INFO_GET_CLASS (info);
1066
1067         g_return_val_if_fail (class->get_device, NULL);
1068
1069         devices = NULL;
1070
1071         element = xml_util_get_element (info->priv->element,
1072                                         "deviceList",
1073                                         NULL);
1074         if (!element)
1075                 return NULL;
1076
1077         for (element = element->children; element; element = element->next) {
1078                 if (!strcmp ("device", (char *) element->name)) {
1079                         GUPnPDeviceInfo *child;
1080
1081                         child = class->get_device (info, element);
1082                         devices = g_list_prepend (devices, child);
1083                 }
1084         }
1085
1086         return devices;
1087 }
1088
1089 /**
1090  * gupnp_device_info_list_device_types:
1091  * @info: A #GUPnPDeviceInfo
1092  *
1093  * Get a #GList of strings representing the types of the devices
1094  * directly contained in @info.
1095  *
1096  * Return value: (element-type utf8) (transfer full): A #GList of strings. The
1097  * elements should be g_free()'d and the list should be g_list_free()'d.
1098  **/
1099 GList *
1100 gupnp_device_info_list_device_types (GUPnPDeviceInfo *info)
1101 {
1102         GList *device_types;
1103         xmlNode *element;
1104
1105         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1106
1107         device_types = NULL;
1108
1109         element = xml_util_get_element (info->priv->element,
1110                                         "deviceList",
1111                                         NULL);
1112         if (!element)
1113                 return NULL;
1114
1115         for (element = element->children; element; element = element->next) {
1116                 if (!strcmp ("device", (char *) element->name)) {
1117                         char *type;
1118
1119                         type = xml_util_get_child_element_content_glib
1120                                                    (element, "deviceType");
1121                         if (!type)
1122                                 continue;
1123
1124                         device_types = g_list_prepend (device_types, type);
1125                 }
1126         }
1127
1128         return device_types;
1129 }
1130
1131 /**
1132  * gupnp_device_info_get_device:
1133  * @info: A #GUPnPDeviceInfo
1134  * @type: The type of the device to be retrieved.
1135  *
1136  * Get the service with type @type directly contained in @info as
1137  * a new object implementing #GUPnPDeviceInfo, or %NULL if no such device
1138  * was found. The returned object should be unreffed when done.
1139  *
1140  * Note that devices are not cached internally, so that every time you call
1141  * this function a new object is created. The application must cache any used
1142  * devices if it wishes to keep them around and re-use them.
1143  *
1144  * Returns: (transfer full)(allow-none): A new #GUPnPDeviceInfo.
1145  **/
1146 GUPnPDeviceInfo *
1147 gupnp_device_info_get_device (GUPnPDeviceInfo *info,
1148                               const char      *type)
1149 {
1150         GUPnPDeviceInfoClass *class;
1151         GUPnPDeviceInfo *device;
1152         xmlNode *element;
1153
1154         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1155         g_return_val_if_fail (type != NULL, NULL);
1156
1157         class = GUPNP_DEVICE_INFO_GET_CLASS (info);
1158
1159         g_return_val_if_fail (class->get_device, NULL);
1160
1161         device = NULL;
1162
1163         element = xml_util_get_element (info->priv->element,
1164                                         "deviceList",
1165                                         NULL);
1166         if (!element)
1167                 return NULL;
1168
1169         for (element = element->children; element; element = element->next) {
1170                 if (!strcmp ("device", (char *) element->name)) {
1171                         xmlNode *type_element;
1172                         xmlChar *type_str;
1173
1174                         type_element = xml_util_get_element (element,
1175                                                              "deviceType",
1176                                                              NULL);
1177                         if (!type_element)
1178                                 continue;
1179
1180                         type_str = xmlNodeGetContent (type_element);
1181                         if (!type_str)
1182                                 continue;
1183
1184                         if (resource_type_match (type, (char *) type_str))
1185                                 device = class->get_device (info, element);
1186
1187                         xmlFree (type_str);
1188
1189                         if (device)
1190                                 break;
1191                 }
1192         }
1193
1194         return device;
1195 }
1196 /**
1197  * gupnp_device_info_list_services:
1198  * @info: A #GUPnPDeviceInfo
1199  *
1200  * Get a #GList of new objects implementing #GUPnPServiceInfo representing the
1201  * services directly contained in @info. The returned list should be
1202  * g_list_free()'d and the elements should be g_object_unref()'d.
1203  *
1204  * Note that services are not cached internally, so that every time you call
1205  * function new objects are created. The application must cache any used
1206  * services if it wishes to keep them around and re-use them.
1207  *
1208  * Return value: (element-type GUPnP.ServiceInfo) (transfer full) : A #GList of
1209  * new #GUPnPServiceInfo objects.
1210  */
1211 GList *
1212 gupnp_device_info_list_services (GUPnPDeviceInfo *info)
1213 {
1214         GUPnPDeviceInfoClass *class;
1215         GList *services;
1216         xmlNode *element;
1217
1218         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1219
1220         class = GUPNP_DEVICE_INFO_GET_CLASS (info);
1221
1222         g_return_val_if_fail (class->get_service, NULL);
1223
1224         services = NULL;
1225
1226         element = xml_util_get_element (info->priv->element,
1227                                         "serviceList",
1228                                         NULL);
1229         if (!element)
1230                 return NULL;
1231
1232         for (element = element->children; element; element = element->next) {
1233                 if (!strcmp ("service", (char *) element->name)) {
1234                         GUPnPServiceInfo *service;
1235
1236                         service = class->get_service (info, element);
1237                         services = g_list_prepend (services, service);
1238                 }
1239         }
1240
1241         return services;
1242 }
1243
1244 /**
1245  * gupnp_device_info_list_service_types:
1246  * @info: A #GUPnPDeviceInfo
1247  *
1248  * Get a #GList of strings representing the types of the services
1249  * directly contained in @info.
1250  *
1251  * Return value: (element-type utf8) (transfer full): A #GList of strings. The
1252  * elements should be g_free()'d and the list should be g_list_free()'d.
1253  **/
1254 GList *
1255 gupnp_device_info_list_service_types (GUPnPDeviceInfo *info)
1256 {
1257         GList *service_types;
1258         xmlNode *element;
1259
1260         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1261
1262         service_types = NULL;
1263
1264         element = xml_util_get_element (info->priv->element,
1265                                         "serviceList",
1266                                         NULL);
1267         if (!element)
1268                 return NULL;
1269
1270         for (element = element->children; element; element = element->next) {
1271                 if (!strcmp ("service", (char *) element->name)) {
1272                         char *type;
1273
1274                         type = xml_util_get_child_element_content_glib
1275                                                    (element, "serviceType");
1276                         if (!type)
1277                                 continue;
1278
1279                         service_types = g_list_prepend (service_types, type);
1280                 }
1281         }
1282
1283         return service_types;
1284 }
1285
1286 /**
1287  * gupnp_device_info_get_service:
1288  * @info: A #GUPnPDeviceInfo
1289  * @type: The type of the service to be retrieved.
1290  *
1291  * Get the service with type @type directly contained in @info as a new object
1292  * implementing #GUPnPServiceInfo, or %NULL if no such device was found. The
1293  * returned object should be unreffed when done.
1294  *
1295  * Note that services are not cached internally, so that every time you call
1296  * this function a new object is created. The application must cache any used
1297  * services if it wishes to keep them around and re-use them.
1298  *
1299  * Returns: (transfer full): A #GUPnPServiceInfo.
1300  **/
1301 GUPnPServiceInfo *
1302 gupnp_device_info_get_service (GUPnPDeviceInfo *info,
1303                                const char      *type)
1304 {
1305         GUPnPDeviceInfoClass *class;
1306         GUPnPServiceInfo *service;
1307         xmlNode *element;
1308
1309         g_return_val_if_fail (GUPNP_IS_DEVICE_INFO (info), NULL);
1310         g_return_val_if_fail (type != NULL, NULL);
1311
1312         class = GUPNP_DEVICE_INFO_GET_CLASS (info);
1313
1314         g_return_val_if_fail (class->get_service, NULL);
1315
1316         service = NULL;
1317
1318         element = xml_util_get_element (info->priv->element,
1319                                         "serviceList",
1320                                         NULL);
1321         if (!element)
1322                 return NULL;
1323
1324         for (element = element->children; element; element = element->next) {
1325                 if (!strcmp ("service", (char *) element->name)) {
1326                         xmlNode *type_element;
1327                         xmlChar *type_str;
1328
1329                         type_element = xml_util_get_element (element,
1330                                                              "serviceType",
1331                                                              NULL);
1332                         if (!type_element)
1333                                 continue;
1334
1335                         type_str = xmlNodeGetContent (type_element);
1336                         if (!type_str)
1337                                 continue;
1338
1339                         if (resource_type_match (type, (char *) type_str))
1340                                 service = class->get_service (info, element);
1341
1342                         xmlFree (type_str);
1343
1344                         if (service)
1345                                 break;
1346                 }
1347         }
1348
1349         return service;
1350 }
1351
1352 /* Return associated xmlDoc */
1353 GUPnPXMLDoc *
1354 _gupnp_device_info_get_document (GUPnPDeviceInfo *info)
1355 {
1356         return info->priv->doc;
1357 }