libexec_PROGRAMS = \
google-poster-downloader \
- imdb-plaintext-downloader
+ imdb-plaintext-downloader \
+ cinaest-google-backend
pkglib_LTLIBRARIES = \
libcatalog-plugin.la \
dbusservice_DATA = \
data/org.maemo.cinaest.service \
data/org.maemo.cinaest.IMDb.service \
+ data/org.maemo.cinaest.GoogleShowtimes.service \
data/org.maemo.movieposter.GoogleImages.service
desktopentry_DATA = \
libgoogle_plugin_la_SOURCES = \
src/plugins/google-plugin.c \
src/plugins/calendar-backend-adapter.cc \
- src/plugins/calendar-backend.c \
- src/plugins/google-parser.c
+ src/plugins/calendar-backend.c
libgoogle_plugin_la_VALASOURCES = \
src/plugins/google-plugin.vala \
- src/plugins/calendar-backend.vala \
- src/plugins/google-parser.vala
+ src/plugins/calendar-backend.vala
libgoogle_plugin_la_VALAFLAGS = --vapidir ./vapi --pkg config --pkg cinaest \
- --pkg hildon-1 --pkg libhildonmime --pkg libosso
-libgoogle_plugin_la_CFLAGS = ${CINAEST_CFLAGS} ${HILDON_CFLAGS} ${HILDONMIME_CFLAGS} ${OSSO_CFLAGS}
+ --pkg dbus-glib-1 --pkg hildon-1 --pkg gconf-2.0 --pkg json-glib-1.0 \
+ --pkg libhildonmime --pkg libosso
+libgoogle_plugin_la_CFLAGS = ${CINAEST_CFLAGS} ${GCONF_CFLAGS} ${HILDON_CFLAGS} ${HILDONMIME_CFLAGS} ${JSON_CFLAGS} ${OSSO_CFLAGS}
libgoogle_plugin_la_CPPFLAGS = ${CALENDAR_CFLAGS}
-libgoogle_plugin_la_LIBADD = ${CALENDAR_LIBS} ${CINAEST_LIBS} ${HILDON_LIBS} ${HILDONMIME_LIBS} ${OSSO_LIBS}
+libgoogle_plugin_la_LIBADD = ${CALENDAR_LIBS} ${CINAEST_LIBS} ${GCONF_LIBS} ${HILDON_LIBS} ${HILDONMIME_LIBS} ${JSON_LIBS} ${OSSO_LIBS}
libgoogle_plugin_la_LDFLAGS = -module
src/plugins/google-plugin.c: ${libgoogle_plugin_la_VALASOURCES}
src/plugins/imdb-plugin.c: ${libimdb_plugin_la_VALASOURCES}
${VALAC} -C ${libimdb_plugin_la_VALASOURCES} ${libimdb_plugin_la_VALAFLAGS}
+cinaest_google_backend_SOURCES = \
+ src/backends/google/google-backend.c \
+ src/backends/google/google-parser.c
+
+cinaest_google_backend_VALASOURCES = \
+ src/backends/google/google-backend.vala \
+ src/backends/google/google-parser.vala
+
+cinaest_google_backend_VALAFLAGS = --vapidir ./vapi --pkg dbus-glib-1 \
+ --pkg gconf-2.0 --pkg gee-1.0 --pkg gio-2.0
+cinaest_google_backend_CFLAGS = ${DBUS_CFLAGS} ${GCONF_CFLAGS} ${GEE_CFLAGS} ${GIO_CFLAGS}
+cinaest_google_backend_LDADD = ${DBUS_LIBS} ${GCONF_LIBS} ${GEE_LIBS} ${GIO_LIBS}
+
+src/backends/google/google-backend.c: ${cinaest_google_backend_VALASOURCES}
+ ${VALAC} -C ${cinaest_google_backend_VALASOURCES} ${cinaest_google_backend_VALAFLAGS}
+
imdb_plaintext_downloader_SOURCES = \
src/imdb/imdb-plaintext-downloader.c \
src/imdb/imdb-ftp-downloader.c \
$(patsubst %.vala,%.c,${libgoogle_plugin_la_VALASOURCES}) \
${libimdb_plugin_la_SOURCES} \
${imdb_plaintext_downloader_SOURCES} \
- ${google_poster_downloader_SOURCES}
+ ${google_poster_downloader_SOURCES} \
+ ${cinaest_google_backend_SOURCES}
AC_SUBST(CURL_LIBS)
AC_SUBST(CURL_CFLAGS)
+PKG_CHECK_MODULES(GEE, gee-1.0)
+AC_SUBST(GEE_LIBS)
+AC_SUBST(GEE_CFLAGS)
+
PKG_CHECK_MODULES(GMODULE, gmodule-2.0 >= 2.20.3)
AC_SUBST(GMODULE_LIBS)
AC_SUBST(GMODULE_CFLAGS)
AC_SUBST(HILDONMIME_LIBS)
AC_SUBST(HILDONMIME_CFLAGS)
+PKG_CHECK_MODULES(JSON, json-glib-1.0)
+AC_SUBST(JSON_LIBS)
+AC_SUBST(JSON_CFLAGS)
+
AC_ARG_ENABLE([maemo-launcher],
[AS_HELP_STRING([--enable-maemo-launcher],
[build with maemo-launcher support])],
po/Makefile
data/org.maemo.cinaest.service
data/org.maemo.cinaest.IMDb.service
+ data/org.maemo.cinaest.GoogleShowtimes.service
data/org.maemo.movieposter.GoogleImages.service
data/cinaest.desktop
])
--- /dev/null
+[D-BUS Service]
+Name=org.maemo.cinaest.GoogleShowtimes
+Exec=@libexecdir@/cinaest-google-backend
usr/lib/cinaest/libgoogle-plugin.so*
+usr/share/dbus-1/services/org.maemo.cinaest.GoogleShowtimes.service
+usr/libexec/cinaest-google-backend
--- /dev/null
+using Gee;
+
+public Gee.HashMap <string, MovieSearch> searches;
+public DBus.Connection conn;
+
+[DBus (name = "org.maemo.cinaest.MovieSearch", signals="movies_found")]
+public class MovieSearch : Object {
+ public int id;
+ bool aborted;
+ string title;
+ GoogleParser parser;
+ GLib.List<GoogleMovie> results;
+ GoogleMovieService service;
+
+ // D-Bus API
+
+ public void abort () {
+ print ("aborting\n");
+ aborted = true;
+ }
+
+ public void start (string query) {
+ parser = new GoogleParser ();
+ title = query;
+
+ query_async.begin ();
+ }
+
+ public signal void movies_found (string[] movies, bool finished);
+
+ // Internal methods
+
+ internal MovieSearch (GoogleMovieService _service) {
+ service = _service;
+ }
+
+ private async void query_async () {
+ var gc = GConf.Client.get_default ();
+
+ results = new GLib.List<GoogleMovie> ();
+
+ string location = null;
+ try {
+ location = gc.get_string ("/apps/cinaest/location");
+ } catch (Error e) {
+ stdout.printf ("Error getting GConf option: %s\n", e.message);
+ }
+ if (location == null)
+ location = "";
+
+ int n = yield parser.query (title, location, receive_movie);
+
+ try {
+ var locations = ((SList<string>) gc.get_list ("/apps/cinaest/locations", GConf.ValueType.STRING)).copy ();
+ if (insert_location_sorted (parser.location, ref locations)) {
+ gc.set_list ("/apps/cinaest/locations", GConf.ValueType.STRING, locations);
+ }
+ if (location != parser.location) {
+ location = parser.location;
+ gc.set_string ("/apps/cinaest/location", location);
+ }
+ } catch (Error e2) {
+ stdout.printf ("GConf error: %s\n", e2.message);
+ }
+
+ print ("got %d movies\n", n);
+
+ var m = new string[results.length ()];
+ int i = 0;
+ for (unowned GLib.List<GoogleMovie> node = results.first (); node != null; node = node.next) {
+ m[i++] = "{\"title\":\"%s\",\"rating\":%f,\"showtimes\":\"%s\",\"cinema_name\":\"%s\",\"cinema_phone\":\"%s\"}".printf (node.data.title, node.data.rating, node.data.showtimes, node.data.cinema.name, node.data.cinema.phone);
+ }
+ movies_found (m, true);
+ service.timeout_quit ();
+ }
+
+ private bool insert_location_sorted (string? location, ref SList<string> locations) {
+ if (location == null)
+ return false;
+ if (locations != null) {
+ for (unowned SList<string> l = locations; l != null; l = l.next) {
+ if (l.data == location) {
+ return false;
+ }
+ if (l.data > location) {
+ l.insert (location, 0);
+ return true;
+ }
+ }
+ }
+ locations.append (location);
+ return true;
+ }
+
+ private void receive_movie (GoogleMovie movie) {
+ // print ("received %s\n", movie.title);
+ results.append (movie);
+ }
+}
+
+[DBus (name = "org.maemo.cinaest.MovieService")]
+public class GoogleMovieService : Object {
+ private MainLoop loop;
+ private uint source_id;
+
+ // D-Bus API
+
+ public string new_search (DBus.BusName sender) {
+ print ("new search requested by %s\n", sender);
+ var search = new MovieSearch (this);
+
+ search.id = searches.size;
+ string path = "/org/maemo/cinaest/googleshowtimes/search%d".printf (search.id);
+ conn.register_object (path, search);
+
+ searches.set (path, search);
+
+ return path;
+ }
+
+ public void unregister (string path) {
+
+ print ("unregistering %s\n", path);
+ searches.remove (path);
+ print ("%d\n", searches.size);
+ }
+
+ // Internal methods
+
+ internal GoogleMovieService () {
+ loop = new MainLoop (null);
+ }
+
+ internal void timeout_quit () {
+ // With every change we reset the timer to 3min
+ if (source_id != 0) {
+ Source.remove (source_id);
+ }
+ source_id = Timeout.add_seconds (180, quit);
+ }
+
+ private bool quit () {
+ loop.quit ();
+
+ // One-shot only
+ return false;
+ }
+
+ internal void run () {
+ loop.run ();
+ }
+}
+
+void on_client_lost (DBus.Object sender, string name, string prev, string newp) {
+ if (newp == "") {
+ // We lost a client
+ print ("lost a client: prev:%s\n", prev);
+ searches.remove (prev);
+ }
+}
+
+void main () {
+ var loop = new MainLoop (null, false);
+
+ try {
+ conn = DBus.Bus.get (DBus.BusType. SESSION);
+
+ searches = new Gee.HashMap <string, MovieSearch> ();
+
+ dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus");
+
+ uint res = bus.request_name ("org.maemo.cinaest.GoogleShowtimes", (uint) 0);
+
+ if (res == DBus.RequestNameReply.PRIMARY_OWNER) {
+ var server = new GoogleMovieService ();
+
+ conn.register_object ("/org/maemo/cinaest/movies", server);
+
+ bus.NameOwnerChanged.connect (on_client_lost);
+
+ server.timeout_quit ();
+ server.run ();
+ }
+ } catch (Error e) {
+ stderr.printf ("Oops: %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/>.
+ */
+
+errordomain ParserError {
+ WRONG_TAG,
+ EOF
+}
+
+public class Cinema {
+ public string name;
+ public string address;
+ public string phone;
+
+ public Cinema (string _name) {
+ name = _name;
+ }
+}
+
+public class GoogleMovie {
+ public string title;
+ public int rating;
+ public string secondary;
+ public Cinema cinema;
+ public string runtime;
+ public string fsk;
+ public string showtimes;
+}
+
+public class GoogleParser : Object {
+ char *current;
+ Cinema last_cinema;
+ public string location;
+ string _title;
+ PatternSpec pattern;
+
+ public delegate void ReceiveMovie (GoogleMovie movie);
+ public ReceiveMovie _get_callback;
+
+ 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 string unescape_unicode (string s) {
+ string result = "";
+ int i, j;
+ long l = s.length;
+
+ for (i = 0; i < l; i++) {
+ if (s[i] == '&' && s[i + 1] == '#') {
+ for (j = i + 2; j < l; j++) {
+ if (!s[j].isdigit ())
+ break;
+ if (s[j] == ';')
+ break;
+ }
+ if (s[j] == ';') {
+ int codepoint = s.substring (i + 2, j - i - 2).to_int ();
+ char[] buf = new char[6];
+ ((unichar) codepoint).to_utf8 ((string) buf);
+ result += (string) buf;
+ i = j;
+ continue;
+ }
+ }
+ if (s.offset (i).has_prefix ("&")) {
+ result += "&";
+ i += 4;
+ continue;
+ }
+ if (s.offset (i).has_prefix (""")) {
+ result += "\"";
+ i += 5;
+ continue;
+ }
+ result += s.substring (i, 1);
+ }
+
+ return result;
+ }
+
+ public void parse_movie () throws Error {
+ expect_tag ("div"); // class=movie
+ expect_tag ("div"); // class=name
+ expect_tag ("a"); // href="/movies?near=city&mid=..."
+ expect_tag ("span"); // dir=ltr
+ var title = unescape_unicode (convert (parse_text (), -1, "utf-8", "iso-8859-1")); // FIXME
+ expect_tag ("/span");
+ expect_tag ("/a");
+ expect_tag ("/div");
+ expect_tag ("span"); // class=info
+ string[] runtime_and_fsk = {};
+ double rating = 0.0;
+ var tag = parse_tag ();
+ if (tag == "a") {
+ // Trailer
+ expect_tag ("/a");
+ tag = parse_tag ();
+ }
+ if (tag == "a") {
+ // IMDb
+ expect_tag ("/a");
+ tag = parse_tag ();
+ }
+ if (tag == "nobr") {
+ expect_tag ("nobr");
+ string rating_string = parse_tag_attribute ("img", "alt").offset (6); // "Rated " ->"0.0 out of 5.0"
+ rating = rating_string.to_double ();
+ expect_tag ("img");
+ expect_tag ("img");
+ expect_tag ("img");
+ expect_tag ("img");
+ expect_tag ("/nobr");
+ expect_tag ("/nobr");
+ runtime_and_fsk = parse_text ().replace ("‎", "").offset (3).split (" - ");
+ if (parse_tag () == "a") {
+ // Trailer
+ expect_tag ("/a");
+ if (parse_tag () == "a") {
+ // IMDb link
+ expect_tag ("/a");
+ expect_tag ("/span");
+ }
+ }
+ }
+ expect_tag ("div"); // class=times
+ var showtimes = parse_text ().replace (" ", ",");
+ while (parse_tag () == "a") {
+ showtimes += parse_text () + ",";
+ expect_tag ("/a");
+ }
+
+ if (pattern == null) {
+ if (!title.has_prefix (_title))
+ return;
+ } else {
+ if (!pattern.match ((uint) title.length, title, null))
+ return;
+ }
+
+ var movie = new GoogleMovie ();
+
+ movie.title = strip_tags (title).replace ("\"", "\\\"");
+ movie.rating = (int) (rating * 10);
+
+ movie.cinema = last_cinema;
+ 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, last_cinema.name, showtimes);
+ else
+ movie.secondary = "%s - %s".printf (last_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 ("div"); // class=theater
+ expect_tag ("div"); // class=desc id=theater_...
+ expect_tag ("h2"); // class=name
+ expect_tag ("a"); // href="/movies?near=city&tid=..."
+ expect_tag ("span"); // dir=ltr
+ var name = unescape_unicode (convert (parse_text (), -1, "utf-8", "iso-8859-1")); // FIXME
+ expect_tag ("/span");
+ expect_tag ("/a");
+ expect_tag ("/h2");
+ expect_tag ("div"); // class=info
+ var address_and_phone = parse_text ().replace (" ", " ").split (" - ");
+ string address = null;
+ string phone = null;
+ if (address_and_phone.length >= 2) {
+ address = address_and_phone[0];
+ phone = address_and_phone[1].replace (" ", "").replace ("-", "");
+ }
+ expect_tag ("a"); // target=_top
+ expect_tag ("/a");
+ expect_tag ("/div");
+ expect_tag ("/div");
+
+ last_cinema = new Cinema (name);
+ last_cinema.address = address;
+ last_cinema.phone = phone;
+ }
+
+ public int parse (ref char[] buf) throws Error {
+ int movies = 0;
+
+ current = buf;
+ next_tag ();
+ while (location == null && current[0] != 0) {
+ int i = 1;
+ while (current[i++] != '>');
+ if (((string) current).has_prefix ("<a href=\"/movies?near=")) {
+ string href = parse_tag_attribute ("a", "href");
+ char* p = (char*) href.offset (13); // skip "/movies?near="
+ int j = -1;
+
+ while (p[++j] != '&' && p[j] != 0);
+ p[0] = p[0].toupper ();
+ location = ((string) p).ndup (j);
+ }
+ current += i;
+ next_tag ();
+ }
+ while (current[0] != 0) {
+ int i = 1;
+ while (current[i++] != '>');
+ if (((string) current).has_prefix ("<div class=movie>")) {
+ parse_movie ();
+ movies++;
+ } else if (((string) current).has_prefix("<div class=theater>")) {
+ parse_cinema ();
+ } else {
+ current += i;
+ }
+ next_tag ();
+ }
+
+ return movies;
+ }
+
+ public async int query (string title, string? location, ReceiveMovie callback, Cancellable? cancellable = null) {
+ _get_callback = callback;
+ _title = title;
+ if (title.chr(title.length, '*') != null) {
+ pattern = new PatternSpec (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;
+
+ stdout.printf ("GET: %s\n", uri);
+
+ File file = File.new_for_uri (uri);
+ InputStream stream = yield file.read_async (Priority.DEFAULT_IDLE, null);
+
+ char[] buf = new char[256*1024];
+ size_t nread;
+ size_t total = 0;
+ while (total < 256*1024) {
+ nread = yield stream.read_async ((char *)buf + total, 256*1024 - total, Priority.DEFAULT_IDLE, cancellable);
+ total += nread;
+ if (cancellable.is_cancelled ())
+ return 0;
+ if (nread == 0)
+ break;
+ }
+ buf[total] = 0;
+ return parse (ref buf);
+ } catch (Error e) {
+ stderr.printf ("Error: %s\n", e.message);
+ }
+
+ return 0;
+ }
+}
+++ /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 Cinema {
- public string name;
- public string address;
- public string phone;
-
- public Cinema (string _name) {
- name = _name;
- }
-}
-
-public class GoogleMovie : Movie {
- public Cinema cinema;
- public string runtime;
- public string fsk;
- public string showtimes;
-}
-
-public class GoogleParser : Object {
- private MovieSource.ReceiveMovieFunction _get_callback;
- char *current;
- Cinema last_cinema;
- public string location;
- 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 string unescape_unicode (string s) {
- string result = "";
- int i, j;
- long l = s.length;
-
- for (i = 0; i < l; i++) {
- if (s[i] == '&' && s[i + 1] == '#') {
- for (j = i + 2; j < l; j++) {
- if (!s[j].isdigit ())
- break;
- if (s[j] == ';')
- break;
- }
- if (s[j] == ';') {
- int codepoint = s.substring (i + 2, j - i - 2).to_int ();
- char[] buf = new char[6];
- ((unichar) codepoint).to_utf8 ((string) buf);
- result += (string) buf;
- i = j;
- continue;
- }
- }
- if (s.offset (i).has_prefix ("&")) {
- result += "&";
- i += 4;
- continue;
- }
- if (s.offset (i).has_prefix (""")) {
- result += "\"";
- i += 5;
- continue;
- }
- result += s.substring (i, 1);
- }
-
- return result;
- }
-
- public void parse_movie () throws Error {
- expect_tag ("div"); // class=movie
- expect_tag ("div"); // class=name
- expect_tag ("a"); // href="/movies?near=city&mid=..."
- expect_tag ("span"); // dir=ltr
- var title = unescape_unicode (convert (parse_text (), -1, "utf-8", "iso-8859-1")); // FIXME
- expect_tag ("/span");
- expect_tag ("/a");
- expect_tag ("/div");
- expect_tag ("span"); // class=info
- string[] runtime_and_fsk = {};
- double rating = 0.0;
- var tag = parse_tag ();
- if (tag == "a") {
- // Trailer
- expect_tag ("/a");
- tag = parse_tag ();
- }
- if (tag == "a") {
- // IMDb
- expect_tag ("/a");
- tag = parse_tag ();
- }
- if (tag == "nobr") {
- expect_tag ("nobr");
- string rating_string = parse_tag_attribute ("img", "alt").offset (6); // "Rated " ->"0.0 out of 5.0"
- rating = rating_string.to_double ();
- expect_tag ("img");
- expect_tag ("img");
- expect_tag ("img");
- expect_tag ("img");
- expect_tag ("/nobr");
- expect_tag ("/nobr");
- runtime_and_fsk = parse_text ().replace ("‎", "").offset (3).split (" - ");
- if (parse_tag () == "a") {
- // Trailer
- expect_tag ("/a");
- if (parse_tag () == "a") {
- // IMDb link
- expect_tag ("/a");
- expect_tag ("/span");
- }
- }
- }
- expect_tag ("div"); // class=times
- var showtimes = parse_text ().replace (" ", ",");
- while (parse_tag () == "a") {
- showtimes += parse_text () + ",";
- expect_tag ("/a");
- }
-
- 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 = last_cinema;
- 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, last_cinema.name, showtimes);
- else
- movie.secondary = "%s - %s".printf (last_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 ("div"); // class=theater
- expect_tag ("div"); // class=desc id=theater_...
- expect_tag ("h2"); // class=name
- expect_tag ("a"); // href="/movies?near=city&tid=..."
- expect_tag ("span"); // dir=ltr
- var name = unescape_unicode (convert (parse_text (), -1, "utf-8", "iso-8859-1")); // FIXME
- expect_tag ("/span");
- expect_tag ("/a");
- expect_tag ("/h2");
- expect_tag ("div"); // class=info
- var address_and_phone = parse_text ().replace (" ", " ").split (" - ");
- string address = null;
- string phone = null;
- if (address_and_phone.length >= 2) {
- address = address_and_phone[0];
- phone = address_and_phone[1].replace (" ", "").replace ("-", "");
- }
- expect_tag ("a"); // target=_top
- expect_tag ("/a");
- expect_tag ("/div");
- expect_tag ("/div");
-
- last_cinema = new Cinema (name);
- last_cinema.address = address;
- last_cinema.phone = phone;
- }
-
- public int parse (ref char[] buf) throws Error {
- int movies = 0;
-
- current = buf;
- next_tag ();
- while (location == null && current[0] != 0) {
- int i = 1;
- while (current[i++] != '>');
- if (((string) current).has_prefix ("<a href=\"/movies?near=")) {
- string href = parse_tag_attribute ("a", "href");
- char* p = (char*) href.offset (13); // skip "/movies?near="
- int j = -1;
-
- while (p[++j] != '&' && p[j] != 0);
- p[0] = p[0].toupper ();
- location = ((string) p).ndup (j);
- }
- current += i;
- next_tag ();
- }
- while (current[0] != 0) {
- int i = 1;
- while (current[i++] != '>');
- if (((string) current).has_prefix ("<div class=movie>")) {
- parse_movie ();
- movies++;
- } else if (((string) current).has_prefix("<div class=theater>")) {
- parse_cinema ();
- } else {
- current += i;
- }
- next_tag ();
- }
-
- return movies;
- }
-
- public async int query (MovieFilter filter, string? location, MovieSource.ReceiveMovieFunction callback, Cancellable? cancellable) {
- _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;
-
- stdout.printf ("GET: %s\n", uri);
-
- File file = File.new_for_uri (uri);
- InputStream stream = yield file.read_async (Priority.DEFAULT_IDLE, null);
-
- char[] buf = new char[256*1024];
- size_t nread;
- size_t total = 0;
- while (total < 256*1024) {
- nread = yield stream.read_async ((char *)buf + total, 256*1024 - total, Priority.DEFAULT_IDLE, cancellable);
- total += nread;
- if (cancellable.is_cancelled ())
- return 0;
- if (nread == 0)
- break;
- }
- buf[total] = 0;
- return parse (ref buf);
- } catch (Error e) {
- stderr.printf ("Error: %s\n", e.message);
- }
-
- return 0;
- }
-}
using Gtk;
using Hildon;
+public class GoogleMovie : Movie {
+ public string cinema_name;
+ public string cinema_phone;
+ public string runtime;
+ public string showtimes;
+}
+
class GooglePlugin : Plugin {
List<MovieSource> sources;
- List<string> locations;
+ SList<string> locations;
string last_location;
+ GConf.Client gconf;
public override void hello (Gtk.Window window, Osso.Context context) {
stdout.printf ("Google Plugin Loaded.\n");
sources.append (source);
locations = null;
+ gconf = GConf.Client.get_default ();
try {
- var config_file = Path.build_filename (Environment.get_user_config_dir (), "cinaest", "cinaest.cfg");
- var keyfile = new KeyFile ();
- if (keyfile.load_from_file (config_file, KeyFileFlags.NONE)
- && keyfile.has_group ("GooglePlugin")) {
- if (keyfile.has_key ("GooglePlugin", "KnownLocations")) {
- var l = keyfile.get_string_list ("GooglePlugin", "KnownLocations");
- for (int i = 0; i < l.length; i++)
- locations.append (l[i]);
- }
- if (keyfile.has_key ("GooglePlugin", "LastLocation")) {
- source.location = last_location = keyfile.get_string ("GooglePlugin", "LastLocation");
- }
- }
+ source.location = last_location = gconf.get_string ("/apps/cinaest/location");
+ locations = gconf.get_list ("/apps/cinaest/locations", GConf.ValueType.STRING);
} catch (Error e) {
- if (!(e is KeyFileError.NOT_FOUND))
- stdout.printf ("Error loading configuration: %s\n", e.message);
+ stdout.printf ("Error getting GConf option: %s\n", e.message);
}
// FIXME - this forces the inclusion of config.h
if (movie != null) {
list.append (new MovieAction (_("Add to calendar"), on_add_calendar_event, movie, window));
- if (movie.cinema != null && movie.cinema.phone != null)
+ if (movie.cinema_phone != null)
list.append (new MovieAction (_("Call cinema"), on_call_cinema, movie, window));
}
runtime = 7200;
}
- res = Calendar.add_event (movie.title, _("Movie"), movie.cinema.name, showtime, showtime + runtime);
+ res = Calendar.add_event (movie.title, _("Movie"), movie.cinema_name, showtime, showtime + runtime);
var banner = (Banner) Banner.show_information_with_markup (window, null, (res == 0) ?
_("Added calendar event at %d:%02d").printf (hour, min) :
_("Failed to add calendar event"));
private void on_call_cinema (Movie _movie, Gtk.Window window) {
var movie = (GoogleMovie) _movie;
- var url = "tel://" + movie.cinema.phone;
+ var url = "tel://" + movie.cinema_phone;
try {
var action = Hildon.URIAction.get_default_action_by_uri (url);
var dialog = new Gtk.Dialog ();
dialog.set_transient_for (window);
dialog.set_title (_("Google plugin settings"));
+ try {
+ source.location = gconf.get_string ("/apps/cinaest/location");
+ locations = gconf.get_list ("/apps/cinaest/locations", GConf.ValueType.STRING);
+ } catch (Error e) {
+ stdout.printf ("Error getting GConf option: %s\n", e.message);
+ }
var selector = new TouchSelectorEntry.text ();
insert_location_sorted (source.location);
int res = dialog.run ();
if (res == ResponseType.ACCEPT) {
source.location = button.get_value ();
- if (insert_location_sorted (source.location) || source.location != last_location) {
- var config_dir = Path.build_filename (Environment.get_user_config_dir (), "cinaest");
- var config_file = Path.build_filename (config_dir, "cinaest.cfg");
-
- // Make sure the directory is available
- DirUtils.create_with_parents (config_dir, 0770);
-
- var keyfile = new KeyFile ();
- try {
- keyfile.load_from_file (config_file, KeyFileFlags.NONE);
- } catch (Error e) {
- if (!(e is KeyFileError.NOT_FOUND))
- stdout.printf ("Error loading configuration: %s\n", e.message);
- }
- var l = new string[locations.length ()];
- for (int i = 0; i < l.length; i++) {
- l[i] = locations.nth_data (i);
- }
- keyfile.set_string_list ("GooglePlugin", "KnownLocations", l);
- keyfile.set_string ("GooglePlugin", "LastLocation", source.location);
- last_location = source.location;
-
- try {
- var file = File.new_for_path (config_file + ".part");
- var stream = file.create (FileCreateFlags.REPLACE_DESTINATION, null);
- var data = keyfile.to_data ();
-
- stream.write (data, data.length, null);
- FileUtils.rename (config_file + ".part", config_file);
- } catch (Error e) {
- stdout.printf ("Failed to store configuration: %s\n", e.message);
- }
+ try {
+ if (insert_location_sorted (source.location))
+ gconf.set_list ("/apps/cinaest/locations", GConf.ValueType.STRING, locations);
+ if (source.location != last_location)
+ gconf.set_string ("/apps/cinaest/location", source.location);
+ } catch (Error e) {
+ stdout.printf ("Error setting GConf option: %s\n", e.message);
}
}
dialog.destroy ();
if (location == null)
return false;
if (locations != null) {
- for (unowned List<string> l = locations.first (); l != null; l = l.next) {
+ for (unowned SList<string> l = locations; l != null; l = l.next) {
if (l.data == location) {
return false;
}
class GoogleSource : MovieSource {
public string location;
public string description;
+ public MovieSource.ReceiveMovieFunction callback;
+ dynamic DBus.Object search;
public override bool active { get; set construct; }
GLib.Object (active: true);
}
- public override async int get_movies (MovieFilter filter, MovieSource.ReceiveMovieFunction callback, int limit, Cancellable? cancellable) {
- var parser = new GoogleParser ();
+ SourceFunc get_movies_callback;
+ public override async int get_movies (MovieFilter filter, MovieSource.ReceiveMovieFunction _callback, int limit, Cancellable? cancellable) {
+ var conn = DBus.Bus.get (DBus.BusType.SESSION);
+ string search_path;
+
+ dynamic DBus.Object server = conn.get_object ("org.maemo.cinaest.GoogleShowtimes",
+ "/org/maemo/cinaest/googleshowtimes",
+ "org.maemo.cinaest.MovieService");
+ server.NewSearch (out search_path);
+
+ search = conn.get_object ("org.maemo.cinaest.GoogleShowtimes",
+ search_path,
+ "org.maemo.cinaest.MovieSearch");
+
+ callback = _callback;
+ search.MoviesFound += on_movies_found;
+ search.start (filter.title);
+
+ get_movies_callback = get_movies.callback;
+ if (cancellable != null)
+ cancellable.cancelled.connect (() => { search.abort (); Idle.add (get_movies_callback); });
+ yield;
+
+ var gc = GConf.Client.get_default ();
+ try {
+ location = gc.get_string ("/apps/cinaest/location");
+ } catch (Error e) {
+ stdout.printf ("Error getting GConf option: %s\n", e.message);
+ }
+
+ return 1 /*n*/;
+ }
+
+ private void on_movies_found (DBus.Object sender, string[] movies, bool finished) {
+ print ("found %d movies\n", movies.length);
+ var parser = new Json.Parser ();
+
+ for (int i = 0; i < movies.length; i++) {
+ var movie = new GoogleMovie ();
+ try {
+ parser.load_from_data (movies[i], -1);
+ } catch (Error e) {
+ stderr.printf ("Error: %s\n%s\n", e.message, movies[i]);
+ }
+
+ var object = parser.get_root ().get_object ();
+ movie.title = object.get_string_member ("title");
+ movie.rating = (int) object.get_double_member ("rating");
+ movie.cinema_name = object.get_string_member ("cinema_name");
+ movie.cinema_phone = object.get_string_member ("cinema_phone");
+ movie.showtimes = object.get_string_member ("showtimes");
+ movie.secondary = movie.cinema_name + " - " + movie.showtimes;
- int n = yield parser.query (filter, location, callback, cancellable);
- if (location == null) {
- location = parser.location;
+ callback (movie);
}
- return n;
+ if (finished) {
+ search = null;
+ Idle.add (get_movies_callback);
+ }
}
public override void add_movie (Movie movie) {