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);
412 changed = file_data_check_changed_files(fd);
414 changed = file_data_check_changed_files_recursive(fd, st);
415 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
416 file_data_check_sidecars(fd, basename_hash);
417 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
422 fd = g_new0(FileData, 1);
424 fd->size = st->st_size;
425 fd->date = st->st_mtime;
426 fd->mode = st->st_mode;
428 fd->magick = 0x12345678;
430 file_data_set_path(fd, path_utf8, basename_hash); /* set path, name, collate_key_*, original_path */
433 file_data_check_sidecars(fd, basename_hash);
438 /* extension must contain only ASCII characters */
439 static GList *check_case_insensitive_ext(gchar *path)
446 sl = path_from_utf8(path);
448 extl = strrchr(sl, '.');
452 extl++; /* the first char after . */
453 ext_len = strlen(extl);
455 for (i = 0; i < (1 << ext_len); i++)
458 gboolean skip = FALSE;
459 for (j = 0; j < ext_len; j++)
461 if (i & (1 << (ext_len - 1 - j)))
463 extl[j] = g_ascii_tolower(extl[j]);
464 /* make sure the result does not contain duplicates */
465 if (extl[j] == g_ascii_toupper(extl[j]))
467 /* no change, probably a number, we have already tested this combination */
473 extl[j] = g_ascii_toupper(extl[j]);
477 if (stat(sl, &st) == 0)
479 list = g_list_prepend(list, file_data_new_local(sl, &st, FALSE, FALSE));
488 static void file_data_check_sidecars(FileData *fd, GHashTable *basename_hash)
492 FileData *parent_fd = NULL;
494 const GList *basename_list = NULL;
495 GList *group_list = NULL;
496 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
499 base_len = fd->extension - fd->path;
500 fname = g_string_new_len(fd->path, base_len);
504 basename_list = g_hash_table_lookup(basename_hash, fname->str);
508 /* check for possible sidecar files;
509 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
510 they have fd->ref set to 0 and file_data unref must chack and free them all together
511 (using fd->ref would cause loops and leaks)
514 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
515 for case-only differences put lowercase first,
516 put the result to group_list
518 work = sidecar_ext_get_list();
521 gchar *ext = work->data;
527 g_string_truncate(fname, base_len);
528 g_string_append(fname, ext);
529 new_list = check_case_insensitive_ext(fname->str);
530 group_list = g_list_concat(group_list, new_list);
534 const GList *work2 = basename_list;
538 FileData *sfd = work2->data;
540 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
542 group_list = g_list_append(group_list, file_data_ref(sfd));
548 g_string_free(fname, TRUE);
550 /* process the group list - the first one is the parent file, others are sidecars */
554 FileData *new_fd = work->data;
557 if (new_fd->disable_grouping)
559 file_data_unref(new_fd);
563 new_fd->ref--; /* do not use ref here */
566 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
568 file_data_merge_sidecar_files(parent_fd, new_fd);
570 g_list_free(group_list);
574 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, GHashTable *basename_hash)
576 gchar *path_utf8 = path_to_utf8(path);
577 FileData *ret = file_data_new(path_utf8, st, check_sidecars, basename_hash);
583 FileData *file_data_new_simple(const gchar *path_utf8)
587 if (!stat_utf8(path_utf8, &st))
593 return file_data_new(path_utf8, &st, TRUE, NULL);
596 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
598 sfd->parent = target;
599 if (!g_list_find(target->sidecar_files, sfd))
600 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
601 file_data_increment_version(sfd); /* increments both sfd and target */
606 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
610 file_data_add_sidecar_file(target, source);
612 work = source->sidecar_files;
615 FileData *sfd = work->data;
616 file_data_add_sidecar_file(target, sfd);
620 g_list_free(source->sidecar_files);
621 source->sidecar_files = NULL;
623 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
628 #ifdef DEBUG_FILEDATA
629 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
631 FileData *file_data_ref(FileData *fd)
634 if (fd == NULL) return NULL;
635 #ifdef DEBUG_FILEDATA
636 if (fd->magick != 0x12345678)
637 DEBUG_0("fd magick mismatch at %s:%d", file, line);
639 g_assert(fd->magick == 0x12345678);
642 #ifdef DEBUG_FILEDATA
643 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
645 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
650 static void file_data_free(FileData *fd)
652 g_assert(fd->magick == 0x12345678);
653 g_assert(fd->ref == 0);
655 g_hash_table_remove(file_data_pool, fd->original_path);
658 g_free(fd->original_path);
659 g_free(fd->collate_key_name);
660 g_free(fd->collate_key_name_nocase);
661 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
662 histmap_free(fd->histmap);
664 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
666 file_data_change_info_free(NULL, fd);
670 #ifdef DEBUG_FILEDATA
671 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
673 void file_data_unref(FileData *fd)
676 if (fd == NULL) return;
677 #ifdef DEBUG_FILEDATA
678 if (fd->magick != 0x12345678)
679 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
681 g_assert(fd->magick == 0x12345678);
684 #ifdef DEBUG_FILEDATA
685 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
687 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
692 FileData *parent = fd->parent ? fd->parent : fd;
694 if (parent->ref > 0) return;
696 work = parent->sidecar_files;
699 FileData *sfd = work->data;
700 if (sfd->ref > 0) return;
704 /* none of parent/children is referenced, we can free everything */
706 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
708 work = parent->sidecar_files;
711 FileData *sfd = work->data;
716 g_list_free(parent->sidecar_files);
717 parent->sidecar_files = NULL;
719 file_data_free(parent);
723 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
725 sfd->parent = target;
726 g_assert(g_list_find(target->sidecar_files, sfd));
728 file_data_increment_version(sfd); /* increments both sfd and target */
730 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
742 /* disables / enables grouping for particular file, sends UPDATE notification */
743 void file_data_disable_grouping(FileData *fd, gboolean disable)
745 if (!fd->disable_grouping == !disable) return;
747 fd->disable_grouping = !!disable;
753 FileData *parent = file_data_ref(fd->parent);
754 file_data_disconnect_sidecar_file(parent, fd);
755 file_data_send_notification(parent, NOTIFY_GROUPING);
756 file_data_unref(parent);
758 else if (fd->sidecar_files)
760 GList *sidecar_files = filelist_copy(fd->sidecar_files);
761 GList *work = sidecar_files;
764 FileData *sfd = work->data;
766 file_data_disconnect_sidecar_file(fd, sfd);
767 file_data_send_notification(sfd, NOTIFY_GROUPING);
769 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
770 filelist_free(sidecar_files);
774 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
779 file_data_increment_version(fd);
780 file_data_check_sidecars(fd, FALSE);
782 file_data_send_notification(fd, NOTIFY_GROUPING);
785 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
792 FileData *fd = work->data;
794 file_data_disable_grouping(fd, disable);
800 /* compare name without extension */
801 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
803 size_t len1 = fd1->extension - fd1->name;
804 size_t len2 = fd2->extension - fd2->name;
806 if (len1 < len2) return -1;
807 if (len1 > len2) return 1;
809 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
812 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
814 if (!fdci && fd) fdci = fd->change;
818 g_free(fdci->source);
823 if (fd) fd->change = NULL;
826 static gboolean file_data_can_write_directly(FileData *fd)
828 return filter_name_is_writable(fd->extension);
831 static gboolean file_data_can_write_sidecar(FileData *fd)
833 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
836 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
838 gchar *sidecar_path = NULL;
841 if (!file_data_can_write_sidecar(fd)) return NULL;
843 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
846 FileData *sfd = work->data;
848 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
850 sidecar_path = g_strdup(sfd->path);
855 if (!existing_only && !sidecar_path)
857 gchar *base = remove_extension_from_path(fd->path);
858 sidecar_path = g_strconcat(base, ".xmp", NULL);
867 *-----------------------------------------------------------------------------
868 * sidecar file info struct
869 *-----------------------------------------------------------------------------
874 static gint sidecar_file_priority(const gchar *path)
876 const gchar *extension = extension_from_path(path);
880 if (extension == NULL)
883 work = sidecar_ext_get_list();
886 gchar *ext = work->data;
889 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
897 *-----------------------------------------------------------------------------
899 *-----------------------------------------------------------------------------
902 static SortType filelist_sort_method = SORT_NONE;
903 static gboolean filelist_sort_ascend = TRUE;
906 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
908 if (!filelist_sort_ascend)
915 switch (filelist_sort_method)
920 if (fa->size < fb->size) return -1;
921 if (fa->size > fb->size) return 1;
922 /* fall back to name */
925 if (fa->date < fb->date) return -1;
926 if (fa->date > fb->date) return 1;
927 /* fall back to name */
929 #ifdef HAVE_STRVERSCMP
931 return strverscmp(fa->name, fb->name);
938 if (options->file_sort.case_sensitive)
939 return strcmp(fa->collate_key_name, fb->collate_key_name);
941 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
944 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
946 filelist_sort_method = method;
947 filelist_sort_ascend = ascend;
948 return filelist_sort_compare_filedata(fa, fb);
951 static gint filelist_sort_file_cb(gpointer a, gpointer b)
953 return filelist_sort_compare_filedata(a, b);
956 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
958 filelist_sort_method = method;
959 filelist_sort_ascend = ascend;
960 return g_list_sort(list, cb);
963 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
965 filelist_sort_method = method;
966 filelist_sort_ascend = ascend;
967 return g_list_insert_sorted(list, data, cb);
970 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
972 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
975 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
977 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
981 static GList *filelist_filter_out_sidecars(GList *flist)
984 GList *flist_filtered = NULL;
988 FileData *fd = work->data;
991 if (fd->parent) /* remove fd's that are children */
994 flist_filtered = g_list_prepend(flist_filtered, fd);
998 return flist_filtered;
1001 static gboolean is_hidden_file(const gchar *name)
1003 if (name[0] != '.') return FALSE;
1004 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1008 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
1013 GList *dlist = NULL;
1014 GList *flist = NULL;
1015 gint (*stat_func)(const gchar *path, struct stat *buf);
1016 GHashTable *basename_hash = NULL;
1018 g_assert(files || dirs);
1020 if (files) *files = NULL;
1021 if (dirs) *dirs = NULL;
1023 pathl = path_from_utf8(dir_fd->path);
1024 if (!pathl) return FALSE;
1026 dp = opendir(pathl);
1033 if (files) basename_hash = file_data_basename_hash_new();
1035 if (follow_symlinks)
1040 while ((dir = readdir(dp)) != NULL)
1042 struct stat ent_sbuf;
1043 const gchar *name = dir->d_name;
1046 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1049 filepath = g_build_filename(pathl, name, NULL);
1050 if (stat_func(filepath, &ent_sbuf) >= 0)
1052 if (S_ISDIR(ent_sbuf.st_mode))
1054 /* we ignore the .thumbnails dir for cleanliness */
1056 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1057 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1058 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1059 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1061 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, NULL));
1066 if (files && filter_name_exists(name))
1068 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, basename_hash));
1078 if (basename_hash) file_data_basename_hash_free(basename_hash);
1080 if (dirs) *dirs = dlist;
1081 if (files) *files = filelist_filter_out_sidecars(flist);
1086 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1088 return filelist_read_real(dir_fd, files, dirs, TRUE);
1091 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1093 return filelist_read_real(dir_fd, files, dirs, FALSE);
1096 void filelist_free(GList *list)
1103 file_data_unref((FileData *)work->data);
1111 GList *filelist_copy(GList *list)
1113 GList *new_list = NULL;
1124 new_list = g_list_prepend(new_list, file_data_ref(fd));
1127 return g_list_reverse(new_list);
1130 GList *filelist_from_path_list(GList *list)
1132 GList *new_list = NULL;
1143 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1146 return g_list_reverse(new_list);
1149 GList *filelist_to_path_list(GList *list)
1151 GList *new_list = NULL;
1162 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1165 return g_list_reverse(new_list);
1168 GList *filelist_filter(GList *list, gboolean is_dir_list)
1172 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1177 FileData *fd = (FileData *)(work->data);
1178 const gchar *name = fd->name;
1180 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1181 (!is_dir_list && !filter_name_exists(name)) ||
1182 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1183 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1187 list = g_list_remove_link(list, link);
1188 file_data_unref(fd);
1199 *-----------------------------------------------------------------------------
1200 * filelist recursive
1201 *-----------------------------------------------------------------------------
1204 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1206 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1209 GList *filelist_sort_path(GList *list)
1211 return g_list_sort(list, filelist_sort_path_cb);
1214 static void filelist_recursive_append(GList **list, GList *dirs)
1221 FileData *fd = (FileData *)(work->data);
1225 if (filelist_read(fd, &f, &d))
1227 f = filelist_filter(f, FALSE);
1228 f = filelist_sort_path(f);
1229 *list = g_list_concat(*list, f);
1231 d = filelist_filter(d, TRUE);
1232 d = filelist_sort_path(d);
1233 filelist_recursive_append(list, d);
1241 GList *filelist_recursive(FileData *dir_fd)
1246 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1247 list = filelist_filter(list, FALSE);
1248 list = filelist_sort_path(list);
1250 d = filelist_filter(d, TRUE);
1251 d = filelist_sort_path(d);
1252 filelist_recursive_append(&list, d);
1260 * marks and orientation
1263 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1264 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1265 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1266 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1268 gboolean file_data_get_mark(FileData *fd, gint n)
1270 gboolean valid = (fd->valid_marks & (1 << n));
1272 if (file_data_get_mark_func[n] && !valid)
1274 guint old = fd->marks;
1275 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1277 if (!value != !(fd->marks & (1 << n)))
1279 fd->marks = fd->marks ^ (1 << n);
1282 fd->valid_marks |= (1 << n);
1283 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1285 file_data_unref(fd);
1287 else if (!old && fd->marks)
1293 return !!(fd->marks & (1 << n));
1296 guint file_data_get_marks(FileData *fd)
1299 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1303 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1306 if (!value == !file_data_get_mark(fd, n)) return;
1308 if (file_data_set_mark_func[n])
1310 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1315 fd->marks = fd->marks ^ (1 << n);
1317 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1319 file_data_unref(fd);
1321 else if (!old && fd->marks)
1326 file_data_increment_version(fd);
1327 file_data_send_notification(fd, NOTIFY_MARKS);
1330 gboolean file_data_filter_marks(FileData *fd, guint filter)
1333 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1334 return ((fd->marks & filter) == filter);
1337 GList *file_data_filter_marks_list(GList *list, guint filter)
1344 FileData *fd = work->data;
1348 if (!file_data_filter_marks(fd, filter))
1350 list = g_list_remove_link(list, link);
1351 file_data_unref(fd);
1359 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1361 FileData *fd = value;
1362 file_data_increment_version(fd);
1363 file_data_send_notification(fd, NOTIFY_MARKS);
1366 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1368 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1370 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1372 file_data_get_mark_func[n] = get_mark_func;
1373 file_data_set_mark_func[n] = set_mark_func;
1374 file_data_mark_func_data[n] = data;
1375 file_data_destroy_mark_func[n] = notify;
1379 /* this effectively changes all known files */
1380 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1386 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1388 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1389 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1390 if (data) *data = file_data_mark_func_data[n];
1393 gint file_data_get_user_orientation(FileData *fd)
1395 return fd->user_orientation;
1398 void file_data_set_user_orientation(FileData *fd, gint value)
1400 if (fd->user_orientation == value) return;
1402 fd->user_orientation = value;
1403 file_data_increment_version(fd);
1404 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1409 * file_data - operates on the given fd
1410 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1414 /* return list of sidecar file extensions in a string */
1415 gchar *file_data_sc_list_to_string(FileData *fd)
1418 GString *result = g_string_new("");
1420 work = fd->sidecar_files;
1423 FileData *sfd = work->data;
1425 result = g_string_append(result, "+ ");
1426 result = g_string_append(result, sfd->extension);
1428 if (work) result = g_string_append_c(result, ' ');
1431 return g_string_free(result, FALSE);
1437 * add FileDataChangeInfo (see typedefs.h) for the given operation
1438 * uses file_data_add_change_info
1440 * fails if the fd->change already exists - change operations can't run in parallel
1441 * fd->change_info works as a lock
1443 * dest can be NULL - in this case the current name is used for now, it will
1448 FileDataChangeInfo types:
1450 MOVE - path is changed, name may be changed too
1451 RENAME - path remains unchanged, name is changed
1452 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1453 sidecar names are changed too, extensions are not changed
1455 UPDATE - file size, date or grouping has been changed
1458 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1460 FileDataChangeInfo *fdci;
1462 if (fd->change) return FALSE;
1464 fdci = g_new0(FileDataChangeInfo, 1);
1469 fdci->source = g_strdup(src);
1471 fdci->source = g_strdup(fd->path);
1474 fdci->dest = g_strdup(dest);
1481 static void file_data_planned_change_remove(FileData *fd)
1483 if (file_data_planned_change_hash &&
1484 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1486 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1488 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1489 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1490 file_data_unref(fd);
1491 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1493 g_hash_table_destroy(file_data_planned_change_hash);
1494 file_data_planned_change_hash = NULL;
1495 DEBUG_1("planned change: empty");
1502 void file_data_free_ci(FileData *fd)
1504 FileDataChangeInfo *fdci = fd->change;
1508 file_data_planned_change_remove(fd);
1510 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1512 g_free(fdci->source);
1520 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1522 FileDataChangeInfo *fdci = fd->change;
1524 fdci->regroup_when_finished = enable;
1527 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1531 if (fd->parent) fd = fd->parent;
1533 if (fd->change) return FALSE;
1535 work = fd->sidecar_files;
1538 FileData *sfd = work->data;
1540 if (sfd->change) return FALSE;
1544 file_data_add_ci(fd, type, NULL, NULL);
1546 work = fd->sidecar_files;
1549 FileData *sfd = work->data;
1551 file_data_add_ci(sfd, type, NULL, NULL);
1558 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1562 if (fd->parent) fd = fd->parent;
1564 if (!fd->change || fd->change->type != type) return FALSE;
1566 work = fd->sidecar_files;
1569 FileData *sfd = work->data;
1571 if (!sfd->change || sfd->change->type != type) return FALSE;
1579 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1581 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1582 file_data_sc_update_ci_copy(fd, dest_path);
1586 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1588 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1589 file_data_sc_update_ci_move(fd, dest_path);
1593 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1595 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1596 file_data_sc_update_ci_rename(fd, dest_path);
1600 gboolean file_data_sc_add_ci_delete(FileData *fd)
1602 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1605 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1607 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1608 file_data_sc_update_ci_unspecified(fd, dest_path);
1612 gboolean file_data_add_ci_write_metadata(FileData *fd)
1614 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1617 void file_data_sc_free_ci(FileData *fd)
1621 if (fd->parent) fd = fd->parent;
1623 file_data_free_ci(fd);
1625 work = fd->sidecar_files;
1628 FileData *sfd = work->data;
1630 file_data_free_ci(sfd);
1635 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1638 gboolean ret = TRUE;
1643 FileData *fd = work->data;
1645 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1652 static void file_data_sc_revert_ci_list(GList *fd_list)
1659 FileData *fd = work->data;
1661 file_data_sc_free_ci(fd);
1666 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1673 FileData *fd = work->data;
1675 if (!func(fd, dest))
1677 file_data_sc_revert_ci_list(work->prev);
1686 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1688 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1691 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1693 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1696 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1698 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1701 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1703 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1706 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1709 gboolean ret = TRUE;
1714 FileData *fd = work->data;
1716 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1723 void file_data_free_ci_list(GList *fd_list)
1730 FileData *fd = work->data;
1732 file_data_free_ci(fd);
1737 void file_data_sc_free_ci_list(GList *fd_list)
1744 FileData *fd = work->data;
1746 file_data_sc_free_ci(fd);
1752 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1753 * fails if fd->change does not exist or the change type does not match
1756 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1758 FileDataChangeType type = fd->change->type;
1760 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1764 if (!file_data_planned_change_hash)
1765 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1767 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1769 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1770 g_hash_table_remove(file_data_planned_change_hash, old_path);
1771 file_data_unref(fd);
1774 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1779 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1780 g_hash_table_remove(file_data_planned_change_hash, new_path);
1781 file_data_unref(ofd);
1784 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1786 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1791 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1793 gchar *old_path = fd->change->dest;
1795 fd->change->dest = g_strdup(dest_path);
1796 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1800 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1802 const gchar *extension = extension_from_path(fd->change->source);
1803 gchar *base = remove_extension_from_path(dest_path);
1804 gchar *old_path = fd->change->dest;
1806 fd->change->dest = g_strconcat(base, extension, NULL);
1807 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1813 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1816 gchar *dest_path_full = NULL;
1818 if (fd->parent) fd = fd->parent;
1822 dest_path = fd->path;
1824 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1826 gchar *dir = remove_level_from_path(fd->path);
1828 dest_path_full = g_build_filename(dir, dest_path, NULL);
1830 dest_path = dest_path_full;
1832 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1834 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1835 dest_path = dest_path_full;
1838 file_data_update_ci_dest(fd, dest_path);
1840 work = fd->sidecar_files;
1843 FileData *sfd = work->data;
1845 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1849 g_free(dest_path_full);
1852 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1854 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1855 file_data_sc_update_ci(fd, dest_path);
1859 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1861 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1864 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1866 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1869 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1871 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1874 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1876 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1879 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1881 gboolean (*func)(FileData *, const gchar *))
1884 gboolean ret = TRUE;
1889 FileData *fd = work->data;
1891 if (!func(fd, dest)) ret = FALSE;
1898 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1900 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1903 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1905 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1908 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1910 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1915 * verify source and dest paths - dest image exists, etc.
1916 * it should detect all possible problems with the planned operation
1919 gint file_data_verify_ci(FileData *fd)
1921 gint ret = CHANGE_OK;
1926 DEBUG_1("Change checked: no change info: %s", fd->path);
1930 if (!isname(fd->path))
1932 /* this probably should not happen */
1933 ret |= CHANGE_NO_SRC;
1934 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1938 dir = remove_level_from_path(fd->path);
1940 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1941 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1942 fd->change->type != FILEDATA_CHANGE_RENAME &&
1943 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1946 ret |= CHANGE_WARN_UNSAVED_META;
1947 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1950 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1951 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1952 !access_file(fd->path, R_OK))
1954 ret |= CHANGE_NO_READ_PERM;
1955 DEBUG_1("Change checked: no read permission: %s", fd->path);
1957 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1958 !access_file(dir, W_OK))
1960 ret |= CHANGE_NO_WRITE_PERM_DIR;
1961 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1963 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1964 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1965 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1966 !access_file(fd->path, W_OK))
1968 ret |= CHANGE_WARN_NO_WRITE_PERM;
1969 DEBUG_1("Change checked: no write permission: %s", fd->path);
1971 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1972 - that means that there are no hard errors and warnings can be disabled
1973 - the destination is determined during the check
1975 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1977 /* determine destination file */
1978 gboolean have_dest = FALSE;
1979 gchar *dest_dir = NULL;
1981 if (options->metadata.save_in_image_file)
1983 if (file_data_can_write_directly(fd))
1985 /* we can write the file directly */
1986 if (access_file(fd->path, W_OK))
1992 if (options->metadata.warn_on_write_problems)
1994 ret |= CHANGE_WARN_NO_WRITE_PERM;
1995 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1999 else if (file_data_can_write_sidecar(fd))
2001 /* we can write sidecar */
2002 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2003 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2005 file_data_update_ci_dest(fd, sidecar);
2010 if (options->metadata.warn_on_write_problems)
2012 ret |= CHANGE_WARN_NO_WRITE_PERM;
2013 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2022 /* write private metadata file under ~/.geeqie */
2024 /* If an existing metadata file exists, we will try writing to
2025 * it's location regardless of the user's preference.
2027 gchar *metadata_path = NULL;
2029 /* but ignore XMP if we are not able to write it */
2030 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2032 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2034 if (metadata_path && !access_file(metadata_path, W_OK))
2036 g_free(metadata_path);
2037 metadata_path = NULL;
2044 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2045 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2047 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2049 metadata_path = g_build_filename(dest_dir, filename, NULL);
2053 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2055 file_data_update_ci_dest(fd, metadata_path);
2060 ret |= CHANGE_NO_WRITE_PERM_DEST;
2061 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2063 g_free(metadata_path);
2068 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2073 same = (strcmp(fd->path, fd->change->dest) == 0);
2077 const gchar *dest_ext = extension_from_path(fd->change->dest);
2078 if (!dest_ext) dest_ext = "";
2080 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2082 ret |= CHANGE_WARN_CHANGED_EXT;
2083 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2088 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2090 ret |= CHANGE_WARN_SAME;
2091 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2095 dest_dir = remove_level_from_path(fd->change->dest);
2097 if (!isdir(dest_dir))
2099 ret |= CHANGE_NO_DEST_DIR;
2100 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2102 else if (!access_file(dest_dir, W_OK))
2104 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2105 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2109 if (isfile(fd->change->dest))
2111 if (!access_file(fd->change->dest, W_OK))
2113 ret |= CHANGE_NO_WRITE_PERM_DEST;
2114 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2118 ret |= CHANGE_WARN_DEST_EXISTS;
2119 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2122 else if (isdir(fd->change->dest))
2124 ret |= CHANGE_DEST_EXISTS;
2125 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2132 fd->change->error = ret;
2133 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2140 gint file_data_sc_verify_ci(FileData *fd)
2145 ret = file_data_verify_ci(fd);
2147 work = fd->sidecar_files;
2150 FileData *sfd = work->data;
2152 ret |= file_data_verify_ci(sfd);
2159 gchar *file_data_get_error_string(gint error)
2161 GString *result = g_string_new("");
2163 if (error & CHANGE_NO_SRC)
2165 if (result->len > 0) g_string_append(result, ", ");
2166 g_string_append(result, _("file or directory does not exist"));
2169 if (error & CHANGE_DEST_EXISTS)
2171 if (result->len > 0) g_string_append(result, ", ");
2172 g_string_append(result, _("destination already exists"));
2175 if (error & CHANGE_NO_WRITE_PERM_DEST)
2177 if (result->len > 0) g_string_append(result, ", ");
2178 g_string_append(result, _("destination can't be overwritten"));
2181 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2183 if (result->len > 0) g_string_append(result, ", ");
2184 g_string_append(result, _("destination directory is not writable"));
2187 if (error & CHANGE_NO_DEST_DIR)
2189 if (result->len > 0) g_string_append(result, ", ");
2190 g_string_append(result, _("destination directory does not exist"));
2193 if (error & CHANGE_NO_WRITE_PERM_DIR)
2195 if (result->len > 0) g_string_append(result, ", ");
2196 g_string_append(result, _("source directory is not writable"));
2199 if (error & CHANGE_NO_READ_PERM)
2201 if (result->len > 0) g_string_append(result, ", ");
2202 g_string_append(result, _("no read permission"));
2205 if (error & CHANGE_WARN_NO_WRITE_PERM)
2207 if (result->len > 0) g_string_append(result, ", ");
2208 g_string_append(result, _("file is readonly"));
2211 if (error & CHANGE_WARN_DEST_EXISTS)
2213 if (result->len > 0) g_string_append(result, ", ");
2214 g_string_append(result, _("destination already exists and will be overwritten"));
2217 if (error & CHANGE_WARN_SAME)
2219 if (result->len > 0) g_string_append(result, ", ");
2220 g_string_append(result, _("source and destination are the same"));
2223 if (error & CHANGE_WARN_CHANGED_EXT)
2225 if (result->len > 0) g_string_append(result, ", ");
2226 g_string_append(result, _("source and destination have different extension"));
2229 if (error & CHANGE_WARN_UNSAVED_META)
2231 if (result->len > 0) g_string_append(result, ", ");
2232 g_string_append(result, _("there are unsaved metadata changes for the file"));
2235 return g_string_free(result, FALSE);
2238 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2241 gint all_errors = 0;
2242 gint common_errors = ~0;
2247 if (!list) return 0;
2249 num = g_list_length(list);
2250 errors = g_new(int, num);
2261 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2262 all_errors |= error;
2263 common_errors &= error;
2270 if (desc && all_errors)
2273 GString *result = g_string_new("");
2277 gchar *str = file_data_get_error_string(common_errors);
2278 g_string_append(result, str);
2279 g_string_append(result, "\n");
2293 error = errors[i] & ~common_errors;
2297 gchar *str = file_data_get_error_string(error);
2298 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2303 *desc = g_string_free(result, FALSE);
2312 * perform the change described by FileFataChangeInfo
2313 * it is used for internal operations,
2314 * this function actually operates with files on the filesystem
2315 * it should implement safe delete
2318 static gboolean file_data_perform_move(FileData *fd)
2320 g_assert(!strcmp(fd->change->source, fd->path));
2321 return move_file(fd->change->source, fd->change->dest);
2324 static gboolean file_data_perform_copy(FileData *fd)
2326 g_assert(!strcmp(fd->change->source, fd->path));
2327 return copy_file(fd->change->source, fd->change->dest);
2330 static gboolean file_data_perform_delete(FileData *fd)
2332 if (isdir(fd->path) && !islink(fd->path))
2333 return rmdir_utf8(fd->path);
2335 if (options->file_ops.safe_delete_enable)
2336 return file_util_safe_unlink(fd->path);
2338 return unlink_file(fd->path);
2341 gboolean file_data_perform_ci(FileData *fd)
2343 FileDataChangeType type = fd->change->type;
2347 case FILEDATA_CHANGE_MOVE:
2348 return file_data_perform_move(fd);
2349 case FILEDATA_CHANGE_COPY:
2350 return file_data_perform_copy(fd);
2351 case FILEDATA_CHANGE_RENAME:
2352 return file_data_perform_move(fd); /* the same as move */
2353 case FILEDATA_CHANGE_DELETE:
2354 return file_data_perform_delete(fd);
2355 case FILEDATA_CHANGE_WRITE_METADATA:
2356 return metadata_write_perform(fd);
2357 case FILEDATA_CHANGE_UNSPECIFIED:
2358 /* nothing to do here */
2366 gboolean file_data_sc_perform_ci(FileData *fd)
2369 gboolean ret = TRUE;
2370 FileDataChangeType type = fd->change->type;
2372 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2374 work = fd->sidecar_files;
2377 FileData *sfd = work->data;
2379 if (!file_data_perform_ci(sfd)) ret = FALSE;
2383 if (!file_data_perform_ci(fd)) ret = FALSE;
2389 * updates FileData structure according to FileDataChangeInfo
2392 gboolean file_data_apply_ci(FileData *fd)
2394 FileDataChangeType type = fd->change->type;
2397 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2399 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2400 file_data_planned_change_remove(fd);
2402 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2404 /* this change overwrites another file which is already known to other modules
2405 renaming fd would create duplicate FileData structure
2406 the best thing we can do is nothing
2407 FIXME: maybe we could copy stuff like marks
2409 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2413 file_data_set_path(fd, fd->change->dest, NULL);
2416 file_data_increment_version(fd);
2417 file_data_send_notification(fd, NOTIFY_CHANGE);
2422 gboolean file_data_sc_apply_ci(FileData *fd)
2425 FileDataChangeType type = fd->change->type;
2427 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2429 work = fd->sidecar_files;
2432 FileData *sfd = work->data;
2434 file_data_apply_ci(sfd);
2438 file_data_apply_ci(fd);
2443 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2446 if (fd->parent) fd = fd->parent;
2447 if (!g_list_find(list, fd)) return FALSE;
2449 work = fd->sidecar_files;
2452 if (!g_list_find(list, work->data)) return FALSE;
2459 static gboolean file_data_list_dump(GList *list)
2461 GList *work, *work2;
2466 FileData *fd = work->data;
2467 printf("%s\n", fd->name);
2468 work2 = fd->sidecar_files;
2471 FileData *fd = work2->data;
2472 printf(" %s\n", fd->name);
2473 work2 = work2->next;
2481 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2486 /* change partial groups to independent files */
2491 FileData *fd = work->data;
2494 if (!file_data_list_contains_whole_group(list, fd))
2496 file_data_disable_grouping(fd, TRUE);
2499 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2505 /* remove sidecars from the list,
2506 they can be still acessed via main_fd->sidecar_files */
2510 FileData *fd = work->data;
2514 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2516 out = g_list_prepend(out, file_data_ref(fd));
2520 filelist_free(list);
2521 out = g_list_reverse(out);
2531 * notify other modules about the change described by FileDataChangeInfo
2534 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2535 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2536 implementation in view_file_list.c */
2541 typedef struct _NotifyData NotifyData;
2543 struct _NotifyData {
2544 FileDataNotifyFunc func;
2546 NotifyPriority priority;
2549 static GList *notify_func_list = NULL;
2551 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2553 NotifyData *nda = (NotifyData *)a;
2554 NotifyData *ndb = (NotifyData *)b;
2556 if (nda->priority < ndb->priority) return -1;
2557 if (nda->priority > ndb->priority) return 1;
2561 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2564 GList *work = notify_func_list;
2568 NotifyData *nd = (NotifyData *)work->data;
2570 if (nd->func == func && nd->data == data)
2572 g_warning("Notify func already registered");
2578 nd = g_new(NotifyData, 1);
2581 nd->priority = priority;
2583 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2584 DEBUG_2("Notify func registered: %p", nd);
2589 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2591 GList *work = notify_func_list;
2595 NotifyData *nd = (NotifyData *)work->data;
2597 if (nd->func == func && nd->data == data)
2599 notify_func_list = g_list_delete_link(notify_func_list, work);
2601 DEBUG_2("Notify func unregistered: %p", nd);
2607 g_warning("Notify func not found");
2612 void file_data_send_notification(FileData *fd, NotifyType type)
2614 GList *work = notify_func_list;
2618 NotifyData *nd = (NotifyData *)work->data;
2620 nd->func(fd, type, nd->data);
2625 static GHashTable *file_data_monitor_pool = NULL;
2626 static guint realtime_monitor_id = 0; /* event source id */
2628 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2632 file_data_check_changed_files(fd);
2634 DEBUG_1("monitor %s", fd->path);
2637 static gboolean realtime_monitor_cb(gpointer data)
2639 if (!options->update_on_time_change) return TRUE;
2640 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2644 gboolean file_data_register_real_time_monitor(FileData *fd)
2650 if (!file_data_monitor_pool)
2651 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2653 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2655 DEBUG_1("Register realtime %d %s", count, fd->path);
2658 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2660 if (!realtime_monitor_id)
2662 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2668 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2672 g_assert(file_data_monitor_pool);
2674 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2676 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2678 g_assert(count > 0);
2683 g_hash_table_remove(file_data_monitor_pool, fd);
2685 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2687 file_data_unref(fd);
2689 if (g_hash_table_size(file_data_monitor_pool) == 0)
2691 g_source_remove(realtime_monitor_id);
2692 realtime_monitor_id = 0;
2698 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */