Optimize redundant tests.
[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 gint file_data_get_user_orientation(FileData *fd)
1072 {
1073         return fd->user_orientation;
1074 }
1075
1076 void file_data_set_user_orientation(FileData *fd, gint value)
1077 {
1078         if (fd->user_orientation == value) return;
1079
1080         fd->user_orientation = value;
1081         file_data_increment_version(fd);
1082         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1083 }
1084
1085
1086
1087 /*
1088  * file_data    - operates on the given fd
1089  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1090  */
1091
1092
1093 /* return list of sidecar file extensions in a string */
1094 gchar *file_data_sc_list_to_string(FileData *fd)
1095 {
1096         GList *work;
1097         GString *result = g_string_new("");
1098
1099         work = fd->sidecar_files;
1100         while (work)
1101                 {
1102                 FileData *sfd = work->data;
1103
1104                 result = g_string_append(result, "+ ");
1105                 result = g_string_append(result, sfd->extension);
1106                 work = work->next;
1107                 if (work) result = g_string_append_c(result, ' ');
1108                 }
1109
1110         return g_string_free(result, FALSE);
1111 }
1112
1113
1114                                 
1115 /* 
1116  * add FileDataChangeInfo (see typedefs.h) for the given operation 
1117  * uses file_data_add_change_info
1118  *
1119  * fails if the fd->change already exists - change operations can't run in parallel
1120  * fd->change_info works as a lock
1121  *
1122  * dest can be NULL - in this case the current name is used for now, it will
1123  * be changed later 
1124  */
1125
1126 /*
1127    FileDataChangeInfo types:
1128    COPY
1129    MOVE   - path is changed, name may be changed too
1130    RENAME - path remains unchanged, name is changed
1131             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1132             sidecar names are changed too, extensions are not changed
1133    DELETE
1134    UPDATE - file size, date or grouping has been changed 
1135 */
1136
1137 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1138 {
1139         FileDataChangeInfo *fdci;
1140
1141         if (fd->change) return FALSE;
1142
1143         fdci = g_new0(FileDataChangeInfo, 1);
1144
1145         fdci->type = type;
1146
1147         if (src)
1148                 fdci->source = g_strdup(src);
1149         else
1150                 fdci->source = g_strdup(fd->path);
1151
1152         if (dest)
1153                 fdci->dest = g_strdup(dest);
1154
1155         fd->change = fdci;
1156         
1157         return TRUE;
1158 }
1159
1160 static void file_data_planned_change_remove(FileData *fd)
1161 {
1162         if (file_data_planned_change_hash && 
1163             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1164                 {
1165                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1166                         {
1167                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1168                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1169                         file_data_unref(fd);
1170                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1171                                 {
1172                                 g_hash_table_destroy(file_data_planned_change_hash);
1173                                 file_data_planned_change_hash = NULL;
1174                                 DEBUG_1("planned change: empty");
1175                                 }
1176                         }
1177                 }
1178 }
1179  
1180
1181 void file_data_free_ci(FileData *fd)
1182 {
1183         FileDataChangeInfo *fdci = fd->change;
1184
1185         if (!fdci)
1186                 return;
1187
1188         file_data_planned_change_remove(fd);
1189
1190         g_free(fdci->source);
1191         g_free(fdci->dest);
1192
1193         g_free(fdci);
1194
1195         fd->change = NULL;
1196 }
1197
1198  
1199 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1200 {
1201         GList *work;
1202
1203         if (fd->parent) fd = fd->parent;
1204         
1205         if (fd->change) return FALSE;
1206         
1207         work = fd->sidecar_files;
1208         while (work)
1209                 {
1210                 FileData *sfd = work->data;
1211                 
1212                 if (sfd->change) return FALSE;
1213                 work = work->next;
1214                 }
1215
1216         file_data_add_ci(fd, type, NULL, NULL);
1217         
1218         work = fd->sidecar_files;
1219         while (work)
1220                 {
1221                 FileData *sfd = work->data;
1222                 
1223                 file_data_add_ci(sfd, type, NULL, NULL);
1224                 work = work->next;
1225                 }
1226                 
1227         return TRUE;    
1228 }
1229
1230 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1231 {
1232         GList *work;
1233         
1234         if (fd->parent) fd = fd->parent;
1235         
1236         if (!fd->change || fd->change->type != type) return FALSE;
1237         
1238         work = fd->sidecar_files;
1239         while (work)
1240                 {
1241                 FileData *sfd = work->data;
1242
1243                 if (!sfd->change || sfd->change->type != type) return FALSE;
1244                 work = work->next;
1245                 }
1246
1247         return TRUE;
1248 }
1249
1250
1251 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1252 {
1253         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1254         file_data_sc_update_ci_copy(fd, dest_path);
1255         return TRUE;
1256 }
1257
1258 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1259 {
1260         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1261         file_data_sc_update_ci_move(fd, dest_path);
1262         return TRUE;
1263 }
1264
1265 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1266 {
1267         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1268         file_data_sc_update_ci_rename(fd, dest_path);
1269         return TRUE;
1270 }
1271
1272 gboolean file_data_sc_add_ci_delete(FileData *fd)
1273 {
1274         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1275 }
1276
1277 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1278 {
1279         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1280         file_data_sc_update_ci_unspecified(fd, dest_path);
1281         return TRUE;
1282 }
1283
1284 void file_data_sc_free_ci(FileData *fd)
1285 {
1286         GList *work;
1287
1288         if (fd->parent) fd = fd->parent;
1289         
1290         file_data_free_ci(fd);
1291         
1292         work = fd->sidecar_files;
1293         while (work)
1294                 {
1295                 FileData *sfd = work->data;
1296         
1297                 file_data_free_ci(sfd);
1298                 work = work->next;
1299                 }
1300 }
1301
1302 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1303 {
1304         GList *work;
1305         gboolean ret = TRUE;
1306
1307         work = fd_list;
1308         while (work)
1309                 {
1310                 FileData *fd = work->data;
1311         
1312                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1313                 work = work->next;
1314                 }
1315
1316         return ret;
1317 }
1318
1319 static void file_data_sc_revert_ci_list(GList *fd_list)
1320 {
1321         GList *work;
1322         
1323         work = fd_list;
1324         while (work)
1325                 {
1326                 FileData *fd = work->data;
1327                 
1328                 file_data_sc_free_ci(fd);
1329                 work = work->prev;
1330                 }
1331 }
1332
1333 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1334 {
1335         GList *work;
1336         
1337         work = fd_list;
1338         while (work)
1339                 {
1340                 FileData *fd = work->data;
1341                 
1342                 if (!func(fd, dest)) 
1343                         {
1344                         file_data_sc_revert_ci_list(work->prev);
1345                         return FALSE;
1346                         }
1347                 work = work->next;
1348                 }
1349         
1350         return TRUE;
1351 }
1352
1353 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1354 {
1355         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1356 }
1357
1358 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1359 {
1360         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1361 }
1362
1363 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1364 {
1365         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1366 }
1367
1368 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1369 {
1370         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1371 }
1372
1373 void file_data_sc_free_ci_list(GList *fd_list)
1374 {
1375         GList *work;
1376         
1377         work = fd_list;
1378         while (work)
1379                 {
1380                 FileData *fd = work->data;
1381                 
1382                 file_data_sc_free_ci(fd);
1383                 work = work->next;
1384                 }
1385 }
1386
1387 /* 
1388  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1389  * fails if fd->change does not exist or the change type does not match
1390  */
1391
1392 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1393 {
1394         FileDataChangeType type = fd->change->type;
1395         
1396         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1397                 {
1398                 FileData *ofd;
1399                 
1400                 if (!file_data_planned_change_hash)
1401                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1402                 
1403                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1404                         {
1405                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1406                         g_hash_table_remove(file_data_planned_change_hash, old_path);
1407                         file_data_unref(fd);
1408                         }
1409
1410                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1411                 if (ofd != fd)
1412                         {
1413                         if (ofd)
1414                                 {
1415                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1416                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
1417                                 file_data_unref(ofd);
1418                                 }
1419                         
1420                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1421                         file_data_ref(fd);
1422                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1423                         }
1424                 }
1425 }
1426
1427 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1428 {
1429         gchar *old_path = fd->change->dest;
1430
1431         fd->change->dest = g_strdup(dest_path);
1432         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1433         g_free(old_path);
1434 }
1435
1436 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1437 {
1438         const char *extension = extension_from_path(fd->change->source);
1439         gchar *base = remove_extension_from_path(dest_path);
1440         gchar *old_path = fd->change->dest;
1441         
1442         fd->change->dest = g_strconcat(base, extension, NULL);
1443         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1444         
1445         g_free(old_path);
1446         g_free(base);
1447 }
1448
1449 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1450 {
1451         GList *work;
1452         gchar *dest_path_full = NULL;
1453         
1454         if (fd->parent) fd = fd->parent;
1455         
1456         if (!dest_path) 
1457                 {
1458                 dest_path = fd->path;
1459                 }
1460         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1461                 {
1462                 gchar *dir = remove_level_from_path(fd->path);
1463                 
1464                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1465                 g_free(dir);
1466                 dest_path = dest_path_full;
1467                 }
1468         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1469                 {
1470                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1471                 dest_path = dest_path_full;
1472                 }
1473                 
1474         file_data_update_ci_dest(fd, dest_path);
1475         
1476         work = fd->sidecar_files;
1477         while (work)
1478                 {
1479                 FileData *sfd = work->data;
1480                 
1481                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1482                 work = work->next;
1483                 }
1484         
1485         g_free(dest_path_full);
1486 }
1487
1488 static gint file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1489 {
1490         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1491         file_data_sc_update_ci(fd, dest_path);
1492         return TRUE;
1493 }
1494
1495 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1496 {
1497         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1498 }
1499         
1500 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1501 {
1502         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1503 }
1504
1505 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1506 {
1507         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1508 }
1509
1510 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1511 {
1512         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1513 }
1514
1515 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1516                                                       const gchar *dest,
1517                                                       gboolean (*func)(FileData *, const gchar *))
1518 {
1519         GList *work;
1520         gboolean ret = TRUE;
1521         
1522         work = fd_list;
1523         while (work)
1524                 {
1525                 FileData *fd = work->data;
1526                 
1527                 if (!func(fd, dest)) ret = FALSE;
1528                 work = work->next;
1529                 }
1530         
1531         return ret;
1532 }
1533
1534 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1535 {
1536         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1537 }
1538
1539 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1540 {
1541         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1542 }
1543
1544 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1545 {
1546         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1547 }
1548
1549
1550 /*
1551  * verify source and dest paths - dest image exists, etc.
1552  * it should detect all possible problems with the planned operation
1553  */
1554
1555 gint file_data_verify_ci(FileData *fd)
1556 {
1557         gint ret = CHANGE_OK;
1558         gchar *dir;
1559         
1560         if (!fd->change)
1561                 {
1562                 DEBUG_1("Change checked: no change info: %s", fd->path);
1563                 return ret; 
1564                 }
1565
1566         if (!isname(fd->path))
1567                 {
1568                 /* this probably should not happen */
1569                 ret |= CHANGE_NO_SRC;
1570                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1571                 return ret; 
1572                 }
1573                 
1574         dir = remove_level_from_path(fd->path);
1575         
1576         if (fd->change->type != FILEDATA_CHANGE_DELETE && 
1577             !access_file(fd->path, R_OK))
1578                 {
1579                 ret |= CHANGE_NO_READ_PERM;
1580                 DEBUG_1("Change checked: no read permission: %s", fd->path);
1581                 }
1582         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1583             !access_file(dir, W_OK))
1584                 {
1585                 ret |= CHANGE_NO_WRITE_PERM_DIR;
1586                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1587                 }
1588         else if (fd->change->type != FILEDATA_CHANGE_COPY && 
1589                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED && 
1590                  !access_file(fd->path, W_OK)) 
1591                 {
1592                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1593                 DEBUG_1("Change checked: no write permission: %s", fd->path);
1594                 }
1595
1596         if (fd->change->dest)
1597                 {
1598                 gchar *dest_dir;
1599                 const gchar *dest_ext = extension_from_path(fd->change->dest);
1600                 if (!dest_ext) dest_ext = "";
1601                 
1602                 if (strcasecmp(fd->extension, dest_ext) != 0)
1603                         {
1604                         ret |= CHANGE_WARN_CHANGED_EXT;
1605                         DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1606                 }
1607
1608                 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED && /* FIXME this is now needed for running editors */
1609                     strcmp(fd->path, fd->change->dest) == 0)
1610                         {
1611                         ret |= CHANGE_WARN_SAME;
1612                         DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
1613                 }
1614
1615                 dest_dir = remove_level_from_path(fd->change->dest);
1616
1617                 if (!isdir(dest_dir))           
1618                         {
1619                         ret |= CHANGE_NO_DEST_DIR;
1620                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1621                         }
1622                 else if (!access_file(dest_dir, W_OK))
1623                         {
1624                         ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1625                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1626                         }
1627                 else if (isfile(fd->change->dest) && (strcmp(fd->change->dest, fd->path) != 0))
1628                         {
1629                         if (!access_file(fd->change->dest, W_OK))
1630                                 {
1631                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
1632                                 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1633                                 }
1634                         else
1635                                 {
1636                                 ret |= CHANGE_WARN_DEST_EXISTS;
1637                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1638                                 }
1639                         }
1640                 else if (isdir(fd->change->dest) && (strcmp(fd->change->dest, fd->path) != 0))
1641                         {
1642                         ret |= CHANGE_DEST_EXISTS;
1643                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1644                         }
1645
1646                 g_free(dest_dir);
1647                 }
1648                 
1649         fd->change->error = ret;
1650         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1651
1652         g_free(dir);
1653         return ret;
1654 }
1655
1656  
1657 gint file_data_sc_verify_ci(FileData *fd)
1658 {
1659         GList *work;
1660         gint ret;
1661
1662         ret = file_data_verify_ci(fd);
1663
1664         work = fd->sidecar_files;
1665         while (work)
1666                 {
1667                 FileData *sfd = work->data;
1668
1669                 ret |= file_data_verify_ci(sfd);
1670                 work = work->next;
1671                 }
1672
1673         return ret;
1674 }
1675
1676 gchar *file_data_get_error_string(gint error)
1677 {
1678         GString *result = g_string_new("");
1679
1680         if (error & CHANGE_NO_SRC)
1681                 {
1682                 if (result->len > 0) g_string_append(result, ", ");
1683                 g_string_append(result, _("file or directory does not exist"));
1684                 }
1685
1686         if (error & CHANGE_DEST_EXISTS)
1687                 {
1688                 if (result->len > 0) g_string_append(result, ", ");
1689                 g_string_append(result, _("destination already exists"));
1690                 }
1691
1692         if (error & CHANGE_NO_WRITE_PERM_DEST)
1693                 {
1694                 if (result->len > 0) g_string_append(result, ", ");
1695                 g_string_append(result, _("destination can't be overwritten"));
1696                 }
1697
1698         if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
1699                 {
1700                 if (result->len > 0) g_string_append(result, ", ");
1701                 g_string_append(result, _("destination directory is not writable"));
1702                 }
1703
1704         if (error & CHANGE_NO_DEST_DIR)
1705                 {
1706                 if (result->len > 0) g_string_append(result, ", ");
1707                 g_string_append(result, _("destination directory does not exist"));
1708                 }
1709
1710         if (error & CHANGE_NO_WRITE_PERM_DIR)
1711                 {
1712                 if (result->len > 0) g_string_append(result, ", ");
1713                 g_string_append(result, _("source directory is not writable"));
1714                 }
1715
1716         if (error & CHANGE_NO_READ_PERM)
1717                 {
1718                 if (result->len > 0) g_string_append(result, ", ");
1719                 g_string_append(result, _("no read permission"));
1720                 }
1721
1722         if (error & CHANGE_WARN_NO_WRITE_PERM)
1723                 {
1724                 if (result->len > 0) g_string_append(result, ", ");
1725                 g_string_append(result, _("file is readonly"));
1726                 }
1727
1728         if (error & CHANGE_WARN_DEST_EXISTS)
1729                 {
1730                 if (result->len > 0) g_string_append(result, ", ");
1731                 g_string_append(result, _("destination already exists and will be overwritten"));
1732                 }
1733                 
1734         if (error & CHANGE_WARN_SAME)
1735                 {
1736                 if (result->len > 0) g_string_append(result, ", ");
1737                 g_string_append(result, _("source and destination are the same"));
1738                 }
1739
1740         if (error & CHANGE_WARN_CHANGED_EXT)
1741                 {
1742                 if (result->len > 0) g_string_append(result, ", ");
1743                 g_string_append(result, _("source and destination have different extension"));
1744                 }
1745
1746         return g_string_free(result, FALSE);
1747 }
1748
1749 gint file_data_sc_verify_ci_list(GList *list, gchar **desc)
1750 {
1751         gint all_errors = 0;
1752         gint common_errors = ~0; 
1753         gint num;
1754         gint *errors;
1755         gint i;
1756         
1757         if (!list) return 0;
1758         
1759         num = g_list_length(list);
1760         errors = g_new(int, num);
1761         GList *work = list;
1762         i = 0;
1763         while (work)
1764                 {
1765                 FileData *fd;
1766                 gint error;
1767
1768                 fd = work->data;
1769                 work = work->next;
1770                         
1771                 error = file_data_sc_verify_ci(fd);
1772                 all_errors |= error;
1773                 common_errors &= error;
1774                 
1775                 errors[i] = error;
1776                 
1777                 i++;
1778                 }
1779         
1780         if (desc && all_errors)
1781                 {
1782                 GString *result = g_string_new("");
1783                 
1784                 if (common_errors)
1785                         {
1786                         gchar *str = file_data_get_error_string(common_errors);
1787                         g_string_append(result, str);
1788                         g_string_append(result, "\n");
1789                         g_free(str);
1790                         }
1791                 
1792                 GList *work = list;
1793                 i = 0;
1794                 while (work)
1795                         {
1796                         FileData *fd;
1797                         gint error;
1798
1799                         fd = work->data;
1800                         work = work->next;
1801                         
1802                         error = errors[i] & ~common_errors;
1803                         
1804                         if (error)
1805                                 {
1806                                 gchar *str = file_data_get_error_string(error);
1807                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
1808                                 g_free(str);
1809                                 }
1810                         i++;
1811                         }
1812                 *desc = g_string_free(result, FALSE);
1813                 }
1814
1815         g_free(errors);
1816         return all_errors;
1817 }
1818
1819
1820 /*
1821  * perform the change described by FileFataChangeInfo
1822  * it is used for internal operations, 
1823  * this function actually operates with files on the filesystem
1824  * it should implement safe delete
1825  */
1826  
1827 static gboolean file_data_perform_move(FileData *fd)
1828 {
1829         g_assert(!strcmp(fd->change->source, fd->path));
1830         return move_file(fd->change->source, fd->change->dest);
1831 }
1832
1833 static gboolean file_data_perform_copy(FileData *fd)
1834 {
1835         g_assert(!strcmp(fd->change->source, fd->path));
1836         return copy_file(fd->change->source, fd->change->dest);
1837 }
1838
1839 static gboolean file_data_perform_delete(FileData *fd)
1840 {
1841         if (isdir(fd->path) && !islink(fd->path))
1842                 return rmdir_utf8(fd->path);
1843         else
1844                 return unlink_file(fd->path);
1845 }
1846
1847 static gboolean file_data_perform_ci(FileData *fd)
1848 {
1849         FileDataChangeType type = fd->change->type;
1850         switch (type)
1851                 {
1852                 case FILEDATA_CHANGE_MOVE:
1853                         return file_data_perform_move(fd);
1854                 case FILEDATA_CHANGE_COPY:
1855                         return file_data_perform_copy(fd);
1856                 case FILEDATA_CHANGE_RENAME:
1857                         return file_data_perform_move(fd); /* the same as move */
1858                 case FILEDATA_CHANGE_DELETE:
1859                         return file_data_perform_delete(fd);
1860                 case FILEDATA_CHANGE_UNSPECIFIED:
1861                         /* nothing to do here */
1862                         break;
1863                 }
1864         return TRUE;
1865 }
1866
1867
1868
1869 gboolean file_data_sc_perform_ci(FileData *fd)
1870 {
1871         GList *work;
1872         gboolean ret = TRUE;
1873         FileDataChangeType type = fd->change->type;
1874         
1875         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1876
1877         work = fd->sidecar_files;
1878         while (work)
1879                 {
1880                 FileData *sfd = work->data;
1881                 
1882                 if (!file_data_perform_ci(sfd)) ret = FALSE;
1883                 work = work->next;
1884                 }
1885         
1886         if (!file_data_perform_ci(fd)) ret = FALSE;
1887         
1888         return ret;
1889 }
1890
1891 /*
1892  * updates FileData structure according to FileDataChangeInfo
1893  */
1894
1895 static void file_data_apply_ci(FileData *fd)
1896 {
1897         FileDataChangeType type = fd->change->type;
1898         
1899         /* FIXME delete ?*/
1900         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1901                 {
1902                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1903                 file_data_planned_change_remove(fd);
1904                 
1905                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
1906                         {
1907                         /* this change overwrites another file which is already known to other modules
1908                            renaming fd would create duplicate FileData structure
1909                            the best thing we can do is nothing
1910                            FIXME: maybe we could copy stuff like marks
1911                         */
1912                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
1913                         }
1914                 else
1915                         {
1916                         file_data_set_path(fd, fd->change->dest);
1917                         }
1918                 }
1919         file_data_increment_version(fd);
1920         file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1921 }
1922
1923 gint file_data_sc_apply_ci(FileData *fd)
1924 {
1925         GList *work;
1926         FileDataChangeType type = fd->change->type;
1927         
1928         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1929
1930         work = fd->sidecar_files;
1931         while (work)
1932                 {
1933                 FileData *sfd = work->data;
1934                 
1935                 file_data_apply_ci(sfd);
1936                 work = work->next;
1937                 }
1938         
1939         file_data_apply_ci(fd);
1940         
1941         return TRUE;
1942 }
1943
1944 /*
1945  * notify other modules about the change described by FileFataChangeInfo
1946  */
1947  
1948 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1949    FIXME do we need the ignore_list? It looks like a workaround for ineffective
1950    implementation in view_file_list.c */
1951
1952
1953
1954
1955 typedef struct _NotifyData NotifyData;
1956
1957 struct _NotifyData {
1958         FileDataNotifyFunc func;
1959         gpointer data;
1960         NotifyPriority priority;
1961         };
1962
1963 static GList *notify_func_list = NULL;
1964
1965 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1966 {
1967         NotifyData *nda = (NotifyData *)a;
1968         NotifyData *ndb = (NotifyData *)b;
1969
1970         if (nda->priority < ndb->priority) return -1;
1971         if (nda->priority > ndb->priority) return 1;
1972         return 0;
1973 }
1974
1975 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1976 {
1977         NotifyData *nd;
1978         
1979         nd = g_new(NotifyData, 1);
1980         nd->func = func;
1981         nd->data = data;
1982         nd->priority = priority;
1983
1984         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1985         DEBUG_1("Notify func registered: %p", nd);
1986         
1987         return TRUE;
1988 }
1989
1990 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1991 {
1992         GList *work = notify_func_list;
1993         
1994         while (work)
1995                 {
1996                 NotifyData *nd = (NotifyData *)work->data;
1997         
1998                 if (nd->func == func && nd->data == data)
1999                         {
2000                         notify_func_list = g_list_delete_link(notify_func_list, work);
2001                         g_free(nd);
2002                         DEBUG_1("Notify func unregistered: %p", nd);
2003                         return TRUE;
2004                         }
2005                 work = work->next;
2006                 }
2007
2008         return FALSE;
2009 }
2010
2011
2012 void file_data_send_notification(FileData *fd, NotifyType type)
2013 {
2014         GList *work = notify_func_list;
2015
2016         while (work)
2017                 {
2018                 NotifyData *nd = (NotifyData *)work->data;
2019                 
2020                 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
2021                 nd->func(fd, type, nd->data);
2022                 work = work->next;
2023                 }
2024 }
2025
2026 static GHashTable *file_data_monitor_pool = NULL;
2027 static gint realtime_monitor_id = -1;
2028
2029 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2030 {
2031         FileData *fd = key;
2032
2033         file_data_check_changed_files(fd);
2034         
2035         DEBUG_1("monitor %s", fd->path);
2036 }
2037
2038 static gboolean realtime_monitor_cb(gpointer data)
2039 {
2040         if (!options->update_on_time_change) return TRUE;
2041         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2042         return TRUE;
2043 }
2044
2045 gint file_data_register_real_time_monitor(FileData *fd)
2046 {
2047         gint count;
2048         
2049         file_data_ref(fd);
2050         
2051         if (!file_data_monitor_pool)
2052                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2053         
2054         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2055
2056         DEBUG_1("Register realtime %d %s", count, fd->path);
2057         
2058         count++;
2059         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2060         
2061         if (realtime_monitor_id == -1)
2062                 {
2063                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2064                 }
2065         
2066         return TRUE;
2067 }
2068
2069 gint file_data_unregister_real_time_monitor(FileData *fd)
2070 {
2071         gint count;
2072
2073         g_assert(file_data_monitor_pool);
2074         
2075         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2076         
2077         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2078         
2079         g_assert(count > 0);
2080         
2081         count--;
2082         
2083         if (count == 0)
2084                 g_hash_table_remove(file_data_monitor_pool, fd);
2085         else
2086                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2087
2088         file_data_unref(fd);
2089         
2090         if (g_hash_table_size(file_data_monitor_pool) == 0)
2091                 {
2092                 g_source_remove(realtime_monitor_id);
2093                 realtime_monitor_id = -1;
2094                 return FALSE;
2095                 }
2096         
2097         return TRUE;
2098 }