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