ef983ab6b83e4a37ee6962edd4009214f46a51c6
[geeqie.git] / src / cache.c
1 /*
2  * GQview
3  * (C) 2004 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 #include "gqview.h"
13 #include "cache.h"
14
15 #include "md5-util.h"
16 #include "ui_fileops.h"
17
18 #include <utime.h>
19 #include <errno.h>
20
21
22 /*
23  *-------------------------------------------------------------------
24  * Cache data file format:
25  *-------------------------------------------------------------------
26  * 
27  * SIMcache
28  * #coment
29  * Dimensions=[<width> x <height>]
30  * Checksum=[<value>]
31  * MD5sum=[<32 character ascii text digest>]
32  * SimilarityGrid[32 x 32]=<3072 bytes of data (1024 pixels in RGB format, 1 pixel is 24bits)>
33  * 
34  * 
35  * The first line (9 bytes) indicates it is a SIMcache format file. (new line char must exist)
36  * Comment lines starting with a # are ignored up to a new line.
37  * All data lines should end with a new line char.
38  * Format is very strict, data must begin with the char immediately following '='.
39  * Currently SimilarityGrid is always assumed to be 32 x 32 RGB.
40  */
41
42
43 /*
44  *-------------------------------------------------------------------
45  * sim cache data
46  *-------------------------------------------------------------------
47  */
48
49 CacheData *cache_sim_data_new(void)
50 {
51         CacheData *cd;
52
53         cd = g_new0(CacheData, 1);
54         cd->dimensions = FALSE;
55         cd->similarity = FALSE;
56
57         return cd;
58 }
59
60 void cache_sim_data_free(CacheData *cd)
61 {
62         if (!cd) return;
63
64         g_free(cd->path);
65         image_sim_free(cd->sim);
66         g_free(cd);
67 }
68
69 /*
70  *-------------------------------------------------------------------
71  * sim cache write
72  *-------------------------------------------------------------------
73  */
74
75 static gint cache_sim_write_dimensions(FILE *f, CacheData *cd)
76 {
77         if (!f || !cd || !cd->dimensions) return FALSE;
78
79         fprintf(f, "Dimensions=[%d x %d]\n", cd->width, cd->height);
80
81         return TRUE;
82 }
83
84 static gint cache_sim_write_checksum(FILE *f, CacheData *cd)
85 {
86         if (!f || !cd || !cd->have_checksum) return FALSE;
87
88         fprintf(f, "Checksum=[%ld]\n", cd->checksum);
89
90         return TRUE;
91 }
92
93 static gint cache_sim_write_md5sum(FILE *f, CacheData *cd)
94 {
95         gchar *text;
96
97         if (!f || !cd || !cd->have_md5sum) return FALSE;
98
99         text = md5_digest_to_text(cd->md5sum);
100         fprintf(f, "MD5sum=[%s]\n", text);
101         g_free(text);
102
103         return TRUE;
104 }
105
106 static gint cache_sim_write_similarity(FILE *f, CacheData *cd)
107 {
108         gint success = FALSE;
109
110         if (!f || !cd || !cd->similarity) return FALSE;
111
112         if (cd->sim && cd->sim->filled)
113                 {
114                 gint x, y;
115                 guint8 buf[96];
116
117                 fprintf(f, "SimilarityGrid[32 x 32]=");
118                 for (y = 0; y < 32; y++)
119                         {
120                         gint s;
121                         guint8 *p;
122
123                         s = y * 32;
124                         p = buf;
125                         for(x = 0; x < 32; x++)
126                                 {
127                                 *p = cd->sim->avg_r[s + x]; p++;
128                                 *p = cd->sim->avg_g[s + x]; p++;
129                                 *p = cd->sim->avg_b[s + x]; p++;
130                                 }
131                         fwrite(buf, sizeof(buf), 1, f);
132                         }
133
134                 fprintf(f, "\n");
135                 success = TRUE;
136                 }
137
138         return success;
139 }
140
141 gint cache_sim_data_save(CacheData *cd)
142 {
143         FILE *f;
144         gchar *pathl;
145
146         if (!cd || !cd->path) return FALSE;
147
148         pathl = path_from_utf8(cd->path);
149         f = fopen(pathl, "w");
150         g_free(pathl);
151
152         if (!f)
153                 {
154                 printf("Unable to save sim cache data: %s\n", cd->path);
155                 return FALSE;
156                 }
157
158         fprintf(f, "SIMcache\n#%s %s\n", PACKAGE, VERSION);
159         cache_sim_write_dimensions(f, cd);
160         cache_sim_write_checksum(f, cd);
161         cache_sim_write_md5sum(f, cd);
162         cache_sim_write_similarity(f, cd);
163
164         fclose (f);
165
166         return TRUE;
167 }
168
169 /*
170  *-------------------------------------------------------------------
171  * sim cache read
172  *-------------------------------------------------------------------
173  */
174
175 static gint cache_sim_read_comment(FILE *f, char *buf, int s, CacheData *cd)
176 {
177         if (!f || !buf || !cd) return FALSE;
178
179         if (buf[0] != '#') return FALSE;
180
181         if (fseek(f, 0 - (s - 1), SEEK_CUR) == 0)
182                 {
183                 char b;
184                 while(fread(&b, sizeof(b), 1, f) == 1)
185                         {
186                         if (b == '\n') return TRUE;
187                         }
188                 return TRUE;
189                 }
190
191         return FALSE;
192 }
193
194 static gint cache_sim_read_dimensions(FILE *f, char *buf, int s, CacheData *cd)
195 {
196         if (!f || !buf || !cd) return FALSE;
197
198         if (s < 10 || strncmp("Dimensions", buf, 10) != 0) return FALSE;
199
200         if (fseek(f, - s, SEEK_CUR) == 0)
201                 {
202                 char b;
203                 char buf[1024];
204                 gint p = 0;
205                 gint w, h;
206
207                 b = 'X';
208                 while (b != '[')
209                         {
210                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
211                         }
212                 while (b != ']' && p < 1023)
213                         {
214                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
215                         buf[p] = b;
216                         p++;
217                         }
218
219                 while (b != '\n')
220                         {
221                         if (fread(&b, sizeof(b), 1, f) != 1) break;
222                         }
223
224                 buf[p] = '\0';
225                 if (sscanf(buf, "%d x %d", &w, &h) != 2) return FALSE;
226
227                 cd->width = w;
228                 cd->height = h;
229                 cd->dimensions = TRUE;
230
231                 return TRUE;
232                 }
233
234         return FALSE;
235 }
236
237 static gint cache_sim_read_checksum(FILE *f, char *buf, int s, CacheData *cd)
238 {
239         if (!f || !buf || !cd) return FALSE;
240
241         if (s < 8 || strncmp("Checksum", buf, 8) != 0) return FALSE;
242
243         if (fseek(f, - s, SEEK_CUR) == 0)
244                 {
245                 char b;
246                 char buf[1024];
247                 gint p = 0;
248
249                 b = 'X';
250                 while (b != '[')
251                         {
252                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
253                         }
254                 while (b != ']' && p < 1023)
255                         {
256                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
257                         buf[p] = b;
258                         p++;
259                         }
260
261                 while (b != '\n')
262                         {
263                         if (fread(&b, sizeof(b), 1, f) != 1) break;
264                         }
265
266                 buf[p] = '\0';
267                 cd->checksum = strtol(buf, NULL, 10);
268
269                 cd->have_checksum = TRUE;
270
271                 return TRUE;
272                 }
273
274         return FALSE;
275 }
276
277 static gint cache_sim_read_md5sum(FILE *f, char *buf, int s, CacheData *cd)
278 {
279         if (!f || !buf || !cd) return FALSE;
280
281         if (s < 8 || strncmp("MD5sum", buf, 6) != 0) return FALSE;
282
283         if (fseek(f, - s, SEEK_CUR) == 0)
284                 {
285                 char b;
286                 char buf[64];
287                 gint p = 0;
288
289                 b = 'X';
290                 while (b != '[')
291                         {
292                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
293                         }
294                 while (b != ']' && p < 63)
295                         {
296                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
297                         buf[p] = b;
298                         p++;
299                         }
300                 while (b != '\n')
301                         {
302                         if (fread(&b, sizeof(b), 1, f) != 1) break;
303                         }
304
305                 buf[p] = '\0';
306                 cd->have_md5sum = md5_digest_from_text(buf, cd->md5sum);
307
308                 return TRUE;
309                 }
310
311         return FALSE;
312 }
313
314 static gint cache_sim_read_similarity(FILE *f, char *buf, int s, CacheData *cd)
315 {
316         if (!f || !buf || !cd) return FALSE;
317
318         if (s < 11 || strncmp("Similarity", buf, 10) != 0) return FALSE;
319
320         if (strncmp("Grid[32 x 32]", buf + 10, 13) != 0) return FALSE;
321
322         if (fseek(f, - s, SEEK_CUR) == 0)
323                 {
324                 char b;
325                 guint8 pixel_buf[3];
326                 ImageSimilarityData *sd;
327                 gint x, y;
328
329                 b = 'X';
330                 while (b != '=')
331                         {
332                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
333                         }
334
335                 if (cd->sim)
336                         {
337                         /* use current sim that may already contain data we will not touch here */
338                         sd = cd->sim;
339                         cd->sim = NULL;
340                         cd->similarity = FALSE;
341                         }
342                 else
343                         {
344                         sd = image_sim_new();
345                         }
346
347                 for (y = 0; y < 32; y++)
348                         {
349                         gint s = y * 32;
350                         for (x = 0; x < 32; x++)
351                                 {
352                                 if (fread(&pixel_buf, sizeof(pixel_buf), 1, f) != 1)
353                                         {
354                                         image_sim_free(sd);
355                                         return FALSE;
356                                         }
357                                 sd->avg_r[s + x] = pixel_buf[0];
358                                 sd->avg_g[s + x] = pixel_buf[1];
359                                 sd->avg_b[s + x] = pixel_buf[2];
360                                 }
361                         }
362
363                 if (fread(&b, sizeof(b), 1, f) == 1)
364                         {
365                         if (b != '\n') fseek(f, -1, SEEK_CUR);
366                         }
367
368                 cd->sim = sd;
369                 cd->sim->filled = TRUE;
370                 cd->similarity = TRUE;
371
372                 return TRUE;
373                 }
374
375         return FALSE;
376 }
377
378 CacheData *cache_sim_data_load(const gchar *path)
379 {
380         FILE *f;
381         CacheData *cd = NULL;
382         char buf[32];
383         gint success = TRUE;
384         gchar *pathl;
385
386         if (!path) return NULL;
387
388         pathl = path_from_utf8(path);
389         f = fopen(pathl, "r");
390         g_free(pathl);
391
392         if (!f) return NULL;
393
394         cd = cache_sim_data_new();
395         cd->path = g_strdup(path);
396
397         if (fread(&buf, sizeof(char), 9, f) != 9 ||
398             strncmp(buf, "SIMcache", 8) != 0)
399                 {
400                 if (debug) printf("%s is not a cache file\n", cd->path);
401                 success = FALSE;
402                 }
403
404         while (success)
405                 {
406                 int s;
407                 s = fread(&buf, sizeof(char), sizeof(buf), f);
408
409                 if (s < 1)
410                         {
411                         success = FALSE;
412                         }
413                 else
414                         {
415                         if (!cache_sim_read_comment(f, buf, s, cd) &&
416                             !cache_sim_read_dimensions(f, buf, s, cd) &&
417                             !cache_sim_read_checksum(f, buf, s, cd) &&
418                             !cache_sim_read_md5sum(f, buf, s, cd) &&
419                             !cache_sim_read_similarity(f, buf, s, cd))
420                                 {
421                                 success = FALSE;
422                                 }
423                         }
424                 }
425
426         fclose(f);
427
428         if (!cd->dimensions && !cd->similarity)
429                 {
430                 cache_sim_data_free(cd);
431                 cd = NULL;
432                 }
433
434         return cd;
435 }
436
437 /*
438  *-------------------------------------------------------------------
439  * sim cache setting
440  *-------------------------------------------------------------------
441  */
442
443 void cache_sim_data_set_dimensions(CacheData *cd, gint w, gint h)
444 {
445         if (!cd) return;
446
447         cd->width = w;
448         cd->height = h;
449         cd->dimensions = TRUE;
450 }
451
452 void cache_sim_data_set_checksum(CacheData *cd, long checksum)
453 {
454         if (!cd) return;
455
456         cd->checksum = checksum;
457         cd->have_checksum = TRUE;
458 }
459
460 void cache_sim_data_set_md5sum(CacheData *cd, guchar digest[16])
461 {
462         gint i;
463
464         if (!cd) return;
465
466         for (i = 0; i < 16; i++)
467                 {
468                 cd->md5sum[i] = digest[i];
469                 }
470         cd->have_md5sum = TRUE;
471 }
472
473 void cache_sim_data_set_similarity(CacheData *cd, ImageSimilarityData *sd)
474 {
475         if (!cd || !sd || !sd->filled) return;
476
477         if (!cd->sim) cd->sim = image_sim_new();
478
479         memcpy(cd->sim->avg_r, sd->avg_r, 1024);
480         memcpy(cd->sim->avg_g, sd->avg_g, 1024);
481         memcpy(cd->sim->avg_b, sd->avg_b, 1024);
482         cd->sim->filled = TRUE;
483
484         cd->similarity = TRUE;
485 }
486
487 gint cache_sim_data_filled(ImageSimilarityData *sd)
488 {
489         if (!sd) return FALSE;
490         return sd->filled;
491 }
492
493 /*
494  *-------------------------------------------------------------------
495  * cache path location utils
496  *-------------------------------------------------------------------
497  */
498
499 /* warning: this func modifies path string contents!, on fail it is set to fail point */
500 gint cache_ensure_dir_exists(gchar *path, mode_t mode)
501 {
502         if (!path) return FALSE;
503
504         if (!isdir(path))
505                 {
506                 gchar *p = path;
507                 while (p[0] != '\0')
508                         {
509                         p++;
510                         if (p[0] == '/' || p[0] == '\0')
511                                 {
512                                 gint end = TRUE;
513                                 if (p[0] != '\0')
514                                         {
515                                         p[0] = '\0';
516                                         end = FALSE;
517                                         }
518                                 if (!isdir(path))
519                                         {
520                                         if (debug) printf("creating sub dir:%s\n", path);
521                                         if (!mkdir_utf8(path, mode))
522                                                 {
523                                                 printf("create dir failed: %s\n", path);
524                                                 return FALSE;
525                                                 }
526                                         }
527                                 if (!end) p[0] = '/';
528                                 }
529                         }
530                 }
531         return TRUE;
532 }
533
534 static void cache_path_parts(CacheType type,
535                              const gchar **cache_rc, const gchar **cache_local, const gchar **cache_ext)
536 {
537         switch (type)
538                 {
539                 case CACHE_TYPE_THUMB:
540                         *cache_rc = GQVIEW_CACHE_RC_THUMB;
541                         *cache_local = GQVIEW_CACHE_LOCAL_THUMB;
542                         *cache_ext = GQVIEW_CACHE_EXT_THUMB;
543                         break;
544                 case CACHE_TYPE_SIM:
545                         *cache_rc = GQVIEW_CACHE_RC_THUMB;
546                         *cache_local = GQVIEW_CACHE_LOCAL_THUMB;
547                         *cache_ext = GQVIEW_CACHE_EXT_SIM;
548                         break;
549                 case CACHE_TYPE_METADATA:
550                         *cache_rc = GQVIEW_CACHE_RC_METADATA;
551                         *cache_local = GQVIEW_CACHE_LOCAL_METADATA;
552                         *cache_ext = GQVIEW_CACHE_EXT_METADATA;
553                         break;
554                 }
555 }
556
557 gchar *cache_get_location(CacheType type, const gchar *source, gint include_name, mode_t *mode)
558 {
559         gchar *path = NULL;
560         gchar *base;
561         gchar *name = NULL;
562         const gchar *cache_rc;
563         const gchar *cache_local;
564         const gchar *cache_ext;
565
566         if (!source) return NULL;
567
568         cache_path_parts(type, &cache_rc, &cache_local, &cache_ext);
569
570         base = remove_level_from_path(source);
571         if (include_name)
572                 {
573                 name = g_strconcat("/", filename_from_path(source), NULL);
574                 }
575         else
576                 {
577                 cache_ext = NULL;
578                 }
579
580         if (((type != CACHE_TYPE_METADATA && enable_thumb_dirs) ||
581              (type == CACHE_TYPE_METADATA && enable_metadata_dirs)) &&
582             access_file(base, W_OK))
583                 {
584                 path = g_strconcat(base, "/", cache_local, name, cache_ext, NULL);
585                 if (mode) *mode = 0775;
586                 }
587
588         if (!path)
589                 {
590                 path = g_strconcat(homedir(), "/", cache_rc, base, name, cache_ext, NULL);
591                 if (mode) *mode = 0755;
592                 }
593
594         g_free(base);
595         if (name) g_free(name);
596
597         return path;
598 }
599
600 gchar *cache_find_location(CacheType type, const gchar *source)
601 {
602         gchar *path;
603         const gchar *name;
604         gchar *base;
605         const gchar *cache_rc;
606         const gchar *cache_local;
607         const gchar *cache_ext;
608         gint prefer_local;
609
610         if (!source) return NULL;
611
612         cache_path_parts(type, &cache_rc, &cache_local, &cache_ext);
613
614         name = filename_from_path(source);
615         base = remove_level_from_path(source);
616
617         if (type == CACHE_TYPE_METADATA)
618                 {
619                 prefer_local = enable_metadata_dirs;
620                 }
621         else
622                 {
623                 prefer_local = enable_thumb_dirs;
624                 }
625
626         if (prefer_local)
627                 {
628                 path = g_strconcat(base, "/", cache_local, "/", name, cache_ext, NULL);
629                 }
630         else
631                 {
632                 path = g_strconcat(homedir(), "/", cache_rc, source, cache_ext, NULL);
633                 }
634
635         if (!isfile(path))
636                 {
637                 g_free(path);
638
639                 /* try the opposite method if not found */
640                 if (!prefer_local)
641                         {
642                         path = g_strconcat(base, "/", cache_local, "/", name, cache_ext, NULL);
643                         }
644                 else
645                         {
646                         path = g_strconcat(homedir(), "/", cache_rc, source, cache_ext, NULL);
647                         }
648
649                 if (!isfile(path))
650                         {
651                         g_free(path);
652                         path = NULL;
653                         }
654                 }
655
656         g_free(base);
657
658         return path;
659 }
660
661 gint cache_time_valid(const gchar *cache, const gchar *path)
662 {
663         struct stat cache_st;
664         struct stat path_st;
665         gchar *cachel;
666         gchar *pathl;
667         gint ret = FALSE;
668
669         if (!cache || !path) return FALSE;
670
671         cachel = path_from_utf8(cache);
672         pathl = path_from_utf8(path);
673
674         if (stat(cachel, &cache_st) == 0 &&
675             stat(pathl, &path_st) == 0)
676                 {
677                 if (cache_st.st_mtime == path_st.st_mtime)
678                         {
679                         ret = TRUE;
680                         }
681                 else if (cache_st.st_mtime > path_st.st_mtime)
682                         {
683                         struct utimbuf ut;
684
685                         ut.actime = ut.modtime = cache_st.st_mtime;
686                         if (utime(cachel, &ut) < 0 &&
687                             errno == EPERM)
688                                 {
689                                 if (debug) printf("cache permission workaround: %s\n", cachel);
690                                 ret = TRUE;
691                                 }
692                         }
693                 }
694
695         g_free(pathl);
696         g_free(cachel);
697
698         return ret;
699 }
700