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