4 * Copyright (C) 2008 - 2009 The Geeqie Team
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
17 #include "filefilter.h"
19 #include "thumb_standard.h"
20 #include "ui_fileops.h"
23 #include "histogram.h"
26 static GHashTable *file_data_pool = NULL;
27 static GHashTable *file_data_planned_change_hash = NULL;
28 static GHashTable *file_data_basename_hash = NULL;
30 static gint sidecar_file_priority(const gchar *path);
31 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars);
35 *-----------------------------------------------------------------------------
36 * text conversion utils
37 *-----------------------------------------------------------------------------
40 gchar *text_from_size(gint64 size)
46 /* what I would like to use is printf("%'d", size)
47 * BUT: not supported on every libc :(
51 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
52 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
56 a = g_strdup_printf("%d", (guint)size);
62 b = g_new(gchar, l + n + 1);
87 gchar *text_from_size_abrev(gint64 size)
89 if (size < (gint64)1024)
91 return g_strdup_printf(_("%d bytes"), (gint)size);
93 if (size < (gint64)1048576)
95 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
97 if (size < (gint64)1073741824)
99 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
102 /* to avoid overflowing the gdouble, do division in two steps */
104 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
107 /* note: returned string is valid until next call to text_from_time() */
108 const gchar *text_from_time(time_t t)
110 static gchar *ret = NULL;
114 GError *error = NULL;
116 btime = localtime(&t);
118 /* the %x warning about 2 digit years is not an error */
119 buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
120 if (buflen < 1) return "";
123 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
126 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
135 *-----------------------------------------------------------------------------
137 *-----------------------------------------------------------------------------
140 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
141 static void file_data_check_sidecars(FileData *fd, gboolean stat_sidecars);
142 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
145 void file_data_increment_version(FileData *fd)
151 fd->parent->version++;
152 fd->parent->valid_marks = 0;
156 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
158 const FileData *fda = a;
159 const FileData *fdb = b;
161 return strcmp(fdb->extension, fda->extension);
164 static void file_data_basename_hash_insert(FileData *fd)
167 const gchar *ext = extension_from_path(fd->path);
168 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
169 if (!file_data_basename_hash)
170 file_data_basename_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
172 list = g_hash_table_lookup(file_data_basename_hash, basename);
174 if (!g_list_find(list, fd))
176 list = g_list_insert_sorted(list, fd, file_data_sort_by_ext);
177 g_hash_table_insert(file_data_basename_hash, basename, list);
185 static void file_data_basename_hash_remove(FileData *fd)
188 const gchar *ext = extension_from_path(fd->path);
189 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
190 if (!file_data_basename_hash)
191 file_data_basename_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
193 list = g_hash_table_lookup(file_data_basename_hash, basename);
195 list = g_list_remove(list, fd);
199 g_hash_table_insert(file_data_basename_hash, basename, list);
203 g_hash_table_remove(file_data_basename_hash, basename);
208 static void file_data_set_collate_keys(FileData *fd)
210 gchar *caseless_name;
212 caseless_name = g_utf8_casefold(fd->name, -1);
214 g_free(fd->collate_key_name);
215 g_free(fd->collate_key_name_nocase);
217 #if GLIB_CHECK_VERSION(2, 8, 0)
218 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
219 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
221 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
222 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
224 g_free(caseless_name);
227 static void file_data_set_path(FileData *fd, const gchar *path)
229 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
230 g_assert(file_data_pool);
232 if (fd->path) file_data_basename_hash_remove(fd);
236 if (fd->original_path)
238 g_hash_table_remove(file_data_pool, fd->original_path);
239 g_free(fd->original_path);
242 g_assert(!g_hash_table_lookup(file_data_pool, path));
244 fd->original_path = g_strdup(path);
245 g_hash_table_insert(file_data_pool, fd->original_path, fd);
247 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
249 fd->path = g_strdup(path);
251 fd->extension = fd->name + 1;
252 file_data_set_collate_keys(fd);
256 fd->path = g_strdup(path);
257 fd->name = filename_from_path(fd->path);
259 if (strcmp(fd->name, "..") == 0)
261 gchar *dir = remove_level_from_path(path);
263 fd->path = remove_level_from_path(dir);
266 fd->extension = fd->name + 2;
267 file_data_set_collate_keys(fd);
270 else if (strcmp(fd->name, ".") == 0)
273 fd->path = remove_level_from_path(path);
275 fd->extension = fd->name + 1;
276 file_data_set_collate_keys(fd);
280 fd->extension = extension_from_path(fd->path);
281 if (fd->extension == NULL)
283 fd->extension = fd->name + strlen(fd->name);
286 file_data_basename_hash_insert(fd); /* we can ignore the special cases above - they don't have extensions */
288 file_data_set_collate_keys(fd);
291 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
293 gboolean ret = FALSE;
296 if (fd->size != st->st_size ||
297 fd->date != st->st_mtime)
299 fd->size = st->st_size;
300 fd->date = st->st_mtime;
301 fd->mode = st->st_mode;
302 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
303 fd->thumb_pixbuf = NULL;
304 file_data_increment_version(fd);
305 file_data_send_notification(fd, NOTIFY_REREAD);
309 work = fd->sidecar_files;
312 FileData *sfd = work->data;
316 if (!stat_utf8(sfd->path, &st))
320 file_data_disconnect_sidecar_file(fd, sfd);
325 ret |= file_data_check_changed_files_recursive(sfd, &st);
331 gboolean file_data_check_changed_files(FileData *fd)
333 gboolean ret = FALSE;
336 if (fd->parent) fd = fd->parent;
338 if (!stat_utf8(fd->path, &st))
341 FileData *sfd = NULL;
343 /* parent is missing, we have to rebuild whole group */
348 work = fd->sidecar_files;
354 file_data_disconnect_sidecar_file(fd, sfd);
356 if (sfd) file_data_check_sidecars(sfd, FALSE); /* this will group the sidecars back together */
357 file_data_send_notification(fd, NOTIFY_REREAD);
361 ret |= file_data_check_changed_files_recursive(fd, &st);
367 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars)
371 DEBUG_2("file_data_new: '%s' %d %d", path_utf8, check_sidecars, stat_sidecars);
374 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
376 fd = g_hash_table_lookup(file_data_pool, path_utf8);
382 if (!fd && file_data_planned_change_hash)
384 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
387 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
389 file_data_apply_ci(fd);
398 changed = file_data_check_changed_files(fd);
400 changed = file_data_check_changed_files_recursive(fd, st);
401 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
402 file_data_check_sidecars(fd, stat_sidecars);
403 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
408 fd = g_new0(FileData, 1);
410 fd->size = st->st_size;
411 fd->date = st->st_mtime;
412 fd->mode = st->st_mode;
414 fd->magick = 0x12345678;
416 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
419 file_data_check_sidecars(fd, stat_sidecars);
424 /* extension must contain only ASCII characters */
425 static GList *check_case_insensitive_ext(gchar *path)
432 sl = path_from_utf8(path);
434 extl = strrchr(sl, '.');
438 extl++; /* the first char after . */
439 ext_len = strlen(extl);
441 for (i = 0; i < (1 << ext_len); i++)
444 for (j = 0; j < ext_len; j++)
446 if (i & (1 << (ext_len - 1 - j)))
447 extl[j] = g_ascii_tolower(extl[j]);
449 extl[j] = g_ascii_toupper(extl[j]);
451 if (stat(sl, &st) == 0)
453 list = g_list_prepend(list, file_data_new_local(sl, &st, FALSE, FALSE));
462 static void file_data_check_sidecars(FileData *fd, gboolean stat_sidecars)
466 FileData *parent_fd = NULL;
468 const GList *basename_list = NULL;
469 GList *group_list = NULL;
470 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
473 base_len = fd->extension - fd->path;
474 fname = g_string_new_len(fd->path, base_len);
478 basename_list = g_hash_table_lookup(file_data_basename_hash, fname->str);
482 /* check for possible sidecar files;
483 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
484 they have fd->ref set to 0 and file_data unref must chack and free them all together
485 (using fd->ref would cause loops and leaks)
488 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
489 for case-only differences put lowercase first,
490 put the result to group_list
492 work = sidecar_ext_get_list();
495 gchar *ext = work->data;
501 g_string_truncate(fname, base_len);
502 g_string_append(fname, ext);
503 new_list = check_case_insensitive_ext(fname->str);
504 group_list = g_list_concat(group_list, new_list);
508 const GList *work2 = basename_list;
513 FileData *sfd = work2->data;
515 if (g_ascii_strcasecmp(ext, sfd->extension) == 0 &&
516 stat_utf8(sfd->path, &nst)) /* basename list can contain deleted files */
518 group_list = g_list_append(group_list, file_data_ref(sfd));
524 g_string_free(fname, TRUE);
526 /* process the group list - the first one is the parent file, others are sidecars */
530 FileData *new_fd = work->data;
533 if (new_fd->disable_grouping)
535 file_data_unref(new_fd);
539 new_fd->ref--; /* do not use ref here */
542 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
544 file_data_merge_sidecar_files(parent_fd, new_fd);
546 g_list_free(group_list);
550 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, gboolean stat_sidecars)
552 gchar *path_utf8 = path_to_utf8(path);
553 FileData *ret = file_data_new(path_utf8, st, check_sidecars, stat_sidecars);
559 FileData *file_data_new_simple(const gchar *path_utf8)
563 if (!stat_utf8(path_utf8, &st))
569 return file_data_new(path_utf8, &st, TRUE, TRUE);
572 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
574 sfd->parent = target;
575 if (!g_list_find(target->sidecar_files, sfd))
576 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
577 file_data_increment_version(sfd); /* increments both sfd and target */
582 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
586 file_data_add_sidecar_file(target, source);
588 work = source->sidecar_files;
591 FileData *sfd = work->data;
592 file_data_add_sidecar_file(target, sfd);
596 g_list_free(source->sidecar_files);
597 source->sidecar_files = NULL;
599 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
604 #ifdef DEBUG_FILEDATA
605 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
607 FileData *file_data_ref(FileData *fd)
610 if (fd == NULL) return NULL;
611 #ifdef DEBUG_FILEDATA
612 if (fd->magick != 0x12345678)
613 DEBUG_0("fd magick mismatch at %s:%d", file, line);
615 g_assert(fd->magick == 0x12345678);
618 #ifdef DEBUG_FILEDATA
619 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
621 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
626 static void file_data_free(FileData *fd)
628 g_assert(fd->magick == 0x12345678);
629 g_assert(fd->ref == 0);
631 if (fd->path) file_data_basename_hash_remove(fd);
632 g_hash_table_remove(file_data_pool, fd->original_path);
635 g_free(fd->original_path);
636 g_free(fd->collate_key_name);
637 g_free(fd->collate_key_name_nocase);
638 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
639 histmap_free(fd->histmap);
641 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
643 file_data_change_info_free(NULL, fd);
647 #ifdef DEBUG_FILEDATA
648 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
650 void file_data_unref(FileData *fd)
653 if (fd == NULL) return;
654 #ifdef DEBUG_FILEDATA
655 if (fd->magick != 0x12345678)
656 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
658 g_assert(fd->magick == 0x12345678);
661 #ifdef DEBUG_FILEDATA
662 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
664 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
669 FileData *parent = fd->parent ? fd->parent : fd;
671 if (parent->ref > 0) return;
673 work = parent->sidecar_files;
676 FileData *sfd = work->data;
677 if (sfd->ref > 0) return;
681 /* none of parent/children is referenced, we can free everything */
683 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
685 work = parent->sidecar_files;
688 FileData *sfd = work->data;
693 g_list_free(parent->sidecar_files);
694 parent->sidecar_files = NULL;
696 file_data_free(parent);
700 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
702 sfd->parent = target;
703 g_assert(g_list_find(target->sidecar_files, sfd));
705 file_data_increment_version(sfd); /* increments both sfd and target */
707 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
719 /* disables / enables grouping for particular file, sends UPDATE notification */
720 void file_data_disable_grouping(FileData *fd, gboolean disable)
722 if (!fd->disable_grouping == !disable) return;
724 fd->disable_grouping = !!disable;
730 FileData *parent = file_data_ref(fd->parent);
731 file_data_disconnect_sidecar_file(parent, fd);
732 file_data_send_notification(parent, NOTIFY_GROUPING);
733 file_data_unref(parent);
735 else if (fd->sidecar_files)
737 GList *sidecar_files = filelist_copy(fd->sidecar_files);
738 GList *work = sidecar_files;
741 FileData *sfd = work->data;
743 file_data_disconnect_sidecar_file(fd, sfd);
744 file_data_send_notification(sfd, NOTIFY_GROUPING);
746 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
747 filelist_free(sidecar_files);
751 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
756 file_data_increment_version(fd);
757 file_data_check_sidecars(fd, FALSE);
759 file_data_send_notification(fd, NOTIFY_GROUPING);
762 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
769 FileData *fd = work->data;
771 file_data_disable_grouping(fd, disable);
777 /* compare name without extension */
778 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
780 size_t len1 = fd1->extension - fd1->name;
781 size_t len2 = fd2->extension - fd2->name;
783 if (len1 < len2) return -1;
784 if (len1 > len2) return 1;
786 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
789 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
791 if (!fdci && fd) fdci = fd->change;
795 g_free(fdci->source);
800 if (fd) fd->change = NULL;
803 static gboolean file_data_can_write_directly(FileData *fd)
805 return filter_name_is_writable(fd->extension);
808 static gboolean file_data_can_write_sidecar(FileData *fd)
810 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
813 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
815 gchar *sidecar_path = NULL;
818 if (!file_data_can_write_sidecar(fd)) return NULL;
820 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
823 FileData *sfd = work->data;
825 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
827 sidecar_path = g_strdup(sfd->path);
832 if (!existing_only && !sidecar_path)
834 gchar *base = remove_extension_from_path(fd->path);
835 sidecar_path = g_strconcat(base, ".xmp", NULL);
844 *-----------------------------------------------------------------------------
845 * sidecar file info struct
846 *-----------------------------------------------------------------------------
851 static gint sidecar_file_priority(const gchar *path)
853 const gchar *extension = extension_from_path(path);
857 if (extension == NULL)
860 work = sidecar_ext_get_list();
863 gchar *ext = work->data;
866 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
874 *-----------------------------------------------------------------------------
876 *-----------------------------------------------------------------------------
879 static SortType filelist_sort_method = SORT_NONE;
880 static gboolean filelist_sort_ascend = TRUE;
883 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
885 if (!filelist_sort_ascend)
892 switch (filelist_sort_method)
897 if (fa->size < fb->size) return -1;
898 if (fa->size > fb->size) return 1;
899 /* fall back to name */
902 if (fa->date < fb->date) return -1;
903 if (fa->date > fb->date) return 1;
904 /* fall back to name */
906 #ifdef HAVE_STRVERSCMP
908 return strverscmp(fa->name, fb->name);
915 if (options->file_sort.case_sensitive)
916 return strcmp(fa->collate_key_name, fb->collate_key_name);
918 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
921 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
923 filelist_sort_method = method;
924 filelist_sort_ascend = ascend;
925 return filelist_sort_compare_filedata(fa, fb);
928 static gint filelist_sort_file_cb(gpointer a, gpointer b)
930 return filelist_sort_compare_filedata(a, b);
933 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
935 filelist_sort_method = method;
936 filelist_sort_ascend = ascend;
937 return g_list_sort(list, cb);
940 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
942 filelist_sort_method = method;
943 filelist_sort_ascend = ascend;
944 return g_list_insert_sorted(list, data, cb);
947 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
949 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
952 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
954 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
958 static GList *filelist_filter_out_sidecars(GList *flist)
961 GList *flist_filtered = NULL;
965 FileData *fd = work->data;
968 if (fd->parent) /* remove fd's that are children */
971 flist_filtered = g_list_prepend(flist_filtered, fd);
975 return flist_filtered;
978 static gboolean is_hidden_file(const gchar *name)
980 if (name[0] != '.') return FALSE;
981 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
985 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
992 gint (*stat_func)(const gchar *path, struct stat *buf);
994 g_assert(files || dirs);
996 if (files) *files = NULL;
997 if (dirs) *dirs = NULL;
999 pathl = path_from_utf8(dir_fd->path);
1000 if (!pathl) return FALSE;
1002 dp = opendir(pathl);
1009 if (follow_symlinks)
1014 while ((dir = readdir(dp)) != NULL)
1016 struct stat ent_sbuf;
1017 const gchar *name = dir->d_name;
1020 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1023 filepath = g_build_filename(pathl, name, NULL);
1024 if (stat_func(filepath, &ent_sbuf) >= 0)
1026 if (S_ISDIR(ent_sbuf.st_mode))
1028 /* we ignore the .thumbnails dir for cleanliness */
1030 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1031 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1032 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1033 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1035 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, FALSE));
1040 if (files && filter_name_exists(name))
1042 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, FALSE));
1053 if (dirs) *dirs = dlist;
1054 if (files) *files = filelist_filter_out_sidecars(flist);
1059 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1061 return filelist_read_real(dir_fd, files, dirs, TRUE);
1064 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1066 return filelist_read_real(dir_fd, files, dirs, FALSE);
1069 void filelist_free(GList *list)
1076 file_data_unref((FileData *)work->data);
1084 GList *filelist_copy(GList *list)
1086 GList *new_list = NULL;
1097 new_list = g_list_prepend(new_list, file_data_ref(fd));
1100 return g_list_reverse(new_list);
1103 GList *filelist_from_path_list(GList *list)
1105 GList *new_list = NULL;
1116 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1119 return g_list_reverse(new_list);
1122 GList *filelist_to_path_list(GList *list)
1124 GList *new_list = NULL;
1135 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1138 return g_list_reverse(new_list);
1141 GList *filelist_filter(GList *list, gboolean is_dir_list)
1145 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1150 FileData *fd = (FileData *)(work->data);
1151 const gchar *name = fd->name;
1153 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1154 (!is_dir_list && !filter_name_exists(name)) ||
1155 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1156 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1160 list = g_list_remove_link(list, link);
1161 file_data_unref(fd);
1172 *-----------------------------------------------------------------------------
1173 * filelist recursive
1174 *-----------------------------------------------------------------------------
1177 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1179 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1182 GList *filelist_sort_path(GList *list)
1184 return g_list_sort(list, filelist_sort_path_cb);
1187 static void filelist_recursive_append(GList **list, GList *dirs)
1194 FileData *fd = (FileData *)(work->data);
1198 if (filelist_read(fd, &f, &d))
1200 f = filelist_filter(f, FALSE);
1201 f = filelist_sort_path(f);
1202 *list = g_list_concat(*list, f);
1204 d = filelist_filter(d, TRUE);
1205 d = filelist_sort_path(d);
1206 filelist_recursive_append(list, d);
1214 GList *filelist_recursive(FileData *dir_fd)
1219 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1220 list = filelist_filter(list, FALSE);
1221 list = filelist_sort_path(list);
1223 d = filelist_filter(d, TRUE);
1224 d = filelist_sort_path(d);
1225 filelist_recursive_append(&list, d);
1233 * marks and orientation
1236 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1237 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1238 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1239 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1241 gboolean file_data_get_mark(FileData *fd, gint n)
1243 gboolean valid = (fd->valid_marks & (1 << n));
1245 if (file_data_get_mark_func[n] && !valid)
1247 guint old = fd->marks;
1248 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1250 if (!value != !(fd->marks & (1 << n)))
1252 fd->marks = fd->marks ^ (1 << n);
1255 fd->valid_marks |= (1 << n);
1256 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1258 file_data_unref(fd);
1260 else if (!old && fd->marks)
1266 return !!(fd->marks & (1 << n));
1269 guint file_data_get_marks(FileData *fd)
1272 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1276 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1279 if (!value == !file_data_get_mark(fd, n)) return;
1281 if (file_data_set_mark_func[n])
1283 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1288 fd->marks = fd->marks ^ (1 << n);
1290 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1292 file_data_unref(fd);
1294 else if (!old && fd->marks)
1299 file_data_increment_version(fd);
1300 file_data_send_notification(fd, NOTIFY_MARKS);
1303 gboolean file_data_filter_marks(FileData *fd, guint filter)
1306 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1307 return ((fd->marks & filter) == filter);
1310 GList *file_data_filter_marks_list(GList *list, guint filter)
1317 FileData *fd = work->data;
1321 if (!file_data_filter_marks(fd, filter))
1323 list = g_list_remove_link(list, link);
1324 file_data_unref(fd);
1332 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1334 FileData *fd = value;
1335 file_data_increment_version(fd);
1336 file_data_send_notification(fd, NOTIFY_MARKS);
1339 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1341 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1343 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1345 file_data_get_mark_func[n] = get_mark_func;
1346 file_data_set_mark_func[n] = set_mark_func;
1347 file_data_mark_func_data[n] = data;
1348 file_data_destroy_mark_func[n] = notify;
1352 /* this effectively changes all known files */
1353 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1359 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1361 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1362 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1363 if (data) *data = file_data_mark_func_data[n];
1366 gint file_data_get_user_orientation(FileData *fd)
1368 return fd->user_orientation;
1371 void file_data_set_user_orientation(FileData *fd, gint value)
1373 if (fd->user_orientation == value) return;
1375 fd->user_orientation = value;
1376 file_data_increment_version(fd);
1377 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1382 * file_data - operates on the given fd
1383 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1387 /* return list of sidecar file extensions in a string */
1388 gchar *file_data_sc_list_to_string(FileData *fd)
1391 GString *result = g_string_new("");
1393 work = fd->sidecar_files;
1396 FileData *sfd = work->data;
1398 result = g_string_append(result, "+ ");
1399 result = g_string_append(result, sfd->extension);
1401 if (work) result = g_string_append_c(result, ' ');
1404 return g_string_free(result, FALSE);
1410 * add FileDataChangeInfo (see typedefs.h) for the given operation
1411 * uses file_data_add_change_info
1413 * fails if the fd->change already exists - change operations can't run in parallel
1414 * fd->change_info works as a lock
1416 * dest can be NULL - in this case the current name is used for now, it will
1421 FileDataChangeInfo types:
1423 MOVE - path is changed, name may be changed too
1424 RENAME - path remains unchanged, name is changed
1425 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1426 sidecar names are changed too, extensions are not changed
1428 UPDATE - file size, date or grouping has been changed
1431 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1433 FileDataChangeInfo *fdci;
1435 if (fd->change) return FALSE;
1437 fdci = g_new0(FileDataChangeInfo, 1);
1442 fdci->source = g_strdup(src);
1444 fdci->source = g_strdup(fd->path);
1447 fdci->dest = g_strdup(dest);
1454 static void file_data_planned_change_remove(FileData *fd)
1456 if (file_data_planned_change_hash &&
1457 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1459 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1461 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1462 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1463 file_data_unref(fd);
1464 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1466 g_hash_table_destroy(file_data_planned_change_hash);
1467 file_data_planned_change_hash = NULL;
1468 DEBUG_1("planned change: empty");
1475 void file_data_free_ci(FileData *fd)
1477 FileDataChangeInfo *fdci = fd->change;
1481 file_data_planned_change_remove(fd);
1483 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1485 g_free(fdci->source);
1493 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1495 FileDataChangeInfo *fdci = fd->change;
1497 fdci->regroup_when_finished = enable;
1500 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1504 if (fd->parent) fd = fd->parent;
1506 if (fd->change) return FALSE;
1508 work = fd->sidecar_files;
1511 FileData *sfd = work->data;
1513 if (sfd->change) return FALSE;
1517 file_data_add_ci(fd, type, NULL, NULL);
1519 work = fd->sidecar_files;
1522 FileData *sfd = work->data;
1524 file_data_add_ci(sfd, type, NULL, NULL);
1531 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1535 if (fd->parent) fd = fd->parent;
1537 if (!fd->change || fd->change->type != type) return FALSE;
1539 work = fd->sidecar_files;
1542 FileData *sfd = work->data;
1544 if (!sfd->change || sfd->change->type != type) return FALSE;
1552 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1554 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1555 file_data_sc_update_ci_copy(fd, dest_path);
1559 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1561 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1562 file_data_sc_update_ci_move(fd, dest_path);
1566 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1568 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1569 file_data_sc_update_ci_rename(fd, dest_path);
1573 gboolean file_data_sc_add_ci_delete(FileData *fd)
1575 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1578 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1580 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1581 file_data_sc_update_ci_unspecified(fd, dest_path);
1585 gboolean file_data_add_ci_write_metadata(FileData *fd)
1587 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1590 void file_data_sc_free_ci(FileData *fd)
1594 if (fd->parent) fd = fd->parent;
1596 file_data_free_ci(fd);
1598 work = fd->sidecar_files;
1601 FileData *sfd = work->data;
1603 file_data_free_ci(sfd);
1608 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1611 gboolean ret = TRUE;
1616 FileData *fd = work->data;
1618 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1625 static void file_data_sc_revert_ci_list(GList *fd_list)
1632 FileData *fd = work->data;
1634 file_data_sc_free_ci(fd);
1639 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1646 FileData *fd = work->data;
1648 if (!func(fd, dest))
1650 file_data_sc_revert_ci_list(work->prev);
1659 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1661 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1664 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1666 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1669 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1671 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1674 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1676 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1679 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1682 gboolean ret = TRUE;
1687 FileData *fd = work->data;
1689 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1696 void file_data_free_ci_list(GList *fd_list)
1703 FileData *fd = work->data;
1705 file_data_free_ci(fd);
1710 void file_data_sc_free_ci_list(GList *fd_list)
1717 FileData *fd = work->data;
1719 file_data_sc_free_ci(fd);
1725 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1726 * fails if fd->change does not exist or the change type does not match
1729 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1731 FileDataChangeType type = fd->change->type;
1733 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1737 if (!file_data_planned_change_hash)
1738 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1740 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1742 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1743 g_hash_table_remove(file_data_planned_change_hash, old_path);
1744 file_data_unref(fd);
1747 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1752 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1753 g_hash_table_remove(file_data_planned_change_hash, new_path);
1754 file_data_unref(ofd);
1757 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1759 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1764 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1766 gchar *old_path = fd->change->dest;
1768 fd->change->dest = g_strdup(dest_path);
1769 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1773 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1775 const gchar *extension = extension_from_path(fd->change->source);
1776 gchar *base = remove_extension_from_path(dest_path);
1777 gchar *old_path = fd->change->dest;
1779 fd->change->dest = g_strconcat(base, extension, NULL);
1780 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1786 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1789 gchar *dest_path_full = NULL;
1791 if (fd->parent) fd = fd->parent;
1795 dest_path = fd->path;
1797 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1799 gchar *dir = remove_level_from_path(fd->path);
1801 dest_path_full = g_build_filename(dir, dest_path, NULL);
1803 dest_path = dest_path_full;
1805 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1807 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1808 dest_path = dest_path_full;
1811 file_data_update_ci_dest(fd, dest_path);
1813 work = fd->sidecar_files;
1816 FileData *sfd = work->data;
1818 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1822 g_free(dest_path_full);
1825 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1827 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1828 file_data_sc_update_ci(fd, dest_path);
1832 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1834 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1837 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1839 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1842 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1844 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1847 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1849 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1852 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1854 gboolean (*func)(FileData *, const gchar *))
1857 gboolean ret = TRUE;
1862 FileData *fd = work->data;
1864 if (!func(fd, dest)) ret = FALSE;
1871 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1873 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1876 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1878 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1881 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1883 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1888 * verify source and dest paths - dest image exists, etc.
1889 * it should detect all possible problems with the planned operation
1892 gint file_data_verify_ci(FileData *fd)
1894 gint ret = CHANGE_OK;
1899 DEBUG_1("Change checked: no change info: %s", fd->path);
1903 if (!isname(fd->path))
1905 /* this probably should not happen */
1906 ret |= CHANGE_NO_SRC;
1907 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1911 dir = remove_level_from_path(fd->path);
1913 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1914 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1915 fd->change->type != FILEDATA_CHANGE_RENAME &&
1916 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1919 ret |= CHANGE_WARN_UNSAVED_META;
1920 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1923 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1924 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1925 !access_file(fd->path, R_OK))
1927 ret |= CHANGE_NO_READ_PERM;
1928 DEBUG_1("Change checked: no read permission: %s", fd->path);
1930 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1931 !access_file(dir, W_OK))
1933 ret |= CHANGE_NO_WRITE_PERM_DIR;
1934 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1936 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1937 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1938 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1939 !access_file(fd->path, W_OK))
1941 ret |= CHANGE_WARN_NO_WRITE_PERM;
1942 DEBUG_1("Change checked: no write permission: %s", fd->path);
1944 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1945 - that means that there are no hard errors and warnings can be disabled
1946 - the destination is determined during the check
1948 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1950 /* determine destination file */
1951 gboolean have_dest = FALSE;
1952 gchar *dest_dir = NULL;
1954 if (options->metadata.save_in_image_file)
1956 if (file_data_can_write_directly(fd))
1958 /* we can write the file directly */
1959 if (access_file(fd->path, W_OK))
1965 if (options->metadata.warn_on_write_problems)
1967 ret |= CHANGE_WARN_NO_WRITE_PERM;
1968 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1972 else if (file_data_can_write_sidecar(fd))
1974 /* we can write sidecar */
1975 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1976 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1978 file_data_update_ci_dest(fd, sidecar);
1983 if (options->metadata.warn_on_write_problems)
1985 ret |= CHANGE_WARN_NO_WRITE_PERM;
1986 DEBUG_1("Change checked: file is not writable: %s", sidecar);
1995 /* write private metadata file under ~/.geeqie */
1997 /* If an existing metadata file exists, we will try writing to
1998 * it's location regardless of the user's preference.
2000 gchar *metadata_path = NULL;
2002 /* but ignore XMP if we are not able to write it */
2003 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2005 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2007 if (metadata_path && !access_file(metadata_path, W_OK))
2009 g_free(metadata_path);
2010 metadata_path = NULL;
2017 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2018 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2020 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2022 metadata_path = g_build_filename(dest_dir, filename, NULL);
2026 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2028 file_data_update_ci_dest(fd, metadata_path);
2033 ret |= CHANGE_NO_WRITE_PERM_DEST;
2034 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2036 g_free(metadata_path);
2041 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2046 same = (strcmp(fd->path, fd->change->dest) == 0);
2050 const gchar *dest_ext = extension_from_path(fd->change->dest);
2051 if (!dest_ext) dest_ext = "";
2053 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2055 ret |= CHANGE_WARN_CHANGED_EXT;
2056 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2061 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2063 ret |= CHANGE_WARN_SAME;
2064 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2068 dest_dir = remove_level_from_path(fd->change->dest);
2070 if (!isdir(dest_dir))
2072 ret |= CHANGE_NO_DEST_DIR;
2073 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2075 else if (!access_file(dest_dir, W_OK))
2077 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2078 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2082 if (isfile(fd->change->dest))
2084 if (!access_file(fd->change->dest, W_OK))
2086 ret |= CHANGE_NO_WRITE_PERM_DEST;
2087 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2091 ret |= CHANGE_WARN_DEST_EXISTS;
2092 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2095 else if (isdir(fd->change->dest))
2097 ret |= CHANGE_DEST_EXISTS;
2098 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2105 fd->change->error = ret;
2106 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2113 gint file_data_sc_verify_ci(FileData *fd)
2118 ret = file_data_verify_ci(fd);
2120 work = fd->sidecar_files;
2123 FileData *sfd = work->data;
2125 ret |= file_data_verify_ci(sfd);
2132 gchar *file_data_get_error_string(gint error)
2134 GString *result = g_string_new("");
2136 if (error & CHANGE_NO_SRC)
2138 if (result->len > 0) g_string_append(result, ", ");
2139 g_string_append(result, _("file or directory does not exist"));
2142 if (error & CHANGE_DEST_EXISTS)
2144 if (result->len > 0) g_string_append(result, ", ");
2145 g_string_append(result, _("destination already exists"));
2148 if (error & CHANGE_NO_WRITE_PERM_DEST)
2150 if (result->len > 0) g_string_append(result, ", ");
2151 g_string_append(result, _("destination can't be overwritten"));
2154 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2156 if (result->len > 0) g_string_append(result, ", ");
2157 g_string_append(result, _("destination directory is not writable"));
2160 if (error & CHANGE_NO_DEST_DIR)
2162 if (result->len > 0) g_string_append(result, ", ");
2163 g_string_append(result, _("destination directory does not exist"));
2166 if (error & CHANGE_NO_WRITE_PERM_DIR)
2168 if (result->len > 0) g_string_append(result, ", ");
2169 g_string_append(result, _("source directory is not writable"));
2172 if (error & CHANGE_NO_READ_PERM)
2174 if (result->len > 0) g_string_append(result, ", ");
2175 g_string_append(result, _("no read permission"));
2178 if (error & CHANGE_WARN_NO_WRITE_PERM)
2180 if (result->len > 0) g_string_append(result, ", ");
2181 g_string_append(result, _("file is readonly"));
2184 if (error & CHANGE_WARN_DEST_EXISTS)
2186 if (result->len > 0) g_string_append(result, ", ");
2187 g_string_append(result, _("destination already exists and will be overwritten"));
2190 if (error & CHANGE_WARN_SAME)
2192 if (result->len > 0) g_string_append(result, ", ");
2193 g_string_append(result, _("source and destination are the same"));
2196 if (error & CHANGE_WARN_CHANGED_EXT)
2198 if (result->len > 0) g_string_append(result, ", ");
2199 g_string_append(result, _("source and destination have different extension"));
2202 if (error & CHANGE_WARN_UNSAVED_META)
2204 if (result->len > 0) g_string_append(result, ", ");
2205 g_string_append(result, _("there are unsaved metadata changes for the file"));
2208 return g_string_free(result, FALSE);
2211 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2214 gint all_errors = 0;
2215 gint common_errors = ~0;
2220 if (!list) return 0;
2222 num = g_list_length(list);
2223 errors = g_new(int, num);
2234 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2235 all_errors |= error;
2236 common_errors &= error;
2243 if (desc && all_errors)
2246 GString *result = g_string_new("");
2250 gchar *str = file_data_get_error_string(common_errors);
2251 g_string_append(result, str);
2252 g_string_append(result, "\n");
2266 error = errors[i] & ~common_errors;
2270 gchar *str = file_data_get_error_string(error);
2271 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2276 *desc = g_string_free(result, FALSE);
2285 * perform the change described by FileFataChangeInfo
2286 * it is used for internal operations,
2287 * this function actually operates with files on the filesystem
2288 * it should implement safe delete
2291 static gboolean file_data_perform_move(FileData *fd)
2293 g_assert(!strcmp(fd->change->source, fd->path));
2294 return move_file(fd->change->source, fd->change->dest);
2297 static gboolean file_data_perform_copy(FileData *fd)
2299 g_assert(!strcmp(fd->change->source, fd->path));
2300 return copy_file(fd->change->source, fd->change->dest);
2303 static gboolean file_data_perform_delete(FileData *fd)
2305 if (isdir(fd->path) && !islink(fd->path))
2306 return rmdir_utf8(fd->path);
2308 if (options->file_ops.safe_delete_enable)
2309 return file_util_safe_unlink(fd->path);
2311 return unlink_file(fd->path);
2314 gboolean file_data_perform_ci(FileData *fd)
2316 FileDataChangeType type = fd->change->type;
2320 case FILEDATA_CHANGE_MOVE:
2321 return file_data_perform_move(fd);
2322 case FILEDATA_CHANGE_COPY:
2323 return file_data_perform_copy(fd);
2324 case FILEDATA_CHANGE_RENAME:
2325 return file_data_perform_move(fd); /* the same as move */
2326 case FILEDATA_CHANGE_DELETE:
2327 return file_data_perform_delete(fd);
2328 case FILEDATA_CHANGE_WRITE_METADATA:
2329 return metadata_write_perform(fd);
2330 case FILEDATA_CHANGE_UNSPECIFIED:
2331 /* nothing to do here */
2339 gboolean file_data_sc_perform_ci(FileData *fd)
2342 gboolean ret = TRUE;
2343 FileDataChangeType type = fd->change->type;
2345 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2347 work = fd->sidecar_files;
2350 FileData *sfd = work->data;
2352 if (!file_data_perform_ci(sfd)) ret = FALSE;
2356 if (!file_data_perform_ci(fd)) ret = FALSE;
2362 * updates FileData structure according to FileDataChangeInfo
2365 gboolean file_data_apply_ci(FileData *fd)
2367 FileDataChangeType type = fd->change->type;
2370 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2372 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2373 file_data_planned_change_remove(fd);
2375 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2377 /* this change overwrites another file which is already known to other modules
2378 renaming fd would create duplicate FileData structure
2379 the best thing we can do is nothing
2380 FIXME: maybe we could copy stuff like marks
2382 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2386 file_data_set_path(fd, fd->change->dest);
2389 file_data_increment_version(fd);
2390 file_data_send_notification(fd, NOTIFY_CHANGE);
2395 gboolean file_data_sc_apply_ci(FileData *fd)
2398 FileDataChangeType type = fd->change->type;
2400 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2402 work = fd->sidecar_files;
2405 FileData *sfd = work->data;
2407 file_data_apply_ci(sfd);
2411 file_data_apply_ci(fd);
2416 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2419 if (fd->parent) fd = fd->parent;
2420 if (!g_list_find(list, fd)) return FALSE;
2422 work = fd->sidecar_files;
2425 if (!g_list_find(list, work->data)) return FALSE;
2432 static gboolean file_data_list_dump(GList *list)
2434 GList *work, *work2;
2439 FileData *fd = work->data;
2440 printf("%s\n", fd->name);
2441 work2 = fd->sidecar_files;
2444 FileData *fd = work2->data;
2445 printf(" %s\n", fd->name);
2446 work2 = work2->next;
2454 GList *file_data_process_groups_in_selection(GList *list, GList **ungrouped_list)
2459 /* change partial groups to independent files */
2462 FileData *fd = work->data;
2465 if (!file_data_list_contains_whole_group(list, fd))
2467 file_data_disable_grouping(fd, TRUE);
2470 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2475 /* remove sidecars from the list,
2476 they can be still acessed via main_fd->sidecar_files */
2480 FileData *fd = work->data;
2485 out = g_list_prepend(out, fd);
2489 file_data_unref(fd);
2494 out = g_list_reverse(out);
2504 * notify other modules about the change described by FileDataChangeInfo
2507 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2508 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2509 implementation in view_file_list.c */
2514 typedef struct _NotifyData NotifyData;
2516 struct _NotifyData {
2517 FileDataNotifyFunc func;
2519 NotifyPriority priority;
2522 static GList *notify_func_list = NULL;
2524 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2526 NotifyData *nda = (NotifyData *)a;
2527 NotifyData *ndb = (NotifyData *)b;
2529 if (nda->priority < ndb->priority) return -1;
2530 if (nda->priority > ndb->priority) return 1;
2534 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2538 nd = g_new(NotifyData, 1);
2541 nd->priority = priority;
2543 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2544 DEBUG_2("Notify func registered: %p", nd);
2549 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2551 GList *work = notify_func_list;
2555 NotifyData *nd = (NotifyData *)work->data;
2557 if (nd->func == func && nd->data == data)
2559 notify_func_list = g_list_delete_link(notify_func_list, work);
2561 DEBUG_2("Notify func unregistered: %p", nd);
2571 void file_data_send_notification(FileData *fd, NotifyType type)
2573 GList *work = notify_func_list;
2577 NotifyData *nd = (NotifyData *)work->data;
2579 nd->func(fd, type, nd->data);
2584 static GHashTable *file_data_monitor_pool = NULL;
2585 static guint realtime_monitor_id = 0; /* event source id */
2587 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2591 file_data_check_changed_files(fd);
2593 DEBUG_1("monitor %s", fd->path);
2596 static gboolean realtime_monitor_cb(gpointer data)
2598 if (!options->update_on_time_change) return TRUE;
2599 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2603 gboolean file_data_register_real_time_monitor(FileData *fd)
2609 if (!file_data_monitor_pool)
2610 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2612 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2614 DEBUG_1("Register realtime %d %s", count, fd->path);
2617 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2619 if (!realtime_monitor_id)
2621 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2627 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2631 g_assert(file_data_monitor_pool);
2633 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2635 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2637 g_assert(count > 0);
2642 g_hash_table_remove(file_data_monitor_pool, fd);
2644 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2646 file_data_unref(fd);
2648 if (g_hash_table_size(file_data_monitor_pool) == 0)
2650 g_source_remove(realtime_monitor_id);
2651 realtime_monitor_id = 0;
2657 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */