[rygel] import plugins into branch
- From: Zeeshan Ali Khattak <zeeshanak src gnome org>
- To: svn-commits-list gnome org
- Subject: [rygel] import plugins into branch
- Date: Sun, 3 May 2009 15:14:28 -0400 (EDT)
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]