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