[rygel] import plugins into branch



commit c8401df502790273c86e793010a4cba9cca98328
Author: Jens Georg <mail jensge org>
Date:   Wed Apr 22 00:31:59 2009 +0200

    import plugins into branch
---
 configure.ac                                       |   15 ++
 src/plugins/Makefile.am                            |   10 +-
 src/plugins/folder/Makefile.am                     |   38 +++++
 src/plugins/folder/rygel-folder-plugin.vala        |   92 +++++++++++
 src/plugins/folder/rygel-folder-rootcontainer.vala |  164 ++++++++++++++++++++
 src/plugins/mediathek/Makefile.am                  |   58 +++++++
 src/plugins/mediathek/rygel-mediathek-asx.vala     |   95 +++++++++++
 .../mediathek/rygel-mediathek-container-root.vala  |   98 ++++++++++++
 .../mediathek/rygel-mediathek-container-rss.vala   |  137 ++++++++++++++++
 src/plugins/mediathek/rygel-mediathek-item.vala    |   83 ++++++++++
 src/plugins/mediathek/rygel-mediathek-plugin.vala  |   25 +++
 11 files changed, 814 insertions(+), 1 deletions(-)

diff --git a/configure.ac b/configure.ac
index f452143..ee30b82 100644
--- a/configure.ac
+++ b/configure.ac
@@ -162,6 +162,17 @@ AC_ARG_ENABLE(dvb-plugin,
 	[  --enable-dvb-plugin          build DVB plugin],,
         enable_dvb_plugin=yes)
 
+# Build Folder plugin
+AC_ARG_ENABLE(folder-plugin,
+	[  --enable-folder-plugin          build Folder plugin],,
+        enable_folder_plugin=yes)
+
+# Build Mediathek plugin
+AC_ARG_ENABLE(mediathek-plugin,
+	[  --enable-mediathek-plugin          build Mediathek plugin],,
+        enable_mediathek_plugin=yes)
+
+
 AC_SUBST(abs_top_builddir)
 
 AM_CONDITIONAL([UNINSTALLED], [test "x$enable_uninstalled" = "xyes"])
@@ -169,6 +180,8 @@ AM_CONDITIONAL([BUILD_TEST_PLUGIN], [test "x$enable_test_plugin" = "xyes"])
 AM_CONDITIONAL([BUILD_TRACKER_PLUGIN],
                [test "x$enable_tracker_plugin" = "xyes"])
 AM_CONDITIONAL([BUILD_DVB_PLUGIN], [test "x$enable_dvb_plugin" = "xyes"])
+AM_CONDITIONAL([BUILD_MEDIATHEK_PLUGIN], [test "x$enable_mediathek_plugin" = "xyes"])
+AM_CONDITIONAL([BUILD_FOLDER_PLUGIN], [test "x$enable_folder_plugin" = "xyes"])
 
 # Gettext
 GETTEXT_PACKAGE=rygel
@@ -187,6 +200,8 @@ src/rygel/Makefile
 src/ui/Makefile
 src/plugins/Makefile
 src/plugins/dvb/Makefile
+src/plugins/folder/Makefile
+src/plugins/mediathek/Makefile
 src/plugins/tracker/Makefile
 src/plugins/test/Makefile
 data/Makefile
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
index f207556..3a5515a 100644
--- a/src/plugins/Makefile.am
+++ b/src/plugins/Makefile.am
@@ -10,6 +10,14 @@ if BUILD_DVB_PLUGIN
 DVB_PLUGIN = dvb
 endif
 
-SUBDIRS = $(TEST_PLUGIN) $(TRACKER_PLUGIN) $(DVB_PLUGIN)
+if BUILD_MEDIATHEK_PLUGIN
+MEDIATHEK_PLUGIN = mediathek
+endif
+
+if BUILD_FOLDER_PLUGIN
+FOLDER_PLUGIN = folder
+endif
+
+SUBDIRS = $(TEST_PLUGIN) $(TRACKER_PLUGIN) $(DVB_PLUGIN) $(MEDIATHEK_PLUGIN) $(FOLDER_PLUGIN)
 
 MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/plugins/folder/Makefile.am b/src/plugins/folder/Makefile.am
new file mode 100644
index 0000000..cad99af
--- /dev/null
+++ b/src/plugins/folder/Makefile.am
@@ -0,0 +1,38 @@
+plugindir = $(libdir)/rygel-1.0
+
+plugin_LTLIBRARIES = librygel-media-folder.la
+
+AM_CFLAGS = $(LIBGUPNP_CFLAGS) \
+	    $(LIBGUPNP_AV_CFLAGS) \
+	    $(LIBDBUS_GLIB_CFLAGS) \
+	    $(LIBGSTREAMER_CFLAGS) \
+	    $(GEE_CFLAGS) \
+	    -I$(top_srcdir)/src/rygel -DDATA_DIR='"$(datadir)"'
+
+BUILT_SOURCES = rygel-media-folder.stamp \
+				rygel-folder-plugin.c
+
+librygel_media_folder_la_SOURCES = \
+									rygel-folder-plugin.h \
+									rygel-folder-plugin.c \
+									rygel-folder-plugin.vala \
+									rygel-folder-rootcontainer.h \
+									rygel-folder-rootcontainer.c \
+									rygel-folder-rootcontainer.vala
+
+rygel-media-folder.stamp: $(filter %.vala,$(librygel_media_folder_la_SOURCES))
+	$(VALAC) -C --vapidir=$(top_srcdir)/src/rygel \
+	--pkg rygel-1.0 --pkg cstuff \
+	--pkg gupnp-1.0 --pkg gupnp-av-1.0 \
+        --pkg libsoup-2.4 --pkg gee-1.0 --pkg libxml-2.0 $^
+	touch $@
+
+librygel_media_folder_la_LIBADD = $(LIBGUPNP_LIBS) \
+			       	   $(LIBGUPNP_AV_LIBS) \
+                          $(LIBDBUS_GLIB_LIBS) \
+	                  $(LIBGSTREAMER_LIBS) \
+				   $(GEE_LIBS)
+librygel_media_folder_la_LDFLAGS = -shared -fPIC -module -avoid-version
+
+CLEANFILES = $(BUILT_SOURCES)
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/plugins/folder/rygel-folder-plugin.vala b/src/plugins/folder/rygel-folder-plugin.vala
new file mode 100644
index 0000000..94f258b
--- /dev/null
+++ b/src/plugins/folder/rygel-folder-plugin.vala
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2008 Jens Georg <mail jensge org>.
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel 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.
+ *
+ * Rygel 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using Rygel;
+using GUPnP;
+using Gee;
+using GLib;
+
+/**
+ * Simple plugin which exposes the media contents of a directory via UPnP.
+ * 
+ * This plugin is currently meant for testing purposes only. It has several 
+ * drawbacks like:
+ *
+ * * No sorting
+ * * flat hierarchy
+ * * no metadata extraction apart from content type
+ * * no monitoring
+ */
+[ModuleInit]
+public Plugin load_plugin() {
+    Plugin plugin = new Plugin("Folder");
+
+    var resource_info = new ResourceInfo (ContentDirectory.UPNP_ID,
+                                          ContentDirectory.UPNP_TYPE,
+                                          ContentDirectory.DESCRIPTION_PATH,
+                                          typeof (Folder.FolderContentDir));
+
+    plugin.add_resource (resource_info);
+
+    return plugin;
+}
+
+public class Folder.FolderContentDir : ContentDirectory {
+
+    // FIXME get some configuration for this
+    public static const string DIR = "/home/jens/Media";
+
+    public override MediaContainer? create_root_container () {
+        return new FolderRootContainer (DIR);
+    }
+}
+
+/**
+ * Very simple media item. 
+ */
+public class Folder.FilesystemMediaItem : Rygel.MediaItem {
+    public FilesystemMediaItem(MediaContainer parent, 
+                               File file, 
+                               FileInfo file_info) {
+        string item_class;
+        var content_type = file_info.get_content_type();
+
+        if (content_type.has_prefix("video/")) {
+            item_class = MediaItem.VIDEO_CLASS;
+        }
+        else if (content_type.has_prefix("audio/")) {
+            item_class = MediaItem.AUDIO_CLASS;
+        }
+        else if (content_type.has_prefix("image/")) {
+            item_class = MediaItem.IMAGE_CLASS;
+        }
+        else {
+            return;
+        }
+
+        base(Checksum.compute_for_string(ChecksumType.MD5, file_info.get_name()), 
+             parent,
+             file_info.get_name(),
+             item_class);
+
+        this.mime_type = content_type;
+        this.uris.add(GLib.Markup.escape_text(file.get_uri()));
+    }
+}
diff --git a/src/plugins/folder/rygel-folder-rootcontainer.vala b/src/plugins/folder/rygel-folder-rootcontainer.vala
new file mode 100644
index 0000000..4819d18
--- /dev/null
+++ b/src/plugins/folder/rygel-folder-rootcontainer.vala
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2008 Jens Georg <mail jensge org>.
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel 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.
+ *
+ * Rygel 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using Gee;
+using GLib;
+using Rygel;
+
+/**
+ * MediaContainer which exposes the contents of a directory 
+ * as items
+ */
+public class Folder.FolderRootContainer : MediaContainer {
+
+    private const int MAX_CHILDREN = 10;
+
+    /**
+     * Flat storage of items found in directory
+     */
+    private ArrayList<MediaItem> items;
+
+    /**
+     * Instance of GLib.File of the directory we expose
+     */
+    private File root_dir;
+
+    // methods overridden from MediaContainer
+
+    public override void get_children(uint offset, 
+                                      uint max_count,
+                                      Cancellable? cancellable, 
+                                      AsyncReadyCallback callback)
+    {
+        uint stop = offset + max_count;
+        stop = stop.clamp(0, this.child_count);
+        var children = this.items.slice ((int)offset, (int)stop);
+        var res = new Rygel.SimpleAsyncResult<Gee.List<MediaObject>> (this, callback);
+        res.data = children;
+        res.complete_in_idle();
+    }
+
+    public override Gee.List<MediaObject>? get_children_finish (AsyncResult res) throws GLib.Error {
+        var simple_res = (Rygel.SimpleAsyncResult<Gee.List<MediaObject>>) res;
+
+        return simple_res.data;
+    }
+
+    public override void find_object (string id, 
+                                      Cancellable? cancellable,
+                                      AsyncReadyCallback callback) {
+        var res = new Rygel.SimpleAsyncResult<string> (this, callback);
+
+        res.data = id;
+        res.complete_in_idle();
+    }
+
+    public override MediaObject? find_object_finish (AsyncResult res) throws GLib.Error {
+        MediaItem item = null;
+        var id = ((Rygel.SimpleAsyncResult<string>)res).data;
+
+        foreach (MediaItem tmp in this.items) {
+            if (id == tmp.id) {
+                item = tmp;
+                break;
+            }
+        }
+
+        return item;
+    }
+
+    /**
+     * Async callback for GLib.FileEnumerator.next_files_async
+     * 
+     * Will iterate over the list of FileInformation and 
+     * create FilesystemMediaItems accordingly
+     */
+    private void on_enumerate_children_next_ready(Object obj, AsyncResult res) {
+        GLib.FileEnumerator file_enumerator = (FileEnumerator)obj;
+
+        try {
+            var list = file_enumerator.next_files_finish(res);
+            if (list != null) {
+                foreach (FileInfo info in list) {
+                    var file = this.root_dir.get_child(info.get_name());
+                    var item = new FilesystemMediaItem(this, file, info);
+                    if (item != null)
+                        items.add(item);
+                }
+                file_enumerator.next_files_async (MAX_CHILDREN, 
+                                                  Priority.DEFAULT, 
+                                                  null, 
+                                                  on_enumerate_children_next_ready);
+            }
+            else {
+                file_enumerator.close(null);
+                this.child_count = this.items.size;
+                this.updated();
+            }
+        }
+        catch (Error e) {
+            warning("Failed to enumerate children: %s", e.message);
+        }
+    }
+
+    /**
+     * Async callback for GLib.File.enumerate_children_async
+     *
+     * Kick of async iteration over result
+     */
+    private void on_enumerate_children_ready(Object obj, AsyncResult res) {
+        File file = (File)obj;
+
+        try {
+            var file_enumerator = file.enumerate_children_finish(res);
+            file_enumerator.next_files_async (MAX_CHILDREN, 
+                                              Priority.DEFAULT, 
+                                              null, 
+                                              on_enumerate_children_next_ready);
+        }
+        catch (Error e) {
+            warning("Failed to enumerate children: %s", e.message);
+        }
+    }
+
+    /**
+     * Create a new root container.
+     * 
+     * Schedules an async enumeration of the children of the 
+     * directory
+     * 
+     * @parameter directory_path, directory you want to expose
+     */
+    public FolderRootContainer (string directory_path) {
+        base.root(directory_path, 0);
+        this.items = new ArrayList<MediaItem> ();
+        this.child_count = 0;
+
+        this.root_dir = GLib.File.new_for_path(directory_path);
+
+        root_dir.enumerate_children_async(FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE + "," +
+                                          FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME + "," +
+                                          FILE_ATTRIBUTE_STANDARD_NAME,
+                                          FileQueryInfoFlags.NONE,
+                                          Priority.DEFAULT, 
+                                          null,
+                                          on_enumerate_children_ready);
+    }
+}
diff --git a/src/plugins/mediathek/Makefile.am b/src/plugins/mediathek/Makefile.am
new file mode 100644
index 0000000..fceb198
--- /dev/null
+++ b/src/plugins/mediathek/Makefile.am
@@ -0,0 +1,58 @@
+plugindir = $(libdir)/rygel-1.0
+
+plugin_LTLIBRARIES = librygel-media-mediathek.la
+
+AM_CFLAGS = \
+	$(LIBGUPNP_CFLAGS) \
+	$(LIBGUPNP_AV_CFLAGS) \
+	$(LIBDBUS_GLIB_CFLAGS) \
+	$(LIBGSTREAMER_CFLAGS) \
+	$(LIBGCONF_CFLAGS) \
+	$(GEE_CFLAGS) \
+	-I$(top_srcdir)/src/rygel \
+	-DDATA_DIR='"$(datadir)"'
+
+BUILT_SOURCES = rygel-media-mediathek.stamp \
+				rygel-mediathek-plugin.c \
+				rygel-mediathek-item.c \
+				rygel-mediathek-container-root.c \
+				rygel-mediathek-container-rss.c
+
+librygel_media_mediathek_la_SOURCES = \
+	rygel-mediathek-asx.c \
+	rygel-mediathek-asx.vala \
+	rygel-mediathek-plugin.c \
+	rygel-mediathek-plugin.vala \
+	rygel-mediathek-item.c \
+	rygel-mediathek-item.vala \
+	rygel-mediathek-container-root.c \
+	rygel-mediathek-container-root.vala \
+	rygel-mediathek-container-rss.c \
+	rygel-mediathek-container-rss.vala 
+
+
+rygel-media-mediathek.stamp: $(filter %.vala,$(librygel_media_mediathek_la_SOURCES))
+	$(VALAC) -C --vapidir=$(top_srcdir)/src/rygel \
+	--pkg rygel-1.0 \
+	--pkg cstuff \
+	--pkg gupnp-1.0 \
+	--pkg gupnp-av-1.0 \
+	--pkg libsoup-2.4 \
+	--pkg gee-1.0 \
+	--pkg libxml-2.0 \
+	--pkg gconf-2.0 \
+	$^
+	touch $@
+
+librygel_media_mediathek_la_LIBADD = \
+	$(LIBGUPNP_LIBS) \
+	$(LIBGUPNP_AV_LIBS) \
+	$(LIBDBUS_GLIB_LIBS) \
+	$(LIBGSTREAMER_LIBS) \
+	$(LIBGCONF_LIBS) \
+	$(GEE_LIBS)
+
+librygel_media_mediathek_la_LDFLAGS = -shared -fPIC -module -avoid-version
+
+CLEANFILES = $(BUILT_SOURCES)
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/plugins/mediathek/rygel-mediathek-asx.vala b/src/plugins/mediathek/rygel-mediathek-asx.vala
new file mode 100644
index 0000000..14203f9
--- /dev/null
+++ b/src/plugins/mediathek/rygel-mediathek-asx.vala
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2009 Jens Georg
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using GLib;
+using Gee;
+using Soup;
+using Xml;
+
+public errordomain ZdfMediathek.AsxPlaylistError {
+    XML_ERROR,
+    NETWORK_ERROR
+}
+
+/**
+ * This class is a simple ASX playlist parser
+ * 
+ * It does nothing but extracting all href tags from an ASX
+ * and ignore all of the other information that may be in it
+ * 
+ * This parser is //only// intended to work with the simple 
+ * ASX files presented by the ZDF Mediathek streaming server
+ */
+public class ZdfMediathek.AsxPlaylist : Object {
+    public ArrayList<string> uris;
+    private string uri;
+
+    public AsxPlaylist(string uri) {
+        this.uris = new ArrayList<string>();
+        this.uri = uri;
+    }
+
+    /** 
+     * Get and parse the ASX file.
+     *
+     * This will fetch the ASX file represented by an uri
+     * using a synchronous soup session. As ASX seems to be
+     * a bit inconsistent wrt tag case all the tags are
+     * converted to lowercase. A XPath query is then used
+     * to extract all of the href attributes for every entry
+     * in the file
+     */
+    public void parse() throws AsxPlaylistError {
+        // FIXME make async using global soup session
+        var session = new Soup.SessionSync();
+        var message = new Soup.Message("GET",
+            this.uri);
+
+        session.send_message(message);
+        if (message.status_code == 200) {
+            try {
+                // lowercase all tags using regex and \L\E syntax
+                var normalizer = new Regex("(<[/]?)([a-zA-Z:]+)");
+                string normalized_content = normalizer.replace(message.response_body.data,
+                        (long)message.response_body.length, 0, "\\1\\L\\2\\E");
+                var doc = Parser.parse_memory(normalized_content, (int)normalized_content.length);
+
+                if (doc != null) {
+                    var ctx = new XPathContext(doc);
+                    var xpo = ctx.eval("/asx/entry/ref/@href");
+                    if (xpo->type == XPathObjectType.NODESET) {
+                        for (int i = 0; i < xpo->nodesetval->length(); i++) {
+                            var item = xpo->nodesetval->item(i);
+                            uris.add(item->children->content);
+                        }
+                    }
+                }
+                else {
+                    throw new AsxPlaylistError.XML_ERROR("Could not received XML");
+                }
+            }
+            catch (RegexError error) { }
+        }
+        else {
+            throw new AsxPlaylistError.NETWORK_ERROR("Could not download playlist, error code was %u (%s)".printf(message.status_code, 
+                Soup.status_get_phrase(message.status_code)));
+        }
+    }
+}
diff --git a/src/plugins/mediathek/rygel-mediathek-container-root.vala b/src/plugins/mediathek/rygel-mediathek-container-root.vala
new file mode 100644
index 0000000..abd1aff
--- /dev/null
+++ b/src/plugins/mediathek/rygel-mediathek-container-root.vala
@@ -0,0 +1,98 @@
+using Gee;
+using Rygel;
+using Soup;
+using GConf;
+
+public class ZdfMediathek.ZdfRootContainer : MediaContainer {
+    private ArrayList<RssContainer> items;
+    internal SessionAsync session;
+    private GConf.Client gconf;
+
+    public override void get_children(uint offset, uint max_count, 
+        Cancellable? cancellable, AsyncReadyCallback callback)
+    {
+        uint stop = offset + max_count;
+        stop = stop.clamp(0, this.child_count);
+        var children = this.items.slice ((int)offset, (int)stop);
+
+        var res = new Rygel.SimpleAsyncResult<Gee.List<MediaObject>> (this,
+        callback);
+        res.data = children;
+        res.complete_in_idle();
+    }
+
+    public override Gee.List<MediaObject>? get_children_finish (AsyncResult
+    res)
+                                                         throws GLib.Error {
+        var simple_res = (Rygel.SimpleAsyncResult<Gee.List<MediaObject>>) res;
+
+        return simple_res.data;
+    }
+    public override void find_object (string id, Cancellable? cancellable,
+    AsyncReadyCallback callback) {
+        var res = new Rygel.SimpleAsyncResult<string> (this,
+            callback);
+
+        res.data = id;
+        res.complete_in_idle();
+    }
+
+    public override MediaObject? find_object_finish (AsyncResult res) throws
+    GLib.Error {
+        MediaObject item = null;
+        var id = ((Rygel.SimpleAsyncResult<string>)res).data;
+
+        foreach (RssContainer tmp in this.items) {
+            if (id == tmp.id) {
+                item = tmp;
+                break;
+            }
+        }
+
+        if (item == null) {
+            foreach (RssContainer container in this.items) {
+                item = container.find_object_sync(id);
+                if (item != null) {
+                    break;
+                }
+            }
+        }
+
+        return item;
+    }
+
+    private bool on_schedule_update() {
+        message("Scheduling update for all feeds....");
+        foreach (RssContainer container in this.items) {
+            container.update();
+        }
+
+        return true;
+    }
+
+    public ZdfRootContainer() {
+        base.root("ZDF Mediathek", 0);
+        this.session = new Soup.SessionAsync ();
+        this.items = new ArrayList<RssContainer>();
+
+        this.gconf = GConf.Client.get_default();
+
+        // get subscribed feeds
+        try {
+            // TODO get gconf prefix from Rygel
+            unowned GLib.SList<int> feeds = 
+                gconf.get_list("/apps/rygel/ZDFMediathek/rss",
+                                GConf.ValueType.INT);
+
+            foreach (int id in feeds) {
+                this.items.add(new RssContainer(this, id));
+            }
+        } catch (GLib.Error error) {
+            message("Could not get RSS items from GConf, using defaults");
+            this.items.add(new RssContainer(this, 508));
+        }
+
+        this.child_count = this.items.size;
+        GLib.Timeout.add_seconds(1800, on_schedule_update);
+    }
+}
diff --git a/src/plugins/mediathek/rygel-mediathek-container-rss.vala b/src/plugins/mediathek/rygel-mediathek-container-rss.vala
new file mode 100644
index 0000000..1973621
--- /dev/null
+++ b/src/plugins/mediathek/rygel-mediathek-container-rss.vala
@@ -0,0 +1,137 @@
+using Gee;
+using Soup;
+using Rygel;
+using Xml;
+
+public class ZdfMediathek.RssContainer : MediaContainer {
+    private ArrayList<MediaItem> items;
+    private uint zdf_content_id;
+    private Soup.Date last_modified = null;
+
+    private void on_feed_got (Soup.Session session, Soup.Message msg) {
+        switch (msg.status_code) {
+            case 304:
+                message("Feed has not changed, nothing to do");
+                break;
+            case 200:
+                if (parse_response(
+                        msg.response_body.data, 
+                        (size_t)msg.response_body.length)) {
+                    last_modified = new Soup.Date.from_string(msg.response_headers.get("Date"));
+                }
+                break;
+            default:
+                // TODO Need to handle redirects....
+                warning("Got unexpected response %u (%s)",
+                    msg.status_code,
+                    Soup.status_get_phrase (msg.status_code));
+                break;
+        }
+    }
+
+    private bool parse_response(string data, size_t length) {
+        bool ret = false;
+        Xml.Doc* doc = Xml.Parser.parse_memory(data, (int)length);
+        if (doc != null) {
+            items.clear();
+            var ctx = new XPathContext(doc);
+            var xpo = ctx.eval("/rss/channel/title");
+            if (xpo->type == Xml.XPathObjectType.NODESET &&
+                xpo->nodesetval->length() > 0) {
+                // just use first title (there should be only one)
+                this.title = xpo->nodesetval->item(0)->get_content();
+            }
+
+            xpo = ctx.eval("/rss/channel/item");
+            if (xpo->type == Xml.XPathObjectType.NODESET) {
+                for (int i = 0; i < xpo->nodesetval->length(); i++) {
+                    Xml.Node* node = xpo->nodesetval->item(i);
+                    try {
+                        var item = VideoItem.create_from_xml(this, node);
+                        this.items.add(item);
+                        ret = true;
+                    }
+                    catch (VideoItemError error) {
+                        GLib.message("Error creating video item: %s", 
+                            error.message); 
+                    }
+                }
+            }
+            else {
+                warning("XPath query failed");
+            }
+
+            delete doc;
+            this.child_count = items.size;
+            this.updated();
+        }
+        else {
+            warning("Failed to parse doc");
+        }
+
+        return ret;
+    }
+
+    public override void get_children(uint offset, uint max_count, 
+        Cancellable? cancellable, AsyncReadyCallback callback) {
+        debug("get_children called");
+        uint stop = offset + max_count;
+        stop = stop.clamp(0, this.child_count);
+        var children = this.items.slice ((int)offset, (int)stop);
+
+        var res = new Rygel.SimpleAsyncResult<Gee.List<MediaObject>> (this,
+        callback);
+        res.data = children;
+        res.complete_in_idle();
+    }
+
+    public override Gee.List<MediaObject>? get_children_finish (AsyncResult res) throws GLib.Error {
+        var simple_res = (Rygel.SimpleAsyncResult<Gee.List<MediaObject>>) res;
+
+        return simple_res.data;
+    }
+
+    public override void find_object (string id, Cancellable? cancellable, AsyncReadyCallback callback) {
+        var res = new Rygel.SimpleAsyncResult<string> (this,
+            callback);
+
+        res.data = id;
+        res.complete_in_idle();
+    }
+
+    public override MediaObject? find_object_finish (AsyncResult res) throws GLib.Error {
+        var id = ((Rygel.SimpleAsyncResult<string>)res).data;
+        return find_object_sync(id);
+    }
+
+    public MediaObject? find_object_sync(string id) {
+        MediaItem item = null;
+        foreach (MediaItem tmp in this.items) {
+            if (id == tmp.id) {
+                item = tmp;
+                break;
+            }
+        }
+
+        return item;
+    }
+
+    public void update() {
+        var message = new Soup.Message ("GET",
+            "http://www.zdf.de/ZDFmediathek/content/%u?view=rss".printf(zdf_content_id)); 
+        if (last_modified != null) {
+            debug("Requesting change since %s", last_modified.to_string(DateFormat.HTTP));
+            message.request_headers.append("If-Modified-Since", last_modified.to_string(DateFormat.HTTP));
+        }
+
+        ((ZdfRootContainer)this.parent).session.queue_message (message, on_feed_got);
+    }
+
+    public RssContainer (MediaContainer parent, uint id) {
+        base("GroupId:%u".printf(id), parent, "ZDF Mediathek RSS feed %u".printf(id), 0);
+        this.items = new ArrayList<MediaItem> ();
+        this.child_count = 0;
+        this.zdf_content_id = id;
+        update();
+    }
+}
diff --git a/src/plugins/mediathek/rygel-mediathek-item.vala b/src/plugins/mediathek/rygel-mediathek-item.vala
new file mode 100644
index 0000000..a5e9115
--- /dev/null
+++ b/src/plugins/mediathek/rygel-mediathek-item.vala
@@ -0,0 +1,83 @@
+using GLib;
+using Rygel;
+using Xml;
+
+public errordomain ZdfMediathek.VideoItemError {
+    XML_PARSE_ERROR
+}
+
+public class ZdfMediathek.VideoItem : Rygel.MediaItem {
+    private VideoItem(MediaContainer parent, string title) {
+        base(Checksum.compute_for_string(ChecksumType.MD5, title), parent, title, MediaItem.VIDEO_CLASS);
+        this.mime_type = "video/x-ms-asf";
+        this.author = "ZDF - Zweites Deutsches Fernsehen";
+    }
+
+    private static bool namespace_ok(Xml.Node* node) {
+        return node->ns != null && node->ns->prefix == "media";
+    }
+
+    public static VideoItem create_from_xml(MediaContainer parent, Xml.Node *item) throws VideoItemError {
+        string title = null;
+        VideoItem video_item = null;
+        AsxPlaylist asx = null;
+
+        for (Xml.Node* item_child = item->children; item_child != null; item_child = item_child->next)
+        {
+            switch (item_child->name) {
+                case "title":
+                    title = item_child->get_content();
+                    break;
+                case "group":
+                    if (namespace_ok(item_child)) {
+                        for (Xml.Node* group = item_child->children; 
+                             group != null;
+                             group = group->next) {
+                            if (group->name == "content") {
+                                if (namespace_ok(group)) {
+                                    Xml.Attr* attr = group->has_prop("url");
+                                    if (attr != null) {
+                                        var url = attr->children->content;
+                                        if (url.has_suffix(".asx")) {
+                                            debug("Found Url %s", url);
+                                            asx = new AsxPlaylist(url);
+                                            asx.parse();
+                                        }
+                                    }
+                                    else {
+                                        throw new VideoItemError.XML_PARSE_ERROR("group node has url property");
+                                    }
+                                }
+                                else {
+                                    throw new VideoItemError.XML_PARSE_ERROR("invalid or no namespace");
+                                }
+                            }
+                        }
+                    }
+                    else {
+                        throw new VideoItemError.XML_PARSE_ERROR("invalid or no namespace on group node");
+                    }
+                    break;
+                default:
+                    //debug("Got node->name %s", node->name);
+                    break;
+             }
+
+        }
+        if (title == null) {
+            throw new VideoItemError.XML_PARSE_ERROR("Could not find title");
+        }
+
+
+        if (asx == null) {
+            throw new VideoItemError.XML_PARSE_ERROR("Could not find uris");
+        }
+
+        video_item = new VideoItem(parent, title);
+        foreach (string uri in asx.uris) {
+            video_item.uris.add(uri);
+        }
+
+        return video_item;
+    }
+}
diff --git a/src/plugins/mediathek/rygel-mediathek-plugin.vala b/src/plugins/mediathek/rygel-mediathek-plugin.vala
new file mode 100644
index 0000000..0e70f93
--- /dev/null
+++ b/src/plugins/mediathek/rygel-mediathek-plugin.vala
@@ -0,0 +1,25 @@
+using Rygel;
+using GUPnP;
+
+[ModuleInit]
+public Plugin load_plugin() {
+    Plugin plugin = new Plugin("ZDFMediathek");
+
+    var resource_info = new ResourceInfo (ContentDirectory.UPNP_ID,
+                                          ContentDirectory.UPNP_TYPE,
+                                          ContentDirectory.DESCRIPTION_PATH,
+                                          typeof (ZdfMediathek.ZdfContentDir));
+
+    plugin.add_resource (resource_info);
+
+    return plugin;
+}
+
+public class ZdfMediathek.ZdfContentDir : ContentDirectory {
+    public override MediaContainer? create_root_container () {
+        return new ZdfRootContainer ();
+    }
+}
+
+
+



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