/*
* 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.
*/
/**
* SECTION:gupnp-media-collection
* @short_description: Media collection writer
*
* #GUPnPMediaCollection is a helper class for writing media collection files.
**/
#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," \
"container,dlna:lifetime"
G_DEFINE_TYPE (GUPnPMediaCollection,
gupnp_media_collection,
G_TYPE_OBJECT);
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
reparent_children (GUPnPMediaCollection *collection)
{
GList *it;
xmlNode *container_node;
container_node = gupnp_didl_lite_object_get_xml_node
(collection->priv->container);
/* Reverse iterate the list to get the correct order in XML */
it = g_list_last (collection->priv->items);
while (it) {
xmlNode *node;
node = gupnp_didl_lite_object_get_xml_node
(GUPNP_DIDL_LITE_OBJECT (it->data));
xmlUnlinkNode (node);
xmlAddChild (container_node, node);
it = it->prev;
}
}
static void
on_container_available (GUPnPMediaCollection *self,
GUPnPDIDLLiteContainer *container,
G_GNUC_UNUSED 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,
G_GNUC_UNUSED 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) {
GUPnPMediaCollectionPrivate *priv = collection->priv;
g_warning ("Failed to parse DIDL-Lite: %s", error->message);
g_error_free (error);
if (priv->container) {
g_object_unref (priv->container);
priv->container = NULL;
}
if (priv->items) {
g_list_free_full (priv->items, g_object_unref);
priv->items = NULL;
}
}
}
static void
gupnp_media_collection_init (GUPnPMediaCollection *collection)
{
collection->priv = G_TYPE_INSTANCE_GET_PRIVATE
(collection,
GUPNP_TYPE_MEDIA_COLLECTION,
GUPnPMediaCollectionPrivate);
/* Initialize as mutable and decide later on in constructed() if we
* really are. */
collection->priv->mutable = TRUE;
}
static void
gupnp_media_collection_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GUPnPMediaCollection *collection;
collection = GUPNP_MEDIA_COLLECTION (object);
switch (property_id) {
case PROP_AUTHOR:
gupnp_media_collection_set_author (collection,
g_value_get_string (value));
break;
case PROP_TITLE:
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;
}
}
static void
gupnp_media_collection_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GUPnPMediaCollection *collection;
collection = GUPNP_MEDIA_COLLECTION (object);
switch (property_id) {
case PROP_AUTHOR:
g_value_set_string
(value, gupnp_media_collection_get_author (collection));
break;
case PROP_TITLE:
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;
}
}
static void
gupnp_media_collection_constructed (GObject *object)
{
GUPnPMediaCollection *collection;
GObjectClass *object_class;
collection = GUPNP_MEDIA_COLLECTION (object);
/* 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);
}
static void
gupnp_media_collection_dispose (GObject *object)
{
GUPnPMediaCollection *collection;
GObjectClass *object_class;
collection = GUPNP_MEDIA_COLLECTION (object);
if (collection->priv->writer) {
g_object_unref (collection->priv->writer);
collection->priv->writer = NULL;
}
if (collection->priv->items) {
g_list_free_full (collection->priv->items, g_object_unref);
collection->priv->items = NULL;
}
if (collection->priv->container) {
g_object_unref (collection->priv->container);
collection->priv->container = NULL;
}
g_free (collection->priv->data);
collection->priv->data = NULL;
object_class = G_OBJECT_CLASS (gupnp_media_collection_parent_class);
object_class->dispose (object);
}
static void
gupnp_media_collection_class_init (GUPnPMediaCollectionClass *klass)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (klass);
object_class->set_property = gupnp_media_collection_set_property;
object_class->get_property = gupnp_media_collection_get_property;
object_class->constructed = gupnp_media_collection_constructed;
object_class->dispose = gupnp_media_collection_dispose;
g_type_class_add_private (klass, sizeof (GUPnPMediaCollectionPrivate));
/**
* GUPnPMediaCollection:author:
*
* The author of this media collection.
**/
g_object_class_install_property
(object_class,
PROP_AUTHOR,
g_param_spec_string ("author",
"Author",
"The author of this collection",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* GUPnPMediaCollection:title:
*
* The title of this media collection.
**/
g_object_class_install_property
(object_class,
PROP_AUTHOR,
g_param_spec_string ("title",
"Title",
"The title of this collection",
NULL,
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. If data is set upon
* construction it will override the other properties and create a
* unmutable collection parsed from data.
**/
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));
}
/**
* gupnp_media_collection_new:
*
* Create a new writable media collection.
*
* Returns: (transfer full): A new #GUPnPMediaCollection.
**/
GUPnPMediaCollection *
gupnp_media_collection_new (void)
{
return g_object_new (GUPNP_TYPE_MEDIA_COLLECTION, NULL);
}
/**
* 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;
*
* Set the title of a #GUPnPMediaCollection.
**/
void
gupnp_media_collection_set_title (GUPnPMediaCollection *collection,
const char *title)
{
GUPnPDIDLLiteContainer *container;
g_return_if_fail (GUPNP_IS_MEDIA_COLLECTION (collection));
g_return_if_fail (collection->priv->mutable);
if (title == NULL)
return;
if (collection->priv->container != NULL) {
gupnp_didl_lite_object_set_title (collection->priv->container,
title);
return;
}
if (collection->priv->writer == NULL)
collection->priv->writer = gupnp_didl_lite_writer_new (NULL);
container = gupnp_didl_lite_writer_add_container
(collection->priv->writer);
collection->priv->container = GUPNP_DIDL_LITE_OBJECT (container);
reparent_children (collection);
gupnp_didl_lite_object_set_title (collection->priv->container,
title);
}
/**
* gupnp_media_collection_get_title:
* @collection: #GUPnPMediaCollection
*
* Returns: The title of this media collection or %NULL if not set.
**/
const char *
gupnp_media_collection_get_title (GUPnPMediaCollection *collection)
{
g_return_val_if_fail (GUPNP_IS_MEDIA_COLLECTION (collection), NULL);
if (collection->priv->container == NULL)
return NULL;
return gupnp_didl_lite_object_get_title (collection->priv->container);
}
/**
* gupnp_media_collection_set_author:
* @collection: #GUPnPMediaCollection
* @author: New author of this media collection.
*
* Set the author of the media collection
**/
void
gupnp_media_collection_set_author (GUPnPMediaCollection *collection,
const char *author)
{
GUPnPDIDLLiteContainer *container;
g_return_if_fail (GUPNP_IS_MEDIA_COLLECTION (collection));
g_return_if_fail (collection->priv->mutable);
if (author == NULL)
return;
if (collection->priv->container != NULL) {
gupnp_didl_lite_object_set_creator (collection->priv->container,
author);
return;
}
if (collection->priv->writer == NULL)
collection->priv->writer = gupnp_didl_lite_writer_new (NULL);
container = gupnp_didl_lite_writer_add_container
(collection->priv->writer);
collection->priv->container = GUPNP_DIDL_LITE_OBJECT (container);
reparent_children (collection);
gupnp_didl_lite_object_set_creator (collection->priv->container,
author);
}
/**
* gupnp_media_collection_get_author:
* @collection: #GUPnPMediaCollection
*
* Returns: The author of this media collection or %NULL if not set.
**/
const char *
gupnp_media_collection_get_author (GUPnPMediaCollection *collection)
{
g_return_val_if_fail (GUPNP_IS_MEDIA_COLLECTION (collection), NULL);
if (collection->priv->container == NULL)
return NULL;
return gupnp_didl_lite_object_get_creator (collection->priv->container);
}
/**
* gupnp_media_collection_add_item:
* @collection: #GUPnPMediaCollection
*
* Return value: (transfer full): A new #GUPnPDIDLLiteItem object. Unref after
* use.
**/
GUPnPDIDLLiteItem *
gupnp_media_collection_add_item (GUPnPMediaCollection *collection)
{
GUPnPDIDLLiteItem *item = NULL;
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);
if (collection->priv->container != NULL)
item = gupnp_didl_lite_writer_add_container_child_item
(collection->priv->writer,
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_prepend (collection->priv->items,
g_object_ref (item));
/* Mandatory in DLNA for object. Not specified if mandatory for
* DIDL_S, but to avoid problems with clients reusing their normal
* DIDL-Lite parser, we set it here if the application doesn't.
*/
gupnp_didl_lite_object_set_restricted (GUPNP_DIDL_LITE_OBJECT (item),
TRUE);
return item;
}
/**
* gupnp_media_collection_get_string:
* @collection: #GUPnPMediaCollection
*
* Return value: (transfer full): XML string representing this media
* 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)
{
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)
{
GList *tmp = NULL, *iter;
g_return_val_if_fail (collection != NULL, NULL);
g_return_val_if_fail (GUPNP_IS_MEDIA_COLLECTION (collection), NULL);
for (iter = collection->priv->items; iter != NULL; iter = iter->next) {
tmp = g_list_prepend (tmp, g_object_ref (iter->data));
}
return 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;
}