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