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