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