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