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