fixes for a built without Exiv2
[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_MOVE && /* the unsaved metadata should survive move and rename operations */
1867             fd->change->type != FILEDATA_CHANGE_RENAME &&
1868             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1869             fd->modified_xmp)
1870                 {
1871                 ret |= CHANGE_WARN_UNSAVED_META;
1872                 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1873                 }
1874         
1875         if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1876             fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1877             !access_file(fd->path, R_OK))
1878                 {
1879                 ret |= CHANGE_NO_READ_PERM;
1880                 DEBUG_1("Change checked: no read permission: %s", fd->path);
1881                 }
1882         else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1883                  !access_file(dir, W_OK))
1884                 {
1885                 ret |= CHANGE_NO_WRITE_PERM_DIR;
1886                 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1887                 }
1888         else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1889                  fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1890                  fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1891                  !access_file(fd->path, W_OK))
1892                 {
1893                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1894                 DEBUG_1("Change checked: no write permission: %s", fd->path);
1895                 }
1896         /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1897            - that means that there are no hard errors and warnings can be disabled
1898            - the destination is determined during the check
1899         */
1900         else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1901                 {
1902                 /* determine destination file */
1903                 gboolean have_dest = FALSE;
1904                 gchar *dest_dir = NULL;
1905                 
1906                 if (options->metadata.save_in_image_file)
1907                         {
1908                         if (file_data_can_write_directly(fd)) 
1909                                 {
1910                                 /* we can write the file directly */
1911                                 if (access_file(fd->path, W_OK))
1912                                         {
1913                                         have_dest = TRUE;
1914                                         }
1915                                 else
1916                                         {
1917                                         if (options->metadata.warn_on_write_problems)
1918                                                 {
1919                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1920                                                 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1921                                                 }
1922                                         }
1923                                 }
1924                         else if (file_data_can_write_sidecar(fd)) 
1925                                 {
1926                                 /* we can write sidecar */
1927                                 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1928                                 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1929                                         {
1930                                         file_data_update_ci_dest(fd, sidecar);
1931                                         have_dest = TRUE;
1932                                         }
1933                                 else
1934                                         {
1935                                         if (options->metadata.warn_on_write_problems)
1936                                                 {
1937                                                 ret |= CHANGE_WARN_NO_WRITE_PERM;
1938                                                 DEBUG_1("Change checked: file is not writable: %s", sidecar);
1939                                                 }
1940                                         }
1941                                 g_free(sidecar);
1942                                 }
1943                         }
1944                 
1945                 if (!have_dest)
1946                         {
1947                         /* write private metadata file under ~/.geeqie */
1948
1949                         /* If an existing metadata file exists, we will try writing to
1950                          * it's location regardless of the user's preference.
1951                          */
1952                         gchar *metadata_path = NULL;
1953 #ifdef HAVE_EXIV2
1954                         /* but ignore XMP if we are not able to write it */
1955                         metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
1956 #endif
1957                         if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
1958                         
1959                         if (metadata_path && !access_file(metadata_path, W_OK))
1960                                 {
1961                                 g_free(metadata_path);
1962                                 metadata_path = NULL;
1963                                 }
1964
1965                         if (!metadata_path)
1966                                 {
1967                                 mode_t mode = 0755;
1968
1969                                 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
1970                                 if (recursive_mkdir_if_not_exists(dest_dir, mode))
1971                                         {
1972                                         gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
1973                         
1974                                         metadata_path = g_build_filename(dest_dir, filename, NULL);
1975                                         g_free(filename);
1976                                         }
1977                                 }
1978                         if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
1979                                 {
1980                                 file_data_update_ci_dest(fd, metadata_path);
1981                                 have_dest = TRUE;
1982                                 }
1983                         else
1984                                 {
1985                                 ret |= CHANGE_NO_WRITE_PERM_DEST;
1986                                 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
1987                                 }
1988                         g_free(metadata_path);
1989                         }
1990                 g_free(dest_dir);
1991                 }
1992                 
1993         if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
1994                 {
1995                 gboolean same;
1996                 gchar *dest_dir;
1997                         
1998                 same = (strcmp(fd->path, fd->change->dest) == 0);
1999
2000                 if (!same)
2001                         {
2002                         const gchar *dest_ext = extension_from_path(fd->change->dest);
2003                         if (!dest_ext) dest_ext = "";
2004
2005                         if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2006                                 {
2007                                 ret |= CHANGE_WARN_CHANGED_EXT;
2008                                 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2009                                 }
2010                         }
2011                 else
2012                         {
2013                         if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2014                                 {
2015                                 ret |= CHANGE_WARN_SAME;
2016                                 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2017                                 }
2018                         }
2019
2020                 dest_dir = remove_level_from_path(fd->change->dest);
2021
2022                 if (!isdir(dest_dir))
2023                         {
2024                         ret |= CHANGE_NO_DEST_DIR;
2025                         DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2026                         }
2027                 else if (!access_file(dest_dir, W_OK))
2028                         {
2029                         ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2030                         DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2031                         }
2032                 else if (!same)
2033                         {
2034                         if (isfile(fd->change->dest))
2035                                 {
2036                                 if (!access_file(fd->change->dest, W_OK))
2037                                         {
2038                                         ret |= CHANGE_NO_WRITE_PERM_DEST;
2039                                         DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2040                                         }
2041                                 else
2042                                         {
2043                                         ret |= CHANGE_WARN_DEST_EXISTS;
2044                                         DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2045                                         }
2046                                 }
2047                         else if (isdir(fd->change->dest))
2048                                 {
2049                                 ret |= CHANGE_DEST_EXISTS;
2050                                 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2051                                 }
2052                         }
2053
2054                 g_free(dest_dir);
2055                 }
2056                 
2057         fd->change->error = ret;
2058         if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2059
2060         g_free(dir);
2061         return ret;
2062 }
2063
2064
2065 gint file_data_sc_verify_ci(FileData *fd)
2066 {
2067         GList *work;
2068         gint ret;
2069
2070         ret = file_data_verify_ci(fd);
2071
2072         work = fd->sidecar_files;
2073         while (work)
2074                 {
2075                 FileData *sfd = work->data;
2076
2077                 ret |= file_data_verify_ci(sfd);
2078                 work = work->next;
2079                 }
2080
2081         return ret;
2082 }
2083
2084 gchar *file_data_get_error_string(gint error)
2085 {
2086         GString *result = g_string_new("");
2087
2088         if (error & CHANGE_NO_SRC)
2089                 {
2090                 if (result->len > 0) g_string_append(result, ", ");
2091                 g_string_append(result, _("file or directory does not exist"));
2092                 }
2093
2094         if (error & CHANGE_DEST_EXISTS)
2095                 {
2096                 if (result->len > 0) g_string_append(result, ", ");
2097                 g_string_append(result, _("destination already exists"));
2098                 }
2099
2100         if (error & CHANGE_NO_WRITE_PERM_DEST)
2101                 {
2102                 if (result->len > 0) g_string_append(result, ", ");
2103                 g_string_append(result, _("destination can't be overwritten"));
2104                 }
2105
2106         if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2107                 {
2108                 if (result->len > 0) g_string_append(result, ", ");
2109                 g_string_append(result, _("destination directory is not writable"));
2110                 }
2111
2112         if (error & CHANGE_NO_DEST_DIR)
2113                 {
2114                 if (result->len > 0) g_string_append(result, ", ");
2115                 g_string_append(result, _("destination directory does not exist"));
2116                 }
2117
2118         if (error & CHANGE_NO_WRITE_PERM_DIR)
2119                 {
2120                 if (result->len > 0) g_string_append(result, ", ");
2121                 g_string_append(result, _("source directory is not writable"));
2122                 }
2123
2124         if (error & CHANGE_NO_READ_PERM)
2125                 {
2126                 if (result->len > 0) g_string_append(result, ", ");
2127                 g_string_append(result, _("no read permission"));
2128                 }
2129
2130         if (error & CHANGE_WARN_NO_WRITE_PERM)
2131                 {
2132                 if (result->len > 0) g_string_append(result, ", ");
2133                 g_string_append(result, _("file is readonly"));
2134                 }
2135
2136         if (error & CHANGE_WARN_DEST_EXISTS)
2137                 {
2138                 if (result->len > 0) g_string_append(result, ", ");
2139                 g_string_append(result, _("destination already exists and will be overwritten"));
2140                 }
2141                 
2142         if (error & CHANGE_WARN_SAME)
2143                 {
2144                 if (result->len > 0) g_string_append(result, ", ");
2145                 g_string_append(result, _("source and destination are the same"));
2146                 }
2147
2148         if (error & CHANGE_WARN_CHANGED_EXT)
2149                 {
2150                 if (result->len > 0) g_string_append(result, ", ");
2151                 g_string_append(result, _("source and destination have different extension"));
2152                 }
2153
2154         if (error & CHANGE_WARN_UNSAVED_META)
2155                 {
2156                 if (result->len > 0) g_string_append(result, ", ");
2157                 g_string_append(result, _("there are unsaved metadata changes for the file"));
2158                 }
2159
2160         return g_string_free(result, FALSE);
2161 }
2162
2163 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2164 {
2165         GList *work;
2166         gint all_errors = 0;
2167         gint common_errors = ~0;
2168         gint num;
2169         gint *errors;
2170         gint i;
2171         
2172         if (!list) return 0;
2173         
2174         num = g_list_length(list);
2175         errors = g_new(int, num);
2176         work = list;
2177         i = 0;
2178         while (work)
2179                 {
2180                 FileData *fd;
2181                 gint error;
2182
2183                 fd = work->data;
2184                 work = work->next;
2185                         
2186                 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2187                 all_errors |= error;
2188                 common_errors &= error;
2189                 
2190                 errors[i] = error;
2191                 
2192                 i++;
2193                 }
2194         
2195         if (desc && all_errors)
2196                 {
2197                 GList *work;
2198                 GString *result = g_string_new("");
2199                 
2200                 if (common_errors)
2201                         {
2202                         gchar *str = file_data_get_error_string(common_errors);
2203                         g_string_append(result, str);
2204                         g_string_append(result, "\n");
2205                         g_free(str);
2206                         }
2207                 
2208                 work = list;
2209                 i = 0;
2210                 while (work)
2211                         {
2212                         FileData *fd;
2213                         gint error;
2214
2215                         fd = work->data;
2216                         work = work->next;
2217                         
2218                         error = errors[i] & ~common_errors;
2219                         
2220                         if (error)
2221                                 {
2222                                 gchar *str = file_data_get_error_string(error);
2223                                 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2224                                 g_free(str);
2225                                 }
2226                         i++;
2227                         }
2228                 *desc = g_string_free(result, FALSE);
2229                 }
2230
2231         g_free(errors);
2232         return all_errors;
2233 }
2234
2235
2236 /*
2237  * perform the change described by FileFataChangeInfo
2238  * it is used for internal operations,
2239  * this function actually operates with files on the filesystem
2240  * it should implement safe delete
2241  */
2242
2243 static gboolean file_data_perform_move(FileData *fd)
2244 {
2245         g_assert(!strcmp(fd->change->source, fd->path));
2246         return move_file(fd->change->source, fd->change->dest);
2247 }
2248
2249 static gboolean file_data_perform_copy(FileData *fd)
2250 {
2251         g_assert(!strcmp(fd->change->source, fd->path));
2252         return copy_file(fd->change->source, fd->change->dest);
2253 }
2254
2255 static gboolean file_data_perform_delete(FileData *fd)
2256 {
2257         if (isdir(fd->path) && !islink(fd->path))
2258                 return rmdir_utf8(fd->path);
2259         else
2260                 if (options->file_ops.safe_delete_enable)
2261                         return file_util_safe_unlink(fd->path);
2262                 else
2263                         return unlink_file(fd->path);
2264 }
2265
2266 gboolean file_data_perform_ci(FileData *fd)
2267 {
2268         FileDataChangeType type = fd->change->type;
2269
2270         switch (type)
2271                 {
2272                 case FILEDATA_CHANGE_MOVE:
2273                         return file_data_perform_move(fd);
2274                 case FILEDATA_CHANGE_COPY:
2275                         return file_data_perform_copy(fd);
2276                 case FILEDATA_CHANGE_RENAME:
2277                         return file_data_perform_move(fd); /* the same as move */
2278                 case FILEDATA_CHANGE_DELETE:
2279                         return file_data_perform_delete(fd);
2280                 case FILEDATA_CHANGE_WRITE_METADATA:
2281                         return metadata_write_perform(fd);
2282                 case FILEDATA_CHANGE_UNSPECIFIED:
2283                         /* nothing to do here */
2284                         break;
2285                 }
2286         return TRUE;
2287 }
2288
2289
2290
2291 gboolean file_data_sc_perform_ci(FileData *fd)
2292 {
2293         GList *work;
2294         gboolean ret = TRUE;
2295         FileDataChangeType type = fd->change->type;
2296         
2297         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2298
2299         work = fd->sidecar_files;
2300         while (work)
2301                 {
2302                 FileData *sfd = work->data;
2303                 
2304                 if (!file_data_perform_ci(sfd)) ret = FALSE;
2305                 work = work->next;
2306                 }
2307         
2308         if (!file_data_perform_ci(fd)) ret = FALSE;
2309         
2310         return ret;
2311 }
2312
2313 /*
2314  * updates FileData structure according to FileDataChangeInfo
2315  */
2316
2317 gboolean file_data_apply_ci(FileData *fd)
2318 {
2319         FileDataChangeType type = fd->change->type;
2320
2321         /* FIXME delete ?*/
2322         if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2323                 {
2324                 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2325                 file_data_planned_change_remove(fd);
2326                 
2327                 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2328                         {
2329                         /* this change overwrites another file which is already known to other modules
2330                            renaming fd would create duplicate FileData structure
2331                            the best thing we can do is nothing
2332                            FIXME: maybe we could copy stuff like marks
2333                         */
2334                         DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2335                         }
2336                 else
2337                         {
2338                         file_data_set_path(fd, fd->change->dest);
2339                         }
2340                 }
2341         file_data_increment_version(fd);
2342         file_data_send_notification(fd, NOTIFY_CHANGE);
2343         
2344         return TRUE;
2345 }
2346
2347 gboolean file_data_sc_apply_ci(FileData *fd)
2348 {
2349         GList *work;
2350         FileDataChangeType type = fd->change->type;
2351         
2352         if (!file_data_sc_check_ci(fd, type)) return FALSE;
2353
2354         work = fd->sidecar_files;
2355         while (work)
2356                 {
2357                 FileData *sfd = work->data;
2358                 
2359                 file_data_apply_ci(sfd);
2360                 work = work->next;
2361                 }
2362         
2363         file_data_apply_ci(fd);
2364         
2365         return TRUE;
2366 }
2367
2368 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2369 {
2370         GList *work;
2371         if (fd->parent) fd = fd->parent;
2372         if (!g_list_find(list, fd)) return FALSE;
2373         
2374         work = fd->sidecar_files;
2375         while (work)
2376                 {
2377                 if (!g_list_find(list, work->data)) return FALSE;
2378                 work = work->next;
2379                 }
2380         return TRUE;
2381 }
2382
2383 #if 0
2384 static gboolean file_data_list_dump(GList *list)
2385 {
2386         GList *work, *work2;
2387
2388         work = list;
2389         while (work)
2390                 {
2391                 FileData *fd = work->data;
2392                 printf("%s\n", fd->name);
2393                 work2 = fd->sidecar_files;
2394                 while (work2)
2395                         {
2396                         FileData *fd = work2->data;
2397                         printf("       %s\n", fd->name);
2398                         work2 = work2->next;
2399                         }
2400                 work = work->next;
2401                 }
2402         return TRUE;
2403 }
2404 #endif
2405
2406 GList *file_data_process_groups_in_selection(GList *list, GList **ungrouped_list)
2407 {
2408         GList *out = NULL;
2409         GList *work = list;
2410
2411         /* change partial groups to independent files */
2412         while (work)
2413                 {
2414                 FileData *fd = work->data;
2415                 work = work->next;
2416                 
2417                 if (!file_data_list_contains_whole_group(list, fd)) 
2418                         {
2419                         file_data_disable_grouping(fd, TRUE);
2420                         if (ungrouped_list) 
2421                                 {
2422                                 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2423                                 }
2424                         }
2425                 }
2426         
2427         /* remove sidecars from the list, 
2428            they can be still acessed via main_fd->sidecar_files */
2429         work = list;
2430         while (work)
2431                 {
2432                 FileData *fd = work->data;
2433                 work = work->next;
2434                 
2435                 if (!fd->parent)
2436                         {
2437                         out = g_list_prepend(out, fd);
2438                         }
2439                 else
2440                         {
2441                         file_data_unref(fd);
2442                         }
2443                 }
2444                 
2445         g_list_free(list);
2446         out = g_list_reverse(out);
2447
2448         return out;
2449 }
2450
2451
2452
2453
2454
2455 /*
2456  * notify other modules about the change described by FileDataChangeInfo
2457  */
2458
2459 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2460    FIXME do we need the ignore_list? It looks like a workaround for ineffective
2461    implementation in view_file_list.c */
2462
2463
2464
2465
2466 typedef struct _NotifyData NotifyData;
2467
2468 struct _NotifyData {
2469         FileDataNotifyFunc func;
2470         gpointer data;
2471         NotifyPriority priority;
2472 };
2473
2474 static GList *notify_func_list = NULL;
2475
2476 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2477 {
2478         NotifyData *nda = (NotifyData *)a;
2479         NotifyData *ndb = (NotifyData *)b;
2480
2481         if (nda->priority < ndb->priority) return -1;
2482         if (nda->priority > ndb->priority) return 1;
2483         return 0;
2484 }
2485
2486 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2487 {
2488         NotifyData *nd;
2489         
2490         nd = g_new(NotifyData, 1);
2491         nd->func = func;
2492         nd->data = data;
2493         nd->priority = priority;
2494
2495         notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2496         DEBUG_2("Notify func registered: %p", nd);
2497         
2498         return TRUE;
2499 }
2500
2501 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2502 {
2503         GList *work = notify_func_list;
2504         
2505         while (work)
2506                 {
2507                 NotifyData *nd = (NotifyData *)work->data;
2508         
2509                 if (nd->func == func && nd->data == data)
2510                         {
2511                         notify_func_list = g_list_delete_link(notify_func_list, work);
2512                         g_free(nd);
2513                         DEBUG_2("Notify func unregistered: %p", nd);
2514                         return TRUE;
2515                         }
2516                 work = work->next;
2517                 }
2518
2519         return FALSE;
2520 }
2521
2522
2523 void file_data_send_notification(FileData *fd, NotifyType type)
2524 {
2525         GList *work = notify_func_list;
2526
2527         while (work)
2528                 {
2529                 NotifyData *nd = (NotifyData *)work->data;
2530                 
2531                 nd->func(fd, type, nd->data);
2532                 work = work->next;
2533                 }
2534 }
2535
2536 static GHashTable *file_data_monitor_pool = NULL;
2537 static guint realtime_monitor_id = 0; /* event source id */
2538
2539 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2540 {
2541         FileData *fd = key;
2542
2543         file_data_check_changed_files(fd);
2544         
2545         DEBUG_1("monitor %s", fd->path);
2546 }
2547
2548 static gboolean realtime_monitor_cb(gpointer data)
2549 {
2550         if (!options->update_on_time_change) return TRUE;
2551         g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2552         return TRUE;
2553 }
2554
2555 gboolean file_data_register_real_time_monitor(FileData *fd)
2556 {
2557         gint count;
2558         
2559         file_data_ref(fd);
2560         
2561         if (!file_data_monitor_pool)
2562                 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2563         
2564         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2565
2566         DEBUG_1("Register realtime %d %s", count, fd->path);
2567         
2568         count++;
2569         g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2570         
2571         if (!realtime_monitor_id)
2572                 {
2573                 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2574                 }
2575         
2576         return TRUE;
2577 }
2578
2579 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2580 {
2581         gint count;
2582
2583         g_assert(file_data_monitor_pool);
2584         
2585         count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2586         
2587         DEBUG_1("Unregister realtime %d %s", count, fd->path);
2588         
2589         g_assert(count > 0);
2590         
2591         count--;
2592         
2593         if (count == 0)
2594                 g_hash_table_remove(file_data_monitor_pool, fd);
2595         else
2596                 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2597
2598         file_data_unref(fd);
2599         
2600         if (g_hash_table_size(file_data_monitor_pool) == 0)
2601                 {
2602                 g_source_remove(realtime_monitor_id);
2603                 realtime_monitor_id = 0;
2604                 return FALSE;
2605                 }
2606         
2607         return TRUE;
2608 }
2609 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */