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