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