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