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