--- /dev/null
+/* This file is part of Cinaest.
+ *
+ * Copyright (C) 2009 Philipp Zabel
+ *
+ * Cinaest 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Cinaest 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 Cinaest. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+errordomain ParserError {
+ WRONG_TAG,
+ EOF
+}
+
+public class GoogleMovie : Movie {
+ public string cinema;
+ public string runtime;
+ public string fsk;
+ public string showtimes;
+}
+
+public class GoogleParser : Object {
+ private MovieSource.ReceiveMovieFunction _get_callback;
+ char *current;
+ string cinema_name;
+ MovieFilter _filter;
+ PatternSpec pattern;
+
+ public int next_tag_offset () {
+ int i = -1;
+ while (current[++i] != '<' && current[i] != 0);
+ return i;
+ }
+
+ public void next_tag () {
+ if (current[0] == 0)
+ return;
+ current += next_tag_offset ();
+ }
+
+ public void finish_tag () {
+ while (current[0] != '>' && current[0] != 0)
+ current++;
+ if (current[0] == '>')
+ current++;
+ }
+
+ public weak string parse_tag (bool finish = true) throws Error {
+ weak string tag;
+ next_tag ();
+ int i = 1;
+ while (current[++i].isalnum ());
+ if (current[i] == 0)
+ throw new ParserError.EOF ("EOF in tag");
+ if (current[i] == '>')
+ finish = false;
+ current[i] = 0;
+ tag = (string) (current + 1);
+ current += i + 1;
+ if (finish)
+ finish_tag ();
+ return tag;
+ }
+
+ public void expect_tag (string tag) throws Error {
+ var found = parse_tag (true);
+ if (tag != found) {
+ throw new ParserError.WRONG_TAG ("Wrong tag \"%s\", expected \"%s\"",
+ found, tag);
+ }
+ }
+
+ public string parse_text () {
+ string text = ((string) current).ndup (next_tag_offset ());
+ next_tag ();
+ return text;
+ }
+
+ public void parse_attribute (string _attr, out string value) {
+ string attr;
+ if (current[0] == 0)
+ return;
+ int i = -1;
+ while (current[++i] != '=' && current[i] != '>' && current[i] != 0) {
+
+ }
+ attr = ((string) current).ndup (i);
+ current += i;
+ if (current[0] == 0)
+ return;
+ current++;
+ i = -1;
+ while (!current[++i].isspace () && current[i] != '>' && current[i] != 0) {
+ if (current[i] == '"')
+ while (current[++i] != '"' && current[i] != 0);
+ }
+ if (attr == _attr) {
+ if (current[0] == '"')
+ value = ((string) current).substring (1, i - 2);
+ else
+ value = ((string) current).ndup (i);
+ }
+ current += i;
+ }
+
+ public void skip_whitespace () {
+ if (current[0] == 0)
+ return;
+ int i = -1;
+ while (current[++i].isspace () && current[i] != 0);
+ current += i;
+ }
+
+ public string? parse_tag_attribute (string tag, string attribute) throws Error {
+ var found = parse_tag (false);
+ if (tag != found) {
+ throw new ParserError.WRONG_TAG ("Wrong tag \"%s\", expected \"%s\"",
+ found, tag);
+ }
+
+ string? value = null;
+ skip_whitespace ();
+ while (current[0] != '>' && current[0] != 0) {
+ parse_attribute (attribute, out value);
+ skip_whitespace ();
+ }
+ // Skip the closing '>' bracket
+ if (current[0] != 0)
+ current++;
+
+ return value;
+ }
+
+ public void parse_movie () throws Error {
+ expect_tag ("a"); // rating
+ expect_tag ("nobr");
+ expect_tag ("nobr");
+ weak string rating_string = parse_tag_attribute ("img", "alt").offset (6); // "Rated " ->"0.0 out of 5.0"
+ double rating = rating_string.to_double ();
+
+ expect_tag ("img");
+ expect_tag ("img");
+ expect_tag ("img");
+ expect_tag ("img");
+ expect_tag ("/nobr");
+ expect_tag ("/nobr");
+ expect_tag ("br");
+ expect_tag ("nobr");
+ expect_tag ("/nobr");
+ expect_tag ("/a");
+ expect_tag ("/font");
+ expect_tag ("/td");
+ expect_tag ("td");
+ expect_tag ("font");
+ expect_tag ("a"); // <a href="/movies?near=city&mid=hexnumber"> --> link
+ expect_tag ("b");
+ var title = convert (parse_text ().replace ("'", "'"), -1, "utf-8", "iso-8859-1"); // FIXME
+ expect_tag ("/b");
+ expect_tag ("/a");
+ expect_tag ("br");
+ var runtime_and_fsk = parse_text ().replace (" ", " ").replace ("‎", "").split (" - ");
+
+ var showtimes = "";
+ if (parse_tag () == "br") {
+ showtimes = parse_text ().replace (" ", ",");
+ expect_tag ("/font");
+ }
+
+ if (pattern == null) {
+ if (!title.has_prefix (_filter.title))
+ return;
+ } else {
+ if (!pattern.match ((uint) title.length, title, null))
+ return;
+ }
+
+ var movie = new GoogleMovie ();
+
+ movie.title = strip_tags (title);
+ movie.year = 0;
+ movie.rating = (int) (rating * 10);
+
+ movie.cinema = cinema_name;
+ if (runtime_and_fsk.length >= 2) {
+ movie.runtime = runtime_and_fsk[0];
+ movie.fsk = runtime_and_fsk[1];
+ }
+ movie.showtimes = showtimes;
+
+ // TODO - could be configurable by settings
+ if (movie.runtime != null)
+ movie.secondary = "%s - %s - %s".printf (movie.runtime, cinema_name, showtimes);
+ else
+ movie.secondary = "%s - %s".printf (cinema_name, showtimes);
+
+ _get_callback (movie);
+ }
+
+ // FIXME - this is specific for Germany
+ private string strip_tags (string title) {
+ string tag_suffix = " (OmU)"; // original audio with subtitles
+ if (title.has_suffix (tag_suffix))
+ return title.substring (0, title.length - tag_suffix.length);
+ tag_suffix = " (OV)"; // original audio
+ if (title.has_suffix (tag_suffix))
+ return title.substring (0, title.length - tag_suffix.length);
+ return title.dup ();
+ }
+
+ public void parse_cinema () throws Error {
+ expect_tag ("tr");
+ expect_tag ("td");
+ expect_tag ("a"); // --> link
+ expect_tag ("b");
+ string name = convert (parse_text ().replace ("&", "&"), -1, "utf-8", "iso-8859-1"); // FIXME
+ expect_tag ("/b");
+ expect_tag ("/a");
+ expect_tag ("br");
+ expect_tag ("font");
+ string address = parse_text ().replace (" ", " ");
+ expect_tag ("a"); // --> map
+ expect_tag ("/a");
+ expect_tag ("/font");
+ expect_tag ("/td");
+ expect_tag ("/tr");
+
+ cinema_name = name;
+ // FIXME - store cinema address for movie detail window
+ }
+
+ public void parse (ref char[] buf) throws Error {
+ current = buf;
+ next_tag ();
+ while (current[0] != 0) {
+ int i = 1;
+ while (current[i++] != '>');
+ if (((string) current).has_prefix ("<a href=\"/movies/reviews?cid="))
+ parse_movie ();
+ else if (((string) current).has_prefix("<tr valign=top><td colspan=4><a href=\"/movies?near="))
+ parse_cinema ();
+ else
+ current += i;
+ next_tag ();
+ }
+ }
+
+ public GoogleParser (MovieFilter filter, string? location, MovieSource.ReceiveMovieFunction callback) {
+ _get_callback = callback;
+ _filter = filter;
+ if (filter.title.chr(filter.title.length, '*') != null) {
+ pattern = new PatternSpec (filter.title);
+ } else {
+ pattern = null;
+ }
+ try {
+ // TODO - use google.de in Germany, also provides genres
+ string uri = "http://google.com/movies";
+ if (location != null && location != "")
+ uri += "?near=" + location;
+ File file = File.new_for_uri (uri);
+ InputStream stream = file.read (null);
+
+ char[] buf = new char[256*1024];
+ size_t nread;
+ bool ok = stream.read_all (buf, buf.length, out nread, null);
+
+ buf[nread] = 0;
+ parse (ref buf);
+ } catch (Error e) {
+ stderr.printf ("Error: %s\n", e.message);
+ }
+ }
+}
--- /dev/null
+/* This file is part of Cinaest.
+ *
+ * Copyright (C) 2009 Philipp Zabel
+ *
+ * Cinaest 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Cinaest 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 Cinaest. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+using Gtk;
+using Hildon;
+
+class GooglePlugin : Plugin {
+ List<MovieSource> sources;
+
+ public override void hello (Gtk.Window window) {
+ stdout.printf ("Google Plugin Loaded.\n");
+
+ var source = new GoogleSource ();
+
+ sources = new List<MovieSource> ();
+ sources.append (source);
+
+ // FIXME - this forces the inclusion of config.h
+ (void) Config.GETTEXT_PACKAGE;
+ }
+
+ public override unowned List<MovieSource> get_sources () {
+ return sources;
+ }
+
+ public override void settings_dialog (Gtk.Window window) {
+ GoogleSource source = (GoogleSource) sources.data;
+ var dialog = new Gtk.Dialog ();
+ dialog.set_transient_for (window);
+ dialog.set_title (_("Google plugin settings"));
+
+ var selector = new TouchSelectorEntry.text ();
+ selector.append_text ("Berlin");
+
+ var button = new PickerButton (SizeType.FINGER_HEIGHT, ButtonArrangement.HORIZONTAL);
+ button.set_title (_("Location"));
+ button.set_selector (selector);
+ button.set_value (source.location);
+
+ var content = (VBox) dialog.get_content_area ();
+ content.pack_start (button, true, true, 0);
+
+ dialog.add_button ("Done", ResponseType.ACCEPT);
+
+ dialog.show_all ();
+ int res = dialog.run ();
+ if (res == ResponseType.ACCEPT) {
+ source.location = button.get_value ();
+ }
+ dialog.destroy ();
+ }
+
+ public override unowned string get_name () {
+ return "Google";
+ }
+}
+
+class GoogleSource : MovieSource {
+ public string location;
+ public string description;
+
+ public override void get_movies (MovieFilter filter, MovieSource.ReceiveMovieFunction callback, int limit) {
+ var parser = new GoogleParser (filter, location, callback);
+ }
+
+ public override void add_movie (Movie movie) {
+ }
+
+ public override unowned string get_name () {
+ return _("Google");
+ }
+
+ public override unowned string get_description () {
+ if (location != null && location != "") {
+ description = _("Movie Showtimes near %s").printf (location);
+ } else {
+ description = _("Movie Showtimes");
+ }
+ return description;
+ }
+}
+
+[ModuleInit]
+public Type register_plugin () {
+ // types are registered automatically
+ return typeof (GooglePlugin);
+}