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