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 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1435 g_free(fdci->source);
1443 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1445 FileDataChangeInfo *fdci = fd->change;
1447 fdci->regroup_when_finished = enable;
1450 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1454 if (fd->parent) fd = fd->parent;
1456 if (fd->change) return FALSE;
1458 work = fd->sidecar_files;
1461 FileData *sfd = work->data;
1463 if (sfd->change) return FALSE;
1467 file_data_add_ci(fd, type, NULL, NULL);
1469 work = fd->sidecar_files;
1472 FileData *sfd = work->data;
1474 file_data_add_ci(sfd, type, NULL, NULL);
1481 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1485 if (fd->parent) fd = fd->parent;
1487 if (!fd->change || fd->change->type != type) return FALSE;
1489 work = fd->sidecar_files;
1492 FileData *sfd = work->data;
1494 if (!sfd->change || sfd->change->type != type) return FALSE;
1502 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1504 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1505 file_data_sc_update_ci_copy(fd, dest_path);
1509 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1511 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1512 file_data_sc_update_ci_move(fd, dest_path);
1516 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1518 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1519 file_data_sc_update_ci_rename(fd, dest_path);
1523 gboolean file_data_sc_add_ci_delete(FileData *fd)
1525 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1528 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1530 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1531 file_data_sc_update_ci_unspecified(fd, dest_path);
1535 gboolean file_data_add_ci_write_metadata(FileData *fd)
1537 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1540 void file_data_sc_free_ci(FileData *fd)
1544 if (fd->parent) fd = fd->parent;
1546 file_data_free_ci(fd);
1548 work = fd->sidecar_files;
1551 FileData *sfd = work->data;
1553 file_data_free_ci(sfd);
1558 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1561 gboolean ret = TRUE;
1566 FileData *fd = work->data;
1568 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1575 static void file_data_sc_revert_ci_list(GList *fd_list)
1582 FileData *fd = work->data;
1584 file_data_sc_free_ci(fd);
1589 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1596 FileData *fd = work->data;
1598 if (!func(fd, dest))
1600 file_data_sc_revert_ci_list(work->prev);
1609 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1611 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1614 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1616 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1619 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1621 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1624 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1626 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1629 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1632 gboolean ret = TRUE;
1637 FileData *fd = work->data;
1639 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1646 void file_data_free_ci_list(GList *fd_list)
1653 FileData *fd = work->data;
1655 file_data_free_ci(fd);
1660 void file_data_sc_free_ci_list(GList *fd_list)
1667 FileData *fd = work->data;
1669 file_data_sc_free_ci(fd);
1675 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1676 * fails if fd->change does not exist or the change type does not match
1679 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1681 FileDataChangeType type = fd->change->type;
1683 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1687 if (!file_data_planned_change_hash)
1688 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1690 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1692 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1693 g_hash_table_remove(file_data_planned_change_hash, old_path);
1694 file_data_unref(fd);
1697 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1702 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1703 g_hash_table_remove(file_data_planned_change_hash, new_path);
1704 file_data_unref(ofd);
1707 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1709 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1714 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1716 gchar *old_path = fd->change->dest;
1718 fd->change->dest = g_strdup(dest_path);
1719 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1723 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1725 const gchar *extension = extension_from_path(fd->change->source);
1726 gchar *base = remove_extension_from_path(dest_path);
1727 gchar *old_path = fd->change->dest;
1729 fd->change->dest = g_strconcat(base, extension, NULL);
1730 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1736 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1739 gchar *dest_path_full = NULL;
1741 if (fd->parent) fd = fd->parent;
1745 dest_path = fd->path;
1747 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1749 gchar *dir = remove_level_from_path(fd->path);
1751 dest_path_full = g_build_filename(dir, dest_path, NULL);
1753 dest_path = dest_path_full;
1755 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1757 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1758 dest_path = dest_path_full;
1761 file_data_update_ci_dest(fd, dest_path);
1763 work = fd->sidecar_files;
1766 FileData *sfd = work->data;
1768 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1772 g_free(dest_path_full);
1775 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1777 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1778 file_data_sc_update_ci(fd, dest_path);
1782 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1784 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1787 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1789 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1792 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1794 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1797 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1799 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1802 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1804 gboolean (*func)(FileData *, const gchar *))
1807 gboolean ret = TRUE;
1812 FileData *fd = work->data;
1814 if (!func(fd, dest)) ret = FALSE;
1821 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1823 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1826 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1828 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1831 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1833 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1838 * verify source and dest paths - dest image exists, etc.
1839 * it should detect all possible problems with the planned operation
1842 gint file_data_verify_ci(FileData *fd)
1844 gint ret = CHANGE_OK;
1849 DEBUG_1("Change checked: no change info: %s", fd->path);
1853 if (!isname(fd->path))
1855 /* this probably should not happen */
1856 ret |= CHANGE_NO_SRC;
1857 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1861 dir = remove_level_from_path(fd->path);
1863 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1864 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1865 !access_file(fd->path, R_OK))
1867 ret |= CHANGE_NO_READ_PERM;
1868 DEBUG_1("Change checked: no read permission: %s", fd->path);
1870 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1871 !access_file(dir, W_OK))
1873 ret |= CHANGE_NO_WRITE_PERM_DIR;
1874 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1876 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1877 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1878 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1879 !access_file(fd->path, W_OK))
1881 ret |= CHANGE_WARN_NO_WRITE_PERM;
1882 DEBUG_1("Change checked: no write permission: %s", fd->path);
1884 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1885 - that means that there are no hard errors and warnings can be disabled
1886 - the destination is determined during the check
1888 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1890 /* determine destination file */
1891 gboolean have_dest = FALSE;
1892 gchar *dest_dir = NULL;
1894 if (options->metadata.save_in_image_file)
1896 if (file_data_can_write_directly(fd))
1898 /* we can write the file directly */
1899 if (access_file(fd->path, W_OK))
1905 if (options->metadata.warn_on_write_problems)
1907 ret |= CHANGE_WARN_NO_WRITE_PERM;
1908 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1912 else if (file_data_can_write_sidecar(fd))
1914 /* we can write sidecar */
1915 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1916 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1918 file_data_update_ci_dest(fd, sidecar);
1923 if (options->metadata.warn_on_write_problems)
1925 ret |= CHANGE_WARN_NO_WRITE_PERM;
1926 DEBUG_1("Change checked: file is not writable: %s", sidecar);
1935 /* write private metadata file under ~/.geeqie */
1937 /* If an existing metadata file exists, we will try writing to
1938 * it's location regardless of the user's preference.
1940 gchar *metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
1941 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
1943 if (metadata_path && !access_file(metadata_path, W_OK))
1945 g_free(metadata_path);
1946 metadata_path = NULL;
1953 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
1954 if (recursive_mkdir_if_not_exists(dest_dir, mode))
1956 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
1958 metadata_path = g_build_filename(dest_dir, filename, NULL);
1962 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
1964 file_data_update_ci_dest(fd, metadata_path);
1969 ret |= CHANGE_NO_WRITE_PERM_DEST;
1970 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
1972 g_free(metadata_path);
1977 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
1982 same = (strcmp(fd->path, fd->change->dest) == 0);
1986 const gchar *dest_ext = extension_from_path(fd->change->dest);
1987 if (!dest_ext) dest_ext = "";
1989 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
1991 ret |= CHANGE_WARN_CHANGED_EXT;
1992 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1997 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
1999 ret |= CHANGE_WARN_SAME;
2000 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2004 dest_dir = remove_level_from_path(fd->change->dest);
2006 if (!isdir(dest_dir))
2008 ret |= CHANGE_NO_DEST_DIR;
2009 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2011 else if (!access_file(dest_dir, W_OK))
2013 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2014 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2018 if (isfile(fd->change->dest))
2020 if (!access_file(fd->change->dest, W_OK))
2022 ret |= CHANGE_NO_WRITE_PERM_DEST;
2023 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2027 ret |= CHANGE_WARN_DEST_EXISTS;
2028 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2031 else if (isdir(fd->change->dest))
2033 ret |= CHANGE_DEST_EXISTS;
2034 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2041 fd->change->error = ret;
2042 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2049 gint file_data_sc_verify_ci(FileData *fd)
2054 ret = file_data_verify_ci(fd);
2056 work = fd->sidecar_files;
2059 FileData *sfd = work->data;
2061 ret |= file_data_verify_ci(sfd);
2068 gchar *file_data_get_error_string(gint error)
2070 GString *result = g_string_new("");
2072 if (error & CHANGE_NO_SRC)
2074 if (result->len > 0) g_string_append(result, ", ");
2075 g_string_append(result, _("file or directory does not exist"));
2078 if (error & CHANGE_DEST_EXISTS)
2080 if (result->len > 0) g_string_append(result, ", ");
2081 g_string_append(result, _("destination already exists"));
2084 if (error & CHANGE_NO_WRITE_PERM_DEST)
2086 if (result->len > 0) g_string_append(result, ", ");
2087 g_string_append(result, _("destination can't be overwritten"));
2090 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2092 if (result->len > 0) g_string_append(result, ", ");
2093 g_string_append(result, _("destination directory is not writable"));
2096 if (error & CHANGE_NO_DEST_DIR)
2098 if (result->len > 0) g_string_append(result, ", ");
2099 g_string_append(result, _("destination directory does not exist"));
2102 if (error & CHANGE_NO_WRITE_PERM_DIR)
2104 if (result->len > 0) g_string_append(result, ", ");
2105 g_string_append(result, _("source directory is not writable"));
2108 if (error & CHANGE_NO_READ_PERM)
2110 if (result->len > 0) g_string_append(result, ", ");
2111 g_string_append(result, _("no read permission"));
2114 if (error & CHANGE_WARN_NO_WRITE_PERM)
2116 if (result->len > 0) g_string_append(result, ", ");
2117 g_string_append(result, _("file is readonly"));
2120 if (error & CHANGE_WARN_DEST_EXISTS)
2122 if (result->len > 0) g_string_append(result, ", ");
2123 g_string_append(result, _("destination already exists and will be overwritten"));
2126 if (error & CHANGE_WARN_SAME)
2128 if (result->len > 0) g_string_append(result, ", ");
2129 g_string_append(result, _("source and destination are the same"));
2132 if (error & CHANGE_WARN_CHANGED_EXT)
2134 if (result->len > 0) g_string_append(result, ", ");
2135 g_string_append(result, _("source and destination have different extension"));
2138 return g_string_free(result, FALSE);
2141 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2144 gint all_errors = 0;
2145 gint common_errors = ~0;
2150 if (!list) return 0;
2152 num = g_list_length(list);
2153 errors = g_new(int, num);
2164 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2165 all_errors |= error;
2166 common_errors &= error;
2173 if (desc && all_errors)
2176 GString *result = g_string_new("");
2180 gchar *str = file_data_get_error_string(common_errors);
2181 g_string_append(result, str);
2182 g_string_append(result, "\n");
2196 error = errors[i] & ~common_errors;
2200 gchar *str = file_data_get_error_string(error);
2201 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2206 *desc = g_string_free(result, FALSE);
2215 * perform the change described by FileFataChangeInfo
2216 * it is used for internal operations,
2217 * this function actually operates with files on the filesystem
2218 * it should implement safe delete
2221 static gboolean file_data_perform_move(FileData *fd)
2223 g_assert(!strcmp(fd->change->source, fd->path));
2224 return move_file(fd->change->source, fd->change->dest);
2227 static gboolean file_data_perform_copy(FileData *fd)
2229 g_assert(!strcmp(fd->change->source, fd->path));
2230 return copy_file(fd->change->source, fd->change->dest);
2233 static gboolean file_data_perform_delete(FileData *fd)
2235 if (isdir(fd->path) && !islink(fd->path))
2236 return rmdir_utf8(fd->path);
2238 if (options->file_ops.safe_delete_enable)
2239 return file_util_safe_unlink(fd->path);
2241 return unlink_file(fd->path);
2244 gboolean file_data_perform_ci(FileData *fd)
2246 FileDataChangeType type = fd->change->type;
2250 case FILEDATA_CHANGE_MOVE:
2251 return file_data_perform_move(fd);
2252 case FILEDATA_CHANGE_COPY:
2253 return file_data_perform_copy(fd);
2254 case FILEDATA_CHANGE_RENAME:
2255 return file_data_perform_move(fd); /* the same as move */
2256 case FILEDATA_CHANGE_DELETE:
2257 return file_data_perform_delete(fd);
2258 case FILEDATA_CHANGE_WRITE_METADATA:
2259 return metadata_write_perform(fd);
2260 case FILEDATA_CHANGE_UNSPECIFIED:
2261 /* nothing to do here */
2269 gboolean file_data_sc_perform_ci(FileData *fd)
2272 gboolean ret = TRUE;
2273 FileDataChangeType type = fd->change->type;
2275 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2277 work = fd->sidecar_files;
2280 FileData *sfd = work->data;
2282 if (!file_data_perform_ci(sfd)) ret = FALSE;
2286 if (!file_data_perform_ci(fd)) ret = FALSE;
2292 * updates FileData structure according to FileDataChangeInfo
2295 gboolean file_data_apply_ci(FileData *fd)
2297 FileDataChangeType type = fd->change->type;
2300 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2302 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2303 file_data_planned_change_remove(fd);
2305 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2307 /* this change overwrites another file which is already known to other modules
2308 renaming fd would create duplicate FileData structure
2309 the best thing we can do is nothing
2310 FIXME: maybe we could copy stuff like marks
2312 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2316 file_data_set_path(fd, fd->change->dest);
2319 file_data_increment_version(fd);
2320 file_data_send_notification(fd, NOTIFY_CHANGE);
2325 gboolean file_data_sc_apply_ci(FileData *fd)
2328 FileDataChangeType type = fd->change->type;
2330 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2332 work = fd->sidecar_files;
2335 FileData *sfd = work->data;
2337 file_data_apply_ci(sfd);
2341 file_data_apply_ci(fd);
2346 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2349 if (fd->parent) fd = fd->parent;
2350 if (!g_list_find(list, fd)) return FALSE;
2352 work = fd->sidecar_files;
2355 if (!g_list_find(list, work->data)) return FALSE;
2362 static gboolean file_data_list_dump(GList *list)
2364 GList *work, *work2;
2369 FileData *fd = work->data;
2370 printf("%s\n", fd->name);
2371 work2 = fd->sidecar_files;
2374 FileData *fd = work2->data;
2375 printf(" %s\n", fd->name);
2376 work2 = work2->next;
2384 GList *file_data_process_groups_in_selection(GList *list, GList **ungrouped_list)
2389 /* change partial groups to independent files */
2392 FileData *fd = work->data;
2395 if (!file_data_list_contains_whole_group(list, fd))
2397 file_data_disable_grouping(fd, TRUE);
2400 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2405 /* remove sidecars from the list,
2406 they can be still acessed via main_fd->sidecar_files */
2410 FileData *fd = work->data;
2415 out = g_list_prepend(out, fd);
2419 file_data_unref(fd);
2424 out = g_list_reverse(out);
2434 * notify other modules about the change described by FileDataChangeInfo
2437 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2438 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2439 implementation in view_file_list.c */
2444 typedef struct _NotifyData NotifyData;
2446 struct _NotifyData {
2447 FileDataNotifyFunc func;
2449 NotifyPriority priority;
2452 static GList *notify_func_list = NULL;
2454 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2456 NotifyData *nda = (NotifyData *)a;
2457 NotifyData *ndb = (NotifyData *)b;
2459 if (nda->priority < ndb->priority) return -1;
2460 if (nda->priority > ndb->priority) return 1;
2464 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2468 nd = g_new(NotifyData, 1);
2471 nd->priority = priority;
2473 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2474 DEBUG_2("Notify func registered: %p", nd);
2479 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2481 GList *work = notify_func_list;
2485 NotifyData *nd = (NotifyData *)work->data;
2487 if (nd->func == func && nd->data == data)
2489 notify_func_list = g_list_delete_link(notify_func_list, work);
2491 DEBUG_2("Notify func unregistered: %p", nd);
2501 void file_data_send_notification(FileData *fd, NotifyType type)
2503 GList *work = notify_func_list;
2507 NotifyData *nd = (NotifyData *)work->data;
2509 nd->func(fd, type, nd->data);
2514 static GHashTable *file_data_monitor_pool = NULL;
2515 static guint realtime_monitor_id = 0; /* event source id */
2517 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2521 file_data_check_changed_files(fd);
2523 DEBUG_1("monitor %s", fd->path);
2526 static gboolean realtime_monitor_cb(gpointer data)
2528 if (!options->update_on_time_change) return TRUE;
2529 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2533 gboolean file_data_register_real_time_monitor(FileData *fd)
2539 if (!file_data_monitor_pool)
2540 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2542 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2544 DEBUG_1("Register realtime %d %s", count, fd->path);
2547 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2549 if (!realtime_monitor_id)
2551 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2557 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2561 g_assert(file_data_monitor_pool);
2563 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2565 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2567 g_assert(count > 0);
2572 g_hash_table_remove(file_data_monitor_pool, fd);
2574 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2576 file_data_unref(fd);
2578 if (g_hash_table_size(file_data_monitor_pool) == 0)
2580 g_source_remove(realtime_monitor_id);
2581 realtime_monitor_id = 0;
2587 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */