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