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