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