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