optimized sidecar file check
[geeqie.git] / src / filedata.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 - 2009 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 "thumb_standard.h"
20 #include "ui_fileops.h"
21 #include "metadata.h"
22 #include "trash.h"
23 #include "histogram.h"
24
25
26 static GHashTable *file_data_pool = NULL;
27 static GHashTable *file_data_planned_change_hash = NULL;
28 static GHashTable *file_data_basename_hash = NULL;
29
30 static gint sidecar_file_priority(const gchar *path);
31
32
33 /*
34  *-----------------------------------------------------------------------------
35  * text conversion utils
36  *-----------------------------------------------------------------------------
37  */
38
39 gchar *text_from_size(gint64 size)
40 {
41         gchar *a, *b;
42         gchar *s, *d;
43         gint l, n, i;
44
45         /* what I would like to use is printf("%'d", size)
46          * BUT: not supported on every libc :(
47          */
48         if (size > G_MAXUINT)
49                 {
50                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
51                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
52                 }
53         else
54                 {
55                 a = g_strdup_printf("%d", (guint)size);
56                 }
57         l = strlen(a);
58         n = (l - 1)/ 3;
59         if (n < 1) return a;
60
61         b = g_new(gchar, l + n + 1);
62
63         s = a;
64         d = b;
65         i = l - n * 3;
66         while (*s != '\0')
67                 {
68                 if (i < 1)
69                         {
70                         i = 3;
71                         *d = ',';
72                         d++;
73                         }
74
75                 *d = *s;
76                 s++;
77                 d++;
78                 i--;
79                 }
80         *d = '\0';
81
82         g_free(a);
83         return b;
84 }
85
86 gchar *text_from_size_abrev(gint64 size)
87 {
88         if (size < (gint64)1024)
89                 {
90                 return g_strdup_printf(_("%d bytes"), (gint)size);
91                 }
92         if (size < (gint64)1048576)
93                 {
94                 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
95                 }
96         if (size < (gint64)1073741824)
97                 {
98                 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
99                 }
100
101         /* to avoid overflowing the gdouble, do division in two steps */
102         size /= 1048576;
103         return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
104 }
105
106 /* note: returned string is valid until next call to text_from_time() */
107 const gchar *text_from_time(time_t t)
108 {
109         static gchar *ret = NULL;
110         gchar buf[128];
111         gint buflen;
112         struct tm *btime;
113         GError *error = NULL;
114
115         btime = localtime(&t);
116
117         /* the %x warning about 2 digit years is not an error */
118         buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
119         if (buflen < 1) return "";
120
121         g_free(ret);
122         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
123         if (error)
124                 {
125                 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
126                 g_error_free(error);
127                 return "";
128                 }
129
130         return ret;
131 }
132
133 /*
134  *-----------------------------------------------------------------------------
135  * file info struct
136  *-----------------------------------------------------------------------------
137  */
138
139 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
140 static void file_data_check_sidecars(FileData *fd, gboolean stat_sidecars);
141 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
142
143
144 void file_data_increment_version(FileData *fd)
145 {
146         fd->version++;
147         fd->valid_marks = 0;
148         if (fd->parent) 
149                 {
150                 fd->parent->version++;
151                 fd->parent->valid_marks = 0;
152                 }
153 }
154
155 static void file_data_basename_hash_insert(FileData *fd)
156 {
157         GList *list;
158         const gchar *ext = extension_from_path(fd->path);
159         gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
160         if (!file_data_basename_hash)
161                 file_data_basename_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
162         
163         list = g_hash_table_lookup(file_data_basename_hash, basename);
164         
165         if (!g_list_find(list, fd))
166                 {
167                 list = g_list_prepend(list, fd);
168                 g_hash_table_insert(file_data_basename_hash, basename, list);
169                 }
170         else 
171                 {
172                 g_free(basename);
173                 }
174 }
175
176 static void file_data_basename_hash_remove(FileData *fd)
177 {
178         GList *list;
179         const gchar *ext = extension_from_path(fd->path);
180         gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
181         if (!file_data_basename_hash)
182                 file_data_basename_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
183         
184         list = g_hash_table_lookup(file_data_basename_hash, basename);
185         
186         list = g_list_remove(list, fd);
187         
188         if (list)
189                 {
190                 g_hash_table_insert(file_data_basename_hash, basename, list);
191                 }
192         else 
193                 {
194                 g_hash_table_remove(file_data_basename_hash, basename);
195                 g_free(basename);
196                 }
197 }
198
199 static void file_data_set_collate_keys(FileData *fd)
200 {
201         gchar *caseless_name;
202
203         caseless_name = g_utf8_casefold(fd->name, -1);
204
205         g_free(fd->collate_key_name);
206         g_free(fd->collate_key_name_nocase);
207
208 #if GLIB_CHECK_VERSION(2, 8, 0)
209         fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
210         fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
211 #else
212         fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
213         fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
214 #endif
215         g_free(caseless_name);
216 }
217
218 static void file_data_set_path(FileData *fd, const gchar *path)
219 {
220         g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
221         g_assert(file_data_pool);
222
223         if (fd->path) file_data_basename_hash_remove(fd);
224         
225         g_free(fd->path);
226
227         if (fd->original_path)
228                 {
229                 g_hash_table_remove(file_data_pool, fd->original_path);
230                 g_free(fd->original_path);
231                 }
232
233         g_assert(!g_hash_table_lookup(file_data_pool, path));
234
235         fd->original_path = g_strdup(path);
236         g_hash_table_insert(file_data_pool, fd->original_path, fd);
237
238         if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
239                 {
240                 fd->path = g_strdup(path);
241                 fd->name = fd->path;
242                 fd->extension = fd->name + 1;
243                 file_data_set_collate_keys(fd);
244                 return;
245                 }
246
247         fd->path = g_strdup(path);
248         fd->name = filename_from_path(fd->path);
249
250         if (strcmp(fd->name, "..") == 0)
251                 {
252                 gchar *dir = remove_level_from_path(path);
253                 g_free(fd->path);
254                 fd->path = remove_level_from_path(dir);
255                 g_free(dir);
256                 fd->name = "..";
257                 fd->extension = fd->name + 2;
258                 file_data_set_collate_keys(fd);
259                 return;
260                 }
261         else if (strcmp(fd->name, ".") == 0)
262                 {
263                 g_free(fd->path);
264                 fd->path = remove_level_from_path(path);
265                 fd->name = ".";
266                 fd->extension = fd->name + 1;
267                 file_data_set_collate_keys(fd);
268                 return;
269                 }
270
271         fd->extension = extension_from_path(fd->path);
272         if (fd->extension == NULL)
273                 {
274                 fd->extension = fd->name + strlen(fd->name);
275                 }
276
277         file_data_basename_hash_insert(fd); /* we can ignore the special cases above - they don't have extensions */
278
279         file_data_set_collate_keys(fd);
280 }
281
282 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
283 {
284         gboolean ret = FALSE;
285         GList *work;
286         
287         if (fd->size != st->st_size ||
288             fd->date != st->st_mtime)
289                 {
290                 fd->size = st->st_size;
291                 fd->date = st->st_mtime;
292                 fd->mode = st->st_mode;
293                 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
294                 fd->thumb_pixbuf = NULL;
295                 file_data_increment_version(fd);
296                 file_data_send_notification(fd, NOTIFY_REREAD);
297                 ret = TRUE;
298                 }
299
300         work = fd->sidecar_files;
301         while (work)
302                 {
303                 FileData *sfd = work->data;
304                 struct stat st;
305                 work = work->next;
306
307                 if (!stat_utf8(sfd->path, &st))
308                         {
309                         fd->size = 0;
310                         fd->date = 0;
311                         file_data_disconnect_sidecar_file(fd, sfd);
312                         ret = TRUE;
313                         continue;
314                         }
315
316                 ret |= file_data_check_changed_files_recursive(sfd, &st);
317                 }
318         return ret;
319 }
320
321
322 gboolean file_data_check_changed_files(FileData *fd)
323 {
324         gboolean ret = FALSE;
325         struct stat st;
326         
327         if (fd->parent) fd = fd->parent;
328
329         if (!stat_utf8(fd->path, &st))
330                 {
331                 GList *work;
332                 FileData *sfd = NULL;
333
334                 /* parent is missing, we have to rebuild whole group */
335                 ret = TRUE;
336                 fd->size = 0;
337                 fd->date = 0;
338                 
339                 work = fd->sidecar_files;
340                 while (work)
341                         {
342                         sfd = work->data;
343                         work = work->next;
344                 
345                         file_data_disconnect_sidecar_file(fd, sfd);
346                         }
347                 if (sfd) file_data_check_sidecars(sfd, FALSE); /* this will group the sidecars back together */
348                 file_data_send_notification(fd, NOTIFY_REREAD);
349                 }
350         else
351                 {
352                 ret |= file_data_check_changed_files_recursive(fd, &st);
353                 }
354
355         return ret;
356 }
357
358 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars)
359 {
360         FileData *fd;
361
362         DEBUG_2("file_data_new: '%s' %d %d", path_utf8, check_sidecars, stat_sidecars);
363
364         if (!file_data_pool)
365                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
366
367         fd = g_hash_table_lookup(file_data_pool, path_utf8);
368         if (fd)
369                 {
370                 file_data_ref(fd);
371                 }
372                 
373         if (!fd && file_data_planned_change_hash)
374                 {
375                 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
376                 if (fd)
377                         {
378                         DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
379                         file_data_ref(fd);
380                         file_data_apply_ci(fd);
381                         }
382                 }
383                 
384         if (fd)
385                 {
386                 gboolean changed;
387                 
388                 if (fd->parent)
389                         changed = file_data_check_changed_files(fd);
390                 else
391                         changed = file_data_check_changed_files_recursive(fd, st);
392                 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
393                         file_data_check_sidecars(fd, stat_sidecars);
394                 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
395                 
396                 return fd;
397                 }
398
399         fd = g_new0(FileData, 1);
400         
401         fd->size = st->st_size;
402         fd->date = st->st_mtime;
403         fd->mode = st->st_mode;
404         fd->ref = 1;
405         fd->magick = 0x12345678;
406
407         file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
408
409         if (check_sidecars)
410                 file_data_check_sidecars(fd, stat_sidecars);
411
412         return fd;
413 }
414
415 static void file_data_check_sidecars(FileData *fd, gboolean stat_sidecars)
416 {
417         gint base_len;
418         GString *fname;
419         FileData *parent_fd = NULL;
420         GList *work;
421         GList *basename_list = NULL;
422
423         if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
424                 return;
425
426         base_len = fd->extension - fd->path;
427         fname = g_string_new_len(fd->path, base_len);
428
429         if (!stat_sidecars)
430                 {
431                 basename_list = g_hash_table_lookup(file_data_basename_hash, fname->str);
432                 }
433
434         work = sidecar_ext_get_list();
435
436         while (work)
437                 {
438                 /* check for possible sidecar files;
439                    the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
440                    they have fd->ref set to 0 and file_data unref must chack and free them all together
441                    (using fd->ref would cause loops and leaks)
442                 */
443
444                 FileData *new_fd;
445                 gchar *ext = work->data;
446
447                 work = work->next;
448
449                 if (g_ascii_strcasecmp(ext, fd->extension) == 0)
450                         {
451                         new_fd = fd; /* processing the original file */
452                         }
453                 else
454                         {
455                         if (stat_sidecars)
456                                 {
457                                 struct stat nst;
458                                 g_string_truncate(fname, base_len);
459                                 if (!stat_utf8_case_insensitive_ext(fname, ext, &nst))
460                                         continue;
461                                 new_fd = file_data_new(fname->str, &nst, FALSE, FALSE);
462                                 }
463                         else
464                                 {
465                                 GList *work2 = basename_list;
466                                 new_fd = NULL;
467                                 
468                                 while (work2)
469                                         {
470                                         FileData *sfd = work2->data;
471                                         if (g_ascii_strcasecmp(ext, sfd->extension) == 0) 
472                                                 {
473                                                 new_fd = file_data_ref(sfd);
474                                                 break;
475                                                 }
476                                         work2 = work2->next;
477                                         }
478                                         
479                                 if (!new_fd) continue;
480                                 }
481                         
482                         if (new_fd->disable_grouping)
483                                 {
484                                 file_data_unref(new_fd);
485                                 continue;
486                                 }
487                         
488                         new_fd->ref--; /* do not use ref here */
489                         }
490
491                 if (!parent_fd)
492                         parent_fd = new_fd; /* parent is the one with the highest prio, found first */
493                 else
494                         file_data_merge_sidecar_files(parent_fd, new_fd);
495                 }
496         g_string_free(fname, TRUE);
497 }
498
499
500 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars)
501 {
502         gchar *path_utf8 = path_to_utf8(path);
503         FileData *ret = file_data_new(path_utf8, st, check_sidecars, stat_sidecars);
504
505         g_free(path_utf8);
506         return ret;
507 }
508
509 FileData *file_data_new_simple(const gchar *path_utf8)
510 {
511         struct stat st;
512
513         if (!stat_utf8(path_utf8, &st))
514                 {
515                 st.st_size = 0;
516                 st.st_mtime = 0;
517                 }
518
519         return file_data_new(path_utf8, &st, TRUE, TRUE);
520 }
521
522 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
523 {
524         sfd->parent = target;
525         if (!g_list_find(target->sidecar_files, sfd))
526                 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
527         file_data_increment_version(sfd); /* increments both sfd and target */
528         return target;
529 }
530
531
532 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
533 {
534         GList *work;
535         
536         file_data_add_sidecar_file(target, source);
537
538         work = source->sidecar_files;
539         while (work)
540                 {
541                 FileData *sfd = work->data;
542                 file_data_add_sidecar_file(target, sfd);
543                 work = work->next;
544                 }
545
546         g_list_free(source->sidecar_files);
547         source->sidecar_files = NULL;
548
549         target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
550         
551         return target;
552 }
553
554 #ifdef DEBUG_FILEDATA
555 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
556 #else
557 FileData *file_data_ref(FileData *fd)
558 #endif
559 {
560         if (fd == NULL) return NULL;
561 #ifdef DEBUG_FILEDATA
562         if (fd->magick != 0x12345678)
563                 DEBUG_0("fd magick mismatch at %s:%d", file, line);
564 #endif
565         g_assert(fd->magick == 0x12345678);
566         fd->ref++;
567
568 #ifdef DEBUG_FILEDATA
569         DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
570 #else
571         DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
572 #endif
573         return fd;
574 }
575
576 static void file_data_free(FileData *fd)
577 {
578         g_assert(fd->magick == 0x12345678);
579         g_assert(fd->ref == 0);
580
581         if (fd->path) file_data_basename_hash_remove(fd);
582         g_hash_table_remove(file_data_pool, fd->original_path);
583
584         g_free(fd->path);
585         g_free(fd->original_path);
586         g_free(fd->collate_key_name);
587         g_free(fd->collate_key_name_nocase);
588         if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
589         histmap_free(fd->histmap);
590         
591         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
592
593         file_data_change_info_free(NULL, fd);
594         g_free(fd);
595 }
596
597 #ifdef DEBUG_FILEDATA
598 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
599 #else
600 void file_data_unref(FileData *fd)
601 #endif
602 {
603         if (fd == NULL) return;
604 #ifdef DEBUG_FILEDATA
605         if (fd->magick != 0x12345678)
606                 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
607 #endif
608         g_assert(fd->magick == 0x12345678);
609         
610         fd->ref--;
611 #ifdef DEBUG_FILEDATA
612         DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
613 #else
614         DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
615 #endif
616         if (fd->ref == 0)
617                 {
618                 GList *work;
619                 FileData *parent = fd->parent ? fd->parent : fd;
620                 
621                 if (parent->ref > 0) return;
622
623                 work = parent->sidecar_files;
624                 while (work)
625                         {
626                         FileData *sfd = work->data;
627                         if (sfd->ref > 0) return;
628                         work = work->next;
629                         }
630
631                 /* none of parent/children is referenced, we can free everything */
632
633                 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
634
635                 work = parent->sidecar_files;
636                 while (work)
637                         {
638                         FileData *sfd = work->data;
639                         file_data_free(sfd);
640                         work = work->next;
641                         }
642
643                 g_list_free(parent->sidecar_files);
644                 parent->sidecar_files = NULL;
645
646                 file_data_free(parent);
647                 }
648 }
649
650 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
651 {
652         sfd->parent = target;
653         g_assert(g_list_find(target->sidecar_files, sfd));
654         
655         file_data_increment_version(sfd); /* increments both sfd and target */
656
657         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
658         sfd->parent = NULL;
659
660         if (sfd->ref == 0)
661                 {
662                 file_data_free(sfd);
663                 return NULL;
664                 }
665
666         return sfd;
667 }
668
669 /* disables / enables grouping for particular file, sends UPDATE notification */
670 void file_data_disable_grouping(FileData *fd, gboolean disable)
671 {
672         if (!fd->disable_grouping == !disable) return;
673         fd->disable_grouping = !!disable;
674         
675         if (disable)
676                 {
677                 if (fd->parent)
678                         {
679                         FileData *parent = file_data_ref(fd->parent);
680                         file_data_disconnect_sidecar_file(parent, fd);
681                         file_data_send_notification(fd, NOTIFY_GROUPING);
682                         file_data_send_notification(parent, NOTIFY_GROUPING);
683                         file_data_unref(parent);
684                         }
685                 else if (fd->sidecar_files)
686                         {
687                         GList *sidecar_files = filelist_copy(fd->sidecar_files);
688                         GList *work = sidecar_files;
689                         while (work)
690                                 {
691                                 FileData *sfd = work->data;
692                                 work = work->next;
693                                 file_data_disconnect_sidecar_file(fd, sfd);
694                                 file_data_send_notification(sfd, NOTIFY_GROUPING);
695                                 }
696                         file_data_send_notification(fd, NOTIFY_GROUPING);
697                         file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
698                         filelist_free(sidecar_files);
699                         }
700                 }
701         else
702                 {
703                 file_data_check_sidecars(fd, FALSE);
704                 file_data_send_notification(fd, NOTIFY_GROUPING);
705                 }
706 }
707
708 /* compare name without extension */
709 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
710 {
711         size_t len1 = fd1->extension - fd1->name;
712         size_t len2 = fd2->extension - fd2->name;
713
714         if (len1 < len2) return -1;
715         if (len1 > len2) return 1;
716
717         return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
718 }
719
720 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
721 {
722         if (!fdci && fd) fdci = fd->change;
723
724         if (!fdci) return;
725
726         g_free(fdci->source);
727         g_free(fdci->dest);
728
729         g_free(fdci);
730
731         if (fd) fd->change = NULL;
732 }
733
734 static gboolean file_data_can_write_directly(FileData *fd)
735 {
736         return filter_name_is_writable(fd->extension);
737 }
738
739 static gboolean file_data_can_write_sidecar(FileData *fd)
740 {
741         return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
742 }
743
744 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
745 {
746         gchar *sidecar_path = NULL;
747         GList *work;
748         
749         if (!file_data_can_write_sidecar(fd)) return NULL;
750         
751         work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
752         while (work)
753                 {
754                 FileData *sfd = work->data;
755                 work = work->next;
756                 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
757                         {
758                         sidecar_path = g_strdup(sfd->path);
759                         break;
760                         }
761                 }
762         
763         if (!existing_only && !sidecar_path)
764                 {
765                 gchar *base = remove_extension_from_path(fd->path);
766                 sidecar_path = g_strconcat(base, ".xmp", NULL);
767                 g_free(base);
768                 }
769
770         return sidecar_path;
771 }
772
773
774 /*
775  *-----------------------------------------------------------------------------
776  * sidecar file info struct
777  *-----------------------------------------------------------------------------
778  */
779
780
781
782 static gint sidecar_file_priority(const gchar *path)
783 {
784         const gchar *extension = extension_from_path(path);
785         gint i = 1;
786         GList *work;
787
788         if (extension == NULL)
789                 return 0;
790
791         work = sidecar_ext_get_list();
792
793         while (work) {
794                 gchar *ext = work->data;
795                 
796                 work = work->next;
797                 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
798                 i++;
799         }
800         return 0;
801 }
802
803
804 /*
805  *-----------------------------------------------------------------------------
806  * load file list
807  *-----------------------------------------------------------------------------
808  */
809
810 static SortType filelist_sort_method = SORT_NONE;
811 static gboolean filelist_sort_ascend = TRUE;
812
813
814 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
815 {
816         if (!filelist_sort_ascend)
817                 {
818                 FileData *tmp = fa;
819                 fa = fb;
820                 fb = tmp;
821                 }
822
823         switch (filelist_sort_method)
824                 {
825                 case SORT_NAME:
826                         break;
827                 case SORT_SIZE:
828                         if (fa->size < fb->size) return -1;
829                         if (fa->size > fb->size) return 1;
830                         /* fall back to name */
831                         break;
832                 case SORT_TIME:
833                         if (fa->date < fb->date) return -1;
834                         if (fa->date > fb->date) return 1;
835                         /* fall back to name */
836                         break;
837 #ifdef HAVE_STRVERSCMP
838                 case SORT_NUMBER:
839                         return strverscmp(fa->name, fb->name);
840                         break;
841 #endif
842                 default:
843                         break;
844                 }
845
846         if (options->file_sort.case_sensitive)
847                 return strcmp(fa->collate_key_name, fb->collate_key_name);
848         else
849                 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
850 }
851
852 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
853 {
854         filelist_sort_method = method;
855         filelist_sort_ascend = ascend;
856         return filelist_sort_compare_filedata(fa, fb);
857 }
858
859 static gint filelist_sort_file_cb(gpointer a, gpointer b)
860 {
861         return filelist_sort_compare_filedata(a, b);
862 }
863
864 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
865 {
866         filelist_sort_method = method;
867         filelist_sort_ascend = ascend;
868         return g_list_sort(list, cb);
869 }
870
871 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
872 {
873         filelist_sort_method = method;
874         filelist_sort_ascend = ascend;
875         return g_list_insert_sorted(list, data, cb);
876 }
877
878 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
879 {
880         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
881 }
882
883 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
884 {
885         return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
886 }
887
888
889 static GList *filelist_filter_out_sidecars(GList *flist)
890 {
891         GList *work = flist;
892         GList *flist_filtered = NULL;
893
894         while (work)
895                 {
896                 FileData *fd = work->data;
897         
898                 work = work->next;
899                 if (fd->parent) /* remove fd's that are children */
900                         file_data_unref(fd);
901                 else
902                         flist_filtered = g_list_prepend(flist_filtered, fd);
903                 }
904         g_list_free(flist);
905
906         return flist_filtered;
907 }
908
909 static gboolean is_hidden_file(const gchar *name)
910 {
911         if (name[0] != '.') return FALSE;
912         if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
913         return TRUE;
914 }
915
916 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
917 {
918         DIR *dp;
919         struct dirent *dir;
920         gchar *pathl;
921         GList *dlist = NULL;
922         GList *flist = NULL;
923         gint (*stat_func)(const gchar *path, struct stat *buf);
924
925         g_assert(files || dirs);
926
927         if (files) *files = NULL;
928         if (dirs) *dirs = NULL;
929
930         pathl = path_from_utf8(dir_fd->path);
931         if (!pathl) return FALSE;
932
933         dp = opendir(pathl);
934         if (dp == NULL)
935                 {
936                 g_free(pathl);
937                 return FALSE;
938                 }
939
940         if (follow_symlinks)
941                 stat_func = stat;
942         else
943                 stat_func = lstat;
944
945         while ((dir = readdir(dp)) != NULL)
946                 {
947                 struct stat ent_sbuf;
948                 const gchar *name = dir->d_name;
949                 gchar *filepath;
950
951                 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
952                         continue;
953
954                 filepath = g_build_filename(pathl, name, NULL);
955                 if (stat_func(filepath, &ent_sbuf) >= 0)
956                         {
957                         if (S_ISDIR(ent_sbuf.st_mode))
958                                 {
959                                 /* we ignore the .thumbnails dir for cleanliness */
960                                 if (dirs &&
961                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
962                                     strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
963                                     strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
964                                     strcmp(name, THUMB_FOLDER_LOCAL) != 0)
965                                         {
966                                         dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, FALSE));
967                                         }
968                                 }
969                         else
970                                 {
971                                 if (files && filter_name_exists(name))
972                                         {
973                                         flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, FALSE));
974                                         }
975                                 }
976                         }
977                 g_free(filepath);
978                 }
979
980         closedir(dp);
981         
982         g_free(pathl);
983
984         if (dirs) *dirs = dlist;
985         if (files) *files = filelist_filter_out_sidecars(flist);
986
987         return TRUE;
988 }
989
990 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
991 {
992         return filelist_read_real(dir_fd, files, dirs, TRUE);
993 }
994
995 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
996 {
997         return filelist_read_real(dir_fd, files, dirs, FALSE);
998 }
999
1000 void filelist_free(GList *list)
1001 {
1002         GList *work;
1003
1004         work = list;
1005         while (work)
1006                 {
1007                 file_data_unref((FileData *)work->data);
1008                 work = work->next;
1009                 }
1010
1011         g_list_free(list);
1012 }
1013
1014
1015 GList *filelist_copy(GList *list)
1016 {
1017         GList *new_list = NULL;
1018         GList *work;
1019
1020         work = list;
1021         while (work)
1022                 {
1023                 FileData *fd;
1024
1025                 fd = work->data;
1026                 work = work->next;
1027
1028                 new_list = g_list_prepend(new_list, file_data_ref(fd));
1029                 }
1030
1031         return g_list_reverse(new_list);
1032 }
1033
1034 GList *filelist_from_path_list(GList *list)
1035 {
1036         GList *new_list = NULL;
1037         GList *work;
1038
1039         work = list;
1040         while (work)
1041                 {
1042                 gchar *path;
1043
1044                 path = work->data;
1045                 work = work->next;
1046
1047                 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1048                 }
1049
1050         return g_list_reverse(new_list);
1051 }
1052
1053 GList *filelist_to_path_list(GList *list)
1054 {
1055         GList *new_list = NULL;
1056         GList *work;
1057
1058         work = list;
1059         while (work)
1060                 {
1061                 FileData *fd;
1062
1063                 fd = work->data;
1064                 work = work->next;
1065
1066                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1067                 }
1068
1069         return g_list_reverse(new_list);
1070 }
1071
1072 GList *filelist_filter(GList *list, gboolean is_dir_list)
1073 {
1074         GList *work;
1075
1076         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1077
1078         work = list;
1079         while (work)
1080                 {
1081                 FileData *fd = (FileData *)(work->data);
1082                 const gchar *name = fd->name;
1083
1084                 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1085                     (!is_dir_list && !filter_name_exists(name)) ||
1086                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1087                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1088                         {
1089                         GList *link = work;
1090                         
1091                         list = g_list_remove_link(list, link);
1092                         file_data_unref(fd);
1093                         g_list_free(link);
1094                         }
1095         
1096                 work = work->next;
1097                 }
1098
1099         return list;
1100 }
1101
1102 /*
1103  *-----------------------------------------------------------------------------
1104  * filelist recursive
1105  *-----------------------------------------------------------------------------
1106  */
1107
1108 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1109 {
1110         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1111 }
1112
1113 GList *filelist_sort_path(GList *list)
1114 {
1115         return g_list_sort(list, filelist_sort_path_cb);
1116 }
1117
1118 static void filelist_recursive_append(GList **list, GList *dirs)
1119 {
1120         GList *work;
1121
1122         work = dirs;
1123         while (work)
1124                 {
1125                 FileData *fd = (FileData *)(work->data);
1126                 GList *f;
1127                 GList *d;
1128
1129                 if (filelist_read(fd, &f, &d))
1130                         {
1131                         f = filelist_filter(f, FALSE);
1132                         f = filelist_sort_path(f);
1133                         *list = g_list_concat(*list, f);
1134
1135                         d = filelist_filter(d, TRUE);
1136                         d = filelist_sort_path(d);
1137                         filelist_recursive_append(list, d);
1138                         filelist_free(d);
1139                         }
1140
1141                 work = work->next;
1142                 }
1143 }
1144
1145 GList *filelist_recursive(FileData *dir_fd)
1146 {
1147         GList *list;
1148         GList *d;
1149
1150         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1151         list = filelist_filter(list, FALSE);
1152         list = filelist_sort_path(list);
1153
1154         d = filelist_filter(d, TRUE);
1155         d = filelist_sort_path(d);
1156         filelist_recursive_append(&list, d);
1157         filelist_free(d);
1158
1159         return list;
1160 }
1161
1162
1163 /*
1164  * marks and orientation
1165  */
1166
1167 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1168 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1169 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1170 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1171
1172 gboolean file_data_get_mark(FileData *fd, gint n)
1173 {
1174         gboolean valid = (fd->valid_marks & (1 << n));
1175         
1176         if (file_data_get_mark_func[n] && !valid) 
1177                 {
1178                 guint old = fd->marks;
1179                 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1180                 
1181                 if (!value != !(fd->marks & (1 << n))) 
1182                         {
1183                         fd->marks = fd->marks ^ (1 << n);
1184                         }
1185                 
1186                 fd->valid_marks |= (1 << n);
1187                 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1188                         {
1189                         file_data_unref(fd);
1190                         }
1191                 else if (!old && fd->marks)
1192                         {
1193                         file_data_ref(fd);
1194                         }
1195                 }
1196
1197         return !!(fd->marks & (1 << n));
1198 }
1199
1200 guint file_data_get_marks(FileData *fd)
1201 {
1202         gint i;
1203         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1204         return fd->marks;
1205 }
1206
1207 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1208 {
1209         guint old;
1210         if (!value == !file_data_get_mark(fd, n)) return;
1211         
1212         if (file_data_set_mark_func[n]) 
1213                 {
1214                 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1215                 }
1216         
1217         old = fd->marks;
1218
1219         fd->marks = fd->marks ^ (1 << n);
1220         
1221         if (old && !fd->marks) /* keep files with non-zero marks in memory */
1222                 {
1223                 file_data_unref(fd);
1224                 }
1225         else if (!old && fd->marks)
1226                 {
1227                 file_data_ref(fd);
1228                 }
1229         
1230         file_data_increment_version(fd);
1231         file_data_send_notification(fd, NOTIFY_MARKS);
1232 }
1233
1234 gboolean file_data_filter_marks(FileData *fd, guint filter)
1235 {
1236         gint i;
1237         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1238         return ((fd->marks & filter) == filter);
1239 }
1240
1241 GList *file_data_filter_marks_list(GList *list, guint filter)
1242 {
1243         GList *work;
1244
1245         work = list;
1246         while (work)
1247                 {
1248                 FileData *fd = work->data;
1249                 GList *link = work;
1250                 work = work->next;
1251
1252                 if (!file_data_filter_marks(fd, filter))
1253                         {
1254                         list = g_list_remove_link(list, link);
1255                         file_data_unref(fd);
1256                         g_list_free(link);
1257                         }
1258                 }
1259
1260         return list;
1261 }
1262
1263 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1264 {
1265         FileData *fd = value;
1266         file_data_increment_version(fd);
1267         file_data_send_notification(fd, NOTIFY_MARKS);
1268 }
1269
1270 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1271 {
1272         if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1273         
1274         if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1275                 
1276         file_data_get_mark_func[n] = get_mark_func;
1277         file_data_set_mark_func[n] = set_mark_func;
1278         file_data_mark_func_data[n] = data;
1279         file_data_destroy_mark_func[n] = notify;
1280
1281         if (get_mark_func)
1282                 {
1283                 /* this effectively changes all known files */
1284                 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1285                 }
1286
1287         return TRUE;
1288 }
1289
1290 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1291 {
1292         if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1293         if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1294         if (data) *data = file_data_mark_func_data[n];
1295 }
1296
1297 gint file_data_get_user_orientation(FileData *fd)
1298 {
1299         return fd->user_orientation;
1300 }
1301
1302 void file_data_set_user_orientation(FileData *fd, gint value)
1303 {
1304         if (fd->user_orientation == value) return;
1305
1306         fd->user_orientation = value;
1307         file_data_increment_version(fd);
1308         file_data_send_notification(fd, NOTIFY_ORIENTATION);
1309 }
1310
1311
1312 /*
1313  * file_data    - operates on the given fd
1314  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1315  */
1316
1317
1318 /* return list of sidecar file extensions in a string */
1319 gchar *file_data_sc_list_to_string(FileData *fd)
1320 {
1321         GList *work;
1322         GString *result = g_string_new("");
1323
1324         work = fd->sidecar_files;
1325         while (work)
1326                 {
1327                 FileData *sfd = work->data;
1328
1329                 result = g_string_append(result, "+ ");
1330                 result = g_string_append(result, sfd->extension);
1331                 work = work->next;
1332                 if (work) result = g_string_append_c(result, ' ');
1333                 }
1334
1335         return g_string_free(result, FALSE);
1336 }
1337
1338
1339
1340 /*
1341  * add FileDataChangeInfo (see typedefs.h) for the given operation
1342  * uses file_data_add_change_info
1343  *
1344  * fails if the fd->change already exists - change operations can't run in parallel
1345  * fd->change_info works as a lock
1346  *
1347  * dest can be NULL - in this case the current name is used for now, it will
1348  * be changed later
1349  */
1350
1351 /*
1352    FileDataChangeInfo types:
1353    COPY
1354    MOVE   - path is changed, name may be changed too
1355    RENAME - path remains unchanged, name is changed
1356             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1357             sidecar names are changed too, extensions are not changed
1358    DELETE
1359    UPDATE - file size, date or grouping has been changed
1360 */
1361
1362 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1363 {
1364         FileDataChangeInfo *fdci;
1365
1366         if (fd->change) return FALSE;
1367
1368         fdci = g_new0(FileDataChangeInfo, 1);
1369
1370         fdci->type = type;
1371
1372         if (src)
1373                 fdci->source = g_strdup(src);
1374         else
1375                 fdci->source = g_strdup(fd->path);
1376
1377         if (dest)
1378                 fdci->dest = g_strdup(dest);
1379
1380         fd->change = fdci;
1381         
1382         return TRUE;
1383 }
1384
1385 static void file_data_planned_change_remove(FileData *fd)
1386 {
1387         if (file_data_planned_change_hash &&
1388             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1389                 {
1390                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1391                         {
1392                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1393                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1394                         file_data_unref(fd);
1395                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1396                                 {
1397                                 g_hash_table_destroy(file_data_planned_change_hash);
1398                                 file_data_planned_change_hash = NULL;
1399                                 DEBUG_1("planned change: empty");
1400                                 }
1401                         }
1402                 }
1403 }
1404
1405
1406 void file_data_free_ci(FileData *fd)
1407 {
1408         FileDataChangeInfo *fdci = fd->change;
1409
1410         if (!fdci) return;
1411
1412         file_data_planned_change_remove(fd);
1413
1414         g_free(fdci->source);
1415         g_free(fdci->dest);
1416
1417         g_free(fdci);
1418
1419         fd->change = NULL;
1420 }
1421
1422
1423 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1424 {
1425         GList *work;
1426
1427         if (fd->parent) fd = fd->parent;
1428         
1429         if (fd->change) return FALSE;
1430         
1431         work = fd->sidecar_files;
1432         while (work)
1433                 {
1434                 FileData *sfd = work->data;
1435                 
1436                 if (sfd->change) return FALSE;
1437                 work = work->next;
1438                 }
1439
1440         file_data_add_ci(fd, type, NULL, NULL);
1441         
1442         work = fd->sidecar_files;
1443         while (work)
1444                 {
1445                 FileData *sfd = work->data;
1446                 
1447                 file_data_add_ci(sfd, type, NULL, NULL);
1448                 work = work->next;
1449                 }
1450                 
1451         return TRUE;
1452 }
1453
1454 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1455 {
1456         GList *work;
1457         
1458         if (fd->parent) fd = fd->parent;
1459         
1460         if (!fd->change || fd->change->type != type) return FALSE;
1461         
1462         work = fd->sidecar_files;
1463         while (work)
1464                 {
1465                 FileData *sfd = work->data;
1466
1467                 if (!sfd->change || sfd->change->type != type) return FALSE;
1468                 work = work->next;
1469                 }
1470
1471         return TRUE;
1472 }
1473
1474
1475 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1476 {
1477         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1478         file_data_sc_update_ci_copy(fd, dest_path);
1479         return TRUE;
1480 }
1481
1482 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1483 {
1484         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1485         file_data_sc_update_ci_move(fd, dest_path);
1486         return TRUE;
1487 }
1488
1489 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1490 {
1491         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1492         file_data_sc_update_ci_rename(fd, dest_path);
1493         return TRUE;
1494 }
1495
1496 gboolean file_data_sc_add_ci_delete(FileData *fd)
1497 {
1498         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1499 }
1500
1501 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1502 {
1503         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1504         file_data_sc_update_ci_unspecified(fd, dest_path);
1505         return TRUE;
1506 }
1507
1508 gboolean file_data_add_ci_write_metadata(FileData *fd)
1509 {
1510         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1511 }
1512
1513 void file_data_sc_free_ci(FileData *fd)
1514 {
1515         GList *work;
1516
1517         if (fd->parent) fd = fd->parent;
1518         
1519         file_data_free_ci(fd);
1520         
1521         work = fd->sidecar_files;
1522         while (work)
1523                 {
1524                 FileData *sfd = work->data;
1525         
1526                 file_data_free_ci(sfd);
1527                 work = work->next;
1528                 }
1529 }
1530
1531 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1532 {
1533         GList *work;
1534         gboolean ret = TRUE;
1535
1536         work = fd_list;
1537         while (work)
1538                 {
1539                 FileData *fd = work->data;
1540         
1541                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1542                 work = work->next;
1543                 }
1544
1545         return ret;
1546 }
1547
1548 static void file_data_sc_revert_ci_list(GList *fd_list)
1549 {
1550         GList *work;
1551         
1552         work = fd_list;
1553         while (work)
1554                 {
1555                 FileData *fd = work->data;
1556                 
1557                 file_data_sc_free_ci(fd);
1558                 work = work->prev;
1559                 }
1560 }
1561
1562 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1563 {
1564         GList *work;
1565         
1566         work = fd_list;
1567         while (work)
1568                 {
1569                 FileData *fd = work->data;
1570                 
1571                 if (!func(fd, dest))
1572                         {
1573                         file_data_sc_revert_ci_list(work->prev);
1574                         return FALSE;
1575                         }
1576                 work = work->next;
1577                 }
1578         
1579         return TRUE;
1580 }
1581
1582 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1583 {
1584         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1585 }
1586
1587 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1588 {
1589         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1590 }
1591
1592 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1593 {
1594         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1595 }
1596
1597 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1598 {
1599         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1600 }
1601
1602 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1603 {
1604         GList *work;
1605         gboolean ret = TRUE;
1606
1607         work = fd_list;
1608         while (work)
1609                 {
1610                 FileData *fd = work->data;
1611         
1612                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1613                 work = work->next;
1614                 }
1615
1616         return ret;
1617 }
1618
1619 void file_data_free_ci_list(GList *fd_list)
1620 {
1621         GList *work;
1622         
1623         work = fd_list;
1624         while (work)
1625                 {
1626                 FileData *fd = work->data;
1627                 
1628                 file_data_free_ci(fd);
1629                 work = work->next;
1630                 }
1631 }
1632
1633 void file_data_sc_free_ci_list(GList *fd_list)
1634 {
1635         GList *work;
1636         
1637         work = fd_list;
1638         while (work)
1639                 {
1640                 FileData *fd = work->data;
1641                 
1642                 file_data_sc_free_ci(fd);
1643                 work = work->next;
1644                 }
1645 }
1646
1647 /*
1648  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1649  * fails if fd->change does not exist or the change type does not match
1650  */
1651
1652 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1653 {
1654         FileDataChangeType type = fd->change->type;
1655         
1656         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1657                 {
1658                 FileData *ofd;
1659                 
1660                 if (!file_data_planned_change_hash)
1661                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1662                 
1663                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1664                         {
1665                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1666                         g_hash_table_remove(file_data_planned_change_hash, old_path);
1667                         file_data_unref(fd);
1668                         }
1669
1670                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1671                 if (ofd != fd)
1672                         {
1673                         if (ofd)
1674                                 {
1675                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1676                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
1677                                 file_data_unref(ofd);
1678                                 }
1679                         
1680                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1681                         file_data_ref(fd);
1682                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1683                         }
1684                 }
1685 }
1686
1687 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1688 {
1689         gchar *old_path = fd->change->dest;
1690
1691         fd->change->dest = g_strdup(dest_path);
1692         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1693         g_free(old_path);
1694 }
1695
1696 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1697 {
1698         const gchar *extension = extension_from_path(fd->change->source);
1699         gchar *base = remove_extension_from_path(dest_path);
1700         gchar *old_path = fd->change->dest;
1701         
1702         fd->change->dest = g_strconcat(base, extension, NULL);
1703         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1704         
1705         g_free(old_path);
1706         g_free(base);
1707 }
1708
1709 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1710 {
1711         GList *work;
1712         gchar *dest_path_full = NULL;
1713         
1714         if (fd->parent) fd = fd->parent;
1715         
1716         if (!dest_path)
1717                 {
1718                 dest_path = fd->path;
1719                 }
1720         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1721                 {
1722                 gchar *dir = remove_level_from_path(fd->path);
1723                 
1724                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1725                 g_free(dir);
1726                 dest_path = dest_path_full;
1727                 }
1728         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1729                 {
1730                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1731                 dest_path = dest_path_full;
1732                 }
1733                 
1734         file_data_update_ci_dest(fd, dest_path);
1735         
1736         work = fd->sidecar_files;
1737         while (work)
1738                 {
1739                 FileData *sfd = work->data;
1740                 
1741                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1742                 work = work->next;
1743                 }
1744         
1745         g_free(dest_path_full);
1746 }
1747
1748 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1749 {
1750         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1751         file_data_sc_update_ci(fd, dest_path);
1752         return TRUE;
1753 }
1754
1755 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1756 {
1757         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1758 }
1759         
1760 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1761 {
1762         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1763 }
1764
1765 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1766 {
1767         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1768 }
1769
1770 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1771 {
1772         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1773 }
1774
1775 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1776                                                       const gchar *dest,
1777                                                       gboolean (*func)(FileData *, const gchar *))
1778 {
1779         GList *work;
1780         gboolean ret = TRUE;
1781         
1782         work = fd_list;
1783         while (work)
1784                 {
1785                 FileData *fd = work->data;
1786                 
1787                 if (!func(fd, dest)) ret = FALSE;
1788                 work = work->next;
1789                 }
1790         
1791         return ret;
1792 }
1793
1794 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1795 {
1796         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1797 }
1798
1799 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1800 {
1801         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1802 }
1803
1804 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1805 {
1806         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1807 }
1808
1809
1810 /*
1811  * verify source and dest paths - dest image exists, etc.
1812  * it should detect all possible problems with the planned operation
1813  */
1814
1815 gint file_data_verify_ci(FileData *fd)
1816 {
1817         gint ret = CHANGE_OK;
1818         gchar *dir;
1819         
1820         if (!fd->change)
1821                 {
1822                 DEBUG_1("Change checked: no change info: %s", fd->path);
1823                 return ret;
1824                 }
1825
1826         if (!isname(fd->path))
1827                 {
1828                 /* this probably should not happen */
1829                 ret |= CHANGE_NO_SRC;
1830                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1831                 return ret;
1832                 }
1833                 
1834         dir = remove_level_from_path(fd->path);
1835         
1836         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1837             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1838             !access_file(fd->path, R_OK))
1839                 {
1840                 ret |= CHANGE_NO_READ_PERM;
1841                 DEBUG_1("Change checked: no read permission: %s", fd->path);
1842                 }
1843         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1844                  !access_file(dir, W_OK))
1845                 {
1846                 ret |= CHANGE_NO_WRITE_PERM_DIR;
1847                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1848                 }
1849         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1850                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1851                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1852                  !access_file(fd->path, W_OK))
1853                 {
1854                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1855                 DEBUG_1("Change checked: no write permission: %s", fd->path);
1856                 }
1857         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1858            - that means that there are no hard errors and warnings can be disabled
1859            - the destination is determined during the check
1860         */
1861         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1862                 {
1863                 /* determine destination file */
1864                 gboolean have_dest = FALSE;
1865                 gchar *dest_dir = NULL;
1866                 
1867                 if (options->metadata.save_in_image_file)
1868                         {
1869                         if (file_data_can_write_directly(fd)) 
1870                                 {
1871                                 /* we can write the file directly */
1872                                 if (access_file(fd->path, W_OK))
1873                                         {
1874                                         have_dest = TRUE;
1875                                         }
1876                                 else
1877                                         {
1878                                         if (options->metadata.warn_on_write_problems)
1879                                                 {
1880                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1881                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1882                                                 }
1883                                         }
1884                                 }
1885                         else if (file_data_can_write_sidecar(fd)) 
1886                                 {
1887                                 /* we can write sidecar */
1888                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1889                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1890                                         {
1891                                         file_data_update_ci_dest(fd, sidecar);
1892                                         have_dest = TRUE;
1893                                         }
1894                                 else
1895                                         {
1896                                         if (options->metadata.warn_on_write_problems)
1897                                                 {
1898                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1899                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
1900                                                 }
1901                                         }
1902                                 g_free(sidecar);
1903                                 }
1904                         }
1905                 
1906                 if (!have_dest)
1907                         {
1908                         /* write private metadata file under ~/.geeqie */
1909
1910                         /* If an existing metadata file exists, we will try writing to
1911                          * it's location regardless of the user's preference.
1912                          */
1913                         gchar *metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
1914                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
1915                         
1916                         if (metadata_path && !access_file(metadata_path, W_OK))
1917                                 {
1918                                 g_free(metadata_path);
1919                                 metadata_path = NULL;
1920                                 }
1921
1922                         if (!metadata_path)
1923                                 {
1924                                 mode_t mode = 0755;
1925
1926                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
1927                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
1928                                         {
1929                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
1930                         
1931                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
1932                                         g_free(filename);
1933                                         }
1934                                 }
1935                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
1936                                 {
1937                                 file_data_update_ci_dest(fd, metadata_path);
1938                                 have_dest = TRUE;
1939                                 }
1940                         else
1941                                 {
1942                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
1943                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
1944                                 }
1945                         g_free(metadata_path);
1946                         }
1947                 g_free(dest_dir);
1948                 }
1949                 
1950         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
1951                 {
1952                 gboolean same;
1953                 gchar *dest_dir;
1954                         
1955                 same = (strcmp(fd->path, fd->change->dest) == 0);
1956
1957                 if (!same)
1958                         {
1959                         const gchar *dest_ext = extension_from_path(fd->change->dest);
1960                         if (!dest_ext) dest_ext = "";
1961
1962                         if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
1963                                 {
1964                                 ret |= CHANGE_WARN_CHANGED_EXT;
1965                                 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1966                                 }
1967                         }
1968                 else
1969                         {
1970                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
1971                                 {
1972                                 ret |= CHANGE_WARN_SAME;
1973                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
1974                                 }
1975                         }
1976
1977                 dest_dir = remove_level_from_path(fd->change->dest);
1978
1979                 if (!isdir(dest_dir))
1980                         {
1981                         ret |= CHANGE_NO_DEST_DIR;
1982                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1983                         }
1984                 else if (!access_file(dest_dir, W_OK))
1985                         {
1986                         ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1987                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1988                         }
1989                 else if (!same)
1990                         {
1991                         if (isfile(fd->change->dest))
1992                                 {
1993                                 if (!access_file(fd->change->dest, W_OK))
1994                                         {
1995                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
1996                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1997                                         }
1998                                 else
1999                                         {
2000                                         ret |= CHANGE_WARN_DEST_EXISTS;
2001                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2002                                         }
2003                                 }
2004                         else if (isdir(fd->change->dest))
2005                                 {
2006                                 ret |= CHANGE_DEST_EXISTS;
2007                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2008                                 }
2009                         }
2010
2011                 g_free(dest_dir);
2012                 }
2013                 
2014         fd->change->error = ret;
2015         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2016
2017         g_free(dir);
2018         return ret;
2019 }
2020
2021
2022 gint file_data_sc_verify_ci(FileData *fd)
2023 {
2024         GList *work;
2025         gint ret;
2026
2027         ret = file_data_verify_ci(fd);
2028
2029         work = fd->sidecar_files;
2030         while (work)
2031                 {
2032                 FileData *sfd = work->data;
2033
2034                 ret |= file_data_verify_ci(sfd);
2035                 work = work->next;
2036                 }
2037
2038         return ret;
2039 }
2040
2041 gchar *file_data_get_error_string(gint error)
2042 {
2043         GString *result = g_string_new("");
2044
2045         if (error & CHANGE_NO_SRC)
2046                 {
2047                 if (result->len > 0) g_string_append(result, ", ");
2048                 g_string_append(result, _("file or directory does not exist"));
2049                 }
2050
2051         if (error & CHANGE_DEST_EXISTS)
2052                 {
2053                 if (result->len > 0) g_string_append(result, ", ");
2054                 g_string_append(result, _("destination already exists"));
2055                 }
2056
2057         if (error & CHANGE_NO_WRITE_PERM_DEST)
2058                 {
2059                 if (result->len > 0) g_string_append(result, ", ");
2060                 g_string_append(result, _("destination can't be overwritten"));
2061                 }
2062
2063         if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2064                 {
2065                 if (result->len > 0) g_string_append(result, ", ");
2066                 g_string_append(result, _("destination directory is not writable"));
2067                 }
2068
2069         if (error & CHANGE_NO_DEST_DIR)
2070                 {
2071                 if (result->len > 0) g_string_append(result, ", ");
2072                 g_string_append(result, _("destination directory does not exist"));
2073                 }
2074
2075         if (error & CHANGE_NO_WRITE_PERM_DIR)
2076                 {
2077                 if (result->len > 0) g_string_append(result, ", ");
2078                 g_string_append(result, _("source directory is not writable"));
2079                 }
2080
2081         if (error & CHANGE_NO_READ_PERM)
2082                 {
2083                 if (result->len > 0) g_string_append(result, ", ");
2084                 g_string_append(result, _("no read permission"));
2085                 }
2086
2087         if (error & CHANGE_WARN_NO_WRITE_PERM)
2088                 {
2089                 if (result->len > 0) g_string_append(result, ", ");
2090                 g_string_append(result, _("file is readonly"));
2091                 }
2092
2093         if (error & CHANGE_WARN_DEST_EXISTS)
2094                 {
2095                 if (result->len > 0) g_string_append(result, ", ");
2096                 g_string_append(result, _("destination already exists and will be overwritten"));
2097                 }
2098                 
2099         if (error & CHANGE_WARN_SAME)
2100                 {
2101                 if (result->len > 0) g_string_append(result, ", ");
2102                 g_string_append(result, _("source and destination are the same"));
2103                 }
2104
2105         if (error & CHANGE_WARN_CHANGED_EXT)
2106                 {
2107                 if (result->len > 0) g_string_append(result, ", ");
2108                 g_string_append(result, _("source and destination have different extension"));
2109                 }
2110
2111         return g_string_free(result, FALSE);
2112 }
2113
2114 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2115 {
2116         GList *work;
2117         gint all_errors = 0;
2118         gint common_errors = ~0;
2119         gint num;
2120         gint *errors;
2121         gint i;
2122         
2123         if (!list) return 0;
2124         
2125         num = g_list_length(list);
2126         errors = g_new(int, num);
2127         work = list;
2128         i = 0;
2129         while (work)
2130                 {
2131                 FileData *fd;
2132                 gint error;
2133
2134                 fd = work->data;
2135                 work = work->next;
2136                         
2137                 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2138                 all_errors |= error;
2139                 common_errors &= error;
2140                 
2141                 errors[i] = error;
2142                 
2143                 i++;
2144                 }
2145         
2146         if (desc && all_errors)
2147                 {
2148                 GList *work;
2149                 GString *result = g_string_new("");
2150                 
2151                 if (common_errors)
2152                         {
2153                         gchar *str = file_data_get_error_string(common_errors);
2154                         g_string_append(result, str);
2155                         g_string_append(result, "\n");
2156                         g_free(str);
2157                         }
2158                 
2159                 work = list;
2160                 i = 0;
2161                 while (work)
2162                         {
2163                         FileData *fd;
2164                         gint error;
2165
2166                         fd = work->data;
2167                         work = work->next;
2168                         
2169                         error = errors[i] & ~common_errors;
2170                         
2171                         if (error)
2172                                 {
2173                                 gchar *str = file_data_get_error_string(error);
2174                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2175                                 g_free(str);
2176                                 }
2177                         i++;
2178                         }
2179                 *desc = g_string_free(result, FALSE);
2180                 }
2181
2182         g_free(errors);
2183         return all_errors;
2184 }
2185
2186
2187 /*
2188  * perform the change described by FileFataChangeInfo
2189  * it is used for internal operations,
2190  * this function actually operates with files on the filesystem
2191  * it should implement safe delete
2192  */
2193
2194 static gboolean file_data_perform_move(FileData *fd)
2195 {
2196         g_assert(!strcmp(fd->change->source, fd->path));
2197         return move_file(fd->change->source, fd->change->dest);
2198 }
2199
2200 static gboolean file_data_perform_copy(FileData *fd)
2201 {
2202         g_assert(!strcmp(fd->change->source, fd->path));
2203         return copy_file(fd->change->source, fd->change->dest);
2204 }
2205
2206 static gboolean file_data_perform_delete(FileData *fd)
2207 {
2208         if (isdir(fd->path) && !islink(fd->path))
2209                 return rmdir_utf8(fd->path);
2210         else
2211                 if (options->file_ops.safe_delete_enable)
2212                         return file_util_safe_unlink(fd->path);
2213                 else
2214                         return unlink_file(fd->path);
2215 }
2216
2217 gboolean file_data_perform_ci(FileData *fd)
2218 {
2219         FileDataChangeType type = fd->change->type;
2220
2221         switch (type)
2222                 {
2223                 case FILEDATA_CHANGE_MOVE:
2224                         return file_data_perform_move(fd);
2225                 case FILEDATA_CHANGE_COPY:
2226                         return file_data_perform_copy(fd);
2227                 case FILEDATA_CHANGE_RENAME:
2228                         return file_data_perform_move(fd); /* the same as move */
2229                 case FILEDATA_CHANGE_DELETE:
2230                         return file_data_perform_delete(fd);
2231                 case FILEDATA_CHANGE_WRITE_METADATA:
2232                         return metadata_write_perform(fd);
2233                 case FILEDATA_CHANGE_UNSPECIFIED:
2234                         /* nothing to do here */
2235                         break;
2236                 }
2237         return TRUE;
2238 }
2239
2240
2241
2242 gboolean file_data_sc_perform_ci(FileData *fd)
2243 {
2244         GList *work;
2245         gboolean ret = TRUE;
2246         FileDataChangeType type = fd->change->type;
2247         
2248         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2249
2250         work = fd->sidecar_files;
2251         while (work)
2252                 {
2253                 FileData *sfd = work->data;
2254                 
2255                 if (!file_data_perform_ci(sfd)) ret = FALSE;
2256                 work = work->next;
2257                 }
2258         
2259         if (!file_data_perform_ci(fd)) ret = FALSE;
2260         
2261         return ret;
2262 }
2263
2264 /*
2265  * updates FileData structure according to FileDataChangeInfo
2266  */
2267
2268 gboolean file_data_apply_ci(FileData *fd)
2269 {
2270         FileDataChangeType type = fd->change->type;
2271
2272         /* FIXME delete ?*/
2273         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2274                 {
2275                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2276                 file_data_planned_change_remove(fd);
2277                 
2278                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2279                         {
2280                         /* this change overwrites another file which is already known to other modules
2281                            renaming fd would create duplicate FileData structure
2282                            the best thing we can do is nothing
2283                            FIXME: maybe we could copy stuff like marks
2284                         */
2285                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2286                         }
2287                 else
2288                         {
2289                         file_data_set_path(fd, fd->change->dest);
2290                         }
2291                 }
2292         file_data_increment_version(fd);
2293         file_data_send_notification(fd, NOTIFY_CHANGE);
2294         
2295         return TRUE;
2296 }
2297
2298 gboolean file_data_sc_apply_ci(FileData *fd)
2299 {
2300         GList *work;
2301         FileDataChangeType type = fd->change->type;
2302         
2303         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2304
2305         work = fd->sidecar_files;
2306         while (work)
2307                 {
2308                 FileData *sfd = work->data;
2309                 
2310                 file_data_apply_ci(sfd);
2311                 work = work->next;
2312                 }
2313         
2314         file_data_apply_ci(fd);
2315         
2316         return TRUE;
2317 }
2318
2319 /*
2320  * notify other modules about the change described by FileFataChangeInfo
2321  */
2322
2323 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2324    FIXME do we need the ignore_list? It looks like a workaround for ineffective
2325    implementation in view_file_list.c */
2326
2327
2328
2329
2330 typedef struct _NotifyData NotifyData;
2331
2332 struct _NotifyData {
2333         FileDataNotifyFunc func;
2334         gpointer data;
2335         NotifyPriority priority;
2336 };
2337
2338 static GList *notify_func_list = NULL;
2339
2340 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2341 {
2342         NotifyData *nda = (NotifyData *)a;
2343         NotifyData *ndb = (NotifyData *)b;
2344
2345         if (nda->priority < ndb->priority) return -1;
2346         if (nda->priority > ndb->priority) return 1;
2347         return 0;
2348 }
2349
2350 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2351 {
2352         NotifyData *nd;
2353         
2354         nd = g_new(NotifyData, 1);
2355         nd->func = func;
2356         nd->data = data;
2357         nd->priority = priority;
2358
2359         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2360         DEBUG_2("Notify func registered: %p", nd);
2361         
2362         return TRUE;
2363 }
2364
2365 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2366 {
2367         GList *work = notify_func_list;
2368         
2369         while (work)
2370                 {
2371                 NotifyData *nd = (NotifyData *)work->data;
2372         
2373                 if (nd->func == func && nd->data == data)
2374                         {
2375                         notify_func_list = g_list_delete_link(notify_func_list, work);
2376                         g_free(nd);
2377                         DEBUG_2("Notify func unregistered: %p", nd);
2378                         return TRUE;
2379                         }
2380                 work = work->next;
2381                 }
2382
2383         return FALSE;
2384 }
2385
2386
2387 void file_data_send_notification(FileData *fd, NotifyType type)
2388 {
2389         GList *work = notify_func_list;
2390
2391         while (work)
2392                 {
2393                 NotifyData *nd = (NotifyData *)work->data;
2394                 
2395                 nd->func(fd, type, nd->data);
2396                 work = work->next;
2397                 }
2398 }
2399
2400 static GHashTable *file_data_monitor_pool = NULL;
2401 static gint realtime_monitor_id = -1;
2402
2403 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2404 {
2405         FileData *fd = key;
2406
2407         file_data_check_changed_files(fd);
2408         
2409         DEBUG_1("monitor %s", fd->path);
2410 }
2411
2412 static gboolean realtime_monitor_cb(gpointer data)
2413 {
2414         if (!options->update_on_time_change) return TRUE;
2415         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2416         return TRUE;
2417 }
2418
2419 gboolean file_data_register_real_time_monitor(FileData *fd)
2420 {
2421         gint count;
2422         
2423         file_data_ref(fd);
2424         
2425         if (!file_data_monitor_pool)
2426                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2427         
2428         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2429
2430         DEBUG_1("Register realtime %d %s", count, fd->path);
2431         
2432         count++;
2433         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2434         
2435         if (realtime_monitor_id == -1)
2436                 {
2437                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2438                 }
2439         
2440         return TRUE;
2441 }
2442
2443 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2444 {
2445         gint count;
2446
2447         g_assert(file_data_monitor_pool);
2448         
2449         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2450         
2451         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2452         
2453         g_assert(count > 0);
2454         
2455         count--;
2456         
2457         if (count == 0)
2458                 g_hash_table_remove(file_data_monitor_pool, fd);
2459         else
2460                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2461
2462         file_data_unref(fd);
2463         
2464         if (g_hash_table_size(file_data_monitor_pool) == 0)
2465                 {
2466                 g_source_remove(realtime_monitor_id);
2467                 realtime_monitor_id = -1;
2468                 return FALSE;
2469                 }
2470         
2471         return TRUE;
2472 }
2473 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */