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