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