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