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