improved debug messages
[geeqie.git] / src / filedata.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 - 2009 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "filedata.h"
16
17 #include "filefilter.h"
18 #include "cache.h"
19 #include "thumb_standard.h"
20 #include "ui_fileops.h"
21 #include "metadata.h"
22 #include "trash.h"
23 #include "histogram.h"
24
25
26 static GHashTable *file_data_pool = NULL;
27 static GHashTable *file_data_planned_change_hash = NULL;
28
29 static gint sidecar_file_priority(const gchar *path);
30
31
32 /*
33  *-----------------------------------------------------------------------------
34  * text conversion utils
35  *-----------------------------------------------------------------------------
36  */
37
38 gchar *text_from_size(gint64 size)
39 {
40         gchar *a, *b;
41         gchar *s, *d;
42         gint l, n, i;
43
44         /* what I would like to use is printf("%'d", size)
45          * BUT: not supported on every libc :(
46          */
47         if (size > G_MAXUINT)
48                 {
49                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
50                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
51                 }
52         else
53                 {
54                 a = g_strdup_printf("%d", (guint)size);
55                 }
56         l = strlen(a);
57         n = (l - 1)/ 3;
58         if (n < 1) return a;
59
60         b = g_new(gchar, l + n + 1);
61
62         s = a;
63         d = b;
64         i = l - n * 3;
65         while (*s != '\0')
66                 {
67                 if (i < 1)
68                         {
69                         i = 3;
70                         *d = ',';
71                         d++;
72                         }
73
74                 *d = *s;
75                 s++;
76                 d++;
77                 i--;
78                 }
79         *d = '\0';
80
81         g_free(a);
82         return b;
83 }
84
85 gchar *text_from_size_abrev(gint64 size)
86 {
87         if (size < (gint64)1024)
88                 {
89                 return g_strdup_printf(_("%d bytes"), (gint)size);
90                 }
91         if (size < (gint64)1048576)
92                 {
93                 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
94                 }
95         if (size < (gint64)1073741824)
96                 {
97                 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
98                 }
99
100         /* to avoid overflowing the gdouble, do division in two steps */
101         size /= 1048576;
102         return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
103 }
104
105 /* note: returned string is valid until next call to text_from_time() */
106 const gchar *text_from_time(time_t t)
107 {
108         static gchar *ret = NULL;
109         gchar buf[128];
110         gint buflen;
111         struct tm *btime;
112         GError *error = NULL;
113
114         btime = localtime(&t);
115
116         /* the %x warning about 2 digit years is not an error */
117         buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
118         if (buflen < 1) return "";
119
120         g_free(ret);
121         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
122         if (error)
123                 {
124                 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
125                 g_error_free(error);
126                 return "";
127                 }
128
129         return ret;
130 }
131
132 /*
133  *-----------------------------------------------------------------------------
134  * file info struct
135  *-----------------------------------------------------------------------------
136  */
137
138 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
139 static void file_data_check_sidecars(FileData *fd);
140 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
141
142
143 void file_data_increment_version(FileData *fd)
144 {
145         fd->version++;
146         fd->valid_marks = 0;
147         if (fd->parent) 
148                 {
149                 fd->parent->version++;
150                 fd->parent->valid_marks = 0;
151                 }
152 }
153
154 static void file_data_set_collate_keys(FileData *fd)
155 {
156         gchar *caseless_name;
157
158         caseless_name = g_utf8_casefold(fd->name, -1);
159
160         g_free(fd->collate_key_name);
161         g_free(fd->collate_key_name_nocase);
162
163 #if GLIB_CHECK_VERSION(2, 8, 0)
164         fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
165         fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
166 #else
167         fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
168         fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
169 #endif
170         g_free(caseless_name);
171 }
172
173 static void file_data_set_path(FileData *fd, const gchar *path)
174 {
175         g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
176         g_assert(file_data_pool);
177
178         g_free(fd->path);
179
180         if (fd->original_path)
181                 {
182                 g_hash_table_remove(file_data_pool, fd->original_path);
183                 g_free(fd->original_path);
184                 }
185
186         g_assert(!g_hash_table_lookup(file_data_pool, path));
187
188         fd->original_path = g_strdup(path);
189         g_hash_table_insert(file_data_pool, fd->original_path, fd);
190
191         if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
192                 {
193                 fd->path = g_strdup(path);
194                 fd->name = fd->path;
195                 fd->extension = fd->name + 1;
196                 file_data_set_collate_keys(fd);
197                 return;
198                 }
199
200         fd->path = g_strdup(path);
201         fd->name = filename_from_path(fd->path);
202
203         if (strcmp(fd->name, "..") == 0)
204                 {
205                 gchar *dir = remove_level_from_path(path);
206                 g_free(fd->path);
207                 fd->path = remove_level_from_path(dir);
208                 g_free(dir);
209                 fd->name = "..";
210                 fd->extension = fd->name + 2;
211                 file_data_set_collate_keys(fd);
212                 return;
213                 }
214         else if (strcmp(fd->name, ".") == 0)
215                 {
216                 g_free(fd->path);
217                 fd->path = remove_level_from_path(path);
218                 fd->name = ".";
219                 fd->extension = fd->name + 1;
220                 file_data_set_collate_keys(fd);
221                 return;
222                 }
223
224         fd->extension = extension_from_path(fd->path);
225         if (fd->extension == NULL)
226                 {
227                 fd->extension = fd->name + strlen(fd->name);
228                 }
229
230         file_data_set_collate_keys(fd);
231 }
232
233 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
234 {
235         gboolean ret = FALSE;
236         GList *work;
237         
238         if (fd->size != st->st_size ||
239             fd->date != st->st_mtime)
240                 {
241                 fd->size = st->st_size;
242                 fd->date = st->st_mtime;
243                 fd->mode = st->st_mode;
244                 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
245                 fd->thumb_pixbuf = NULL;
246                 file_data_increment_version(fd);
247                 file_data_send_notification(fd, NOTIFY_REREAD);
248                 ret = TRUE;
249                 }
250
251         work = fd->sidecar_files;
252         while (work)
253                 {
254                 FileData *sfd = work->data;
255                 struct stat st;
256                 work = work->next;
257
258                 if (!stat_utf8(sfd->path, &st))
259                         {
260                         fd->size = 0;
261                         fd->date = 0;
262                         file_data_disconnect_sidecar_file(fd, sfd);
263                         ret = TRUE;
264                         continue;
265                         }
266
267                 ret |= file_data_check_changed_files_recursive(sfd, &st);
268                 }
269         return ret;
270 }
271
272
273 gboolean file_data_check_changed_files(FileData *fd)
274 {
275         gboolean ret = FALSE;
276         struct stat st;
277         
278         if (fd->parent) fd = fd->parent;
279
280         if (!stat_utf8(fd->path, &st))
281                 {
282                 GList *work;
283                 FileData *sfd = NULL;
284
285                 /* parent is missing, we have to rebuild whole group */
286                 ret = TRUE;
287                 fd->size = 0;
288                 fd->date = 0;
289                 
290                 work = fd->sidecar_files;
291                 while (work)
292                         {
293                         sfd = work->data;
294                         work = work->next;
295                 
296                         file_data_disconnect_sidecar_file(fd, sfd);
297                         }
298                 if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
299                 file_data_send_notification(fd, NOTIFY_REREAD);
300                 }
301         else
302                 {
303                 ret |= file_data_check_changed_files_recursive(fd, &st);
304                 }
305
306         return ret;
307 }
308
309 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
310 {
311         FileData *fd;
312
313         DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
314
315         if (!file_data_pool)
316                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
317
318         fd = g_hash_table_lookup(file_data_pool, path_utf8);
319         if (fd)
320                 {
321                 file_data_ref(fd);
322                 }
323                 
324         if (!fd && file_data_planned_change_hash)
325                 {
326                 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
327                 if (fd)
328                         {
329                         DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
330                         file_data_ref(fd);
331                         file_data_apply_ci(fd);
332                         }
333                 }
334                 
335         if (fd)
336                 {
337                 gboolean changed;
338                 
339                 if (fd->parent)
340                         changed = file_data_check_changed_files(fd);
341                 else
342                         changed = file_data_check_changed_files_recursive(fd, st);
343                 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
344                         file_data_check_sidecars(fd);
345                 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
346                 
347                 return fd;
348                 }
349
350         fd = g_new0(FileData, 1);
351         
352         fd->size = st->st_size;
353         fd->date = st->st_mtime;
354         fd->mode = st->st_mode;
355         fd->ref = 1;
356         fd->magick = 0x12345678;
357
358         file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
359
360         if (check_sidecars)
361                 file_data_check_sidecars(fd);
362
363         return fd;
364 }
365
366 static void file_data_check_sidecars(FileData *fd)
367 {
368         gint base_len;
369         GString *fname;
370         FileData *parent_fd = NULL;
371         GList *work;
372
373         if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
374                 return;
375
376         base_len = fd->extension - fd->path;
377         fname = g_string_new_len(fd->path, base_len);
378         work = sidecar_ext_get_list();
379
380         while (work)
381                 {
382                 /* check for possible sidecar files;
383                    the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
384                    they have fd->ref set to 0 and file_data unref must chack and free them all together
385                    (using fd->ref would cause loops and leaks)
386                 */
387
388                 FileData *new_fd;
389                 gchar *ext = work->data;
390
391                 work = work->next;
392
393                 if (g_ascii_strcasecmp(ext, fd->extension) == 0)
394                         {
395                         new_fd = fd; /* processing the original file */
396                         }
397                 else
398                         {
399                         struct stat nst;
400                         g_string_truncate(fname, base_len);
401
402                         if (!stat_utf8_case_insensitive_ext(fname, ext, &nst))
403                                 continue;
404
405                         new_fd = file_data_new(fname->str, &nst, FALSE);
406                         
407                         if (new_fd->disable_grouping)
408                                 {
409                                 file_data_unref(new_fd);
410                                 continue;
411                                 }
412                         
413                         new_fd->ref--; /* do not use ref here */
414                         }
415
416                 if (!parent_fd)
417                         parent_fd = new_fd; /* parent is the one with the highest prio, found first */
418                 else
419                         file_data_merge_sidecar_files(parent_fd, new_fd);
420                 }
421         g_string_free(fname, TRUE);
422 }
423
424
425 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
426 {
427         gchar *path_utf8 = path_to_utf8(path);
428         FileData *ret = file_data_new(path_utf8, st, check_sidecars);
429
430         g_free(path_utf8);
431         return ret;
432 }
433
434 FileData *file_data_new_simple(const gchar *path_utf8)
435 {
436         struct stat st;
437
438         if (!stat_utf8(path_utf8, &st))
439                 {
440                 st.st_size = 0;
441                 st.st_mtime = 0;
442                 }
443
444         return file_data_new(path_utf8, &st, TRUE);
445 }
446
447 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
448 {
449         sfd->parent = target;
450         if (!g_list_find(target->sidecar_files, sfd))
451                 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
452         file_data_increment_version(sfd); /* increments both sfd and target */
453         return target;
454 }
455
456
457 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
458 {
459         GList *work;
460         
461         file_data_add_sidecar_file(target, source);
462
463         work = source->sidecar_files;
464         while (work)
465                 {
466                 FileData *sfd = work->data;
467                 file_data_add_sidecar_file(target, sfd);
468                 work = work->next;
469                 }
470
471         g_list_free(source->sidecar_files);
472         source->sidecar_files = NULL;
473
474         target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
475         
476         return target;
477 }
478
479 #ifdef DEBUG_FILEDATA
480 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
481 #else
482 FileData *file_data_ref(FileData *fd)
483 #endif
484 {
485         if (fd == NULL) return NULL;
486 #ifdef DEBUG_FILEDATA
487         if (fd->magick != 0x12345678)
488                 DEBUG_0("fd magick mismatch at %s:%d", file, line);
489 #endif
490         g_assert(fd->magick == 0x12345678);
491         fd->ref++;
492
493 #ifdef DEBUG_FILEDATA
494         DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
495 #else
496         DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
497 #endif
498         return fd;
499 }
500
501 static void file_data_free(FileData *fd)
502 {
503         g_assert(fd->magick == 0x12345678);
504         g_assert(fd->ref == 0);
505
506         g_hash_table_remove(file_data_pool, fd->original_path);
507
508         g_free(fd->path);
509         g_free(fd->original_path);
510         g_free(fd->collate_key_name);
511         g_free(fd->collate_key_name_nocase);
512         if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
513         histmap_free(fd->histmap);
514         
515         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
516
517         file_data_change_info_free(NULL, fd);
518         g_free(fd);
519 }
520
521 #ifdef DEBUG_FILEDATA
522 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
523 #else
524 void file_data_unref(FileData *fd)
525 #endif
526 {
527         if (fd == NULL) return;
528 #ifdef DEBUG_FILEDATA
529         if (fd->magick != 0x12345678)
530                 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
531 #endif
532         g_assert(fd->magick == 0x12345678);
533         
534         fd->ref--;
535 #ifdef DEBUG_FILEDATA
536         DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
537 #else
538         DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
539 #endif
540         if (fd->ref == 0)
541                 {
542                 GList *work;
543                 FileData *parent = fd->parent ? fd->parent : fd;
544                 
545                 if (parent->ref > 0) return;
546
547                 work = parent->sidecar_files;
548                 while (work)
549                         {
550                         FileData *sfd = work->data;
551                         if (sfd->ref > 0) return;
552                         work = work->next;
553                         }
554
555                 /* none of parent/children is referenced, we can free everything */
556
557                 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
558
559                 work = parent->sidecar_files;
560                 while (work)
561                         {
562                         FileData *sfd = work->data;
563                         file_data_free(sfd);
564                         work = work->next;
565                         }
566
567                 g_list_free(parent->sidecar_files);
568                 parent->sidecar_files = NULL;
569
570                 file_data_free(parent);
571                 }
572 }
573
574 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
575 {
576         sfd->parent = target;
577         g_assert(g_list_find(target->sidecar_files, sfd));
578         
579         file_data_increment_version(sfd); /* increments both sfd and target */
580
581         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
582         sfd->parent = NULL;
583
584         if (sfd->ref == 0)
585                 {
586                 file_data_free(sfd);
587                 return NULL;
588                 }
589
590         return sfd;
591 }
592
593 /* disables / enables grouping for particular file, sends UPDATE notification */
594 void file_data_disable_grouping(FileData *fd, gboolean disable)
595 {
596         if (!fd->disable_grouping == !disable) return;
597         fd->disable_grouping = !!disable;
598         
599         if (disable)
600                 {
601                 if (fd->parent)
602                         {
603                         FileData *parent = file_data_ref(fd->parent);
604                         file_data_disconnect_sidecar_file(parent, fd);
605                         file_data_send_notification(fd, NOTIFY_GROUPING);
606                         file_data_send_notification(parent, NOTIFY_GROUPING);
607                         file_data_unref(parent);
608                         }
609                 else if (fd->sidecar_files)
610                         {
611                         GList *sidecar_files = filelist_copy(fd->sidecar_files);
612                         GList *work = sidecar_files;
613                         while (work)
614                                 {
615                                 FileData *sfd = work->data;
616                                 work = work->next;
617                                 file_data_disconnect_sidecar_file(fd, sfd);
618                                 file_data_send_notification(sfd, NOTIFY_GROUPING);
619                                 }
620                         file_data_send_notification(fd, NOTIFY_GROUPING);
621                         file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
622                         filelist_free(sidecar_files);
623                         }
624                 }
625         else
626                 {
627                 file_data_check_sidecars(fd);
628                 file_data_send_notification(fd, NOTIFY_GROUPING);
629                 }
630 }
631
632 /* compare name without extension */
633 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
634 {
635         size_t len1 = fd1->extension - fd1->name;
636         size_t len2 = fd2->extension - fd2->name;
637
638         if (len1 < len2) return -1;
639         if (len1 > len2) return 1;
640
641         return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
642 }
643
644 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
645 {
646         if (!fdci && fd) fdci = fd->change;
647
648         if (!fdci) return;
649
650         g_free(fdci->source);
651         g_free(fdci->dest);
652
653         g_free(fdci);
654
655         if (fd) fd->change = NULL;
656 }
657
658 static gboolean file_data_can_write_directly(FileData *fd)
659 {
660         return filter_name_is_writable(fd->extension);
661 }
662
663 static gboolean file_data_can_write_sidecar(FileData *fd)
664 {
665         return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
666 }
667
668 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
669 {
670         gchar *sidecar_path = NULL;
671         GList *work;
672         
673         if (!file_data_can_write_sidecar(fd)) return NULL;
674         
675         work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
676         while (work)
677                 {
678                 FileData *sfd = work->data;
679                 work = work->next;
680                 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
681                         {
682                         sidecar_path = g_strdup(sfd->path);
683                         break;
684                         }
685                 }
686         
687         if (!existing_only && !sidecar_path)
688                 {
689                 gchar *base = remove_extension_from_path(fd->path);
690                 sidecar_path = g_strconcat(base, ".xmp", NULL);
691                 g_free(base);
692                 }
693
694         return sidecar_path;
695 }
696
697
698 /*
699  *-----------------------------------------------------------------------------
700  * sidecar file info struct
701  *-----------------------------------------------------------------------------
702  */
703
704
705
706 static gint sidecar_file_priority(const gchar *path)
707 {
708         const gchar *extension = extension_from_path(path);
709         gint i = 1;
710         GList *work;
711
712         if (extension == NULL)
713                 return 0;
714
715         work = sidecar_ext_get_list();
716
717         while (work) {
718                 gchar *ext = work->data;
719                 
720                 work = work->next;
721                 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
722                 i++;
723         }
724         return 0;
725 }
726
727
728 /*
729  *-----------------------------------------------------------------------------
730  * load file list
731  *-----------------------------------------------------------------------------
732  */
733
734 static SortType filelist_sort_method = SORT_NONE;
735 static gboolean filelist_sort_ascend = TRUE;
736
737
738 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
739 {
740         if (!filelist_sort_ascend)
741                 {
742                 FileData *tmp = fa;
743                 fa = fb;
744                 fb = tmp;
745                 }
746
747         switch (filelist_sort_method)
748                 {
749                 case SORT_NAME:
750                         break;
751                 case SORT_SIZE:
752                         if (fa->size < fb->size) return -1;
753                         if (fa->size > fb->size) return 1;
754                         /* fall back to name */
755                         break;
756                 case SORT_TIME:
757                         if (fa->date < fb->date) return -1;
758                         if (fa->date > fb->date) return 1;
759                         /* fall back to name */
760                         break;
761 #ifdef HAVE_STRVERSCMP
762                 case SORT_NUMBER:
763                         return strverscmp(fa->name, fb->name);
764                         break;
765 #endif
766                 default:
767                         break;
768                 }
769
770         if (options->file_sort.case_sensitive)
771                 return strcmp(fa->collate_key_name, fb->collate_key_name);
772         else
773                 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
774 }
775
776 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
777 {
778         filelist_sort_method = method;
779         filelist_sort_ascend = ascend;
780         return filelist_sort_compare_filedata(fa, fb);
781 }
782
783 static gint filelist_sort_file_cb(gpointer a, gpointer b)
784 {
785         return filelist_sort_compare_filedata(a, b);
786 }
787
788 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
789 {
790         filelist_sort_method = method;
791         filelist_sort_ascend = ascend;
792         return g_list_sort(list, cb);
793 }
794
795 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
796 {
797         filelist_sort_method = method;
798         filelist_sort_ascend = ascend;
799         return g_list_insert_sorted(list, data, cb);
800 }
801
802 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
803 {
804         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
805 }
806
807 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
808 {
809         return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
810 }
811
812
813 static GList *filelist_filter_out_sidecars(GList *flist)
814 {
815         GList *work = flist;
816         GList *flist_filtered = NULL;
817
818         while (work)
819                 {
820                 FileData *fd = work->data;
821         
822                 work = work->next;
823                 if (fd->parent) /* remove fd's that are children */
824                         file_data_unref(fd);
825                 else
826                         flist_filtered = g_list_prepend(flist_filtered, fd);
827                 }
828         g_list_free(flist);
829
830         return flist_filtered;
831 }
832
833 static gboolean is_hidden_file(const gchar *name)
834 {
835         if (name[0] != '.') return FALSE;
836         if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
837         return TRUE;
838 }
839
840 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
841 {
842         DIR *dp;
843         struct dirent *dir;
844         gchar *pathl;
845         GList *dlist = NULL;
846         GList *flist = NULL;
847         gint (*stat_func)(const gchar *path, struct stat *buf);
848
849         g_assert(files || dirs);
850
851         if (files) *files = NULL;
852         if (dirs) *dirs = NULL;
853
854         pathl = path_from_utf8(dir_fd->path);
855         if (!pathl) return FALSE;
856
857         dp = opendir(pathl);
858         if (dp == NULL)
859                 {
860                 g_free(pathl);
861                 return FALSE;
862                 }
863
864         if (follow_symlinks)
865                 stat_func = stat;
866         else
867                 stat_func = lstat;
868
869         while ((dir = readdir(dp)) != NULL)
870                 {
871                 struct stat ent_sbuf;
872                 const gchar *name = dir->d_name;
873                 gchar *filepath;
874
875                 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
876                         continue;
877
878                 filepath = g_build_filename(pathl, name, NULL);
879                 if (stat_func(filepath, &ent_sbuf) >= 0)
880                         {
881                         if (S_ISDIR(ent_sbuf.st_mode))
882                                 {
883                                 /* we ignore the .thumbnails dir for cleanliness */
884                                 if (dirs &&
885                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
886                                     strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
887                                     strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
888                                     strcmp(name, THUMB_FOLDER_LOCAL) != 0)
889                                         {
890                                         dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
891                                         }
892                                 }
893                         else
894                                 {
895                                 if (files && filter_name_exists(name))
896                                         {
897                                         flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
898                                         }
899                                 }
900                         }
901                 g_free(filepath);
902                 }
903
904         closedir(dp);
905         
906         g_free(pathl);
907
908         if (dirs) *dirs = dlist;
909         if (files) *files = filelist_filter_out_sidecars(flist);
910
911         return TRUE;
912 }
913
914 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
915 {
916         return filelist_read_real(dir_fd, files, dirs, TRUE);
917 }
918
919 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
920 {
921         return filelist_read_real(dir_fd, files, dirs, FALSE);
922 }
923
924 void filelist_free(GList *list)
925 {
926         GList *work;
927
928         work = list;
929         while (work)
930                 {
931                 file_data_unref((FileData *)work->data);
932                 work = work->next;
933                 }
934
935         g_list_free(list);
936 }
937
938
939 GList *filelist_copy(GList *list)
940 {
941         GList *new_list = NULL;
942         GList *work;
943
944         work = list;
945         while (work)
946                 {
947                 FileData *fd;
948
949                 fd = work->data;
950                 work = work->next;
951
952                 new_list = g_list_prepend(new_list, file_data_ref(fd));
953                 }
954
955         return g_list_reverse(new_list);
956 }
957
958 GList *filelist_from_path_list(GList *list)
959 {
960         GList *new_list = NULL;
961         GList *work;
962
963         work = list;
964         while (work)
965                 {
966                 gchar *path;
967
968                 path = work->data;
969                 work = work->next;
970
971                 new_list = g_list_prepend(new_list, file_data_new_simple(path));
972                 }
973
974         return g_list_reverse(new_list);
975 }
976
977 GList *filelist_to_path_list(GList *list)
978 {
979         GList *new_list = NULL;
980         GList *work;
981
982         work = list;
983         while (work)
984                 {
985                 FileData *fd;
986
987                 fd = work->data;
988                 work = work->next;
989
990                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
991                 }
992
993         return g_list_reverse(new_list);
994 }
995
996 GList *filelist_filter(GList *list, gboolean is_dir_list)
997 {
998         GList *work;
999
1000         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1001
1002         work = list;
1003         while (work)
1004                 {
1005                 FileData *fd = (FileData *)(work->data);
1006                 const gchar *name = fd->name;
1007
1008                 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1009                     (!is_dir_list && !filter_name_exists(name)) ||
1010                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1011                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1012                         {
1013                         GList *link = work;
1014                         
1015                         list = g_list_remove_link(list, link);
1016                         file_data_unref(fd);
1017                         g_list_free(link);
1018                         }
1019         
1020                 work = work->next;
1021                 }
1022
1023         return list;
1024 }
1025
1026 /*
1027  *-----------------------------------------------------------------------------
1028  * filelist recursive
1029  *-----------------------------------------------------------------------------
1030  */
1031
1032 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1033 {
1034         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1035 }
1036
1037 GList *filelist_sort_path(GList *list)
1038 {
1039         return g_list_sort(list, filelist_sort_path_cb);
1040 }
1041
1042 static void filelist_recursive_append(GList **list, GList *dirs)
1043 {
1044         GList *work;
1045
1046         work = dirs;
1047         while (work)
1048                 {
1049                 FileData *fd = (FileData *)(work->data);
1050                 GList *f;
1051                 GList *d;
1052
1053                 if (filelist_read(fd, &f, &d))
1054                         {
1055                         f = filelist_filter(f, FALSE);
1056                         f = filelist_sort_path(f);
1057                         *list = g_list_concat(*list, f);
1058
1059                         d = filelist_filter(d, TRUE);
1060                         d = filelist_sort_path(d);
1061                         filelist_recursive_append(list, d);
1062                         filelist_free(d);
1063                         }
1064
1065                 work = work->next;
1066                 }
1067 }
1068
1069 GList *filelist_recursive(FileData *dir_fd)
1070 {
1071         GList *list;
1072         GList *d;
1073
1074         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1075         list = filelist_filter(list, FALSE);
1076         list = filelist_sort_path(list);
1077
1078         d = filelist_filter(d, TRUE);
1079         d = filelist_sort_path(d);
1080         filelist_recursive_append(&list, d);
1081         filelist_free(d);
1082
1083         return list;
1084 }
1085
1086
1087 /*
1088  * marks and orientation
1089  */
1090
1091 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1092 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1093 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1094 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1095
1096 gboolean file_data_get_mark(FileData *fd, gint n)
1097 {
1098         gboolean valid = (fd->valid_marks & (1 << n));
1099         
1100         if (file_data_get_mark_func[n] && !valid) 
1101                 {
1102                 guint old = fd->marks;
1103                 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1104                 
1105                 if (!value != !(fd->marks & (1 << n))) 
1106                         {
1107                         fd->marks = fd->marks ^ (1 << n);
1108                         }
1109                 
1110                 fd->valid_marks |= (1 << n);
1111                 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1112                         {
1113                         file_data_unref(fd);
1114                         }
1115                 else if (!old && fd->marks)
1116                         {
1117                         file_data_ref(fd);
1118                         }
1119                 }
1120
1121         return !!(fd->marks & (1 << n));
1122 }
1123
1124 guint file_data_get_marks(FileData *fd)
1125 {
1126         gint i;
1127         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1128         return fd->marks;
1129 }
1130
1131 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1132 {
1133         guint old;
1134         if (!value == !file_data_get_mark(fd, n)) return;
1135         
1136         if (file_data_set_mark_func[n]) 
1137                 {
1138                 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1139                 }
1140         
1141         old = fd->marks;
1142
1143         fd->marks = fd->marks ^ (1 << n);
1144         
1145         if (old && !fd->marks) /* keep files with non-zero marks in memory */
1146                 {
1147                 file_data_unref(fd);
1148                 }
1149         else if (!old && fd->marks)
1150                 {
1151                 file_data_ref(fd);
1152                 }
1153         
1154         file_data_increment_version(fd);
1155         file_data_send_notification(fd, NOTIFY_MARKS);
1156 }
1157
1158 gboolean file_data_filter_marks(FileData *fd, guint filter)
1159 {
1160         gint i;
1161         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1162         return ((fd->marks & filter) == filter);
1163 }
1164
1165 GList *file_data_filter_marks_list(GList *list, guint filter)
1166 {
1167         GList *work;
1168
1169         work = list;
1170         while (work)
1171                 {
1172                 FileData *fd = work->data;
1173                 GList *link = work;
1174                 work = work->next;
1175
1176                 if (!file_data_filter_marks(fd, filter))
1177                         {
1178                         list = g_list_remove_link(list, link);
1179                         file_data_unref(fd);
1180                         g_list_free(link);
1181                         }
1182                 }
1183
1184         return list;
1185 }
1186
1187 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1188 {
1189         FileData *fd = value;
1190         file_data_increment_version(fd);
1191         file_data_send_notification(fd, NOTIFY_MARKS);
1192 }
1193
1194 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1195 {
1196         if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1197         
1198         if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1199                 
1200         file_data_get_mark_func[n] = get_mark_func;
1201         file_data_set_mark_func[n] = set_mark_func;
1202         file_data_mark_func_data[n] = data;
1203         file_data_destroy_mark_func[n] = notify;
1204
1205         if (get_mark_func)
1206                 {
1207                 /* this effectively changes all known files */
1208                 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1209                 }
1210
1211         return TRUE;
1212 }
1213
1214 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1215 {
1216         if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1217         if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1218         if (data) *data = file_data_mark_func_data[n];
1219 }
1220
1221 gint file_data_get_user_orientation(FileData *fd)
1222 {
1223         return fd->user_orientation;
1224 }
1225
1226 void file_data_set_user_orientation(FileData *fd, gint value)
1227 {
1228         if (fd->user_orientation == value) return;
1229
1230         fd->user_orientation = value;
1231         file_data_increment_version(fd);
1232         file_data_send_notification(fd, NOTIFY_ORIENTATION);
1233 }
1234
1235
1236 /*
1237  * file_data    - operates on the given fd
1238  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1239  */
1240
1241
1242 /* return list of sidecar file extensions in a string */
1243 gchar *file_data_sc_list_to_string(FileData *fd)
1244 {
1245         GList *work;
1246         GString *result = g_string_new("");
1247
1248         work = fd->sidecar_files;
1249         while (work)
1250                 {
1251                 FileData *sfd = work->data;
1252
1253                 result = g_string_append(result, "+ ");
1254                 result = g_string_append(result, sfd->extension);
1255                 work = work->next;
1256                 if (work) result = g_string_append_c(result, ' ');
1257                 }
1258
1259         return g_string_free(result, FALSE);
1260 }
1261
1262
1263
1264 /*
1265  * add FileDataChangeInfo (see typedefs.h) for the given operation
1266  * uses file_data_add_change_info
1267  *
1268  * fails if the fd->change already exists - change operations can't run in parallel
1269  * fd->change_info works as a lock
1270  *
1271  * dest can be NULL - in this case the current name is used for now, it will
1272  * be changed later
1273  */
1274
1275 /*
1276    FileDataChangeInfo types:
1277    COPY
1278    MOVE   - path is changed, name may be changed too
1279    RENAME - path remains unchanged, name is changed
1280             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1281             sidecar names are changed too, extensions are not changed
1282    DELETE
1283    UPDATE - file size, date or grouping has been changed
1284 */
1285
1286 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1287 {
1288         FileDataChangeInfo *fdci;
1289
1290         if (fd->change) return FALSE;
1291
1292         fdci = g_new0(FileDataChangeInfo, 1);
1293
1294         fdci->type = type;
1295
1296         if (src)
1297                 fdci->source = g_strdup(src);
1298         else
1299                 fdci->source = g_strdup(fd->path);
1300
1301         if (dest)
1302                 fdci->dest = g_strdup(dest);
1303
1304         fd->change = fdci;
1305         
1306         return TRUE;
1307 }
1308
1309 static void file_data_planned_change_remove(FileData *fd)
1310 {
1311         if (file_data_planned_change_hash &&
1312             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1313                 {
1314                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1315                         {
1316                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1317                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1318                         file_data_unref(fd);
1319                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1320                                 {
1321                                 g_hash_table_destroy(file_data_planned_change_hash);
1322                                 file_data_planned_change_hash = NULL;
1323                                 DEBUG_1("planned change: empty");
1324                                 }
1325                         }
1326                 }
1327 }
1328
1329
1330 void file_data_free_ci(FileData *fd)
1331 {
1332         FileDataChangeInfo *fdci = fd->change;
1333
1334         if (!fdci) return;
1335
1336         file_data_planned_change_remove(fd);
1337
1338         g_free(fdci->source);
1339         g_free(fdci->dest);
1340
1341         g_free(fdci);
1342
1343         fd->change = NULL;
1344 }
1345
1346
1347 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1348 {
1349         GList *work;
1350
1351         if (fd->parent) fd = fd->parent;
1352         
1353         if (fd->change) return FALSE;
1354         
1355         work = fd->sidecar_files;
1356         while (work)
1357                 {
1358                 FileData *sfd = work->data;
1359                 
1360                 if (sfd->change) return FALSE;
1361                 work = work->next;
1362                 }
1363
1364         file_data_add_ci(fd, type, NULL, NULL);
1365         
1366         work = fd->sidecar_files;
1367         while (work)
1368                 {
1369                 FileData *sfd = work->data;
1370                 
1371                 file_data_add_ci(sfd, type, NULL, NULL);
1372                 work = work->next;
1373                 }
1374                 
1375         return TRUE;
1376 }
1377
1378 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1379 {
1380         GList *work;
1381         
1382         if (fd->parent) fd = fd->parent;
1383         
1384         if (!fd->change || fd->change->type != type) return FALSE;
1385         
1386         work = fd->sidecar_files;
1387         while (work)
1388                 {
1389                 FileData *sfd = work->data;
1390
1391                 if (!sfd->change || sfd->change->type != type) return FALSE;
1392                 work = work->next;
1393                 }
1394
1395         return TRUE;
1396 }
1397
1398
1399 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1400 {
1401         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1402         file_data_sc_update_ci_copy(fd, dest_path);
1403         return TRUE;
1404 }
1405
1406 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1407 {
1408         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1409         file_data_sc_update_ci_move(fd, dest_path);
1410         return TRUE;
1411 }
1412
1413 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1414 {
1415         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1416         file_data_sc_update_ci_rename(fd, dest_path);
1417         return TRUE;
1418 }
1419
1420 gboolean file_data_sc_add_ci_delete(FileData *fd)
1421 {
1422         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1423 }
1424
1425 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1426 {
1427         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1428         file_data_sc_update_ci_unspecified(fd, dest_path);
1429         return TRUE;
1430 }
1431
1432 gboolean file_data_add_ci_write_metadata(FileData *fd)
1433 {
1434         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1435 }
1436
1437 void file_data_sc_free_ci(FileData *fd)
1438 {
1439         GList *work;
1440
1441         if (fd->parent) fd = fd->parent;
1442         
1443         file_data_free_ci(fd);
1444         
1445         work = fd->sidecar_files;
1446         while (work)
1447                 {
1448                 FileData *sfd = work->data;
1449         
1450                 file_data_free_ci(sfd);
1451                 work = work->next;
1452                 }
1453 }
1454
1455 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1456 {
1457         GList *work;
1458         gboolean ret = TRUE;
1459
1460         work = fd_list;
1461         while (work)
1462                 {
1463                 FileData *fd = work->data;
1464         
1465                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1466                 work = work->next;
1467                 }
1468
1469         return ret;
1470 }
1471
1472 static void file_data_sc_revert_ci_list(GList *fd_list)
1473 {
1474         GList *work;
1475         
1476         work = fd_list;
1477         while (work)
1478                 {
1479                 FileData *fd = work->data;
1480                 
1481                 file_data_sc_free_ci(fd);
1482                 work = work->prev;
1483                 }
1484 }
1485
1486 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1487 {
1488         GList *work;
1489         
1490         work = fd_list;
1491         while (work)
1492                 {
1493                 FileData *fd = work->data;
1494                 
1495                 if (!func(fd, dest))
1496                         {
1497                         file_data_sc_revert_ci_list(work->prev);
1498                         return FALSE;
1499                         }
1500                 work = work->next;
1501                 }
1502         
1503         return TRUE;
1504 }
1505
1506 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1507 {
1508         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1509 }
1510
1511 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1512 {
1513         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1514 }
1515
1516 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1517 {
1518         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1519 }
1520
1521 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1522 {
1523         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1524 }
1525
1526 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1527 {
1528         GList *work;
1529         gboolean ret = TRUE;
1530
1531         work = fd_list;
1532         while (work)
1533                 {
1534                 FileData *fd = work->data;
1535         
1536                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1537                 work = work->next;
1538                 }
1539
1540         return ret;
1541 }
1542
1543 void file_data_free_ci_list(GList *fd_list)
1544 {
1545         GList *work;
1546         
1547         work = fd_list;
1548         while (work)
1549                 {
1550                 FileData *fd = work->data;
1551                 
1552                 file_data_free_ci(fd);
1553                 work = work->next;
1554                 }
1555 }
1556
1557 void file_data_sc_free_ci_list(GList *fd_list)
1558 {
1559         GList *work;
1560         
1561         work = fd_list;
1562         while (work)
1563                 {
1564                 FileData *fd = work->data;
1565                 
1566                 file_data_sc_free_ci(fd);
1567                 work = work->next;
1568                 }
1569 }
1570
1571 /*
1572  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1573  * fails if fd->change does not exist or the change type does not match
1574  */
1575
1576 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1577 {
1578         FileDataChangeType type = fd->change->type;
1579         
1580         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1581                 {
1582                 FileData *ofd;
1583                 
1584                 if (!file_data_planned_change_hash)
1585                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1586                 
1587                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1588                         {
1589                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1590                         g_hash_table_remove(file_data_planned_change_hash, old_path);
1591                         file_data_unref(fd);
1592                         }
1593
1594                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1595                 if (ofd != fd)
1596                         {
1597                         if (ofd)
1598                                 {
1599                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1600                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
1601                                 file_data_unref(ofd);
1602                                 }
1603                         
1604                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1605                         file_data_ref(fd);
1606                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1607                         }
1608                 }
1609 }
1610
1611 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1612 {
1613         gchar *old_path = fd->change->dest;
1614
1615         fd->change->dest = g_strdup(dest_path);
1616         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1617         g_free(old_path);
1618 }
1619
1620 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1621 {
1622         const gchar *extension = extension_from_path(fd->change->source);
1623         gchar *base = remove_extension_from_path(dest_path);
1624         gchar *old_path = fd->change->dest;
1625         
1626         fd->change->dest = g_strconcat(base, extension, NULL);
1627         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1628         
1629         g_free(old_path);
1630         g_free(base);
1631 }
1632
1633 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1634 {
1635         GList *work;
1636         gchar *dest_path_full = NULL;
1637         
1638         if (fd->parent) fd = fd->parent;
1639         
1640         if (!dest_path)
1641                 {
1642                 dest_path = fd->path;
1643                 }
1644         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1645                 {
1646                 gchar *dir = remove_level_from_path(fd->path);
1647                 
1648                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1649                 g_free(dir);
1650                 dest_path = dest_path_full;
1651                 }
1652         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1653                 {
1654                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1655                 dest_path = dest_path_full;
1656                 }
1657                 
1658         file_data_update_ci_dest(fd, dest_path);
1659         
1660         work = fd->sidecar_files;
1661         while (work)
1662                 {
1663                 FileData *sfd = work->data;
1664                 
1665                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1666                 work = work->next;
1667                 }
1668         
1669         g_free(dest_path_full);
1670 }
1671
1672 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1673 {
1674         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1675         file_data_sc_update_ci(fd, dest_path);
1676         return TRUE;
1677 }
1678
1679 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1680 {
1681         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1682 }
1683         
1684 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1685 {
1686         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1687 }
1688
1689 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1690 {
1691         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1692 }
1693
1694 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1695 {
1696         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1697 }
1698
1699 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1700                                                       const gchar *dest,
1701                                                       gboolean (*func)(FileData *, const gchar *))
1702 {
1703         GList *work;
1704         gboolean ret = TRUE;
1705         
1706         work = fd_list;
1707         while (work)
1708                 {
1709                 FileData *fd = work->data;
1710                 
1711                 if (!func(fd, dest)) ret = FALSE;
1712                 work = work->next;
1713                 }
1714         
1715         return ret;
1716 }
1717
1718 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1719 {
1720         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1721 }
1722
1723 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1724 {
1725         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1726 }
1727
1728 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1729 {
1730         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1731 }
1732
1733
1734 /*
1735  * verify source and dest paths - dest image exists, etc.
1736  * it should detect all possible problems with the planned operation
1737  */
1738
1739 gint file_data_verify_ci(FileData *fd)
1740 {
1741         gint ret = CHANGE_OK;
1742         gchar *dir;
1743         
1744         if (!fd->change)
1745                 {
1746                 DEBUG_1("Change checked: no change info: %s", fd->path);
1747                 return ret;
1748                 }
1749
1750         if (!isname(fd->path))
1751                 {
1752                 /* this probably should not happen */
1753                 ret |= CHANGE_NO_SRC;
1754                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1755                 return ret;
1756                 }
1757                 
1758         dir = remove_level_from_path(fd->path);
1759         
1760         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1761             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1762             !access_file(fd->path, R_OK))
1763                 {
1764                 ret |= CHANGE_NO_READ_PERM;
1765                 DEBUG_1("Change checked: no read permission: %s", fd->path);
1766                 }
1767         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1768                  !access_file(dir, W_OK))
1769                 {
1770                 ret |= CHANGE_NO_WRITE_PERM_DIR;
1771                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1772                 }
1773         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1774                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1775                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1776                  !access_file(fd->path, W_OK))
1777                 {
1778                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1779                 DEBUG_1("Change checked: no write permission: %s", fd->path);
1780                 }
1781         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1782            - that means that there are no hard errors and warnings can be disabled
1783            - the destination is determined during the check
1784         */
1785         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1786                 {
1787                 /* determine destination file */
1788                 gboolean have_dest = FALSE;
1789                 gchar *dest_dir = NULL;
1790                 
1791                 if (options->metadata.save_in_image_file)
1792                         {
1793                         if (file_data_can_write_directly(fd)) 
1794                                 {
1795                                 /* we can write the file directly */
1796                                 if (access_file(fd->path, W_OK))
1797                                         {
1798                                         have_dest = TRUE;
1799                                         }
1800                                 else
1801                                         {
1802                                         if (options->metadata.warn_on_write_problems)
1803                                                 {
1804                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1805                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1806                                                 }
1807                                         }
1808                                 }
1809                         else if (file_data_can_write_sidecar(fd)) 
1810                                 {
1811                                 /* we can write sidecar */
1812                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1813                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1814                                         {
1815                                         file_data_update_ci_dest(fd, sidecar);
1816                                         have_dest = TRUE;
1817                                         }
1818                                 else
1819                                         {
1820                                         if (options->metadata.warn_on_write_problems)
1821                                                 {
1822                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1823                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
1824                                                 }
1825                                         }
1826                                 g_free(sidecar);
1827                                 }
1828                         }
1829                 
1830                 if (!have_dest)
1831                         {
1832                         /* write private metadata file under ~/.geeqie */
1833
1834                         /* If an existing metadata file exists, we will try writing to
1835                          * it's location regardless of the user's preference.
1836                          */
1837                         gchar *metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
1838                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
1839                         
1840                         if (metadata_path && !access_file(metadata_path, W_OK))
1841                                 {
1842                                 g_free(metadata_path);
1843                                 metadata_path = NULL;
1844                                 }
1845
1846                         if (!metadata_path)
1847                                 {
1848                                 mode_t mode = 0755;
1849
1850                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
1851                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
1852                                         {
1853                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
1854                         
1855                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
1856                                         g_free(filename);
1857                                         }
1858                                 }
1859                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
1860                                 {
1861                                 file_data_update_ci_dest(fd, metadata_path);
1862                                 have_dest = TRUE;
1863                                 }
1864                         else
1865                                 {
1866                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
1867                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
1868                                 }
1869                         g_free(metadata_path);
1870                         }
1871                 g_free(dest_dir);
1872                 }
1873                 
1874         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
1875                 {
1876                 gboolean same;
1877                 gchar *dest_dir;
1878                         
1879                 same = (strcmp(fd->path, fd->change->dest) == 0);
1880
1881                 if (!same)
1882                         {
1883                         const gchar *dest_ext = extension_from_path(fd->change->dest);
1884                         if (!dest_ext) dest_ext = "";
1885
1886                         if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
1887                                 {
1888                                 ret |= CHANGE_WARN_CHANGED_EXT;
1889                                 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1890                                 }
1891                         }
1892                 else
1893                         {
1894                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
1895                                 {
1896                                 ret |= CHANGE_WARN_SAME;
1897                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
1898                                 }
1899                         }
1900
1901                 dest_dir = remove_level_from_path(fd->change->dest);
1902
1903                 if (!isdir(dest_dir))
1904                         {
1905                         ret |= CHANGE_NO_DEST_DIR;
1906                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1907                         }
1908                 else if (!access_file(dest_dir, W_OK))
1909                         {
1910                         ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1911                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1912                         }
1913                 else if (!same)
1914                         {
1915                         if (isfile(fd->change->dest))
1916                                 {
1917                                 if (!access_file(fd->change->dest, W_OK))
1918                                         {
1919                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
1920                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1921                                         }
1922                                 else
1923                                         {
1924                                         ret |= CHANGE_WARN_DEST_EXISTS;
1925                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1926                                         }
1927                                 }
1928                         else if (isdir(fd->change->dest))
1929                                 {
1930                                 ret |= CHANGE_DEST_EXISTS;
1931                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1932                                 }
1933                         }
1934
1935                 g_free(dest_dir);
1936                 }
1937                 
1938         fd->change->error = ret;
1939         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1940
1941         g_free(dir);
1942         return ret;
1943 }
1944
1945
1946 gint file_data_sc_verify_ci(FileData *fd)
1947 {
1948         GList *work;
1949         gint ret;
1950
1951         ret = file_data_verify_ci(fd);
1952
1953         work = fd->sidecar_files;
1954         while (work)
1955                 {
1956                 FileData *sfd = work->data;
1957
1958                 ret |= file_data_verify_ci(sfd);
1959                 work = work->next;
1960                 }
1961
1962         return ret;
1963 }
1964
1965 gchar *file_data_get_error_string(gint error)
1966 {
1967         GString *result = g_string_new("");
1968
1969         if (error & CHANGE_NO_SRC)
1970                 {
1971                 if (result->len > 0) g_string_append(result, ", ");
1972                 g_string_append(result, _("file or directory does not exist"));
1973                 }
1974
1975         if (error & CHANGE_DEST_EXISTS)
1976                 {
1977                 if (result->len > 0) g_string_append(result, ", ");
1978                 g_string_append(result, _("destination already exists"));
1979                 }
1980
1981         if (error & CHANGE_NO_WRITE_PERM_DEST)
1982                 {
1983                 if (result->len > 0) g_string_append(result, ", ");
1984                 g_string_append(result, _("destination can't be overwritten"));
1985                 }
1986
1987         if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
1988                 {
1989                 if (result->len > 0) g_string_append(result, ", ");
1990                 g_string_append(result, _("destination directory is not writable"));
1991                 }
1992
1993         if (error & CHANGE_NO_DEST_DIR)
1994                 {
1995                 if (result->len > 0) g_string_append(result, ", ");
1996                 g_string_append(result, _("destination directory does not exist"));
1997                 }
1998
1999         if (error & CHANGE_NO_WRITE_PERM_DIR)
2000                 {
2001                 if (result->len > 0) g_string_append(result, ", ");
2002                 g_string_append(result, _("source directory is not writable"));
2003                 }
2004
2005         if (error & CHANGE_NO_READ_PERM)
2006                 {
2007                 if (result->len > 0) g_string_append(result, ", ");
2008                 g_string_append(result, _("no read permission"));
2009                 }
2010
2011         if (error & CHANGE_WARN_NO_WRITE_PERM)
2012                 {
2013                 if (result->len > 0) g_string_append(result, ", ");
2014                 g_string_append(result, _("file is readonly"));
2015                 }
2016
2017         if (error & CHANGE_WARN_DEST_EXISTS)
2018                 {
2019                 if (result->len > 0) g_string_append(result, ", ");
2020                 g_string_append(result, _("destination already exists and will be overwritten"));
2021                 }
2022                 
2023         if (error & CHANGE_WARN_SAME)
2024                 {
2025                 if (result->len > 0) g_string_append(result, ", ");
2026                 g_string_append(result, _("source and destination are the same"));
2027                 }
2028
2029         if (error & CHANGE_WARN_CHANGED_EXT)
2030                 {
2031                 if (result->len > 0) g_string_append(result, ", ");
2032                 g_string_append(result, _("source and destination have different extension"));
2033                 }
2034
2035         return g_string_free(result, FALSE);
2036 }
2037
2038 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2039 {
2040         GList *work;
2041         gint all_errors = 0;
2042         gint common_errors = ~0;
2043         gint num;
2044         gint *errors;
2045         gint i;
2046         
2047         if (!list) return 0;
2048         
2049         num = g_list_length(list);
2050         errors = g_new(int, num);
2051         work = list;
2052         i = 0;
2053         while (work)
2054                 {
2055                 FileData *fd;
2056                 gint error;
2057
2058                 fd = work->data;
2059                 work = work->next;
2060                         
2061                 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2062                 all_errors |= error;
2063                 common_errors &= error;
2064                 
2065                 errors[i] = error;
2066                 
2067                 i++;
2068                 }
2069         
2070         if (desc && all_errors)
2071                 {
2072                 GList *work;
2073                 GString *result = g_string_new("");
2074                 
2075                 if (common_errors)
2076                         {
2077                         gchar *str = file_data_get_error_string(common_errors);
2078                         g_string_append(result, str);
2079                         g_string_append(result, "\n");
2080                         g_free(str);
2081                         }
2082                 
2083                 work = list;
2084                 i = 0;
2085                 while (work)
2086                         {
2087                         FileData *fd;
2088                         gint error;
2089
2090                         fd = work->data;
2091                         work = work->next;
2092                         
2093                         error = errors[i] & ~common_errors;
2094                         
2095                         if (error)
2096                                 {
2097                                 gchar *str = file_data_get_error_string(error);
2098                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2099                                 g_free(str);
2100                                 }
2101                         i++;
2102                         }
2103                 *desc = g_string_free(result, FALSE);
2104                 }
2105
2106         g_free(errors);
2107         return all_errors;
2108 }
2109
2110
2111 /*
2112  * perform the change described by FileFataChangeInfo
2113  * it is used for internal operations,
2114  * this function actually operates with files on the filesystem
2115  * it should implement safe delete
2116  */
2117
2118 static gboolean file_data_perform_move(FileData *fd)
2119 {
2120         g_assert(!strcmp(fd->change->source, fd->path));
2121         return move_file(fd->change->source, fd->change->dest);
2122 }
2123
2124 static gboolean file_data_perform_copy(FileData *fd)
2125 {
2126         g_assert(!strcmp(fd->change->source, fd->path));
2127         return copy_file(fd->change->source, fd->change->dest);
2128 }
2129
2130 static gboolean file_data_perform_delete(FileData *fd)
2131 {
2132         if (isdir(fd->path) && !islink(fd->path))
2133                 return rmdir_utf8(fd->path);
2134         else
2135                 if (options->file_ops.safe_delete_enable)
2136                         return file_util_safe_unlink(fd->path);
2137                 else
2138                         return unlink_file(fd->path);
2139 }
2140
2141 gboolean file_data_perform_ci(FileData *fd)
2142 {
2143         FileDataChangeType type = fd->change->type;
2144
2145         switch (type)
2146                 {
2147                 case FILEDATA_CHANGE_MOVE:
2148                         return file_data_perform_move(fd);
2149                 case FILEDATA_CHANGE_COPY:
2150                         return file_data_perform_copy(fd);
2151                 case FILEDATA_CHANGE_RENAME:
2152                         return file_data_perform_move(fd); /* the same as move */
2153                 case FILEDATA_CHANGE_DELETE:
2154                         return file_data_perform_delete(fd);
2155                 case FILEDATA_CHANGE_WRITE_METADATA:
2156                         return metadata_write_perform(fd);
2157                 case FILEDATA_CHANGE_UNSPECIFIED:
2158                         /* nothing to do here */
2159                         break;
2160                 }
2161         return TRUE;
2162 }
2163
2164
2165
2166 gboolean file_data_sc_perform_ci(FileData *fd)
2167 {
2168         GList *work;
2169         gboolean ret = TRUE;
2170         FileDataChangeType type = fd->change->type;
2171         
2172         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2173
2174         work = fd->sidecar_files;
2175         while (work)
2176                 {
2177                 FileData *sfd = work->data;
2178                 
2179                 if (!file_data_perform_ci(sfd)) ret = FALSE;
2180                 work = work->next;
2181                 }
2182         
2183         if (!file_data_perform_ci(fd)) ret = FALSE;
2184         
2185         return ret;
2186 }
2187
2188 /*
2189  * updates FileData structure according to FileDataChangeInfo
2190  */
2191
2192 gboolean file_data_apply_ci(FileData *fd)
2193 {
2194         FileDataChangeType type = fd->change->type;
2195
2196         /* FIXME delete ?*/
2197         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2198                 {
2199                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2200                 file_data_planned_change_remove(fd);
2201                 
2202                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2203                         {
2204                         /* this change overwrites another file which is already known to other modules
2205                            renaming fd would create duplicate FileData structure
2206                            the best thing we can do is nothing
2207                            FIXME: maybe we could copy stuff like marks
2208                         */
2209                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2210                         }
2211                 else
2212                         {
2213                         file_data_set_path(fd, fd->change->dest);
2214                         }
2215                 }
2216         file_data_increment_version(fd);
2217         file_data_send_notification(fd, NOTIFY_CHANGE);
2218         
2219         return TRUE;
2220 }
2221
2222 gboolean file_data_sc_apply_ci(FileData *fd)
2223 {
2224         GList *work;
2225         FileDataChangeType type = fd->change->type;
2226         
2227         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2228
2229         work = fd->sidecar_files;
2230         while (work)
2231                 {
2232                 FileData *sfd = work->data;
2233                 
2234                 file_data_apply_ci(sfd);
2235                 work = work->next;
2236                 }
2237         
2238         file_data_apply_ci(fd);
2239         
2240         return TRUE;
2241 }
2242
2243 /*
2244  * notify other modules about the change described by FileFataChangeInfo
2245  */
2246
2247 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2248    FIXME do we need the ignore_list? It looks like a workaround for ineffective
2249    implementation in view_file_list.c */
2250
2251
2252
2253
2254 typedef struct _NotifyData NotifyData;
2255
2256 struct _NotifyData {
2257         FileDataNotifyFunc func;
2258         gpointer data;
2259         NotifyPriority priority;
2260 };
2261
2262 static GList *notify_func_list = NULL;
2263
2264 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2265 {
2266         NotifyData *nda = (NotifyData *)a;
2267         NotifyData *ndb = (NotifyData *)b;
2268
2269         if (nda->priority < ndb->priority) return -1;
2270         if (nda->priority > ndb->priority) return 1;
2271         return 0;
2272 }
2273
2274 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2275 {
2276         NotifyData *nd;
2277         
2278         nd = g_new(NotifyData, 1);
2279         nd->func = func;
2280         nd->data = data;
2281         nd->priority = priority;
2282
2283         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2284         DEBUG_2("Notify func registered: %p", nd);
2285         
2286         return TRUE;
2287 }
2288
2289 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2290 {
2291         GList *work = notify_func_list;
2292         
2293         while (work)
2294                 {
2295                 NotifyData *nd = (NotifyData *)work->data;
2296         
2297                 if (nd->func == func && nd->data == data)
2298                         {
2299                         notify_func_list = g_list_delete_link(notify_func_list, work);
2300                         g_free(nd);
2301                         DEBUG_2("Notify func unregistered: %p", nd);
2302                         return TRUE;
2303                         }
2304                 work = work->next;
2305                 }
2306
2307         return FALSE;
2308 }
2309
2310
2311 void file_data_send_notification(FileData *fd, NotifyType type)
2312 {
2313         GList *work = notify_func_list;
2314
2315         while (work)
2316                 {
2317                 NotifyData *nd = (NotifyData *)work->data;
2318                 
2319                 nd->func(fd, type, nd->data);
2320                 work = work->next;
2321                 }
2322 }
2323
2324 static GHashTable *file_data_monitor_pool = NULL;
2325 static gint realtime_monitor_id = -1;
2326
2327 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2328 {
2329         FileData *fd = key;
2330
2331         file_data_check_changed_files(fd);
2332         
2333         DEBUG_1("monitor %s", fd->path);
2334 }
2335
2336 static gboolean realtime_monitor_cb(gpointer data)
2337 {
2338         if (!options->update_on_time_change) return TRUE;
2339         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2340         return TRUE;
2341 }
2342
2343 gboolean file_data_register_real_time_monitor(FileData *fd)
2344 {
2345         gint count;
2346         
2347         file_data_ref(fd);
2348         
2349         if (!file_data_monitor_pool)
2350                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2351         
2352         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2353
2354         DEBUG_1("Register realtime %d %s", count, fd->path);
2355         
2356         count++;
2357         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2358         
2359         if (realtime_monitor_id == -1)
2360                 {
2361                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2362                 }
2363         
2364         return TRUE;
2365 }
2366
2367 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2368 {
2369         gint count;
2370
2371         g_assert(file_data_monitor_pool);
2372         
2373         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2374         
2375         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2376         
2377         g_assert(count > 0);
2378         
2379         count--;
2380         
2381         if (count == 0)
2382                 g_hash_table_remove(file_data_monitor_pool, fd);
2383         else
2384                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2385
2386         file_data_unref(fd);
2387         
2388         if (g_hash_table_size(file_data_monitor_pool) == 0)
2389                 {
2390                 g_source_remove(realtime_monitor_id);
2391                 realtime_monitor_id = -1;
2392                 return FALSE;
2393                 }
2394         
2395         return TRUE;
2396 }
2397 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */