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