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