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