2 * Copyright (C) 2010 Nokia Corporation.
4 * Authors: Arun Raghavan <arun.raghavan@collabora.co.uk>
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.
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.
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.
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"
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
37 copy_func (GQuark field_id, const GValue *value, gpointer data)
39 GstStructure *st2 = (GstStructure *)data;
41 if (!gst_structure_has_field (st2, g_quark_to_string (field_id)))
42 gst_structure_id_set_value (st2, field_id, value);
47 /* Note: It is assumed that caps1 and caps2 have only 1 structure each */
49 merge_caps (GstCaps *caps1, GstCaps *caps2)
51 GstStructure *st1, *st2;
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)) {
59 gst_caps_merge (caps1, gst_caps_copy (caps2));
60 gst_caps_do_simplify (caps1);
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);
70 /* Either one of the caps was GST_CAPS_ANY, or there were no
72 st2 = gst_caps_get_structure (caps2, 0);
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));
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);
88 if (gst_caps_get_size (caps1) == 2)
89 gst_caps_remove_structure (ret, 1);
95 get_value (xmlTextReaderPtr reader)
97 xmlChar *value = NULL, *curr;
100 curr = xmlTextReaderName (reader);
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);
112 tag = xmlTextReaderName (reader);
114 if (xmlTextReaderNodeType (reader) == 1 &&
115 xmlStrEqual (tag, BAD_CAST ("value"))) {
118 /* Note: This assumes you won't have a comment in the
119 * middle of your text */
121 ret = xmlTextReaderRead (reader);
123 xmlTextReaderNodeType (reader) != 3 &&
124 xmlTextReaderNodeType (reader) != 15);
126 /* We're now at the real text between a <value> and a
129 if (xmlTextReaderNodeType (reader) == 3)
130 value = xmlTextReaderValue (reader);
133 if (xmlTextReaderNodeType (reader) == 15 &&
134 xmlStrEqual (tag, BAD_CAST ("value"))) {
142 ret = xmlTextReaderRead (reader);
146 g_warning ("Empty <value>s are illegal");
152 xml_str_free (xmlChar *str, gpointer unused)
158 free_restrictions_struct (gpointer data, gpointer user_data)
160 GUPnPDLNARestrictions *restr = (GUPnPDLNARestrictions *)data;
163 gst_caps_unref (restr->caps);
170 process_range (xmlTextReaderPtr reader, GString *caps_str)
174 min = xmlTextReaderGetAttribute (reader, BAD_CAST ("min"));
175 max = xmlTextReaderGetAttribute (reader, BAD_CAST ("max"));
177 g_string_append_printf (caps_str, "[ %s, %s ]", min, max);
184 process_field (xmlTextReaderPtr reader,
186 gboolean relaxed_mode,
187 gboolean extended_mode)
193 GList *values = NULL;
194 gboolean done = FALSE, skip = FALSE;
197 * Parse the 'used' attribute and figure out the mode we
200 used = xmlTextReaderGetAttribute (reader, BAD_CAST ("used"));
202 if ((relaxed_mode == FALSE) &&
203 xmlStrEqual (used, BAD_CAST ("in-relaxed"))) {
205 } else if ((relaxed_mode == TRUE) &&
206 (xmlStrEqual (used, BAD_CAST ("in-strict")))) {
213 name = xmlTextReaderGetAttribute (reader, BAD_CAST ("name"));
214 type = xmlTextReaderGetAttribute (reader, BAD_CAST ("type"));
217 * This function reads a <field> and appends it to caps_str in the
218 * GstCaps-as-a-string format:
220 * Single value: field = (type) value
221 * Multiple values: field = (type) { value1, value2, value3 }
222 * Range: field = (type) [ min, max ]
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 */
228 g_string_append_printf (caps_str, ", %s = (%s) ", name, type);
233 ret = xmlTextReaderRead (reader);
234 while (ret == 1 && !done) {
237 tag = xmlTextReaderName (reader);
239 switch (xmlTextReaderNodeType (reader)) {
244 if (xmlStrEqual (tag, BAD_CAST ("range"))) {
246 process_range (reader, caps_str);
247 } else if (xmlStrEqual (tag, BAD_CAST ("value"))) {
251 value = get_value (reader);
254 values = g_list_append (values, value);
260 if (xmlStrEqual (tag, BAD_CAST ("field")))
271 ret = xmlTextReaderRead (reader);
277 if (g_list_length (values) == 1)
279 g_string_append_printf (caps_str,
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,
287 (xmlChar *) values->data);
290 g_string_append_printf (caps_str,
292 (xmlChar *) tmp->data);
293 } while ((tmp = tmp->next) != NULL);
295 g_string_append_printf (caps_str, " }");
299 g_list_foreach (values, (GFunc) xml_str_free, NULL);
300 g_list_free (values);
306 static GUPnPDLNARestrictions *
307 process_parent (xmlTextReaderPtr reader, GUPnPDLNALoadState *data)
311 GUPnPDLNARestrictions *restr = NULL;
314 * Check to see if we need to follow any relaxed/strict mode
317 used = xmlTextReaderGetAttribute (reader, BAD_CAST ("used"));
319 if ((data->relaxed_mode == FALSE) &&
320 xmlStrEqual (used, BAD_CAST ("in-relaxed"))) {
323 } else if ((data->relaxed_mode == TRUE) &&
324 (xmlStrEqual (used, BAD_CAST ("in-strict")))) {
330 parent = xmlTextReaderGetAttribute (reader, BAD_CAST ("name"));
331 restr = g_hash_table_lookup (data->restrictions, parent);
334 g_warning ("Could not find parent restriction: %s", parent);
344 static GUPnPDLNARestrictions *
345 process_restriction (xmlTextReaderPtr reader, GUPnPDLNALoadState *data)
347 GUPnPDLNARestrictions *restr = NULL;
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;
354 gboolean done = FALSE, skip = FALSE;
357 * First we parse the 'used' attribute and figure out
358 * the mode we need to comply to.
360 used = xmlTextReaderGetAttribute (reader, BAD_CAST ("used"));
362 if ((data->relaxed_mode == FALSE) &&
363 xmlStrEqual (used, BAD_CAST ("in-relaxed"))) {
365 } else if ((data->relaxed_mode == TRUE) &&
366 (xmlStrEqual (used, BAD_CAST ("in-strict")))) {
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 */
375 id = xmlTextReaderGetAttribute (reader, BAD_CAST ("id"));
376 restr_type = xmlTextReaderGetAttribute (reader, BAD_CAST ("type"));
378 ret = xmlTextReaderRead (reader);
379 while (ret == 1 && !done) {
382 tag = xmlTextReaderName (reader);
384 switch (xmlTextReaderNodeType (reader)) {
389 if (xmlStrEqual (tag, BAD_CAST ("field"))) {
393 field = xmlTextReaderGetAttribute
394 (reader, BAD_CAST ("name"));
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);
401 process_field (reader,
404 data->extended_mode);
407 } else if (xmlStrEqual (tag, BAD_CAST ("parent"))) {
409 GUPnPDLNARestrictions *restr =
410 process_parent (reader, data);
412 if (restr && restr->caps)
413 /* Collect parents in a list - we'll
414 * coalesce them later */
415 parents = g_list_append (parents,
423 if (xmlStrEqual (tag, BAD_CAST ("restriction")))
434 ret = xmlTextReaderRead (reader);
440 /* If the restriction doesn't have a name, we make it up */
442 name = BAD_CAST (g_strdup (GST_CAPS_NULL_NAME));
443 g_string_prepend (caps_str, (gchar *) name);
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;
455 g_warning ("Support for '%s' restrictions not yet implemented",
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);
466 /* Merge all the parent caps. The child overrides parent
468 GstCaps *tmp_caps = (GstCaps *)tmp->data;
469 caps = merge_caps (caps, tmp_caps);
470 gst_caps_unref (tmp_caps);
474 restr = g_new0 (GUPnPDLNARestrictions, 1);
476 restr->caps = gst_caps_copy (caps);
480 g_hash_table_insert (data->restrictions, id, restr);
483 xmlFree (restr_type);
487 gst_caps_unref (caps);
489 g_list_free (parents);
495 process_restrictions (xmlTextReaderPtr reader, GUPnPDLNALoadState *data)
497 int ret = xmlTextReaderRead (reader);
502 tag = xmlTextReaderName (reader);
504 switch (xmlTextReaderNodeType (reader)) {
506 if (xmlStrEqual (tag, BAD_CAST ("restriction"))) {
508 process_restriction (reader, data);
514 if (xmlStrEqual (tag, BAD_CAST ("restrictions"))) {
515 /* </restrictions> */
525 ret = xmlTextReaderRead (reader);
530 process_dlna_profile (xmlTextReaderPtr reader,
532 GUPnPDLNALoadState *data)
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;
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"));
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 ();
555 g_assert (mime == NULL);
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 (""));
564 if (extended && xmlStrEqual (extended, BAD_CAST ("true"))) {
565 /* If we're not in extended mode, skip this profile */
566 if (!data->extended_mode)
572 ret = xmlTextReaderRead (reader);
573 while (ret == 1 && !done) {
576 tag = xmlTextReaderName (reader);
578 switch (xmlTextReaderNodeType (reader)) {
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);
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));
598 g_assert_not_reached ();
603 if (xmlStrEqual (tag, BAD_CAST ("dlna-profile")))
611 ret = xmlTextReaderRead (reader);
615 base = g_hash_table_lookup (data->profile_ids, base_profile);
617 g_warning ("Invalid base-profile reference");
621 /* create a new GUPnPDLNAProfile */
622 profile = gupnp_dlna_profile_new ((gchar *)name,
629 /* Inherit from base profile, if it exists*/
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);
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));
651 /* The merged caps will be our new GUPnPDLNAProfile */
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);
660 *profiles = g_list_append (*profiles, profile);
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);
671 gst_caps_unref (temp_container);
673 gst_caps_unref (temp_audio);
675 gst_caps_unref (temp_video);
682 xmlFree (base_profile);
688 process_include (xmlTextReaderPtr reader, GUPnPDLNALoadState *data)
693 path = xmlTextReaderGetAttribute (reader, BAD_CAST ("ref"));
695 if (!g_path_is_absolute ((gchar *) path)) {
696 gchar *tmp = g_strconcat (DLNA_DATA_DIR,
701 path = BAD_CAST (tmp);
704 ret = gupnp_dlna_load_profiles_from_file ((gchar *) path,
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
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:
717 * 1. cd dirname(path)
720 * 4. abspath = absdir + basename(path)
723 canonicalize_path_name (const char *path)
725 gchar *dir_name = NULL, *file_name = NULL, *abs_dir = NULL,
726 *old_dir = NULL, *ret = NULL;
728 if (g_path_is_absolute (path))
729 return g_strdup (path);
731 old_dir = g_get_current_dir ();
732 dir_name = g_path_get_dirname (path);
734 if (g_chdir (dir_name) < 0) {
735 ret = g_strdup (path);
739 abs_dir = g_get_current_dir ();
742 file_name = g_path_get_basename (path);
743 ret = g_build_filename (abs_dir, file_name, NULL);
755 gupnp_dlna_load_profiles_from_file (const char *file_name,
756 GUPnPDLNALoadState *data)
758 GList *profiles = NULL;
760 xmlTextReaderPtr reader;
761 xmlRelaxNGParserCtxtPtr rngp;
765 path = canonicalize_path_name (file_name);
766 if (g_hash_table_lookup_extended (data->files_hash, path, NULL, NULL))
769 g_hash_table_insert (data->files_hash, g_strdup (path), NULL);
771 reader = xmlNewTextReaderFilename (path);
775 /* Load the schema for validation */
776 rngp = xmlRelaxNGNewParserCtxt (DLNA_DATA_DIR "dlna-profiles.rng");
777 rngs = xmlRelaxNGParse (rngp);
778 xmlTextReaderRelaxNGSetSchema (reader, rngs);
780 ret = xmlTextReaderRead (reader);
784 tag = xmlTextReaderName (reader);
786 switch (xmlTextReaderNodeType (reader)) {
789 if (xmlStrEqual (tag, BAD_CAST ("include"))) {
792 process_include (reader,
794 profiles = g_list_concat (profiles,
796 } else if (xmlStrEqual (tag,
797 BAD_CAST ("restrictions"))) {
799 process_restrictions (reader,
801 } else if (xmlStrEqual (tag,
802 BAD_CAST ("dlna-profile"))) {
804 process_dlna_profile (reader,
817 ret = xmlTextReaderRead (reader);
820 xmlFreeTextReader (reader);
821 xmlRelaxNGFree (rngs);
822 xmlRelaxNGFreeParserCtxt (rngp);
831 gupnp_dlna_load_profiles_from_dir (gchar *profile_dir, GUPnPDLNALoadState *data)
836 g_hash_table_new_full (g_str_hash,
838 (GDestroyNotify) xmlFree,
840 free_restrictions_struct);
842 g_hash_table_new_full (g_str_hash,
844 (GDestroyNotify) xmlFree,
848 GList *profiles = NULL;
850 if ((dir = g_dir_open (profile_dir, 0, NULL))) {
853 while ((entry = g_dir_read_name (dir))) {
854 gchar *path = g_strconcat (profile_dir,
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 (
873 g_hash_table_unref (data->restrictions);
874 g_hash_table_unref (data->profile_ids);
880 gupnp_dlna_load_profiles_from_disk (gboolean relaxed_mode,
881 gboolean extended_mode)
883 GUPnPDLNALoadState *load_data;
886 load_data = g_new0 (GUPnPDLNALoadState, 1);
889 load_data->files_hash = g_hash_table_new_full (g_str_hash,
893 load_data->relaxed_mode = relaxed_mode;
894 load_data->extended_mode = extended_mode;
897 ret = gupnp_dlna_load_profiles_from_dir (DLNA_DATA_DIR,
900 /* Now that we're done loading profiles, remove all profiles with no
901 * name which are only used for inheritance and not matching. */
905 GUPnPDLNAProfile *profile = i->data;
906 GstEncodingProfile *enc_profile =
907 gupnp_dlna_profile_get_encoding_profile
909 GList *tmp = g_list_next (i);
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);
920 g_hash_table_unref (load_data->files_hash);