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