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