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