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