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