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