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