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