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