histogram caching
[geeqie.git] / src / filedata.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 - 2009 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         g_free(fd->histmap);
519         
520         g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
521
522         file_data_change_info_free(NULL, fd);
523         g_free(fd);
524 }
525
526 #ifdef DEBUG_FILEDATA
527 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
528 #else
529 void file_data_unref(FileData *fd)
530 #endif
531 {
532         if (fd == NULL) return;
533 #ifdef DEBUG_FILEDATA
534         if (fd->magick != 0x12345678)
535                 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
536 #endif
537         g_assert(fd->magick == 0x12345678);
538         
539         fd->ref--;
540 #ifdef DEBUG_FILEDATA
541         DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
542
543 #else
544         DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
545 #endif
546         if (fd->ref == 0)
547                 {
548                 GList *work;
549                 FileData *parent = fd->parent ? fd->parent : fd;
550                 
551                 if (parent->ref > 0)
552                         return;
553
554                 work = parent->sidecar_files;
555                 while (work)
556                         {
557                         FileData *sfd = work->data;
558                         if (sfd->ref > 0)
559                                 return;
560                         work = work->next;
561                         }
562
563                 /* none of parent/children is referenced, we can free everything */
564
565                 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
566
567                 work = parent->sidecar_files;
568                 while (work)
569                         {
570                         FileData *sfd = work->data;
571                         file_data_free(sfd);
572                         work = work->next;
573                         }
574
575                 g_list_free(parent->sidecar_files);
576                 parent->sidecar_files = NULL;
577
578                 file_data_free(parent);
579                 }
580 }
581
582 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
583 {
584         sfd->parent = target;
585         g_assert(g_list_find(target->sidecar_files, sfd));
586         
587         file_data_increment_version(sfd); /* increments both sfd and target */
588
589         target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
590         sfd->parent = NULL;
591
592         if (sfd->ref == 0)
593                 {
594                 file_data_free(sfd);
595                 return NULL;
596                 }
597
598         return sfd;
599 }
600
601 /* disables / enables grouping for particular file, sends UPDATE notification */
602 void file_data_disable_grouping(FileData *fd, gboolean disable)
603 {
604         if (!fd->disable_grouping == !disable) return;
605         fd->disable_grouping = !!disable;
606         
607         if (disable)
608                 {
609                 if (fd->parent)
610                         {
611                         FileData *parent = file_data_ref(fd->parent);
612                         file_data_disconnect_sidecar_file(parent, fd);
613                         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
614                         file_data_send_notification(parent, NOTIFY_TYPE_INTERNAL);
615                         file_data_unref(parent);
616                         }
617                 else if (fd->sidecar_files)
618                         {
619                         GList *sidecar_files = filelist_copy(fd->sidecar_files);
620                         GList *work = sidecar_files;
621                         while (work)
622                                 {
623                                 FileData *sfd = work->data;
624                                 work = work->next;
625                                 file_data_disconnect_sidecar_file(fd, sfd);
626                                 file_data_send_notification(sfd, NOTIFY_TYPE_INTERNAL);
627                                 }
628                         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
629                         file_data_check_sidecars((FileData *)sidecar_files->data); /* this will group the sidecars back together */
630                         filelist_free(sidecar_files);
631                         }
632                 }
633         else
634                 {
635                 file_data_check_sidecars(fd);
636                 file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
637                 }
638 }
639
640 /* compare name without extension */
641 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
642 {
643         size_t len1 = fd1->extension - fd1->name;
644         size_t len2 = fd2->extension - fd2->name;
645
646         if (len1 < len2) return -1;
647         if (len1 > len2) return 1;
648
649         return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
650 }
651
652 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
653 {
654         if (!fdci && fd)
655                 fdci = fd->change;
656
657         if (!fdci)
658                 return;
659
660         g_free(fdci->source);
661         g_free(fdci->dest);
662
663         g_free(fdci);
664
665         if (fd)
666                 fd->change = NULL;
667 }
668
669 static gboolean file_data_can_write_directly(FileData *fd)
670 {
671         return filter_name_is_writable(fd->extension);
672 }
673
674 static gboolean file_data_can_write_sidecar(FileData *fd)
675 {
676         return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
677 }
678
679 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
680 {
681         gchar *sidecar_path = NULL;
682         GList *work;
683         if (!file_data_can_write_sidecar(fd)) return NULL;
684         
685         work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
686         while (work)
687                 {
688                 FileData *sfd = work->data;
689                 work = work->next;
690                 if (strcasecmp(sfd->extension, ".xmp") == 0)
691                         {
692                         sidecar_path = g_strdup(sfd->path);
693                         break;
694                         }
695                 }
696         
697         if (!existing_only && !sidecar_path)
698                 {
699                 gchar *base = remove_extension_from_path(fd->path);
700                 sidecar_path = g_strconcat(base, ".xmp", NULL);
701                 g_free(base);
702                 }
703
704         return sidecar_path;
705 }
706
707
708 /*
709  *-----------------------------------------------------------------------------
710  * sidecar file info struct
711  *-----------------------------------------------------------------------------
712  */
713
714
715
716 static gint sidecar_file_priority(const gchar *path)
717 {
718         const gchar *extension = extension_from_path(path);
719         gint i = 1;
720         GList *work;
721
722         if (extension == NULL)
723                 return 0;
724
725         work = sidecar_ext_get_list();
726
727         while (work) {
728                 gchar *ext = work->data;
729                 
730                 work = work->next;
731                 if (strcasecmp(extension, ext) == 0) return i;
732                 i++;
733         }
734         return 0;
735 }
736
737
738 /*
739  *-----------------------------------------------------------------------------
740  * load file list
741  *-----------------------------------------------------------------------------
742  */
743
744 static SortType filelist_sort_method = SORT_NONE;
745 static gint filelist_sort_ascend = TRUE;
746
747
748 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
749 {
750         if (!filelist_sort_ascend)
751                 {
752                 FileData *tmp = fa;
753                 fa = fb;
754                 fb = tmp;
755                 }
756
757         switch (filelist_sort_method)
758                 {
759                 case SORT_NAME:
760                         break;
761                 case SORT_SIZE:
762                         if (fa->size < fb->size) return -1;
763                         if (fa->size > fb->size) return 1;
764                         /* fall back to name */
765                         break;
766                 case SORT_TIME:
767                         if (fa->date < fb->date) return -1;
768                         if (fa->date > fb->date) return 1;
769                         /* fall back to name */
770                         break;
771 #ifdef HAVE_STRVERSCMP
772                 case SORT_NUMBER:
773                         return strverscmp(fa->name, fb->name);
774                         break;
775 #endif
776                 default:
777                         break;
778                 }
779
780         if (options->file_sort.case_sensitive)
781                 return strcmp(fa->collate_key_name, fb->collate_key_name);
782         else
783                 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
784 }
785
786 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gint ascend)
787 {
788         filelist_sort_method = method;
789         filelist_sort_ascend = ascend;
790         return filelist_sort_compare_filedata(fa, fb);
791 }
792
793 static gint filelist_sort_file_cb(gpointer a, gpointer b)
794 {
795         return filelist_sort_compare_filedata(a, b);
796 }
797
798 GList *filelist_sort_full(GList *list, SortType method, gint ascend, GCompareFunc cb)
799 {
800         filelist_sort_method = method;
801         filelist_sort_ascend = ascend;
802         return g_list_sort(list, cb);
803 }
804
805 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gint ascend, GCompareFunc cb)
806 {
807         filelist_sort_method = method;
808         filelist_sort_ascend = ascend;
809         return g_list_insert_sorted(list, data, cb);
810 }
811
812 GList *filelist_sort(GList *list, SortType method, gint ascend)
813 {
814         return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
815 }
816
817 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gint ascend)
818 {
819         return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
820 }
821
822
823 static GList *filelist_filter_out_sidecars(GList *flist)
824 {
825         GList *work = flist;
826         GList *flist_filtered = NULL;
827
828         while (work)
829                 {
830                 FileData *fd = work->data;
831         
832                 work = work->next;
833                 if (fd->parent) /* remove fd's that are children */
834                         file_data_unref(fd);
835                 else
836                         flist_filtered = g_list_prepend(flist_filtered, fd);
837                 }
838         g_list_free(flist);
839
840         return flist_filtered;
841 }
842
843 static gint filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gint follow_symlinks)
844 {
845         DIR *dp;
846         struct dirent *dir;
847         gchar *pathl;
848         GList *dlist = NULL;
849         GList *flist = NULL;
850         gint (*stat_func)(const gchar *path, struct stat *buf);
851
852         g_assert(files || dirs);
853
854         if (files) *files = NULL;
855         if (dirs) *dirs = NULL;
856
857         pathl = path_from_utf8(dir_fd->path);
858         if (!pathl) return FALSE;
859
860         dp = opendir(pathl);
861         if (dp == NULL)
862                 {
863                 g_free(pathl);
864                 return FALSE;
865                 }
866
867         if (follow_symlinks)
868                 stat_func = stat;
869         else
870                 stat_func = lstat;
871
872         while ((dir = readdir(dp)) != NULL)
873                 {
874                 struct stat ent_sbuf;
875                 const gchar *name = dir->d_name;
876                 gchar *filepath;
877
878                 if (!options->file_filter.show_hidden_files && ishidden(name))
879                         continue;
880
881                 filepath = g_build_filename(pathl, name, NULL);
882                 if (stat_func(filepath, &ent_sbuf) >= 0)
883                         {
884                         if (S_ISDIR(ent_sbuf.st_mode))
885                                 {
886                                 /* we ignore the .thumbnails dir for cleanliness */
887                                 if (dirs &&
888                                     !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
889                                     strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
890                                     strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
891                                     strcmp(name, THUMB_FOLDER_LOCAL) != 0)
892                                         {
893                                         dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE));
894                                         }
895                                 }
896                         else
897                                 {
898                                 if (files && filter_name_exists(name))
899                                         {
900                                         flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE));
901                                         }
902                                 }
903                         }
904                 g_free(filepath);
905                 }
906
907         closedir(dp);
908         
909         g_free(pathl);
910
911         if (dirs) *dirs = dlist;
912         if (files) *files = filelist_filter_out_sidecars(flist);
913
914         return TRUE;
915 }
916
917 gint filelist_read(FileData *dir_fd, GList **files, GList **dirs)
918 {
919         return filelist_read_real(dir_fd, files, dirs, TRUE);
920 }
921
922 gint filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
923 {
924         return filelist_read_real(dir_fd, files, dirs, FALSE);
925 }
926
927 void filelist_free(GList *list)
928 {
929         GList *work;
930
931         work = list;
932         while (work)
933                 {
934                 file_data_unref((FileData *)work->data);
935                 work = work->next;
936                 }
937
938         g_list_free(list);
939 }
940
941
942 GList *filelist_copy(GList *list)
943 {
944         GList *new_list = NULL;
945         GList *work;
946
947         work = list;
948         while (work)
949                 {
950                 FileData *fd;
951
952                 fd = work->data;
953                 work = work->next;
954
955                 new_list = g_list_prepend(new_list, file_data_ref(fd));
956                 }
957
958         return g_list_reverse(new_list);
959 }
960
961 GList *filelist_from_path_list(GList *list)
962 {
963         GList *new_list = NULL;
964         GList *work;
965
966         work = list;
967         while (work)
968                 {
969                 gchar *path;
970
971                 path = work->data;
972                 work = work->next;
973
974                 new_list = g_list_prepend(new_list, file_data_new_simple(path));
975                 }
976
977         return g_list_reverse(new_list);
978 }
979
980 GList *filelist_to_path_list(GList *list)
981 {
982         GList *new_list = NULL;
983         GList *work;
984
985         work = list;
986         while (work)
987                 {
988                 FileData *fd;
989
990                 fd = work->data;
991                 work = work->next;
992
993                 new_list = g_list_prepend(new_list, g_strdup(fd->path));
994                 }
995
996         return g_list_reverse(new_list);
997 }
998
999 GList *filelist_filter(GList *list, gint is_dir_list)
1000 {
1001         GList *work;
1002
1003         if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1004
1005         work = list;
1006         while (work)
1007                 {
1008                 FileData *fd = (FileData *)(work->data);
1009                 const gchar *name = fd->name;
1010
1011                 if ((!options->file_filter.show_hidden_files && ishidden(name)) ||
1012                     (!is_dir_list && !filter_name_exists(name)) ||
1013                     (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1014                                                        strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1015                         {
1016                         GList *link = work;
1017                         
1018                         list = g_list_remove_link(list, link);
1019                         file_data_unref(fd);
1020                         g_list_free(link);
1021                         }
1022         
1023                 work = work->next;
1024                 }
1025
1026         return list;
1027 }
1028
1029 /*
1030  *-----------------------------------------------------------------------------
1031  * filelist recursive
1032  *-----------------------------------------------------------------------------
1033  */
1034
1035 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1036 {
1037         return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1038 }
1039
1040 GList *filelist_sort_path(GList *list)
1041 {
1042         return g_list_sort(list, filelist_sort_path_cb);
1043 }
1044
1045 static void filelist_recursive_append(GList **list, GList *dirs)
1046 {
1047         GList *work;
1048
1049         work = dirs;
1050         while (work)
1051                 {
1052                 FileData *fd = (FileData *)(work->data);
1053                 GList *f;
1054                 GList *d;
1055
1056                 if (filelist_read(fd, &f, &d))
1057                         {
1058                         f = filelist_filter(f, FALSE);
1059                         f = filelist_sort_path(f);
1060                         *list = g_list_concat(*list, f);
1061
1062                         d = filelist_filter(d, TRUE);
1063                         d = filelist_sort_path(d);
1064                         filelist_recursive_append(list, d);
1065                         filelist_free(d);
1066                         }
1067
1068                 work = work->next;
1069                 }
1070 }
1071
1072 GList *filelist_recursive(FileData *dir_fd)
1073 {
1074         GList *list;
1075         GList *d;
1076
1077         if (!filelist_read(dir_fd, &list, &d)) return NULL;
1078         list = filelist_filter(list, FALSE);
1079         list = filelist_sort_path(list);
1080
1081         d = filelist_filter(d, TRUE);
1082         d = filelist_sort_path(d);
1083         filelist_recursive_append(&list, d);
1084         filelist_free(d);
1085
1086         return list;
1087 }
1088
1089
1090 /*
1091  * marks and orientation
1092  */
1093
1094 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1095 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1096 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1097
1098 gboolean file_data_get_mark(FileData *fd, gint n)
1099 {
1100         gboolean valid = (fd->valid_marks & (1 << n));
1101         if (file_data_get_mark_func[n] && !valid) 
1102                 {
1103                 guint old = fd->marks;
1104                 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1105                 if (!value != !(fd->marks & (1 << n))) 
1106                         {
1107                         fd->marks = fd->marks ^ (1 << n);
1108                         }
1109                 fd->valid_marks |= (1 << n);
1110                 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1111                         {
1112                         file_data_unref(fd);
1113                         }
1114                 else if (!old && fd->marks)
1115                         {
1116                         file_data_ref(fd);
1117                         }
1118                 }
1119
1120         return !!(fd->marks & (1 << n));
1121 }
1122
1123 guint file_data_get_marks(FileData *fd)
1124 {
1125         gint i;
1126         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1127         return fd->marks;
1128 }
1129
1130 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1131 {
1132         guint old;
1133         if (!value == !file_data_get_mark(fd, n)) return;
1134         
1135         if (file_data_set_mark_func[n]) 
1136                 {
1137                 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1138                 }
1139         
1140         old = fd->marks;
1141
1142         fd->marks = fd->marks ^ (1 << n);
1143         
1144         if (old && !fd->marks) /* keep files with non-zero marks in memory */
1145                 {
1146                 file_data_unref(fd);
1147                 }
1148         else if (!old && fd->marks)
1149                 {
1150                 file_data_ref(fd);
1151                 }
1152         
1153         file_data_increment_version(fd);
1154         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1155 }
1156
1157 gboolean file_data_filter_marks(FileData *fd, guint filter)
1158 {
1159         gint i;
1160         for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1161         return ((fd->marks & filter) == filter);
1162 }
1163
1164 GList *file_data_filter_marks_list(GList *list, guint filter)
1165 {
1166         GList *work;
1167
1168         work = list;
1169         while (work)
1170                 {
1171                 FileData *fd = work->data;
1172                 GList *link = work;
1173                 work = work->next;
1174
1175                 if (!file_data_filter_marks(fd, filter))
1176                         {
1177                         list = g_list_remove_link(list, link);
1178                         file_data_unref(fd);
1179                         g_list_free(link);
1180                         }
1181                 }
1182
1183         return list;
1184 }
1185
1186 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1187 {
1188         FileData *fd = value;
1189         file_data_increment_version(fd);
1190         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1191 }
1192
1193 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data)
1194 {
1195         if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1196                 
1197         file_data_get_mark_func[n] = get_mark_func;
1198         file_data_set_mark_func[n] = set_mark_func;
1199         file_data_mark_func_data[n] = data;
1200         
1201         if (get_mark_func)
1202                 {
1203                 /* this effectively changes all known files */
1204                 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1205                 }
1206         
1207         return TRUE;
1208 }
1209
1210 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1211 {
1212         if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1213         if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1214         if (data) *data = file_data_mark_func_data[n];
1215 }
1216
1217 gint file_data_get_user_orientation(FileData *fd)
1218 {
1219         return fd->user_orientation;
1220 }
1221
1222 void file_data_set_user_orientation(FileData *fd, gint value)
1223 {
1224         if (fd->user_orientation == value) return;
1225
1226         fd->user_orientation = value;
1227         file_data_increment_version(fd);
1228         file_data_send_notification(fd, NOTIFY_TYPE_INTERNAL);
1229 }
1230
1231
1232 /*
1233  * file_data    - operates on the given fd
1234  * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1235  */
1236
1237
1238 /* return list of sidecar file extensions in a string */
1239 gchar *file_data_sc_list_to_string(FileData *fd)
1240 {
1241         GList *work;
1242         GString *result = g_string_new("");
1243
1244         work = fd->sidecar_files;
1245         while (work)
1246                 {
1247                 FileData *sfd = work->data;
1248
1249                 result = g_string_append(result, "+ ");
1250                 result = g_string_append(result, sfd->extension);
1251                 work = work->next;
1252                 if (work) result = g_string_append_c(result, ' ');
1253                 }
1254
1255         return g_string_free(result, FALSE);
1256 }
1257
1258
1259
1260 /*
1261  * add FileDataChangeInfo (see typedefs.h) for the given operation
1262  * uses file_data_add_change_info
1263  *
1264  * fails if the fd->change already exists - change operations can't run in parallel
1265  * fd->change_info works as a lock
1266  *
1267  * dest can be NULL - in this case the current name is used for now, it will
1268  * be changed later
1269  */
1270
1271 /*
1272    FileDataChangeInfo types:
1273    COPY
1274    MOVE   - path is changed, name may be changed too
1275    RENAME - path remains unchanged, name is changed
1276             extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1277             sidecar names are changed too, extensions are not changed
1278    DELETE
1279    UPDATE - file size, date or grouping has been changed
1280 */
1281
1282 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1283 {
1284         FileDataChangeInfo *fdci;
1285
1286         if (fd->change) return FALSE;
1287
1288         fdci = g_new0(FileDataChangeInfo, 1);
1289
1290         fdci->type = type;
1291
1292         if (src)
1293                 fdci->source = g_strdup(src);
1294         else
1295                 fdci->source = g_strdup(fd->path);
1296
1297         if (dest)
1298                 fdci->dest = g_strdup(dest);
1299
1300         fd->change = fdci;
1301         
1302         return TRUE;
1303 }
1304
1305 static void file_data_planned_change_remove(FileData *fd)
1306 {
1307         if (file_data_planned_change_hash &&
1308             (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1309                 {
1310                 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1311                         {
1312                         DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1313                         g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1314                         file_data_unref(fd);
1315                         if (g_hash_table_size(file_data_planned_change_hash) == 0)
1316                                 {
1317                                 g_hash_table_destroy(file_data_planned_change_hash);
1318                                 file_data_planned_change_hash = NULL;
1319                                 DEBUG_1("planned change: empty");
1320                                 }
1321                         }
1322                 }
1323 }
1324
1325
1326 void file_data_free_ci(FileData *fd)
1327 {
1328         FileDataChangeInfo *fdci = fd->change;
1329
1330         if (!fdci)
1331                 return;
1332
1333         file_data_planned_change_remove(fd);
1334
1335         g_free(fdci->source);
1336         g_free(fdci->dest);
1337
1338         g_free(fdci);
1339
1340         fd->change = NULL;
1341 }
1342
1343
1344 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1345 {
1346         GList *work;
1347
1348         if (fd->parent) fd = fd->parent;
1349         
1350         if (fd->change) return FALSE;
1351         
1352         work = fd->sidecar_files;
1353         while (work)
1354                 {
1355                 FileData *sfd = work->data;
1356                 
1357                 if (sfd->change) return FALSE;
1358                 work = work->next;
1359                 }
1360
1361         file_data_add_ci(fd, type, NULL, NULL);
1362         
1363         work = fd->sidecar_files;
1364         while (work)
1365                 {
1366                 FileData *sfd = work->data;
1367                 
1368                 file_data_add_ci(sfd, type, NULL, NULL);
1369                 work = work->next;
1370                 }
1371                 
1372         return TRUE;
1373 }
1374
1375 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1376 {
1377         GList *work;
1378         
1379         if (fd->parent) fd = fd->parent;
1380         
1381         if (!fd->change || fd->change->type != type) return FALSE;
1382         
1383         work = fd->sidecar_files;
1384         while (work)
1385                 {
1386                 FileData *sfd = work->data;
1387
1388                 if (!sfd->change || sfd->change->type != type) return FALSE;
1389                 work = work->next;
1390                 }
1391
1392         return TRUE;
1393 }
1394
1395
1396 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1397 {
1398         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1399         file_data_sc_update_ci_copy(fd, dest_path);
1400         return TRUE;
1401 }
1402
1403 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1404 {
1405         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1406         file_data_sc_update_ci_move(fd, dest_path);
1407         return TRUE;
1408 }
1409
1410 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1411 {
1412         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1413         file_data_sc_update_ci_rename(fd, dest_path);
1414         return TRUE;
1415 }
1416
1417 gboolean file_data_sc_add_ci_delete(FileData *fd)
1418 {
1419         return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1420 }
1421
1422 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1423 {
1424         if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1425         file_data_sc_update_ci_unspecified(fd, dest_path);
1426         return TRUE;
1427 }
1428
1429 gboolean file_data_add_ci_write_metadata(FileData *fd)
1430 {
1431         return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1432 }
1433
1434 void file_data_sc_free_ci(FileData *fd)
1435 {
1436         GList *work;
1437
1438         if (fd->parent) fd = fd->parent;
1439         
1440         file_data_free_ci(fd);
1441         
1442         work = fd->sidecar_files;
1443         while (work)
1444                 {
1445                 FileData *sfd = work->data;
1446         
1447                 file_data_free_ci(sfd);
1448                 work = work->next;
1449                 }
1450 }
1451
1452 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1453 {
1454         GList *work;
1455         gboolean ret = TRUE;
1456
1457         work = fd_list;
1458         while (work)
1459                 {
1460                 FileData *fd = work->data;
1461         
1462                 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1463                 work = work->next;
1464                 }
1465
1466         return ret;
1467 }
1468
1469 static void file_data_sc_revert_ci_list(GList *fd_list)
1470 {
1471         GList *work;
1472         
1473         work = fd_list;
1474         while (work)
1475                 {
1476                 FileData *fd = work->data;
1477                 
1478                 file_data_sc_free_ci(fd);
1479                 work = work->prev;
1480                 }
1481 }
1482
1483 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1484 {
1485         GList *work;
1486         
1487         work = fd_list;
1488         while (work)
1489                 {
1490                 FileData *fd = work->data;
1491                 
1492                 if (!func(fd, dest))
1493                         {
1494                         file_data_sc_revert_ci_list(work->prev);
1495                         return FALSE;
1496                         }
1497                 work = work->next;
1498                 }
1499         
1500         return TRUE;
1501 }
1502
1503 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1504 {
1505         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1506 }
1507
1508 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1509 {
1510         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1511 }
1512
1513 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1514 {
1515         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1516 }
1517
1518 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1519 {
1520         return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1521 }
1522
1523 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1524 {
1525         GList *work;
1526         gboolean ret = TRUE;
1527
1528         work = fd_list;
1529         while (work)
1530                 {
1531                 FileData *fd = work->data;
1532         
1533                 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1534                 work = work->next;
1535                 }
1536
1537         return ret;
1538 }
1539
1540 void file_data_free_ci_list(GList *fd_list)
1541 {
1542         GList *work;
1543         
1544         work = fd_list;
1545         while (work)
1546                 {
1547                 FileData *fd = work->data;
1548                 
1549                 file_data_free_ci(fd);
1550                 work = work->next;
1551                 }
1552 }
1553
1554 void file_data_sc_free_ci_list(GList *fd_list)
1555 {
1556         GList *work;
1557         
1558         work = fd_list;
1559         while (work)
1560                 {
1561                 FileData *fd = work->data;
1562                 
1563                 file_data_sc_free_ci(fd);
1564                 work = work->next;
1565                 }
1566 }
1567
1568 /*
1569  * update existing fd->change, it will be used from dialog callbacks for interactive editing
1570  * fails if fd->change does not exist or the change type does not match
1571  */
1572
1573 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1574 {
1575         FileDataChangeType type = fd->change->type;
1576         
1577         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1578                 {
1579                 FileData *ofd;
1580                 
1581                 if (!file_data_planned_change_hash)
1582                         file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1583                 
1584                 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1585                         {
1586                         DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1587                         g_hash_table_remove(file_data_planned_change_hash, old_path);
1588                         file_data_unref(fd);
1589                         }
1590
1591                 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1592                 if (ofd != fd)
1593                         {
1594                         if (ofd)
1595                                 {
1596                                 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1597                                 g_hash_table_remove(file_data_planned_change_hash, new_path);
1598                                 file_data_unref(ofd);
1599                                 }
1600                         
1601                         DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1602                         file_data_ref(fd);
1603                         g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1604                         }
1605                 }
1606 }
1607
1608 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1609 {
1610         gchar *old_path = fd->change->dest;
1611
1612         fd->change->dest = g_strdup(dest_path);
1613         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1614         g_free(old_path);
1615 }
1616
1617 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1618 {
1619         const gchar *extension = extension_from_path(fd->change->source);
1620         gchar *base = remove_extension_from_path(dest_path);
1621         gchar *old_path = fd->change->dest;
1622         
1623         fd->change->dest = g_strconcat(base, extension, NULL);
1624         file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1625         
1626         g_free(old_path);
1627         g_free(base);
1628 }
1629
1630 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1631 {
1632         GList *work;
1633         gchar *dest_path_full = NULL;
1634         
1635         if (fd->parent) fd = fd->parent;
1636         
1637         if (!dest_path)
1638                 {
1639                 dest_path = fd->path;
1640                 }
1641         else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1642                 {
1643                 gchar *dir = remove_level_from_path(fd->path);
1644                 
1645                 dest_path_full = g_build_filename(dir, dest_path, NULL);
1646                 g_free(dir);
1647                 dest_path = dest_path_full;
1648                 }
1649         else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1650                 {
1651                 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1652                 dest_path = dest_path_full;
1653                 }
1654                 
1655         file_data_update_ci_dest(fd, dest_path);
1656         
1657         work = fd->sidecar_files;
1658         while (work)
1659                 {
1660                 FileData *sfd = work->data;
1661                 
1662                 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1663                 work = work->next;
1664                 }
1665         
1666         g_free(dest_path_full);
1667 }
1668
1669 static gint file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1670 {
1671         if (!file_data_sc_check_ci(fd, type)) return FALSE;
1672         file_data_sc_update_ci(fd, dest_path);
1673         return TRUE;
1674 }
1675
1676 gint file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1677 {
1678         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1679 }
1680         
1681 gint file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1682 {
1683         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1684 }
1685
1686 gint file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1687 {
1688         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1689 }
1690
1691 gint file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1692 {
1693         return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1694 }
1695
1696 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1697                                                       const gchar *dest,
1698                                                       gboolean (*func)(FileData *, const gchar *))
1699 {
1700         GList *work;
1701         gboolean ret = TRUE;
1702         
1703         work = fd_list;
1704         while (work)
1705                 {
1706                 FileData *fd = work->data;
1707                 
1708                 if (!func(fd, dest)) ret = FALSE;
1709                 work = work->next;
1710                 }
1711         
1712         return ret;
1713 }
1714
1715 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1716 {
1717         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1718 }
1719
1720 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1721 {
1722         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1723 }
1724
1725 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1726 {
1727         return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1728 }
1729
1730
1731 /*
1732  * verify source and dest paths - dest image exists, etc.
1733  * it should detect all possible problems with the planned operation
1734  */
1735
1736 gint file_data_verify_ci(FileData *fd)
1737 {
1738         gint ret = CHANGE_OK;
1739         gchar *dir;
1740         
1741         if (!fd->change)
1742                 {
1743                 DEBUG_1("Change checked: no change info: %s", fd->path);
1744                 return ret;
1745                 }
1746
1747         if (!isname(fd->path))
1748                 {
1749                 /* this probably should not happen */
1750                 ret |= CHANGE_NO_SRC;
1751                 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1752                 return ret;
1753                 }
1754                 
1755         dir = remove_level_from_path(fd->path);
1756         
1757         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1758             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1759             !access_file(fd->path, R_OK))
1760                 {
1761                 ret |= CHANGE_NO_READ_PERM;
1762                 DEBUG_1("Change checked: no read permission: %s", fd->path);
1763                 }
1764         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1765                  !access_file(dir, W_OK))
1766                 {
1767                 ret |= CHANGE_NO_WRITE_PERM_DIR;
1768                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1769                 }
1770         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1771                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1772                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1773                  !access_file(fd->path, W_OK))
1774                 {
1775                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1776                 DEBUG_1("Change checked: no write permission: %s", fd->path);
1777                 }
1778         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1779            - that means that there are no hard errors and warnings can be disabled
1780            - the destination is determined during the check
1781         */
1782         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1783                 {
1784                 /* determine destination file */
1785                 gboolean have_dest = FALSE;
1786                 gchar *dest_dir = NULL;
1787                 
1788                 if (options->metadata.save_in_image_file)
1789                         {
1790                         if (file_data_can_write_directly(fd)) 
1791                                 {
1792                                 /* we can write the file directly */
1793                                 if (access_file(fd->path, W_OK))
1794                                         {
1795                                         have_dest = TRUE;
1796                                         }
1797                                 else
1798                                         {
1799                                         if (options->metadata.warn_on_write_problems)
1800                                                 {
1801                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1802                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1803                                                 }
1804                                         }
1805                                 }
1806                         else if (file_data_can_write_sidecar(fd)) 
1807                                 {
1808                                 /* we can write sidecar */
1809                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1810                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1811                                         {
1812                                         file_data_update_ci_dest(fd, sidecar);
1813                                         have_dest = TRUE;
1814                                         }
1815                                 else
1816                                         {
1817                                         if (options->metadata.warn_on_write_problems)
1818                                                 {
1819                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1820                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
1821                                                 }
1822                                         }
1823                                 g_free(sidecar);
1824                                 }
1825                         }
1826                 
1827                 if (!have_dest)
1828                         {
1829                         /* write private metadata file under ~/.geeqie */
1830
1831                         /* If an existing metadata file exists, we will try writing to
1832                          * it's location regardless of the user's preference.
1833                          */
1834                         gchar *metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
1835                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
1836                         
1837                         if (metadata_path && !access_file(metadata_path, W_OK))
1838                                 {
1839                                 g_free(metadata_path);
1840                                 metadata_path = NULL;
1841                                 }
1842
1843                         if (!metadata_path)
1844                                 {
1845                                 mode_t mode = 0755;
1846
1847                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
1848                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
1849                                         {
1850                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
1851                         
1852                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
1853                                         g_free(filename);
1854                                         }
1855                                 }
1856                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
1857                                 {
1858                                 file_data_update_ci_dest(fd, metadata_path);
1859                                 have_dest = TRUE;
1860                                 }
1861                         else
1862                                 {
1863                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
1864                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
1865                                 }
1866                         g_free(metadata_path);
1867                         }
1868                 g_free(dest_dir);
1869                 }
1870                 
1871         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
1872                 {
1873                 gboolean same;
1874                 gchar *dest_dir;
1875                         
1876                 same = (strcmp(fd->path, fd->change->dest) == 0);
1877
1878                 if (!same)
1879                         {
1880                         const gchar *dest_ext = extension_from_path(fd->change->dest);
1881                         if (!dest_ext) dest_ext = "";
1882
1883                         if (strcasecmp(fd->extension, dest_ext) != 0)
1884                                 {
1885                                 ret |= CHANGE_WARN_CHANGED_EXT;
1886                                 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1887                                 }
1888                         }
1889                 else
1890                         {
1891                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
1892                                 {
1893                                 ret |= CHANGE_WARN_SAME;
1894                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
1895                                 }
1896                         }
1897
1898                 dest_dir = remove_level_from_path(fd->change->dest);
1899
1900                 if (!isdir(dest_dir))
1901                         {
1902                         ret |= CHANGE_NO_DEST_DIR;
1903                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1904                         }
1905                 else if (!access_file(dest_dir, W_OK))
1906                         {
1907                         ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1908                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1909                         }
1910                 else if (!same)
1911                         {
1912                         if (isfile(fd->change->dest))
1913                                 {
1914                                 if (!access_file(fd->change->dest, W_OK))
1915                                         {
1916                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
1917                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
1918                                         }
1919                                 else
1920                                         {
1921                                         ret |= CHANGE_WARN_DEST_EXISTS;
1922                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1923                                         }
1924                                 }
1925                         else if (isdir(fd->change->dest))
1926                                 {
1927                                 ret |= CHANGE_DEST_EXISTS;
1928                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
1929                                 }
1930                         }
1931
1932                 g_free(dest_dir);
1933                 }
1934                 
1935         fd->change->error = ret;
1936         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
1937
1938         g_free(dir);
1939         return ret;
1940 }
1941
1942
1943 gint file_data_sc_verify_ci(FileData *fd)
1944 {
1945         GList *work;
1946         gint ret;
1947
1948         ret = file_data_verify_ci(fd);
1949
1950         work = fd->sidecar_files;
1951         while (work)
1952                 {
1953                 FileData *sfd = work->data;
1954
1955                 ret |= file_data_verify_ci(sfd);
1956                 work = work->next;
1957                 }
1958
1959         return ret;
1960 }
1961
1962 gchar *file_data_get_error_string(gint error)
1963 {
1964         GString *result = g_string_new("");
1965
1966         if (error & CHANGE_NO_SRC)
1967                 {
1968                 if (result->len > 0) g_string_append(result, ", ");
1969                 g_string_append(result, _("file or directory does not exist"));
1970                 }
1971
1972         if (error & CHANGE_DEST_EXISTS)
1973                 {
1974                 if (result->len > 0) g_string_append(result, ", ");
1975                 g_string_append(result, _("destination already exists"));
1976                 }
1977
1978         if (error & CHANGE_NO_WRITE_PERM_DEST)
1979                 {
1980                 if (result->len > 0) g_string_append(result, ", ");
1981                 g_string_append(result, _("destination can't be overwritten"));
1982                 }
1983
1984         if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
1985                 {
1986                 if (result->len > 0) g_string_append(result, ", ");
1987                 g_string_append(result, _("destination directory is not writable"));
1988                 }
1989
1990         if (error & CHANGE_NO_DEST_DIR)
1991                 {
1992                 if (result->len > 0) g_string_append(result, ", ");
1993                 g_string_append(result, _("destination directory does not exist"));
1994                 }
1995
1996         if (error & CHANGE_NO_WRITE_PERM_DIR)
1997                 {
1998                 if (result->len > 0) g_string_append(result, ", ");
1999                 g_string_append(result, _("source directory is not writable"));
2000                 }
2001
2002         if (error & CHANGE_NO_READ_PERM)
2003                 {
2004                 if (result->len > 0) g_string_append(result, ", ");
2005                 g_string_append(result, _("no read permission"));
2006                 }
2007
2008         if (error & CHANGE_WARN_NO_WRITE_PERM)
2009                 {
2010                 if (result->len > 0) g_string_append(result, ", ");
2011                 g_string_append(result, _("file is readonly"));
2012                 }
2013
2014         if (error & CHANGE_WARN_DEST_EXISTS)
2015                 {
2016                 if (result->len > 0) g_string_append(result, ", ");
2017                 g_string_append(result, _("destination already exists and will be overwritten"));
2018                 }
2019                 
2020         if (error & CHANGE_WARN_SAME)
2021                 {
2022                 if (result->len > 0) g_string_append(result, ", ");
2023                 g_string_append(result, _("source and destination are the same"));
2024                 }
2025
2026         if (error & CHANGE_WARN_CHANGED_EXT)
2027                 {
2028                 if (result->len > 0) g_string_append(result, ", ");
2029                 g_string_append(result, _("source and destination have different extension"));
2030                 }
2031
2032         return g_string_free(result, FALSE);
2033 }
2034
2035 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2036 {
2037         GList *work;
2038         gint all_errors = 0;
2039         gint common_errors = ~0;
2040         gint num;
2041         gint *errors;
2042         gint i;
2043         
2044         if (!list) return 0;
2045         
2046         num = g_list_length(list);
2047         errors = g_new(int, num);
2048         work = list;
2049         i = 0;
2050         while (work)
2051                 {
2052                 FileData *fd;
2053                 gint error;
2054
2055                 fd = work->data;
2056                 work = work->next;
2057                         
2058                 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2059                 all_errors |= error;
2060                 common_errors &= error;
2061                 
2062                 errors[i] = error;
2063                 
2064                 i++;
2065                 }
2066         
2067         if (desc && all_errors)
2068                 {
2069                 GList *work;
2070                 GString *result = g_string_new("");
2071                 
2072                 if (common_errors)
2073                         {
2074                         gchar *str = file_data_get_error_string(common_errors);
2075                         g_string_append(result, str);
2076                         g_string_append(result, "\n");
2077                         g_free(str);
2078                         }
2079                 
2080                 work = list;
2081                 i = 0;
2082                 while (work)
2083                         {
2084                         FileData *fd;
2085                         gint error;
2086
2087                         fd = work->data;
2088                         work = work->next;
2089                         
2090                         error = errors[i] & ~common_errors;
2091                         
2092                         if (error)
2093                                 {
2094                                 gchar *str = file_data_get_error_string(error);
2095                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2096                                 g_free(str);
2097                                 }
2098                         i++;
2099                         }
2100                 *desc = g_string_free(result, FALSE);
2101                 }
2102
2103         g_free(errors);
2104         return all_errors;
2105 }
2106
2107
2108 /*
2109  * perform the change described by FileFataChangeInfo
2110  * it is used for internal operations,
2111  * this function actually operates with files on the filesystem
2112  * it should implement safe delete
2113  */
2114
2115 static gboolean file_data_perform_move(FileData *fd)
2116 {
2117         g_assert(!strcmp(fd->change->source, fd->path));
2118         return move_file(fd->change->source, fd->change->dest);
2119 }
2120
2121 static gboolean file_data_perform_copy(FileData *fd)
2122 {
2123         g_assert(!strcmp(fd->change->source, fd->path));
2124         return copy_file(fd->change->source, fd->change->dest);
2125 }
2126
2127 static gboolean file_data_perform_delete(FileData *fd)
2128 {
2129         if (isdir(fd->path) && !islink(fd->path))
2130                 return rmdir_utf8(fd->path);
2131         else
2132                 if (options->file_ops.safe_delete_enable)
2133                         return file_util_safe_unlink(fd->path);
2134                 else
2135                         return unlink_file(fd->path);
2136 }
2137
2138 gboolean file_data_perform_ci(FileData *fd)
2139 {
2140         FileDataChangeType type = fd->change->type;
2141         switch (type)
2142                 {
2143                 case FILEDATA_CHANGE_MOVE:
2144                         return file_data_perform_move(fd);
2145                 case FILEDATA_CHANGE_COPY:
2146                         return file_data_perform_copy(fd);
2147                 case FILEDATA_CHANGE_RENAME:
2148                         return file_data_perform_move(fd); /* the same as move */
2149                 case FILEDATA_CHANGE_DELETE:
2150                         return file_data_perform_delete(fd);
2151                 case FILEDATA_CHANGE_WRITE_METADATA:
2152                         return metadata_write_perform(fd);
2153                 case FILEDATA_CHANGE_UNSPECIFIED:
2154                         /* nothing to do here */
2155                         break;
2156                 }
2157         return TRUE;
2158 }
2159
2160
2161
2162 gboolean file_data_sc_perform_ci(FileData *fd)
2163 {
2164         GList *work;
2165         gboolean ret = TRUE;
2166         FileDataChangeType type = fd->change->type;
2167         
2168         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2169
2170         work = fd->sidecar_files;
2171         while (work)
2172                 {
2173                 FileData *sfd = work->data;
2174                 
2175                 if (!file_data_perform_ci(sfd)) ret = FALSE;
2176                 work = work->next;
2177                 }
2178         
2179         if (!file_data_perform_ci(fd)) ret = FALSE;
2180         
2181         return ret;
2182 }
2183
2184 /*
2185  * updates FileData structure according to FileDataChangeInfo
2186  */
2187
2188 gint file_data_apply_ci(FileData *fd)
2189 {
2190         FileDataChangeType type = fd->change->type;
2191
2192         /* FIXME delete ?*/
2193         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2194                 {
2195                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2196                 file_data_planned_change_remove(fd);
2197                 
2198                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2199                         {
2200                         /* this change overwrites another file which is already known to other modules
2201                            renaming fd would create duplicate FileData structure
2202                            the best thing we can do is nothing
2203                            FIXME: maybe we could copy stuff like marks
2204                         */
2205                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2206                         }
2207                 else
2208                         {
2209                         file_data_set_path(fd, fd->change->dest);
2210                         }
2211                 }
2212         file_data_increment_version(fd);
2213         file_data_send_notification(fd, NOTIFY_TYPE_CHANGE);
2214         
2215         return TRUE;
2216 }
2217
2218 gint file_data_sc_apply_ci(FileData *fd)
2219 {
2220         GList *work;
2221         FileDataChangeType type = fd->change->type;
2222         
2223         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2224
2225         work = fd->sidecar_files;
2226         while (work)
2227                 {
2228                 FileData *sfd = work->data;
2229                 
2230                 file_data_apply_ci(sfd);
2231                 work = work->next;
2232                 }
2233         
2234         file_data_apply_ci(fd);
2235         
2236         return TRUE;
2237 }
2238
2239 /*
2240  * notify other modules about the change described by FileFataChangeInfo
2241  */
2242
2243 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2244    FIXME do we need the ignore_list? It looks like a workaround for ineffective
2245    implementation in view_file_list.c */
2246
2247
2248
2249
2250 typedef struct _NotifyData NotifyData;
2251
2252 struct _NotifyData {
2253         FileDataNotifyFunc func;
2254         gpointer data;
2255         NotifyPriority priority;
2256         };
2257
2258 static GList *notify_func_list = NULL;
2259
2260 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2261 {
2262         NotifyData *nda = (NotifyData *)a;
2263         NotifyData *ndb = (NotifyData *)b;
2264
2265         if (nda->priority < ndb->priority) return -1;
2266         if (nda->priority > ndb->priority) return 1;
2267         return 0;
2268 }
2269
2270 gint file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2271 {
2272         NotifyData *nd;
2273         
2274         nd = g_new(NotifyData, 1);
2275         nd->func = func;
2276         nd->data = data;
2277         nd->priority = priority;
2278
2279         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2280         DEBUG_1("Notify func registered: %p", nd);
2281         
2282         return TRUE;
2283 }
2284
2285 gint file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2286 {
2287         GList *work = notify_func_list;
2288         
2289         while (work)
2290                 {
2291                 NotifyData *nd = (NotifyData *)work->data;
2292         
2293                 if (nd->func == func && nd->data == data)
2294                         {
2295                         notify_func_list = g_list_delete_link(notify_func_list, work);
2296                         g_free(nd);
2297                         DEBUG_1("Notify func unregistered: %p", nd);
2298                         return TRUE;
2299                         }
2300                 work = work->next;
2301                 }
2302
2303         return FALSE;
2304 }
2305
2306
2307 void file_data_send_notification(FileData *fd, NotifyType type)
2308 {
2309         GList *work = notify_func_list;
2310
2311         while (work)
2312                 {
2313                 NotifyData *nd = (NotifyData *)work->data;
2314                 
2315                 DEBUG_1("Notify func calling: %p %s", nd, fd->path);
2316                 nd->func(fd, type, nd->data);
2317                 work = work->next;
2318                 }
2319 }
2320
2321 static GHashTable *file_data_monitor_pool = NULL;
2322 static gint realtime_monitor_id = -1;
2323
2324 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2325 {
2326         FileData *fd = key;
2327
2328         file_data_check_changed_files(fd);
2329         
2330         DEBUG_1("monitor %s", fd->path);
2331 }
2332
2333 static gboolean realtime_monitor_cb(gpointer data)
2334 {
2335         if (!options->update_on_time_change) return TRUE;
2336         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2337         return TRUE;
2338 }
2339
2340 gint file_data_register_real_time_monitor(FileData *fd)
2341 {
2342         gint count;
2343         
2344         file_data_ref(fd);
2345         
2346         if (!file_data_monitor_pool)
2347                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2348         
2349         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2350
2351         DEBUG_1("Register realtime %d %s", count, fd->path);
2352         
2353         count++;
2354         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2355         
2356         if (realtime_monitor_id == -1)
2357                 {
2358                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2359                 }
2360         
2361         return TRUE;
2362 }
2363
2364 gint file_data_unregister_real_time_monitor(FileData *fd)
2365 {
2366         gint count;
2367
2368         g_assert(file_data_monitor_pool);
2369         
2370         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2371         
2372         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2373         
2374         g_assert(count > 0);
2375         
2376         count--;
2377         
2378         if (count == 0)
2379                 g_hash_table_remove(file_data_monitor_pool, fd);
2380         else
2381                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2382
2383         file_data_unref(fd);
2384         
2385         if (g_hash_table_size(file_data_monitor_pool) == 0)
2386                 {
2387                 g_source_remove(realtime_monitor_id);
2388                 realtime_monitor_id = -1;
2389                 return FALSE;
2390                 }
2391         
2392         return TRUE;
2393 }
2394 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */