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