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