9
|
1 /*
|
|
2 * GQview
|
|
3 * (C) 2005 John Ellis
|
|
4 *
|
|
5 * Author: John Ellis
|
|
6 *
|
|
7 * This software is released under the GNU General Public License (GNU GPL).
|
|
8 * Please read the included file COPYING for more information.
|
|
9 * This software comes with no warranty of any kind, use at your own risk!
|
|
10 */
|
|
11
|
|
12
|
|
13 #include "gqview.h"
|
|
14 #include "thumb_standard.h"
|
|
15
|
|
16 #include "cache.h" /* for cache_ensure_dir_exists */
|
|
17 #include "image-load.h"
|
|
18 #include "md5-util.h"
|
|
19 #include "pixbuf_util.h"
|
|
20 #include "ui_fileops.h"
|
|
21
|
|
22
|
|
23 /*
|
|
24 * This thumbnail caching implementation attempts to conform
|
|
25 * to the Thumbnail Managing Standard proposed on freedesktop.org
|
|
26 * The standard is documented here:
|
|
27 * http://triq.net/~jens/thumbnail-spec/index.html
|
|
28 * (why isn't it actually hosted on freedesktop.org?)
|
|
29 *
|
|
30 * This code attempts to conform to version 0.7.0 of the standard.
|
|
31 *
|
|
32 * Notes:
|
|
33 * > Validation of the thumb's embedded uri is a simple strcmp between our
|
|
34 * version of the escaped uri and the thumb's escaped uri. But not all uri
|
|
35 * escape functions escape the same set of chars, comparing the unescaped
|
|
36 * versions may be more accurate.
|
|
37 * > Only Thumb::URI and Thumb::MTime are stored in a thumb at this time.
|
|
38 * Storing the Size, Width, Height should probably be implemented.
|
|
39 */
|
|
40
|
|
41
|
|
42 #define THUMB_SIZE_NORMAL 128
|
|
43 #define THUMB_SIZE_LARGE 256
|
|
44
|
|
45 #define THUMB_MARKER_URI "tEXt::Thumb::URI"
|
|
46 #define THUMB_MARKER_MTIME "tEXt::Thumb::MTime"
|
|
47 #define THUMB_MARKER_SIZE "tEXt::Thumb::Size"
|
|
48 #define THUMB_MARKER_WIDTH "tEXt::Thumb::Image::Width"
|
|
49 #define THUMB_MARKER_HEIGHT "tEXt::Thumb::Image::Height"
|
|
50 #define THUMB_MARKER_APP "tEXt::Software"
|
|
51
|
|
52 #define THUMB_PERMS_FOLDER 0700
|
|
53 #define THUMB_PERMS_THUMB 0600
|
|
54
|
|
55
|
|
56
|
|
57 /*
|
|
58 *-----------------------------------------------------------------------------
|
|
59 * thumbnail loader
|
|
60 *-----------------------------------------------------------------------------
|
|
61 */
|
|
62
|
|
63
|
|
64 static void thumb_loader_std_error_cb(ImageLoader *il, gpointer data);
|
|
65 static gint thumb_loader_std_setup(ThumbLoaderStd *tl, const gchar *path);
|
|
66
|
|
67
|
|
68 ThumbLoaderStd *thumb_loader_std_new(gint width, gint height)
|
|
69 {
|
|
70 ThumbLoaderStd *tl;
|
|
71
|
|
72 tl = g_new0(ThumbLoaderStd, 1);
|
|
73
|
|
74 tl->standard_loader = TRUE;
|
|
75
|
|
76 tl->requested_width = width;
|
|
77 tl->requested_height = height;
|
|
78
|
|
79 tl->pixbuf = NULL;
|
|
80 tl->il = NULL;
|
|
81 tl->source_path = NULL;
|
|
82
|
|
83 tl->cache_enable = enable_thumb_caching;
|
|
84 tl->cache_local = FALSE;
|
|
85 tl->cache_retry = FALSE;
|
|
86
|
|
87 return tl;
|
|
88 }
|
|
89
|
|
90 void thumb_loader_std_set_callbacks(ThumbLoaderStd *tl,
|
|
91 ThumbLoaderStdFunc func_done,
|
|
92 ThumbLoaderStdFunc func_error,
|
|
93 ThumbLoaderStdFunc func_progress,
|
|
94 gpointer data)
|
|
95 {
|
|
96 if (!tl) return;
|
|
97
|
|
98 tl->func_done = func_done;
|
|
99 tl->func_error = func_error;
|
|
100 tl->func_progress = func_progress;
|
|
101 tl->data = data;
|
|
102 }
|
|
103
|
|
104 static void thumb_loader_std_reset(ThumbLoaderStd *tl)
|
|
105 {
|
|
106 if (tl->pixbuf) g_object_unref(tl->pixbuf);
|
|
107 tl->pixbuf = NULL;
|
|
108
|
|
109 image_loader_free(tl->il);
|
|
110 tl->il = NULL;
|
|
111
|
|
112 g_free(tl->source_path);
|
|
113 tl->source_path = NULL;
|
|
114
|
|
115 g_free(tl->thumb_path);
|
|
116 tl->thumb_path = NULL;
|
|
117
|
|
118 g_free(tl->thumb_uri);
|
|
119 tl->thumb_uri = NULL;
|
|
120 tl->local_uri = NULL;
|
|
121
|
|
122 tl->thumb_path_local = FALSE;
|
|
123
|
|
124 tl->cache_hit = FALSE;
|
|
125
|
|
126 tl->source_mtime = 0;
|
|
127 tl->source_size = 0;
|
|
128 tl->source_mode = 0;
|
|
129
|
|
130 tl->progress = 0.0;
|
|
131 }
|
|
132
|
|
133 static gchar *thumb_std_cache_path(const gchar *path, const gchar *uri, gint local,
|
|
134 const gchar *cache_subfolder)
|
|
135 {
|
|
136 gchar *result = NULL;
|
|
137 gchar *cache_base;
|
|
138 gchar *md5_text;
|
|
139 guchar digest[16];
|
|
140
|
|
141 if (!path || !uri || !cache_subfolder) return NULL;
|
|
142
|
|
143 if (local)
|
|
144 {
|
|
145 gchar *base;
|
|
146
|
|
147 base = remove_level_from_path(path);
|
|
148 cache_base = g_strconcat(base, "/", THUMB_FOLDER, "/", cache_subfolder, NULL);
|
|
149 g_free(base);
|
|
150 }
|
|
151 else
|
|
152 {
|
|
153 cache_base = g_strconcat(homedir(), "/", THUMB_FOLDER, "/", cache_subfolder, NULL);
|
|
154 }
|
|
155
|
|
156 md5_get_digest(uri, strlen(uri), digest);
|
|
157 md5_text = md5_digest_to_text(digest);
|
|
158
|
|
159 if (cache_base && md5_text)
|
|
160 {
|
|
161 result = g_strconcat(cache_base, "/", md5_text, THUMB_NAME_EXTENSION, NULL);
|
|
162 }
|
|
163
|
|
164 g_free(cache_base);
|
|
165 g_free(md5_text);
|
|
166
|
|
167 return result;
|
|
168 }
|
|
169
|
|
170 static gchar *thumb_loader_std_cache_path(ThumbLoaderStd *tl, gint local, GdkPixbuf *pixbuf, gint fail)
|
|
171 {
|
|
172 #if 0
|
|
173 gchar *result = NULL;
|
|
174 gchar *cache_base;
|
|
175 #endif
|
|
176 const gchar *folder_size;
|
|
177 #if 0
|
|
178 const gchar *uri;
|
|
179 gchar *md5_text;
|
|
180 guchar digest[16];
|
|
181 #endif
|
|
182 gint w, h;
|
|
183
|
|
184 if (!tl->source_path || !tl->thumb_uri) return NULL;
|
|
185
|
|
186 if (pixbuf)
|
|
187 {
|
|
188 w = gdk_pixbuf_get_width(pixbuf);
|
|
189 h = gdk_pixbuf_get_height(pixbuf);
|
|
190 }
|
|
191 else
|
|
192 {
|
|
193 w = tl->requested_width;
|
|
194 h = tl->requested_height;
|
|
195 }
|
|
196
|
|
197 if (fail)
|
|
198 {
|
|
199 folder_size = THUMB_FOLDER_FAIL;
|
|
200 }
|
|
201 else if (w > THUMB_SIZE_NORMAL || h > THUMB_SIZE_NORMAL)
|
|
202 {
|
|
203 folder_size = THUMB_FOLDER_LARGE;
|
|
204 }
|
|
205 else
|
|
206 {
|
|
207 folder_size = THUMB_FOLDER_NORMAL;
|
|
208 }
|
|
209
|
|
210 return thumb_std_cache_path(tl->source_path,
|
|
211 (local) ? tl->local_uri : tl->thumb_uri,
|
|
212 local, folder_size);
|
|
213
|
|
214 #if 0
|
|
215 if (local)
|
|
216 {
|
|
217 gchar *base;
|
|
218
|
|
219 base = remove_level_from_path(tl->source_path);
|
|
220 cache_base = g_strconcat(base, "/", THUMB_FOLDER, "/", folder_size, NULL);
|
|
221 g_free(base);
|
|
222 }
|
|
223 else
|
|
224 {
|
|
225 cache_base = g_strconcat(homedir(), "/", THUMB_FOLDER, "/", folder_size, NULL);
|
|
226 }
|
|
227
|
|
228 uri = (local) ? tl->local_uri : tl->thumb_uri;
|
|
229 md5_get_digest(uri, strlen(uri), digest);
|
|
230 md5_text = md5_digest_to_text(digest);
|
|
231
|
|
232 if (cache_base && md5_text)
|
|
233 {
|
|
234 result = g_strconcat(cache_base, "/", md5_text, THUMB_NAME_EXTENSION, NULL);
|
|
235 }
|
|
236
|
|
237 g_free(cache_base);
|
|
238 g_free(md5_text);
|
|
239
|
|
240 return result;
|
|
241 #endif
|
|
242 }
|
|
243
|
|
244 static gint thumb_loader_std_fail_check(ThumbLoaderStd *tl)
|
|
245 {
|
|
246 gchar *fail_path;
|
|
247 gint result = FALSE;
|
|
248
|
|
249 fail_path = thumb_loader_std_cache_path(tl, FALSE, NULL, TRUE);
|
|
250 if (isfile(fail_path))
|
|
251 {
|
|
252 GdkPixbuf *pixbuf;
|
|
253
|
|
254 if (tl->cache_retry)
|
|
255 {
|
|
256 pixbuf = NULL;
|
|
257 }
|
|
258 else
|
|
259 {
|
|
260 gchar *pathl;
|
|
261
|
|
262 pathl = path_from_utf8(fail_path);
|
|
263 pixbuf = gdk_pixbuf_new_from_file(pathl, NULL);
|
|
264 g_free(pathl);
|
|
265 }
|
|
266
|
|
267 if (pixbuf)
|
|
268 {
|
|
269 const gchar *mtime_str;
|
|
270
|
|
271 mtime_str = gdk_pixbuf_get_option(pixbuf, THUMB_MARKER_MTIME);
|
|
272 if (mtime_str && strtol(mtime_str, NULL, 10) == tl->source_mtime)
|
|
273 {
|
|
274 result = TRUE;
|
|
275 if (debug)
|
|
276 {
|
|
277 printf("thumb fail valid: %s\n", tl->source_path);
|
|
278 printf(" thumb: %s\n", fail_path);
|
|
279 }
|
|
280 }
|
|
281
|
|
282 g_object_unref(G_OBJECT(pixbuf));
|
|
283 }
|
|
284
|
|
285 if (!result) unlink_file(fail_path);
|
|
286 }
|
|
287 g_free(fail_path);
|
|
288
|
|
289 return result;
|
|
290 }
|
|
291
|
|
292 static gint thumb_loader_std_validate(ThumbLoaderStd *tl, GdkPixbuf *pixbuf)
|
|
293 {
|
|
294 const gchar *valid_uri;
|
|
295 const gchar *uri;
|
|
296 const gchar *mtime_str;
|
|
297 time_t mtime;
|
|
298 gint w, h;
|
|
299
|
|
300 if (!pixbuf) return FALSE;
|
|
301
|
|
302 w = gdk_pixbuf_get_width(pixbuf);
|
|
303 h = gdk_pixbuf_get_height(pixbuf);
|
|
304
|
|
305 if (w != THUMB_SIZE_NORMAL && w != THUMB_SIZE_LARGE &&
|
|
306 h != THUMB_SIZE_NORMAL && h != THUMB_SIZE_LARGE) return FALSE;
|
|
307
|
|
308 valid_uri = (tl->thumb_path_local) ? tl->local_uri : tl->thumb_uri;
|
|
309
|
|
310 uri = gdk_pixbuf_get_option(pixbuf, THUMB_MARKER_URI);
|
|
311 mtime_str = gdk_pixbuf_get_option(pixbuf, THUMB_MARKER_MTIME);
|
|
312
|
|
313 if (!mtime_str || !uri || !valid_uri) return FALSE;
|
|
314 if (strcmp(uri, valid_uri) != 0) return FALSE;
|
|
315
|
|
316 mtime = strtol(mtime_str, NULL, 10);
|
|
317 if (tl->source_mtime != mtime) return FALSE;
|
|
318
|
|
319 return TRUE;
|
|
320 }
|
|
321
|
|
322 static void thumb_loader_std_save(ThumbLoaderStd *tl, GdkPixbuf *pixbuf)
|
|
323 {
|
|
324 gchar *base_path;
|
|
325 gchar *tmp_path;
|
|
326 gint fail;
|
|
327
|
|
328 if (!tl->cache_enable || tl->cache_hit) return;
|
|
329 if (tl->thumb_path) return;
|
|
330
|
|
331 if (!pixbuf)
|
|
332 {
|
|
333 /* local failures are not stored */
|
|
334 if (tl->cache_local) return;
|
|
335
|
|
336 pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
|
|
337 fail = TRUE;
|
|
338 }
|
|
339 else
|
|
340 {
|
|
341 g_object_ref(G_OBJECT(pixbuf));
|
|
342 fail = FALSE;
|
|
343 }
|
|
344
|
|
345 tl->thumb_path = thumb_loader_std_cache_path(tl, tl->cache_local, pixbuf, fail);
|
|
346 if (!tl->thumb_path)
|
|
347 {
|
|
348 g_object_unref(G_OBJECT(pixbuf));
|
|
349 return;
|
|
350 }
|
|
351 tl->thumb_path_local = tl->cache_local;
|
|
352
|
|
353 /* create thumbnail dir if needed */
|
|
354 base_path = remove_level_from_path(tl->thumb_path);
|
|
355 if (tl->cache_local)
|
|
356 {
|
|
357 if (!isdir(base_path))
|
|
358 {
|
|
359 struct stat st;
|
|
360 gchar *source_base;
|
|
361
|
|
362 source_base = remove_level_from_path(tl->source_path);
|
|
363 if (stat_utf8(source_base, &st))
|
|
364 {
|
|
365 cache_ensure_dir_exists(base_path, st.st_mode);
|
|
366 }
|
|
367 g_free(source_base);
|
|
368 }
|
|
369 }
|
|
370 else
|
|
371 {
|
|
372 cache_ensure_dir_exists(base_path, THUMB_PERMS_FOLDER);
|
|
373 }
|
|
374 g_free(base_path);
|
|
375
|
|
376 if (debug)
|
|
377 {
|
|
378 printf("thumb saving: %s\n", tl->source_path);
|
|
379 printf(" saved: %s\n", tl->thumb_path);
|
|
380 }
|
|
381
|
|
382 /* save thumb, using a temp file then renaming into place */
|
|
383 tmp_path = unique_filename(tl->thumb_path, ".tmp", "_", 2);
|
|
384 if (tmp_path)
|
|
385 {
|
|
386 const gchar *mark_uri;
|
|
387 gchar *mark_app;
|
|
388 gchar *mark_mtime;
|
|
389 gchar *pathl;
|
|
390 gint success;
|
|
391
|
|
392 mark_uri = (tl->cache_local) ? tl->local_uri :tl->thumb_uri;
|
|
393
|
|
394 mark_app = g_strdup_printf("GQview %s", VERSION);
|
|
395 mark_mtime = g_strdup_printf("%lu", tl->source_mtime);
|
|
396
|
|
397 pathl = path_from_utf8(tmp_path);
|
|
398 success = gdk_pixbuf_save(pixbuf, pathl, "png", NULL,
|
|
399 THUMB_MARKER_URI, mark_uri,
|
|
400 THUMB_MARKER_MTIME, mark_mtime,
|
|
401 THUMB_MARKER_APP, mark_app,
|
|
402 NULL);
|
|
403 if (success)
|
|
404 {
|
|
405 chmod(pathl, (tl->cache_local) ? tl->source_mode : THUMB_PERMS_THUMB);
|
|
406 success = rename_file(tmp_path, tl->thumb_path);
|
|
407 }
|
|
408
|
|
409 g_free(pathl);
|
|
410
|
|
411 g_free(mark_mtime);
|
|
412 g_free(mark_app);
|
|
413
|
|
414 g_free(tmp_path);
|
|
415 if (!success && debug)
|
|
416 {
|
|
417 printf("thumb save failed: %s\n", tl->source_path);
|
|
418 printf(" thumb: %s\n", tl->thumb_path);
|
|
419 }
|
|
420
|
|
421 }
|
|
422
|
|
423 g_object_unref(G_OBJECT(pixbuf));
|
|
424 }
|
|
425
|
|
426 static gint thumb_loader_std_scale_aspect(gint req_w, gint req_h, gint old_w, gint old_h,
|
|
427 gint *new_w, gint *new_h)
|
|
428 {
|
|
429 if (((gdouble)req_w / old_w) < ((gdouble)req_h / old_h))
|
|
430 {
|
|
431 *new_w = req_w;
|
|
432 *new_h = (gdouble)*new_w / old_w * old_h;
|
|
433 if (*new_h < 1) *new_h = 1;
|
|
434 }
|
|
435 else
|
|
436 {
|
|
437 *new_h = req_h;
|
|
438 *new_w = (gdouble)*new_h / old_h * old_w;
|
|
439 if (*new_w < 1) *new_w = 1;
|
|
440 }
|
|
441
|
|
442 return (*new_w != old_w || *new_h != old_h);
|
|
443 }
|
|
444
|
|
445 static GdkPixbuf *thumb_loader_std_finish(ThumbLoaderStd *tl, GdkPixbuf *pixbuf)
|
|
446 {
|
|
447 GdkPixbuf *pixbuf_thumb = NULL;
|
|
448 GdkPixbuf *result;
|
|
449 gint sw, sh;
|
|
450
|
|
451 sw = gdk_pixbuf_get_width(pixbuf);
|
|
452 sh = gdk_pixbuf_get_height(pixbuf);
|
|
453
|
|
454 if (tl->cache_enable && !tl->cache_hit &&
|
|
455 (sw >= THUMB_SIZE_NORMAL || sh >= THUMB_SIZE_NORMAL))
|
|
456 {
|
|
457 gint cache_w, cache_h;
|
|
458 gint thumb_w, thumb_h;
|
|
459
|
|
460 if (tl->requested_width > THUMB_SIZE_NORMAL || tl->requested_height > THUMB_SIZE_NORMAL)
|
|
461 {
|
|
462 cache_w = cache_h = THUMB_SIZE_LARGE;
|
|
463 }
|
|
464 else
|
|
465 {
|
|
466 cache_w = cache_h = THUMB_SIZE_NORMAL;
|
|
467 }
|
|
468
|
|
469 if (thumb_loader_std_scale_aspect(cache_w, cache_h, sw, sh,
|
|
470 &thumb_w, &thumb_h))
|
|
471 {
|
|
472 pixbuf_thumb = gdk_pixbuf_scale_simple(pixbuf, thumb_w, thumb_h,
|
|
473 (GdkInterpType)thumbnail_quality);
|
|
474 }
|
|
475 else
|
|
476 {
|
|
477 pixbuf_thumb = pixbuf;
|
|
478 g_object_ref(G_OBJECT(pixbuf_thumb));
|
|
479 }
|
|
480
|
|
481 thumb_loader_std_save(tl, pixbuf_thumb);
|
|
482 }
|
|
483 else if (tl->cache_enable && tl->cache_local &&
|
|
484 tl->cache_hit && !tl->thumb_path_local)
|
|
485 {
|
|
486 /* A local cache save was requested, but a valid thumb is in $HOME,
|
|
487 * so specifically save as a local thumbnail.
|
|
488 */
|
|
489 g_free(tl->thumb_path);
|
|
490 tl->thumb_path = NULL;
|
|
491
|
|
492 tl->cache_hit = FALSE;
|
|
493
|
|
494 if (debug) printf("thumb copied: %s\n", tl->source_path);
|
|
495
|
|
496 thumb_loader_std_save(tl, pixbuf);
|
|
497 }
|
|
498
|
|
499 if (sw <= tl->requested_width && sh <= tl->requested_height)
|
|
500 {
|
|
501 result = pixbuf;
|
|
502 g_object_ref(result);
|
|
503 }
|
|
504 else
|
|
505 {
|
|
506 gint thumb_w, thumb_h;
|
|
507
|
|
508 if (pixbuf_thumb)
|
|
509 {
|
|
510 pixbuf = pixbuf_thumb;
|
|
511 sw = gdk_pixbuf_get_width(pixbuf);
|
|
512 sh = gdk_pixbuf_get_height(pixbuf);
|
|
513 }
|
|
514
|
|
515 if (thumb_loader_std_scale_aspect(tl->requested_width, tl->requested_height, sw, sh,
|
|
516 &thumb_w, &thumb_h))
|
|
517 {
|
|
518 result = gdk_pixbuf_scale_simple(pixbuf, thumb_w, thumb_h,
|
|
519 (GdkInterpType)thumbnail_quality);
|
|
520 }
|
|
521 else
|
|
522 {
|
|
523 result = pixbuf;
|
|
524 g_object_ref(result);
|
|
525 }
|
|
526 }
|
|
527
|
|
528 if (pixbuf_thumb) g_object_unref(pixbuf_thumb);
|
|
529
|
|
530 return result;
|
|
531 }
|
|
532
|
|
533 static gint thumb_loader_std_next_source(ThumbLoaderStd *tl, gint remove_broken)
|
|
534 {
|
|
535 image_loader_free(tl->il);
|
|
536 tl->il = NULL;
|
|
537
|
|
538 if (tl->thumb_path)
|
|
539 {
|
|
540 if (!tl->thumb_path_local && remove_broken)
|
|
541 {
|
|
542 if (debug) printf("thumb broken, unlinking: %s\n", tl->thumb_path);
|
|
543 unlink_file(tl->thumb_path);
|
|
544 }
|
|
545
|
|
546 g_free(tl->thumb_path);
|
|
547 tl->thumb_path = NULL;
|
|
548
|
|
549 if (!tl->thumb_path_local)
|
|
550 {
|
|
551 tl->thumb_path = thumb_loader_std_cache_path(tl, TRUE, NULL, FALSE);
|
|
552 if (isfile(tl->thumb_path) && thumb_loader_std_setup(tl, tl->thumb_path))
|
|
553 {
|
|
554 tl->thumb_path_local = TRUE;
|
|
555 return TRUE;
|
|
556 }
|
|
557
|
|
558 g_free(tl->thumb_path);
|
|
559 tl->thumb_path = NULL;
|
|
560 }
|
|
561
|
|
562 if (thumb_loader_std_setup(tl, tl->source_path)) return TRUE;
|
|
563 }
|
|
564
|
|
565 thumb_loader_std_save(tl, NULL);
|
|
566 return FALSE;
|
|
567 }
|
|
568
|
|
569 static void thumb_loader_std_done_cb(ImageLoader *il, gpointer data)
|
|
570 {
|
|
571 ThumbLoaderStd *tl = data;
|
|
572 GdkPixbuf *pixbuf;
|
|
573
|
|
574 if (debug)
|
|
575 {
|
|
576 printf("thumb image done: %s\n", tl->source_path);
|
|
577 printf(" from: %s\n", tl->il->path);
|
|
578 }
|
|
579
|
|
580 pixbuf = image_loader_get_pixbuf(tl->il);
|
|
581 if (!pixbuf)
|
|
582 {
|
|
583 if (debug) printf("...but no pixbuf\n");
|
|
584 thumb_loader_std_error_cb(il, data);
|
|
585 return;
|
|
586 }
|
|
587
|
|
588 if (tl->thumb_path && !thumb_loader_std_validate(tl, pixbuf))
|
|
589 {
|
|
590 if (thumb_loader_std_next_source(tl, TRUE)) return;
|
|
591
|
|
592 if (tl->func_error) tl->func_error(tl, tl->data);
|
|
593 return;
|
|
594 }
|
|
595
|
|
596 tl->cache_hit = (tl->thumb_path != NULL);
|
|
597
|
|
598 tl->pixbuf = thumb_loader_std_finish(tl, pixbuf);
|
|
599
|
|
600 if (tl->func_done) tl->func_done(tl, tl->data);
|
|
601 }
|
|
602
|
|
603 static void thumb_loader_std_error_cb(ImageLoader *il, gpointer data)
|
|
604 {
|
|
605 ThumbLoaderStd *tl = data;
|
|
606
|
|
607 /* if at least some of the image is available, go to done */
|
|
608 if (image_loader_get_pixbuf(tl->il) != NULL)
|
|
609 {
|
|
610 thumb_loader_std_done_cb(il, data);
|
|
611 return;
|
|
612 }
|
|
613
|
|
614 if (debug)
|
|
615 {
|
|
616 printf("thumb image error: %s\n", tl->source_path);
|
|
617 printf(" from: %s\n", tl->il->path);
|
|
618 }
|
|
619
|
|
620 if (thumb_loader_std_next_source(tl, TRUE)) return;
|
|
621
|
|
622 if (tl->func_error) tl->func_error(tl, tl->data);
|
|
623 }
|
|
624
|
|
625 static void thumb_loader_std_progress_cb(ImageLoader *il, gdouble percent, gpointer data)
|
|
626 {
|
|
627 ThumbLoaderStd *tl = data;
|
|
628
|
|
629 tl->progress = (gdouble)percent;
|
|
630
|
|
631 if (tl->func_progress) tl->func_progress(tl, tl->data);
|
|
632 }
|
|
633
|
|
634 static gint thumb_loader_std_setup(ThumbLoaderStd *tl, const gchar *path)
|
|
635 {
|
|
636 tl->il = image_loader_new(path);
|
|
637
|
|
638 #if 0
|
|
639 /* this will speed up jpegs by up to 3x in some cases */
|
|
640 if (tl->requested_width <= THUMB_SIZE_NORMAL &&
|
|
641 tl->requested_height <= THUMB_SIZE_NORMAL)
|
|
642 {
|
|
643 image_loader_set_requested_size(tl->il, THUMB_SIZE_NORMAL, THUMB_SIZE_NORMAL);
|
|
644 }
|
|
645 else
|
|
646 {
|
|
647 image_loader_set_requested_size(tl->il, THUMB_SIZE_LARGE, THUMB_SIZE_LARGE);
|
|
648 }
|
|
649 #endif
|
|
650
|
|
651 image_loader_set_error_func(tl->il, thumb_loader_std_error_cb, tl);
|
|
652 if (tl->func_progress)
|
|
653 {
|
|
654 image_loader_set_percent_func(tl->il, thumb_loader_std_progress_cb, tl);
|
|
655 }
|
|
656
|
|
657 if (image_loader_start(tl->il, thumb_loader_std_done_cb, tl))
|
|
658 {
|
|
659 return TRUE;
|
|
660 }
|
|
661
|
|
662 image_loader_free(tl->il);
|
|
663 tl->il = NULL;
|
|
664 return FALSE;
|
|
665 }
|
|
666
|
|
667 /*
|
|
668 * Note: Currently local_cache only specifies where to save a _new_ thumb, if
|
|
669 * a valid existing thumb is found anywhere the local thumb will not be created.
|
|
670 */
|
|
671 void thumb_loader_std_set_cache(ThumbLoaderStd *tl, gint enable_cache, gint local, gint retry_failed)
|
|
672 {
|
|
673 if (!tl) return;
|
|
674
|
|
675 tl->cache_enable = enable_cache;
|
|
676 tl->cache_local = local;
|
|
677 tl->cache_retry = retry_failed;
|
|
678 }
|
|
679
|
|
680 gint thumb_loader_std_start(ThumbLoaderStd *tl, const gchar *path)
|
|
681 {
|
|
682 static gchar *thumb_cache = NULL;
|
|
683 struct stat st;
|
|
684
|
|
685 if (!tl || !path) return FALSE;
|
|
686
|
|
687 thumb_loader_std_reset(tl);
|
|
688
|
|
689 if (!stat_utf8(path, &st)) return FALSE;
|
|
690
|
|
691 tl->source_path = g_strdup(path);
|
|
692 tl->source_mtime = st.st_mtime;
|
|
693 tl->source_size = st.st_size;
|
|
694 tl->source_mode = st.st_mode;
|
|
695
|
|
696 if (!thumb_cache) thumb_cache = g_strconcat(homedir(), "/", THUMB_FOLDER, NULL);
|
|
697 if (strncmp(tl->source_path, thumb_cache, strlen(thumb_cache)) != 0)
|
|
698 {
|
|
699 gchar *pathl;
|
|
700
|
|
701 pathl = path_from_utf8(path);
|
|
702 tl->thumb_uri = g_filename_to_uri(pathl, NULL, NULL);
|
|
703 tl->local_uri = filename_from_path(tl->thumb_uri);
|
|
704 g_free(pathl);
|
|
705 }
|
|
706
|
|
707 if (tl->cache_enable)
|
|
708 {
|
|
709 gint found;
|
|
710
|
|
711 if (thumb_loader_std_fail_check(tl)) return FALSE;
|
|
712
|
|
713 tl->thumb_path = thumb_loader_std_cache_path(tl, FALSE, NULL, FALSE);
|
|
714 tl->thumb_path_local = FALSE;
|
|
715
|
|
716 found = isfile(tl->thumb_path);
|
|
717 if (found && thumb_loader_std_setup(tl, tl->thumb_path)) return TRUE;
|
|
718
|
|
719 return thumb_loader_std_next_source(tl, found);
|
|
720 }
|
|
721
|
|
722 if (!thumb_loader_std_setup(tl, tl->source_path))
|
|
723 {
|
|
724 thumb_loader_std_save(tl, NULL);
|
|
725 return FALSE;
|
|
726 }
|
|
727
|
|
728 return TRUE;
|
|
729 }
|
|
730
|
|
731 void thumb_loader_std_free(ThumbLoaderStd *tl)
|
|
732 {
|
|
733 if (!tl) return;
|
|
734
|
|
735 thumb_loader_std_reset(tl);
|
|
736 g_free(tl);
|
|
737 }
|
|
738
|
|
739 GdkPixbuf *thumb_loader_std_get_pixbuf(ThumbLoaderStd *tl, gint with_fallback)
|
|
740 {
|
|
741 GdkPixbuf *pixbuf;
|
|
742
|
|
743 if (tl && tl->pixbuf)
|
|
744 {
|
|
745 pixbuf = tl->pixbuf;
|
|
746 g_object_ref(pixbuf);
|
|
747 }
|
|
748 else if (with_fallback)
|
|
749 {
|
|
750 gint w, h;
|
|
751
|
|
752 pixbuf = pixbuf_inline(PIXBUF_INLINE_BROKEN);
|
|
753 w = gdk_pixbuf_get_width(pixbuf);
|
|
754 h = gdk_pixbuf_get_height(pixbuf);
|
|
755
|
|
756 if (w > tl->requested_width || h > tl->requested_height)
|
|
757 {
|
|
758 gint nw, nh;
|
|
759
|
|
760 if (thumb_loader_std_scale_aspect(tl->requested_width, tl->requested_height,
|
|
761 w, h, &nw, &nh))
|
|
762 {
|
|
763 GdkPixbuf *tmp;
|
|
764
|
|
765 tmp = pixbuf;
|
|
766 pixbuf = gdk_pixbuf_scale_simple(tmp, nw, nh, GDK_INTERP_TILES);
|
|
767 g_object_unref(G_OBJECT(tmp));
|
|
768 }
|
|
769 }
|
|
770 }
|
|
771 else
|
|
772 {
|
|
773 pixbuf = NULL;
|
|
774 }
|
|
775
|
|
776 return pixbuf;
|
|
777 }
|
|
778
|
|
779
|
|
780 typedef struct _ThumbValidate ThumbValidate;
|
|
781 struct _ThumbValidate
|
|
782 {
|
|
783 ThumbLoaderStd *tl;
|
|
784 gchar *path;
|
|
785 gint days;
|
|
786
|
|
787 void (*func_valid)(const gchar *path, gint valid, gpointer data);
|
|
788 gpointer data;
|
|
789
|
|
790 gint idle_id;
|
|
791 };
|
|
792
|
|
793 static void thumb_loader_std_thumb_file_validate_free(ThumbValidate *tv)
|
|
794 {
|
|
795 thumb_loader_std_free(tv->tl);
|
|
796 g_free(tv->path);
|
|
797 g_free(tv);
|
|
798 }
|
|
799
|
|
800 void thumb_loader_std_thumb_file_validate_cancel(ThumbLoaderStd *tl)
|
|
801 {
|
|
802 ThumbValidate *tv;
|
|
803
|
|
804 if (!tl) return;
|
|
805
|
|
806 tv = tl->data;
|
|
807
|
|
808 if (tv->idle_id != -1) g_source_remove(tv->idle_id);
|
|
809 tv->idle_id = -1;
|
|
810
|
|
811 thumb_loader_std_thumb_file_validate_free(tv);
|
|
812 }
|
|
813
|
|
814 static void thumb_loader_std_thumb_file_validate_finish(ThumbValidate *tv, gint valid)
|
|
815 {
|
|
816 if (tv->func_valid) tv->func_valid(tv->path, valid, tv->data);
|
|
817
|
|
818 thumb_loader_std_thumb_file_validate_free(tv);
|
|
819 }
|
|
820
|
|
821 static void thumb_loader_std_thumb_file_validate_done_cb(ThumbLoaderStd *tl, gpointer data)
|
|
822 {
|
|
823 ThumbValidate *tv = data;
|
|
824 GdkPixbuf *pixbuf;
|
|
825 gint valid = FALSE;
|
|
826
|
|
827 pixbuf = thumb_loader_std_get_pixbuf(tv->tl, FALSE);
|
|
828 if (pixbuf)
|
|
829 {
|
|
830 const gchar *uri;
|
|
831 const gchar *mtime_str;
|
|
832
|
|
833 uri = gdk_pixbuf_get_option(pixbuf, THUMB_MARKER_URI);
|
|
834 mtime_str = gdk_pixbuf_get_option(pixbuf, THUMB_MARKER_MTIME);
|
|
835 if (uri && mtime_str)
|
|
836 {
|
|
837 if (strncmp(uri, "file:", strlen("file:")) == 0)
|
|
838 {
|
|
839 struct stat st;
|
|
840 gchar *target;
|
|
841
|
|
842 target = g_filename_from_uri(uri, NULL, NULL);
|
|
843 if (stat(target, &st) == 0 &&
|
|
844 st.st_mtime == strtol(mtime_str, NULL, 10))
|
|
845 {
|
|
846 valid = TRUE;
|
|
847 }
|
|
848 g_free(target);
|
|
849 }
|
|
850 else
|
|
851 {
|
|
852 struct stat st;
|
|
853
|
|
854 if (debug) printf("thumb uri foreign, doing day check: %s\n", uri);
|
|
855
|
|
856 if (stat_utf8(tv->path, &st))
|
|
857 {
|
|
858 time_t now;
|
|
859
|
|
860 now = time(NULL);
|
|
861 if (st.st_atime >= now - (time_t)tv->days * 24 * 60 * 60)
|
|
862 {
|
|
863 valid = TRUE;
|
|
864 }
|
|
865 }
|
|
866 }
|
|
867 }
|
|
868
|
|
869 g_object_unref(pixbuf);
|
|
870 }
|
|
871
|
|
872 thumb_loader_std_thumb_file_validate_finish(tv, valid);
|
|
873 }
|
|
874
|
|
875 static void thumb_loader_std_thumb_file_validate_error_cb(ThumbLoaderStd *tl, gpointer data)
|
|
876 {
|
|
877 ThumbValidate *tv = data;
|
|
878
|
|
879 thumb_loader_std_thumb_file_validate_finish(tv, FALSE);
|
|
880 }
|
|
881
|
|
882 static gint thumb_loader_std_thumb_file_validate_idle_cb(gpointer data)
|
|
883 {
|
|
884 ThumbValidate *tv = data;
|
|
885
|
|
886 tv->idle_id = -1;
|
|
887 thumb_loader_std_thumb_file_validate_finish(tv, FALSE);
|
|
888
|
|
889 return FALSE;
|
|
890 }
|
|
891
|
|
892 ThumbLoaderStd *thumb_loader_std_thumb_file_validate(const gchar *thumb_path, gint allowed_days,
|
|
893 void (*func_valid)(const gchar *path, gint valid, gpointer data),
|
|
894 gpointer data)
|
|
895 {
|
|
896 ThumbValidate *tv;
|
|
897
|
|
898 tv = g_new0(ThumbValidate, 1);
|
|
899
|
|
900 tv->tl = thumb_loader_std_new(THUMB_SIZE_LARGE, THUMB_SIZE_LARGE);
|
|
901 thumb_loader_std_set_callbacks(tv->tl,
|
|
902 thumb_loader_std_thumb_file_validate_done_cb,
|
|
903 thumb_loader_std_thumb_file_validate_error_cb,
|
|
904 NULL,
|
|
905 tv);
|
|
906 thumb_loader_std_reset(tv->tl);
|
|
907
|
|
908 tv->path = g_strdup(thumb_path);
|
|
909 tv->days = allowed_days;
|
|
910 tv->func_valid = func_valid;
|
|
911 tv->data = data;
|
|
912
|
|
913 if (!thumb_loader_std_setup(tv->tl, thumb_path))
|
|
914 {
|
|
915 tv->idle_id = g_idle_add(thumb_loader_std_thumb_file_validate_idle_cb, tv);
|
|
916 }
|
|
917 else
|
|
918 {
|
|
919 tv->idle_id = -1;
|
|
920 }
|
|
921
|
|
922 return tv->tl;
|
|
923 }
|
|
924
|
|
925 static void thumb_std_maint_remove_one(const gchar *source, const gchar *uri, gint local,
|
|
926 const gchar *subfolder)
|
|
927 {
|
|
928 gchar *thumb_path;
|
|
929
|
|
930 thumb_path = thumb_std_cache_path(source,
|
|
931 (local) ? filename_from_path(uri) : uri,
|
|
932 local, subfolder);
|
|
933 if (isfile(thumb_path))
|
|
934 {
|
|
935 if (debug) printf("thumb removing: %s\n", thumb_path);
|
|
936 unlink_file(thumb_path);
|
|
937 }
|
|
938 g_free(thumb_path);
|
|
939 }
|
|
940
|
|
941 /* this also removes local thumbnails (the source is gone so it makes sense) */
|
|
942 void thumb_std_maint_removed(const gchar *source)
|
|
943 {
|
|
944 gchar *uri;
|
|
945 gchar *sourcel;
|
|
946
|
|
947 sourcel = path_from_utf8(source);
|
|
948 uri = g_filename_to_uri(sourcel, NULL, NULL);
|
|
949 g_free(sourcel);
|
|
950
|
|
951 /* all this to remove a thumbnail? */
|
|
952
|
|
953 thumb_std_maint_remove_one(source, uri, FALSE, THUMB_FOLDER_NORMAL);
|
|
954 thumb_std_maint_remove_one(source, uri, FALSE, THUMB_FOLDER_LARGE);
|
|
955 thumb_std_maint_remove_one(source, uri, FALSE, THUMB_FOLDER_FAIL);
|
|
956 thumb_std_maint_remove_one(source, uri, TRUE, THUMB_FOLDER_NORMAL);
|
|
957 thumb_std_maint_remove_one(source, uri, TRUE, THUMB_FOLDER_LARGE);
|
|
958
|
|
959 g_free(uri);
|
|
960 }
|
|
961
|
|
962 typedef struct _TMaintMove TMaintMove;
|
|
963 struct _TMaintMove
|
|
964 {
|
|
965 gchar *source;
|
|
966 gchar *dest;
|
|
967
|
|
968 ThumbLoaderStd *tl;
|
|
969 gchar *source_uri;
|
|
970 gchar *thumb_path;
|
|
971
|
|
972 gint pass;
|
|
973 };
|
|
974
|
|
975 static GList *thumb_std_maint_move_list = NULL;
|
|
976 static GList *thumb_std_maint_move_tail = NULL;
|
|
977
|
|
978
|
|
979 static void thumb_std_maint_move_step(TMaintMove *tm);
|
|
980 static gint thumb_std_maint_move_idle(gpointer data);
|
|
981
|
|
982
|
|
983 static void thumb_std_maint_move_validate_cb(const gchar *path, gint valid, gpointer data)
|
|
984 {
|
|
985 TMaintMove *tm = data;
|
|
986 GdkPixbuf *pixbuf;
|
|
987
|
|
988 pixbuf = thumb_loader_std_get_pixbuf(tm->tl, FALSE);
|
|
989 if (pixbuf)
|
|
990 {
|
|
991 const gchar *uri;
|
|
992 const gchar *mtime_str;
|
|
993
|
|
994 uri = gdk_pixbuf_get_option(pixbuf, THUMB_MARKER_URI);
|
|
995 mtime_str = gdk_pixbuf_get_option(pixbuf, THUMB_MARKER_MTIME);
|
|
996
|
|
997 if (uri && mtime_str && strcmp(uri, tm->source_uri) == 0)
|
|
998 {
|
|
999 gchar *pathl;
|
|
1000
|
|
1001 /* The validation utility abuses ThumbLoader, and we
|
|
1002 * abuse the utility just to load the thumbnail,
|
|
1003 * but the loader needs to look sane for the save to complete.
|
|
1004 */
|
|
1005
|
|
1006 tm->tl->cache_enable = TRUE;
|
|
1007 tm->tl->cache_hit = FALSE;
|
|
1008 tm->tl->cache_local = FALSE;
|
|
1009
|
|
1010 g_free(tm->tl->source_path);
|
|
1011 tm->tl->source_path = g_strdup(tm->dest);
|
|
1012 tm->tl->source_mtime = strtol(mtime_str, NULL, 10);
|
|
1013
|
|
1014 pathl = path_from_utf8(tm->tl->source_path);
|
|
1015 g_free(tm->tl->thumb_uri);
|
|
1016 tm->tl->thumb_uri = g_filename_to_uri(pathl, NULL, NULL);
|
|
1017 tm->tl->local_uri = filename_from_path(tm->tl->thumb_uri);
|
|
1018 g_free(pathl);
|
|
1019
|
|
1020 g_free(tm->tl->thumb_path);
|
|
1021 tm->tl->thumb_path = NULL;
|
|
1022 tm->tl->thumb_path_local = FALSE;
|
|
1023
|
|
1024 if (debug) printf("thumb move attempting save:\n");
|
|
1025
|
|
1026 thumb_loader_std_save(tm->tl, pixbuf);
|
|
1027 }
|
|
1028
|
|
1029 if (debug) printf("thumb move unlink: %s\n", tm->thumb_path);
|
|
1030 unlink_file(tm->thumb_path);
|
|
1031 }
|
|
1032
|
|
1033 thumb_std_maint_move_step(tm);
|
|
1034 }
|
|
1035
|
|
1036 static void thumb_std_maint_move_step(TMaintMove *tm)
|
|
1037 {
|
|
1038 const gchar *folder;
|
|
1039
|
|
1040 tm->pass++;
|
|
1041 if (tm->pass > 2)
|
|
1042 {
|
|
1043 g_free(tm->source);
|
|
1044 g_free(tm->dest);
|
|
1045 g_free(tm->source_uri);
|
|
1046 g_free(tm->thumb_path);
|
|
1047 g_free(tm);
|
|
1048
|
|
1049 if (thumb_std_maint_move_list)
|
|
1050 {
|
|
1051 g_idle_add_full(G_PRIORITY_LOW, thumb_std_maint_move_idle, NULL, NULL);
|
|
1052 }
|
|
1053
|
|
1054 return;
|
|
1055 }
|
|
1056
|
|
1057 folder = (tm->pass == 1) ? THUMB_FOLDER_NORMAL : THUMB_FOLDER_LARGE;
|
|
1058
|
|
1059 g_free(tm->thumb_path);
|
|
1060 tm->thumb_path = thumb_std_cache_path(tm->source, tm->source_uri, FALSE, folder);
|
|
1061 tm->tl = thumb_loader_std_thumb_file_validate(tm->thumb_path, 0,
|
|
1062 thumb_std_maint_move_validate_cb, tm);
|
|
1063 }
|
|
1064
|
|
1065 static gint thumb_std_maint_move_idle(gpointer data)
|
|
1066 {
|
|
1067 TMaintMove *tm;
|
|
1068 gchar *pathl;
|
|
1069
|
|
1070 if (!thumb_std_maint_move_list) return FALSE;
|
|
1071
|
|
1072 tm = thumb_std_maint_move_list->data;
|
|
1073
|
|
1074 thumb_std_maint_move_list = g_list_remove(thumb_std_maint_move_list, tm);
|
|
1075 if (!thumb_std_maint_move_list) thumb_std_maint_move_tail = NULL;
|
|
1076
|
|
1077 pathl = path_from_utf8(tm->source);
|
|
1078 tm->source_uri = g_filename_to_uri(pathl, NULL, NULL);
|
|
1079 g_free(pathl);
|
|
1080
|
|
1081 tm->pass = 0;
|
|
1082
|
|
1083 thumb_std_maint_move_step(tm);
|
|
1084
|
|
1085 return FALSE;
|
|
1086 }
|
|
1087
|
|
1088 /* This will schedule a move of the thumbnail for source image to dest when idle.
|
|
1089 * We do this so that file renaming or moving speed is not sacrificed by
|
|
1090 * moving the thumbnails at the same time because:
|
|
1091 *
|
|
1092 * This cache design requires the tedious task of loading the png thumbnails and saving them.
|
|
1093 *
|
|
1094 * The thumbnails are processed when the app is idle. If the app
|
|
1095 * exits early well too bad - they can simply be regenerated from scratch.
|
|
1096 *
|
|
1097 * This does not manage local thumbnails (fixme ?)
|
|
1098 */
|
|
1099 void thumb_std_maint_moved(const gchar *source, const gchar *dest)
|
|
1100 {
|
|
1101 TMaintMove *tm;
|
|
1102
|
|
1103 tm = g_new0(TMaintMove, 1);
|
|
1104 tm->source = g_strdup(source);
|
|
1105 tm->dest = g_strdup(dest);
|
|
1106
|
|
1107 if (!thumb_std_maint_move_list)
|
|
1108 {
|
|
1109 g_idle_add_full(G_PRIORITY_LOW, thumb_std_maint_move_idle, NULL, NULL);
|
|
1110 }
|
|
1111
|
|
1112 if (thumb_std_maint_move_tail)
|
|
1113 {
|
|
1114 g_list_append(thumb_std_maint_move_tail, tm);
|
|
1115 thumb_std_maint_move_tail = thumb_std_maint_move_tail->next;
|
|
1116 }
|
|
1117 else
|
|
1118 {
|
|
1119 thumb_std_maint_move_list = g_list_append(thumb_std_maint_move_list, tm);
|
|
1120 thumb_std_maint_move_tail = thumb_std_maint_move_list;
|
|
1121 }
|
|
1122 }
|
|
1123
|
|
1124
|