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