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