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;
29 static gint sidecar_file_priority(const gchar *path);
30 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, GHashTable *basename_hash);
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, GHashTable *basename_hash);
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 gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
157 const FileData *fda = a;
158 const FileData *fdb = b;
160 return strcmp(fdb->extension, fda->extension);
163 static GHashTable *file_data_basename_hash_new(void)
165 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
168 static void file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
171 const gchar *ext = extension_from_path(fd->path);
172 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
174 list = g_hash_table_lookup(basename_hash, basename);
176 if (!g_list_find(list, fd))
178 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
179 g_hash_table_insert(basename_hash, basename, list);
187 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
190 const gchar *ext = extension_from_path(fd->path);
191 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
193 list = g_hash_table_lookup(basename_hash, basename);
195 if (!g_list_find(list, fd)) return;
197 list = g_list_remove(list, fd);
202 g_hash_table_insert(basename_hash, basename, list);
206 g_hash_table_remove(basename_hash, basename);
211 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
213 filelist_free((GList *)value);
216 static void file_data_basename_hash_free(GHashTable *basename_hash)
218 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
219 g_hash_table_destroy(basename_hash);
222 static void file_data_set_collate_keys(FileData *fd)
224 gchar *caseless_name;
226 caseless_name = g_utf8_casefold(fd->name, -1);
228 g_free(fd->collate_key_name);
229 g_free(fd->collate_key_name_nocase);
231 #if GLIB_CHECK_VERSION(2, 8, 0)
232 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
233 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
235 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
236 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
238 g_free(caseless_name);
241 static void file_data_set_path(FileData *fd, const gchar *path, GHashTable *basename_hash)
243 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
244 g_assert(file_data_pool);
246 if (basename_hash && fd->path) file_data_basename_hash_remove(basename_hash, fd);
250 if (fd->original_path)
252 g_hash_table_remove(file_data_pool, fd->original_path);
253 g_free(fd->original_path);
256 g_assert(!g_hash_table_lookup(file_data_pool, path));
258 fd->original_path = g_strdup(path);
259 g_hash_table_insert(file_data_pool, fd->original_path, fd);
261 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
263 fd->path = g_strdup(path);
265 fd->extension = fd->name + 1;
266 file_data_set_collate_keys(fd);
270 fd->path = g_strdup(path);
271 fd->name = filename_from_path(fd->path);
273 if (strcmp(fd->name, "..") == 0)
275 gchar *dir = remove_level_from_path(path);
277 fd->path = remove_level_from_path(dir);
280 fd->extension = fd->name + 2;
281 file_data_set_collate_keys(fd);
284 else if (strcmp(fd->name, ".") == 0)
287 fd->path = remove_level_from_path(path);
289 fd->extension = fd->name + 1;
290 file_data_set_collate_keys(fd);
294 fd->extension = extension_from_path(fd->path);
295 if (fd->extension == NULL)
297 fd->extension = fd->name + strlen(fd->name);
300 if (basename_hash) file_data_basename_hash_insert(basename_hash, fd); /* we can ignore the special cases above - they don't have extensions */
302 file_data_set_collate_keys(fd);
305 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
307 gboolean ret = FALSE;
310 if (fd->size != st->st_size ||
311 fd->date != st->st_mtime)
313 fd->size = st->st_size;
314 fd->date = st->st_mtime;
315 fd->mode = st->st_mode;
316 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
317 fd->thumb_pixbuf = NULL;
318 file_data_increment_version(fd);
319 file_data_send_notification(fd, NOTIFY_REREAD);
323 work = fd->sidecar_files;
326 FileData *sfd = work->data;
330 if (!stat_utf8(sfd->path, &st))
334 file_data_disconnect_sidecar_file(fd, sfd);
339 ret |= file_data_check_changed_files_recursive(sfd, &st);
345 gboolean file_data_check_changed_files(FileData *fd)
347 gboolean ret = FALSE;
350 if (fd->parent) fd = fd->parent;
352 if (!stat_utf8(fd->path, &st))
355 FileData *sfd = NULL;
357 /* parent is missing, we have to rebuild whole group */
362 work = fd->sidecar_files;
368 file_data_disconnect_sidecar_file(fd, sfd);
370 if (sfd) file_data_check_sidecars(sfd, NULL); /* this will group the sidecars back together */
371 file_data_send_notification(fd, NOTIFY_REREAD);
375 ret |= file_data_check_changed_files_recursive(fd, &st);
381 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars, GHashTable *basename_hash)
385 DEBUG_2("file_data_new: '%s' %d %d", path_utf8, check_sidecars, !!basename_hash);
388 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
390 fd = g_hash_table_lookup(file_data_pool, path_utf8);
396 if (!fd && file_data_planned_change_hash)
398 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
401 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
403 file_data_apply_ci(fd);
410 if (basename_hash) file_data_basename_hash_insert(basename_hash, fd);
413 changed = file_data_check_changed_files(fd);
415 changed = file_data_check_changed_files_recursive(fd, st);
416 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
417 file_data_check_sidecars(fd, basename_hash);
418 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
423 fd = g_new0(FileData, 1);
425 fd->size = st->st_size;
426 fd->date = st->st_mtime;
427 fd->mode = st->st_mode;
429 fd->magick = 0x12345678;
431 file_data_set_path(fd, path_utf8, basename_hash); /* set path, name, collate_key_*, original_path */
434 file_data_check_sidecars(fd, basename_hash);
439 /* extension must contain only ASCII characters */
440 static GList *check_case_insensitive_ext(gchar *path)
447 sl = path_from_utf8(path);
449 extl = strrchr(sl, '.');
453 extl++; /* the first char after . */
454 ext_len = strlen(extl);
456 for (i = 0; i < (1 << ext_len); i++)
459 gboolean skip = FALSE;
460 for (j = 0; j < ext_len; j++)
462 if (i & (1 << (ext_len - 1 - j)))
464 extl[j] = g_ascii_tolower(extl[j]);
465 /* make sure the result does not contain duplicates */
466 if (extl[j] == g_ascii_toupper(extl[j]))
468 /* no change, probably a number, we have already tested this combination */
474 extl[j] = g_ascii_toupper(extl[j]);
478 if (stat(sl, &st) == 0)
480 list = g_list_prepend(list, file_data_new_local(sl, &st, FALSE, FALSE));
489 static void file_data_check_sidecars(FileData *fd, GHashTable *basename_hash)
493 FileData *parent_fd = NULL;
495 const GList *basename_list = NULL;
496 GList *group_list = NULL;
497 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
500 base_len = fd->extension - fd->path;
501 fname = g_string_new_len(fd->path, base_len);
505 basename_list = g_hash_table_lookup(basename_hash, fname->str);
509 /* check for possible sidecar files;
510 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
511 they have fd->ref set to 0 and file_data unref must chack and free them all together
512 (using fd->ref would cause loops and leaks)
515 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
516 for case-only differences put lowercase first,
517 put the result to group_list
519 work = sidecar_ext_get_list();
522 gchar *ext = work->data;
528 g_string_truncate(fname, base_len);
529 g_string_append(fname, ext);
530 new_list = check_case_insensitive_ext(fname->str);
531 group_list = g_list_concat(group_list, new_list);
535 const GList *work2 = basename_list;
539 FileData *sfd = work2->data;
541 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
543 group_list = g_list_append(group_list, file_data_ref(sfd));
549 g_string_free(fname, TRUE);
551 /* process the group list - the first one is the parent file, others are sidecars */
555 FileData *new_fd = work->data;
558 if (new_fd->disable_grouping)
560 file_data_unref(new_fd);
564 new_fd->ref--; /* do not use ref here */
567 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
569 file_data_merge_sidecar_files(parent_fd, new_fd);
571 g_list_free(group_list);
575 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, GHashTable *basename_hash)
577 gchar *path_utf8 = path_to_utf8(path);
578 FileData *ret = file_data_new(path_utf8, st, check_sidecars, basename_hash);
584 FileData *file_data_new_simple(const gchar *path_utf8)
588 if (!stat_utf8(path_utf8, &st))
594 return file_data_new(path_utf8, &st, TRUE, NULL);
597 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
599 sfd->parent = target;
600 if (!g_list_find(target->sidecar_files, sfd))
601 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
602 file_data_increment_version(sfd); /* increments both sfd and target */
607 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
611 file_data_add_sidecar_file(target, source);
613 work = source->sidecar_files;
616 FileData *sfd = work->data;
617 file_data_add_sidecar_file(target, sfd);
621 g_list_free(source->sidecar_files);
622 source->sidecar_files = NULL;
624 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
629 #ifdef DEBUG_FILEDATA
630 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
632 FileData *file_data_ref(FileData *fd)
635 if (fd == NULL) return NULL;
636 #ifdef DEBUG_FILEDATA
637 if (fd->magick != 0x12345678)
638 DEBUG_0("fd magick mismatch at %s:%d", file, line);
640 g_assert(fd->magick == 0x12345678);
643 #ifdef DEBUG_FILEDATA
644 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
646 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
651 static void file_data_free(FileData *fd)
653 g_assert(fd->magick == 0x12345678);
654 g_assert(fd->ref == 0);
656 g_hash_table_remove(file_data_pool, fd->original_path);
659 g_free(fd->original_path);
660 g_free(fd->collate_key_name);
661 g_free(fd->collate_key_name_nocase);
662 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
663 histmap_free(fd->histmap);
665 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
667 file_data_change_info_free(NULL, fd);
671 #ifdef DEBUG_FILEDATA
672 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
674 void file_data_unref(FileData *fd)
677 if (fd == NULL) return;
678 #ifdef DEBUG_FILEDATA
679 if (fd->magick != 0x12345678)
680 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
682 g_assert(fd->magick == 0x12345678);
685 #ifdef DEBUG_FILEDATA
686 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
688 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
693 FileData *parent = fd->parent ? fd->parent : fd;
695 if (parent->ref > 0) return;
697 work = parent->sidecar_files;
700 FileData *sfd = work->data;
701 if (sfd->ref > 0) return;
705 /* none of parent/children is referenced, we can free everything */
707 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
709 work = parent->sidecar_files;
712 FileData *sfd = work->data;
717 g_list_free(parent->sidecar_files);
718 parent->sidecar_files = NULL;
720 file_data_free(parent);
724 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
726 sfd->parent = target;
727 g_assert(g_list_find(target->sidecar_files, sfd));
729 file_data_increment_version(sfd); /* increments both sfd and target */
731 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
743 /* disables / enables grouping for particular file, sends UPDATE notification */
744 void file_data_disable_grouping(FileData *fd, gboolean disable)
746 if (!fd->disable_grouping == !disable) return;
748 fd->disable_grouping = !!disable;
754 FileData *parent = file_data_ref(fd->parent);
755 file_data_disconnect_sidecar_file(parent, fd);
756 file_data_send_notification(parent, NOTIFY_GROUPING);
757 file_data_unref(parent);
759 else if (fd->sidecar_files)
761 GList *sidecar_files = filelist_copy(fd->sidecar_files);
762 GList *work = sidecar_files;
765 FileData *sfd = work->data;
767 file_data_disconnect_sidecar_file(fd, sfd);
768 file_data_send_notification(sfd, NOTIFY_GROUPING);
770 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
771 filelist_free(sidecar_files);
775 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
780 file_data_increment_version(fd);
781 file_data_check_sidecars(fd, FALSE);
783 file_data_send_notification(fd, NOTIFY_GROUPING);
786 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
793 FileData *fd = work->data;
795 file_data_disable_grouping(fd, disable);
801 /* compare name without extension */
802 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
804 size_t len1 = fd1->extension - fd1->name;
805 size_t len2 = fd2->extension - fd2->name;
807 if (len1 < len2) return -1;
808 if (len1 > len2) return 1;
810 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
813 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
815 if (!fdci && fd) fdci = fd->change;
819 g_free(fdci->source);
824 if (fd) fd->change = NULL;
827 static gboolean file_data_can_write_directly(FileData *fd)
829 return filter_name_is_writable(fd->extension);
832 static gboolean file_data_can_write_sidecar(FileData *fd)
834 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
837 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
839 gchar *sidecar_path = NULL;
842 if (!file_data_can_write_sidecar(fd)) return NULL;
844 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
847 FileData *sfd = work->data;
849 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
851 sidecar_path = g_strdup(sfd->path);
856 if (!existing_only && !sidecar_path)
858 gchar *base = remove_extension_from_path(fd->path);
859 sidecar_path = g_strconcat(base, ".xmp", NULL);
868 *-----------------------------------------------------------------------------
869 * sidecar file info struct
870 *-----------------------------------------------------------------------------
875 static gint sidecar_file_priority(const gchar *path)
877 const gchar *extension = extension_from_path(path);
881 if (extension == NULL)
884 work = sidecar_ext_get_list();
887 gchar *ext = work->data;
890 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
898 *-----------------------------------------------------------------------------
900 *-----------------------------------------------------------------------------
903 static SortType filelist_sort_method = SORT_NONE;
904 static gboolean filelist_sort_ascend = TRUE;
907 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
909 if (!filelist_sort_ascend)
916 switch (filelist_sort_method)
921 if (fa->size < fb->size) return -1;
922 if (fa->size > fb->size) return 1;
923 /* fall back to name */
926 if (fa->date < fb->date) return -1;
927 if (fa->date > fb->date) return 1;
928 /* fall back to name */
930 #ifdef HAVE_STRVERSCMP
932 return strverscmp(fa->name, fb->name);
939 if (options->file_sort.case_sensitive)
940 return strcmp(fa->collate_key_name, fb->collate_key_name);
942 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
945 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
947 filelist_sort_method = method;
948 filelist_sort_ascend = ascend;
949 return filelist_sort_compare_filedata(fa, fb);
952 static gint filelist_sort_file_cb(gpointer a, gpointer b)
954 return filelist_sort_compare_filedata(a, b);
957 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
959 filelist_sort_method = method;
960 filelist_sort_ascend = ascend;
961 return g_list_sort(list, cb);
964 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
966 filelist_sort_method = method;
967 filelist_sort_ascend = ascend;
968 return g_list_insert_sorted(list, data, cb);
971 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
973 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
976 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
978 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
982 static GList *filelist_filter_out_sidecars(GList *flist)
985 GList *flist_filtered = NULL;
989 FileData *fd = work->data;
992 if (fd->parent) /* remove fd's that are children */
995 flist_filtered = g_list_prepend(flist_filtered, fd);
999 return flist_filtered;
1002 static gboolean is_hidden_file(const gchar *name)
1004 if (name[0] != '.') return FALSE;
1005 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1009 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
1014 GList *dlist = NULL;
1015 GList *flist = NULL;
1016 gint (*stat_func)(const gchar *path, struct stat *buf);
1017 GHashTable *basename_hash = NULL;
1019 g_assert(files || dirs);
1021 if (files) *files = NULL;
1022 if (dirs) *dirs = NULL;
1024 pathl = path_from_utf8(dir_fd->path);
1025 if (!pathl) return FALSE;
1027 dp = opendir(pathl);
1034 if (files) basename_hash = file_data_basename_hash_new();
1036 if (follow_symlinks)
1041 while ((dir = readdir(dp)) != NULL)
1043 struct stat ent_sbuf;
1044 const gchar *name = dir->d_name;
1047 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1050 filepath = g_build_filename(pathl, name, NULL);
1051 if (stat_func(filepath, &ent_sbuf) >= 0)
1053 if (S_ISDIR(ent_sbuf.st_mode))
1055 /* we ignore the .thumbnails dir for cleanliness */
1057 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1058 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1059 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1060 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1062 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, NULL));
1067 if (files && filter_name_exists(name))
1069 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, basename_hash));
1079 if (basename_hash) file_data_basename_hash_free(basename_hash);
1081 if (dirs) *dirs = dlist;
1082 if (files) *files = filelist_filter_out_sidecars(flist);
1087 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1089 return filelist_read_real(dir_fd, files, dirs, TRUE);
1092 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1094 return filelist_read_real(dir_fd, files, dirs, FALSE);
1097 void filelist_free(GList *list)
1104 file_data_unref((FileData *)work->data);
1112 GList *filelist_copy(GList *list)
1114 GList *new_list = NULL;
1125 new_list = g_list_prepend(new_list, file_data_ref(fd));
1128 return g_list_reverse(new_list);
1131 GList *filelist_from_path_list(GList *list)
1133 GList *new_list = NULL;
1144 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1147 return g_list_reverse(new_list);
1150 GList *filelist_to_path_list(GList *list)
1152 GList *new_list = NULL;
1163 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1166 return g_list_reverse(new_list);
1169 GList *filelist_filter(GList *list, gboolean is_dir_list)
1173 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1178 FileData *fd = (FileData *)(work->data);
1179 const gchar *name = fd->name;
1181 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1182 (!is_dir_list && !filter_name_exists(name)) ||
1183 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1184 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1188 list = g_list_remove_link(list, link);
1189 file_data_unref(fd);
1200 *-----------------------------------------------------------------------------
1201 * filelist recursive
1202 *-----------------------------------------------------------------------------
1205 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1207 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1210 GList *filelist_sort_path(GList *list)
1212 return g_list_sort(list, filelist_sort_path_cb);
1215 static void filelist_recursive_append(GList **list, GList *dirs)
1222 FileData *fd = (FileData *)(work->data);
1226 if (filelist_read(fd, &f, &d))
1228 f = filelist_filter(f, FALSE);
1229 f = filelist_sort_path(f);
1230 *list = g_list_concat(*list, f);
1232 d = filelist_filter(d, TRUE);
1233 d = filelist_sort_path(d);
1234 filelist_recursive_append(list, d);
1242 GList *filelist_recursive(FileData *dir_fd)
1247 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1248 list = filelist_filter(list, FALSE);
1249 list = filelist_sort_path(list);
1251 d = filelist_filter(d, TRUE);
1252 d = filelist_sort_path(d);
1253 filelist_recursive_append(&list, d);
1261 * marks and orientation
1264 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1265 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1266 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1267 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1269 gboolean file_data_get_mark(FileData *fd, gint n)
1271 gboolean valid = (fd->valid_marks & (1 << n));
1273 if (file_data_get_mark_func[n] && !valid)
1275 guint old = fd->marks;
1276 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1278 if (!value != !(fd->marks & (1 << n)))
1280 fd->marks = fd->marks ^ (1 << n);
1283 fd->valid_marks |= (1 << n);
1284 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1286 file_data_unref(fd);
1288 else if (!old && fd->marks)
1294 return !!(fd->marks & (1 << n));
1297 guint file_data_get_marks(FileData *fd)
1300 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1304 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1307 if (!value == !file_data_get_mark(fd, n)) return;
1309 if (file_data_set_mark_func[n])
1311 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1316 fd->marks = fd->marks ^ (1 << n);
1318 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1320 file_data_unref(fd);
1322 else if (!old && fd->marks)
1327 file_data_increment_version(fd);
1328 file_data_send_notification(fd, NOTIFY_MARKS);
1331 gboolean file_data_filter_marks(FileData *fd, guint filter)
1334 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1335 return ((fd->marks & filter) == filter);
1338 GList *file_data_filter_marks_list(GList *list, guint filter)
1345 FileData *fd = work->data;
1349 if (!file_data_filter_marks(fd, filter))
1351 list = g_list_remove_link(list, link);
1352 file_data_unref(fd);
1360 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1362 FileData *fd = value;
1363 file_data_increment_version(fd);
1364 file_data_send_notification(fd, NOTIFY_MARKS);
1367 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1369 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1371 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1373 file_data_get_mark_func[n] = get_mark_func;
1374 file_data_set_mark_func[n] = set_mark_func;
1375 file_data_mark_func_data[n] = data;
1376 file_data_destroy_mark_func[n] = notify;
1380 /* this effectively changes all known files */
1381 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1387 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1389 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1390 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1391 if (data) *data = file_data_mark_func_data[n];
1394 gint file_data_get_user_orientation(FileData *fd)
1396 return fd->user_orientation;
1399 void file_data_set_user_orientation(FileData *fd, gint value)
1401 if (fd->user_orientation == value) return;
1403 fd->user_orientation = value;
1404 file_data_increment_version(fd);
1405 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1410 * file_data - operates on the given fd
1411 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1415 /* return list of sidecar file extensions in a string */
1416 gchar *file_data_sc_list_to_string(FileData *fd)
1419 GString *result = g_string_new("");
1421 work = fd->sidecar_files;
1424 FileData *sfd = work->data;
1426 result = g_string_append(result, "+ ");
1427 result = g_string_append(result, sfd->extension);
1429 if (work) result = g_string_append_c(result, ' ');
1432 return g_string_free(result, FALSE);
1438 * add FileDataChangeInfo (see typedefs.h) for the given operation
1439 * uses file_data_add_change_info
1441 * fails if the fd->change already exists - change operations can't run in parallel
1442 * fd->change_info works as a lock
1444 * dest can be NULL - in this case the current name is used for now, it will
1449 FileDataChangeInfo types:
1451 MOVE - path is changed, name may be changed too
1452 RENAME - path remains unchanged, name is changed
1453 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1454 sidecar names are changed too, extensions are not changed
1456 UPDATE - file size, date or grouping has been changed
1459 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1461 FileDataChangeInfo *fdci;
1463 if (fd->change) return FALSE;
1465 fdci = g_new0(FileDataChangeInfo, 1);
1470 fdci->source = g_strdup(src);
1472 fdci->source = g_strdup(fd->path);
1475 fdci->dest = g_strdup(dest);
1482 static void file_data_planned_change_remove(FileData *fd)
1484 if (file_data_planned_change_hash &&
1485 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1487 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1489 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1490 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1491 file_data_unref(fd);
1492 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1494 g_hash_table_destroy(file_data_planned_change_hash);
1495 file_data_planned_change_hash = NULL;
1496 DEBUG_1("planned change: empty");
1503 void file_data_free_ci(FileData *fd)
1505 FileDataChangeInfo *fdci = fd->change;
1509 file_data_planned_change_remove(fd);
1511 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1513 g_free(fdci->source);
1521 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1523 FileDataChangeInfo *fdci = fd->change;
1525 fdci->regroup_when_finished = enable;
1528 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1532 if (fd->parent) fd = fd->parent;
1534 if (fd->change) return FALSE;
1536 work = fd->sidecar_files;
1539 FileData *sfd = work->data;
1541 if (sfd->change) return FALSE;
1545 file_data_add_ci(fd, type, NULL, NULL);
1547 work = fd->sidecar_files;
1550 FileData *sfd = work->data;
1552 file_data_add_ci(sfd, type, NULL, NULL);
1559 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1563 if (fd->parent) fd = fd->parent;
1565 if (!fd->change || fd->change->type != type) return FALSE;
1567 work = fd->sidecar_files;
1570 FileData *sfd = work->data;
1572 if (!sfd->change || sfd->change->type != type) return FALSE;
1580 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1582 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1583 file_data_sc_update_ci_copy(fd, dest_path);
1587 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1589 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1590 file_data_sc_update_ci_move(fd, dest_path);
1594 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1596 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1597 file_data_sc_update_ci_rename(fd, dest_path);
1601 gboolean file_data_sc_add_ci_delete(FileData *fd)
1603 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1606 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1608 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1609 file_data_sc_update_ci_unspecified(fd, dest_path);
1613 gboolean file_data_add_ci_write_metadata(FileData *fd)
1615 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1618 void file_data_sc_free_ci(FileData *fd)
1622 if (fd->parent) fd = fd->parent;
1624 file_data_free_ci(fd);
1626 work = fd->sidecar_files;
1629 FileData *sfd = work->data;
1631 file_data_free_ci(sfd);
1636 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1639 gboolean ret = TRUE;
1644 FileData *fd = work->data;
1646 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1653 static void file_data_sc_revert_ci_list(GList *fd_list)
1660 FileData *fd = work->data;
1662 file_data_sc_free_ci(fd);
1667 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1674 FileData *fd = work->data;
1676 if (!func(fd, dest))
1678 file_data_sc_revert_ci_list(work->prev);
1687 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1689 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1692 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1694 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1697 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1699 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1702 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1704 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1707 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1710 gboolean ret = TRUE;
1715 FileData *fd = work->data;
1717 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1724 void file_data_free_ci_list(GList *fd_list)
1731 FileData *fd = work->data;
1733 file_data_free_ci(fd);
1738 void file_data_sc_free_ci_list(GList *fd_list)
1745 FileData *fd = work->data;
1747 file_data_sc_free_ci(fd);
1753 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1754 * fails if fd->change does not exist or the change type does not match
1757 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1759 FileDataChangeType type = fd->change->type;
1761 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1765 if (!file_data_planned_change_hash)
1766 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1768 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1770 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1771 g_hash_table_remove(file_data_planned_change_hash, old_path);
1772 file_data_unref(fd);
1775 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1780 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1781 g_hash_table_remove(file_data_planned_change_hash, new_path);
1782 file_data_unref(ofd);
1785 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1787 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1792 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1794 gchar *old_path = fd->change->dest;
1796 fd->change->dest = g_strdup(dest_path);
1797 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1801 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1803 const gchar *extension = extension_from_path(fd->change->source);
1804 gchar *base = remove_extension_from_path(dest_path);
1805 gchar *old_path = fd->change->dest;
1807 fd->change->dest = g_strconcat(base, extension, NULL);
1808 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1814 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1817 gchar *dest_path_full = NULL;
1819 if (fd->parent) fd = fd->parent;
1823 dest_path = fd->path;
1825 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1827 gchar *dir = remove_level_from_path(fd->path);
1829 dest_path_full = g_build_filename(dir, dest_path, NULL);
1831 dest_path = dest_path_full;
1833 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1835 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1836 dest_path = dest_path_full;
1839 file_data_update_ci_dest(fd, dest_path);
1841 work = fd->sidecar_files;
1844 FileData *sfd = work->data;
1846 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1850 g_free(dest_path_full);
1853 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1855 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1856 file_data_sc_update_ci(fd, dest_path);
1860 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1862 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1865 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1867 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1870 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1872 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1875 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1877 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1880 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1882 gboolean (*func)(FileData *, const gchar *))
1885 gboolean ret = TRUE;
1890 FileData *fd = work->data;
1892 if (!func(fd, dest)) ret = FALSE;
1899 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1901 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1904 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1906 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1909 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1911 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1916 * verify source and dest paths - dest image exists, etc.
1917 * it should detect all possible problems with the planned operation
1920 gint file_data_verify_ci(FileData *fd)
1922 gint ret = CHANGE_OK;
1927 DEBUG_1("Change checked: no change info: %s", fd->path);
1931 if (!isname(fd->path))
1933 /* this probably should not happen */
1934 ret |= CHANGE_NO_SRC;
1935 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1939 dir = remove_level_from_path(fd->path);
1941 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1942 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1943 fd->change->type != FILEDATA_CHANGE_RENAME &&
1944 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1947 ret |= CHANGE_WARN_UNSAVED_META;
1948 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1951 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1952 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1953 !access_file(fd->path, R_OK))
1955 ret |= CHANGE_NO_READ_PERM;
1956 DEBUG_1("Change checked: no read permission: %s", fd->path);
1958 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1959 !access_file(dir, W_OK))
1961 ret |= CHANGE_NO_WRITE_PERM_DIR;
1962 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1964 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1965 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1966 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1967 !access_file(fd->path, W_OK))
1969 ret |= CHANGE_WARN_NO_WRITE_PERM;
1970 DEBUG_1("Change checked: no write permission: %s", fd->path);
1972 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1973 - that means that there are no hard errors and warnings can be disabled
1974 - the destination is determined during the check
1976 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1978 /* determine destination file */
1979 gboolean have_dest = FALSE;
1980 gchar *dest_dir = NULL;
1982 if (options->metadata.save_in_image_file)
1984 if (file_data_can_write_directly(fd))
1986 /* we can write the file directly */
1987 if (access_file(fd->path, W_OK))
1993 if (options->metadata.warn_on_write_problems)
1995 ret |= CHANGE_WARN_NO_WRITE_PERM;
1996 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2000 else if (file_data_can_write_sidecar(fd))
2002 /* we can write sidecar */
2003 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2004 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2006 file_data_update_ci_dest(fd, sidecar);
2011 if (options->metadata.warn_on_write_problems)
2013 ret |= CHANGE_WARN_NO_WRITE_PERM;
2014 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2023 /* write private metadata file under ~/.geeqie */
2025 /* If an existing metadata file exists, we will try writing to
2026 * it's location regardless of the user's preference.
2028 gchar *metadata_path = NULL;
2030 /* but ignore XMP if we are not able to write it */
2031 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2033 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2035 if (metadata_path && !access_file(metadata_path, W_OK))
2037 g_free(metadata_path);
2038 metadata_path = NULL;
2045 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2046 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2048 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2050 metadata_path = g_build_filename(dest_dir, filename, NULL);
2054 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2056 file_data_update_ci_dest(fd, metadata_path);
2061 ret |= CHANGE_NO_WRITE_PERM_DEST;
2062 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2064 g_free(metadata_path);
2069 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2074 same = (strcmp(fd->path, fd->change->dest) == 0);
2078 const gchar *dest_ext = extension_from_path(fd->change->dest);
2079 if (!dest_ext) dest_ext = "";
2081 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2083 ret |= CHANGE_WARN_CHANGED_EXT;
2084 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2089 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2091 ret |= CHANGE_WARN_SAME;
2092 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2096 dest_dir = remove_level_from_path(fd->change->dest);
2098 if (!isdir(dest_dir))
2100 ret |= CHANGE_NO_DEST_DIR;
2101 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2103 else if (!access_file(dest_dir, W_OK))
2105 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2106 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2110 if (isfile(fd->change->dest))
2112 if (!access_file(fd->change->dest, W_OK))
2114 ret |= CHANGE_NO_WRITE_PERM_DEST;
2115 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2119 ret |= CHANGE_WARN_DEST_EXISTS;
2120 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2123 else if (isdir(fd->change->dest))
2125 ret |= CHANGE_DEST_EXISTS;
2126 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2133 fd->change->error = ret;
2134 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2141 gint file_data_sc_verify_ci(FileData *fd)
2146 ret = file_data_verify_ci(fd);
2148 work = fd->sidecar_files;
2151 FileData *sfd = work->data;
2153 ret |= file_data_verify_ci(sfd);
2160 gchar *file_data_get_error_string(gint error)
2162 GString *result = g_string_new("");
2164 if (error & CHANGE_NO_SRC)
2166 if (result->len > 0) g_string_append(result, ", ");
2167 g_string_append(result, _("file or directory does not exist"));
2170 if (error & CHANGE_DEST_EXISTS)
2172 if (result->len > 0) g_string_append(result, ", ");
2173 g_string_append(result, _("destination already exists"));
2176 if (error & CHANGE_NO_WRITE_PERM_DEST)
2178 if (result->len > 0) g_string_append(result, ", ");
2179 g_string_append(result, _("destination can't be overwritten"));
2182 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2184 if (result->len > 0) g_string_append(result, ", ");
2185 g_string_append(result, _("destination directory is not writable"));
2188 if (error & CHANGE_NO_DEST_DIR)
2190 if (result->len > 0) g_string_append(result, ", ");
2191 g_string_append(result, _("destination directory does not exist"));
2194 if (error & CHANGE_NO_WRITE_PERM_DIR)
2196 if (result->len > 0) g_string_append(result, ", ");
2197 g_string_append(result, _("source directory is not writable"));
2200 if (error & CHANGE_NO_READ_PERM)
2202 if (result->len > 0) g_string_append(result, ", ");
2203 g_string_append(result, _("no read permission"));
2206 if (error & CHANGE_WARN_NO_WRITE_PERM)
2208 if (result->len > 0) g_string_append(result, ", ");
2209 g_string_append(result, _("file is readonly"));
2212 if (error & CHANGE_WARN_DEST_EXISTS)
2214 if (result->len > 0) g_string_append(result, ", ");
2215 g_string_append(result, _("destination already exists and will be overwritten"));
2218 if (error & CHANGE_WARN_SAME)
2220 if (result->len > 0) g_string_append(result, ", ");
2221 g_string_append(result, _("source and destination are the same"));
2224 if (error & CHANGE_WARN_CHANGED_EXT)
2226 if (result->len > 0) g_string_append(result, ", ");
2227 g_string_append(result, _("source and destination have different extension"));
2230 if (error & CHANGE_WARN_UNSAVED_META)
2232 if (result->len > 0) g_string_append(result, ", ");
2233 g_string_append(result, _("there are unsaved metadata changes for the file"));
2236 return g_string_free(result, FALSE);
2239 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2242 gint all_errors = 0;
2243 gint common_errors = ~0;
2248 if (!list) return 0;
2250 num = g_list_length(list);
2251 errors = g_new(int, num);
2262 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2263 all_errors |= error;
2264 common_errors &= error;
2271 if (desc && all_errors)
2274 GString *result = g_string_new("");
2278 gchar *str = file_data_get_error_string(common_errors);
2279 g_string_append(result, str);
2280 g_string_append(result, "\n");
2294 error = errors[i] & ~common_errors;
2298 gchar *str = file_data_get_error_string(error);
2299 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2304 *desc = g_string_free(result, FALSE);
2313 * perform the change described by FileFataChangeInfo
2314 * it is used for internal operations,
2315 * this function actually operates with files on the filesystem
2316 * it should implement safe delete
2319 static gboolean file_data_perform_move(FileData *fd)
2321 g_assert(!strcmp(fd->change->source, fd->path));
2322 return move_file(fd->change->source, fd->change->dest);
2325 static gboolean file_data_perform_copy(FileData *fd)
2327 g_assert(!strcmp(fd->change->source, fd->path));
2328 return copy_file(fd->change->source, fd->change->dest);
2331 static gboolean file_data_perform_delete(FileData *fd)
2333 if (isdir(fd->path) && !islink(fd->path))
2334 return rmdir_utf8(fd->path);
2336 if (options->file_ops.safe_delete_enable)
2337 return file_util_safe_unlink(fd->path);
2339 return unlink_file(fd->path);
2342 gboolean file_data_perform_ci(FileData *fd)
2344 FileDataChangeType type = fd->change->type;
2348 case FILEDATA_CHANGE_MOVE:
2349 return file_data_perform_move(fd);
2350 case FILEDATA_CHANGE_COPY:
2351 return file_data_perform_copy(fd);
2352 case FILEDATA_CHANGE_RENAME:
2353 return file_data_perform_move(fd); /* the same as move */
2354 case FILEDATA_CHANGE_DELETE:
2355 return file_data_perform_delete(fd);
2356 case FILEDATA_CHANGE_WRITE_METADATA:
2357 return metadata_write_perform(fd);
2358 case FILEDATA_CHANGE_UNSPECIFIED:
2359 /* nothing to do here */
2367 gboolean file_data_sc_perform_ci(FileData *fd)
2370 gboolean ret = TRUE;
2371 FileDataChangeType type = fd->change->type;
2373 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2375 work = fd->sidecar_files;
2378 FileData *sfd = work->data;
2380 if (!file_data_perform_ci(sfd)) ret = FALSE;
2384 if (!file_data_perform_ci(fd)) ret = FALSE;
2390 * updates FileData structure according to FileDataChangeInfo
2393 gboolean file_data_apply_ci(FileData *fd)
2395 FileDataChangeType type = fd->change->type;
2398 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2400 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2401 file_data_planned_change_remove(fd);
2403 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2405 /* this change overwrites another file which is already known to other modules
2406 renaming fd would create duplicate FileData structure
2407 the best thing we can do is nothing
2408 FIXME: maybe we could copy stuff like marks
2410 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2414 file_data_set_path(fd, fd->change->dest, NULL);
2417 file_data_increment_version(fd);
2418 file_data_send_notification(fd, NOTIFY_CHANGE);
2423 gboolean file_data_sc_apply_ci(FileData *fd)
2426 FileDataChangeType type = fd->change->type;
2428 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2430 work = fd->sidecar_files;
2433 FileData *sfd = work->data;
2435 file_data_apply_ci(sfd);
2439 file_data_apply_ci(fd);
2444 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2447 if (fd->parent) fd = fd->parent;
2448 if (!g_list_find(list, fd)) return FALSE;
2450 work = fd->sidecar_files;
2453 if (!g_list_find(list, work->data)) return FALSE;
2460 static gboolean file_data_list_dump(GList *list)
2462 GList *work, *work2;
2467 FileData *fd = work->data;
2468 printf("%s\n", fd->name);
2469 work2 = fd->sidecar_files;
2472 FileData *fd = work2->data;
2473 printf(" %s\n", fd->name);
2474 work2 = work2->next;
2482 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2487 /* change partial groups to independent files */
2492 FileData *fd = work->data;
2495 if (!file_data_list_contains_whole_group(list, fd))
2497 file_data_disable_grouping(fd, TRUE);
2500 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2506 /* remove sidecars from the list,
2507 they can be still acessed via main_fd->sidecar_files */
2511 FileData *fd = work->data;
2515 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2517 out = g_list_prepend(out, file_data_ref(fd));
2521 filelist_free(list);
2522 out = g_list_reverse(out);
2532 * notify other modules about the change described by FileDataChangeInfo
2535 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2536 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2537 implementation in view_file_list.c */
2542 typedef struct _NotifyData NotifyData;
2544 struct _NotifyData {
2545 FileDataNotifyFunc func;
2547 NotifyPriority priority;
2550 static GList *notify_func_list = NULL;
2552 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2554 NotifyData *nda = (NotifyData *)a;
2555 NotifyData *ndb = (NotifyData *)b;
2557 if (nda->priority < ndb->priority) return -1;
2558 if (nda->priority > ndb->priority) return 1;
2562 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2565 GList *work = notify_func_list;
2569 NotifyData *nd = (NotifyData *)work->data;
2571 if (nd->func == func && nd->data == data)
2573 g_warning("Notify func already registered");
2579 nd = g_new(NotifyData, 1);
2582 nd->priority = priority;
2584 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2585 DEBUG_2("Notify func registered: %p", nd);
2590 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2592 GList *work = notify_func_list;
2596 NotifyData *nd = (NotifyData *)work->data;
2598 if (nd->func == func && nd->data == data)
2600 notify_func_list = g_list_delete_link(notify_func_list, work);
2602 DEBUG_2("Notify func unregistered: %p", nd);
2608 g_warning("Notify func not found");
2613 void file_data_send_notification(FileData *fd, NotifyType type)
2615 GList *work = notify_func_list;
2619 NotifyData *nd = (NotifyData *)work->data;
2621 nd->func(fd, type, nd->data);
2626 static GHashTable *file_data_monitor_pool = NULL;
2627 static guint realtime_monitor_id = 0; /* event source id */
2629 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2633 file_data_check_changed_files(fd);
2635 DEBUG_1("monitor %s", fd->path);
2638 static gboolean realtime_monitor_cb(gpointer data)
2640 if (!options->update_on_time_change) return TRUE;
2641 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2645 gboolean file_data_register_real_time_monitor(FileData *fd)
2651 if (!file_data_monitor_pool)
2652 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2654 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2656 DEBUG_1("Register realtime %d %s", count, fd->path);
2659 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2661 if (!realtime_monitor_id)
2663 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2669 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2673 g_assert(file_data_monitor_pool);
2675 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2677 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2679 g_assert(count > 0);
2684 g_hash_table_remove(file_data_monitor_pool, fd);
2686 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2688 file_data_unref(fd);
2690 if (g_hash_table_size(file_data_monitor_pool) == 0)
2692 g_source_remove(realtime_monitor_id);
2693 realtime_monitor_id = 0;
2699 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */