Mon Aug 15 18:27:38 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_skipline(FILE *f, int s)
186 {
187         if (!f) return FALSE;
188
189         if (fseek(f, 0 - s, SEEK_CUR) == 0)
190                 {
191                 char b;
192                 while (fread(&b, sizeof(b), 1, f) == 1)
193                         {
194                         if (b == '\n') return TRUE;
195                         }
196                 return TRUE;
197                 }
198
199         return FALSE;
200 }
201
202 static gint cache_sim_read_comment(FILE *f, char *buf, int s, CacheData *cd)
203 {
204         if (!f || !buf || !cd) return FALSE;
205
206         if (s < 1 || buf[0] != '#') return FALSE;
207
208         return cache_sim_read_skipline(f, s - 1);
209 }
210
211 static gint cache_sim_read_dimensions(FILE *f, char *buf, int s, CacheData *cd)
212 {
213         if (!f || !buf || !cd) return FALSE;
214
215         if (s < 10 || strncmp("Dimensions", buf, 10) != 0) return FALSE;
216
217         if (fseek(f, - s, SEEK_CUR) == 0)
218                 {
219                 char b;
220                 char buf[1024];
221                 gint p = 0;
222                 gint w, h;
223
224                 b = 'X';
225                 while (b != '[')
226                         {
227                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
228                         }
229                 while (b != ']' && p < 1023)
230                         {
231                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
232                         buf[p] = b;
233                         p++;
234                         }
235
236                 while (b != '\n')
237                         {
238                         if (fread(&b, sizeof(b), 1, f) != 1) break;
239                         }
240
241                 buf[p] = '\0';
242                 if (sscanf(buf, "%d x %d", &w, &h) != 2) return FALSE;
243
244                 cd->width = w;
245                 cd->height = h;
246                 cd->dimensions = TRUE;
247
248                 return TRUE;
249                 }
250
251         return FALSE;
252 }
253
254 static gint cache_sim_read_date(FILE *f, char *buf, int s, CacheData *cd)
255 {
256         if (!f || !buf || !cd) return FALSE;
257
258         if (s < 4 || strncmp("Date", buf, 4) != 0) return FALSE;
259
260         if (fseek(f, - s, SEEK_CUR) == 0)
261                 {
262                 char b;
263                 char buf[1024];
264                 gint p = 0;
265
266                 b = 'X';
267                 while (b != '[')
268                         {
269                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
270                         }
271                 while (b != ']' && p < 1023)
272                         {
273                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
274                         buf[p] = b;
275                         p++;
276                         }
277
278                 while (b != '\n')
279                         {
280                         if (fread(&b, sizeof(b), 1, f) != 1) break;
281                         }
282
283                 buf[p] = '\0';
284                 cd->date = strtol(buf, NULL, 10);
285
286                 cd->have_date = TRUE;
287
288                 return TRUE;
289                 }
290
291         return FALSE;
292 }
293
294 static gint cache_sim_read_checksum(FILE *f, char *buf, int s, CacheData *cd)
295 {
296         if (!f || !buf || !cd) return FALSE;
297
298         if (s < 8 || strncmp("Checksum", buf, 8) != 0) return FALSE;
299
300         if (fseek(f, - s, SEEK_CUR) == 0)
301                 {
302                 char b;
303                 char buf[1024];
304                 gint p = 0;
305
306                 b = 'X';
307                 while (b != '[')
308                         {
309                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
310                         }
311                 while (b != ']' && p < 1023)
312                         {
313                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
314                         buf[p] = b;
315                         p++;
316                         }
317
318                 while (b != '\n')
319                         {
320                         if (fread(&b, sizeof(b), 1, f) != 1) break;
321                         }
322
323                 buf[p] = '\0';
324                 cd->checksum = strtol(buf, NULL, 10);
325
326                 cd->have_checksum = TRUE;
327
328                 return TRUE;
329                 }
330
331         return FALSE;
332 }
333
334 static gint cache_sim_read_md5sum(FILE *f, char *buf, int s, CacheData *cd)
335 {
336         if (!f || !buf || !cd) return FALSE;
337
338         if (s < 8 || strncmp("MD5sum", buf, 6) != 0) return FALSE;
339
340         if (fseek(f, - s, SEEK_CUR) == 0)
341                 {
342                 char b;
343                 char buf[64];
344                 gint p = 0;
345
346                 b = 'X';
347                 while (b != '[')
348                         {
349                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
350                         }
351                 while (b != ']' && p < 63)
352                         {
353                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
354                         buf[p] = b;
355                         p++;
356                         }
357                 while (b != '\n')
358                         {
359                         if (fread(&b, sizeof(b), 1, f) != 1) break;
360                         }
361
362                 buf[p] = '\0';
363                 cd->have_md5sum = md5_digest_from_text(buf, cd->md5sum);
364
365                 return TRUE;
366                 }
367
368         return FALSE;
369 }
370
371 static gint cache_sim_read_similarity(FILE *f, char *buf, int s, CacheData *cd)
372 {
373         if (!f || !buf || !cd) return FALSE;
374
375         if (s < 11 || strncmp("Similarity", buf, 10) != 0) return FALSE;
376
377         if (strncmp("Grid[32 x 32]", buf + 10, 13) != 0) return FALSE;
378
379         if (fseek(f, - s, SEEK_CUR) == 0)
380                 {
381                 char b;
382                 guint8 pixel_buf[3];
383                 ImageSimilarityData *sd;
384                 gint x, y;
385
386                 b = 'X';
387                 while (b != '=')
388                         {
389                         if (fread(&b, sizeof(b), 1, f) != 1) return FALSE;
390                         }
391
392                 if (cd->sim)
393                         {
394                         /* use current sim that may already contain data we will not touch here */
395                         sd = cd->sim;
396                         cd->sim = NULL;
397                         cd->similarity = FALSE;
398                         }
399                 else
400                         {
401                         sd = image_sim_new();
402                         }
403
404                 for (y = 0; y < 32; y++)
405                         {
406                         gint s = y * 32;
407                         for (x = 0; x < 32; x++)
408                                 {
409                                 if (fread(&pixel_buf, sizeof(pixel_buf), 1, f) != 1)
410                                         {
411                                         image_sim_free(sd);
412                                         return FALSE;
413                                         }
414                                 sd->avg_r[s + x] = pixel_buf[0];
415                                 sd->avg_g[s + x] = pixel_buf[1];
416                                 sd->avg_b[s + x] = pixel_buf[2];
417                                 }
418                         }
419
420                 if (fread(&b, sizeof(b), 1, f) == 1)
421                         {
422                         if (b != '\n') fseek(f, -1, SEEK_CUR);
423                         }
424
425                 cd->sim = sd;
426                 cd->sim->filled = TRUE;
427                 cd->similarity = TRUE;
428
429                 return TRUE;
430                 }
431
432         return FALSE;
433 }
434
435 #define CACHE_LOAD_LINE_NOISE 8
436
437 CacheData *cache_sim_data_load(const gchar *path)
438 {
439         FILE *f;
440         CacheData *cd = NULL;
441         char buf[32];
442         gint success = CACHE_LOAD_LINE_NOISE;
443         gchar *pathl;
444
445         if (!path) return NULL;
446
447         pathl = path_from_utf8(path);
448         f = fopen(pathl, "r");
449         g_free(pathl);
450
451         if (!f) return NULL;
452
453         cd = cache_sim_data_new();
454         cd->path = g_strdup(path);
455
456         if (fread(&buf, sizeof(char), 9, f) != 9 ||
457             strncmp(buf, "SIMcache", 8) != 0)
458                 {
459                 if (debug) printf("%s is not a cache file\n", cd->path);
460                 success = 0;
461                 }
462
463         while (success > 0)
464                 {
465                 int s;
466                 s = fread(&buf, sizeof(char), sizeof(buf), f);
467
468                 if (s < 1)
469                         {
470                         success = 0;
471                         }
472                 else
473                         {
474                         if (!cache_sim_read_comment(f, buf, s, cd) &&
475                             !cache_sim_read_dimensions(f, buf, s, cd) &&
476                             !cache_sim_read_date(f, buf, s, cd) &&
477                             !cache_sim_read_checksum(f, buf, s, cd) &&
478                             !cache_sim_read_md5sum(f, buf, s, cd) &&
479                             !cache_sim_read_similarity(f, buf, s, cd))
480                                 {
481                                 if (!cache_sim_read_skipline(f, s))
482                                         {
483                                         success = 0;
484                                         }
485                                 else
486                                         {
487                                         success--;
488                                         }
489                                 }
490                         else
491                                 {
492                                 success = CACHE_LOAD_LINE_NOISE;
493                                 }
494                         }
495                 }
496
497         fclose(f);
498
499         if (!cd->dimensions &&
500             !cd->have_date &&
501             !cd->have_checksum &&
502             !cd->have_md5sum &&
503             !cd->similarity)
504                 {
505                 cache_sim_data_free(cd);
506                 cd = NULL;
507                 }
508
509         return cd;
510 }
511
512 /*
513  *-------------------------------------------------------------------
514  * sim cache setting
515  *-------------------------------------------------------------------
516  */
517
518 void cache_sim_data_set_dimensions(CacheData *cd, gint w, gint h)
519 {
520         if (!cd) return;
521
522         cd->width = w;
523         cd->height = h;
524         cd->dimensions = TRUE;
525 }
526
527 void cache_sim_data_set_date(CacheData *cd, time_t date)
528 {
529         if (!cd) return;
530
531         cd->date = date;
532         cd->have_date = TRUE;
533 }
534
535 void cache_sim_data_set_checksum(CacheData *cd, long checksum)
536 {
537         if (!cd) return;
538
539         cd->checksum = checksum;
540         cd->have_checksum = TRUE;
541 }
542
543 void cache_sim_data_set_md5sum(CacheData *cd, guchar digest[16])
544 {
545         gint i;
546
547         if (!cd) return;
548
549         for (i = 0; i < 16; i++)
550                 {
551                 cd->md5sum[i] = digest[i];
552                 }
553         cd->have_md5sum = TRUE;
554 }
555
556 void cache_sim_data_set_similarity(CacheData *cd, ImageSimilarityData *sd)
557 {
558         if (!cd || !sd || !sd->filled) return;
559
560         if (!cd->sim) cd->sim = image_sim_new();
561
562         memcpy(cd->sim->avg_r, sd->avg_r, 1024);
563         memcpy(cd->sim->avg_g, sd->avg_g, 1024);
564         memcpy(cd->sim->avg_b, sd->avg_b, 1024);
565         cd->sim->filled = TRUE;
566
567         cd->similarity = TRUE;
568 }
569
570 gint cache_sim_data_filled(ImageSimilarityData *sd)
571 {
572         if (!sd) return FALSE;
573         return sd->filled;
574 }
575
576 /*
577  *-------------------------------------------------------------------
578  * cache path location utils
579  *-------------------------------------------------------------------
580  */
581
582 /* warning: this func modifies path string contents!, on fail it is set to fail point */
583 gint cache_ensure_dir_exists(gchar *path, mode_t mode)
584 {
585         if (!path) return FALSE;
586
587         if (!isdir(path))
588                 {
589                 gchar *p = path;
590                 while (p[0] != '\0')
591                         {
592                         p++;
593                         if (p[0] == '/' || p[0] == '\0')
594                                 {
595                                 gint end = TRUE;
596                                 if (p[0] != '\0')
597                                         {
598                                         p[0] = '\0';
599                                         end = FALSE;
600                                         }
601                                 if (!isdir(path))
602                                         {
603                                         if (debug) printf("creating sub dir:%s\n", path);
604                                         if (!mkdir_utf8(path, mode))
605                                                 {
606                                                 printf("create dir failed: %s\n", path);
607                                                 return FALSE;
608                                                 }
609                                         }
610                                 if (!end) p[0] = '/';
611                                 }
612                         }
613                 }
614         return TRUE;
615 }
616
617 static void cache_path_parts(CacheType type,
618                              const gchar **cache_rc, const gchar **cache_local, const gchar **cache_ext)
619 {
620         switch (type)
621                 {
622                 case CACHE_TYPE_THUMB:
623                         *cache_rc = GQVIEW_CACHE_RC_THUMB;
624                         *cache_local = GQVIEW_CACHE_LOCAL_THUMB;
625                         *cache_ext = GQVIEW_CACHE_EXT_THUMB;
626                         break;
627                 case CACHE_TYPE_SIM:
628                         *cache_rc = GQVIEW_CACHE_RC_THUMB;
629                         *cache_local = GQVIEW_CACHE_LOCAL_THUMB;
630                         *cache_ext = GQVIEW_CACHE_EXT_SIM;
631                         break;
632                 case CACHE_TYPE_METADATA:
633                         *cache_rc = GQVIEW_CACHE_RC_METADATA;
634                         *cache_local = GQVIEW_CACHE_LOCAL_METADATA;
635                         *cache_ext = GQVIEW_CACHE_EXT_METADATA;
636                         break;
637                 }
638 }
639
640 gchar *cache_get_location(CacheType type, const gchar *source, gint include_name, mode_t *mode)
641 {
642         gchar *path = NULL;
643         gchar *base;
644         gchar *name = NULL;
645         const gchar *cache_rc;
646         const gchar *cache_local;
647         const gchar *cache_ext;
648
649         if (!source) return NULL;
650
651         cache_path_parts(type, &cache_rc, &cache_local, &cache_ext);
652
653         base = remove_level_from_path(source);
654         if (include_name)
655                 {
656                 name = g_strconcat("/", filename_from_path(source), NULL);
657                 }
658         else
659                 {
660                 cache_ext = NULL;
661                 }
662
663         if (((type != CACHE_TYPE_METADATA && enable_thumb_dirs) ||
664              (type == CACHE_TYPE_METADATA && enable_metadata_dirs)) &&
665             access_file(base, W_OK))
666                 {
667                 path = g_strconcat(base, "/", cache_local, name, cache_ext, NULL);
668                 if (mode) *mode = 0775;
669                 }
670
671         if (!path)
672                 {
673                 path = g_strconcat(homedir(), "/", cache_rc, base, name, cache_ext, NULL);
674                 if (mode) *mode = 0755;
675                 }
676
677         g_free(base);
678         if (name) g_free(name);
679
680         return path;
681 }
682
683 gchar *cache_find_location(CacheType type, const gchar *source)
684 {
685         gchar *path;
686         const gchar *name;
687         gchar *base;
688         const gchar *cache_rc;
689         const gchar *cache_local;
690         const gchar *cache_ext;
691         gint prefer_local;
692
693         if (!source) return NULL;
694
695         cache_path_parts(type, &cache_rc, &cache_local, &cache_ext);
696
697         name = filename_from_path(source);
698         base = remove_level_from_path(source);
699
700         if (type == CACHE_TYPE_METADATA)
701                 {
702                 prefer_local = enable_metadata_dirs;
703                 }
704         else
705                 {
706                 prefer_local = enable_thumb_dirs;
707                 }
708
709         if (prefer_local)
710                 {
711                 path = g_strconcat(base, "/", cache_local, "/", name, cache_ext, NULL);
712                 }
713         else
714                 {
715                 path = g_strconcat(homedir(), "/", cache_rc, source, cache_ext, NULL);
716                 }
717
718         if (!isfile(path))
719                 {
720                 g_free(path);
721
722                 /* try the opposite method if not found */
723                 if (!prefer_local)
724                         {
725                         path = g_strconcat(base, "/", cache_local, "/", name, cache_ext, NULL);
726                         }
727                 else
728                         {
729                         path = g_strconcat(homedir(), "/", cache_rc, source, cache_ext, NULL);
730                         }
731
732                 if (!isfile(path))
733                         {
734                         g_free(path);
735                         path = NULL;
736                         }
737                 }
738
739         g_free(base);
740
741         return path;
742 }
743
744 gint cache_time_valid(const gchar *cache, const gchar *path)
745 {
746         struct stat cache_st;
747         struct stat path_st;
748         gchar *cachel;
749         gchar *pathl;
750         gint ret = FALSE;
751
752         if (!cache || !path) return FALSE;
753
754         cachel = path_from_utf8(cache);
755         pathl = path_from_utf8(path);
756
757         if (stat(cachel, &cache_st) == 0 &&
758             stat(pathl, &path_st) == 0)
759                 {
760                 if (cache_st.st_mtime == path_st.st_mtime)
761                         {
762                         ret = TRUE;
763                         }
764                 else if (cache_st.st_mtime > path_st.st_mtime)
765                         {
766                         struct utimbuf ut;
767
768                         ut.actime = ut.modtime = cache_st.st_mtime;
769                         if (utime(cachel, &ut) < 0 &&
770                             errno == EPERM)
771                                 {
772                                 if (debug) printf("cache permission workaround: %s\n", cachel);
773                                 ret = TRUE;
774                                 }
775                         }
776                 }
777
778         g_free(pathl);
779         g_free(cachel);
780
781         return ret;
782 }
783