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