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