2 * Copyright (C) 2004 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
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.
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.
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.
26 #include "secure-save.h"
27 #include "thumb-standard.h"
28 #include "ui-fileops.h"
35 *-------------------------------------------------------------------
36 * Cache data file format:
37 *-------------------------------------------------------------------
41 * Dimensions=[<width> x <height>] \n
42 * Date=[<value in time_t format, or -1 if no embedded date>] \n
43 * MD5sum=[<32 character ascii text digest>] \n
44 * SimilarityGrid[32 x 32]=<3072 bytes of data (1024 pixels in RGB format, 1 pixel is 24bits)>
46 * The first line (9 bytes) indicates it is a SIMcache format file. (new line char must exist) \n
47 * Comment lines starting with a # are ignored up to a new line. \n
48 * All data lines should end with a new line char. \n
49 * Format is very strict, data must begin with the char immediately following '='. \n
50 * Currently SimilarityGrid is always assumed to be 32 x 32 RGB. \n
55 *-------------------------------------------------------------------
57 *-------------------------------------------------------------------
60 CacheData *cache_sim_data_new()
64 cd = g_new0(CacheData, 1);
70 void cache_sim_data_free(CacheData *cd)
75 image_sim_free(cd->sim);
80 *-------------------------------------------------------------------
82 *-------------------------------------------------------------------
85 static gboolean cache_sim_write_dimensions(SecureSaveInfo *ssi, CacheData *cd)
87 if (!cd || !cd->dimensions) return FALSE;
89 secure_fprintf(ssi, "Dimensions=[%d x %d]\n", cd->width, cd->height);
94 static gboolean cache_sim_write_date(SecureSaveInfo *ssi, CacheData *cd)
96 if (!cd || !cd->have_date) return FALSE;
98 secure_fprintf(ssi, "Date=[%ld]\n", cd->date);
103 static gboolean cache_sim_write_md5sum(SecureSaveInfo *ssi, CacheData *cd)
107 if (!cd || !cd->have_md5sum) return FALSE;
109 text = md5_digest_to_text(cd->md5sum);
110 secure_fprintf(ssi, "MD5sum=[%s]\n", text);
116 static gboolean cache_sim_write_similarity(SecureSaveInfo *ssi, CacheData *cd)
121 if (!cd || !cd->similarity || !cd->sim || !cd->sim->filled) return FALSE;
123 secure_fprintf(ssi, "SimilarityGrid[32 x 32]=");
124 for (y = 0; y < 32; y++)
127 guint8 *avg_r = &cd->sim->avg_r[s];
128 guint8 *avg_g = &cd->sim->avg_g[s];
129 guint8 *avg_b = &cd->sim->avg_b[s];
132 for (x = 0; x < 32; x++)
139 secure_fwrite(buf, sizeof(buf), 1, ssi);
142 secure_fputc(ssi, '\n');
147 gboolean cache_sim_data_save(CacheData *cd)
152 if (!cd || !cd->path) return FALSE;
154 pathl = path_from_utf8(cd->path);
155 ssi = secure_open(pathl);
160 log_printf("Unable to save sim cache data: %s\n", cd->path);
164 secure_fprintf(ssi, "SIMcache\n#%s %s\n", PACKAGE, VERSION);
165 cache_sim_write_dimensions(ssi, cd);
166 cache_sim_write_date(ssi, cd);
167 cache_sim_write_md5sum(ssi, cd);
168 cache_sim_write_similarity(ssi, cd);
170 if (secure_close(ssi))
172 log_printf(_("error saving sim cache data: %s\nerror: %s\n"), cd->path,
173 secsave_strerror(secsave_errno));
181 *-------------------------------------------------------------------
183 *-------------------------------------------------------------------
186 static gboolean cache_sim_read_skipline(FILE *f, gint s)
188 if (!f) return FALSE;
190 if (fseek(f, 0 - s, SEEK_CUR) == 0)
193 while (fread(&b, sizeof(b), 1, f) == 1)
195 if (b == '\n') return TRUE;
203 static gboolean cache_sim_read_comment(FILE *f, gchar *buf, gint s, CacheData *cd)
205 if (!f || !buf || !cd) return FALSE;
207 if (s < 1 || buf[0] != '#') return FALSE;
209 return cache_sim_read_skipline(f, s - 1);
212 static gboolean cache_sim_read_dimensions(FILE *f, gchar *buf, gint s, CacheData *cd)
214 if (!f || !buf || !cd) return FALSE;
216 if (s < 10 || strncmp("Dimensions", buf, 10) != 0) return FALSE;
218 if (fseek(f, - s, SEEK_CUR) == 0)
228 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
230 while (b != ']' && p < sizeof(buf) - 1)
232 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
239 if (fread(&b, sizeof(b), 1, f) != 1) break;
243 if (sscanf(buf, "%d x %d", &w, &h) != 2) return FALSE;
247 cd->dimensions = TRUE;
255 static gboolean cache_sim_read_date(FILE *f, gchar *buf, gint s, CacheData *cd)
257 if (!f || !buf || !cd) return FALSE;
259 if (s < 4 || strncmp("Date", buf, 4) != 0) return FALSE;
261 if (fseek(f, - s, SEEK_CUR) == 0)
270 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
272 while (b != ']' && p < sizeof(buf) - 1)
274 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
281 if (fread(&b, sizeof(b), 1, f) != 1) break;
285 cd->date = strtol(buf, nullptr, 10);
287 cd->have_date = TRUE;
295 static gboolean cache_sim_read_md5sum(FILE *f, gchar *buf, gint s, CacheData *cd)
297 if (!f || !buf || !cd) return FALSE;
299 if (s < 8 || strncmp("MD5sum", buf, 6) != 0) return FALSE;
301 if (fseek(f, - s, SEEK_CUR) == 0)
310 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
312 while (b != ']' && p < sizeof(buf) - 1)
314 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
320 if (fread(&b, sizeof(b), 1, f) != 1) break;
324 cd->have_md5sum = md5_digest_from_text(buf, cd->md5sum);
332 static gboolean cache_sim_read_similarity(FILE *f, gchar *buf, gint s, CacheData *cd)
334 if (!f || !buf || !cd) return FALSE;
336 if (s < 11 || strncmp("Similarity", buf, 10) != 0) return FALSE;
338 if (strncmp("Grid[32 x 32]", buf + 10, 13) != 0) return FALSE;
340 if (fseek(f, - s, SEEK_CUR) == 0)
344 ImageSimilarityData *sd;
350 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
355 /* use current sim that may already contain data we will not touch here */
358 cd->similarity = FALSE;
362 sd = image_sim_new();
365 for (y = 0; y < 32; y++)
368 for (x = 0; x < 32; x++)
370 if (fread(&pixel_buf, sizeof(pixel_buf), 1, f) != 1)
375 sd->avg_r[s + x] = pixel_buf[0];
376 sd->avg_g[s + x] = pixel_buf[1];
377 sd->avg_b[s + x] = pixel_buf[2];
381 if (fread(&b, sizeof(b), 1, f) == 1)
383 if (b != '\n') fseek(f, -1, SEEK_CUR);
387 cd->sim->filled = TRUE;
388 cd->similarity = TRUE;
396 #define CACHE_LOAD_LINE_NOISE 8
398 CacheData *cache_sim_data_load(const gchar *path)
401 CacheData *cd = nullptr;
403 gint success = CACHE_LOAD_LINE_NOISE;
406 if (!path) return nullptr;
408 pathl = path_from_utf8(path);
409 f = fopen(pathl, "r");
412 if (!f) return nullptr;
414 cd = cache_sim_data_new();
415 cd->path = g_strdup(path);
417 if (fread(&buf, sizeof(gchar), 9, f) != 9 ||
418 strncmp(buf, "SIMcache", 8) != 0)
420 DEBUG_1("%s is not a cache file", cd->path);
427 s = fread(&buf, sizeof(gchar), sizeof(buf), f);
435 if (!cache_sim_read_comment(f, buf, s, cd) &&
436 !cache_sim_read_dimensions(f, buf, s, cd) &&
437 !cache_sim_read_date(f, buf, s, cd) &&
438 !cache_sim_read_md5sum(f, buf, s, cd) &&
439 !cache_sim_read_similarity(f, buf, s, cd))
441 if (!cache_sim_read_skipline(f, s))
452 success = CACHE_LOAD_LINE_NOISE;
459 if (!cd->dimensions &&
464 cache_sim_data_free(cd);
472 *-------------------------------------------------------------------
474 *-------------------------------------------------------------------
477 void cache_sim_data_set_dimensions(CacheData *cd, gint w, gint h)
483 cd->dimensions = TRUE;
486 #pragma GCC diagnostic push
487 #pragma GCC diagnostic ignored "-Wunused-function"
488 void cache_sim_data_set_date_unused(CacheData *cd, time_t date)
493 cd->have_date = TRUE;
495 #pragma GCC diagnostic pop
497 void cache_sim_data_set_md5sum(CacheData *cd, guchar digest[16])
503 for (i = 0; i < 16; i++)
505 cd->md5sum[i] = digest[i];
507 cd->have_md5sum = TRUE;
510 void cache_sim_data_set_similarity(CacheData *cd, ImageSimilarityData *sd)
512 if (!cd || !sd || !sd->filled) return;
514 if (!cd->sim) cd->sim = image_sim_new();
516 memcpy(cd->sim->avg_r, sd->avg_r, 1024);
517 memcpy(cd->sim->avg_g, sd->avg_g, 1024);
518 memcpy(cd->sim->avg_b, sd->avg_b, 1024);
519 cd->sim->filled = TRUE;
521 cd->similarity = TRUE;
524 gboolean cache_sim_data_filled(ImageSimilarityData *sd)
526 if (!sd) return FALSE;
531 *-------------------------------------------------------------------
532 * cache path location utils
533 *-------------------------------------------------------------------
537 static void cache_path_parts(CacheType type,
538 const gchar **cache_rc, const gchar **cache_local, const gchar **cache_ext)
542 case CACHE_TYPE_THUMB:
543 *cache_rc = get_thumbnails_cache_dir();
544 *cache_local = GQ_CACHE_LOCAL_THUMB;
545 *cache_ext = GQ_CACHE_EXT_THUMB;
548 *cache_rc = get_thumbnails_cache_dir();
549 *cache_local = GQ_CACHE_LOCAL_THUMB;
550 *cache_ext = GQ_CACHE_EXT_SIM;
552 case CACHE_TYPE_METADATA:
553 *cache_rc = get_metadata_cache_dir();
554 *cache_local = GQ_CACHE_LOCAL_METADATA;
555 *cache_ext = GQ_CACHE_EXT_METADATA;
557 case CACHE_TYPE_XMP_METADATA:
558 *cache_rc = get_metadata_cache_dir();
559 *cache_local = GQ_CACHE_LOCAL_METADATA;
560 *cache_ext = GQ_CACHE_EXT_XMP_METADATA;
565 gchar *cache_get_location(CacheType type, const gchar *source, gint include_name, mode_t *mode)
567 gchar *path = nullptr;
569 gchar *name = nullptr;
570 const gchar *cache_rc;
571 const gchar *cache_local;
572 const gchar *cache_ext;
574 if (!source) return nullptr;
576 cache_path_parts(type, &cache_rc, &cache_local, &cache_ext);
578 base = remove_level_from_path(source);
581 name = g_strconcat(filename_from_path(source), cache_ext, NULL);
584 if (((type != CACHE_TYPE_METADATA && type != CACHE_TYPE_XMP_METADATA && options->thumbnails.cache_into_dirs) ||
585 ((type == CACHE_TYPE_METADATA || type == CACHE_TYPE_XMP_METADATA) && options->metadata.enable_metadata_dirs)) &&
586 access_file(base, W_OK))
588 path = g_build_filename(base, cache_local, name, NULL);
589 if (mode) *mode = 0775;
594 path = g_build_filename(cache_rc, base, name, NULL);
595 if (mode) *mode = 0755;
599 if (name) g_free(name);
604 static gchar *cache_build_path_local(const gchar *source, const gchar *cache_local, const gchar *cache_ext)
607 gchar *base = remove_level_from_path(source);
608 gchar *name = g_strconcat(filename_from_path(source), cache_ext, NULL);
609 path = g_build_filename(base, cache_local, name, NULL);
616 static gchar *cache_build_path_rc(const gchar *source, const gchar *cache_rc, const gchar *cache_ext)
619 gchar *name = g_strconcat(source, cache_ext, NULL);
620 path = g_build_filename(cache_rc, name, NULL);
626 gchar *cache_find_location(CacheType type, const gchar *source)
629 const gchar *cache_rc;
630 const gchar *cache_local;
631 const gchar *cache_ext;
632 gboolean prefer_local;
634 if (!source) return nullptr;
636 cache_path_parts(type, &cache_rc, &cache_local, &cache_ext);
638 if (type == CACHE_TYPE_METADATA || type == CACHE_TYPE_XMP_METADATA)
640 prefer_local = options->metadata.enable_metadata_dirs;
644 prefer_local = options->thumbnails.cache_into_dirs;
649 path = cache_build_path_local(source, cache_local, cache_ext);
653 path = cache_build_path_rc(source, cache_rc, cache_ext);
660 /* try the opposite method if not found */
663 path = cache_build_path_local(source, cache_local, cache_ext);
667 path = cache_build_path_rc(source, cache_rc, cache_ext);
680 gboolean cache_time_valid(const gchar *cache, const gchar *path)
682 struct stat cache_st;
686 gboolean ret = FALSE;
688 if (!cache || !path) return FALSE;
690 cachel = path_from_utf8(cache);
691 pathl = path_from_utf8(path);
693 if (stat(cachel, &cache_st) == 0 &&
694 stat(pathl, &path_st) == 0)
696 if (cache_st.st_mtime == path_st.st_mtime)
700 else if (cache_st.st_mtime > path_st.st_mtime)
704 ut.actime = ut.modtime = cache_st.st_mtime;
705 if (utime(cachel, &ut) < 0 &&
708 DEBUG_1("cache permission workaround: %s", cachel);
720 const gchar *get_thumbnails_cache_dir()
722 static gchar *thumbnails_cache_dir = nullptr;
724 if (thumbnails_cache_dir) return thumbnails_cache_dir;
728 thumbnails_cache_dir = g_build_filename(xdg_cache_home_get(),
729 GQ_APPNAME_LC, GQ_CACHE_THUMB, NULL);
733 thumbnails_cache_dir = g_build_filename(get_rc_dir(), GQ_CACHE_THUMB, NULL);
736 return thumbnails_cache_dir;
739 const gchar *get_thumbnails_standard_cache_dir()
741 static gchar *thumbnails_standard_cache_dir = nullptr;
743 if (thumbnails_standard_cache_dir) return thumbnails_standard_cache_dir;
745 thumbnails_standard_cache_dir = g_build_filename(xdg_cache_home_get(),
746 THUMB_FOLDER_GLOBAL, NULL);
748 return thumbnails_standard_cache_dir;
751 const gchar *get_metadata_cache_dir()
753 static gchar *metadata_cache_dir = nullptr;
755 if (metadata_cache_dir) return metadata_cache_dir;
759 /* Metadata go to $XDG_DATA_HOME.
760 * "Keywords and comments, among other things, are irreplaceable and cannot be auto-generated,
761 * so I don't think they'd be appropriate for the cache directory." -- Omari Stephens on geeqie-devel ml
763 metadata_cache_dir = g_build_filename(xdg_data_home_get(), GQ_APPNAME_LC, GQ_CACHE_METADATA, NULL);
767 metadata_cache_dir = g_build_filename(get_rc_dir(), GQ_CACHE_METADATA, NULL);
770 return metadata_cache_dir;
773 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */