1 /* This file is part of Cinaest.
3 * Copyright (C) 2009 Philipp Zabel
5 * Cinaest is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * Cinaest is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with Cinaest. If not, see <http://www.gnu.org/licenses/>.
21 namespace MoviePoster {
23 public delegate void RequestCallback (Gdk.Pixbuf movieposter, Movie movie);
25 public class Factory : Object {
26 private static Factory the_factory = null;
27 internal List<Request> requests;
28 internal dynamic DBus.Object server;
29 internal bool download_posters;
30 private GConf.Client gc;
32 private Gdk.Pixbuf poster_failed;
33 private Gdk.Pixbuf icon_failed;
37 var conn = DBus.Bus.get (DBus.BusType.SESSION);
38 server = conn.get_object ("org.maemo.movieposter.IMDb",
39 "/org/maemo/movieposter/IMDb",
40 "org.maemo.movieposter.Provider");
41 server.Fetched.connect (this.on_poster_fetched);
42 server.Failed.connect (this.on_poster_failed);
44 warning ("Couldn't connect to IMDb poster downloader: %s\n", e.message);
46 gc = GConf.Client.get_default ();
49 download_posters = gc.get_bool ("/apps/cinaest/download_posters");
50 gc.add_dir ("/apps/cinaest", GConf.ClientPreloadType.ONELEVEL);
51 cxnid = gc.notify_add ("/apps/cinaest/download_posters", on_download_posters_changed);
53 stdout.printf ("Error installing GConf notification: %s\n", e.message);
57 private static void on_download_posters_changed (GConf.Client gc, uint cxnid, GConf.Entry entry) {
58 the_factory.download_posters = entry.get_value ().get_bool ();
61 public static Factory get_instance () {
62 if (the_factory == null)
63 the_factory = new MoviePoster.Factory ();
67 public int queue (Movie movie, int width, int height, bool cropped, RequestCallback callback) throws Error {
68 string path = get_poster_path (movie);
70 foreach (Request request in requests) {
71 if (request.movie == movie &&
72 request.width >= width &&
73 request.height >= height)
77 // TODO: make this async / put out of process
79 if (width <= 256 && height <= 256)
80 pixbuf = request_thumbnail (path, width, height, cropped);
82 pixbuf = pixbuf_from_path (path, width, height, cropped);
84 callback (pixbuf, movie);
88 if (server != null && download_posters) {
89 var request = new Request ();
91 request.handle = server.Fetch (movie.title, movie.year.to_string (), "movie", width, height);
92 request.movie = movie;
93 request.callback = callback;
94 request.width = width;
95 request.height = height;
96 request.cropped = cropped;
97 requests.append (request);
102 private Gdk.Pixbuf? request_thumbnail (string path, int width, int height, bool cropped) {
103 string thumbnail_path = get_thumbnail_path (path, width, height);
105 var pixbuf = pixbuf_from_path (thumbnail_path, width, height, cropped);
106 if (pixbuf == null) {
107 pixbuf = pixbuf_from_path (path, width, height, cropped);
109 pixbuf.save (thumbnail_path, "jpeg", null);
115 private Gdk.Pixbuf? pixbuf_from_path (string path, int width, int height, bool cropped) {
116 if (FileUtils.test (path, FileTest.IS_REGULAR)) {
117 var pixbuf = new Gdk.Pixbuf.from_file (path);
119 if (pixbuf.width == width && pixbuf.height == height)
122 if (!cropped && (pixbuf.width == width || pixbuf.height == height))
125 if (!download_posters ||
126 (pixbuf.width >= width && pixbuf.height >= height) ||
127 (pixbuf.width >= Poster.SMALL_WIDTH && pixbuf.height >= Poster.SMALL_HEIGHT)) {
129 return scale_to_fit (pixbuf, width, height, cropped);
137 private Gdk.Pixbuf scale_to_fit (Gdk.Pixbuf source, int width, int height, bool cropped) {
138 Gdk.InterpType interp_type;
140 if (width > source.width && height > source.height)
141 interp_type = Gdk.InterpType.HYPER;
143 interp_type = Gdk.InterpType.BILINEAR;
146 double xscale = (double) width / (double) source.width;
147 double yscale = (double) height / (double) source.height;
151 if (xscale > yscale) {
153 ofs_y = (int) (height - source.height * scale) / 2;
156 ofs_x = (int) (width - source.width * scale) / 2;
159 var dest = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, width, height);
160 source.scale (dest, 0, 0, width, height, ofs_x, ofs_y, scale, scale, interp_type);
164 width = int.min (width,
165 (source.width * height + source.height / 2) / source.height);
166 height = int.min ((source.height * width + source.width / 2) / source.width,
169 return source.scale_simple (width, height, interp_type);
173 private void on_poster_fetched (dynamic DBus.Object server, int handle, string path) {
174 Request request = null;
175 foreach (Request r in requests) {
176 if (r.handle == handle) {
184 var pixbuf = request_thumbnail (path, request.width, request.height, request.cropped);
186 requests.remove (request);
187 request.callback (pixbuf, request.movie);
190 private void on_poster_failed (dynamic DBus.Object server, int handle) {
191 Request request = null;
192 foreach (Request r in requests) {
193 if (r.handle == handle) {
200 requests.remove (request);
201 request.callback (failed_poster (request.width, request.height), request.movie);
204 private Gdk.Pixbuf failed_poster (int width, int height) {
205 if (width == Poster.ICON_WIDTH && height == Poster.ICON_HEIGHT) {
206 if (icon_failed == null) {
207 // An empty icon for the list view
208 icon_failed = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, Poster.ICON_WIDTH, Poster.ICON_HEIGHT);
209 icon_failed.fill (0);
213 if (width == Poster.SMALL_WIDTH && height == Poster.SMALL_HEIGHT) {
214 if (poster_failed == null) try {
215 // Broken image icon for the poster view
216 var no_pic = new Gdk.Pixbuf.from_file ("/usr/share/icons/hicolor/64x64/hildon/imageviewer_no_pic.png");
217 poster_failed = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, Poster.SMALL_WIDTH, Poster.SMALL_HEIGHT);
218 poster_failed.fill (0);
219 no_pic.copy_area (0, 0, no_pic.width, no_pic.height, poster_failed,
220 (Poster.SMALL_WIDTH - no_pic.width) / 2, (Poster.SMALL_HEIGHT - no_pic.height) / 2);
222 critical ("Missing imageviewer_no_pic icon: %s\n", e.message);
224 return poster_failed;
229 public void join () {
232 public static void factory_remove (Movie movie) {
235 public static void factory_clean_cache (int max_size, time_t min_mtime) {
238 public void clear_queue () {
239 if (server != null) {
240 foreach (Request r in requests)
241 server.Unqueue (r.handle);
247 public class Request {
250 public RequestCallback callback;
255 public void unqueue () {
256 if (Factory.get_instance ().server != null)
257 Factory.get_instance ().server.Unqueue (this.handle);
259 Factory.get_instance ().requests.remove (this);
262 public void join () {
266 public static bool is_cached (Movie movie) {
267 string filename = get_poster_path (movie);
268 if (FileUtils.test (filename, FileTest.IS_REGULAR))
274 public static string get_poster_path (Movie movie) {
275 // return Hildon.albumart_get_path (movie.title, movie.year.to_string (), "movie");
276 return Path.build_filename (Environment.get_user_cache_dir (),
278 "movie-%s-%s.jpeg".printf (
279 Checksum.compute_for_string (ChecksumType.MD5, movie.title.down ()),
280 Checksum.compute_for_string (ChecksumType.MD5, movie.year.to_string ())));
283 public static string get_thumbnail_path (string poster_path, int width, int height) {
284 bool large = width > 128 || height > 128;
286 return Path.build_filename (Environment.get_home_dir (),
288 large ? "large" : "normal",
290 Checksum.compute_for_string (ChecksumType.MD5, "file://" + poster_path)));