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