xml: Relax AVC_MP4_MP_HD_1080i_AAC profile restrictions
[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;
581         gboolean done = FALSE;
582
583         name = xmlTextReaderGetAttribute (reader, BAD_CAST ("name"));
584         mime = xmlTextReaderGetAttribute (reader, BAD_CAST ("mime"));
585         id = xmlTextReaderGetAttribute (reader, BAD_CAST ("id"));
586         base_profile = xmlTextReaderGetAttribute (reader,
587                                                   BAD_CAST ("base-profile"));
588
589         if (!name) {
590                 g_assert (mime == NULL);
591
592                 /* We need a non-NULL string to not trigger asserts in the
593                  * places these are used. Profiles without names are used
594                  * only for inheritance, not for actual matching. */
595                 name = xmlStrdup (BAD_CAST (""));
596                 mime = xmlStrdup (BAD_CAST (""));
597         }
598
599         ret = xmlTextReaderRead (reader);
600         while (ret == 1 && !done) {
601                 xmlChar *tag;
602
603                 tag = xmlTextReaderName (reader);
604
605                 switch (xmlTextReaderNodeType (reader)) {
606                 case 1:
607                         if (xmlStrEqual (tag, BAD_CAST ("restriction"))) {
608                                 stream_profile =
609                                         process_restriction (reader,
610                                                              data->restrictions,
611                                                              data->relaxed_mode,
612                                                              data->extended_mode);
613                         } else if (xmlStrEqual (tag, BAD_CAST ("parent"))) {
614                                 stream_profile =
615                                         process_parent (reader,
616                                                         data->restrictions,
617                                                         data->relaxed_mode,
618                                                         data->extended_mode);
619                         }
620
621                         if (!stream_profile)
622                                 break;
623
624                         if (stream_profile->type ==
625                                         GST_ENCODING_PROFILE_UNKNOWN) {
626                                 format = gst_caps_copy (
627                                                 stream_profile->format);
628                                 gst_stream_encoding_profile_free (stream_profile);
629                         } else {
630                                 stream_profiles =
631                                         g_list_append (stream_profiles,
632                                                        stream_profile);
633                         }
634
635                         break;
636
637                 case 15:
638                         if (xmlStrEqual (tag, BAD_CAST ("dlna-profile")))
639                                 done = TRUE;
640
641                 default:
642                         break;
643                 }
644
645                 xmlFree (tag);
646                 ret = xmlTextReaderRead (reader);
647         }
648
649         if (base_profile) {
650                 base = g_hash_table_lookup (data->profile_ids, base_profile);
651                 if (!base)
652                         g_warning ("Invalid base-profile reference");
653         }
654
655         if (!base) {
656                 /* Create a new GstEncodingProfile */
657                 if (!format)
658                         format = GST_CAPS_NONE;
659                 enc_profile = gst_encoding_profile_new ((gchar *) name,
660                                                         format,
661                                                         NULL,
662                                                         0);
663         } else {
664                 /* We're inherting from a parent profile */
665                 enc_profile = gst_encoding_profile_copy (base);
666
667                 g_free (enc_profile->name);
668                 enc_profile->name = g_strdup ((gchar *) name);
669
670                 if (format) {
671                         gst_caps_unref (enc_profile->format);
672                         enc_profile->format = gst_caps_copy (format);
673                 }
674         }
675
676         for (streams = stream_profiles; streams; streams = streams->next) {
677                 GstStreamEncodingProfile *stream_profile =
678                                         (GstStreamEncodingProfile *)
679                                         streams->data;
680                 /* The stream profile *must* not be referenced after this */
681                 dlna_encoding_profile_add_stream (enc_profile, stream_profile);
682         }
683
684         profile = gupnp_dlna_profile_new ((gchar *) name,
685                                           (gchar *) mime,
686                                           enc_profile,
687                                           FALSE);
688         *profiles = g_list_append (*profiles, profile);
689
690         if (id)
691                 /* id is freed when the hash table is destroyed */
692                 g_hash_table_insert (data->profile_ids, id, enc_profile);
693         else
694                 /* we've got a copy in profile, so we're done with this */
695                 gst_encoding_profile_free (enc_profile);
696
697         g_list_free (stream_profiles);
698         if (format)
699                 gst_caps_unref (format);
700         xmlFree (mime);
701         xmlFree (name);
702         if (base_profile)
703                 xmlFree (base_profile);
704
705         return ret;
706 }
707
708 static GList *
709 process_include (xmlTextReaderPtr   reader,
710                  GUPnPDLNALoadState *data)
711 {
712         xmlChar *path;
713         GList *ret;
714
715         path = xmlTextReaderGetAttribute (reader, BAD_CAST ("ref"));
716
717         if (!g_path_is_absolute ((gchar *) path)) {
718                 gchar *tmp = g_strconcat (DLNA_DATA_DIR,
719                                           G_DIR_SEPARATOR_S,
720                                           path,
721                                           NULL);
722                 xmlFree (path);
723                 path = BAD_CAST (tmp);
724         }
725
726         ret = gupnp_dlna_load_profiles_from_file ((gchar *) path,
727                                                   data);
728         xmlFree (path);
729
730         return ret;
731 }
732
733 /* This can go away once we have a glib function to canonicalize paths (see
734  * https://bugzilla.gnome.org/show_bug.cgi?id=111848
735  *
736  * The implementationis not generic enough, but sufficient for our use. The
737  * idea is taken from Tristan Van Berkom's comment in the bug mentioned above:
738  *
739  *   1. cd dirname(path)
740  *   2. absdir = $CWD
741  *   3. cd $OLDPWD
742  *   4. abspath = absdir + basename(path)
743  */
744 static gchar *
745 canonicalize_path_name (const char *path)
746 {
747         gchar *dir_name = NULL, *file_name = NULL, *abs_dir = NULL,
748               *old_dir = NULL, *ret = NULL;
749
750         if (g_path_is_absolute (path))
751                 return g_strdup (path);
752
753         old_dir = g_get_current_dir ();
754         dir_name = g_path_get_dirname (path);
755
756         if (g_chdir (dir_name) < 0) {
757                 ret = g_strdup (path);
758                 goto out;
759         }
760
761         abs_dir = g_get_current_dir ();
762         g_chdir (old_dir);
763
764         file_name = g_path_get_basename (path);
765         ret = g_build_filename (abs_dir, file_name, NULL);
766
767 out:
768         g_free (dir_name);
769         g_free (file_name);
770         g_free (abs_dir);
771         g_free (old_dir);
772
773         return ret;
774 }
775
776 GList *
777 gupnp_dlna_load_profiles_from_file (const char         *file_name,
778                                     GUPnPDLNALoadState *data)
779 {
780         GList *profiles = NULL;
781         gchar *path = NULL;
782         xmlTextReaderPtr reader;
783         xmlRelaxNGParserCtxtPtr rngp;
784         xmlRelaxNGPtr rngs;
785         int ret;
786
787         path = canonicalize_path_name (file_name);
788         if (g_hash_table_lookup_extended (data->files_hash, path, NULL, NULL))
789                 goto out;
790         else
791                 g_hash_table_insert (data->files_hash, g_strdup (path), NULL);
792
793         reader = xmlNewTextReaderFilename (path);
794         if (!reader)
795                 goto out;
796
797         /* Load the schema for validation */
798         rngp = xmlRelaxNGNewParserCtxt (DLNA_DATA_DIR "dlna-profiles.rng");
799         rngs = xmlRelaxNGParse (rngp);
800         xmlTextReaderRelaxNGSetSchema (reader, rngs);
801
802         ret = xmlTextReaderRead (reader);
803         while (ret == 1) {
804                 xmlChar *tag;
805
806                 tag = xmlTextReaderName (reader);
807
808                 switch (xmlTextReaderNodeType (reader)) {
809                         /* Start tag */
810                         case 1:
811                                 if (xmlStrEqual (tag, BAD_CAST ("include"))) {
812                                         /* <include> */
813                                         GList *include =
814                                                 process_include (reader,
815                                                                  data);
816                                         profiles = g_list_concat (profiles,
817                                                                   include);
818                                 } else if (xmlStrEqual (tag,
819                                         BAD_CAST ("restrictions"))) {
820                                         /* <restrictions> */
821                                         process_restrictions (reader,
822                                                               data->restrictions,
823                                                               data->relaxed_mode,
824                                                               data->extended_mode);
825                                 } else if (xmlStrEqual (tag,
826                                         BAD_CAST ("dlna-profile"))) {
827                                         /* <dlna-profile> */
828                                         process_dlna_profile (reader,
829                                                               &profiles,
830                                                               data);
831
832                                 }
833
834                                 break;
835
836                         default:
837                                 break;
838                 }
839
840                 xmlFree (tag);
841                 ret = xmlTextReaderRead (reader);
842         }
843
844         xmlFreeTextReader (reader);
845         xmlRelaxNGFree (rngs);
846         xmlRelaxNGFreeParserCtxt (rngp);
847
848 out:
849         g_free (path);
850         return profiles;
851 }
852
853 GList *
854 gupnp_dlna_load_profiles_from_dir (gchar              *profile_dir,
855                                    GUPnPDLNALoadState *data)
856 {
857         GDir *dir;
858         data->restrictions =
859                 g_hash_table_new_full (g_str_hash,
860                                        g_str_equal,
861                                        (GDestroyNotify) xmlFree,
862                                        (GDestroyNotify)
863                                         gst_stream_encoding_profile_free);
864         data->profile_ids =
865                 g_hash_table_new_full (g_str_hash,
866                                        g_str_equal,
867                                        (GDestroyNotify) xmlFree,
868                                        (GDestroyNotify)
869                                         gst_encoding_profile_free);
870         GList *profiles = NULL;
871
872         if ((dir = g_dir_open (profile_dir, 0, NULL))) {
873                 const gchar *entry;
874
875                 while ((entry = g_dir_read_name (dir))) {
876                         gchar *path = g_strconcat (profile_dir,
877                                                    G_DIR_SEPARATOR_S,
878                                                    entry,
879                                                    NULL);
880
881                         if (g_str_has_suffix (entry, ".xml") &&
882                             g_file_test (path, G_FILE_TEST_IS_REGULAR)) {
883                                 profiles = g_list_concat (profiles,
884                                         gupnp_dlna_load_profiles_from_file (
885                                                 path,
886                                                 data));
887                         }
888
889                         g_free (path);
890                 }
891
892                 g_dir_close (dir);
893         }
894
895         g_hash_table_unref (data->restrictions);
896         g_hash_table_unref (data->profile_ids);
897         return profiles;
898 }
899
900 GList *
901 gupnp_dlna_load_profiles_from_disk (gboolean relaxed_mode,
902                                     gboolean extended_mode)
903 {
904         GUPnPDLNALoadState *load_data;
905         GList *ret, *i;
906
907         load_data = g_new (GUPnPDLNALoadState, 1);
908
909         if (load_data) {
910                 load_data->files_hash = g_hash_table_new_full (g_str_hash,
911                                                                g_str_equal,
912                                                                g_free,
913                                                                NULL);
914                 load_data->relaxed_mode = relaxed_mode;
915                 load_data->extended_mode = extended_mode;
916         }
917
918         ret = gupnp_dlna_load_profiles_from_dir (DLNA_DATA_DIR,
919                                                  load_data);
920
921         /* Now that we're done loading profiles, remove all profiles with no
922          * name which are only used for inheritance and not matching. */
923         i = ret;
924         while (i) {
925                 GUPnPDLNAProfile *profile = i->data;
926                 const GstEncodingProfile *enc_profile =
927                                         gupnp_dlna_profile_get_encoding_profile (profile);
928                 GList *tmp = g_list_next (i);
929
930                 if (enc_profile->name[0] == '\0') {
931                         ret = g_list_delete_link (ret, i);
932                         g_object_unref (profile);
933                 }
934
935                 i = tmp;
936         }
937
938         g_hash_table_unref (load_data->files_hash);
939         g_free (load_data);
940         load_data = NULL;
941         return ret;
942 }