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