[gupnp-av/wip/didl-lite-fragments] wip: XML fragments support.
- From: Krzesimir Nowak <krnowak src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gupnp-av/wip/didl-lite-fragments] wip: XML fragments support.
- Date: Tue, 16 Oct 2012 14:33:22 +0000 (UTC)
commit 52b0004b162e047434a1bade66fb3170a44d64f9
Author: Krzesimir Nowak <krnowak openismus com>
Date: Tue Oct 16 15:30:37 2012 +0200
wip: XML fragments support.
.dir-locals.el | 2 +
libgupnp-av/gupnp-didl-lite-object.c | 667 ++++++++++++++++++++++++++++++++++
libgupnp-av/gupnp-didl-lite-object.h | 23 ++
3 files changed, 692 insertions(+), 0 deletions(-)
---
diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644
index 0000000..c02278d
--- /dev/null
+++ b/.dir-locals.el
@@ -0,0 +1,2 @@
+((c-mode . ((indent-tabs-mode . nil)
+ (c-basic-offset . 8)))))
diff --git a/libgupnp-av/gupnp-didl-lite-object.c b/libgupnp-av/gupnp-didl-lite-object.c
index 47c2476..3d2341a 100644
--- a/libgupnp-av/gupnp-didl-lite-object.c
+++ b/libgupnp-av/gupnp-didl-lite-object.c
@@ -2097,3 +2097,670 @@ gupnp_didl_lite_object_add_descriptor (GUPnPDIDLLiteObject *object)
return gupnp_didl_lite_descriptor_new_from_xml (desc_node,
object->priv->xml_doc);
}
+
+typedef struct {
+ gchar *node_name;
+ gchar *attribute_name;
+} NodeDiff;
+
+static NodeDiff *
+node_diff_new (const xmlChar *node_name,
+ const xmlChar *attribute_name)
+{
+ NodeDiff *diff = g_slice_new (NodeDiff);
+
+ diff->node_name = g_strdup ((gchar *) node_name);
+ diff->attribute_name = g_strdup ((gchar *) attribute_name);
+
+ return diff;
+}
+
+static void
+node_diff_free (NodeDiff *diff)
+{
+ if (diff) {
+ g_free (diff->node_name);
+ g_free (diff->attribute_name);
+ g_slice_free (NodeDiff, diff);
+ }
+}
+
+static gboolean
+node_deep_equal (xmlNodePtr first,
+ xmlNodePtr second);
+
+static gboolean
+node_deep_equal (xmlNodePtr first,
+ xmlNodePtr second)
+{
+ GHashTable *first_attributes = g_hash_table_new (g_str_hash,
+ g_str_equal);
+ xmlAttrPtr attribute;
+ gboolean equal = FALSE;
+
+ if (xmlStrcmp (first->name, second->name))
+ return FALSE;
+
+ /* compare attributes */
+ for (attribute = first->properties;
+ attribute;
+ attribute = attribute->next) {
+ g_hash_table_insert (first_attributes,
+ (gpointer) attribute->name,
+ attribute->children->content);
+ }
+ for (attribute = second->properties;
+ attribute;
+ attribute = attribute->next) {
+ const xmlChar *value = NULL;
+ const xmlChar *key = attribute->name;
+
+ if (g_hash_table_lookup_extended (first_attributes, key, NULL, (gpointer *) &value)) {
+ if (!xmlStrcmp (value, attribute->children->content)) {
+ g_hash_table_remove (first_attributes, key);
+ continue;
+ }
+ }
+ goto out;
+ }
+
+ if (g_hash_table_size (first_attributes))
+ goto out;
+
+ /* compare content */
+ if (xmlStrcmp (first->content, second->content))
+ goto out;
+ equal = TRUE;
+ out:
+ g_hash_table_unref (first_attributes);
+ if (equal) {
+ xmlNodePtr first_child;
+ xmlNodePtr second_child;
+
+ for (first_child = first->children,
+ second_child = second->children;
+ first_child && second_child;
+ first_child = first_child->next,
+ second_child = second_child->next) {
+ if (!node_deep_equal (first_child, second_child)) {
+ return FALSE;
+ }
+ }
+ if (first_child || second_child)
+ return FALSE;
+ }
+
+ return equal;
+}
+
+static xmlNodePtr
+find_node (xmlNodePtr haystack,
+ xmlNodePtr needle);
+
+static xmlNodePtr
+find_node (xmlNodePtr haystack,
+ xmlNodePtr needle)
+{
+ xmlNodePtr iter;
+
+ if (node_deep_equal (haystack, needle))
+ return haystack;
+
+ for (iter = haystack->children;
+ iter;
+ iter = iter->next) {
+ xmlNodePtr found_node = find_node (iter, needle);
+
+ if (found_node)
+ return found_node;
+ }
+
+ return NULL;
+}
+
+static gboolean
+is_current_doc_part_of_this_doc (xmlDocPtr this_doc,
+ xmlDocPtr current_doc)
+{
+ xmlNodePtr current_node = current_doc->children->children;
+ xmlNodePtr this_node = find_node (this_doc->children, current_node);
+
+ if (!this_node)
+ return FALSE;
+
+ for (current_node = current_node->next, this_node = this_node->next;
+ current_node && this_node;
+ current_node = current_node->next, this_node = this_node->next) {
+ if (!node_deep_equal (current_node, this_node))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+is_read_only (const gchar *changed_element,
+ const gchar *changed_attribute)
+{
+ static GHashTable *readonly_props = NULL;
+ static gsize readonly_props_loaded = 0;
+
+ if (g_once_init_enter (&readonly_props_loaded)) {
+ readonly_props = g_hash_table_new (g_str_hash,
+ g_str_equal);
+
+ g_hash_table_add (readonly_props, "@id");
+ g_hash_table_add (readonly_props, "@parentID");
+ g_hash_table_add (readonly_props, "@refID");
+ g_hash_table_add (readonly_props, "@restricted");
+ g_hash_table_add (readonly_props, "@searchable");
+ g_hash_table_add (readonly_props, "@childCount");
+ g_hash_table_add (readonly_props, "searchClass");
+ g_hash_table_add (readonly_props, "searchClass name");
+ g_hash_table_add (readonly_props, "searchClass includeDerived");
+ g_hash_table_add (readonly_props, "createClass");
+ g_hash_table_add (readonly_props, "createClass name");
+ g_hash_table_add (readonly_props, "createClass includeDerived");
+ g_hash_table_add (readonly_props, "writeStatus");
+ g_hash_table_add (readonly_props, "res importUri");
+ g_hash_table_add (readonly_props, "storageTotal");
+ g_hash_table_add (readonly_props, "storageUsed");
+ g_hash_table_add (readonly_props, "storageFree");
+ g_hash_table_add (readonly_props, "storageMaxPartition");
+ g_hash_table_add (readonly_props, "storageMedium");
+ g_hash_table_add (readonly_props, "playbackCount");
+ g_hash_table_add (readonly_props, "srsRecordScheduleID");
+ g_hash_table_add (readonly_props, "srsRecordTaskID");
+ g_hash_table_add (readonly_props, "price");
+ g_hash_table_add (readonly_props, "price currency");
+ g_hash_table_add (readonly_props, "payPerView");
+ g_hash_table_add (readonly_props, "dateTimeRange");
+ g_hash_table_add (readonly_props, "dateTimeRange daylightSaving");
+ g_hash_table_add (readonly_props, "signalStrength");
+ g_hash_table_add (readonly_props, "signalLocked");
+ g_hash_table_add (readonly_props, "tuned");
+ g_hash_table_add (readonly_props, "containerUpdateID");
+ g_hash_table_add (readonly_props, "objectUpdateID");
+ g_hash_table_add (readonly_props, "totalDeletedChildCount");
+ g_hash_table_add (readonly_props, "res updateCount");
+ g_once_init_leave (&readonly_props_loaded, 1);
+ }
+ if (changed_element) {
+
+ if (changed_attribute) {
+ gchar *test_prop = g_strdup_printf ("%s %s",
+ changed_element,
+ changed_attribute);
+ gboolean result = g_hash_table_contains (readonly_props,
+ test_prop);
+
+ g_free (test_prop);
+ if (result)
+ return TRUE;
+ test_prop = g_strdup_printf ("@%s", changed_attribute);
+ result = g_hash_table_contains (readonly_props,
+ test_prop);
+ g_free (test_prop);
+ if (result)
+ return TRUE;
+ }
+ return g_hash_table_contains (readonly_props, changed_element);
+ }
+ return FALSE;
+}
+
+typedef struct {
+ gboolean required;
+ GHashTable* required_dep_props; /* string set */
+ GHashTable* required_indep_props; /* string to indep prop */
+} IndependentProperty;
+
+void
+independent_property_free (IndependentProperty *indep)
+{
+ if (indep) {
+ g_hash_table_unref (indep->required_dep_props);
+ g_hash_table_unref (indep->required_indep_props);
+ g_slice_free (IndependentProperty, indep);
+ }
+}
+
+IndependentProperty *
+independent_property_new (gboolean required)
+{
+ IndependentProperty *indep = g_slice_new (IndependentProperty);
+
+ indep->required = required;
+ indep->required_dep_props = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
+ indep->required_indep_props = g_hash_table_new_full
+ (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) independent_property_free);
+
+ return indep;
+}
+
+static void
+insert_indep_prop (GHashTable *props,
+ gchar *name,
+ IndependentProperty *prop)
+{
+ g_hash_table_insert (props, g_strdup (name), prop);
+}
+
+static void
+insert_indep_prop_to_indep (IndependentProperty *prop,
+ gchar *name,
+ IndependentProperty *req_prop)
+{
+ insert_indep_prop (prop->required_indep_props, name, req_prop);
+}
+
+static void
+add_dep_prop (IndependentProperty *indep,
+ gchar *name)
+{
+ g_hash_table_add (indep->required_dep_props, g_strdup (name));
+}
+
+IndependentProperty *
+create_prop_with_required_dep_props (gboolean required,
+ gchar *dep_prop,
+ ...)
+{
+ IndependentProperty *indep = independent_property_new (required);
+
+ if (dep_prop) {
+ va_list var_args;
+ gchar *name = dep_prop;
+
+ va_start (var_args, dep_prop);
+ do {
+ add_dep_prop (indep, name);
+ name = va_arg (var_args, gchar *);
+ } while (name);
+ va_end (var_args);
+ }
+
+ return indep;
+}
+
+IndependentProperty *
+create_foreign_metadata_props (void)
+{
+ IndependentProperty *fm = independent_property_new (FALSE);
+ IndependentProperty *other;
+
+ add_dep_prop (fm, "type");
+
+ other = independent_property_new (TRUE);
+ insert_indep_prop_to_indep (fm, "fmId", other);
+
+ other = independent_property_new (TRUE);
+ insert_indep_prop_to_indep (fm, "fmClass", other);
+
+ other = independent_property_new (TRUE);
+ insert_indep_prop_to_indep (fm, "fmProvider", other);
+
+ other = independent_property_new (TRUE);
+ add_dep_prop (other, "xmlFlag");
+ insert_indep_prop_to_indep (fm, "fmBody", other);
+
+ return fm;
+}
+
+static GHashTable *
+get_required_properties (void)
+{
+ static GHashTable *required_props = NULL;
+ static gsize required_props_loaded = 0;
+
+ if (g_once_init_enter (&required_props_loaded)) {
+ required_props = g_hash_table_new_full
+ (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) independent_property_free);
+
+ insert_indep_prop (required_props,
+ NULL,
+ create_prop_with_required_dep_props
+ (FALSE,
+ "id",
+ "parentID",
+ "restricted",
+ NULL));
+
+ insert_indep_prop (required_props,
+ "title",
+ independent_property_new (TRUE));
+ insert_indep_prop (required_props,
+ "class",
+ independent_property_new (TRUE));
+
+ insert_indep_prop (required_props,
+ "res",
+ create_prop_with_required_dep_props
+ (FALSE,
+ "protocolInfo",
+ NULL));
+ insert_indep_prop (required_props,
+ "programID",
+ create_prop_with_required_dep_props
+ (FALSE,
+ "type",
+ NULL));
+ insert_indep_prop (required_props,
+ "seriesID",
+ create_prop_with_required_dep_props
+ (FALSE,
+ "type",
+ NULL));
+ insert_indep_prop (required_props,
+ "channelID",
+ create_prop_with_required_dep_props
+ (FALSE,
+ "type",
+ NULL));
+ insert_indep_prop (required_props,
+ "programCode",
+ create_prop_with_required_dep_props
+ (FALSE,
+ "type",
+ NULL));
+ insert_indep_prop (required_props,
+ "channelGroupName",
+ create_prop_with_required_dep_props
+ (FALSE,
+ "id",
+ NULL));
+ insert_indep_prop (required_props,
+ "price",
+ create_prop_with_required_dep_props
+ (FALSE,
+ "currency",
+ NULL));
+ insert_indep_prop (required_props,
+ "desc",
+ create_prop_with_required_dep_props
+ (FALSE,
+ "nameSpace",
+ NULL));
+ insert_indep_prop (required_props,
+ "deviceUDN",
+ create_prop_with_required_dep_props
+ (FALSE,
+ "serviceType",
+ "serviceId",
+ NULL));
+ insert_indep_prop (required_props,
+ "stateVariableCollection",
+ create_prop_with_required_dep_props
+ (FALSE,
+ "serviceName",
+ "rcsInstanceType",
+ NULL));
+ insert_indep_prop (required_props,
+ "foreignMetadata",
+ create_foreign_metadata_props ());
+ g_once_init_leave (&required_props_loaded, 1);
+ }
+
+ return required_props;
+}
+
+static gboolean
+is_required (const xmlChar *changed_element,
+ const xmlChar *changed_attribute)
+{
+ GHashTable *required_props = get_required_properties ();
+
+ if (changed_element) {
+ IndependentProperty *toplevel_prop = g_hash_table_lookup
+ (required_props,
+ NULL);
+ IndependentProperty *this_prop = g_hash_table_lookup
+ (required_props,
+ (gpointer) changed_element);
+
+ if (changed_attribute) {
+ if (g_hash_table_contains (toplevel_prop->required_dep_props,
+ changed_attribute))
+ return TRUE;
+ if (g_hash_table_contains (this_prop->required_dep_props,
+ changed_attribute))
+ return TRUE;
+ }
+ if (g_hash_table_contains (toplevel_prop->required_indep_props,
+ changed_element))
+ return TRUE;
+ /* TODO: check if changed element is not a required
+ * property of its parent element. That needs some
+ * additions in IndepependentProperty.
+ */
+ }
+ return FALSE;
+}
+
+static gboolean
+is_valid (xmlNodePtr node G_GNUC_UNUSED)
+{
+ return TRUE;
+}
+
+static GList *
+get_toplevel_changes (xmlNodePtr current_node,
+ xmlNodePtr new_node)
+{
+ xmlAttrPtr attribute;
+ GHashTable *current_attributes = g_hash_table_new (g_str_hash,
+ g_str_equal);
+ GList *changes = NULL;
+ const xmlChar *name = new_node->name;
+
+ /* compare attributes */
+ for (attribute = current_node->properties;
+ attribute;
+ attribute = attribute->next) {
+ g_hash_table_insert (current_attributes,
+ (gpointer) attribute->name,
+ attribute->children->content);
+ }
+ for (attribute = new_node->properties;
+ attribute;
+ attribute = attribute->next) {
+ const xmlChar *value = NULL;
+ const xmlChar *key = attribute->name;
+ gboolean differs = FALSE;
+
+ if (g_hash_table_lookup_extended (current_attributes,
+ key,
+ NULL,
+ (gpointer *) &value)) {
+ if (xmlStrcmp (value, attribute->children->content)) {
+ differs = TRUE;
+ }
+ g_hash_table_remove (current_attributes, key);
+ } else
+ differs = TRUE;
+ if (differs)
+ changes = g_list_prepend (changes,
+ node_diff_new (name,
+ key));
+ }
+
+ if (g_hash_table_size (current_attributes) > 0) {
+ GHashTableIter iter;
+ xmlChar *key = NULL;
+
+ g_hash_table_iter_init (&iter, current_attributes);
+ while (g_hash_table_iter_next (&iter,
+ (gpointer *) &key,
+ NULL)) {
+ changes = g_list_prepend (changes, node_diff_new (name,
+ key));
+ }
+ }
+
+ g_hash_table_unref (current_attributes);
+
+ return changes;
+}
+
+static gboolean
+new_doc_is_valid_modification (xmlDocPtr current_doc,
+ xmlDocPtr new_doc,
+ GUPnPDIDLLiteFragmentResult *result) {
+ xmlNodePtr current_node;
+ xmlNodePtr new_node;
+
+ for (current_node = current_doc->children->children,
+ new_node = new_doc->children->children;
+ current_node && new_node;
+ current_node = current_node->next,
+ new_node = new_node->next) {
+ GList *changes;
+
+ if (node_deep_equal (current_node, new_node)) {
+ /* this is just a context, skip the checks. */
+ continue;
+ }
+ if (xmlStrcmp (current_node->name, new_node->name)) {
+ *result = GUPNP_DIDL_LITE_FRAGMENT_RESULT_NEW_INVALID;
+ return FALSE;
+ }
+ changes = get_toplevel_changes (current_node, new_node);
+ if (changes) {
+ GList *iter;
+
+ for (iter = changes; iter; iter = iter->next) {
+ NodeDiff *diff = (NodeDiff *) iter->data;
+
+ if (is_read_only (diff->node_name,
+ diff->attribute_name)) {
+ *result = GUPNP_DIDL_LITE_FRAGMENT_RESULT_READONLY_TAG;
+ g_list_free_full (changes, (GDestroyNotify) node_diff_free);
+ return FALSE;
+ }
+ }
+ }
+ }
+ /* If there are some more nodes in current fragment then it
+ * means they are going to be removed. Check against required
+ * or read-only tag removal.
+ */
+ for (; current_node; current_node = current_node->next) {
+ /* TODO: should we check if there are some readonly
+ * attributes when we remove whole element?
+ */
+ if (is_read_only ((gchar *) current_node->name, NULL)) {
+ *result = GUPNP_DIDL_LITE_FRAGMENT_RESULT_READONLY_TAG;
+ return FALSE;
+ }
+ /* We don't check for required attributes or
+ * subelements, because most of them are required only
+ * when the element exists. And we are removing this
+ * one.
+ */
+ if (is_required (current_node->name, NULL)) {
+ *result = GUPNP_DIDL_LITE_FRAGMENT_RESULT_REQUIRED_TAG;
+ return FALSE;
+ }
+ }
+ /* If there are some more nodes in new fragment then it means
+ * they are going to be added. Check against read-only tags
+ * addition and general sanity check.
+ */
+ for (; new_node; new_node = new_node->next) {
+ if (is_read_only ((gchar *) new_node->name, NULL)) {
+ *result = GUPNP_DIDL_LITE_FRAGMENT_RESULT_READONLY_TAG;
+ return FALSE;
+ }
+ /* TODO: We probably should check if newly added node
+ * has all required properties.
+ */
+ if (!is_valid (new_node)) {
+ *result = GUPNP_DIDL_LITE_FRAGMENT_RESULT_NEW_INVALID;
+ }
+ }
+
+ return TRUE;
+}
+
+static gchar *
+fix_fragment (const gchar *fragment)
+{
+ return g_strdup_printf ("<DIDLLiteFragment>%s</DIDLLiteFragment>",
+ fragment);
+}
+
+GUPnPDIDLLiteFragmentResult
+gupnp_didl_lite_object_is_fragment_pair_valid
+ (GUPnPDIDLLiteObject *object,
+ const gchar *current_fragment,
+ const gchar *new_fragment)
+{
+ xmlDocPtr this_doc;
+ xmlDocPtr current_doc;
+ xmlDocPtr new_doc;
+ GUPnPDIDLLiteFragmentResult result;
+ gchar *fixed_current_fragment;
+ gchar *fixed_new_fragment;
+
+ g_return_val_if_fail (GUPNP_IS_DIDL_LITE_OBJECT (object),
+ GUPNP_DIDL_LITE_FRAGMENT_RESULT_UNKNOWN_ERROR);
+ g_return_val_if_fail (current_fragment != NULL,
+ GUPNP_DIDL_LITE_FRAGMENT_RESULT_UNKNOWN_ERROR);
+ g_return_val_if_fail (new_fragment != NULL,
+ GUPNP_DIDL_LITE_FRAGMENT_RESULT_UNKNOWN_ERROR);
+
+ fixed_current_fragment = fix_fragment (current_fragment);
+ fixed_new_fragment = fix_fragment (new_fragment);
+ result = GUPNP_DIDL_LITE_FRAGMENT_RESULT_APPLIABLE;
+ this_doc = object->priv->xml_doc->doc;
+ current_doc = xmlRecoverMemory (fixed_current_fragment,
+ strlen (fixed_current_fragment));
+ new_doc = xmlRecoverMemory (fixed_new_fragment,
+ strlen (fixed_new_fragment));
+
+ if (!current_doc) {
+ result = GUPNP_DIDL_LITE_FRAGMENT_RESULT_CURRENT_BAD_XML;
+ goto out;
+ }
+ if (!new_doc) {
+ result = GUPNP_DIDL_LITE_FRAGMENT_RESULT_NEW_BAD_XML;
+ goto out;
+ }
+
+ if (!is_current_doc_part_of_this_doc (this_doc, current_doc)) {
+ result = GUPNP_DIDL_LITE_FRAGMENT_RESULT_CURRENT_INVALID;
+ goto out;
+ }
+
+ if (!new_doc_is_valid_modification (current_doc, new_doc, &result)) {
+ goto out;
+ }
+
+ out:
+ g_free (fixed_new_fragment);
+ g_free (fixed_current_fragment);
+ if (new_doc)
+ xmlFreeDoc (new_doc);
+ if (current_doc)
+ xmlFreeDoc (current_doc);
+
+ return result;
+}
+
+gboolean
+gupnp_didl_lite_object_apply_fragment_pair
+ (GUPnPDIDLLiteObject *object G_GNUC_UNUSED,
+ const gchar *current_fragment G_GNUC_UNUSED,
+ const gchar *new_fragment G_GNUC_UNUSED)
+{
+ return FALSE;
+}
diff --git a/libgupnp-av/gupnp-didl-lite-object.h b/libgupnp-av/gupnp-didl-lite-object.h
index 8c66df1..747b9ae 100644
--- a/libgupnp-av/gupnp-didl-lite-object.h
+++ b/libgupnp-av/gupnp-didl-lite-object.h
@@ -37,6 +37,17 @@
G_BEGIN_DECLS
+typedef enum {
+ GUPNP_DIDL_LITE_FRAGMENT_RESULT_APPLIABLE,
+ GUPNP_DIDL_LITE_FRAGMENT_RESULT_CURRENT_BAD_XML,
+ GUPNP_DIDL_LITE_FRAGMENT_RESULT_NEW_BAD_XML,
+ GUPNP_DIDL_LITE_FRAGMENT_RESULT_CURRENT_INVALID,
+ GUPNP_DIDL_LITE_FRAGMENT_RESULT_NEW_INVALID,
+ GUPNP_DIDL_LITE_FRAGMENT_RESULT_REQUIRED_TAG,
+ GUPNP_DIDL_LITE_FRAGMENT_RESULT_READONLY_TAG,
+ GUPNP_DIDL_LITE_FRAGMENT_RESULT_UNKNOWN_ERROR
+} GUPnPDIDLLiteFragmentResult;
+
GType
gupnp_didl_lite_object_get_type (void) G_GNUC_CONST;
@@ -265,6 +276,18 @@ gupnp_didl_lite_object_set_update_id (GUPnPDIDLLiteObject *object,
void
gupnp_didl_lite_object_unset_update_id (GUPnPDIDLLiteObject *object);
+GUPnPDIDLLiteFragmentResult
+gupnp_didl_lite_object_is_fragment_pair_valid
+ (GUPnPDIDLLiteObject *object,
+ const gchar *current_fragment,
+ const gchar *new_fragment);
+
+gboolean
+gupnp_didl_lite_object_apply_fragment_pair
+ (GUPnPDIDLLiteObject *object,
+ const gchar *current_fragment,
+ const gchar *new_fragment);
+
G_END_DECLS
#endif /* __GUPNP_DIDL_LITE_OBJECT_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]