metadata write mode (direct or sidecar) made configurable for each file
[geeqie.git] / src / filedata.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "filedata.h"
16
17 #include "filefilter.h"
18 #include "cache.h"
19 #include "thumb_standard.h"
20 #include "ui_fileops.h"
21 #include "metadata.h"
22 #include "trash.h"
23
24
25 static GHashTable *file_data_pool = NULL;
26 static GHashTable *file_data_planned_change_hash = NULL;
27
28 static gint sidecar_file_priority(const gchar *path);
29
30
31 /*
32  *-----------------------------------------------------------------------------
33  * text conversion utils
34  *-----------------------------------------------------------------------------
35  */
36
37 gchar *text_from_size(gint64 size)
38 {
39         gchar *a, *b;
40         gchar *s, *d;
41         gint l, n, i;
42
43         /* what I would like to use is printf("%'d", size)
44          * BUT: not supported on every libc :(
45          */
46         if (size > G_MAXUINT)
47                 {
48                 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
49                 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
50                 }
51         else
52                 {
53                 a = g_strdup_printf("%d", (guint)size);
54                 }
55         l = strlen(a);
56         n = (l - 1)/ 3;
57         if (n < 1) return a;
58
59         b = g_new(gchar, l + n + 1);
60
61         s = a;
62         d = b;
63         i = l - n * 3;
64         while (*s != '\0')
65                 {
66                 if (i < 1)
67                         {
68                         i = 3;
69                         *d = ',';
70                         d++;
71                         }
72
73                 *d = *s;
74                 s++;
75                 d++;
76                 i--;
77                 }
78         *d = '\0';
79
80         g_free(a);
81         return b;
82 }
83
84 gchar *text_from_size_abrev(gint64 size)
85 {
86         if (size < (gint64)1024)
87                 {
88                 return g_strdup_printf(_("%d bytes"), (gint)size);
89                 }
90         if (size < (gint64)1048576)
91                 {
92                 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
93                 }
94         if (size < (gint64)1073741824)
95                 {
96                 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
97                 }
98
99         /* to avoid overflowing the gdouble, do division in two steps */
100         size /= 1048576;
101         return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
102 }
103
104 /* note: returned string is valid until next call to text_from_time() */
105 const gchar *text_from_time(time_t t)
106 {
107         static gchar *ret = NULL;
108         gchar buf[128];
109         gint buflen;
110         struct tm *btime;
111         GError *error = NULL;
112
113         btime = localtime(&t);
114
115         /* the %x warning about 2 digit years is not an error */
116         buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
117         if (buflen < 1) return "";
118
119         g_free(ret);
120         ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
121         if (error)
122                 {
123                 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
124                 g_error_free(error);
125                 return "";
126                 }
127
128         return ret;
129 }
130
131 /*
132  *-----------------------------------------------------------------------------
133  * file info struct
134  *-----------------------------------------------------------------------------
135  */
136
137 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
138 static void file_data_check_sidecars(FileData *fd);
139 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
140
141
142 void file_data_increment_version(FileData *fd)
143 {
144         fd->version++;
145         fd->valid_marks = 0;
146         if (fd->parent) 
147                 {
148                 fd->parent->version++;
149                 fd->parent->valid_marks = 0;
150                 }
151 }
152
153 static void file_data_set_collate_keys(FileData *fd)
154 {
155         gchar *caseless_name;
156
157         caseless_name = g_utf8_casefold(fd->name, -1);
158
159         g_free(fd->collate_key_name);
160         g_free(fd->collate_key_name_nocase);
161
162 #if GLIB_CHECK_VERSION(2, 8, 0)
163         fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
164         fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
165 #else
166         fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
167         fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
168 #endif
169         g_free(caseless_name);
170 }
171
172 static void file_data_set_path(FileData *fd, const gchar *path)
173 {
174         g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
175         g_assert(file_data_pool);
176
177         g_free(fd->path);
178
179         if (fd->original_path)
180                 {
181                 g_hash_table_remove(file_data_pool, fd->original_path);
182                 g_free(fd->original_path);
183                 }
184
185         g_assert(!g_hash_table_lookup(file_data_pool, path));
186
187         fd->original_path = g_strdup(path);
188         g_hash_table_insert(file_data_pool, fd->original_path, fd);
189
190         if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
191                 {
192                 fd->path = g_strdup(path);
193                 fd->name = fd->path;
194                 fd->extension = fd->name + 1;
195                 file_data_set_collate_keys(fd);
196                 return;
197                 }
198
199         fd->path = g_strdup(path);
200         fd->name = filename_from_path(fd->path);
201
202         if (strcmp(fd->name, "..") == 0)
203                 {
204                 gchar *dir = remove_level_from_path(path);
205                 g_free(fd->path);
206                 fd->path = remove_level_from_path(dir);
207                 g_free(dir);
208                 fd->name = "..";
209                 fd->extension = fd->name + 2;
210                 file_data_set_collate_keys(fd);
211                 return;
212                 }
213         else if (strcmp(fd->name, ".") == 0)
214                 {
215                 g_free(fd->path);
216                 fd->path = remove_level_from_path(path);
217                 fd->name = ".";
218                 fd->extension = fd->name + 1;
219                 file_data_set_collate_keys(fd);
220                 return;
221                 }
222
223         fd->extension = extension_from_path(fd->path);
224         if (fd->extension == NULL)
225                 fd->extension = fd->name + strlen(fd->name);
226
227         file_data_set_collate_keys(fd);
228 }
229
230 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
231 {
232         gboolean ret = FALSE;
233         GList *work;
234         
235         if (fd->size != st->st_size ||
236             fd->date != st->st_mtime)
237                 {
238                 fd->size = st->st_size;
239                 fd->date = st->st_mtime;
240                 fd->mode = st->st_mode;
241                 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
242                 fd->thumb_pixbuf = NULL;
243                 file_data_increment_version(fd);
244                 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
245                 ret = TRUE;
246                 }
247
248         work = fd->sidecar_files;
249         while (work)
250                 {
251                 FileData *sfd = work->data;
252                 struct stat st;
253                 work = work->next;
254
255                 if (!stat_utf8(sfd->path, &st))
256                         {
257                         fd->size = 0;
258                         fd->date = 0;
259                         file_data_disconnect_sidecar_file(fd, sfd);
260                         ret = TRUE;
261                         continue;
262                         }
263
264                 ret |= file_data_check_changed_files_recursive(sfd, &st);
265                 }
266         return ret;
267 }
268
269
270 gboolean file_data_check_changed_files(FileData *fd)
271 {
272         gboolean ret = FALSE;
273         struct stat st;
274         
275         if (fd->parent) fd = fd->parent;
276
277         if (!stat_utf8(fd->path, &st))
278                 {
279                 GList *work;
280                 FileData *sfd = NULL;
281
282                 /* parent is missing, we have to rebuild whole group */
283                 ret = TRUE;
284                 fd->size = 0;
285                 fd->date = 0;
286                 
287                 work = fd->sidecar_files;
288                 while (work)
289                         {
290                         sfd = work->data;
291                         work = work->next;
292                 
293                         file_data_disconnect_sidecar_file(fd, sfd);
294                         }
295                 if (sfd) file_data_check_sidecars(sfd); /* this will group the sidecars back together */
296                 file_data_send_notification(fd, NOTIFY_TYPE_REREAD);
297                 }
298         else
299                 {
300                 ret |= file_data_check_changed_files_recursive(fd, &st);
301                 }
302
303         return ret;
304 }
305
306 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars)
307 {
308         FileData *fd;
309
310         DEBUG_2("file_data_new: '%s' %d", path_utf8, check_sidecars);
311
312         if (!file_data_pool)
313                 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
314
315         fd = g_hash_table_lookup(file_data_pool, path_utf8);
316         if (fd)
317                 {
318                 file_data_ref(fd);
319                 }
320                 
321         if (!fd && file_data_planned_change_hash)
322                 {
323                 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
324                 if (fd)
325                         {
326                         DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
327                         file_data_ref(fd);
328                         file_data_apply_ci(fd);
329                         }
330                 }
331                 
332         if (fd)
333                 {
334                 gboolean changed;
335                 
336                 if (fd->parent)
337                         changed = file_data_check_changed_files(fd);
338                 else
339                         changed = file_data_check_changed_files_recursive(fd, st);
340                 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
341                         file_data_check_sidecars(fd);
342                 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
343                 
344                 return fd;
345                 }
346
347         fd = g_new0(FileData, 1);
348         
349         fd->path = NULL;
350         fd->name = NULL;
351         fd->collate_key_name = NULL;
352         fd->collate_key_name_nocase = NULL;
353         fd->original_path = NULL;
354
355         fd->size = st->st_size;
356         fd->date = st->st_mtime;
357         fd->mode = st->st_mode;
358         fd->thumb_pixbuf = NULL;
359         fd->sidecar_files = NULL;
360         fd->ref = 1;
361         fd->magick = 0x12345678;
362
363         file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
364
365         if (check_sidecars)
366                 file_data_check_sidecars(fd);
367
368         return fd;
369 }
370
371 static void file_data_check_sidecars(FileData *fd)
372 {
373         gint base_len;
374         GString *fname;
375         FileData *parent_fd = NULL;
376         GList *work;
377
378         if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
379                 return;
380
381         base_len = fd->extension - fd->path;
382         fname = g_string_new_len(fd->path, base_len);
383         work = sidecar_ext_get_list();
384
385         while (work)
386                 {
387                 /* check for possible sidecar files;
388                    the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
389                    they have fd->ref set to 0 and file_data unref must chack and free them all together
390                    (using fd->ref would cause loops and leaks)
391                 */
392
393                 FileData *new_fd;
394                 gchar *ext = work->data;
395
396                 work = work->next;
397
398                 if (strcasecmp(ext, fd->extension) == 0)
399                         {
400                         new_fd = fd; /* processing the original file */
401                         }
402                 else
403                         {
404                         struct stat nst;
405                         g_string_truncate(fname, base_len);
406
407                         if (!stat_utf8_case_insensitive_ext(fname, ext, &nst))
408                                 continue;
409
410                         new_fd = file_data_new(fname->str, &nst, FALSE);
411                         
412                         if (new_fd->disable_grouping)
413                                 {
414                                 file_data_unref(new_fd);
415                                 continue;
416                                 }
417                         
418                         new_fd->ref--; /* do not use ref here */
419                         }
420
421                 if (!parent_fd)
422                         parent_fd = new_fd; /* parent is the one with the highest prio, found first */
423                 else
424                         file_data_merge_sidecar_files(parent_fd, new_fd);
425                 }
426         g_string_free(fname, TRUE);
427 }
428
429
430 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars)
431 {
432         gchar *path_utf8 = path_to_utf8(path);
433         FileData *ret = file_data_new(path_utf8, st, check_sidecars);
434
435         g_free(path_utf8);
436         return ret;
437 }
438
439 FileData *file_data_new_simple(const gchar *path_utf8)
440 {
441         struct stat st;
442
443         if (!stat_utf8(path_utf8, &st))
444                 {
445                 st.st_size = 0;
446                 st.st_mtime = 0;
447                 }
448
449         return file_data_new(path_utf8, &st, TRUE);
450 }
451
452 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
453 {
454         sfd->parent = target;
455         if (!g_list_find(target->sidecar_files, sfd))
456                 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
457         file_data_increment_version(sfd); /* increments both sfd and target */
458         return target;
459 }
460
461
462 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
463 {
464         GList *work;
465         
466         file_data_add_sidecar_file(target, source);
467
468         work = source->sidecar_files;
469         while (work)
470                 {
471                 FileData *sfd = work->data;
472                 file_data_add_sidecar_file(target, sfd);
473                 work = work->next;
474                 }
475
476         g_list_free(source->sidecar_files);
477         source->sidecar_files = NULL;
478
479         target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
480         
481         return target;
482 }
483
484 #ifdef DEBUG_FILEDATA
485 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
486 #else
487 FileData *file_data_ref(FileData *fd)
488 #endif
489 {
490         if (fd == NULL) return NULL;
491 #ifdef DEBUG_FILEDATA
492         if (fd->magick != 0x12345678)
493                 DEBUG_0("fd magick mismatch at %s:%d", file, line);
494 #endif
495         g_assert(fd->magick == 0x12345678);
496         fd->ref++;
497
498 #ifdef DEBUG_FILEDATA
499         DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
500 #else
501         DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
502 #endif
503         return fd;
504 }
505
506 static void file_data_free(FileData *fd)
507 {
508         g_assert(fd->magick == 0x12345678);
509         g_assert(fd->ref == 0);
510
511         g_hash_table_remove(file_data_pool, fd->original_path);
512
513         g_free(fd->path);
514         g_free(fd->original_path);
515         g_free(fd->collate_key_name);
516         g_free(fd->collate_key_name_nocase);
517         if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
518
519         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
520
521         file_data_change_info_free(NULL, fd);
522         g_free(fd);
523 }
524
525 #ifdef DEBUG_FILEDATA
526 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
527 #else
528 void file_data_unref(FileData *fd)
529 #endif
530 {
531         if (fd == NULL) return;
532 #ifdef DEBUG_FILEDATA
533         if (fd->magick != 0x12345678)
534                 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
535 #endif
536         g_assert(fd->magick == 0x12345678);
537         
538         fd->ref--;
539 #ifdef DEBUG_FILEDATA
540         DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
541
542 #else
543         DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
544 #endif
545         if (fd->ref == 0)
546                 {
547                 GList *work;
548                 FileData *parent = fd->parent ? fd->parent : fd;
549                 
550                 if (parent->ref > 0)
551                         return;
552
553                 work = parent->sidecar_files;
554                 while (work)
555                         {
556                         FileData *sfd = work->data;
557                         if (sfd->ref > 0)
558                                 return;
559                         work = work->next;
560                         }
561
562                 /* none of parent/children is referenced, we can free everything */
563
564                 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
565
566                 work = parent->sidecar_files;
567                 while (work)
568                         {
569                         FileData *sfd = work->data;
570                         file_data_free(sfd);
571                         work = work->next;
572                         }
573
574                 g_list_free(parent->sidecar_files);
575                 parent->sidecar_files = NULL;
576
577                 file_data_free(parent);
578                 }
579 }
580
581 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
582 {
583         sfd->parent = target;
584         g_assert(g_list_find(target->sidecar_files, sfd));
585         
586         file_data_increment_version(sfd); /* increments both sfd and target */
587
588         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
589         sfd->parent = NULL;
590
591         if (sfd->ref == 0)
592                 {
593                 file_data_free(sfd);
594                 return NULL;
595                 }
596
597         return sfd;
598 }
599
600 /* disables / enables grouping for particular file, sends UPDATE notification */
601 void file_data_disable_grouping(FileData *fd, gboolean disable)
602 {
603         if (!fd->disable_grouping == !disable) return;
604         fd->disable_grouping = !!disable;
605         
606         if (disable)
607                 {
608                 if (fd->parent)
609                         {
610                         FileData *parent = file_data_ref(fd->parent);
611                         file_data_disconnect_sidecar_file(parent, fd);
612                         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
613                         file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
614                         file_data_unref(parent);
615                         }
616                 else if (fd->sidecar_files)
617                         {
618                         GList *sidecar_files = filelist_copy(fd->sidecar_files);
619                         GList *work = sidecar_files;
620                         while (work)
621                                 {
622                                 FileData *sfd = work->data;
623                                 work = work->next;
624                                 file_data_disconnect_sidecar_file(fd, sfd);
625                                 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
626                                 }
627                         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
628                         file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
629                         filelist_free(sidecar_files);
630                         }
631                 }
632         else
633                 {
634                 file_data_check_sidecars(fd);
635                 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
636                 }
637 }
638
639 /* compare name without extension */
640 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
641 {
642         size_t len1 = fd1->extension - fd1->name;
643         size_t len2 = fd2->extension - fd2->name;
644
645         if (len1 < len2) return -1;
646         if (len1 > len2) return 1;
647
648         return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
649 }
650
651 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
652 {
653         if (!fdci && fd)
654                 fdci = fd->change;
655
656         if (!fdci)
657                 return;
658
659         g_free(fdci->source);
660         g_free(fdci->dest);
661
662         g_free(fdci);
663
664         if (fd)
665                 fd->change = NULL;
666 }
667
668 static gboolean file_data_can_write_directly(FileData *fd)
669 {
670         return filter_name_is_writable(fd->extension);
671 }
672
673 static gboolean file_data_can_write_sidecar(FileData *fd)
674 {
675         return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
676 }
677
678 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
679 {
680         gchar *sidecar_path = NULL;
681         GList *work;
682         if (!file_data_can_write_sidecar(fd)) return NULL;
683         
684         work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
685         while (work)
686                 {
687                 FileData *sfd = work->data;
688                 work = work->next;
689                 if (strcasecmp(sfd->extension, ".xmp") == 0)
690                         {
691                         sidecar_path = g_strdup(sfd->path);
692                         break;
693                         }
694                 }
695         
696         if (!existing_only && !sidecar_path)
697                 {
698                 gchar *base = remove_extension_from_path(fd->path);
699                 sidecar_path = g_strconcat(base, ".xmp", NULL);
700                 g_free(base);
701                 }
702
703         return sidecar_path;
704 }
705
706
707 /*
708  *-----------------------------------------------------------------------------
709  * sidecar file info struct
710  *-----------------------------------------------------------------------------
711  */
712
713
714
715 static gint sidecar_file_priority(const gchar *path)
716 {
717         const gchar *extension = extension_from_path(path);
718         gint i = 1;
719         GList *work;
720
721         if (extension == NULL)
722                 return 0;
723
724         work = sidecar_ext_get_list();
725
726         while (work) {
727                 gchar *ext = work->data;
728                 
729                 work = work->next;
730                 if (strcasecmp(extension, ext) == 0) return i;
731                 i++;
732         }
733         return 0;
734 }
735
736
737 /*
738  *-----------------------------------------------------------------------------
739  * load file list
740  *-----------------------------------------------------------------------------
741  */
742
743 static SortType filelist_sort_method = SORT_NONE;
744 static gint filelist_sort_ascend = TRUE;
745
746
747 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
748 {
749         if (!filelist_sort_ascend)
750                 {
751                 FileData *tmp = fa;
752                 fa = fb;
753                 fb = tmp;
754                 }
755
756         switch (filelist_sort_method)
757                 {
758                 case SORT_NAME:
759                         break;
760                 case SORT_SIZE:
761                         if (fa->size < fb->size) return -1;
762                         if (fa->size > fb->size) return 1;
763                         /* fall back to name */
764                         break;
765                 case SORT_TIME:
766                         if (fa->date < fb->date) return -1;
767                         if (fa->date > fb->date) return 1;
768                         /* fall back to name */
769                         break;
770 #ifdef HAVE_STRVERSCMP
771                 case SORT_NUMBER:
772                         return strverscmp(fa->name, fb->name);
773                         break;
774 #endif
775                 default:
776                         break;
777                 }
778
779         if (options->file_sort.case_sensitive)
780                 return strcmp(fa->collate_key_name, fb->collate_key_name);
781         else
782                 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
783 }
784
785 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
786 {
787         filelist_sort_method = method;
788         filelist_sort_ascend = ascend;
789         return filelist_sort_compare_filedata(fa, fb);
790 }
791
792 static gint filelist_sort_file_cb(gpointer a, gpointer b)
793 {
794         return filelist_sort_compare_filedata(a, b);
795 }
796
797 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
798 {
799         filelist_sort_method = method;
800         filelist_sort_ascend = ascend;
801         return g_list_sort(list, cb);
802 }
803
804 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gint ascend, GCompareFunc cb)
805 {
806         filelist_sort_method = method;
807         filelist_sort_ascend = ascend;
808         return g_list_insert_sorted(list, data, cb);
809 }
810
811 GList *filelist_sort(GList *list, SortType method, gint ascend)
812 {
813         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
814 }
815
816 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
817 {
818         return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
819 }
820
821
822 static GList *filelist_filter_out_sidecars(GList *flist)
823 {
824         GList *work = flist;
825         GList *flist_filtered = NULL;
826
827         while (work)
828                 {
829                 FileData *fd = work->data;
830         
831                 work = work->next;
832                 if (fd->parent) /* remove fd's that are children */
833                         file_data_unref(fd);
834                 else
835                         flist_filtered = g_list_prepend(flist_filtered, fd);
836                 }
837         g_list_free(flist);
838
839         return flist_filtered;
840 }
841
842 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
843 {
844         DIR *dp;
845         struct dirent *dir;
846         gchar *pathl;
847         GList *dlist = NULL;
848         GList *flist = NULL;
849         gint (*stat_func)(const gchar *path, struct stat *buf);
850
851         g_assert(files || dirs);
852
853         if (files) *files = NULL;
854         if (dirs) *dirs = NULL;
855
856         pathl = path_from_utf8(dir_fd->path);
857         if (!pathl) return FALSE;
858
859         dp = opendir(pathl);
860         if (dp == NULL)
861                 {
862                 g_free(pathl);
863                 return FALSE;
864                 }
865
866         if (follow_symlinks)
867                 stat_func = stat;
868         else
869                 stat_func = lstat;
870
871         while ((dir = readdir(dp)) != NULL)
872                 {
873                 struct stat ent_sbuf;
874                 const gchar *name = dir->d_name;
875                 gchar *filepath;
876
877                 if (!options->file_filter.show_hidden_files && ishidden(name))
878                         continue;
879
880                 filepath = g_build_filename(pathl, name, NULL);
881                 if (stat_func(filepath, &ent_sbuf) >= 0)
882                         {
883                         if (S_ISDIR(ent_sbuf.st_mode))
884                                 {
885                                 /* we ignore the .thumbnails dir for cleanliness */
886                                 if (dirs &&
887                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
888                                     strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
889                                     strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
890                                     strcmp(name, THUMB_FOLDER_LOCAL) != 0)
891                                         {
892                                         dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
893                                         }
894                                 }
895                         else
896                                 {
897                                 if (files && filter_name_exists(name))
898                                         {
899                                         flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
900                                         }
901                                 }
902                         }
903                 g_free(filepath);
904                 }
905
906         closedir(dp);
907         
908         g_free(pathl);
909
910         if (dirs) *dirs = dlist;
911         if (files) *files = filelist_filter_out_sidecars(flist);
912
913         return TRUE;
914 }
915
916 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
917 {
918         return filelist_read_real(dir_fd, files, dirs, TRUE);
919 }
920
921 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
922 {
923         return filelist_read_real(dir_fd, files, dirs, FALSE);
924 }
925
926 void filelist_free(GList *list)
927 {
928         GList *work;
929
930         work = list;
931         while (work)
932                 {
933                 file_data_unref((FileData *)work->data);
934                 work = work->next;
935                 }
936
937         g_list_free(list);
938 }
939
940
941 GList *filelist_copy(GList *list)
942 {
943         GList *new_list = NULL;
944         GList *work;
945
946         work = list;
947         while (work)
948                 {
949                 FileData *fd;
950
951                 fd = work->data;
952                 work = work->next;
953
954                 new_list = g_list_prepend(new_list, file_data_ref(fd));
955                 }
956
957         return g_list_reverse(new_list);
958 }
959
960 GList *filelist_from_path_list(GList *list)
961 {
962         GList *new_list = NULL;
963         GList *work;
964
965         work = list;
966         while (work)
967                 {
968                 gchar *path;
969
970                 path = work->data;
971                 work = work->next;
972
973                 new_list = g_list_prepend(new_list, file_data_new_simple(path));
974                 }
975
976         return g_list_reverse(new_list);
977 }
978
979 GList *filelist_to_path_list(GList *list)
980 {
981         GList *new_list = NULL;
982         GList *work;
983
984         work = list;
985         while (work)
986                 {
987                 FileData *fd;
988
989                 fd = work->data;
990                 work = work->next;
991
992                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
993                 }
994
995         return g_list_reverse(new_list);
996 }
997
998 GList *filelist_filter(GList *list, gint is_dir_list)
999 {
1000         GList *work;
1001
1002         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1003
1004         work = list;
1005         while (work)
1006                 {
1007                 FileData *fd = (FileData *)(work->data);
1008                 const gchar *name = fd->name;
1009
1010                 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
1011                     (!is_dir_list && !filter_name_exists(name)) ||
1012                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1013                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1014                         {
1015                         GList *link = work;
1016                         
1017                         list = g_list_remove_link(list, link);
1018                         file_data_unref(fd);
1019                         g_list_free(link);
1020                         }
1021         
1022                 work = work->next;
1023                 }
1024
1025         return list;
1026 }
1027
1028 /*
1029  *-----------------------------------------------------------------------------
1030  * filelist recursive
1031  *-----------------------------------------------------------------------------
1032  */
1033
1034 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1035 {
1036         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1037 }
1038
1039 GList *filelist_sort_path(GList *list)
1040 {
1041         return g_list_sort(list, filelist_sort_path_cb);
1042 }
1043
1044 static void filelist_recursive_append(GList **list, GList *dirs)
1045 {
1046         GList *work;
1047
1048         work = dirs;
1049         while (work)
1050                 {
1051                 FileData *fd = (FileData *)(work->data);
1052                 GList *f;
1053                 GList *d;
1054
1055                 if (filelist_read(fd, &f, &d))
1056                         {
1057                         f = filelist_filter(f, FALSE);
1058                         f = filelist_sort_path(f);
1059                         *list = g_list_concat(*list, f);
1060
1061                         d = filelist_filter(d, TRUE);
1062                         d = filelist_sort_path(d);
1063                         filelist_recursive_append(list, d);
1064                         filelist_free(d);
1065                         }
1066
1067                 work = work->next;
1068                 }
1069 }
1070
1071 GList *filelist_recursive(FileData *dir_fd)
1072 {
1073         GList *list;
1074         GList *d;
1075
1076         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1077         list = filelist_filter(list, FALSE);
1078         list = filelist_sort_path(list);
1079
1080         d = filelist_filter(d, TRUE);
1081         d = filelist_sort_path(d);
1082         filelist_recursive_append(&list, d);
1083         filelist_free(d);
1084
1085         return list;
1086 }
1087
1088
1089 /*
1090  * marks and orientation
1091  */
1092
1093 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1094 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1095 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1096
1097 gboolean file_data_get_mark(FileData *fd, gint n)
1098 {
1099         gboolean valid = (fd->valid_marks & (1 << n));
1100         if (file_data_get_mark_func[n] && !valid) 
1101                 {
1102                 guint old = fd->marks;
1103                 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1104                 if (!value != !(fd->marks & (1 << n))) 
1105                         {
1106                         fd->marks = fd->marks ^ (1 << n);
1107                         }
1108                 fd->valid_marks |= (1 << n);
1109                 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1110                         {
1111                         file_data_unref(fd);
1112                         }
1113                 else if (!old && fd->marks)
1114                         {
1115                         file_data_ref(fd);
1116                         }
1117                 }
1118
1119         return !!(fd->marks & (1 << n));
1120 }
1121
1122 guint file_data_get_marks(FileData *fd)
1123 {
1124         gint i;
1125         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1126         return fd->marks;
1127 }
1128
1129 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1130 {
1131         guint old;
1132         if (!value == !file_data_get_mark(fd, n)) return;
1133         
1134         if (file_data_set_mark_func[n]) 
1135                 {
1136                 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1137                 }
1138         
1139         old = fd->marks;
1140
1141         fd->marks = fd->marks ^ (1 << n);
1142         
1143         if (old && !fd->marks) /* keep files with non-zero marks in memory */
1144                 {
1145                 file_data_unref(fd);
1146                 }
1147         else if (!old && fd->marks)
1148                 {
1149                 file_data_ref(fd);
1150                 }
1151         
1152         file_data_increment_version(fd);
1153         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1154 }
1155
1156 gboolean file_data_filter_marks(FileData *fd, guint filter)
1157 {
1158         gint i;
1159         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1160         return ((fd->marks & filter) == filter);
1161 }
1162
1163 GList *file_data_filter_marks_list(GList *list, guint filter)
1164 {
1165         GList *work;
1166
1167         work = list;
1168         while (work)
1169                 {
1170                 FileData *fd = work->data;
1171                 GList *link = work;
1172                 work = work->next;
1173
1174                 if (!file_data_filter_marks(fd, filter))
1175                         {
1176                         list = g_list_remove_link(list, link);
1177                         file_data_unref(fd);
1178                         g_list_free(link);
1179                         }
1180                 }
1181
1182         return list;
1183 }
1184
1185 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1186 {
1187         FileData *fd = value;
1188         file_data_increment_version(fd);
1189         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1190 }
1191
1192 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data)
1193 {
1194         if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1195                 
1196         file_data_get_mark_func[n] = get_mark_func;
1197         file_data_set_mark_func[n] = set_mark_func;
1198         file_data_mark_func_data[n] = data;
1199         
1200         if (get_mark_func)
1201                 {
1202                 /* this effectively changes all known files */
1203                 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1204                 }
1205         
1206         return TRUE;
1207 }
1208
1209 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1210 {
1211         if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1212         if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1213         if (data) *data = file_data_mark_func_data[n];
1214 }
1215
1216 gint file_data_get_user_orientation(FileData *fd)
1217 {
1218         return fd->user_orientation;
1219 }
1220
1221 void file_data_set_user_orientation(FileData *fd, gint value)
1222 {
1223         if (fd->user_orientation == value) return;
1224
1225         fd->user_orientation = value;
1226         file_data_increment_version(fd);
1227         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1228 }
1229
1230
1231 /*
1232  * file_data    - operates on the given fd
1233  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1234  */
1235
1236
1237 /* return list of sidecar file extensions in a string */
1238 gchar *file_data_sc_list_to_string(FileData *fd)
1239 {
1240         GList *work;
1241         GString *result = g_string_new("");
1242
1243         work = fd->sidecar_files;
1244         while (work)
1245                 {
1246                 FileData *sfd = work->data;
1247
1248                 result = g_string_append(result, "+ ");
1249                 result = g_string_append(result, sfd->extension);
1250                 work = work->next;
1251                 if (work) result = g_string_append_c(result, ' ');
1252                 }
1253
1254         return g_string_free(result, FALSE);
1255 }
1256
1257
1258
1259 /*
1260  * add FileDataChangeInfo (see typedefs.h) for the given operation
1261  * uses file_data_add_change_info
1262  *
1263  * fails if the fd->change already exists - change operations can't run in parallel
1264  * fd->change_info works as a lock
1265  *
1266  * dest can be NULL - in this case the current name is used for now, it will
1267  * be changed later
1268  */
1269
1270 /*
1271    FileDataChangeInfo types:
1272    COPY
1273    MOVE   - path is changed, name may be changed too
1274    RENAME - path remains unchanged, name is changed
1275             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1276             sidecar names are changed too, extensions are not changed
1277    DELETE
1278    UPDATE - file size, date or grouping has been changed
1279 */
1280
1281 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1282 {
1283         FileDataChangeInfo *fdci;
1284
1285         if (fd->change) return FALSE;
1286
1287         fdci = g_new0(FileDataChangeInfo, 1);
1288
1289         fdci->type = type;
1290
1291         if (src)
1292                 fdci->source = g_strdup(src);
1293         else
1294                 fdci->source = g_strdup(fd->path);
1295
1296         if (dest)
1297                 fdci->dest = g_strdup(dest);
1298
1299         fd->change = fdci;
1300         
1301         return TRUE;
1302 }
1303
1304 static void file_data_planned_change_remove(FileData *fd)
1305 {
1306         if (file_data_planned_change_hash &&
1307             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1308                 {
1309                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1310                         {
1311                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1312                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1313                         file_data_unref(fd);
1314                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1315                                 {
1316                                 g_hash_table_destroy(file_data_planned_change_hash);
1317                                 file_data_planned_change_hash = NULL;
1318                                 DEBUG_1("planned change: empty");
1319                                 }
1320                         }
1321                 }
1322 }
1323
1324
1325 void file_data_free_ci(FileData *fd)
1326 {
1327         FileDataChangeInfo *fdci = fd->change;
1328
1329         if (!fdci)
1330                 return;
1331
1332         file_data_planned_change_remove(fd);
1333
1334         g_free(fdci->source);
1335         g_free(fdci->dest);
1336
1337         g_free(fdci);
1338
1339         fd->change = NULL;
1340 }
1341
1342
1343 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1344 {
1345         GList *work;
1346
1347         if (fd->parent) fd = fd->parent;
1348         
1349         if (fd->change) return FALSE;
1350         
1351         work = fd->sidecar_files;
1352         while (work)
1353                 {
1354                 FileData *sfd = work->data;
1355                 
1356                 if (sfd->change) return FALSE;
1357                 work = work->next;
1358                 }
1359
1360         file_data_add_ci(fd, type, NULL, NULL);
1361         
1362         work = fd->sidecar_files;
1363         while (work)
1364                 {
1365                 FileData *sfd = work->data;
1366                 
1367                 file_data_add_ci(sfd, type, NULL, NULL);
1368                 work = work->next;
1369                 }
1370                 
1371         return TRUE;
1372 }
1373
1374 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1375 {
1376         GList *work;
1377         
1378         if (fd->parent) fd = fd->parent;
1379         
1380         if (!fd->change || fd->change->type != type) return FALSE;
1381         
1382         work = fd->sidecar_files;
1383         while (work)
1384                 {
1385                 FileData *sfd = work->data;
1386
1387                 if (!sfd->change || sfd->change->type != type) return FALSE;
1388                 work = work->next;
1389                 }
1390
1391         return TRUE;
1392 }
1393
1394
1395 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1396 {
1397         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1398         file_data_sc_update_ci_copy(fd, dest_path);
1399         return TRUE;
1400 }
1401
1402 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1403 {
1404         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1405         file_data_sc_update_ci_move(fd, dest_path);
1406         return TRUE;
1407 }
1408
1409 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1410 {
1411         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1412         file_data_sc_update_ci_rename(fd, dest_path);
1413         return TRUE;
1414 }
1415
1416 gboolean file_data_sc_add_ci_delete(FileData *fd)
1417 {
1418         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1419 }
1420
1421 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1422 {
1423         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1424         file_data_sc_update_ci_unspecified(fd, dest_path);
1425         return TRUE;
1426 }
1427
1428 gboolean file_data_add_ci_write_metadata(FileData *fd)
1429 {
1430         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1431 }
1432
1433 void file_data_sc_free_ci(FileData *fd)
1434 {
1435         GList *work;
1436
1437         if (fd->parent) fd = fd->parent;
1438         
1439         file_data_free_ci(fd);
1440         
1441         work = fd->sidecar_files;
1442         while (work)
1443                 {
1444                 FileData *sfd = work->data;
1445         
1446                 file_data_free_ci(sfd);
1447                 work = work->next;
1448                 }
1449 }
1450
1451 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1452 {
1453         GList *work;
1454         gboolean ret = TRUE;
1455
1456         work = fd_list;
1457         while (work)
1458                 {
1459                 FileData *fd = work->data;
1460         
1461                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1462                 work = work->next;
1463                 }
1464
1465         return ret;
1466 }
1467
1468 static void file_data_sc_revert_ci_list(GList *fd_list)
1469 {
1470         GList *work;
1471         
1472         work = fd_list;
1473         while (work)
1474                 {
1475                 FileData *fd = work->data;
1476                 
1477                 file_data_sc_free_ci(fd);
1478                 work = work->prev;
1479                 }
1480 }
1481
1482 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1483 {
1484         GList *work;
1485         
1486         work = fd_list;
1487         while (work)
1488                 {
1489                 FileData *fd = work->data;
1490                 
1491                 if (!func(fd, dest))
1492                         {
1493                         file_data_sc_revert_ci_list(work->prev);
1494                         return FALSE;
1495                         }
1496                 work = work->next;
1497                 }
1498         
1499         return TRUE;
1500 }
1501
1502 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1503 {
1504         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1505 }
1506
1507 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1508 {
1509         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1510 }
1511
1512 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1513 {
1514         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1515 }
1516
1517 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1518 {
1519         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1520 }
1521
1522 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1523 {
1524         GList *work;
1525         gboolean ret = TRUE;
1526
1527         work = fd_list;
1528         while (work)
1529                 {
1530                 FileData *fd = work->data;
1531         
1532                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1533                 work = work->next;
1534                 }
1535
1536         return ret;
1537 }
1538
1539 void file_data_free_ci_list(GList *fd_list)
1540 {
1541         GList *work;
1542         
1543         work = fd_list;
1544         while (work)
1545                 {
1546                 FileData *fd = work->data;
1547                 
1548                 file_data_free_ci(fd);
1549                 work = work->next;
1550                 }
1551 }
1552
1553 void file_data_sc_free_ci_list(GList *fd_list)
1554 {
1555         GList *work;
1556         
1557         work = fd_list;
1558         while (work)
1559                 {
1560                 FileData *fd = work->data;
1561                 
1562                 file_data_sc_free_ci(fd);
1563                 work = work->next;
1564                 }
1565 }
1566
1567 /*
1568  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1569  * fails if fd->change does not exist or the change type does not match
1570  */
1571
1572 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1573 {
1574         FileDataChangeType type = fd->change->type;
1575         
1576         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1577                 {
1578                 FileData *ofd;
1579                 
1580                 if (!file_data_planned_change_hash)
1581                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1582                 
1583                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1584                         {
1585                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1586                         g_hash_table_remove(file_data_planned_change_hash, old_path);
1587                         file_data_unref(fd);
1588                         }
1589
1590                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1591                 if (ofd != fd)
1592                         {
1593                         if (ofd)
1594                                 {
1595                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1596                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
1597                                 file_data_unref(ofd);
1598                                 }
1599                         
1600                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1601                         file_data_ref(fd);
1602                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1603                         }
1604                 }
1605 }
1606
1607 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1608 {
1609         gchar *old_path = fd->change->dest;
1610
1611         fd->change->dest = g_strdup(dest_path);
1612         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1613         g_free(old_path);
1614 }
1615
1616 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1617 {
1618         const gchar *extension = extension_from_path(fd->change->source);
1619         gchar *base = remove_extension_from_path(dest_path);
1620         gchar *old_path = fd->change->dest;
1621         
1622         fd->change->dest = g_strconcat(base, extension, NULL);
1623         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1624         
1625         g_free(old_path);
1626         g_free(base);
1627 }
1628
1629 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1630 {
1631         GList *work;
1632         gchar *dest_path_full = NULL;
1633         
1634         if (fd->parent) fd = fd->parent;
1635         
1636         if (!dest_path)
1637                 {
1638                 dest_path = fd->path;
1639                 }
1640         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1641                 {
1642                 gchar *dir = remove_level_from_path(fd->path);
1643                 
1644                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1645                 g_free(dir);
1646                 dest_path = dest_path_full;
1647                 }
1648         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1649                 {
1650                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1651                 dest_path = dest_path_full;
1652                 }
1653                 
1654         file_data_update_ci_dest(fd, dest_path);
1655         
1656         work = fd->sidecar_files;
1657         while (work)
1658                 {
1659                 FileData *sfd = work->data;
1660                 
1661                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1662                 work = work->next;
1663                 }
1664         
1665         g_free(dest_path_full);
1666 }
1667
1668 static gint file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1669 {
1670         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1671         file_data_sc_update_ci(fd, dest_path);
1672         return TRUE;
1673 }
1674
1675 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1676 {
1677         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1678 }
1679         
1680 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1681 {
1682         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1683 }
1684
1685 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1686 {
1687         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1688 }
1689
1690 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1691 {
1692         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1693 }
1694
1695 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1696                                                       const gchar *dest,
1697                                                       gboolean (*func)(FileData *, const gchar *))
1698 {
1699         GList *work;
1700         gboolean ret = TRUE;
1701         
1702         work = fd_list;
1703         while (work)
1704                 {
1705                 FileData *fd = work->data;
1706                 
1707                 if (!func(fd, dest)) ret = FALSE;
1708                 work = work->next;
1709                 }
1710         
1711         return ret;
1712 }
1713
1714 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1715 {
1716         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1717 }
1718
1719 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1720 {
1721         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1722 }
1723
1724 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1725 {
1726         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1727 }
1728
1729
1730 /*
1731  * verify source and dest paths - dest image exists, etc.
1732  * it should detect all possible problems with the planned operation
1733  */
1734
1735 gint file_data_verify_ci(FileData *fd)
1736 {
1737         gint ret = CHANGE_OK;
1738         gchar *dir;
1739         
1740         if (!fd->change)
1741                 {
1742                 DEBUG_1("Change checked: no change info: %s", fd->path);
1743                 return ret;
1744                 }
1745
1746         if (!isname(fd->path))
1747                 {
1748                 /* this probably should not happen */
1749                 ret |= CHANGE_NO_SRC;
1750                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1751                 return ret;
1752                 }
1753                 
1754         dir = remove_level_from_path(fd->path);
1755         
1756         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1757             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1758             !access_file(fd->path, R_OK))
1759                 {
1760                 ret |= CHANGE_NO_READ_PERM;
1761                 DEBUG_1("Change checked: no read permission: %s", fd->path);
1762                 }
1763         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1764                  !access_file(dir, W_OK))
1765                 {
1766                 ret |= CHANGE_NO_WRITE_PERM_DIR;
1767                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1768                 }
1769         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1770                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1771                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1772                  !access_file(fd->path, W_OK))
1773                 {
1774                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1775                 DEBUG_1("Change checked: no write permission: %s", fd->path);
1776                 }
1777         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1778            - that means that there are no hard errors and warnings can be disabled
1779            - the destination is determined during the check
1780         */
1781         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1782                 {
1783                 /* determine destination file */
1784                 gboolean have_dest = FALSE;
1785                 gchar *dest_dir = NULL;
1786                 
1787                 if (options->metadata.save_in_image_file)
1788                         {
1789                         if (file_data_can_write_directly(fd)) 
1790                                 {
1791                                 /* we can write the file directly */
1792                                 if (access_file(fd->path, W_OK))
1793                                         {
1794                                         have_dest = TRUE;
1795                                         }
1796                                 else
1797                                         {
1798                                         if (options->metadata.warn_on_write_problems)
1799                                                 {
1800                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1801                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1802                                                 }
1803                                         }
1804                                 }
1805                         else if (file_data_can_write_sidecar(fd)) 
1806                                 {
1807                                 /* we can write sidecar */
1808                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1809                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1810                                         {
1811                                         file_data_update_ci_dest(fd, sidecar);
1812                                         have_dest = TRUE;
1813                                         }
1814                                 else
1815                                         {
1816                                         if (options->metadata.warn_on_write_problems)
1817                                                 {
1818                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1819                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
1820                                                 }
1821                                         }
1822                                 g_free(sidecar);
1823                                 }
1824                         }
1825                 
1826                 if (!have_dest)
1827                         {
1828                         /* write private metadata file under ~/.geeqie */
1829
1830                         /* If an existing metadata file exists, we will try writing to
1831                          * it's location regardless of the user's preference.
1832                          */
1833                         gchar *metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
1834                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
1835                         
1836                         if (metadata_path && !access_file(metadata_path, W_OK))
1837                                 {
1838                                 g_free(metadata_path);
1839                                 metadata_path = NULL;
1840                                 }
1841
1842                         if (!metadata_path)
1843                                 {
1844                                 mode_t mode = 0755;
1845
1846                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
1847                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
1848                                         {
1849                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
1850                         
1851                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
1852                                         g_free(filename);
1853                                         }
1854                                 }
1855                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
1856                                 {
1857                                 file_data_update_ci_dest(fd, metadata_path);
1858                                 have_dest = TRUE;
1859                                 }
1860                         else
1861                                 {
1862                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
1863                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
1864                                 }
1865                         g_free(metadata_path);
1866                         }
1867                 g_free(dest_dir);
1868                 }
1869                 
1870         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
1871                 {
1872                 gboolean same;
1873                 gchar *dest_dir;
1874                         
1875                 same = (strcmp(fd->path, fd->change->dest) == 0);
1876
1877                 if (!same)
1878                         {
1879                         const gchar *dest_ext = extension_from_path(fd->change->dest);
1880                         if (!dest_ext) dest_ext = "";
1881
1882                         if (strcasecmp(fd->extension, dest_ext) != 0)
1883                                 {
1884                                 ret |= CHANGE_WARN_CHANGED_EXT;
1885                                 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1886                                 }
1887                         }
1888                 else
1889                         {
1890                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
1891                                 {
1892                                 ret |= CHANGE_WARN_SAME;
1893                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
1894                                 }
1895                         }
1896
1897                 dest_dir = remove_level_from_path(fd->change->dest);
1898
1899                 if (!isdir(dest_dir))
1900                         {
1901                         ret |= CHANGE_NO_DEST_DIR;
1902                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1903                         }
1904                 else if (!access_file(dest_dir, W_OK))
1905                         {
1906                         ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1907                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1908                         }
1909                 else if (!same)
1910                         {
1911                         if (isfile(fd->change->dest))
1912                                 {
1913                                 if (!access_file(fd->change->dest, W_OK))
1914                                         {
1915                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
1916                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1917                                         }
1918                                 else
1919                                         {
1920                                         ret |= CHANGE_WARN_DEST_EXISTS;
1921                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1922                                         }
1923                                 }
1924                         else if (isdir(fd->change->dest))
1925                                 {
1926                                 ret |= CHANGE_DEST_EXISTS;
1927                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1928                                 }
1929                         }
1930
1931                 g_free(dest_dir);
1932                 }
1933                 
1934         fd->change->error = ret;
1935         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1936
1937         g_free(dir);
1938         return ret;
1939 }
1940
1941
1942 gint file_data_sc_verify_ci(FileData *fd)
1943 {
1944         GList *work;
1945         gint ret;
1946
1947         ret = file_data_verify_ci(fd);
1948
1949         work = fd->sidecar_files;
1950         while (work)
1951                 {
1952                 FileData *sfd = work->data;
1953
1954                 ret |= file_data_verify_ci(sfd);
1955                 work = work->next;
1956                 }
1957
1958         return ret;
1959 }
1960
1961 gchar *file_data_get_error_string(gint error)
1962 {
1963         GString *result = g_string_new("");
1964
1965         if (error & CHANGE_NO_SRC)
1966                 {
1967                 if (result->len > 0) g_string_append(result, ", ");
1968                 g_string_append(result, _("file or directory does not exist"));
1969                 }
1970
1971         if (error & CHANGE_DEST_EXISTS)
1972                 {
1973                 if (result->len > 0) g_string_append(result, ", ");
1974                 g_string_append(result, _("destination already exists"));
1975                 }
1976
1977         if (error & CHANGE_NO_WRITE_PERM_DEST)
1978                 {
1979                 if (result->len > 0) g_string_append(result, ", ");
1980                 g_string_append(result, _("destination can't be overwritten"));
1981                 }
1982
1983         if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
1984                 {
1985                 if (result->len > 0) g_string_append(result, ", ");
1986                 g_string_append(result, _("destination directory is not writable"));
1987                 }
1988
1989         if (error & CHANGE_NO_DEST_DIR)
1990                 {
1991                 if (result->len > 0) g_string_append(result, ", ");
1992                 g_string_append(result, _("destination directory does not exist"));
1993                 }
1994
1995         if (error & CHANGE_NO_WRITE_PERM_DIR)
1996                 {
1997                 if (result->len > 0) g_string_append(result, ", ");
1998                 g_string_append(result, _("source directory is not writable"));
1999                 }
2000
2001         if (error & CHANGE_NO_READ_PERM)
2002                 {
2003                 if (result->len > 0) g_string_append(result, ", ");
2004                 g_string_append(result, _("no read permission"));
2005                 }
2006
2007         if (error & CHANGE_WARN_NO_WRITE_PERM)
2008                 {
2009                 if (result->len > 0) g_string_append(result, ", ");
2010                 g_string_append(result, _("file is readonly"));
2011                 }
2012
2013         if (error & CHANGE_WARN_DEST_EXISTS)
2014                 {
2015                 if (result->len > 0) g_string_append(result, ", ");
2016                 g_string_append(result, _("destination already exists and will be overwritten"));
2017                 }
2018                 
2019         if (error & CHANGE_WARN_SAME)
2020                 {
2021                 if (result->len > 0) g_string_append(result, ", ");
2022                 g_string_append(result, _("source and destination are the same"));
2023                 }
2024
2025         if (error & CHANGE_WARN_CHANGED_EXT)
2026                 {
2027                 if (result->len > 0) g_string_append(result, ", ");
2028                 g_string_append(result, _("source and destination have different extension"));
2029                 }
2030
2031         return g_string_free(result, FALSE);
2032 }
2033
2034 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2035 {
2036         GList *work;
2037         gint all_errors = 0;
2038         gint common_errors = ~0;
2039         gint num;
2040         gint *errors;
2041         gint i;
2042         
2043         if (!list) return 0;
2044         
2045         num = g_list_length(list);
2046         errors = g_new(int, num);
2047         work = list;
2048         i = 0;
2049         while (work)
2050                 {
2051                 FileData *fd;
2052                 gint error;
2053
2054                 fd = work->data;
2055                 work = work->next;
2056                         
2057                 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2058                 all_errors |= error;
2059                 common_errors &= error;
2060                 
2061                 errors[i] = error;
2062                 
2063                 i++;
2064                 }
2065         
2066         if (desc && all_errors)
2067                 {
2068                 GList *work;
2069                 GString *result = g_string_new("");
2070                 
2071                 if (common_errors)
2072                         {
2073                         gchar *str = file_data_get_error_string(common_errors);
2074                         g_string_append(result, str);
2075                         g_string_append(result, "\n");
2076                         g_free(str);
2077                         }
2078                 
2079                 work = list;
2080                 i = 0;
2081                 while (work)
2082                         {
2083                         FileData *fd;
2084                         gint error;
2085
2086                         fd = work->data;
2087                         work = work->next;
2088                         
2089                         error = errors[i] & ~common_errors;
2090                         
2091                         if (error)
2092                                 {
2093                                 gchar *str = file_data_get_error_string(error);
2094                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2095                                 g_free(str);
2096                                 }
2097                         i++;
2098                         }
2099                 *desc = g_string_free(result, FALSE);
2100                 }
2101
2102         g_free(errors);
2103         return all_errors;
2104 }
2105
2106
2107 /*
2108  * perform the change described by FileFataChangeInfo
2109  * it is used for internal operations,
2110  * this function actually operates with files on the filesystem
2111  * it should implement safe delete
2112  */
2113
2114 static gboolean file_data_perform_move(FileData *fd)
2115 {
2116         g_assert(!strcmp(fd->change->source, fd->path));
2117         return move_file(fd->change->source, fd->change->dest);
2118 }
2119
2120 static gboolean file_data_perform_copy(FileData *fd)
2121 {
2122         g_assert(!strcmp(fd->change->source, fd->path));
2123         return copy_file(fd->change->source, fd->change->dest);
2124 }
2125
2126 static gboolean file_data_perform_delete(FileData *fd)
2127 {
2128         if (isdir(fd->path) && !islink(fd->path))
2129                 return rmdir_utf8(fd->path);
2130         else
2131                 if (options->file_ops.safe_delete_enable)
2132                         return file_util_safe_unlink(fd->path);
2133                 else
2134                         return unlink_file(fd->path);
2135 }
2136
2137 gboolean file_data_perform_ci(FileData *fd)
2138 {
2139         FileDataChangeType type = fd->change->type;
2140         switch (type)
2141                 {
2142                 case FILEDATA_CHANGE_MOVE:
2143                         return file_data_perform_move(fd);
2144                 case FILEDATA_CHANGE_COPY:
2145                         return file_data_perform_copy(fd);
2146                 case FILEDATA_CHANGE_RENAME:
2147                         return file_data_perform_move(fd); /* the same as move */
2148                 case FILEDATA_CHANGE_DELETE:
2149                         return file_data_perform_delete(fd);
2150                 case FILEDATA_CHANGE_WRITE_METADATA:
2151                         return metadata_write_perform(fd);
2152                 case FILEDATA_CHANGE_UNSPECIFIED:
2153                         /* nothing to do here */
2154                         break;
2155                 }
2156         return TRUE;
2157 }
2158
2159
2160
2161 gboolean file_data_sc_perform_ci(FileData *fd)
2162 {
2163         GList *work;
2164         gboolean ret = TRUE;
2165         FileDataChangeType type = fd->change->type;
2166         
2167         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2168
2169         work = fd->sidecar_files;
2170         while (work)
2171                 {
2172                 FileData *sfd = work->data;
2173                 
2174                 if (!file_data_perform_ci(sfd)) ret = FALSE;
2175                 work = work->next;
2176                 }
2177         
2178         if (!file_data_perform_ci(fd)) ret = FALSE;
2179         
2180         return ret;
2181 }
2182
2183 /*
2184  * updates FileData structure according to FileDataChangeInfo
2185  */
2186
2187 gint file_data_apply_ci(FileData *fd)
2188 {
2189         FileDataChangeType type = fd->change->type;
2190
2191         /* FIXME delete ?*/
2192         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2193                 {
2194                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2195                 file_data_planned_change_remove(fd);
2196                 
2197                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2198                         {
2199                         /* this change overwrites another file which is already known to other modules
2200                            renaming fd would create duplicate FileData structure
2201                            the best thing we can do is nothing
2202                            FIXME: maybe we could copy stuff like marks
2203                         */
2204                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2205                         }
2206                 else
2207                         {
2208                         file_data_set_path(fd, fd->change->dest);
2209                         }
2210                 }
2211         file_data_increment_version(fd);
2212         file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
2213         
2214         return TRUE;
2215 }
2216
2217 gint file_data_sc_apply_ci(FileData *fd)
2218 {
2219         GList *work;
2220         FileDataChangeType type = fd->change->type;
2221         
2222         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2223
2224         work = fd->sidecar_files;
2225         while (work)
2226                 {
2227                 FileData *sfd = work->data;
2228                 
2229                 file_data_apply_ci(sfd);
2230                 work = work->next;
2231                 }
2232         
2233         file_data_apply_ci(fd);
2234         
2235         return TRUE;
2236 }
2237
2238 /*
2239  * notify other modules about the change described by FileFataChangeInfo
2240  */
2241
2242 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2243    FIXME do we need the ignore_list? It looks like a workaround for ineffective
2244    implementation in view_file_list.c */
2245
2246
2247
2248
2249 typedef struct _NotifyData NotifyData;
2250
2251 struct _NotifyData {
2252         FileDataNotifyFunc func;
2253         gpointer data;
2254         NotifyPriority priority;
2255         };
2256
2257 static GList *notify_func_list = NULL;
2258
2259 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2260 {
2261         NotifyData *nda = (NotifyData *)a;
2262         NotifyData *ndb = (NotifyData *)b;
2263
2264         if (nda->priority < ndb->priority) return -1;
2265         if (nda->priority > ndb->priority) return 1;
2266         return 0;
2267 }
2268
2269 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2270 {
2271         NotifyData *nd;
2272         
2273         nd = g_new(NotifyData, 1);
2274         nd->func = func;
2275         nd->data = data;
2276         nd->priority = priority;
2277
2278         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2279         DEBUG_1("Notify func registered: %p", nd);
2280         
2281         return TRUE;
2282 }
2283
2284 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2285 {
2286         GList *work = notify_func_list;
2287         
2288         while (work)
2289                 {
2290                 NotifyData *nd = (NotifyData *)work->data;
2291         
2292                 if (nd->func == func && nd->data == data)
2293                         {
2294                         notify_func_list = g_list_delete_link(notify_func_list, work);
2295                         g_free(nd);
2296                         DEBUG_1("Notify func unregistered: %p", nd);
2297                         return TRUE;
2298                         }
2299                 work = work->next;
2300                 }
2301
2302         return FALSE;
2303 }
2304
2305
2306 void file_data_send_notification(FileData *fd, NotifyType type)
2307 {
2308         GList *work = notify_func_list;
2309
2310         while (work)
2311                 {
2312                 NotifyData *nd = (NotifyData *)work->data;
2313                 
2314                 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
2315                 nd->func(fd, type, nd->data);
2316                 work = work->next;
2317                 }
2318 }
2319
2320 static GHashTable *file_data_monitor_pool = NULL;
2321 static gint realtime_monitor_id = -1;
2322
2323 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2324 {
2325         FileData *fd = key;
2326
2327         file_data_check_changed_files(fd);
2328         
2329         DEBUG_1("monitor %s", fd->path);
2330 }
2331
2332 static gboolean realtime_monitor_cb(gpointer data)
2333 {
2334         if (!options->update_on_time_change) return TRUE;
2335         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2336         return TRUE;
2337 }
2338
2339 gint file_data_register_real_time_monitor(FileData *fd)
2340 {
2341         gint count;
2342         
2343         file_data_ref(fd);
2344         
2345         if (!file_data_monitor_pool)
2346                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2347         
2348         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2349
2350         DEBUG_1("Register realtime %d %s", count, fd->path);
2351         
2352         count++;
2353         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2354         
2355         if (realtime_monitor_id == -1)
2356                 {
2357                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2358                 }
2359         
2360         return TRUE;
2361 }
2362
2363 gint file_data_unregister_real_time_monitor(FileData *fd)
2364 {
2365         gint count;
2366
2367         g_assert(file_data_monitor_pool);
2368         
2369         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2370         
2371         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2372         
2373         g_assert(count > 0);
2374         
2375         count--;
2376         
2377         if (count == 0)
2378                 g_hash_table_remove(file_data_monitor_pool, fd);
2379         else
2380                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2381
2382         file_data_unref(fd);
2383         
2384         if (g_hash_table_size(file_data_monitor_pool) == 0)
2385                 {
2386                 g_source_remove(realtime_monitor_id);
2387                 realtime_monitor_id = -1;
2388                 return FALSE;
2389                 }
2390         
2391         return TRUE;
2392 }
2393 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */