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