3 public Gee.HashMap <string, MovieSearch> searches;
4 public DBus.Connection conn;
6 // A movie, serialized as JSON object in the movies_found D-Bus signal
8 public string title { get; set; }
9 public int year { get; set; }
10 public double rating { get; set; }
11 public string genres { get; set; }
12 public string id { get; set; }
15 [DBus (name = "org.maemo.cinaest.MovieSearch", signals="movies_found")]
16 public class MovieSearch : Object {
17 private const string SERVICE_URL = "http://www.moviepilot.de";
18 private const string API_KEY = "1dab2d86f46d669766de572ba9b8eb";
20 private Rest.Proxy proxy;
25 MoviePilotMovieService service;
29 private SourceFunc callback = null;
30 private GLib.List<Movie> results = null;
34 public void abort () {
39 public void start (string query) {
43 print ("starting query %d: \"%s\"\n", id, title);
47 public signal void movies_found (string[] movies, bool finished);
51 internal MovieSearch (MoviePilotMovieService _service, string _sender, string _path) {
57 internal async void query_async () {
60 callback = query_async.callback;
62 print ("_search_movies (%s)\n", title);
63 _search_movies (title);
66 var movies = new string[results.length ()];
68 for (weak GLib.List<Movie> node = results.first (); node != null; node = node.next) {
69 // FIXME: why does Json.serialize_gobject fail here?
70 // movies[i++] = Json.serialize_gobject (node.data, null);
71 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);
73 print ("got %d movies\n", movies.length);
74 movies_found (movies, true);
78 service.timeout_quit ();
79 } catch (GLib.Error e) {
80 critical ("Error: %s\n", e.message);
85 // MoviePilot RESTful API calls, executed using librest
87 private void _search_movies (string query, int per_page = 20, int page = 1) throws GLib.Error {
88 var call = proxy.new_call ();
95 call.set_function ("searches/movies.json");
96 call.set_method ("GET");
97 call.add_params ("q", query,
99 "per_page", per_page.to_string (),
100 "page", page.to_string ());
101 call.run_async (search_movies_cb, this);
104 internal void get_info (string id) throws GLib.Error {
105 var call = proxy.new_call ();
107 call.set_function ("movies/%s.json".printf (id));
108 call.set_method ("GET");
109 call.add_params ("api_key", API_KEY);
110 call.run_async (get_info_cb, proxy);
113 // Callback functions handle JSON results using libjson-glib
115 private void search_movies_cb (Rest.ProxyCall call, GLib.Error? _error, GLib.Object? weak_object) {
116 var parser = new Json.Parser ();
117 unowned Json.Node node;
120 parser.load_from_data (call.get_payload (), (ssize_t) call.get_payload_length ());
122 critical ("Payload: %s\nParse error: %s", call.get_payload (), e.message);
123 Idle.add (this.callback);
127 node = parser.get_root ();
128 if (node.get_node_type () != Json.NodeType.OBJECT) {
129 critical ("JSON error, not an object:\n%s\n", call.get_payload ());
130 Idle.add (this.callback);
134 var object = node.get_object ();
135 if (!object.has_member ("total_entries")) {
136 if (object.has_member ("error")) {
137 critical ("Error: %s\n", object.get_string_member ("error"));
139 critical ("JSON error, not a movie search result:\n%s\n", call.get_payload ());
140 //{"suggestions":["matrix","material","matilda"],"total-entries":0}
142 Idle.add (this.callback);
146 if (object.get_int_member ("total_entries") == 0) {
147 Idle.add (this.callback);
151 if (!object.has_member ("movies")) {
152 critical ("JSON error, not a movie search result:\n%s\n", call.get_payload ());
153 Idle.add (this.callback);
157 // var total_results = object.get_int_member ("total_entries");
158 // print ("total_results = %lld\n", total_results);
160 node = object.get_member ("movies");
161 if (node.get_node_type () != Json.NodeType.ARRAY) {
162 critical ("JSON error, not a movie array\n");
166 var array = node.get_array ();
167 foreach (weak Json.Node n in array.get_elements ()) {
168 var movie = handle_movie (n);
170 results.append (movie);
173 Idle.add (this.callback);
176 private void get_info_cb (Rest.ProxyCall call, GLib.Error? _error, GLib.Object? weak_object) {
177 var parser = new Json.Parser ();
180 parser.load_from_data (call.get_payload (), (ssize_t) call.get_payload_length ());
182 critical ("Payload: %s\nParse error: %s", call.get_payload (), e.message);
186 var movie = handle_movie (parser.get_root ());
188 results.append (movie);
190 Idle.add (this.callback);
193 private Movie handle_movie (Json.Node node) requires (node.get_node_type () == Json.NodeType.OBJECT) {
194 var object = node.get_object ();
195 var movie = new Movie ();
197 movie.title = object.get_string_member ("display_title");
198 movie.year = object.get_string_member ("production_year").to_int ();
199 movie.rating = object.get_double_member ("average_community_rating");
201 movie.genres = object.get_string_member ("genres_list"); // CSV
203 var url = object.get_string_member ("restful_url");
204 if (url.has_prefix ("http://www.moviepilot.de/movies/")) {
205 movie.id = url.offset (32); // "http://www.moviepilot.de/movies/".length ();
207 critical ("unknown restful_url prefix: %s", url);
210 // var countries = object.get_string_member ("countries_list");
211 // var description = object.get_string_member ("short_description");
212 // var on_tv = object.get_bool_member ("on_tv");
213 // var homepage = object.get_string_member ("homepage");
214 // var long_description = object.get_string_member ("long_description");
215 // var premiere = object.get_string_member ("premiere_date"); // YYYY-MM-DD
216 // object.get_member ("alternative_identifiers"); // an object with "service":"id" pairs
217 // 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
218 // object.get_string_member ("cinema_start_date") // YYYY-MM-DD
219 // object.get_int_member ("runtime");
220 // object.get_double_member ("average_critics_rating");
221 // object.get_string_member ("dvd_start_date");
223 // FIXME: cache movie here
229 proxy = new Rest.Proxy (SERVICE_URL, false);
233 [DBus (name = "org.maemo.cinaest.MovieService")]
234 public class MoviePilotMovieService : Object {
235 private MainLoop loop;
236 private uint source_id;
241 public string new_search (DBus.BusName sender) {
242 var path = "/org/maemo/cinaest/moviepilot/search%d".printf (id);
243 var search = new MovieSearch (this, sender, path);
245 conn.register_object (path, search);
247 print ("creating new search %s for %s\n", path, sender);
248 searches.set (path, search);
253 public void unregister (string path) {
254 print ("unregistering search %s\n", path);
256 searches.remove (path);
257 print ("%d\n", searches.size);
262 internal MoviePilotMovieService () {
263 loop = new MainLoop (null);
266 internal void timeout_quit () {
267 // With every change we reset the timer to 3min
268 if (source_id != 0) {
269 Source.remove (source_id);
271 source_id = Timeout.add_seconds (180, quit);
274 private bool quit () {
281 internal void run () {
286 void on_client_lost (DBus.Object sender, string name, string prev, string newp) {
288 var remove_list = new SList<string> ();
290 print ("lost a client: %s\n", prev);
291 foreach (MovieSearch search in searches.values) {
292 if (search.sender == prev) {
293 print ("removing %s\n", search.path);
294 remove_list.append (search.path);
297 foreach (string path in remove_list)
298 searches.remove (path);
304 conn = DBus.Bus.get (DBus.BusType. SESSION);
306 searches = new Gee.HashMap <string, MovieSearch> ();
308 dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus",
309 "/org/freedesktop/DBus",
310 "org.freedesktop.DBus");
312 uint res = bus.request_name ("org.maemo.cinaest.MoviePilot", (uint) 0);
314 if (res == DBus.RequestNameReply.PRIMARY_OWNER) {
315 var server = new MoviePilotMovieService ();
317 conn.register_object ("/org/maemo/cinaest/moviepilot", server);
319 bus.NameOwnerChanged.connect (on_client_lost);
321 server.timeout_quit ();
325 stderr.printf ("Oops: %s\n", e.message);