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;
470 FileData *sfd = work2->data;
471 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
473 new_fd = file_data_ref(sfd);
479 if (!new_fd) continue;
482 if (new_fd->disable_grouping)
484 file_data_unref(new_fd);
488 new_fd->ref--; /* do not use ref here */
492 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
494 file_data_merge_sidecar_files(parent_fd, new_fd);
496 g_string_free(fname, TRUE);
500 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars)
502 gchar *path_utf8 = path_to_utf8(path);
503 FileData *ret = file_data_new(path_utf8, st, check_sidecars, stat_sidecars);
509 FileData *file_data_new_simple(const gchar *path_utf8)
513 if (!stat_utf8(path_utf8, &st))
519 return file_data_new(path_utf8, &st, TRUE, TRUE);
522 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
524 sfd->parent = target;
525 if (!g_list_find(target->sidecar_files, sfd))
526 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
527 file_data_increment_version(sfd); /* increments both sfd and target */
532 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
536 file_data_add_sidecar_file(target, source);
538 work = source->sidecar_files;
541 FileData *sfd = work->data;
542 file_data_add_sidecar_file(target, sfd);
546 g_list_free(source->sidecar_files);
547 source->sidecar_files = NULL;
549 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
554 #ifdef DEBUG_FILEDATA
555 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
557 FileData *file_data_ref(FileData *fd)
560 if (fd == NULL) return NULL;
561 #ifdef DEBUG_FILEDATA
562 if (fd->magick != 0x12345678)
563 DEBUG_0("fd magick mismatch at %s:%d", file, line);
565 g_assert(fd->magick == 0x12345678);
568 #ifdef DEBUG_FILEDATA
569 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
571 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
576 static void file_data_free(FileData *fd)
578 g_assert(fd->magick == 0x12345678);
579 g_assert(fd->ref == 0);
581 if (fd->path) file_data_basename_hash_remove(fd);
582 g_hash_table_remove(file_data_pool, fd->original_path);
585 g_free(fd->original_path);
586 g_free(fd->collate_key_name);
587 g_free(fd->collate_key_name_nocase);
588 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
589 histmap_free(fd->histmap);
591 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
593 file_data_change_info_free(NULL, fd);
597 #ifdef DEBUG_FILEDATA
598 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
600 void file_data_unref(FileData *fd)
603 if (fd == NULL) return;
604 #ifdef DEBUG_FILEDATA
605 if (fd->magick != 0x12345678)
606 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
608 g_assert(fd->magick == 0x12345678);
611 #ifdef DEBUG_FILEDATA
612 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
614 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
619 FileData *parent = fd->parent ? fd->parent : fd;
621 if (parent->ref > 0) return;
623 work = parent->sidecar_files;
626 FileData *sfd = work->data;
627 if (sfd->ref > 0) return;
631 /* none of parent/children is referenced, we can free everything */
633 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
635 work = parent->sidecar_files;
638 FileData *sfd = work->data;
643 g_list_free(parent->sidecar_files);
644 parent->sidecar_files = NULL;
646 file_data_free(parent);
650 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
652 sfd->parent = target;
653 g_assert(g_list_find(target->sidecar_files, sfd));
655 file_data_increment_version(sfd); /* increments both sfd and target */
657 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
669 /* disables / enables grouping for particular file, sends UPDATE notification */
670 void file_data_disable_grouping(FileData *fd, gboolean disable)
672 if (!fd->disable_grouping == !disable) return;
674 fd->disable_grouping = !!disable;
680 FileData *parent = file_data_ref(fd->parent);
681 file_data_disconnect_sidecar_file(parent, fd);
682 file_data_send_notification(parent, NOTIFY_GROUPING);
683 file_data_unref(parent);
685 else if (fd->sidecar_files)
687 GList *sidecar_files = filelist_copy(fd->sidecar_files);
688 GList *work = sidecar_files;
691 FileData *sfd = work->data;
693 file_data_disconnect_sidecar_file(fd, sfd);
694 file_data_send_notification(sfd, NOTIFY_GROUPING);
696 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
697 filelist_free(sidecar_files);
701 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
706 file_data_increment_version(fd);
707 file_data_check_sidecars(fd, FALSE);
709 file_data_send_notification(fd, NOTIFY_GROUPING);
712 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
719 FileData *fd = work->data;
721 file_data_disable_grouping(fd, disable);
727 /* compare name without extension */
728 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
730 size_t len1 = fd1->extension - fd1->name;
731 size_t len2 = fd2->extension - fd2->name;
733 if (len1 < len2) return -1;
734 if (len1 > len2) return 1;
736 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
739 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
741 if (!fdci && fd) fdci = fd->change;
745 g_free(fdci->source);
750 if (fd) fd->change = NULL;
753 static gboolean file_data_can_write_directly(FileData *fd)
755 return filter_name_is_writable(fd->extension);
758 static gboolean file_data_can_write_sidecar(FileData *fd)
760 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
763 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
765 gchar *sidecar_path = NULL;
768 if (!file_data_can_write_sidecar(fd)) return NULL;
770 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
773 FileData *sfd = work->data;
775 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
777 sidecar_path = g_strdup(sfd->path);
782 if (!existing_only && !sidecar_path)
784 gchar *base = remove_extension_from_path(fd->path);
785 sidecar_path = g_strconcat(base, ".xmp", NULL);
794 *-----------------------------------------------------------------------------
795 * sidecar file info struct
796 *-----------------------------------------------------------------------------
801 static gint sidecar_file_priority(const gchar *path)
803 const gchar *extension = extension_from_path(path);
807 if (extension == NULL)
810 work = sidecar_ext_get_list();
813 gchar *ext = work->data;
816 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
824 *-----------------------------------------------------------------------------
826 *-----------------------------------------------------------------------------
829 static SortType filelist_sort_method = SORT_NONE;
830 static gboolean filelist_sort_ascend = TRUE;
833 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
835 if (!filelist_sort_ascend)
842 switch (filelist_sort_method)
847 if (fa->size < fb->size) return -1;
848 if (fa->size > fb->size) return 1;
849 /* fall back to name */
852 if (fa->date < fb->date) return -1;
853 if (fa->date > fb->date) return 1;
854 /* fall back to name */
856 #ifdef HAVE_STRVERSCMP
858 return strverscmp(fa->name, fb->name);
865 if (options->file_sort.case_sensitive)
866 return strcmp(fa->collate_key_name, fb->collate_key_name);
868 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
871 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
873 filelist_sort_method = method;
874 filelist_sort_ascend = ascend;
875 return filelist_sort_compare_filedata(fa, fb);
878 static gint filelist_sort_file_cb(gpointer a, gpointer b)
880 return filelist_sort_compare_filedata(a, b);
883 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
885 filelist_sort_method = method;
886 filelist_sort_ascend = ascend;
887 return g_list_sort(list, cb);
890 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
892 filelist_sort_method = method;
893 filelist_sort_ascend = ascend;
894 return g_list_insert_sorted(list, data, cb);
897 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
899 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
902 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
904 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
908 static GList *filelist_filter_out_sidecars(GList *flist)
911 GList *flist_filtered = NULL;
915 FileData *fd = work->data;
918 if (fd->parent) /* remove fd's that are children */
921 flist_filtered = g_list_prepend(flist_filtered, fd);
925 return flist_filtered;
928 static gboolean is_hidden_file(const gchar *name)
930 if (name[0] != '.') return FALSE;
931 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
935 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
942 gint (*stat_func)(const gchar *path, struct stat *buf);
944 g_assert(files || dirs);
946 if (files) *files = NULL;
947 if (dirs) *dirs = NULL;
949 pathl = path_from_utf8(dir_fd->path);
950 if (!pathl) return FALSE;
964 while ((dir = readdir(dp)) != NULL)
966 struct stat ent_sbuf;
967 const gchar *name = dir->d_name;
970 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
973 filepath = g_build_filename(pathl, name, NULL);
974 if (stat_func(filepath, &ent_sbuf) >= 0)
976 if (S_ISDIR(ent_sbuf.st_mode))
978 /* we ignore the .thumbnails dir for cleanliness */
980 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
981 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
982 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
983 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
985 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, FALSE));
990 if (files && filter_name_exists(name))
992 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, FALSE));
1003 if (dirs) *dirs = dlist;
1004 if (files) *files = filelist_filter_out_sidecars(flist);
1009 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1011 return filelist_read_real(dir_fd, files, dirs, TRUE);
1014 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1016 return filelist_read_real(dir_fd, files, dirs, FALSE);
1019 void filelist_free(GList *list)
1026 file_data_unref((FileData *)work->data);
1034 GList *filelist_copy(GList *list)
1036 GList *new_list = NULL;
1047 new_list = g_list_prepend(new_list, file_data_ref(fd));
1050 return g_list_reverse(new_list);
1053 GList *filelist_from_path_list(GList *list)
1055 GList *new_list = NULL;
1066 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1069 return g_list_reverse(new_list);
1072 GList *filelist_to_path_list(GList *list)
1074 GList *new_list = NULL;
1085 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1088 return g_list_reverse(new_list);
1091 GList *filelist_filter(GList *list, gboolean is_dir_list)
1095 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1100 FileData *fd = (FileData *)(work->data);
1101 const gchar *name = fd->name;
1103 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1104 (!is_dir_list && !filter_name_exists(name)) ||
1105 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1106 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1110 list = g_list_remove_link(list, link);
1111 file_data_unref(fd);
1122 *-----------------------------------------------------------------------------
1123 * filelist recursive
1124 *-----------------------------------------------------------------------------
1127 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1129 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1132 GList *filelist_sort_path(GList *list)
1134 return g_list_sort(list, filelist_sort_path_cb);
1137 static void filelist_recursive_append(GList **list, GList *dirs)
1144 FileData *fd = (FileData *)(work->data);
1148 if (filelist_read(fd, &f, &d))
1150 f = filelist_filter(f, FALSE);
1151 f = filelist_sort_path(f);
1152 *list = g_list_concat(*list, f);
1154 d = filelist_filter(d, TRUE);
1155 d = filelist_sort_path(d);
1156 filelist_recursive_append(list, d);
1164 GList *filelist_recursive(FileData *dir_fd)
1169 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1170 list = filelist_filter(list, FALSE);
1171 list = filelist_sort_path(list);
1173 d = filelist_filter(d, TRUE);
1174 d = filelist_sort_path(d);
1175 filelist_recursive_append(&list, d);
1183 * marks and orientation
1186 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1187 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1188 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1189 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1191 gboolean file_data_get_mark(FileData *fd, gint n)
1193 gboolean valid = (fd->valid_marks & (1 << n));
1195 if (file_data_get_mark_func[n] && !valid)
1197 guint old = fd->marks;
1198 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1200 if (!value != !(fd->marks & (1 << n)))
1202 fd->marks = fd->marks ^ (1 << n);
1205 fd->valid_marks |= (1 << n);
1206 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1208 file_data_unref(fd);
1210 else if (!old && fd->marks)
1216 return !!(fd->marks & (1 << n));
1219 guint file_data_get_marks(FileData *fd)
1222 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1226 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1229 if (!value == !file_data_get_mark(fd, n)) return;
1231 if (file_data_set_mark_func[n])
1233 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1238 fd->marks = fd->marks ^ (1 << n);
1240 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1242 file_data_unref(fd);
1244 else if (!old && fd->marks)
1249 file_data_increment_version(fd);
1250 file_data_send_notification(fd, NOTIFY_MARKS);
1253 gboolean file_data_filter_marks(FileData *fd, guint filter)
1256 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1257 return ((fd->marks & filter) == filter);
1260 GList *file_data_filter_marks_list(GList *list, guint filter)
1267 FileData *fd = work->data;
1271 if (!file_data_filter_marks(fd, filter))
1273 list = g_list_remove_link(list, link);
1274 file_data_unref(fd);
1282 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1284 FileData *fd = value;
1285 file_data_increment_version(fd);
1286 file_data_send_notification(fd, NOTIFY_MARKS);
1289 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1291 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1293 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1295 file_data_get_mark_func[n] = get_mark_func;
1296 file_data_set_mark_func[n] = set_mark_func;
1297 file_data_mark_func_data[n] = data;
1298 file_data_destroy_mark_func[n] = notify;
1302 /* this effectively changes all known files */
1303 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1309 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1311 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1312 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1313 if (data) *data = file_data_mark_func_data[n];
1316 gint file_data_get_user_orientation(FileData *fd)
1318 return fd->user_orientation;
1321 void file_data_set_user_orientation(FileData *fd, gint value)
1323 if (fd->user_orientation == value) return;
1325 fd->user_orientation = value;
1326 file_data_increment_version(fd);
1327 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1332 * file_data - operates on the given fd
1333 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1337 /* return list of sidecar file extensions in a string */
1338 gchar *file_data_sc_list_to_string(FileData *fd)
1341 GString *result = g_string_new("");
1343 work = fd->sidecar_files;
1346 FileData *sfd = work->data;
1348 result = g_string_append(result, "+ ");
1349 result = g_string_append(result, sfd->extension);
1351 if (work) result = g_string_append_c(result, ' ');
1354 return g_string_free(result, FALSE);
1360 * add FileDataChangeInfo (see typedefs.h) for the given operation
1361 * uses file_data_add_change_info
1363 * fails if the fd->change already exists - change operations can't run in parallel
1364 * fd->change_info works as a lock
1366 * dest can be NULL - in this case the current name is used for now, it will
1371 FileDataChangeInfo types:
1373 MOVE - path is changed, name may be changed too
1374 RENAME - path remains unchanged, name is changed
1375 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1376 sidecar names are changed too, extensions are not changed
1378 UPDATE - file size, date or grouping has been changed
1381 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1383 FileDataChangeInfo *fdci;
1385 if (fd->change) return FALSE;
1387 fdci = g_new0(FileDataChangeInfo, 1);
1392 fdci->source = g_strdup(src);
1394 fdci->source = g_strdup(fd->path);
1397 fdci->dest = g_strdup(dest);
1404 static void file_data_planned_change_remove(FileData *fd)
1406 if (file_data_planned_change_hash &&
1407 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1409 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1411 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1412 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1413 file_data_unref(fd);
1414 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1416 g_hash_table_destroy(file_data_planned_change_hash);
1417 file_data_planned_change_hash = NULL;
1418 DEBUG_1("planned change: empty");
1425 void file_data_free_ci(FileData *fd)
1427 FileDataChangeInfo *fdci = fd->change;
1431 file_data_planned_change_remove(fd);
1433 g_free(fdci->source);
1442 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1446 if (fd->parent) fd = fd->parent;
1448 if (fd->change) return FALSE;
1450 work = fd->sidecar_files;
1453 FileData *sfd = work->data;
1455 if (sfd->change) return FALSE;
1459 file_data_add_ci(fd, type, NULL, NULL);
1461 work = fd->sidecar_files;
1464 FileData *sfd = work->data;
1466 file_data_add_ci(sfd, type, NULL, NULL);
1473 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1477 if (fd->parent) fd = fd->parent;
1479 if (!fd->change || fd->change->type != type) return FALSE;
1481 work = fd->sidecar_files;
1484 FileData *sfd = work->data;
1486 if (!sfd->change || sfd->change->type != type) return FALSE;
1494 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1496 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1497 file_data_sc_update_ci_copy(fd, dest_path);
1501 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1503 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1504 file_data_sc_update_ci_move(fd, dest_path);
1508 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1510 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1511 file_data_sc_update_ci_rename(fd, dest_path);
1515 gboolean file_data_sc_add_ci_delete(FileData *fd)
1517 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1520 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1522 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1523 file_data_sc_update_ci_unspecified(fd, dest_path);
1527 gboolean file_data_add_ci_write_metadata(FileData *fd)
1529 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1532 void file_data_sc_free_ci(FileData *fd)
1536 if (fd->parent) fd = fd->parent;
1538 file_data_free_ci(fd);
1540 work = fd->sidecar_files;
1543 FileData *sfd = work->data;
1545 file_data_free_ci(sfd);
1550 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1553 gboolean ret = TRUE;
1558 FileData *fd = work->data;
1560 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1567 static void file_data_sc_revert_ci_list(GList *fd_list)
1574 FileData *fd = work->data;
1576 file_data_sc_free_ci(fd);
1581 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1588 FileData *fd = work->data;
1590 if (!func(fd, dest))
1592 file_data_sc_revert_ci_list(work->prev);
1601 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1603 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1606 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1608 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1611 gboolean file_data_sc_add_ci_rename_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_rename);
1616 gboolean file_data_sc_add_ci_unspecified_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_unspecified);
1621 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1624 gboolean ret = TRUE;
1629 FileData *fd = work->data;
1631 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1638 void file_data_free_ci_list(GList *fd_list)
1645 FileData *fd = work->data;
1647 file_data_free_ci(fd);
1652 void file_data_sc_free_ci_list(GList *fd_list)
1659 FileData *fd = work->data;
1661 file_data_sc_free_ci(fd);
1667 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1668 * fails if fd->change does not exist or the change type does not match
1671 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1673 FileDataChangeType type = fd->change->type;
1675 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1679 if (!file_data_planned_change_hash)
1680 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1682 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1684 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1685 g_hash_table_remove(file_data_planned_change_hash, old_path);
1686 file_data_unref(fd);
1689 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1694 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1695 g_hash_table_remove(file_data_planned_change_hash, new_path);
1696 file_data_unref(ofd);
1699 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1701 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1706 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1708 gchar *old_path = fd->change->dest;
1710 fd->change->dest = g_strdup(dest_path);
1711 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1715 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1717 const gchar *extension = extension_from_path(fd->change->source);
1718 gchar *base = remove_extension_from_path(dest_path);
1719 gchar *old_path = fd->change->dest;
1721 fd->change->dest = g_strconcat(base, extension, NULL);
1722 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1728 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1731 gchar *dest_path_full = NULL;
1733 if (fd->parent) fd = fd->parent;
1737 dest_path = fd->path;
1739 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1741 gchar *dir = remove_level_from_path(fd->path);
1743 dest_path_full = g_build_filename(dir, dest_path, NULL);
1745 dest_path = dest_path_full;
1747 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1749 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1750 dest_path = dest_path_full;
1753 file_data_update_ci_dest(fd, dest_path);
1755 work = fd->sidecar_files;
1758 FileData *sfd = work->data;
1760 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1764 g_free(dest_path_full);
1767 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1769 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1770 file_data_sc_update_ci(fd, dest_path);
1774 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1776 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1779 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1781 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1784 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1786 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1789 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1791 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1794 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1796 gboolean (*func)(FileData *, const gchar *))
1799 gboolean ret = TRUE;
1804 FileData *fd = work->data;
1806 if (!func(fd, dest)) ret = FALSE;
1813 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1815 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1818 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1820 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1823 gboolean file_data_sc_update_ci_unspecified_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_unspecified);
1830 * verify source and dest paths - dest image exists, etc.
1831 * it should detect all possible problems with the planned operation
1834 gint file_data_verify_ci(FileData *fd)
1836 gint ret = CHANGE_OK;
1841 DEBUG_1("Change checked: no change info: %s", fd->path);
1845 if (!isname(fd->path))
1847 /* this probably should not happen */
1848 ret |= CHANGE_NO_SRC;
1849 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1853 dir = remove_level_from_path(fd->path);
1855 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1856 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1857 !access_file(fd->path, R_OK))
1859 ret |= CHANGE_NO_READ_PERM;
1860 DEBUG_1("Change checked: no read permission: %s", fd->path);
1862 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1863 !access_file(dir, W_OK))
1865 ret |= CHANGE_NO_WRITE_PERM_DIR;
1866 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1868 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1869 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1870 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1871 !access_file(fd->path, W_OK))
1873 ret |= CHANGE_WARN_NO_WRITE_PERM;
1874 DEBUG_1("Change checked: no write permission: %s", fd->path);
1876 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1877 - that means that there are no hard errors and warnings can be disabled
1878 - the destination is determined during the check
1880 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1882 /* determine destination file */
1883 gboolean have_dest = FALSE;
1884 gchar *dest_dir = NULL;
1886 if (options->metadata.save_in_image_file)
1888 if (file_data_can_write_directly(fd))
1890 /* we can write the file directly */
1891 if (access_file(fd->path, W_OK))
1897 if (options->metadata.warn_on_write_problems)
1899 ret |= CHANGE_WARN_NO_WRITE_PERM;
1900 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1904 else if (file_data_can_write_sidecar(fd))
1906 /* we can write sidecar */
1907 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1908 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1910 file_data_update_ci_dest(fd, sidecar);
1915 if (options->metadata.warn_on_write_problems)
1917 ret |= CHANGE_WARN_NO_WRITE_PERM;
1918 DEBUG_1("Change checked: file is not writable: %s", sidecar);
1927 /* write private metadata file under ~/.geeqie */
1929 /* If an existing metadata file exists, we will try writing to
1930 * it's location regardless of the user's preference.
1932 gchar *metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
1933 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
1935 if (metadata_path && !access_file(metadata_path, W_OK))
1937 g_free(metadata_path);
1938 metadata_path = NULL;
1945 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
1946 if (recursive_mkdir_if_not_exists(dest_dir, mode))
1948 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
1950 metadata_path = g_build_filename(dest_dir, filename, NULL);
1954 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
1956 file_data_update_ci_dest(fd, metadata_path);
1961 ret |= CHANGE_NO_WRITE_PERM_DEST;
1962 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
1964 g_free(metadata_path);
1969 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
1974 same = (strcmp(fd->path, fd->change->dest) == 0);
1978 const gchar *dest_ext = extension_from_path(fd->change->dest);
1979 if (!dest_ext) dest_ext = "";
1981 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
1983 ret |= CHANGE_WARN_CHANGED_EXT;
1984 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1989 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
1991 ret |= CHANGE_WARN_SAME;
1992 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
1996 dest_dir = remove_level_from_path(fd->change->dest);
1998 if (!isdir(dest_dir))
2000 ret |= CHANGE_NO_DEST_DIR;
2001 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2003 else if (!access_file(dest_dir, W_OK))
2005 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2006 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2010 if (isfile(fd->change->dest))
2012 if (!access_file(fd->change->dest, W_OK))
2014 ret |= CHANGE_NO_WRITE_PERM_DEST;
2015 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2019 ret |= CHANGE_WARN_DEST_EXISTS;
2020 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2023 else if (isdir(fd->change->dest))
2025 ret |= CHANGE_DEST_EXISTS;
2026 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2033 fd->change->error = ret;
2034 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2041 gint file_data_sc_verify_ci(FileData *fd)
2046 ret = file_data_verify_ci(fd);
2048 work = fd->sidecar_files;
2051 FileData *sfd = work->data;
2053 ret |= file_data_verify_ci(sfd);
2060 gchar *file_data_get_error_string(gint error)
2062 GString *result = g_string_new("");
2064 if (error & CHANGE_NO_SRC)
2066 if (result->len > 0) g_string_append(result, ", ");
2067 g_string_append(result, _("file or directory does not exist"));
2070 if (error & CHANGE_DEST_EXISTS)
2072 if (result->len > 0) g_string_append(result, ", ");
2073 g_string_append(result, _("destination already exists"));
2076 if (error & CHANGE_NO_WRITE_PERM_DEST)
2078 if (result->len > 0) g_string_append(result, ", ");
2079 g_string_append(result, _("destination can't be overwritten"));
2082 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2084 if (result->len > 0) g_string_append(result, ", ");
2085 g_string_append(result, _("destination directory is not writable"));
2088 if (error & CHANGE_NO_DEST_DIR)
2090 if (result->len > 0) g_string_append(result, ", ");
2091 g_string_append(result, _("destination directory does not exist"));
2094 if (error & CHANGE_NO_WRITE_PERM_DIR)
2096 if (result->len > 0) g_string_append(result, ", ");
2097 g_string_append(result, _("source directory is not writable"));
2100 if (error & CHANGE_NO_READ_PERM)
2102 if (result->len > 0) g_string_append(result, ", ");
2103 g_string_append(result, _("no read permission"));
2106 if (error & CHANGE_WARN_NO_WRITE_PERM)
2108 if (result->len > 0) g_string_append(result, ", ");
2109 g_string_append(result, _("file is readonly"));
2112 if (error & CHANGE_WARN_DEST_EXISTS)
2114 if (result->len > 0) g_string_append(result, ", ");
2115 g_string_append(result, _("destination already exists and will be overwritten"));
2118 if (error & CHANGE_WARN_SAME)
2120 if (result->len > 0) g_string_append(result, ", ");
2121 g_string_append(result, _("source and destination are the same"));
2124 if (error & CHANGE_WARN_CHANGED_EXT)
2126 if (result->len > 0) g_string_append(result, ", ");
2127 g_string_append(result, _("source and destination have different extension"));
2130 return g_string_free(result, FALSE);
2133 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2136 gint all_errors = 0;
2137 gint common_errors = ~0;
2142 if (!list) return 0;
2144 num = g_list_length(list);
2145 errors = g_new(int, num);
2156 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2157 all_errors |= error;
2158 common_errors &= error;
2165 if (desc && all_errors)
2168 GString *result = g_string_new("");
2172 gchar *str = file_data_get_error_string(common_errors);
2173 g_string_append(result, str);
2174 g_string_append(result, "\n");
2188 error = errors[i] & ~common_errors;
2192 gchar *str = file_data_get_error_string(error);
2193 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2198 *desc = g_string_free(result, FALSE);
2207 * perform the change described by FileFataChangeInfo
2208 * it is used for internal operations,
2209 * this function actually operates with files on the filesystem
2210 * it should implement safe delete
2213 static gboolean file_data_perform_move(FileData *fd)
2215 g_assert(!strcmp(fd->change->source, fd->path));
2216 return move_file(fd->change->source, fd->change->dest);
2219 static gboolean file_data_perform_copy(FileData *fd)
2221 g_assert(!strcmp(fd->change->source, fd->path));
2222 return copy_file(fd->change->source, fd->change->dest);
2225 static gboolean file_data_perform_delete(FileData *fd)
2227 if (isdir(fd->path) && !islink(fd->path))
2228 return rmdir_utf8(fd->path);
2230 if (options->file_ops.safe_delete_enable)
2231 return file_util_safe_unlink(fd->path);
2233 return unlink_file(fd->path);
2236 gboolean file_data_perform_ci(FileData *fd)
2238 FileDataChangeType type = fd->change->type;
2242 case FILEDATA_CHANGE_MOVE:
2243 return file_data_perform_move(fd);
2244 case FILEDATA_CHANGE_COPY:
2245 return file_data_perform_copy(fd);
2246 case FILEDATA_CHANGE_RENAME:
2247 return file_data_perform_move(fd); /* the same as move */
2248 case FILEDATA_CHANGE_DELETE:
2249 return file_data_perform_delete(fd);
2250 case FILEDATA_CHANGE_WRITE_METADATA:
2251 return metadata_write_perform(fd);
2252 case FILEDATA_CHANGE_UNSPECIFIED:
2253 /* nothing to do here */
2261 gboolean file_data_sc_perform_ci(FileData *fd)
2264 gboolean ret = TRUE;
2265 FileDataChangeType type = fd->change->type;
2267 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2269 work = fd->sidecar_files;
2272 FileData *sfd = work->data;
2274 if (!file_data_perform_ci(sfd)) ret = FALSE;
2278 if (!file_data_perform_ci(fd)) ret = FALSE;
2284 * updates FileData structure according to FileDataChangeInfo
2287 gboolean file_data_apply_ci(FileData *fd)
2289 FileDataChangeType type = fd->change->type;
2292 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2294 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2295 file_data_planned_change_remove(fd);
2297 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2299 /* this change overwrites another file which is already known to other modules
2300 renaming fd would create duplicate FileData structure
2301 the best thing we can do is nothing
2302 FIXME: maybe we could copy stuff like marks
2304 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2308 file_data_set_path(fd, fd->change->dest);
2311 file_data_increment_version(fd);
2312 file_data_send_notification(fd, NOTIFY_CHANGE);
2317 gboolean file_data_sc_apply_ci(FileData *fd)
2320 FileDataChangeType type = fd->change->type;
2322 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2324 work = fd->sidecar_files;
2327 FileData *sfd = work->data;
2329 file_data_apply_ci(sfd);
2333 file_data_apply_ci(fd);
2339 * notify other modules about the change described by FileFataChangeInfo
2342 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2343 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2344 implementation in view_file_list.c */
2349 typedef struct _NotifyData NotifyData;
2351 struct _NotifyData {
2352 FileDataNotifyFunc func;
2354 NotifyPriority priority;
2357 static GList *notify_func_list = NULL;
2359 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2361 NotifyData *nda = (NotifyData *)a;
2362 NotifyData *ndb = (NotifyData *)b;
2364 if (nda->priority < ndb->priority) return -1;
2365 if (nda->priority > ndb->priority) return 1;
2369 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2373 nd = g_new(NotifyData, 1);
2376 nd->priority = priority;
2378 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2379 DEBUG_2("Notify func registered: %p", nd);
2384 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2386 GList *work = notify_func_list;
2390 NotifyData *nd = (NotifyData *)work->data;
2392 if (nd->func == func && nd->data == data)
2394 notify_func_list = g_list_delete_link(notify_func_list, work);
2396 DEBUG_2("Notify func unregistered: %p", nd);
2406 void file_data_send_notification(FileData *fd, NotifyType type)
2408 GList *work = notify_func_list;
2412 NotifyData *nd = (NotifyData *)work->data;
2414 nd->func(fd, type, nd->data);
2419 static GHashTable *file_data_monitor_pool = NULL;
2420 static guint realtime_monitor_id = 0; /* event source id */
2422 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2426 file_data_check_changed_files(fd);
2428 DEBUG_1("monitor %s", fd->path);
2431 static gboolean realtime_monitor_cb(gpointer data)
2433 if (!options->update_on_time_change) return TRUE;
2434 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2438 gboolean file_data_register_real_time_monitor(FileData *fd)
2444 if (!file_data_monitor_pool)
2445 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2447 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2449 DEBUG_1("Register realtime %d %s", count, fd->path);
2452 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2454 if (!realtime_monitor_id)
2456 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2462 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2466 g_assert(file_data_monitor_pool);
2468 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2470 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2472 g_assert(count > 0);
2477 g_hash_table_remove(file_data_monitor_pool, fd);
2479 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2481 file_data_unref(fd);
2483 if (g_hash_table_size(file_data_monitor_pool) == 0)
2485 g_source_remove(realtime_monitor_id);
2486 realtime_monitor_id = 0;
2492 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */