/* 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 . */ using GLib; namespace MoviePoster { public delegate void RequestCallback (Gdk.Pixbuf movieposter, Movie movie); public class Factory : Object { private static Factory the_factory = null; internal List requests; internal dynamic DBus.Object server; 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.IMDb", "/org/maemo/movieposter/IMDb", "org.maemo.movieposter.Provider"); server.Fetched.connect (this.on_poster_fetched); server.Failed.connect (this.on_poster_failed); } catch (Error e) { warning ("Couldn't connect to IMDb poster downloader: %s\n", e.message); } gc = GConf.Client.get_default (); 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) { the_factory.download_posters = entry.get_value ().get_bool (); } public static Factory get_instance () { if (the_factory == null) the_factory = new MoviePoster.Factory (); return the_factory; } public int queue (Movie movie, int width, int height, bool cropped, RequestCallback callback) throws Error { string path = get_poster_path (movie); 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); return 0; } if (server != null && download_posters) { var request = new Request (); request.handle = server.Fetch (movie.title, movie.year.to_string (), "movie", width, height); request.movie = movie; request.callback = callback; request.width = width; request.height = height; request.cropped = cropped; requests.append (request); } return 0; } 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)) { var pixbuf = new Gdk.Pixbuf.from_file (path); 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); } } private void on_poster_fetched (dynamic DBus.Object server, int handle, string path) { Request request = null; foreach (Request r in requests) { if (r.handle == handle) { request = r; break; } } if (request == null) return; 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; 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 () { } public static void factory_remove (Movie movie) { } public static void factory_clean_cache (int max_size, time_t min_mtime) { } public void clear_queue () { if (server != null) { foreach (Request r in requests) server.Unqueue (r.handle); } requests = null; } } public class Request { public int handle; public Movie movie; 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 ().requests.remove (this); } public void join () { } } public static bool is_cached (Movie movie) { string filename = get_poster_path (movie); if (FileUtils.test (filename, FileTest.IS_REGULAR)) return true; else return false; } 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_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))); } }