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>]
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)>
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.
47 *-------------------------------------------------------------------
49 *-------------------------------------------------------------------
52 CacheData *cache_sim_data_new(void)
56 cd = g_new0(CacheData, 1);
62 void cache_sim_data_free(CacheData *cd)
67 image_sim_free(cd->sim);
72 *-------------------------------------------------------------------
74 *-------------------------------------------------------------------
77 static gboolean cache_sim_write_dimensions(SecureSaveInfo *ssi, CacheData *cd)
79 if (!cd || !cd->dimensions) return FALSE;
81 secure_fprintf(ssi, "Dimensions=[%d x %d]\n", cd->width, cd->height);
86 static gboolean cache_sim_write_date(SecureSaveInfo *ssi, CacheData *cd)
88 if (!cd || !cd->have_date) return FALSE;
90 secure_fprintf(ssi, "Date=[%ld]\n", cd->date);
95 static gboolean cache_sim_write_checksum(SecureSaveInfo *ssi, CacheData *cd)
97 if (!cd || !cd->have_checksum) return FALSE;
99 secure_fprintf(ssi, "Checksum=[%ld]\n", cd->checksum);
104 static gboolean cache_sim_write_md5sum(SecureSaveInfo *ssi, CacheData *cd)
108 if (!cd || !cd->have_md5sum) return FALSE;
110 text = md5_digest_to_text(cd->md5sum);
111 secure_fprintf(ssi, "MD5sum=[%s]\n", text);
117 static gboolean cache_sim_write_similarity(SecureSaveInfo *ssi, CacheData *cd)
122 if (!cd || !cd->similarity || !cd->sim || !cd->sim->filled) return FALSE;
124 secure_fprintf(ssi, "SimilarityGrid[32 x 32]=");
125 for (y = 0; y < 32; y++)
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];
133 for (x = 0; x < 32; x++)
140 secure_fwrite(buf, sizeof(buf), 1, ssi);
143 secure_fputc(ssi, '\n');
148 gboolean cache_sim_data_save(CacheData *cd)
153 if (!cd || !cd->path) return FALSE;
155 pathl = path_from_utf8(cd->path);
156 ssi = secure_open(pathl);
161 log_printf("Unable to save sim cache data: %s\n", cd->path);
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_checksum(ssi, cd);
169 cache_sim_write_md5sum(ssi, cd);
170 cache_sim_write_similarity(ssi, cd);
172 if (secure_close(ssi))
174 log_printf(_("error saving sim cache data: %s\nerror: %s\n"), cd->path,
175 secsave_strerror(secsave_errno));
183 *-------------------------------------------------------------------
185 *-------------------------------------------------------------------
188 static gboolean cache_sim_read_skipline(FILE *f, gint s)
190 if (!f) return FALSE;
192 if (fseek(f, 0 - s, SEEK_CUR) == 0)
195 while (fread(&b, sizeof(b), 1, f) == 1)
197 if (b == '\n') return TRUE;
205 static gboolean cache_sim_read_comment(FILE *f, gchar *buf, gint s, CacheData *cd)
207 if (!f || !buf || !cd) return FALSE;
209 if (s < 1 || buf[0] != '#') return FALSE;
211 return cache_sim_read_skipline(f, s - 1);
214 static gboolean cache_sim_read_dimensions(FILE *f, gchar *buf, gint s, CacheData *cd)
216 if (!f || !buf || !cd) return FALSE;
218 if (s < 10 || strncmp("Dimensions", buf, 10) != 0) return FALSE;
220 if (fseek(f, - s, SEEK_CUR) == 0)
230 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
232 while (b != ']' && p < sizeof(buf) - 1)
234 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
241 if (fread(&b, sizeof(b), 1, f) != 1) break;
245 if (sscanf(buf, "%d x %d", &w, &h) != 2) return FALSE;
249 cd->dimensions = TRUE;
257 static gboolean cache_sim_read_date(FILE *f, gchar *buf, gint s, CacheData *cd)
259 if (!f || !buf || !cd) return FALSE;
261 if (s < 4 || strncmp("Date", buf, 4) != 0) return FALSE;
263 if (fseek(f, - s, SEEK_CUR) == 0)
272 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
274 while (b != ']' && p < sizeof(buf) - 1)
276 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
283 if (fread(&b, sizeof(b), 1, f) != 1) break;
287 cd->date = strtol(buf, NULL, 10);
289 cd->have_date = TRUE;
297 static gboolean cache_sim_read_checksum(FILE *f, gchar *buf, gint s, CacheData *cd)
299 if (!f || !buf || !cd) return FALSE;
301 if (s < 8 || strncmp("Checksum", buf, 8) != 0) return FALSE;
303 if (fseek(f, - s, SEEK_CUR) == 0)
312 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
314 while (b != ']' && p < sizeof(buf) - 1)
316 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
323 if (fread(&b, sizeof(b), 1, f) != 1) break;
327 cd->checksum = strtol(buf, NULL, 10);
329 cd->have_checksum = TRUE;
337 static gboolean cache_sim_read_md5sum(FILE *f, gchar *buf, gint s, CacheData *cd)
339 if (!f || !buf || !cd) return FALSE;
341 if (s < 8 || strncmp("MD5sum", buf, 6) != 0) return FALSE;
343 if (fseek(f, - s, SEEK_CUR) == 0)
352 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
354 while (b != ']' && p < sizeof(buf) - 1)
356 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
362 if (fread(&b, sizeof(b), 1, f) != 1) break;
366 cd->have_md5sum = md5_digest_from_text(buf, cd->md5sum);
374 static gboolean cache_sim_read_similarity(FILE *f, gchar *buf, gint s, CacheData *cd)
376 if (!f || !buf || !cd) return FALSE;
378 if (s < 11 || strncmp("Similarity", buf, 10) != 0) return FALSE;
380 if (strncmp("Grid[32 x 32]", buf + 10, 13) != 0) return FALSE;
382 if (fseek(f, - s, SEEK_CUR) == 0)
386 ImageSimilarityData *sd;
392 if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
397 /* use current sim that may already contain data we will not touch here */
400 cd->similarity = FALSE;
404 sd = image_sim_new();
407 for (y = 0; y < 32; y++)
410 for (x = 0; x < 32; x++)
412 if (fread(&pixel_buf, sizeof(pixel_buf), 1, f) != 1)
417 sd->avg_r[s + x] = pixel_buf[0];
418 sd->avg_g[s + x] = pixel_buf[1];
419 sd->avg_b[s + x] = pixel_buf[2];
423 if (fread(&b, sizeof(b), 1, f) == 1)
425 if (b != '\n') fseek(f, -1, SEEK_CUR);
429 cd->sim->filled = TRUE;
430 cd->similarity = TRUE;
438 #define CACHE_LOAD_LINE_NOISE 8
440 CacheData *cache_sim_data_load(const gchar *path)
443 CacheData *cd = NULL;
445 gint success = CACHE_LOAD_LINE_NOISE;
448 if (!path) return NULL;
450 pathl = path_from_utf8(path);
451 f = fopen(pathl, "r");
456 cd = cache_sim_data_new();
457 cd->path = g_strdup(path);
459 if (fread(&buf, sizeof(gchar), 9, f) != 9 ||
460 strncmp(buf, "SIMcache", 8) != 0)
462 DEBUG_1("%s is not a cache file", cd->path);
469 s = fread(&buf, sizeof(gchar), sizeof(buf), f);
477 if (!cache_sim_read_comment(f, buf, s, cd) &&
478 !cache_sim_read_dimensions(f, buf, s, cd) &&
479 !cache_sim_read_date(f, buf, s, cd) &&
480 !cache_sim_read_checksum(f, buf, s, cd) &&
481 !cache_sim_read_md5sum(f, buf, s, cd) &&
482 !cache_sim_read_similarity(f, buf, s, cd))
484 if (!cache_sim_read_skipline(f, s))
495 success = CACHE_LOAD_LINE_NOISE;
502 if (!cd->dimensions &&
504 !cd->have_checksum &&
508 cache_sim_data_free(cd);
516 *-------------------------------------------------------------------
518 *-------------------------------------------------------------------
521 void cache_sim_data_set_dimensions(CacheData *cd, gint w, gint h)
527 cd->dimensions = TRUE;
530 void cache_sim_data_set_date(CacheData *cd, time_t date)
535 cd->have_date = TRUE;
538 void cache_sim_data_set_checksum(CacheData *cd, glong checksum)
542 cd->checksum = checksum;
543 cd->have_checksum = TRUE;
546 void cache_sim_data_set_md5sum(CacheData *cd, guchar digest[16])
552 for (i = 0; i < 16; i++)
554 cd->md5sum[i] = digest[i];
556 cd->have_md5sum = TRUE;
559 void cache_sim_data_set_similarity(CacheData *cd, ImageSimilarityData *sd)
561 if (!cd || !sd || !sd->filled) return;
563 if (!cd->sim) cd->sim = image_sim_new();
565 memcpy(cd->sim->avg_r, sd->avg_r, 1024);
566 memcpy(cd->sim->avg_g, sd->avg_g, 1024);
567 memcpy(cd->sim->avg_b, sd->avg_b, 1024);
568 cd->sim->filled = TRUE;
570 cd->similarity = TRUE;
573 gboolean cache_sim_data_filled(ImageSimilarityData *sd)
575 if (!sd) return FALSE;
580 *-------------------------------------------------------------------
581 * cache path location utils
582 *-------------------------------------------------------------------
586 static void cache_path_parts(CacheType type,
587 const gchar **cache_rc, const gchar **cache_local, const gchar **cache_ext)
591 case CACHE_TYPE_THUMB:
592 *cache_rc = get_thumbnails_cache_dir();
593 *cache_local = GQ_CACHE_LOCAL_THUMB;
594 *cache_ext = GQ_CACHE_EXT_THUMB;
597 *cache_rc = get_thumbnails_cache_dir();
598 *cache_local = GQ_CACHE_LOCAL_THUMB;
599 *cache_ext = GQ_CACHE_EXT_SIM;
601 case CACHE_TYPE_METADATA:
602 *cache_rc = get_metadata_cache_dir();
603 *cache_local = GQ_CACHE_LOCAL_METADATA;
604 *cache_ext = GQ_CACHE_EXT_METADATA;
606 case CACHE_TYPE_XMP_METADATA:
607 *cache_rc = get_metadata_cache_dir();
608 *cache_local = GQ_CACHE_LOCAL_METADATA;
609 *cache_ext = GQ_CACHE_EXT_XMP_METADATA;
614 gchar *cache_get_location(CacheType type, const gchar *source, gint include_name, mode_t *mode)
619 const gchar *cache_rc;
620 const gchar *cache_local;
621 const gchar *cache_ext;
623 if (!source) return NULL;
625 cache_path_parts(type, &cache_rc, &cache_local, &cache_ext);
627 base = remove_level_from_path(source);
630 name = g_strconcat(filename_from_path(source), cache_ext, NULL);
633 if (((type != CACHE_TYPE_METADATA && type != CACHE_TYPE_XMP_METADATA && options->thumbnails.cache_into_dirs) ||
634 ((type == CACHE_TYPE_METADATA || type == CACHE_TYPE_XMP_METADATA) && options->metadata.enable_metadata_dirs)) &&
635 access_file(base, W_OK))
637 path = g_build_filename(base, cache_local, name, NULL);
638 if (mode) *mode = 0775;
643 path = g_build_filename(cache_rc, base, name, NULL);
644 if (mode) *mode = 0755;
648 if (name) g_free(name);
653 static gchar *cache_build_path_local(const gchar *source, const gchar *cache_local, const gchar *cache_ext)
656 gchar *base = remove_level_from_path(source);
657 gchar *name = g_strconcat(filename_from_path(source), cache_ext, NULL);
658 path = g_build_filename(base, cache_local, name, NULL);
665 static gchar *cache_build_path_rc(const gchar *source, const gchar *cache_rc, const gchar *cache_ext)
668 gchar *name = g_strconcat(source, cache_ext, NULL);
669 path = g_build_filename(cache_rc, name, NULL);
675 gchar *cache_find_location(CacheType type, const gchar *source)
678 const gchar *cache_rc;
679 const gchar *cache_local;
680 const gchar *cache_ext;
681 gboolean prefer_local;
683 if (!source) return NULL;
685 cache_path_parts(type, &cache_rc, &cache_local, &cache_ext);
687 if (type == CACHE_TYPE_METADATA || type == CACHE_TYPE_XMP_METADATA)
689 prefer_local = options->metadata.enable_metadata_dirs;
693 prefer_local = options->thumbnails.cache_into_dirs;
698 path = cache_build_path_local(source, cache_local, cache_ext);
702 path = cache_build_path_rc(source, cache_rc, cache_ext);
709 /* try the opposite method if not found */
712 path = cache_build_path_local(source, cache_local, cache_ext);
716 path = cache_build_path_rc(source, cache_rc, cache_ext);
729 gboolean cache_time_valid(const gchar *cache, const gchar *path)
731 struct stat cache_st;
735 gboolean ret = FALSE;
737 if (!cache || !path) return FALSE;
739 cachel = path_from_utf8(cache);
740 pathl = path_from_utf8(path);
742 if (stat(cachel, &cache_st) == 0 &&
743 stat(pathl, &path_st) == 0)
745 if (cache_st.st_mtime == path_st.st_mtime)
749 else if (cache_st.st_mtime > path_st.st_mtime)
753 ut.actime = ut.modtime = cache_st.st_mtime;
754 if (utime(cachel, &ut) < 0 &&
757 DEBUG_1("cache permission workaround: %s", cachel);
769 const gchar *get_thumbnails_cache_dir(void)
771 static gchar *thumbnails_cache_dir = NULL;
773 if (thumbnails_cache_dir) return thumbnails_cache_dir;
777 thumbnails_cache_dir = g_build_filename(xdg_cache_home_get(), GQ_APPNAME_LC, GQ_CACHE_THUMB, NULL);
781 thumbnails_cache_dir = g_build_filename(get_rc_dir(), GQ_CACHE_THUMB, NULL);
784 return thumbnails_cache_dir;
787 const gchar *get_metadata_cache_dir(void)
789 static gchar *metadata_cache_dir = NULL;
791 if (metadata_cache_dir) return metadata_cache_dir;
795 /* Metadata go to $XDG_DATA_HOME.
796 * "Keywords and comments, among other things, are irreplaceable and cannot be auto-generated,
797 * so I don't think they'd be appropriate for the cache directory." -- Omari Stephens on geeqie-devel ml
799 metadata_cache_dir = g_build_filename(xdg_data_home_get(), GQ_APPNAME_LC, GQ_CACHE_METADATA, NULL);
803 metadata_cache_dir = g_build_filename(get_rc_dir(), GQ_CACHE_METADATA, NULL);
806 return metadata_cache_dir;
809 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */