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