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