call notify functions in in an idle call
[geeqie.git] / src / filedata.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 - 2010 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 "thumb_standard.h"
20 #include "ui_fileops.h"
21 #include "metadata.h"
22 #include "trash.h"
23 #include "histogram.h"
24
25 #include <errno.h>
26
27 static GHashTable *file_data_pool = NULL;
28 static GHashTable *file_data_planned_change_hash = NULL;
29
30 static gint sidecar_file_priority(const gchar *path);
31 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars);
32
33
34 /*
35  *-----------------------------------------------------------------------------
36  * text conversion utils
37  *-----------------------------------------------------------------------------
38  */
39
40 gchar *text_from_size(gint64 size)
41 {
42         gchar *a, *b;
43         gchar *s, *d;
44         gint l, n, i;
45
46         /* what I would like to use is printf("%'d", size)
47          * BUT: not supported on every libc :(
48          */
49         if (size > G_MAXUINT)
50                 {
51                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
52                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
53                 }
54         else
55                 {
56                 a = g_strdup_printf("%d", (guint)size);
57                 }
58         l = strlen(a);
59         n = (l - 1)/ 3;
60         if (n < 1) return a;
61
62         b = g_new(gchar, l + n + 1);
63
64         s = a;
65         d = b;
66         i = l - n * 3;
67         while (*s != '\0')
68                 {
69                 if (i < 1)
70                         {
71                         i = 3;
72                         *d = ',';
73                         d++;
74                         }
75
76                 *d = *s;
77                 s++;
78                 d++;
79                 i--;
80                 }
81         *d = '\0';
82
83         g_free(a);
84         return b;
85 }
86
87 gchar *text_from_size_abrev(gint64 size)
88 {
89         if (size < (gint64)1024)
90                 {
91                 return g_strdup_printf(_("%d bytes"), (gint)size);
92                 }
93         if (size < (gint64)1048576)
94                 {
95                 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
96                 }
97         if (size < (gint64)1073741824)
98                 {
99                 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
100                 }
101
102         /* to avoid overflowing the gdouble, do division in two steps */
103         size /= 1048576;
104         return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
105 }
106
107 /* note: returned string is valid until next call to text_from_time() */
108 const gchar *text_from_time(time_t t)
109 {
110         static gchar *ret = NULL;
111         gchar buf[128];
112         gint buflen;
113         struct tm *btime;
114         GError *error = NULL;
115
116         btime = localtime(&t);
117
118         /* the %x warning about 2 digit years is not an error */
119         buflen = strftime(buf, sizeof(buf), "%x %X", btime);
120         if (buflen < 1) return "";
121
122         g_free(ret);
123         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
124         if (error)
125                 {
126                 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
127                 g_error_free(error);
128                 return "";
129                 }
130
131         return ret;
132 }
133
134 /*
135  *-----------------------------------------------------------------------------
136  * file info struct
137  *-----------------------------------------------------------------------------
138  */
139
140 static FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
141 static void file_data_check_sidecars(const GList *basename_list);
142 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
143
144
145 void file_data_increment_version(FileData *fd)
146 {
147         fd->version++;
148         fd->valid_marks = 0;
149         if (fd->parent) 
150                 {
151                 fd->parent->version++;
152                 fd->parent->valid_marks = 0;
153                 }
154 }
155
156 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
157 {
158         const FileData *fda = a;
159         const FileData *fdb = b;
160         
161         if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
162         if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
163         
164         return strcmp(fdb->extension, fda->extension);
165 }
166
167 static GHashTable *file_data_basename_hash_new(void)
168 {
169         return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
170 }
171
172 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
173 {
174         GList *list;
175         gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
176
177         list = g_hash_table_lookup(basename_hash, basename);
178         
179         if (!g_list_find(list, fd))
180                 {
181                 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
182                 g_hash_table_insert(basename_hash, basename, list);
183                 }
184         else 
185                 {
186                 g_free(basename);
187                 }
188         return list;
189 }
190
191 #if 0
192 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
193 {
194         GList *list;
195         gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
196         
197         list = g_hash_table_lookup(basename_hash, basename);
198         
199         if (!g_list_find(list, fd)) return;
200         
201         list = g_list_remove(list, fd);
202         file_data_unref(fd);
203         
204         if (list)
205                 {
206                 g_hash_table_insert(basename_hash, basename, list);
207                 }
208         else 
209                 {
210                 g_hash_table_remove(basename_hash, basename);
211                 g_free(basename);
212                 }
213 }
214 #endif
215
216 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
217 {
218         filelist_free((GList *)value);
219 }
220
221 static void file_data_basename_hash_free(GHashTable *basename_hash)
222 {
223         g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL); 
224         g_hash_table_destroy(basename_hash);
225 }
226
227 static void file_data_set_collate_keys(FileData *fd)
228 {
229         gchar *caseless_name;
230
231         caseless_name = g_utf8_casefold(fd->name, -1);
232
233         g_free(fd->collate_key_name);
234         g_free(fd->collate_key_name_nocase);
235
236 #if 0 && GLIB_CHECK_VERSION(2, 8, 0)
237         fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
238         fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
239 #else
240         fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
241         fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
242 #endif
243         g_free(caseless_name);
244 }
245
246 static void file_data_set_path(FileData *fd, const gchar *path)
247 {
248         g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
249         g_assert(file_data_pool);
250
251         g_free(fd->path);
252
253         if (fd->original_path)
254                 {
255                 g_hash_table_remove(file_data_pool, fd->original_path);
256                 g_free(fd->original_path);
257                 }
258
259         g_assert(!g_hash_table_lookup(file_data_pool, path));
260
261         fd->original_path = g_strdup(path);
262         g_hash_table_insert(file_data_pool, fd->original_path, fd);
263
264         if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
265                 {
266                 fd->path = g_strdup(path);
267                 fd->name = fd->path;
268                 fd->extension = fd->name + 1;
269                 file_data_set_collate_keys(fd);
270                 return;
271                 }
272
273         fd->path = g_strdup(path);
274         fd->name = filename_from_path(fd->path);
275
276         if (strcmp(fd->name, "..") == 0)
277                 {
278                 gchar *dir = remove_level_from_path(path);
279                 g_free(fd->path);
280                 fd->path = remove_level_from_path(dir);
281                 g_free(dir);
282                 fd->name = "..";
283                 fd->extension = fd->name + 2;
284                 file_data_set_collate_keys(fd);
285                 return;
286                 }
287         else if (strcmp(fd->name, ".") == 0)
288                 {
289                 g_free(fd->path);
290                 fd->path = remove_level_from_path(path);
291                 fd->name = ".";
292                 fd->extension = fd->name + 1;
293                 file_data_set_collate_keys(fd);
294                 return;
295                 }
296
297         fd->extension = extension_from_path(fd->path);
298         if (fd->extension == NULL)
299                 {
300                 fd->extension = fd->name + strlen(fd->name);
301                 }
302                 
303         fd->sidecar_priority = sidecar_file_priority(fd->extension);
304         file_data_set_collate_keys(fd);
305 }
306
307 static gboolean file_data_check_changed(FileData *fd, struct stat *st)
308 {
309         if (fd->size != st->st_size ||
310             fd->date != st->st_mtime)
311                 {
312                 fd->size = st->st_size;
313                 fd->date = st->st_mtime;
314                 fd->mode = st->st_mode;
315                 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
316                 fd->thumb_pixbuf = NULL;
317                 file_data_increment_version(fd);
318                 file_data_send_notification(fd, NOTIFY_REREAD);
319                 return TRUE;
320                 }
321         return FALSE;
322 }
323
324 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
325 {
326         gboolean ret = FALSE;
327         GList *work;
328         
329         ret = file_data_check_changed(fd, st);
330
331         work = fd->sidecar_files;
332         while (work)
333                 {
334                 FileData *sfd = work->data;
335                 struct stat st;
336                 work = work->next;
337
338                 if (!stat_utf8(sfd->path, &st))
339                         {
340                         fd->size = 0;
341                         fd->date = 0;
342                         file_data_disconnect_sidecar_file(fd, sfd);
343                         ret = TRUE;
344                         continue;
345                         }
346
347                 ret |= file_data_check_changed_files_recursive(sfd, &st);
348                 }
349         return ret;
350 }
351
352
353 gboolean file_data_check_changed_files(FileData *fd)
354 {
355         gboolean ret = FALSE;
356         struct stat st;
357         
358         if (fd->parent) fd = fd->parent;
359
360         if (!stat_utf8(fd->path, &st))
361                 {
362                 GList *sidecars;
363                 GList *work;
364                 FileData *sfd = NULL;
365
366                 /* parent is missing, we have to rebuild whole group */
367                 ret = TRUE;
368                 fd->size = 0;
369                 fd->date = 0;
370                 
371                 /* file_data_disconnect_sidecar_file might delete the file,
372                    we have to keep the reference to prevent this */
373                 sidecars = filelist_copy(fd->sidecar_files);
374                 work = sidecars;
375                 while (work)
376                         {
377                         sfd = work->data;
378                         work = work->next;
379                 
380                         file_data_disconnect_sidecar_file(fd, sfd);
381                         }
382                 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
383                 /* now we can release the sidecars */
384                 filelist_free(sidecars);
385                 file_data_send_notification(fd, NOTIFY_REREAD);
386                 }
387         else
388                 {
389                 ret |= file_data_check_changed_files_recursive(fd, &st);
390                 }
391
392         return ret;
393 }
394
395 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
396 {
397         FileData *fd;
398
399         DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
400
401         if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE; 
402
403         if (!file_data_pool)
404                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
405
406         fd = g_hash_table_lookup(file_data_pool, path_utf8);
407         if (fd)
408                 {
409                 file_data_ref(fd);
410                 }
411                 
412         if (!fd && file_data_planned_change_hash)
413                 {
414                 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
415                 if (fd)
416                         {
417                         DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
418                         file_data_ref(fd);
419                         file_data_apply_ci(fd);
420                         }
421                 }
422                 
423         if (fd)
424                 {
425                 gboolean changed;
426                 
427                 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
428                 
429                 
430                 changed = file_data_check_changed(fd, st);
431
432                 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
433                 
434                 return fd;
435                 }
436
437         fd = g_new0(FileData, 1);
438         
439         fd->size = st->st_size;
440         fd->date = st->st_mtime;
441         fd->mode = st->st_mode;
442         fd->ref = 1;
443         fd->magick = 0x12345678;
444         
445         if (disable_sidecars) fd->disable_grouping = TRUE;
446
447         file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
448
449         return fd;
450 }
451
452
453 static void file_data_check_sidecars(const GList *basename_list)
454 {
455         GList *work;
456         FileData *parent_fd;
457         if (!basename_list) return;
458         /* process the group list - the first one is the parent file, others are sidecars */
459         parent_fd = basename_list->data;
460         work = basename_list->next;
461         while (work)
462                 {
463                 FileData *sfd = work->data;
464                 work = work->next;
465
466                 file_data_merge_sidecar_files(parent_fd, sfd);
467                 }
468                 
469         /* there may be some sidecars that are already deleted - disconnect them */
470         work = parent_fd->sidecar_files;
471         while (work)
472                 {
473                 FileData *sfd = work->data;
474                 work = work->next;
475                 
476                 if (!g_list_find((GList *)basename_list, sfd)) 
477                         {
478                         printf("removing unknown %s: %s \n", parent_fd->path, sfd->path);
479                         file_data_disconnect_sidecar_file(parent_fd, sfd);
480                         file_data_send_notification(sfd, NOTIFY_REREAD);
481                         file_data_send_notification(parent_fd, NOTIFY_REREAD);
482                         }
483                 }
484 }
485
486
487 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
488 {
489         gchar *path_utf8 = path_to_utf8(path);
490         FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
491
492         g_free(path_utf8);
493         return ret;
494 }
495
496 static FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
497 {
498         sfd->parent = target;
499         if (!g_list_find(target->sidecar_files, sfd))
500                 target->sidecar_files = g_list_insert_sorted(target->sidecar_files, sfd, file_data_sort_by_ext);
501         file_data_increment_version(sfd); /* increments both sfd and target */
502         return target;
503 }
504
505
506 static FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
507 {
508         GList *work;
509         
510         file_data_add_sidecar_file(target, source);
511
512         work = source->sidecar_files;
513         while (work)
514                 {
515                 FileData *sfd = work->data;
516                 file_data_add_sidecar_file(target, sfd);
517                 work = work->next;
518                 }
519
520         g_list_free(source->sidecar_files);
521         source->sidecar_files = NULL;
522
523         return target;
524 }
525
526 #ifdef DEBUG_FILEDATA
527 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
528 #else
529 FileData *file_data_ref(FileData *fd)
530 #endif
531 {
532         if (fd == NULL) return NULL;
533 #ifdef DEBUG_FILEDATA
534         if (fd->magick != 0x12345678)
535                 DEBUG_0("fd magick mismatch at %s:%d", file, line);
536 #endif
537         g_assert(fd->magick == 0x12345678);
538         fd->ref++;
539
540 #ifdef DEBUG_FILEDATA
541         DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
542 #else
543         DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
544 #endif
545         return fd;
546 }
547
548 static void file_data_free(FileData *fd)
549 {
550         g_assert(fd->magick == 0x12345678);
551         g_assert(fd->ref == 0);
552
553         metadata_cache_free(fd);
554         g_hash_table_remove(file_data_pool, fd->original_path);
555
556         g_free(fd->path);
557         g_free(fd->original_path);
558         g_free(fd->collate_key_name);
559         g_free(fd->collate_key_name_nocase);
560         if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
561         histmap_free(fd->histmap);
562         
563         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
564
565         file_data_change_info_free(NULL, fd);
566         g_free(fd);
567 }
568
569 #ifdef DEBUG_FILEDATA
570 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
571 #else
572 void file_data_unref(FileData *fd)
573 #endif
574 {
575         if (fd == NULL) return;
576 #ifdef DEBUG_FILEDATA
577         if (fd->magick != 0x12345678)
578                 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
579 #endif
580         g_assert(fd->magick == 0x12345678);
581         
582         fd->ref--;
583 #ifdef DEBUG_FILEDATA
584         DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
585 #else
586         DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
587 #endif
588         if (fd->ref == 0)
589                 {
590                 GList *work;
591                 FileData *parent = fd->parent ? fd->parent : fd;
592                 
593                 if (parent->ref > 0) return;
594
595                 work = parent->sidecar_files;
596                 while (work)
597                         {
598                         FileData *sfd = work->data;
599                         if (sfd->ref > 0) return;
600                         work = work->next;
601                         }
602
603                 /* none of parent/children is referenced, we can free everything */
604
605                 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
606
607                 work = parent->sidecar_files;
608                 while (work)
609                         {
610                         FileData *sfd = work->data;
611                         file_data_free(sfd);
612                         work = work->next;
613                         }
614
615                 g_list_free(parent->sidecar_files);
616                 parent->sidecar_files = NULL;
617
618                 file_data_free(parent);
619                 }
620 }
621
622 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
623 {
624         sfd->parent = target;
625         g_assert(g_list_find(target->sidecar_files, sfd));
626         
627         file_data_increment_version(sfd); /* increments both sfd and target */
628
629         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
630         sfd->parent = NULL;
631
632         if (sfd->ref == 0)
633                 {
634                 file_data_free(sfd);
635                 return NULL;
636                 }
637
638         return sfd;
639 }
640
641 /* disables / enables grouping for particular file, sends UPDATE notification */
642 void file_data_disable_grouping(FileData *fd, gboolean disable)
643 {
644         if (!fd->disable_grouping == !disable) return;
645         
646         fd->disable_grouping = !!disable;
647         
648         if (disable)
649                 {
650                 if (fd->parent)
651                         {
652                         FileData *parent = file_data_ref(fd->parent);
653                         file_data_disconnect_sidecar_file(parent, fd);
654                         file_data_send_notification(parent, NOTIFY_GROUPING);
655                         file_data_unref(parent);
656                         }
657                 else if (fd->sidecar_files)
658                         {
659                         GList *sidecar_files = filelist_copy(fd->sidecar_files);
660                         GList *work = sidecar_files;
661                         while (work)
662                                 {
663                                 FileData *sfd = work->data;
664                                 work = work->next;
665                                 file_data_disconnect_sidecar_file(fd, sfd);
666                                 file_data_send_notification(sfd, NOTIFY_GROUPING);
667                                 }
668                         file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
669                         filelist_free(sidecar_files);
670                         }
671                 else
672                         {
673                         file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
674                         }
675                 }
676         else
677                 {
678                 file_data_increment_version(fd);
679                 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
680                 }
681         file_data_send_notification(fd, NOTIFY_GROUPING);
682 }
683
684 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
685 {
686         GList *work;
687         
688         work = fd_list;
689         while (work)
690                 {
691                 FileData *fd = work->data;
692                 
693                 file_data_disable_grouping(fd, disable);
694                 work = work->next;
695                 }
696 }
697
698
699 /* compare name without extension */
700 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
701 {
702         size_t len1 = fd1->extension - fd1->name;
703         size_t len2 = fd2->extension - fd2->name;
704
705         if (len1 < len2) return -1;
706         if (len1 > len2) return 1;
707
708         return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
709 }
710
711 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
712 {
713         if (!fdci && fd) fdci = fd->change;
714
715         if (!fdci) return;
716
717         g_free(fdci->source);
718         g_free(fdci->dest);
719
720         g_free(fdci);
721
722         if (fd) fd->change = NULL;
723 }
724
725 static gboolean file_data_can_write_directly(FileData *fd)
726 {
727         return filter_name_is_writable(fd->extension);
728 }
729
730 static gboolean file_data_can_write_sidecar(FileData *fd)
731 {
732         return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
733 }
734
735 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
736 {
737         gchar *sidecar_path = NULL;
738         GList *work;
739         
740         if (!file_data_can_write_sidecar(fd)) return NULL;
741         
742         work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
743         while (work)
744                 {
745                 FileData *sfd = work->data;
746                 work = work->next;
747                 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
748                         {
749                         sidecar_path = g_strdup(sfd->path);
750                         break;
751                         }
752                 }
753         
754         if (!existing_only && !sidecar_path)
755                 {
756                 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
757                 sidecar_path = g_strconcat(base, ".xmp", NULL);
758                 g_free(base);
759                 }
760
761         return sidecar_path;
762 }
763
764
765 /*
766  *-----------------------------------------------------------------------------
767  * sidecar file info struct
768  *-----------------------------------------------------------------------------
769  */
770
771
772
773 static gint sidecar_file_priority(const gchar *path)
774 {
775         const gchar *extension = extension_from_path(path);
776         gint i = 1;
777         GList *work;
778
779         if (extension == NULL)
780                 return 0;
781
782         work = sidecar_ext_get_list();
783
784         while (work) {
785                 gchar *ext = work->data;
786                 
787                 work = work->next;
788                 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
789                 i++;
790         }
791         return 0;
792 }
793
794
795 /*
796  *-----------------------------------------------------------------------------
797  * load file list
798  *-----------------------------------------------------------------------------
799  */
800
801 static SortType filelist_sort_method = SORT_NONE;
802 static gboolean filelist_sort_ascend = TRUE;
803
804
805 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
806 {
807         gint ret;
808         if (!filelist_sort_ascend)
809                 {
810                 FileData *tmp = fa;
811                 fa = fb;
812                 fb = tmp;
813                 }
814
815         switch (filelist_sort_method)
816                 {
817                 case SORT_NAME:
818                         break;
819                 case SORT_SIZE:
820                         if (fa->size < fb->size) return -1;
821                         if (fa->size > fb->size) return 1;
822                         /* fall back to name */
823                         break;
824                 case SORT_TIME:
825                         if (fa->date < fb->date) return -1;
826                         if (fa->date > fb->date) return 1;
827                         /* fall back to name */
828                         break;
829 #ifdef HAVE_STRVERSCMP
830                 case SORT_NUMBER:
831                         ret = strverscmp(fa->name, fb->name);
832                         if (ret != 0) return ret;
833                         break;
834 #endif
835                 default:
836                         break;
837                 }
838
839         if (options->file_sort.case_sensitive)
840                 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
841         else
842                 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
843
844         if (ret != 0) return ret;
845         
846         /* do not return 0 unless the files are really the same 
847            file_data_pool ensures that original_path is unique 
848         */
849         return strcmp(fa->original_path, fb->original_path);
850 }
851
852 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
853 {
854         filelist_sort_method = method;
855         filelist_sort_ascend = ascend;
856         return filelist_sort_compare_filedata(fa, fb);
857 }
858
859 static gint filelist_sort_file_cb(gpointer a, gpointer b)
860 {
861         return filelist_sort_compare_filedata(a, b);
862 }
863
864 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
865 {
866         filelist_sort_method = method;
867         filelist_sort_ascend = ascend;
868         return g_list_sort(list, cb);
869 }
870
871 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
872 {
873         filelist_sort_method = method;
874         filelist_sort_ascend = ascend;
875         return g_list_insert_sorted(list, data, cb);
876 }
877
878 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
879 {
880         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
881 }
882
883 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
884 {
885         return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
886 }
887
888
889 static GList *filelist_filter_out_sidecars(GList *flist)
890 {
891         GList *work = flist;
892         GList *flist_filtered = NULL;
893
894         while (work)
895                 {
896                 FileData *fd = work->data;
897         
898                 work = work->next;
899                 if (fd->parent) /* remove fd's that are children */
900                         file_data_unref(fd);
901                 else
902                         flist_filtered = g_list_prepend(flist_filtered, fd);
903                 }
904         g_list_free(flist);
905
906         return flist_filtered;
907 }
908
909 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
910 {
911         GList *basename_list = (GList *)value;
912         file_data_check_sidecars(basename_list);
913 }
914
915
916 static gboolean is_hidden_file(const gchar *name)
917 {
918         if (name[0] != '.') return FALSE;
919         if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
920         return TRUE;
921 }
922
923 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
924 {
925         DIR *dp;
926         struct dirent *dir;
927         gchar *pathl;
928         GList *dlist = NULL;
929         GList *flist = NULL;
930         gint (*stat_func)(const gchar *path, struct stat *buf);
931         GHashTable *basename_hash = NULL;
932
933         g_assert(files || dirs);
934
935         if (files) *files = NULL;
936         if (dirs) *dirs = NULL;
937
938         pathl = path_from_utf8(dir_path);
939         if (!pathl) return FALSE;
940
941         dp = opendir(pathl);
942         if (dp == NULL)
943                 {
944                 g_free(pathl);
945                 return FALSE;
946                 }
947
948         if (files) basename_hash = file_data_basename_hash_new();
949
950         if (follow_symlinks)
951                 stat_func = stat;
952         else
953                 stat_func = lstat;
954
955         while ((dir = readdir(dp)) != NULL)
956                 {
957                 struct stat ent_sbuf;
958                 const gchar *name = dir->d_name;
959                 gchar *filepath;
960
961                 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
962                         continue;
963
964                 filepath = g_build_filename(pathl, name, NULL);
965                 if (stat_func(filepath, &ent_sbuf) >= 0)
966                         {
967                         if (S_ISDIR(ent_sbuf.st_mode))
968                                 {
969                                 /* we ignore the .thumbnails dir for cleanliness */
970                                 if (dirs &&
971                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
972                                     strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
973                                     strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
974                                     strcmp(name, THUMB_FOLDER_LOCAL) != 0)
975                                         {
976                                         dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
977                                         }
978                                 }
979                         else
980                                 {
981                                 if (files && filter_name_exists(name))
982                                         {
983                                         FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
984                                         flist = g_list_prepend(flist, fd);
985                                         if (fd->sidecar_priority && !fd->disable_grouping) 
986                                                 {
987                                                 file_data_basename_hash_insert(basename_hash, fd);
988                                                 }
989                                         }
990                                 }
991                         }
992                 else
993                         {
994                         if (errno == EOVERFLOW)
995                                 {
996                                 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
997                                 }
998                         }
999                 g_free(filepath);
1000                 }
1001
1002         closedir(dp);
1003         
1004         g_free(pathl);
1005
1006         if (dirs) *dirs = dlist;
1007         if (files) 
1008                 {
1009                 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL); 
1010
1011                 *files = filelist_filter_out_sidecars(flist);
1012                 }
1013         if (basename_hash) file_data_basename_hash_free(basename_hash);
1014
1015         return TRUE;
1016 }
1017
1018 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1019 {
1020         return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1021 }
1022
1023 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1024 {
1025         return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1026 }
1027
1028 FileData *file_data_new_simple(const gchar *path_utf8)
1029 {
1030         gchar *dir;
1031         struct stat st;
1032         FileData *fd;
1033         GList *files;
1034
1035         if (!stat_utf8(path_utf8, &st))
1036                 {
1037                 st.st_size = 0;
1038                 st.st_mtime = 0;
1039                 }
1040
1041         if (S_ISDIR(st.st_mode))
1042                 return file_data_new(path_utf8, &st, TRUE);
1043         
1044         dir = remove_level_from_path(path_utf8);
1045         
1046         filelist_read_real(dir, &files, NULL, TRUE);
1047         
1048         fd = g_hash_table_lookup(file_data_pool, path_utf8);
1049         g_assert(fd);
1050         file_data_ref(fd);
1051         
1052         filelist_free(files);
1053         g_free(dir);
1054         return fd;
1055 }
1056
1057 FileData *file_data_new_no_grouping(const gchar *path_utf8)
1058 {
1059         struct stat st;
1060
1061         if (!stat_utf8(path_utf8, &st))
1062                 {
1063                 st.st_size = 0;
1064                 st.st_mtime = 0;
1065                 }
1066
1067         return file_data_new(path_utf8, &st, TRUE);
1068 }
1069
1070 FileData *file_data_new_dir(const gchar *path_utf8)
1071 {
1072         struct stat st;
1073
1074         if (!stat_utf8(path_utf8, &st))
1075                 {
1076                 st.st_size = 0;
1077                 st.st_mtime = 0;
1078                 }
1079
1080         g_assert(S_ISDIR(st.st_mode));
1081         return file_data_new(path_utf8, &st, TRUE);
1082 }
1083
1084 void filelist_free(GList *list)
1085 {
1086         GList *work;
1087
1088         work = list;
1089         while (work)
1090                 {
1091                 file_data_unref((FileData *)work->data);
1092                 work = work->next;
1093                 }
1094
1095         g_list_free(list);
1096 }
1097
1098
1099 GList *filelist_copy(GList *list)
1100 {
1101         GList *new_list = NULL;
1102         GList *work;
1103
1104         work = list;
1105         while (work)
1106                 {
1107                 FileData *fd;
1108
1109                 fd = work->data;
1110                 work = work->next;
1111
1112                 new_list = g_list_prepend(new_list, file_data_ref(fd));
1113                 }
1114
1115         return g_list_reverse(new_list);
1116 }
1117
1118 GList *filelist_from_path_list(GList *list)
1119 {
1120         GList *new_list = NULL;
1121         GList *work;
1122
1123         work = list;
1124         while (work)
1125                 {
1126                 gchar *path;
1127
1128                 path = work->data;
1129                 work = work->next;
1130
1131                 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1132                 }
1133
1134         return g_list_reverse(new_list);
1135 }
1136
1137 GList *filelist_to_path_list(GList *list)
1138 {
1139         GList *new_list = NULL;
1140         GList *work;
1141
1142         work = list;
1143         while (work)
1144                 {
1145                 FileData *fd;
1146
1147                 fd = work->data;
1148                 work = work->next;
1149
1150                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1151                 }
1152
1153         return g_list_reverse(new_list);
1154 }
1155
1156 GList *filelist_filter(GList *list, gboolean is_dir_list)
1157 {
1158         GList *work;
1159
1160         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1161
1162         work = list;
1163         while (work)
1164                 {
1165                 FileData *fd = (FileData *)(work->data);
1166                 const gchar *name = fd->name;
1167
1168                 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1169                     (!is_dir_list && !filter_name_exists(name)) ||
1170                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1171                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1172                         {
1173                         GList *link = work;
1174                         
1175                         list = g_list_remove_link(list, link);
1176                         file_data_unref(fd);
1177                         g_list_free(link);
1178                         }
1179         
1180                 work = work->next;
1181                 }
1182
1183         return list;
1184 }
1185
1186 /*
1187  *-----------------------------------------------------------------------------
1188  * filelist recursive
1189  *-----------------------------------------------------------------------------
1190  */
1191
1192 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1193 {
1194         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1195 }
1196
1197 GList *filelist_sort_path(GList *list)
1198 {
1199         return g_list_sort(list, filelist_sort_path_cb);
1200 }
1201
1202 static void filelist_recursive_append(GList **list, GList *dirs)
1203 {
1204         GList *work;
1205
1206         work = dirs;
1207         while (work)
1208                 {
1209                 FileData *fd = (FileData *)(work->data);
1210                 GList *f;
1211                 GList *d;
1212
1213                 if (filelist_read(fd, &f, &d))
1214                         {
1215                         f = filelist_filter(f, FALSE);
1216                         f = filelist_sort_path(f);
1217                         *list = g_list_concat(*list, f);
1218
1219                         d = filelist_filter(d, TRUE);
1220                         d = filelist_sort_path(d);
1221                         filelist_recursive_append(list, d);
1222                         filelist_free(d);
1223                         }
1224
1225                 work = work->next;
1226                 }
1227 }
1228
1229 GList *filelist_recursive(FileData *dir_fd)
1230 {
1231         GList *list;
1232         GList *d;
1233
1234         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1235         list = filelist_filter(list, FALSE);
1236         list = filelist_sort_path(list);
1237
1238         d = filelist_filter(d, TRUE);
1239         d = filelist_sort_path(d);
1240         filelist_recursive_append(&list, d);
1241         filelist_free(d);
1242
1243         return list;
1244 }
1245
1246
1247 /*
1248  * marks and orientation
1249  */
1250
1251 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1252 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1253 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1254 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1255
1256 gboolean file_data_get_mark(FileData *fd, gint n)
1257 {
1258         gboolean valid = (fd->valid_marks & (1 << n));
1259         
1260         if (file_data_get_mark_func[n] && !valid) 
1261                 {
1262                 guint old = fd->marks;
1263                 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1264                 
1265                 if (!value != !(fd->marks & (1 << n))) 
1266                         {
1267                         fd->marks = fd->marks ^ (1 << n);
1268                         }
1269                 
1270                 fd->valid_marks |= (1 << n);
1271                 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1272                         {
1273                         file_data_unref(fd);
1274                         }
1275                 else if (!old && fd->marks)
1276                         {
1277                         file_data_ref(fd);
1278                         }
1279                 }
1280
1281         return !!(fd->marks & (1 << n));
1282 }
1283
1284 guint file_data_get_marks(FileData *fd)
1285 {
1286         gint i;
1287         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1288         return fd->marks;
1289 }
1290
1291 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1292 {
1293         guint old;
1294         if (!value == !file_data_get_mark(fd, n)) return;
1295         
1296         if (file_data_set_mark_func[n]) 
1297                 {
1298                 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1299                 }
1300         
1301         old = fd->marks;
1302
1303         fd->marks = fd->marks ^ (1 << n);
1304         
1305         if (old && !fd->marks) /* keep files with non-zero marks in memory */
1306                 {
1307                 file_data_unref(fd);
1308                 }
1309         else if (!old && fd->marks)
1310                 {
1311                 file_data_ref(fd);
1312                 }
1313         
1314         file_data_increment_version(fd);
1315         file_data_send_notification(fd, NOTIFY_MARKS);
1316 }
1317
1318 gboolean file_data_filter_marks(FileData *fd, guint filter)
1319 {
1320         gint i;
1321         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1322         return ((fd->marks & filter) == filter);
1323 }
1324
1325 GList *file_data_filter_marks_list(GList *list, guint filter)
1326 {
1327         GList *work;
1328
1329         work = list;
1330         while (work)
1331                 {
1332                 FileData *fd = work->data;
1333                 GList *link = work;
1334                 work = work->next;
1335
1336                 if (!file_data_filter_marks(fd, filter))
1337                         {
1338                         list = g_list_remove_link(list, link);
1339                         file_data_unref(fd);
1340                         g_list_free(link);
1341                         }
1342                 }
1343
1344         return list;
1345 }
1346
1347 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1348 {
1349         FileData *fd = value;
1350         file_data_increment_version(fd);
1351         file_data_send_notification(fd, NOTIFY_MARKS);
1352 }
1353
1354 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1355 {
1356         if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1357         
1358         if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1359                 
1360         file_data_get_mark_func[n] = get_mark_func;
1361         file_data_set_mark_func[n] = set_mark_func;
1362         file_data_mark_func_data[n] = data;
1363         file_data_destroy_mark_func[n] = notify;
1364
1365         if (get_mark_func)
1366                 {
1367                 /* this effectively changes all known files */
1368                 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1369                 }
1370
1371         return TRUE;
1372 }
1373
1374 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1375 {
1376         if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1377         if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1378         if (data) *data = file_data_mark_func_data[n];
1379 }
1380
1381 gint file_data_get_user_orientation(FileData *fd)
1382 {
1383         return fd->user_orientation;
1384 }
1385
1386 void file_data_set_user_orientation(FileData *fd, gint value)
1387 {
1388         if (fd->user_orientation == value) return;
1389
1390         fd->user_orientation = value;
1391         file_data_increment_version(fd);
1392         file_data_send_notification(fd, NOTIFY_ORIENTATION);
1393 }
1394
1395
1396 /*
1397  * file_data    - operates on the given fd
1398  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1399  */
1400
1401
1402 /* return list of sidecar file extensions in a string */
1403 gchar *file_data_sc_list_to_string(FileData *fd)
1404 {
1405         GList *work;
1406         GString *result = g_string_new("");
1407
1408         work = fd->sidecar_files;
1409         while (work)
1410                 {
1411                 FileData *sfd = work->data;
1412
1413                 result = g_string_append(result, "+ ");
1414                 result = g_string_append(result, sfd->extension);
1415                 work = work->next;
1416                 if (work) result = g_string_append_c(result, ' ');
1417                 }
1418
1419         return g_string_free(result, FALSE);
1420 }
1421
1422
1423
1424 /*
1425  * add FileDataChangeInfo (see typedefs.h) for the given operation
1426  * uses file_data_add_change_info
1427  *
1428  * fails if the fd->change already exists - change operations can't run in parallel
1429  * fd->change_info works as a lock
1430  *
1431  * dest can be NULL - in this case the current name is used for now, it will
1432  * be changed later
1433  */
1434
1435 /*
1436    FileDataChangeInfo types:
1437    COPY
1438    MOVE   - path is changed, name may be changed too
1439    RENAME - path remains unchanged, name is changed
1440             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1441             sidecar names are changed too, extensions are not changed
1442    DELETE
1443    UPDATE - file size, date or grouping has been changed
1444 */
1445
1446 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1447 {
1448         FileDataChangeInfo *fdci;
1449
1450         if (fd->change) return FALSE;
1451
1452         fdci = g_new0(FileDataChangeInfo, 1);
1453
1454         fdci->type = type;
1455
1456         if (src)
1457                 fdci->source = g_strdup(src);
1458         else
1459                 fdci->source = g_strdup(fd->path);
1460
1461         if (dest)
1462                 fdci->dest = g_strdup(dest);
1463
1464         fd->change = fdci;
1465         
1466         return TRUE;
1467 }
1468
1469 static void file_data_planned_change_remove(FileData *fd)
1470 {
1471         if (file_data_planned_change_hash &&
1472             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1473                 {
1474                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1475                         {
1476                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1477                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1478                         file_data_unref(fd);
1479                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1480                                 {
1481                                 g_hash_table_destroy(file_data_planned_change_hash);
1482                                 file_data_planned_change_hash = NULL;
1483                                 DEBUG_1("planned change: empty");
1484                                 }
1485                         }
1486                 }
1487 }
1488
1489
1490 void file_data_free_ci(FileData *fd)
1491 {
1492         FileDataChangeInfo *fdci = fd->change;
1493
1494         if (!fdci) return;
1495
1496         file_data_planned_change_remove(fd);
1497         
1498         if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1499
1500         g_free(fdci->source);
1501         g_free(fdci->dest);
1502
1503         g_free(fdci);
1504
1505         fd->change = NULL;
1506 }
1507
1508 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1509 {
1510         FileDataChangeInfo *fdci = fd->change;
1511         if (!fdci) return;
1512         fdci->regroup_when_finished = enable;
1513 }
1514
1515 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1516 {
1517         GList *work;
1518
1519         if (fd->parent) fd = fd->parent;
1520         
1521         if (fd->change) return FALSE;
1522         
1523         work = fd->sidecar_files;
1524         while (work)
1525                 {
1526                 FileData *sfd = work->data;
1527                 
1528                 if (sfd->change) return FALSE;
1529                 work = work->next;
1530                 }
1531
1532         file_data_add_ci(fd, type, NULL, NULL);
1533         
1534         work = fd->sidecar_files;
1535         while (work)
1536                 {
1537                 FileData *sfd = work->data;
1538                 
1539                 file_data_add_ci(sfd, type, NULL, NULL);
1540                 work = work->next;
1541                 }
1542                 
1543         return TRUE;
1544 }
1545
1546 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1547 {
1548         GList *work;
1549         
1550         if (fd->parent) fd = fd->parent;
1551         
1552         if (!fd->change || fd->change->type != type) return FALSE;
1553         
1554         work = fd->sidecar_files;
1555         while (work)
1556                 {
1557                 FileData *sfd = work->data;
1558
1559                 if (!sfd->change || sfd->change->type != type) return FALSE;
1560                 work = work->next;
1561                 }
1562
1563         return TRUE;
1564 }
1565
1566
1567 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1568 {
1569         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1570         file_data_sc_update_ci_copy(fd, dest_path);
1571         return TRUE;
1572 }
1573
1574 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1575 {
1576         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1577         file_data_sc_update_ci_move(fd, dest_path);
1578         return TRUE;
1579 }
1580
1581 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1582 {
1583         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1584         file_data_sc_update_ci_rename(fd, dest_path);
1585         return TRUE;
1586 }
1587
1588 gboolean file_data_sc_add_ci_delete(FileData *fd)
1589 {
1590         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1591 }
1592
1593 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1594 {
1595         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1596         file_data_sc_update_ci_unspecified(fd, dest_path);
1597         return TRUE;
1598 }
1599
1600 gboolean file_data_add_ci_write_metadata(FileData *fd)
1601 {
1602         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1603 }
1604
1605 void file_data_sc_free_ci(FileData *fd)
1606 {
1607         GList *work;
1608
1609         if (fd->parent) fd = fd->parent;
1610         
1611         file_data_free_ci(fd);
1612         
1613         work = fd->sidecar_files;
1614         while (work)
1615                 {
1616                 FileData *sfd = work->data;
1617         
1618                 file_data_free_ci(sfd);
1619                 work = work->next;
1620                 }
1621 }
1622
1623 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1624 {
1625         GList *work;
1626         gboolean ret = TRUE;
1627
1628         work = fd_list;
1629         while (work)
1630                 {
1631                 FileData *fd = work->data;
1632         
1633                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1634                 work = work->next;
1635                 }
1636
1637         return ret;
1638 }
1639
1640 static void file_data_sc_revert_ci_list(GList *fd_list)
1641 {
1642         GList *work;
1643         
1644         work = fd_list;
1645         while (work)
1646                 {
1647                 FileData *fd = work->data;
1648                 
1649                 file_data_sc_free_ci(fd);
1650                 work = work->prev;
1651                 }
1652 }
1653
1654 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1655 {
1656         GList *work;
1657         
1658         work = fd_list;
1659         while (work)
1660                 {
1661                 FileData *fd = work->data;
1662                 
1663                 if (!func(fd, dest))
1664                         {
1665                         file_data_sc_revert_ci_list(work->prev);
1666                         return FALSE;
1667                         }
1668                 work = work->next;
1669                 }
1670         
1671         return TRUE;
1672 }
1673
1674 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1675 {
1676         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1677 }
1678
1679 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1680 {
1681         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1682 }
1683
1684 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1685 {
1686         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1687 }
1688
1689 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1690 {
1691         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1692 }
1693
1694 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1695 {
1696         GList *work;
1697         gboolean ret = TRUE;
1698
1699         work = fd_list;
1700         while (work)
1701                 {
1702                 FileData *fd = work->data;
1703         
1704                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1705                 work = work->next;
1706                 }
1707
1708         return ret;
1709 }
1710
1711 void file_data_free_ci_list(GList *fd_list)
1712 {
1713         GList *work;
1714         
1715         work = fd_list;
1716         while (work)
1717                 {
1718                 FileData *fd = work->data;
1719                 
1720                 file_data_free_ci(fd);
1721                 work = work->next;
1722                 }
1723 }
1724
1725 void file_data_sc_free_ci_list(GList *fd_list)
1726 {
1727         GList *work;
1728         
1729         work = fd_list;
1730         while (work)
1731                 {
1732                 FileData *fd = work->data;
1733                 
1734                 file_data_sc_free_ci(fd);
1735                 work = work->next;
1736                 }
1737 }
1738
1739 /*
1740  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1741  * fails if fd->change does not exist or the change type does not match
1742  */
1743
1744 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1745 {
1746         FileDataChangeType type = fd->change->type;
1747         
1748         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1749                 {
1750                 FileData *ofd;
1751                 
1752                 if (!file_data_planned_change_hash)
1753                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1754                 
1755                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1756                         {
1757                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1758                         g_hash_table_remove(file_data_planned_change_hash, old_path);
1759                         file_data_unref(fd);
1760                         }
1761
1762                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1763                 if (ofd != fd)
1764                         {
1765                         if (ofd)
1766                                 {
1767                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1768                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
1769                                 file_data_unref(ofd);
1770                                 }
1771                         
1772                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1773                         file_data_ref(fd);
1774                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1775                         }
1776                 }
1777 }
1778
1779 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1780 {
1781         gchar *old_path = fd->change->dest;
1782
1783         fd->change->dest = g_strdup(dest_path);
1784         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1785         g_free(old_path);
1786 }
1787
1788 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1789 {
1790         const gchar *extension = extension_from_path(fd->change->source);
1791         gchar *base = remove_extension_from_path(dest_path);
1792         gchar *old_path = fd->change->dest;
1793         
1794         fd->change->dest = g_strconcat(base, extension, NULL);
1795         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1796         
1797         g_free(old_path);
1798         g_free(base);
1799 }
1800
1801 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1802 {
1803         GList *work;
1804         gchar *dest_path_full = NULL;
1805         
1806         if (fd->parent) fd = fd->parent;
1807         
1808         if (!dest_path)
1809                 {
1810                 dest_path = fd->path;
1811                 }
1812         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1813                 {
1814                 gchar *dir = remove_level_from_path(fd->path);
1815                 
1816                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1817                 g_free(dir);
1818                 dest_path = dest_path_full;
1819                 }
1820         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1821                 {
1822                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1823                 dest_path = dest_path_full;
1824                 }
1825                 
1826         file_data_update_ci_dest(fd, dest_path);
1827         
1828         work = fd->sidecar_files;
1829         while (work)
1830                 {
1831                 FileData *sfd = work->data;
1832                 
1833                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1834                 work = work->next;
1835                 }
1836         
1837         g_free(dest_path_full);
1838 }
1839
1840 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1841 {
1842         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1843         file_data_sc_update_ci(fd, dest_path);
1844         return TRUE;
1845 }
1846
1847 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1848 {
1849         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1850 }
1851         
1852 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1853 {
1854         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1855 }
1856
1857 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1858 {
1859         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1860 }
1861
1862 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1863 {
1864         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1865 }
1866
1867 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1868                                                       const gchar *dest,
1869                                                       gboolean (*func)(FileData *, const gchar *))
1870 {
1871         GList *work;
1872         gboolean ret = TRUE;
1873         
1874         work = fd_list;
1875         while (work)
1876                 {
1877                 FileData *fd = work->data;
1878                 
1879                 if (!func(fd, dest)) ret = FALSE;
1880                 work = work->next;
1881                 }
1882         
1883         return ret;
1884 }
1885
1886 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1887 {
1888         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1889 }
1890
1891 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1892 {
1893         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1894 }
1895
1896 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1897 {
1898         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1899 }
1900
1901
1902 /*
1903  * verify source and dest paths - dest image exists, etc.
1904  * it should detect all possible problems with the planned operation
1905  */
1906
1907 gint file_data_verify_ci(FileData *fd)
1908 {
1909         gint ret = CHANGE_OK;
1910         gchar *dir;
1911         
1912         if (!fd->change)
1913                 {
1914                 DEBUG_1("Change checked: no change info: %s", fd->path);
1915                 return ret;
1916                 }
1917
1918         if (!isname(fd->path))
1919                 {
1920                 /* this probably should not happen */
1921                 ret |= CHANGE_NO_SRC;
1922                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1923                 return ret;
1924                 }
1925                 
1926         dir = remove_level_from_path(fd->path);
1927         
1928         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1929             fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1930             fd->change->type != FILEDATA_CHANGE_RENAME &&
1931             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1932             fd->modified_xmp)
1933                 {
1934                 ret |= CHANGE_WARN_UNSAVED_META;
1935                 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1936                 }
1937         
1938         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1939             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1940             !access_file(fd->path, R_OK))
1941                 {
1942                 ret |= CHANGE_NO_READ_PERM;
1943                 DEBUG_1("Change checked: no read permission: %s", fd->path);
1944                 }
1945         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1946                  !access_file(dir, W_OK))
1947                 {
1948                 ret |= CHANGE_NO_WRITE_PERM_DIR;
1949                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1950                 }
1951         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1952                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1953                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1954                  !access_file(fd->path, W_OK))
1955                 {
1956                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1957                 DEBUG_1("Change checked: no write permission: %s", fd->path);
1958                 }
1959         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1960            - that means that there are no hard errors and warnings can be disabled
1961            - the destination is determined during the check
1962         */
1963         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1964                 {
1965                 /* determine destination file */
1966                 gboolean have_dest = FALSE;
1967                 gchar *dest_dir = NULL;
1968                 
1969                 if (options->metadata.save_in_image_file)
1970                         {
1971                         if (file_data_can_write_directly(fd)) 
1972                                 {
1973                                 /* we can write the file directly */
1974                                 if (access_file(fd->path, W_OK))
1975                                         {
1976                                         have_dest = TRUE;
1977                                         }
1978                                 else
1979                                         {
1980                                         if (options->metadata.warn_on_write_problems)
1981                                                 {
1982                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1983                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1984                                                 }
1985                                         }
1986                                 }
1987                         else if (file_data_can_write_sidecar(fd)) 
1988                                 {
1989                                 /* we can write sidecar */
1990                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1991                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1992                                         {
1993                                         file_data_update_ci_dest(fd, sidecar);
1994                                         have_dest = TRUE;
1995                                         }
1996                                 else
1997                                         {
1998                                         if (options->metadata.warn_on_write_problems)
1999                                                 {
2000                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2001                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2002                                                 }
2003                                         }
2004                                 g_free(sidecar);
2005                                 }
2006                         }
2007                 
2008                 if (!have_dest)
2009                         {
2010                         /* write private metadata file under ~/.geeqie */
2011
2012                         /* If an existing metadata file exists, we will try writing to
2013                          * it's location regardless of the user's preference.
2014                          */
2015                         gchar *metadata_path = NULL;
2016 #ifdef HAVE_EXIV2
2017                         /* but ignore XMP if we are not able to write it */
2018                         metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2019 #endif
2020                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2021                         
2022                         if (metadata_path && !access_file(metadata_path, W_OK))
2023                                 {
2024                                 g_free(metadata_path);
2025                                 metadata_path = NULL;
2026                                 }
2027
2028                         if (!metadata_path)
2029                                 {
2030                                 mode_t mode = 0755;
2031
2032                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2033                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2034                                         {
2035                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2036                         
2037                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
2038                                         g_free(filename);
2039                                         }
2040                                 }
2041                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2042                                 {
2043                                 file_data_update_ci_dest(fd, metadata_path);
2044                                 have_dest = TRUE;
2045                                 }
2046                         else
2047                                 {
2048                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
2049                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2050                                 }
2051                         g_free(metadata_path);
2052                         }
2053                 g_free(dest_dir);
2054                 }
2055                 
2056         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2057                 {
2058                 gboolean same;
2059                 gchar *dest_dir;
2060                         
2061                 same = (strcmp(fd->path, fd->change->dest) == 0);
2062
2063                 if (!same)
2064                         {
2065                         const gchar *dest_ext = extension_from_path(fd->change->dest);
2066                         if (!dest_ext) dest_ext = "";
2067
2068                         if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2069                                 {
2070                                 ret |= CHANGE_WARN_CHANGED_EXT;
2071                                 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2072                                 }
2073                         }
2074                 else
2075                         {
2076                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2077                                 {
2078                                 ret |= CHANGE_WARN_SAME;
2079                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2080                                 }
2081                         }
2082
2083                 dest_dir = remove_level_from_path(fd->change->dest);
2084
2085                 if (!isdir(dest_dir))
2086                         {
2087                         ret |= CHANGE_NO_DEST_DIR;
2088                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2089                         }
2090                 else if (!access_file(dest_dir, W_OK))
2091                         {
2092                         ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2093                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2094                         }
2095                 else if (!same)
2096                         {
2097                         if (isfile(fd->change->dest))
2098                                 {
2099                                 if (!access_file(fd->change->dest, W_OK))
2100                                         {
2101                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
2102                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2103                                         }
2104                                 else
2105                                         {
2106                                         ret |= CHANGE_WARN_DEST_EXISTS;
2107                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2108                                         }
2109                                 }
2110                         else if (isdir(fd->change->dest))
2111                                 {
2112                                 ret |= CHANGE_DEST_EXISTS;
2113                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2114                                 }
2115                         }
2116
2117                 g_free(dest_dir);
2118                 }
2119                 
2120         fd->change->error = ret;
2121         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2122
2123         g_free(dir);
2124         return ret;
2125 }
2126
2127
2128 gint file_data_sc_verify_ci(FileData *fd)
2129 {
2130         GList *work;
2131         gint ret;
2132
2133         ret = file_data_verify_ci(fd);
2134
2135         work = fd->sidecar_files;
2136         while (work)
2137                 {
2138                 FileData *sfd = work->data;
2139
2140                 ret |= file_data_verify_ci(sfd);
2141                 work = work->next;
2142                 }
2143
2144         return ret;
2145 }
2146
2147 gchar *file_data_get_error_string(gint error)
2148 {
2149         GString *result = g_string_new("");
2150
2151         if (error & CHANGE_NO_SRC)
2152                 {
2153                 if (result->len > 0) g_string_append(result, ", ");
2154                 g_string_append(result, _("file or directory does not exist"));
2155                 }
2156
2157         if (error & CHANGE_DEST_EXISTS)
2158                 {
2159                 if (result->len > 0) g_string_append(result, ", ");
2160                 g_string_append(result, _("destination already exists"));
2161                 }
2162
2163         if (error & CHANGE_NO_WRITE_PERM_DEST)
2164                 {
2165                 if (result->len > 0) g_string_append(result, ", ");
2166                 g_string_append(result, _("destination can't be overwritten"));
2167                 }
2168
2169         if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2170                 {
2171                 if (result->len > 0) g_string_append(result, ", ");
2172                 g_string_append(result, _("destination directory is not writable"));
2173                 }
2174
2175         if (error & CHANGE_NO_DEST_DIR)
2176                 {
2177                 if (result->len > 0) g_string_append(result, ", ");
2178                 g_string_append(result, _("destination directory does not exist"));
2179                 }
2180
2181         if (error & CHANGE_NO_WRITE_PERM_DIR)
2182                 {
2183                 if (result->len > 0) g_string_append(result, ", ");
2184                 g_string_append(result, _("source directory is not writable"));
2185                 }
2186
2187         if (error & CHANGE_NO_READ_PERM)
2188                 {
2189                 if (result->len > 0) g_string_append(result, ", ");
2190                 g_string_append(result, _("no read permission"));
2191                 }
2192
2193         if (error & CHANGE_WARN_NO_WRITE_PERM)
2194                 {
2195                 if (result->len > 0) g_string_append(result, ", ");
2196                 g_string_append(result, _("file is readonly"));
2197                 }
2198
2199         if (error & CHANGE_WARN_DEST_EXISTS)
2200                 {
2201                 if (result->len > 0) g_string_append(result, ", ");
2202                 g_string_append(result, _("destination already exists and will be overwritten"));
2203                 }
2204                 
2205         if (error & CHANGE_WARN_SAME)
2206                 {
2207                 if (result->len > 0) g_string_append(result, ", ");
2208                 g_string_append(result, _("source and destination are the same"));
2209                 }
2210
2211         if (error & CHANGE_WARN_CHANGED_EXT)
2212                 {
2213                 if (result->len > 0) g_string_append(result, ", ");
2214                 g_string_append(result, _("source and destination have different extension"));
2215                 }
2216
2217         if (error & CHANGE_WARN_UNSAVED_META)
2218                 {
2219                 if (result->len > 0) g_string_append(result, ", ");
2220                 g_string_append(result, _("there are unsaved metadata changes for the file"));
2221                 }
2222
2223         return g_string_free(result, FALSE);
2224 }
2225
2226 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2227 {
2228         GList *work;
2229         gint all_errors = 0;
2230         gint common_errors = ~0;
2231         gint num;
2232         gint *errors;
2233         gint i;
2234         
2235         if (!list) return 0;
2236         
2237         num = g_list_length(list);
2238         errors = g_new(int, num);
2239         work = list;
2240         i = 0;
2241         while (work)
2242                 {
2243                 FileData *fd;
2244                 gint error;
2245
2246                 fd = work->data;
2247                 work = work->next;
2248                         
2249                 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2250                 all_errors |= error;
2251                 common_errors &= error;
2252                 
2253                 errors[i] = error;
2254                 
2255                 i++;
2256                 }
2257         
2258         if (desc && all_errors)
2259                 {
2260                 GList *work;
2261                 GString *result = g_string_new("");
2262                 
2263                 if (common_errors)
2264                         {
2265                         gchar *str = file_data_get_error_string(common_errors);
2266                         g_string_append(result, str);
2267                         g_string_append(result, "\n");
2268                         g_free(str);
2269                         }
2270                 
2271                 work = list;
2272                 i = 0;
2273                 while (work)
2274                         {
2275                         FileData *fd;
2276                         gint error;
2277
2278                         fd = work->data;
2279                         work = work->next;
2280                         
2281                         error = errors[i] & ~common_errors;
2282                         
2283                         if (error)
2284                                 {
2285                                 gchar *str = file_data_get_error_string(error);
2286                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2287                                 g_free(str);
2288                                 }
2289                         i++;
2290                         }
2291                 *desc = g_string_free(result, FALSE);
2292                 }
2293
2294         g_free(errors);
2295         return all_errors;
2296 }
2297
2298
2299 /*
2300  * perform the change described by FileFataChangeInfo
2301  * it is used for internal operations,
2302  * this function actually operates with files on the filesystem
2303  * it should implement safe delete
2304  */
2305
2306 static gboolean file_data_perform_move(FileData *fd)
2307 {
2308         g_assert(!strcmp(fd->change->source, fd->path));
2309         return move_file(fd->change->source, fd->change->dest);
2310 }
2311
2312 static gboolean file_data_perform_copy(FileData *fd)
2313 {
2314         g_assert(!strcmp(fd->change->source, fd->path));
2315         return copy_file(fd->change->source, fd->change->dest);
2316 }
2317
2318 static gboolean file_data_perform_delete(FileData *fd)
2319 {
2320         if (isdir(fd->path) && !islink(fd->path))
2321                 return rmdir_utf8(fd->path);
2322         else
2323                 if (options->file_ops.safe_delete_enable)
2324                         return file_util_safe_unlink(fd->path);
2325                 else
2326                         return unlink_file(fd->path);
2327 }
2328
2329 gboolean file_data_perform_ci(FileData *fd)
2330 {
2331         FileDataChangeType type = fd->change->type;
2332
2333         switch (type)
2334                 {
2335                 case FILEDATA_CHANGE_MOVE:
2336                         return file_data_perform_move(fd);
2337                 case FILEDATA_CHANGE_COPY:
2338                         return file_data_perform_copy(fd);
2339                 case FILEDATA_CHANGE_RENAME:
2340                         return file_data_perform_move(fd); /* the same as move */
2341                 case FILEDATA_CHANGE_DELETE:
2342                         return file_data_perform_delete(fd);
2343                 case FILEDATA_CHANGE_WRITE_METADATA:
2344                         return metadata_write_perform(fd);
2345                 case FILEDATA_CHANGE_UNSPECIFIED:
2346                         /* nothing to do here */
2347                         break;
2348                 }
2349         return TRUE;
2350 }
2351
2352
2353
2354 gboolean file_data_sc_perform_ci(FileData *fd)
2355 {
2356         GList *work;
2357         gboolean ret = TRUE;
2358         FileDataChangeType type = fd->change->type;
2359         
2360         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2361
2362         work = fd->sidecar_files;
2363         while (work)
2364                 {
2365                 FileData *sfd = work->data;
2366                 
2367                 if (!file_data_perform_ci(sfd)) ret = FALSE;
2368                 work = work->next;
2369                 }
2370         
2371         if (!file_data_perform_ci(fd)) ret = FALSE;
2372         
2373         return ret;
2374 }
2375
2376 /*
2377  * updates FileData structure according to FileDataChangeInfo
2378  */
2379
2380 gboolean file_data_apply_ci(FileData *fd)
2381 {
2382         FileDataChangeType type = fd->change->type;
2383
2384         /* FIXME delete ?*/
2385         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2386                 {
2387                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2388                 file_data_planned_change_remove(fd);
2389                 
2390                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2391                         {
2392                         /* this change overwrites another file which is already known to other modules
2393                            renaming fd would create duplicate FileData structure
2394                            the best thing we can do is nothing
2395                            FIXME: maybe we could copy stuff like marks
2396                         */
2397                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2398                         }
2399                 else
2400                         {
2401                         file_data_set_path(fd, fd->change->dest);
2402                         }
2403                 }
2404         file_data_increment_version(fd);
2405         file_data_send_notification(fd, NOTIFY_CHANGE);
2406         
2407         return TRUE;
2408 }
2409
2410 gboolean file_data_sc_apply_ci(FileData *fd)
2411 {
2412         GList *work;
2413         FileDataChangeType type = fd->change->type;
2414         
2415         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2416
2417         work = fd->sidecar_files;
2418         while (work)
2419                 {
2420                 FileData *sfd = work->data;
2421                 
2422                 file_data_apply_ci(sfd);
2423                 work = work->next;
2424                 }
2425         
2426         file_data_apply_ci(fd);
2427         
2428         return TRUE;
2429 }
2430
2431 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2432 {
2433         GList *work;
2434         if (fd->parent) fd = fd->parent;
2435         if (!g_list_find(list, fd)) return FALSE;
2436         
2437         work = fd->sidecar_files;
2438         while (work)
2439                 {
2440                 if (!g_list_find(list, work->data)) return FALSE;
2441                 work = work->next;
2442                 }
2443         return TRUE;
2444 }
2445
2446 #if 0
2447 static gboolean file_data_list_dump(GList *list)
2448 {
2449         GList *work, *work2;
2450
2451         work = list;
2452         while (work)
2453                 {
2454                 FileData *fd = work->data;
2455                 printf("%s\n", fd->name);
2456                 work2 = fd->sidecar_files;
2457                 while (work2)
2458                         {
2459                         FileData *fd = work2->data;
2460                         printf("       %s\n", fd->name);
2461                         work2 = work2->next;
2462                         }
2463                 work = work->next;
2464                 }
2465         return TRUE;
2466 }
2467 #endif
2468
2469 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2470 {
2471         GList *out = NULL;
2472         GList *work = list;
2473
2474         /* change partial groups to independent files */
2475         if (ungroup)
2476                 {
2477                 while (work)
2478                         {
2479                         FileData *fd = work->data;
2480                         work = work->next;
2481                 
2482                         if (!file_data_list_contains_whole_group(list, fd)) 
2483                                 {
2484                                 file_data_disable_grouping(fd, TRUE);
2485                                 if (ungrouped_list) 
2486                                         {
2487                                         *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2488                                         }
2489                                 }
2490                         }
2491                 }
2492         
2493         /* remove sidecars from the list, 
2494            they can be still acessed via main_fd->sidecar_files */
2495         work = list;
2496         while (work)
2497                 {
2498                 FileData *fd = work->data;
2499                 work = work->next;
2500                 
2501                 if (!fd->parent ||
2502                     (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2503                         {
2504                         out = g_list_prepend(out, file_data_ref(fd));
2505                         }
2506                 }
2507                 
2508         filelist_free(list);
2509         out = g_list_reverse(out);
2510
2511         return out;
2512 }
2513
2514
2515
2516
2517
2518 /*
2519  * notify other modules about the change described by FileDataChangeInfo
2520  */
2521
2522 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2523    FIXME do we need the ignore_list? It looks like a workaround for ineffective
2524    implementation in view_file_list.c */
2525
2526
2527 typedef struct _NotifyIdleData NotifyIdleData;
2528
2529 struct _NotifyIdleData {
2530         FileData *fd;
2531         NotifyType type;
2532 };
2533
2534
2535 typedef struct _NotifyData NotifyData;
2536
2537 struct _NotifyData {
2538         FileDataNotifyFunc func;
2539         gpointer data;
2540         NotifyPriority priority;
2541 };
2542
2543 static GList *notify_func_list = NULL;
2544
2545 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2546 {
2547         NotifyData *nda = (NotifyData *)a;
2548         NotifyData *ndb = (NotifyData *)b;
2549
2550         if (nda->priority < ndb->priority) return -1;
2551         if (nda->priority > ndb->priority) return 1;
2552         return 0;
2553 }
2554
2555 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2556 {
2557         NotifyData *nd;
2558         GList *work = notify_func_list;
2559         
2560         while (work)
2561                 {
2562                 NotifyData *nd = (NotifyData *)work->data;
2563         
2564                 if (nd->func == func && nd->data == data)
2565                         {
2566                         g_warning("Notify func already registered");
2567                         return FALSE;
2568                         }
2569                 work = work->next;
2570                 }
2571         
2572         nd = g_new(NotifyData, 1);
2573         nd->func = func;
2574         nd->data = data;
2575         nd->priority = priority;
2576
2577         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2578         DEBUG_2("Notify func registered: %p", nd);
2579         
2580         return TRUE;
2581 }
2582
2583 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2584 {
2585         GList *work = notify_func_list;
2586         
2587         while (work)
2588                 {
2589                 NotifyData *nd = (NotifyData *)work->data;
2590         
2591                 if (nd->func == func && nd->data == data)
2592                         {
2593                         notify_func_list = g_list_delete_link(notify_func_list, work);
2594                         g_free(nd);
2595                         DEBUG_2("Notify func unregistered: %p", nd);
2596                         return TRUE;
2597                         }
2598                 work = work->next;
2599                 }
2600
2601         g_warning("Notify func not found");
2602         return FALSE;
2603 }
2604
2605
2606 gboolean file_data_send_notification_idle_cb(gpointer data)
2607 {
2608         NotifyIdleData *nid = (NotifyIdleData *)data;
2609         GList *work = notify_func_list;
2610
2611         while (work)
2612                 {
2613                 NotifyData *nd = (NotifyData *)work->data;
2614                 
2615                 nd->func(nid->fd, nid->type, nd->data);
2616                 work = work->next;
2617                 }
2618         file_data_unref(nid->fd);
2619         g_free(nid);
2620         return FALSE;
2621 }
2622
2623 void file_data_send_notification(FileData *fd, NotifyType type)
2624 {
2625         NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2626         nid->fd = file_data_ref(fd);
2627         nid->type = type;
2628         g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2629 }
2630
2631 static GHashTable *file_data_monitor_pool = NULL;
2632 static guint realtime_monitor_id = 0; /* event source id */
2633
2634 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2635 {
2636         FileData *fd = key;
2637
2638         file_data_check_changed_files(fd);
2639         
2640         DEBUG_1("monitor %s", fd->path);
2641 }
2642
2643 static gboolean realtime_monitor_cb(gpointer data)
2644 {
2645         if (!options->update_on_time_change) return TRUE;
2646         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2647         return TRUE;
2648 }
2649
2650 gboolean file_data_register_real_time_monitor(FileData *fd)
2651 {
2652         gint count;
2653         
2654         file_data_ref(fd);
2655         
2656         if (!file_data_monitor_pool)
2657                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2658         
2659         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2660
2661         DEBUG_1("Register realtime %d %s", count, fd->path);
2662         
2663         count++;
2664         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2665         
2666         if (!realtime_monitor_id)
2667                 {
2668                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2669                 }
2670         
2671         return TRUE;
2672 }
2673
2674 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2675 {
2676         gint count;
2677
2678         g_assert(file_data_monitor_pool);
2679         
2680         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2681         
2682         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2683         
2684         g_assert(count > 0);
2685         
2686         count--;
2687         
2688         if (count == 0)
2689                 g_hash_table_remove(file_data_monitor_pool, fd);
2690         else
2691                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2692
2693         file_data_unref(fd);
2694         
2695         if (g_hash_table_size(file_data_monitor_pool) == 0)
2696                 {
2697                 g_source_remove(realtime_monitor_id);
2698                 realtime_monitor_id = 0;
2699                 return FALSE;
2700                 }
2701         
2702         return TRUE;
2703 }
2704 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */