Wed Apr 13 18:16:14 2005 John Ellis <johne@verizon.net>
[geeqie.git] / src / cache.c
1 /*
2  * GQview
3  * (C) 2004 John Ellis
4  *
5  * Author: John Ellis
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12 #include "gqview.h"
13 #include "cache.h"
14
15 #include "md5-util.h"
16 #include "ui_fileops.h"
17
18 #include <utime.h>
19 #include <errno.h>
20
21
22 /*
23  *-------------------------------------------------------------------
24  * Cache data file format:
25  *-------------------------------------------------------------------
26  * 
27  * SIMcache
28  * #coment
29  * Dimensions=[<width> x <height>]
30  * Date=[<value in time_t format, or -1 if no embedded date>]
31  * Checksum=[<value>]
32  * MD5sum=[<32 character ascii text digest>]
33  * SimilarityGrid[32 x 32]=<3072 bytes of data (1024 pixels in RGB format, 1 pixel is 24bits)>
34  * 
35  * 
36  * The first line (9 bytes) indicates it is a SIMcache format file. (new line char must exist)
37  * Comment lines starting with a # are ignored up to a new line.
38  * All data lines should end with a new line char.
39  * Format is very strict, data must begin with the char immediately following '='.
40  * Currently SimilarityGrid is always assumed to be 32 x 32 RGB.
41  */
42
43
44 /*
45  *-------------------------------------------------------------------
46  * sim cache data
47  *-------------------------------------------------------------------
48  */
49
50 CacheData *cache_sim_data_new(void)
51 {
52         CacheData *cd;
53
54         cd = g_new0(CacheData, 1);
55         cd->date = -1;
56
57         return cd;
58 }
59
60 void cache_sim_data_free(CacheData *cd)
61 {
62         if (!cd) return;
63
64         g_free(cd->path);
65         image_sim_free(cd->sim);
66         g_free(cd);
67 }
68
69 /*
70  *-------------------------------------------------------------------
71  * sim cache write
72  *-------------------------------------------------------------------
73  */
74
75 static gint cache_sim_write_dimensions(FILE *f, CacheData *cd)
76 {
77         if (!f || !cd || !cd->dimensions) return FALSE;
78
79         fprintf(f, "Dimensions=[%d x %d]\n", cd->width, cd->height);
80
81         return TRUE;
82 }
83
84 static gint cache_sim_write_date(FILE *f, CacheData *cd)
85 {
86         if (!f || !cd || !cd->have_date) return FALSE;
87
88         fprintf(f, "Date=[%ld]\n", cd->date);
89
90         return TRUE;
91 }
92
93 static gint cache_sim_write_checksum(FILE *f, CacheData *cd)
94 {
95         if (!f || !cd || !cd->have_checksum) return FALSE;
96
97         fprintf(f, "Checksum=[%ld]\n", cd->checksum);
98
99         return TRUE;
100 }
101
102 static gint cache_sim_write_md5sum(FILE *f, CacheData *cd)
103 {
104         gchar *text;
105
106         if (!f || !cd || !cd->have_md5sum) return FALSE;
107
108         text = md5_digest_to_text(cd->md5sum);
109         fprintf(f, "MD5sum=[%s]\n", text);
110         g_free(text);
111
112         return TRUE;
113 }
114
115 static gint cache_sim_write_similarity(FILE *f, CacheData *cd)
116 {
117         gint success = FALSE;
118
119         if (!f || !cd || !cd->similarity) return FALSE;
120
121         if (cd->sim && cd->sim->filled)
122                 {
123                 gint x, y;
124                 guint8 buf[96];
125
126                 fprintf(f, "SimilarityGrid[32 x 32]=");
127                 for (y = 0; y < 32; y++)
128                         {
129                         gint s;
130                         guint8 *p;
131
132                         s = y * 32;
133                         p = buf;
134                         for(x = 0; x < 32; x++)
135                                 {
136                                 *p = cd->sim->avg_r[s + x]; p++;
137                                 *p = cd->sim->avg_g[s + x]; p++;
138                                 *p = cd->sim->avg_b[s + x]; p++;
139                                 }
140                         fwrite(buf, sizeof(buf), 1, f);
141                         }
142
143                 fprintf(f, "\n");
144                 success = TRUE;
145                 }
146
147         return success;
148 }
149
150 gint cache_sim_data_save(CacheData *cd)
151 {
152         FILE *f;
153         gchar *pathl;
154
155         if (!cd || !cd->path) return FALSE;
156
157         pathl = path_from_utf8(cd->path);
158         f = fopen(pathl, "w");
159         g_free(pathl);
160
161         if (!f)
162                 {
163                 printf("Unable to save sim cache data: %s\n", cd->path);
164                 return FALSE;
165                 }
166
167         fprintf(f, "SIMcache\n#%s %s\n", PACKAGE, VERSION);
168         cache_sim_write_dimensions(f, cd);
169         cache_sim_write_date(f, cd);
170         cache_sim_write_checksum(f, cd);
171         cache_sim_write_md5sum(f, cd);
172         cache_sim_write_similarity(f, cd);
173
174         fclose (f);
175
176         return TRUE;
177 }
178
179 /*
180  *-------------------------------------------------------------------
181  * sim cache read
182  *-------------------------------------------------------------------
183  */
184
185 static gint cache_sim_read_comment(FILE *f, char *buf, int s, CacheData *cd)
186 {
187         if (!f || !buf || !cd) return FALSE;
188
189         if (buf[0] != '#') return FALSE;
190
191         if (fseek(f, 0 - (s - 1), SEEK_CUR) == 0)
192                 {
193                 char 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 gint cache_sim_read_dimensions(FILE *f, char *buf, int s, CacheData *cd)
205 {
206         if (!f || !buf || !cd) return FALSE;
207
208         if (s < 10 || strncmp("Dimensions", buf, 10) != 0) return FALSE;
209
210         if (fseek(f, - s, SEEK_CUR) == 0)
211                 {
212                 char b;
213                 char buf[1024];
214                 gint p = 0;
215                 gint w, h;
216
217                 b = 'X';
218                 while (b != '[')
219                         {
220                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
221                         }
222                 while (b != ']' && p < 1023)
223                         {
224                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
225                         buf[p] = b;
226                         p++;
227                         }
228
229                 while (b != '\n')
230                         {
231                         if (fread(&b, sizeof(b), 1, f) != 1) break;
232                         }
233
234                 buf[p] = '\0';
235                 if (sscanf(buf, "%d x %d", &w, &h) != 2) return FALSE;
236
237                 cd->width = w;
238                 cd->height = h;
239                 cd->dimensions = TRUE;
240
241                 return TRUE;
242                 }
243
244         return FALSE;
245 }
246
247 static gint cache_sim_read_date(FILE *f, char *buf, int s, CacheData *cd)
248 {
249         if (!f || !buf || !cd) return FALSE;
250
251         if (s < 4 || strncmp("Date", buf, 4) != 0) return FALSE;
252
253         if (fseek(f, - s, SEEK_CUR) == 0)
254                 {
255                 char b;
256                 char buf[1024];
257                 gint p = 0;
258
259                 b = 'X';
260                 while (b != '[')
261                         {
262                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
263                         }
264                 while (b != ']' && p < 1023)
265                         {
266                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
267                         buf[p] = b;
268                         p++;
269                         }
270
271                 while (b != '\n')
272                         {
273                         if (fread(&b, sizeof(b), 1, f) != 1) break;
274                         }
275
276                 buf[p] = '\0';
277                 cd->date = strtol(buf, NULL, 10);
278
279                 cd->have_date = TRUE;
280
281                 return TRUE;
282                 }
283
284         return FALSE;
285 }
286
287 static gint cache_sim_read_checksum(FILE *f, char *buf, int s, CacheData *cd)
288 {
289         if (!f || !buf || !cd) return FALSE;
290
291         if (s < 8 || strncmp("Checksum", buf, 8) != 0) return FALSE;
292
293         if (fseek(f, - s, SEEK_CUR) == 0)
294                 {
295                 char b;
296                 char buf[1024];
297                 gint p = 0;
298
299                 b = 'X';
300                 while (b != '[')
301                         {
302                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
303                         }
304                 while (b != ']' && p < 1023)
305                         {
306                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
307                         buf[p] = b;
308                         p++;
309                         }
310
311                 while (b != '\n')
312                         {
313                         if (fread(&b, sizeof(b), 1, f) != 1) break;
314                         }
315
316                 buf[p] = '\0';
317                 cd->checksum = strtol(buf, NULL, 10);
318
319                 cd->have_checksum = TRUE;
320
321                 return TRUE;
322                 }
323
324         return FALSE;
325 }
326
327 static gint cache_sim_read_md5sum(FILE *f, char *buf, int s, CacheData *cd)
328 {
329         if (!f || !buf || !cd) return FALSE;
330
331         if (s < 8 || strncmp("MD5sum", buf, 6) != 0) return FALSE;
332
333         if (fseek(f, - s, SEEK_CUR) == 0)
334                 {
335                 char b;
336                 char buf[64];
337                 gint p = 0;
338
339                 b = 'X';
340                 while (b != '[')
341                         {
342                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
343                         }
344                 while (b != ']' && p < 63)
345                         {
346                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
347                         buf[p] = b;
348                         p++;
349                         }
350                 while (b != '\n')
351                         {
352                         if (fread(&b, sizeof(b), 1, f) != 1) break;
353                         }
354
355                 buf[p] = '\0';
356                 cd->have_md5sum = md5_digest_from_text(buf, cd->md5sum);
357
358                 return TRUE;
359                 }
360
361         return FALSE;
362 }
363
364 static gint cache_sim_read_similarity(FILE *f, char *buf, int s, CacheData *cd)
365 {
366         if (!f || !buf || !cd) return FALSE;
367
368         if (s < 11 || strncmp("Similarity", buf, 10) != 0) return FALSE;
369
370         if (strncmp("Grid[32 x 32]", buf + 10, 13) != 0) return FALSE;
371
372         if (fseek(f, - s, SEEK_CUR) == 0)
373                 {
374                 char b;
375                 guint8 pixel_buf[3];
376                 ImageSimilarityData *sd;
377                 gint x, y;
378
379                 b = 'X';
380                 while (b != '=')
381                         {
382                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
383                         }
384
385                 if (cd->sim)
386                         {
387                         /* use current sim that may already contain data we will not touch here */
388                         sd = cd->sim;
389                         cd->sim = NULL;
390                         cd->similarity = FALSE;
391                         }
392                 else
393                         {
394                         sd = image_sim_new();
395                         }
396
397                 for (y = 0; y < 32; y++)
398                         {
399                         gint s = y * 32;
400                         for (x = 0; x < 32; x++)
401                                 {
402                                 if (fread(&pixel_buf, sizeof(pixel_buf), 1, f) != 1)
403                                         {
404                                         image_sim_free(sd);
405                                         return FALSE;
406                                         }
407                                 sd->avg_r[s + x] = pixel_buf[0];
408                                 sd->avg_g[s + x] = pixel_buf[1];
409                                 sd->avg_b[s + x] = pixel_buf[2];
410                                 }
411                         }
412
413                 if (fread(&b, sizeof(b), 1, f) == 1)
414                         {
415                         if (b != '\n') fseek(f, -1, SEEK_CUR);
416                         }
417
418                 cd->sim = sd;
419                 cd->sim->filled = TRUE;
420                 cd->similarity = TRUE;
421
422                 return TRUE;
423                 }
424
425         return FALSE;
426 }
427
428 CacheData *cache_sim_data_load(const gchar *path)
429 {
430         FILE *f;
431         CacheData *cd = NULL;
432         char buf[32];
433         gint success = TRUE;
434         gchar *pathl;
435
436         if (!path) return NULL;
437
438         pathl = path_from_utf8(path);
439         f = fopen(pathl, "r");
440         g_free(pathl);
441
442         if (!f) return NULL;
443
444         cd = cache_sim_data_new();
445         cd->path = g_strdup(path);
446
447         if (fread(&buf, sizeof(char), 9, f) != 9 ||
448             strncmp(buf, "SIMcache", 8) != 0)
449                 {
450                 if (debug) printf("%s is not a cache file\n", cd->path);
451                 success = FALSE;
452                 }
453
454         while (success)
455                 {
456                 int s;
457                 s = fread(&buf, sizeof(char), sizeof(buf), f);
458
459                 if (s < 1)
460                         {
461                         success = FALSE;
462                         }
463                 else
464                         {
465                         if (!cache_sim_read_comment(f, buf, s, cd) &&
466                             !cache_sim_read_dimensions(f, buf, s, cd) &&
467                             !cache_sim_read_date(f, buf, s, cd) &&
468                             !cache_sim_read_checksum(f, buf, s, cd) &&
469                             !cache_sim_read_md5sum(f, buf, s, cd) &&
470                             !cache_sim_read_similarity(f, buf, s, cd))
471                                 {
472                                 success = FALSE;
473                                 }
474                         }
475                 }
476
477         fclose(f);
478
479         if (!cd->dimensions && !cd->similarity)
480                 {
481                 cache_sim_data_free(cd);
482                 cd = NULL;
483                 }
484
485         return cd;
486 }
487
488 /*
489  *-------------------------------------------------------------------
490  * sim cache setting
491  *-------------------------------------------------------------------
492  */
493
494 void cache_sim_data_set_dimensions(CacheData *cd, gint w, gint h)
495 {
496         if (!cd) return;
497
498         cd->width = w;
499         cd->height = h;
500         cd->dimensions = TRUE;
501 }
502
503 void cache_sim_data_set_date(CacheData *cd, time_t date)
504 {
505         if (!cd) return;
506
507         cd->date = date;
508         cd->have_date = TRUE;
509 }
510
511 void cache_sim_data_set_checksum(CacheData *cd, long checksum)
512 {
513         if (!cd) return;
514
515         cd->checksum = checksum;
516         cd->have_checksum = TRUE;
517 }
518
519 void cache_sim_data_set_md5sum(CacheData *cd, guchar digest[16])
520 {
521         gint i;
522
523         if (!cd) return;
524
525         for (i = 0; i < 16; i++)
526                 {
527                 cd->md5sum[i] = digest[i];
528                 }
529         cd->have_md5sum = TRUE;
530 }
531
532 void cache_sim_data_set_similarity(CacheData *cd, ImageSimilarityData *sd)
533 {
534         if (!cd || !sd || !sd->filled) return;
535
536         if (!cd->sim) cd->sim = image_sim_new();
537
538         memcpy(cd->sim->avg_r, sd->avg_r, 1024);
539         memcpy(cd->sim->avg_g, sd->avg_g, 1024);
540         memcpy(cd->sim->avg_b, sd->avg_b, 1024);
541         cd->sim->filled = TRUE;
542
543         cd->similarity = TRUE;
544 }
545
546 gint cache_sim_data_filled(ImageSimilarityData *sd)
547 {
548         if (!sd) return FALSE;
549         return sd->filled;
550 }
551
552 /*
553  *-------------------------------------------------------------------
554  * cache path location utils
555  *-------------------------------------------------------------------
556  */
557
558 /* warning: this func modifies path string contents!, on fail it is set to fail point */
559 gint cache_ensure_dir_exists(gchar *path, mode_t mode)
560 {
561         if (!path) return FALSE;
562
563         if (!isdir(path))
564                 {
565                 gchar *p = path;
566                 while (p[0] != '\0')
567                         {
568                         p++;
569                         if (p[0] == '/' || p[0] == '\0')
570                                 {
571                                 gint end = TRUE;
572                                 if (p[0] != '\0')
573                                         {
574                                         p[0] = '\0';
575                                         end = FALSE;
576                                         }
577                                 if (!isdir(path))
578                                         {
579                                         if (debug) printf("creating sub dir:%s\n", path);
580                                         if (!mkdir_utf8(path, mode))
581                                                 {
582                                                 printf("create dir failed: %s\n", path);
583                                                 return FALSE;
584                                                 }
585                                         }
586                                 if (!end) p[0] = '/';
587                                 }
588                         }
589                 }
590         return TRUE;
591 }
592
593 static void cache_path_parts(CacheType type,
594                              const gchar **cache_rc, const gchar **cache_local, const gchar **cache_ext)
595 {
596         switch (type)
597                 {
598                 case CACHE_TYPE_THUMB:
599                         *cache_rc = GQVIEW_CACHE_RC_THUMB;
600                         *cache_local = GQVIEW_CACHE_LOCAL_THUMB;
601                         *cache_ext = GQVIEW_CACHE_EXT_THUMB;
602                         break;
603                 case CACHE_TYPE_SIM:
604                         *cache_rc = GQVIEW_CACHE_RC_THUMB;
605                         *cache_local = GQVIEW_CACHE_LOCAL_THUMB;
606                         *cache_ext = GQVIEW_CACHE_EXT_SIM;
607                         break;
608                 case CACHE_TYPE_METADATA:
609                         *cache_rc = GQVIEW_CACHE_RC_METADATA;
610                         *cache_local = GQVIEW_CACHE_LOCAL_METADATA;
611                         *cache_ext = GQVIEW_CACHE_EXT_METADATA;
612                         break;
613                 }
614 }
615
616 gchar *cache_get_location(CacheType type, const gchar *source, gint include_name, mode_t *mode)
617 {
618         gchar *path = NULL;
619         gchar *base;
620         gchar *name = NULL;
621         const gchar *cache_rc;
622         const gchar *cache_local;
623         const gchar *cache_ext;
624
625         if (!source) return NULL;
626
627         cache_path_parts(type, &cache_rc, &cache_local, &cache_ext);
628
629         base = remove_level_from_path(source);
630         if (include_name)
631                 {
632                 name = g_strconcat("/", filename_from_path(source), NULL);
633                 }
634         else
635                 {
636                 cache_ext = NULL;
637                 }
638
639         if (((type != CACHE_TYPE_METADATA && enable_thumb_dirs) ||
640              (type == CACHE_TYPE_METADATA && enable_metadata_dirs)) &&
641             access_file(base, W_OK))
642                 {
643                 path = g_strconcat(base, "/", cache_local, name, cache_ext, NULL);
644                 if (mode) *mode = 0775;
645                 }
646
647         if (!path)
648                 {
649                 path = g_strconcat(homedir(), "/", cache_rc, base, name, cache_ext, NULL);
650                 if (mode) *mode = 0755;
651                 }
652
653         g_free(base);
654         if (name) g_free(name);
655
656         return path;
657 }
658
659 gchar *cache_find_location(CacheType type, const gchar *source)
660 {
661         gchar *path;
662         const gchar *name;
663         gchar *base;
664         const gchar *cache_rc;
665         const gchar *cache_local;
666         const gchar *cache_ext;
667         gint prefer_local;
668
669         if (!source) return NULL;
670
671         cache_path_parts(type, &cache_rc, &cache_local, &cache_ext);
672
673         name = filename_from_path(source);
674         base = remove_level_from_path(source);
675
676         if (type == CACHE_TYPE_METADATA)
677                 {
678                 prefer_local = enable_metadata_dirs;
679                 }
680         else
681                 {
682                 prefer_local = enable_thumb_dirs;
683                 }
684
685         if (prefer_local)
686                 {
687                 path = g_strconcat(base, "/", cache_local, "/", name, cache_ext, NULL);
688                 }
689         else
690                 {
691                 path = g_strconcat(homedir(), "/", cache_rc, source, cache_ext, NULL);
692                 }
693
694         if (!isfile(path))
695                 {
696                 g_free(path);
697
698                 /* try the opposite method if not found */
699                 if (!prefer_local)
700                         {
701                         path = g_strconcat(base, "/", cache_local, "/", name, cache_ext, NULL);
702                         }
703                 else
704                         {
705                         path = g_strconcat(homedir(), "/", cache_rc, source, cache_ext, NULL);
706                         }
707
708                 if (!isfile(path))
709                         {
710                         g_free(path);
711                         path = NULL;
712                         }
713                 }
714
715         g_free(base);
716
717         return path;
718 }
719
720 gint cache_time_valid(const gchar *cache, const gchar *path)
721 {
722         struct stat cache_st;
723         struct stat path_st;
724         gchar *cachel;
725         gchar *pathl;
726         gint ret = FALSE;
727
728         if (!cache || !path) return FALSE;
729
730         cachel = path_from_utf8(cache);
731         pathl = path_from_utf8(path);
732
733         if (stat(cachel, &cache_st) == 0 &&
734             stat(pathl, &path_st) == 0)
735                 {
736                 if (cache_st.st_mtime == path_st.st_mtime)
737                         {
738                         ret = TRUE;
739                         }
740                 else if (cache_st.st_mtime > path_st.st_mtime)
741                         {
742                         struct utimbuf ut;
743
744                         ut.actime = ut.modtime = cache_st.st_mtime;
745                         if (utime(cachel, &ut) < 0 &&
746                             errno == EPERM)
747                                 {
748                                 if (debug) printf("cache permission workaround: %s\n", cachel);
749                                 ret = TRUE;
750                                 }
751                         }
752                 }
753
754         g_free(pathl);
755         g_free(cachel);
756
757         return ret;
758 }
759