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