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