Patch to fix duplicate window update
[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                         if (!options->file_filter.disable_file_extension_checks)
2350                                 {
2351                                 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2352                                         {
2353                                         ret |= CHANGE_WARN_CHANGED_EXT;
2354                                         DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2355                                         }
2356                                 }
2357                         }
2358                 else
2359                         {
2360                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2361                                 {
2362                                 ret |= CHANGE_WARN_SAME;
2363                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2364                                 }
2365                         }
2366
2367                 dest_dir = remove_level_from_path(fd->change->dest);
2368
2369                 if (!isdir(dest_dir))
2370                         {
2371                         ret |= CHANGE_NO_DEST_DIR;
2372                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2373                         }
2374                 else if (!access_file(dest_dir, W_OK))
2375                         {
2376                         ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2377                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2378                         }
2379                 else if (!same)
2380                         {
2381                         if (isfile(fd->change->dest))
2382                                 {
2383                                 if (!access_file(fd->change->dest, W_OK))
2384                                         {
2385                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
2386                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2387                                         }
2388                                 else
2389                                         {
2390                                         ret |= CHANGE_WARN_DEST_EXISTS;
2391                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2392                                         }
2393                                 }
2394                         else if (isdir(fd->change->dest))
2395                                 {
2396                                 ret |= CHANGE_DEST_EXISTS;
2397                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2398                                 }
2399                         }
2400
2401                 g_free(dest_dir);
2402                 }
2403
2404         /* During a rename operation, check if another planned destination file has
2405          * the same filename
2406          */
2407         if(fd->change->type == FILEDATA_CHANGE_RENAME)
2408                 {
2409                 work = list;
2410                 while (work)
2411                         {
2412                         fd1 = work->data;
2413                         work = work->next;
2414                         if (fd1 != NULL && fd != fd1 )
2415                                 {
2416                                 if (!strcmp(fd->change->dest, fd1->change->dest))
2417                                         {
2418                                         ret |= CHANGE_DUPLICATE_DEST;
2419                                         }
2420                                 }
2421                         }
2422                 }
2423
2424         fd->change->error = ret;
2425         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2426
2427         g_free(dir);
2428         return ret;
2429 }
2430
2431
2432 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2433 {
2434         GList *work;
2435         gint ret;
2436
2437         ret = file_data_verify_ci(fd, list);
2438
2439         work = fd->sidecar_files;
2440         while (work)
2441                 {
2442                 FileData *sfd = work->data;
2443
2444                 ret |= file_data_verify_ci(sfd, list);
2445                 work = work->next;
2446                 }
2447
2448         return ret;
2449 }
2450
2451 gchar *file_data_get_error_string(gint error)
2452 {
2453         GString *result = g_string_new("");
2454
2455         if (error & CHANGE_NO_SRC)
2456                 {
2457                 if (result->len > 0) g_string_append(result, ", ");
2458                 g_string_append(result, _("file or directory does not exist"));
2459                 }
2460
2461         if (error & CHANGE_DEST_EXISTS)
2462                 {
2463                 if (result->len > 0) g_string_append(result, ", ");
2464                 g_string_append(result, _("destination already exists"));
2465                 }
2466
2467         if (error & CHANGE_NO_WRITE_PERM_DEST)
2468                 {
2469                 if (result->len > 0) g_string_append(result, ", ");
2470                 g_string_append(result, _("destination can't be overwritten"));
2471                 }
2472
2473         if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2474                 {
2475                 if (result->len > 0) g_string_append(result, ", ");
2476                 g_string_append(result, _("destination directory is not writable"));
2477                 }
2478
2479         if (error & CHANGE_NO_DEST_DIR)
2480                 {
2481                 if (result->len > 0) g_string_append(result, ", ");
2482                 g_string_append(result, _("destination directory does not exist"));
2483                 }
2484
2485         if (error & CHANGE_NO_WRITE_PERM_DIR)
2486                 {
2487                 if (result->len > 0) g_string_append(result, ", ");
2488                 g_string_append(result, _("source directory is not writable"));
2489                 }
2490
2491         if (error & CHANGE_NO_READ_PERM)
2492                 {
2493                 if (result->len > 0) g_string_append(result, ", ");
2494                 g_string_append(result, _("no read permission"));
2495                 }
2496
2497         if (error & CHANGE_WARN_NO_WRITE_PERM)
2498                 {
2499                 if (result->len > 0) g_string_append(result, ", ");
2500                 g_string_append(result, _("file is readonly"));
2501                 }
2502
2503         if (error & CHANGE_WARN_DEST_EXISTS)
2504                 {
2505                 if (result->len > 0) g_string_append(result, ", ");
2506                 g_string_append(result, _("destination already exists and will be overwritten"));
2507                 }
2508
2509         if (error & CHANGE_WARN_SAME)
2510                 {
2511                 if (result->len > 0) g_string_append(result, ", ");
2512                 g_string_append(result, _("source and destination are the same"));
2513                 }
2514
2515         if (error & CHANGE_WARN_CHANGED_EXT)
2516                 {
2517                 if (result->len > 0) g_string_append(result, ", ");
2518                 g_string_append(result, _("source and destination have different extension"));
2519                 }
2520
2521         if (error & CHANGE_WARN_UNSAVED_META)
2522                 {
2523                 if (result->len > 0) g_string_append(result, ", ");
2524                 g_string_append(result, _("there are unsaved metadata changes for the file"));
2525                 }
2526
2527         if (error & CHANGE_DUPLICATE_DEST)
2528                 {
2529                 if (result->len > 0) g_string_append(result, ", ");
2530                 g_string_append(result, _("another destination file has the same filename"));
2531                 }
2532
2533         return g_string_free(result, FALSE);
2534 }
2535
2536 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2537 {
2538         GList *work;
2539         gint all_errors = 0;
2540         gint common_errors = ~0;
2541         gint num;
2542         gint *errors;
2543         gint i;
2544
2545         if (!list) return 0;
2546
2547         num = g_list_length(list);
2548         errors = g_new(int, num);
2549         work = list;
2550         i = 0;
2551         while (work)
2552                 {
2553                 FileData *fd;
2554                 gint error;
2555
2556                 fd = work->data;
2557                 work = work->next;
2558
2559                 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2560                 all_errors |= error;
2561                 common_errors &= error;
2562
2563                 errors[i] = error;
2564
2565                 i++;
2566                 }
2567
2568         if (desc && all_errors)
2569                 {
2570                 GList *work;
2571                 GString *result = g_string_new("");
2572
2573                 if (common_errors)
2574                         {
2575                         gchar *str = file_data_get_error_string(common_errors);
2576                         g_string_append(result, str);
2577                         g_string_append(result, "\n");
2578                         g_free(str);
2579                         }
2580
2581                 work = list;
2582                 i = 0;
2583                 while (work)
2584                         {
2585                         FileData *fd;
2586                         gint error;
2587
2588                         fd = work->data;
2589                         work = work->next;
2590
2591                         error = errors[i] & ~common_errors;
2592
2593                         if (error)
2594                                 {
2595                                 gchar *str = file_data_get_error_string(error);
2596                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2597                                 g_free(str);
2598                                 }
2599                         i++;
2600                         }
2601                 *desc = g_string_free(result, FALSE);
2602                 }
2603
2604         g_free(errors);
2605         return all_errors;
2606 }
2607
2608
2609 /*
2610  * perform the change described by FileFataChangeInfo
2611  * it is used for internal operations,
2612  * this function actually operates with files on the filesystem
2613  * it should implement safe delete
2614  */
2615
2616 static gboolean file_data_perform_move(FileData *fd)
2617 {
2618         g_assert(!strcmp(fd->change->source, fd->path));
2619         return move_file(fd->change->source, fd->change->dest);
2620 }
2621
2622 static gboolean file_data_perform_copy(FileData *fd)
2623 {
2624         g_assert(!strcmp(fd->change->source, fd->path));
2625         return copy_file(fd->change->source, fd->change->dest);
2626 }
2627
2628 static gboolean file_data_perform_delete(FileData *fd)
2629 {
2630         if (isdir(fd->path) && !islink(fd->path))
2631                 return rmdir_utf8(fd->path);
2632         else
2633                 if (options->file_ops.safe_delete_enable)
2634                         return file_util_safe_unlink(fd->path);
2635                 else
2636                         return unlink_file(fd->path);
2637 }
2638
2639 gboolean file_data_perform_ci(FileData *fd)
2640 {
2641         FileDataChangeType type = fd->change->type;
2642
2643         switch (type)
2644                 {
2645                 case FILEDATA_CHANGE_MOVE:
2646                         return file_data_perform_move(fd);
2647                 case FILEDATA_CHANGE_COPY:
2648                         return file_data_perform_copy(fd);
2649                 case FILEDATA_CHANGE_RENAME:
2650                         return file_data_perform_move(fd); /* the same as move */
2651                 case FILEDATA_CHANGE_DELETE:
2652                         return file_data_perform_delete(fd);
2653                 case FILEDATA_CHANGE_WRITE_METADATA:
2654                         return metadata_write_perform(fd);
2655                 case FILEDATA_CHANGE_UNSPECIFIED:
2656                         /* nothing to do here */
2657                         break;
2658                 }
2659         return TRUE;
2660 }
2661
2662
2663
2664 gboolean file_data_sc_perform_ci(FileData *fd)
2665 {
2666         GList *work;
2667         gboolean ret = TRUE;
2668         FileDataChangeType type = fd->change->type;
2669
2670         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2671
2672         work = fd->sidecar_files;
2673         while (work)
2674                 {
2675                 FileData *sfd = work->data;
2676
2677                 if (!file_data_perform_ci(sfd)) ret = FALSE;
2678                 work = work->next;
2679                 }
2680
2681         if (!file_data_perform_ci(fd)) ret = FALSE;
2682
2683         return ret;
2684 }
2685
2686 /*
2687  * updates FileData structure according to FileDataChangeInfo
2688  */
2689
2690 gboolean file_data_apply_ci(FileData *fd)
2691 {
2692         FileDataChangeType type = fd->change->type;
2693
2694         /* FIXME delete ?*/
2695         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2696                 {
2697                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2698                 file_data_planned_change_remove(fd);
2699
2700                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2701                         {
2702                         /* this change overwrites another file which is already known to other modules
2703                            renaming fd would create duplicate FileData structure
2704                            the best thing we can do is nothing
2705                            FIXME: maybe we could copy stuff like marks
2706                         */
2707                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2708                         }
2709                 else
2710                         {
2711                         file_data_set_path(fd, fd->change->dest);
2712                         }
2713                 }
2714         file_data_increment_version(fd);
2715         file_data_send_notification(fd, NOTIFY_CHANGE);
2716
2717         return TRUE;
2718 }
2719
2720 gboolean file_data_sc_apply_ci(FileData *fd)
2721 {
2722         GList *work;
2723         FileDataChangeType type = fd->change->type;
2724
2725         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2726
2727         work = fd->sidecar_files;
2728         while (work)
2729                 {
2730                 FileData *sfd = work->data;
2731
2732                 file_data_apply_ci(sfd);
2733                 work = work->next;
2734                 }
2735
2736         file_data_apply_ci(fd);
2737
2738         return TRUE;
2739 }
2740
2741 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2742 {
2743         GList *work;
2744         if (fd->parent) fd = fd->parent;
2745         if (!g_list_find(list, fd)) return FALSE;
2746
2747         work = fd->sidecar_files;
2748         while (work)
2749                 {
2750                 if (!g_list_find(list, work->data)) return FALSE;
2751                 work = work->next;
2752                 }
2753         return TRUE;
2754 }
2755
2756 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2757 {
2758         GList *out = NULL;
2759         GList *work = list;
2760
2761         /* change partial groups to independent files */
2762         if (ungroup)
2763                 {
2764                 while (work)
2765                         {
2766                         FileData *fd = work->data;
2767                         work = work->next;
2768
2769                         if (!file_data_list_contains_whole_group(list, fd))
2770                                 {
2771                                 file_data_disable_grouping(fd, TRUE);
2772                                 if (ungrouped_list)
2773                                         {
2774                                         *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2775                                         }
2776                                 }
2777                         }
2778                 }
2779
2780         /* remove sidecars from the list,
2781            they can be still acessed via main_fd->sidecar_files */
2782         work = list;
2783         while (work)
2784                 {
2785                 FileData *fd = work->data;
2786                 work = work->next;
2787
2788                 if (!fd->parent ||
2789                     (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2790                         {
2791                         out = g_list_prepend(out, file_data_ref(fd));
2792                         }
2793                 }
2794
2795         filelist_free(list);
2796         out = g_list_reverse(out);
2797
2798         return out;
2799 }
2800
2801
2802
2803
2804
2805 /*
2806  * notify other modules about the change described by FileDataChangeInfo
2807  */
2808
2809 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2810    FIXME do we need the ignore_list? It looks like a workaround for ineffective
2811    implementation in view_file_list.c */
2812
2813
2814 typedef struct _NotifyIdleData NotifyIdleData;
2815
2816 struct _NotifyIdleData {
2817         FileData *fd;
2818         NotifyType type;
2819 };
2820
2821
2822 typedef struct _NotifyData NotifyData;
2823
2824 struct _NotifyData {
2825         FileDataNotifyFunc func;
2826         gpointer data;
2827         NotifyPriority priority;
2828 };
2829
2830 static GList *notify_func_list = NULL;
2831
2832 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2833 {
2834         NotifyData *nda = (NotifyData *)a;
2835         NotifyData *ndb = (NotifyData *)b;
2836
2837         if (nda->priority < ndb->priority) return -1;
2838         if (nda->priority > ndb->priority) return 1;
2839         return 0;
2840 }
2841
2842 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2843 {
2844         NotifyData *nd;
2845         GList *work = notify_func_list;
2846
2847         while (work)
2848                 {
2849                 NotifyData *nd = (NotifyData *)work->data;
2850
2851                 if (nd->func == func && nd->data == data)
2852                         {
2853                         g_warning("Notify func already registered");
2854                         return FALSE;
2855                         }
2856                 work = work->next;
2857                 }
2858
2859         nd = g_new(NotifyData, 1);
2860         nd->func = func;
2861         nd->data = data;
2862         nd->priority = priority;
2863
2864         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2865         DEBUG_2("Notify func registered: %p", nd);
2866
2867         return TRUE;
2868 }
2869
2870 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2871 {
2872         GList *work = notify_func_list;
2873
2874         while (work)
2875                 {
2876                 NotifyData *nd = (NotifyData *)work->data;
2877
2878                 if (nd->func == func && nd->data == data)
2879                         {
2880                         notify_func_list = g_list_delete_link(notify_func_list, work);
2881                         g_free(nd);
2882                         DEBUG_2("Notify func unregistered: %p", nd);
2883                         return TRUE;
2884                         }
2885                 work = work->next;
2886                 }
2887
2888         g_warning("Notify func not found");
2889         return FALSE;
2890 }
2891
2892
2893 gboolean file_data_send_notification_idle_cb(gpointer data)
2894 {
2895         NotifyIdleData *nid = (NotifyIdleData *)data;
2896         GList *work = notify_func_list;
2897
2898         while (work)
2899                 {
2900                 NotifyData *nd = (NotifyData *)work->data;
2901
2902                 nd->func(nid->fd, nid->type, nd->data);
2903                 work = work->next;
2904                 }
2905         file_data_unref(nid->fd);
2906         g_free(nid);
2907         return FALSE;
2908 }
2909
2910 void file_data_send_notification(FileData *fd, NotifyType type)
2911 {
2912         GList *work = notify_func_list;
2913
2914         while (work)
2915                 {
2916                 NotifyData *nd = (NotifyData *)work->data;
2917
2918                 nd->func(fd, type, nd->data);
2919                 work = work->next;
2920                 }
2921     /*
2922         NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2923         nid->fd = file_data_ref(fd);
2924         nid->type = type;
2925         g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2926     */
2927 }
2928
2929 static GHashTable *file_data_monitor_pool = NULL;
2930 static guint realtime_monitor_id = 0; /* event source id */
2931
2932 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2933 {
2934         FileData *fd = key;
2935
2936         file_data_check_changed_files(fd);
2937
2938         DEBUG_1("monitor %s", fd->path);
2939 }
2940
2941 static gboolean realtime_monitor_cb(gpointer data)
2942 {
2943         if (!options->update_on_time_change) return TRUE;
2944         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2945         return TRUE;
2946 }
2947
2948 gboolean file_data_register_real_time_monitor(FileData *fd)
2949 {
2950         gint count;
2951
2952         file_data_ref(fd);
2953
2954         if (!file_data_monitor_pool)
2955                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2956
2957         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2958
2959         DEBUG_1("Register realtime %d %s", count, fd->path);
2960
2961         count++;
2962         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2963
2964         if (!realtime_monitor_id)
2965                 {
2966                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2967                 }
2968
2969         return TRUE;
2970 }
2971
2972 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2973 {
2974         gint count;
2975
2976         g_assert(file_data_monitor_pool);
2977
2978         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2979
2980         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2981
2982         g_assert(count > 0);
2983
2984         count--;
2985
2986         if (count == 0)
2987                 g_hash_table_remove(file_data_monitor_pool, fd);
2988         else
2989                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2990
2991         file_data_unref(fd);
2992
2993         if (g_hash_table_size(file_data_monitor_pool) == 0)
2994                 {
2995                 g_source_remove(realtime_monitor_id);
2996                 realtime_monitor_id = 0;
2997                 return FALSE;
2998                 }
2999
3000         return TRUE;
3001 }
3002 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */