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