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