4 * Copyright (C) 2008 - 2009 The Geeqie Team
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!
17 #include "filefilter.h"
19 #include "thumb_standard.h"
20 #include "ui_fileops.h"
23 #include "histogram.h"
26 static GHashTable *file_data_pool = NULL;
27 static GHashTable *file_data_planned_change_hash = NULL;
28 static GHashTable *file_data_basename_hash = NULL;
30 static gint sidecar_file_priority(const gchar *path);
34 *-----------------------------------------------------------------------------
35 * text conversion utils
36 *-----------------------------------------------------------------------------
39 gchar *text_from_size(gint64 size)
45 /* what I would like to use is printf("%'d", size)
46 * BUT: not supported on every libc :(
50 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
51 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
55 a = g_strdup_printf("%d", (guint)size);
61 b = g_new(gchar, l + n + 1);
86 gchar *text_from_size_abrev(gint64 size)
88 if (size < (gint64)1024)
90 return g_strdup_printf(_("%d bytes"), (gint)size);
92 if (size < (gint64)1048576)
94 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
96 if (size < (gint64)1073741824)
98 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
101 /* to avoid overflowing the gdouble, do division in two steps */
103 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
106 /* note: returned string is valid until next call to text_from_time() */
107 const gchar *text_from_time(time_t t)
109 static gchar *ret = NULL;
113 GError *error = NULL;
115 btime = localtime(&t);
117 /* the %x warning about 2 digit years is not an error */
118 buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
119 if (buflen < 1) return "";
122 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
125 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
134 *-----------------------------------------------------------------------------
136 *-----------------------------------------------------------------------------
139 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
140 static void file_data_check_sidecars(FileData *fd, gboolean stat_sidecars);
141 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
144 void file_data_increment_version(FileData *fd)
150 fd->parent->version++;
151 fd->parent->valid_marks = 0;
155 static void file_data_basename_hash_insert(FileData *fd)
158 const gchar *ext = extension_from_path(fd->path);
159 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
160 if (!file_data_basename_hash)
161 file_data_basename_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
163 list = g_hash_table_lookup(file_data_basename_hash, basename);
165 if (!g_list_find(list, fd))
167 list = g_list_prepend(list, fd);
168 g_hash_table_insert(file_data_basename_hash, basename, list);
176 static void file_data_basename_hash_remove(FileData *fd)
179 const gchar *ext = extension_from_path(fd->path);
180 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
181 if (!file_data_basename_hash)
182 file_data_basename_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
184 list = g_hash_table_lookup(file_data_basename_hash, basename);
186 list = g_list_remove(list, fd);
190 g_hash_table_insert(file_data_basename_hash, basename, list);
194 g_hash_table_remove(file_data_basename_hash, basename);
199 static void file_data_set_collate_keys(FileData *fd)
201 gchar *caseless_name;
203 caseless_name = g_utf8_casefold(fd->name, -1);
205 g_free(fd->collate_key_name);
206 g_free(fd->collate_key_name_nocase);
208 #if GLIB_CHECK_VERSION(2, 8, 0)
209 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
210 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
212 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
213 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
215 g_free(caseless_name);
218 static void file_data_set_path(FileData *fd, const gchar *path)
220 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
221 g_assert(file_data_pool);
223 if (fd->path) file_data_basename_hash_remove(fd);
227 if (fd->original_path)
229 g_hash_table_remove(file_data_pool, fd->original_path);
230 g_free(fd->original_path);
233 g_assert(!g_hash_table_lookup(file_data_pool, path));
235 fd->original_path = g_strdup(path);
236 g_hash_table_insert(file_data_pool, fd->original_path, fd);
238 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
240 fd->path = g_strdup(path);
242 fd->extension = fd->name + 1;
243 file_data_set_collate_keys(fd);
247 fd->path = g_strdup(path);
248 fd->name = filename_from_path(fd->path);
250 if (strcmp(fd->name, "..") == 0)
252 gchar *dir = remove_level_from_path(path);
254 fd->path = remove_level_from_path(dir);
257 fd->extension = fd->name + 2;
258 file_data_set_collate_keys(fd);
261 else if (strcmp(fd->name, ".") == 0)
264 fd->path = remove_level_from_path(path);
266 fd->extension = fd->name + 1;
267 file_data_set_collate_keys(fd);
271 fd->extension = extension_from_path(fd->path);
272 if (fd->extension == NULL)
274 fd->extension = fd->name + strlen(fd->name);
277 file_data_basename_hash_insert(fd); /* we can ignore the special cases above - they don't have extensions */
279 file_data_set_collate_keys(fd);
282 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
284 gboolean ret = FALSE;
287 if (fd->size != st->st_size ||
288 fd->date != st->st_mtime)
290 fd->size = st->st_size;
291 fd->date = st->st_mtime;
292 fd->mode = st->st_mode;
293 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
294 fd->thumb_pixbuf = NULL;
295 file_data_increment_version(fd);
296 file_data_send_notification(fd, NOTIFY_REREAD);
300 work = fd->sidecar_files;
303 FileData *sfd = work->data;
307 if (!stat_utf8(sfd->path, &st))
311 file_data_disconnect_sidecar_file(fd, sfd);
316 ret |= file_data_check_changed_files_recursive(sfd, &st);
322 gboolean file_data_check_changed_files(FileData *fd)
324 gboolean ret = FALSE;
327 if (fd->parent) fd = fd->parent;
329 if (!stat_utf8(fd->path, &st))
332 FileData *sfd = NULL;
334 /* parent is missing, we have to rebuild whole group */
339 work = fd->sidecar_files;
345 file_data_disconnect_sidecar_file(fd, sfd);
347 if (sfd) file_data_check_sidecars(sfd, FALSE); /* this will group the sidecars back together */
348 file_data_send_notification(fd, NOTIFY_REREAD);
352 ret |= file_data_check_changed_files_recursive(fd, &st);
358 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars)
362 DEBUG_2("file_data_new: '%s' %d %d", path_utf8, check_sidecars, stat_sidecars);
365 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
367 fd = g_hash_table_lookup(file_data_pool, path_utf8);
373 if (!fd && file_data_planned_change_hash)
375 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
378 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
380 file_data_apply_ci(fd);
389 changed = file_data_check_changed_files(fd);
391 changed = file_data_check_changed_files_recursive(fd, st);
392 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
393 file_data_check_sidecars(fd, stat_sidecars);
394 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
399 fd = g_new0(FileData, 1);
401 fd->size = st->st_size;
402 fd->date = st->st_mtime;
403 fd->mode = st->st_mode;
405 fd->magick = 0x12345678;
407 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
410 file_data_check_sidecars(fd, stat_sidecars);
415 static void file_data_check_sidecars(FileData *fd, gboolean stat_sidecars)
419 FileData *parent_fd = NULL;
421 GList *basename_list = NULL;
423 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
426 base_len = fd->extension - fd->path;
427 fname = g_string_new_len(fd->path, base_len);
431 basename_list = g_hash_table_lookup(file_data_basename_hash, fname->str);
434 work = sidecar_ext_get_list();
438 /* check for possible sidecar files;
439 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
440 they have fd->ref set to 0 and file_data unref must chack and free them all together
441 (using fd->ref would cause loops and leaks)
445 gchar *ext = work->data;
449 if (g_ascii_strcasecmp(ext, fd->extension) == 0)
451 new_fd = fd; /* processing the original file */
458 g_string_truncate(fname, base_len);
459 if (!stat_utf8_case_insensitive_ext(fname, ext, &nst))
461 new_fd = file_data_new(fname->str, &nst, FALSE, FALSE);
465 GList *work2 = basename_list;
471 FileData *sfd = work2->data;
472 if (g_ascii_strcasecmp(ext, sfd->extension) == 0 &&
473 stat_utf8(sfd->path, &nst)) /* basename list can contain deleted files */
475 new_fd = file_data_ref(sfd);
481 if (!new_fd) continue;
484 if (new_fd->disable_grouping)
486 file_data_unref(new_fd);
490 new_fd->ref--; /* do not use ref here */
494 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
496 file_data_merge_sidecar_files(parent_fd, new_fd);
498 g_string_free(fname, TRUE);
502 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars)
504 gchar *path_utf8 = path_to_utf8(path);
505 FileData *ret = file_data_new(path_utf8, st, check_sidecars, stat_sidecars);
511 FileData *file_data_new_simple(const gchar *path_utf8)
515 if (!stat_utf8(path_utf8, &st))
521 return file_data_new(path_utf8, &st, TRUE, TRUE);
524 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
526 sfd->parent = target;
527 if (!g_list_find(target->sidecar_files, sfd))
528 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
529 file_data_increment_version(sfd); /* increments both sfd and target */
534 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
538 file_data_add_sidecar_file(target, source);
540 work = source->sidecar_files;
543 FileData *sfd = work->data;
544 file_data_add_sidecar_file(target, sfd);
548 g_list_free(source->sidecar_files);
549 source->sidecar_files = NULL;
551 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
556 #ifdef DEBUG_FILEDATA
557 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
559 FileData *file_data_ref(FileData *fd)
562 if (fd == NULL) return NULL;
563 #ifdef DEBUG_FILEDATA
564 if (fd->magick != 0x12345678)
565 DEBUG_0("fd magick mismatch at %s:%d", file, line);
567 g_assert(fd->magick == 0x12345678);
570 #ifdef DEBUG_FILEDATA
571 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
573 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
578 static void file_data_free(FileData *fd)
580 g_assert(fd->magick == 0x12345678);
581 g_assert(fd->ref == 0);
583 if (fd->path) file_data_basename_hash_remove(fd);
584 g_hash_table_remove(file_data_pool, fd->original_path);
587 g_free(fd->original_path);
588 g_free(fd->collate_key_name);
589 g_free(fd->collate_key_name_nocase);
590 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
591 histmap_free(fd->histmap);
593 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
595 file_data_change_info_free(NULL, fd);
599 #ifdef DEBUG_FILEDATA
600 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
602 void file_data_unref(FileData *fd)
605 if (fd == NULL) return;
606 #ifdef DEBUG_FILEDATA
607 if (fd->magick != 0x12345678)
608 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
610 g_assert(fd->magick == 0x12345678);
613 #ifdef DEBUG_FILEDATA
614 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
616 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
621 FileData *parent = fd->parent ? fd->parent : fd;
623 if (parent->ref > 0) return;
625 work = parent->sidecar_files;
628 FileData *sfd = work->data;
629 if (sfd->ref > 0) return;
633 /* none of parent/children is referenced, we can free everything */
635 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
637 work = parent->sidecar_files;
640 FileData *sfd = work->data;
645 g_list_free(parent->sidecar_files);
646 parent->sidecar_files = NULL;
648 file_data_free(parent);
652 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
654 sfd->parent = target;
655 g_assert(g_list_find(target->sidecar_files, sfd));
657 file_data_increment_version(sfd); /* increments both sfd and target */
659 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
671 /* disables / enables grouping for particular file, sends UPDATE notification */
672 void file_data_disable_grouping(FileData *fd, gboolean disable)
674 if (!fd->disable_grouping == !disable) return;
676 fd->disable_grouping = !!disable;
682 FileData *parent = file_data_ref(fd->parent);
683 file_data_disconnect_sidecar_file(parent, fd);
684 file_data_send_notification(parent, NOTIFY_GROUPING);
685 file_data_unref(parent);
687 else if (fd->sidecar_files)
689 GList *sidecar_files = filelist_copy(fd->sidecar_files);
690 GList *work = sidecar_files;
693 FileData *sfd = work->data;
695 file_data_disconnect_sidecar_file(fd, sfd);
696 file_data_send_notification(sfd, NOTIFY_GROUPING);
698 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
699 filelist_free(sidecar_files);
703 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
708 file_data_increment_version(fd);
709 file_data_check_sidecars(fd, FALSE);
711 file_data_send_notification(fd, NOTIFY_GROUPING);
714 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
721 FileData *fd = work->data;
723 file_data_disable_grouping(fd, disable);
729 /* compare name without extension */
730 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
732 size_t len1 = fd1->extension - fd1->name;
733 size_t len2 = fd2->extension - fd2->name;
735 if (len1 < len2) return -1;
736 if (len1 > len2) return 1;
738 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
741 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
743 if (!fdci && fd) fdci = fd->change;
747 g_free(fdci->source);
752 if (fd) fd->change = NULL;
755 static gboolean file_data_can_write_directly(FileData *fd)
757 return filter_name_is_writable(fd->extension);
760 static gboolean file_data_can_write_sidecar(FileData *fd)
762 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
765 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
767 gchar *sidecar_path = NULL;
770 if (!file_data_can_write_sidecar(fd)) return NULL;
772 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
775 FileData *sfd = work->data;
777 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
779 sidecar_path = g_strdup(sfd->path);
784 if (!existing_only && !sidecar_path)
786 gchar *base = remove_extension_from_path(fd->path);
787 sidecar_path = g_strconcat(base, ".xmp", NULL);
796 *-----------------------------------------------------------------------------
797 * sidecar file info struct
798 *-----------------------------------------------------------------------------
803 static gint sidecar_file_priority(const gchar *path)
805 const gchar *extension = extension_from_path(path);
809 if (extension == NULL)
812 work = sidecar_ext_get_list();
815 gchar *ext = work->data;
818 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
826 *-----------------------------------------------------------------------------
828 *-----------------------------------------------------------------------------
831 static SortType filelist_sort_method = SORT_NONE;
832 static gboolean filelist_sort_ascend = TRUE;
835 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
837 if (!filelist_sort_ascend)
844 switch (filelist_sort_method)
849 if (fa->size < fb->size) return -1;
850 if (fa->size > fb->size) return 1;
851 /* fall back to name */
854 if (fa->date < fb->date) return -1;
855 if (fa->date > fb->date) return 1;
856 /* fall back to name */
858 #ifdef HAVE_STRVERSCMP
860 return strverscmp(fa->name, fb->name);
867 if (options->file_sort.case_sensitive)
868 return strcmp(fa->collate_key_name, fb->collate_key_name);
870 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
873 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
875 filelist_sort_method = method;
876 filelist_sort_ascend = ascend;
877 return filelist_sort_compare_filedata(fa, fb);
880 static gint filelist_sort_file_cb(gpointer a, gpointer b)
882 return filelist_sort_compare_filedata(a, b);
885 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
887 filelist_sort_method = method;
888 filelist_sort_ascend = ascend;
889 return g_list_sort(list, cb);
892 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
894 filelist_sort_method = method;
895 filelist_sort_ascend = ascend;
896 return g_list_insert_sorted(list, data, cb);
899 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
901 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
904 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
906 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
910 static GList *filelist_filter_out_sidecars(GList *flist)
913 GList *flist_filtered = NULL;
917 FileData *fd = work->data;
920 if (fd->parent) /* remove fd's that are children */
923 flist_filtered = g_list_prepend(flist_filtered, fd);
927 return flist_filtered;
930 static gboolean is_hidden_file(const gchar *name)
932 if (name[0] != '.') return FALSE;
933 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
937 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
944 gint (*stat_func)(const gchar *path, struct stat *buf);
946 g_assert(files || dirs);
948 if (files) *files = NULL;
949 if (dirs) *dirs = NULL;
951 pathl = path_from_utf8(dir_fd->path);
952 if (!pathl) return FALSE;
966 while ((dir = readdir(dp)) != NULL)
968 struct stat ent_sbuf;
969 const gchar *name = dir->d_name;
972 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
975 filepath = g_build_filename(pathl, name, NULL);
976 if (stat_func(filepath, &ent_sbuf) >= 0)
978 if (S_ISDIR(ent_sbuf.st_mode))
980 /* we ignore the .thumbnails dir for cleanliness */
982 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
983 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
984 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
985 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
987 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, FALSE));
992 if (files && filter_name_exists(name))
994 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, FALSE));
1005 if (dirs) *dirs = dlist;
1006 if (files) *files = filelist_filter_out_sidecars(flist);
1011 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1013 return filelist_read_real(dir_fd, files, dirs, TRUE);
1016 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1018 return filelist_read_real(dir_fd, files, dirs, FALSE);
1021 void filelist_free(GList *list)
1028 file_data_unref((FileData *)work->data);
1036 GList *filelist_copy(GList *list)
1038 GList *new_list = NULL;
1049 new_list = g_list_prepend(new_list, file_data_ref(fd));
1052 return g_list_reverse(new_list);
1055 GList *filelist_from_path_list(GList *list)
1057 GList *new_list = NULL;
1068 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1071 return g_list_reverse(new_list);
1074 GList *filelist_to_path_list(GList *list)
1076 GList *new_list = NULL;
1087 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1090 return g_list_reverse(new_list);
1093 GList *filelist_filter(GList *list, gboolean is_dir_list)
1097 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1102 FileData *fd = (FileData *)(work->data);
1103 const gchar *name = fd->name;
1105 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1106 (!is_dir_list && !filter_name_exists(name)) ||
1107 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1108 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1112 list = g_list_remove_link(list, link);
1113 file_data_unref(fd);
1124 *-----------------------------------------------------------------------------
1125 * filelist recursive
1126 *-----------------------------------------------------------------------------
1129 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1131 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1134 GList *filelist_sort_path(GList *list)
1136 return g_list_sort(list, filelist_sort_path_cb);
1139 static void filelist_recursive_append(GList **list, GList *dirs)
1146 FileData *fd = (FileData *)(work->data);
1150 if (filelist_read(fd, &f, &d))
1152 f = filelist_filter(f, FALSE);
1153 f = filelist_sort_path(f);
1154 *list = g_list_concat(*list, f);
1156 d = filelist_filter(d, TRUE);
1157 d = filelist_sort_path(d);
1158 filelist_recursive_append(list, d);
1166 GList *filelist_recursive(FileData *dir_fd)
1171 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1172 list = filelist_filter(list, FALSE);
1173 list = filelist_sort_path(list);
1175 d = filelist_filter(d, TRUE);
1176 d = filelist_sort_path(d);
1177 filelist_recursive_append(&list, d);
1185 * marks and orientation
1188 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1189 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1190 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1191 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1193 gboolean file_data_get_mark(FileData *fd, gint n)
1195 gboolean valid = (fd->valid_marks & (1 << n));
1197 if (file_data_get_mark_func[n] && !valid)
1199 guint old = fd->marks;
1200 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1202 if (!value != !(fd->marks & (1 << n)))
1204 fd->marks = fd->marks ^ (1 << n);
1207 fd->valid_marks |= (1 << n);
1208 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1210 file_data_unref(fd);
1212 else if (!old && fd->marks)
1218 return !!(fd->marks & (1 << n));
1221 guint file_data_get_marks(FileData *fd)
1224 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1228 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1231 if (!value == !file_data_get_mark(fd, n)) return;
1233 if (file_data_set_mark_func[n])
1235 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1240 fd->marks = fd->marks ^ (1 << n);
1242 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1244 file_data_unref(fd);
1246 else if (!old && fd->marks)
1251 file_data_increment_version(fd);
1252 file_data_send_notification(fd, NOTIFY_MARKS);
1255 gboolean file_data_filter_marks(FileData *fd, guint filter)
1258 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1259 return ((fd->marks & filter) == filter);
1262 GList *file_data_filter_marks_list(GList *list, guint filter)
1269 FileData *fd = work->data;
1273 if (!file_data_filter_marks(fd, filter))
1275 list = g_list_remove_link(list, link);
1276 file_data_unref(fd);
1284 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1286 FileData *fd = value;
1287 file_data_increment_version(fd);
1288 file_data_send_notification(fd, NOTIFY_MARKS);
1291 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1293 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1295 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1297 file_data_get_mark_func[n] = get_mark_func;
1298 file_data_set_mark_func[n] = set_mark_func;
1299 file_data_mark_func_data[n] = data;
1300 file_data_destroy_mark_func[n] = notify;
1304 /* this effectively changes all known files */
1305 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1311 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1313 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1314 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1315 if (data) *data = file_data_mark_func_data[n];
1318 gint file_data_get_user_orientation(FileData *fd)
1320 return fd->user_orientation;
1323 void file_data_set_user_orientation(FileData *fd, gint value)
1325 if (fd->user_orientation == value) return;
1327 fd->user_orientation = value;
1328 file_data_increment_version(fd);
1329 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1334 * file_data - operates on the given fd
1335 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1339 /* return list of sidecar file extensions in a string */
1340 gchar *file_data_sc_list_to_string(FileData *fd)
1343 GString *result = g_string_new("");
1345 work = fd->sidecar_files;
1348 FileData *sfd = work->data;
1350 result = g_string_append(result, "+ ");
1351 result = g_string_append(result, sfd->extension);
1353 if (work) result = g_string_append_c(result, ' ');
1356 return g_string_free(result, FALSE);
1362 * add FileDataChangeInfo (see typedefs.h) for the given operation
1363 * uses file_data_add_change_info
1365 * fails if the fd->change already exists - change operations can't run in parallel
1366 * fd->change_info works as a lock
1368 * dest can be NULL - in this case the current name is used for now, it will
1373 FileDataChangeInfo types:
1375 MOVE - path is changed, name may be changed too
1376 RENAME - path remains unchanged, name is changed
1377 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1378 sidecar names are changed too, extensions are not changed
1380 UPDATE - file size, date or grouping has been changed
1383 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1385 FileDataChangeInfo *fdci;
1387 if (fd->change) return FALSE;
1389 fdci = g_new0(FileDataChangeInfo, 1);
1394 fdci->source = g_strdup(src);
1396 fdci->source = g_strdup(fd->path);
1399 fdci->dest = g_strdup(dest);
1406 static void file_data_planned_change_remove(FileData *fd)
1408 if (file_data_planned_change_hash &&
1409 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1411 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1413 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1414 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1415 file_data_unref(fd);
1416 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1418 g_hash_table_destroy(file_data_planned_change_hash);
1419 file_data_planned_change_hash = NULL;
1420 DEBUG_1("planned change: empty");
1427 void file_data_free_ci(FileData *fd)
1429 FileDataChangeInfo *fdci = fd->change;
1433 file_data_planned_change_remove(fd);
1435 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1437 g_free(fdci->source);
1445 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1447 FileDataChangeInfo *fdci = fd->change;
1449 fdci->regroup_when_finished = enable;
1452 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1456 if (fd->parent) fd = fd->parent;
1458 if (fd->change) return FALSE;
1460 work = fd->sidecar_files;
1463 FileData *sfd = work->data;
1465 if (sfd->change) return FALSE;
1469 file_data_add_ci(fd, type, NULL, NULL);
1471 work = fd->sidecar_files;
1474 FileData *sfd = work->data;
1476 file_data_add_ci(sfd, type, NULL, NULL);
1483 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1487 if (fd->parent) fd = fd->parent;
1489 if (!fd->change || fd->change->type != type) return FALSE;
1491 work = fd->sidecar_files;
1494 FileData *sfd = work->data;
1496 if (!sfd->change || sfd->change->type != type) return FALSE;
1504 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1506 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1507 file_data_sc_update_ci_copy(fd, dest_path);
1511 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1513 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1514 file_data_sc_update_ci_move(fd, dest_path);
1518 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1520 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1521 file_data_sc_update_ci_rename(fd, dest_path);
1525 gboolean file_data_sc_add_ci_delete(FileData *fd)
1527 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1530 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1532 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1533 file_data_sc_update_ci_unspecified(fd, dest_path);
1537 gboolean file_data_add_ci_write_metadata(FileData *fd)
1539 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1542 void file_data_sc_free_ci(FileData *fd)
1546 if (fd->parent) fd = fd->parent;
1548 file_data_free_ci(fd);
1550 work = fd->sidecar_files;
1553 FileData *sfd = work->data;
1555 file_data_free_ci(sfd);
1560 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1563 gboolean ret = TRUE;
1568 FileData *fd = work->data;
1570 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1577 static void file_data_sc_revert_ci_list(GList *fd_list)
1584 FileData *fd = work->data;
1586 file_data_sc_free_ci(fd);
1591 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1598 FileData *fd = work->data;
1600 if (!func(fd, dest))
1602 file_data_sc_revert_ci_list(work->prev);
1611 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1613 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1616 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1618 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1621 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1623 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1626 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1628 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1631 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1634 gboolean ret = TRUE;
1639 FileData *fd = work->data;
1641 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1648 void file_data_free_ci_list(GList *fd_list)
1655 FileData *fd = work->data;
1657 file_data_free_ci(fd);
1662 void file_data_sc_free_ci_list(GList *fd_list)
1669 FileData *fd = work->data;
1671 file_data_sc_free_ci(fd);
1677 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1678 * fails if fd->change does not exist or the change type does not match
1681 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1683 FileDataChangeType type = fd->change->type;
1685 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1689 if (!file_data_planned_change_hash)
1690 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1692 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1694 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1695 g_hash_table_remove(file_data_planned_change_hash, old_path);
1696 file_data_unref(fd);
1699 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1704 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1705 g_hash_table_remove(file_data_planned_change_hash, new_path);
1706 file_data_unref(ofd);
1709 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1711 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1716 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1718 gchar *old_path = fd->change->dest;
1720 fd->change->dest = g_strdup(dest_path);
1721 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1725 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1727 const gchar *extension = extension_from_path(fd->change->source);
1728 gchar *base = remove_extension_from_path(dest_path);
1729 gchar *old_path = fd->change->dest;
1731 fd->change->dest = g_strconcat(base, extension, NULL);
1732 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1738 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1741 gchar *dest_path_full = NULL;
1743 if (fd->parent) fd = fd->parent;
1747 dest_path = fd->path;
1749 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1751 gchar *dir = remove_level_from_path(fd->path);
1753 dest_path_full = g_build_filename(dir, dest_path, NULL);
1755 dest_path = dest_path_full;
1757 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1759 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1760 dest_path = dest_path_full;
1763 file_data_update_ci_dest(fd, dest_path);
1765 work = fd->sidecar_files;
1768 FileData *sfd = work->data;
1770 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1774 g_free(dest_path_full);
1777 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1779 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1780 file_data_sc_update_ci(fd, dest_path);
1784 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1786 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1789 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1791 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1794 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1796 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1799 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1801 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1804 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1806 gboolean (*func)(FileData *, const gchar *))
1809 gboolean ret = TRUE;
1814 FileData *fd = work->data;
1816 if (!func(fd, dest)) ret = FALSE;
1823 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1825 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1828 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1830 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1833 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1835 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1840 * verify source and dest paths - dest image exists, etc.
1841 * it should detect all possible problems with the planned operation
1844 gint file_data_verify_ci(FileData *fd)
1846 gint ret = CHANGE_OK;
1851 DEBUG_1("Change checked: no change info: %s", fd->path);
1855 if (!isname(fd->path))
1857 /* this probably should not happen */
1858 ret |= CHANGE_NO_SRC;
1859 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1863 dir = remove_level_from_path(fd->path);
1865 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1866 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1867 fd->change->type != FILEDATA_CHANGE_RENAME &&
1868 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1871 ret |= CHANGE_WARN_UNSAVED_META;
1872 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1875 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1876 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1877 !access_file(fd->path, R_OK))
1879 ret |= CHANGE_NO_READ_PERM;
1880 DEBUG_1("Change checked: no read permission: %s", fd->path);
1882 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1883 !access_file(dir, W_OK))
1885 ret |= CHANGE_NO_WRITE_PERM_DIR;
1886 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1888 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1889 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1890 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1891 !access_file(fd->path, W_OK))
1893 ret |= CHANGE_WARN_NO_WRITE_PERM;
1894 DEBUG_1("Change checked: no write permission: %s", fd->path);
1896 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1897 - that means that there are no hard errors and warnings can be disabled
1898 - the destination is determined during the check
1900 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1902 /* determine destination file */
1903 gboolean have_dest = FALSE;
1904 gchar *dest_dir = NULL;
1906 if (options->metadata.save_in_image_file)
1908 if (file_data_can_write_directly(fd))
1910 /* we can write the file directly */
1911 if (access_file(fd->path, W_OK))
1917 if (options->metadata.warn_on_write_problems)
1919 ret |= CHANGE_WARN_NO_WRITE_PERM;
1920 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1924 else if (file_data_can_write_sidecar(fd))
1926 /* we can write sidecar */
1927 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1928 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1930 file_data_update_ci_dest(fd, sidecar);
1935 if (options->metadata.warn_on_write_problems)
1937 ret |= CHANGE_WARN_NO_WRITE_PERM;
1938 DEBUG_1("Change checked: file is not writable: %s", sidecar);
1947 /* write private metadata file under ~/.geeqie */
1949 /* If an existing metadata file exists, we will try writing to
1950 * it's location regardless of the user's preference.
1952 gchar *metadata_path = NULL;
1954 /* but ignore XMP if we are not able to write it */
1955 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
1957 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
1959 if (metadata_path && !access_file(metadata_path, W_OK))
1961 g_free(metadata_path);
1962 metadata_path = NULL;
1969 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
1970 if (recursive_mkdir_if_not_exists(dest_dir, mode))
1972 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
1974 metadata_path = g_build_filename(dest_dir, filename, NULL);
1978 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
1980 file_data_update_ci_dest(fd, metadata_path);
1985 ret |= CHANGE_NO_WRITE_PERM_DEST;
1986 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
1988 g_free(metadata_path);
1993 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
1998 same = (strcmp(fd->path, fd->change->dest) == 0);
2002 const gchar *dest_ext = extension_from_path(fd->change->dest);
2003 if (!dest_ext) dest_ext = "";
2005 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2007 ret |= CHANGE_WARN_CHANGED_EXT;
2008 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2013 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2015 ret |= CHANGE_WARN_SAME;
2016 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2020 dest_dir = remove_level_from_path(fd->change->dest);
2022 if (!isdir(dest_dir))
2024 ret |= CHANGE_NO_DEST_DIR;
2025 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2027 else if (!access_file(dest_dir, W_OK))
2029 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2030 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2034 if (isfile(fd->change->dest))
2036 if (!access_file(fd->change->dest, W_OK))
2038 ret |= CHANGE_NO_WRITE_PERM_DEST;
2039 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2043 ret |= CHANGE_WARN_DEST_EXISTS;
2044 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2047 else if (isdir(fd->change->dest))
2049 ret |= CHANGE_DEST_EXISTS;
2050 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2057 fd->change->error = ret;
2058 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2065 gint file_data_sc_verify_ci(FileData *fd)
2070 ret = file_data_verify_ci(fd);
2072 work = fd->sidecar_files;
2075 FileData *sfd = work->data;
2077 ret |= file_data_verify_ci(sfd);
2084 gchar *file_data_get_error_string(gint error)
2086 GString *result = g_string_new("");
2088 if (error & CHANGE_NO_SRC)
2090 if (result->len > 0) g_string_append(result, ", ");
2091 g_string_append(result, _("file or directory does not exist"));
2094 if (error & CHANGE_DEST_EXISTS)
2096 if (result->len > 0) g_string_append(result, ", ");
2097 g_string_append(result, _("destination already exists"));
2100 if (error & CHANGE_NO_WRITE_PERM_DEST)
2102 if (result->len > 0) g_string_append(result, ", ");
2103 g_string_append(result, _("destination can't be overwritten"));
2106 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2108 if (result->len > 0) g_string_append(result, ", ");
2109 g_string_append(result, _("destination directory is not writable"));
2112 if (error & CHANGE_NO_DEST_DIR)
2114 if (result->len > 0) g_string_append(result, ", ");
2115 g_string_append(result, _("destination directory does not exist"));
2118 if (error & CHANGE_NO_WRITE_PERM_DIR)
2120 if (result->len > 0) g_string_append(result, ", ");
2121 g_string_append(result, _("source directory is not writable"));
2124 if (error & CHANGE_NO_READ_PERM)
2126 if (result->len > 0) g_string_append(result, ", ");
2127 g_string_append(result, _("no read permission"));
2130 if (error & CHANGE_WARN_NO_WRITE_PERM)
2132 if (result->len > 0) g_string_append(result, ", ");
2133 g_string_append(result, _("file is readonly"));
2136 if (error & CHANGE_WARN_DEST_EXISTS)
2138 if (result->len > 0) g_string_append(result, ", ");
2139 g_string_append(result, _("destination already exists and will be overwritten"));
2142 if (error & CHANGE_WARN_SAME)
2144 if (result->len > 0) g_string_append(result, ", ");
2145 g_string_append(result, _("source and destination are the same"));
2148 if (error & CHANGE_WARN_CHANGED_EXT)
2150 if (result->len > 0) g_string_append(result, ", ");
2151 g_string_append(result, _("source and destination have different extension"));
2154 if (error & CHANGE_WARN_UNSAVED_META)
2156 if (result->len > 0) g_string_append(result, ", ");
2157 g_string_append(result, _("there are unsaved metadata changes for the file"));
2160 return g_string_free(result, FALSE);
2163 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2166 gint all_errors = 0;
2167 gint common_errors = ~0;
2172 if (!list) return 0;
2174 num = g_list_length(list);
2175 errors = g_new(int, num);
2186 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2187 all_errors |= error;
2188 common_errors &= error;
2195 if (desc && all_errors)
2198 GString *result = g_string_new("");
2202 gchar *str = file_data_get_error_string(common_errors);
2203 g_string_append(result, str);
2204 g_string_append(result, "\n");
2218 error = errors[i] & ~common_errors;
2222 gchar *str = file_data_get_error_string(error);
2223 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2228 *desc = g_string_free(result, FALSE);
2237 * perform the change described by FileFataChangeInfo
2238 * it is used for internal operations,
2239 * this function actually operates with files on the filesystem
2240 * it should implement safe delete
2243 static gboolean file_data_perform_move(FileData *fd)
2245 g_assert(!strcmp(fd->change->source, fd->path));
2246 return move_file(fd->change->source, fd->change->dest);
2249 static gboolean file_data_perform_copy(FileData *fd)
2251 g_assert(!strcmp(fd->change->source, fd->path));
2252 return copy_file(fd->change->source, fd->change->dest);
2255 static gboolean file_data_perform_delete(FileData *fd)
2257 if (isdir(fd->path) && !islink(fd->path))
2258 return rmdir_utf8(fd->path);
2260 if (options->file_ops.safe_delete_enable)
2261 return file_util_safe_unlink(fd->path);
2263 return unlink_file(fd->path);
2266 gboolean file_data_perform_ci(FileData *fd)
2268 FileDataChangeType type = fd->change->type;
2272 case FILEDATA_CHANGE_MOVE:
2273 return file_data_perform_move(fd);
2274 case FILEDATA_CHANGE_COPY:
2275 return file_data_perform_copy(fd);
2276 case FILEDATA_CHANGE_RENAME:
2277 return file_data_perform_move(fd); /* the same as move */
2278 case FILEDATA_CHANGE_DELETE:
2279 return file_data_perform_delete(fd);
2280 case FILEDATA_CHANGE_WRITE_METADATA:
2281 return metadata_write_perform(fd);
2282 case FILEDATA_CHANGE_UNSPECIFIED:
2283 /* nothing to do here */
2291 gboolean file_data_sc_perform_ci(FileData *fd)
2294 gboolean ret = TRUE;
2295 FileDataChangeType type = fd->change->type;
2297 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2299 work = fd->sidecar_files;
2302 FileData *sfd = work->data;
2304 if (!file_data_perform_ci(sfd)) ret = FALSE;
2308 if (!file_data_perform_ci(fd)) ret = FALSE;
2314 * updates FileData structure according to FileDataChangeInfo
2317 gboolean file_data_apply_ci(FileData *fd)
2319 FileDataChangeType type = fd->change->type;
2322 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2324 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2325 file_data_planned_change_remove(fd);
2327 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2329 /* this change overwrites another file which is already known to other modules
2330 renaming fd would create duplicate FileData structure
2331 the best thing we can do is nothing
2332 FIXME: maybe we could copy stuff like marks
2334 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2338 file_data_set_path(fd, fd->change->dest);
2341 file_data_increment_version(fd);
2342 file_data_send_notification(fd, NOTIFY_CHANGE);
2347 gboolean file_data_sc_apply_ci(FileData *fd)
2350 FileDataChangeType type = fd->change->type;
2352 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2354 work = fd->sidecar_files;
2357 FileData *sfd = work->data;
2359 file_data_apply_ci(sfd);
2363 file_data_apply_ci(fd);
2368 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2371 if (fd->parent) fd = fd->parent;
2372 if (!g_list_find(list, fd)) return FALSE;
2374 work = fd->sidecar_files;
2377 if (!g_list_find(list, work->data)) return FALSE;
2384 static gboolean file_data_list_dump(GList *list)
2386 GList *work, *work2;
2391 FileData *fd = work->data;
2392 printf("%s\n", fd->name);
2393 work2 = fd->sidecar_files;
2396 FileData *fd = work2->data;
2397 printf(" %s\n", fd->name);
2398 work2 = work2->next;
2406 GList *file_data_process_groups_in_selection(GList *list, GList **ungrouped_list)
2411 /* change partial groups to independent files */
2414 FileData *fd = work->data;
2417 if (!file_data_list_contains_whole_group(list, fd))
2419 file_data_disable_grouping(fd, TRUE);
2422 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2427 /* remove sidecars from the list,
2428 they can be still acessed via main_fd->sidecar_files */
2432 FileData *fd = work->data;
2437 out = g_list_prepend(out, fd);
2441 file_data_unref(fd);
2446 out = g_list_reverse(out);
2456 * notify other modules about the change described by FileDataChangeInfo
2459 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2460 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2461 implementation in view_file_list.c */
2466 typedef struct _NotifyData NotifyData;
2468 struct _NotifyData {
2469 FileDataNotifyFunc func;
2471 NotifyPriority priority;
2474 static GList *notify_func_list = NULL;
2476 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2478 NotifyData *nda = (NotifyData *)a;
2479 NotifyData *ndb = (NotifyData *)b;
2481 if (nda->priority < ndb->priority) return -1;
2482 if (nda->priority > ndb->priority) return 1;
2486 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2490 nd = g_new(NotifyData, 1);
2493 nd->priority = priority;
2495 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2496 DEBUG_2("Notify func registered: %p", nd);
2501 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2503 GList *work = notify_func_list;
2507 NotifyData *nd = (NotifyData *)work->data;
2509 if (nd->func == func && nd->data == data)
2511 notify_func_list = g_list_delete_link(notify_func_list, work);
2513 DEBUG_2("Notify func unregistered: %p", nd);
2523 void file_data_send_notification(FileData *fd, NotifyType type)
2525 GList *work = notify_func_list;
2529 NotifyData *nd = (NotifyData *)work->data;
2531 nd->func(fd, type, nd->data);
2536 static GHashTable *file_data_monitor_pool = NULL;
2537 static guint realtime_monitor_id = 0; /* event source id */
2539 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2543 file_data_check_changed_files(fd);
2545 DEBUG_1("monitor %s", fd->path);
2548 static gboolean realtime_monitor_cb(gpointer data)
2550 if (!options->update_on_time_change) return TRUE;
2551 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2555 gboolean file_data_register_real_time_monitor(FileData *fd)
2561 if (!file_data_monitor_pool)
2562 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2564 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2566 DEBUG_1("Register realtime %d %s", count, fd->path);
2569 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2571 if (!realtime_monitor_id)
2573 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2579 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2583 g_assert(file_data_monitor_pool);
2585 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2587 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2589 g_assert(count > 0);
2594 g_hash_table_remove(file_data_monitor_pool, fd);
2596 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2598 file_data_unref(fd);
2600 if (g_hash_table_size(file_data_monitor_pool) == 0)
2602 g_source_remove(realtime_monitor_id);
2603 realtime_monitor_id = 0;
2609 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */