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