libgupnp-dlna: add 'extended' mode property
[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 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 #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, GString *caps_str)
199 {
200         int ret;
201         xmlChar *name;
202         xmlChar *type;
203         GList *values = NULL;
204         gboolean done = FALSE;
205
206         name = xmlTextReaderGetAttribute (reader, BAD_CAST ("name"));
207         type = xmlTextReaderGetAttribute (reader, BAD_CAST ("type"));
208
209         /*
210          * This function reads a <field> and appends it to caps_str in the
211          * GstCaps-as-a-string format:
212          *
213          *   Single value: field = (type) value
214          *   Multiple values: field = (type) { value1, value2, value3 }
215          *   Range: field = (type) [ min, max ]
216          */
217
218         /* Fields are comma-separeted. The leading comma is okay for the first
219          * field - we will be prepending the restriction name to this string */
220         g_string_append_printf (caps_str, ", %s = (%s) ", name, type);
221         xmlFree (name);
222         xmlFree (type);
223
224         ret = xmlTextReaderRead (reader);
225         while (ret == 1 && !done) {
226                 xmlChar *tag;
227
228                 tag = xmlTextReaderName (reader);
229
230                 switch (xmlTextReaderNodeType (reader)) {
231                 case 1:
232                         if (xmlStrEqual (tag, BAD_CAST ("range"))) {
233                                 /* <range> */
234                                 process_range (reader, caps_str);
235                         } else if (xmlStrEqual (tag, BAD_CAST ("value"))) {
236                                 /* <value> */
237                                 xmlChar *value;
238
239                                 value = get_value (reader);
240
241                                 if (value)
242                                         values = g_list_append (values, value);
243                         }
244
245                         break;
246
247                 case 15:
248                         if (xmlStrEqual (tag, BAD_CAST ("field")))
249                                 /* </field> */
250                                 done = TRUE;
251
252                         break;
253
254                 default:
255                         break;
256                 }
257
258                 xmlFree (tag);
259                 ret = xmlTextReaderRead (reader);
260         }
261
262         if (g_list_length (values) == 1)
263                 /* Single value */
264                 g_string_append_printf (caps_str, "%s",
265                                         (xmlChar *) values->data);
266         else if (g_list_length (values) > 1) {
267                 /* Multiple values */
268                 GList *tmp = values->next;
269                 g_string_append_printf (caps_str, "{ %s",
270                                         (xmlChar *) values->data);
271
272                 do {
273                         g_string_append_printf (caps_str, ", %s",
274                                                 (xmlChar *) tmp->data);
275                 } while ((tmp = tmp->next) != NULL);
276
277                 g_string_append_printf (caps_str, " }");
278         }
279
280         if (values) {
281                 g_list_foreach (values, (GFunc) xml_str_free, NULL);
282                 g_list_free (values);
283         }
284
285         return ret;
286 }
287
288 static GstStreamEncodingProfile *
289 process_parent (xmlTextReaderPtr reader, GHashTable *restrictions)
290 {
291         xmlChar *parent;
292         GstStreamEncodingProfile *profile;
293
294         parent = xmlTextReaderGetAttribute (reader, BAD_CAST ("name"));
295         profile = g_hash_table_lookup (restrictions, parent);
296
297         if (!profile) {
298                 g_warning ("Could not find parent restriction: %s", parent);
299                 return NULL;
300         }
301
302         xmlFree (parent);
303         return gst_stream_encoding_profile_copy (profile);
304 }
305
306 static GstStreamEncodingProfile *
307 process_restriction (xmlTextReaderPtr reader, GHashTable *restrictions)
308 {
309         GstStreamEncodingProfile *stream_profile = NULL;
310         GstEncodingProfileType type;
311         GstCaps *caps = NULL;
312         GString *caps_str = g_string_sized_new (100);
313         GList *parents = NULL, *tmp;
314         xmlChar *id, *name = NULL, *restr_type;
315         int ret;
316         gboolean done = FALSE;
317
318         /* First, we walk through the fields in this restriction, and make a
319          * string that can be parsed by gst_caps_from_string (). We then make
320          * a GstCaps from this string, and use the other metadata to make a
321          * GstStreamEncodingProfile */
322
323         id = xmlTextReaderGetAttribute (reader, BAD_CAST ("id"));
324         restr_type = xmlTextReaderGetAttribute (reader, BAD_CAST ("type"));
325
326         ret = xmlTextReaderRead (reader);
327         while (ret == 1 && !done) {
328                 xmlChar *tag;
329
330                 tag = xmlTextReaderName (reader);
331
332                 switch (xmlTextReaderNodeType (reader)) {
333                 case 1:
334                         if (xmlStrEqual (tag, BAD_CAST ("field"))) {
335                                 /* <field> */
336                                 xmlChar *field;
337
338                                 field = xmlTextReaderGetAttribute (reader,
339                                         BAD_CAST ("name"));
340
341                                 /* We handle the "name" field specially - if
342                                  * present, it is the caps name */
343                                 if (xmlStrEqual (field, BAD_CAST ("name")))
344                                         name = get_value (reader);
345                                 else
346                                         process_field (reader, caps_str);
347
348                                 xmlFree (field);
349                         } else if (xmlStrEqual (tag, BAD_CAST ("parent"))) {
350                                 /* <parent> */
351                                 GstStreamEncodingProfile *profile =
352                                         process_parent (reader, restrictions);
353
354                                 if (profile)
355                                         /* Collect parents in a list - we'll
356                                          * coalesce them later */
357                                         parents = g_list_append (parents,
358                                                                  profile);
359                         }
360
361                         break;
362
363                 case 15:
364                         if (xmlStrEqual (tag, BAD_CAST ("restriction")))
365                                 /* </restriction> */
366                                 done = TRUE;
367
368                         break;
369
370                 default:
371                         break;
372                 }
373
374                 xmlFree (tag);
375                 ret = xmlTextReaderRead (reader);
376         }
377
378         /* If the restriction doesn't have a name, we make it up */
379         if (!name)
380                 name = BAD_CAST (g_strdup (GST_CAPS_NULL_NAME));
381         g_string_prepend (caps_str, (gchar *) name);
382         xmlFree (name);
383
384         if (xmlStrEqual (restr_type, BAD_CAST ("container")))
385                 type = GST_ENCODING_PROFILE_UNKNOWN;
386         else if (xmlStrEqual (restr_type, BAD_CAST ("audio")))
387                 type = GST_ENCODING_PROFILE_AUDIO;
388         else if (xmlStrEqual (restr_type, BAD_CAST ("video")))
389                 type = GST_ENCODING_PROFILE_VIDEO;
390         else if (xmlStrEqual (restr_type, BAD_CAST ("image")))
391                 type = GST_ENCODING_PROFILE_IMAGE;
392         else {
393                 g_warning ("Support for '%s' restrictions not yet implemented",
394                            restr_type);
395                 goto out;
396         }
397
398         caps = gst_caps_from_string (caps_str->str);
399         g_string_free (caps_str, TRUE);
400         g_return_val_if_fail (caps != NULL, NULL);
401
402         tmp = parents;
403         while (tmp) {
404                 /* Merge all the parent caps. The child overrides parent
405                  * attributes */
406                 GstStreamEncodingProfile *profile = tmp->data;
407                 caps = merge_caps (caps, profile->format);
408                 gst_stream_encoding_profile_free (profile);
409                 tmp = tmp->next;
410         }
411
412         stream_profile = gst_stream_encoding_profile_new (type,
413                                                           caps,
414                                                           NULL,
415                                                           GST_CAPS_ANY,
416                                                           0);
417
418         if (id) {
419                 /* Make a copy so we can free it at the end of processing
420                  * without worrying about it being reffed by an encoding
421                  * profile */
422                 GstStreamEncodingProfile *tmp =
423                         gst_stream_encoding_profile_copy (stream_profile);
424                 g_hash_table_insert (restrictions, id, tmp);
425         }
426
427 out:
428         xmlFree (restr_type);
429         if (caps)
430                 gst_caps_unref (caps);
431         if (parents)
432                 g_list_free (parents);
433         return stream_profile;
434 }
435
436 static void
437 process_restrictions (xmlTextReaderPtr reader, GHashTable *restrictions)
438 {
439         /* While we use a GstStreamEncodingProfile to store restrictions here,
440          * this is not how they are finally used. This is just a convenient
441          * container for the format caps and stream type. Once the restriction
442          * is used in a profile, all the restrictions of the same type
443          * (audio/video) are merged into a single GstStreamEncodingProfile,
444          * which is added to the GstEncodingProfile for the DLNA profile.
445          */
446
447         int ret = xmlTextReaderRead (reader);
448
449         while (ret == 1) {
450                 xmlChar *tag;
451
452                 tag = xmlTextReaderName (reader);
453
454                 switch (xmlTextReaderNodeType (reader)) {
455                 case 1:
456                         if (xmlStrEqual (tag, BAD_CAST ("restriction"))) {
457                                 /* <restriction> */
458                                 GstStreamEncodingProfile *stream =
459                                         process_restriction (reader,
460                                                              restrictions);
461                                 gst_stream_encoding_profile_free (stream);
462                         }
463
464                         break;
465
466                 case 15:
467                         if (xmlStrEqual (tag, BAD_CAST ("restrictions"))) {
468                                 /* </restrictions> */
469                                 xmlFree (tag);
470                                 return;
471                         }
472
473                 default:
474                         break;
475                 }
476
477                 xmlFree (tag);
478                 ret = xmlTextReaderRead (reader);
479         }
480 }
481
482 static int
483 process_dlna_profile (xmlTextReaderPtr reader,
484                       GList            **profiles,
485                       GHashTable       *restrictions,
486                       GHashTable       *profile_ids)
487 {
488         int ret;
489         GUPnPDLNAProfile *profile;
490         GstStreamEncodingProfile *stream_profile;
491         GstEncodingProfile *enc_profile, *base = NULL;
492         GstCaps *format = NULL;
493         GList *stream_profiles = NULL, *streams;
494         xmlChar *name, *mime, *id, *base_profile;
495         gboolean done = FALSE;
496
497         name = xmlTextReaderGetAttribute (reader, BAD_CAST ("name"));
498         mime = xmlTextReaderGetAttribute (reader, BAD_CAST ("mime"));
499         id = xmlTextReaderGetAttribute (reader, BAD_CAST ("id"));
500         base_profile = xmlTextReaderGetAttribute (reader,
501                                                   BAD_CAST ("base-profile"));
502
503         if (!name) {
504                 g_assert (mime == NULL);
505
506                 /* We need a non-NULL string to not trigger asserts in the
507                  * places these are used. Profiles without names are used
508                  * only for inheritance, not for actual matching. */
509                 name = xmlStrdup (BAD_CAST (""));
510                 mime = xmlStrdup (BAD_CAST (""));
511         }
512
513         ret = xmlTextReaderRead (reader);
514         while (ret == 1 && !done) {
515                 xmlChar *tag;
516
517                 tag = xmlTextReaderName (reader);
518
519                 switch (xmlTextReaderNodeType (reader)) {
520                 case 1:
521                         if (xmlStrEqual (tag, BAD_CAST ("restriction"))) {
522                                 stream_profile =
523                                         process_restriction (reader,
524                                                              restrictions);
525                         } else if (xmlStrEqual (tag, BAD_CAST ("parent"))) {
526                                 stream_profile =
527                                         process_parent (reader, restrictions);
528                         }
529
530                         if (!stream_profile)
531                                 break;
532
533                         if (stream_profile->type ==
534                                         GST_ENCODING_PROFILE_UNKNOWN) {
535                                 format = gst_caps_copy (
536                                                 stream_profile->format);
537                                 gst_stream_encoding_profile_free (stream_profile);
538                         } else {
539                                 stream_profiles =
540                                         g_list_append (stream_profiles,
541                                                        stream_profile);
542                         }
543
544                         break;
545
546                 case 15:
547                         if (xmlStrEqual (tag, BAD_CAST ("dlna-profile")))
548                                 done = TRUE;
549
550                 default:
551                         break;
552                 }
553
554                 xmlFree (tag);
555                 ret = xmlTextReaderRead (reader);
556         }
557
558         if (base_profile) {
559                 base = g_hash_table_lookup (profile_ids, base_profile);
560                 if (!base)
561                         g_warning ("Invalid base-profile reference");
562         }
563
564         if (!base) {
565                 /* Create a new GstEncodingProfile */
566                 if (!format)
567                         format = GST_CAPS_NONE;
568                 enc_profile = gst_encoding_profile_new ((gchar *) name,
569                                                         format,
570                                                         NULL,
571                                                         0);
572         } else {
573                 /* We're inherting from a parent profile */
574                 enc_profile = gst_encoding_profile_copy (base);
575
576                 g_free (enc_profile->name);
577                 enc_profile->name = g_strdup ((gchar *) name);
578
579                 if (format) {
580                         gst_caps_unref (enc_profile->format);
581                         enc_profile->format = gst_caps_copy (format);
582                 }
583         }
584
585         for (streams = stream_profiles; streams; streams = streams->next) {
586                 GstStreamEncodingProfile *stream_profile =
587                                         (GstStreamEncodingProfile *)
588                                         streams->data;
589                 /* The stream profile *must* not be referenced after this */
590                 dlna_encoding_profile_add_stream (enc_profile, stream_profile);
591         }
592
593         profile = gupnp_dlna_profile_new ((gchar *) name,
594                                           (gchar *) mime,
595                                           enc_profile,
596                                           FALSE);
597         *profiles = g_list_append (*profiles, profile);
598
599         if (id)
600                 /* id is freed when the hash table is destroyed */
601                 g_hash_table_insert (profile_ids, id, enc_profile);
602         else
603                 /* we've got a copy in profile, so we're done with this */
604                 gst_encoding_profile_free (enc_profile);
605
606         g_list_free (stream_profiles);
607         if (format)
608                 gst_caps_unref (format);
609         xmlFree (mime);
610         xmlFree (name);
611         if (base_profile)
612                 xmlFree (base_profile);
613
614         return ret;
615 }
616
617 static GList *
618 process_include (xmlTextReaderPtr reader,
619                  GHashTable       *restrictions,
620                  GHashTable       *profile_ids,
621                  GHashTable       *files_hash)
622 {
623         xmlChar *path;
624         GList *ret;
625
626         path = xmlTextReaderGetAttribute (reader, BAD_CAST ("ref"));
627
628         if (!g_path_is_absolute ((gchar *) path)) {
629                 gchar *tmp = g_strconcat (DLNA_DATA_DIR,
630                                           G_DIR_SEPARATOR_S,
631                                           path,
632                                           NULL);
633                 xmlFree (path);
634                 path = BAD_CAST (tmp);
635         }
636
637         ret = gupnp_dlna_load_profiles_from_file ((gchar *) path,
638                                                   restrictions,
639                                                   profile_ids,
640                                                   files_hash);
641         xmlFree (path);
642
643         return ret;
644 }
645
646 /* This can go away once we have a glib function to canonicalize paths (see
647  * https://bugzilla.gnome.org/show_bug.cgi?id=111848
648  *
649  * The implementationis not generic enough, but sufficient for our use. The
650  * idea is taken from Tristan Van Berkom's comment in the bug mentioned above:
651  *
652  *   1. cd dirname(path)
653  *   2. absdir = $CWD
654  *   3. cd $OLDPWD
655  *   4. abspath = absdir + basename(path)
656  */
657 static gchar *
658 canonicalize_path_name (const char *path)
659 {
660         gchar *dir_name = NULL, *file_name = NULL, *abs_dir = NULL,
661               *old_dir = NULL, *ret = NULL;
662
663         if (g_path_is_absolute (path))
664                 return g_strdup (path);
665
666         old_dir = g_get_current_dir ();
667         dir_name = g_path_get_dirname (path);
668
669         if (g_chdir (dir_name) < 0) {
670                 ret = g_strdup (path);
671                 goto out;
672         }
673
674         abs_dir = g_get_current_dir ();
675         g_chdir (old_dir);
676
677         file_name = g_path_get_basename (path);
678         ret = g_build_filename (abs_dir, file_name, NULL);
679
680 out:
681         g_free (dir_name);
682         g_free (file_name);
683         g_free (abs_dir);
684         g_free (old_dir);
685
686         return ret;
687 }
688
689 GList *
690 gupnp_dlna_load_profiles_from_file (const char *file_name,
691                                     GHashTable *restrictions,
692                                     GHashTable *profile_ids,
693                                     GHashTable *files_hash)
694 {
695         GList *profiles = NULL;
696         gchar *path = NULL;
697         xmlTextReaderPtr reader;
698         xmlRelaxNGParserCtxtPtr rngp;
699         xmlRelaxNGPtr rngs;
700         int ret;
701
702         path = canonicalize_path_name (file_name);
703         if (g_hash_table_lookup_extended (files_hash, path, NULL, NULL))
704                 goto out;
705         else
706                 g_hash_table_insert (files_hash, g_strdup (path), NULL);
707
708         reader = xmlNewTextReaderFilename (path);
709         if (!reader)
710                 goto out;
711
712         /* Load the schema for validation */
713         rngp = xmlRelaxNGNewParserCtxt (DLNA_DATA_DIR "dlna-profiles.rng");
714         rngs = xmlRelaxNGParse (rngp);
715         xmlTextReaderRelaxNGSetSchema (reader, rngs);
716
717         ret = xmlTextReaderRead (reader);
718         while (ret == 1) {
719                 xmlChar *tag;
720
721                 tag = xmlTextReaderName (reader);
722
723                 switch (xmlTextReaderNodeType (reader)) {
724                         /* Start tag */
725                         case 1:
726                                 if (xmlStrEqual (tag, BAD_CAST ("include"))) {
727                                         /* <include> */
728                                         GList *include =
729                                                 process_include (reader,
730                                                                  restrictions,
731                                                                  profile_ids,
732                                                                  files_hash);
733                                         profiles = g_list_concat (profiles,
734                                                                   include);
735                                 } else if (xmlStrEqual (tag,
736                                         BAD_CAST ("restrictions"))) {
737                                         /* <restrictions> */
738                                         process_restrictions (reader,
739                                                               restrictions);
740                                 } else if (xmlStrEqual (tag,
741                                         BAD_CAST ("dlna-profile"))) {
742                                         /* <dlna-profile> */
743                                         process_dlna_profile (reader,
744                                                               &profiles,
745                                                               restrictions,
746                                                               profile_ids);
747                                 }
748
749                                 break;
750
751                         default:
752                                 break;
753                 }
754
755                 xmlFree (tag);
756                 ret = xmlTextReaderRead (reader);
757         }
758
759         xmlFreeTextReader (reader);
760         xmlRelaxNGFree (rngs);
761         xmlRelaxNGFreeParserCtxt (rngp);
762
763 out:
764         g_free (path);
765         return profiles;
766 }
767
768 GList *
769 gupnp_dlna_load_profiles_from_dir (gchar *profile_dir, GHashTable *files_hash)
770 {
771         GDir *dir;
772         GHashTable *restrictions =
773                 g_hash_table_new_full (g_str_hash,
774                                        g_str_equal,
775                                        (GDestroyNotify) xmlFree,
776                                        (GDestroyNotify)
777                                         gst_stream_encoding_profile_free);
778         GHashTable *profile_ids =
779                 g_hash_table_new_full (g_str_hash,
780                                        g_str_equal,
781                                        (GDestroyNotify) xmlFree,
782                                        (GDestroyNotify)
783                                         gst_encoding_profile_free);
784         GList *profiles = NULL;
785
786         if ((dir = g_dir_open (profile_dir, 0, NULL))) {
787                 const gchar *entry;
788
789                 while ((entry = g_dir_read_name (dir))) {
790                         gchar *path = g_strconcat (profile_dir,
791                                                    G_DIR_SEPARATOR_S,
792                                                    entry,
793                                                    NULL);
794
795                         if (g_str_has_suffix (entry, ".xml") &&
796                             g_file_test (path, G_FILE_TEST_IS_REGULAR)) {
797                                 profiles = g_list_concat (profiles,
798                                         gupnp_dlna_load_profiles_from_file (
799                                                 path,
800                                                 restrictions,
801                                                 profile_ids,
802                                                 files_hash));
803                         }
804
805                         g_free (path);
806                 }
807
808                 g_dir_close (dir);
809         }
810
811         g_hash_table_unref (restrictions);
812         g_hash_table_unref (profile_ids);
813         return profiles;
814 }
815
816 GList *
817 gupnp_dlna_load_profiles_from_disk (void)
818 {
819         GHashTable *files_hash = g_hash_table_new_full (g_str_hash,
820                                                         g_str_equal,
821                                                         g_free,
822                                                         NULL);
823         GList *ret, *i;
824
825         ret = gupnp_dlna_load_profiles_from_dir (DLNA_DATA_DIR, files_hash);
826
827         /* Now that we're done loading profiles, remove all profiles with no
828          * name which are only used for inheritance and not matching. */
829         i = ret;
830         while (i) {
831                 GUPnPDLNAProfile *profile = i->data;
832                 const GstEncodingProfile *enc_profile =
833                                         gupnp_dlna_profile_get_encoding_profile (profile);
834                 GList *tmp = g_list_next (i);
835
836                 if (enc_profile->name[0] == '\0') {
837                         ret = g_list_delete_link (ret, i);
838                         g_object_unref (profile);
839                 }
840
841                 i = tmp;
842         }
843
844         g_hash_table_unref (files_hash);
845         return ret;
846 }