From 71f4d0cd88f4e229a2a28ed87b97b3e0695d9a4c Mon Sep 17 00:00:00 2001 From: Parthasarathi Susarla Date: Wed, 23 Feb 2011 15:21:32 +1100 Subject: [PATCH] Rename gupnp-dlna-load.[hc] to profile-loading.[hc] --- doc/Makefile.am | 2 +- libgupnp-dlna/Makefile.am | 4 +- libgupnp-dlna/gupnp-dlna-discoverer.c | 2 +- libgupnp-dlna/gupnp-dlna-load.c | 925 ---------------------------------- libgupnp-dlna/gupnp-dlna-load.h | 56 -- libgupnp-dlna/profile-loading.c | 925 ++++++++++++++++++++++++++++++++++ libgupnp-dlna/profile-loading.h | 56 ++ tests/dlna-profile-parser.c | 2 +- tools/gupnp-dlna-info.c | 2 +- 9 files changed, 987 insertions(+), 987 deletions(-) delete mode 100644 libgupnp-dlna/gupnp-dlna-load.c delete mode 100644 libgupnp-dlna/gupnp-dlna-load.h create mode 100644 libgupnp-dlna/profile-loading.c create mode 100644 libgupnp-dlna/profile-loading.h diff --git a/doc/Makefile.am b/doc/Makefile.am index 254b4e3..5601294 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -49,7 +49,7 @@ CFILE_GLOB=$(top_srcdir)/libgupnp-dlna/*.c # e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h IGNORE_HFILES= xml-util.h \ gvalue-util.h \ - gupnp-dlna-load.h \ + profile-loading.h \ gupnp-dlna-marshal.h # Images to copy into HTML directory. diff --git a/libgupnp-dlna/Makefile.am b/libgupnp-dlna/Makefile.am index 47d114c..eee6170 100644 --- a/libgupnp-dlna/Makefile.am +++ b/libgupnp-dlna/Makefile.am @@ -38,14 +38,14 @@ libgupnp_dlna_inc_HEADERS = gupnp-dlna-profile.h \ gupnp-dlna-information.h \ gupnp-dlna-discoverer.h -noinst_HEADERS = gupnp-dlna-load.h \ +noinst_HEADERS = profile-loading.h \ gupnp-dlna-profile-private.h libgupnp_dlna_1_0_la_SOURCES = gupnp-dlna-information.c \ gupnp-dlna-discoverer.c \ gupnp-dlna-profile.c \ gupnp-dlna-profiles.c \ - gupnp-dlna-load.c \ + profile-loading.c \ $(BUILT_SOURCES) libgupnp_dlna_1_0_la_LIBADD = $(LIBXML_LIBS) \ diff --git a/libgupnp-dlna/gupnp-dlna-discoverer.c b/libgupnp-dlna/gupnp-dlna-discoverer.c index d111aaf..a6283eb 100644 --- a/libgupnp-dlna/gupnp-dlna-discoverer.c +++ b/libgupnp-dlna/gupnp-dlna-discoverer.c @@ -21,7 +21,7 @@ #include "gupnp-dlna-discoverer.h" #include "gupnp-dlna-marshal.h" -#include "gupnp-dlna-load.h" +#include "profile-loading.h" /** * SECTION:gupnp-dlna-discoverer diff --git a/libgupnp-dlna/gupnp-dlna-load.c b/libgupnp-dlna/gupnp-dlna-load.c deleted file mode 100644 index 527bf57..0000000 --- a/libgupnp-dlna/gupnp-dlna-load.c +++ /dev/null @@ -1,925 +0,0 @@ -/* - * Copyright (C) 2010 Nokia Corporation. - * - * Authors: Arun Raghavan - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#include -#include -#include -#include -#include -#include -#include "gupnp-dlna-load.h" -#include "gupnp-dlna-profile.h" -#include "gupnp-dlna-profile-private.h" - -#define GST_CAPS_NULL_NAME "NULL" -#define DLNA_DATA_DIR DATA_DIR \ - G_DIR_SEPARATOR_S "dlna-profiles" G_DIR_SEPARATOR_S - -static gboolean -copy_func (GQuark field_id, const GValue *value, gpointer data) -{ - GstStructure *st2 = (GstStructure *)data; - - if (!gst_structure_has_field (st2, g_quark_to_string (field_id))) - gst_structure_id_set_value (st2, field_id, value); - - return TRUE; -} - -/* Note: It is assumed that caps1 and caps2 have only 1 structure each */ -static GstCaps * -merge_caps (GstCaps *caps1, GstCaps *caps2) -{ - GstStructure *st1, *st2; - GstCaps *ret; - gboolean any = FALSE; - - /* If one of the caps GST_CAPS_ANY, gst_caps_merge will result in a - * GST_CAPS_ANY, which might not be correct for us */ - if (!gst_caps_is_any (caps1) && !gst_caps_is_any (caps2)) { - any = TRUE; - gst_caps_merge (caps1, gst_caps_copy (caps2)); - gst_caps_do_simplify (caps1); - } - - ret = gst_caps_make_writable (caps1); - st1 = gst_caps_get_structure (ret, 0); - if (gst_caps_get_size (caps1) == 2) - /* Non-merged fields were copied to a second structure in caps - * at gst_merge_caps() time */ - st2 = gst_caps_get_structure (ret, 1); - else - /* Either one of the caps was GST_CAPS_ANY, or there were no - * unmerged fields */ - st2 = gst_caps_get_structure (caps2, 0); - - /* If caps1 has a name, we retain it. If not, and caps2 does, caps1 - * gets caps2's name. */ - if ((g_strcmp0 (GST_CAPS_NULL_NAME, - gst_structure_get_name (st1)) == 0) && - (g_strcmp0 (GST_CAPS_NULL_NAME, - gst_structure_get_name (st2)) != 0)) { - gst_structure_set_name (st1, gst_structure_get_name (st2)); - } - - /* We now walk over the structures and append any fields that are in - * caps2 but not in caps1. */ - if (any || gst_caps_get_size (caps1) == 2) - gst_structure_foreach (st2, copy_func, st1); - - if (gst_caps_get_size (caps1) == 2) - gst_caps_remove_structure (ret, 1); - - return ret; -} - -static xmlChar * -get_value (xmlTextReaderPtr reader) -{ - xmlChar *value = NULL, *curr; - int ret = 1; - - curr = xmlTextReaderName (reader); - - /* This function may be called with reader pointing to a or - * the element just below a . In the former case, we move the - * cursor forward and then continue processing. */ - if (xmlStrEqual (curr, BAD_CAST ("field"))) - ret = xmlTextReaderRead (reader); - xmlFree (curr); - - while (ret == 1) { - xmlChar *tag; - - tag = xmlTextReaderName (reader); - - if (xmlTextReaderNodeType (reader) == 1 && - xmlStrEqual (tag, BAD_CAST ("value"))) { - /* */ - - /* Note: This assumes you won't have a comment in the - * middle of your text */ - do { - ret = xmlTextReaderRead (reader); - } while (ret == 1 && - xmlTextReaderNodeType (reader) != 3 && - xmlTextReaderNodeType (reader) != 15); - - /* We're now at the real text between a and a - * */ - - if (xmlTextReaderNodeType (reader) == 3) - value = xmlTextReaderValue (reader); - } - - if (xmlTextReaderNodeType (reader) == 15 && - xmlStrEqual (tag, BAD_CAST ("value"))) { - /* */ - xmlFree (tag); - - break; - } - - xmlFree (tag); - ret = xmlTextReaderRead (reader); - } - - if (!value) - g_warning ("Empty s are illegal"); - - return value; -} - -static void -xml_str_free (xmlChar *str, gpointer unused) -{ - xmlFree (str); -} - -static void -free_restrictions_struct (gpointer data, gpointer user_data) -{ - GUPnPDLNARestrictions *restr = (GUPnPDLNARestrictions *)data; - if (restr) { - if (restr->caps) - gst_caps_unref (restr->caps); - - g_free (restr); - } -} - -static void -process_range (xmlTextReaderPtr reader, GString *caps_str) -{ - xmlChar *min, *max; - - min = xmlTextReaderGetAttribute (reader, BAD_CAST ("min")); - max = xmlTextReaderGetAttribute (reader, BAD_CAST ("max")); - - g_string_append_printf (caps_str, "[ %s, %s ]", min, max); - - xmlFree (min); - xmlFree (max); -} - -static int -process_field (xmlTextReaderPtr reader, - GString *caps_str, - gboolean relaxed_mode, - gboolean extended_mode) -{ - int ret; - xmlChar *name; - xmlChar *type; - xmlChar *used; - GList *values = NULL; - gboolean done = FALSE, skip = FALSE; - - /* - * Parse the 'used' attribute and figure out the mode we - * need to follow. - */ - used = xmlTextReaderGetAttribute (reader, BAD_CAST ("used")); - if (used) { - if ((relaxed_mode == FALSE) && - xmlStrEqual (used, BAD_CAST ("in-relaxed"))) { - skip = TRUE; - } else if ((relaxed_mode == TRUE) && - (xmlStrEqual (used, BAD_CAST ("in-strict")))) { - skip = TRUE; - } - - xmlFree (used); - } - - name = xmlTextReaderGetAttribute (reader, BAD_CAST ("name")); - type = xmlTextReaderGetAttribute (reader, BAD_CAST ("type")); - - /* - * This function reads a and appends it to caps_str in the - * GstCaps-as-a-string format: - * - * Single value: field = (type) value - * Multiple values: field = (type) { value1, value2, value3 } - * Range: field = (type) [ min, max ] - */ - - /* Fields are comma-separeted. The leading comma is okay for the first - * field - we will be prepending the restriction name to this string */ - if (!skip) - g_string_append_printf (caps_str, ", %s = (%s) ", name, type); - - xmlFree (name); - xmlFree (type); - - ret = xmlTextReaderRead (reader); - while (ret == 1 && !done) { - xmlChar *tag; - - tag = xmlTextReaderName (reader); - - switch (xmlTextReaderNodeType (reader)) { - case 1: - if (skip) - break; - - if (xmlStrEqual (tag, BAD_CAST ("range"))) { - /* */ - process_range (reader, caps_str); - } else if (xmlStrEqual (tag, BAD_CAST ("value"))) { - /* */ - xmlChar *value; - - value = get_value (reader); - - if (value) - values = g_list_append (values, value); - } - - break; - - case 15: - if (xmlStrEqual (tag, BAD_CAST ("field"))) - /* */ - done = TRUE; - - break; - - default: - break; - } - - xmlFree (tag); - ret = xmlTextReaderRead (reader); - } - - if (skip) - return ret; - - if (g_list_length (values) == 1) - /* Single value */ - g_string_append_printf (caps_str, - "%s", - (xmlChar *) values->data); - else if (g_list_length (values) > 1) { - /* Multiple values */ - GList *tmp = values->next; - g_string_append_printf (caps_str, - "{ %s", - (xmlChar *) values->data); - - do { - g_string_append_printf (caps_str, - ", %s", - (xmlChar *) tmp->data); - } while ((tmp = tmp->next) != NULL); - - g_string_append_printf (caps_str, " }"); - } - - if (values) { - g_list_foreach (values, (GFunc) xml_str_free, NULL); - g_list_free (values); - } - - return ret; -} - -static GUPnPDLNARestrictions * -process_parent (xmlTextReaderPtr reader, GUPnPDLNALoadState *data) -{ - xmlChar *parent; - xmlChar *used; - GUPnPDLNARestrictions *restr = NULL; - - /* - * Check to see if we need to follow any relaxed/strict mode - * restrictions. - */ - used = xmlTextReaderGetAttribute (reader, BAD_CAST ("used")); - if (used) { - if ((data->relaxed_mode == FALSE) && - xmlStrEqual (used, BAD_CAST ("in-relaxed"))) { - xmlFree (used); - return NULL; - } else if ((data->relaxed_mode == TRUE) && - (xmlStrEqual (used, BAD_CAST ("in-strict")))) { - xmlFree (used); - return NULL; - } - } - - parent = xmlTextReaderGetAttribute (reader, BAD_CAST ("name")); - restr = g_hash_table_lookup (data->restrictions, parent); - - if (!restr) { - g_warning ("Could not find parent restriction: %s", parent); - return NULL; - } - - xmlFree (parent); - xmlFree (used); - - return restr; -} - -static GUPnPDLNARestrictions * -process_restriction (xmlTextReaderPtr reader, GUPnPDLNALoadState *data) -{ - GUPnPDLNARestrictions *restr = NULL; - GType type; - GstCaps *caps = NULL; - GString *caps_str = g_string_sized_new (100); - GList *parents = NULL, *tmp; - xmlChar *id, *name = NULL, *restr_type, *used; - int ret; - gboolean done = FALSE, skip = FALSE; - - /* - * First we parse the 'used' attribute and figure out - * the mode we need to comply to. - */ - used = xmlTextReaderGetAttribute (reader, BAD_CAST ("used")); - if (used) { - if ((data->relaxed_mode == FALSE) && - xmlStrEqual (used, BAD_CAST ("in-relaxed"))) { - skip = TRUE; - } else if ((data->relaxed_mode == TRUE) && - (xmlStrEqual (used, BAD_CAST ("in-strict")))) { - skip = TRUE; - } - } - - /* We then walk through the fields in this restriction, and make a - * string that can be parsed by gst_caps_from_string (). We then make - * a GstCaps from this string */ - - id = xmlTextReaderGetAttribute (reader, BAD_CAST ("id")); - restr_type = xmlTextReaderGetAttribute (reader, BAD_CAST ("type")); - - ret = xmlTextReaderRead (reader); - while (ret == 1 && !done) { - xmlChar *tag; - - tag = xmlTextReaderName (reader); - - switch (xmlTextReaderNodeType (reader)) { - case 1: - if (skip) - break; - - if (xmlStrEqual (tag, BAD_CAST ("field"))) { - /* */ - xmlChar *field; - - field = xmlTextReaderGetAttribute - (reader, BAD_CAST ("name")); - - /* We handle the "name" field specially - if - * present, it is the caps name */ - if (xmlStrEqual (field, BAD_CAST ("name"))) - name = get_value (reader); - else - process_field (reader, - caps_str, - data->relaxed_mode, - data->extended_mode); - - xmlFree (field); - } else if (xmlStrEqual (tag, BAD_CAST ("parent"))) { - /* */ - GUPnPDLNARestrictions *restr = - process_parent (reader, data); - - if (restr && restr->caps) - /* Collect parents in a list - we'll - * coalesce them later */ - parents = g_list_append (parents, - gst_caps_copy - (restr->caps)); - } - - break; - - case 15: - if (xmlStrEqual (tag, BAD_CAST ("restriction"))) - /* */ - done = TRUE; - - break; - - default: - break; - } - - xmlFree (tag); - ret = xmlTextReaderRead (reader); - } - - if (skip) - goto out; - - /* If the restriction doesn't have a name, we make it up */ - if (!name) - name = BAD_CAST (g_strdup (GST_CAPS_NULL_NAME)); - g_string_prepend (caps_str, (gchar *) name); - xmlFree (name); - - if (xmlStrEqual (restr_type, BAD_CAST ("container"))) - type = GST_TYPE_ENCODING_CONTAINER_PROFILE; - else if (xmlStrEqual (restr_type, BAD_CAST ("audio"))) - type = GST_TYPE_ENCODING_AUDIO_PROFILE; - else if (xmlStrEqual (restr_type, BAD_CAST ("video"))) - type = GST_TYPE_ENCODING_VIDEO_PROFILE; - else if (xmlStrEqual (restr_type, BAD_CAST ("image"))) - type = GST_TYPE_ENCODING_VIDEO_PROFILE; - else { - g_warning ("Support for '%s' restrictions not yet implemented", - restr_type); - goto out; - } - - caps = gst_caps_from_string (caps_str->str); - g_string_free (caps_str, TRUE); - g_return_val_if_fail (caps != NULL, NULL); - - tmp = parents; - while (tmp) { - /* Merge all the parent caps. The child overrides parent - * attributes */ - GstCaps *tmp_caps = (GstCaps *)tmp->data; - caps = merge_caps (caps, tmp_caps); - gst_caps_unref (tmp_caps); - tmp = tmp->next; - } - - restr = g_new0 (GUPnPDLNARestrictions, 1); - - restr->caps = gst_caps_copy (caps); - restr->type = type; - - if (id) - g_hash_table_insert (data->restrictions, id, restr); - -out: - xmlFree (restr_type); - if (used) - xmlFree (used); - if (caps) - gst_caps_unref (caps); - if (parents) - g_list_free (parents); - - return restr; -} - -static void -process_restrictions (xmlTextReaderPtr reader, GUPnPDLNALoadState *data) -{ - int ret = xmlTextReaderRead (reader); - - while (ret == 1) { - xmlChar *tag; - - tag = xmlTextReaderName (reader); - - switch (xmlTextReaderNodeType (reader)) { - case 1: - if (xmlStrEqual (tag, BAD_CAST ("restriction"))) { - /* */ - process_restriction (reader, data); - } - - break; - - case 15: - if (xmlStrEqual (tag, BAD_CAST ("restrictions"))) { - /* */ - xmlFree (tag); - return; - } - - default: - break; - } - - xmlFree (tag); - ret = xmlTextReaderRead (reader); - } -} - -static int -process_dlna_profile (xmlTextReaderPtr reader, - GList **profiles, - GUPnPDLNALoadState *data) -{ - guint ret; - GUPnPDLNAProfile *profile = NULL; - GUPnPDLNAProfile *base = NULL; - GUPnPDLNARestrictions *restr = NULL; - GstCaps *temp_audio = NULL, *temp_video = NULL, *temp_container = NULL; - xmlChar *name, *mime, *id, *base_profile, *extended; - gboolean done = FALSE, is_extended = FALSE; - - name = xmlTextReaderGetAttribute (reader, BAD_CAST ("name")); - mime = xmlTextReaderGetAttribute (reader, BAD_CAST ("mime")); - extended = xmlTextReaderGetAttribute (reader, BAD_CAST ("extended")); - id = xmlTextReaderGetAttribute (reader, BAD_CAST ("id")); - base_profile = xmlTextReaderGetAttribute (reader, - BAD_CAST ("base-profile")); - - /* Create temporary place-holders for caps */ - temp_container = gst_caps_new_empty (); - temp_video = gst_caps_new_empty (); - temp_audio = gst_caps_new_empty (); - - if (!name) { - g_assert (mime == NULL); - - /* We need a non-NULL string to not trigger asserts in the - * places these are used. Profiles without names are used - * only for inheritance, not for actual matching. */ - name = xmlStrdup (BAD_CAST ("")); - mime = xmlStrdup (BAD_CAST ("")); - } - - if (extended && xmlStrEqual (extended, BAD_CAST ("true"))) { - /* If we're not in extended mode, skip this profile */ - if (!data->extended_mode) - goto out; - - is_extended = TRUE; - } - - ret = xmlTextReaderRead (reader); - while (ret == 1 && !done) { - xmlChar *tag; - - tag = xmlTextReaderName (reader); - - switch (xmlTextReaderNodeType (reader)) { - case 1: - if (xmlStrEqual (tag, BAD_CAST ("restriction"))) - restr = process_restriction (reader, data); - else if (xmlStrEqual (tag, BAD_CAST ("parent"))) - restr = process_parent (reader, data); - - if (!restr) - break; - - if (restr->type == GST_TYPE_ENCODING_CONTAINER_PROFILE) - gst_caps_merge (temp_container, - gst_caps_copy (restr->caps)); - else if (restr->type == GST_TYPE_ENCODING_VIDEO_PROFILE) - gst_caps_merge (temp_video, - gst_caps_copy (restr->caps)); - else if (restr->type == GST_TYPE_ENCODING_AUDIO_PROFILE) - gst_caps_merge (temp_audio, - gst_caps_copy (restr->caps)); - else - g_assert_not_reached (); - - break; - - case 15: - if (xmlStrEqual (tag, BAD_CAST ("dlna-profile"))) - done = TRUE; - - default: - break; - } - - xmlFree (tag); - ret = xmlTextReaderRead (reader); - } - - if (base_profile) { - base = g_hash_table_lookup (data->profile_ids, base_profile); - if (!base) - g_warning ("Invalid base-profile reference"); - } - - - /* create a new GUPnPDLNAProfile */ - profile = gupnp_dlna_profile_new ((gchar *)name, - (gchar *)mime, - GST_CAPS_NONE, - GST_CAPS_NONE, - GST_CAPS_NONE, - is_extended); - - /* Inherit from base profile, if it exists*/ - if (base) { - const GstCaps *video_caps = - gupnp_dlna_profile_get_video_caps (base); - const GstCaps *audio_caps = - gupnp_dlna_profile_get_audio_caps (base); - const GstCaps *container_caps = - gupnp_dlna_profile_get_container_caps (base); - - if (GST_IS_CAPS (video_caps)) - gst_caps_merge (temp_video, - gst_caps_copy (video_caps)); - if (GST_IS_CAPS (audio_caps)) - gst_caps_merge (temp_audio, - gst_caps_copy (audio_caps)); - if (GST_IS_CAPS (container_caps)) - gst_caps_merge (temp_container, - gst_caps_copy (container_caps)); - - } - - - /* The merged caps will be our new GUPnPDLNAProfile */ - - if (GST_IS_CAPS (temp_container) && !gst_caps_is_empty (temp_container)) - gupnp_dlna_profile_set_container_caps (profile, temp_container); - if (GST_IS_CAPS (temp_video) && !gst_caps_is_empty (temp_video)) - gupnp_dlna_profile_set_video_caps (profile, temp_video); - if (GST_IS_CAPS (temp_audio) && !gst_caps_is_empty (temp_audio)) - gupnp_dlna_profile_set_audio_caps (profile, temp_audio); - - *profiles = g_list_append (*profiles, profile); - - if (id) { - /* id is freed when the hash table is destroyed */ - g_object_ref (profile); - g_hash_table_insert (data->profile_ids, id, profile); - } - -out: - - if (temp_container) - gst_caps_unref (temp_container); - if (temp_audio) - gst_caps_unref (temp_audio); - if (temp_video) - gst_caps_unref (temp_video); - - xmlFree (mime); - xmlFree (name); - if (extended) - xmlFree (extended); - if (base_profile) - xmlFree (base_profile); - - return ret; -} - -static GList * -process_include (xmlTextReaderPtr reader, GUPnPDLNALoadState *data) -{ - xmlChar *path; - GList *ret; - - path = xmlTextReaderGetAttribute (reader, BAD_CAST ("ref")); - - if (!g_path_is_absolute ((gchar *) path)) { - gchar *tmp = g_strconcat (DLNA_DATA_DIR, - G_DIR_SEPARATOR_S, - path, - NULL); - xmlFree (path); - path = BAD_CAST (tmp); - } - - ret = gupnp_dlna_load_profiles_from_file ((gchar *) path, - data); - xmlFree (path); - - return ret; -} - -/* This can go away once we have a glib function to canonicalize paths (see - * https://bugzilla.gnome.org/show_bug.cgi?id=111848 - * - * The implementationis not generic enough, but sufficient for our use. The - * idea is taken from Tristan Van Berkom's comment in the bug mentioned above: - * - * 1. cd dirname(path) - * 2. absdir = $CWD - * 3. cd $OLDPWD - * 4. abspath = absdir + basename(path) - */ -static gchar * -canonicalize_path_name (const char *path) -{ - gchar *dir_name = NULL, *file_name = NULL, *abs_dir = NULL, - *old_dir = NULL, *ret = NULL; - - if (g_path_is_absolute (path)) - return g_strdup (path); - - old_dir = g_get_current_dir (); - dir_name = g_path_get_dirname (path); - - if (g_chdir (dir_name) < 0) { - ret = g_strdup (path); - goto out; - } - - abs_dir = g_get_current_dir (); - g_chdir (old_dir); - - file_name = g_path_get_basename (path); - ret = g_build_filename (abs_dir, file_name, NULL); - -out: - g_free (dir_name); - g_free (file_name); - g_free (abs_dir); - g_free (old_dir); - - return ret; -} - -GList * -gupnp_dlna_load_profiles_from_file (const char *file_name, - GUPnPDLNALoadState *data) -{ - GList *profiles = NULL; - gchar *path = NULL; - xmlTextReaderPtr reader; - xmlRelaxNGParserCtxtPtr rngp; - xmlRelaxNGPtr rngs; - int ret; - - path = canonicalize_path_name (file_name); - if (g_hash_table_lookup_extended (data->files_hash, path, NULL, NULL)) - goto out; - else - g_hash_table_insert (data->files_hash, g_strdup (path), NULL); - - reader = xmlNewTextReaderFilename (path); - if (!reader) - goto out; - - /* Load the schema for validation */ - rngp = xmlRelaxNGNewParserCtxt (DLNA_DATA_DIR "dlna-profiles.rng"); - rngs = xmlRelaxNGParse (rngp); - xmlTextReaderRelaxNGSetSchema (reader, rngs); - - ret = xmlTextReaderRead (reader); - while (ret == 1) { - xmlChar *tag; - - tag = xmlTextReaderName (reader); - - switch (xmlTextReaderNodeType (reader)) { - /* Start tag */ - case 1: - if (xmlStrEqual (tag, BAD_CAST ("include"))) { - /* */ - GList *include = - process_include (reader, - data); - profiles = g_list_concat (profiles, - include); - } else if (xmlStrEqual (tag, - BAD_CAST ("restrictions"))) { - /* */ - process_restrictions (reader, - data); - } else if (xmlStrEqual (tag, - BAD_CAST ("dlna-profile"))) { - /* */ - process_dlna_profile (reader, - &profiles, - data); - - } - - break; - - default: - break; - } - - xmlFree (tag); - ret = xmlTextReaderRead (reader); - } - - xmlFreeTextReader (reader); - xmlRelaxNGFree (rngs); - xmlRelaxNGFreeParserCtxt (rngp); - -out: - g_free (path); - - return profiles; -} - -GList * -gupnp_dlna_load_profiles_from_dir (gchar *profile_dir, GUPnPDLNALoadState *data) -{ - GDir *dir; - - data->restrictions = - g_hash_table_new_full (g_str_hash, - g_str_equal, - (GDestroyNotify) xmlFree, - (GDestroyNotify) - free_restrictions_struct); - data->profile_ids = - g_hash_table_new_full (g_str_hash, - g_str_equal, - (GDestroyNotify) xmlFree, - (GDestroyNotify) - g_object_unref); - - GList *profiles = NULL; - - if ((dir = g_dir_open (profile_dir, 0, NULL))) { - const gchar *entry; - - while ((entry = g_dir_read_name (dir))) { - gchar *path = g_strconcat (profile_dir, - G_DIR_SEPARATOR_S, - entry, - NULL); - - if (g_str_has_suffix (entry, ".xml") && - g_file_test (path, G_FILE_TEST_IS_REGULAR)) { - profiles = g_list_concat (profiles, - gupnp_dlna_load_profiles_from_file ( - path, - data)); - } - - g_free (path); - } - - g_dir_close (dir); - } - - g_hash_table_unref (data->restrictions); - g_hash_table_unref (data->profile_ids); - - return profiles; -} - -GList * -gupnp_dlna_load_profiles_from_disk (gboolean relaxed_mode, - gboolean extended_mode) -{ - GUPnPDLNALoadState *load_data; - GList *ret, *i; - - load_data = g_new0 (GUPnPDLNALoadState, 1); - - if (load_data) { - load_data->files_hash = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - NULL); - load_data->relaxed_mode = relaxed_mode; - load_data->extended_mode = extended_mode; - } - - ret = gupnp_dlna_load_profiles_from_dir (DLNA_DATA_DIR, - load_data); - - /* Now that we're done loading profiles, remove all profiles with no - * name which are only used for inheritance and not matching. */ - i = ret; - while (i) { - const gchar *name; - GUPnPDLNAProfile *profile = i->data; - GstEncodingProfile *enc_profile = - gupnp_dlna_profile_get_encoding_profile - (profile); - GList *tmp = g_list_next (i); - - name = gst_encoding_profile_get_name (enc_profile); - if (name[0] == '\0') { - ret = g_list_delete_link (ret, i); - g_object_unref (profile); - } - - i = tmp; - } - - g_hash_table_unref (load_data->files_hash); - g_free (load_data); - load_data = NULL; - - return ret; -} diff --git a/libgupnp-dlna/gupnp-dlna-load.h b/libgupnp-dlna/gupnp-dlna-load.h deleted file mode 100644 index fa1f966..0000000 --- a/libgupnp-dlna/gupnp-dlna-load.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2009 Nokia Corporation. - * - * Authors: Zeeshan Ali - * - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifndef __GUPNP_DLNA_LOAD_H__ -#define __GUPNP_DLNA_LOAD_H__ - -#include - -G_BEGIN_DECLS - -typedef struct { - GHashTable *restrictions; - GHashTable *profile_ids; - GHashTable *files_hash; - gboolean relaxed_mode; - gboolean extended_mode; -} GUPnPDLNALoadState; - -typedef struct { - GstCaps *caps; - GType type; -} GUPnPDLNARestrictions; - -GList * -gupnp_dlna_load_profiles_from_file (const gchar *file_name, - GUPnPDLNALoadState *data); -GList * -gupnp_dlna_load_profiles_from_dir (gchar *profile_dir, - GUPnPDLNALoadState *data); - -GList * -gupnp_dlna_load_profiles_from_disk (gboolean relaxed_mode, - gboolean extended_mode); - -G_END_DECLS - -#endif /* __GUPNP_DLNA_LOAD_H__ */ diff --git a/libgupnp-dlna/profile-loading.c b/libgupnp-dlna/profile-loading.c new file mode 100644 index 0000000..e2bc93a --- /dev/null +++ b/libgupnp-dlna/profile-loading.c @@ -0,0 +1,925 @@ +/* + * Copyright (C) 2010 Nokia Corporation. + * + * Authors: Arun Raghavan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include "profile-loading.h" +#include "gupnp-dlna-profile.h" +#include "gupnp-dlna-profile-private.h" + +#define GST_CAPS_NULL_NAME "NULL" +#define DLNA_DATA_DIR DATA_DIR \ + G_DIR_SEPARATOR_S "dlna-profiles" G_DIR_SEPARATOR_S + +static gboolean +copy_func (GQuark field_id, const GValue *value, gpointer data) +{ + GstStructure *st2 = (GstStructure *)data; + + if (!gst_structure_has_field (st2, g_quark_to_string (field_id))) + gst_structure_id_set_value (st2, field_id, value); + + return TRUE; +} + +/* Note: It is assumed that caps1 and caps2 have only 1 structure each */ +static GstCaps * +merge_caps (GstCaps *caps1, GstCaps *caps2) +{ + GstStructure *st1, *st2; + GstCaps *ret; + gboolean any = FALSE; + + /* If one of the caps GST_CAPS_ANY, gst_caps_merge will result in a + * GST_CAPS_ANY, which might not be correct for us */ + if (!gst_caps_is_any (caps1) && !gst_caps_is_any (caps2)) { + any = TRUE; + gst_caps_merge (caps1, gst_caps_copy (caps2)); + gst_caps_do_simplify (caps1); + } + + ret = gst_caps_make_writable (caps1); + st1 = gst_caps_get_structure (ret, 0); + if (gst_caps_get_size (caps1) == 2) + /* Non-merged fields were copied to a second structure in caps + * at gst_merge_caps() time */ + st2 = gst_caps_get_structure (ret, 1); + else + /* Either one of the caps was GST_CAPS_ANY, or there were no + * unmerged fields */ + st2 = gst_caps_get_structure (caps2, 0); + + /* If caps1 has a name, we retain it. If not, and caps2 does, caps1 + * gets caps2's name. */ + if ((g_strcmp0 (GST_CAPS_NULL_NAME, + gst_structure_get_name (st1)) == 0) && + (g_strcmp0 (GST_CAPS_NULL_NAME, + gst_structure_get_name (st2)) != 0)) { + gst_structure_set_name (st1, gst_structure_get_name (st2)); + } + + /* We now walk over the structures and append any fields that are in + * caps2 but not in caps1. */ + if (any || gst_caps_get_size (caps1) == 2) + gst_structure_foreach (st2, copy_func, st1); + + if (gst_caps_get_size (caps1) == 2) + gst_caps_remove_structure (ret, 1); + + return ret; +} + +static xmlChar * +get_value (xmlTextReaderPtr reader) +{ + xmlChar *value = NULL, *curr; + int ret = 1; + + curr = xmlTextReaderName (reader); + + /* This function may be called with reader pointing to a or + * the element just below a . In the former case, we move the + * cursor forward and then continue processing. */ + if (xmlStrEqual (curr, BAD_CAST ("field"))) + ret = xmlTextReaderRead (reader); + xmlFree (curr); + + while (ret == 1) { + xmlChar *tag; + + tag = xmlTextReaderName (reader); + + if (xmlTextReaderNodeType (reader) == 1 && + xmlStrEqual (tag, BAD_CAST ("value"))) { + /* */ + + /* Note: This assumes you won't have a comment in the + * middle of your text */ + do { + ret = xmlTextReaderRead (reader); + } while (ret == 1 && + xmlTextReaderNodeType (reader) != 3 && + xmlTextReaderNodeType (reader) != 15); + + /* We're now at the real text between a and a + * */ + + if (xmlTextReaderNodeType (reader) == 3) + value = xmlTextReaderValue (reader); + } + + if (xmlTextReaderNodeType (reader) == 15 && + xmlStrEqual (tag, BAD_CAST ("value"))) { + /* */ + xmlFree (tag); + + break; + } + + xmlFree (tag); + ret = xmlTextReaderRead (reader); + } + + if (!value) + g_warning ("Empty s are illegal"); + + return value; +} + +static void +xml_str_free (xmlChar *str, gpointer unused) +{ + xmlFree (str); +} + +static void +free_restrictions_struct (gpointer data, gpointer user_data) +{ + GUPnPDLNARestrictions *restr = (GUPnPDLNARestrictions *)data; + if (restr) { + if (restr->caps) + gst_caps_unref (restr->caps); + + g_free (restr); + } +} + +static void +process_range (xmlTextReaderPtr reader, GString *caps_str) +{ + xmlChar *min, *max; + + min = xmlTextReaderGetAttribute (reader, BAD_CAST ("min")); + max = xmlTextReaderGetAttribute (reader, BAD_CAST ("max")); + + g_string_append_printf (caps_str, "[ %s, %s ]", min, max); + + xmlFree (min); + xmlFree (max); +} + +static int +process_field (xmlTextReaderPtr reader, + GString *caps_str, + gboolean relaxed_mode, + gboolean extended_mode) +{ + int ret; + xmlChar *name; + xmlChar *type; + xmlChar *used; + GList *values = NULL; + gboolean done = FALSE, skip = FALSE; + + /* + * Parse the 'used' attribute and figure out the mode we + * need to follow. + */ + used = xmlTextReaderGetAttribute (reader, BAD_CAST ("used")); + if (used) { + if ((relaxed_mode == FALSE) && + xmlStrEqual (used, BAD_CAST ("in-relaxed"))) { + skip = TRUE; + } else if ((relaxed_mode == TRUE) && + (xmlStrEqual (used, BAD_CAST ("in-strict")))) { + skip = TRUE; + } + + xmlFree (used); + } + + name = xmlTextReaderGetAttribute (reader, BAD_CAST ("name")); + type = xmlTextReaderGetAttribute (reader, BAD_CAST ("type")); + + /* + * This function reads a and appends it to caps_str in the + * GstCaps-as-a-string format: + * + * Single value: field = (type) value + * Multiple values: field = (type) { value1, value2, value3 } + * Range: field = (type) [ min, max ] + */ + + /* Fields are comma-separeted. The leading comma is okay for the first + * field - we will be prepending the restriction name to this string */ + if (!skip) + g_string_append_printf (caps_str, ", %s = (%s) ", name, type); + + xmlFree (name); + xmlFree (type); + + ret = xmlTextReaderRead (reader); + while (ret == 1 && !done) { + xmlChar *tag; + + tag = xmlTextReaderName (reader); + + switch (xmlTextReaderNodeType (reader)) { + case 1: + if (skip) + break; + + if (xmlStrEqual (tag, BAD_CAST ("range"))) { + /* */ + process_range (reader, caps_str); + } else if (xmlStrEqual (tag, BAD_CAST ("value"))) { + /* */ + xmlChar *value; + + value = get_value (reader); + + if (value) + values = g_list_append (values, value); + } + + break; + + case 15: + if (xmlStrEqual (tag, BAD_CAST ("field"))) + /* */ + done = TRUE; + + break; + + default: + break; + } + + xmlFree (tag); + ret = xmlTextReaderRead (reader); + } + + if (skip) + return ret; + + if (g_list_length (values) == 1) + /* Single value */ + g_string_append_printf (caps_str, + "%s", + (xmlChar *) values->data); + else if (g_list_length (values) > 1) { + /* Multiple values */ + GList *tmp = values->next; + g_string_append_printf (caps_str, + "{ %s", + (xmlChar *) values->data); + + do { + g_string_append_printf (caps_str, + ", %s", + (xmlChar *) tmp->data); + } while ((tmp = tmp->next) != NULL); + + g_string_append_printf (caps_str, " }"); + } + + if (values) { + g_list_foreach (values, (GFunc) xml_str_free, NULL); + g_list_free (values); + } + + return ret; +} + +static GUPnPDLNARestrictions * +process_parent (xmlTextReaderPtr reader, GUPnPDLNALoadState *data) +{ + xmlChar *parent; + xmlChar *used; + GUPnPDLNARestrictions *restr = NULL; + + /* + * Check to see if we need to follow any relaxed/strict mode + * restrictions. + */ + used = xmlTextReaderGetAttribute (reader, BAD_CAST ("used")); + if (used) { + if ((data->relaxed_mode == FALSE) && + xmlStrEqual (used, BAD_CAST ("in-relaxed"))) { + xmlFree (used); + return NULL; + } else if ((data->relaxed_mode == TRUE) && + (xmlStrEqual (used, BAD_CAST ("in-strict")))) { + xmlFree (used); + return NULL; + } + } + + parent = xmlTextReaderGetAttribute (reader, BAD_CAST ("name")); + restr = g_hash_table_lookup (data->restrictions, parent); + + if (!restr) { + g_warning ("Could not find parent restriction: %s", parent); + return NULL; + } + + xmlFree (parent); + xmlFree (used); + + return restr; +} + +static GUPnPDLNARestrictions * +process_restriction (xmlTextReaderPtr reader, GUPnPDLNALoadState *data) +{ + GUPnPDLNARestrictions *restr = NULL; + GType type; + GstCaps *caps = NULL; + GString *caps_str = g_string_sized_new (100); + GList *parents = NULL, *tmp; + xmlChar *id, *name = NULL, *restr_type, *used; + int ret; + gboolean done = FALSE, skip = FALSE; + + /* + * First we parse the 'used' attribute and figure out + * the mode we need to comply to. + */ + used = xmlTextReaderGetAttribute (reader, BAD_CAST ("used")); + if (used) { + if ((data->relaxed_mode == FALSE) && + xmlStrEqual (used, BAD_CAST ("in-relaxed"))) { + skip = TRUE; + } else if ((data->relaxed_mode == TRUE) && + (xmlStrEqual (used, BAD_CAST ("in-strict")))) { + skip = TRUE; + } + } + + /* We then walk through the fields in this restriction, and make a + * string that can be parsed by gst_caps_from_string (). We then make + * a GstCaps from this string */ + + id = xmlTextReaderGetAttribute (reader, BAD_CAST ("id")); + restr_type = xmlTextReaderGetAttribute (reader, BAD_CAST ("type")); + + ret = xmlTextReaderRead (reader); + while (ret == 1 && !done) { + xmlChar *tag; + + tag = xmlTextReaderName (reader); + + switch (xmlTextReaderNodeType (reader)) { + case 1: + if (skip) + break; + + if (xmlStrEqual (tag, BAD_CAST ("field"))) { + /* */ + xmlChar *field; + + field = xmlTextReaderGetAttribute + (reader, BAD_CAST ("name")); + + /* We handle the "name" field specially - if + * present, it is the caps name */ + if (xmlStrEqual (field, BAD_CAST ("name"))) + name = get_value (reader); + else + process_field (reader, + caps_str, + data->relaxed_mode, + data->extended_mode); + + xmlFree (field); + } else if (xmlStrEqual (tag, BAD_CAST ("parent"))) { + /* */ + GUPnPDLNARestrictions *restr = + process_parent (reader, data); + + if (restr && restr->caps) + /* Collect parents in a list - we'll + * coalesce them later */ + parents = g_list_append (parents, + gst_caps_copy + (restr->caps)); + } + + break; + + case 15: + if (xmlStrEqual (tag, BAD_CAST ("restriction"))) + /* */ + done = TRUE; + + break; + + default: + break; + } + + xmlFree (tag); + ret = xmlTextReaderRead (reader); + } + + if (skip) + goto out; + + /* If the restriction doesn't have a name, we make it up */ + if (!name) + name = BAD_CAST (g_strdup (GST_CAPS_NULL_NAME)); + g_string_prepend (caps_str, (gchar *) name); + xmlFree (name); + + if (xmlStrEqual (restr_type, BAD_CAST ("container"))) + type = GST_TYPE_ENCODING_CONTAINER_PROFILE; + else if (xmlStrEqual (restr_type, BAD_CAST ("audio"))) + type = GST_TYPE_ENCODING_AUDIO_PROFILE; + else if (xmlStrEqual (restr_type, BAD_CAST ("video"))) + type = GST_TYPE_ENCODING_VIDEO_PROFILE; + else if (xmlStrEqual (restr_type, BAD_CAST ("image"))) + type = GST_TYPE_ENCODING_VIDEO_PROFILE; + else { + g_warning ("Support for '%s' restrictions not yet implemented", + restr_type); + goto out; + } + + caps = gst_caps_from_string (caps_str->str); + g_string_free (caps_str, TRUE); + g_return_val_if_fail (caps != NULL, NULL); + + tmp = parents; + while (tmp) { + /* Merge all the parent caps. The child overrides parent + * attributes */ + GstCaps *tmp_caps = (GstCaps *)tmp->data; + caps = merge_caps (caps, tmp_caps); + gst_caps_unref (tmp_caps); + tmp = tmp->next; + } + + restr = g_new0 (GUPnPDLNARestrictions, 1); + + restr->caps = gst_caps_copy (caps); + restr->type = type; + + if (id) + g_hash_table_insert (data->restrictions, id, restr); + +out: + xmlFree (restr_type); + if (used) + xmlFree (used); + if (caps) + gst_caps_unref (caps); + if (parents) + g_list_free (parents); + + return restr; +} + +static void +process_restrictions (xmlTextReaderPtr reader, GUPnPDLNALoadState *data) +{ + int ret = xmlTextReaderRead (reader); + + while (ret == 1) { + xmlChar *tag; + + tag = xmlTextReaderName (reader); + + switch (xmlTextReaderNodeType (reader)) { + case 1: + if (xmlStrEqual (tag, BAD_CAST ("restriction"))) { + /* */ + process_restriction (reader, data); + } + + break; + + case 15: + if (xmlStrEqual (tag, BAD_CAST ("restrictions"))) { + /* */ + xmlFree (tag); + return; + } + + default: + break; + } + + xmlFree (tag); + ret = xmlTextReaderRead (reader); + } +} + +static int +process_dlna_profile (xmlTextReaderPtr reader, + GList **profiles, + GUPnPDLNALoadState *data) +{ + guint ret; + GUPnPDLNAProfile *profile = NULL; + GUPnPDLNAProfile *base = NULL; + GUPnPDLNARestrictions *restr = NULL; + GstCaps *temp_audio = NULL, *temp_video = NULL, *temp_container = NULL; + xmlChar *name, *mime, *id, *base_profile, *extended; + gboolean done = FALSE, is_extended = FALSE; + + name = xmlTextReaderGetAttribute (reader, BAD_CAST ("name")); + mime = xmlTextReaderGetAttribute (reader, BAD_CAST ("mime")); + extended = xmlTextReaderGetAttribute (reader, BAD_CAST ("extended")); + id = xmlTextReaderGetAttribute (reader, BAD_CAST ("id")); + base_profile = xmlTextReaderGetAttribute (reader, + BAD_CAST ("base-profile")); + + /* Create temporary place-holders for caps */ + temp_container = gst_caps_new_empty (); + temp_video = gst_caps_new_empty (); + temp_audio = gst_caps_new_empty (); + + if (!name) { + g_assert (mime == NULL); + + /* We need a non-NULL string to not trigger asserts in the + * places these are used. Profiles without names are used + * only for inheritance, not for actual matching. */ + name = xmlStrdup (BAD_CAST ("")); + mime = xmlStrdup (BAD_CAST ("")); + } + + if (extended && xmlStrEqual (extended, BAD_CAST ("true"))) { + /* If we're not in extended mode, skip this profile */ + if (!data->extended_mode) + goto out; + + is_extended = TRUE; + } + + ret = xmlTextReaderRead (reader); + while (ret == 1 && !done) { + xmlChar *tag; + + tag = xmlTextReaderName (reader); + + switch (xmlTextReaderNodeType (reader)) { + case 1: + if (xmlStrEqual (tag, BAD_CAST ("restriction"))) + restr = process_restriction (reader, data); + else if (xmlStrEqual (tag, BAD_CAST ("parent"))) + restr = process_parent (reader, data); + + if (!restr) + break; + + if (restr->type == GST_TYPE_ENCODING_CONTAINER_PROFILE) + gst_caps_merge (temp_container, + gst_caps_copy (restr->caps)); + else if (restr->type == GST_TYPE_ENCODING_VIDEO_PROFILE) + gst_caps_merge (temp_video, + gst_caps_copy (restr->caps)); + else if (restr->type == GST_TYPE_ENCODING_AUDIO_PROFILE) + gst_caps_merge (temp_audio, + gst_caps_copy (restr->caps)); + else + g_assert_not_reached (); + + break; + + case 15: + if (xmlStrEqual (tag, BAD_CAST ("dlna-profile"))) + done = TRUE; + + default: + break; + } + + xmlFree (tag); + ret = xmlTextReaderRead (reader); + } + + if (base_profile) { + base = g_hash_table_lookup (data->profile_ids, base_profile); + if (!base) + g_warning ("Invalid base-profile reference"); + } + + + /* create a new GUPnPDLNAProfile */ + profile = gupnp_dlna_profile_new ((gchar *)name, + (gchar *)mime, + GST_CAPS_NONE, + GST_CAPS_NONE, + GST_CAPS_NONE, + is_extended); + + /* Inherit from base profile, if it exists*/ + if (base) { + const GstCaps *video_caps = + gupnp_dlna_profile_get_video_caps (base); + const GstCaps *audio_caps = + gupnp_dlna_profile_get_audio_caps (base); + const GstCaps *container_caps = + gupnp_dlna_profile_get_container_caps (base); + + if (GST_IS_CAPS (video_caps)) + gst_caps_merge (temp_video, + gst_caps_copy (video_caps)); + if (GST_IS_CAPS (audio_caps)) + gst_caps_merge (temp_audio, + gst_caps_copy (audio_caps)); + if (GST_IS_CAPS (container_caps)) + gst_caps_merge (temp_container, + gst_caps_copy (container_caps)); + + } + + + /* The merged caps will be our new GUPnPDLNAProfile */ + + if (GST_IS_CAPS (temp_container) && !gst_caps_is_empty (temp_container)) + gupnp_dlna_profile_set_container_caps (profile, temp_container); + if (GST_IS_CAPS (temp_video) && !gst_caps_is_empty (temp_video)) + gupnp_dlna_profile_set_video_caps (profile, temp_video); + if (GST_IS_CAPS (temp_audio) && !gst_caps_is_empty (temp_audio)) + gupnp_dlna_profile_set_audio_caps (profile, temp_audio); + + *profiles = g_list_append (*profiles, profile); + + if (id) { + /* id is freed when the hash table is destroyed */ + g_object_ref (profile); + g_hash_table_insert (data->profile_ids, id, profile); + } + +out: + + if (temp_container) + gst_caps_unref (temp_container); + if (temp_audio) + gst_caps_unref (temp_audio); + if (temp_video) + gst_caps_unref (temp_video); + + xmlFree (mime); + xmlFree (name); + if (extended) + xmlFree (extended); + if (base_profile) + xmlFree (base_profile); + + return ret; +} + +static GList * +process_include (xmlTextReaderPtr reader, GUPnPDLNALoadState *data) +{ + xmlChar *path; + GList *ret; + + path = xmlTextReaderGetAttribute (reader, BAD_CAST ("ref")); + + if (!g_path_is_absolute ((gchar *) path)) { + gchar *tmp = g_strconcat (DLNA_DATA_DIR, + G_DIR_SEPARATOR_S, + path, + NULL); + xmlFree (path); + path = BAD_CAST (tmp); + } + + ret = gupnp_dlna_load_profiles_from_file ((gchar *) path, + data); + xmlFree (path); + + return ret; +} + +/* This can go away once we have a glib function to canonicalize paths (see + * https://bugzilla.gnome.org/show_bug.cgi?id=111848 + * + * The implementationis not generic enough, but sufficient for our use. The + * idea is taken from Tristan Van Berkom's comment in the bug mentioned above: + * + * 1. cd dirname(path) + * 2. absdir = $CWD + * 3. cd $OLDPWD + * 4. abspath = absdir + basename(path) + */ +static gchar * +canonicalize_path_name (const char *path) +{ + gchar *dir_name = NULL, *file_name = NULL, *abs_dir = NULL, + *old_dir = NULL, *ret = NULL; + + if (g_path_is_absolute (path)) + return g_strdup (path); + + old_dir = g_get_current_dir (); + dir_name = g_path_get_dirname (path); + + if (g_chdir (dir_name) < 0) { + ret = g_strdup (path); + goto out; + } + + abs_dir = g_get_current_dir (); + g_chdir (old_dir); + + file_name = g_path_get_basename (path); + ret = g_build_filename (abs_dir, file_name, NULL); + +out: + g_free (dir_name); + g_free (file_name); + g_free (abs_dir); + g_free (old_dir); + + return ret; +} + +GList * +gupnp_dlna_load_profiles_from_file (const char *file_name, + GUPnPDLNALoadState *data) +{ + GList *profiles = NULL; + gchar *path = NULL; + xmlTextReaderPtr reader; + xmlRelaxNGParserCtxtPtr rngp; + xmlRelaxNGPtr rngs; + int ret; + + path = canonicalize_path_name (file_name); + if (g_hash_table_lookup_extended (data->files_hash, path, NULL, NULL)) + goto out; + else + g_hash_table_insert (data->files_hash, g_strdup (path), NULL); + + reader = xmlNewTextReaderFilename (path); + if (!reader) + goto out; + + /* Load the schema for validation */ + rngp = xmlRelaxNGNewParserCtxt (DLNA_DATA_DIR "dlna-profiles.rng"); + rngs = xmlRelaxNGParse (rngp); + xmlTextReaderRelaxNGSetSchema (reader, rngs); + + ret = xmlTextReaderRead (reader); + while (ret == 1) { + xmlChar *tag; + + tag = xmlTextReaderName (reader); + + switch (xmlTextReaderNodeType (reader)) { + /* Start tag */ + case 1: + if (xmlStrEqual (tag, BAD_CAST ("include"))) { + /* */ + GList *include = + process_include (reader, + data); + profiles = g_list_concat (profiles, + include); + } else if (xmlStrEqual (tag, + BAD_CAST ("restrictions"))) { + /* */ + process_restrictions (reader, + data); + } else if (xmlStrEqual (tag, + BAD_CAST ("dlna-profile"))) { + /* */ + process_dlna_profile (reader, + &profiles, + data); + + } + + break; + + default: + break; + } + + xmlFree (tag); + ret = xmlTextReaderRead (reader); + } + + xmlFreeTextReader (reader); + xmlRelaxNGFree (rngs); + xmlRelaxNGFreeParserCtxt (rngp); + +out: + g_free (path); + + return profiles; +} + +GList * +gupnp_dlna_load_profiles_from_dir (gchar *profile_dir, GUPnPDLNALoadState *data) +{ + GDir *dir; + + data->restrictions = + g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) xmlFree, + (GDestroyNotify) + free_restrictions_struct); + data->profile_ids = + g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) xmlFree, + (GDestroyNotify) + g_object_unref); + + GList *profiles = NULL; + + if ((dir = g_dir_open (profile_dir, 0, NULL))) { + const gchar *entry; + + while ((entry = g_dir_read_name (dir))) { + gchar *path = g_strconcat (profile_dir, + G_DIR_SEPARATOR_S, + entry, + NULL); + + if (g_str_has_suffix (entry, ".xml") && + g_file_test (path, G_FILE_TEST_IS_REGULAR)) { + profiles = g_list_concat (profiles, + gupnp_dlna_load_profiles_from_file ( + path, + data)); + } + + g_free (path); + } + + g_dir_close (dir); + } + + g_hash_table_unref (data->restrictions); + g_hash_table_unref (data->profile_ids); + + return profiles; +} + +GList * +gupnp_dlna_load_profiles_from_disk (gboolean relaxed_mode, + gboolean extended_mode) +{ + GUPnPDLNALoadState *load_data; + GList *ret, *i; + + load_data = g_new0 (GUPnPDLNALoadState, 1); + + if (load_data) { + load_data->files_hash = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + load_data->relaxed_mode = relaxed_mode; + load_data->extended_mode = extended_mode; + } + + ret = gupnp_dlna_load_profiles_from_dir (DLNA_DATA_DIR, + load_data); + + /* Now that we're done loading profiles, remove all profiles with no + * name which are only used for inheritance and not matching. */ + i = ret; + while (i) { + const gchar *name; + GUPnPDLNAProfile *profile = i->data; + GstEncodingProfile *enc_profile = + gupnp_dlna_profile_get_encoding_profile + (profile); + GList *tmp = g_list_next (i); + + name = gst_encoding_profile_get_name (enc_profile); + if (name[0] == '\0') { + ret = g_list_delete_link (ret, i); + g_object_unref (profile); + } + + i = tmp; + } + + g_hash_table_unref (load_data->files_hash); + g_free (load_data); + load_data = NULL; + + return ret; +} diff --git a/libgupnp-dlna/profile-loading.h b/libgupnp-dlna/profile-loading.h new file mode 100644 index 0000000..fa1f966 --- /dev/null +++ b/libgupnp-dlna/profile-loading.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009 Nokia Corporation. + * + * Authors: Zeeshan Ali + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GUPNP_DLNA_LOAD_H__ +#define __GUPNP_DLNA_LOAD_H__ + +#include + +G_BEGIN_DECLS + +typedef struct { + GHashTable *restrictions; + GHashTable *profile_ids; + GHashTable *files_hash; + gboolean relaxed_mode; + gboolean extended_mode; +} GUPnPDLNALoadState; + +typedef struct { + GstCaps *caps; + GType type; +} GUPnPDLNARestrictions; + +GList * +gupnp_dlna_load_profiles_from_file (const gchar *file_name, + GUPnPDLNALoadState *data); +GList * +gupnp_dlna_load_profiles_from_dir (gchar *profile_dir, + GUPnPDLNALoadState *data); + +GList * +gupnp_dlna_load_profiles_from_disk (gboolean relaxed_mode, + gboolean extended_mode); + +G_END_DECLS + +#endif /* __GUPNP_DLNA_LOAD_H__ */ diff --git a/tests/dlna-profile-parser.c b/tests/dlna-profile-parser.c index 1482b1b..ee45d10 100644 --- a/tests/dlna-profile-parser.c +++ b/tests/dlna-profile-parser.c @@ -20,7 +20,7 @@ */ #include -#include +#include #include #include #include diff --git a/tools/gupnp-dlna-info.c b/tools/gupnp-dlna-info.c index 54edd45..961cc76 100644 --- a/tools/gupnp-dlna-info.c +++ b/tools/gupnp-dlna-info.c @@ -40,7 +40,7 @@ #include #include -#include +#include #include #include #include -- 2.1.4