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