fixed renaming of directories
[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) 
1455                 {
1456                 dest_path = fd->path;
1457                 }
1458         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1459                 {
1460                 gchar *dir = remove_level_from_path(fd->path);
1461                 
1462                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1463                 g_free(dir);
1464                 dest_path = dest_path_full;
1465                 }
1466         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1467                 {
1468                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1469                 dest_path = dest_path_full;
1470                 }
1471                 
1472         file_data_update_ci_dest(fd, dest_path);
1473         
1474         work = fd->sidecar_files;
1475         while (work)
1476                 {
1477                 FileData *sfd = work->data;
1478                 
1479                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1480                 work = work->next;
1481                 }
1482         
1483         g_free(dest_path_full);
1484 }
1485
1486 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1487 {
1488         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1489         file_data_sc_update_ci(fd, dest_path);
1490         return TRUE;
1491 }
1492         
1493 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1494 {
1495         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1496         file_data_sc_update_ci(fd, dest_path);
1497         return TRUE;
1498 }
1499
1500 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1501 {
1502         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1503         file_data_sc_update_ci(fd, dest_path);
1504         return TRUE;
1505 }
1506
1507 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1508 {
1509         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1510         file_data_sc_update_ci(fd, dest_path);
1511         return TRUE;
1512 }
1513
1514
1515 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1516 {
1517         GList *work;
1518         gboolean ret = TRUE;
1519         
1520         work = fd_list;
1521         while (work)
1522                 {
1523                 FileData *fd = work->data;
1524                 
1525                 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1526                 work = work->next;
1527                 }
1528         
1529         return ret;
1530 }
1531
1532 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1533 {
1534         GList *work;
1535         gboolean ret = TRUE;
1536         
1537         work = fd_list;
1538         while (work)
1539                 {
1540                 FileData *fd = work->data;
1541                 
1542                 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1543                 work = work->next;
1544                 }
1545         
1546         return ret;
1547 }
1548
1549 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1550 {
1551         GList *work;
1552         gboolean ret = TRUE;
1553         
1554         work = fd_list;
1555         while (work)
1556                 {
1557                 FileData *fd = work->data;
1558                 
1559                 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1560                 work = work->next;
1561                 }
1562         
1563         return ret;
1564 }
1565
1566
1567 /*
1568  * verify source and dest paths - dest image exists, etc.
1569  * it should detect all possible problems with the planned operation
1570  */
1571
1572 gint file_data_verify_ci(FileData *fd)
1573 {
1574         gint ret = CHANGE_OK;
1575         gchar *dir;
1576         gchar *dest_dir = NULL;
1577         
1578         g_assert(fd->change);
1579
1580         if (!isname(fd->path))
1581                 {
1582                 /* this probably should not happen */
1583                 ret |= CHANGE_NO_SRC;
1584                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1585                 return ret; 
1586                 }
1587                 
1588         dir = remove_level_from_path(fd->path);
1589         
1590         if (fd->change->dest) dest_dir = remove_level_from_path(fd->change->dest);
1591
1592         if (fd->change->type != FILEDATA_CHANGE_DELETE && 
1593             !access_file(fd->path, R_OK))
1594                 {
1595                 ret |= CHANGE_NO_READ_PERM;
1596                 DEBUG_1("Change checked: no read permission: %s", fd->path);
1597                 }
1598         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1599             !access_file(dir, W_OK))
1600                 {
1601                 ret |= CHANGE_NO_WRITE_PERM_DIR;
1602                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1603                 }
1604         else if (fd->change->type != FILEDATA_CHANGE_COPY && 
1605                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED && 
1606                  !access_file(fd->path, W_OK)) 
1607                 {
1608                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1609                 DEBUG_1("Change checked: no write permission: %s", fd->path);
1610                 }
1611
1612         if (fd->change->dest)
1613                 {
1614                 const gchar *dest_ext = extension_from_path(fd->change->dest);
1615                 if (!dest_ext) dest_ext = "";
1616                 
1617                 if (strcasecmp(fd->extension, dest_ext) != 0)
1618                         {
1619                         ret |= CHANGE_WARN_CHANGED_EXT;
1620                         DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1621                 }
1622
1623                 if (strcmp(fd->path, fd->change->dest) == 0)
1624                         {
1625                         ret |= CHANGE_WARN_SAME;
1626                         DEBUG_1("Change checked: source and destination is the same: %s -> %s", fd->path, fd->change->dest);
1627                 }
1628
1629                 if (!isdir(dest_dir))           
1630                         {
1631                         ret |= CHANGE_NO_DEST_DIR;
1632                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1633                         }
1634                 else if (!access_file(dest_dir, W_OK))
1635                         {
1636                         ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1637                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1638                         }
1639                 else if (isfile(fd->change->dest) && !access_file(fd->change->dest, W_OK) && (strcmp(fd->change->dest, fd->path) != 0))
1640                         {
1641                         ret |= CHANGE_NO_WRITE_PERM_DEST;
1642                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1643                         }
1644                 else if (isfile(fd->change->dest) && (strcmp(fd->change->dest, fd->path) != 0))
1645                         {
1646                         ret |= CHANGE_WARN_DEST_EXISTS;
1647                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1648                         }
1649                 else if (isdir(fd->change->dest) && (strcmp(fd->change->dest, fd->path) != 0))
1650                         {
1651                         ret |= CHANGE_DEST_EXISTS;
1652                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1653                         }
1654                 }
1655                 
1656         fd->change->error = ret;
1657         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1658
1659         g_free(dir);
1660         g_free(dest_dir);
1661         return ret;
1662 }
1663
1664  
1665 gint file_data_sc_verify_ci(FileData *fd)
1666 {
1667         GList *work;
1668         gint ret;
1669
1670         ret = file_data_verify_ci(fd);
1671
1672         work = fd->sidecar_files;
1673         while (work)
1674                 {
1675                 FileData *sfd = work->data;
1676
1677                 ret |= file_data_verify_ci(sfd);
1678                 work = work->next;
1679                 }
1680
1681         return ret;
1682 }
1683
1684 gchar *file_data_get_error_string(gint error)
1685 {
1686         GString *result = g_string_new("");
1687
1688         if (error & CHANGE_NO_SRC)
1689                 {
1690                 if (result->len > 0) g_string_append(result, ", ");
1691                 g_string_append(result, _("file or directory does not exist"));
1692                 }
1693
1694         if (error & CHANGE_DEST_EXISTS)
1695                 {
1696                 if (result->len > 0) g_string_append(result, ", ");
1697                 g_string_append(result, _("destination already exists"));
1698                 }
1699
1700         if (error & CHANGE_NO_WRITE_PERM_DEST)
1701                 {
1702                 if (result->len > 0) g_string_append(result, ", ");
1703                 g_string_append(result, _("destination can't be overwritten"));
1704                 }
1705
1706         if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
1707                 {
1708                 if (result->len > 0) g_string_append(result, ", ");
1709                 g_string_append(result, _("destination directory is not writable"));
1710                 }
1711
1712         if (error & CHANGE_NO_DEST_DIR)
1713                 {
1714                 if (result->len > 0) g_string_append(result, ", ");
1715                 g_string_append(result, _("destination directory does not exist"));
1716                 }
1717
1718         if (error & CHANGE_NO_WRITE_PERM_DIR)
1719                 {
1720                 if (result->len > 0) g_string_append(result, ", ");
1721                 g_string_append(result, _("source directory is not writable"));
1722                 }
1723
1724         if (error & CHANGE_NO_READ_PERM)
1725                 {
1726                 if (result->len > 0) g_string_append(result, ", ");
1727                 g_string_append(result, _("no read permission"));
1728                 }
1729
1730         if (error & CHANGE_WARN_NO_WRITE_PERM)
1731                 {
1732                 if (result->len > 0) g_string_append(result, ", ");
1733                 g_string_append(result, _("file is readonly"));
1734                 }
1735
1736         if (error & CHANGE_WARN_DEST_EXISTS)
1737                 {
1738                 if (result->len > 0) g_string_append(result, ", ");
1739                 g_string_append(result, _("destination already exists and will be overwritten"));
1740                 }
1741                 
1742         if (error & CHANGE_WARN_SAME)
1743                 {
1744                 if (result->len > 0) g_string_append(result, ", ");
1745                 g_string_append(result, _("source and destination is the same"));
1746                 }
1747
1748         if (error & CHANGE_WARN_CHANGED_EXT)
1749                 {
1750                 if (result->len > 0) g_string_append(result, ", ");
1751                 g_string_append(result, _("source and destination have different extension"));
1752                 }
1753
1754         return g_string_free(result, FALSE);
1755 }
1756
1757 gint file_data_sc_verify_ci_list(GList *list, gchar **desc)
1758 {
1759         gint all_errors = 0;
1760         gint common_errors = ~0; 
1761         gint num;
1762         gint *errors;
1763         gint i;
1764         
1765         if (!list) return 0;
1766         
1767         num = g_list_length(list);
1768         errors = g_new(int, num);
1769         GList *work = list;
1770         i = 0;
1771         while (work)
1772                 {
1773                 FileData *fd;
1774                 gint error;
1775
1776                 fd = work->data;
1777                 work = work->next;
1778                         
1779                 error = file_data_sc_verify_ci(fd);
1780                 all_errors |= error;
1781                 common_errors &= error;
1782                 
1783                 errors[i] = error;
1784                 
1785                 i++;
1786                 }
1787         
1788         if (desc && all_errors)
1789                 {
1790                 GString *result = g_string_new("");
1791                 
1792                 if (common_errors)
1793                         {
1794                         gchar *str = file_data_get_error_string(common_errors);
1795                         g_string_append(result, str);
1796                         g_string_append(result, "\n");
1797                         g_free(str);
1798                         }
1799                 
1800                 GList *work = list;
1801                 i = 0;
1802                 while (work)
1803                         {
1804                         FileData *fd;
1805                         gint error;
1806
1807                         fd = work->data;
1808                         work = work->next;
1809                         
1810                         error = errors[i] & ~common_errors;
1811                         
1812                         if (error)
1813                                 {
1814                                 gchar *str = file_data_get_error_string(error);
1815                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
1816                                 g_free(str);
1817                                 }
1818                         i++;
1819                         }
1820                 *desc = g_string_free(result, FALSE);
1821                 }
1822
1823         g_free(errors);
1824         return all_errors;
1825 }
1826
1827
1828 /*
1829  * perform the change described by FileFataChangeInfo
1830  * it is used for internal operations, 
1831  * this function actually operates with files on the filesystem
1832  * it should implement safe delete
1833  */
1834  
1835 static gboolean file_data_perform_move(FileData *fd)
1836 {
1837         g_assert(!strcmp(fd->change->source, fd->path));
1838         return move_file(fd->change->source, fd->change->dest);
1839 }
1840
1841 static gboolean file_data_perform_copy(FileData *fd)
1842 {
1843         g_assert(!strcmp(fd->change->source, fd->path));
1844         return copy_file(fd->change->source, fd->change->dest);
1845 }
1846
1847 static gboolean file_data_perform_delete(FileData *fd)
1848 {
1849         if (isdir(fd->path) && !islink(fd->path))
1850                 return rmdir_utf8(fd->path);
1851         else
1852                 return unlink_file(fd->path);
1853 }
1854
1855 static gboolean file_data_perform_ci(FileData *fd)
1856 {
1857         FileDataChangeType type = fd->change->type;
1858         switch (type)
1859                 {
1860                 case FILEDATA_CHANGE_MOVE:
1861                         return file_data_perform_move(fd);
1862                 case FILEDATA_CHANGE_COPY:
1863                         return file_data_perform_copy(fd);
1864                 case FILEDATA_CHANGE_RENAME:
1865                         return file_data_perform_move(fd); /* the same as move */
1866                 case FILEDATA_CHANGE_DELETE:
1867                         return file_data_perform_delete(fd);
1868                 case FILEDATA_CHANGE_UNSPECIFIED:
1869                         /* nothing to do here */
1870                         break;
1871                 }
1872         return TRUE;
1873 }
1874
1875
1876
1877 gboolean file_data_sc_perform_ci(FileData *fd)
1878 {
1879         GList *work;
1880         gboolean ret = TRUE;
1881         FileDataChangeType type = fd->change->type;
1882         
1883         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1884
1885         work = fd->sidecar_files;
1886         while (work)
1887                 {
1888                 FileData *sfd = work->data;
1889                 
1890                 if (!file_data_perform_ci(sfd)) ret = FALSE;
1891                 work = work->next;
1892                 }
1893         
1894         if (!file_data_perform_ci(fd)) ret = FALSE;
1895         
1896         return ret;
1897 }
1898
1899 /*
1900  * updates FileData structure according to FileDataChangeInfo
1901  */
1902
1903 static void file_data_apply_ci(FileData *fd)
1904 {
1905         FileDataChangeType type = fd->change->type;
1906         
1907         /* FIXME delete ?*/
1908         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1909                 {
1910                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1911                 file_data_planned_change_remove(fd);
1912                 
1913                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
1914                         {
1915                         /* this change overwrites another file which is already known to other modules
1916                            renaming fd would create duplicate FileData structure
1917                            the best thing we can do is nothing
1918                            FIXME: maybe we could copy stuff like marks
1919                         */
1920                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
1921                         }
1922                 else
1923                         {
1924                         file_data_set_path(fd, fd->change->dest);
1925                         }
1926                 }
1927         file_data_increment_version(fd);
1928         file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1929 }
1930
1931 gint file_data_sc_apply_ci(FileData *fd)
1932 {
1933         GList *work;
1934         FileDataChangeType type = fd->change->type;
1935         
1936         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1937
1938         work = fd->sidecar_files;
1939         while (work)
1940                 {
1941                 FileData *sfd = work->data;
1942                 
1943                 file_data_apply_ci(sfd);
1944                 work = work->next;
1945                 }
1946         
1947         file_data_apply_ci(fd);
1948         
1949         return TRUE;
1950 }
1951
1952 /*
1953  * notify other modules about the change described by FileFataChangeInfo
1954  */
1955  
1956 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1957    FIXME do we need the ignore_list? It looks like a workaround for ineffective
1958    implementation in view_file_list.c */
1959
1960
1961
1962
1963 typedef struct _NotifyData NotifyData;
1964
1965 struct _NotifyData {
1966         FileDataNotifyFunc func;
1967         gpointer data;
1968         NotifyPriority priority;
1969         };
1970
1971 static GList *notify_func_list = NULL;
1972
1973 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1974 {
1975         NotifyData *nda = (NotifyData *)a;
1976         NotifyData *ndb = (NotifyData *)b;
1977
1978         if (nda->priority < ndb->priority) return -1;
1979         if (nda->priority > ndb->priority) return 1;
1980         return 0;
1981 }
1982
1983 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1984 {
1985         NotifyData *nd;
1986         
1987         nd = g_new(NotifyData, 1);
1988         nd->func = func;
1989         nd->data = data;
1990         nd->priority = priority;
1991
1992         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1993         DEBUG_1("Notify func registered: %p", nd);
1994         
1995         return TRUE;
1996 }
1997
1998 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1999 {
2000         GList *work = notify_func_list;
2001         
2002         while (work)
2003                 {
2004                 NotifyData *nd = (NotifyData *)work->data;
2005         
2006                 if (nd->func == func && nd->data == data)
2007                         {
2008                         notify_func_list = g_list_delete_link(notify_func_list, work);
2009                         g_free(nd);
2010                         DEBUG_1("Notify func unregistered: %p", nd);
2011                         return TRUE;
2012                         }
2013                 work = work->next;
2014                 }
2015
2016         return FALSE;
2017 }
2018
2019
2020 void file_data_send_notification(FileData *fd, NotifyType type)
2021 {
2022         GList *work = notify_func_list;
2023
2024         while (work)
2025                 {
2026                 NotifyData *nd = (NotifyData *)work->data;
2027                 
2028                 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
2029                 nd->func(fd, type, nd->data);
2030                 work = work->next;
2031                 }
2032 }
2033
2034 static GHashTable *file_data_monitor_pool = NULL;
2035 static gint realtime_monitor_id = -1;
2036
2037 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2038 {
2039         FileData *fd = key;
2040
2041         file_data_check_changed_files(fd);
2042         
2043         DEBUG_1("monitor %s", fd->path);
2044 }
2045
2046 static gboolean realtime_monitor_cb(gpointer data)
2047 {
2048         if (!options->update_on_time_change) return TRUE;
2049         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2050         return TRUE;
2051 }
2052
2053 gint file_data_register_real_time_monitor(FileData *fd)
2054 {
2055         gint count = 0;
2056         
2057         file_data_ref(fd);
2058         
2059         if (!file_data_monitor_pool)
2060                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2061         
2062         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2063
2064         DEBUG_1("Register realtime %d %s", count, fd->path);
2065         
2066         count++;
2067         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2068         
2069         if (realtime_monitor_id == -1)
2070                 {
2071                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2072                 }
2073         
2074         return TRUE;
2075 }
2076
2077 gint file_data_unregister_real_time_monitor(FileData *fd)
2078 {
2079         gint count;
2080
2081         g_assert(file_data_monitor_pool);
2082         
2083         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2084         
2085         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2086         
2087         g_assert(count > 0);
2088         
2089         count--;
2090         
2091         if (count == 0)
2092                 g_hash_table_remove(file_data_monitor_pool, fd);
2093         else
2094                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2095
2096         file_data_unref(fd);
2097         
2098         if (g_hash_table_size(file_data_monitor_pool) == 0)
2099                 {
2100                 g_source_remove(realtime_monitor_id);
2101                 realtime_monitor_id = -1;
2102                 return FALSE;
2103                 }
2104         
2105         return TRUE;
2106 }