]> git.street.me.uk Git - andy/viking.git/blame - src/thumbnails.c
Detect gdk_pixbuf_save failures in thumbnail generation
[andy/viking.git] / src / thumbnails.c
CommitLineData
50a14534
EB
1/*
2 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
3 *
4 * Copyright (C) 2003-2005, Evan Battaglia <gtoevan@gmx.net>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 *
20 */
21
22/*
23 * Large (and important) sections of this file were adapted from
24 * ROX-Filer source code, Copyright (C) 2003, the ROX-Filer team,
25 * originally licensed under the GPL v2 or greater (as above).
26 *
27 */
28
45acf79e
MA
29#ifdef HAVE_CONFIG_H
30#include "config.h"
31#endif
32
50a14534 33#include <stdlib.h>
45acf79e
MA
34#ifdef HAVE_UNISTD_H
35#include <unistd.h>
36#endif
50a14534
EB
37#include <errno.h>
38#include <string.h>
f83131b9
MA
39#include <glib.h>
40#include <glib/gstdio.h>
50a14534
EB
41#include "viking.h"
42#include "thumbnails.h"
5bfafde9 43#include "icons/icons.h"
50a14534
EB
44
45#ifdef __CYGWIN__
46#ifdef __CYGWIN_USE_BIG_TYPES__
47#define ST_SIZE_FMT "%lld"
48#else
49#define ST_SIZE_FMT "%ld"
50#endif
51#else
52/* FIXME -- on some systems this may need to me "lld", see ROX-Filer code */
53#define ST_SIZE_FMT "%ld"
54#endif
55
56#undef MIN /* quit yer whining, gcc */
57#undef MAX
90e25247
GB
58#ifndef MAX
59/* We need MAX macro and some system does not offer it */
60#define MAX(a,b) (((a)>(b))?(a):(b))
61#endif
50a14534 62
118b901b
MA
63#define HOME_DIR g_get_home_dir()
64
50a14534 65#ifdef WINDOWS
50a14534
EB
66#define THUMB_DIR "\\THUMBNAILS\\" /* viking maps default viking\maps */
67#define THUMB_SUB_DIR "normal\\"
50a14534 68#else
50a14534
EB
69#define THUMB_DIR "/.thumbnails/"
70#define THUMB_SUB_DIR "normal/"
71#endif
72
73#define PIXMAP_THUMB_SIZE 128
74
75static char *md5_hash(const char *message);
50a14534
EB
76static GdkPixbuf *save_thumbnail(const char *pathname, GdkPixbuf *full);
77static GdkPixbuf *child_create_thumbnail(const gchar *path);
78
79gboolean a_thumbnails_exists ( const gchar *filename )
80{
81 GdkPixbuf *pixbuf = a_thumbnails_get(filename);
82 if ( pixbuf )
83 {
84 g_object_unref ( G_OBJECT ( pixbuf ) );
85 return TRUE;
86 }
87 return FALSE;
88}
89
90GdkPixbuf *a_thumbnails_get_default ()
91{
5bfafde9 92 return gdk_pixbuf_from_pixdata ( &thumbnails_pixbuf, FALSE, NULL );
50a14534
EB
93}
94
95/* filename must be absolute. you could have a function to make sure it exists and absolutize it */
96
97void a_thumbnails_create(const gchar *filename)
98{
99 GdkPixbuf *pixbuf = a_thumbnails_get(filename);
100
101 if ( ! pixbuf )
102 pixbuf = child_create_thumbnail(filename);
103
104 if ( pixbuf )
105 g_object_unref ( G_OBJECT ( pixbuf ) );
106}
107
108GdkPixbuf *a_thumbnails_scale_pixbuf(GdkPixbuf *src, int max_w, int max_h)
109{
110 int w, h;
111
112 w = gdk_pixbuf_get_width(src);
113 h = gdk_pixbuf_get_height(src);
114
115 if (w <= max_w && h <= max_h)
116 {
f2fcc2d2 117 g_object_ref ( G_OBJECT ( src ) );
50a14534
EB
118 return src;
119 }
120 else
121 {
122 float scale_x = ((float) w) / max_w;
123 float scale_y = ((float) h) / max_h;
124 float scale = MAX(scale_x, scale_y);
125 int dest_w = w / scale;
126 int dest_h = h / scale;
127
128 return gdk_pixbuf_scale_simple(src,
129 MAX(dest_w, 1),
130 MAX(dest_h, 1),
131 GDK_INTERP_BILINEAR);
132 }
133}
134
135static GdkPixbuf *child_create_thumbnail(const gchar *path)
136{
6d0927b1 137 GdkPixbuf *image, *tmpbuf;
50a14534
EB
138
139 image = gdk_pixbuf_new_from_file(path, NULL);
778e43b4
RN
140 if (!image)
141 return NULL;
142
6d0927b1
GK
143 tmpbuf = gdk_pixbuf_apply_embedded_orientation(image);
144 g_object_unref(G_OBJECT(image));
145 image = tmpbuf;
50a14534
EB
146
147 if (image)
148 {
149 GdkPixbuf *thumb = save_thumbnail(path, image);
f2fcc2d2 150 g_object_unref ( G_OBJECT ( image ) );
50a14534
EB
151 return thumb;
152 }
153
154 return NULL;
155}
156
157static GdkPixbuf *save_thumbnail(const char *pathname, GdkPixbuf *full)
158{
159 struct stat info;
160 gchar *path;
161 int original_width, original_height;
6d0927b1 162 const gchar* orientation;
50a14534
EB
163 GString *to;
164 char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
165 mode_t old_mask;
166 int name_len;
167 GdkPixbuf *thumb;
168
169 if (stat(pathname, &info) != 0)
170 return NULL;
171
172 thumb = a_thumbnails_scale_pixbuf(full, PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE);
173
6d0927b1
GK
174 orientation = gdk_pixbuf_get_option (full, "orientation");
175
50a14534
EB
176 original_width = gdk_pixbuf_get_width(full);
177 original_height = gdk_pixbuf_get_height(full);
178
179
180 swidth = g_strdup_printf("%d", original_width);
181 sheight = g_strdup_printf("%d", original_height);
182 ssize = g_strdup_printf(ST_SIZE_FMT, info.st_size);
183 smtime = g_strdup_printf("%ld", (long) info.st_mtime);
184
1b14d0d2 185 path = file_realpath_dup(pathname);
50a14534
EB
186 uri = g_strconcat("file://", path, NULL);
187 md5 = md5_hash(uri);
188 g_free(path);
189
190 to = g_string_new(HOME_DIR);
50a14534 191 g_string_append(to, THUMB_DIR);
50a14534 192 g_string_append(to, THUMB_SUB_DIR);
ab7f5df1 193 g_mkdir_with_parents(to->str, 0700);
50a14534
EB
194 g_string_append(to, md5);
195 name_len = to->len + 4; /* Truncate to this length when renaming */
196#ifdef WINDOWS
197 g_string_append_printf(to, ".png.Viking");
198#else
199 g_string_append_printf(to, ".png.Viking-%ld", (long) getpid());
200#endif
201
202 g_free(md5);
203
204 old_mask = umask(0077);
56f6a97c
RN
205 GError *error = NULL;
206 gdk_pixbuf_save(thumb, to->str, "png", &error,
50a14534
EB
207 "tEXt::Thumb::Image::Width", swidth,
208 "tEXt::Thumb::Image::Height", sheight,
209 "tEXt::Thumb::Size", ssize,
210 "tEXt::Thumb::MTime", smtime,
211 "tEXt::Thumb::URI", uri,
212 "tEXt::Software", PROJECT,
6d0927b1 213 "tEXt::Software::Orientation", orientation ? orientation : "0",
50a14534
EB
214 NULL);
215 umask(old_mask);
216
56f6a97c
RN
217 if (error) {
218 g_warning ( "%s::%s", __FUNCTION__, error->message );
219 g_error_free ( error );
220 g_object_unref ( G_OBJECT(thumb) );
221 thumb = NULL; /* return NULL */
222 }
223 else
224 /* We create the file ###.png.Viking-PID and rename it to avoid
50a14534
EB
225 * a race condition if two programs create the same thumb at
226 * once.
227 */
228 {
229 gchar *final;
230
231 final = g_strndup(to->str, name_len);
232 if (rename(to->str, final))
233 {
234 g_warning("Failed to rename '%s' to '%s': %s",
235 to->str, final, g_strerror(errno));
236 g_object_unref ( G_OBJECT(thumb) );
237 thumb = NULL; /* return NULL */
238 }
239
240 g_free(final);
241 }
242
243 g_string_free(to, TRUE);
244 g_free(swidth);
245 g_free(sheight);
246 g_free(ssize);
247 g_free(smtime);
248 g_free(uri);
249
250 return thumb;
251}
252
253
254GdkPixbuf *a_thumbnails_get(const gchar *pathname)
255{
256 GdkPixbuf *thumb = NULL;
257 char *thumb_path, *md5, *uri, *path;
258 const char *ssize, *smtime;
259 struct stat info;
260
1b14d0d2 261 path = file_realpath_dup(pathname);
50a14534
EB
262 uri = g_strconcat("file://", path, NULL);
263 md5 = md5_hash(uri);
264 g_free(uri);
265
266 thumb_path = g_strdup_printf("%s%s%s%s.png", HOME_DIR, THUMB_DIR, THUMB_SUB_DIR, md5);
267
268 g_free(md5);
269
270 thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
271 if (!thumb)
272 goto err;
273
274 /* Note that these don't need freeing... */
275 ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
276 if (!ssize)
277 goto err;
278
279 smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
280 if (!smtime)
281 goto err;
282
283 if (stat(path, &info) != 0)
284 goto err;
285
286 if (info.st_mtime != atol(smtime) || info.st_size != atol(ssize))
287 goto err;
288
289 goto out;
290err:
291 if (thumb)
f2fcc2d2 292 g_object_unref ( G_OBJECT ( thumb ) );
50a14534
EB
293 thumb = NULL;
294out:
295 g_free(path);
296 g_free(thumb_path);
297 return thumb;
298}
299
50a14534
EB
300/*
301 * This code implements the MD5 message-digest algorithm.
302 * The algorithm is due to Ron Rivest. The original code was
303 * written by Colin Plumb in 1993, and put in the public domain.
304 *
305 * Modified to use glib datatypes. Put under GPL to simplify
306 * licensing for ROX-Filer. Taken from Debian's dpkg package.
307 *
308 */
309
310#define md5byte unsigned char
311
312typedef struct _MD5Context MD5Context;
313
314struct _MD5Context {
315 guint32 buf[4];
316 guint32 bytes[2];
317 guint32 in[16];
318};
319
320static void MD5Init(MD5Context *ctx);
321static void MD5Update(MD5Context *ctx, md5byte const *buf, unsigned len);
322static char *MD5Final(MD5Context *ctx);
323static void MD5Transform(guint32 buf[4], guint32 const in[16]);
324
325#if G_BYTE_ORDER == G_BIG_ENDIAN
326static void byteSwap(guint32 *buf, unsigned words)
327{
328 md5byte *p = (md5byte *)buf;
329
330 do {
331 *buf++ = (guint32)((unsigned)p[3] << 8 | p[2]) << 16 |
332 ((unsigned)p[1] << 8 | p[0]);
333 p += 4;
334 } while (--words);
335}
336#else
337#define byteSwap(buf,words)
338#endif
339
340/*
341 * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
342 * initialization constants.
343 */
344static void MD5Init(MD5Context *ctx)
345{
346 ctx->buf[0] = 0x67452301;
347 ctx->buf[1] = 0xefcdab89;
348 ctx->buf[2] = 0x98badcfe;
349 ctx->buf[3] = 0x10325476;
350
351 ctx->bytes[0] = 0;
352 ctx->bytes[1] = 0;
353}
354
355/*
356 * Update context to reflect the concatenation of another buffer full
357 * of bytes.
358 */
359static void MD5Update(MD5Context *ctx, md5byte const *buf, unsigned len)
360{
361 guint32 t;
362
363 /* Update byte count */
364
365 t = ctx->bytes[0];
366 if ((ctx->bytes[0] = t + len) < t)
367 ctx->bytes[1]++; /* Carry from low to high */
368
369 t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */
370 if (t > len) {
371 memcpy((md5byte *)ctx->in + 64 - t, buf, len);
372 return;
373 }
374 /* First chunk is an odd size */
375 memcpy((md5byte *)ctx->in + 64 - t, buf, t);
376 byteSwap(ctx->in, 16);
377 MD5Transform(ctx->buf, ctx->in);
378 buf += t;
379 len -= t;
380
381 /* Process data in 64-byte chunks */
382 while (len >= 64) {
383 memcpy(ctx->in, buf, 64);
384 byteSwap(ctx->in, 16);
385 MD5Transform(ctx->buf, ctx->in);
386 buf += 64;
387 len -= 64;
388 }
389
390 /* Handle any remaining bytes of data. */
391 memcpy(ctx->in, buf, len);
392}
393
394/*
395 * Final wrapup - pad to 64-byte boundary with the bit pattern
396 * 1 0* (64-bit count of bits processed, MSB-first)
397 * Returns the newly allocated string of the hash.
398 */
399static char *MD5Final(MD5Context *ctx)
400{
401 char *retval;
402 int i;
403 int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */
404 md5byte *p = (md5byte *)ctx->in + count;
405 guint8 *bytes;
406
407 /* Set the first char of padding to 0x80. There is always room. */
408 *p++ = 0x80;
409
410 /* Bytes of padding needed to make 56 bytes (-8..55) */
411 count = 56 - 1 - count;
412
413 if (count < 0) { /* Padding forces an extra block */
414 memset(p, 0, count + 8);
415 byteSwap(ctx->in, 16);
416 MD5Transform(ctx->buf, ctx->in);
417 p = (md5byte *)ctx->in;
418 count = 56;
419 }
420 memset(p, 0, count);
421 byteSwap(ctx->in, 14);
422
423 /* Append length in bits and transform */
424 ctx->in[14] = ctx->bytes[0] << 3;
425 ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29;
426 MD5Transform(ctx->buf, ctx->in);
427
428 byteSwap(ctx->buf, 4);
429
430 retval = g_malloc(33);
431 bytes = (guint8 *) ctx->buf;
432 for (i = 0; i < 16; i++)
433 sprintf(retval + (i * 2), "%02x", bytes[i]);
434 retval[32] = '\0';
435
436 return retval;
437}
438
439# ifndef ASM_MD5
440
441/* The four core functions - F1 is optimized somewhat */
442
443/* #define F1(x, y, z) (x & y | ~x & z) */
444#define F1(x, y, z) (z ^ (x & (y ^ z)))
445#define F2(x, y, z) F1(z, x, y)
446#define F3(x, y, z) (x ^ y ^ z)
447#define F4(x, y, z) (y ^ (x | ~z))
448
449/* This is the central step in the MD5 algorithm. */
450#define MD5STEP(f,w,x,y,z,in,s) \
451 (w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x)
452
453/*
454 * The core of the MD5 algorithm, this alters an existing MD5 hash to
455 * reflect the addition of 16 longwords of new data. MD5Update blocks
456 * the data and converts bytes into longwords for this routine.
457 */
458static void MD5Transform(guint32 buf[4], guint32 const in[16])
459{
460 register guint32 a, b, c, d;
461
462 a = buf[0];
463 b = buf[1];
464 c = buf[2];
465 d = buf[3];
466
467 MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
468 MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
469 MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
470 MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
471 MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
472 MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
473 MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
474 MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
475 MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
476 MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
477 MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
478 MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
479 MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
480 MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
481 MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
482 MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
483
484 MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
485 MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
486 MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
487 MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
488 MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
489 MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
490 MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
491 MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
492 MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
493 MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
494 MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
495 MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
496 MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
497 MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
498 MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
499 MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
500
501 MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
502 MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
503 MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
504 MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
505 MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
506 MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
507 MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
508 MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
509 MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
510 MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
511 MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
512 MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
513 MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
514 MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
515 MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
516 MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
517
518 MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
519 MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
520 MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
521 MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
522 MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
523 MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
524 MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
525 MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
526 MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
527 MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
528 MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
529 MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
530 MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
531 MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
532 MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
533 MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
534
535 buf[0] += a;
536 buf[1] += b;
537 buf[2] += c;
538 buf[3] += d;
539}
540
541# endif /* ASM_MD5 */
542
543static char *md5_hash(const char *message)
544{
545 MD5Context ctx;
546
547 MD5Init(&ctx);
4243132f 548 MD5Update(&ctx, (md5byte *) message, strlen(message));
50a14534
EB
549 return MD5Final(&ctx);
550}