Tidy up.
[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 "rcfile.h"
20 #include "secure_save.h"
21 #include "thumb_standard.h"
22 #include "ui_fileops.h"
23
24
25 static GHashTable *file_data_pool = NULL;
26
27 static gint sidecar_file_priority(const gchar *path);
28
29
30 /*
31  *-----------------------------------------------------------------------------
32  * text conversion utils
33  *-----------------------------------------------------------------------------
34  */
35
36 gchar *text_from_size(gint64 size)
37 {
38         gchar *a, *b;
39         gchar *s, *d;
40         gint l, n, i;
41
42         /* what I would like to use is printf("%'d", size)
43          * BUT: not supported on every libc :(
44          */
45         if (size > G_MAXUINT)
46                 {
47                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
48                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
49                 }
50         else
51                 {
52                 a = g_strdup_printf("%d", (guint)size);
53                 }
54         l = strlen(a);
55         n = (l - 1)/ 3;
56         if (n < 1) return a;
57
58         b = g_new(gchar, l + n + 1);
59
60         s = a;
61         d = b;
62         i = l - n * 3;
63         while (*s != '\0')
64                 {
65                 if (i < 1)
66                         {
67                         i = 3;
68                         *d = ',';
69                         d++;
70                         }
71
72                 *d = *s;
73                 s++;
74                 d++;
75                 i--;
76                 }
77         *d = '\0';
78
79         g_free(a);
80         return b;
81 }
82
83 gchar *text_from_size_abrev(gint64 size)
84 {
85         if (size < (gint64)1024)
86                 {
87                 return g_strdup_printf(_("%d bytes"), (gint)size);
88                 }
89         if (size < (gint64)1048576)
90                 {
91                 return g_strdup_printf(_("%.1f K"), (double)size / 1024.0);
92                 }
93         if (size < (gint64)1073741824)
94                 {
95                 return g_strdup_printf(_("%.1f MB"), (double)size / 1048576.0);
96                 }
97
98         /* to avoid overflowing the double, do division in two steps */
99         size /= 1048576;
100         return g_strdup_printf(_("%.1f GB"), (double)size / 1024.0);
101 }
102
103 /* note: returned string is valid until next call to text_from_time() */
104 const gchar *text_from_time(time_t t)
105 {
106         static gchar *ret = NULL;
107         gchar buf[128];
108         gint buflen;
109         struct tm *btime;
110         GError *error = NULL;
111
112         btime = localtime(&t);
113
114         /* the %x warning about 2 digit years is not an error */
115         buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
116         if (buflen < 1) return "";
117
118         g_free(ret);
119         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
120         if (error)
121                 {
122                 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
123                 g_error_free(error);
124                 return "";
125                 }
126
127         return ret;
128 }
129
130 /*
131  *-----------------------------------------------------------------------------
132  * file info struct
133  *-----------------------------------------------------------------------------
134  */
135
136 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
137 static void file_data_check_sidecars(FileData *fd);
138 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
139
140
141 void file_data_increment_version(FileData *fd)
142 {
143         fd->version++;
144         if (fd->parent) fd->parent->version++;
145 }
146
147 static void file_data_set_collate_keys(FileData *fd)
148 {
149         gchar *caseless_name;
150
151         g_assert(g_utf8_validate(fd->name, -1, NULL));
152
153         caseless_name = g_utf8_casefold(fd->name, -1);
154
155         g_free(fd->collate_key_name);
156         g_free(fd->collate_key_name_nocase);
157
158 #if GLIB_CHECK_VERSION(2, 8, 0)
159         fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
160         fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
161 #else
162         fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
163         fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
164 #endif
165         g_free(caseless_name);
166 }
167
168 static void file_data_set_path(FileData *fd, const gchar *path)
169 {
170         g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
171         g_assert(file_data_pool);
172
173         g_free(fd->path);
174
175         if (fd->original_path)
176                 {
177                 g_hash_table_remove(file_data_pool, fd->original_path);
178                 g_free(fd->original_path);
179                 }
180         fd->original_path = g_strdup(path);
181         g_hash_table_insert(file_data_pool, fd->original_path, fd);
182
183         if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
184                 {
185                 fd->path = g_strdup(path);
186                 fd->name = fd->path;
187                 fd->extension = fd->name + 1;
188                 file_data_set_collate_keys(fd);
189                 return;
190                 }
191
192         fd->path = g_strdup(path);
193         fd->name = filename_from_path(fd->path);
194
195         if (strcmp(fd->name, "..") == 0)
196                 {
197                 gchar *dir = remove_level_from_path(path);
198                 g_free(fd->path);
199                 fd->path = remove_level_from_path(dir);
200                 g_free(dir);
201                 fd->name = "..";
202                 fd->extension = fd->name + 2;
203                 file_data_set_collate_keys(fd);
204                 return;
205                 }
206         else if (strcmp(fd->name, ".") == 0)
207                 {
208                 g_free(fd->path);
209                 fd->path = remove_level_from_path(path);
210                 fd->name = ".";
211                 fd->extension = fd->name + 1;
212                 file_data_set_collate_keys(fd);
213                 return;
214                 }
215
216         fd->extension = extension_from_path(fd->path);
217         if (fd->extension == NULL)
218                 fd->extension = fd->name + strlen(fd->name);
219
220         file_data_set_collate_keys(fd);
221 }
222
223 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
224 {
225         gboolean ret = FALSE;
226         GList *work;
227         
228         if (fd->size != st->st_size ||
229             fd->date != st->st_mtime)
230                 {
231                 fd->size = st->st_size;
232                 fd->date = st->st_mtime;
233                 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
234                 fd->thumb_pixbuf = NULL;
235                 file_data_increment_version(fd);
236                 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
237                 ret = TRUE;
238                 }
239
240         work = fd->sidecar_files;
241         while (work)
242                 {
243                 FileData *sfd = work->data;
244                 struct stat st;
245                 work = work->next;
246
247                 if (!stat_utf8(sfd->path, &st))
248                         {
249                         fd->size = 0;
250                         fd->date = 0;
251                         file_data_disconnect_sidecar_file(fd, sfd);
252                         ret = TRUE;
253                         continue;
254                         }
255
256                 ret |= file_data_check_changed_files_recursive(sfd, &st);
257                 }
258         return ret;
259 }
260
261
262 static gboolean file_data_check_changed_files(FileData *fd)
263 {
264         gboolean ret = FALSE;
265         struct stat st;
266         
267         if (fd->parent) fd = fd->parent;
268
269         if (!stat_utf8(fd->path, &st))
270                 {
271                 GList *work;
272                 FileData *sfd = NULL;
273
274                 /* parent is missing, we have to rebuild whole group */
275                 ret = TRUE;
276                 fd->size = 0;
277                 fd->date = 0;
278                 
279                 work = fd->sidecar_files;
280                 while (work)
281                         {
282                         sfd = work->data;
283                         work = work->next;
284                 
285                         file_data_disconnect_sidecar_file(fd, sfd);
286                         }
287                 if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
288                 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
289                 }
290         else
291                 {
292                 ret |= file_data_check_changed_files_recursive(fd, &st);
293                 }
294
295         return ret;
296 }
297
298 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
299 {
300         FileData *fd;
301
302         DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
303
304         if (!file_data_pool)
305                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
306
307         fd = g_hash_table_lookup(file_data_pool, path_utf8);
308         if (fd)
309                 {
310                 gboolean changed;
311
312                 if (fd->parent)
313                         changed = file_data_check_changed_files(fd);
314                 else
315                         changed = file_data_check_changed_files_recursive(fd, st);
316                 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
317                         file_data_check_sidecars(fd);
318                 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
319                 
320                 return file_data_ref(fd);
321                 }
322
323         fd = g_new0(FileData, 1);
324         
325         fd->path = NULL;
326         fd->name = NULL;
327         fd->collate_key_name = NULL;
328         fd->collate_key_name_nocase = NULL;
329         fd->original_path = NULL;
330
331         fd->size = st->st_size;
332         fd->date = st->st_mtime;
333         fd->thumb_pixbuf = NULL;
334         fd->sidecar_files = NULL;
335         fd->ref = 1;
336         fd->magick = 0x12345678;
337
338         file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
339
340         if (check_sidecars)
341                 file_data_check_sidecars(fd);
342
343         return fd;
344 }
345
346 static void file_data_check_sidecars(FileData *fd)
347 {
348         int base_len;
349         GString *fname;
350         FileData *parent_fd = NULL;
351         GList *work;
352
353         if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
354                 return;
355
356         base_len = fd->extension - fd->path;
357         fname = g_string_new_len(fd->path, base_len);
358         work = sidecar_ext_get_list();
359
360         while (work)
361                 {
362                 /* check for possible sidecar files;
363                    the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
364                    they have fd->ref set to 0 and file_data unref must chack and free them all together
365                    (using fd->ref would cause loops and leaks)
366                 */
367
368                 FileData *new_fd;
369                 gchar *ext = work->data;
370
371                 work = work->next;
372
373                 if (strcmp(ext, fd->extension) == 0)
374                         {
375                         new_fd = fd; /* processing the original file */
376                         }
377                 else
378                         {
379                         struct stat nst;
380                         g_string_truncate(fname, base_len);
381                         g_string_append(fname, ext);
382
383                         if (!stat_utf8(fname->str, &nst))
384                                 continue;
385
386                         new_fd = file_data_new(fname->str, &nst, FALSE);
387                         
388                         if (new_fd->disable_grouping)
389                                 {
390                                 file_data_unref(new_fd);
391                                 continue;
392                                 }
393                         
394                         new_fd->ref--; /* do not use ref here */
395                         }
396
397                 if (!parent_fd)
398                         parent_fd = new_fd; /* parent is the one with the highest prio, found first */
399                 else
400                         file_data_merge_sidecar_files(parent_fd, new_fd);
401                 }
402         g_string_free(fname, TRUE);
403 }
404
405
406 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
407 {
408         gchar *path_utf8 = path_to_utf8(path);
409         FileData *ret = file_data_new(path_utf8, st, check_sidecars);
410
411         g_free(path_utf8);
412         return ret;
413 }
414
415 FileData *file_data_new_simple(const gchar *path_utf8)
416 {
417         struct stat st;
418
419         if (!stat_utf8(path_utf8, &st))
420                 {
421                 st.st_size = 0;
422                 st.st_mtime = 0;
423                 }
424
425         return file_data_new(path_utf8, &st, TRUE);
426 }
427
428 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
429 {
430         sfd->parent = target;
431         if (!g_list_find(target->sidecar_files, sfd))
432                 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
433         file_data_increment_version(sfd); /* increments both sfd and target */
434         return target;
435 }
436
437
438 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
439 {
440         GList *work;
441         
442         file_data_add_sidecar_file(target, source);
443
444         work = source->sidecar_files;
445         while (work)
446                 {
447                 FileData *sfd = work->data;
448                 file_data_add_sidecar_file(target, sfd);
449                 work = work->next;
450                 }
451
452         g_list_free(source->sidecar_files);
453         source->sidecar_files = NULL;
454
455         target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
456         
457         return target;
458 }
459
460
461 FileData *file_data_ref(FileData *fd)
462 {
463         if (fd == NULL) return NULL;
464         DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
465
466 //      return g_memdup(fd, sizeof(FileData));
467         g_assert(fd->magick == 0x12345678);
468         fd->ref++;
469         return fd;
470 }
471
472 static void file_data_free(FileData *fd)
473 {
474         g_assert(fd->magick == 0x12345678);
475         g_assert(fd->ref == 0);
476
477         g_hash_table_remove(file_data_pool, fd->original_path);
478
479         g_free(fd->path);
480         g_free(fd->original_path);
481         g_free(fd->collate_key_name);
482         g_free(fd->collate_key_name_nocase);
483         if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
484
485         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
486
487         file_data_change_info_free(NULL, fd);
488         g_free(fd);
489 }
490
491 void file_data_unref(FileData *fd)
492 {
493         if (fd == NULL) return;
494         g_assert(fd->magick == 0x12345678);
495
496         fd->ref--;
497         DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
498
499         if (fd->ref == 0)
500                 {
501                 GList *work;
502                 FileData *parent = fd->parent ? fd->parent : fd;
503                 
504                 if (parent->ref > 0)
505                         return;
506
507                 work = parent->sidecar_files;
508                 while (work)
509                         {
510                         FileData *sfd = work->data;
511                         if (sfd->ref > 0)
512                                 return;
513                         work = work->next;
514                         }
515
516                 /* none of parent/children is referenced, we can free everything */
517
518                 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, parent->path);
519
520                 work = parent->sidecar_files;
521                 while (work)
522                         {
523                         FileData *sfd = work->data;
524                         file_data_free(sfd);
525                         work = work->next;
526                         }
527
528                 g_list_free(parent->sidecar_files);
529                 parent->sidecar_files = NULL;
530
531                 file_data_free(parent);
532                 }
533 }
534
535 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
536 {
537         sfd->parent = target;
538         g_assert(g_list_find(target->sidecar_files, sfd));
539         
540         file_data_increment_version(sfd); /* increments both sfd and target */
541
542         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
543         sfd->parent = NULL;
544
545         if (sfd->ref == 0)
546                 {
547                 file_data_free(sfd);
548                 return NULL;
549                 }
550
551         return sfd;
552 }
553
554 /* disables / enables grouping for particular file, sends UPDATE notification */
555 void file_data_disable_grouping(FileData *fd, gboolean disable)
556 {
557         if (!fd->disable_grouping == !disable) return;
558         fd->disable_grouping = !!disable;
559         
560         if (disable)
561                 {
562                 if (fd->parent)
563                         {
564                         FileData *parent = file_data_ref(fd->parent);
565                         file_data_disconnect_sidecar_file(parent, fd);
566                         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
567                         file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
568                         file_data_unref(parent);
569                         }
570                 else if (fd->sidecar_files)
571                         {
572                         GList *sidecar_files = filelist_copy(fd->sidecar_files);
573                         GList *work = sidecar_files;
574                         while (work)
575                                 {
576                                 FileData *sfd = work->data;
577                                 work = work->next;
578                                 file_data_disconnect_sidecar_file(fd, sfd);
579                                 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
580                                 }
581                         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
582                         file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
583                         filelist_free(sidecar_files);
584                         }
585                 }
586         else
587                 {
588                 file_data_check_sidecars(fd);
589                 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
590                 }
591 }
592
593 /* compare name without extension */
594 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
595 {
596         size_t len1 = fd1->extension - fd1->name;
597         size_t len2 = fd2->extension - fd2->name;
598
599         if (len1 < len2) return -1;
600         if (len1 > len2) return 1;
601
602         return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
603 }
604
605 gboolean file_data_add_change_info(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
606 {
607         FileDataChangeInfo *fdci;
608
609         if (fd->change) return FALSE;
610
611         fdci = g_new0(FileDataChangeInfo, 1);
612         fdci->type = type;
613
614         if (src)
615                 fdci->source = g_strdup(src);
616         else
617                 fdci->source = g_strdup(fd->path);
618
619         if (dest)
620                 fdci->dest = g_strdup(dest);
621
622         fd->change = fdci;
623
624         return TRUE;
625 }
626
627 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
628 {
629         if (!fdci && fd)
630                 fdci = fd->change;
631
632         if (!fdci)
633                 return;
634
635         g_free(fdci->source);
636         g_free(fdci->dest);
637
638         g_free(fdci);
639
640         if (fd)
641                 fd->change = NULL;
642 }
643
644
645
646
647 /*
648  *-----------------------------------------------------------------------------
649  * sidecar file info struct
650  *-----------------------------------------------------------------------------
651  */
652
653
654
655 static gint sidecar_file_priority(const gchar *path)
656 {
657         const char *extension = extension_from_path(path);
658         int i = 1;
659         GList *work;
660
661         if (extension == NULL)
662                 return 0;
663
664         work = sidecar_ext_get_list();
665
666         while (work) {
667                 gchar *ext = work->data;
668                 
669                 work = work->next;
670                 if (strcmp(extension, ext) == 0) return i;
671                 i++;
672         }
673         return 0;
674 }
675
676
677 /*
678  *-----------------------------------------------------------------------------
679  * load file list
680  *-----------------------------------------------------------------------------
681  */
682
683 static SortType filelist_sort_method = SORT_NONE;
684 static gint filelist_sort_ascend = TRUE;
685
686
687 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
688 {
689         if (!filelist_sort_ascend)
690                 {
691                 FileData *tmp = fa;
692                 fa = fb;
693                 fb = tmp;
694                 }
695
696         switch (filelist_sort_method)
697                 {
698                 case SORT_NAME:
699                         break;
700                 case SORT_SIZE:
701                         if (fa->size < fb->size) return -1;
702                         if (fa->size > fb->size) return 1;
703                         /* fall back to name */
704                         break;
705                 case SORT_TIME:
706                         if (fa->date < fb->date) return -1;
707                         if (fa->date > fb->date) return 1;
708                         /* fall back to name */
709                         break;
710 #ifdef HAVE_STRVERSCMP
711                 case SORT_NUMBER:
712                         return strverscmp(fa->name, fb->name);
713                         break;
714 #endif
715                 default:
716                         break;
717                 }
718
719         if (options->file_sort.case_sensitive)
720                 return strcmp(fa->collate_key_name, fb->collate_key_name);
721         else
722                 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
723 }
724
725 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
726 {
727         filelist_sort_method = method;
728         filelist_sort_ascend = ascend;
729         return filelist_sort_compare_filedata(fa, fb);
730 }
731
732 static gint filelist_sort_file_cb(void *a, void *b)
733 {
734         return filelist_sort_compare_filedata(a, b);
735 }
736
737 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
738 {
739         filelist_sort_method = method;
740         filelist_sort_ascend = ascend;
741         return g_list_sort(list, cb);
742 }
743
744 GList *filelist_insert_sort_full(GList *list, void *data, SortType method, gint ascend, GCompareFunc cb)
745 {
746         filelist_sort_method = method;
747         filelist_sort_ascend = ascend;
748         return g_list_insert_sorted(list, data, cb);
749 }
750
751 GList *filelist_sort(GList *list, SortType method, gint ascend)
752 {
753         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
754 }
755
756 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
757 {
758         return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
759 }
760
761
762 static GList *filelist_filter_out_sidecars(GList *flist)
763 {
764         GList *work = flist;
765         GList *flist_filtered = NULL;
766
767         while (work)
768                 {
769                 FileData *fd = work->data;
770         
771                 work = work->next;
772                 if (fd->parent) /* remove fd's that are children */
773                         file_data_unref(fd);
774                 else
775                         flist_filtered = g_list_prepend(flist_filtered, fd);
776                 }
777         g_list_free(flist);
778
779         return flist_filtered;
780 }
781
782 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
783 {
784         DIR *dp;
785         struct dirent *dir;
786         gchar *pathl;
787         GList *dlist = NULL;
788         GList *flist = NULL;
789         int (*stat_func)(const char *path, struct stat *buf);
790
791         g_assert(files || dirs);
792
793         if (files) *files = NULL;
794         if (dirs) *dirs = NULL;
795
796         pathl = path_from_utf8(dir_fd->path);
797         if (!pathl) return FALSE;
798
799         dp = opendir(pathl);
800         if (dp == NULL)
801                 {
802                 g_free(pathl);
803                 return FALSE;
804                 }
805
806         if (follow_symlinks)
807                 stat_func = stat;
808         else
809                 stat_func = lstat;
810
811         while ((dir = readdir(dp)) != NULL)
812                 {
813                 struct stat ent_sbuf;
814                 const gchar *name = dir->d_name;
815                 gchar *filepath;
816
817                 if (!options->file_filter.show_hidden_files && ishidden(name))
818                         continue;
819
820                 filepath = g_build_filename(pathl, name, NULL);
821                 if (stat_func(filepath, &ent_sbuf) >= 0)
822                         {
823                         if (S_ISDIR(ent_sbuf.st_mode))
824                                 {
825                                 /* we ignore the .thumbnails dir for cleanliness */
826                                 if (dirs &&
827                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
828                                     strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
829                                     strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
830                                     strcmp(name, THUMB_FOLDER_LOCAL) != 0)
831                                         {
832                                         dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
833                                         }
834                                 }
835                         else
836                                 {
837                                 if (files && filter_name_exists(name))
838                                         {
839                                         flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
840                                         }
841                                 }
842                         }
843                 g_free(filepath);
844                 }
845
846         closedir(dp);
847         
848         g_free(pathl);
849
850         if (dirs) *dirs = dlist;
851         if (files) *files = filelist_filter_out_sidecars(flist);
852
853         return TRUE;
854 }
855
856 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
857 {
858         return filelist_read_real(dir_fd, files, dirs, TRUE);
859 }
860
861 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
862 {
863         return filelist_read_real(dir_fd, files, dirs, FALSE);
864 }
865
866 void filelist_free(GList *list)
867 {
868         GList *work;
869
870         work = list;
871         while (work)
872                 {
873                 file_data_unref((FileData *)work->data);
874                 work = work->next;
875                 }
876
877         g_list_free(list);
878 }
879
880
881 GList *filelist_copy(GList *list)
882 {
883         GList *new_list = NULL;
884         GList *work;
885
886         work = list;
887         while (work)
888                 {
889                 FileData *fd;
890
891                 fd = work->data;
892                 work = work->next;
893
894                 new_list = g_list_prepend(new_list, file_data_ref(fd));
895                 }
896
897         return g_list_reverse(new_list);
898 }
899
900 GList *filelist_from_path_list(GList *list)
901 {
902         GList *new_list = NULL;
903         GList *work;
904
905         work = list;
906         while (work)
907                 {
908                 gchar *path;
909
910                 path = work->data;
911                 work = work->next;
912
913                 new_list = g_list_prepend(new_list, file_data_new_simple(path));
914                 }
915
916         return g_list_reverse(new_list);
917 }
918
919 GList *filelist_to_path_list(GList *list)
920 {
921         GList *new_list = NULL;
922         GList *work;
923
924         work = list;
925         while (work)
926                 {
927                 FileData *fd;
928
929                 fd = work->data;
930                 work = work->next;
931
932                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
933                 }
934
935         return g_list_reverse(new_list);
936 }
937
938 GList *filelist_filter(GList *list, gint is_dir_list)
939 {
940         GList *work;
941
942         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
943
944         work = list;
945         while (work)
946                 {
947                 FileData *fd = (FileData *)(work->data);
948                 const gchar *name = fd->name;
949
950                 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
951                     (!is_dir_list && !filter_name_exists(name)) ||
952                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
953                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
954                         {
955                         GList *link = work;
956                         
957                         list = g_list_remove_link(list, link);
958                         file_data_unref(fd);
959                         g_list_free(link);
960                         }
961         
962                 work = work->next;
963                 }
964
965         return list;
966 }
967
968 /*
969  *-----------------------------------------------------------------------------
970  * filelist recursive
971  *-----------------------------------------------------------------------------
972  */
973
974 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
975 {
976         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
977 }
978
979 GList *filelist_sort_path(GList *list)
980 {
981         return g_list_sort(list, filelist_sort_path_cb);
982 }
983
984 static void filelist_recursive_append(GList **list, GList *dirs)
985 {
986         GList *work;
987
988         work = dirs;
989         while (work)
990                 {
991                 FileData *fd = (FileData *)(work->data);
992                 GList *f;
993                 GList *d;
994
995                 if (filelist_read(fd, &f, &d))
996                         {
997                         f = filelist_filter(f, FALSE);
998                         f = filelist_sort_path(f);
999                         *list = g_list_concat(*list, f);
1000
1001                         d = filelist_filter(d, TRUE);
1002                         d = filelist_sort_path(d);
1003                         filelist_recursive_append(list, d);
1004                         filelist_free(d);
1005                         }
1006
1007                 work = work->next;
1008                 }
1009 }
1010
1011 GList *filelist_recursive(FileData *dir_fd)
1012 {
1013         GList *list;
1014         GList *d;
1015
1016         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1017         list = filelist_filter(list, FALSE);
1018         list = filelist_sort_path(list);
1019
1020         d = filelist_filter(d, TRUE);
1021         d = filelist_sort_path(d);
1022         filelist_recursive_append(&list, d);
1023         filelist_free(d);
1024
1025         return list;
1026 }
1027
1028
1029 /*
1030  * marks and orientation
1031  */
1032  
1033  
1034 gboolean file_data_get_mark(FileData *fd, gint n)
1035 {
1036         return !!(fd->marks & (1 << n));
1037 }
1038
1039 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1040 {
1041         if (!value == !(fd->marks & (1 << n))) return;
1042
1043         fd->marks = fd->marks ^ (1 << n);
1044         file_data_increment_version(fd);
1045         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1046 }
1047
1048 gint file_data_get_user_orientation(FileData *fd)
1049 {
1050         return fd->user_orientation;
1051 }
1052
1053 void file_data_set_user_orientation(FileData *fd, gint value)
1054 {
1055         if (fd->user_orientation == value) return;
1056
1057         fd->user_orientation = value;
1058         file_data_increment_version(fd);
1059         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1060 }
1061
1062
1063
1064 /*
1065  * file_data    - operates on the given fd
1066  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1067  */
1068
1069
1070 /* return list of sidecar file extensions in a string */
1071 gchar *file_data_sc_list_to_string(FileData *fd)
1072 {
1073         GList *work;
1074         GString *result = g_string_new("");
1075
1076         work = fd->sidecar_files;
1077         while (work)
1078                 {
1079                 FileData *sfd = work->data;
1080
1081                 result = g_string_append(result, "+ ");
1082                 result = g_string_append(result, sfd->extension);
1083                 work = work->next;
1084                 if (work) result = g_string_append_c(result, ' ');
1085                 }
1086
1087         return g_string_free(result, FALSE);
1088 }
1089
1090
1091                                 
1092 /* 
1093  * add FileDataChangeInfo (see typedefs.h) for the given operation 
1094  * uses file_data_add_change_info
1095  *
1096  * fails if the fd->change already exists - change operations can't run in parallel
1097  * fd->change_info works as a lock
1098  *
1099  * dest can be NULL - in this case the current name is used for now, it will
1100  * be changed later 
1101  */
1102
1103 /*
1104    FileDataChangeInfo types:
1105    COPY
1106    MOVE - patch is changed, name may be changed too
1107    RENAME - path remains unchanged, name is changed
1108             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1109             sidecar names are changed too, extensions are not changed
1110    DELETE
1111    UPDATE - file size, date or grouping has been changed 
1112 */
1113
1114 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1115 {
1116         FileDataChangeInfo *fdci;
1117
1118         if (fd->change) return FALSE;
1119
1120         fdci = g_new0(FileDataChangeInfo, 1);
1121
1122         fdci->type = type;
1123
1124         if (src)
1125                 fdci->source = g_strdup(src);
1126         else
1127                 fdci->source = g_strdup(fd->path);
1128
1129         if (dest)
1130                 fdci->dest = g_strdup(dest);
1131
1132         fd->change = fdci;
1133         
1134         return TRUE;
1135 }
1136
1137 void file_data_free_ci(FileData *fd)
1138 {
1139         FileDataChangeInfo *fdci = fd->change;
1140
1141         if (!fdci)
1142                 return;
1143
1144         g_free(fdci->source);
1145         g_free(fdci->dest);
1146
1147         g_free(fdci);
1148
1149         fd->change = NULL;
1150 }
1151
1152  
1153 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1154 {
1155         GList *work;
1156
1157         if (fd->parent) fd = fd->parent;
1158         
1159         if (fd->change) return FALSE;
1160         
1161         work = fd->sidecar_files;
1162         while (work)
1163                 {
1164                 FileData *sfd = work->data;
1165                 
1166                 if (sfd->change) return FALSE;
1167                 work = work->next;
1168                 }
1169
1170         file_data_add_ci(fd, type, NULL, NULL);
1171         
1172         work = fd->sidecar_files;
1173         while (work)
1174                 {
1175                 FileData *sfd = work->data;
1176                 
1177                 file_data_add_ci(sfd, type, NULL, NULL);
1178                 work = work->next;
1179                 }
1180                 
1181         return TRUE;    
1182 }
1183
1184 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1185 {
1186         GList *work;
1187         
1188         if (fd->parent) fd = fd->parent;
1189         
1190         if (!fd->change || fd->change->type != type) return FALSE;
1191         
1192         work = fd->sidecar_files;
1193         while (work)
1194                 {
1195                 FileData *sfd = work->data;
1196
1197                 if (!sfd->change || sfd->change->type != type) return FALSE;
1198                 work = work->next;
1199                 }
1200
1201         return TRUE;
1202 }
1203
1204
1205 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1206 {
1207         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1208         file_data_sc_update_ci_copy(fd, dest_path);
1209         return TRUE;
1210 }
1211
1212 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1213 {
1214         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1215         file_data_sc_update_ci_move(fd, dest_path);
1216         return TRUE;
1217 }
1218
1219 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1220 {
1221         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1222         file_data_sc_update_ci_rename(fd, dest_path);
1223         return TRUE;
1224 }
1225
1226 gboolean file_data_sc_add_ci_delete(FileData *fd)
1227 {
1228         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1229 }
1230
1231 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1232 {
1233         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1234         file_data_sc_update_ci_unspecified(fd, dest_path);
1235         return TRUE;
1236 }
1237
1238 void file_data_sc_free_ci(FileData *fd)
1239 {
1240         GList *work;
1241
1242         if (fd->parent) fd = fd->parent;
1243         
1244         file_data_free_ci(fd);
1245         
1246         work = fd->sidecar_files;
1247         while (work)
1248                 {
1249                 FileData *sfd = work->data;
1250         
1251                 file_data_free_ci(sfd);
1252                 work = work->next;
1253                 }
1254 }
1255
1256 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1257 {
1258         GList *work;
1259         gboolean ret = TRUE;
1260
1261         work = fd_list;
1262         while (work)
1263                 {
1264                 FileData *fd = work->data;
1265         
1266                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1267                 work = work->next;
1268                 }
1269
1270         return ret;
1271 }
1272
1273 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1274 {
1275         GList *work;
1276         gboolean ret = TRUE;
1277         
1278         work = fd_list;
1279         while (work)
1280                 {
1281                 FileData *fd = work->data;
1282                 
1283                 if (!file_data_sc_add_ci_copy(fd, dest)) ret = FALSE;
1284                 work = work->next;
1285                 }
1286         
1287         return ret;
1288 }
1289
1290 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1291 {
1292         GList *work;
1293         gboolean ret = TRUE;
1294         
1295         work = fd_list;
1296         while (work)
1297                 {
1298                 FileData *fd = work->data;
1299                 
1300                 if (!file_data_sc_add_ci_move(fd, dest)) ret = FALSE;
1301                 work = work->next;
1302                 }
1303         
1304         return ret;
1305 }
1306
1307 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1308 {
1309         GList *work;
1310         gboolean ret = TRUE;
1311         
1312         work = fd_list;
1313         while (work)
1314                 {
1315                 FileData *fd = work->data;
1316                 
1317                 if (!file_data_sc_add_ci_rename(fd, dest)) ret = FALSE;
1318                 work = work->next;
1319                 }
1320         
1321         return ret;
1322 }
1323
1324 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1325 {
1326         GList *work;
1327         gboolean ret = TRUE;
1328         
1329         work = fd_list;
1330         while (work)
1331                 {
1332                 FileData *fd = work->data;
1333                 
1334                 if (!file_data_sc_add_ci_unspecified(fd, dest)) ret = FALSE;
1335                 work = work->next;
1336                 }
1337         
1338         return ret;
1339 }
1340
1341 void file_data_sc_free_ci_list(GList *fd_list)
1342 {
1343         GList *work;
1344         
1345         work = fd_list;
1346         while (work)
1347                 {
1348                 FileData *fd = work->data;
1349                 
1350                 file_data_sc_free_ci(fd);
1351                 work = work->next;
1352                 }
1353 }
1354
1355 /* 
1356  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1357  * fails if fd->change does not exist or the change type does not match
1358  */
1359
1360 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1361 {
1362         g_free(fd->change->dest);
1363         fd->change->dest = g_strdup(dest_path);
1364 }
1365
1366 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1367 {
1368         const char *extension = extension_from_path(fd->change->source);
1369         gchar *base = remove_extension_from_path(dest_path);
1370         
1371         g_free(fd->change->dest);
1372         fd->change->dest = g_strdup_printf("%s%s", base, extension);
1373         
1374         g_free(base);
1375 }
1376
1377 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1378 {
1379         GList *work;
1380         gchar *dest_path_full = NULL;
1381         
1382         if (fd->parent) fd = fd->parent;
1383         
1384         if (!dest_path) dest_path = fd->path;
1385         
1386         if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1387                 {
1388                 gchar *dir = remove_level_from_path(fd->path);
1389                 
1390                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1391                 g_free(dir);
1392                 dest_path = dest_path_full;
1393                 }
1394         
1395         if (isdir(dest_path))
1396                 {
1397                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1398                 dest_path = dest_path_full;
1399                 }
1400                 
1401         file_data_update_ci_dest(fd, dest_path);
1402         
1403         work = fd->sidecar_files;
1404         while (work)
1405                 {
1406                 FileData *sfd = work->data;
1407                 
1408                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1409                 work = work->next;
1410                 }
1411         
1412         g_free(dest_path_full);
1413 }
1414
1415 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1416 {
1417         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1418         file_data_sc_update_ci(fd, dest_path);
1419         return TRUE;
1420 }
1421         
1422 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1423 {
1424         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1425         file_data_sc_update_ci(fd, dest_path);
1426         return TRUE;
1427 }
1428
1429 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1430 {
1431         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1432         file_data_sc_update_ci(fd, dest_path);
1433         return TRUE;
1434 }
1435
1436 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1437 {
1438         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1439         file_data_sc_update_ci(fd, dest_path);
1440         return TRUE;
1441 }
1442
1443
1444 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1445 {
1446         GList *work;
1447         gboolean ret = TRUE;
1448         
1449         work = fd_list;
1450         while (work)
1451                 {
1452                 FileData *fd = work->data;
1453                 
1454                 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1455                 work = work->next;
1456                 }
1457         
1458         return ret;
1459 }
1460
1461 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1462 {
1463         GList *work;
1464         gboolean ret = TRUE;
1465         
1466         work = fd_list;
1467         while (work)
1468                 {
1469                 FileData *fd = work->data;
1470                 
1471                 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1472                 work = work->next;
1473                 }
1474         
1475         return ret;
1476 }
1477
1478 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1479 {
1480         GList *work;
1481         gboolean ret = TRUE;
1482         
1483         work = fd_list;
1484         while (work)
1485                 {
1486                 FileData *fd = work->data;
1487                 
1488                 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1489                 work = work->next;
1490                 }
1491         
1492         return ret;
1493 }
1494
1495
1496 /*
1497  * check dest paths - dest image exists, etc.
1498  * returns FIXME
1499  * it should detect all possible problems with the planned operation
1500  */
1501  
1502 gint file_data_sc_check_ci_dest(FileData *fd)
1503 {
1504 }
1505
1506
1507
1508
1509 /*
1510  * perform the change described by FileFataChangeInfo
1511  * it is used for internal operations, 
1512  * this function actually operates with files on the filesystem
1513  * it should implement safe delete
1514  */
1515  
1516 static gboolean file_data_perform_move(FileData *fd)
1517 {
1518         g_assert(!strcmp(fd->change->source, fd->path));
1519         return move_file(fd->change->source, fd->change->dest);
1520 }
1521
1522 static gboolean file_data_perform_copy(FileData *fd)
1523 {
1524         g_assert(!strcmp(fd->change->source, fd->path));
1525         return copy_file(fd->change->source, fd->change->dest);
1526 }
1527
1528 static gboolean file_data_perform_delete(FileData *fd)
1529 {
1530         return unlink_file(fd->path);
1531 }
1532
1533 static gboolean file_data_perform_ci(FileData *fd)
1534 {
1535         FileDataChangeType type = fd->change->type;
1536         switch (type)
1537                 {
1538                 case FILEDATA_CHANGE_MOVE:
1539                         return file_data_perform_move(fd);
1540                 case FILEDATA_CHANGE_COPY:
1541                         return file_data_perform_copy(fd);
1542                 case FILEDATA_CHANGE_RENAME:
1543                         return file_data_perform_move(fd); /* the same as move */
1544                 case FILEDATA_CHANGE_DELETE:
1545                         return file_data_perform_delete(fd);
1546                 case FILEDATA_CHANGE_UNSPECIFIED:
1547                         /* nothing to do here */
1548                         break;
1549                 }
1550         return TRUE;
1551 }
1552
1553
1554
1555 gboolean file_data_sc_perform_ci(FileData *fd)
1556 {
1557         GList *work;
1558         gboolean ret = TRUE;
1559         FileDataChangeType type = fd->change->type;
1560         
1561         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1562
1563         work = fd->sidecar_files;
1564         while (work)
1565                 {
1566                 FileData *sfd = work->data;
1567                 
1568                 if (!file_data_perform_ci(sfd)) ret = FALSE;
1569                 work = work->next;
1570                 }
1571         
1572         if (!file_data_perform_ci(fd)) ret = FALSE;
1573         
1574         return ret;
1575 }
1576
1577 /*
1578  * updates FileData structure according to FileDataChangeInfo
1579  */
1580  
1581 static void file_data_apply_ci(FileData *fd)
1582 {
1583         FileDataChangeType type = fd->change->type;
1584         
1585         /* FIXME delete ?*/
1586         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1587                 {
1588                 file_data_set_path(fd, fd->change->dest);
1589                 }
1590         file_data_increment_version(fd);
1591         file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1592 }
1593
1594 gint file_data_sc_apply_ci(FileData *fd)
1595 {
1596         GList *work;
1597         FileDataChangeType type = fd->change->type;
1598         
1599         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1600
1601         work = fd->sidecar_files;
1602         while (work)
1603                 {
1604                 FileData *sfd = work->data;
1605                 
1606                 file_data_apply_ci(sfd);
1607                 work = work->next;
1608                 }
1609         
1610         file_data_apply_ci(fd);
1611         
1612         return TRUE;
1613 }
1614
1615
1616 /*
1617  * notify other modules about the change described by FileFataChangeInfo
1618  */
1619  
1620 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1621    FIXME do we need the ignore_list? It looks like a workaround for ineffective
1622    implementation in view_file_list.c */
1623
1624
1625
1626
1627 typedef struct _NotifyData NotifyData;
1628
1629 struct _NotifyData {
1630         FileDataNotifyFunc func;
1631         gpointer data;
1632         NotifyPriority priority;
1633         };
1634
1635 static GList *notify_func_list = NULL;
1636
1637 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1638 {
1639         NotifyData *nda = (NotifyData *)a;
1640         NotifyData *ndb = (NotifyData *)b;
1641
1642         if (nda->priority < ndb->priority) return -1;
1643         if (nda->priority > ndb->priority) return 1;
1644         return 0;
1645 }
1646
1647 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1648 {
1649         NotifyData *nd;
1650         
1651         nd = g_new(NotifyData, 1);
1652         nd->func = func;
1653         nd->data = data;
1654         nd->priority = priority;
1655
1656         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1657         DEBUG_1("Notify func registered: %p", nd);
1658         
1659         return TRUE;
1660 }
1661
1662 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1663 {
1664         GList *work = notify_func_list;
1665         
1666         while (work)
1667                 {
1668                 NotifyData *nd = (NotifyData *)work->data;
1669         
1670                 if (nd->func == func && nd->data == data)
1671                         {
1672                         notify_func_list = g_list_delete_link(notify_func_list, work);
1673                         g_free(nd);
1674                         DEBUG_1("Notify func unregistered: %p", nd);
1675                         return TRUE;
1676                         }
1677                 work = work->next;
1678                 }
1679
1680         return FALSE;
1681 }
1682
1683
1684 void file_data_send_notification(FileData *fd, NotifyType type)
1685 {
1686         GList *work = notify_func_list;
1687
1688         while (work)
1689                 {
1690                 NotifyData *nd = (NotifyData *)work->data;
1691                 
1692                 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1693                 nd->func(fd, type, nd->data);
1694                 work = work->next;
1695                 }
1696 }
1697
1698 static GHashTable *file_data_monitor_pool = NULL;
1699 static gint realtime_monitor_id = -1;
1700
1701 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1702 {
1703         FileData *fd = key;
1704
1705         file_data_check_changed_files(fd);
1706         
1707         DEBUG_1("monitor %s", fd->path);
1708 }
1709
1710 static gboolean realtime_monitor_cb(gpointer data)
1711 {
1712         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1713         return TRUE;
1714 }
1715
1716 gint file_data_register_real_time_monitor(FileData *fd)
1717 {
1718         gint count = 0;
1719         
1720         file_data_ref(fd);
1721         
1722         if (!file_data_monitor_pool)
1723                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1724         
1725         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1726
1727         DEBUG_1("Register realtime %d %s", count, fd->path);
1728         
1729         count++;
1730         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1731         
1732         if (realtime_monitor_id == -1)
1733                 {
1734                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1735                 }
1736         
1737         return TRUE;
1738 }
1739
1740 gint file_data_unregister_real_time_monitor(FileData *fd)
1741 {
1742         gint count;
1743
1744         g_assert(file_data_monitor_pool);
1745         
1746         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1747         
1748         DEBUG_1("Unregister realtime %d %s", count, fd->path);
1749         
1750         g_assert(count > 0);
1751         
1752         count--;
1753         
1754         if (count == 0)
1755                 g_hash_table_remove(file_data_monitor_pool, fd);
1756         else
1757                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1758
1759         file_data_unref(fd);
1760         
1761         if (g_hash_table_size(file_data_monitor_pool) == 0)
1762                 {
1763                 g_source_remove(realtime_monitor_id);
1764                 realtime_monitor_id = -1;
1765                 return FALSE;
1766                 }
1767         
1768         return TRUE;
1769 }