libexec_PROGRAMS = \
google-poster-downloader \
imdb-plaintext-downloader \
- cinaest-google-backend
+ cinaest-google-backend \
+ cinaest-moviepilot-backend
pkglib_LTLIBRARIES = \
libcatalog-plugin.la \
libgoogle-plugin.la \
- libimdb-plugin.la
+ libimdb-plugin.la \
+ libmoviepilot-plugin.la
dbusservice_DATA = \
data/org.maemo.cinaest.service \
data/org.maemo.cinaest.IMDb.service \
data/org.maemo.cinaest.GoogleShowtimes.service \
+ data/org.maemo.cinaest.MoviePilot.service \
data/org.maemo.movieposter.GoogleImages.service
desktopentry_DATA = \
src/plugins/imdb-plugin.c: ${libimdb_plugin_la_VALASOURCES}
${VALAC} -C ${libimdb_plugin_la_VALASOURCES} ${libimdb_plugin_la_VALAFLAGS}
+libmoviepilot_plugin_la_SOURCES = \
+ src/plugins/moviepilot-plugin.c
+
+libmoviepilot_plugin_la_VALASOURCES = \
+ src/plugins/moviepilot-plugin.vala
+
+libmoviepilot_plugin_la_VALAFLAGS = --vapidir ./vapi --pkg config --pkg cinaest \
+ --pkg dbus-glib-1 --pkg hildon-1 --pkg json-glib-1.0 --pkg libosso
+libmoviepilot_plugin_la_CFLAGS = ${CINAEST_CFLAGS} ${HILDON_CFLAGS} ${JSON_CFLAGS} ${OSSO_CFLAGS}
+libmoviepilot_plugin_la_LIBADD = ${CINAEST_LIBS} ${HILDON_LIBS} ${JSON_LIBS} ${OSSO_LIBS}
+libmoviepilot_plugin_la_LDFLAGS = -module
+
+src/plugins/moviepilot-plugin.c: ${libmoviepilot_plugin_la_VALASOURCES}
+ ${VALAC} -C $^ ${libmoviepilot_plugin_la_VALAFLAGS}
+
cinaest_google_backend_SOURCES = \
src/backends/google/google-backend.c \
src/backends/google/google-parser.c
src/backends/google/google-backend.c: ${cinaest_google_backend_VALASOURCES}
${VALAC} -C ${cinaest_google_backend_VALASOURCES} ${cinaest_google_backend_VALAFLAGS}
+cinaest_moviepilot_backend_SOURCES = \
+ src/backends/moviepilot/moviepilot-backend.c
+
+cinaest_moviepilot_backend_VALASOURCES = \
+ src/backends/moviepilot/moviepilot-backend.vala
+
+cinaest_moviepilot_backend_VALAFLAGS = --thread --vapidir ./vapi --pkg dbus-glib-1 \
+ --pkg gee-1.0 --pkg gio-2.0 --pkg json-glib-1.0 --pkg rest-0.6
+cinaest_moviepilot_backend_CFLAGS = ${DBUS_CFLAGS} ${GEE_CFLAGS} ${GIO_CFLAGS} ${JSON_CFLAGS} ${REST_CFLAGS}
+cinaest_moviepilot_backend_LDADD = ${DBUS_LIBS} ${GEE_LIBS} ${GIO_LIBS} ${JSON_LIBS} ${REST_LIBS}
+
+src/backends/moviepilot/moviepilot-backend.c: ${cinaest_moviepilot_backend_VALASOURCES}
+ ${VALAC} -C $^ ${cinaest_moviepilot_backend_VALAFLAGS}
+
imdb_plaintext_downloader_SOURCES = \
src/imdb/imdb-plaintext-downloader.c \
src/imdb/imdb-ftp-downloader.c \
${libcatalog_plugin_la_SOURCES} \
$(patsubst %.vala,%.c,${libgoogle_plugin_la_VALASOURCES}) \
${libimdb_plugin_la_SOURCES} \
+ ${libmoviepilot_plugin_la_SOURCES} \
${imdb_plaintext_downloader_SOURCES} \
${google_poster_downloader_SOURCES} \
- ${cinaest_google_backend_SOURCES}
+ ${cinaest_google_backend_SOURCES} \
+ ${cinaest_moviepilot_backend_SOURCES}
--- /dev/null
+using Gee;
+
+public Gee.HashMap <string, MovieSearch> searches;
+public DBus.Connection conn;
+
+// A movie, serialized as JSON object in the movies_found D-Bus signal
+class Movie : Object {
+ public string title { get; set; }
+ public int year { get; set; }
+ public double rating { get; set; }
+ public string genres { get; set; }
+ public string id { get; set; }
+}
+
+[DBus (name = "org.maemo.cinaest.MovieSearch", signals="movies_found")]
+public class MovieSearch : Object {
+ private const string SERVICE_URL = "http://www.moviepilot.de";
+ private const string API_KEY = "1dab2d86f46d669766de572ba9b8eb";
+
+ private Rest.Proxy proxy;
+
+ public int id;
+ bool aborted;
+ string title;
+ MoviePilotMovieService service;
+ public string path;
+ public string sender;
+
+ private SourceFunc callback = null;
+ private GLib.List<Movie> results = null;
+
+ // D-Bus API
+
+ public void abort () {
+ print ("aborting\n");
+ aborted = true;
+ }
+
+ public void start (string query) {
+ title = query;
+ aborted = false;
+
+ print ("starting query %d: \"%s\"\n", id, title);
+ query_async.begin ();
+ }
+
+ public signal void movies_found (string[] movies, bool finished);
+
+ // Internal methods
+
+ internal MovieSearch (MoviePilotMovieService _service, string _sender, string _path) {
+ service = _service;
+ sender = _sender;
+ path = _path;
+ }
+
+ internal async void query_async () {
+ if (callback != null)
+ return;
+ callback = query_async.callback;
+ try {
+ print ("_search_movies (%s)\n", title);
+ _search_movies (title);
+ yield;
+ if (!aborted) {
+ var movies = new string[results.length ()];
+ int i = 0;
+ for (weak GLib.List<Movie> node = results.first (); node != null; node = node.next) {
+ // FIXME: why does Json.serialize_gobject fail here?
+ // movies[i++] = Json.serialize_gobject (node.data, null);
+ movies[i++] = "{\"title\":\"%s\",\"year\":%d,\"rating\":%f,\"genres\":\"%s\",\"id\":\"%s\"}".printf (node.data.title, node.data.year, node.data.rating, node.data.genres, node.data.id);
+ }
+ print ("got %d movies\n", movies.length);
+ movies_found (movies, true);
+ } else {
+ print ("aborted\n");
+ }
+ service.timeout_quit ();
+ } catch (GLib.Error e) {
+ critical ("Error: %s\n", e.message);
+ }
+ callback = null;
+ }
+
+ // MoviePilot RESTful API calls, executed using librest
+
+ private void _search_movies (string query, int per_page = 20, int page = 1) throws GLib.Error {
+ var call = proxy.new_call ();
+
+ if (query == "") {
+ Idle.add (callback);
+ return;
+ }
+
+ call.set_function ("searches/movies.json");
+ call.set_method ("GET");
+ call.add_params ("q", query,
+ "api_key", API_KEY,
+ "per_page", per_page.to_string (),
+ "page", page.to_string ());
+ call.run_async (search_movies_cb, this);
+ }
+/*
+ internal void get_info (string id) throws GLib.Error {
+ var call = proxy.new_call ();
+
+ call.set_function ("movies/%s.json".printf (id));
+ call.set_method ("GET");
+ call.add_params ("api_key", API_KEY);
+ call.run_async (get_info_cb, proxy);
+ }
+*/
+ // Callback functions handle JSON results using libjson-glib
+
+ private void search_movies_cb (Rest.ProxyCall call, GLib.Error? _error, GLib.Object? weak_object) {
+ var parser = new Json.Parser ();
+ unowned Json.Node node;
+
+ try {
+ parser.load_from_data (call.get_payload (), (ssize_t) call.get_payload_length ());
+ } catch (Error e) {
+ critical ("Payload: %s\nParse error: %s", call.get_payload (), e.message);
+ Idle.add (this.callback);
+ return;
+ }
+
+ node = parser.get_root ();
+ if (node.get_node_type () != Json.NodeType.OBJECT) {
+ critical ("JSON error, not an object:\n%s\n", call.get_payload ());
+ Idle.add (this.callback);
+ return;
+ }
+
+ var object = node.get_object ();
+ if (!object.has_member ("total_entries")) {
+ if (object.has_member ("error")) {
+ critical ("Error: %s\n", object.get_string_member ("error"));
+ } else {
+ critical ("JSON error, not a movie search result:\n%s\n", call.get_payload ());
+ //{"suggestions":["matrix","material","matilda"],"total-entries":0}
+ }
+ Idle.add (this.callback);
+ return;
+ }
+
+ if (object.get_int_member ("total_entries") == 0) {
+ Idle.add (this.callback);
+ return;
+ }
+
+ if (!object.has_member ("movies")) {
+ critical ("JSON error, not a movie search result:\n%s\n", call.get_payload ());
+ Idle.add (this.callback);
+ return;
+ }
+
+ // var total_results = object.get_int_member ("total_entries");
+ // print ("total_results = %lld\n", total_results);
+
+ node = object.get_member ("movies");
+ if (node.get_node_type () != Json.NodeType.ARRAY) {
+ critical ("JSON error, not a movie array\n");
+ return;
+ }
+
+ var array = node.get_array ();
+ foreach (weak Json.Node n in array.get_elements ()) {
+ var movie = handle_movie (n);
+
+ results.append (movie);
+ }
+
+ Idle.add (this.callback);
+ }
+
+ private void get_info_cb (Rest.ProxyCall call, GLib.Error? _error, GLib.Object? weak_object) {
+ var parser = new Json.Parser ();
+
+ try {
+ parser.load_from_data (call.get_payload (), (ssize_t) call.get_payload_length ());
+ } catch (Error e) {
+ critical ("Payload: %s\nParse error: %s", call.get_payload (), e.message);
+ return;
+ }
+
+ var movie = handle_movie (parser.get_root ());
+
+ results.append (movie);
+
+ Idle.add (this.callback);
+ }
+
+ private Movie handle_movie (Json.Node node) requires (node.get_node_type () == Json.NodeType.OBJECT) {
+ var object = node.get_object ();
+ var movie = new Movie ();
+
+ movie.title = object.get_string_member ("display_title");
+ movie.year = object.get_string_member ("production_year").to_int ();
+ movie.rating = object.get_double_member ("average_community_rating");
+
+ movie.genres = object.get_string_member ("genres_list"); // CSV
+
+ var url = object.get_string_member ("restful_url");
+ if (url.has_prefix ("http://www.moviepilot.de/movies/")) {
+ movie.id = url.offset (32); // "http://www.moviepilot.de/movies/".length ();
+ } else {
+ critical ("unknown restful_url prefix: %s", url);
+ }
+
+ // var countries = object.get_string_member ("countries_list");
+ // var description = object.get_string_member ("short_description");
+ // var on_tv = object.get_bool_member ("on_tv");
+ // var homepage = object.get_string_member ("homepage");
+ // var long_description = object.get_string_member ("long_description");
+ // var premiere = object.get_string_member ("premiere_date"); // YYYY-MM-DD
+ // object.get_member ("alternative_identifiers"); // an object with "service":"id" pairs
+ // object.get_member ("poster"); // object with copyright, title, height, extension, width, photo_id, base_url, mime_type, size, restful_url and file_name_base properties
+ // object.get_string_member ("cinema_start_date") // YYYY-MM-DD
+ // object.get_int_member ("runtime");
+ // object.get_double_member ("average_critics_rating");
+ // object.get_string_member ("dvd_start_date");
+
+ // FIXME: cache movie here
+
+ return movie;
+ }
+
+ construct {
+ proxy = new Rest.Proxy (SERVICE_URL, false);
+ }
+}
+
+[DBus (name = "org.maemo.cinaest.MovieService")]
+public class MoviePilotMovieService : Object {
+ private MainLoop loop;
+ private uint source_id;
+ int id = 0;
+
+ // D-Bus API
+
+ public string new_search (DBus.BusName sender) {
+ var path = "/org/maemo/cinaest/moviepilot/search%d".printf (id);
+ var search = new MovieSearch (this, sender, path);
+ search.id = id++;
+ conn.register_object (path, search);
+
+ print ("creating new search %s for %s\n", path, sender);
+ searches.set (path, search);
+
+ return path;
+ }
+
+ public void unregister (string path) {
+ print ("unregistering search %s\n", path);
+
+ searches.remove (path);
+ print ("%d\n", searches.size);
+ }
+
+ // Internal methods
+
+ internal MoviePilotMovieService () {
+ 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 == "") {
+ var remove_list = new SList<string> ();
+ // We lost a client
+ print ("lost a client: %s\n", prev);
+ foreach (MovieSearch search in searches.values) {
+ if (search.sender == prev) {
+ print ("removing %s\n", search.path);
+ remove_list.append (search.path);
+ }
+ }
+ foreach (string path in remove_list)
+ searches.remove (path);
+ }
+}
+
+void main () {
+ 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.MoviePilot", (uint) 0);
+
+ if (res == DBus.RequestNameReply.PRIMARY_OWNER) {
+ var server = new MoviePilotMovieService ();
+
+ conn.register_object ("/org/maemo/cinaest/moviepilot", 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/>.
+ */
+
+using Gtk;
+using Hildon;
+
+class MoviePilotPlugin : Plugin {
+ List<MovieSource> sources;
+
+ public override void hello (Gtk.Window window, Osso.Context context) {
+ stdout.printf ("MoviePilot Plugin Loaded.\n");
+
+ var source = new MoviePilotSource ();
+
+ 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 List<MovieAction> get_actions (Movie _movie, Gtk.Window window) {
+ return new List<MovieAction> ();
+ }
+
+ public override void settings_dialog (Gtk.Window window) {
+ MoviePilotSource source = (MoviePilotSource) sources.data;
+ var dialog = new Gtk.Dialog ();
+ dialog.set_transient_for (window);
+ dialog.set_title (_("MoviePilot plugin settings"));
+
+ // Username
+ // Password
+ var hbox = new Gtk.HBox (false, 0);
+ var vbox = new Gtk.VBox (true, 0);
+ var label = new Gtk.Label ("User name");
+ vbox.pack_start (label, true, true, 0);
+ label = new Gtk.Label ("Password");
+ vbox.pack_start (label, true, true, 0);
+ hbox.pack_start (vbox, false, false, 0);
+ vbox = new Gtk.VBox (true, 0);
+ var entry = new Hildon.Entry (SizeType.FINGER_HEIGHT);
+ vbox.pack_start (entry, true, true, 0);
+ entry = new Hildon.Entry (SizeType.FINGER_HEIGHT);
+ vbox.pack_start (entry, true, true, 0);
+ hbox.pack_start (vbox, true, true, 0);
+
+ var content = (VBox) dialog.get_content_area ();
+ content.pack_start (hbox, true, true, 0);
+
+ dialog.add_button (_("Save"), ResponseType.ACCEPT);
+
+ dialog.show_all ();
+ int res = dialog.run ();
+ if (res == ResponseType.ACCEPT) {
+ /* ... */
+ }
+ dialog.destroy ();
+ }
+
+ public override unowned string get_name () {
+ return "MoviePilot";
+ }
+}
+
+class MoviePilotSource : MovieSource {
+ public string location;
+ public string description;
+ public MovieSource.ReceiveMovieFunction callback;
+ dynamic DBus.Object search;
+
+ public override bool active { get; set construct; }
+
+ public MoviePilotSource () {
+ GLib.Object (active: true);
+ }
+
+ SourceFunc get_movies_callback;
+ public override async int get_movies (MovieFilter filter, MovieSource.ReceiveMovieFunction _callback, int limit, Cancellable? cancellable) {
+ try {
+ string search_path;
+ dynamic DBus.Object server;
+ var conn = DBus.Bus.get (DBus.BusType.SESSION);
+
+ server = conn.get_object ("org.maemo.cinaest.MoviePilot",
+ "/org/maemo/cinaest/moviepilot",
+ "org.maemo.cinaest.MovieService");
+ server.NewSearch (out search_path);
+
+ search = conn.get_object ("org.maemo.cinaest.MoviePilot",
+ search_path,
+ "org.maemo.cinaest.MovieSearch");
+
+ callback = _callback;
+ search.MoviesFound.connect (on_movies_found);
+ print ("get_movies (%s)\n", filter.title);
+ search.start (filter.title);
+ } catch (Error e1) {
+ Banner.show_information (null, null, e1.message);
+ return 0;
+ }
+
+ get_movies_callback = get_movies.callback;
+ if (cancellable != null)
+ cancellable.cancelled.connect (() => { search.abort (); Idle.add (get_movies_callback); });
+ yield;
+
+ return 1 /* FIXME: 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 Movie ();
+ 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.year = (int) object.get_int_member ("year");
+ movie.rating = (int) object.get_double_member ("rating");
+ movie.secondary = object.get_string_member ("genres").replace (",", ", ");
+
+ callback (movie);
+ }
+
+ if (finished) {
+ search = null;
+ Idle.add (get_movies_callback);
+ }
+ }
+
+ public override void add_movie (Movie movie) {
+ }
+
+ public override void delete_movie (Movie movie) {
+ }
+
+ public override unowned string get_name () {
+ return _("MoviePilot");
+ }
+
+ public override unowned string get_description () {
+ description = _("Movies on MoviePilot");
+ return description;
+ }
+
+ public override bool get_editable () {
+ return false;
+ }
+}
+
+[ModuleInit]
+public Type register_plugin () {
+ // types are registered automatically
+ return typeof (MoviePilotPlugin);
+}