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