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