doc: Update NEWS for 0.4.1
[gupnp:gupnp-dlna.git] / libgupnp-dlna / gupnp-dlna-load.c
1 /*
2  * Copyright (C) 2010 Nokia Corporation.
3  *
4  * Authors: Arun Raghavan <arun.raghavan@collabora.co.uk>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser 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  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser 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 #include <glib.h>
23 #include <glib/gstdio.h>
24 #include <glib-object.h>
25 #include <libxml/xmlreader.h>
26 #include <libxml/relaxng.h>
27 #include <gst/profile/gstprofile.h>
28 #include "gupnp-dlna-load.h"
29 #include "gupnp-dlna-profile.h"
30
31 #define GST_CAPS_NULL_NAME "NULL"
32 #define DLNA_DATA_DIR DATA_DIR \
33         G_DIR_SEPARATOR_S "dlna-profiles" G_DIR_SEPARATOR_S
34
35 static gboolean
36 copy_func (GQuark field_id, const GValue *value, gpointer data)
37 {
38         GstStructure *st2 = (GstStructure *)data;
39
40         if (!gst_structure_has_field (st2, g_quark_to_string (field_id)))
41                 gst_structure_id_set_value (st2, field_id, value);
42
43         return TRUE;
44 }
45
46 /* Note: It is assumed that caps1 and caps2 have only 1 structure each */
47 static GstCaps *
48 merge_caps (GstCaps *caps1, GstCaps *caps2)
49 {
50         GstStructure *st1, *st2;
51         GstCaps *ret;
52         gboolean any = FALSE;
53
54         /* If one of the caps GST_CAPS_ANY, gst_caps_merge will result in a
55          * GST_CAPS_ANY, which might not be correct for us */
56         if (!gst_caps_is_any (caps1) && !gst_caps_is_any (caps2)) {
57           any = TRUE;
58           gst_caps_merge (caps1, gst_caps_copy (caps2));
59           gst_caps_do_simplify (caps1);
60         }
61
62         ret = gst_caps_make_writable (caps1);
63         st1 = gst_caps_get_structure (ret, 0);
64         if (gst_caps_get_size (caps1) == 2)
65           /* Non-merged fields were copied to a second structure in caps1 at
66            * gst_merge_caps() time */
67           st2 = gst_caps_get_structure (ret, 1);
68         else
69           /* Either one of the caps was GST_CAPS_ANY, or there were no
70            * unmerged fields */
71           st2 = gst_caps_get_structure (caps2, 0);
72
73         /* If caps1 has a name, we retain it. If not, and caps2 does, caps1
74          * gets caps2's name. */
75         if ((g_strcmp0 (GST_CAPS_NULL_NAME,
76                         gst_structure_get_name (st1)) == 0) &&
77             (g_strcmp0 (GST_CAPS_NULL_NAME,
78                         gst_structure_get_name (st2)) != 0)) {
79                 gst_structure_set_name (st1, gst_structure_get_name (st2));
80         }
81
82         /* We now walk over the structures and append any fields that are in
83          * caps2 but not in caps1. */
84         if (any || gst_caps_get_size (caps1) == 2)
85           gst_structure_foreach (st2, copy_func, st1);
86
87         if (gst_caps_get_size (caps1) == 2)
88           gst_caps_remove_structure (ret, 1);
89
90         return ret;
91 }
92
93 static xmlChar *
94 get_value (xmlTextReaderPtr reader)
95 {
96         xmlChar *value = NULL, *curr;
97         int ret = 1;
98
99         curr = xmlTextReaderName (reader);
100
101         /* This function may be called with reader pointing to a <field> or
102          * the element just below a <field>. In the former case, we move the
103          * cursor forward and then continue processing. */
104         if (xmlStrEqual (curr, BAD_CAST ("field")))
105                 ret = xmlTextReaderRead (reader);
106         xmlFree (curr);
107
108         while (ret == 1) {
109                 xmlChar *tag;
110
111                 tag = xmlTextReaderName (reader);
112
113                 if (xmlTextReaderNodeType (reader) == 1 &&
114                     xmlStrEqual (tag, BAD_CAST ("value"))) {
115                         /* <value> */
116
117                         /* Note: This assumes you won't have a comment in the
118                          *       middle of your text */
119                         do {
120                                 ret = xmlTextReaderRead (reader);
121                         } while (ret == 1 &&
122                                  xmlTextReaderNodeType (reader) != 3 &&
123                                  xmlTextReaderNodeType (reader) != 15);
124
125                         /* We're now at the real text between a <value> and a
126                          * </value> */
127
128                         if (xmlTextReaderNodeType (reader) == 3)
129                                 value = xmlTextReaderValue (reader);
130                 }
131
132                 if (xmlTextReaderNodeType (reader) == 15 &&
133                     xmlStrEqual (tag, BAD_CAST ("value"))) {
134                         /* </value> */
135                         xmlFree (tag);
136                         break;
137                 }
138
139                 xmlFree (tag);
140                 ret = xmlTextReaderRead (reader);
141         }
142
143         if (!value)
144                 g_warning ("Empty <value>s are illegal");
145         return value;
146 }
147
148 static void
149 xml_str_free (xmlChar *str, gpointer unused)
150 {
151         xmlFree (str);
152 }
153
154 static void
155 dlna_encoding_profile_add_stream (GstEncodingProfile       *profile,
156                                   GstStreamEncodingProfile *stream_profile)
157 {
158         GList *i;
159
160         /* Try to merge with an existing stream profile of the same type */
161         for (i = profile->encodingprofiles; i; i = i->next) {
162                 GstStreamEncodingProfile *cur =
163                                         (GstStreamEncodingProfile *) i->data;
164
165                 if (cur->type != stream_profile->type)
166                         continue;
167
168                 /* Since we maintain only one stream profile for each type,
169                  * this will get executed exactly once */
170                 gst_caps_merge (cur->format,
171                                 gst_caps_copy (stream_profile->format));
172
173                 gst_stream_encoding_profile_free (stream_profile);
174                 goto done;
175         }
176
177         /* If we get here, there's no existing stream of this type */
178         gst_encoding_profile_add_stream (profile, stream_profile);
179
180 done:
181         return;
182 }
183
184 static void process_range (xmlTextReaderPtr reader, GString *caps_str)
185 {
186         xmlChar *min, *max;
187
188         min = xmlTextReaderGetAttribute (reader, BAD_CAST ("min"));
189         max = xmlTextReaderGetAttribute (reader, BAD_CAST ("max"));
190
191         g_string_append_printf (caps_str, "[ %s, %s ]", min, max);
192
193         xmlFree (min);
194         xmlFree (max);
195 }
196
197 static int
198 process_field (xmlTextReaderPtr reader,
199                GString          *caps_str,
200                gboolean         relaxed_mode,
201                gboolean         extended_mode)
202 {
203         int ret;
204         xmlChar *name;
205         xmlChar *type;
206         xmlChar *used;
207         GList *values = NULL;
208         gboolean done = FALSE, skip = FALSE;
209
210         /*
211          * Parse the 'used' attribute and figure out the mode we
212          * need to follow.
213          */
214         used = xmlTextReaderGetAttribute (reader, BAD_CAST ("used"));
215         if (used) {
216                 if ((relaxed_mode == FALSE) &&
217                     xmlStrEqual (used, BAD_CAST ("in-relaxed"))) {
218                         xmlFree (used);
219                         skip = TRUE;
220                 } else if ((relaxed_mode == TRUE) &&
221                            (xmlStrEqual (used, BAD_CAST ("in-strict")))) {
222                         xmlFree (used);
223                         skip = TRUE;
224                 }
225         }
226
227         name = xmlTextReaderGetAttribute (reader, BAD_CAST ("name"));
228         type = xmlTextReaderGetAttribute (reader, BAD_CAST ("type"));
229
230         /*
231          * This function reads a <field> and appends it to caps_str in the
232          * GstCaps-as-a-string format:
233          *
234          *   Single value: field = (type) value
235          *   Multiple values: field = (type) { value1, value2, value3 }
236          *   Range: field = (type) [ min, max ]
237          */
238
239         /* Fields are comma-separeted. The leading comma is okay for the first
240          * field - we will be prepending the restriction name to this string */
241         if (!skip)
242                 g_string_append_printf (caps_str, ", %s = (%s) ", name, type);
243         xmlFree (name);
244         xmlFree (type);
245
246         ret = xmlTextReaderRead (reader);
247         while (ret == 1 && !done) {
248                 xmlChar *tag;
249
250                 tag = xmlTextReaderName (reader);
251
252                 switch (xmlTextReaderNodeType (reader)) {
253                 case 1:
254                         if (skip)
255                                 break;
256
257                         if (xmlStrEqual (tag, BAD_CAST ("range"))) {
258                                 /* <range> */
259                                 process_range (reader, caps_str);
260                         } else if (xmlStrEqual (tag, BAD_CAST ("value"))) {
261                                 /* <value> */
262                                 xmlChar *value;
263
264                                 value = get_value (reader);
265
266                                 if (value)
267                                         values = g_list_append (values, value);
268                         }
269
270                         break;
271
272                 case 15:
273                         if (xmlStrEqual (tag, BAD_CAST ("field")))
274                                 /* </field> */
275                                 done = TRUE;
276
277                         break;
278
279                 default:
280                         break;
281                 }
282
283                 xmlFree (tag);
284                 ret = xmlTextReaderRead (reader);
285         }
286
287         if (skip)
288                 return ret;
289
290         if (g_list_length (values) == 1)
291                 /* Single value */
292                 g_string_append_printf (caps_str, "%s",
293                                         (xmlChar *) values->data);
294         else if (g_list_length (values) > 1) {
295                 /* Multiple values */
296                 GList *tmp = values->next;
297                 g_string_append_printf (caps_str, "{ %s",
298                                         (xmlChar *) values->data);
299
300                 do {
301                         g_string_append_printf (caps_str, ", %s",
302                                                 (xmlChar *) tmp->data);
303                 } while ((tmp = tmp->next) != NULL);
304
305                 g_string_append_printf (caps_str, " }");
306         }
307
308         if (values) {
309                 g_list_foreach (values, (GFunc) xml_str_free, NULL);
310                 g_list_free (values);
311         }
312
313         return ret;
314 }
315
316 static GstStreamEncodingProfile *
317 process_parent (xmlTextReaderPtr reader,
318                 GHashTable       *restrictions,
319                 gboolean         relaxed_mode,
320                 gboolean         extended_mode)
321 {
322         xmlChar *parent;
323         xmlChar *used;
324         GstStreamEncodingProfile *profile;
325
326         /*
327          * Check to see if we need to follow any relaxed/strict mode
328          * restrictions.
329          */
330         used = xmlTextReaderGetAttribute (reader, BAD_CAST ("used"));
331         if (used) {
332                 if ((relaxed_mode == FALSE) &&
333                     xmlStrEqual (used, BAD_CAST ("in-relaxed"))) {
334                         xmlFree (used);
335                         return NULL;
336                 } else if ((relaxed_mode == TRUE) &&
337                            (xmlStrEqual (used, BAD_CAST ("in-strict")))) {
338                         xmlFree (used);
339                         return NULL;
340                 }
341         }
342
343         parent = xmlTextReaderGetAttribute (reader, BAD_CAST ("name"));
344         profile = g_hash_table_lookup (restrictions, parent);
345
346         if (!profile) {
347                 g_warning ("Could not find parent restriction: %s", parent);
348                 return NULL;
349         }
350
351         xmlFree (parent);
352         xmlFree (used);
353         return gst_stream_encoding_profile_copy (profile);
354 }
355
356 static GstStreamEncodingProfile *
357 process_restriction (xmlTextReaderPtr reader,
358                      GHashTable       *restrictions,
359                      gboolean         relaxed_mode,
360                      gboolean         extended_mode)
361 {
362         GstStreamEncodingProfile *stream_profile = NULL;
363         GstEncodingProfileType type;
364         GstCaps *caps = NULL;
365         GString *caps_str = g_string_sized_new (100);
366         GList *parents = NULL, *tmp;
367         xmlChar *id, *name = NULL, *restr_type, *used;
368         int ret;
369         gboolean done = FALSE, skip = FALSE;
370
371         /*
372          * First we parse the 'used' attribute and figure out
373          * the mode we need to comply to.
374          */
375         used = xmlTextReaderGetAttribute (reader, BAD_CAST ("used"));
376         if (used) {
377                 if ((relaxed_mode == FALSE) &&
378                     xmlStrEqual (used, BAD_CAST ("in-relaxed"))) {
379                         skip = TRUE;
380                 } else if ((relaxed_mode == TRUE) &&
381                            (xmlStrEqual (used, BAD_CAST ("in-strict")))) {
382                         skip = TRUE;
383                 }
384         }
385
386         /* We then walk through the fields in this restriction, and make a
387          * string that can be parsed by gst_caps_from_string (). We then make
388          * a GstCaps from this string, and use the other metadata to make a
389          * GstStreamEncodingProfile */
390
391         id = xmlTextReaderGetAttribute (reader, BAD_CAST ("id"));
392         restr_type = xmlTextReaderGetAttribute (reader, BAD_CAST ("type"));
393
394         ret = xmlTextReaderRead (reader);
395         while (ret == 1 && !done) {
396                 xmlChar *tag;
397
398                 tag = xmlTextReaderName (reader);
399
400                 switch (xmlTextReaderNodeType (reader)) {
401                 case 1:
402                         if (skip)
403                                 break;
404
405                         if (xmlStrEqual (tag, BAD_CAST ("field"))) {
406                                 /* <field> */
407                                 xmlChar *field;
408
409                                 field = xmlTextReaderGetAttribute (reader,
410                                         BAD_CAST ("name"));
411
412                                 /* We handle the "name" field specially - if
413                                  * present, it is the caps name */
414                                 if (xmlStrEqual (field, BAD_CAST ("name")))
415                                         name = get_value (reader);
416                                 else
417                                         process_field (reader,
418                                                        caps_str,
419                                                        relaxed_mode,
420                                                        extended_mode);
421
422                                 xmlFree (field);
423                         } else if (xmlStrEqual (tag, BAD_CAST ("parent"))) {
424                                 /* <parent> */
425                                 GstStreamEncodingProfile *profile =
426                                         process_parent (reader,
427                                                         restrictions,
428                                                         relaxed_mode,
429                                                         extended_mode);
430
431                                 if (profile)
432                                         /* Collect parents in a list - we'll
433                                          * coalesce them later */
434                                         parents = g_list_append (parents,
435                                                                  profile);
436                         }
437
438                         break;
439
440                 case 15:
441                         if (xmlStrEqual (tag, BAD_CAST ("restriction")))
442                                 /* </restriction> */
443                                 done = TRUE;
444
445                         break;
446
447                 default:
448                         break;
449                 }
450
451                 xmlFree (tag);
452                 ret = xmlTextReaderRead (reader);
453         }
454
455         if (skip)
456                 goto out;
457
458         /* If the restriction doesn't have a name, we make it up */
459         if (!name)
460                 name = BAD_CAST (g_strdup (GST_CAPS_NULL_NAME));
461         g_string_prepend (caps_str, (gchar *) name);
462         xmlFree (name);
463
464         if (xmlStrEqual (restr_type, BAD_CAST ("container")))
465                 type = GST_ENCODING_PROFILE_UNKNOWN;
466         else if (xmlStrEqual (restr_type, BAD_CAST ("audio")))
467                 type = GST_ENCODING_PROFILE_AUDIO;
468         else if (xmlStrEqual (restr_type, BAD_CAST ("video")))
469                 type = GST_ENCODING_PROFILE_VIDEO;
470         else if (xmlStrEqual (restr_type, BAD_CAST ("image")))
471                 type = GST_ENCODING_PROFILE_IMAGE;
472         else {
473                 g_warning ("Support for '%s' restrictions not yet implemented",
474                            restr_type);
475                 goto out;
476         }
477
478         caps = gst_caps_from_string (caps_str->str);
479         g_string_free (caps_str, TRUE);
480         g_return_val_if_fail (caps != NULL, NULL);
481
482         tmp = parents;
483         while (tmp) {
484                 /* Merge all the parent caps. The child overrides parent
485                  * attributes */
486                 GstStreamEncodingProfile *profile = tmp->data;
487                 caps = merge_caps (caps, profile->format);
488                 gst_stream_encoding_profile_free (profile);
489                 tmp = tmp->next;
490         }
491
492         stream_profile = gst_stream_encoding_profile_new (type,
493                                                           caps,
494                                                           NULL,
495                                                           GST_CAPS_ANY,
496                                                           0);
497
498         if (id) {
499                 /* Make a copy so we can free it at the end of processing
500                  * without worrying about it being reffed by an encoding
501                  * profile */
502                 GstStreamEncodingProfile *tmp =
503                         gst_stream_encoding_profile_copy (stream_profile);
504                 g_hash_table_insert (restrictions, id, tmp);
505         }
506
507 out:
508         xmlFree (restr_type);
509         if (used)
510                 xmlFree (used);
511         if (caps)
512                 gst_caps_unref (caps);
513         if (parents)
514                 g_list_free (parents);
515         return stream_profile;
516 }
517
518 static void
519 process_restrictions (xmlTextReaderPtr reader,
520                       GHashTable       *restrictions,
521                       gboolean         relaxed_mode,
522                       gboolean         extended_mode)
523 {
524         /* While we use a GstStreamEncodingProfile to store restrictions here,
525          * this is not how they are finally used. This is just a convenient
526          * container for the format caps and stream type. Once the restriction
527          * is used in a profile, all the restrictions of the same type
528          * (audio/video) are merged into a single GstStreamEncodingProfile,
529          * which is added to the GstEncodingProfile for the DLNA profile.
530          */
531
532         int ret = xmlTextReaderRead (reader);
533
534         while (ret == 1) {
535                 xmlChar *tag;
536
537                 tag = xmlTextReaderName (reader);
538
539                 switch (xmlTextReaderNodeType (reader)) {
540                 case 1:
541                         if (xmlStrEqual (tag, BAD_CAST ("restriction"))) {
542                                 /* <restriction> */
543                                 GstStreamEncodingProfile *stream =
544                                         process_restriction (reader,
545                                                              restrictions,
546                                                              relaxed_mode,
547                                                              extended_mode);
548                                 gst_stream_encoding_profile_free (stream);
549                         }
550
551                         break;
552
553                 case 15:
554                         if (xmlStrEqual (tag, BAD_CAST ("restrictions"))) {
555                                 /* </restrictions> */
556                                 xmlFree (tag);
557                                 return;
558                         }
559
560                 default:
561                         break;
562                 }
563
564                 xmlFree (tag);
565                 ret = xmlTextReaderRead (reader);
566         }
567 }
568
569 static int
570 process_dlna_profile (xmlTextReaderPtr   reader,
571                       GList              **profiles,
572                       GUPnPDLNALoadState *data)
573 {
574         int ret;
575         GUPnPDLNAProfile *profile;
576         GstStreamEncodingProfile *stream_profile;
577         GstEncodingProfile *enc_profile, *base = NULL;
578         GstCaps *format = NULL;
579         GList *stream_profiles = NULL, *streams;
580         xmlChar *name, *mime, *id, *base_profile, *extended;
581         gboolean done = FALSE, is_extended = FALSE;;
582
583         name = xmlTextReaderGetAttribute (reader, BAD_CAST ("name"));
584         mime = xmlTextReaderGetAttribute (reader, BAD_CAST ("mime"));
585         extended = xmlTextReaderGetAttribute (reader, BAD_CAST ("extended"));
586         id = xmlTextReaderGetAttribute (reader, BAD_CAST ("id"));
587         base_profile = xmlTextReaderGetAttribute (reader,
588                                                   BAD_CAST ("base-profile"));
589
590         if (!name) {
591                 g_assert (mime == NULL);
592
593                 /* We need a non-NULL string to not trigger asserts in the
594                  * places these are used. Profiles without names are used
595                  * only for inheritance, not for actual matching. */
596                 name = xmlStrdup (BAD_CAST (""));
597                 mime = xmlStrdup (BAD_CAST (""));
598         }
599
600         if (extended && xmlStrEqual (extended, BAD_CAST ("true"))) {
601                 /* If we're not in extended mode, skip this profile */
602                 if (!data->extended_mode)
603                         goto out;
604
605                 is_extended = TRUE;
606         }
607
608         ret = xmlTextReaderRead (reader);
609         while (ret == 1 && !done) {
610                 xmlChar *tag;
611
612                 tag = xmlTextReaderName (reader);
613
614                 switch (xmlTextReaderNodeType (reader)) {
615                 case 1:
616                         if (xmlStrEqual (tag, BAD_CAST ("restriction"))) {
617                                 stream_profile =
618                                         process_restriction (reader,
619                                                              data->restrictions,
620                                                              data->relaxed_mode,
621                                                              data->extended_mode);
622                         } else if (xmlStrEqual (tag, BAD_CAST ("parent"))) {
623                                 stream_profile =
624                                         process_parent (reader,
625                                                         data->restrictions,
626                                                         data->relaxed_mode,
627                                                         data->extended_mode);
628                         }
629
630                         if (!stream_profile)
631                                 break;
632
633                         if (stream_profile->type ==
634                                         GST_ENCODING_PROFILE_UNKNOWN) {
635                                 format = gst_caps_copy (
636                                                 stream_profile->format);
637                                 gst_stream_encoding_profile_free (stream_profile);
638                         } else {
639                                 stream_profiles =
640                                         g_list_append (stream_profiles,
641                                                        stream_profile);
642                         }
643
644                         break;
645
646                 case 15:
647                         if (xmlStrEqual (tag, BAD_CAST ("dlna-profile")))
648                                 done = TRUE;
649
650                 default:
651                         break;
652                 }
653
654                 xmlFree (tag);
655                 ret = xmlTextReaderRead (reader);
656         }
657
658         if (base_profile) {
659                 base = g_hash_table_lookup (data->profile_ids, base_profile);
660                 if (!base)
661                         g_warning ("Invalid base-profile reference");
662         }
663
664         if (!base) {
665                 /* Create a new GstEncodingProfile */
666                 if (!format)
667                         format = GST_CAPS_NONE;
668                 enc_profile = gst_encoding_profile_new ((gchar *) name,
669                                                         format,
670                                                         NULL,
671                                                         0);
672         } else {
673                 /* We're inherting from a parent profile */
674                 enc_profile = gst_encoding_profile_copy (base);
675
676                 g_free (enc_profile->name);
677                 enc_profile->name = g_strdup ((gchar *) name);
678
679                 if (format) {
680                         gst_caps_unref (enc_profile->format);
681                         enc_profile->format = gst_caps_copy (format);
682                 }
683         }
684
685         for (streams = stream_profiles; streams; streams = streams->next) {
686                 GstStreamEncodingProfile *stream_profile =
687                                         (GstStreamEncodingProfile *)
688                                         streams->data;
689                 /* The stream profile *must* not be referenced after this */
690                 dlna_encoding_profile_add_stream (enc_profile, stream_profile);
691         }
692
693         profile = gupnp_dlna_profile_new ((gchar *) name,
694                                           (gchar *) mime,
695                                           enc_profile,
696                                           is_extended);
697         *profiles = g_list_append (*profiles, profile);
698
699         if (id)
700                 /* id is freed when the hash table is destroyed */
701                 g_hash_table_insert (data->profile_ids, id, enc_profile);
702         else
703                 /* we've got a copy in profile, so we're done with this */
704                 gst_encoding_profile_free (enc_profile);
705
706 out:
707         g_list_free (stream_profiles);
708         if (format)
709                 gst_caps_unref (format);
710         xmlFree (mime);
711         xmlFree (name);
712         if (extended)
713                 xmlFree (extended);
714         if (base_profile)
715                 xmlFree (base_profile);
716
717         return ret;
718 }
719
720 static GList *
721 process_include (xmlTextReaderPtr   reader,
722                  GUPnPDLNALoadState *data)
723 {
724         xmlChar *path;
725         GList *ret;
726
727         path = xmlTextReaderGetAttribute (reader, BAD_CAST ("ref"));
728
729         if (!g_path_is_absolute ((gchar *) path)) {
730                 gchar *tmp = g_strconcat (DLNA_DATA_DIR,
731                                           G_DIR_SEPARATOR_S,
732                                           path,
733                                           NULL);
734                 xmlFree (path);
735                 path = BAD_CAST (tmp);
736         }
737
738         ret = gupnp_dlna_load_profiles_from_file ((gchar *) path,
739                                                   data);
740         xmlFree (path);
741
742         return ret;
743 }
744
745 /* This can go away once we have a glib function to canonicalize paths (see
746  * https://bugzilla.gnome.org/show_bug.cgi?id=111848
747  *
748  * The implementationis not generic enough, but sufficient for our use. The
749  * idea is taken from Tristan Van Berkom's comment in the bug mentioned above:
750  *
751  *   1. cd dirname(path)
752  *   2. absdir = $CWD
753  *   3. cd $OLDPWD
754  *   4. abspath = absdir + basename(path)
755  */
756 static gchar *
757 canonicalize_path_name (const char *path)
758 {
759         gchar *dir_name = NULL, *file_name = NULL, *abs_dir = NULL,
760               *old_dir = NULL, *ret = NULL;
761
762         if (g_path_is_absolute (path))
763                 return g_strdup (path);
764
765         old_dir = g_get_current_dir ();
766         dir_name = g_path_get_dirname (path);
767
768         if (g_chdir (dir_name) < 0) {
769                 ret = g_strdup (path);
770                 goto out;
771         }
772
773         abs_dir = g_get_current_dir ();
774         g_chdir (old_dir);
775
776         file_name = g_path_get_basename (path);
777         ret = g_build_filename (abs_dir, file_name, NULL);
778
779 out:
780         g_free (dir_name);
781         g_free (file_name);
782         g_free (abs_dir);
783         g_free (old_dir);
784
785         return ret;
786 }
787
788 GList *
789 gupnp_dlna_load_profiles_from_file (const char         *file_name,
790                                     GUPnPDLNALoadState *data)
791 {
792         GList *profiles = NULL;
793         gchar *path = NULL;
794         xmlTextReaderPtr reader;
795         xmlRelaxNGParserCtxtPtr rngp;
796         xmlRelaxNGPtr rngs;
797         int ret;
798
799         path = canonicalize_path_name (file_name);
800         if (g_hash_table_lookup_extended (data->files_hash, path, NULL, NULL))
801                 goto out;
802         else
803                 g_hash_table_insert (data->files_hash, g_strdup (path), NULL);
804
805         reader = xmlNewTextReaderFilename (path);
806         if (!reader)
807                 goto out;
808
809         /* Load the schema for validation */
810         rngp = xmlRelaxNGNewParserCtxt (DLNA_DATA_DIR "dlna-profiles.rng");
811         rngs = xmlRelaxNGParse (rngp);
812         xmlTextReaderRelaxNGSetSchema (reader, rngs);
813
814         ret = xmlTextReaderRead (reader);
815         while (ret == 1) {
816                 xmlChar *tag;
817
818                 tag = xmlTextReaderName (reader);
819
820                 switch (xmlTextReaderNodeType (reader)) {
821                         /* Start tag */
822                         case 1:
823                                 if (xmlStrEqual (tag, BAD_CAST ("include"))) {
824                                         /* <include> */
825                                         GList *include =
826                                                 process_include (reader,
827                                                                  data);
828                                         profiles = g_list_concat (profiles,
829                                                                   include);
830                                 } else if (xmlStrEqual (tag,
831                                         BAD_CAST ("restrictions"))) {
832                                         /* <restrictions> */
833                                         process_restrictions (reader,
834                                                               data->restrictions,
835                                                               data->relaxed_mode,
836                                                               data->extended_mode);
837                                 } else if (xmlStrEqual (tag,
838                                         BAD_CAST ("dlna-profile"))) {
839                                         /* <dlna-profile> */
840                                         process_dlna_profile (reader,
841                                                               &profiles,
842                                                               data);
843
844                                 }
845
846                                 break;
847
848                         default:
849                                 break;
850                 }
851
852                 xmlFree (tag);
853                 ret = xmlTextReaderRead (reader);
854         }
855
856         xmlFreeTextReader (reader);
857         xmlRelaxNGFree (rngs);
858         xmlRelaxNGFreeParserCtxt (rngp);
859
860 out:
861         g_free (path);
862         return profiles;
863 }
864
865 GList *
866 gupnp_dlna_load_profiles_from_dir (gchar              *profile_dir,
867                                    GUPnPDLNALoadState *data)
868 {
869         GDir *dir;
870         data->restrictions =
871                 g_hash_table_new_full (g_str_hash,
872                                        g_str_equal,
873                                        (GDestroyNotify) xmlFree,
874                                        (GDestroyNotify)
875                                         gst_stream_encoding_profile_free);
876         data->profile_ids =
877                 g_hash_table_new_full (g_str_hash,
878                                        g_str_equal,
879                                        (GDestroyNotify) xmlFree,
880                                        (GDestroyNotify)
881                                         gst_encoding_profile_free);
882         GList *profiles = NULL;
883
884         if ((dir = g_dir_open (profile_dir, 0, NULL))) {
885                 const gchar *entry;
886
887                 while ((entry = g_dir_read_name (dir))) {
888                         gchar *path = g_strconcat (profile_dir,
889                                                    G_DIR_SEPARATOR_S,
890                                                    entry,
891                                                    NULL);
892
893                         if (g_str_has_suffix (entry, ".xml") &&
894                             g_file_test (path, G_FILE_TEST_IS_REGULAR)) {
895                                 profiles = g_list_concat (profiles,
896                                         gupnp_dlna_load_profiles_from_file (
897                                                 path,
898                                                 data));
899                         }
900
901                         g_free (path);
902                 }
903
904                 g_dir_close (dir);
905         }
906
907         g_hash_table_unref (data->restrictions);
908         g_hash_table_unref (data->profile_ids);
909         return profiles;
910 }
911
912 GList *
913 gupnp_dlna_load_profiles_from_disk (gboolean relaxed_mode,
914                                     gboolean extended_mode)
915 {
916         GUPnPDLNALoadState *load_data;
917         GList *ret, *i;
918
919         load_data = g_new (GUPnPDLNALoadState, 1);
920
921         if (load_data) {
922                 load_data->files_hash = g_hash_table_new_full (g_str_hash,
923                                                                g_str_equal,
924                                                                g_free,
925                                                                NULL);
926                 load_data->relaxed_mode = relaxed_mode;
927                 load_data->extended_mode = extended_mode;
928         }
929
930         ret = gupnp_dlna_load_profiles_from_dir (DLNA_DATA_DIR,
931                                                  load_data);
932
933         /* Now that we're done loading profiles, remove all profiles with no
934          * name which are only used for inheritance and not matching. */
935         i = ret;
936         while (i) {
937                 GUPnPDLNAProfile *profile = i->data;
938                 const GstEncodingProfile *enc_profile =
939                                         gupnp_dlna_profile_get_encoding_profile (profile);
940                 GList *tmp = g_list_next (i);
941
942                 if (enc_profile->name[0] == '\0') {
943                         ret = g_list_delete_link (ret, i);
944                         g_object_unref (profile);
945                 }
946
947                 i = tmp;
948         }
949
950         g_hash_table_unref (load_data->files_hash);
951         g_free (load_data);
952         load_data = NULL;
953         return ret;
954 }