updated copyright in source files
[geeqie.git] / src / filelist.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
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!
11  */
12
13
14 #include "main.h"
15 #include "filelist.h"
16
17 #include "cache.h"
18 #include "rcfile.h"
19 #include "secure_save.h"
20 #include "thumb_standard.h"
21 #include "ui_fileops.h"
22
23
24 /*
25  *-----------------------------------------------------------------------------
26  * file filtering
27  *-----------------------------------------------------------------------------
28  */
29
30 static GList *filter_list = NULL;
31 static GList *extension_list = NULL;
32 static GList *sidecar_ext_list = NULL;
33
34 static GList *file_class_extension_list[FILE_FORMAT_CLASSES];
35
36 static gint sidecar_file_priority(const gchar *path);
37
38
39 gint ishidden(const gchar *name)
40 {
41         if (name[0] != '.') return FALSE;
42         if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
43         return TRUE;
44 }
45
46 static FilterEntry *filter_entry_new(const gchar *key, const gchar *description,
47                                      const gchar *extensions, FileFormatClass file_class, gint enabled)
48 {
49         FilterEntry *fe;
50
51         fe = g_new0(FilterEntry, 1);
52         fe->key = g_strdup(key);
53         fe->description = g_strdup(description);
54         fe->extensions = g_strdup(extensions);
55         fe->enabled = enabled;
56         fe->file_class = file_class;
57
58         return fe;
59 }
60
61 static void filter_entry_free(FilterEntry *fe)
62 {
63         if (!fe) return;
64
65         g_free(fe->key);
66         g_free(fe->description);
67         g_free(fe->extensions);
68         g_free(fe);
69 }
70
71 GList *filter_get_list(void)
72 {
73         return filter_list;
74 }
75
76 void filter_remove_entry(FilterEntry *fe)
77 {
78         if (!g_list_find(filter_list, fe)) return;
79
80         filter_list = g_list_remove(filter_list, fe);
81         filter_entry_free(fe);
82 }
83
84 static gint filter_key_exists(const gchar *key)
85 {
86         GList *work;
87
88         if (!key) return FALSE;
89
90         work = filter_list;
91         while (work)
92                 {
93                 FilterEntry *fe = work->data;
94                 work = work->next;
95
96                 if (strcmp(fe->key, key) == 0) return TRUE;
97                 }
98
99         return FALSE;
100 }
101
102 void filter_add(const gchar *key, const gchar *description, const gchar *extensions, FileFormatClass file_class, gint enabled)
103 {
104         filter_list = g_list_append(filter_list, filter_entry_new(key, description, extensions, file_class, enabled));
105 }
106
107 void filter_add_unique(const gchar *description, const gchar *extensions, FileFormatClass file_class, gint enabled)
108 {
109         gchar *key;
110         gint n;
111
112         key = g_strdup("user0");
113         n = 1;
114         while (filter_key_exists(key))
115                 {
116                 g_free(key);
117                 if (n > 999) return;
118                 key = g_strdup_printf("user%d", n);
119                 n++;
120                 }
121
122         filter_add(key, description, extensions, file_class, enabled);
123         g_free(key);
124 }
125
126 static void filter_add_if_missing(const gchar *key, const gchar *description, const gchar *extensions, FileFormatClass file_class, gint enabled)
127 {
128         GList *work;
129
130         if (!key) return;
131
132         work = filter_list;
133         while (work)
134                 {
135                 FilterEntry *fe = work->data;
136                 work = work->next;
137                 if (fe->key && strcmp(fe->key, key) == 0)
138                         {
139                         if (fe->file_class == FORMAT_CLASS_UNKNOWN)
140                                 fe->file_class = file_class;    /* for compatibility */
141                         return;
142                         }
143                 }
144
145         filter_add(key, description, extensions, file_class, enabled);
146 }
147
148 void filter_reset(void)
149 {
150         GList *work;
151
152         work = filter_list;
153         while (work)
154                 {
155                 FilterEntry *fe = work->data;
156                 work = work->next;
157                 filter_entry_free(fe);
158                 }
159
160         g_list_free(filter_list);
161         filter_list = NULL;
162 }
163
164 void filter_add_defaults(void)
165 {
166         GSList *list, *work;
167
168         list = gdk_pixbuf_get_formats();
169         work = list;
170         while (work)
171                 {
172                 GdkPixbufFormat *format;
173                 gchar *name;
174                 gchar *desc;
175                 gchar **extensions;
176                 GString *filter = NULL;
177                 gint i;
178
179                 format = work->data;
180                 work = work->next;
181
182                 name = gdk_pixbuf_format_get_name(format);
183                 desc = gdk_pixbuf_format_get_description(format);
184                 extensions = gdk_pixbuf_format_get_extensions(format);
185
186                 i = 0;
187                 while (extensions[i])
188                         {
189                         if (!filter)
190                                 {
191                                 filter = g_string_new(".");
192                                 filter = g_string_append(filter, extensions[i]);
193                                 }
194                         else
195                                 {
196                                 filter = g_string_append(filter, ";.");
197                                 filter = g_string_append(filter, extensions[i]);
198                                 }
199                         i++;
200                         }
201
202                 if (debug) printf("loader reported [%s] [%s] [%s]\n", name, desc, filter->str);
203
204                 filter_add_if_missing(name, desc, filter->str, FORMAT_CLASS_IMAGE, TRUE);
205
206                 g_free(name);
207                 g_free(desc);
208                 g_strfreev(extensions);
209                 g_string_free(filter, TRUE);
210                 }
211         g_slist_free(list);
212
213         /* add defaults even if gdk-pixbuf does not have them, but disabled */
214         filter_add_if_missing("jpeg", "JPEG group", ".jpg;.jpeg;.jpe", FORMAT_CLASS_IMAGE, FALSE);
215         filter_add_if_missing("png", "Portable Network Graphic", ".png", FORMAT_CLASS_IMAGE, FALSE);
216         filter_add_if_missing("tiff", "Tiff", ".tif;.tiff", FORMAT_CLASS_IMAGE, FALSE);
217         filter_add_if_missing("pnm", "Packed Pixel formats", ".pbm;.pgm;.pnm;.ppm", FORMAT_CLASS_IMAGE, FALSE);
218         filter_add_if_missing("gif", "Graphics Interchange Format", ".gif", FORMAT_CLASS_IMAGE, FALSE);
219         filter_add_if_missing("xbm", "X bitmap", ".xbm", FORMAT_CLASS_IMAGE, FALSE);
220         filter_add_if_missing("xpm", "X pixmap", ".xpm", FORMAT_CLASS_IMAGE, FALSE);
221         filter_add_if_missing("bmp", "Bitmap", ".bmp", FORMAT_CLASS_IMAGE, FALSE);
222         filter_add_if_missing("ico", "Icon file", ".ico;.cur", FORMAT_CLASS_IMAGE, FALSE);
223         filter_add_if_missing("ras", "Raster", ".ras", FORMAT_CLASS_IMAGE, FALSE);
224         filter_add_if_missing("svg", "Scalable Vector Graphics", ".svg", FORMAT_CLASS_IMAGE, FALSE);
225
226         /* non-image files that might be desirable to show */
227         filter_add_if_missing("xmp", "XMP sidecar", ".xmp", FORMAT_CLASS_META, TRUE);
228
229         /* These are the raw camera formats with embedded jpeg/exif.
230          * (see format_raw.c and/or exiv2.cc)
231          */
232         filter_add_if_missing("arw", "Sony raw format", ".arw;.srf;.sr2", FORMAT_CLASS_RAWIMAGE, TRUE);
233         filter_add_if_missing("crw", "Canon raw format", ".crw;.cr2", FORMAT_CLASS_RAWIMAGE, TRUE);
234         filter_add_if_missing("kdc", "Kodak raw format", ".kdc;.dcr", FORMAT_CLASS_RAWIMAGE, TRUE);
235         filter_add_if_missing("raf", "Fujifilm raw format", ".raf", FORMAT_CLASS_RAWIMAGE, TRUE);
236         filter_add_if_missing("mef", "Mamiya raw format", ".mef;.mos", FORMAT_CLASS_RAWIMAGE, TRUE);
237         filter_add_if_missing("mrw", "Minolta raw format", ".mrw", FORMAT_CLASS_RAWIMAGE, TRUE);
238         filter_add_if_missing("nef", "Nikon raw format", ".nef", FORMAT_CLASS_RAWIMAGE, TRUE);
239         filter_add_if_missing("orf", "Olympus raw format", ".orf", FORMAT_CLASS_RAWIMAGE, TRUE);
240         filter_add_if_missing("pef", "Pentax or Samsung raw format", ".pef;.ptx", FORMAT_CLASS_RAWIMAGE, TRUE);
241         filter_add_if_missing("dng", "Adobe Digital Negative raw format", ".dng", FORMAT_CLASS_RAWIMAGE, TRUE);
242         filter_add_if_missing("x3f", "Sigma raw format", ".x3f", FORMAT_CLASS_RAWIMAGE, TRUE);
243         filter_add_if_missing("raw", "Panasonic raw format", ".raw", FORMAT_CLASS_RAWIMAGE, TRUE);
244         filter_add_if_missing("r3d", "Red raw format", ".r3d", FORMAT_CLASS_RAWIMAGE, TRUE);
245         filter_add_if_missing("3fr", "Hasselblad raw format", ".3fr", FORMAT_CLASS_RAWIMAGE, TRUE);
246         filter_add_if_missing("erf", "Epson raw format", ".erf", FORMAT_CLASS_RAWIMAGE, TRUE);
247 }
248
249 GList *filter_to_list(const gchar *extensions)
250 {
251         GList *list = NULL;
252         const gchar *p;
253
254         if (!extensions) return NULL;
255
256         p = extensions;
257         while (*p != '\0')
258                 {
259                 const gchar *b;
260                 gint l = 0;
261
262                 b = p;
263                 while (*p != '\0' && *p != ';')
264                         {
265                         p++;
266                         l++;
267                         }
268                 list = g_list_append(list, g_strndup(b, l));
269                 if (*p == ';') p++;
270                 }
271
272         return list;
273 }
274
275 void filter_rebuild(void)
276 {
277         GList *work;
278         gint i;
279
280         string_list_free(extension_list);
281         extension_list = NULL;
282
283         for (i = 0; i < FILE_FORMAT_CLASSES; i++)
284                 {
285                 string_list_free(file_class_extension_list[i]);
286                 file_class_extension_list[i] = NULL;
287                 }
288
289         work = filter_list;
290         while (work)
291                 {
292                 FilterEntry *fe;
293
294                 fe = work->data;
295                 work = work->next;
296
297                 if (fe->enabled)
298                         {
299                         GList *ext;
300
301                         ext = filter_to_list(fe->extensions);
302                         if (ext) extension_list = g_list_concat(extension_list, ext);
303
304                         if (fe->file_class >= 0 && fe->file_class < FILE_FORMAT_CLASSES)
305                                 {
306                                 ext = filter_to_list(fe->extensions);
307                                 if (ext) file_class_extension_list[fe->file_class] = g_list_concat(file_class_extension_list[fe->file_class], ext);
308                                 }
309                         else
310                                 {
311                                 printf("WARNING: invalid file class %d\n", fe->file_class);
312                                 }
313                         }
314                 }
315 }
316
317 gint filter_name_exists(const gchar *name)
318 {
319         GList *work;
320         gint ln;
321
322         if (!extension_list || options->file_filter.disable) return TRUE;
323
324         ln = strlen(name);
325         work = extension_list;
326         while (work)
327                 {
328                 gchar *filter = work->data;
329                 gint lf = strlen(filter);
330
331                 if (ln >= lf)
332                         {
333                         if (strncasecmp(name + ln - lf, filter, lf) == 0) return TRUE;
334                         }
335                 work = work->next;
336                 }
337
338         return FALSE;
339 }
340
341 gint filter_file_class(const gchar *name, FileFormatClass file_class)
342 {
343         GList *work;
344         gint ln;
345
346         if (file_class < 0 || file_class >= FILE_FORMAT_CLASSES)
347                 {
348                 printf("WARNING: invalid file class %d\n", file_class);
349                 return FALSE;
350                 }
351
352         ln = strlen(name);
353         work = file_class_extension_list[file_class];
354         while (work)
355                 {
356                 gchar *filter = work->data;
357                 gint lf = strlen(filter);
358
359                 if (ln >= lf)
360                         {
361                         if (strncasecmp(name + ln - lf, filter, lf) == 0) return TRUE;
362                         }
363                 work = work->next;
364                 }
365
366         return FALSE;
367 }
368
369 void filter_write_list(SecureSaveInfo *ssi)
370 {
371         GList *work;
372
373         work = filter_list;
374         while (work)
375                 {
376                 FilterEntry *fe = work->data;
377                 work = work->next;
378
379                 gchar *extensions = escquote_value(fe->extensions);
380                 gchar *description = escquote_value(fe->description);
381
382                 secure_fprintf(ssi, "file_filter.ext: \"%s%s\" %s %s %d\n",
383                                (fe->enabled) ? "" : "#",
384                                fe->key, extensions, description, fe->file_class);
385                 g_free(extensions);
386                 g_free(description);
387                 }
388 }
389
390 void filter_parse(const gchar *text)
391 {
392         const gchar *p;
393         gchar *key;
394         gchar *ext;
395         gchar *desc;
396         gint enabled = TRUE;
397         gint file_class;
398
399         if (!text || text[0] != '"') return;
400
401         key = quoted_value(text, &p);
402         if (!key) return;
403
404         ext = quoted_value(p, &p);
405         desc = quoted_value(p, &p);
406
407         file_class = strtol(p, NULL, 10);
408
409         if (file_class < 0 || file_class >= FILE_FORMAT_CLASSES) file_class = FORMAT_CLASS_UNKNOWN;
410
411         if (key && key[0] == '#')
412                 {
413                 gchar *tmp;
414                 tmp = g_strdup(key + 1);
415                 g_free(key);
416                 key = tmp;
417
418                 enabled = FALSE;
419                 }
420
421         if (key && strlen(key) > 0 && ext) filter_add(key, desc, ext, file_class, enabled);
422
423         g_free(key);
424         g_free(ext);
425         g_free(desc);
426 }
427
428 GList *path_list_filter(GList *list, gint is_dir_list)
429 {
430         GList *work;
431
432         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
433
434         work = list;
435         while (work)
436                 {
437                 gchar *name = work->data;
438                 const gchar *base;
439
440                 base = filename_from_path(name);
441
442                 if ((!options->file_filter.show_hidden_files && ishidden(base)) ||
443                     (!is_dir_list && !filter_name_exists(base)) ||
444                     (is_dir_list && base[0] == '.' && (strcmp(base, GQ_CACHE_LOCAL_THUMB) == 0 ||
445                                                        strcmp(base, GQ_CACHE_LOCAL_METADATA) == 0)) )
446                         {
447                         GList *link = work;
448                         work = work->next;
449                         list = g_list_remove_link(list, link);
450                         g_free(name);
451                         g_list_free(link);
452                         }
453                 else
454                         {
455                         work = work->next;
456                         }
457                 }
458
459         return list;
460 }
461
462
463 /*
464  *-----------------------------------------------------------------------------
465  * sidecar extension list
466  *-----------------------------------------------------------------------------
467  */
468
469 static GList *sidecar_ext_get_list(void)
470 {
471         return sidecar_ext_list;
472 }
473
474 void sidecar_ext_parse(const gchar *text, gint quoted)
475 {
476         GList *work;
477         gchar *value;
478
479         work = sidecar_ext_list;
480         while (work)
481                 {
482                 gchar *ext = work->data;
483                 work = work->next;
484                 g_free(ext);
485                 }
486         g_list_free(sidecar_ext_list);
487         sidecar_ext_list = NULL;
488
489         if (quoted)
490                 value = quoted_value(text, NULL);
491         else
492                 value = g_strdup(text);
493
494         if (value == NULL) return;
495
496         sidecar_ext_list = filter_to_list(value);
497
498         g_free(value);
499 }
500
501 void sidecar_ext_write(SecureSaveInfo *ssi)
502 {
503         secure_fprintf(ssi, "sidecar.ext: \"%s\"\n", sidecar_ext_to_string());
504 }
505
506 char *sidecar_ext_to_string()
507 {
508         GList *work;
509         GString *str = g_string_new("");
510
511         work = sidecar_ext_list;
512         while (work)
513                 {
514                 gchar *ext = work->data;
515                 work = work->next;
516                 g_string_append(str, ext);
517                 if (work) g_string_append(str, ";");
518                 }
519         return g_string_free(str, FALSE);
520 }
521
522 void sidecar_ext_add_defaults()
523 {
524         sidecar_ext_parse(".jpg;.cr2;.nef;.crw;.xmp", FALSE);
525 }
526
527 /*
528  *-----------------------------------------------------------------------------
529  * path list recursive
530  *-----------------------------------------------------------------------------
531  */
532
533 static gint path_list_sort_cb(gconstpointer a, gconstpointer b)
534 {
535         return CASE_SORT((gchar *)a, (gchar *)b);
536 }
537
538 GList *path_list_sort(GList *list)
539 {
540         return g_list_sort(list, path_list_sort_cb);
541 }
542
543 static void path_list_recursive_append(GList **list, GList *dirs)
544 {
545         GList *work;
546
547         work = dirs;
548         while (work)
549                 {
550                 const gchar *path = work->data;
551                 GList *f = NULL;
552                 GList *d = NULL;
553
554                 if (path_list(path, &f, &d))
555                         {
556                         f = path_list_filter(f, FALSE);
557                         f = path_list_sort(f);
558                         *list = g_list_concat(*list, f);
559
560                         d = path_list_filter(d, TRUE);
561                         d = path_list_sort(d);
562                         path_list_recursive_append(list, d);
563                         path_list_free(d);
564                         }
565
566                 work = work->next;
567                 }
568 }
569
570 GList *path_list_recursive(const gchar *path)
571 {
572         GList *list = NULL;
573         GList *d = NULL;
574
575         if (!path_list(path, &list, &d)) return NULL;
576         list = path_list_filter(list, FALSE);
577         list = path_list_sort(list);
578
579         d = path_list_filter(d, TRUE);
580         d = path_list_sort(d);
581         path_list_recursive_append(&list, d);
582         path_list_free(d);
583
584         return list;
585 }
586
587 /*
588  *-----------------------------------------------------------------------------
589  * text conversion utils
590  *-----------------------------------------------------------------------------
591  */
592
593 gchar *text_from_size(gint64 size)
594 {
595         gchar *a, *b;
596         gchar *s, *d;
597         gint l, n, i;
598
599         /* what I would like to use is printf("%'d", size)
600          * BUT: not supported on every libc :(
601          */
602         if (size > G_MAXUINT)
603                 {
604                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
605                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
606                 }
607         else
608                 {
609                 a = g_strdup_printf("%d", (guint)size);
610                 }
611         l = strlen(a);
612         n = (l - 1)/ 3;
613         if (n < 1) return a;
614
615         b = g_new(gchar, l + n + 1);
616
617         s = a;
618         d = b;
619         i = l - n * 3;
620         while (*s != '\0')
621                 {
622                 if (i < 1)
623                         {
624                         i = 3;
625                         *d = ',';
626                         d++;
627                         }
628
629                 *d = *s;
630                 s++;
631                 d++;
632                 i--;
633                 }
634         *d = '\0';
635
636         g_free(a);
637         return b;
638 }
639
640 gchar *text_from_size_abrev(gint64 size)
641 {
642         if (size < (gint64)1024)
643                 {
644                 return g_strdup_printf(_("%d bytes"), (gint)size);
645                 }
646         if (size < (gint64)1048576)
647                 {
648                 return g_strdup_printf(_("%.1f K"), (double)size / 1024.0);
649                 }
650         if (size < (gint64)1073741824)
651                 {
652                 return g_strdup_printf(_("%.1f MB"), (double)size / 1048576.0);
653                 }
654
655         /* to avoid overflowing the double, do division in two steps */
656         size /= 1048576;
657         return g_strdup_printf(_("%.1f GB"), (double)size / 1024.0);
658 }
659
660 /* note: returned string is valid until next call to text_from_time() */
661 const gchar *text_from_time(time_t t)
662 {
663         static gchar *ret = NULL;
664         gchar buf[128];
665         gint buflen;
666         struct tm *btime;
667         GError *error = NULL;
668
669         btime = localtime(&t);
670
671         /* the %x warning about 2 digit years is not an error */
672         buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
673         if (buflen < 1) return "";
674
675         g_free(ret);
676         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
677         if (error)
678                 {
679                 printf("Error converting locale strftime to UTF-8: %s\n", error->message);
680                 g_error_free(error);
681                 return "";
682                 }
683
684         return ret;
685 }
686
687 /*
688  *-----------------------------------------------------------------------------
689  * file info struct
690  *-----------------------------------------------------------------------------
691  */
692
693 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
694 static void file_data_check_sidecars(FileData *fd);
695 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
696
697
698 static void file_data_set_path(FileData *fd, const gchar *path)
699 {
700
701         if (strcmp(path, "/") == 0)
702                 {
703                 fd->path = g_strdup(path);
704                 fd->name = fd->path;
705                 fd->extension = fd->name + 1;
706                 return;
707                 }
708
709         fd->path = g_strdup(path);
710         fd->name = filename_from_path(fd->path);
711
712         if (strcmp(fd->name, "..") == 0)
713                 {
714                 gchar *dir = remove_level_from_path(path);
715                 g_free(fd->path);
716                 fd->path = remove_level_from_path(dir);
717                 g_free(dir);
718                 fd->name = "..";
719                 fd->extension = fd->name + 2;
720                 return;
721                 }
722         else if (strcmp(fd->name, ".") == 0)
723                 {
724                 g_free(fd->path);
725                 fd->path = remove_level_from_path(path);
726                 fd->name = ".";
727                 fd->extension = fd->name + 1;
728                 return;
729                 }
730
731         fd->extension = extension_from_path(fd->path);
732         if (fd->extension == NULL)
733                 fd->extension = fd->name + strlen(fd->name);
734 }
735
736 static void file_data_check_changed_files(FileData *fd, struct stat *st)
737 {
738         GList *work;
739         if (fd->size != st->st_size ||
740             fd->date != st->st_mtime)
741                 {
742                 fd->size = st->st_size;
743                 fd->date = st->st_mtime;
744                 if (fd->pixbuf) g_object_unref(fd->pixbuf);
745                 fd->pixbuf = NULL;
746                 }
747
748         work = fd->sidecar_files;
749         while (work)
750                 {
751                 FileData *sfd = work->data;
752                 struct stat st;
753
754                 if (!stat_utf8(sfd->path, &st))
755                         {
756                         file_data_disconnect_sidecar_file(fd, sfd);
757                         }
758
759                 file_data_check_changed_files(sfd, &st);
760                 work = work->next;
761                 }
762 }
763
764 static GHashTable *file_data_pool = NULL;
765
766 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
767 {
768         FileData *fd;
769
770         if (debug) printf("file_data_new: '%s' %d\n", path_utf8, check_sidecars);
771
772         if (!file_data_pool)
773                 file_data_pool = g_hash_table_new (g_str_hash, g_str_equal);
774
775         fd = g_hash_table_lookup(file_data_pool, path_utf8);
776         if (fd)
777                 {
778                 file_data_check_changed_files(fd, st);
779                 if (debug) printf("file_data_pool hit: '%s'\n", fd->path);
780                 return file_data_ref(fd);
781                 }
782
783         fd = g_new0(FileData, 1);
784
785         file_data_set_path(fd, path_utf8);
786
787         fd->original_path = g_strdup(path_utf8);
788         fd->size = st->st_size;
789         fd->date = st->st_mtime;
790         fd->pixbuf = NULL;
791         fd->sidecar_files = NULL;
792         fd->ref = 1;
793         fd->magick = 0x12345678;
794
795         g_hash_table_insert(file_data_pool, fd->original_path, fd);
796
797         if (check_sidecars && sidecar_file_priority(fd->extension))
798                 file_data_check_sidecars(fd);
799         return fd;
800 }
801
802 static void file_data_check_sidecars(FileData *fd)
803 {
804         int base_len = fd->extension - fd->path;
805         GString *fname = g_string_new_len(fd->path, base_len);
806         FileData *parent_fd = NULL;
807         GList *work = sidecar_ext_get_list();
808         while (work)
809                 {
810                 /* check for possible sidecar files;
811                    the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
812                    they have fd->ref set to 0 and file_data unref must chack and free them all together
813                    (using fd->ref would cause loops and leaks)
814                 */
815
816                 FileData *new_fd;
817
818                 gchar *ext = work->data;
819                 work = work->next;
820
821                 if (strcmp(ext, fd->extension) == 0)
822                         {
823                         new_fd = fd; /* processing the original file */
824                         }
825                 else
826                         {
827                         struct stat nst;
828                         g_string_truncate(fname, base_len);
829                         g_string_append(fname, ext);
830
831                         if (!stat_utf8(fname->str, &nst))
832                                 continue;
833
834                         new_fd = file_data_new(fname->str, &nst, FALSE);
835                         new_fd->ref--; /* do not use ref here */
836                         }
837
838                 if (!parent_fd)
839                         parent_fd = new_fd; /* parent is the one with the highest prio, found first */
840                 else
841                         file_data_merge_sidecar_files(parent_fd, new_fd);
842                 }
843         g_string_free(fname, TRUE);
844 }
845
846
847 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
848 {
849         gchar *path_utf8 = path_to_utf8(path);
850         FileData *ret = file_data_new(path_utf8, st, check_sidecars);
851         g_free(path_utf8);
852         return ret;
853 }
854
855 FileData *file_data_new_simple(const gchar *path_utf8)
856 {
857         struct stat st;
858
859         if (!stat_utf8(path_utf8, &st))
860                 {
861                 st.st_size = 0;
862                 st.st_mtime = 0;
863                 }
864
865         return file_data_new(path_utf8, &st, TRUE);
866 }
867
868 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
869 {
870         sfd->parent = target;
871         if(!g_list_find(target->sidecar_files, sfd))
872                 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
873         return target;
874 }
875
876
877 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
878 {
879         GList *work;
880         file_data_add_sidecar_file(target, source);
881
882         work = source->sidecar_files;
883         while (work)
884                 {
885                 FileData *sfd = work->data;
886                 file_data_add_sidecar_file(target, sfd);
887                 work = work->next;
888                 }
889
890         g_list_free(source->sidecar_files);
891         source->sidecar_files = NULL;
892
893         target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
894         return target;
895 }
896
897
898
899 FileData *file_data_ref(FileData *fd)
900 {
901         if (fd == NULL) return NULL;
902
903 //      return g_memdup(fd, sizeof(FileData));
904         g_assert(fd->magick == 0x12345678);
905         fd->ref++;
906         return fd;
907 }
908
909 static void file_data_free(FileData *fd)
910 {
911         g_assert(fd->magick == 0x12345678);
912         g_assert(fd->ref == 0);
913
914         g_hash_table_remove(file_data_pool, fd->original_path);
915
916         g_free(fd->path);
917         g_free(fd->original_path);
918         if (fd->pixbuf) g_object_unref(fd->pixbuf);
919
920
921         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
922
923         file_data_change_info_free(NULL, fd);
924         g_free(fd);
925 }
926
927 void file_data_unref(FileData *fd)
928 {
929         if (fd == NULL) return;
930         g_assert(fd->magick == 0x12345678);
931
932         fd->ref--;
933         if (debug) printf("file_data_unref (%d): '%s'\n", fd->ref, fd->path);
934
935         if (fd->ref == 0)
936                 {
937                 FileData *parent = fd->parent ? fd->parent : fd;
938
939                 GList *work;
940
941                 if (parent->ref > 0)
942                         return;
943
944                 work = parent->sidecar_files;
945                 while (work)
946                         {
947                         FileData *sfd = work->data;
948                         if (sfd->ref > 0)
949                                 return;
950                         work = work->next;
951                         }
952
953                 /* none of parent/children is referenced, we can free everything */
954
955                 if (debug) printf("file_data_unref: deleting '%s', parent '%s'\n", fd->path, parent->path);
956
957                 work = parent->sidecar_files;
958                 while (work)
959                         {
960                         FileData *sfd = work->data;
961                         file_data_free(sfd);
962                         work = work->next;
963                         }
964
965                 g_list_free(parent->sidecar_files);
966                 parent->sidecar_files = NULL;
967
968                 file_data_free(parent);
969
970                 }
971 }
972
973 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
974 {
975         sfd->parent = target;
976         g_assert(g_list_find(target->sidecar_files, sfd));
977
978         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
979         sfd->parent = NULL;
980
981         if (sfd->ref == 0) {
982                 file_data_free(sfd);
983                 return NULL;
984         }
985
986         return sfd;
987 }
988
989 /* compare name without extension */
990 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
991 {
992         size_t len1 = fd1->extension - fd1->name;
993         size_t len2 = fd2->extension - fd2->name;
994
995         if (len1 < len2) return -1;
996         if (len1 > len2) return 1;
997
998         return strncmp(fd1->name, fd2->name, len1);
999 }
1000
1001 void file_data_do_change(FileData *fd)
1002 {
1003 //FIXME sidecars
1004         g_assert(fd->change);
1005         g_free(fd->path);
1006         g_hash_table_remove(file_data_pool, fd->original_path);
1007         g_free(fd->original_path);
1008         file_data_set_path(fd, fd->change->dest);
1009         fd->original_path = g_strdup(fd->change->dest);
1010         g_hash_table_insert(file_data_pool, fd->original_path, fd);
1011
1012 }
1013
1014 gboolean file_data_add_change_info(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1015 {
1016
1017         FileDataChangeInfo *fdci;
1018
1019         if (fd->change) return FALSE;
1020
1021         fdci = g_new0(FileDataChangeInfo, 1);
1022
1023         fdci->type = type;
1024
1025         if (src)
1026                 fdci->source = g_strdup(src);
1027         else
1028                 fdci->source = g_strdup(fd->path);
1029
1030         if (dest)
1031                 fdci->dest = g_strdup(dest);
1032
1033         fd->change = fdci;
1034         return TRUE;
1035 }
1036
1037 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1038 {
1039         if (!fdci && fd)
1040                 fdci = fd->change;
1041
1042         if (!fdci)
1043                 return;
1044
1045         g_free(fdci->source);
1046         g_free(fdci->dest);
1047
1048         g_free(fdci);
1049
1050         if (fd)
1051                 fd->change = NULL;
1052 }
1053
1054
1055
1056
1057 /*
1058  *-----------------------------------------------------------------------------
1059  * sidecar file info struct
1060  *-----------------------------------------------------------------------------
1061  */
1062
1063
1064
1065 static gint sidecar_file_priority(const gchar *path)
1066 {
1067         const char *extension = extension_from_path(path);
1068         int i = 1;
1069         GList *work;
1070         if (extension == NULL)
1071                 return 0;
1072
1073         work = sidecar_ext_get_list();
1074
1075         while (work) {
1076                 gchar *ext = work->data;
1077                 work = work->next;
1078                 if (strcmp(extension, ext) == 0) return i;
1079                 i++;
1080         }
1081         return 0;
1082 }
1083
1084 gchar *sidecar_file_data_list_to_string(FileData *fd)
1085 {
1086         GList *work;
1087         GString *result = g_string_new("");
1088
1089         work = fd->sidecar_files;
1090         while (work)
1091                 {
1092                 FileData *sfd = work->data;
1093                 result = g_string_append(result, "+ ");
1094                 result = g_string_append(result, sfd->extension);
1095                 work = work->next;
1096                 if (work) result = g_string_append_c(result, ' ');
1097                 }
1098
1099         return g_string_free(result, FALSE);
1100 }
1101
1102 /*
1103  *-----------------------------------------------------------------------------
1104  * load file list
1105  *-----------------------------------------------------------------------------
1106  */
1107
1108 static SortType filelist_sort_method = SORT_NONE;
1109 static gint filelist_sort_ascend = TRUE;
1110
1111
1112 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1113 {
1114         if (!filelist_sort_ascend)
1115                 {
1116                 FileData *tmp = fa;
1117                 fa = fb;
1118                 fb = tmp;
1119                 }
1120
1121         switch (filelist_sort_method)
1122                 {
1123                 case SORT_SIZE:
1124                         if (fa->size < fb->size) return -1;
1125                         if (fa->size > fb->size) return 1;
1126                         return CASE_SORT(fa->name, fb->name); /* fall back to name */
1127                         break;
1128                 case SORT_TIME:
1129                         if (fa->date < fb->date) return -1;
1130                         if (fa->date > fb->date) return 1;
1131                         return CASE_SORT(fa->name, fb->name); /* fall back to name */
1132                         break;
1133 #ifdef HAVE_STRVERSCMP
1134                 case SORT_NUMBER:
1135                         return strverscmp(fa->name, fb->name);
1136                         break;
1137 #endif
1138                 case SORT_NAME:
1139                 default:
1140                         return CASE_SORT(fa->name, fb->name);
1141                         break;
1142                 }
1143 }
1144
1145 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
1146 {
1147         filelist_sort_method = method;
1148         filelist_sort_ascend = ascend;
1149         return filelist_sort_compare_filedata(fa, fb);
1150 }
1151
1152 static gint filelist_sort_file_cb(void *a, void *b)
1153 {
1154         return filelist_sort_compare_filedata(a, b);
1155 }
1156
1157 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
1158 {
1159         filelist_sort_method = method;
1160         filelist_sort_ascend = ascend;
1161         return g_list_sort(list, cb);
1162 }
1163
1164 GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
1165 {
1166         filelist_sort_method = method;
1167         filelist_sort_ascend = ascend;
1168         return g_list_insert_sorted(list, data, cb);
1169 }
1170
1171 GList *filelist_sort(GList *list, SortType method, gint ascend)
1172 {
1173         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1174 }
1175
1176 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
1177 {
1178         return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1179 }
1180
1181
1182 static GList *filelist_filter_out_sidecars(GList *flist)
1183 {
1184         GList *work = flist;
1185         GList *flist_filtered = NULL;
1186
1187         while (work)
1188                 {
1189                 FileData *fd = work->data;
1190                 work = work->next;
1191                 if (fd->parent) /* remove fd's that are children */
1192                         file_data_unref(fd);
1193                 else
1194                         flist_filtered = g_list_prepend(flist_filtered, fd);
1195                 }
1196         g_list_free(flist);
1197         return flist_filtered;
1198 }
1199
1200 static gint filelist_read_real(const gchar *path, GList **files, GList **dirs, gint follow_symlinks)
1201 {
1202         DIR *dp;
1203         struct dirent *dir;
1204         struct stat ent_sbuf;
1205         gchar *pathl;
1206         GList *dlist;
1207         GList *flist;
1208
1209         dlist = NULL;
1210         flist = NULL;
1211
1212         pathl = path_from_utf8(path);
1213         if (!pathl || (dp = opendir(pathl)) == NULL)
1214                 {
1215                 g_free(pathl);
1216                 if (files) *files = NULL;
1217                 if (dirs) *dirs = NULL;
1218                 return FALSE;
1219                 }
1220
1221         /* root dir fix */
1222         if (pathl[0] == '/' && pathl[1] == '\0')
1223                 {
1224                 g_free(pathl);
1225                 pathl = g_strdup("");
1226                 }
1227
1228         while ((dir = readdir(dp)) != NULL)
1229                 {
1230                 gchar *name = dir->d_name;
1231                 if (options->file_filter.show_hidden_files || !ishidden(name))
1232                         {
1233                         gchar *filepath = g_strconcat(pathl, "/", name, NULL);
1234                         if ((follow_symlinks ?
1235                                 stat(filepath, &ent_sbuf) :
1236                                 lstat(filepath, &ent_sbuf)) >= 0)
1237                                 {
1238                                 if (S_ISDIR(ent_sbuf.st_mode))
1239                                         {
1240                                         /* we ignore the .thumbnails dir for cleanliness */
1241                                         if ((dirs) &&
1242                                             !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1243                                             strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1244                                             strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1245                                             strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1246                                                 {
1247                                                 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
1248                                                 }
1249                                         }
1250                                 else
1251                                         {
1252                                         if ((files) && filter_name_exists(name))
1253                                                 {
1254                                                 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1255                                                 }
1256                                         }
1257                                 }
1258                         g_free(filepath);
1259                         }
1260                 }
1261
1262         closedir(dp);
1263
1264         g_free(pathl);
1265
1266         flist = filelist_filter_out_sidecars(flist);
1267
1268         if (dirs) *dirs = dlist;
1269         if (files) *files = flist;
1270
1271         return TRUE;
1272 }
1273
1274 gint filelist_read(const gchar *path, GList **files, GList **dirs)
1275 {
1276         return filelist_read_real(path, files, dirs, TRUE);
1277 }
1278
1279 gint filelist_read_lstat(const gchar *path, GList **files, GList **dirs)
1280 {
1281         return filelist_read_real(path, files, dirs, FALSE);
1282 }
1283
1284 void filelist_free(GList *list)
1285 {
1286         GList *work;
1287
1288         work = list;
1289         while (work)
1290                 {
1291                 file_data_unref((FileData *)work->data);
1292                 work = work->next;
1293                 }
1294
1295         g_list_free(list);
1296 }
1297
1298
1299 GList *filelist_copy(GList *list)
1300 {
1301         GList *new_list = NULL;
1302         GList *work;
1303
1304         work = list;
1305         while (work)
1306                 {
1307                 FileData *fd;
1308
1309                 fd = work->data;
1310                 work = work->next;
1311
1312                 new_list = g_list_prepend(new_list, file_data_ref(fd));
1313                 }
1314
1315         return g_list_reverse(new_list);
1316 }
1317
1318 GList *filelist_from_path_list(GList *list)
1319 {
1320         GList *new_list = NULL;
1321         GList *work;
1322
1323         work = list;
1324         while (work)
1325                 {
1326                 gchar *path;
1327
1328                 path = work->data;
1329                 work = work->next;
1330
1331                 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1332                 }
1333
1334         return g_list_reverse(new_list);
1335 }
1336
1337 GList *filelist_to_path_list(GList *list)
1338 {
1339         GList *new_list = NULL;
1340         GList *work;
1341
1342         work = list;
1343         while (work)
1344                 {
1345                 FileData *fd;
1346
1347                 fd = work->data;
1348                 work = work->next;
1349
1350                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1351                 }
1352
1353         return g_list_reverse(new_list);
1354 }
1355
1356 GList *filelist_filter(GList *list, gint is_dir_list)
1357 {
1358         GList *work;
1359
1360         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1361
1362         work = list;
1363         while (work)
1364                 {
1365                 FileData *fd = (FileData *)(work->data);
1366                 const gchar *name = fd->name;
1367
1368                 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
1369                     (!is_dir_list && !filter_name_exists(name)) ||
1370                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1371                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1372                         {
1373                         GList *link = work;
1374                         work = work->next;
1375                         list = g_list_remove_link(list, link);
1376                         file_data_unref(fd);
1377                         g_list_free(link);
1378                         }
1379                 else
1380                         {
1381                         work = work->next;
1382                         }
1383                 }
1384
1385         return list;
1386 }
1387
1388 /*
1389  *-----------------------------------------------------------------------------
1390  * filelist recursive
1391  *-----------------------------------------------------------------------------
1392  */
1393
1394 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1395 {
1396         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1397 }
1398
1399 GList *filelist_sort_path(GList *list)
1400 {
1401         return g_list_sort(list, filelist_sort_path_cb);
1402 }
1403
1404 static void filelist_recursive_append(GList **list, GList *dirs)
1405 {
1406         GList *work;
1407
1408         work = dirs;
1409         while (work)
1410                 {
1411                 FileData *fd = (FileData *)(work->data);
1412                 const gchar *path = fd->path;
1413                 GList *f = NULL;
1414                 GList *d = NULL;
1415
1416                 if (filelist_read(path, &f, &d))
1417                         {
1418                         f = filelist_filter(f, FALSE);
1419                         f = filelist_sort_path(f);
1420                         *list = g_list_concat(*list, f);
1421
1422                         d = filelist_filter(d, TRUE);
1423                         d = filelist_sort_path(d);
1424                         filelist_recursive_append(list, d);
1425                         filelist_free(d);
1426                         }
1427
1428                 work = work->next;
1429                 }
1430 }
1431
1432 GList *filelist_recursive(const gchar *path)
1433 {
1434         GList *list = NULL;
1435         GList *d = NULL;
1436
1437         if (!filelist_read(path, &list, &d)) return NULL;
1438         list = filelist_filter(list, FALSE);
1439         list = filelist_sort_path(list);
1440
1441         d = filelist_filter(d, TRUE);
1442         d = filelist_sort_path(d);
1443         filelist_recursive_append(&list, d);
1444         filelist_free(d);
1445
1446         return list;
1447 }