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