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