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