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