[gupnp-av/wip/didl-s: 9/10] Parse DIDL_S playlists



commit ef20f4c8ebda77a570ee293db8908efb37c23f2e
Author: Jens Georg <mail jensge org>
Date:   Mon Nov 12 15:32:04 2012 +0100

    Parse DIDL_S playlists

 doc/gupnp-av-sections.txt                    |    3 +
 libgupnp-av/gupnp-didl-lite-parser-private.h |   36 +++++
 libgupnp-av/gupnp-didl-lite-parser.c         |   80 ++++++++++--
 libgupnp-av/gupnp-media-collection.c         |  195 +++++++++++++++++++++++++-
 libgupnp-av/gupnp-media-collection.h         |   25 +++-
 5 files changed, 315 insertions(+), 24 deletions(-)
---
diff --git a/doc/gupnp-av-sections.txt b/doc/gupnp-av-sections.txt
index 3c1549e..1cc764d 100644
--- a/doc/gupnp-av-sections.txt
+++ b/doc/gupnp-av-sections.txt
@@ -384,12 +384,15 @@ GUPnPMediaCollectionType
 GUPnPMediaCollection
 GUPnPMediaCollectionClass
 gupnp_media_collection_new
+gupnp_media_collection_new_from_string
 gupnp_media_collection_set_title
 gupnp_media_collection_get_title
 gupnp_media_collection_set_author
 gupnp_media_collection_get_author
 gupnp_media_collection_add_item
 gupnp_media_collection_get_string
+gupnp_media_collection_get_items
+gupnp_media_collection_get_mutable
 <SUBSECTION Standard>
 GUPNP_IS_MEDIA_COLLECTION
 GUPNP_IS_MEDIA_COLLECTION_CLASS
diff --git a/libgupnp-av/gupnp-didl-lite-parser-private.h b/libgupnp-av/gupnp-didl-lite-parser-private.h
new file mode 100644
index 0000000..b9e88aa
--- /dev/null
+++ b/libgupnp-av/gupnp-didl-lite-parser-private.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 Intel Corporation.
+ *
+ * Authors: Jens Georg <jensg openismus com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GUPNP_DIDL_LITE_PARSER_PRIVATE_H__
+#define __GUPNP_DIDL_LITE_PARSER_PRIVATE_H__
+
+#include "gupnp-didl-lite-parser.h"
+
+G_BEGIN_DECLS
+
+G_GNUC_INTERNAL gboolean
+gupnp_didl_lite_parser_parse_didl_recursive (GUPnPDIDLLiteParser *parser,
+                                             const char          *didl,
+                                             gboolean             recursive,
+                                             GError             **error);
+G_END_DECLS
+
+#endif /* __GUPNP_DIDL_LITE_PARSER_PRIVATE_H__ */
diff --git a/libgupnp-av/gupnp-didl-lite-parser.c b/libgupnp-av/gupnp-didl-lite-parser.c
index 8b1088d..d0a5091 100644
--- a/libgupnp-av/gupnp-didl-lite-parser.c
+++ b/libgupnp-av/gupnp-didl-lite-parser.c
@@ -75,6 +75,16 @@ verify_didl_attributes (xmlNode *node)
         return xml_util_verify_attribute_is_boolean (node, "restricted");
 }
 
+static gboolean
+parse_elements (GUPnPDIDLLiteParser *parser,
+                xmlNode             *node,
+                GUPnPXMLDoc         *xml_doc,
+                xmlNs               *upnp_ns,
+                xmlNs               *dc_ns,
+                xmlNs               *dlna_ns,
+                gboolean             recursive,
+                GError             **error);
+
 static void
 gupnp_didl_lite_parser_init (GUPnPDIDLLiteParser *parser)
 {
@@ -190,6 +200,28 @@ gupnp_didl_lite_parser_parse_didl (GUPnPDIDLLiteParser *parser,
                                    const char          *didl,
                                    GError             **error)
 {
+
+    gupnp_didl_lite_parser_parse_didl_recursive (parser, didl, FALSE, error);
+}
+
+/**
+ * gupnp_didl_lite_parser_parse_didl_recursive:
+ * @parser: A #GUPnPDIDLLiteParser
+ * @didl: The DIDL-Lite XML string to be parsed
+ * @error: The location where to store any error, or NULL
+ *
+ * Parses DIDL-Lite XML string @didl, emitting the ::object-available,
+ * ::item-available and ::container-available signals appropriately during the
+ * process.
+ *
+ * Return value: TRUE on success.
+ **/
+gboolean
+gupnp_didl_lite_parser_parse_didl_recursive (GUPnPDIDLLiteParser *parser,
+                                             const char          *didl,
+                                             gboolean             recursive,
+                                             GError             **error)
+{
         xmlDoc       *doc;
         xmlNode      *element;
         xmlNode      *node;
@@ -200,7 +232,7 @@ gupnp_didl_lite_parser_parse_didl (GUPnPDIDLLiteParser *parser,
         GUPnPXMLDoc  *xml_doc;
 
         doc = xmlRecoverMemory (didl, strlen (didl));
-	if (doc == NULL) {
+        if (doc == NULL) {
                 g_set_error (error,
                              GUPNP_XML_ERROR,
                              GUPNP_XML_ERROR_PARSE,
@@ -284,7 +316,29 @@ gupnp_didl_lite_parser_parse_didl (GUPnPDIDLLiteParser *parser,
 
         xml_doc = gupnp_xml_doc_new (doc);
 
-        for (element = element->children; element; element = element->next) {
+        return parse_elements (parser,
+                               element,
+                               xml_doc,
+                               upnp_ns,
+                               dc_ns,
+                               dlna_ns,
+                               recursive,
+                               error);
+}
+
+static gboolean
+parse_elements (GUPnPDIDLLiteParser *parser,
+                xmlNode             *node,
+                GUPnPXMLDoc         *xml_doc,
+                xmlNs               *upnp_ns,
+                xmlNs               *dc_ns,
+                xmlNs               *dlna_ns,
+                gboolean             recursive,
+                GError             **error)
+{
+        xmlNode      *element;
+
+        for (element = node->children; element; element = element->next) {
                 GUPnPDIDLLiteObject *object;
 
                 object = gupnp_didl_lite_object_new_from_xml (element, xml_doc,
@@ -294,12 +348,21 @@ gupnp_didl_lite_parser_parse_didl (GUPnPDIDLLiteParser *parser,
                 if (object == NULL)
                         continue;
 
-                if (GUPNP_IS_DIDL_LITE_CONTAINER (object))
+                if (GUPNP_IS_DIDL_LITE_CONTAINER (object)) {
                         g_signal_emit (parser,
-                                        signals[CONTAINER_AVAILABLE],
-                                        0,
-                                        object);
-                else if (GUPNP_IS_DIDL_LITE_ITEM (object)) {
+                                       signals[CONTAINER_AVAILABLE],
+                                       0,
+                                       object);
+                        if (recursive)
+                                parse_elements (parser,
+                                                element,
+                                                xml_doc,
+                                                upnp_ns,
+                                                dc_ns,
+                                                dlna_ns,
+                                                recursive,
+                                                error);
+                } else if (GUPNP_IS_DIDL_LITE_ITEM (object)) {
                         node = gupnp_didl_lite_object_get_xml_node(object);
                         if (!verify_didl_attributes(node)) {
                             g_object_unref (object);
@@ -307,8 +370,7 @@ gupnp_didl_lite_parser_parse_didl (GUPnPDIDLLiteParser *parser,
                             g_set_error (error,
                                          GUPNP_XML_ERROR,
                                          GUPNP_XML_ERROR_INVALID_ATTRIBUTE,
-                                         "Could not parse DIDL-Lite XML:\n%s",
-                                         didl);
+                                         "Could not parse DIDL-Lite XML");
 
                             return FALSE;
                         }
diff --git a/libgupnp-av/gupnp-media-collection.c b/libgupnp-av/gupnp-media-collection.c
index d222dc7..0660b83 100644
--- a/libgupnp-av/gupnp-media-collection.c
+++ b/libgupnp-av/gupnp-media-collection.c
@@ -29,6 +29,8 @@
 #include "gupnp-media-collection.h"
 #include "gupnp-didl-lite-writer.h"
 #include "gupnp-didl-lite-writer-private.h"
+#include "gupnp-didl-lite-parser.h"
+#include "gupnp-didl-lite-parser-private.h"
 
 // DIDL_S allowed tags as per DLNA Guidelines 11.1
 #define DIDL_S_FILTER "dc:title,dc:creator,upnp:class,upnp:album,res,item," \
@@ -42,12 +44,16 @@ struct _GUPnPMediaCollectionPrivate {
         GUPnPDIDLLiteWriter *writer;
         GUPnPDIDLLiteObject *container;
         GList               *items;
+        gboolean             mutable;
+        char                *data;
 };
 
 enum {
         PROP_0,
         PROP_AUTHOR,
         PROP_TITLE,
+        PROP_MUTABLE,
+        PROP_DATA,
 };
 
 static void
@@ -70,6 +76,54 @@ reparent_children (GUPnPMediaCollection *collection)
         }
 }
 
+static void
+on_container_available (GUPnPMediaCollection   *self,
+                        GUPnPDIDLLiteContainer *container,
+                        gpointer                user_data)
+{
+        /* According to media format spec, there's only one container allowed;
+         * We allow any number of containers, but only the last one wins. */
+        if (self->priv->container != NULL)
+                g_object_unref (self->priv->container);
+
+        self->priv->container = g_object_ref (container);
+}
+
+static void
+on_item_available (GUPnPMediaCollection   *self,
+                   GUPnPDIDLLiteItem      *item,
+                   gpointer                user_data)
+{
+        self->priv->items = g_list_prepend (self->priv->items,
+                                            g_object_ref (item));
+}
+
+static void
+parse_data (GUPnPMediaCollection *collection, const char *data)
+{
+        GUPnPDIDLLiteParser *parser;
+        GError *error = NULL;
+        gboolean result;
+
+        parser = gupnp_didl_lite_parser_new ();
+        g_signal_connect_swapped (G_OBJECT (parser),
+                                  "container-available",
+                                  G_CALLBACK (on_container_available),
+                                  collection);
+        g_signal_connect_swapped (G_OBJECT (parser),
+                                  "item-available",
+                                  G_CALLBACK (on_item_available),
+                                  collection);
+
+        result = gupnp_didl_lite_parser_parse_didl_recursive (parser,
+                                                              data,
+                                                              TRUE,
+                                                              &error);
+        if (!result) {
+                g_warning ("Failed to parse DIDL-Lite: %s", error->message);
+                g_error_free (error);
+        }
+}
 
 static void
 gupnp_media_collection_init (GUPnPMediaCollection *collection)
@@ -99,6 +153,9 @@ gupnp_media_collection_set_property (GObject      *object,
                 gupnp_media_collection_set_title (collection,
                                                   g_value_get_string (value));
                 break;
+        case PROP_DATA:
+                collection->priv->data = g_value_dup_string (value);
+                break;
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
                 break;
@@ -124,6 +181,10 @@ gupnp_media_collection_get_property (GObject    *object,
                 g_value_set_string
                         (value, gupnp_media_collection_get_title (collection));
                 break;
+        case PROP_MUTABLE:
+                g_value_set_boolean
+                        (value, gupnp_media_collection_get_mutable (collection));
+                break;
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
                 break;
@@ -137,9 +198,29 @@ gupnp_media_collection_constructed (GObject *object)
         GObjectClass         *object_class;
 
         collection = GUPNP_MEDIA_COLLECTION (object);
-        if (collection->priv->writer == NULL)
-                collection->priv->writer = gupnp_didl_lite_writer_new (NULL);
 
+        /* Check if we have some data. If there's data, we assume that the
+         * user meant to parse a playlist. We ignore title and author then. */
+        if (collection->priv->data != NULL) {
+                if (collection->priv->container != NULL) {
+                        g_object_unref (collection->priv->container);
+                        collection->priv->container = NULL;
+                }
+
+                if (collection->priv->writer != NULL) {
+                        g_object_unref (collection->priv->writer);
+                        collection->priv->writer = NULL;
+                }
+
+                parse_data (collection, collection->priv->data);
+                collection->priv->mutable = FALSE;
+        } else if (collection->priv->writer == NULL) {
+                collection->priv->writer =
+                                        gupnp_didl_lite_writer_new (NULL);
+                collection->priv->mutable = TRUE;
+        }
+
+        /* Chain up */
         object_class = G_OBJECT_CLASS (gupnp_media_collection_parent_class);
         if (object_class->constructed != NULL)
                 object_class->constructed (object);
@@ -168,6 +249,9 @@ gupnp_media_collection_dispose (GObject *object)
                 collection->priv->container = NULL;
         }
 
+        g_free (collection->priv->container);
+        collection->priv->container = NULL;
+
         object_class = G_OBJECT_CLASS (gupnp_media_collection_parent_class);
         object_class->dispose (object);
 }
@@ -217,6 +301,37 @@ gupnp_media_collection_class_init (GUPnPMediaCollectionClass *klass)
                                       G_PARAM_READWRITE |
                                       G_PARAM_CONSTRUCT |
                                       G_PARAM_STATIC_STRINGS));
+
+        /**
+         * GUPnPMediaCollection:mutable:
+         *
+         * Whether this media collation is modifyable or not.
+         **/
+        g_object_class_install_property
+                (object_class,
+                 PROP_MUTABLE,
+                 g_param_spec_boolean ("mutable",
+                                       "Mutable",
+                                       "The mutability of this collection",
+                                       FALSE,
+                                       G_PARAM_READABLE |
+                                       G_PARAM_STATIC_STRINGS));
+
+        /**
+         * GUPnPMediaCollection:data:
+         *
+         * Block of data to parse a collection from.
+         **/
+        g_object_class_install_property
+                (object_class,
+                 PROP_DATA,
+                 g_param_spec_string ("data",
+                                      "Data",
+                                      "Data to construct the playlist from",
+                                      NULL,
+                                      G_PARAM_WRITABLE |
+                                      G_PARAM_CONSTRUCT_ONLY |
+                                      G_PARAM_STATIC_STRINGS));
 }
 
 /**
@@ -233,6 +348,22 @@ gupnp_media_collection_new ()
 }
 
 /**
+ * gupnp_media_collection_new_from_string:
+ * @data: XML string.
+ *
+ * Parse a new #GUPnPMediaCollection from a block of XML data.
+ *
+ * Returns: (transfer full): A new #GUPnPMediaCollection.
+ **/
+GUPnPMediaCollection *
+gupnp_media_collection_new_from_string (const char *data)
+{
+        return g_object_new (GUPNP_TYPE_MEDIA_COLLECTION,
+                             "data", data,
+                             NULL);
+}
+
+/**
  * gupnp_media_collection_set_title:
  * @collection: #GUPnPMediaCollection
  * @title: New Title of this collection;
@@ -246,6 +377,7 @@ gupnp_media_collection_set_title  (GUPnPMediaCollection *collection,
         GUPnPDIDLLiteContainer *container;
 
         g_return_if_fail (GUPNP_IS_MEDIA_COLLECTION (collection));
+        g_return_if_fail (collection->priv->mutable);
 
         if (title == NULL)
                 return;
@@ -281,6 +413,8 @@ gupnp_media_collection_get_title  (GUPnPMediaCollection *collection)
 {
         g_return_val_if_fail (GUPNP_IS_MEDIA_COLLECTION (collection), NULL);
 
+        g_print ("=> %p\n", collection->priv->container);
+
         if (collection->priv->container == NULL)
                 return NULL;
 
@@ -301,6 +435,7 @@ gupnp_media_collection_set_author (GUPnPMediaCollection *collection,
         GUPnPDIDLLiteContainer *container;
 
         g_return_if_fail (GUPNP_IS_MEDIA_COLLECTION (collection));
+        g_return_if_fail (collection->priv->mutable);
 
         if (author == NULL)
                 return;
@@ -347,26 +482,29 @@ gupnp_media_collection_get_author (GUPnPMediaCollection *collection)
  * @collection: #GUPnPMediaCollection
  *
  * Return value: (transfer full): A new #GUPnPDIDLLiteItem object. Unref after
- * usage.
+ * use.
  **/
 GUPnPDIDLLiteItem *
 gupnp_media_collection_add_item (GUPnPMediaCollection *collection)
 {
         g_return_val_if_fail (collection != NULL, NULL);
         g_return_val_if_fail (GUPNP_IS_MEDIA_COLLECTION (collection), NULL);
+        g_return_val_if_fail (collection->priv->mutable, NULL);
+
         GUPnPDIDLLiteItem *item = NULL;
 
         if (collection->priv->container != NULL)
                 item = gupnp_didl_lite_writer_add_container_child_item
                                         (collection->priv->writer,
-                                         GUPNP_DIDL_LITE_CONTAINER (collection->priv->container));
+                                         GUPNP_DIDL_LITE_CONTAINER
+                                                (collection->priv->container));
         else
                 item = gupnp_didl_lite_writer_add_item
                                         (collection->priv->writer);
 
         /* Keep a reference of the object in case we need to do reparenting */
-        collection->priv->items = g_list_append (collection->priv->items,
-                                                 g_object_ref (item));
+        collection->priv->items = g_list_prepend (collection->priv->items,
+                                                  g_object_ref (item));
 
         return item;
 }
@@ -376,7 +514,8 @@ gupnp_media_collection_add_item (GUPnPMediaCollection *collection)
  * @collection: #GUPnPMediaCollection
  *
  * Return value: (transfer full): XML string representing this media
- * collection. g_free() after use.
+ * collection. g_free() after use. If the colleciton is not mutable, returns a
+ * copy of the original string.
  **/
 char *
 gupnp_media_collection_get_string (GUPnPMediaCollection *collection)
@@ -384,7 +523,49 @@ gupnp_media_collection_get_string (GUPnPMediaCollection *collection)
         g_return_val_if_fail (collection != NULL, NULL);
         g_return_val_if_fail (GUPNP_IS_MEDIA_COLLECTION (collection), NULL);
 
+        if (collection->priv->data)
+                return g_strdup (collection->priv->data);
+
         gupnp_didl_lite_writer_filter_tags (collection->priv->writer,
                                             DIDL_S_FILTER);
+
         return gupnp_didl_lite_writer_get_string (collection->priv->writer);
 }
+
+/**
+ * gupnp_media_collection_get_items:
+ * @collection: #GUPnPMediaCollection
+ *
+ * Return value: (transfer full)(element-type GUPnPDIDLLiteItem): A #GList
+ * containing the elemens of this collection, in proper order. Unref all items
+ * and free the list after use.
+ **/
+GList *
+gupnp_media_collection_get_items (GUPnPMediaCollection *collection)
+{
+        g_return_val_if_fail (collection != NULL, NULL);
+        g_return_val_if_fail (GUPNP_IS_MEDIA_COLLECTION (collection), NULL);
+
+        GList *tmp = NULL, *iter;
+
+        for (iter = collection->priv->items; iter != NULL; iter = iter->next) {
+                tmp = g_list_prepend (tmp, g_object_ref (iter->data));
+        }
+
+        return g_list_reverse (tmp);
+}
+
+/**
+ * gupnp_media_collection_get_mutable:
+ * @collection: #GUPnPMediaCollection
+ *
+ * Return value: #TRUE if the collections is modifiable, #FALSE otherwise.
+ **/
+gboolean
+gupnp_media_collection_get_mutable (GUPnPMediaCollection *collection)
+{
+        g_return_val_if_fail (collection != NULL, FALSE);
+        g_return_val_if_fail (GUPNP_IS_MEDIA_COLLECTION (collection), FALSE);
+
+        return collection->priv->mutable;
+}
diff --git a/libgupnp-av/gupnp-media-collection.h b/libgupnp-av/gupnp-media-collection.h
index fba0e42..aeb049c 100644
--- a/libgupnp-av/gupnp-media-collection.h
+++ b/libgupnp-av/gupnp-media-collection.h
@@ -75,25 +75,34 @@ struct _GUPnPMediaCollectionClass {
 GUPnPMediaCollection *
 gupnp_media_collection_new ();
 
+GUPnPMediaCollection *
+gupnp_media_collection_new_from_string (const char *data);
+
 void
-gupnp_media_collection_set_title  (GUPnPMediaCollection *collection,
-                                   const char           *title);
+gupnp_media_collection_set_title       (GUPnPMediaCollection *collection,
+                                        const char           *title);
 
 const char *
-gupnp_media_collection_get_title  (GUPnPMediaCollection *collection);
+gupnp_media_collection_get_title       (GUPnPMediaCollection *collection);
 
 void
-gupnp_media_collection_set_author (GUPnPMediaCollection *collection,
-                                   const char           *author);
+gupnp_media_collection_set_author      (GUPnPMediaCollection *collection,
+                                        const char           *author);
 
 const char *
-gupnp_media_collection_get_author (GUPnPMediaCollection *collection);
+gupnp_media_collection_get_author      (GUPnPMediaCollection *collection);
 
 GUPnPDIDLLiteItem *
-gupnp_media_collection_add_item   (GUPnPMediaCollection *collection);
+gupnp_media_collection_add_item        (GUPnPMediaCollection *collection);
 
 char *
-gupnp_media_collection_get_string (GUPnPMediaCollection *collection);
+gupnp_media_collection_get_string      (GUPnPMediaCollection *collection);
+
+GList *
+gupnp_media_collection_get_items       (GUPnPMediaCollection *collection);
+
+gboolean
+gupnp_media_collection_get_mutable     (GUPnPMediaCollection *collection);
 
 G_END_DECLS
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]