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  *-------------------------------------------------------------------
36  * Cache data file format:
37  *-------------------------------------------------------------------
38  *
39  * SIMcache
40  * #comment
41  * Dimensions=[<width> x <height>]
42  * Date=[<value in time_t format, or -1 if no embedded date>]
43  * MD5sum=[<32 character ascii text digest>]
44  * SimilarityGrid[32 x 32]=<3072 bytes of data (1024 pixels in RGB format, 1 pixel is 24bits)>
45  *
46  *
47  * The first line (9 bytes) indicates it is a SIMcache format file. (new line char must exist)
48  * Comment lines starting with a # are ignored up to a new line.
49  * All data lines should end with a new line char.
50  * Format is very strict, data must begin with the char immediately following '='.
51  * Currently SimilarityGrid is always assumed to be 32 x 32 RGB.
52  */
53 
54 
55 /*
56  *-------------------------------------------------------------------
57  * sim cache data
58  *-------------------------------------------------------------------
59  */
60 
cache_sim_data_new(void)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 
cache_sim_data_free(CacheData * cd)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 
cache_sim_write_dimensions(SecureSaveInfo * ssi,CacheData * cd)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 
cache_sim_write_date(SecureSaveInfo * ssi,CacheData * cd)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 
cache_sim_write_md5sum(SecureSaveInfo * ssi,CacheData * cd)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 
cache_sim_write_similarity(SecureSaveInfo * ssi,CacheData * cd)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 
cache_sim_data_save(CacheData * cd)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 
cache_sim_read_skipline(FILE * f,gint s)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 
cache_sim_read_comment(FILE * f,gchar * buf,gint s,CacheData * cd)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 
cache_sim_read_dimensions(FILE * f,gchar * buf,gint s,CacheData * cd)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 
cache_sim_read_date(FILE * f,gchar * buf,gint s,CacheData * cd)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 
cache_sim_read_md5sum(FILE * f,gchar * buf,gint s,CacheData * cd)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 
cache_sim_read_similarity(FILE * f,gchar * buf,gint s,CacheData * cd)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 
cache_sim_data_load(const gchar * path)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 
cache_sim_data_set_dimensions(CacheData * cd,gint w,gint h)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 
cache_sim_data_set_date(CacheData * cd,time_t date)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 
cache_sim_data_set_md5sum(CacheData * cd,guchar digest[16])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 
cache_sim_data_set_similarity(CacheData * cd,ImageSimilarityData * sd)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 
cache_sim_data_filled(ImageSimilarityData * sd)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 
cache_path_parts(CacheType type,const gchar ** cache_rc,const gchar ** cache_local,const gchar ** cache_ext)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 
cache_get_location(CacheType type,const gchar * source,gint include_name,mode_t * mode)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 
cache_build_path_local(const gchar * source,const gchar * cache_local,const gchar * cache_ext)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 
cache_build_path_rc(const gchar * source,const gchar * cache_rc,const gchar * cache_ext)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 
cache_find_location(CacheType type,const gchar * source)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 
cache_time_valid(const gchar * cache,const gchar * path)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 
get_thumbnails_cache_dir(void)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 
get_thumbnails_standard_cache_dir(void)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 
get_metadata_cache_dir(void)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: */
772