[rygel] mediathek: Make item creation async; fixes bgo#638269
- From: Zeeshan Ali Khattak <zeeshanak src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rygel] mediathek: Make item creation async; fixes bgo#638269
- Date: Fri, 28 Jan 2011 12:55:45 +0000 (UTC)
commit 1de8e79520526aa02a1f116e763d45352748a69e
Author: Jens Georg <mail jensge org>
Date: Sat Jan 15 19:41:16 2011 +0100
mediathek: Make item creation async; fixes bgo#638269
src/plugins/mediathek/Makefile.am | 7 +-
.../rygel-mediathek-asx-playlist-parser.vala | 107 +++++++++++++
.../mediathek/rygel-mediathek-asx-playlist.vala | 106 -------------
.../mediathek/rygel-mediathek-root-container.vala | 32 +++--
.../mediathek/rygel-mediathek-rss-container.vala | 157 +++++++++++---------
.../mediathek/rygel-mediathek-soup-utils.vala | 31 ++++
.../rygel-mediathek-video-item-factory.vala | 124 +++++++++++++++
.../mediathek/rygel-mediathek-video-item.vala | 127 ----------------
8 files changed, 370 insertions(+), 321 deletions(-)
---
diff --git a/src/plugins/mediathek/Makefile.am b/src/plugins/mediathek/Makefile.am
index 544c57e..93a1478 100644
--- a/src/plugins/mediathek/Makefile.am
+++ b/src/plugins/mediathek/Makefile.am
@@ -18,11 +18,12 @@ AM_CFLAGS = $(LIBGUPNP_CFLAGS) \
-DDATA_DIR='"$(shareddir)"' \
-include config.h
-librygel_mediathek_la_SOURCES = rygel-mediathek-asx-playlist.vala \
+librygel_mediathek_la_SOURCES = rygel-mediathek-asx-playlist-parser.vala \
rygel-mediathek-plugin.vala \
- rygel-mediathek-video-item.vala \
+ rygel-mediathek-video-item-factory.vala \
rygel-mediathek-root-container.vala \
- rygel-mediathek-rss-container.vala
+ rygel-mediathek-rss-container.vala \
+ rygel-mediathek-soup-utils.vala
librygel_mediathek_la_VALAFLAGS = --vapidir=$(top_srcdir)/src/rygel \
--pkg rygel-1.0 \
diff --git a/src/plugins/mediathek/rygel-mediathek-asx-playlist-parser.vala b/src/plugins/mediathek/rygel-mediathek-asx-playlist-parser.vala
new file mode 100644
index 0000000..14f5fbd
--- /dev/null
+++ b/src/plugins/mediathek/rygel-mediathek-asx-playlist-parser.vala
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2009-2011 Jens Georg
+ *
+ * Author: 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 Soup;
+using Xml;
+
+/**
+ * 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
+ */
+internal class Rygel.Mediathek.AsxPlaylistParser : Object {
+ private Regex normalizer;
+ private Session session;
+
+ public AsxPlaylistParser (Session session) {
+ try {
+ this.normalizer = new Regex ("(<[/]?)([a-zA-Z:]+)");
+ } catch (RegexError error) {};
+ this.session = session;
+ }
+
+ /**
+ * Get and parse the ASX file.
+ *
+ * This will fetch the ASX file using the soup session configured on
+ * configure time.As ASX seems to be a bit inconsistent with regard to 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.
+ *
+ * @param uri network location of the ASX file
+ * @return a list of uris found in this file
+ */
+ public async Gee.List<string>? parse (string uri) throws VideoItemError {
+ var message = new Soup.Message ("GET", uri);
+
+ yield SoupUtils.queue_message (session, message);
+
+ if (message.status_code != 200) {
+ throw new VideoItemError.NETWORK_ERROR
+ ("Playlist download failed: %u (%s)",
+ message.status_code,
+ Soup.status_get_phrase
+ (message.status_code));
+ }
+
+ try {
+ // lowercase all tags using regex and \L\E syntax
+ var normalized_content = this.normalizer.replace
+ ((string) 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) {
+ throw new VideoItemError.XML_PARSE_ERROR
+ ("Could not parse playlist");
+ }
+
+ var context = new XPath.Context (doc);
+ var xpath_object = context.eval ("/asx/entry/ref/@href");
+ if (xpath_object->type == XPath.ObjectType.NODESET) {
+ var uris = new LinkedList<string> ();
+ for (int i = 0;
+ i < xpath_object->nodesetval->length ();
+ i++) {
+ var item = xpath_object->nodesetval->item (i);
+ uris.add (item->children->content);
+ }
+
+ return uris;
+ }
+
+ delete doc;
+ } catch (RegexError error) {
+ throw new VideoItemError.XML_PARSE_ERROR ("Failed to normalize");
+ }
+
+ return null;
+ }
+}
diff --git a/src/plugins/mediathek/rygel-mediathek-root-container.vala b/src/plugins/mediathek/rygel-mediathek-root-container.vala
index 7b33d3d..13bdfde 100644
--- a/src/plugins/mediathek/rygel-mediathek-root-container.vala
+++ b/src/plugins/mediathek/rygel-mediathek-root-container.vala
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 Jens Georg
+ * Copyright (C) 2009-2011 Jens Georg
*
* Author: Jens Georg <mail jensge org>
*
@@ -24,29 +24,29 @@ using Gee;
using Soup;
public class Rygel.Mediathek.RootContainer : Rygel.SimpleContainer {
- internal SessionAsync session;
+ private SessionAsync session;
private static RootContainer instance;
-
- private bool on_schedule_update () {
- message("Scheduling update for all feeds....");
- foreach (var container in this.children) {
- ((RssContainer) container).update ();
- }
-
- return true;
- }
+ private static uint UPDATE_TIMEOUT = 1800;
public static RootContainer get_instance () {
if (RootContainer.instance == null) {
RootContainer.instance = new RootContainer ();
+ RootContainer.instance.init ();
}
return instance;
}
+ public static SessionAsync get_default_session () {
+ return get_instance ().session;
+ }
+
private RootContainer () {
base.root ("ZDF Mediathek");
this.session = new Soup.SessionAsync ();
+ }
+
+ private void init () {
Gee.ArrayList<int> feeds = null;
var config = Rygel.MetaConfig.get_default ();
@@ -65,6 +65,14 @@ public class Rygel.Mediathek.RootContainer : Rygel.SimpleContainer {
this.add_child_container (new RssContainer (this, id));
}
- GLib.Timeout.add_seconds (1800, on_schedule_update);
+ Timeout.add_seconds (UPDATE_TIMEOUT, () => {
+ foreach (var child in this.children) {
+ var container = child as RssContainer;
+
+ container.update ();
+ }
+
+ return true;
+ });
}
}
diff --git a/src/plugins/mediathek/rygel-mediathek-rss-container.vala b/src/plugins/mediathek/rygel-mediathek-rss-container.vala
index 983a356..6d19771 100644
--- a/src/plugins/mediathek/rygel-mediathek-rss-container.vala
+++ b/src/plugins/mediathek/rygel-mediathek-rss-container.vala
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 Jens Georg
+ * Copyright (C) 2009-2011 Jens Georg
*
* Author: Jens Georg <mail jensge org>
*
@@ -25,95 +25,106 @@ using Soup;
using Xml;
public class Rygel.Mediathek.RssContainer : Rygel.SimpleContainer {
- private uint zdf_content_id;
+ private const string uri_template = "http://www.zdf.de/ZDFmediathek/" +
+ "content/%u?view=rss";
+ private uint content_id;
private Soup.Date last_modified = null;
+ private string feed_uri;
- 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 ((string) msg.response_body.data,
- (size_t) msg.response_body.length)) {
- last_modified = new Soup.Date.from_string (
- msg.response_headers.get_one ("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 async bool parse_response (Message message) {
+ var factory = VideoItemFactory.get_default ();
+ unowned MessageBody response = message.response_body;
+
+ var doc = Xml.Parser.parse_memory ((string) response.data,
+ (int) response.length);
+ if (doc == null) {
+ warning ("Failed to parse XML document");
+
+ return false;
}
- }
- 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) {
- this.children.clear ();
- this.child_count = 0;
-
- var ctx = new XPath.Context (doc);
- var xpo = ctx.eval ("/rss/channel/title");
- if (xpo->type == Xml.XPath.ObjectType.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.XPath.ObjectType.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.add_child_item (item);
- ret = true;
- }
- catch (VideoItemError error) {
- warning ("Error creating video item: %s",
- error.message);
- }
- }
- }
- else {
- warning ("XPath query failed");
- }
+ var context = new XPath.Context (doc);
+ var xpo = context.eval ("/rss/channel/title");
+ if (xpo->type == XPath.ObjectType.NODESET &&
+ xpo->nodesetval->length () > 0) {
+ // just use first title (there should be only one)
+ this.title = xpo->nodesetval->item (0)->get_content ();
+ }
- delete doc;
- this.updated ();
+ xpo = context.eval ("/rss/channel/item");
+ if (xpo->type != XPath.ObjectType.NODESET) {
+ warning ("RSS feed doesn't have items");
+
+ return false;
}
- else {
- warning ("Failed to parse doc");
+
+ this.children.clear ();
+ this.child_count = 0;
+ for (int i = 0; i < xpo->nodesetval->length (); i++) {
+ var node = xpo->nodesetval->item (i);
+ try {
+ var item = yield factory.create (this, node);
+ if (item != null) {
+ this.add_child_item (item);
+ }
+ } catch (VideoItemError error) {
+ warning ("Error creating video item: %s",
+ error.message);
+ }
}
- return ret;
+ this.updated ();
+
+ return this.child_count > 0;
}
- 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));
+ private Message get_update_message () {
+ var message = new Soup.Message ("GET", this.feed_uri);
+ if (this.last_modified != null) {
+ var datestring = this.last_modified.to_string (DateFormat.HTTP);
+
+ debug ("Requesting change since %s", datestring);
+ message.request_headers.append("If-Modified-Since", datestring);
}
- ((RootContainer) this.parent).session.queue_message (message,
- on_feed_got);
+ return message;
+ }
+
+ public async void update () {
+ var message = this.get_update_message ();
+ yield SoupUtils.queue_message (RootContainer.get_default_session (),
+ message);
+
+ switch (message.status_code) {
+ case 304:
+ debug ("Feed at %s did not change, nothing to do.",
+ message.uri.to_string (false));
+ break;
+ case 200:
+ var success = yield this.parse_response (message);
+ if (success) {
+ var date = message.response_headers.get_one ("Date");
+
+ this.last_modified = new Soup.Date.from_string (date);
+ }
+ break;
+ default:
+ warning ("Unexpected response %u for %s: %s",
+ message.status_code,
+ message.uri.to_string (false),
+ Soup.status_get_phrase (message.status_code));
+ break;
+ }
}
public RssContainer (MediaContainer parent, uint id) {
base ("GroupId:%u".printf(id),
- parent,
- "ZDF Mediathek RSS feed %u".printf (id));
+ parent,
+ "ZDF Mediathek RSS feed %u".printf (id));
- this.zdf_content_id = id;
- update ();
+ this.content_id = id;
+ this.feed_uri = uri_template.printf (id);
+ this.update ();
}
}
diff --git a/src/plugins/mediathek/rygel-mediathek-soup-utils.vala b/src/plugins/mediathek/rygel-mediathek-soup-utils.vala
new file mode 100644
index 0000000..08ad438
--- /dev/null
+++ b/src/plugins/mediathek/rygel-mediathek-soup-utils.vala
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 Jens Georg
+ *
+ * Author: 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.
+ */
+
+internal class Rygel.Mediathek.SoupUtils : Object {
+ public static async void queue_message (Soup.Session session,
+ Soup.Message message) {
+ SourceFunc asyc_callback = queue_message.callback;
+
+ session.queue_message (message, () => { asyc_callback (); });
+ yield;
+ }
+}
diff --git a/src/plugins/mediathek/rygel-mediathek-video-item-factory.vala b/src/plugins/mediathek/rygel-mediathek-video-item-factory.vala
new file mode 100644
index 0000000..d5e4a73
--- /dev/null
+++ b/src/plugins/mediathek/rygel-mediathek-video-item-factory.vala
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2011 Jens Georg
+ *
+ * Author: 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.
+ */
+
+internal errordomain Rygel.Mediathek.VideoItemError {
+ XML_PARSE_ERROR,
+ NETWORK_ERROR
+}
+
+internal class Rygel.Mediathek.VideoItemFactory : Object {
+ private static VideoItemFactory instance;
+ private AsxPlaylistParser playlist_parser;
+
+ public static VideoItemFactory get_default () {
+ if (instance == null) {
+ instance = new VideoItemFactory ();
+ }
+
+ return instance;
+ }
+
+ public async VideoItem? create (MediaContainer parent,
+ Xml.Node *xml_item)
+ throws VideoItemError {
+ string title;
+ string playlist_url;
+ this.extract_data_from_xml (xml_item,
+ out title,
+ out playlist_url);
+
+ var resolved_uris = yield playlist_parser.parse (playlist_url);
+
+ if (resolved_uris == null || resolved_uris.size == 0) {
+ return null;
+ }
+
+ var id = Checksum.compute_for_string (ChecksumType.MD5, title);
+ var item = new VideoItem (id, parent, title);
+
+ item.mime_type = "video/x-ms-wmv";
+ item.author = "ZDF - Second German TV Channel Streams";
+
+ foreach (var uri in resolved_uris) {
+ item.add_uri (uri);
+ }
+
+ return item;
+ }
+
+ private VideoItemFactory () {
+ playlist_parser = new AsxPlaylistParser
+ (RootContainer.get_default_session ());
+ }
+
+ private bool namespace_ok (Xml.Node* node) {
+ return node->ns != null && node->ns->prefix == "media";
+ }
+
+ private void extract_data_from_xml (Xml.Node *item,
+ out string title,
+ out string playlist_url)
+ throws VideoItemError {
+ var title_node = XMLUtils.get_element (item, "title");
+ var group = XMLUtils.get_element (item, "group");
+
+ if (title_node == null) {
+ throw new VideoItemError.XML_PARSE_ERROR ("No 'title' element");
+ }
+
+ if (group == null) {
+ throw new VideoItemError.XML_PARSE_ERROR ("No 'group' element");
+ }
+
+ if (!namespace_ok (group)) {
+ throw new VideoItemError.XML_PARSE_ERROR ("Invalid namespace");
+ }
+
+ var content = XMLUtils.get_element (group, "content");
+ if (content == null) {
+ throw new VideoItemError.XML_PARSE_ERROR
+ ("'group' has no 'content' element");
+ }
+
+ // content points to the first content subnode now
+ while (content != null) {
+ var url_attribute = content->has_prop ("url");
+ if (url_attribute != null && namespace_ok (content)) {
+
+ unowned string url = url_attribute->children->content;
+ if (url.has_suffix (".asx")) {
+ playlist_url = url;
+
+ break;
+ }
+
+ }
+ content = content->next;
+ }
+
+ if (playlist_url == null) {
+ throw new VideoItemError.XML_PARSE_ERROR ("No URL found");
+ }
+
+ title = title_node->get_content ();
+ }
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]