Add Google plugin
authorPhilipp Zabel <philipp.zabel@gmail.com>
Mon, 9 Nov 2009 10:11:10 +0000 (11:11 +0100)
committerPhilipp Zabel <philipp.zabel@gmail.com>
Wed, 11 Nov 2009 18:36:16 +0000 (19:36 +0100)
This plugin searches for movies using Google's Movie Showtimes search.
For now, the secondary info just contains cinema and today's showtimes.

Makefile.am
debian/cinaest-plugin-google.install [new file with mode: 0644]
debian/control
po/POTFILES.in
src/plugins/google-parser.vala [new file with mode: 0644]
src/plugins/google-plugin.vala [new file with mode: 0644]

index 68aa931..76f4638 100644 (file)
@@ -13,6 +13,7 @@ libexec_PROGRAMS = \
        imdb-plaintext-downloader
 
 pkglib_LTLIBRARIES = \
+       libgoogle-plugin.la \
        libimdb-plugin.la
 
 dbusservice_DATA = \
@@ -61,6 +62,31 @@ cinaest_CFLAGS = ${HILDON_CFLAGS} ${OSSO_CFLAGS} ${GMODULE_CFLAGS} \
        -DGETTEXT_PACKAGE=\"@GETTEXT_PACKAGE@\"
 cinaest_LDADD = ${HILDON_LIBS} ${OSSO_LIBS} ${GMODULE_LIBS}
 
+libgoogle_plugin_la_SOURCES = \
+       src/plugins/google-plugin.c \
+       src/genres.c \
+        src/movie.c \
+       src/movie-filter.c \
+       src/plugin-interface.c \
+       src/plugins/google-parser.c
+
+libgoogle_plugin_la_VALASOURCES = \
+       src/plugins/google-plugin.vala \
+       src/genres.vala \
+        src/movie.vala \
+       src/movie-filter.vala \
+       src/plugin-interface.vala \
+       src/plugins/google-parser.vala
+
+libgoogle_plugin_la_VALAFLAGS = --vapidir ./vapi --pkg config \
+       --pkg hildon-1
+libgoogle_plugin_la_CFLAGS = ${HILDON_CFLAGS}
+libgoogle_plugin_la_LIBADD = ${HILDON_LIBS}
+libgoogle_plugin_la_LDFLAGS = -module
+
+${libgoogle_plugin_la_SOURCES}: ${libgoogle_plugin_la_VALASOURCES}
+       ${VALAC} -C ${libgoogle_plugin_la_VALASOURCES} ${libgoogle_plugin_la_VALAFLAGS}
+
 libimdb_plugin_la_SOURCES = \
         src/plugins/imdb-plugin.c \
        src/genres.c \
diff --git a/debian/cinaest-plugin-google.install b/debian/cinaest-plugin-google.install
new file mode 100644 (file)
index 0000000..5937ff2
--- /dev/null
@@ -0,0 +1 @@
+usr/lib/cinaest/libgoogle-plugin.so*
index 0fe3091..16acc36 100644 (file)
@@ -20,3 +20,10 @@ Depends: ${shlibs:Depends}, ${misc:Depends}
 Description: Look up movies on IMDb
   This Cinaest plugin can look up movies in an offline copy of
   the Internet Movie Database (IMDb).
+
+Package: cinaest-plugin-google
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Look up movie showtimes on Google
+  This Cineast plugin contains an online interface to the
+  Movie Showtimes Google Search.
index 6e5fca9..220fac1 100644 (file)
@@ -1,5 +1,7 @@
 src/main.vala
 src/movie-list-menu.vala
 src/movie-list-window.vala
+src/plugins/imdb-plugin.vala
+src/plugins/google-plugin.vala
 src/settings-dialog.vala
 src/source-dialog.vala
diff --git a/src/plugins/google-parser.vala b/src/plugins/google-parser.vala
new file mode 100644 (file)
index 0000000..e36cce5
--- /dev/null
@@ -0,0 +1,282 @@
+/* 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 ("&#39;", "'"), -1, "utf-8", "iso-8859-1"); // FIXME
+               expect_tag ("/b");
+               expect_tag ("/a");
+               expect_tag ("br");
+               var runtime_and_fsk = parse_text ().replace ("&nbsp;", " ").replace ("&#8206;", "").split (" - ");
+
+               var showtimes = "";
+               if (parse_tag () == "br") {
+                       showtimes = parse_text ().replace ("&nbsp;", ",");
+                       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 ("&amp;", "&"), -1, "utf-8", "iso-8859-1"); // FIXME
+               expect_tag ("/b");
+               expect_tag ("/a");
+               expect_tag ("br");
+               expect_tag ("font");
+               string address = parse_text ().replace ("&nbsp;", " ");
+               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);
+               }
+       }
+}
diff --git a/src/plugins/google-plugin.vala b/src/plugins/google-plugin.vala
new file mode 100644 (file)
index 0000000..2b88eef
--- /dev/null
@@ -0,0 +1,102 @@
+/* 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);
+}