Include DEBUG_BT() and DEBUG_FD() functions
[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 /**
690  * @brief Print ref. count and image name
691  * @param  
692  * 
693  * Print image ref. count and full path name of all images in
694  * the file_data_pool.
695  *
696  * Used only by DEBUG_FD()
697  */
698 void file_data_dump()
699 {
700         FileData *fd;
701         GList *list;
702
703         if (file_data_pool)
704                 {
705                 list = g_hash_table_get_values(file_data_pool);
706
707                 log_printf("%d", global_file_data_count);
708                 log_printf("%d", g_list_length(list));
709
710                 while (list)
711                         {
712                         fd = list->data;
713                         log_printf("%-4d %s", fd->ref, fd->path);
714                         list = list->next;
715                         }
716
717                 g_list_free(list);
718                 }
719 }
720
721 static void file_data_free(FileData *fd)
722 {
723         g_assert(fd->magick == FD_MAGICK);
724         g_assert(fd->ref == 0);
725         g_assert(!fd->locked);
726
727 #ifdef DEBUG_FILEDATA
728         global_file_data_count--;
729         DEBUG_2("file data count--: %d", global_file_data_count);
730 #endif
731
732         metadata_cache_free(fd);
733         g_hash_table_remove(file_data_pool, fd->original_path);
734
735         g_free(fd->path);
736         g_free(fd->original_path);
737         g_free(fd->collate_key_name);
738         g_free(fd->collate_key_name_nocase);
739         g_free(fd->extended_extension);
740         if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
741         histmap_free(fd->histmap);
742         g_free(fd->owner);
743         g_free(fd->group);
744         g_free(fd->sym_link);
745         g_free(fd->format_name);
746         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
747
748         file_data_change_info_free(NULL, fd);
749         g_free(fd);
750 }
751
752 /**
753  * @brief Checks if the FileData is referenced
754  *
755  * Checks the refcount and whether the FileData is locked.
756  */
757 static gboolean file_data_check_has_ref(FileData *fd)
758 {
759         return fd->ref > 0 || fd->locked;
760 }
761
762 /**
763  * @brief Consider freeing a FileData.
764  *
765  * This function will free a FileData and its children provided that neither its parent nor it has
766  * a positive refcount, and provided that neither is locked.
767  */
768 static void file_data_consider_free(FileData *fd)
769 {
770         GList *work;
771         FileData *parent = fd->parent ? fd->parent : fd;
772
773         g_assert(fd->magick == FD_MAGICK);
774         if (file_data_check_has_ref(fd)) return;
775         if (file_data_check_has_ref(parent)) return;
776
777         work = parent->sidecar_files;
778         while (work)
779                 {
780                 FileData *sfd = work->data;
781                 if (file_data_check_has_ref(sfd)) return;
782                 work = work->next;
783                 }
784
785         /* Neither the parent nor the siblings are referenced, so we can free everything */
786         DEBUG_2("file_data_consider_free: deleting '%s', parent '%s'",
787                 fd->path, fd->parent ? parent->path : "-");
788
789         work = parent->sidecar_files;
790         while (work)
791                 {
792                 FileData *sfd = work->data;
793                 file_data_free(sfd);
794                 work = work->next;
795                 }
796
797         g_list_free(parent->sidecar_files);
798         parent->sidecar_files = NULL;
799
800         file_data_free(parent);
801 }
802
803 #ifdef DEBUG_FILEDATA
804 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
805 #else
806 void file_data_unref(FileData *fd)
807 #endif
808 {
809         if (fd == NULL) return;
810         if (fd->magick != FD_MAGICK)
811 #ifdef DEBUG_FILEDATA
812                 log_printf("Error: fd magick mismatch @ %s:%d  fd=%p", file, line, (void *)fd);
813 #else
814                 log_printf("Error: fd magick mismatch fd=%p", fd);
815 #endif
816         g_assert(fd->magick == FD_MAGICK);
817
818         fd->ref--;
819 #ifdef DEBUG_FILEDATA
820         DEBUG_2("file_data_unref fd=%p (%d:%d): '%s' @ %s:%d", (void *)fd, fd->ref, fd->locked, fd->path,
821                 file, line);
822 #else
823         DEBUG_2("file_data_unref fd=%p (%d:%d): '%s'", fd, fd->ref, fd->locked, fd->path);
824 #endif
825
826         // Free FileData if it's no longer ref'd
827         file_data_consider_free(fd);
828 }
829
830 /**
831  * @brief Lock the FileData in memory.
832  *
833  * This allows the caller to prevent a FileData from being freed, even after its refcount is zero.
834  * This is intended to be used in cases where a FileData _should_ stay in memory as an optimization,
835  * even if the code would continue to function properly even if the FileData were freed.  Code that
836  * _requires_ the FileData to remain in memory should continue to use file_data_(un)ref.
837  * <p />
838  * Note: This differs from file_data_ref in that the behavior is reentrant -- after N calls to
839  * file_data_lock, a single call to file_data_unlock will unlock the FileData.
840  */
841 void file_data_lock(FileData *fd)
842 {
843         if (fd == NULL) return;
844         if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
845
846         g_assert(fd->magick == FD_MAGICK);
847         fd->locked = TRUE;
848
849         DEBUG_2("file_data_ref fd=%p (%d): '%s'", (void *)fd, fd->ref, fd->path);
850 }
851
852 /**
853  * @brief Reset the maintain-FileData-in-memory lock
854  *
855  * This again allows the FileData to be freed when its refcount drops to zero.  Automatically frees
856  * the FileData if its refcount is already zero (which will happen if the lock is the only thing
857  * keeping it from being freed.
858  */
859 void file_data_unlock(FileData *fd)
860 {
861         if (fd == NULL) return;
862         if (fd->magick != FD_MAGICK) log_printf("Error: fd magick mismatch fd=%p", (void *)fd);
863
864         g_assert(fd->magick == FD_MAGICK);
865         fd->locked = FALSE;
866
867         // Free FileData if it's no longer ref'd
868         file_data_consider_free(fd);
869 }
870
871 /**
872  * @brief Lock all of the FileDatas in the provided list
873  *
874  * @see file_data_lock(#FileData)
875  */
876 void file_data_lock_list(GList *list)
877 {
878         GList *work;
879
880         work = list;
881         while (work)
882                 {
883                 FileData *fd = work->data;
884                 work = work->next;
885                 file_data_lock(fd);
886                 }
887 }
888
889 /**
890  * @brief Unlock all of the FileDatas in the provided list
891  *
892  * @see #file_data_unlock(#FileData)
893  */
894 void file_data_unlock_list(GList *list)
895 {
896         GList *work;
897
898         work = list;
899         while (work)
900                 {
901                 FileData *fd = work->data;
902                 work = work->next;
903                 file_data_unlock(fd);
904                 }
905 }
906
907 /*
908  *-----------------------------------------------------------------------------
909  * sidecar file info struct
910  *-----------------------------------------------------------------------------
911  */
912
913 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
914 {
915         const FileData *fda = a;
916         const FileData *fdb = b;
917
918         if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
919         if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
920
921         return strcmp(fdb->extension, fda->extension);
922 }
923
924
925 static gint sidecar_file_priority(const gchar *extension)
926 {
927         gint i = 1;
928         GList *work;
929
930         if (extension == NULL)
931                 return 0;
932
933         work = sidecar_ext_get_list();
934
935         while (work) {
936                 gchar *ext = work->data;
937
938                 work = work->next;
939                 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
940                 i++;
941         }
942         return 0;
943 }
944
945 static void file_data_check_sidecars(const GList *basename_list)
946 {
947         /* basename_list contains the new group - first is the parent, then sorted sidecars */
948         /* all files in the list have ref count > 0 */
949
950         const GList *work;
951         GList *s_work, *new_sidecars;
952         FileData *parent_fd;
953
954         if (!basename_list) return;
955
956
957         DEBUG_2("basename start");
958         work = basename_list;
959         while (work)
960                 {
961                 FileData *fd = work->data;
962                 work = work->next;
963                 g_assert(fd->magick == FD_MAGICK);
964                 DEBUG_2("basename: %p %s", (void *)fd, fd->name);
965                 if (fd->parent)
966                         {
967                         g_assert(fd->parent->magick == FD_MAGICK);
968                         DEBUG_2("                  parent: %p", (void *)fd->parent);
969                         }
970                 s_work = fd->sidecar_files;
971                 while (s_work)
972                         {
973                         FileData *sfd = s_work->data;
974                         s_work = s_work->next;
975                         g_assert(sfd->magick == FD_MAGICK);
976                         DEBUG_2("                  sidecar: %p %s", (void *)sfd, sfd->name);
977                         }
978
979                 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
980                 }
981
982         parent_fd = basename_list->data;
983
984         /* check if the second and next entries of basename_list are already connected
985            as sidecars of the first entry (parent_fd) */
986         work = basename_list->next;
987         s_work = parent_fd->sidecar_files;
988
989         while (work && s_work)
990                 {
991                 if (work->data != s_work->data) break;
992                 work = work->next;
993                 s_work = s_work->next;
994                 }
995
996         if (!work && !s_work)
997                 {
998                 DEBUG_2("basename no change");
999                 return; /* no change in grouping */
1000                 }
1001
1002         /* we have to regroup it */
1003
1004         /* first, disconnect everything and send notification*/
1005
1006         work = basename_list;
1007         while (work)
1008                 {
1009                 FileData *fd = work->data;
1010                 work = work->next;
1011                 g_assert(fd->parent == NULL || fd->sidecar_files == NULL);
1012
1013                 if (fd->parent)
1014                         {
1015                         FileData *old_parent = fd->parent;
1016                         g_assert(old_parent->parent == NULL || old_parent->sidecar_files == NULL);
1017                         file_data_ref(old_parent);
1018                         file_data_disconnect_sidecar_file(old_parent, fd);
1019                         file_data_send_notification(old_parent, NOTIFY_REREAD);
1020                         file_data_unref(old_parent);
1021                         }
1022
1023                 while (fd->sidecar_files)
1024                         {
1025                         FileData *sfd = fd->sidecar_files->data;
1026                         g_assert(sfd->parent == NULL || sfd->sidecar_files == NULL);
1027                         file_data_ref(sfd);
1028                         file_data_disconnect_sidecar_file(fd, sfd);
1029                         file_data_send_notification(sfd, NOTIFY_REREAD);
1030                         file_data_unref(sfd);
1031                         }
1032                 file_data_send_notification(fd, NOTIFY_GROUPING);
1033
1034                 g_assert(fd->parent == NULL && fd->sidecar_files == NULL);
1035                 }
1036
1037         /* now we can form the new group */
1038         work = basename_list->next;
1039         new_sidecars = NULL;
1040         while (work)
1041                 {
1042                 FileData *sfd = work->data;
1043                 g_assert(sfd->magick == FD_MAGICK);
1044                 g_assert(sfd->parent == NULL && sfd->sidecar_files == NULL);
1045                 sfd->parent = parent_fd;
1046                 new_sidecars = g_list_prepend(new_sidecars, sfd);
1047                 work = work->next;
1048                 }
1049         g_assert(parent_fd->sidecar_files == NULL);
1050         parent_fd->sidecar_files = g_list_reverse(new_sidecars);
1051         DEBUG_1("basename group changed for %s", parent_fd->path);
1052 }
1053
1054
1055 static void file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
1056 {
1057         g_assert(target->magick == FD_MAGICK);
1058         g_assert(sfd->magick == FD_MAGICK);
1059         g_assert(g_list_find(target->sidecar_files, sfd));
1060
1061         file_data_ref(target);
1062         file_data_ref(sfd);
1063
1064         g_assert(sfd->parent == target);
1065
1066         file_data_increment_version(sfd); /* increments both sfd and target */
1067
1068         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
1069         sfd->parent = NULL;
1070         g_free(sfd->extended_extension);
1071         sfd->extended_extension = NULL;
1072
1073         file_data_unref(target);
1074         file_data_unref(sfd);
1075 }
1076
1077 /* disables / enables grouping for particular file, sends UPDATE notification */
1078 void file_data_disable_grouping(FileData *fd, gboolean disable)
1079 {
1080         if (!fd->disable_grouping == !disable) return;
1081
1082         fd->disable_grouping = !!disable;
1083
1084         if (disable)
1085                 {
1086                 if (fd->parent)
1087                         {
1088                         FileData *parent = file_data_ref(fd->parent);
1089                         file_data_disconnect_sidecar_file(parent, fd);
1090                         file_data_send_notification(parent, NOTIFY_GROUPING);
1091                         file_data_unref(parent);
1092                         }
1093                 else if (fd->sidecar_files)
1094                         {
1095                         GList *sidecar_files = filelist_copy(fd->sidecar_files);
1096                         GList *work = sidecar_files;
1097                         while (work)
1098                                 {
1099                                 FileData *sfd = work->data;
1100                                 work = work->next;
1101                                 file_data_disconnect_sidecar_file(fd, sfd);
1102                                 file_data_send_notification(sfd, NOTIFY_GROUPING);
1103                                 }
1104                         file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
1105                         filelist_free(sidecar_files);
1106                         }
1107                 else
1108                         {
1109                         file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
1110                         }
1111                 }
1112         else
1113                 {
1114                 file_data_increment_version(fd);
1115                 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
1116                 }
1117         file_data_send_notification(fd, NOTIFY_GROUPING);
1118 }
1119
1120 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
1121 {
1122         GList *work;
1123
1124         work = fd_list;
1125         while (work)
1126                 {
1127                 FileData *fd = work->data;
1128
1129                 file_data_disable_grouping(fd, disable);
1130                 work = work->next;
1131                 }
1132 }
1133
1134
1135
1136 /*
1137  *-----------------------------------------------------------------------------
1138  * filelist sorting
1139  *-----------------------------------------------------------------------------
1140  */
1141
1142
1143 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
1144 {
1145         gint ret;
1146         if (!filelist_sort_ascend)
1147                 {
1148                 FileData *tmp = fa;
1149                 fa = fb;
1150                 fb = tmp;
1151                 }
1152
1153         switch (filelist_sort_method)
1154                 {
1155                 case SORT_NAME:
1156                         break;
1157                 case SORT_SIZE:
1158                         if (fa->size < fb->size) return -1;
1159                         if (fa->size > fb->size) return 1;
1160                         /* fall back to name */
1161                         break;
1162                 case SORT_TIME:
1163                         if (fa->date < fb->date) return -1;
1164                         if (fa->date > fb->date) return 1;
1165                         /* fall back to name */
1166                         break;
1167                 case SORT_CTIME:
1168                         if (fa->cdate < fb->cdate) return -1;
1169                         if (fa->cdate > fb->cdate) return 1;
1170                         /* fall back to name */
1171                         break;
1172                 case SORT_EXIFTIME:
1173                         if (fa->exifdate < fb->exifdate) return -1;
1174                         if (fa->exifdate > fb->exifdate) return 1;
1175                         /* fall back to name */
1176                         break;
1177                 case SORT_EXIFTIMEDIGITIZED:
1178                         if (fa->exifdate_digitized < fb->exifdate_digitized) return -1;
1179                         if (fa->exifdate_digitized > fb->exifdate_digitized) return 1;
1180                         /* fall back to name */
1181                         break;
1182                 case SORT_RATING:
1183                         if (fa->rating < fb->rating) return -1;
1184                         if (fa->rating > fb->rating) return 1;
1185                         /* fall back to name */
1186                         break;
1187                 case SORT_CLASS:
1188                         if (fa->format_class < fb->format_class) return -1;
1189                         if (fa->format_class > fb->format_class) return 1;
1190                         /* fall back to name */
1191                         break;
1192                 default:
1193                         break;
1194                 }
1195
1196         if (options->file_sort.case_sensitive)
1197                 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
1198         else
1199                 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
1200
1201         if (ret != 0) return ret;
1202
1203         /* do not return 0 unless the files are really the same
1204            file_data_pool ensures that original_path is unique
1205         */
1206         return strcmp(fa->original_path, fb->original_path);
1207 }
1208
1209 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
1210 {
1211         filelist_sort_method = method;
1212         filelist_sort_ascend = ascend;
1213         return filelist_sort_compare_filedata(fa, fb);
1214 }
1215
1216 static gint filelist_sort_file_cb(gpointer a, gpointer b)
1217 {
1218         return filelist_sort_compare_filedata(a, b);
1219 }
1220
1221 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
1222 {
1223         filelist_sort_method = method;
1224         filelist_sort_ascend = ascend;
1225         return g_list_sort(list, cb);
1226 }
1227
1228 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
1229 {
1230         filelist_sort_method = method;
1231         filelist_sort_ascend = ascend;
1232         return g_list_insert_sorted(list, data, cb);
1233 }
1234
1235 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
1236 {
1237         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1238 }
1239
1240 //GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
1241 //{
1242         //return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1243 //}
1244
1245 /*
1246  *-----------------------------------------------------------------------------
1247  * basename hash - grouping of sidecars in filelist
1248  *-----------------------------------------------------------------------------
1249  */
1250
1251
1252 static GHashTable *file_data_basename_hash_new(void)
1253 {
1254         return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1255 }
1256
1257 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
1258 {
1259         GList *list;
1260         gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
1261
1262         list = g_hash_table_lookup(basename_hash, basename);
1263
1264         if (!list)
1265                 {
1266                 DEBUG_1("TG: basename_hash not found for %s",fd->path);
1267                 const gchar *parent_extension = registered_extension_from_path(basename);
1268
1269                 if (parent_extension)
1270                         {
1271                         DEBUG_1("TG: parent extension %s",parent_extension);
1272                         gchar *parent_basename = g_strndup(basename, parent_extension - basename);
1273                         DEBUG_1("TG: parent basename %s",parent_basename);
1274                         FileData *parent_fd = g_hash_table_lookup(file_data_pool, basename);
1275                         if (parent_fd)
1276                                 {
1277                                 DEBUG_1("TG: parent fd found");
1278                                 list = g_hash_table_lookup(basename_hash, parent_basename);
1279                                 if (!g_list_find(list, parent_fd))
1280                                         {
1281                                         DEBUG_1("TG: parent fd doesn't fit");
1282                                         g_free(parent_basename);
1283                                         list = NULL;
1284                                         }
1285                                 else
1286                                         {
1287                                         g_free(basename);
1288                                         basename = parent_basename;
1289                                         fd->extended_extension = g_strconcat(parent_extension, fd->extension, NULL);
1290                                         }
1291                                 }
1292                         }
1293                 }
1294
1295         if (!g_list_find(list, fd))
1296                 {
1297                 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
1298                 g_hash_table_insert(basename_hash, basename, list);
1299                 }
1300         else
1301                 {
1302                 g_free(basename);
1303                 }
1304         return list;
1305 }
1306
1307 static void file_data_basename_hash_insert_cb(gpointer fd, gpointer basename_hash)
1308 {
1309         file_data_basename_hash_insert((GHashTable *)basename_hash, (FileData *)fd);
1310 }
1311
1312 static void file_data_basename_hash_remove_list(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1313 {
1314         filelist_free((GList *)value);
1315 }
1316
1317 static void file_data_basename_hash_free(GHashTable *basename_hash)
1318 {
1319         g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
1320         g_hash_table_destroy(basename_hash);
1321 }
1322
1323 /*
1324  *-----------------------------------------------------------------------------
1325  * handling sidecars in filelist
1326  *-----------------------------------------------------------------------------
1327  */
1328
1329 static GList *filelist_filter_out_sidecars(GList *flist)
1330 {
1331         GList *work = flist;
1332         GList *flist_filtered = NULL;
1333
1334         while (work)
1335                 {
1336                 FileData *fd = work->data;
1337
1338                 work = work->next;
1339                 if (fd->parent) /* remove fd's that are children */
1340                         file_data_unref(fd);
1341                 else
1342                         flist_filtered = g_list_prepend(flist_filtered, fd);
1343                 }
1344         g_list_free(flist);
1345
1346         return flist_filtered;
1347 }
1348
1349 static void file_data_basename_hash_to_sidecars(gpointer UNUSED(key), gpointer value, gpointer UNUSED(data))
1350 {
1351         GList *basename_list = (GList *)value;
1352         file_data_check_sidecars(basename_list);
1353 }
1354
1355
1356 static gboolean is_hidden_file(const gchar *name)
1357 {
1358         if (name[0] != '.') return FALSE;
1359         if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1360         return TRUE;
1361 }
1362
1363 /*
1364  *-----------------------------------------------------------------------------
1365  * the main filelist function
1366  *-----------------------------------------------------------------------------
1367  */
1368
1369 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
1370 {
1371         DIR *dp;
1372         struct dirent *dir;
1373         gchar *pathl;
1374         GList *dlist = NULL;
1375         GList *flist = NULL;
1376         GList *xmp_files = NULL;
1377         gint (*stat_func)(const gchar *path, struct stat *buf);
1378         GHashTable *basename_hash = NULL;
1379
1380         g_assert(files || dirs);
1381
1382         if (files) *files = NULL;
1383         if (dirs) *dirs = NULL;
1384
1385         pathl = path_from_utf8(dir_path);
1386         if (!pathl) return FALSE;
1387
1388         dp = opendir(pathl);
1389         if (dp == NULL)
1390                 {
1391                 g_free(pathl);
1392                 return FALSE;
1393                 }
1394
1395         if (files) basename_hash = file_data_basename_hash_new();
1396
1397         if (follow_symlinks)
1398                 stat_func = stat;
1399         else
1400                 stat_func = lstat;
1401
1402         while ((dir = readdir(dp)) != NULL)
1403                 {
1404                 struct stat ent_sbuf;
1405                 const gchar *name = dir->d_name;
1406                 gchar *filepath;
1407
1408                 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1409                         continue;
1410
1411                 filepath = g_build_filename(pathl, name, NULL);
1412                 if (stat_func(filepath, &ent_sbuf) >= 0)
1413                         {
1414                         if (S_ISDIR(ent_sbuf.st_mode))
1415                                 {
1416                                 /* we ignore the .thumbnails dir for cleanliness */
1417                                 if (dirs &&
1418                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1419                                     strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1420                                     strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1421                                     strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1422                                         {
1423                                         dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
1424                                         }
1425                                 }
1426                         else
1427                                 {
1428                                 if (files && filter_name_exists(name))
1429                                         {
1430                                         FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
1431                                         flist = g_list_prepend(flist, fd);
1432                                         if (fd->sidecar_priority && !fd->disable_grouping)
1433                                                 {
1434                                                 if (strcmp(fd->extension, ".xmp") != 0)
1435                                                         file_data_basename_hash_insert(basename_hash, fd);
1436                                                 else
1437                                                         xmp_files = g_list_append(xmp_files, fd);
1438                                                 }
1439                                         }
1440                                 }
1441                         }
1442                 else
1443                         {
1444                         if (errno == EOVERFLOW)
1445                                 {
1446                                 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1447                                 }
1448                         }
1449                 g_free(filepath);
1450                 }
1451
1452         closedir(dp);
1453
1454         g_free(pathl);
1455
1456         if (xmp_files)
1457                 {
1458                 g_list_foreach(xmp_files,file_data_basename_hash_insert_cb,basename_hash);
1459                 g_list_free(xmp_files);
1460                 }
1461
1462         if (dirs) *dirs = dlist;
1463
1464         if (files)
1465                 {
1466                 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1467
1468                 *files = filelist_filter_out_sidecars(flist);
1469                 }
1470         if (basename_hash) file_data_basename_hash_free(basename_hash);
1471
1472         return TRUE;
1473 }
1474
1475 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1476 {
1477         return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1478 }
1479
1480 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1481 {
1482         return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1483 }
1484
1485 FileData *file_data_new_group(const gchar *path_utf8)
1486 {
1487         gchar *dir;
1488         struct stat st;
1489         FileData *fd;
1490         GList *files;
1491
1492         if (!stat_utf8(path_utf8, &st))
1493                 {
1494                 st.st_size = 0;
1495                 st.st_mtime = 0;
1496                 }
1497
1498         if (S_ISDIR(st.st_mode))
1499                 return file_data_new(path_utf8, &st, TRUE);
1500
1501         dir = remove_level_from_path(path_utf8);
1502
1503         filelist_read_real(dir, &files, NULL, TRUE);
1504
1505         fd = g_hash_table_lookup(file_data_pool, path_utf8);
1506         if (!fd) fd = file_data_new(path_utf8, &st, TRUE);
1507         if (fd)
1508                 {
1509                 file_data_ref(fd);
1510                 }
1511
1512         filelist_free(files);
1513         g_free(dir);
1514         return fd;
1515 }
1516
1517
1518 void filelist_free(GList *list)
1519 {
1520         GList *work;
1521
1522         work = list;
1523         while (work)
1524                 {
1525                 file_data_unref((FileData *)work->data);
1526                 work = work->next;
1527                 }
1528
1529         g_list_free(list);
1530 }
1531
1532
1533 GList *filelist_copy(GList *list)
1534 {
1535         GList *new_list = NULL;
1536         GList *work;
1537
1538         work = list;
1539         while (work)
1540                 {
1541                 FileData *fd;
1542
1543                 fd = work->data;
1544                 work = work->next;
1545
1546                 new_list = g_list_prepend(new_list, file_data_ref(fd));
1547                 }
1548
1549         return g_list_reverse(new_list);
1550 }
1551
1552 GList *filelist_from_path_list(GList *list)
1553 {
1554         GList *new_list = NULL;
1555         GList *work;
1556
1557         work = list;
1558         while (work)
1559                 {
1560                 gchar *path;
1561
1562                 path = work->data;
1563                 work = work->next;
1564
1565                 new_list = g_list_prepend(new_list, file_data_new_group(path));
1566                 }
1567
1568         return g_list_reverse(new_list);
1569 }
1570
1571 GList *filelist_to_path_list(GList *list)
1572 {
1573         GList *new_list = NULL;
1574         GList *work;
1575
1576         work = list;
1577         while (work)
1578                 {
1579                 FileData *fd;
1580
1581                 fd = work->data;
1582                 work = work->next;
1583
1584                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1585                 }
1586
1587         return g_list_reverse(new_list);
1588 }
1589
1590 GList *filelist_filter(GList *list, gboolean is_dir_list)
1591 {
1592         GList *work;
1593
1594         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1595
1596         work = list;
1597         while (work)
1598                 {
1599                 FileData *fd = (FileData *)(work->data);
1600                 const gchar *name = fd->name;
1601
1602                 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1603                     (!is_dir_list && !filter_name_exists(name)) ||
1604                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1605                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1606                         {
1607                         GList *link = work;
1608
1609                         list = g_list_remove_link(list, link);
1610                         file_data_unref(fd);
1611                         g_list_free(link);
1612                         }
1613
1614                 work = work->next;
1615                 }
1616
1617         return list;
1618 }
1619
1620 /*
1621  *-----------------------------------------------------------------------------
1622  * filelist recursive
1623  *-----------------------------------------------------------------------------
1624  */
1625
1626 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1627 {
1628         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1629 }
1630
1631 GList *filelist_sort_path(GList *list)
1632 {
1633         return g_list_sort(list, filelist_sort_path_cb);
1634 }
1635
1636 static void filelist_recursive_append(GList **list, GList *dirs)
1637 {
1638         GList *work;
1639
1640         work = dirs;
1641         while (work)
1642                 {
1643                 FileData *fd = (FileData *)(work->data);
1644                 GList *f;
1645                 GList *d;
1646
1647                 if (filelist_read(fd, &f, &d))
1648                         {
1649                         f = filelist_filter(f, FALSE);
1650                         f = filelist_sort_path(f);
1651                         *list = g_list_concat(*list, f);
1652
1653                         d = filelist_filter(d, TRUE);
1654                         d = filelist_sort_path(d);
1655                         filelist_recursive_append(list, d);
1656                         filelist_free(d);
1657                         }
1658
1659                 work = work->next;
1660                 }
1661 }
1662
1663 static void filelist_recursive_append_full(GList **list, GList *dirs, SortType method, gboolean ascend)
1664 {
1665         GList *work;
1666
1667         work = dirs;
1668         while (work)
1669                 {
1670                 FileData *fd = (FileData *)(work->data);
1671                 GList *f;
1672                 GList *d;
1673
1674                 if (filelist_read(fd, &f, &d))
1675                         {
1676                         f = filelist_filter(f, FALSE);
1677                         f = filelist_sort_full(f, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1678                         *list = g_list_concat(*list, f);
1679
1680                         d = filelist_filter(d, TRUE);
1681                         d = filelist_sort_path(d);
1682                         filelist_recursive_append_full(list, d, method, ascend);
1683                         filelist_free(d);
1684                         }
1685
1686                 work = work->next;
1687                 }
1688 }
1689
1690 GList *filelist_recursive(FileData *dir_fd)
1691 {
1692         GList *list;
1693         GList *d;
1694
1695         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1696         list = filelist_filter(list, FALSE);
1697         list = filelist_sort_path(list);
1698
1699         d = filelist_filter(d, TRUE);
1700         d = filelist_sort_path(d);
1701         filelist_recursive_append(&list, d);
1702         filelist_free(d);
1703
1704         return list;
1705 }
1706
1707 GList *filelist_recursive_full(FileData *dir_fd, SortType method, gboolean ascend)
1708 {
1709         GList *list;
1710         GList *d;
1711
1712         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1713         list = filelist_filter(list, FALSE);
1714         list = filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
1715
1716         d = filelist_filter(d, TRUE);
1717         d = filelist_sort_path(d);
1718         filelist_recursive_append_full(&list, d, method, ascend);
1719         filelist_free(d);
1720
1721         return list;
1722 }
1723
1724 /*
1725  *-----------------------------------------------------------------------------
1726  * file modification support
1727  *-----------------------------------------------------------------------------
1728  */
1729
1730
1731 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1732 {
1733         if (!fdci && fd) fdci = fd->change;
1734
1735         if (!fdci) return;
1736
1737         g_free(fdci->source);
1738         g_free(fdci->dest);
1739
1740         g_free(fdci);
1741
1742         if (fd) fd->change = NULL;
1743 }
1744
1745 static gboolean file_data_can_write_directly(FileData *fd)
1746 {
1747         return filter_name_is_writable(fd->extension);
1748 }
1749
1750 static gboolean file_data_can_write_sidecar(FileData *fd)
1751 {
1752         return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1753 }
1754
1755 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1756 {
1757         gchar *sidecar_path = NULL;
1758         GList *work;
1759
1760         if (!file_data_can_write_sidecar(fd)) return NULL;
1761
1762         work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1763         gchar *extended_extension = g_strconcat(fd->parent ? fd->parent->extension : fd->extension, ".xmp", NULL);
1764         while (work)
1765                 {
1766                 FileData *sfd = work->data;
1767                 work = work->next;
1768                 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0 || g_ascii_strcasecmp(sfd->extension, extended_extension) == 0)
1769                         {
1770                         sidecar_path = g_strdup(sfd->path);
1771                         break;
1772                         }
1773                 }
1774         g_free(extended_extension);
1775
1776         if (!existing_only && !sidecar_path)
1777                 {
1778                 if (options->metadata.sidecar_extended_name)
1779                         sidecar_path = g_strconcat(fd->path, ".xmp", NULL);
1780                 else
1781                         {
1782                         gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1783                         sidecar_path = g_strconcat(base, ".xmp", NULL);
1784                         g_free(base);
1785                         }
1786                 }
1787
1788         return sidecar_path;
1789 }
1790
1791 /*
1792  * marks and orientation
1793  */
1794
1795 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1796 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1797 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1798 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1799
1800 gboolean file_data_get_mark(FileData *fd, gint n)
1801 {
1802         gboolean valid = (fd->valid_marks & (1 << n));
1803
1804         if (file_data_get_mark_func[n] && !valid)
1805                 {
1806                 guint old = fd->marks;
1807                 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1808
1809                 if (!value != !(fd->marks & (1 << n)))
1810                         {
1811                         fd->marks = fd->marks ^ (1 << n);
1812                         }
1813
1814                 fd->valid_marks |= (1 << n);
1815                 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1816                         {
1817                         file_data_unref(fd);
1818                         }
1819                 else if (!old && fd->marks)
1820                         {
1821                         file_data_ref(fd);
1822                         }
1823                 }
1824
1825         return !!(fd->marks & (1 << n));
1826 }
1827
1828 guint file_data_get_marks(FileData *fd)
1829 {
1830         gint i;
1831         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1832         return fd->marks;
1833 }
1834
1835 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1836 {
1837         guint old;
1838         if (!value == !file_data_get_mark(fd, n)) return;
1839
1840         if (file_data_set_mark_func[n])
1841                 {
1842                 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1843                 }
1844
1845         old = fd->marks;
1846
1847         fd->marks = fd->marks ^ (1 << n);
1848
1849         if (old && !fd->marks) /* keep files with non-zero marks in memory */
1850                 {
1851                 file_data_unref(fd);
1852                 }
1853         else if (!old && fd->marks)
1854                 {
1855                 file_data_ref(fd);
1856                 }
1857
1858         file_data_increment_version(fd);
1859         file_data_send_notification(fd, NOTIFY_MARKS);
1860 }
1861
1862 gboolean file_data_filter_marks(FileData *fd, guint filter)
1863 {
1864         gint i;
1865         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1866         return ((fd->marks & filter) == filter);
1867 }
1868
1869 GList *file_data_filter_marks_list(GList *list, guint filter)
1870 {
1871         GList *work;
1872
1873         work = list;
1874         while (work)
1875                 {
1876                 FileData *fd = work->data;
1877                 GList *link = work;
1878                 work = work->next;
1879
1880                 if (!file_data_filter_marks(fd, filter))
1881                         {
1882                         list = g_list_remove_link(list, link);
1883                         file_data_unref(fd);
1884                         g_list_free(link);
1885                         }
1886                 }
1887
1888         return list;
1889 }
1890
1891 gboolean file_data_filter_file_filter(FileData *fd, GRegex *filter)
1892 {
1893         return g_regex_match(filter, fd->name, 0, NULL);
1894 }
1895
1896 GList *file_data_filter_file_filter_list(GList *list, GRegex *filter)
1897 {
1898         GList *work;
1899
1900         work = list;
1901         while (work)
1902                 {
1903                 FileData *fd = work->data;
1904                 GList *link = work;
1905                 work = work->next;
1906
1907                 if (!file_data_filter_file_filter(fd, filter))
1908                         {
1909                         list = g_list_remove_link(list, link);
1910                         file_data_unref(fd);
1911                         g_list_free(link);
1912                         }
1913                 }
1914
1915         return list;
1916 }
1917
1918 static gboolean file_data_filter_class(FileData *fd, guint filter)
1919 {
1920         gint i;
1921
1922         for (i = 0; i < FILE_FORMAT_CLASSES; i++)
1923                 {
1924                 if (filter & (1 << i))
1925                         {
1926                         if ((FileFormatClass)i == filter_file_get_class(fd->path))
1927                                 {
1928                                 return TRUE;
1929                                 }
1930                         }
1931                 }
1932
1933         return FALSE;
1934 }
1935
1936 GList *file_data_filter_class_list(GList *list, guint filter)
1937 {
1938         GList *work;
1939
1940         work = list;
1941         while (work)
1942                 {
1943                 FileData *fd = work->data;
1944                 GList *link = work;
1945                 work = work->next;
1946
1947                 if (!file_data_filter_class(fd, filter))
1948                         {
1949                         list = g_list_remove_link(list, link);
1950                         file_data_unref(fd);
1951                         g_list_free(link);
1952                         }
1953                 }
1954
1955         return list;
1956 }
1957
1958 static void file_data_notify_mark_func(gpointer UNUSED(key), gpointer value, gpointer UNUSED(user_data))
1959 {
1960         FileData *fd = value;
1961         file_data_increment_version(fd);
1962         file_data_send_notification(fd, NOTIFY_MARKS);
1963 }
1964
1965 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1966 {
1967         if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1968
1969         if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1970
1971         file_data_get_mark_func[n] = get_mark_func;
1972         file_data_set_mark_func[n] = set_mark_func;
1973         file_data_mark_func_data[n] = data;
1974         file_data_destroy_mark_func[n] = notify;
1975
1976         if (get_mark_func && file_data_pool)
1977                 {
1978                 /* this effectively changes all known files */
1979                 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1980                 }
1981
1982         return TRUE;
1983 }
1984
1985 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1986 {
1987         if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1988         if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1989         if (data) *data = file_data_mark_func_data[n];
1990 }
1991
1992 //gint file_data_get_user_orientation(FileData *fd)
1993 //{
1994         //return fd->user_orientation;
1995 //}
1996
1997 //void file_data_set_user_orientation(FileData *fd, gint value)
1998 //{
1999         //if (fd->user_orientation == value) return;
2000
2001         //fd->user_orientation = value;
2002         //file_data_increment_version(fd);
2003         //file_data_send_notification(fd, NOTIFY_ORIENTATION);
2004 //}
2005
2006
2007 /*
2008  * file_data    - operates on the given fd
2009  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
2010  */
2011
2012
2013 /* return list of sidecar file extensions in a string */
2014 gchar *file_data_sc_list_to_string(FileData *fd)
2015 {
2016         GList *work;
2017         GString *result = g_string_new("");
2018
2019         work = fd->sidecar_files;
2020         while (work)
2021                 {
2022                 FileData *sfd = work->data;
2023
2024                 result = g_string_append(result, "+ ");
2025                 result = g_string_append(result, sfd->extension);
2026                 work = work->next;
2027                 if (work) result = g_string_append_c(result, ' ');
2028                 }
2029
2030         return g_string_free(result, FALSE);
2031 }
2032
2033
2034
2035 /*
2036  * add FileDataChangeInfo (see typedefs.h) for the given operation
2037  * uses file_data_add_change_info
2038  *
2039  * fails if the fd->change already exists - change operations can't run in parallel
2040  * fd->change_info works as a lock
2041  *
2042  * dest can be NULL - in this case the current name is used for now, it will
2043  * be changed later
2044  */
2045
2046 /*
2047    FileDataChangeInfo types:
2048    COPY
2049    MOVE   - path is changed, name may be changed too
2050    RENAME - path remains unchanged, name is changed
2051             extension should remain (FIXME should we allow editing extension? it will make problems with grouping)
2052             sidecar names are changed too, extensions are not changed
2053    DELETE
2054    UPDATE - file size, date or grouping has been changed
2055 */
2056
2057 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
2058 {
2059         FileDataChangeInfo *fdci;
2060
2061         if (fd->change) return FALSE;
2062
2063         fdci = g_new0(FileDataChangeInfo, 1);
2064
2065         fdci->type = type;
2066
2067         if (src)
2068                 fdci->source = g_strdup(src);
2069         else
2070                 fdci->source = g_strdup(fd->path);
2071
2072         if (dest)
2073                 fdci->dest = g_strdup(dest);
2074
2075         fd->change = fdci;
2076
2077         return TRUE;
2078 }
2079
2080 static void file_data_planned_change_remove(FileData *fd)
2081 {
2082         if (file_data_planned_change_hash &&
2083             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
2084                 {
2085                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
2086                         {
2087                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
2088                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
2089                         file_data_unref(fd);
2090                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
2091                                 {
2092                                 g_hash_table_destroy(file_data_planned_change_hash);
2093                                 file_data_planned_change_hash = NULL;
2094                                 DEBUG_1("planned change: empty");
2095                                 }
2096                         }
2097                 }
2098 }
2099
2100
2101 void file_data_free_ci(FileData *fd)
2102 {
2103         FileDataChangeInfo *fdci = fd->change;
2104
2105         if (!fdci) return;
2106
2107         file_data_planned_change_remove(fd);
2108
2109         if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
2110
2111         g_free(fdci->source);
2112         g_free(fdci->dest);
2113
2114         g_free(fdci);
2115
2116         fd->change = NULL;
2117 }
2118
2119 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
2120 {
2121         FileDataChangeInfo *fdci = fd->change;
2122         if (!fdci) return;
2123         fdci->regroup_when_finished = enable;
2124 }
2125
2126 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
2127 {
2128         GList *work;
2129
2130         if (fd->parent) fd = fd->parent;
2131
2132         if (fd->change) return FALSE;
2133
2134         work = fd->sidecar_files;
2135         while (work)
2136                 {
2137                 FileData *sfd = work->data;
2138
2139                 if (sfd->change) return FALSE;
2140                 work = work->next;
2141                 }
2142
2143         file_data_add_ci(fd, type, NULL, NULL);
2144
2145         work = fd->sidecar_files;
2146         while (work)
2147                 {
2148                 FileData *sfd = work->data;
2149
2150                 file_data_add_ci(sfd, type, NULL, NULL);
2151                 work = work->next;
2152                 }
2153
2154         return TRUE;
2155 }
2156
2157 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
2158 {
2159         GList *work;
2160
2161         if (fd->parent) fd = fd->parent;
2162
2163         if (!fd->change || fd->change->type != type) return FALSE;
2164
2165         work = fd->sidecar_files;
2166         while (work)
2167                 {
2168                 FileData *sfd = work->data;
2169
2170                 if (!sfd->change || sfd->change->type != type) return FALSE;
2171                 work = work->next;
2172                 }
2173
2174         return TRUE;
2175 }
2176
2177
2178 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
2179 {
2180         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
2181         file_data_sc_update_ci_copy(fd, dest_path);
2182         return TRUE;
2183 }
2184
2185 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
2186 {
2187         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
2188         file_data_sc_update_ci_move(fd, dest_path);
2189         return TRUE;
2190 }
2191
2192 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
2193 {
2194         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
2195         file_data_sc_update_ci_rename(fd, dest_path);
2196         return TRUE;
2197 }
2198
2199 gboolean file_data_sc_add_ci_delete(FileData *fd)
2200 {
2201         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
2202 }
2203
2204 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
2205 {
2206         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
2207         file_data_sc_update_ci_unspecified(fd, dest_path);
2208         return TRUE;
2209 }
2210
2211 gboolean file_data_add_ci_write_metadata(FileData *fd)
2212 {
2213         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
2214 }
2215
2216 void file_data_sc_free_ci(FileData *fd)
2217 {
2218         GList *work;
2219
2220         if (fd->parent) fd = fd->parent;
2221
2222         file_data_free_ci(fd);
2223
2224         work = fd->sidecar_files;
2225         while (work)
2226                 {
2227                 FileData *sfd = work->data;
2228
2229                 file_data_free_ci(sfd);
2230                 work = work->next;
2231                 }
2232 }
2233
2234 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
2235 {
2236         GList *work;
2237         gboolean ret = TRUE;
2238
2239         work = fd_list;
2240         while (work)
2241                 {
2242                 FileData *fd = work->data;
2243
2244                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
2245                 work = work->next;
2246                 }
2247
2248         return ret;
2249 }
2250
2251 static void file_data_sc_revert_ci_list(GList *fd_list)
2252 {
2253         GList *work;
2254
2255         work = fd_list;
2256         while (work)
2257                 {
2258                 FileData *fd = work->data;
2259
2260                 file_data_sc_free_ci(fd);
2261                 work = work->prev;
2262                 }
2263 }
2264
2265 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
2266 {
2267         GList *work;
2268
2269         work = fd_list;
2270         while (work)
2271                 {
2272                 FileData *fd = work->data;
2273
2274                 if (!func(fd, dest))
2275                         {
2276                         file_data_sc_revert_ci_list(work->prev);
2277                         return FALSE;
2278                         }
2279                 work = work->next;
2280                 }
2281
2282         return TRUE;
2283 }
2284
2285 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
2286 {
2287         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
2288 }
2289
2290 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
2291 {
2292         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
2293 }
2294
2295 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
2296 {
2297         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
2298 }
2299
2300 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
2301 {
2302         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
2303 }
2304
2305 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
2306 {
2307         GList *work;
2308         gboolean ret = TRUE;
2309
2310         work = fd_list;
2311         while (work)
2312                 {
2313                 FileData *fd = work->data;
2314
2315                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
2316                 work = work->next;
2317                 }
2318
2319         return ret;
2320 }
2321
2322 void file_data_free_ci_list(GList *fd_list)
2323 {
2324         GList *work;
2325
2326         work = fd_list;
2327         while (work)
2328                 {
2329                 FileData *fd = work->data;
2330
2331                 file_data_free_ci(fd);
2332                 work = work->next;
2333                 }
2334 }
2335
2336 void file_data_sc_free_ci_list(GList *fd_list)
2337 {
2338         GList *work;
2339
2340         work = fd_list;
2341         while (work)
2342                 {
2343                 FileData *fd = work->data;
2344
2345                 file_data_sc_free_ci(fd);
2346                 work = work->next;
2347                 }
2348 }
2349
2350 /*
2351  * update existing fd->change, it will be used from dialog callbacks for interactive editing
2352  * fails if fd->change does not exist or the change type does not match
2353  */
2354
2355 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
2356 {
2357         FileDataChangeType type = fd->change->type;
2358
2359         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2360                 {
2361                 FileData *ofd;
2362
2363                 if (!file_data_planned_change_hash)
2364                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
2365
2366                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
2367                         {
2368                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
2369                         g_hash_table_remove(file_data_planned_change_hash, old_path);
2370                         file_data_unref(fd);
2371                         }
2372
2373                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
2374                 if (ofd != fd)
2375                         {
2376                         if (ofd)
2377                                 {
2378                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
2379                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
2380                                 file_data_unref(ofd);
2381                                 }
2382
2383                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
2384                         file_data_ref(fd);
2385                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
2386                         }
2387                 }
2388 }
2389
2390 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
2391 {
2392         gchar *old_path = fd->change->dest;
2393
2394         fd->change->dest = g_strdup(dest_path);
2395         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2396         g_free(old_path);
2397 }
2398
2399 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
2400 {
2401         const gchar *extension = registered_extension_from_path(fd->change->source);
2402         gchar *base = remove_extension_from_path(dest_path);
2403         gchar *old_path = fd->change->dest;
2404
2405         fd->change->dest = g_strconcat(base, fd->extended_extension ? fd->extended_extension : extension, NULL);
2406         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
2407
2408         g_free(old_path);
2409         g_free(base);
2410 }
2411
2412 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
2413 {
2414         GList *work;
2415         gchar *dest_path_full = NULL;
2416
2417         if (fd->parent) fd = fd->parent;
2418
2419         if (!dest_path)
2420                 {
2421                 dest_path = fd->path;
2422                 }
2423         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
2424                 {
2425                 gchar *dir = remove_level_from_path(fd->path);
2426
2427                 dest_path_full = g_build_filename(dir, dest_path, NULL);
2428                 g_free(dir);
2429                 dest_path = dest_path_full;
2430                 }
2431         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
2432                 {
2433                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
2434                 dest_path = dest_path_full;
2435                 }
2436
2437         file_data_update_ci_dest(fd, dest_path);
2438
2439         work = fd->sidecar_files;
2440         while (work)
2441                 {
2442                 FileData *sfd = work->data;
2443
2444                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
2445                 work = work->next;
2446                 }
2447
2448         g_free(dest_path_full);
2449 }
2450
2451 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
2452 {
2453         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2454         file_data_sc_update_ci(fd, dest_path);
2455         return TRUE;
2456 }
2457
2458 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
2459 {
2460         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
2461 }
2462
2463 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
2464 {
2465         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
2466 }
2467
2468 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
2469 {
2470         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
2471 }
2472
2473 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
2474 {
2475         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
2476 }
2477
2478 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
2479                                                       const gchar *dest,
2480                                                       gboolean (*func)(FileData *, const gchar *))
2481 {
2482         GList *work;
2483         gboolean ret = TRUE;
2484
2485         work = fd_list;
2486         while (work)
2487                 {
2488                 FileData *fd = work->data;
2489
2490                 if (!func(fd, dest)) ret = FALSE;
2491                 work = work->next;
2492                 }
2493
2494         return ret;
2495 }
2496
2497 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
2498 {
2499         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
2500 }
2501
2502 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
2503 {
2504         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
2505 }
2506
2507 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
2508 {
2509         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
2510 }
2511
2512
2513 /*
2514  * verify source and dest paths - dest image exists, etc.
2515  * it should detect all possible problems with the planned operation
2516  */
2517
2518 gint file_data_verify_ci(FileData *fd, GList *list)
2519 {
2520         gint ret = CHANGE_OK;
2521         gchar *dir;
2522         GList *work = NULL;
2523         FileData *fd1 = NULL;
2524
2525         if (!fd->change)
2526                 {
2527                 DEBUG_1("Change checked: no change info: %s", fd->path);
2528                 return ret;
2529                 }
2530
2531         if (!isname(fd->path))
2532                 {
2533                 /* this probably should not happen */
2534                 ret |= CHANGE_NO_SRC;
2535                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
2536                 return ret;
2537                 }
2538
2539         dir = remove_level_from_path(fd->path);
2540
2541         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2542             fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
2543             fd->change->type != FILEDATA_CHANGE_RENAME &&
2544             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2545             fd->modified_xmp)
2546                 {
2547                 ret |= CHANGE_WARN_UNSAVED_META;
2548                 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
2549                 }
2550
2551         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
2552             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2553             !access_file(fd->path, R_OK))
2554                 {
2555                 ret |= CHANGE_NO_READ_PERM;
2556                 DEBUG_1("Change checked: no read permission: %s", fd->path);
2557                 }
2558         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
2559                  !access_file(dir, W_OK))
2560                 {
2561                 ret |= CHANGE_NO_WRITE_PERM_DIR;
2562                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
2563                 }
2564         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
2565                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
2566                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
2567                  !access_file(fd->path, W_OK))
2568                 {
2569                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2570                 DEBUG_1("Change checked: no write permission: %s", fd->path);
2571                 }
2572         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
2573            - that means that there are no hard errors and warnings can be disabled
2574            - the destination is determined during the check
2575         */
2576         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
2577                 {
2578                 /* determine destination file */
2579                 gboolean have_dest = FALSE;
2580                 gchar *dest_dir = NULL;
2581
2582                 if (options->metadata.save_in_image_file)
2583                         {
2584                         if (file_data_can_write_directly(fd))
2585                                 {
2586                                 /* we can write the file directly */
2587                                 if (access_file(fd->path, W_OK))
2588                                         {
2589                                         have_dest = TRUE;
2590                                         }
2591                                 else
2592                                         {
2593                                         if (options->metadata.warn_on_write_problems)
2594                                                 {
2595                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2596                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2597                                                 }
2598                                         }
2599                                 }
2600                         else if (file_data_can_write_sidecar(fd))
2601                                 {
2602                                 /* we can write sidecar */
2603                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2604                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2605                                         {
2606                                         file_data_update_ci_dest(fd, sidecar);
2607                                         have_dest = TRUE;
2608                                         }
2609                                 else
2610                                         {
2611                                         if (options->metadata.warn_on_write_problems)
2612                                                 {
2613                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
2614                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2615                                                 }
2616                                         }
2617                                 g_free(sidecar);
2618                                 }
2619                         }
2620
2621                 if (!have_dest)
2622                         {
2623                         /* write private metadata file under ~/.geeqie */
2624
2625                         /* If an existing metadata file exists, we will try writing to
2626                          * it's location regardless of the user's preference.
2627                          */
2628                         gchar *metadata_path = NULL;
2629 #ifdef HAVE_EXIV2
2630                         /* but ignore XMP if we are not able to write it */
2631                         metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2632 #endif
2633                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2634
2635                         if (metadata_path && !access_file(metadata_path, W_OK))
2636                                 {
2637                                 g_free(metadata_path);
2638                                 metadata_path = NULL;
2639                                 }
2640
2641                         if (!metadata_path)
2642                                 {
2643                                 mode_t mode = 0755;
2644
2645                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2646                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2647                                         {
2648                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2649
2650                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
2651                                         g_free(filename);
2652                                         }
2653                                 }
2654                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2655                                 {
2656                                 file_data_update_ci_dest(fd, metadata_path);
2657                                 have_dest = TRUE;
2658                                 }
2659                         else
2660                                 {
2661                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
2662                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2663                                 }
2664                         g_free(metadata_path);
2665                         }
2666                 g_free(dest_dir);
2667                 }
2668
2669         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2670                 {
2671                 gboolean same;
2672                 gchar *dest_dir;
2673
2674                 same = (strcmp(fd->path, fd->change->dest) == 0);
2675
2676                 if (!same)
2677                         {
2678                         const gchar *dest_ext = registered_extension_from_path(fd->change->dest);
2679                         if (!dest_ext) dest_ext = "";
2680                         if (!options->file_filter.disable_file_extension_checks)
2681                                 {
2682                                 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2683                                         {
2684                                         ret |= CHANGE_WARN_CHANGED_EXT;
2685                                         DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2686                                         }
2687                                 }
2688                         }
2689                 else
2690                         {
2691                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /** @FIXME this is now needed for running editors */
2692                                 {
2693                                 ret |= CHANGE_WARN_SAME;
2694                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2695                                 }
2696                         }
2697
2698                 dest_dir = remove_level_from_path(fd->change->dest);
2699
2700                 if (!isdir(dest_dir))
2701                         {
2702                         ret |= CHANGE_NO_DEST_DIR;
2703                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2704                         }
2705                 else if (!access_file(dest_dir, W_OK))
2706                         {
2707                         ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2708                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2709                         }
2710                 else if (!same)
2711                         {
2712                         if (isfile(fd->change->dest))
2713                                 {
2714                                 if (!access_file(fd->change->dest, W_OK))
2715                                         {
2716                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
2717                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2718                                         }
2719                                 else
2720                                         {
2721                                         ret |= CHANGE_WARN_DEST_EXISTS;
2722                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2723                                         }
2724                                 }
2725                         else if (isdir(fd->change->dest))
2726                                 {
2727                                 ret |= CHANGE_DEST_EXISTS;
2728                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2729                                 }
2730                         }
2731
2732                 g_free(dest_dir);
2733                 }
2734
2735         /* During a rename operation, check if another planned destination file has
2736          * the same filename
2737          */
2738         if(fd->change->type == FILEDATA_CHANGE_RENAME ||
2739                                 fd->change->type == FILEDATA_CHANGE_COPY ||
2740                                 fd->change->type == FILEDATA_CHANGE_MOVE)
2741                 {
2742                 work = list;
2743                 while (work)
2744                         {
2745                         fd1 = work->data;
2746                         work = work->next;
2747                         if (fd1 != NULL && fd != fd1 )
2748                                 {
2749                                 if (!strcmp(fd->change->dest, fd1->change->dest))
2750                                         {
2751                                         ret |= CHANGE_DUPLICATE_DEST;
2752                                         }
2753                                 }
2754                         }
2755                 }
2756
2757         fd->change->error = ret;
2758         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2759
2760         g_free(dir);
2761         return ret;
2762 }
2763
2764
2765 gint file_data_sc_verify_ci(FileData *fd, GList *list)
2766 {
2767         GList *work;
2768         gint ret;
2769
2770         ret = file_data_verify_ci(fd, list);
2771
2772         work = fd->sidecar_files;
2773         while (work)
2774                 {
2775                 FileData *sfd = work->data;
2776
2777                 ret |= file_data_verify_ci(sfd, list);
2778                 work = work->next;
2779                 }
2780
2781         return ret;
2782 }
2783
2784 gchar *file_data_get_error_string(gint error)
2785 {
2786         GString *result = g_string_new("");
2787
2788         if (error & CHANGE_NO_SRC)
2789                 {
2790                 if (result->len > 0) g_string_append(result, ", ");
2791                 g_string_append(result, _("file or directory does not exist"));
2792                 }
2793
2794         if (error & CHANGE_DEST_EXISTS)
2795                 {
2796                 if (result->len > 0) g_string_append(result, ", ");
2797                 g_string_append(result, _("destination already exists"));
2798                 }
2799
2800         if (error & CHANGE_NO_WRITE_PERM_DEST)
2801                 {
2802                 if (result->len > 0) g_string_append(result, ", ");
2803                 g_string_append(result, _("destination can't be overwritten"));
2804                 }
2805
2806         if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2807                 {
2808                 if (result->len > 0) g_string_append(result, ", ");
2809                 g_string_append(result, _("destination directory is not writable"));
2810                 }
2811
2812         if (error & CHANGE_NO_DEST_DIR)
2813                 {
2814                 if (result->len > 0) g_string_append(result, ", ");
2815                 g_string_append(result, _("destination directory does not exist"));
2816                 }
2817
2818         if (error & CHANGE_NO_WRITE_PERM_DIR)
2819                 {
2820                 if (result->len > 0) g_string_append(result, ", ");
2821                 g_string_append(result, _("source directory is not writable"));
2822                 }
2823
2824         if (error & CHANGE_NO_READ_PERM)
2825                 {
2826                 if (result->len > 0) g_string_append(result, ", ");
2827                 g_string_append(result, _("no read permission"));
2828                 }
2829
2830         if (error & CHANGE_WARN_NO_WRITE_PERM)
2831                 {
2832                 if (result->len > 0) g_string_append(result, ", ");
2833                 g_string_append(result, _("file is readonly"));
2834                 }
2835
2836         if (error & CHANGE_WARN_DEST_EXISTS)
2837                 {
2838                 if (result->len > 0) g_string_append(result, ", ");
2839                 g_string_append(result, _("destination already exists and will be overwritten"));
2840                 }
2841
2842         if (error & CHANGE_WARN_SAME)
2843                 {
2844                 if (result->len > 0) g_string_append(result, ", ");
2845                 g_string_append(result, _("source and destination are the same"));
2846                 }
2847
2848         if (error & CHANGE_WARN_CHANGED_EXT)
2849                 {
2850                 if (result->len > 0) g_string_append(result, ", ");
2851                 g_string_append(result, _("source and destination have different extension"));
2852                 }
2853
2854         if (error & CHANGE_WARN_UNSAVED_META)
2855                 {
2856                 if (result->len > 0) g_string_append(result, ", ");
2857                 g_string_append(result, _("there are unsaved metadata changes for the file"));
2858                 }
2859
2860         if (error & CHANGE_DUPLICATE_DEST)
2861                 {
2862                 if (result->len > 0) g_string_append(result, ", ");
2863                 g_string_append(result, _("another destination file has the same filename"));
2864                 }
2865
2866         return g_string_free(result, FALSE);
2867 }
2868
2869 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2870 {
2871         GList *work;
2872         gint all_errors = 0;
2873         gint common_errors = ~0;
2874         gint num;
2875         gint *errors;
2876         gint i;
2877
2878         if (!list) return 0;
2879
2880         num = g_list_length(list);
2881         errors = g_new(int, num);
2882         work = list;
2883         i = 0;
2884         while (work)
2885                 {
2886                 FileData *fd;
2887                 gint error;
2888
2889                 fd = work->data;
2890                 work = work->next;
2891
2892                 error = with_sidecars ? file_data_sc_verify_ci(fd, list) : file_data_verify_ci(fd, list);
2893                 all_errors |= error;
2894                 common_errors &= error;
2895
2896                 errors[i] = error;
2897
2898                 i++;
2899                 }
2900
2901         if (desc && all_errors)
2902                 {
2903                 GList *work;
2904                 GString *result = g_string_new("");
2905
2906                 if (common_errors)
2907                         {
2908                         gchar *str = file_data_get_error_string(common_errors);
2909                         g_string_append(result, str);
2910                         g_string_append(result, "\n");
2911                         g_free(str);
2912                         }
2913
2914                 work = list;
2915                 i = 0;
2916                 while (work)
2917                         {
2918                         FileData *fd;
2919                         gint error;
2920
2921                         fd = work->data;
2922                         work = work->next;
2923
2924                         error = errors[i] & ~common_errors;
2925
2926                         if (error)
2927                                 {
2928                                 gchar *str = file_data_get_error_string(error);
2929                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2930                                 g_free(str);
2931                                 }
2932                         i++;
2933                         }
2934                 *desc = g_string_free(result, FALSE);
2935                 }
2936
2937         g_free(errors);
2938         return all_errors;
2939 }
2940
2941
2942 /*
2943  * perform the change described by FileFataChangeInfo
2944  * it is used for internal operations,
2945  * this function actually operates with files on the filesystem
2946  * it should implement safe delete
2947  */
2948
2949 static gboolean file_data_perform_move(FileData *fd)
2950 {
2951         g_assert(!strcmp(fd->change->source, fd->path));
2952         return move_file(fd->change->source, fd->change->dest);
2953 }
2954
2955 static gboolean file_data_perform_copy(FileData *fd)
2956 {
2957         g_assert(!strcmp(fd->change->source, fd->path));
2958         return copy_file(fd->change->source, fd->change->dest);
2959 }
2960
2961 static gboolean file_data_perform_delete(FileData *fd)
2962 {
2963         if (isdir(fd->path) && !islink(fd->path))
2964                 return rmdir_utf8(fd->path);
2965         else
2966                 if (options->file_ops.safe_delete_enable)
2967                         return file_util_safe_unlink(fd->path);
2968                 else
2969                         return unlink_file(fd->path);
2970 }
2971
2972 gboolean file_data_perform_ci(FileData *fd)
2973 {
2974         FileDataChangeType type = fd->change->type;
2975
2976         switch (type)
2977                 {
2978                 case FILEDATA_CHANGE_MOVE:
2979                         return file_data_perform_move(fd);
2980                 case FILEDATA_CHANGE_COPY:
2981                         return file_data_perform_copy(fd);
2982                 case FILEDATA_CHANGE_RENAME:
2983                         return file_data_perform_move(fd); /* the same as move */
2984                 case FILEDATA_CHANGE_DELETE:
2985                         return file_data_perform_delete(fd);
2986                 case FILEDATA_CHANGE_WRITE_METADATA:
2987                         return metadata_write_perform(fd);
2988                 case FILEDATA_CHANGE_UNSPECIFIED:
2989                         /* nothing to do here */
2990                         break;
2991                 }
2992         return TRUE;
2993 }
2994
2995
2996
2997 gboolean file_data_sc_perform_ci(FileData *fd)
2998 {
2999         GList *work;
3000         gboolean ret = TRUE;
3001         FileDataChangeType type = fd->change->type;
3002
3003         if (!file_data_sc_check_ci(fd, type)) return FALSE;
3004
3005         work = fd->sidecar_files;
3006         while (work)
3007                 {
3008                 FileData *sfd = work->data;
3009
3010                 if (!file_data_perform_ci(sfd)) ret = FALSE;
3011                 work = work->next;
3012                 }
3013
3014         if (!file_data_perform_ci(fd)) ret = FALSE;
3015
3016         return ret;
3017 }
3018
3019 /*
3020  * updates FileData structure according to FileDataChangeInfo
3021  */
3022
3023 gboolean file_data_apply_ci(FileData *fd)
3024 {
3025         FileDataChangeType type = fd->change->type;
3026
3027         /** @FIXME delete ?*/
3028         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
3029                 {
3030                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
3031                 file_data_planned_change_remove(fd);
3032
3033                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
3034                         {
3035                         /* this change overwrites another file which is already known to other modules
3036                            renaming fd would create duplicate FileData structure
3037                            the best thing we can do is nothing
3038                         */
3039                         /**  @FIXME maybe we could copy stuff like marks
3040                         */
3041                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
3042                         }
3043                 else
3044                         {
3045                         file_data_set_path(fd, fd->change->dest);
3046                         }
3047                 }
3048         file_data_increment_version(fd);
3049         file_data_send_notification(fd, NOTIFY_CHANGE);
3050
3051         return TRUE;
3052 }
3053
3054 gboolean file_data_sc_apply_ci(FileData *fd)
3055 {
3056         GList *work;
3057         FileDataChangeType type = fd->change->type;
3058
3059         if (!file_data_sc_check_ci(fd, type)) return FALSE;
3060
3061         work = fd->sidecar_files;
3062         while (work)
3063                 {
3064                 FileData *sfd = work->data;
3065
3066                 file_data_apply_ci(sfd);
3067                 work = work->next;
3068                 }
3069
3070         file_data_apply_ci(fd);
3071
3072         return TRUE;
3073 }
3074
3075 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
3076 {
3077         GList *work;
3078         if (fd->parent) fd = fd->parent;
3079         if (!g_list_find(list, fd)) return FALSE;
3080
3081         work = fd->sidecar_files;
3082         while (work)
3083                 {
3084                 if (!g_list_find(list, work->data)) return FALSE;
3085                 work = work->next;
3086                 }
3087         return TRUE;
3088 }
3089
3090 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
3091 {
3092         GList *out = NULL;
3093         GList *work = list;
3094
3095         /* change partial groups to independent files */
3096         if (ungroup)
3097                 {
3098                 while (work)
3099                         {
3100                         FileData *fd = work->data;
3101                         work = work->next;
3102
3103                         if (!file_data_list_contains_whole_group(list, fd))
3104                                 {
3105                                 file_data_disable_grouping(fd, TRUE);
3106                                 if (ungrouped_list)
3107                                         {
3108                                         *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
3109                                         }
3110                                 }
3111                         }
3112                 }
3113
3114         /* remove sidecars from the list,
3115            they can be still accessed via main_fd->sidecar_files */
3116         work = list;
3117         while (work)
3118                 {
3119                 FileData *fd = work->data;
3120                 work = work->next;
3121
3122                 if (!fd->parent ||
3123                     (!ungroup && !file_data_list_contains_whole_group(list, fd)))
3124                         {
3125                         out = g_list_prepend(out, file_data_ref(fd));
3126                         }
3127                 }
3128
3129         filelist_free(list);
3130         out = g_list_reverse(out);
3131
3132         return out;
3133 }
3134
3135
3136
3137
3138
3139 /*
3140  * notify other modules about the change described by FileDataChangeInfo
3141  */
3142
3143 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks */
3144 /** @FIXME do we need the ignore_list? It looks like a workaround for ineffective
3145    implementation in view-file-list.cc */
3146
3147
3148 typedef struct _NotifyIdleData NotifyIdleData;
3149
3150 struct _NotifyIdleData {
3151         FileData *fd;
3152         NotifyType type;
3153 };
3154
3155
3156 typedef struct _NotifyData NotifyData;
3157
3158 struct _NotifyData {
3159         FileDataNotifyFunc func;
3160         gpointer data;
3161         NotifyPriority priority;
3162 };
3163
3164 static GList *notify_func_list = NULL;
3165
3166 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
3167 {
3168         NotifyData *nda = (NotifyData *)a;
3169         NotifyData *ndb = (NotifyData *)b;
3170
3171         if (nda->priority < ndb->priority) return -1;
3172         if (nda->priority > ndb->priority) return 1;
3173         return 0;
3174 }
3175
3176 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
3177 {
3178         NotifyData *nd;
3179         GList *work = notify_func_list;
3180
3181         while (work)
3182                 {
3183                 NotifyData *nd = (NotifyData *)work->data;
3184
3185                 if (nd->func == func && nd->data == data)
3186                         {
3187                         g_warning("Notify func already registered");
3188                         return FALSE;
3189                         }
3190                 work = work->next;
3191                 }
3192
3193         nd = g_new(NotifyData, 1);
3194         nd->func = func;
3195         nd->data = data;
3196         nd->priority = priority;
3197
3198         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
3199         DEBUG_2("Notify func registered: %p", (void *)nd);
3200
3201         return TRUE;
3202 }
3203
3204 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
3205 {
3206         GList *work = notify_func_list;
3207
3208         while (work)
3209                 {
3210                 NotifyData *nd = (NotifyData *)work->data;
3211
3212                 if (nd->func == func && nd->data == data)
3213                         {
3214                         notify_func_list = g_list_delete_link(notify_func_list, work);
3215                         g_free(nd);
3216                         DEBUG_2("Notify func unregistered: %p", (void *)nd);
3217                         return TRUE;
3218                         }
3219                 work = work->next;
3220                 }
3221
3222         g_warning("Notify func not found");
3223         return FALSE;
3224 }
3225
3226
3227 //gboolean file_data_send_notification_idle_cb(gpointer data)
3228 //{
3229         //NotifyIdleData *nid = (NotifyIdleData *)data;
3230         //GList *work = notify_func_list;
3231
3232         //while (work)
3233                 //{
3234                 //NotifyData *nd = (NotifyData *)work->data;
3235
3236                 //nd->func(nid->fd, nid->type, nd->data);
3237                 //work = work->next;
3238                 //}
3239         //file_data_unref(nid->fd);
3240         //g_free(nid);
3241         //return FALSE;
3242 //}
3243
3244 void file_data_send_notification(FileData *fd, NotifyType type)
3245 {
3246         GList *work = notify_func_list;
3247
3248         while (work)
3249                 {
3250                 NotifyData *nd = (NotifyData *)work->data;
3251
3252                 nd->func(fd, type, nd->data);
3253                 work = work->next;
3254                 }
3255     /*
3256         NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
3257         nid->fd = file_data_ref(fd);
3258         nid->type = type;
3259         g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
3260     */
3261 }
3262
3263 static GHashTable *file_data_monitor_pool = NULL;
3264 static guint realtime_monitor_id = 0; /* event source id */
3265
3266 static void realtime_monitor_check_cb(gpointer key, gpointer UNUSED(value), gpointer UNUSED(data))
3267 {
3268         FileData *fd = key;
3269
3270         file_data_check_changed_files(fd);
3271
3272         DEBUG_1("monitor %s", fd->path);
3273 }
3274
3275 static gboolean realtime_monitor_cb(gpointer UNUSED(data))
3276 {
3277         if (!options->update_on_time_change) return TRUE;
3278         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
3279         return TRUE;
3280 }
3281
3282 gboolean file_data_register_real_time_monitor(FileData *fd)
3283 {
3284         gint count;
3285
3286         file_data_ref(fd);
3287
3288         if (!file_data_monitor_pool)
3289                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
3290
3291         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3292
3293         DEBUG_1("Register realtime %d %s", count, fd->path);
3294
3295         count++;
3296         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3297
3298         if (!realtime_monitor_id)
3299                 {
3300                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
3301                 }
3302
3303         return TRUE;
3304 }
3305
3306 gboolean file_data_unregister_real_time_monitor(FileData *fd)
3307 {
3308         gint count;
3309
3310         g_assert(file_data_monitor_pool);
3311
3312         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
3313
3314         DEBUG_1("Unregister realtime %d %s", count, fd->path);
3315
3316         g_assert(count > 0);
3317
3318         count--;
3319
3320         if (count == 0)
3321                 g_hash_table_remove(file_data_monitor_pool, fd);
3322         else
3323                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
3324
3325         file_data_unref(fd);
3326
3327         if (g_hash_table_size(file_data_monitor_pool) == 0)
3328                 {
3329                 g_source_remove(realtime_monitor_id);
3330                 realtime_monitor_id = 0;
3331                 return FALSE;
3332                 }
3333
3334         return TRUE;
3335 }
3336
3337 /*
3338  *-----------------------------------------------------------------------------
3339  * Saving marks list, clearing marks
3340  * Uses file_data_pool
3341  *-----------------------------------------------------------------------------
3342  */
3343
3344 static void marks_get_files(gpointer key, gpointer value, gpointer userdata)
3345 {
3346         gchar *file_name = key;
3347         GString *result = userdata;
3348         FileData *fd;
3349
3350         if (isfile(file_name))
3351                 {
3352                 fd = value;
3353                 if (fd && fd->marks > 0)
3354                         {
3355                         g_string_append_printf(result, "%s,%i\n", fd->path, fd->marks);
3356                         }
3357                 }
3358 }
3359
3360 gboolean marks_list_load(const gchar *path)
3361 {
3362         FILE *f;
3363         gchar s_buf[1024];
3364         gchar *pathl;
3365         gchar *file_path;
3366         gchar *marks_value;
3367
3368         pathl = path_from_utf8(path);
3369         f = fopen(pathl, "r");
3370         g_free(pathl);
3371         if (!f) return FALSE;
3372
3373         /* first line must start with Marks comment */
3374         if (!fgets(s_buf, sizeof(s_buf), f) ||
3375                                         strncmp(s_buf, "#Marks", 6) != 0)
3376                 {
3377                 fclose(f);
3378                 return FALSE;
3379                 }
3380
3381         while (fgets(s_buf, sizeof(s_buf), f))
3382                 {
3383                 if (s_buf[0]=='#') continue;
3384                         file_path = strtok(s_buf, ",");
3385                         marks_value = strtok(NULL, ",");
3386                         if (isfile(file_path))
3387                                 {
3388                                 FileData *fd = file_data_new_no_grouping(file_path);
3389                                 file_data_ref(fd);
3390                                 gint n = 0;
3391                                 while (n <= 9)
3392                                         {
3393                                         gint mark_no = 1 << n;
3394                                         if (atoi(marks_value) & mark_no)
3395                                                 {
3396                                                 file_data_set_mark(fd, n , 1);
3397                                                 }
3398                                         n++;
3399                                         }
3400                                 }
3401                 }
3402
3403         fclose(f);
3404         return TRUE;
3405 }
3406
3407 gboolean marks_list_save(gchar *path, gboolean save)
3408 {
3409         SecureSaveInfo *ssi;
3410         gchar *pathl;
3411         GString  *marks = g_string_new("");
3412
3413         pathl = path_from_utf8(path);
3414         ssi = secure_open(pathl);
3415         g_free(pathl);
3416         if (!ssi)
3417                 {
3418                 log_printf(_("Error: Unable to write marks lists to: %s\n"), path);
3419                 return FALSE;
3420                 }
3421
3422         secure_fprintf(ssi, "#Marks lists\n");
3423
3424         if (save)
3425                 {
3426                 g_hash_table_foreach(file_data_pool, marks_get_files, marks);
3427                 }
3428         secure_fprintf(ssi, "%s", marks->str);
3429         g_string_free(marks, FALSE);
3430
3431         secure_fprintf(ssi, "#end\n");
3432         return (secure_close(ssi) == 0);
3433 }
3434
3435 static void marks_clear(gpointer key, gpointer value, gpointer UNUSED(userdata))
3436 {
3437         gchar *file_name = key;
3438         gint mark_no;
3439         gint n;
3440         FileData *fd;
3441
3442         if (isfile(file_name))
3443                 {
3444                 fd = value;
3445                 if (fd && fd->marks > 0)
3446                         {
3447                         n = 0;
3448                         while (n <= 9)
3449                                 {
3450                                 mark_no = 1 << n;
3451                                 if (fd->marks & mark_no)
3452                                         {
3453                                         file_data_set_mark(fd, n , 0);
3454                                         }
3455                                 n++;
3456                                 }
3457                         }
3458                 }
3459 }
3460
3461 void marks_clear_all()
3462 {
3463         g_hash_table_foreach(file_data_pool, marks_clear, NULL);
3464 }
3465
3466 void file_data_set_page_num(FileData *fd, gint page_num)
3467 {
3468         if (fd->page_total > 1 && page_num < 0)
3469                 {
3470                 fd->page_num = fd->page_total - 1;
3471                 }
3472         else if (fd->page_total > 1 && page_num <= fd->page_total)
3473                 {
3474                 fd->page_num = page_num - 1;
3475                 }
3476         else
3477                 {
3478                 fd->page_num = 0;
3479                 }
3480         file_data_send_notification(fd, NOTIFY_REREAD);
3481 }
3482
3483 void file_data_inc_page_num(FileData *fd)
3484 {
3485         if (fd->page_total > 0 && fd->page_num < fd->page_total - 1)
3486                 {
3487                 fd->page_num = fd->page_num + 1;
3488                 }
3489         else if (fd->page_total == 0)
3490                 {
3491                 fd->page_num = fd->page_num + 1;
3492                 }
3493         file_data_send_notification(fd, NOTIFY_REREAD);
3494 }
3495
3496 void file_data_dec_page_num(FileData *fd)
3497 {
3498         if (fd->page_num > 0)
3499                 {
3500                 fd->page_num = fd->page_num - 1;
3501                 }
3502         file_data_send_notification(fd, NOTIFY_REREAD);
3503 }
3504
3505 void file_data_set_page_total(FileData *fd, gint page_total)
3506 {
3507         fd->page_total = page_total;
3508 }
3509
3510 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */