Movie poster factory: return a "no poster" pixmap if the poster download failed
[cinaest] / src / poster / movie-poster-factory.vala
1 /* This file is part of Cinaest.
2  *
3  * Copyright (C) 2009 Philipp Zabel
4  *
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.
9  *
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.
14  *
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/>.
17  */
18
19 using GLib;
20
21 namespace MoviePoster {
22
23         public delegate void RequestCallback (Gdk.Pixbuf movieposter, Movie movie);
24
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;
31                 private uint cxnid;
32                 private Gdk.Pixbuf poster_failed;
33                 private Gdk.Pixbuf icon_failed;
34
35                 construct {
36                         try {
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);
43                         } catch (Error e) {
44                                 warning ("Couldn't connect to IMDb poster downloader: %s\n", e.message);
45                         }
46                         gc = GConf.Client.get_default ();
47
48                         try {
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);
52                         } catch (Error e) {
53                                 stdout.printf ("Error installing GConf notification: %s\n", e.message);
54                         }
55                 }
56
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 ();
59                 }
60
61                 public static Factory get_instance () {
62                         if (the_factory == null)
63                                 the_factory = new MoviePoster.Factory ();
64                         return the_factory;
65                 }
66
67                 public int queue (Movie movie, RequestCallback callback) throws Error {
68                         string path = get_path (movie);
69
70                         foreach (Request request in requests)
71                                 if (request.movie == movie)
72                                         return 0;
73
74                         if (FileUtils.test (path, FileTest.IS_REGULAR)) {
75                                 // TODO: make this async?
76                                 var pixbuf = new Gdk.Pixbuf.from_file_at_scale (path, 288, 400, true);
77                                 callback (pixbuf, movie);
78                         } else if (server != null && download_posters) {
79                                 var request = new Request ();
80
81                                 request.handle = server.Fetch (movie.title, movie.year.to_string (), "movie");
82                                 request.movie = movie;
83                                 request.callback = callback;
84                                 request.width = 288;
85                                 request.height = 400;
86                                 requests.append (request);
87                         }
88                         return 0;
89                 }
90
91                 public int queue_thumbnail (Movie movie, uint width, uint height, bool cropped, RequestCallback callback) throws Error {
92                         string path = get_path_thumbnail (movie);
93
94                         foreach (Request request in requests)
95                                 if (request.movie == movie)
96                                         return 0;
97
98                         if (FileUtils.test (path, FileTest.IS_REGULAR)) {
99                                 // TODO: make this async?
100                                 var pixbuf = new Gdk.Pixbuf.from_file_at_scale (path, (int) width, (int) height, true);
101
102                                 callback (pixbuf, movie);
103                         } else if (server != null && download_posters) {
104                                 var request = new Request ();
105
106                                 request.handle = server.FetchThumbnail (movie.title, movie.year.to_string (), "movie");
107                                 request.movie = movie;
108                                 request.callback = callback;
109                                 request.width = (int) width;
110                                 request.height = (int) height;
111                                 requests.append (request);
112                         }
113                         return 0;
114                 }
115
116                 private void on_poster_fetched (dynamic DBus.Object server, int handle, string path) {
117                         Request request = null;
118                         foreach (Request r in requests) {
119                                 if (r.handle == handle) {
120                                         request = r;
121                                         break;
122                                 }
123                         }
124                         if (request == null)
125                                 return;
126                         try {
127                                 var pixbuf = new Gdk.Pixbuf.from_file_at_size (path, request.width, request.height);
128
129                                 requests.remove (request);
130                                 request.callback (pixbuf, request.movie);
131                                 return;
132                         } catch (Error e) {
133                                 warning ("Failed to open poster: %s\n", e.message);
134                         }
135                 }
136
137                 private void on_poster_failed (dynamic DBus.Object server, int handle) {
138                         Request request = null;
139                         foreach (Request r in requests) {
140                                 if (r.handle == handle) {
141                                         request = r;
142                                         break;
143                                 }
144                         }
145                         if (request == null)
146                                 return;
147                         requests.remove (request);
148                         request.callback (failed_poster (request.width, request.height), request.movie);
149                 }
150
151                 private Gdk.Pixbuf failed_poster (int width, int height) {
152                         if (width == Poster.ICON_WIDTH && height == Poster.ICON_HEIGHT) {
153                                 if (icon_failed == null) {
154                                         // An empty icon for the list view
155                                         icon_failed = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, Poster.ICON_WIDTH, Poster.ICON_HEIGHT);
156                                         icon_failed.fill (0);
157                                 }
158                                 return icon_failed;
159                         }
160                         if (width == Poster.SMALL_WIDTH && height == Poster.SMALL_HEIGHT) {
161                                 if (poster_failed == null) try {
162                                         // Broken image icon for the poster view
163                                         var no_pic = new Gdk.Pixbuf.from_file ("/usr/share/icons/hicolor/64x64/hildon/imageviewer_no_pic.png");
164                                         poster_failed = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, Poster.SMALL_WIDTH, Poster.SMALL_HEIGHT);
165                                         poster_failed.fill (0);
166                                         no_pic.copy_area (0, 0, no_pic.width, no_pic.height, poster_failed,
167                                                           (Poster.SMALL_WIDTH - no_pic.width) / 2, (Poster.SMALL_HEIGHT - no_pic.height) / 2);
168                                 } catch (Error e) {
169                                         critical ("Missing imageviewer_no_pic icon: %s\n", e.message);
170                                 }
171                                 return poster_failed;
172                         }
173                         return null;
174                 }
175
176                 public void join () {
177                 }
178
179                 public static void factory_remove (Movie movie) {
180                 }
181
182                 public static void factory_clean_cache (int max_size, time_t min_mtime) {
183                 }
184
185                 public void clear_queue () {
186                         if (server != null) {
187                                 foreach (Request r in requests)
188                                         server.Unqueue (r.handle);
189                         }
190                         requests = null;
191                 }
192         }
193
194         public class Request {
195                 public int handle;
196                 public Movie movie;
197                 public RequestCallback callback;
198                 public int width;
199                 public int height;
200
201                 public void unqueue () {
202                         if (Factory.get_instance ().server != null)
203                                 Factory.get_instance ().server.Unqueue (this.handle);
204
205                         Factory.get_instance ().requests.remove (this);
206                 }
207
208                 public void join () {
209                 }
210         }
211
212         public static bool is_cached (Movie movie) {
213                 string filename = get_path (movie);
214                 if (FileUtils.test (filename, FileTest.IS_REGULAR))
215                         return true;
216                 else
217                         return false;
218         }
219
220         public static string get_path (Movie movie) {
221                 return Path.build_filename (Environment.get_user_cache_dir (), "media-art", "movie-" +
222                                             Checksum.compute_for_string (ChecksumType.MD5, movie.title.down ()) + "-" +
223                                             Checksum.compute_for_string (ChecksumType.MD5, movie.year.to_string ()) + ".jpeg");
224         }
225
226         public static string get_path_thumbnail (Movie movie) {
227                 return Path.build_filename (Environment.get_tmp_dir (), "cinaest-thumbnails", "movie-" +
228                                             Checksum.compute_for_string (ChecksumType.MD5, movie.title.down ()) + "-" +
229                                             Checksum.compute_for_string (ChecksumType.MD5, movie.year.to_string ()) + ".jpeg");
230         }
231 }
232