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