Additional file info parameters
[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 GList *filelist_recursive(FileData *dir_fd)
1637 {
1638         GList *list;
1639         GList *d;
1640
1641         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1642         list = filelist_filter(list, FALSE);
1643         list = filelist_sort_path(list);
1644
1645         d = filelist_filter(d, TRUE);
1646         d = filelist_sort_path(d);
1647         filelist_recursive_append(&list, d);
1648         filelist_free(d);
1649
1650         return list;
1651 }
1652
1653 /*
1654  *-----------------------------------------------------------------------------
1655  * file modification support
1656  *-----------------------------------------------------------------------------
1657  */
1658
1659
1660 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1661 {
1662         if (!fdci && fd) fdci = fd->change;
1663
1664         if (!fdci) return;
1665
1666         g_free(fdci->source);
1667         g_free(fdci->dest);
1668
1669         g_free(fdci);
1670
1671         if (fd) fd->change = NULL;
1672 }
1673
1674 static gboolean file_data_can_write_directly(FileData *fd)
1675 {
1676         return filter_name_is_writable(fd->extension);
1677 }
1678
1679 static gboolean file_data_can_write_sidecar(FileData *fd)
1680 {
1681         return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1682 }
1683
1684 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1685 {
1686         gchar *sidecar_path = NULL;
1687         GList *work;
1688
1689         if (!file_data_can_write_sidecar(fd)) return NULL;
1690
1691         work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1692         gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1693         while (work)
1694                 {
1695                 FileData *sfd = work->data;
1696                 work = work->next;
1697                 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1698                         {
1699                         sidecar_path = g_strdup(sfd->path);
1700                         break;
1701                         }
1702                 }
1703         g_free(extended_extension);
1704
1705         if (!existing_only && !sidecar_path)
1706                 {
1707                 if (options->metadata.sidecar_extended_name)
1708                         sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1709                 else
1710                         {
1711                         gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1712                         sidecar_path = g_strconcat(base, ".xmp", NULL);
1713                         g_free(base);
1714                         }
1715                 }
1716
1717         return sidecar_path;
1718 }
1719
1720 /*
1721  * marks and orientation
1722  */
1723
1724 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1725 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1726 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1727 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1728
1729 gboolean file_data_get_mark(FileData *fd, gint n)
1730 {
1731         gboolean valid = (fd->valid_marks & (1 << n));
1732
1733         if (file_data_get_mark_func[n] && !valid)
1734                 {
1735                 guint old = fd->marks;
1736                 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1737
1738                 if (!value != !(fd->marks & (1 << n)))
1739                         {
1740                         fd->marks = fd->marks ^ (1 << n);
1741                         }
1742
1743                 fd->valid_marks |= (1 << n);
1744                 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1745                         {
1746                         file_data_unref(fd);
1747                         }
1748                 else if (!old && fd->marks)
1749                         {
1750                         file_data_ref(fd);
1751                         }
1752                 }
1753
1754         return !!(fd->marks & (1 << n));
1755 }
1756
1757 guint file_data_get_marks(FileData *fd)
1758 {
1759         gint i;
1760         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1761         return fd->marks;
1762 }
1763
1764 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1765 {
1766         guint old;
1767         if (!value == !file_data_get_mark(fd, n)) return;
1768
1769         if (file_data_set_mark_func[n])
1770                 {
1771                 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1772                 }
1773
1774         old = fd->marks;
1775
1776         fd->marks = fd->marks ^ (1 << n);
1777
1778         if (old && !fd->marks) /* keep files with non-zero marks in memory */
1779                 {
1780                 file_data_unref(fd);
1781                 }
1782         else if (!old && fd->marks)
1783                 {
1784                 file_data_ref(fd);
1785                 }
1786
1787         file_data_increment_version(fd);
1788         file_data_send_notification(fd, NOTIFY_MARKS);
1789 }
1790
1791 gboolean file_data_filter_marks(FileData *fd, guint filter)
1792 {
1793         gint i;
1794         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1795         return ((fd->marks & filter) == filter);
1796 }
1797
1798 GList *file_data_filter_marks_list(GList *list, guint filter)
1799 {
1800         GList *work;
1801
1802         work = list;
1803         while (work)
1804                 {
1805                 FileData *fd = work->data;
1806                 GList *link = work;
1807                 work = work->next;
1808
1809                 if (!file_data_filter_marks(fd, filter))
1810                         {
1811                         list = g_list_remove_link(list, link);
1812                         file_data_unref(fd);
1813                         g_list_free(link);
1814                         }
1815                 }
1816
1817         return list;
1818 }
1819
1820 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1821 {
1822         FileData *fd = value;
1823         file_data_increment_version(fd);
1824         file_data_send_notification(fd, NOTIFY_MARKS);
1825 }
1826
1827 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1828 {
1829         if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1830
1831         if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1832
1833         file_data_get_mark_func[n] = get_mark_func;
1834         file_data_set_mark_func[n] = set_mark_func;
1835         file_data_mark_func_data[n] = data;
1836         file_data_destroy_mark_func[n] = notify;
1837
1838         if (get_mark_func && file_data_pool)
1839                 {
1840                 /* this effectively changes all known files */
1841                 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1842                 }
1843
1844         return TRUE;
1845 }
1846
1847 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1848 {
1849         if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1850         if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1851         if (data) *data = file_data_mark_func_data[n];
1852 }
1853
1854 gint file_data_get_user_orientation(FileData *fd)
1855 {
1856         return fd->user_orientation;
1857 }
1858
1859 void file_data_set_user_orientation(FileData *fd, gint value)
1860 {
1861         if (fd->user_orientation == value) return;
1862
1863         fd->user_orientation = value;
1864         file_data_increment_version(fd);
1865         file_data_send_notification(fd, NOTIFY_ORIENTATION);
1866 }
1867
1868
1869 /*
1870  * file_data    - operates on the given fd
1871  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1872  */
1873
1874
1875 /* return list of sidecar file extensions in a string */
1876 gchar *file_data_sc_list_to_string(FileData *fd)
1877 {
1878         GList *work;
1879         GString *result = g_string_new("");
1880
1881         work = fd->sidecar_files;
1882         while (work)
1883                 {
1884                 FileData *sfd = work->data;
1885
1886                 result = g_string_append(result, "+ ");
1887                 result = g_string_append(result, sfd->extension);
1888                 work = work->next;
1889                 if (work) result = g_string_append_c(result, ' ');
1890                 }
1891
1892         return g_string_free(result, FALSE);
1893 }
1894
1895
1896
1897 /*
1898  * add FileDataChangeInfo (see typedefs.h) for the given operation
1899  * uses file_data_add_change_info
1900  *
1901  * fails if the fd->change already exists - change operations can't run in parallel
1902  * fd->change_info works as a lock
1903  *
1904  * dest can be NULL - in this case the current name is used for now, it will
1905  * be changed later
1906  */
1907
1908 /*
1909    FileDataChangeInfo types:
1910    COPY
1911    MOVE   - path is changed, name may be changed too
1912    RENAME - path remains unchanged, name is changed
1913             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1914             sidecar names are changed too, extensions are not changed
1915    DELETE
1916    UPDATE - file size, date or grouping has been changed
1917 */
1918
1919 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1920 {
1921         FileDataChangeInfo *fdci;
1922
1923         if (fd->change) return FALSE;
1924
1925         fdci = g_new0(FileDataChangeInfo, 1);
1926
1927         fdci->type = type;
1928
1929         if (src)
1930                 fdci->source = g_strdup(src);
1931         else
1932                 fdci->source = g_strdup(fd->path);
1933
1934         if (dest)
1935                 fdci->dest = g_strdup(dest);
1936
1937         fd->change = fdci;
1938
1939         return TRUE;
1940 }
1941
1942 static void file_data_planned_change_remove(FileData *fd)
1943 {
1944         if (file_data_planned_change_hash &&
1945             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1946                 {
1947                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1948                         {
1949                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1950                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1951                         file_data_unref(fd);
1952                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1953                                 {
1954                                 g_hash_table_destroy(file_data_planned_change_hash);
1955                                 file_data_planned_change_hash = NULL;
1956                                 DEBUG_1("planned change: empty");
1957                                 }
1958                         }
1959                 }
1960 }
1961
1962
1963 void file_data_free_ci(FileData *fd)
1964 {
1965         FileDataChangeInfo *fdci = fd->change;
1966
1967         if (!fdci) return;
1968
1969         file_data_planned_change_remove(fd);
1970
1971         if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1972
1973         g_free(fdci->source);
1974         g_free(fdci->dest);
1975
1976         g_free(fdci);
1977
1978         fd->change = NULL;
1979 }
1980
1981 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1982 {
1983         FileDataChangeInfo *fdci = fd->change;
1984         if (!fdci) return;
1985         fdci->regroup_when_finished = enable;
1986 }
1987
1988 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1989 {
1990         GList *work;
1991
1992         if (fd->parent) fd = fd->parent;
1993
1994         if (fd->change) return FALSE;
1995
1996         work = fd->sidecar_files;
1997         while (work)
1998                 {
1999                 FileData *sfd = work->data;
2000
2001                 if (sfd->change) return FALSE;
2002                 work = work->next;
2003                 }
2004
2005         file_data_add_ci(fd, type, NULL, NULL);
2006
2007         work = fd->sidecar_files;
2008         while (work)
2009                 {
2010                 FileData *sfd = work->data;
2011
2012                 file_data_add_ci(sfd, type, NULL, NULL);
2013                 work = work->next;
2014                 }
2015
2016         return TRUE;
2017 }
2018
2019 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2020 {
2021         GList *work;
2022
2023         if (fd->parent) fd = fd->parent;
2024
2025         if (!fd->change || fd->change->type != type) return FALSE;
2026
2027         work = fd->sidecar_files;
2028         while (work)
2029                 {
2030                 FileData *sfd = work->data;
2031
2032                 if (!sfd->change || sfd->change->type != type) return FALSE;
2033                 work = work->next;
2034                 }
2035
2036         return TRUE;
2037 }
2038
2039
2040 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2041 {
2042         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2043         file_data_sc_update_ci_copy(fd, dest_path);
2044         return TRUE;
2045 }
2046
2047 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2048 {
2049         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2050         file_data_sc_update_ci_move(fd, dest_path);
2051         return TRUE;
2052 }
2053
2054 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2055 {
2056         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2057         file_data_sc_update_ci_rename(fd, dest_path);
2058         return TRUE;
2059 }
2060
2061 gboolean file_data_sc_add_ci_delete(FileData *fd)
2062 {
2063         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2064 }
2065
2066 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2067 {
2068         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2069         file_data_sc_update_ci_unspecified(fd, dest_path);
2070         return TRUE;
2071 }
2072
2073 gboolean file_data_add_ci_write_metadata(FileData *fd)
2074 {
2075         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2076 }
2077
2078 void file_data_sc_free_ci(FileData *fd)
2079 {
2080         GList *work;
2081
2082         if (fd->parent) fd = fd->parent;
2083
2084         file_data_free_ci(fd);
2085
2086         work = fd->sidecar_files;
2087         while (work)
2088                 {
2089                 FileData *sfd = work->data;
2090
2091                 file_data_free_ci(sfd);
2092                 work = work->next;
2093                 }
2094 }
2095
2096 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2097 {
2098         GList *work;
2099         gboolean ret = TRUE;
2100
2101         work = fd_list;
2102         while (work)
2103                 {
2104                 FileData *fd = work->data;
2105
2106                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2107                 work = work->next;
2108                 }
2109
2110         return ret;
2111 }
2112
2113 static void file_data_sc_revert_ci_list(GList *fd_list)
2114 {
2115         GList *work;
2116
2117         work = fd_list;
2118         while (work)
2119                 {
2120                 FileData *fd = work->data;
2121
2122                 file_data_sc_free_ci(fd);
2123                 work = work->prev;
2124                 }
2125 }
2126
2127 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2128 {
2129         GList *work;
2130
2131         work = fd_list;
2132         while (work)
2133                 {
2134                 FileData *fd = work->data;
2135
2136                 if (!func(fd, dest))
2137                         {
2138                         file_data_sc_revert_ci_list(work->prev);
2139                         return FALSE;
2140                         }
2141                 work = work->next;
2142                 }
2143
2144         return TRUE;
2145 }
2146
2147 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2148 {
2149         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2150 }
2151
2152 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2153 {
2154         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2155 }
2156
2157 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2158 {
2159         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2160 }
2161
2162 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2163 {
2164         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2165 }
2166
2167 gboolean file_data_add_ci_write_metadata_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_add_ci_write_metadata(fd)) ret = FALSE;
2178                 work = work->next;
2179                 }
2180
2181         return ret;
2182 }
2183
2184 void file_data_free_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_free_ci(fd);
2194                 work = work->next;
2195                 }
2196 }
2197
2198 void file_data_sc_free_ci_list(GList *fd_list)
2199 {
2200         GList *work;
2201
2202         work = fd_list;
2203         while (work)
2204                 {
2205                 FileData *fd = work->data;
2206
2207                 file_data_sc_free_ci(fd);
2208                 work = work->next;
2209                 }
2210 }
2211
2212 /*
2213  * update existing fd->change, it will be used from dialog callbacks for interactive editing
2214  * fails if fd->change does not exist or the change type does not match
2215  */
2216
2217 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2218 {
2219         FileDataChangeType type = fd->change->type;
2220
2221         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2222                 {
2223                 FileData *ofd;
2224
2225                 if (!file_data_planned_change_hash)
2226                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2227
2228                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2229                         {
2230                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2231                         g_hash_table_remove(file_data_planned_change_hash, old_path);
2232                         file_data_unref(fd);
2233                         }
2234
2235                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2236                 if (ofd != fd)
2237                         {
2238                         if (ofd)
2239                                 {
2240                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2241                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
2242                                 file_data_unref(ofd);
2243                                 }
2244
2245                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2246                         file_data_ref(fd);
2247                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2248                         }
2249                 }
2250 }
2251
2252 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2253 {
2254         gchar *old_path = fd->change->dest;
2255
2256         fd->change->dest = g_strdup(dest_path);
2257         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2258         g_free(old_path);
2259 }
2260
2261 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2262 {
2263         const gchar *extension = registered_extension_from_path(fd->change->source);
2264         gchar *base = remove_extension_from_path(dest_path);
2265         gchar *old_path = fd->change->dest;
2266
2267         fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2268         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2269
2270         g_free(old_path);
2271         g_free(base);
2272 }
2273
2274 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2275 {
2276         GList *work;
2277         gchar *dest_path_full = NULL;
2278
2279         if (fd->parent) fd = fd->parent;
2280
2281         if (!dest_path)
2282                 {
2283                 dest_path = fd->path;
2284                 }
2285         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2286                 {
2287                 gchar *dir = remove_level_from_path(fd->path);
2288
2289                 dest_path_full = g_build_filename(dir, dest_path, NULL);
2290                 g_free(dir);
2291                 dest_path = dest_path_full;
2292                 }
2293         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2294                 {
2295                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2296                 dest_path = dest_path_full;
2297                 }
2298
2299         file_data_update_ci_dest(fd, dest_path);
2300
2301         work = fd->sidecar_files;
2302         while (work)
2303                 {
2304                 FileData *sfd = work->data;
2305
2306                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2307                 work = work->next;
2308                 }
2309
2310         g_free(dest_path_full);
2311 }
2312
2313 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2314 {
2315         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2316         file_data_sc_update_ci(fd, dest_path);
2317         return TRUE;
2318 }
2319
2320 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2321 {
2322         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2323 }
2324
2325 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2326 {
2327         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2328 }
2329
2330 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2331 {
2332         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2333 }
2334
2335 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2336 {
2337         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2338 }
2339
2340 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2341                                                       const gchar *dest,
2342                                                       gboolean (*func)(FileData *, const gchar *))
2343 {
2344         GList *work;
2345         gboolean ret = TRUE;
2346
2347         work = fd_list;
2348         while (work)
2349                 {
2350                 FileData *fd = work->data;
2351
2352                 if (!func(fd, dest)) ret = FALSE;
2353                 work = work->next;
2354                 }
2355
2356         return ret;
2357 }
2358
2359 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2360 {
2361         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2362 }
2363
2364 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2365 {
2366         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2367 }
2368
2369 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2370 {
2371         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2372 }
2373
2374
2375 /*
2376  * verify source and dest paths - dest image exists, etc.
2377  * it should detect all possible problems with the planned operation
2378  */
2379
2380 gint file_data_verify_ci(FileData *fd, GList *list)
2381 {
2382         gint ret = CHANGE_OK;
2383         gchar *dir;
2384         GList *work = NULL;
2385         FileData *fd1 = NULL;
2386
2387         if (!fd->change)
2388                 {
2389                 DEBUG_1("Change checked: no change info: %s", fd->path);
2390                 return ret;
2391                 }
2392
2393         if (!isname(fd->path))
2394                 {
2395                 /* this probably should not happen */
2396                 ret |= CHANGE_NO_SRC;
2397                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2398                 return ret;
2399                 }
2400
2401         dir = remove_level_from_path(fd->path);
2402
2403         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2404             fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2405             fd->change->type != FILEDATA_CHANGE_RENAME &&
2406             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2407             fd->modified_xmp)
2408                 {
2409                 ret |= CHANGE_WARN_UNSAVED_META;
2410                 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2411                 }
2412
2413         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2414             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2415             !access_file(fd->path, R_OK))
2416                 {
2417                 ret |= CHANGE_NO_READ_PERM;
2418                 DEBUG_1("Change checked: no read permission: %s", fd->path);
2419                 }
2420         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2421                  !access_file(dir, W_OK))
2422                 {
2423                 ret |= CHANGE_NO_WRITE_PERM_DIR;
2424                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2425                 }
2426         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2427                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2428                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2429                  !access_file(fd->path, W_OK))
2430                 {
2431                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2432                 DEBUG_1("Change checked: no write permission: %s", fd->path);
2433                 }
2434         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2435            - that means that there are no hard errors and warnings can be disabled
2436            - the destination is determined during the check
2437         */
2438         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2439                 {
2440                 /* determine destination file */
2441                 gboolean have_dest = FALSE;
2442                 gchar *dest_dir = NULL;
2443
2444                 if (options->metadata.save_in_image_file)
2445                         {
2446                         if (file_data_can_write_directly(fd))
2447                                 {
2448                                 /* we can write the file directly */
2449                                 if (access_file(fd->path, W_OK))
2450                                         {
2451                                         have_dest = TRUE;
2452                                         }
2453                                 else
2454                                         {
2455                                         if (options->metadata.warn_on_write_problems)
2456                                                 {
2457                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2458                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2459                                                 }
2460                                         }
2461                                 }
2462                         else if (file_data_can_write_sidecar(fd))
2463                                 {
2464                                 /* we can write sidecar */
2465                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2466                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2467                                         {
2468                                         file_data_update_ci_dest(fd, sidecar);
2469                                         have_dest = TRUE;
2470                                         }
2471                                 else
2472                                         {
2473                                         if (options->metadata.warn_on_write_problems)
2474                                                 {
2475                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2476                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2477                                                 }
2478                                         }
2479                                 g_free(sidecar);
2480                                 }
2481                         }
2482
2483                 if (!have_dest)
2484                         {
2485                         /* write private metadata file under ~/.geeqie */
2486
2487                         /* If an existing metadata file exists, we will try writing to
2488                          * it's location regardless of the user's preference.
2489                          */
2490                         gchar *metadata_path = NULL;
2491 #ifdef HAVE_EXIV2
2492                         /* but ignore XMP if we are not able to write it */
2493                         metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2494 #endif
2495                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2496
2497                         if (metadata_path && !access_file(metadata_path, W_OK))
2498                                 {
2499                                 g_free(metadata_path);
2500                                 metadata_path = NULL;
2501                                 }
2502
2503                         if (!metadata_path)
2504                                 {
2505                                 mode_t mode = 0755;
2506
2507                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2508                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2509                                         {
2510                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2511
2512                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
2513                                         g_free(filename);
2514                                         }
2515                                 }
2516                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2517                                 {
2518                                 file_data_update_ci_dest(fd, metadata_path);
2519                                 have_dest = TRUE;
2520                                 }
2521                         else
2522                                 {
2523                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
2524                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2525                                 }
2526                         g_free(metadata_path);
2527                         }
2528                 g_free(dest_dir);
2529                 }
2530
2531         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2532                 {
2533                 gboolean same;
2534                 gchar *dest_dir;
2535
2536                 same = (strcmp(fd->path, fd->change->dest) == 0);
2537
2538                 if (!same)
2539                         {
2540                         const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2541                         if (!dest_ext) dest_ext = "";
2542                         if (!options->file_filter.disable_file_extension_checks)
2543                                 {
2544                                 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2545                                         {
2546                                         ret |= CHANGE_WARN_CHANGED_EXT;
2547                                         DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2548                                         }
2549                                 }
2550                         }
2551                 else
2552                         {
2553                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2554                                 {
2555                                 ret |= CHANGE_WARN_SAME;
2556                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2557                                 }
2558                         }
2559
2560                 dest_dir = remove_level_from_path(fd->change->dest);
2561
2562                 if (!isdir(dest_dir))
2563                         {
2564                         ret |= CHANGE_NO_DEST_DIR;
2565                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2566                         }
2567                 else if (!access_file(dest_dir, W_OK))
2568                         {
2569                         ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2570                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2571                         }
2572                 else if (!same)
2573                         {
2574                         if (isfile(fd->change->dest))
2575                                 {
2576                                 if (!access_file(fd->change->dest, W_OK))
2577                                         {
2578                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
2579                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2580                                         }
2581                                 else
2582                                         {
2583                                         ret |= CHANGE_WARN_DEST_EXISTS;
2584                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2585                                         }
2586                                 }
2587                         else if (isdir(fd->change->dest))
2588                                 {
2589                                 ret |= CHANGE_DEST_EXISTS;
2590                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2591                                 }
2592                         }
2593
2594                 g_free(dest_dir);
2595                 }
2596
2597         /* During a rename operation, check if another planned destination file has
2598          * the same filename
2599          */
2600         if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2601                                 fd->change->type == FILEDATA_CHANGE_COPY ||
2602                                 fd->change->type == FILEDATA_CHANGE_MOVE)
2603                 {
2604                 work = list;
2605                 while (work)
2606                         {
2607                         fd1 = work->data;
2608                         work = work->next;
2609                         if (fd1 != NULL && fd != fd1 )
2610                                 {
2611                                 if (!strcmp(fd->change->dest, fd1->change->dest))
2612                                         {
2613                                         ret |= CHANGE_DUPLICATE_DEST;
2614                                         }
2615                                 }
2616                         }
2617                 }
2618
2619         fd->change->error = ret;
2620         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2621
2622         g_free(dir);
2623         return ret;
2624 }
2625
2626
2627 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2628 {
2629         GList *work;
2630         gint ret;
2631
2632         ret = file_data_verify_ci(fd, list);
2633
2634         work = fd->sidecar_files;
2635         while (work)
2636                 {
2637                 FileData *sfd = work->data;
2638
2639                 ret |= file_data_verify_ci(sfd, list);
2640                 work = work->next;
2641                 }
2642
2643         return ret;
2644 }
2645
2646 gchar *file_data_get_error_string(gint error)
2647 {
2648         GString *result = g_string_new("");
2649
2650         if (error & CHANGE_NO_SRC)
2651                 {
2652                 if (result->len > 0) g_string_append(result, ", ");
2653                 g_string_append(result, _("file or directory does not exist"));
2654                 }
2655
2656         if (error & CHANGE_DEST_EXISTS)
2657                 {
2658                 if (result->len > 0) g_string_append(result, ", ");
2659                 g_string_append(result, _("destination already exists"));
2660                 }
2661
2662         if (error & CHANGE_NO_WRITE_PERM_DEST)
2663                 {
2664                 if (result->len > 0) g_string_append(result, ", ");
2665                 g_string_append(result, _("destination can't be overwritten"));
2666                 }
2667
2668         if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2669                 {
2670                 if (result->len > 0) g_string_append(result, ", ");
2671                 g_string_append(result, _("destination directory is not writable"));
2672                 }
2673
2674         if (error & CHANGE_NO_DEST_DIR)
2675                 {
2676                 if (result->len > 0) g_string_append(result, ", ");
2677                 g_string_append(result, _("destination directory does not exist"));
2678                 }
2679
2680         if (error & CHANGE_NO_WRITE_PERM_DIR)
2681                 {
2682                 if (result->len > 0) g_string_append(result, ", ");
2683                 g_string_append(result, _("source directory is not writable"));
2684                 }
2685
2686         if (error & CHANGE_NO_READ_PERM)
2687                 {
2688                 if (result->len > 0) g_string_append(result, ", ");
2689                 g_string_append(result, _("no read permission"));
2690                 }
2691
2692         if (error & CHANGE_WARN_NO_WRITE_PERM)
2693                 {
2694                 if (result->len > 0) g_string_append(result, ", ");
2695                 g_string_append(result, _("file is readonly"));
2696                 }
2697
2698         if (error & CHANGE_WARN_DEST_EXISTS)
2699                 {
2700                 if (result->len > 0) g_string_append(result, ", ");
2701                 g_string_append(result, _("destination already exists and will be overwritten"));
2702                 }
2703
2704         if (error & CHANGE_WARN_SAME)
2705                 {
2706                 if (result->len > 0) g_string_append(result, ", ");
2707                 g_string_append(result, _("source and destination are the same"));
2708                 }
2709
2710         if (error & CHANGE_WARN_CHANGED_EXT)
2711                 {
2712                 if (result->len > 0) g_string_append(result, ", ");
2713                 g_string_append(result, _("source and destination have different extension"));
2714                 }
2715
2716         if (error & CHANGE_WARN_UNSAVED_META)
2717                 {
2718                 if (result->len > 0) g_string_append(result, ", ");
2719                 g_string_append(result, _("there are unsaved metadata changes for the file"));
2720                 }
2721
2722         if (error & CHANGE_DUPLICATE_DEST)
2723                 {
2724                 if (result->len > 0) g_string_append(result, ", ");
2725                 g_string_append(result, _("another destination file has the same filename"));
2726                 }
2727
2728         return g_string_free(result, FALSE);
2729 }
2730
2731 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2732 {
2733         GList *work;
2734         gint all_errors = 0;
2735         gint common_errors = ~0;
2736         gint num;
2737         gint *errors;
2738         gint i;
2739
2740         if (!list) return 0;
2741
2742         num = g_list_length(list);
2743         errors = g_new(int, num);
2744         work = list;
2745         i = 0;
2746         while (work)
2747                 {
2748                 FileData *fd;
2749                 gint error;
2750
2751                 fd = work->data;
2752                 work = work->next;
2753
2754                 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2755                 all_errors |= error;
2756                 common_errors &= error;
2757
2758                 errors[i] = error;
2759
2760                 i++;
2761                 }
2762
2763         if (desc && all_errors)
2764                 {
2765                 GList *work;
2766                 GString *result = g_string_new("");
2767
2768                 if (common_errors)
2769                         {
2770                         gchar *str = file_data_get_error_string(common_errors);
2771                         g_string_append(result, str);
2772                         g_string_append(result, "\n");
2773                         g_free(str);
2774                         }
2775
2776                 work = list;
2777                 i = 0;
2778                 while (work)
2779                         {
2780                         FileData *fd;
2781                         gint error;
2782
2783                         fd = work->data;
2784                         work = work->next;
2785
2786                         error = errors[i] & ~common_errors;
2787
2788                         if (error)
2789                                 {
2790                                 gchar *str = file_data_get_error_string(error);
2791                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2792                                 g_free(str);
2793                                 }
2794                         i++;
2795                         }
2796                 *desc = g_string_free(result, FALSE);
2797                 }
2798
2799         g_free(errors);
2800         return all_errors;
2801 }
2802
2803
2804 /*
2805  * perform the change described by FileFataChangeInfo
2806  * it is used for internal operations,
2807  * this function actually operates with files on the filesystem
2808  * it should implement safe delete
2809  */
2810
2811 static gboolean file_data_perform_move(FileData *fd)
2812 {
2813         g_assert(!strcmp(fd->change->source, fd->path));
2814         return move_file(fd->change->source, fd->change->dest);
2815 }
2816
2817 static gboolean file_data_perform_copy(FileData *fd)
2818 {
2819         g_assert(!strcmp(fd->change->source, fd->path));
2820         return copy_file(fd->change->source, fd->change->dest);
2821 }
2822
2823 static gboolean file_data_perform_delete(FileData *fd)
2824 {
2825         if (isdir(fd->path) && !islink(fd->path))
2826                 return rmdir_utf8(fd->path);
2827         else
2828                 if (options->file_ops.safe_delete_enable)
2829                         return file_util_safe_unlink(fd->path);
2830                 else
2831                         return unlink_file(fd->path);
2832 }
2833
2834 gboolean file_data_perform_ci(FileData *fd)
2835 {
2836         FileDataChangeType type = fd->change->type;
2837
2838         switch (type)
2839                 {
2840                 case FILEDATA_CHANGE_MOVE:
2841                         return file_data_perform_move(fd);
2842                 case FILEDATA_CHANGE_COPY:
2843                         return file_data_perform_copy(fd);
2844                 case FILEDATA_CHANGE_RENAME:
2845                         return file_data_perform_move(fd); /* the same as move */
2846                 case FILEDATA_CHANGE_DELETE:
2847                         return file_data_perform_delete(fd);
2848                 case FILEDATA_CHANGE_WRITE_METADATA:
2849                         return metadata_write_perform(fd);
2850                 case FILEDATA_CHANGE_UNSPECIFIED:
2851                         /* nothing to do here */
2852                         break;
2853                 }
2854         return TRUE;
2855 }
2856
2857
2858
2859 gboolean file_data_sc_perform_ci(FileData *fd)
2860 {
2861         GList *work;
2862         gboolean ret = TRUE;
2863         FileDataChangeType type = fd->change->type;
2864
2865         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2866
2867         work = fd->sidecar_files;
2868         while (work)
2869                 {
2870                 FileData *sfd = work->data;
2871
2872                 if (!file_data_perform_ci(sfd)) ret = FALSE;
2873                 work = work->next;
2874                 }
2875
2876         if (!file_data_perform_ci(fd)) ret = FALSE;
2877
2878         return ret;
2879 }
2880
2881 /*
2882  * updates FileData structure according to FileDataChangeInfo
2883  */
2884
2885 gboolean file_data_apply_ci(FileData *fd)
2886 {
2887         FileDataChangeType type = fd->change->type;
2888
2889         /* FIXME delete ?*/
2890         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2891                 {
2892                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2893                 file_data_planned_change_remove(fd);
2894
2895                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2896                         {
2897                         /* this change overwrites another file which is already known to other modules
2898                            renaming fd would create duplicate FileData structure
2899                            the best thing we can do is nothing
2900                            FIXME: maybe we could copy stuff like marks
2901                         */
2902                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2903                         }
2904                 else
2905                         {
2906                         file_data_set_path(fd, fd->change->dest);
2907                         }
2908                 }
2909         file_data_increment_version(fd);
2910         file_data_send_notification(fd, NOTIFY_CHANGE);
2911
2912         return TRUE;
2913 }
2914
2915 gboolean file_data_sc_apply_ci(FileData *fd)
2916 {
2917         GList *work;
2918         FileDataChangeType type = fd->change->type;
2919
2920         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2921
2922         work = fd->sidecar_files;
2923         while (work)
2924                 {
2925                 FileData *sfd = work->data;
2926
2927                 file_data_apply_ci(sfd);
2928                 work = work->next;
2929                 }
2930
2931         file_data_apply_ci(fd);
2932
2933         return TRUE;
2934 }
2935
2936 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2937 {
2938         GList *work;
2939         if (fd->parent) fd = fd->parent;
2940         if (!g_list_find(list, fd)) return FALSE;
2941
2942         work = fd->sidecar_files;
2943         while (work)
2944                 {
2945                 if (!g_list_find(list, work->data)) return FALSE;
2946                 work = work->next;
2947                 }
2948         return TRUE;
2949 }
2950
2951 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2952 {
2953         GList *out = NULL;
2954         GList *work = list;
2955
2956         /* change partial groups to independent files */
2957         if (ungroup)
2958                 {
2959                 while (work)
2960                         {
2961                         FileData *fd = work->data;
2962                         work = work->next;
2963
2964                         if (!file_data_list_contains_whole_group(list, fd))
2965                                 {
2966                                 file_data_disable_grouping(fd, TRUE);
2967                                 if (ungrouped_list)
2968                                         {
2969                                         *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2970                                         }
2971                                 }
2972                         }
2973                 }
2974
2975         /* remove sidecars from the list,
2976            they can be still acessed via main_fd->sidecar_files */
2977         work = list;
2978         while (work)
2979                 {
2980                 FileData *fd = work->data;
2981                 work = work->next;
2982
2983                 if (!fd->parent ||
2984                     (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2985                         {
2986                         out = g_list_prepend(out, file_data_ref(fd));
2987                         }
2988                 }
2989
2990         filelist_free(list);
2991         out = g_list_reverse(out);
2992
2993         return out;
2994 }
2995
2996
2997
2998
2999
3000 /*
3001  * notify other modules about the change described by FileDataChangeInfo
3002  */
3003
3004 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
3005    FIXME do we need the ignore_list? It looks like a workaround for ineffective
3006    implementation in view_file_list.c */
3007
3008
3009 typedef struct _NotifyIdleData NotifyIdleData;
3010
3011 struct _NotifyIdleData {
3012         FileData *fd;
3013         NotifyType type;
3014 };
3015
3016
3017 typedef struct _NotifyData NotifyData;
3018
3019 struct _NotifyData {
3020         FileDataNotifyFunc func;
3021         gpointer data;
3022         NotifyPriority priority;
3023 };
3024
3025 static GList *notify_func_list = NULL;
3026
3027 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3028 {
3029         NotifyData *nda = (NotifyData *)a;
3030         NotifyData *ndb = (NotifyData *)b;
3031
3032         if (nda->priority < ndb->priority) return -1;
3033         if (nda->priority > ndb->priority) return 1;
3034         return 0;
3035 }
3036
3037 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3038 {
3039         NotifyData *nd;
3040         GList *work = notify_func_list;
3041
3042         while (work)
3043                 {
3044                 NotifyData *nd = (NotifyData *)work->data;
3045
3046                 if (nd->func == func && nd->data == data)
3047                         {
3048                         g_warning("Notify func already registered");
3049                         return FALSE;
3050                         }
3051                 work = work->next;
3052                 }
3053
3054         nd = g_new(NotifyData, 1);
3055         nd->func = func;
3056         nd->data = data;
3057         nd->priority = priority;
3058
3059         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3060         DEBUG_2("Notify func registered: %p", nd);
3061
3062         return TRUE;
3063 }
3064
3065 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3066 {
3067         GList *work = notify_func_list;
3068
3069         while (work)
3070                 {
3071                 NotifyData *nd = (NotifyData *)work->data;
3072
3073                 if (nd->func == func && nd->data == data)
3074                         {
3075                         notify_func_list = g_list_delete_link(notify_func_list, work);
3076                         g_free(nd);
3077                         DEBUG_2("Notify func unregistered: %p", nd);
3078                         return TRUE;
3079                         }
3080                 work = work->next;
3081                 }
3082
3083         g_warning("Notify func not found");
3084         return FALSE;
3085 }
3086
3087
3088 gboolean file_data_send_notification_idle_cb(gpointer data)
3089 {
3090         NotifyIdleData *nid = (NotifyIdleData *)data;
3091         GList *work = notify_func_list;
3092
3093         while (work)
3094                 {
3095                 NotifyData *nd = (NotifyData *)work->data;
3096
3097                 nd->func(nid->fd, nid->type, nd->data);
3098                 work = work->next;
3099                 }
3100         file_data_unref(nid->fd);
3101         g_free(nid);
3102         return FALSE;
3103 }
3104
3105 void file_data_send_notification(FileData *fd, NotifyType type)
3106 {
3107         GList *work = notify_func_list;
3108
3109         while (work)
3110                 {
3111                 NotifyData *nd = (NotifyData *)work->data;
3112
3113                 nd->func(fd, type, nd->data);
3114                 work = work->next;
3115                 }
3116     /*
3117         NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3118         nid->fd = file_data_ref(fd);
3119         nid->type = type;
3120         g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3121     */
3122 }
3123
3124 static GHashTable *file_data_monitor_pool = NULL;
3125 static guint realtime_monitor_id = 0; /* event source id */
3126
3127 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3128 {
3129         FileData *fd = key;
3130
3131         file_data_check_changed_files(fd);
3132
3133         DEBUG_1("monitor %s", fd->path);
3134 }
3135
3136 static gboolean realtime_monitor_cb(gpointer data)
3137 {
3138         if (!options->update_on_time_change) return TRUE;
3139         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3140         return TRUE;
3141 }
3142
3143 gboolean file_data_register_real_time_monitor(FileData *fd)
3144 {
3145         gint count;
3146
3147         file_data_ref(fd);
3148
3149         if (!file_data_monitor_pool)
3150                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3151
3152         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3153
3154         DEBUG_1("Register realtime %d %s", count, fd->path);
3155
3156         count++;
3157         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3158
3159         if (!realtime_monitor_id)
3160                 {
3161                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3162                 }
3163
3164         return TRUE;
3165 }
3166
3167 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3168 {
3169         gint count;
3170
3171         g_assert(file_data_monitor_pool);
3172
3173         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3174
3175         DEBUG_1("Unregister realtime %d %s", count, fd->path);
3176
3177         g_assert(count > 0);
3178
3179         count--;
3180
3181         if (count == 0)
3182                 g_hash_table_remove(file_data_monitor_pool, fd);
3183         else
3184                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3185
3186         file_data_unref(fd);
3187
3188         if (g_hash_table_size(file_data_monitor_pool) == 0)
3189                 {
3190                 g_source_remove(realtime_monitor_id);
3191                 realtime_monitor_id = 0;
3192                 return FALSE;
3193                 }
3194
3195         return TRUE;
3196 }
3197
3198 /*
3199  *-----------------------------------------------------------------------------
3200  * Saving marks list, clearing marks
3201  * Uses file_data_pool
3202  *-----------------------------------------------------------------------------
3203  */
3204
3205 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3206 {
3207         gchar *file_name = key;
3208         GString *result = userdata;
3209         FileData *fd;
3210
3211         if (isfile(file_name))
3212                 {
3213                 fd = value;
3214                 if (fd && fd->marks > 0)
3215                         {
3216                         g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3217                         }
3218                 }
3219 }
3220
3221 gboolean marks_list_load(const gchar *path)
3222 {
3223         FILE *f;
3224         gchar s_buf[1024];
3225         gchar *pathl;
3226         gchar *file_path;
3227         gchar *marks_value;
3228
3229         pathl = path_from_utf8(path);
3230         f = fopen(pathl, "r");
3231         g_free(pathl);
3232         if (!f) return FALSE;
3233
3234         /* first line must start with Marks comment */
3235         if (!fgets(s_buf, sizeof(s_buf), f) ||
3236                                         strncmp(s_buf, "#Marks", 6) != 0)
3237                 {
3238                 fclose(f);
3239                 return FALSE;
3240                 }
3241
3242         while (fgets(s_buf, sizeof(s_buf), f))
3243                 {
3244                 if (s_buf[0]=='#') continue;
3245                         file_path = strtok(s_buf, ",");
3246                         marks_value = strtok(NULL, ",");
3247                         if (isfile(file_path))
3248                                 {
3249                                 FileData *fd = file_data_new_group(file_path);
3250                                 file_data_ref(fd);
3251                                 gint n = 0;
3252                                 while (n <= 9)
3253                                         {
3254                                         gint mark_no = 1 << n;
3255                                         if (atoi(marks_value) & mark_no)
3256                                                 {
3257                                                 file_data_set_mark(fd, n , 1);
3258                                                 }
3259                                         n++;
3260                                         }
3261                                 }
3262                 }
3263
3264         fclose(f);
3265         return TRUE;
3266 }
3267
3268 gboolean marks_list_save(gchar *path, gboolean save)
3269 {
3270         SecureSaveInfo *ssi;
3271         gchar *pathl;
3272         GString  *marks = g_string_new("");
3273
3274         pathl = path_from_utf8(path);
3275         ssi = secure_open(pathl);
3276         g_free(pathl);
3277         if (!ssi)
3278                 {
3279                 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3280                 return FALSE;
3281                 }
3282
3283         secure_fprintf(ssi, "#Marks lists\n");
3284
3285         if (save)
3286                 {
3287                 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3288                 }
3289         secure_fprintf(ssi, "%s", marks->str);
3290         g_string_free(marks, FALSE);
3291
3292         secure_fprintf(ssi, "#end\n");
3293         return (secure_close(ssi) == 0);
3294 }
3295
3296 static void marks_clear(gpointer key, gpointer value, gpointer userdata)
3297 {
3298         gchar *file_name = key;
3299         gint mark_no;
3300         gint n;
3301         FileData *fd;
3302
3303         if (isfile(file_name))
3304                 {
3305                 fd = value;
3306                 if (fd && fd->marks > 0)
3307                         {
3308                         n = 0;
3309                         while (n <= 9)
3310                                 {
3311                                 mark_no = 1 << n;
3312                                 if (fd->marks & mark_no)
3313                                         {
3314                                         file_data_set_mark(fd, n , 0);
3315                                         }
3316                                 n++;
3317                                 }
3318                         }
3319                 }
3320 }
3321
3322 void marks_clear_all()
3323 {
3324         g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3325 }
3326 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */