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