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