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