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