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