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