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