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