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;
673 fd->disable_grouping = !!disable;
679 FileData *parent = file_data_ref(fd->parent);
680 file_data_disconnect_sidecar_file(parent, fd);
681 file_data_send_notification(fd, NOTIFY_GROUPING);
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_send_notification(fd, NOTIFY_GROUPING);
697 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
698 filelist_free(sidecar_files);
703 file_data_check_sidecars(fd, FALSE);
704 file_data_send_notification(fd, NOTIFY_GROUPING);
708 /* compare name without extension */
709 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
711 size_t len1 = fd1->extension - fd1->name;
712 size_t len2 = fd2->extension - fd2->name;
714 if (len1 < len2) return -1;
715 if (len1 > len2) return 1;
717 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
720 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
722 if (!fdci && fd) fdci = fd->change;
726 g_free(fdci->source);
731 if (fd) fd->change = NULL;
734 static gboolean file_data_can_write_directly(FileData *fd)
736 return filter_name_is_writable(fd->extension);
739 static gboolean file_data_can_write_sidecar(FileData *fd)
741 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
744 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
746 gchar *sidecar_path = NULL;
749 if (!file_data_can_write_sidecar(fd)) return NULL;
751 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
754 FileData *sfd = work->data;
756 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
758 sidecar_path = g_strdup(sfd->path);
763 if (!existing_only && !sidecar_path)
765 gchar *base = remove_extension_from_path(fd->path);
766 sidecar_path = g_strconcat(base, ".xmp", NULL);
775 *-----------------------------------------------------------------------------
776 * sidecar file info struct
777 *-----------------------------------------------------------------------------
782 static gint sidecar_file_priority(const gchar *path)
784 const gchar *extension = extension_from_path(path);
788 if (extension == NULL)
791 work = sidecar_ext_get_list();
794 gchar *ext = work->data;
797 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
805 *-----------------------------------------------------------------------------
807 *-----------------------------------------------------------------------------
810 static SortType filelist_sort_method = SORT_NONE;
811 static gboolean filelist_sort_ascend = TRUE;
814 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
816 if (!filelist_sort_ascend)
823 switch (filelist_sort_method)
828 if (fa->size < fb->size) return -1;
829 if (fa->size > fb->size) return 1;
830 /* fall back to name */
833 if (fa->date < fb->date) return -1;
834 if (fa->date > fb->date) return 1;
835 /* fall back to name */
837 #ifdef HAVE_STRVERSCMP
839 return strverscmp(fa->name, fb->name);
846 if (options->file_sort.case_sensitive)
847 return strcmp(fa->collate_key_name, fb->collate_key_name);
849 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
852 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
854 filelist_sort_method = method;
855 filelist_sort_ascend = ascend;
856 return filelist_sort_compare_filedata(fa, fb);
859 static gint filelist_sort_file_cb(gpointer a, gpointer b)
861 return filelist_sort_compare_filedata(a, b);
864 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
866 filelist_sort_method = method;
867 filelist_sort_ascend = ascend;
868 return g_list_sort(list, cb);
871 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
873 filelist_sort_method = method;
874 filelist_sort_ascend = ascend;
875 return g_list_insert_sorted(list, data, cb);
878 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
880 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
883 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
885 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
889 static GList *filelist_filter_out_sidecars(GList *flist)
892 GList *flist_filtered = NULL;
896 FileData *fd = work->data;
899 if (fd->parent) /* remove fd's that are children */
902 flist_filtered = g_list_prepend(flist_filtered, fd);
906 return flist_filtered;
909 static gboolean is_hidden_file(const gchar *name)
911 if (name[0] != '.') return FALSE;
912 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
916 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
923 gint (*stat_func)(const gchar *path, struct stat *buf);
925 g_assert(files || dirs);
927 if (files) *files = NULL;
928 if (dirs) *dirs = NULL;
930 pathl = path_from_utf8(dir_fd->path);
931 if (!pathl) return FALSE;
945 while ((dir = readdir(dp)) != NULL)
947 struct stat ent_sbuf;
948 const gchar *name = dir->d_name;
951 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
954 filepath = g_build_filename(pathl, name, NULL);
955 if (stat_func(filepath, &ent_sbuf) >= 0)
957 if (S_ISDIR(ent_sbuf.st_mode))
959 /* we ignore the .thumbnails dir for cleanliness */
961 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
962 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
963 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
964 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
966 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, FALSE));
971 if (files && filter_name_exists(name))
973 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, FALSE));
984 if (dirs) *dirs = dlist;
985 if (files) *files = filelist_filter_out_sidecars(flist);
990 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
992 return filelist_read_real(dir_fd, files, dirs, TRUE);
995 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
997 return filelist_read_real(dir_fd, files, dirs, FALSE);
1000 void filelist_free(GList *list)
1007 file_data_unref((FileData *)work->data);
1015 GList *filelist_copy(GList *list)
1017 GList *new_list = NULL;
1028 new_list = g_list_prepend(new_list, file_data_ref(fd));
1031 return g_list_reverse(new_list);
1034 GList *filelist_from_path_list(GList *list)
1036 GList *new_list = NULL;
1047 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1050 return g_list_reverse(new_list);
1053 GList *filelist_to_path_list(GList *list)
1055 GList *new_list = NULL;
1066 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1069 return g_list_reverse(new_list);
1072 GList *filelist_filter(GList *list, gboolean is_dir_list)
1076 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1081 FileData *fd = (FileData *)(work->data);
1082 const gchar *name = fd->name;
1084 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1085 (!is_dir_list && !filter_name_exists(name)) ||
1086 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1087 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1091 list = g_list_remove_link(list, link);
1092 file_data_unref(fd);
1103 *-----------------------------------------------------------------------------
1104 * filelist recursive
1105 *-----------------------------------------------------------------------------
1108 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1110 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1113 GList *filelist_sort_path(GList *list)
1115 return g_list_sort(list, filelist_sort_path_cb);
1118 static void filelist_recursive_append(GList **list, GList *dirs)
1125 FileData *fd = (FileData *)(work->data);
1129 if (filelist_read(fd, &f, &d))
1131 f = filelist_filter(f, FALSE);
1132 f = filelist_sort_path(f);
1133 *list = g_list_concat(*list, f);
1135 d = filelist_filter(d, TRUE);
1136 d = filelist_sort_path(d);
1137 filelist_recursive_append(list, d);
1145 GList *filelist_recursive(FileData *dir_fd)
1150 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1151 list = filelist_filter(list, FALSE);
1152 list = filelist_sort_path(list);
1154 d = filelist_filter(d, TRUE);
1155 d = filelist_sort_path(d);
1156 filelist_recursive_append(&list, d);
1164 * marks and orientation
1167 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1168 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1169 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1170 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1172 gboolean file_data_get_mark(FileData *fd, gint n)
1174 gboolean valid = (fd->valid_marks & (1 << n));
1176 if (file_data_get_mark_func[n] && !valid)
1178 guint old = fd->marks;
1179 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1181 if (!value != !(fd->marks & (1 << n)))
1183 fd->marks = fd->marks ^ (1 << n);
1186 fd->valid_marks |= (1 << n);
1187 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1189 file_data_unref(fd);
1191 else if (!old && fd->marks)
1197 return !!(fd->marks & (1 << n));
1200 guint file_data_get_marks(FileData *fd)
1203 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1207 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1210 if (!value == !file_data_get_mark(fd, n)) return;
1212 if (file_data_set_mark_func[n])
1214 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1219 fd->marks = fd->marks ^ (1 << n);
1221 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1223 file_data_unref(fd);
1225 else if (!old && fd->marks)
1230 file_data_increment_version(fd);
1231 file_data_send_notification(fd, NOTIFY_MARKS);
1234 gboolean file_data_filter_marks(FileData *fd, guint filter)
1237 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1238 return ((fd->marks & filter) == filter);
1241 GList *file_data_filter_marks_list(GList *list, guint filter)
1248 FileData *fd = work->data;
1252 if (!file_data_filter_marks(fd, filter))
1254 list = g_list_remove_link(list, link);
1255 file_data_unref(fd);
1263 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1265 FileData *fd = value;
1266 file_data_increment_version(fd);
1267 file_data_send_notification(fd, NOTIFY_MARKS);
1270 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1272 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1274 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1276 file_data_get_mark_func[n] = get_mark_func;
1277 file_data_set_mark_func[n] = set_mark_func;
1278 file_data_mark_func_data[n] = data;
1279 file_data_destroy_mark_func[n] = notify;
1283 /* this effectively changes all known files */
1284 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1290 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1292 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1293 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1294 if (data) *data = file_data_mark_func_data[n];
1297 gint file_data_get_user_orientation(FileData *fd)
1299 return fd->user_orientation;
1302 void file_data_set_user_orientation(FileData *fd, gint value)
1304 if (fd->user_orientation == value) return;
1306 fd->user_orientation = value;
1307 file_data_increment_version(fd);
1308 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1313 * file_data - operates on the given fd
1314 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1318 /* return list of sidecar file extensions in a string */
1319 gchar *file_data_sc_list_to_string(FileData *fd)
1322 GString *result = g_string_new("");
1324 work = fd->sidecar_files;
1327 FileData *sfd = work->data;
1329 result = g_string_append(result, "+ ");
1330 result = g_string_append(result, sfd->extension);
1332 if (work) result = g_string_append_c(result, ' ');
1335 return g_string_free(result, FALSE);
1341 * add FileDataChangeInfo (see typedefs.h) for the given operation
1342 * uses file_data_add_change_info
1344 * fails if the fd->change already exists - change operations can't run in parallel
1345 * fd->change_info works as a lock
1347 * dest can be NULL - in this case the current name is used for now, it will
1352 FileDataChangeInfo types:
1354 MOVE - path is changed, name may be changed too
1355 RENAME - path remains unchanged, name is changed
1356 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1357 sidecar names are changed too, extensions are not changed
1359 UPDATE - file size, date or grouping has been changed
1362 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1364 FileDataChangeInfo *fdci;
1366 if (fd->change) return FALSE;
1368 fdci = g_new0(FileDataChangeInfo, 1);
1373 fdci->source = g_strdup(src);
1375 fdci->source = g_strdup(fd->path);
1378 fdci->dest = g_strdup(dest);
1385 static void file_data_planned_change_remove(FileData *fd)
1387 if (file_data_planned_change_hash &&
1388 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1390 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1392 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1393 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1394 file_data_unref(fd);
1395 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1397 g_hash_table_destroy(file_data_planned_change_hash);
1398 file_data_planned_change_hash = NULL;
1399 DEBUG_1("planned change: empty");
1406 void file_data_free_ci(FileData *fd)
1408 FileDataChangeInfo *fdci = fd->change;
1412 file_data_planned_change_remove(fd);
1414 g_free(fdci->source);
1423 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1427 if (fd->parent) fd = fd->parent;
1429 if (fd->change) return FALSE;
1431 work = fd->sidecar_files;
1434 FileData *sfd = work->data;
1436 if (sfd->change) return FALSE;
1440 file_data_add_ci(fd, type, NULL, NULL);
1442 work = fd->sidecar_files;
1445 FileData *sfd = work->data;
1447 file_data_add_ci(sfd, type, NULL, NULL);
1454 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1458 if (fd->parent) fd = fd->parent;
1460 if (!fd->change || fd->change->type != type) return FALSE;
1462 work = fd->sidecar_files;
1465 FileData *sfd = work->data;
1467 if (!sfd->change || sfd->change->type != type) return FALSE;
1475 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1477 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1478 file_data_sc_update_ci_copy(fd, dest_path);
1482 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1484 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1485 file_data_sc_update_ci_move(fd, dest_path);
1489 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1491 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1492 file_data_sc_update_ci_rename(fd, dest_path);
1496 gboolean file_data_sc_add_ci_delete(FileData *fd)
1498 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1501 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1503 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1504 file_data_sc_update_ci_unspecified(fd, dest_path);
1508 gboolean file_data_add_ci_write_metadata(FileData *fd)
1510 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1513 void file_data_sc_free_ci(FileData *fd)
1517 if (fd->parent) fd = fd->parent;
1519 file_data_free_ci(fd);
1521 work = fd->sidecar_files;
1524 FileData *sfd = work->data;
1526 file_data_free_ci(sfd);
1531 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1534 gboolean ret = TRUE;
1539 FileData *fd = work->data;
1541 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1548 static void file_data_sc_revert_ci_list(GList *fd_list)
1555 FileData *fd = work->data;
1557 file_data_sc_free_ci(fd);
1562 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1569 FileData *fd = work->data;
1571 if (!func(fd, dest))
1573 file_data_sc_revert_ci_list(work->prev);
1582 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1584 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1587 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1589 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1592 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1594 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1597 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1599 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1602 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1605 gboolean ret = TRUE;
1610 FileData *fd = work->data;
1612 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1619 void file_data_free_ci_list(GList *fd_list)
1626 FileData *fd = work->data;
1628 file_data_free_ci(fd);
1633 void file_data_sc_free_ci_list(GList *fd_list)
1640 FileData *fd = work->data;
1642 file_data_sc_free_ci(fd);
1648 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1649 * fails if fd->change does not exist or the change type does not match
1652 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1654 FileDataChangeType type = fd->change->type;
1656 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1660 if (!file_data_planned_change_hash)
1661 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1663 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1665 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1666 g_hash_table_remove(file_data_planned_change_hash, old_path);
1667 file_data_unref(fd);
1670 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1675 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1676 g_hash_table_remove(file_data_planned_change_hash, new_path);
1677 file_data_unref(ofd);
1680 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1682 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1687 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1689 gchar *old_path = fd->change->dest;
1691 fd->change->dest = g_strdup(dest_path);
1692 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1696 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1698 const gchar *extension = extension_from_path(fd->change->source);
1699 gchar *base = remove_extension_from_path(dest_path);
1700 gchar *old_path = fd->change->dest;
1702 fd->change->dest = g_strconcat(base, extension, NULL);
1703 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1709 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1712 gchar *dest_path_full = NULL;
1714 if (fd->parent) fd = fd->parent;
1718 dest_path = fd->path;
1720 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1722 gchar *dir = remove_level_from_path(fd->path);
1724 dest_path_full = g_build_filename(dir, dest_path, NULL);
1726 dest_path = dest_path_full;
1728 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1730 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1731 dest_path = dest_path_full;
1734 file_data_update_ci_dest(fd, dest_path);
1736 work = fd->sidecar_files;
1739 FileData *sfd = work->data;
1741 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1745 g_free(dest_path_full);
1748 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1750 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1751 file_data_sc_update_ci(fd, dest_path);
1755 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1757 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1760 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1762 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1765 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1767 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1770 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1772 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1775 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1777 gboolean (*func)(FileData *, const gchar *))
1780 gboolean ret = TRUE;
1785 FileData *fd = work->data;
1787 if (!func(fd, dest)) ret = FALSE;
1794 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1796 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1799 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1801 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1804 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1806 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1811 * verify source and dest paths - dest image exists, etc.
1812 * it should detect all possible problems with the planned operation
1815 gint file_data_verify_ci(FileData *fd)
1817 gint ret = CHANGE_OK;
1822 DEBUG_1("Change checked: no change info: %s", fd->path);
1826 if (!isname(fd->path))
1828 /* this probably should not happen */
1829 ret |= CHANGE_NO_SRC;
1830 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1834 dir = remove_level_from_path(fd->path);
1836 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1837 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1838 !access_file(fd->path, R_OK))
1840 ret |= CHANGE_NO_READ_PERM;
1841 DEBUG_1("Change checked: no read permission: %s", fd->path);
1843 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1844 !access_file(dir, W_OK))
1846 ret |= CHANGE_NO_WRITE_PERM_DIR;
1847 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1849 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1850 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1851 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1852 !access_file(fd->path, W_OK))
1854 ret |= CHANGE_WARN_NO_WRITE_PERM;
1855 DEBUG_1("Change checked: no write permission: %s", fd->path);
1857 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1858 - that means that there are no hard errors and warnings can be disabled
1859 - the destination is determined during the check
1861 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1863 /* determine destination file */
1864 gboolean have_dest = FALSE;
1865 gchar *dest_dir = NULL;
1867 if (options->metadata.save_in_image_file)
1869 if (file_data_can_write_directly(fd))
1871 /* we can write the file directly */
1872 if (access_file(fd->path, W_OK))
1878 if (options->metadata.warn_on_write_problems)
1880 ret |= CHANGE_WARN_NO_WRITE_PERM;
1881 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1885 else if (file_data_can_write_sidecar(fd))
1887 /* we can write sidecar */
1888 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1889 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1891 file_data_update_ci_dest(fd, sidecar);
1896 if (options->metadata.warn_on_write_problems)
1898 ret |= CHANGE_WARN_NO_WRITE_PERM;
1899 DEBUG_1("Change checked: file is not writable: %s", sidecar);
1908 /* write private metadata file under ~/.geeqie */
1910 /* If an existing metadata file exists, we will try writing to
1911 * it's location regardless of the user's preference.
1913 gchar *metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
1914 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
1916 if (metadata_path && !access_file(metadata_path, W_OK))
1918 g_free(metadata_path);
1919 metadata_path = NULL;
1926 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
1927 if (recursive_mkdir_if_not_exists(dest_dir, mode))
1929 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
1931 metadata_path = g_build_filename(dest_dir, filename, NULL);
1935 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
1937 file_data_update_ci_dest(fd, metadata_path);
1942 ret |= CHANGE_NO_WRITE_PERM_DEST;
1943 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
1945 g_free(metadata_path);
1950 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
1955 same = (strcmp(fd->path, fd->change->dest) == 0);
1959 const gchar *dest_ext = extension_from_path(fd->change->dest);
1960 if (!dest_ext) dest_ext = "";
1962 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
1964 ret |= CHANGE_WARN_CHANGED_EXT;
1965 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
1970 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
1972 ret |= CHANGE_WARN_SAME;
1973 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
1977 dest_dir = remove_level_from_path(fd->change->dest);
1979 if (!isdir(dest_dir))
1981 ret |= CHANGE_NO_DEST_DIR;
1982 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
1984 else if (!access_file(dest_dir, W_OK))
1986 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
1987 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
1991 if (isfile(fd->change->dest))
1993 if (!access_file(fd->change->dest, W_OK))
1995 ret |= CHANGE_NO_WRITE_PERM_DEST;
1996 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2000 ret |= CHANGE_WARN_DEST_EXISTS;
2001 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2004 else if (isdir(fd->change->dest))
2006 ret |= CHANGE_DEST_EXISTS;
2007 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2014 fd->change->error = ret;
2015 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2022 gint file_data_sc_verify_ci(FileData *fd)
2027 ret = file_data_verify_ci(fd);
2029 work = fd->sidecar_files;
2032 FileData *sfd = work->data;
2034 ret |= file_data_verify_ci(sfd);
2041 gchar *file_data_get_error_string(gint error)
2043 GString *result = g_string_new("");
2045 if (error & CHANGE_NO_SRC)
2047 if (result->len > 0) g_string_append(result, ", ");
2048 g_string_append(result, _("file or directory does not exist"));
2051 if (error & CHANGE_DEST_EXISTS)
2053 if (result->len > 0) g_string_append(result, ", ");
2054 g_string_append(result, _("destination already exists"));
2057 if (error & CHANGE_NO_WRITE_PERM_DEST)
2059 if (result->len > 0) g_string_append(result, ", ");
2060 g_string_append(result, _("destination can't be overwritten"));
2063 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2065 if (result->len > 0) g_string_append(result, ", ");
2066 g_string_append(result, _("destination directory is not writable"));
2069 if (error & CHANGE_NO_DEST_DIR)
2071 if (result->len > 0) g_string_append(result, ", ");
2072 g_string_append(result, _("destination directory does not exist"));
2075 if (error & CHANGE_NO_WRITE_PERM_DIR)
2077 if (result->len > 0) g_string_append(result, ", ");
2078 g_string_append(result, _("source directory is not writable"));
2081 if (error & CHANGE_NO_READ_PERM)
2083 if (result->len > 0) g_string_append(result, ", ");
2084 g_string_append(result, _("no read permission"));
2087 if (error & CHANGE_WARN_NO_WRITE_PERM)
2089 if (result->len > 0) g_string_append(result, ", ");
2090 g_string_append(result, _("file is readonly"));
2093 if (error & CHANGE_WARN_DEST_EXISTS)
2095 if (result->len > 0) g_string_append(result, ", ");
2096 g_string_append(result, _("destination already exists and will be overwritten"));
2099 if (error & CHANGE_WARN_SAME)
2101 if (result->len > 0) g_string_append(result, ", ");
2102 g_string_append(result, _("source and destination are the same"));
2105 if (error & CHANGE_WARN_CHANGED_EXT)
2107 if (result->len > 0) g_string_append(result, ", ");
2108 g_string_append(result, _("source and destination have different extension"));
2111 return g_string_free(result, FALSE);
2114 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2117 gint all_errors = 0;
2118 gint common_errors = ~0;
2123 if (!list) return 0;
2125 num = g_list_length(list);
2126 errors = g_new(int, num);
2137 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2138 all_errors |= error;
2139 common_errors &= error;
2146 if (desc && all_errors)
2149 GString *result = g_string_new("");
2153 gchar *str = file_data_get_error_string(common_errors);
2154 g_string_append(result, str);
2155 g_string_append(result, "\n");
2169 error = errors[i] & ~common_errors;
2173 gchar *str = file_data_get_error_string(error);
2174 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2179 *desc = g_string_free(result, FALSE);
2188 * perform the change described by FileFataChangeInfo
2189 * it is used for internal operations,
2190 * this function actually operates with files on the filesystem
2191 * it should implement safe delete
2194 static gboolean file_data_perform_move(FileData *fd)
2196 g_assert(!strcmp(fd->change->source, fd->path));
2197 return move_file(fd->change->source, fd->change->dest);
2200 static gboolean file_data_perform_copy(FileData *fd)
2202 g_assert(!strcmp(fd->change->source, fd->path));
2203 return copy_file(fd->change->source, fd->change->dest);
2206 static gboolean file_data_perform_delete(FileData *fd)
2208 if (isdir(fd->path) && !islink(fd->path))
2209 return rmdir_utf8(fd->path);
2211 if (options->file_ops.safe_delete_enable)
2212 return file_util_safe_unlink(fd->path);
2214 return unlink_file(fd->path);
2217 gboolean file_data_perform_ci(FileData *fd)
2219 FileDataChangeType type = fd->change->type;
2223 case FILEDATA_CHANGE_MOVE:
2224 return file_data_perform_move(fd);
2225 case FILEDATA_CHANGE_COPY:
2226 return file_data_perform_copy(fd);
2227 case FILEDATA_CHANGE_RENAME:
2228 return file_data_perform_move(fd); /* the same as move */
2229 case FILEDATA_CHANGE_DELETE:
2230 return file_data_perform_delete(fd);
2231 case FILEDATA_CHANGE_WRITE_METADATA:
2232 return metadata_write_perform(fd);
2233 case FILEDATA_CHANGE_UNSPECIFIED:
2234 /* nothing to do here */
2242 gboolean file_data_sc_perform_ci(FileData *fd)
2245 gboolean ret = TRUE;
2246 FileDataChangeType type = fd->change->type;
2248 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2250 work = fd->sidecar_files;
2253 FileData *sfd = work->data;
2255 if (!file_data_perform_ci(sfd)) ret = FALSE;
2259 if (!file_data_perform_ci(fd)) ret = FALSE;
2265 * updates FileData structure according to FileDataChangeInfo
2268 gboolean file_data_apply_ci(FileData *fd)
2270 FileDataChangeType type = fd->change->type;
2273 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2275 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2276 file_data_planned_change_remove(fd);
2278 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2280 /* this change overwrites another file which is already known to other modules
2281 renaming fd would create duplicate FileData structure
2282 the best thing we can do is nothing
2283 FIXME: maybe we could copy stuff like marks
2285 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2289 file_data_set_path(fd, fd->change->dest);
2292 file_data_increment_version(fd);
2293 file_data_send_notification(fd, NOTIFY_CHANGE);
2298 gboolean file_data_sc_apply_ci(FileData *fd)
2301 FileDataChangeType type = fd->change->type;
2303 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2305 work = fd->sidecar_files;
2308 FileData *sfd = work->data;
2310 file_data_apply_ci(sfd);
2314 file_data_apply_ci(fd);
2320 * notify other modules about the change described by FileFataChangeInfo
2323 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2324 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2325 implementation in view_file_list.c */
2330 typedef struct _NotifyData NotifyData;
2332 struct _NotifyData {
2333 FileDataNotifyFunc func;
2335 NotifyPriority priority;
2338 static GList *notify_func_list = NULL;
2340 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2342 NotifyData *nda = (NotifyData *)a;
2343 NotifyData *ndb = (NotifyData *)b;
2345 if (nda->priority < ndb->priority) return -1;
2346 if (nda->priority > ndb->priority) return 1;
2350 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2354 nd = g_new(NotifyData, 1);
2357 nd->priority = priority;
2359 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2360 DEBUG_2("Notify func registered: %p", nd);
2365 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2367 GList *work = notify_func_list;
2371 NotifyData *nd = (NotifyData *)work->data;
2373 if (nd->func == func && nd->data == data)
2375 notify_func_list = g_list_delete_link(notify_func_list, work);
2377 DEBUG_2("Notify func unregistered: %p", nd);
2387 void file_data_send_notification(FileData *fd, NotifyType type)
2389 GList *work = notify_func_list;
2393 NotifyData *nd = (NotifyData *)work->data;
2395 nd->func(fd, type, nd->data);
2400 static GHashTable *file_data_monitor_pool = NULL;
2401 static guint realtime_monitor_id = 0; /* event source id */
2403 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2407 file_data_check_changed_files(fd);
2409 DEBUG_1("monitor %s", fd->path);
2412 static gboolean realtime_monitor_cb(gpointer data)
2414 if (!options->update_on_time_change) return TRUE;
2415 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2419 gboolean file_data_register_real_time_monitor(FileData *fd)
2425 if (!file_data_monitor_pool)
2426 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2428 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2430 DEBUG_1("Register realtime %d %s", count, fd->path);
2433 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2435 if (!realtime_monitor_id)
2437 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2443 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2447 g_assert(file_data_monitor_pool);
2449 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2451 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2453 g_assert(count > 0);
2458 g_hash_table_remove(file_data_monitor_pool, fd);
2460 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2462 file_data_unref(fd);
2464 if (g_hash_table_size(file_data_monitor_pool) == 0)
2466 g_source_remove(realtime_monitor_id);
2467 realtime_monitor_id = 0;
2473 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */