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