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