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