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);
31 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars);
35 *-----------------------------------------------------------------------------
36 * text conversion utils
37 *-----------------------------------------------------------------------------
40 gchar *text_from_size(gint64 size)
46 /* what I would like to use is printf("%'d", size)
47 * BUT: not supported on every libc :(
51 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
52 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
56 a = g_strdup_printf("%d", (guint)size);
62 b = g_new(gchar, l + n + 1);
87 gchar *text_from_size_abrev(gint64 size)
89 if (size < (gint64)1024)
91 return g_strdup_printf(_("%d bytes"), (gint)size);
93 if (size < (gint64)1048576)
95 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
97 if (size < (gint64)1073741824)
99 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
102 /* to avoid overflowing the gdouble, do division in two steps */
104 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
107 /* note: returned string is valid until next call to text_from_time() */
108 const gchar *text_from_time(time_t t)
110 static gchar *ret = NULL;
114 GError *error = NULL;
116 btime = localtime(&t);
118 /* the %x warning about 2 digit years is not an error */
119 buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
120 if (buflen < 1) return "";
123 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
126 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
135 *-----------------------------------------------------------------------------
137 *-----------------------------------------------------------------------------
140 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
141 static void file_data_check_sidecars(FileData *fd, gboolean stat_sidecars);
142 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
145 void file_data_increment_version(FileData *fd)
151 fd->parent->version++;
152 fd->parent->valid_marks = 0;
156 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
158 const FileData *fda = a;
159 const FileData *fdb = b;
161 return strcmp(fdb->extension, fda->extension);
164 static void file_data_basename_hash_insert(FileData *fd)
167 const gchar *ext = extension_from_path(fd->path);
168 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
169 if (!file_data_basename_hash)
170 file_data_basename_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
172 list = g_hash_table_lookup(file_data_basename_hash, basename);
174 if (!g_list_find(list, fd))
176 list = g_list_insert_sorted(list, fd, file_data_sort_by_ext);
177 g_hash_table_insert(file_data_basename_hash, basename, list);
185 static void file_data_basename_hash_remove(FileData *fd)
188 const gchar *ext = extension_from_path(fd->path);
189 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
190 if (!file_data_basename_hash)
191 file_data_basename_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
193 list = g_hash_table_lookup(file_data_basename_hash, basename);
195 list = g_list_remove(list, fd);
199 g_hash_table_insert(file_data_basename_hash, basename, list);
203 g_hash_table_remove(file_data_basename_hash, basename);
208 static void file_data_set_collate_keys(FileData *fd)
210 gchar *caseless_name;
212 caseless_name = g_utf8_casefold(fd->name, -1);
214 g_free(fd->collate_key_name);
215 g_free(fd->collate_key_name_nocase);
217 #if GLIB_CHECK_VERSION(2, 8, 0)
218 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
219 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
221 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
222 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
224 g_free(caseless_name);
227 static void file_data_set_path(FileData *fd, const gchar *path)
229 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
230 g_assert(file_data_pool);
232 if (fd->path) file_data_basename_hash_remove(fd);
236 if (fd->original_path)
238 g_hash_table_remove(file_data_pool, fd->original_path);
239 g_free(fd->original_path);
242 g_assert(!g_hash_table_lookup(file_data_pool, path));
244 fd->original_path = g_strdup(path);
245 g_hash_table_insert(file_data_pool, fd->original_path, fd);
247 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
249 fd->path = g_strdup(path);
251 fd->extension = fd->name + 1;
252 file_data_set_collate_keys(fd);
256 fd->path = g_strdup(path);
257 fd->name = filename_from_path(fd->path);
259 if (strcmp(fd->name, "..") == 0)
261 gchar *dir = remove_level_from_path(path);
263 fd->path = remove_level_from_path(dir);
266 fd->extension = fd->name + 2;
267 file_data_set_collate_keys(fd);
270 else if (strcmp(fd->name, ".") == 0)
273 fd->path = remove_level_from_path(path);
275 fd->extension = fd->name + 1;
276 file_data_set_collate_keys(fd);
280 fd->extension = extension_from_path(fd->path);
281 if (fd->extension == NULL)
283 fd->extension = fd->name + strlen(fd->name);
286 file_data_basename_hash_insert(fd); /* we can ignore the special cases above - they don't have extensions */
288 file_data_set_collate_keys(fd);
291 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
293 gboolean ret = FALSE;
296 if (fd->size != st->st_size ||
297 fd->date != st->st_mtime)
299 fd->size = st->st_size;
300 fd->date = st->st_mtime;
301 fd->mode = st->st_mode;
302 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
303 fd->thumb_pixbuf = NULL;
304 file_data_increment_version(fd);
305 file_data_send_notification(fd, NOTIFY_REREAD);
309 work = fd->sidecar_files;
312 FileData *sfd = work->data;
316 if (!stat_utf8(sfd->path, &st))
320 file_data_disconnect_sidecar_file(fd, sfd);
325 ret |= file_data_check_changed_files_recursive(sfd, &st);
331 gboolean file_data_check_changed_files(FileData *fd)
333 gboolean ret = FALSE;
336 if (fd->parent) fd = fd->parent;
338 if (!stat_utf8(fd->path, &st))
341 FileData *sfd = NULL;
343 /* parent is missing, we have to rebuild whole group */
348 work = fd->sidecar_files;
354 file_data_disconnect_sidecar_file(fd, sfd);
356 if (sfd) file_data_check_sidecars(sfd, FALSE); /* this will group the sidecars back together */
357 file_data_send_notification(fd, NOTIFY_REREAD);
361 ret |= file_data_check_changed_files_recursive(fd, &st);
367 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars)
371 DEBUG_2("file_data_new: '%s' %d %d", path_utf8, check_sidecars, stat_sidecars);
374 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
376 fd = g_hash_table_lookup(file_data_pool, path_utf8);
382 if (!fd && file_data_planned_change_hash)
384 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
387 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
389 file_data_apply_ci(fd);
398 changed = file_data_check_changed_files(fd);
400 changed = file_data_check_changed_files_recursive(fd, st);
401 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
402 file_data_check_sidecars(fd, stat_sidecars);
403 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
408 fd = g_new0(FileData, 1);
410 fd->size = st->st_size;
411 fd->date = st->st_mtime;
412 fd->mode = st->st_mode;
414 fd->magick = 0x12345678;
416 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
419 file_data_check_sidecars(fd, stat_sidecars);
424 /* extension must contain only ASCII characters */
425 static GList *check_case_insensitive_ext(gchar *path)
432 sl = path_from_utf8(path);
434 extl = strrchr(sl, '.');
438 extl++; /* the first char after . */
439 ext_len = strlen(extl);
441 for (i = 0; i < (1 << ext_len); i++)
444 for (j = 0; j < ext_len; j++)
446 if (i & (1 << (ext_len - 1 - j)))
447 extl[j] = g_ascii_tolower(extl[j]);
449 extl[j] = g_ascii_toupper(extl[j]);
451 if (stat(sl, &st) == 0)
453 list = g_list_prepend(list, file_data_new_local(sl, &st, FALSE, FALSE));
462 static void file_data_check_sidecars(FileData *fd, gboolean stat_sidecars)
466 FileData *parent_fd = NULL;
468 const GList *basename_list = NULL;
469 GList *group_list = NULL;
470 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
473 base_len = fd->extension - fd->path;
474 fname = g_string_new_len(fd->path, base_len);
478 basename_list = g_hash_table_lookup(file_data_basename_hash, fname->str);
482 /* check for possible sidecar files;
483 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
484 they have fd->ref set to 0 and file_data unref must chack and free them all together
485 (using fd->ref would cause loops and leaks)
488 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
489 for case-only differences put lowercase first,
490 put the result to group_list
492 work = sidecar_ext_get_list();
495 gchar *ext = work->data;
501 g_string_truncate(fname, base_len);
502 g_string_append(fname, ext);
503 new_list = check_case_insensitive_ext(fname->str);
504 group_list = g_list_concat(group_list, new_list);
508 const GList *work2 = basename_list;
513 FileData *sfd = work2->data;
515 if (g_ascii_strcasecmp(ext, sfd->extension) == 0 &&
516 (sfd == fd || stat_utf8(sfd->path, &nst)))
517 /* basename list can contain deleted files, fd was recently stat'd by caller */
519 group_list = g_list_append(group_list, file_data_ref(sfd));
525 g_string_free(fname, TRUE);
527 /* process the group list - the first one is the parent file, others are sidecars */
531 FileData *new_fd = work->data;
534 if (new_fd->disable_grouping)
536 file_data_unref(new_fd);
540 new_fd->ref--; /* do not use ref here */
543 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
545 file_data_merge_sidecar_files(parent_fd, new_fd);
547 g_list_free(group_list);
551 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars)
553 gchar *path_utf8 = path_to_utf8(path);
554 FileData *ret = file_data_new(path_utf8, st, check_sidecars, stat_sidecars);
560 FileData *file_data_new_simple(const gchar *path_utf8)
564 if (!stat_utf8(path_utf8, &st))
570 return file_data_new(path_utf8, &st, TRUE, TRUE);
573 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
575 sfd->parent = target;
576 if (!g_list_find(target->sidecar_files, sfd))
577 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
578 file_data_increment_version(sfd); /* increments both sfd and target */
583 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
587 file_data_add_sidecar_file(target, source);
589 work = source->sidecar_files;
592 FileData *sfd = work->data;
593 file_data_add_sidecar_file(target, sfd);
597 g_list_free(source->sidecar_files);
598 source->sidecar_files = NULL;
600 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
605 #ifdef DEBUG_FILEDATA
606 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
608 FileData *file_data_ref(FileData *fd)
611 if (fd == NULL) return NULL;
612 #ifdef DEBUG_FILEDATA
613 if (fd->magick != 0x12345678)
614 DEBUG_0("fd magick mismatch at %s:%d", file, line);
616 g_assert(fd->magick == 0x12345678);
619 #ifdef DEBUG_FILEDATA
620 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
622 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
627 static void file_data_free(FileData *fd)
629 g_assert(fd->magick == 0x12345678);
630 g_assert(fd->ref == 0);
632 if (fd->path) file_data_basename_hash_remove(fd);
633 g_hash_table_remove(file_data_pool, fd->original_path);
636 g_free(fd->original_path);
637 g_free(fd->collate_key_name);
638 g_free(fd->collate_key_name_nocase);
639 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
640 histmap_free(fd->histmap);
642 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
644 file_data_change_info_free(NULL, fd);
648 #ifdef DEBUG_FILEDATA
649 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
651 void file_data_unref(FileData *fd)
654 if (fd == NULL) return;
655 #ifdef DEBUG_FILEDATA
656 if (fd->magick != 0x12345678)
657 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
659 g_assert(fd->magick == 0x12345678);
662 #ifdef DEBUG_FILEDATA
663 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
665 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
670 FileData *parent = fd->parent ? fd->parent : fd;
672 if (parent->ref > 0) return;
674 work = parent->sidecar_files;
677 FileData *sfd = work->data;
678 if (sfd->ref > 0) return;
682 /* none of parent/children is referenced, we can free everything */
684 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
686 work = parent->sidecar_files;
689 FileData *sfd = work->data;
694 g_list_free(parent->sidecar_files);
695 parent->sidecar_files = NULL;
697 file_data_free(parent);
701 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
703 sfd->parent = target;
704 g_assert(g_list_find(target->sidecar_files, sfd));
706 file_data_increment_version(sfd); /* increments both sfd and target */
708 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
720 /* disables / enables grouping for particular file, sends UPDATE notification */
721 void file_data_disable_grouping(FileData *fd, gboolean disable)
723 if (!fd->disable_grouping == !disable) return;
725 fd->disable_grouping = !!disable;
731 FileData *parent = file_data_ref(fd->parent);
732 file_data_disconnect_sidecar_file(parent, fd);
733 file_data_send_notification(parent, NOTIFY_GROUPING);
734 file_data_unref(parent);
736 else if (fd->sidecar_files)
738 GList *sidecar_files = filelist_copy(fd->sidecar_files);
739 GList *work = sidecar_files;
742 FileData *sfd = work->data;
744 file_data_disconnect_sidecar_file(fd, sfd);
745 file_data_send_notification(sfd, NOTIFY_GROUPING);
747 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
748 filelist_free(sidecar_files);
752 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
757 file_data_increment_version(fd);
758 file_data_check_sidecars(fd, FALSE);
760 file_data_send_notification(fd, NOTIFY_GROUPING);
763 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
770 FileData *fd = work->data;
772 file_data_disable_grouping(fd, disable);
778 /* compare name without extension */
779 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
781 size_t len1 = fd1->extension - fd1->name;
782 size_t len2 = fd2->extension - fd2->name;
784 if (len1 < len2) return -1;
785 if (len1 > len2) return 1;
787 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
790 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
792 if (!fdci && fd) fdci = fd->change;
796 g_free(fdci->source);
801 if (fd) fd->change = NULL;
804 static gboolean file_data_can_write_directly(FileData *fd)
806 return filter_name_is_writable(fd->extension);
809 static gboolean file_data_can_write_sidecar(FileData *fd)
811 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
814 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
816 gchar *sidecar_path = NULL;
819 if (!file_data_can_write_sidecar(fd)) return NULL;
821 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
824 FileData *sfd = work->data;
826 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
828 sidecar_path = g_strdup(sfd->path);
833 if (!existing_only && !sidecar_path)
835 gchar *base = remove_extension_from_path(fd->path);
836 sidecar_path = g_strconcat(base, ".xmp", NULL);
845 *-----------------------------------------------------------------------------
846 * sidecar file info struct
847 *-----------------------------------------------------------------------------
852 static gint sidecar_file_priority(const gchar *path)
854 const gchar *extension = extension_from_path(path);
858 if (extension == NULL)
861 work = sidecar_ext_get_list();
864 gchar *ext = work->data;
867 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
875 *-----------------------------------------------------------------------------
877 *-----------------------------------------------------------------------------
880 static SortType filelist_sort_method = SORT_NONE;
881 static gboolean filelist_sort_ascend = TRUE;
884 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
886 if (!filelist_sort_ascend)
893 switch (filelist_sort_method)
898 if (fa->size < fb->size) return -1;
899 if (fa->size > fb->size) return 1;
900 /* fall back to name */
903 if (fa->date < fb->date) return -1;
904 if (fa->date > fb->date) return 1;
905 /* fall back to name */
907 #ifdef HAVE_STRVERSCMP
909 return strverscmp(fa->name, fb->name);
916 if (options->file_sort.case_sensitive)
917 return strcmp(fa->collate_key_name, fb->collate_key_name);
919 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
922 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
924 filelist_sort_method = method;
925 filelist_sort_ascend = ascend;
926 return filelist_sort_compare_filedata(fa, fb);
929 static gint filelist_sort_file_cb(gpointer a, gpointer b)
931 return filelist_sort_compare_filedata(a, b);
934 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
936 filelist_sort_method = method;
937 filelist_sort_ascend = ascend;
938 return g_list_sort(list, cb);
941 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
943 filelist_sort_method = method;
944 filelist_sort_ascend = ascend;
945 return g_list_insert_sorted(list, data, cb);
948 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
950 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
953 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
955 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
959 static GList *filelist_filter_out_sidecars(GList *flist)
962 GList *flist_filtered = NULL;
966 FileData *fd = work->data;
969 if (fd->parent) /* remove fd's that are children */
972 flist_filtered = g_list_prepend(flist_filtered, fd);
976 return flist_filtered;
979 static gboolean is_hidden_file(const gchar *name)
981 if (name[0] != '.') return FALSE;
982 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
986 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
993 gint (*stat_func)(const gchar *path, struct stat *buf);
995 g_assert(files || dirs);
997 if (files) *files = NULL;
998 if (dirs) *dirs = NULL;
1000 pathl = path_from_utf8(dir_fd->path);
1001 if (!pathl) return FALSE;
1003 dp = opendir(pathl);
1010 if (follow_symlinks)
1015 while ((dir = readdir(dp)) != NULL)
1017 struct stat ent_sbuf;
1018 const gchar *name = dir->d_name;
1021 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1024 filepath = g_build_filename(pathl, name, NULL);
1025 if (stat_func(filepath, &ent_sbuf) >= 0)
1027 if (S_ISDIR(ent_sbuf.st_mode))
1029 /* we ignore the .thumbnails dir for cleanliness */
1031 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1032 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1033 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1034 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1036 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, FALSE));
1041 if (files && filter_name_exists(name))
1043 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, FALSE));
1054 if (dirs) *dirs = dlist;
1055 if (files) *files = filelist_filter_out_sidecars(flist);
1060 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1062 return filelist_read_real(dir_fd, files, dirs, TRUE);
1065 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1067 return filelist_read_real(dir_fd, files, dirs, FALSE);
1070 void filelist_free(GList *list)
1077 file_data_unref((FileData *)work->data);
1085 GList *filelist_copy(GList *list)
1087 GList *new_list = NULL;
1098 new_list = g_list_prepend(new_list, file_data_ref(fd));
1101 return g_list_reverse(new_list);
1104 GList *filelist_from_path_list(GList *list)
1106 GList *new_list = NULL;
1117 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1120 return g_list_reverse(new_list);
1123 GList *filelist_to_path_list(GList *list)
1125 GList *new_list = NULL;
1136 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1139 return g_list_reverse(new_list);
1142 GList *filelist_filter(GList *list, gboolean is_dir_list)
1146 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1151 FileData *fd = (FileData *)(work->data);
1152 const gchar *name = fd->name;
1154 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1155 (!is_dir_list && !filter_name_exists(name)) ||
1156 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1157 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1161 list = g_list_remove_link(list, link);
1162 file_data_unref(fd);
1173 *-----------------------------------------------------------------------------
1174 * filelist recursive
1175 *-----------------------------------------------------------------------------
1178 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1180 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1183 GList *filelist_sort_path(GList *list)
1185 return g_list_sort(list, filelist_sort_path_cb);
1188 static void filelist_recursive_append(GList **list, GList *dirs)
1195 FileData *fd = (FileData *)(work->data);
1199 if (filelist_read(fd, &f, &d))
1201 f = filelist_filter(f, FALSE);
1202 f = filelist_sort_path(f);
1203 *list = g_list_concat(*list, f);
1205 d = filelist_filter(d, TRUE);
1206 d = filelist_sort_path(d);
1207 filelist_recursive_append(list, d);
1215 GList *filelist_recursive(FileData *dir_fd)
1220 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1221 list = filelist_filter(list, FALSE);
1222 list = filelist_sort_path(list);
1224 d = filelist_filter(d, TRUE);
1225 d = filelist_sort_path(d);
1226 filelist_recursive_append(&list, d);
1234 * marks and orientation
1237 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1238 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1239 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1240 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1242 gboolean file_data_get_mark(FileData *fd, gint n)
1244 gboolean valid = (fd->valid_marks & (1 << n));
1246 if (file_data_get_mark_func[n] && !valid)
1248 guint old = fd->marks;
1249 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1251 if (!value != !(fd->marks & (1 << n)))
1253 fd->marks = fd->marks ^ (1 << n);
1256 fd->valid_marks |= (1 << n);
1257 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1259 file_data_unref(fd);
1261 else if (!old && fd->marks)
1267 return !!(fd->marks & (1 << n));
1270 guint file_data_get_marks(FileData *fd)
1273 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1277 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1280 if (!value == !file_data_get_mark(fd, n)) return;
1282 if (file_data_set_mark_func[n])
1284 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1289 fd->marks = fd->marks ^ (1 << n);
1291 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1293 file_data_unref(fd);
1295 else if (!old && fd->marks)
1300 file_data_increment_version(fd);
1301 file_data_send_notification(fd, NOTIFY_MARKS);
1304 gboolean file_data_filter_marks(FileData *fd, guint filter)
1307 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1308 return ((fd->marks & filter) == filter);
1311 GList *file_data_filter_marks_list(GList *list, guint filter)
1318 FileData *fd = work->data;
1322 if (!file_data_filter_marks(fd, filter))
1324 list = g_list_remove_link(list, link);
1325 file_data_unref(fd);
1333 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1335 FileData *fd = value;
1336 file_data_increment_version(fd);
1337 file_data_send_notification(fd, NOTIFY_MARKS);
1340 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1342 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1344 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1346 file_data_get_mark_func[n] = get_mark_func;
1347 file_data_set_mark_func[n] = set_mark_func;
1348 file_data_mark_func_data[n] = data;
1349 file_data_destroy_mark_func[n] = notify;
1353 /* this effectively changes all known files */
1354 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1360 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1362 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1363 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1364 if (data) *data = file_data_mark_func_data[n];
1367 gint file_data_get_user_orientation(FileData *fd)
1369 return fd->user_orientation;
1372 void file_data_set_user_orientation(FileData *fd, gint value)
1374 if (fd->user_orientation == value) return;
1376 fd->user_orientation = value;
1377 file_data_increment_version(fd);
1378 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1383 * file_data - operates on the given fd
1384 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1388 /* return list of sidecar file extensions in a string */
1389 gchar *file_data_sc_list_to_string(FileData *fd)
1392 GString *result = g_string_new("");
1394 work = fd->sidecar_files;
1397 FileData *sfd = work->data;
1399 result = g_string_append(result, "+ ");
1400 result = g_string_append(result, sfd->extension);
1402 if (work) result = g_string_append_c(result, ' ');
1405 return g_string_free(result, FALSE);
1411 * add FileDataChangeInfo (see typedefs.h) for the given operation
1412 * uses file_data_add_change_info
1414 * fails if the fd->change already exists - change operations can't run in parallel
1415 * fd->change_info works as a lock
1417 * dest can be NULL - in this case the current name is used for now, it will
1422 FileDataChangeInfo types:
1424 MOVE - path is changed, name may be changed too
1425 RENAME - path remains unchanged, name is changed
1426 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1427 sidecar names are changed too, extensions are not changed
1429 UPDATE - file size, date or grouping has been changed
1432 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1434 FileDataChangeInfo *fdci;
1436 if (fd->change) return FALSE;
1438 fdci = g_new0(FileDataChangeInfo, 1);
1443 fdci->source = g_strdup(src);
1445 fdci->source = g_strdup(fd->path);
1448 fdci->dest = g_strdup(dest);
1455 static void file_data_planned_change_remove(FileData *fd)
1457 if (file_data_planned_change_hash &&
1458 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1460 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1462 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1463 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1464 file_data_unref(fd);
1465 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1467 g_hash_table_destroy(file_data_planned_change_hash);
1468 file_data_planned_change_hash = NULL;
1469 DEBUG_1("planned change: empty");
1476 void file_data_free_ci(FileData *fd)
1478 FileDataChangeInfo *fdci = fd->change;
1482 file_data_planned_change_remove(fd);
1484 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1486 g_free(fdci->source);
1494 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1496 FileDataChangeInfo *fdci = fd->change;
1498 fdci->regroup_when_finished = enable;
1501 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1505 if (fd->parent) fd = fd->parent;
1507 if (fd->change) return FALSE;
1509 work = fd->sidecar_files;
1512 FileData *sfd = work->data;
1514 if (sfd->change) return FALSE;
1518 file_data_add_ci(fd, type, NULL, NULL);
1520 work = fd->sidecar_files;
1523 FileData *sfd = work->data;
1525 file_data_add_ci(sfd, type, NULL, NULL);
1532 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1536 if (fd->parent) fd = fd->parent;
1538 if (!fd->change || fd->change->type != type) return FALSE;
1540 work = fd->sidecar_files;
1543 FileData *sfd = work->data;
1545 if (!sfd->change || sfd->change->type != type) return FALSE;
1553 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1555 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1556 file_data_sc_update_ci_copy(fd, dest_path);
1560 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1562 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1563 file_data_sc_update_ci_move(fd, dest_path);
1567 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1569 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1570 file_data_sc_update_ci_rename(fd, dest_path);
1574 gboolean file_data_sc_add_ci_delete(FileData *fd)
1576 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1579 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1581 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1582 file_data_sc_update_ci_unspecified(fd, dest_path);
1586 gboolean file_data_add_ci_write_metadata(FileData *fd)
1588 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1591 void file_data_sc_free_ci(FileData *fd)
1595 if (fd->parent) fd = fd->parent;
1597 file_data_free_ci(fd);
1599 work = fd->sidecar_files;
1602 FileData *sfd = work->data;
1604 file_data_free_ci(sfd);
1609 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1612 gboolean ret = TRUE;
1617 FileData *fd = work->data;
1619 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1626 static void file_data_sc_revert_ci_list(GList *fd_list)
1633 FileData *fd = work->data;
1635 file_data_sc_free_ci(fd);
1640 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1647 FileData *fd = work->data;
1649 if (!func(fd, dest))
1651 file_data_sc_revert_ci_list(work->prev);
1660 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1662 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1665 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1667 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1670 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1672 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1675 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1677 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1680 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1683 gboolean ret = TRUE;
1688 FileData *fd = work->data;
1690 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1697 void file_data_free_ci_list(GList *fd_list)
1704 FileData *fd = work->data;
1706 file_data_free_ci(fd);
1711 void file_data_sc_free_ci_list(GList *fd_list)
1718 FileData *fd = work->data;
1720 file_data_sc_free_ci(fd);
1726 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1727 * fails if fd->change does not exist or the change type does not match
1730 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1732 FileDataChangeType type = fd->change->type;
1734 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1738 if (!file_data_planned_change_hash)
1739 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1741 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1743 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1744 g_hash_table_remove(file_data_planned_change_hash, old_path);
1745 file_data_unref(fd);
1748 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1753 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1754 g_hash_table_remove(file_data_planned_change_hash, new_path);
1755 file_data_unref(ofd);
1758 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1760 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1765 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1767 gchar *old_path = fd->change->dest;
1769 fd->change->dest = g_strdup(dest_path);
1770 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1774 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1776 const gchar *extension = extension_from_path(fd->change->source);
1777 gchar *base = remove_extension_from_path(dest_path);
1778 gchar *old_path = fd->change->dest;
1780 fd->change->dest = g_strconcat(base, extension, NULL);
1781 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1787 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1790 gchar *dest_path_full = NULL;
1792 if (fd->parent) fd = fd->parent;
1796 dest_path = fd->path;
1798 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1800 gchar *dir = remove_level_from_path(fd->path);
1802 dest_path_full = g_build_filename(dir, dest_path, NULL);
1804 dest_path = dest_path_full;
1806 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1808 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1809 dest_path = dest_path_full;
1812 file_data_update_ci_dest(fd, dest_path);
1814 work = fd->sidecar_files;
1817 FileData *sfd = work->data;
1819 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1823 g_free(dest_path_full);
1826 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1828 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1829 file_data_sc_update_ci(fd, dest_path);
1833 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1835 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1838 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1840 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1843 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1845 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1848 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1850 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1853 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1855 gboolean (*func)(FileData *, const gchar *))
1858 gboolean ret = TRUE;
1863 FileData *fd = work->data;
1865 if (!func(fd, dest)) ret = FALSE;
1872 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1874 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1877 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1879 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1882 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1884 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1889 * verify source and dest paths - dest image exists, etc.
1890 * it should detect all possible problems with the planned operation
1893 gint file_data_verify_ci(FileData *fd)
1895 gint ret = CHANGE_OK;
1900 DEBUG_1("Change checked: no change info: %s", fd->path);
1904 if (!isname(fd->path))
1906 /* this probably should not happen */
1907 ret |= CHANGE_NO_SRC;
1908 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1912 dir = remove_level_from_path(fd->path);
1914 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1915 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1916 fd->change->type != FILEDATA_CHANGE_RENAME &&
1917 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1920 ret |= CHANGE_WARN_UNSAVED_META;
1921 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1924 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1925 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1926 !access_file(fd->path, R_OK))
1928 ret |= CHANGE_NO_READ_PERM;
1929 DEBUG_1("Change checked: no read permission: %s", fd->path);
1931 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1932 !access_file(dir, W_OK))
1934 ret |= CHANGE_NO_WRITE_PERM_DIR;
1935 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1937 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1938 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1939 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1940 !access_file(fd->path, W_OK))
1942 ret |= CHANGE_WARN_NO_WRITE_PERM;
1943 DEBUG_1("Change checked: no write permission: %s", fd->path);
1945 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1946 - that means that there are no hard errors and warnings can be disabled
1947 - the destination is determined during the check
1949 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1951 /* determine destination file */
1952 gboolean have_dest = FALSE;
1953 gchar *dest_dir = NULL;
1955 if (options->metadata.save_in_image_file)
1957 if (file_data_can_write_directly(fd))
1959 /* we can write the file directly */
1960 if (access_file(fd->path, W_OK))
1966 if (options->metadata.warn_on_write_problems)
1968 ret |= CHANGE_WARN_NO_WRITE_PERM;
1969 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1973 else if (file_data_can_write_sidecar(fd))
1975 /* we can write sidecar */
1976 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1977 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1979 file_data_update_ci_dest(fd, sidecar);
1984 if (options->metadata.warn_on_write_problems)
1986 ret |= CHANGE_WARN_NO_WRITE_PERM;
1987 DEBUG_1("Change checked: file is not writable: %s", sidecar);
1996 /* write private metadata file under ~/.geeqie */
1998 /* If an existing metadata file exists, we will try writing to
1999 * it's location regardless of the user's preference.
2001 gchar *metadata_path = NULL;
2003 /* but ignore XMP if we are not able to write it */
2004 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2006 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2008 if (metadata_path && !access_file(metadata_path, W_OK))
2010 g_free(metadata_path);
2011 metadata_path = NULL;
2018 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2019 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2021 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2023 metadata_path = g_build_filename(dest_dir, filename, NULL);
2027 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2029 file_data_update_ci_dest(fd, metadata_path);
2034 ret |= CHANGE_NO_WRITE_PERM_DEST;
2035 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2037 g_free(metadata_path);
2042 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2047 same = (strcmp(fd->path, fd->change->dest) == 0);
2051 const gchar *dest_ext = extension_from_path(fd->change->dest);
2052 if (!dest_ext) dest_ext = "";
2054 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2056 ret |= CHANGE_WARN_CHANGED_EXT;
2057 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2062 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2064 ret |= CHANGE_WARN_SAME;
2065 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2069 dest_dir = remove_level_from_path(fd->change->dest);
2071 if (!isdir(dest_dir))
2073 ret |= CHANGE_NO_DEST_DIR;
2074 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2076 else if (!access_file(dest_dir, W_OK))
2078 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2079 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2083 if (isfile(fd->change->dest))
2085 if (!access_file(fd->change->dest, W_OK))
2087 ret |= CHANGE_NO_WRITE_PERM_DEST;
2088 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2092 ret |= CHANGE_WARN_DEST_EXISTS;
2093 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2096 else if (isdir(fd->change->dest))
2098 ret |= CHANGE_DEST_EXISTS;
2099 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2106 fd->change->error = ret;
2107 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2114 gint file_data_sc_verify_ci(FileData *fd)
2119 ret = file_data_verify_ci(fd);
2121 work = fd->sidecar_files;
2124 FileData *sfd = work->data;
2126 ret |= file_data_verify_ci(sfd);
2133 gchar *file_data_get_error_string(gint error)
2135 GString *result = g_string_new("");
2137 if (error & CHANGE_NO_SRC)
2139 if (result->len > 0) g_string_append(result, ", ");
2140 g_string_append(result, _("file or directory does not exist"));
2143 if (error & CHANGE_DEST_EXISTS)
2145 if (result->len > 0) g_string_append(result, ", ");
2146 g_string_append(result, _("destination already exists"));
2149 if (error & CHANGE_NO_WRITE_PERM_DEST)
2151 if (result->len > 0) g_string_append(result, ", ");
2152 g_string_append(result, _("destination can't be overwritten"));
2155 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2157 if (result->len > 0) g_string_append(result, ", ");
2158 g_string_append(result, _("destination directory is not writable"));
2161 if (error & CHANGE_NO_DEST_DIR)
2163 if (result->len > 0) g_string_append(result, ", ");
2164 g_string_append(result, _("destination directory does not exist"));
2167 if (error & CHANGE_NO_WRITE_PERM_DIR)
2169 if (result->len > 0) g_string_append(result, ", ");
2170 g_string_append(result, _("source directory is not writable"));
2173 if (error & CHANGE_NO_READ_PERM)
2175 if (result->len > 0) g_string_append(result, ", ");
2176 g_string_append(result, _("no read permission"));
2179 if (error & CHANGE_WARN_NO_WRITE_PERM)
2181 if (result->len > 0) g_string_append(result, ", ");
2182 g_string_append(result, _("file is readonly"));
2185 if (error & CHANGE_WARN_DEST_EXISTS)
2187 if (result->len > 0) g_string_append(result, ", ");
2188 g_string_append(result, _("destination already exists and will be overwritten"));
2191 if (error & CHANGE_WARN_SAME)
2193 if (result->len > 0) g_string_append(result, ", ");
2194 g_string_append(result, _("source and destination are the same"));
2197 if (error & CHANGE_WARN_CHANGED_EXT)
2199 if (result->len > 0) g_string_append(result, ", ");
2200 g_string_append(result, _("source and destination have different extension"));
2203 if (error & CHANGE_WARN_UNSAVED_META)
2205 if (result->len > 0) g_string_append(result, ", ");
2206 g_string_append(result, _("there are unsaved metadata changes for the file"));
2209 return g_string_free(result, FALSE);
2212 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2215 gint all_errors = 0;
2216 gint common_errors = ~0;
2221 if (!list) return 0;
2223 num = g_list_length(list);
2224 errors = g_new(int, num);
2235 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2236 all_errors |= error;
2237 common_errors &= error;
2244 if (desc && all_errors)
2247 GString *result = g_string_new("");
2251 gchar *str = file_data_get_error_string(common_errors);
2252 g_string_append(result, str);
2253 g_string_append(result, "\n");
2267 error = errors[i] & ~common_errors;
2271 gchar *str = file_data_get_error_string(error);
2272 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2277 *desc = g_string_free(result, FALSE);
2286 * perform the change described by FileFataChangeInfo
2287 * it is used for internal operations,
2288 * this function actually operates with files on the filesystem
2289 * it should implement safe delete
2292 static gboolean file_data_perform_move(FileData *fd)
2294 g_assert(!strcmp(fd->change->source, fd->path));
2295 return move_file(fd->change->source, fd->change->dest);
2298 static gboolean file_data_perform_copy(FileData *fd)
2300 g_assert(!strcmp(fd->change->source, fd->path));
2301 return copy_file(fd->change->source, fd->change->dest);
2304 static gboolean file_data_perform_delete(FileData *fd)
2306 if (isdir(fd->path) && !islink(fd->path))
2307 return rmdir_utf8(fd->path);
2309 if (options->file_ops.safe_delete_enable)
2310 return file_util_safe_unlink(fd->path);
2312 return unlink_file(fd->path);
2315 gboolean file_data_perform_ci(FileData *fd)
2317 FileDataChangeType type = fd->change->type;
2321 case FILEDATA_CHANGE_MOVE:
2322 return file_data_perform_move(fd);
2323 case FILEDATA_CHANGE_COPY:
2324 return file_data_perform_copy(fd);
2325 case FILEDATA_CHANGE_RENAME:
2326 return file_data_perform_move(fd); /* the same as move */
2327 case FILEDATA_CHANGE_DELETE:
2328 return file_data_perform_delete(fd);
2329 case FILEDATA_CHANGE_WRITE_METADATA:
2330 return metadata_write_perform(fd);
2331 case FILEDATA_CHANGE_UNSPECIFIED:
2332 /* nothing to do here */
2340 gboolean file_data_sc_perform_ci(FileData *fd)
2343 gboolean ret = TRUE;
2344 FileDataChangeType type = fd->change->type;
2346 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2348 work = fd->sidecar_files;
2351 FileData *sfd = work->data;
2353 if (!file_data_perform_ci(sfd)) ret = FALSE;
2357 if (!file_data_perform_ci(fd)) ret = FALSE;
2363 * updates FileData structure according to FileDataChangeInfo
2366 gboolean file_data_apply_ci(FileData *fd)
2368 FileDataChangeType type = fd->change->type;
2371 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2373 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2374 file_data_planned_change_remove(fd);
2376 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2378 /* this change overwrites another file which is already known to other modules
2379 renaming fd would create duplicate FileData structure
2380 the best thing we can do is nothing
2381 FIXME: maybe we could copy stuff like marks
2383 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2387 file_data_set_path(fd, fd->change->dest);
2390 file_data_increment_version(fd);
2391 file_data_send_notification(fd, NOTIFY_CHANGE);
2396 gboolean file_data_sc_apply_ci(FileData *fd)
2399 FileDataChangeType type = fd->change->type;
2401 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2403 work = fd->sidecar_files;
2406 FileData *sfd = work->data;
2408 file_data_apply_ci(sfd);
2412 file_data_apply_ci(fd);
2417 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2420 if (fd->parent) fd = fd->parent;
2421 if (!g_list_find(list, fd)) return FALSE;
2423 work = fd->sidecar_files;
2426 if (!g_list_find(list, work->data)) return FALSE;
2433 static gboolean file_data_list_dump(GList *list)
2435 GList *work, *work2;
2440 FileData *fd = work->data;
2441 printf("%s\n", fd->name);
2442 work2 = fd->sidecar_files;
2445 FileData *fd = work2->data;
2446 printf(" %s\n", fd->name);
2447 work2 = work2->next;
2455 GList *file_data_process_groups_in_selection(GList *list, GList **ungrouped_list)
2460 /* change partial groups to independent files */
2463 FileData *fd = work->data;
2466 if (!file_data_list_contains_whole_group(list, fd))
2468 file_data_disable_grouping(fd, TRUE);
2471 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2476 /* remove sidecars from the list,
2477 they can be still acessed via main_fd->sidecar_files */
2481 FileData *fd = work->data;
2486 out = g_list_prepend(out, fd);
2490 file_data_unref(fd);
2495 out = g_list_reverse(out);
2505 * notify other modules about the change described by FileDataChangeInfo
2508 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2509 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2510 implementation in view_file_list.c */
2515 typedef struct _NotifyData NotifyData;
2517 struct _NotifyData {
2518 FileDataNotifyFunc func;
2520 NotifyPriority priority;
2523 static GList *notify_func_list = NULL;
2525 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2527 NotifyData *nda = (NotifyData *)a;
2528 NotifyData *ndb = (NotifyData *)b;
2530 if (nda->priority < ndb->priority) return -1;
2531 if (nda->priority > ndb->priority) return 1;
2535 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2539 nd = g_new(NotifyData, 1);
2542 nd->priority = priority;
2544 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2545 DEBUG_2("Notify func registered: %p", nd);
2550 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2552 GList *work = notify_func_list;
2556 NotifyData *nd = (NotifyData *)work->data;
2558 if (nd->func == func && nd->data == data)
2560 notify_func_list = g_list_delete_link(notify_func_list, work);
2562 DEBUG_2("Notify func unregistered: %p", nd);
2572 void file_data_send_notification(FileData *fd, NotifyType type)
2574 GList *work = notify_func_list;
2578 NotifyData *nd = (NotifyData *)work->data;
2580 nd->func(fd, type, nd->data);
2585 static GHashTable *file_data_monitor_pool = NULL;
2586 static guint realtime_monitor_id = 0; /* event source id */
2588 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2592 file_data_check_changed_files(fd);
2594 DEBUG_1("monitor %s", fd->path);
2597 static gboolean realtime_monitor_cb(gpointer data)
2599 if (!options->update_on_time_change) return TRUE;
2600 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2604 gboolean file_data_register_real_time_monitor(FileData *fd)
2610 if (!file_data_monitor_pool)
2611 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2613 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2615 DEBUG_1("Register realtime %d %s", count, fd->path);
2618 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2620 if (!realtime_monitor_id)
2622 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2628 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2632 g_assert(file_data_monitor_pool);
2634 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2636 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2638 g_assert(count > 0);
2643 g_hash_table_remove(file_data_monitor_pool, fd);
2645 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2647 file_data_unref(fd);
2649 if (g_hash_table_size(file_data_monitor_pool) == 0)
2651 g_source_remove(realtime_monitor_id);
2652 realtime_monitor_id = 0;
2658 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */