4 * Copyright (C) 2008 - 2012 The Geeqie Team
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!
17 #include "secure_save.h"
18 #include "ui_fileops.h"
25 *-------------------------------------------------------------------
26 * Cache data file format:
27 *-------------------------------------------------------------------
31 * Dimensions=[<width> x <height>]
32 * Date=[<value in time_t format, or -1 if no embedded date>]
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)>
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.
46 *-------------------------------------------------------------------
48 *-------------------------------------------------------------------
51 CacheData *cache_sim_data_new(void)
55 cd = g_new0(CacheData, 1);
61 void cache_sim_data_free(CacheData *cd)
66 image_sim_free(cd->sim);
71 *-------------------------------------------------------------------
73 *-------------------------------------------------------------------
76 static gboolean cache_sim_write_dimensions(SecureSaveInfo *ssi, CacheData *cd)
78 if (!cd || !cd->dimensions) return FALSE;
80 secure_fprintf(ssi, "Dimensions=[%d x %d]\n", cd->width, cd->height);
85 static gboolean cache_sim_write_date(SecureSaveInfo *ssi, CacheData *cd)
87 if (!cd || !cd->have_date) return FALSE;
89 secure_fprintf(ssi, "Date=[%ld]\n", cd->date);
94 static gboolean cache_sim_write_md5sum(SecureSaveInfo *ssi, CacheData *cd)
98 if (!cd || !cd->have_md5sum) return FALSE;
100 text = md5_digest_to_text(cd->md5sum);
101 secure_fprintf(ssi, "MD5sum=[%s]\n", text);
107 static gboolean cache_sim_write_similarity(SecureSaveInfo *ssi, CacheData *cd)
112 if (!cd || !cd->similarity || !cd->sim || !cd->sim->filled) return FALSE;
114 secure_fprintf(ssi, "SimilarityGrid[32 x 32]=");
115 for (y = 0; y < 32; y++)
118 guint8 *avg_r = &cd->sim->avg_r[s];
119 guint8 *avg_g = &cd->sim->avg_g[s];
120 guint8 *avg_b = &cd->sim->avg_b[s];
123 for (x = 0; x < 32; x++)
130 secure_fwrite(buf, sizeof(buf), 1, ssi);
133 secure_fputc(ssi, '\n');
138 gboolean cache_sim_data_save(CacheData *cd)
143 if (!cd || !cd->path) return FALSE;
145 pathl = path_from_utf8(cd->path);
146 ssi = secure_open(pathl);
151 log_printf("Unable to save sim cache data: %s\n", cd->path);
155 secure_fprintf(ssi, "SIMcache\n#%s %s\n", PACKAGE, VERSION);
156 cache_sim_write_dimensions(ssi, cd);
157 cache_sim_write_date(ssi, cd);
158 cache_sim_write_md5sum(ssi, cd);
159 cache_sim_write_similarity(ssi, cd);
161 if (secure_close(ssi))
163 log_printf(_("error saving sim cache data: %s\nerror: %s\n"), cd->path,
164 secsave_strerror(secsave_errno));
172 *-------------------------------------------------------------------
174 *-------------------------------------------------------------------
177 static gboolean cache_sim_read_skipline(FILE *f, gint s)
179 if (!f) return FALSE;
181 if (fseek(f, 0 - s, SEEK_CUR) == 0)
184 while (fread(&b, sizeof(b), 1, f) == 1)
186 if (b == '\n') return TRUE;
194 static gboolean cache_sim_read_comment(FILE *f, gchar *buf, gint s, CacheData *cd)
196 if (!f || !buf || !cd) return FALSE;
198 if (s < 1 || buf[0] != '#') return FALSE;
200 return cache_sim_read_skipline(f, s - 1);
203 static gboolean cache_sim_read_dimensions(FILE *f, gchar *buf, gint s, CacheData *cd)
205 if (!f || !buf || !cd) return FALSE;
207 if (s < 10 || strncmp("Dimensions", buf, 10) != 0) return FALSE;
209 if (fseek(f, - s, SEEK_CUR) == 0)
219 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
221 while (b != ']' && p < sizeof(buf) - 1)
223 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
230 if (fread(&b, sizeof(b), 1, f) != 1) break;
234 if (sscanf(buf, "%d x %d", &w, &h) != 2) return FALSE;
238 cd->dimensions = TRUE;
246 static gboolean cache_sim_read_date(FILE *f, gchar *buf, gint s, CacheData *cd)
248 if (!f || !buf || !cd) return FALSE;
250 if (s < 4 || strncmp("Date", buf, 4) != 0) return FALSE;
252 if (fseek(f, - s, SEEK_CUR) == 0)
261 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
263 while (b != ']' && p < sizeof(buf) - 1)
265 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
272 if (fread(&b, sizeof(b), 1, f) != 1) break;
276 cd->date = strtol(buf, NULL, 10);
278 cd->have_date = TRUE;
286 static gboolean cache_sim_read_md5sum(FILE *f, gchar *buf, gint s, CacheData *cd)
288 if (!f || !buf || !cd) return FALSE;
290 if (s < 8 || strncmp("MD5sum", buf, 6) != 0) return FALSE;
292 if (fseek(f, - s, SEEK_CUR) == 0)
301 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
303 while (b != ']' && p < sizeof(buf) - 1)
305 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
311 if (fread(&b, sizeof(b), 1, f) != 1) break;
315 cd->have_md5sum = md5_digest_from_text(buf, cd->md5sum);
323 static gboolean cache_sim_read_similarity(FILE *f, gchar *buf, gint s, CacheData *cd)
325 if (!f || !buf || !cd) return FALSE;
327 if (s < 11 || strncmp("Similarity", buf, 10) != 0) return FALSE;
329 if (strncmp("Grid[32 x 32]", buf + 10, 13) != 0) return FALSE;
331 if (fseek(f, - s, SEEK_CUR) == 0)
335 ImageSimilarityData *sd;
341 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
346 /* use current sim that may already contain data we will not touch here */
349 cd->similarity = FALSE;
353 sd = image_sim_new();
356 for (y = 0; y < 32; y++)
359 for (x = 0; x < 32; x++)
361 if (fread(&pixel_buf, sizeof(pixel_buf), 1, f) != 1)
366 sd->avg_r[s + x] = pixel_buf[0];
367 sd->avg_g[s + x] = pixel_buf[1];
368 sd->avg_b[s + x] = pixel_buf[2];
372 if (fread(&b, sizeof(b), 1, f) == 1)
374 if (b != '\n') fseek(f, -1, SEEK_CUR);
378 cd->sim->filled = TRUE;
379 cd->similarity = TRUE;
387 #define CACHE_LOAD_LINE_NOISE 8
389 CacheData *cache_sim_data_load(const gchar *path)
392 CacheData *cd = NULL;
394 gint success = CACHE_LOAD_LINE_NOISE;
397 if (!path) return NULL;
399 pathl = path_from_utf8(path);
400 f = fopen(pathl, "r");
405 cd = cache_sim_data_new();
406 cd->path = g_strdup(path);
408 if (fread(&buf, sizeof(gchar), 9, f) != 9 ||
409 strncmp(buf, "SIMcache", 8) != 0)
411 DEBUG_1("%s is not a cache file", cd->path);
418 s = fread(&buf, sizeof(gchar), sizeof(buf), f);
426 if (!cache_sim_read_comment(f, buf, s, cd) &&
427 !cache_sim_read_dimensions(f, buf, s, cd) &&
428 !cache_sim_read_date(f, buf, s, cd) &&
429 !cache_sim_read_md5sum(f, buf, s, cd) &&
430 !cache_sim_read_similarity(f, buf, s, cd))
432 if (!cache_sim_read_skipline(f, s))
443 success = CACHE_LOAD_LINE_NOISE;
450 if (!cd->dimensions &&
455 cache_sim_data_free(cd);
463 *-------------------------------------------------------------------
465 *-------------------------------------------------------------------
468 void cache_sim_data_set_dimensions(CacheData *cd, gint w, gint h)
474 cd->dimensions = TRUE;
477 void cache_sim_data_set_date(CacheData *cd, time_t date)
482 cd->have_date = TRUE;
485 void cache_sim_data_set_md5sum(CacheData *cd, guchar digest[16])
491 for (i = 0; i < 16; i++)
493 cd->md5sum[i] = digest[i];
495 cd->have_md5sum = TRUE;
498 void cache_sim_data_set_similarity(CacheData *cd, ImageSimilarityData *sd)
500 if (!cd || !sd || !sd->filled) return;
502 if (!cd->sim) cd->sim = image_sim_new();
504 memcpy(cd->sim->avg_r, sd->avg_r, 1024);
505 memcpy(cd->sim->avg_g, sd->avg_g, 1024);
506 memcpy(cd->sim->avg_b, sd->avg_b, 1024);
507 cd->sim->filled = TRUE;
509 cd->similarity = TRUE;
512 gboolean cache_sim_data_filled(ImageSimilarityData *sd)
514 if (!sd) return FALSE;
519 *-------------------------------------------------------------------
520 * cache path location utils
521 *-------------------------------------------------------------------
525 static void cache_path_parts(CacheType type,
526 const gchar **cache_rc, const gchar **cache_local, const gchar **cache_ext)
530 case CACHE_TYPE_THUMB:
531 *cache_rc = get_thumbnails_cache_dir();
532 *cache_local = GQ_CACHE_LOCAL_THUMB;
533 *cache_ext = GQ_CACHE_EXT_THUMB;
536 *cache_rc = get_thumbnails_cache_dir();
537 *cache_local = GQ_CACHE_LOCAL_THUMB;
538 *cache_ext = GQ_CACHE_EXT_SIM;
540 case CACHE_TYPE_METADATA:
541 *cache_rc = get_metadata_cache_dir();
542 *cache_local = GQ_CACHE_LOCAL_METADATA;
543 *cache_ext = GQ_CACHE_EXT_METADATA;
545 case CACHE_TYPE_XMP_METADATA:
546 *cache_rc = get_metadata_cache_dir();
547 *cache_local = GQ_CACHE_LOCAL_METADATA;
548 *cache_ext = GQ_CACHE_EXT_XMP_METADATA;
553 gchar *cache_get_location(CacheType type, const gchar *source, gint include_name, mode_t *mode)
558 const gchar *cache_rc;
559 const gchar *cache_local;
560 const gchar *cache_ext;
562 if (!source) return NULL;
564 cache_path_parts(type, &cache_rc, &cache_local, &cache_ext);
566 base = remove_level_from_path(source);
569 name = g_strconcat(filename_from_path(source), cache_ext, NULL);
572 if (((type != CACHE_TYPE_METADATA && type != CACHE_TYPE_XMP_METADATA && options->thumbnails.cache_into_dirs) ||
573 ((type == CACHE_TYPE_METADATA || type == CACHE_TYPE_XMP_METADATA) && options->metadata.enable_metadata_dirs)) &&
574 access_file(base, W_OK))
576 path = g_build_filename(base, cache_local, name, NULL);
577 if (mode) *mode = 0775;
582 path = g_build_filename(cache_rc, base, name, NULL);
583 if (mode) *mode = 0755;
587 if (name) g_free(name);
592 static gchar *cache_build_path_local(const gchar *source, const gchar *cache_local, const gchar *cache_ext)
595 gchar *base = remove_level_from_path(source);
596 gchar *name = g_strconcat(filename_from_path(source), cache_ext, NULL);
597 path = g_build_filename(base, cache_local, name, NULL);
604 static gchar *cache_build_path_rc(const gchar *source, const gchar *cache_rc, const gchar *cache_ext)
607 gchar *name = g_strconcat(source, cache_ext, NULL);
608 path = g_build_filename(cache_rc, name, NULL);
614 gchar *cache_find_location(CacheType type, const gchar *source)
617 const gchar *cache_rc;
618 const gchar *cache_local;
619 const gchar *cache_ext;
620 gboolean prefer_local;
622 if (!source) return NULL;
624 cache_path_parts(type, &cache_rc, &cache_local, &cache_ext);
626 if (type == CACHE_TYPE_METADATA || type == CACHE_TYPE_XMP_METADATA)
628 prefer_local = options->metadata.enable_metadata_dirs;
632 prefer_local = options->thumbnails.cache_into_dirs;
637 path = cache_build_path_local(source, cache_local, cache_ext);
641 path = cache_build_path_rc(source, cache_rc, cache_ext);
648 /* try the opposite method if not found */
651 path = cache_build_path_local(source, cache_local, cache_ext);
655 path = cache_build_path_rc(source, cache_rc, cache_ext);
668 gboolean cache_time_valid(const gchar *cache, const gchar *path)
670 struct stat cache_st;
674 gboolean ret = FALSE;
676 if (!cache || !path) return FALSE;
678 cachel = path_from_utf8(cache);
679 pathl = path_from_utf8(path);
681 if (stat(cachel, &cache_st) == 0 &&
682 stat(pathl, &path_st) == 0)
684 if (cache_st.st_mtime == path_st.st_mtime)
688 else if (cache_st.st_mtime > path_st.st_mtime)
692 ut.actime = ut.modtime = cache_st.st_mtime;
693 if (utime(cachel, &ut) < 0 &&
696 DEBUG_1("cache permission workaround: %s", cachel);
708 const gchar *get_thumbnails_cache_dir(void)
710 static gchar *thumbnails_cache_dir = NULL;
712 if (thumbnails_cache_dir) return thumbnails_cache_dir;
716 thumbnails_cache_dir = g_build_filename(xdg_cache_home_get(), GQ_APPNAME_LC, GQ_CACHE_THUMB, NULL);
720 thumbnails_cache_dir = g_build_filename(get_rc_dir(), GQ_CACHE_THUMB, NULL);
723 return thumbnails_cache_dir;
726 const gchar *get_metadata_cache_dir(void)
728 static gchar *metadata_cache_dir = NULL;
730 if (metadata_cache_dir) return metadata_cache_dir;
734 /* Metadata go to $XDG_DATA_HOME.
735 * "Keywords and comments, among other things, are irreplaceable and cannot be auto-generated,
736 * so I don't think they'd be appropriate for the cache directory." -- Omari Stephens on geeqie-devel ml
738 metadata_cache_dir = g_build_filename(xdg_data_home_get(), GQ_APPNAME_LC, GQ_CACHE_METADATA, NULL);
742 metadata_cache_dir = g_build_filename(get_rc_dir(), GQ_CACHE_METADATA, NULL);
745 return metadata_cache_dir;
748 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */