remove items from file_data_planned_change_hash when the operation is
[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 static void file_data_planned_change_remove(FileData *fd)
1176 {
1177         if (file_data_planned_change_hash && 
1178             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1179                 {
1180                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1181                         {
1182                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1183                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1184                         file_data_unref(fd);
1185                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1186                                 {
1187                                 g_hash_table_destroy(file_data_planned_change_hash);
1188                                 file_data_planned_change_hash = NULL;
1189                                 DEBUG_1("planned change: empty");
1190                                 }
1191                         }
1192                 }
1193 }
1194  
1195
1196 void file_data_free_ci(FileData *fd)
1197 {
1198         FileDataChangeInfo *fdci = fd->change;
1199
1200         if (!fdci)
1201                 return;
1202
1203         file_data_planned_change_remove(fd);
1204
1205         g_free(fdci->source);
1206         g_free(fdci->dest);
1207
1208         g_free(fdci);
1209
1210         fd->change = NULL;
1211 }
1212
1213  
1214 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1215 {
1216         GList *work;
1217
1218         if (fd->parent) fd = fd->parent;
1219         
1220         if (fd->change) return FALSE;
1221         
1222         work = fd->sidecar_files;
1223         while (work)
1224                 {
1225                 FileData *sfd = work->data;
1226                 
1227                 if (sfd->change) return FALSE;
1228                 work = work->next;
1229                 }
1230
1231         file_data_add_ci(fd, type, NULL, NULL);
1232         
1233         work = fd->sidecar_files;
1234         while (work)
1235                 {
1236                 FileData *sfd = work->data;
1237                 
1238                 file_data_add_ci(sfd, type, NULL, NULL);
1239                 work = work->next;
1240                 }
1241                 
1242         return TRUE;    
1243 }
1244
1245 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1246 {
1247         GList *work;
1248         
1249         if (fd->parent) fd = fd->parent;
1250         
1251         if (!fd->change || fd->change->type != type) return FALSE;
1252         
1253         work = fd->sidecar_files;
1254         while (work)
1255                 {
1256                 FileData *sfd = work->data;
1257
1258                 if (!sfd->change || sfd->change->type != type) return FALSE;
1259                 work = work->next;
1260                 }
1261
1262         return TRUE;
1263 }
1264
1265
1266 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1267 {
1268         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1269         file_data_sc_update_ci_copy(fd, dest_path);
1270         return TRUE;
1271 }
1272
1273 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1274 {
1275         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1276         file_data_sc_update_ci_move(fd, dest_path);
1277         return TRUE;
1278 }
1279
1280 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1281 {
1282         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1283         file_data_sc_update_ci_rename(fd, dest_path);
1284         return TRUE;
1285 }
1286
1287 gboolean file_data_sc_add_ci_delete(FileData *fd)
1288 {
1289         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1290 }
1291
1292 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1293 {
1294         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1295         file_data_sc_update_ci_unspecified(fd, dest_path);
1296         return TRUE;
1297 }
1298
1299 void file_data_sc_free_ci(FileData *fd)
1300 {
1301         GList *work;
1302
1303         if (fd->parent) fd = fd->parent;
1304         
1305         file_data_free_ci(fd);
1306         
1307         work = fd->sidecar_files;
1308         while (work)
1309                 {
1310                 FileData *sfd = work->data;
1311         
1312                 file_data_free_ci(sfd);
1313                 work = work->next;
1314                 }
1315 }
1316
1317 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1318 {
1319         GList *work;
1320         gboolean ret = TRUE;
1321
1322         work = fd_list;
1323         while (work)
1324                 {
1325                 FileData *fd = work->data;
1326         
1327                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1328                 work = work->next;
1329                 }
1330
1331         return ret;
1332 }
1333
1334 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1335 {
1336         GList *work;
1337         gboolean ret = TRUE;
1338         
1339         work = fd_list;
1340         while (work)
1341                 {
1342                 FileData *fd = work->data;
1343                 
1344                 if (!file_data_sc_add_ci_copy(fd, dest)) ret = FALSE;
1345                 work = work->next;
1346                 }
1347         
1348         return ret;
1349 }
1350
1351 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1352 {
1353         GList *work;
1354         gboolean ret = TRUE;
1355         
1356         work = fd_list;
1357         while (work)
1358                 {
1359                 FileData *fd = work->data;
1360                 
1361                 if (!file_data_sc_add_ci_move(fd, dest)) ret = FALSE;
1362                 work = work->next;
1363                 }
1364         
1365         return ret;
1366 }
1367
1368 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1369 {
1370         GList *work;
1371         gboolean ret = TRUE;
1372         
1373         work = fd_list;
1374         while (work)
1375                 {
1376                 FileData *fd = work->data;
1377                 
1378                 if (!file_data_sc_add_ci_rename(fd, dest)) ret = FALSE;
1379                 work = work->next;
1380                 }
1381         
1382         return ret;
1383 }
1384
1385 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1386 {
1387         GList *work;
1388         gboolean ret = TRUE;
1389         
1390         work = fd_list;
1391         while (work)
1392                 {
1393                 FileData *fd = work->data;
1394                 
1395                 if (!file_data_sc_add_ci_unspecified(fd, dest)) ret = FALSE;
1396                 work = work->next;
1397                 }
1398         
1399         return ret;
1400 }
1401
1402 void file_data_sc_free_ci_list(GList *fd_list)
1403 {
1404         GList *work;
1405         
1406         work = fd_list;
1407         while (work)
1408                 {
1409                 FileData *fd = work->data;
1410                 
1411                 file_data_sc_free_ci(fd);
1412                 work = work->next;
1413                 }
1414 }
1415
1416 /* 
1417  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1418  * fails if fd->change does not exist or the change type does not match
1419  */
1420
1421 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1422 {
1423         FileDataChangeType type = fd->change->type;
1424         
1425         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1426                 {
1427                 FileData *ofd;
1428                 
1429                 if (!file_data_planned_change_hash)
1430                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1431                 
1432                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1433                         {
1434                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1435                         g_hash_table_remove(file_data_planned_change_hash, old_path);
1436                         file_data_unref(fd);
1437                         }
1438
1439                 if ((ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path)) != fd)
1440                         {
1441                         if (ofd)
1442                                 {
1443                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1444                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
1445                                 file_data_unref(ofd);
1446                                 }
1447                         
1448                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1449                         file_data_ref(fd);
1450                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1451                         }
1452                 }
1453 }
1454
1455 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1456 {
1457         gchar *old_path = fd->change->dest;
1458         fd->change->dest = g_strdup(dest_path);
1459         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1460         g_free(old_path);
1461 }
1462
1463 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1464 {
1465         const char *extension = extension_from_path(fd->change->source);
1466         gchar *base = remove_extension_from_path(dest_path);
1467         gchar *old_path = fd->change->dest;
1468         
1469         fd->change->dest = g_strdup_printf("%s%s", base, extension);
1470         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1471         
1472         g_free(old_path);
1473         g_free(base);
1474 }
1475
1476 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1477 {
1478         GList *work;
1479         gchar *dest_path_full = NULL;
1480         
1481         if (fd->parent) fd = fd->parent;
1482         
1483         if (!dest_path) dest_path = fd->path;
1484         
1485         if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1486                 {
1487                 gchar *dir = remove_level_from_path(fd->path);
1488                 
1489                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1490                 g_free(dir);
1491                 dest_path = dest_path_full;
1492                 }
1493         
1494         if (isdir(dest_path))
1495                 {
1496                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1497                 dest_path = dest_path_full;
1498                 }
1499                 
1500         file_data_update_ci_dest(fd, dest_path);
1501         
1502         work = fd->sidecar_files;
1503         while (work)
1504                 {
1505                 FileData *sfd = work->data;
1506                 
1507                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1508                 work = work->next;
1509                 }
1510         
1511         g_free(dest_path_full);
1512 }
1513
1514 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1515 {
1516         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1517         file_data_sc_update_ci(fd, dest_path);
1518         return TRUE;
1519 }
1520         
1521 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1522 {
1523         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1524         file_data_sc_update_ci(fd, dest_path);
1525         return TRUE;
1526 }
1527
1528 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1529 {
1530         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1531         file_data_sc_update_ci(fd, dest_path);
1532         return TRUE;
1533 }
1534
1535 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1536 {
1537         if (!file_data_sc_check_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1538         file_data_sc_update_ci(fd, dest_path);
1539         return TRUE;
1540 }
1541
1542
1543 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1544 {
1545         GList *work;
1546         gboolean ret = TRUE;
1547         
1548         work = fd_list;
1549         while (work)
1550                 {
1551                 FileData *fd = work->data;
1552                 
1553                 if (!file_data_sc_update_ci_move(fd, dest)) ret = FALSE;
1554                 work = work->next;
1555                 }
1556         
1557         return ret;
1558 }
1559
1560 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1561 {
1562         GList *work;
1563         gboolean ret = TRUE;
1564         
1565         work = fd_list;
1566         while (work)
1567                 {
1568                 FileData *fd = work->data;
1569                 
1570                 if (!file_data_sc_update_ci_copy(fd, dest)) ret = FALSE;
1571                 work = work->next;
1572                 }
1573         
1574         return ret;
1575 }
1576
1577 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1578 {
1579         GList *work;
1580         gboolean ret = TRUE;
1581         
1582         work = fd_list;
1583         while (work)
1584                 {
1585                 FileData *fd = work->data;
1586                 
1587                 if (!file_data_sc_update_ci_unspecified(fd, dest)) ret = FALSE;
1588                 work = work->next;
1589                 }
1590         
1591         return ret;
1592 }
1593
1594
1595 /*
1596  * check dest paths - dest image exists, etc.
1597  * returns FIXME
1598  * it should detect all possible problems with the planned operation
1599  */
1600  
1601 gint file_data_sc_check_ci_dest(FileData *fd)
1602 {
1603 }
1604
1605
1606
1607
1608 /*
1609  * perform the change described by FileFataChangeInfo
1610  * it is used for internal operations, 
1611  * this function actually operates with files on the filesystem
1612  * it should implement safe delete
1613  */
1614  
1615 static gboolean file_data_perform_move(FileData *fd)
1616 {
1617         g_assert(!strcmp(fd->change->source, fd->path));
1618         return move_file(fd->change->source, fd->change->dest);
1619 }
1620
1621 static gboolean file_data_perform_copy(FileData *fd)
1622 {
1623         g_assert(!strcmp(fd->change->source, fd->path));
1624         return copy_file(fd->change->source, fd->change->dest);
1625 }
1626
1627 static gboolean file_data_perform_delete(FileData *fd)
1628 {
1629         if (isdir(fd->path) && !islink(fd->path))
1630                 return rmdir_utf8(fd->path);
1631         else
1632                 return unlink_file(fd->path);
1633 }
1634
1635 static gboolean file_data_perform_ci(FileData *fd)
1636 {
1637         FileDataChangeType type = fd->change->type;
1638         switch (type)
1639                 {
1640                 case FILEDATA_CHANGE_MOVE:
1641                         return file_data_perform_move(fd);
1642                 case FILEDATA_CHANGE_COPY:
1643                         return file_data_perform_copy(fd);
1644                 case FILEDATA_CHANGE_RENAME:
1645                         return file_data_perform_move(fd); /* the same as move */
1646                 case FILEDATA_CHANGE_DELETE:
1647                         return file_data_perform_delete(fd);
1648                 case FILEDATA_CHANGE_UNSPECIFIED:
1649                         /* nothing to do here */
1650                         break;
1651                 }
1652         return TRUE;
1653 }
1654
1655
1656
1657 gboolean file_data_sc_perform_ci(FileData *fd)
1658 {
1659         GList *work;
1660         gboolean ret = TRUE;
1661         FileDataChangeType type = fd->change->type;
1662         
1663         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1664
1665         work = fd->sidecar_files;
1666         while (work)
1667                 {
1668                 FileData *sfd = work->data;
1669                 
1670                 if (!file_data_perform_ci(sfd)) ret = FALSE;
1671                 work = work->next;
1672                 }
1673         
1674         if (!file_data_perform_ci(fd)) ret = FALSE;
1675         
1676         return ret;
1677 }
1678
1679 /*
1680  * updates FileData structure according to FileDataChangeInfo
1681  */
1682
1683 static void file_data_apply_ci(FileData *fd)
1684 {
1685         FileDataChangeType type = fd->change->type;
1686         
1687         /* FIXME delete ?*/
1688         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1689                 {
1690                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
1691                 file_data_planned_change_remove(fd);
1692                 file_data_set_path(fd, fd->change->dest);
1693                 }
1694         file_data_increment_version(fd);
1695         file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
1696 }
1697
1698 gint file_data_sc_apply_ci(FileData *fd)
1699 {
1700         GList *work;
1701         FileDataChangeType type = fd->change->type;
1702         
1703         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1704
1705         work = fd->sidecar_files;
1706         while (work)
1707                 {
1708                 FileData *sfd = work->data;
1709                 
1710                 file_data_apply_ci(sfd);
1711                 work = work->next;
1712                 }
1713         
1714         file_data_apply_ci(fd);
1715         
1716         return TRUE;
1717 }
1718
1719 /*
1720  * notify other modules about the change described by FileFataChangeInfo
1721  */
1722  
1723 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
1724    FIXME do we need the ignore_list? It looks like a workaround for ineffective
1725    implementation in view_file_list.c */
1726
1727
1728
1729
1730 typedef struct _NotifyData NotifyData;
1731
1732 struct _NotifyData {
1733         FileDataNotifyFunc func;
1734         gpointer data;
1735         NotifyPriority priority;
1736         };
1737
1738 static GList *notify_func_list = NULL;
1739
1740 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
1741 {
1742         NotifyData *nda = (NotifyData *)a;
1743         NotifyData *ndb = (NotifyData *)b;
1744
1745         if (nda->priority < ndb->priority) return -1;
1746         if (nda->priority > ndb->priority) return 1;
1747         return 0;
1748 }
1749
1750 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
1751 {
1752         NotifyData *nd;
1753         
1754         nd = g_new(NotifyData, 1);
1755         nd->func = func;
1756         nd->data = data;
1757         nd->priority = priority;
1758
1759         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
1760         DEBUG_1("Notify func registered: %p", nd);
1761         
1762         return TRUE;
1763 }
1764
1765 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
1766 {
1767         GList *work = notify_func_list;
1768         
1769         while (work)
1770                 {
1771                 NotifyData *nd = (NotifyData *)work->data;
1772         
1773                 if (nd->func == func && nd->data == data)
1774                         {
1775                         notify_func_list = g_list_delete_link(notify_func_list, work);
1776                         g_free(nd);
1777                         DEBUG_1("Notify func unregistered: %p", nd);
1778                         return TRUE;
1779                         }
1780                 work = work->next;
1781                 }
1782
1783         return FALSE;
1784 }
1785
1786
1787 void file_data_send_notification(FileData *fd, NotifyType type)
1788 {
1789         GList *work = notify_func_list;
1790
1791         while (work)
1792                 {
1793                 NotifyData *nd = (NotifyData *)work->data;
1794                 
1795                 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
1796                 nd->func(fd, type, nd->data);
1797                 work = work->next;
1798                 }
1799 }
1800
1801 static GHashTable *file_data_monitor_pool = NULL;
1802 static gint realtime_monitor_id = -1;
1803
1804 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
1805 {
1806         FileData *fd = key;
1807
1808         file_data_check_changed_files(fd);
1809         
1810         DEBUG_1("monitor %s", fd->path);
1811 }
1812
1813 static gboolean realtime_monitor_cb(gpointer data)
1814 {
1815         if (!options->update_on_time_change) return TRUE;
1816         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
1817         return TRUE;
1818 }
1819
1820 gint file_data_register_real_time_monitor(FileData *fd)
1821 {
1822         gint count = 0;
1823         
1824         file_data_ref(fd);
1825         
1826         if (!file_data_monitor_pool)
1827                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
1828         
1829         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1830
1831         DEBUG_1("Register realtime %d %s", count, fd->path);
1832         
1833         count++;
1834         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1835         
1836         if (realtime_monitor_id == -1)
1837                 {
1838                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
1839                 }
1840         
1841         return TRUE;
1842 }
1843
1844 gint file_data_unregister_real_time_monitor(FileData *fd)
1845 {
1846         gint count;
1847
1848         g_assert(file_data_monitor_pool);
1849         
1850         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
1851         
1852         DEBUG_1("Unregister realtime %d %s", count, fd->path);
1853         
1854         g_assert(count > 0);
1855         
1856         count--;
1857         
1858         if (count == 0)
1859                 g_hash_table_remove(file_data_monitor_pool, fd);
1860         else
1861                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
1862
1863         file_data_unref(fd);
1864         
1865         if (g_hash_table_size(file_data_monitor_pool) == 0)
1866                 {
1867                 g_source_remove(realtime_monitor_id);
1868                 realtime_monitor_id = -1;
1869                 return FALSE;
1870                 }
1871         
1872         return TRUE;
1873 }