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