Poster downloading update
[cinaest] / src / poster / movie-poster-factory.vala
index 98d0e32..6e4d410 100644 (file)
@@ -29,22 +29,29 @@ namespace MoviePoster {
                internal bool download_posters;
                private GConf.Client gc;
                private uint cxnid;
+               private Gdk.Pixbuf poster_failed;
+               private Gdk.Pixbuf icon_failed;
 
                construct {
                        try {
                                var conn = DBus.Bus.get (DBus.BusType.SESSION);
-                               server = conn.get_object ("org.maemo.movieposter.GoogleImages",
-                                                         "/org/maemo/movieposter/GoogleImages",
+                               server = conn.get_object ("org.maemo.movieposter.IMDb",
+                                                         "/org/maemo/movieposter/IMDb",
                                                          "org.maemo.movieposter.Provider");
-                               server.Fetched += this.on_poster_fetched;
+                               server.Fetched.connect (this.on_poster_fetched);
+                               server.Failed.connect (this.on_poster_failed);
                        } catch (Error e) {
-                               warning ("Couldn't connect to Google image downloader: %s\n", e.message);
+                               warning ("Couldn't connect to IMDb poster downloader: %s\n", e.message);
                        }
                        gc = GConf.Client.get_default ();
 
-                       download_posters = gc.get_bool ("/apps/cinaest/download_posters");
-                       gc.add_dir ("/apps/cinaest", GConf.ClientPreloadType.ONELEVEL);
-                       cxnid = gc.notify_add ("/apps/cinaest/download_posters", on_download_posters_changed);
+                       try {
+                               download_posters = gc.get_bool ("/apps/cinaest/download_posters");
+                               gc.add_dir ("/apps/cinaest", GConf.ClientPreloadType.ONELEVEL);
+                               cxnid = gc.notify_add ("/apps/cinaest/download_posters", on_download_posters_changed);
+                       } catch (Error e) {
+                               stdout.printf ("Error installing GConf notification: %s\n", e.message);
+                       }
                }
 
                private static void on_download_posters_changed (GConf.Client gc, uint cxnid, GConf.Entry entry) {
@@ -57,44 +64,110 @@ namespace MoviePoster {
                        return the_factory;
                }
 
-               public int queue (Movie movie, RequestCallback callback) throws Error {
-                       string path = get_path (movie);
+               public int queue (Movie movie, int width, int height, bool cropped, RequestCallback callback) throws Error {
+                       string path = get_poster_path (movie);
 
-                       if (FileUtils.test (path, FileTest.IS_REGULAR)) {
-                               // TODO: make this async?
-                               var pixbuf = new Gdk.Pixbuf.from_file_at_size (path, 268, 424);
+                       foreach (Request request in requests) {
+                               if (request.movie == movie &&
+                                   request.width >= width &&
+                                   request.height >= height)
+                                       return 0;
+                       }
+
+                       // TODO: make this async / put out of process
+                       Gdk.Pixbuf pixbuf;
+                       if (width <= 256 && height <= 256)
+                               pixbuf = request_thumbnail (path, width, height, cropped);
+                       else
+                               pixbuf = pixbuf_from_path (path, width, height, cropped);
+                       if (pixbuf != null) {
                                callback (pixbuf, movie);
-                       } else if (server != null && download_posters) {
+                               return 0;
+                       }
+
+                       if (server != null && download_posters) {
                                var request = new Request ();
 
-                               request.handle = server.fetch (movie.title.down (), movie.year.to_string (), "movie");
+                               request.handle = server.Fetch (movie.title, movie.year.to_string (), "movie", width, height);
                                request.movie = movie;
                                request.callback = callback;
-                               request.width = 268;
-                               request.height = 424;
+                               request.width = width;
+                               request.height = height;
+                               request.cropped = cropped;
                                requests.append (request);
                        }
                        return 0;
                }
 
-               public int queue_thumbnail (Movie movie, uint width, uint height, bool cropped, RequestCallback callback) throws Error {
-                       string path = get_path_thumbnail (movie);
+               private Gdk.Pixbuf? request_thumbnail (string path, int width, int height, bool cropped) {
+                       string thumbnail_path = get_thumbnail_path (path, width, height);
+
+                       var pixbuf = pixbuf_from_path (thumbnail_path, width, height, cropped);
+                       if (pixbuf == null) {
+                               pixbuf = pixbuf_from_path (path, width, height, cropped);
+                               if (pixbuf != null)
+                                       pixbuf.save (thumbnail_path, "jpeg", null);
+                       }
 
+                       return pixbuf;
+               }
+
+               private Gdk.Pixbuf? pixbuf_from_path (string path, int width, int height, bool cropped) {
                        if (FileUtils.test (path, FileTest.IS_REGULAR)) {
-                               // TODO: make this async?
-                               var pixbuf = new Gdk.Pixbuf.from_file_at_size (path, (int) width, (int) height);
-                               callback (pixbuf, movie);
-                       } else if (server != null && download_posters) {
-                               var request = new Request ();
+                               var pixbuf = new Gdk.Pixbuf.from_file (path);
 
-                               request.handle = server.fetch_thumbnail (movie.title.down (), movie.year.to_string (), "movie");
-                               request.movie = movie;
-                               request.callback = callback;
-                               request.width = (int) width;
-                               request.height = (int) height;
-                               requests.append (request);
+                               if (pixbuf.width == width && pixbuf.height == height)
+                                       return pixbuf;
+
+                               if (!cropped && (pixbuf.width == width || pixbuf.height == height))
+                                       return pixbuf;
+
+                               if (!download_posters ||
+                                          (pixbuf.width >= width && pixbuf.height >= height) ||
+                                          (pixbuf.width >= Poster.SMALL_WIDTH && pixbuf.height >= Poster.SMALL_HEIGHT)) {
+                                       // Scale to fit
+                                       return scale_to_fit (pixbuf, width, height, cropped);
+
+                               }
+                       }
+
+                       return null;
+               }
+
+               private Gdk.Pixbuf scale_to_fit (Gdk.Pixbuf source, int width, int height, bool cropped) {
+                       Gdk.InterpType interp_type;
+
+                       if (width > source.width && height > source.height)
+                               interp_type = Gdk.InterpType.HYPER;
+                       else
+                               interp_type = Gdk.InterpType.BILINEAR;
+
+                       if (cropped) {
+                               double xscale = (double) width / (double) source.width;
+                               double yscale = (double) height / (double) source.height;
+                               double scale;
+                               int ofs_x = 0;
+                               int ofs_y = 0;
+                               if (xscale > yscale) {
+                                       scale = xscale;
+                                       ofs_y = (int) (height - source.height * scale) / 2;
+                               } else {
+                                       scale = yscale;
+                                       ofs_x = (int) (width - source.width * scale) / 2;
+                               }
+
+                               var dest = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, width, height);
+                               source.scale (dest, 0, 0, width, height, ofs_x, ofs_y, scale, scale, interp_type);
+
+                               return dest;
+                       } else {
+                               width = int.min (width,
+                                                (source.width * height + source.height / 2) / source.height);
+                               height = int.min ((source.height * width + source.width / 2) / source.width,
+                                                 height);
+
+                               return source.scale_simple (width, height, interp_type);
                        }
-                       return 0;
                }
 
                private void on_poster_fetched (dynamic DBus.Object server, int handle, string path) {
@@ -107,15 +180,50 @@ namespace MoviePoster {
                        }
                        if (request == null)
                                return;
-                       try {
-                               var pixbuf = new Gdk.Pixbuf.from_file_at_size (path, request.width, request.height);
 
-                               requests.remove (request);
-                               request.callback (pixbuf, request.movie);
+                       var pixbuf = request_thumbnail (path, request.width, request.height, request.cropped);
+
+                       requests.remove (request);
+                       request.callback (pixbuf, request.movie);
+               }
+
+               private void on_poster_failed (dynamic DBus.Object server, int handle) {
+                       Request request = null;
+                       foreach (Request r in requests) {
+                               if (r.handle == handle) {
+                                       request = r;
+                                       break;
+                               }
+                       }
+                       if (request == null)
                                return;
-                       } catch (Error e) {
-                               warning ("Failed to open poster: %s\n", e.message);
+                       requests.remove (request);
+                       request.callback (failed_poster (request.width, request.height), request.movie);
+               }
+
+               private Gdk.Pixbuf failed_poster (int width, int height) {
+                       if (width == Poster.ICON_WIDTH && height == Poster.ICON_HEIGHT) {
+                               if (icon_failed == null) {
+                                       // An empty icon for the list view
+                                       icon_failed = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, Poster.ICON_WIDTH, Poster.ICON_HEIGHT);
+                                       icon_failed.fill (0);
+                               }
+                               return icon_failed;
                        }
+                       if (width == Poster.SMALL_WIDTH && height == Poster.SMALL_HEIGHT) {
+                               if (poster_failed == null) try {
+                                       // Broken image icon for the poster view
+                                       var no_pic = new Gdk.Pixbuf.from_file ("/usr/share/icons/hicolor/64x64/hildon/imageviewer_no_pic.png");
+                                       poster_failed = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, Poster.SMALL_WIDTH, Poster.SMALL_HEIGHT);
+                                       poster_failed.fill (0);
+                                       no_pic.copy_area (0, 0, no_pic.width, no_pic.height, poster_failed,
+                                                         (Poster.SMALL_WIDTH - no_pic.width) / 2, (Poster.SMALL_HEIGHT - no_pic.height) / 2);
+                               } catch (Error e) {
+                                       critical ("Missing imageviewer_no_pic icon: %s\n", e.message);
+                               }
+                               return poster_failed;
+                       }
+                       return null;
                }
 
                public void join () {
@@ -128,11 +236,10 @@ namespace MoviePoster {
                }
 
                public void clear_queue () {
-
-                       // FIXME
-                       if (server != null)
-                               server.unqueue (0);
-
+                       if (server != null) {
+                               foreach (Request r in requests)
+                                       server.Unqueue (r.handle);
+                       }
                        requests = null;
                }
        }
@@ -143,10 +250,11 @@ namespace MoviePoster {
                public RequestCallback callback;
                public int width;
                public int height;
+               public bool cropped;
 
                public void unqueue () {
                        if (Factory.get_instance ().server != null)
-                               Factory.get_instance ().server.unqueue (this.handle);
+                               Factory.get_instance ().server.Unqueue (this.handle);
 
                        Factory.get_instance ().requests.remove (this);
                }
@@ -156,23 +264,30 @@ namespace MoviePoster {
        }
 
        public static bool is_cached (Movie movie) {
-               string filename = get_path (movie);
+               string filename = get_poster_path (movie);
                if (FileUtils.test (filename, FileTest.IS_REGULAR))
                        return true;
                else
                        return false;
        }
 
-       public static string get_path (Movie movie) {
-               return Path.build_filename (Environment.get_user_cache_dir (), "media-art", "movie-" +
-                                           Checksum.compute_for_string (ChecksumType.MD5, movie.title.down ()) + "-" +
-                                           Checksum.compute_for_string (ChecksumType.MD5, movie.year.to_string ()) + ".jpeg");
+       public static string get_poster_path (Movie movie) {
+       //      return Hildon.albumart_get_path (movie.title, movie.year.to_string (), "movie");
+               return Path.build_filename (Environment.get_user_cache_dir (),
+                                           "media-art",
+                                           "movie-%s-%s.jpeg".printf (
+                      Checksum.compute_for_string (ChecksumType.MD5, movie.title.down ()),
+                      Checksum.compute_for_string (ChecksumType.MD5, movie.year.to_string ())));
        }
 
-       public static string get_path_thumbnail (Movie movie) {
-               return Path.build_filename (Environment.get_tmp_dir (), "cinaest-thumbnails", "movie-" +
-                                           Checksum.compute_for_string (ChecksumType.MD5, movie.title.down ()) + "-" +
-                                           Checksum.compute_for_string (ChecksumType.MD5, movie.year.to_string ()) + ".jpeg");
+       public static string get_thumbnail_path (string poster_path, int width, int height) {
+               bool large = width > 128 || height > 128;
+
+               return Path.build_filename (Environment.get_home_dir (),
+                                           ".thumbnails",
+                                           large ? "large" : "normal",
+                                           "%s.jpeg".printf (
+                      Checksum.compute_for_string (ChecksumType.MD5, "file://" + poster_path)));
        }
 }