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