fixed safe delete
[geeqie.git] / src / filedata.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "filedata.h"
16
17 #include "filefilter.h"
18 #include "cache.h"
19 #include "thumb_standard.h"
20 #include "ui_fileops.h"
21 #include "metadata.h"
22 #include "trash.h"
23
24
25 static GHashTable *file_data_pool = NULL;
26 static GHashTable *file_data_planned_change_hash = NULL;
27
28 static gint sidecar_file_priority(const gchar *path);
29
30
31 /*
32  *-----------------------------------------------------------------------------
33  * text conversion utils
34  *-----------------------------------------------------------------------------
35  */
36
37 gchar *text_from_size(gint64 size)
38 {
39         gchar *a, *b;
40         gchar *s, *d;
41         gint l, n, i;
42
43         /* what I would like to use is printf("%'d", size)
44          * BUT: not supported on every libc :(
45          */
46         if (size > G_MAXUINT)
47                 {
48                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
49                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
50                 }
51         else
52                 {
53                 a = g_strdup_printf("%d", (guint)size);
54                 }
55         l = strlen(a);
56         n = (l - 1)/ 3;
57         if (n < 1) return a;
58
59         b = g_new(gchar, l + n + 1);
60
61         s = a;
62         d = b;
63         i = l - n * 3;
64         while (*s != '\0')
65                 {
66                 if (i < 1)
67                         {
68                         i = 3;
69                         *d = ',';
70                         d++;
71                         }
72
73                 *d = *s;
74                 s++;
75                 d++;
76                 i--;
77                 }
78         *d = '\0';
79
80         g_free(a);
81         return b;
82 }
83
84 gchar *text_from_size_abrev(gint64 size)
85 {
86         if (size < (gint64)1024)
87                 {
88                 return g_strdup_printf(_("%d bytes"), (gint)size);
89                 }
90         if (size < (gint64)1048576)
91                 {
92                 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
93                 }
94         if (size < (gint64)1073741824)
95                 {
96                 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
97                 }
98
99         /* to avoid overflowing the gdouble, do division in two steps */
100         size /= 1048576;
101         return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
102 }
103
104 /* note: returned string is valid until next call to text_from_time() */
105 const gchar *text_from_time(time_t t)
106 {
107         static gchar *ret = NULL;
108         gchar buf[128];
109         gint buflen;
110         struct tm *btime;
111         GError *error = NULL;
112
113         btime = localtime(&t);
114
115         /* the %x warning about 2 digit years is not an error */
116         buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
117         if (buflen < 1) return "";
118
119         g_free(ret);
120         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
121         if (error)
122                 {
123                 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
124                 g_error_free(error);
125                 return "";
126                 }
127
128         return ret;
129 }
130
131 /*
132  *-----------------------------------------------------------------------------
133  * file info struct
134  *-----------------------------------------------------------------------------
135  */
136
137 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
138 static void file_data_check_sidecars(FileData *fd);
139 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
140
141
142 void file_data_increment_version(FileData *fd)
143 {
144         fd->version++;
145         if (fd->parent) fd->parent->version++;
146 }
147
148 static void file_data_set_collate_keys(FileData *fd)
149 {
150         gchar *caseless_name;
151
152         caseless_name = g_utf8_casefold(fd->name, -1);
153
154         g_free(fd->collate_key_name);
155         g_free(fd->collate_key_name_nocase);
156
157 #if GLIB_CHECK_VERSION(2, 8, 0)
158         fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
159         fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
160 #else
161         fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
162         fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
163 #endif
164         g_free(caseless_name);
165 }
166
167 static void file_data_set_path(FileData *fd, const gchar *path)
168 {
169         g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
170         g_assert(file_data_pool);
171
172         g_free(fd->path);
173
174         if (fd->original_path)
175                 {
176                 g_hash_table_remove(file_data_pool, fd->original_path);
177                 g_free(fd->original_path);
178                 }
179
180         g_assert(!g_hash_table_lookup(file_data_pool, path));
181
182         fd->original_path = g_strdup(path);
183         g_hash_table_insert(file_data_pool, fd->original_path, fd);
184
185         if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
186                 {
187                 fd->path = g_strdup(path);
188                 fd->name = fd->path;
189                 fd->extension = fd->name + 1;
190                 file_data_set_collate_keys(fd);
191                 return;
192                 }
193
194         fd->path = g_strdup(path);
195         fd->name = filename_from_path(fd->path);
196
197         if (strcmp(fd->name, "..") == 0)
198                 {
199                 gchar *dir = remove_level_from_path(path);
200                 g_free(fd->path);
201                 fd->path = remove_level_from_path(dir);
202                 g_free(dir);
203                 fd->name = "..";
204                 fd->extension = fd->name + 2;
205                 file_data_set_collate_keys(fd);
206                 return;
207                 }
208         else if (strcmp(fd->name, ".") == 0)
209                 {
210                 g_free(fd->path);
211                 fd->path = remove_level_from_path(path);
212                 fd->name = ".";
213                 fd->extension = fd->name + 1;
214                 file_data_set_collate_keys(fd);
215                 return;
216                 }
217
218         fd->extension = extension_from_path(fd->path);
219         if (fd->extension == NULL)
220                 fd->extension = fd->name + strlen(fd->name);
221
222         file_data_set_collate_keys(fd);
223 }
224
225 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
226 {
227         gboolean ret = FALSE;
228         GList *work;
229         
230         if (fd->size != st->st_size ||
231             fd->date != st->st_mtime)
232                 {
233                 fd->size = st->st_size;
234                 fd->date = st->st_mtime;
235                 fd->mode = st->st_mode;
236                 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
237                 fd->thumb_pixbuf = NULL;
238                 file_data_increment_version(fd);
239                 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
240                 ret = TRUE;
241                 }
242
243         work = fd->sidecar_files;
244         while (work)
245                 {
246                 FileData *sfd = work->data;
247                 struct stat st;
248                 work = work->next;
249
250                 if (!stat_utf8(sfd->path, &st))
251                         {
252                         fd->size = 0;
253                         fd->date = 0;
254                         file_data_disconnect_sidecar_file(fd, sfd);
255                         ret = TRUE;
256                         continue;
257                         }
258
259                 ret |= file_data_check_changed_files_recursive(sfd, &st);
260                 }
261         return ret;
262 }
263
264
265 gboolean file_data_check_changed_files(FileData *fd)
266 {
267         gboolean ret = FALSE;
268         struct stat st;
269         
270         if (fd->parent) fd = fd->parent;
271
272         if (!stat_utf8(fd->path, &st))
273                 {
274                 GList *work;
275                 FileData *sfd = NULL;
276
277                 /* parent is missing, we have to rebuild whole group */
278                 ret = TRUE;
279                 fd->size = 0;
280                 fd->date = 0;
281                 
282                 work = fd->sidecar_files;
283                 while (work)
284                         {
285                         sfd = work->data;
286                         work = work->next;
287                 
288                         file_data_disconnect_sidecar_file(fd, sfd);
289                         }
290                 if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
291                 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
292                 }
293         else
294                 {
295                 ret |= file_data_check_changed_files_recursive(fd, &st);
296                 }
297
298         return ret;
299 }
300
301 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
302 {
303         FileData *fd;
304
305         DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
306
307         if (!file_data_pool)
308                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
309
310         fd = g_hash_table_lookup(file_data_pool, path_utf8);
311         if (fd)
312                 {
313                 file_data_ref(fd);
314                 }
315                 
316         if (!fd && file_data_planned_change_hash)
317                 {
318                 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
319                 if (fd)
320                         {
321                         DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
322                         file_data_ref(fd);
323                         file_data_apply_ci(fd);
324                         }
325                 }
326                 
327         if (fd)
328                 {
329                 gboolean changed;
330                 
331                 if (fd->parent)
332                         changed = file_data_check_changed_files(fd);
333                 else
334                         changed = file_data_check_changed_files_recursive(fd, st);
335                 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
336                         file_data_check_sidecars(fd);
337                 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
338                 
339                 return fd;
340                 }
341
342         fd = g_new0(FileData, 1);
343         
344         fd->path = NULL;
345         fd->name = NULL;
346         fd->collate_key_name = NULL;
347         fd->collate_key_name_nocase = NULL;
348         fd->original_path = NULL;
349
350         fd->size = st->st_size;
351         fd->date = st->st_mtime;
352         fd->mode = st->st_mode;
353         fd->thumb_pixbuf = NULL;
354         fd->sidecar_files = NULL;
355         fd->ref = 1;
356         fd->magick = 0x12345678;
357
358         file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
359
360         if (check_sidecars)
361                 file_data_check_sidecars(fd);
362
363         return fd;
364 }
365
366 static void file_data_check_sidecars(FileData *fd)
367 {
368         gint base_len;
369         GString *fname;
370         FileData *parent_fd = NULL;
371         GList *work;
372
373         if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
374                 return;
375
376         base_len = fd->extension - fd->path;
377         fname = g_string_new_len(fd->path, base_len);
378         work = sidecar_ext_get_list();
379
380         while (work)
381                 {
382                 /* check for possible sidecar files;
383                    the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
384                    they have fd->ref set to 0 and file_data unref must chack and free them all together
385                    (using fd->ref would cause loops and leaks)
386                 */
387
388                 FileData *new_fd;
389                 gchar *ext = work->data;
390
391                 work = work->next;
392
393                 if (strcmp(ext, fd->extension) == 0)
394                         {
395                         new_fd = fd; /* processing the original file */
396                         }
397                 else
398                         {
399                         struct stat nst;
400                         g_string_truncate(fname, base_len);
401                         g_string_append(fname, ext);
402
403                         if (!stat_utf8(fname->str, &nst))
404                                 continue;
405
406                         new_fd = file_data_new(fname->str, &nst, FALSE);
407                         
408                         if (new_fd->disable_grouping)
409                                 {
410                                 file_data_unref(new_fd);
411                                 continue;
412                                 }
413                         
414                         new_fd->ref--; /* do not use ref here */
415                         }
416
417                 if (!parent_fd)
418                         parent_fd = new_fd; /* parent is the one with the highest prio, found first */
419                 else
420                         file_data_merge_sidecar_files(parent_fd, new_fd);
421                 }
422         g_string_free(fname, TRUE);
423 }
424
425
426 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
427 {
428         gchar *path_utf8 = path_to_utf8(path);
429         FileData *ret = file_data_new(path_utf8, st, check_sidecars);
430
431         g_free(path_utf8);
432         return ret;
433 }
434
435 FileData *file_data_new_simple(const gchar *path_utf8)
436 {
437         struct stat st;
438
439         if (!stat_utf8(path_utf8, &st))
440                 {
441                 st.st_size = 0;
442                 st.st_mtime = 0;
443                 }
444
445         return file_data_new(path_utf8, &st, TRUE);
446 }
447
448 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
449 {
450         sfd->parent = target;
451         if (!g_list_find(target->sidecar_files, sfd))
452                 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
453         file_data_increment_version(sfd); /* increments both sfd and target */
454         return target;
455 }
456
457
458 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
459 {
460         GList *work;
461         
462         file_data_add_sidecar_file(target, source);
463
464         work = source->sidecar_files;
465         while (work)
466                 {
467                 FileData *sfd = work->data;
468                 file_data_add_sidecar_file(target, sfd);
469                 work = work->next;
470                 }
471
472         g_list_free(source->sidecar_files);
473         source->sidecar_files = NULL;
474
475         target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
476         
477         return target;
478 }
479
480 #ifdef DEBUG_FILEDATA
481 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
482 #else
483 FileData *file_data_ref(FileData *fd)
484 #endif
485 {
486         if (fd == NULL) return NULL;
487 #ifdef DEBUG_FILEDATA
488         if (fd->magick != 0x12345678)
489                 DEBUG_0("fd magick mismatch at %s:%d", file, line);
490 #endif
491         g_assert(fd->magick == 0x12345678);
492         fd->ref++;
493
494 #ifdef DEBUG_FILEDATA
495         DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
496 #else
497         DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
498 #endif
499         return fd;
500 }
501
502 static void file_data_free(FileData *fd)
503 {
504         g_assert(fd->magick == 0x12345678);
505         g_assert(fd->ref == 0);
506
507         g_hash_table_remove(file_data_pool, fd->original_path);
508
509         g_free(fd->path);
510         g_free(fd->original_path);
511         g_free(fd->collate_key_name);
512         g_free(fd->collate_key_name_nocase);
513         if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
514
515         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
516
517         file_data_change_info_free(NULL, fd);
518         g_free(fd);
519 }
520
521 #ifdef DEBUG_FILEDATA
522 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
523 #else
524 void file_data_unref(FileData *fd)
525 #endif
526 {
527         if (fd == NULL) return;
528 #ifdef DEBUG_FILEDATA
529         if (fd->magick != 0x12345678)
530                 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
531 #endif
532         g_assert(fd->magick == 0x12345678);
533         
534         fd->ref--;
535 #ifdef DEBUG_FILEDATA
536         DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
537
538 #else
539         DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
540 #endif
541         if (fd->ref == 0)
542                 {
543                 GList *work;
544                 FileData *parent = fd->parent ? fd->parent : fd;
545                 
546                 if (parent->ref > 0)
547                         return;
548
549                 work = parent->sidecar_files;
550                 while (work)
551                         {
552                         FileData *sfd = work->data;
553                         if (sfd->ref > 0)
554                                 return;
555                         work = work->next;
556                         }
557
558                 /* none of parent/children is referenced, we can free everything */
559
560                 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
561
562                 work = parent->sidecar_files;
563                 while (work)
564                         {
565                         FileData *sfd = work->data;
566                         file_data_free(sfd);
567                         work = work->next;
568                         }
569
570                 g_list_free(parent->sidecar_files);
571                 parent->sidecar_files = NULL;
572
573                 file_data_free(parent);
574                 }
575 }
576
577 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
578 {
579         sfd->parent = target;
580         g_assert(g_list_find(target->sidecar_files, sfd));
581         
582         file_data_increment_version(sfd); /* increments both sfd and target */
583
584         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
585         sfd->parent = NULL;
586
587         if (sfd->ref == 0)
588                 {
589                 file_data_free(sfd);
590                 return NULL;
591                 }
592
593         return sfd;
594 }
595
596 /* disables / enables grouping for particular file, sends UPDATE notification */
597 void file_data_disable_grouping(FileData *fd, gboolean disable)
598 {
599         if (!fd->disable_grouping == !disable) return;
600         fd->disable_grouping = !!disable;
601         
602         if (disable)
603                 {
604                 if (fd->parent)
605                         {
606                         FileData *parent = file_data_ref(fd->parent);
607                         file_data_disconnect_sidecar_file(parent, fd);
608                         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
609                         file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
610                         file_data_unref(parent);
611                         }
612                 else if (fd->sidecar_files)
613                         {
614                         GList *sidecar_files = filelist_copy(fd->sidecar_files);
615                         GList *work = sidecar_files;
616                         while (work)
617                                 {
618                                 FileData *sfd = work->data;
619                                 work = work->next;
620                                 file_data_disconnect_sidecar_file(fd, sfd);
621                                 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
622                                 }
623                         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
624                         file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
625                         filelist_free(sidecar_files);
626                         }
627                 }
628         else
629                 {
630                 file_data_check_sidecars(fd);
631                 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
632                 }
633 }
634
635 /* compare name without extension */
636 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
637 {
638         size_t len1 = fd1->extension - fd1->name;
639         size_t len2 = fd2->extension - fd2->name;
640
641         if (len1 < len2) return -1;
642         if (len1 > len2) return 1;
643
644         return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
645 }
646
647 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
648 {
649         if (!fdci && fd)
650                 fdci = fd->change;
651
652         if (!fdci)
653                 return;
654
655         g_free(fdci->source);
656         g_free(fdci->dest);
657
658         g_free(fdci);
659
660         if (fd)
661                 fd->change = NULL;
662 }
663
664
665
666
667 /*
668  *-----------------------------------------------------------------------------
669  * sidecar file info struct
670  *-----------------------------------------------------------------------------
671  */
672
673
674
675 static gint sidecar_file_priority(const gchar *path)
676 {
677         const gchar *extension = extension_from_path(path);
678         gint i = 1;
679         GList *work;
680
681         if (extension == NULL)
682                 return 0;
683
684         work = sidecar_ext_get_list();
685
686         while (work) {
687                 gchar *ext = work->data;
688                 
689                 work = work->next;
690                 if (strcmp(extension, ext) == 0) return i;
691                 i++;
692         }
693         return 0;
694 }
695
696
697 /*
698  *-----------------------------------------------------------------------------
699  * load file list
700  *-----------------------------------------------------------------------------
701  */
702
703 static SortType filelist_sort_method = SORT_NONE;
704 static gint filelist_sort_ascend = TRUE;
705
706
707 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
708 {
709         if (!filelist_sort_ascend)
710                 {
711                 FileData *tmp = fa;
712                 fa = fb;
713                 fb = tmp;
714                 }
715
716         switch (filelist_sort_method)
717                 {
718                 case SORT_NAME:
719                         break;
720                 case SORT_SIZE:
721                         if (fa->size < fb->size) return -1;
722                         if (fa->size > fb->size) return 1;
723                         /* fall back to name */
724                         break;
725                 case SORT_TIME:
726                         if (fa->date < fb->date) return -1;
727                         if (fa->date > fb->date) return 1;
728                         /* fall back to name */
729                         break;
730 #ifdef HAVE_STRVERSCMP
731                 case SORT_NUMBER:
732                         return strverscmp(fa->name, fb->name);
733                         break;
734 #endif
735                 default:
736                         break;
737                 }
738
739         if (options->file_sort.case_sensitive)
740                 return strcmp(fa->collate_key_name, fb->collate_key_name);
741         else
742                 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
743 }
744
745 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
746 {
747         filelist_sort_method = method;
748         filelist_sort_ascend = ascend;
749         return filelist_sort_compare_filedata(fa, fb);
750 }
751
752 static gint filelist_sort_file_cb(gpointer a, gpointer b)
753 {
754         return filelist_sort_compare_filedata(a, b);
755 }
756
757 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
758 {
759         filelist_sort_method = method;
760         filelist_sort_ascend = ascend;
761         return g_list_sort(list, cb);
762 }
763
764 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gint ascend, GCompareFunc cb)
765 {
766         filelist_sort_method = method;
767         filelist_sort_ascend = ascend;
768         return g_list_insert_sorted(list, data, cb);
769 }
770
771 GList *filelist_sort(GList *list, SortType method, gint ascend)
772 {
773         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
774 }
775
776 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
777 {
778         return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
779 }
780
781
782 static GList *filelist_filter_out_sidecars(GList *flist)
783 {
784         GList *work = flist;
785         GList *flist_filtered = NULL;
786
787         while (work)
788                 {
789                 FileData *fd = work->data;
790         
791                 work = work->next;
792                 if (fd->parent) /* remove fd's that are children */
793                         file_data_unref(fd);
794                 else
795                         flist_filtered = g_list_prepend(flist_filtered, fd);
796                 }
797         g_list_free(flist);
798
799         return flist_filtered;
800 }
801
802 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
803 {
804         DIR *dp;
805         struct dirent *dir;
806         gchar *pathl;
807         GList *dlist = NULL;
808         GList *flist = NULL;
809         gint (*stat_func)(const gchar *path, struct stat *buf);
810
811         g_assert(files || dirs);
812
813         if (files) *files = NULL;
814         if (dirs) *dirs = NULL;
815
816         pathl = path_from_utf8(dir_fd->path);
817         if (!pathl) return FALSE;
818
819         dp = opendir(pathl);
820         if (dp == NULL)
821                 {
822                 g_free(pathl);
823                 return FALSE;
824                 }
825
826         if (follow_symlinks)
827                 stat_func = stat;
828         else
829                 stat_func = lstat;
830
831         while ((dir = readdir(dp)) != NULL)
832                 {
833                 struct stat ent_sbuf;
834                 const gchar *name = dir->d_name;
835                 gchar *filepath;
836
837                 if (!options->file_filter.show_hidden_files && ishidden(name))
838                         continue;
839
840                 filepath = g_build_filename(pathl, name, NULL);
841                 if (stat_func(filepath, &ent_sbuf) >= 0)
842                         {
843                         if (S_ISDIR(ent_sbuf.st_mode))
844                                 {
845                                 /* we ignore the .thumbnails dir for cleanliness */
846                                 if (dirs &&
847                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
848                                     strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
849                                     strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
850                                     strcmp(name, THUMB_FOLDER_LOCAL) != 0)
851                                         {
852                                         dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
853                                         }
854                                 }
855                         else
856                                 {
857                                 if (files && filter_name_exists(name))
858                                         {
859                                         flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
860                                         }
861                                 }
862                         }
863                 g_free(filepath);
864                 }
865
866         closedir(dp);
867         
868         g_free(pathl);
869
870         if (dirs) *dirs = dlist;
871         if (files) *files = filelist_filter_out_sidecars(flist);
872
873         return TRUE;
874 }
875
876 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
877 {
878         return filelist_read_real(dir_fd, files, dirs, TRUE);
879 }
880
881 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
882 {
883         return filelist_read_real(dir_fd, files, dirs, FALSE);
884 }
885
886 void filelist_free(GList *list)
887 {
888         GList *work;
889
890         work = list;
891         while (work)
892                 {
893                 file_data_unref((FileData *)work->data);
894                 work = work->next;
895                 }
896
897         g_list_free(list);
898 }
899
900
901 GList *filelist_copy(GList *list)
902 {
903         GList *new_list = NULL;
904         GList *work;
905
906         work = list;
907         while (work)
908                 {
909                 FileData *fd;
910
911                 fd = work->data;
912                 work = work->next;
913
914                 new_list = g_list_prepend(new_list, file_data_ref(fd));
915                 }
916
917         return g_list_reverse(new_list);
918 }
919
920 GList *filelist_from_path_list(GList *list)
921 {
922         GList *new_list = NULL;
923         GList *work;
924
925         work = list;
926         while (work)
927                 {
928                 gchar *path;
929
930                 path = work->data;
931                 work = work->next;
932
933                 new_list = g_list_prepend(new_list, file_data_new_simple(path));
934                 }
935
936         return g_list_reverse(new_list);
937 }
938
939 GList *filelist_to_path_list(GList *list)
940 {
941         GList *new_list = NULL;
942         GList *work;
943
944         work = list;
945         while (work)
946                 {
947                 FileData *fd;
948
949                 fd = work->data;
950                 work = work->next;
951
952                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
953                 }
954
955         return g_list_reverse(new_list);
956 }
957
958 GList *filelist_filter(GList *list, gint is_dir_list)
959 {
960         GList *work;
961
962         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
963
964         work = list;
965         while (work)
966                 {
967                 FileData *fd = (FileData *)(work->data);
968                 const gchar *name = fd->name;
969
970                 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
971                     (!is_dir_list && !filter_name_exists(name)) ||
972                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
973                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
974                         {
975                         GList *link = work;
976                         
977                         list = g_list_remove_link(list, link);
978                         file_data_unref(fd);
979                         g_list_free(link);
980                         }
981         
982                 work = work->next;
983                 }
984
985         return list;
986 }
987
988 /*
989  *-----------------------------------------------------------------------------
990  * filelist recursive
991  *-----------------------------------------------------------------------------
992  */
993
994 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
995 {
996         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
997 }
998
999 GList *filelist_sort_path(GList *list)
1000 {
1001         return g_list_sort(list, filelist_sort_path_cb);
1002 }
1003
1004 static void filelist_recursive_append(GList **list, GList *dirs)
1005 {
1006         GList *work;
1007
1008         work = dirs;
1009         while (work)
1010                 {
1011                 FileData *fd = (FileData *)(work->data);
1012                 GList *f;
1013                 GList *d;
1014
1015                 if (filelist_read(fd, &f, &d))
1016                         {
1017                         f = filelist_filter(f, FALSE);
1018                         f = filelist_sort_path(f);
1019                         *list = g_list_concat(*list, f);
1020
1021                         d = filelist_filter(d, TRUE);
1022                         d = filelist_sort_path(d);
1023                         filelist_recursive_append(list, d);
1024                         filelist_free(d);
1025                         }
1026
1027                 work = work->next;
1028                 }
1029 }
1030
1031 GList *filelist_recursive(FileData *dir_fd)
1032 {
1033         GList *list;
1034         GList *d;
1035
1036         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1037         list = filelist_filter(list, FALSE);
1038         list = filelist_sort_path(list);
1039
1040         d = filelist_filter(d, TRUE);
1041         d = filelist_sort_path(d);
1042         filelist_recursive_append(&list, d);
1043         filelist_free(d);
1044
1045         return list;
1046 }
1047
1048
1049 /*
1050  * marks and orientation
1051  */
1052
1053
1054 gboolean file_data_get_mark(FileData *fd, gint n)
1055 {
1056         return !!(fd->marks & (1 << n));
1057 }
1058
1059 guint file_data_get_marks(FileData *fd)
1060 {
1061         return fd->marks;
1062 }
1063
1064 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1065 {
1066         guint old = fd->marks;
1067         if (!value == !(fd->marks & (1 << n))) return;
1068
1069         fd->marks = fd->marks ^ (1 << n);
1070         
1071         if (old && !fd->marks) /* keep files with non-zero marks in memory */
1072                 {
1073                 file_data_unref(fd);
1074                 }
1075         else if (!old && fd->marks)
1076                 {
1077                 file_data_ref(fd);
1078                 }
1079         
1080         file_data_increment_version(fd);
1081         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1082 }
1083
1084 gboolean file_data_filter_marks(FileData *fd, guint filter)
1085 {
1086         return ((fd->marks & filter) == filter);
1087 }
1088
1089 GList *file_data_filter_marks_list(GList *list, guint filter)
1090 {
1091         GList *work;
1092
1093         work = list;
1094         while (work)
1095                 {
1096                 FileData *fd = work->data;
1097                 GList *link = work;
1098                 work = work->next;
1099
1100                 if (!file_data_filter_marks(fd, filter))
1101                         {
1102                         list = g_list_remove_link(list, link);
1103                         file_data_unref(fd);
1104                         g_list_free(link);
1105                         }
1106                 }
1107
1108         return list;
1109 }
1110
1111 gint file_data_get_user_orientation(FileData *fd)
1112 {
1113         return fd->user_orientation;
1114 }
1115
1116 void file_data_set_user_orientation(FileData *fd, gint value)
1117 {
1118         if (fd->user_orientation == value) return;
1119
1120         fd->user_orientation = value;
1121         file_data_increment_version(fd);
1122         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1123 }
1124
1125
1126 /*
1127  * file_data    - operates on the given fd
1128  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1129  */
1130
1131
1132 /* return list of sidecar file extensions in a string */
1133 gchar *file_data_sc_list_to_string(FileData *fd)
1134 {
1135         GList *work;
1136         GString *result = g_string_new("");
1137
1138         work = fd->sidecar_files;
1139         while (work)
1140                 {
1141                 FileData *sfd = work->data;
1142
1143                 result = g_string_append(result, "+ ");
1144                 result = g_string_append(result, sfd->extension);
1145                 work = work->next;
1146                 if (work) result = g_string_append_c(result, ' ');
1147                 }
1148
1149         return g_string_free(result, FALSE);
1150 }
1151
1152
1153
1154 /*
1155  * add FileDataChangeInfo (see typedefs.h) for the given operation
1156  * uses file_data_add_change_info
1157  *
1158  * fails if the fd->change already exists - change operations can't run in parallel
1159  * fd->change_info works as a lock
1160  *
1161  * dest can be NULL - in this case the current name is used for now, it will
1162  * be changed later
1163  */
1164
1165 /*
1166    FileDataChangeInfo types:
1167    COPY
1168    MOVE   - path is changed, name may be changed too
1169    RENAME - path remains unchanged, name is changed
1170             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1171             sidecar names are changed too, extensions are not changed
1172    DELETE
1173    UPDATE - file size, date or grouping has been changed
1174 */
1175
1176 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1177 {
1178         FileDataChangeInfo *fdci;
1179
1180         if (fd->change) return FALSE;
1181
1182         fdci = g_new0(FileDataChangeInfo, 1);
1183
1184         fdci->type = type;
1185
1186         if (src)
1187                 fdci->source = g_strdup(src);
1188         else
1189                 fdci->source = g_strdup(fd->path);
1190
1191         if (dest)
1192                 fdci->dest = g_strdup(dest);
1193
1194         fd->change = fdci;
1195         
1196         return TRUE;
1197 }
1198
1199 static void file_data_planned_change_remove(FileData *fd)
1200 {
1201         if (file_data_planned_change_hash &&
1202             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1203                 {
1204                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1205                         {
1206                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1207                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1208                         file_data_unref(fd);
1209                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1210                                 {
1211                                 g_hash_table_destroy(file_data_planned_change_hash);
1212                                 file_data_planned_change_hash = NULL;
1213                                 DEBUG_1("planned change: empty");
1214                                 }
1215                         }
1216                 }
1217 }
1218
1219
1220 void file_data_free_ci(FileData *fd)
1221 {
1222         FileDataChangeInfo *fdci = fd->change;
1223
1224         if (!fdci)
1225                 return;
1226
1227         file_data_planned_change_remove(fd);
1228
1229         g_free(fdci->source);
1230         g_free(fdci->dest);
1231
1232         g_free(fdci);
1233
1234         fd->change = NULL;
1235 }
1236
1237
1238 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1239 {
1240         GList *work;
1241
1242         if (fd->parent) fd = fd->parent;
1243         
1244         if (fd->change) return FALSE;
1245         
1246         work = fd->sidecar_files;
1247         while (work)
1248                 {
1249                 FileData *sfd = work->data;
1250                 
1251                 if (sfd->change) return FALSE;
1252                 work = work->next;
1253                 }
1254
1255         file_data_add_ci(fd, type, NULL, NULL);
1256         
1257         work = fd->sidecar_files;
1258         while (work)
1259                 {
1260                 FileData *sfd = work->data;
1261                 
1262                 file_data_add_ci(sfd, type, NULL, NULL);
1263                 work = work->next;
1264                 }
1265                 
1266         return TRUE;
1267 }
1268
1269 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1270 {
1271         GList *work;
1272         
1273         if (fd->parent) fd = fd->parent;
1274         
1275         if (!fd->change || fd->change->type != type) return FALSE;
1276         
1277         work = fd->sidecar_files;
1278         while (work)
1279                 {
1280                 FileData *sfd = work->data;
1281
1282                 if (!sfd->change || sfd->change->type != type) return FALSE;
1283                 work = work->next;
1284                 }
1285
1286         return TRUE;
1287 }
1288
1289
1290 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1291 {
1292         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1293         file_data_sc_update_ci_copy(fd, dest_path);
1294         return TRUE;
1295 }
1296
1297 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1298 {
1299         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1300         file_data_sc_update_ci_move(fd, dest_path);
1301         return TRUE;
1302 }
1303
1304 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1305 {
1306         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1307         file_data_sc_update_ci_rename(fd, dest_path);
1308         return TRUE;
1309 }
1310
1311 gboolean file_data_sc_add_ci_delete(FileData *fd)
1312 {
1313         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1314 }
1315
1316 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1317 {
1318         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1319         file_data_sc_update_ci_unspecified(fd, dest_path);
1320         return TRUE;
1321 }
1322
1323 gboolean file_data_add_ci_write_metadata(FileData *fd)
1324 {
1325         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1326 }
1327
1328 void file_data_sc_free_ci(FileData *fd)
1329 {
1330         GList *work;
1331
1332         if (fd->parent) fd = fd->parent;
1333         
1334         file_data_free_ci(fd);
1335         
1336         work = fd->sidecar_files;
1337         while (work)
1338                 {
1339                 FileData *sfd = work->data;
1340         
1341                 file_data_free_ci(sfd);
1342                 work = work->next;
1343                 }
1344 }
1345
1346 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1347 {
1348         GList *work;
1349         gboolean ret = TRUE;
1350
1351         work = fd_list;
1352         while (work)
1353                 {
1354                 FileData *fd = work->data;
1355         
1356                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1357                 work = work->next;
1358                 }
1359
1360         return ret;
1361 }
1362
1363 static void file_data_sc_revert_ci_list(GList *fd_list)
1364 {
1365         GList *work;
1366         
1367         work = fd_list;
1368         while (work)
1369                 {
1370                 FileData *fd = work->data;
1371                 
1372                 file_data_sc_free_ci(fd);
1373                 work = work->prev;
1374                 }
1375 }
1376
1377 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1378 {
1379         GList *work;
1380         
1381         work = fd_list;
1382         while (work)
1383                 {
1384                 FileData *fd = work->data;
1385                 
1386                 if (!func(fd, dest))
1387                         {
1388                         file_data_sc_revert_ci_list(work->prev);
1389                         return FALSE;
1390                         }
1391                 work = work->next;
1392                 }
1393         
1394         return TRUE;
1395 }
1396
1397 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1398 {
1399         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1400 }
1401
1402 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1403 {
1404         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1405 }
1406
1407 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1408 {
1409         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1410 }
1411
1412 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1413 {
1414         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1415 }
1416
1417 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1418 {
1419         GList *work;
1420         gboolean ret = TRUE;
1421
1422         work = fd_list;
1423         while (work)
1424                 {
1425                 FileData *fd = work->data;
1426         
1427                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1428                 work = work->next;
1429                 }
1430
1431         return ret;
1432 }
1433
1434 void file_data_free_ci_list(GList *fd_list)
1435 {
1436         GList *work;
1437         
1438         work = fd_list;
1439         while (work)
1440                 {
1441                 FileData *fd = work->data;
1442                 
1443                 file_data_free_ci(fd);
1444                 work = work->next;
1445                 }
1446 }
1447
1448 void file_data_sc_free_ci_list(GList *fd_list)
1449 {
1450         GList *work;
1451         
1452         work = fd_list;
1453         while (work)
1454                 {
1455                 FileData *fd = work->data;
1456                 
1457                 file_data_sc_free_ci(fd);
1458                 work = work->next;
1459                 }
1460 }
1461
1462 /*
1463  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1464  * fails if fd->change does not exist or the change type does not match
1465  */
1466
1467 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1468 {
1469         FileDataChangeType type = fd->change->type;
1470         
1471         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1472                 {
1473                 FileData *ofd;
1474                 
1475                 if (!file_data_planned_change_hash)
1476                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1477                 
1478                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1479                         {
1480                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1481                         g_hash_table_remove(file_data_planned_change_hash, old_path);
1482                         file_data_unref(fd);
1483                         }
1484
1485                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1486                 if (ofd != fd)
1487                         {
1488                         if (ofd)
1489                                 {
1490                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1491                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
1492                                 file_data_unref(ofd);
1493                                 }
1494                         
1495                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1496                         file_data_ref(fd);
1497                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1498                         }
1499                 }
1500 }
1501
1502 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1503 {
1504         gchar *old_path = fd->change->dest;
1505
1506         fd->change->dest = g_strdup(dest_path);
1507         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1508         g_free(old_path);
1509 }
1510
1511 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1512 {
1513         const gchar *extension = extension_from_path(fd->change->source);
1514         gchar *base = remove_extension_from_path(dest_path);
1515         gchar *old_path = fd->change->dest;
1516         
1517         fd->change->dest = g_strconcat(base, extension, NULL);
1518         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1519         
1520         g_free(old_path);
1521         g_free(base);
1522 }
1523
1524 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1525 {
1526         GList *work;
1527         gchar *dest_path_full = NULL;
1528         
1529         if (fd->parent) fd = fd->parent;
1530         
1531         if (!dest_path)
1532                 {
1533                 dest_path = fd->path;
1534                 }
1535         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1536                 {
1537                 gchar *dir = remove_level_from_path(fd->path);
1538                 
1539                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1540                 g_free(dir);
1541                 dest_path = dest_path_full;
1542                 }
1543         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1544                 {
1545                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1546                 dest_path = dest_path_full;
1547                 }
1548                 
1549         file_data_update_ci_dest(fd, dest_path);
1550         
1551         work = fd->sidecar_files;
1552         while (work)
1553                 {
1554                 FileData *sfd = work->data;
1555                 
1556                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1557                 work = work->next;
1558                 }
1559         
1560         g_free(dest_path_full);
1561 }
1562
1563 static gint file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1564 {
1565         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1566         file_data_sc_update_ci(fd, dest_path);
1567         return TRUE;
1568 }
1569
1570 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1571 {
1572         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1573 }
1574         
1575 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1576 {
1577         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1578 }
1579
1580 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1581 {
1582         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1583 }
1584
1585 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1586 {
1587         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1588 }
1589
1590 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1591                                                       const gchar *dest,
1592                                                       gboolean (*func)(FileData *, const gchar *))
1593 {
1594         GList *work;
1595         gboolean ret = TRUE;
1596         
1597         work = fd_list;
1598         while (work)
1599                 {
1600                 FileData *fd = work->data;
1601                 
1602                 if (!func(fd, dest)) ret = FALSE;
1603                 work = work->next;
1604                 }
1605         
1606         return ret;
1607 }
1608
1609 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1610 {
1611         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1612 }
1613
1614 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1615 {
1616         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1617 }
1618
1619 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1620 {
1621         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1622 }
1623
1624
1625 /*
1626  * verify source and dest paths - dest image exists, etc.
1627  * it should detect all possible problems with the planned operation
1628  */
1629
1630 gint file_data_verify_ci(FileData *fd)
1631 {
1632         gint ret = CHANGE_OK;
1633         gchar *dir;
1634         
1635         if (!fd->change)
1636                 {
1637                 DEBUG_1("Change checked: no change info: %s", fd->path);
1638                 return ret;
1639                 }
1640
1641         if (!isname(fd->path) && 
1642             !filter_file_class(fd->extension, FORMAT_CLASS_META)) /* xmp sidecar can be eventually created from scratch */
1643                 {
1644                 /* this probably should not happen */
1645                 ret |= CHANGE_NO_SRC;
1646                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1647                 return ret;
1648                 }
1649                 
1650         dir = remove_level_from_path(fd->path);
1651         
1652         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1653             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1654             !access_file(fd->path, R_OK))
1655                 {
1656                 ret |= CHANGE_NO_READ_PERM;
1657                 DEBUG_1("Change checked: no read permission: %s", fd->path);
1658                 }
1659         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1660                  !access_file(dir, W_OK))
1661                 {
1662                 ret |= CHANGE_NO_WRITE_PERM_DIR;
1663                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1664                 }
1665         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1666                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1667                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1668                  !access_file(fd->path, W_OK))
1669                 {
1670                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1671                 DEBUG_1("Change checked: no write permission: %s", fd->path);
1672                 }
1673         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1674            - that means that there are no hard errors and warnings can be disabled
1675         */
1676         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA && 
1677                  options->metadata.save_in_image_file && options->metadata.warn_on_write_problems)
1678                 {
1679                 if (isname(fd->path) && !access_file(fd->path, W_OK))
1680                         {
1681                         ret |= CHANGE_WARN_NO_WRITE_PERM;
1682                         DEBUG_1("Change checked: file is readonly: %s", fd->path);
1683                         }
1684                 
1685                 else if (!access_file(dir, W_OK))
1686                         {
1687                         ret |= CHANGE_WARN_NO_WRITE_PERM;
1688                         DEBUG_1("Change checked: dir is readonly: %s", fd->path);
1689                         }
1690                 }
1691                 
1692         if (fd->change->dest)
1693                 {
1694                 gboolean same;
1695                 gchar *dest_dir;
1696                         
1697                 same = (strcmp(fd->path, fd->change->dest) == 0);
1698
1699                 if (!same)
1700                         {
1701                         const gchar *dest_ext = extension_from_path(fd->change->dest);
1702                         if (!dest_ext) dest_ext = "";
1703
1704                         if (strcasecmp(fd->extension, dest_ext) != 0)
1705                                 {
1706                                 ret |= CHANGE_WARN_CHANGED_EXT;
1707                                 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1708                                 }
1709                         }
1710                 else
1711                         {
1712                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
1713                                 {
1714                                 ret |= CHANGE_WARN_SAME;
1715                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
1716                                 }
1717                         }
1718
1719                 dest_dir = remove_level_from_path(fd->change->dest);
1720
1721                 if (!isdir(dest_dir))
1722                         {
1723                         ret |= CHANGE_NO_DEST_DIR;
1724                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1725                         }
1726                 else if (!access_file(dest_dir, W_OK))
1727                         {
1728                         ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1729                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1730                         }
1731                 else if (!same)
1732                         {
1733                         if (isfile(fd->change->dest))
1734                                 {
1735                                 if (!access_file(fd->change->dest, W_OK))
1736                                         {
1737                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
1738                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1739                                         }
1740                                 else
1741                                         {
1742                                         ret |= CHANGE_WARN_DEST_EXISTS;
1743                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1744                                         }
1745                                 }
1746                         else if (isdir(fd->change->dest))
1747                                 {
1748                                 ret |= CHANGE_DEST_EXISTS;
1749                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1750                                 }
1751                         }
1752
1753                 g_free(dest_dir);
1754                 }
1755                 
1756         fd->change->error = ret;
1757         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1758
1759         g_free(dir);
1760         return ret;
1761 }
1762
1763
1764 gint file_data_sc_verify_ci(FileData *fd)
1765 {
1766         GList *work;
1767         gint ret;
1768
1769         ret = file_data_verify_ci(fd);
1770
1771         work = fd->sidecar_files;
1772         while (work)
1773                 {
1774                 FileData *sfd = work->data;
1775
1776                 ret |= file_data_verify_ci(sfd);
1777                 work = work->next;
1778                 }
1779
1780         return ret;
1781 }
1782
1783 gchar *file_data_get_error_string(gint error)
1784 {
1785         GString *result = g_string_new("");
1786
1787         if (error & CHANGE_NO_SRC)
1788                 {
1789                 if (result->len > 0) g_string_append(result, ", ");
1790                 g_string_append(result, _("file or directory does not exist"));
1791                 }
1792
1793         if (error & CHANGE_DEST_EXISTS)
1794                 {
1795                 if (result->len > 0) g_string_append(result, ", ");
1796                 g_string_append(result, _("destination already exists"));
1797                 }
1798
1799         if (error & CHANGE_NO_WRITE_PERM_DEST)
1800                 {
1801                 if (result->len > 0) g_string_append(result, ", ");
1802                 g_string_append(result, _("destination can't be overwritten"));
1803                 }
1804
1805         if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
1806                 {
1807                 if (result->len > 0) g_string_append(result, ", ");
1808                 g_string_append(result, _("destination directory is not writable"));
1809                 }
1810
1811         if (error & CHANGE_NO_DEST_DIR)
1812                 {
1813                 if (result->len > 0) g_string_append(result, ", ");
1814                 g_string_append(result, _("destination directory does not exist"));
1815                 }
1816
1817         if (error & CHANGE_NO_WRITE_PERM_DIR)
1818                 {
1819                 if (result->len > 0) g_string_append(result, ", ");
1820                 g_string_append(result, _("source directory is not writable"));
1821                 }
1822
1823         if (error & CHANGE_NO_READ_PERM)
1824                 {
1825                 if (result->len > 0) g_string_append(result, ", ");
1826                 g_string_append(result, _("no read permission"));
1827                 }
1828
1829         if (error & CHANGE_WARN_NO_WRITE_PERM)
1830                 {
1831                 if (result->len > 0) g_string_append(result, ", ");
1832                 g_string_append(result, _("file is readonly"));
1833                 }
1834
1835         if (error & CHANGE_WARN_DEST_EXISTS)
1836                 {
1837                 if (result->len > 0) g_string_append(result, ", ");
1838                 g_string_append(result, _("destination already exists and will be overwritten"));
1839                 }
1840                 
1841         if (error & CHANGE_WARN_SAME)
1842                 {
1843                 if (result->len > 0) g_string_append(result, ", ");
1844                 g_string_append(result, _("source and destination are the same"));
1845                 }
1846
1847         if (error & CHANGE_WARN_CHANGED_EXT)
1848                 {
1849                 if (result->len > 0) g_string_append(result, ", ");
1850                 g_string_append(result, _("source and destination have different extension"));
1851                 }
1852
1853         return g_string_free(result, FALSE);
1854 }
1855
1856 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
1857 {
1858         GList *work;
1859         gint all_errors = 0;
1860         gint common_errors = ~0;
1861         gint num;
1862         gint *errors;
1863         gint i;
1864         
1865         if (!list) return 0;
1866         
1867         num = g_list_length(list);
1868         errors = g_new(int, num);
1869         work = list;
1870         i = 0;
1871         while (work)
1872                 {
1873                 FileData *fd;
1874                 gint error;
1875
1876                 fd = work->data;
1877                 work = work->next;
1878                         
1879                 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
1880                 all_errors |= error;
1881                 common_errors &= error;
1882                 
1883                 errors[i] = error;
1884                 
1885                 i++;
1886                 }
1887         
1888         if (desc && all_errors)
1889                 {
1890                 GList *work;
1891                 GString *result = g_string_new("");
1892                 
1893                 if (common_errors)
1894                         {
1895                         gchar *str = file_data_get_error_string(common_errors);
1896                         g_string_append(result, str);
1897                         g_string_append(result, "\n");
1898                         g_free(str);
1899                         }
1900                 
1901                 work = list;
1902                 i = 0;
1903                 while (work)
1904                         {
1905                         FileData *fd;
1906                         gint error;
1907
1908                         fd = work->data;
1909                         work = work->next;
1910                         
1911                         error = errors[i] & ~common_errors;
1912                         
1913                         if (error)
1914                                 {
1915                                 gchar *str = file_data_get_error_string(error);
1916                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
1917                                 g_free(str);
1918                                 }
1919                         i++;
1920                         }
1921                 *desc = g_string_free(result, FALSE);
1922                 }
1923
1924         g_free(errors);
1925         return all_errors;
1926 }
1927
1928
1929 /*
1930  * perform the change described by FileFataChangeInfo
1931  * it is used for internal operations,
1932  * this function actually operates with files on the filesystem
1933  * it should implement safe delete
1934  */
1935
1936 static gboolean file_data_perform_move(FileData *fd)
1937 {
1938         g_assert(!strcmp(fd->change->source, fd->path));
1939         return move_file(fd->change->source, fd->change->dest);
1940 }
1941
1942 static gboolean file_data_perform_copy(FileData *fd)
1943 {
1944         g_assert(!strcmp(fd->change->source, fd->path));
1945         return copy_file(fd->change->source, fd->change->dest);
1946 }
1947
1948 static gboolean file_data_perform_delete(FileData *fd)
1949 {
1950         if (isdir(fd->path) && !islink(fd->path))
1951                 return rmdir_utf8(fd->path);
1952         else
1953                 if (options->file_ops.safe_delete_enable)
1954                         return file_util_safe_unlink(fd->path);
1955                 else
1956                         return unlink_file(fd->path);
1957 }
1958
1959 gboolean file_data_perform_ci(FileData *fd)
1960 {
1961         FileDataChangeType type = fd->change->type;
1962         switch (type)
1963                 {
1964                 case FILEDATA_CHANGE_MOVE:
1965                         return file_data_perform_move(fd);
1966                 case FILEDATA_CHANGE_COPY:
1967                         return file_data_perform_copy(fd);
1968                 case FILEDATA_CHANGE_RENAME:
1969                         return file_data_perform_move(fd); /* the same as move */
1970                 case FILEDATA_CHANGE_DELETE:
1971                         return file_data_perform_delete(fd);
1972                 case FILEDATA_CHANGE_WRITE_METADATA:
1973                         return metadata_write_perform(fd);
1974                 case FILEDATA_CHANGE_UNSPECIFIED:
1975                         /* nothing to do here */
1976                         break;
1977                 }
1978         return TRUE;
1979 }
1980
1981
1982
1983 gboolean file_data_sc_perform_ci(FileData *fd)
1984 {
1985         GList *work;
1986         gboolean ret = TRUE;
1987         FileDataChangeType type = fd->change->type;
1988         
1989         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1990
1991         work = fd->sidecar_files;
1992         while (work)
1993                 {
1994                 FileData *sfd = work->data;
1995                 
1996                 if (!file_data_perform_ci(sfd)) ret = FALSE;
1997                 work = work->next;
1998                 }
1999         
2000         if (!file_data_perform_ci(fd)) ret = FALSE;
2001         
2002         return ret;
2003 }
2004
2005 /*
2006  * updates FileData structure according to FileDataChangeInfo
2007  */
2008
2009 gint file_data_apply_ci(FileData *fd)
2010 {
2011         FileDataChangeType type = fd->change->type;
2012
2013         /* FIXME delete ?*/
2014         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2015                 {
2016                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2017                 file_data_planned_change_remove(fd);
2018                 
2019                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2020                         {
2021                         /* this change overwrites another file which is already known to other modules
2022                            renaming fd would create duplicate FileData structure
2023                            the best thing we can do is nothing
2024                            FIXME: maybe we could copy stuff like marks
2025                         */
2026                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2027                         }
2028                 else
2029                         {
2030                         file_data_set_path(fd, fd->change->dest);
2031                         }
2032                 }
2033         file_data_increment_version(fd);
2034         file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
2035         
2036         return TRUE;
2037 }
2038
2039 gint file_data_sc_apply_ci(FileData *fd)
2040 {
2041         GList *work;
2042         FileDataChangeType type = fd->change->type;
2043         
2044         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2045
2046         work = fd->sidecar_files;
2047         while (work)
2048                 {
2049                 FileData *sfd = work->data;
2050                 
2051                 file_data_apply_ci(sfd);
2052                 work = work->next;
2053                 }
2054         
2055         file_data_apply_ci(fd);
2056         
2057         return TRUE;
2058 }
2059
2060 /*
2061  * notify other modules about the change described by FileFataChangeInfo
2062  */
2063
2064 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2065    FIXME do we need the ignore_list? It looks like a workaround for ineffective
2066    implementation in view_file_list.c */
2067
2068
2069
2070
2071 typedef struct _NotifyData NotifyData;
2072
2073 struct _NotifyData {
2074         FileDataNotifyFunc func;
2075         gpointer data;
2076         NotifyPriority priority;
2077         };
2078
2079 static GList *notify_func_list = NULL;
2080
2081 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2082 {
2083         NotifyData *nda = (NotifyData *)a;
2084         NotifyData *ndb = (NotifyData *)b;
2085
2086         if (nda->priority < ndb->priority) return -1;
2087         if (nda->priority > ndb->priority) return 1;
2088         return 0;
2089 }
2090
2091 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2092 {
2093         NotifyData *nd;
2094         
2095         nd = g_new(NotifyData, 1);
2096         nd->func = func;
2097         nd->data = data;
2098         nd->priority = priority;
2099
2100         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2101         DEBUG_1("Notify func registered: %p", nd);
2102         
2103         return TRUE;
2104 }
2105
2106 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2107 {
2108         GList *work = notify_func_list;
2109         
2110         while (work)
2111                 {
2112                 NotifyData *nd = (NotifyData *)work->data;
2113         
2114                 if (nd->func == func && nd->data == data)
2115                         {
2116                         notify_func_list = g_list_delete_link(notify_func_list, work);
2117                         g_free(nd);
2118                         DEBUG_1("Notify func unregistered: %p", nd);
2119                         return TRUE;
2120                         }
2121                 work = work->next;
2122                 }
2123
2124         return FALSE;
2125 }
2126
2127
2128 void file_data_send_notification(FileData *fd, NotifyType type)
2129 {
2130         GList *work = notify_func_list;
2131
2132         while (work)
2133                 {
2134                 NotifyData *nd = (NotifyData *)work->data;
2135                 
2136                 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
2137                 nd->func(fd, type, nd->data);
2138                 work = work->next;
2139                 }
2140 }
2141
2142 static GHashTable *file_data_monitor_pool = NULL;
2143 static gint realtime_monitor_id = -1;
2144
2145 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2146 {
2147         FileData *fd = key;
2148
2149         file_data_check_changed_files(fd);
2150         
2151         DEBUG_1("monitor %s", fd->path);
2152 }
2153
2154 static gboolean realtime_monitor_cb(gpointer data)
2155 {
2156         if (!options->update_on_time_change) return TRUE;
2157         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2158         return TRUE;
2159 }
2160
2161 gint file_data_register_real_time_monitor(FileData *fd)
2162 {
2163         gint count;
2164         
2165         file_data_ref(fd);
2166         
2167         if (!file_data_monitor_pool)
2168                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2169         
2170         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2171
2172         DEBUG_1("Register realtime %d %s", count, fd->path);
2173         
2174         count++;
2175         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2176         
2177         if (realtime_monitor_id == -1)
2178                 {
2179                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2180                 }
2181         
2182         return TRUE;
2183 }
2184
2185 gint file_data_unregister_real_time_monitor(FileData *fd)
2186 {
2187         gint count;
2188
2189         g_assert(file_data_monitor_pool);
2190         
2191         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2192         
2193         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2194         
2195         g_assert(count > 0);
2196         
2197         count--;
2198         
2199         if (count == 0)
2200                 g_hash_table_remove(file_data_monitor_pool, fd);
2201         else
2202                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2203
2204         file_data_unref(fd);
2205         
2206         if (g_hash_table_size(file_data_monitor_pool) == 0)
2207                 {
2208                 g_source_remove(realtime_monitor_id);
2209                 realtime_monitor_id = -1;
2210                 return FALSE;
2211                 }
2212         
2213         return TRUE;
2214 }
2215 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */