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