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