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