Additional commands for multi-page files
[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 K"), (gdouble)size / 1024.0);
117                 }
118         if (size < (gint64)1073741824)
119                 {
120                 return g_strdup_printf(_("%.1f MB"), (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 GB"), (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 GTK_CHECK_VERSION(2, 8, 0)
286         if (options->file_sort.natural)
287                 {
288                 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
289                 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
290                 }
291         else
292                 {
293                 fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
294                 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
295                 }
296 #else
297         fd->collate_key_name = g_utf8_collate_key(valid_name, -1);
298         fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
299 #endif
300
301         g_free(valid_name);
302         g_free(caseless_name);
303 }
304
305 static void file_data_set_path(FileData *fd, const gchar *path)
306 {
307         g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
308         g_assert(file_data_pool);
309
310         g_free(fd->path);
311
312         if (fd->original_path)
313                 {
314                 g_hash_table_remove(file_data_pool, fd->original_path);
315                 g_free(fd->original_path);
316                 }
317
318         g_assert(!g_hash_table_lookup(file_data_pool, path));
319
320         fd->original_path = g_strdup(path);
321         g_hash_table_insert(file_data_pool, fd->original_path, fd);
322
323         if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
324                 {
325                 fd->path = g_strdup(path);
326                 fd->name = fd->path;
327                 fd->extension = fd->name + 1;
328                 file_data_set_collate_keys(fd);
329                 return;
330                 }
331
332         fd->path = g_strdup(path);
333         fd->name = filename_from_path(fd->path);
334
335         if (strcmp(fd->name, "..") == 0)
336                 {
337                 gchar *dir = remove_level_from_path(path);
338                 g_free(fd->path);
339                 fd->path = remove_level_from_path(dir);
340                 g_free(dir);
341                 fd->name = "..";
342                 fd->extension = fd->name + 2;
343                 file_data_set_collate_keys(fd);
344                 return;
345                 }
346         else if (strcmp(fd->name, ".") == 0)
347                 {
348                 g_free(fd->path);
349                 fd->path = remove_level_from_path(path);
350                 fd->name = ".";
351                 fd->extension = fd->name + 1;
352                 file_data_set_collate_keys(fd);
353                 return;
354                 }
355
356         fd->extension = registered_extension_from_path(fd->path);
357         if (fd->extension == NULL)
358                 {
359                 fd->extension = fd->name + strlen(fd->name);
360                 }
361
362         fd->sidecar_priority = sidecar_file_priority(fd->extension);
363         file_data_set_collate_keys(fd);
364 }
365
366 /*
367  *-----------------------------------------------------------------------------
368  * create or reuse Filedata
369  *-----------------------------------------------------------------------------
370  */
371
372 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
373 {
374         FileData *fd;
375         struct passwd *user;
376         struct group *group;
377
378         DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
379
380         if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
381
382         if (!file_data_pool)
383                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
384
385         fd = g_hash_table_lookup(file_data_pool, path_utf8);
386         if (fd)
387                 {
388                 file_data_ref(fd);
389                 }
390
391         if (!fd && file_data_planned_change_hash)
392                 {
393                 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
394                 if (fd)
395                         {
396                         DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
397                         if (!isfile(fd->path))
398                                 {
399                                 file_data_ref(fd);
400                                 file_data_apply_ci(fd);
401                                 }
402                         else
403                                 {
404                                 fd = NULL;
405                                 }
406                         }
407                 }
408
409         if (fd)
410                 {
411                 gboolean changed;
412
413                 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
414
415
416                 changed = file_data_check_changed_single_file(fd, st);
417
418                 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
419
420                 return fd;
421                 }
422
423         fd = g_new0(FileData, 1);
424 #ifdef DEBUG_FILEDATA
425         global_file_data_count++;
426         DEBUG_2("file data count++: %d", global_file_data_count);
427 #endif
428
429         fd->size = st->st_size;
430         fd->date = st->st_mtime;
431         fd->cdate = st->st_ctime;
432         fd->mode = st->st_mode;
433         fd->ref = 1;
434         fd->magick = FD_MAGICK;
435         fd->exifdate = 0;
436         fd->rating = STAR_RATING_NOT_READ;
437         fd->format_class = filter_file_get_class(path_utf8);
438         fd->page_num = 0;
439         fd->page_total = 0;
440
441         user = getpwuid(st->st_uid);
442         if (!user)
443                 {
444                 fd->owner = g_strdup_printf("%u", st->st_uid);
445                 }
446         else
447                 {
448                 fd->owner = g_strdup(user->pw_name);
449                 }
450
451         group = getgrgid(st->st_gid);
452         if (!group)
453                 {
454                 fd->group = g_strdup_printf("%u", st->st_gid);
455                 }
456         else
457                 {
458                 fd->group = g_strdup(group->gr_name);
459                 }
460
461         fd->sym_link = get_symbolic_link(path_utf8);
462
463         if (disable_sidecars) fd->disable_grouping = TRUE;
464
465         file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
466
467         return fd;
468 }
469
470 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
471 {
472         gchar *path_utf8 = path_to_utf8(path);
473         FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
474
475         g_free(path_utf8);
476         return ret;
477 }
478
479 FileData *file_data_new_simple(const gchar *path_utf8)
480 {
481         struct stat st;
482         FileData *fd;
483
484         if (!stat_utf8(path_utf8, &st))
485                 {
486                 st.st_size = 0;
487                 st.st_mtime = 0;
488                 }
489
490         fd = g_hash_table_lookup(file_data_pool, path_utf8);
491         if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
492         if (fd)
493                 {
494                 file_data_ref(fd);
495                 }
496
497         return fd;
498 }
499
500 void read_exif_time_data(FileData *file)
501 {
502         if (file->exifdate > 0)
503                 {
504                 DEBUG_1("%s set_exif_time_data: Already exists for %s", get_exec_time(), file->path);
505                 return;
506                 }
507
508         if (!file->exif)
509                 {
510                 exif_read_fd(file);
511                 }
512
513         if (file->exif)
514                 {
515                 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeOriginal");
516                 DEBUG_2("%s set_exif_time_data: reading %p %s", get_exec_time(), file, file->path);
517
518                 if (tmp)
519                         {
520                         struct tm time_str;
521                         uint year, month, day, hour, min, sec;
522
523                         sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
524                         time_str.tm_year  = year - 1900;
525                         time_str.tm_mon   = month - 1;
526                         time_str.tm_mday  = day;
527                         time_str.tm_hour  = hour;
528                         time_str.tm_min   = min;
529                         time_str.tm_sec   = sec;
530                         time_str.tm_isdst = 0;
531
532                         file->exifdate = mktime(&time_str);
533                         g_free(tmp);
534                         }
535                 }
536 }
537
538 void read_exif_time_digitized_data(FileData *file)
539 {
540         if (file->exifdate_digitized > 0)
541                 {
542                 DEBUG_1("%s set_exif_time_digitized_data: Already exists for %s", get_exec_time(), file->path);
543                 return;
544                 }
545
546         if (!file->exif)
547                 {
548                 exif_read_fd(file);
549                 }
550
551         if (file->exif)
552                 {
553                 gchar *tmp = exif_get_data_as_text(file->exif, "Exif.Photo.DateTimeDigitized");
554                 DEBUG_2("%s set_exif_time_digitized_data: reading %p %s", get_exec_time(), file, file->path);
555
556                 if (tmp)
557                         {
558                         struct tm time_str;
559                         uint year, month, day, hour, min, sec;
560
561                         sscanf(tmp, "%4d:%2d:%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
562                         time_str.tm_year  = year - 1900;
563                         time_str.tm_mon   = month - 1;
564                         time_str.tm_mday  = day;
565                         time_str.tm_hour  = hour;
566                         time_str.tm_min   = min;
567                         time_str.tm_sec   = sec;
568                         time_str.tm_isdst = 0;
569
570                         file->exifdate_digitized = mktime(&time_str);
571                         g_free(tmp);
572                         }
573                 }
574 }
575
576 void read_rating_data(FileData *file)
577 {
578         gchar *rating_str;
579
580         rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
581         if (rating_str)
582                 {
583                 file->rating = atoi(rating_str);
584                 g_free(rating_str);
585                 }
586 }
587
588 void set_exif_time_data(GList *files)
589 {
590         DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
591
592         while (files)
593                 {
594                 FileData *file = files->data;
595
596                 read_exif_time_data(file);
597                 files = files->next;
598                 }
599 }
600
601 void set_exif_time_digitized_data(GList *files)
602 {
603         DEBUG_1("%s set_exif_time_digitized_data: ...", get_exec_time());
604
605         while (files)
606                 {
607                 FileData *file = files->data;
608
609                 read_exif_time_digitized_data(file);
610                 files = files->next;
611                 }
612 }
613
614 void set_rating_data(GList *files)
615 {
616         gchar *rating_str;
617         DEBUG_1("%s set_rating_data: ...", get_exec_time());
618
619         while (files)
620                 {
621                 FileData *file = files->data;
622                 rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
623                 if (rating_str )
624                         {
625                         file->rating = atoi(rating_str);
626                         g_free(rating_str);
627                         }
628                 files = files->next;
629                 }
630 }
631
632 FileData *file_data_new_no_grouping(const gchar *path_utf8)
633 {
634         struct stat st;
635
636         if (!stat_utf8(path_utf8, &st))
637                 {
638                 st.st_size = 0;
639                 st.st_mtime = 0;
640                 }
641
642         return file_data_new(path_utf8, &st, TRUE);
643 }
644
645 FileData *file_data_new_dir(const gchar *path_utf8)
646 {
647         struct stat st;
648
649         if (!stat_utf8(path_utf8, &st))
650                 {
651                 st.st_size = 0;
652                 st.st_mtime = 0;
653                 }
654         else
655                 /* dir or non-existing yet */
656                 g_assert(S_ISDIR(st.st_mode));
657
658         return file_data_new(path_utf8, &st, TRUE);
659 }
660
661 /*
662  *-----------------------------------------------------------------------------
663  * reference counting
664  *-----------------------------------------------------------------------------
665  */
666
667 #ifdef DEBUG_FILEDATA
668 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
669 #else
670 FileData *file_data_ref(FileData *fd)
671 #endif
672 {
673         if (fd == NULL) return NULL;
674         if (fd->magick != FD_MAGICK)
675 #ifdef DEBUG_FILEDATA
676                 log_printf("Error: fd magick mismatch @ %s:%d  fd=%p", file, line, fd);
677 #else
678                 log_printf("Error: fd magick mismatch fd=%p", fd);
679 #endif
680         g_assert(fd->magick == FD_MAGICK);
681         fd->ref++;
682
683 #ifdef DEBUG_FILEDATA
684         DEBUG_2("file_data_ref fd=%p (%d): '%s' @ %s:%d", fd, fd->ref, fd->path, file, line);
685 #else
686         DEBUG_2("file_data_ref fd=%p (%d): '%s'", fd, fd->ref, fd->path);
687 #endif
688         return fd;
689 }
690
691 static void file_data_free(FileData *fd)
692 {
693         g_assert(fd->magick == FD_MAGICK);
694         g_assert(fd->ref == 0);
695         g_assert(!fd->locked);
696
697 #ifdef DEBUG_FILEDATA
698         global_file_data_count--;
699         DEBUG_2("file data count--: %d", global_file_data_count);
700 #endif
701
702         metadata_cache_free(fd);
703         g_hash_table_remove(file_data_pool, fd->original_path);
704
705         g_free(fd->path);
706         g_free(fd->original_path);
707         g_free(fd->collate_key_name);
708         g_free(fd->collate_key_name_nocase);
709         g_free(fd->extended_extension);
710         if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
711         histmap_free(fd->histmap);
712         g_free(fd->owner);
713         g_free(fd->group);
714         g_free(fd->sym_link);
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, 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", 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", fd);
814
815         g_assert(fd->magick == FD_MAGICK);
816         fd->locked = TRUE;
817
818         DEBUG_2("file_data_ref fd=%p (%d): '%s'", 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", 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", fd, fd->name);
934                 if (fd->parent)
935                         {
936                         g_assert(fd->parent->magick == FD_MAGICK);
937                         DEBUG_2("                  parent: %p", 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", 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 key, gpointer value, gpointer 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 key, gpointer value, gpointer 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 void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1894 {
1895         FileData *fd = value;
1896         file_data_increment_version(fd);
1897         file_data_send_notification(fd, NOTIFY_MARKS);
1898 }
1899
1900 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1901 {
1902         if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1903
1904         if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1905
1906         file_data_get_mark_func[n] = get_mark_func;
1907         file_data_set_mark_func[n] = set_mark_func;
1908         file_data_mark_func_data[n] = data;
1909         file_data_destroy_mark_func[n] = notify;
1910
1911         if (get_mark_func && file_data_pool)
1912                 {
1913                 /* this effectively changes all known files */
1914                 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1915                 }
1916
1917         return TRUE;
1918 }
1919
1920 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1921 {
1922         if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1923         if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1924         if (data) *data = file_data_mark_func_data[n];
1925 }
1926
1927 gint file_data_get_user_orientation(FileData *fd)
1928 {
1929         return fd->user_orientation;
1930 }
1931
1932 void file_data_set_user_orientation(FileData *fd, gint value)
1933 {
1934         if (fd->user_orientation == value) return;
1935
1936         fd->user_orientation = value;
1937         file_data_increment_version(fd);
1938         file_data_send_notification(fd, NOTIFY_ORIENTATION);
1939 }
1940
1941
1942 /*
1943  * file_data    - operates on the given fd
1944  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1945  */
1946
1947
1948 /* return list of sidecar file extensions in a string */
1949 gchar *file_data_sc_list_to_string(FileData *fd)
1950 {
1951         GList *work;
1952         GString *result = g_string_new("");
1953
1954         work = fd->sidecar_files;
1955         while (work)
1956                 {
1957                 FileData *sfd = work->data;
1958
1959                 result = g_string_append(result, "+ ");
1960                 result = g_string_append(result, sfd->extension);
1961                 work = work->next;
1962                 if (work) result = g_string_append_c(result, ' ');
1963                 }
1964
1965         return g_string_free(result, FALSE);
1966 }
1967
1968
1969
1970 /*
1971  * add FileDataChangeInfo (see typedefs.h) for the given operation
1972  * uses file_data_add_change_info
1973  *
1974  * fails if the fd->change already exists - change operations can't run in parallel
1975  * fd->change_info works as a lock
1976  *
1977  * dest can be NULL - in this case the current name is used for now, it will
1978  * be changed later
1979  */
1980
1981 /*
1982    FileDataChangeInfo types:
1983    COPY
1984    MOVE   - path is changed, name may be changed too
1985    RENAME - path remains unchanged, name is changed
1986             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1987             sidecar names are changed too, extensions are not changed
1988    DELETE
1989    UPDATE - file size, date or grouping has been changed
1990 */
1991
1992 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1993 {
1994         FileDataChangeInfo *fdci;
1995
1996         if (fd->change) return FALSE;
1997
1998         fdci = g_new0(FileDataChangeInfo, 1);
1999
2000         fdci->type = type;
2001
2002         if (src)
2003                 fdci->source = g_strdup(src);
2004         else
2005                 fdci->source = g_strdup(fd->path);
2006
2007         if (dest)
2008                 fdci->dest = g_strdup(dest);
2009
2010         fd->change = fdci;
2011
2012         return TRUE;
2013 }
2014
2015 static void file_data_planned_change_remove(FileData *fd)
2016 {
2017         if (file_data_planned_change_hash &&
2018             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2019                 {
2020                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2021                         {
2022                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2023                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2024                         file_data_unref(fd);
2025                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
2026                                 {
2027                                 g_hash_table_destroy(file_data_planned_change_hash);
2028                                 file_data_planned_change_hash = NULL;
2029                                 DEBUG_1("planned change: empty");
2030                                 }
2031                         }
2032                 }
2033 }
2034
2035
2036 void file_data_free_ci(FileData *fd)
2037 {
2038         FileDataChangeInfo *fdci = fd->change;
2039
2040         if (!fdci) return;
2041
2042         file_data_planned_change_remove(fd);
2043
2044         if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2045
2046         g_free(fdci->source);
2047         g_free(fdci->dest);
2048
2049         g_free(fdci);
2050
2051         fd->change = NULL;
2052 }
2053
2054 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2055 {
2056         FileDataChangeInfo *fdci = fd->change;
2057         if (!fdci) return;
2058         fdci->regroup_when_finished = enable;
2059 }
2060
2061 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2062 {
2063         GList *work;
2064
2065         if (fd->parent) fd = fd->parent;
2066
2067         if (fd->change) return FALSE;
2068
2069         work = fd->sidecar_files;
2070         while (work)
2071                 {
2072                 FileData *sfd = work->data;
2073
2074                 if (sfd->change) return FALSE;
2075                 work = work->next;
2076                 }
2077
2078         file_data_add_ci(fd, type, NULL, NULL);
2079
2080         work = fd->sidecar_files;
2081         while (work)
2082                 {
2083                 FileData *sfd = work->data;
2084
2085                 file_data_add_ci(sfd, type, NULL, NULL);
2086                 work = work->next;
2087                 }
2088
2089         return TRUE;
2090 }
2091
2092 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2093 {
2094         GList *work;
2095
2096         if (fd->parent) fd = fd->parent;
2097
2098         if (!fd->change || fd->change->type != type) return FALSE;
2099
2100         work = fd->sidecar_files;
2101         while (work)
2102                 {
2103                 FileData *sfd = work->data;
2104
2105                 if (!sfd->change || sfd->change->type != type) return FALSE;
2106                 work = work->next;
2107                 }
2108
2109         return TRUE;
2110 }
2111
2112
2113 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2114 {
2115         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2116         file_data_sc_update_ci_copy(fd, dest_path);
2117         return TRUE;
2118 }
2119
2120 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2121 {
2122         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2123         file_data_sc_update_ci_move(fd, dest_path);
2124         return TRUE;
2125 }
2126
2127 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2128 {
2129         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2130         file_data_sc_update_ci_rename(fd, dest_path);
2131         return TRUE;
2132 }
2133
2134 gboolean file_data_sc_add_ci_delete(FileData *fd)
2135 {
2136         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2137 }
2138
2139 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2140 {
2141         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2142         file_data_sc_update_ci_unspecified(fd, dest_path);
2143         return TRUE;
2144 }
2145
2146 gboolean file_data_add_ci_write_metadata(FileData *fd)
2147 {
2148         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2149 }
2150
2151 void file_data_sc_free_ci(FileData *fd)
2152 {
2153         GList *work;
2154
2155         if (fd->parent) fd = fd->parent;
2156
2157         file_data_free_ci(fd);
2158
2159         work = fd->sidecar_files;
2160         while (work)
2161                 {
2162                 FileData *sfd = work->data;
2163
2164                 file_data_free_ci(sfd);
2165                 work = work->next;
2166                 }
2167 }
2168
2169 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2170 {
2171         GList *work;
2172         gboolean ret = TRUE;
2173
2174         work = fd_list;
2175         while (work)
2176                 {
2177                 FileData *fd = work->data;
2178
2179                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2180                 work = work->next;
2181                 }
2182
2183         return ret;
2184 }
2185
2186 static void file_data_sc_revert_ci_list(GList *fd_list)
2187 {
2188         GList *work;
2189
2190         work = fd_list;
2191         while (work)
2192                 {
2193                 FileData *fd = work->data;
2194
2195                 file_data_sc_free_ci(fd);
2196                 work = work->prev;
2197                 }
2198 }
2199
2200 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2201 {
2202         GList *work;
2203
2204         work = fd_list;
2205         while (work)
2206                 {
2207                 FileData *fd = work->data;
2208
2209                 if (!func(fd, dest))
2210                         {
2211                         file_data_sc_revert_ci_list(work->prev);
2212                         return FALSE;
2213                         }
2214                 work = work->next;
2215                 }
2216
2217         return TRUE;
2218 }
2219
2220 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2221 {
2222         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2223 }
2224
2225 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2226 {
2227         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2228 }
2229
2230 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2231 {
2232         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2233 }
2234
2235 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2236 {
2237         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2238 }
2239
2240 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2241 {
2242         GList *work;
2243         gboolean ret = TRUE;
2244
2245         work = fd_list;
2246         while (work)
2247                 {
2248                 FileData *fd = work->data;
2249
2250                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2251                 work = work->next;
2252                 }
2253
2254         return ret;
2255 }
2256
2257 void file_data_free_ci_list(GList *fd_list)
2258 {
2259         GList *work;
2260
2261         work = fd_list;
2262         while (work)
2263                 {
2264                 FileData *fd = work->data;
2265
2266                 file_data_free_ci(fd);
2267                 work = work->next;
2268                 }
2269 }
2270
2271 void file_data_sc_free_ci_list(GList *fd_list)
2272 {
2273         GList *work;
2274
2275         work = fd_list;
2276         while (work)
2277                 {
2278                 FileData *fd = work->data;
2279
2280                 file_data_sc_free_ci(fd);
2281                 work = work->next;
2282                 }
2283 }
2284
2285 /*
2286  * update existing fd->change, it will be used from dialog callbacks for interactive editing
2287  * fails if fd->change does not exist or the change type does not match
2288  */
2289
2290 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2291 {
2292         FileDataChangeType type = fd->change->type;
2293
2294         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2295                 {
2296                 FileData *ofd;
2297
2298                 if (!file_data_planned_change_hash)
2299                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2300
2301                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2302                         {
2303                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2304                         g_hash_table_remove(file_data_planned_change_hash, old_path);
2305                         file_data_unref(fd);
2306                         }
2307
2308                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2309                 if (ofd != fd)
2310                         {
2311                         if (ofd)
2312                                 {
2313                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2314                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
2315                                 file_data_unref(ofd);
2316                                 }
2317
2318                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2319                         file_data_ref(fd);
2320                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2321                         }
2322                 }
2323 }
2324
2325 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2326 {
2327         gchar *old_path = fd->change->dest;
2328
2329         fd->change->dest = g_strdup(dest_path);
2330         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2331         g_free(old_path);
2332 }
2333
2334 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2335 {
2336         const gchar *extension = registered_extension_from_path(fd->change->source);
2337         gchar *base = remove_extension_from_path(dest_path);
2338         gchar *old_path = fd->change->dest;
2339
2340         fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2341         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2342
2343         g_free(old_path);
2344         g_free(base);
2345 }
2346
2347 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2348 {
2349         GList *work;
2350         gchar *dest_path_full = NULL;
2351
2352         if (fd->parent) fd = fd->parent;
2353
2354         if (!dest_path)
2355                 {
2356                 dest_path = fd->path;
2357                 }
2358         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2359                 {
2360                 gchar *dir = remove_level_from_path(fd->path);
2361
2362                 dest_path_full = g_build_filename(dir, dest_path, NULL);
2363                 g_free(dir);
2364                 dest_path = dest_path_full;
2365                 }
2366         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2367                 {
2368                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2369                 dest_path = dest_path_full;
2370                 }
2371
2372         file_data_update_ci_dest(fd, dest_path);
2373
2374         work = fd->sidecar_files;
2375         while (work)
2376                 {
2377                 FileData *sfd = work->data;
2378
2379                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2380                 work = work->next;
2381                 }
2382
2383         g_free(dest_path_full);
2384 }
2385
2386 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2387 {
2388         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2389         file_data_sc_update_ci(fd, dest_path);
2390         return TRUE;
2391 }
2392
2393 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2394 {
2395         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2396 }
2397
2398 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2399 {
2400         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2401 }
2402
2403 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2404 {
2405         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2406 }
2407
2408 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2409 {
2410         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2411 }
2412
2413 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2414                                                       const gchar *dest,
2415                                                       gboolean (*func)(FileData *, const gchar *))
2416 {
2417         GList *work;
2418         gboolean ret = TRUE;
2419
2420         work = fd_list;
2421         while (work)
2422                 {
2423                 FileData *fd = work->data;
2424
2425                 if (!func(fd, dest)) ret = FALSE;
2426                 work = work->next;
2427                 }
2428
2429         return ret;
2430 }
2431
2432 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2433 {
2434         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2435 }
2436
2437 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2438 {
2439         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2440 }
2441
2442 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2443 {
2444         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2445 }
2446
2447
2448 /*
2449  * verify source and dest paths - dest image exists, etc.
2450  * it should detect all possible problems with the planned operation
2451  */
2452
2453 gint file_data_verify_ci(FileData *fd, GList *list)
2454 {
2455         gint ret = CHANGE_OK;
2456         gchar *dir;
2457         GList *work = NULL;
2458         FileData *fd1 = NULL;
2459
2460         if (!fd->change)
2461                 {
2462                 DEBUG_1("Change checked: no change info: %s", fd->path);
2463                 return ret;
2464                 }
2465
2466         if (!isname(fd->path))
2467                 {
2468                 /* this probably should not happen */
2469                 ret |= CHANGE_NO_SRC;
2470                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2471                 return ret;
2472                 }
2473
2474         dir = remove_level_from_path(fd->path);
2475
2476         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2477             fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2478             fd->change->type != FILEDATA_CHANGE_RENAME &&
2479             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2480             fd->modified_xmp)
2481                 {
2482                 ret |= CHANGE_WARN_UNSAVED_META;
2483                 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2484                 }
2485
2486         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2487             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2488             !access_file(fd->path, R_OK))
2489                 {
2490                 ret |= CHANGE_NO_READ_PERM;
2491                 DEBUG_1("Change checked: no read permission: %s", fd->path);
2492                 }
2493         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2494                  !access_file(dir, W_OK))
2495                 {
2496                 ret |= CHANGE_NO_WRITE_PERM_DIR;
2497                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2498                 }
2499         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2500                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2501                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2502                  !access_file(fd->path, W_OK))
2503                 {
2504                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2505                 DEBUG_1("Change checked: no write permission: %s", fd->path);
2506                 }
2507         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2508            - that means that there are no hard errors and warnings can be disabled
2509            - the destination is determined during the check
2510         */
2511         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2512                 {
2513                 /* determine destination file */
2514                 gboolean have_dest = FALSE;
2515                 gchar *dest_dir = NULL;
2516
2517                 if (options->metadata.save_in_image_file)
2518                         {
2519                         if (file_data_can_write_directly(fd))
2520                                 {
2521                                 /* we can write the file directly */
2522                                 if (access_file(fd->path, W_OK))
2523                                         {
2524                                         have_dest = TRUE;
2525                                         }
2526                                 else
2527                                         {
2528                                         if (options->metadata.warn_on_write_problems)
2529                                                 {
2530                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2531                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2532                                                 }
2533                                         }
2534                                 }
2535                         else if (file_data_can_write_sidecar(fd))
2536                                 {
2537                                 /* we can write sidecar */
2538                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2539                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2540                                         {
2541                                         file_data_update_ci_dest(fd, sidecar);
2542                                         have_dest = TRUE;
2543                                         }
2544                                 else
2545                                         {
2546                                         if (options->metadata.warn_on_write_problems)
2547                                                 {
2548                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2549                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2550                                                 }
2551                                         }
2552                                 g_free(sidecar);
2553                                 }
2554                         }
2555
2556                 if (!have_dest)
2557                         {
2558                         /* write private metadata file under ~/.geeqie */
2559
2560                         /* If an existing metadata file exists, we will try writing to
2561                          * it's location regardless of the user's preference.
2562                          */
2563                         gchar *metadata_path = NULL;
2564 #ifdef HAVE_EXIV2
2565                         /* but ignore XMP if we are not able to write it */
2566                         metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2567 #endif
2568                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2569
2570                         if (metadata_path && !access_file(metadata_path, W_OK))
2571                                 {
2572                                 g_free(metadata_path);
2573                                 metadata_path = NULL;
2574                                 }
2575
2576                         if (!metadata_path)
2577                                 {
2578                                 mode_t mode = 0755;
2579
2580                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2581                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2582                                         {
2583                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2584
2585                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
2586                                         g_free(filename);
2587                                         }
2588                                 }
2589                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2590                                 {
2591                                 file_data_update_ci_dest(fd, metadata_path);
2592                                 have_dest = TRUE;
2593                                 }
2594                         else
2595                                 {
2596                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
2597                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2598                                 }
2599                         g_free(metadata_path);
2600                         }
2601                 g_free(dest_dir);
2602                 }
2603
2604         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2605                 {
2606                 gboolean same;
2607                 gchar *dest_dir;
2608
2609                 same = (strcmp(fd->path, fd->change->dest) == 0);
2610
2611                 if (!same)
2612                         {
2613                         const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2614                         if (!dest_ext) dest_ext = "";
2615                         if (!options->file_filter.disable_file_extension_checks)
2616                                 {
2617                                 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2618                                         {
2619                                         ret |= CHANGE_WARN_CHANGED_EXT;
2620                                         DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2621                                         }
2622                                 }
2623                         }
2624                 else
2625                         {
2626                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2627                                 {
2628                                 ret |= CHANGE_WARN_SAME;
2629                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2630                                 }
2631                         }
2632
2633                 dest_dir = remove_level_from_path(fd->change->dest);
2634
2635                 if (!isdir(dest_dir))
2636                         {
2637                         ret |= CHANGE_NO_DEST_DIR;
2638                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2639                         }
2640                 else if (!access_file(dest_dir, W_OK))
2641                         {
2642                         ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2643                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2644                         }
2645                 else if (!same)
2646                         {
2647                         if (isfile(fd->change->dest))
2648                                 {
2649                                 if (!access_file(fd->change->dest, W_OK))
2650                                         {
2651                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
2652                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2653                                         }
2654                                 else
2655                                         {
2656                                         ret |= CHANGE_WARN_DEST_EXISTS;
2657                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2658                                         }
2659                                 }
2660                         else if (isdir(fd->change->dest))
2661                                 {
2662                                 ret |= CHANGE_DEST_EXISTS;
2663                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2664                                 }
2665                         }
2666
2667                 g_free(dest_dir);
2668                 }
2669
2670         /* During a rename operation, check if another planned destination file has
2671          * the same filename
2672          */
2673         if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2674                                 fd->change->type == FILEDATA_CHANGE_COPY ||
2675                                 fd->change->type == FILEDATA_CHANGE_MOVE)
2676                 {
2677                 work = list;
2678                 while (work)
2679                         {
2680                         fd1 = work->data;
2681                         work = work->next;
2682                         if (fd1 != NULL && fd != fd1 )
2683                                 {
2684                                 if (!strcmp(fd->change->dest, fd1->change->dest))
2685                                         {
2686                                         ret |= CHANGE_DUPLICATE_DEST;
2687                                         }
2688                                 }
2689                         }
2690                 }
2691
2692         fd->change->error = ret;
2693         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2694
2695         g_free(dir);
2696         return ret;
2697 }
2698
2699
2700 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2701 {
2702         GList *work;
2703         gint ret;
2704
2705         ret = file_data_verify_ci(fd, list);
2706
2707         work = fd->sidecar_files;
2708         while (work)
2709                 {
2710                 FileData *sfd = work->data;
2711
2712                 ret |= file_data_verify_ci(sfd, list);
2713                 work = work->next;
2714                 }
2715
2716         return ret;
2717 }
2718
2719 gchar *file_data_get_error_string(gint error)
2720 {
2721         GString *result = g_string_new("");
2722
2723         if (error & CHANGE_NO_SRC)
2724                 {
2725                 if (result->len > 0) g_string_append(result, ", ");
2726                 g_string_append(result, _("file or directory does not exist"));
2727                 }
2728
2729         if (error & CHANGE_DEST_EXISTS)
2730                 {
2731                 if (result->len > 0) g_string_append(result, ", ");
2732                 g_string_append(result, _("destination already exists"));
2733                 }
2734
2735         if (error & CHANGE_NO_WRITE_PERM_DEST)
2736                 {
2737                 if (result->len > 0) g_string_append(result, ", ");
2738                 g_string_append(result, _("destination can't be overwritten"));
2739                 }
2740
2741         if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2742                 {
2743                 if (result->len > 0) g_string_append(result, ", ");
2744                 g_string_append(result, _("destination directory is not writable"));
2745                 }
2746
2747         if (error & CHANGE_NO_DEST_DIR)
2748                 {
2749                 if (result->len > 0) g_string_append(result, ", ");
2750                 g_string_append(result, _("destination directory does not exist"));
2751                 }
2752
2753         if (error & CHANGE_NO_WRITE_PERM_DIR)
2754                 {
2755                 if (result->len > 0) g_string_append(result, ", ");
2756                 g_string_append(result, _("source directory is not writable"));
2757                 }
2758
2759         if (error & CHANGE_NO_READ_PERM)
2760                 {
2761                 if (result->len > 0) g_string_append(result, ", ");
2762                 g_string_append(result, _("no read permission"));
2763                 }
2764
2765         if (error & CHANGE_WARN_NO_WRITE_PERM)
2766                 {
2767                 if (result->len > 0) g_string_append(result, ", ");
2768                 g_string_append(result, _("file is readonly"));
2769                 }
2770
2771         if (error & CHANGE_WARN_DEST_EXISTS)
2772                 {
2773                 if (result->len > 0) g_string_append(result, ", ");
2774                 g_string_append(result, _("destination already exists and will be overwritten"));
2775                 }
2776
2777         if (error & CHANGE_WARN_SAME)
2778                 {
2779                 if (result->len > 0) g_string_append(result, ", ");
2780                 g_string_append(result, _("source and destination are the same"));
2781                 }
2782
2783         if (error & CHANGE_WARN_CHANGED_EXT)
2784                 {
2785                 if (result->len > 0) g_string_append(result, ", ");
2786                 g_string_append(result, _("source and destination have different extension"));
2787                 }
2788
2789         if (error & CHANGE_WARN_UNSAVED_META)
2790                 {
2791                 if (result->len > 0) g_string_append(result, ", ");
2792                 g_string_append(result, _("there are unsaved metadata changes for the file"));
2793                 }
2794
2795         if (error & CHANGE_DUPLICATE_DEST)
2796                 {
2797                 if (result->len > 0) g_string_append(result, ", ");
2798                 g_string_append(result, _("another destination file has the same filename"));
2799                 }
2800
2801         return g_string_free(result, FALSE);
2802 }
2803
2804 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2805 {
2806         GList *work;
2807         gint all_errors = 0;
2808         gint common_errors = ~0;
2809         gint num;
2810         gint *errors;
2811         gint i;
2812
2813         if (!list) return 0;
2814
2815         num = g_list_length(list);
2816         errors = g_new(int, num);
2817         work = list;
2818         i = 0;
2819         while (work)
2820                 {
2821                 FileData *fd;
2822                 gint error;
2823
2824                 fd = work->data;
2825                 work = work->next;
2826
2827                 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2828                 all_errors |= error;
2829                 common_errors &= error;
2830
2831                 errors[i] = error;
2832
2833                 i++;
2834                 }
2835
2836         if (desc && all_errors)
2837                 {
2838                 GList *work;
2839                 GString *result = g_string_new("");
2840
2841                 if (common_errors)
2842                         {
2843                         gchar *str = file_data_get_error_string(common_errors);
2844                         g_string_append(result, str);
2845                         g_string_append(result, "\n");
2846                         g_free(str);
2847                         }
2848
2849                 work = list;
2850                 i = 0;
2851                 while (work)
2852                         {
2853                         FileData *fd;
2854                         gint error;
2855
2856                         fd = work->data;
2857                         work = work->next;
2858
2859                         error = errors[i] & ~common_errors;
2860
2861                         if (error)
2862                                 {
2863                                 gchar *str = file_data_get_error_string(error);
2864                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2865                                 g_free(str);
2866                                 }
2867                         i++;
2868                         }
2869                 *desc = g_string_free(result, FALSE);
2870                 }
2871
2872         g_free(errors);
2873         return all_errors;
2874 }
2875
2876
2877 /*
2878  * perform the change described by FileFataChangeInfo
2879  * it is used for internal operations,
2880  * this function actually operates with files on the filesystem
2881  * it should implement safe delete
2882  */
2883
2884 static gboolean file_data_perform_move(FileData *fd)
2885 {
2886         g_assert(!strcmp(fd->change->source, fd->path));
2887         return move_file(fd->change->source, fd->change->dest);
2888 }
2889
2890 static gboolean file_data_perform_copy(FileData *fd)
2891 {
2892         g_assert(!strcmp(fd->change->source, fd->path));
2893         return copy_file(fd->change->source, fd->change->dest);
2894 }
2895
2896 static gboolean file_data_perform_delete(FileData *fd)
2897 {
2898         if (isdir(fd->path) && !islink(fd->path))
2899                 return rmdir_utf8(fd->path);
2900         else
2901                 if (options->file_ops.safe_delete_enable)
2902                         return file_util_safe_unlink(fd->path);
2903                 else
2904                         return unlink_file(fd->path);
2905 }
2906
2907 gboolean file_data_perform_ci(FileData *fd)
2908 {
2909         FileDataChangeType type = fd->change->type;
2910
2911         switch (type)
2912                 {
2913                 case FILEDATA_CHANGE_MOVE:
2914                         return file_data_perform_move(fd);
2915                 case FILEDATA_CHANGE_COPY:
2916                         return file_data_perform_copy(fd);
2917                 case FILEDATA_CHANGE_RENAME:
2918                         return file_data_perform_move(fd); /* the same as move */
2919                 case FILEDATA_CHANGE_DELETE:
2920                         return file_data_perform_delete(fd);
2921                 case FILEDATA_CHANGE_WRITE_METADATA:
2922                         return metadata_write_perform(fd);
2923                 case FILEDATA_CHANGE_UNSPECIFIED:
2924                         /* nothing to do here */
2925                         break;
2926                 }
2927         return TRUE;
2928 }
2929
2930
2931
2932 gboolean file_data_sc_perform_ci(FileData *fd)
2933 {
2934         GList *work;
2935         gboolean ret = TRUE;
2936         FileDataChangeType type = fd->change->type;
2937
2938         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2939
2940         work = fd->sidecar_files;
2941         while (work)
2942                 {
2943                 FileData *sfd = work->data;
2944
2945                 if (!file_data_perform_ci(sfd)) ret = FALSE;
2946                 work = work->next;
2947                 }
2948
2949         if (!file_data_perform_ci(fd)) ret = FALSE;
2950
2951         return ret;
2952 }
2953
2954 /*
2955  * updates FileData structure according to FileDataChangeInfo
2956  */
2957
2958 gboolean file_data_apply_ci(FileData *fd)
2959 {
2960         FileDataChangeType type = fd->change->type;
2961
2962         /* FIXME delete ?*/
2963         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2964                 {
2965                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2966                 file_data_planned_change_remove(fd);
2967
2968                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2969                         {
2970                         /* this change overwrites another file which is already known to other modules
2971                            renaming fd would create duplicate FileData structure
2972                            the best thing we can do is nothing
2973                            FIXME: maybe we could copy stuff like marks
2974                         */
2975                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2976                         }
2977                 else
2978                         {
2979                         file_data_set_path(fd, fd->change->dest);
2980                         }
2981                 }
2982         file_data_increment_version(fd);
2983         file_data_send_notification(fd, NOTIFY_CHANGE);
2984
2985         return TRUE;
2986 }
2987
2988 gboolean file_data_sc_apply_ci(FileData *fd)
2989 {
2990         GList *work;
2991         FileDataChangeType type = fd->change->type;
2992
2993         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2994
2995         work = fd->sidecar_files;
2996         while (work)
2997                 {
2998                 FileData *sfd = work->data;
2999
3000                 file_data_apply_ci(sfd);
3001                 work = work->next;
3002                 }
3003
3004         file_data_apply_ci(fd);
3005
3006         return TRUE;
3007 }
3008
3009 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3010 {
3011         GList *work;
3012         if (fd->parent) fd = fd->parent;
3013         if (!g_list_find(list, fd)) return FALSE;
3014
3015         work = fd->sidecar_files;
3016         while (work)
3017                 {
3018                 if (!g_list_find(list, work->data)) return FALSE;
3019                 work = work->next;
3020                 }
3021         return TRUE;
3022 }
3023
3024 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3025 {
3026         GList *out = NULL;
3027         GList *work = list;
3028
3029         /* change partial groups to independent files */
3030         if (ungroup)
3031                 {
3032                 while (work)
3033                         {
3034                         FileData *fd = work->data;
3035                         work = work->next;
3036
3037                         if (!file_data_list_contains_whole_group(list, fd))
3038                                 {
3039                                 file_data_disable_grouping(fd, TRUE);
3040                                 if (ungrouped_list)
3041                                         {
3042                                         *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3043                                         }
3044                                 }
3045                         }
3046                 }
3047
3048         /* remove sidecars from the list,
3049            they can be still acessed via main_fd->sidecar_files */
3050         work = list;
3051         while (work)
3052                 {
3053                 FileData *fd = work->data;
3054                 work = work->next;
3055
3056                 if (!fd->parent ||
3057                     (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3058                         {
3059                         out = g_list_prepend(out, file_data_ref(fd));
3060                         }
3061                 }
3062
3063         filelist_free(list);
3064         out = g_list_reverse(out);
3065
3066         return out;
3067 }
3068
3069
3070
3071
3072
3073 /*
3074  * notify other modules about the change described by FileDataChangeInfo
3075  */
3076
3077 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
3078    FIXME do we need the ignore_list? It looks like a workaround for ineffective
3079    implementation in view_file_list.c */
3080
3081
3082 typedef struct _NotifyIdleData NotifyIdleData;
3083
3084 struct _NotifyIdleData {
3085         FileData *fd;
3086         NotifyType type;
3087 };
3088
3089
3090 typedef struct _NotifyData NotifyData;
3091
3092 struct _NotifyData {
3093         FileDataNotifyFunc func;
3094         gpointer data;
3095         NotifyPriority priority;
3096 };
3097
3098 static GList *notify_func_list = NULL;
3099
3100 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3101 {
3102         NotifyData *nda = (NotifyData *)a;
3103         NotifyData *ndb = (NotifyData *)b;
3104
3105         if (nda->priority < ndb->priority) return -1;
3106         if (nda->priority > ndb->priority) return 1;
3107         return 0;
3108 }
3109
3110 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3111 {
3112         NotifyData *nd;
3113         GList *work = notify_func_list;
3114
3115         while (work)
3116                 {
3117                 NotifyData *nd = (NotifyData *)work->data;
3118
3119                 if (nd->func == func && nd->data == data)
3120                         {
3121                         g_warning("Notify func already registered");
3122                         return FALSE;
3123                         }
3124                 work = work->next;
3125                 }
3126
3127         nd = g_new(NotifyData, 1);
3128         nd->func = func;
3129         nd->data = data;
3130         nd->priority = priority;
3131
3132         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3133         DEBUG_2("Notify func registered: %p", nd);
3134
3135         return TRUE;
3136 }
3137
3138 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3139 {
3140         GList *work = notify_func_list;
3141
3142         while (work)
3143                 {
3144                 NotifyData *nd = (NotifyData *)work->data;
3145
3146                 if (nd->func == func && nd->data == data)
3147                         {
3148                         notify_func_list = g_list_delete_link(notify_func_list, work);
3149                         g_free(nd);
3150                         DEBUG_2("Notify func unregistered: %p", nd);
3151                         return TRUE;
3152                         }
3153                 work = work->next;
3154                 }
3155
3156         g_warning("Notify func not found");
3157         return FALSE;
3158 }
3159
3160
3161 gboolean file_data_send_notification_idle_cb(gpointer data)
3162 {
3163         NotifyIdleData *nid = (NotifyIdleData *)data;
3164         GList *work = notify_func_list;
3165
3166         while (work)
3167                 {
3168                 NotifyData *nd = (NotifyData *)work->data;
3169
3170                 nd->func(nid->fd, nid->type, nd->data);
3171                 work = work->next;
3172                 }
3173         file_data_unref(nid->fd);
3174         g_free(nid);
3175         return FALSE;
3176 }
3177
3178 void file_data_send_notification(FileData *fd, NotifyType type)
3179 {
3180         GList *work = notify_func_list;
3181
3182         while (work)
3183                 {
3184                 NotifyData *nd = (NotifyData *)work->data;
3185
3186                 nd->func(fd, type, nd->data);
3187                 work = work->next;
3188                 }
3189     /*
3190         NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3191         nid->fd = file_data_ref(fd);
3192         nid->type = type;
3193         g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3194     */
3195 }
3196
3197 static GHashTable *file_data_monitor_pool = NULL;
3198 static guint realtime_monitor_id = 0; /* event source id */
3199
3200 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
3201 {
3202         FileData *fd = key;
3203
3204         file_data_check_changed_files(fd);
3205
3206         DEBUG_1("monitor %s", fd->path);
3207 }
3208
3209 static gboolean realtime_monitor_cb(gpointer data)
3210 {
3211         if (!options->update_on_time_change) return TRUE;
3212         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3213         return TRUE;
3214 }
3215
3216 gboolean file_data_register_real_time_monitor(FileData *fd)
3217 {
3218         gint count;
3219
3220         file_data_ref(fd);
3221
3222         if (!file_data_monitor_pool)
3223                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3224
3225         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3226
3227         DEBUG_1("Register realtime %d %s", count, fd->path);
3228
3229         count++;
3230         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3231
3232         if (!realtime_monitor_id)
3233                 {
3234                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3235                 }
3236
3237         return TRUE;
3238 }
3239
3240 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3241 {
3242         gint count;
3243
3244         g_assert(file_data_monitor_pool);
3245
3246         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3247
3248         DEBUG_1("Unregister realtime %d %s", count, fd->path);
3249
3250         g_assert(count > 0);
3251
3252         count--;
3253
3254         if (count == 0)
3255                 g_hash_table_remove(file_data_monitor_pool, fd);
3256         else
3257                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3258
3259         file_data_unref(fd);
3260
3261         if (g_hash_table_size(file_data_monitor_pool) == 0)
3262                 {
3263                 g_source_remove(realtime_monitor_id);
3264                 realtime_monitor_id = 0;
3265                 return FALSE;
3266                 }
3267
3268         return TRUE;
3269 }
3270
3271 /*
3272  *-----------------------------------------------------------------------------
3273  * Saving marks list, clearing marks
3274  * Uses file_data_pool
3275  *-----------------------------------------------------------------------------
3276  */
3277
3278 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3279 {
3280         gchar *file_name = key;
3281         GString *result = userdata;
3282         FileData *fd;
3283
3284         if (isfile(file_name))
3285                 {
3286                 fd = value;
3287                 if (fd && fd->marks > 0)
3288                         {
3289                         g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3290                         }
3291                 }
3292 }
3293
3294 gboolean marks_list_load(const gchar *path)
3295 {
3296         FILE *f;
3297         gchar s_buf[1024];
3298         gchar *pathl;
3299         gchar *file_path;
3300         gchar *marks_value;
3301
3302         pathl = path_from_utf8(path);
3303         f = fopen(pathl, "r");
3304         g_free(pathl);
3305         if (!f) return FALSE;
3306
3307         /* first line must start with Marks comment */
3308         if (!fgets(s_buf, sizeof(s_buf), f) ||
3309                                         strncmp(s_buf, "#Marks", 6) != 0)
3310                 {
3311                 fclose(f);
3312                 return FALSE;
3313                 }
3314
3315         while (fgets(s_buf, sizeof(s_buf), f))
3316                 {
3317                 if (s_buf[0]=='#') continue;
3318                         file_path = strtok(s_buf, ",");
3319                         marks_value = strtok(NULL, ",");
3320                         if (isfile(file_path))
3321                                 {
3322                                 FileData *fd = file_data_new_group(file_path);
3323                                 file_data_ref(fd);
3324                                 gint n = 0;
3325                                 while (n <= 9)
3326                                         {
3327                                         gint mark_no = 1 << n;
3328                                         if (atoi(marks_value) & mark_no)
3329                                                 {
3330                                                 file_data_set_mark(fd, n , 1);
3331                                                 }
3332                                         n++;
3333                                         }
3334                                 }
3335                 }
3336
3337         fclose(f);
3338         return TRUE;
3339 }
3340
3341 gboolean marks_list_save(gchar *path, gboolean save)
3342 {
3343         SecureSaveInfo *ssi;
3344         gchar *pathl;
3345         GString  *marks = g_string_new("");
3346
3347         pathl = path_from_utf8(path);
3348         ssi = secure_open(pathl);
3349         g_free(pathl);
3350         if (!ssi)
3351                 {
3352                 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3353                 return FALSE;
3354                 }
3355
3356         secure_fprintf(ssi, "#Marks lists\n");
3357
3358         if (save)
3359                 {
3360                 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3361                 }
3362         secure_fprintf(ssi, "%s", marks->str);
3363         g_string_free(marks, FALSE);
3364
3365         secure_fprintf(ssi, "#end\n");
3366         return (secure_close(ssi) == 0);
3367 }
3368
3369 static void marks_clear(gpointer key, gpointer value, gpointer userdata)
3370 {
3371         gchar *file_name = key;
3372         gint mark_no;
3373         gint n;
3374         FileData *fd;
3375
3376         if (isfile(file_name))
3377                 {
3378                 fd = value;
3379                 if (fd && fd->marks > 0)
3380                         {
3381                         n = 0;
3382                         while (n <= 9)
3383                                 {
3384                                 mark_no = 1 << n;
3385                                 if (fd->marks & mark_no)
3386                                         {
3387                                         file_data_set_mark(fd, n , 0);
3388                                         }
3389                                 n++;
3390                                 }
3391                         }
3392                 }
3393 }
3394
3395 void marks_clear_all()
3396 {
3397         g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3398 }
3399
3400 void file_data_set_page_num(FileData *fd, gint page_num)
3401 {
3402         if (fd->page_total > 1 && page_num < 0)
3403                 {
3404                 fd->page_num = fd->page_total - 1;
3405                 }
3406         else if (fd->page_total > 1 && page_num <= fd->page_total)
3407                 {
3408                 fd->page_num = page_num - 1;
3409                 }
3410         else
3411                 {
3412                 fd->page_num = 0;
3413                 }
3414         file_data_send_notification(fd, NOTIFY_REREAD);
3415 }
3416
3417 void file_data_inc_page_num(FileData *fd)
3418 {
3419         if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3420                 {
3421                 fd->page_num = fd->page_num + 1;
3422                 }
3423         else if (fd->page_total == 0)
3424                 {
3425                 fd->page_num = fd->page_num + 1;
3426                 }
3427         file_data_send_notification(fd, NOTIFY_REREAD);
3428 }
3429
3430 void file_data_dec_page_num(FileData *fd)
3431 {
3432         if (fd->page_num > 0)
3433                 {
3434                 fd->page_num = fd->page_num - 1;
3435                 }
3436         file_data_send_notification(fd, NOTIFY_REREAD);
3437 }
3438
3439 void file_data_set_page_total(FileData *fd, gint page_total)
3440 {
3441         fd->page_total = page_total;
3442 }
3443
3444 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */