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