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