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, FALSE); /* 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 for (j = 0; j < ext_len; j++)
460 if (i & (1 << (ext_len - 1 - j)))
461 extl[j] = g_ascii_tolower(extl[j]);
463 extl[j] = g_ascii_toupper(extl[j]);
465 if (stat(sl, &st) == 0)
467 list = g_list_prepend(list, file_data_new_local(sl, &st, FALSE, FALSE));
476 static void file_data_check_sidecars(FileData *fd, GHashTable *basename_hash)
480 FileData *parent_fd = NULL;
482 const GList *basename_list = NULL;
483 GList *group_list = NULL;
484 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
487 base_len = fd->extension - fd->path;
488 fname = g_string_new_len(fd->path, base_len);
492 basename_list = g_hash_table_lookup(basename_hash, fname->str);
496 /* check for possible sidecar files;
497 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
498 they have fd->ref set to 0 and file_data unref must chack and free them all together
499 (using fd->ref would cause loops and leaks)
502 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
503 for case-only differences put lowercase first,
504 put the result to group_list
506 work = sidecar_ext_get_list();
509 gchar *ext = work->data;
515 g_string_truncate(fname, base_len);
516 g_string_append(fname, ext);
517 new_list = check_case_insensitive_ext(fname->str);
518 group_list = g_list_concat(group_list, new_list);
522 const GList *work2 = basename_list;
526 FileData *sfd = work2->data;
528 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
530 group_list = g_list_append(group_list, file_data_ref(sfd));
536 g_string_free(fname, TRUE);
538 /* process the group list - the first one is the parent file, others are sidecars */
542 FileData *new_fd = work->data;
545 if (new_fd->disable_grouping)
547 file_data_unref(new_fd);
551 new_fd->ref--; /* do not use ref here */
554 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
556 file_data_merge_sidecar_files(parent_fd, new_fd);
558 g_list_free(group_list);
562 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, GHashTable *basename_hash)
564 gchar *path_utf8 = path_to_utf8(path);
565 FileData *ret = file_data_new(path_utf8, st, check_sidecars, basename_hash);
571 FileData *file_data_new_simple(const gchar *path_utf8)
575 if (!stat_utf8(path_utf8, &st))
581 return file_data_new(path_utf8, &st, TRUE, NULL);
584 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
586 sfd->parent = target;
587 if (!g_list_find(target->sidecar_files, sfd))
588 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
589 file_data_increment_version(sfd); /* increments both sfd and target */
594 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
598 file_data_add_sidecar_file(target, source);
600 work = source->sidecar_files;
603 FileData *sfd = work->data;
604 file_data_add_sidecar_file(target, sfd);
608 g_list_free(source->sidecar_files);
609 source->sidecar_files = NULL;
611 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
616 #ifdef DEBUG_FILEDATA
617 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
619 FileData *file_data_ref(FileData *fd)
622 if (fd == NULL) return NULL;
623 #ifdef DEBUG_FILEDATA
624 if (fd->magick != 0x12345678)
625 DEBUG_0("fd magick mismatch at %s:%d", file, line);
627 g_assert(fd->magick == 0x12345678);
630 #ifdef DEBUG_FILEDATA
631 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
633 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
638 static void file_data_free(FileData *fd)
640 g_assert(fd->magick == 0x12345678);
641 g_assert(fd->ref == 0);
643 g_hash_table_remove(file_data_pool, fd->original_path);
646 g_free(fd->original_path);
647 g_free(fd->collate_key_name);
648 g_free(fd->collate_key_name_nocase);
649 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
650 histmap_free(fd->histmap);
652 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
654 file_data_change_info_free(NULL, fd);
658 #ifdef DEBUG_FILEDATA
659 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
661 void file_data_unref(FileData *fd)
664 if (fd == NULL) return;
665 #ifdef DEBUG_FILEDATA
666 if (fd->magick != 0x12345678)
667 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
669 g_assert(fd->magick == 0x12345678);
672 #ifdef DEBUG_FILEDATA
673 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
675 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
680 FileData *parent = fd->parent ? fd->parent : fd;
682 if (parent->ref > 0) return;
684 work = parent->sidecar_files;
687 FileData *sfd = work->data;
688 if (sfd->ref > 0) return;
692 /* none of parent/children is referenced, we can free everything */
694 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
696 work = parent->sidecar_files;
699 FileData *sfd = work->data;
704 g_list_free(parent->sidecar_files);
705 parent->sidecar_files = NULL;
707 file_data_free(parent);
711 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
713 sfd->parent = target;
714 g_assert(g_list_find(target->sidecar_files, sfd));
716 file_data_increment_version(sfd); /* increments both sfd and target */
718 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
730 /* disables / enables grouping for particular file, sends UPDATE notification */
731 void file_data_disable_grouping(FileData *fd, gboolean disable)
733 if (!fd->disable_grouping == !disable) return;
735 fd->disable_grouping = !!disable;
741 FileData *parent = file_data_ref(fd->parent);
742 file_data_disconnect_sidecar_file(parent, fd);
743 file_data_send_notification(parent, NOTIFY_GROUPING);
744 file_data_unref(parent);
746 else if (fd->sidecar_files)
748 GList *sidecar_files = filelist_copy(fd->sidecar_files);
749 GList *work = sidecar_files;
752 FileData *sfd = work->data;
754 file_data_disconnect_sidecar_file(fd, sfd);
755 file_data_send_notification(sfd, NOTIFY_GROUPING);
757 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
758 filelist_free(sidecar_files);
762 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
767 file_data_increment_version(fd);
768 file_data_check_sidecars(fd, FALSE);
770 file_data_send_notification(fd, NOTIFY_GROUPING);
773 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
780 FileData *fd = work->data;
782 file_data_disable_grouping(fd, disable);
788 /* compare name without extension */
789 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
791 size_t len1 = fd1->extension - fd1->name;
792 size_t len2 = fd2->extension - fd2->name;
794 if (len1 < len2) return -1;
795 if (len1 > len2) return 1;
797 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
800 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
802 if (!fdci && fd) fdci = fd->change;
806 g_free(fdci->source);
811 if (fd) fd->change = NULL;
814 static gboolean file_data_can_write_directly(FileData *fd)
816 return filter_name_is_writable(fd->extension);
819 static gboolean file_data_can_write_sidecar(FileData *fd)
821 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
824 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
826 gchar *sidecar_path = NULL;
829 if (!file_data_can_write_sidecar(fd)) return NULL;
831 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
834 FileData *sfd = work->data;
836 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
838 sidecar_path = g_strdup(sfd->path);
843 if (!existing_only && !sidecar_path)
845 gchar *base = remove_extension_from_path(fd->path);
846 sidecar_path = g_strconcat(base, ".xmp", NULL);
855 *-----------------------------------------------------------------------------
856 * sidecar file info struct
857 *-----------------------------------------------------------------------------
862 static gint sidecar_file_priority(const gchar *path)
864 const gchar *extension = extension_from_path(path);
868 if (extension == NULL)
871 work = sidecar_ext_get_list();
874 gchar *ext = work->data;
877 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
885 *-----------------------------------------------------------------------------
887 *-----------------------------------------------------------------------------
890 static SortType filelist_sort_method = SORT_NONE;
891 static gboolean filelist_sort_ascend = TRUE;
894 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
896 if (!filelist_sort_ascend)
903 switch (filelist_sort_method)
908 if (fa->size < fb->size) return -1;
909 if (fa->size > fb->size) return 1;
910 /* fall back to name */
913 if (fa->date < fb->date) return -1;
914 if (fa->date > fb->date) return 1;
915 /* fall back to name */
917 #ifdef HAVE_STRVERSCMP
919 return strverscmp(fa->name, fb->name);
926 if (options->file_sort.case_sensitive)
927 return strcmp(fa->collate_key_name, fb->collate_key_name);
929 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
932 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
934 filelist_sort_method = method;
935 filelist_sort_ascend = ascend;
936 return filelist_sort_compare_filedata(fa, fb);
939 static gint filelist_sort_file_cb(gpointer a, gpointer b)
941 return filelist_sort_compare_filedata(a, b);
944 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
946 filelist_sort_method = method;
947 filelist_sort_ascend = ascend;
948 return g_list_sort(list, cb);
951 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
953 filelist_sort_method = method;
954 filelist_sort_ascend = ascend;
955 return g_list_insert_sorted(list, data, cb);
958 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
960 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
963 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
965 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
969 static GList *filelist_filter_out_sidecars(GList *flist)
972 GList *flist_filtered = NULL;
976 FileData *fd = work->data;
979 if (fd->parent) /* remove fd's that are children */
982 flist_filtered = g_list_prepend(flist_filtered, fd);
986 return flist_filtered;
989 static gboolean is_hidden_file(const gchar *name)
991 if (name[0] != '.') return FALSE;
992 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
996 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
1001 GList *dlist = NULL;
1002 GList *flist = NULL;
1003 gint (*stat_func)(const gchar *path, struct stat *buf);
1004 GHashTable *basename_hash = NULL;
1006 g_assert(files || dirs);
1008 if (files) *files = NULL;
1009 if (dirs) *dirs = NULL;
1011 pathl = path_from_utf8(dir_fd->path);
1012 if (!pathl) return FALSE;
1014 dp = opendir(pathl);
1021 if (files) basename_hash = file_data_basename_hash_new();
1023 if (follow_symlinks)
1028 while ((dir = readdir(dp)) != NULL)
1030 struct stat ent_sbuf;
1031 const gchar *name = dir->d_name;
1034 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1037 filepath = g_build_filename(pathl, name, NULL);
1038 if (stat_func(filepath, &ent_sbuf) >= 0)
1040 if (S_ISDIR(ent_sbuf.st_mode))
1042 /* we ignore the .thumbnails dir for cleanliness */
1044 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1045 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1046 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1047 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1049 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, NULL));
1054 if (files && filter_name_exists(name))
1056 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, basename_hash));
1066 if (basename_hash) file_data_basename_hash_free(basename_hash);
1068 if (dirs) *dirs = dlist;
1069 if (files) *files = filelist_filter_out_sidecars(flist);
1074 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1076 return filelist_read_real(dir_fd, files, dirs, TRUE);
1079 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1081 return filelist_read_real(dir_fd, files, dirs, FALSE);
1084 void filelist_free(GList *list)
1091 file_data_unref((FileData *)work->data);
1099 GList *filelist_copy(GList *list)
1101 GList *new_list = NULL;
1112 new_list = g_list_prepend(new_list, file_data_ref(fd));
1115 return g_list_reverse(new_list);
1118 GList *filelist_from_path_list(GList *list)
1120 GList *new_list = NULL;
1131 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1134 return g_list_reverse(new_list);
1137 GList *filelist_to_path_list(GList *list)
1139 GList *new_list = NULL;
1150 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1153 return g_list_reverse(new_list);
1156 GList *filelist_filter(GList *list, gboolean is_dir_list)
1160 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1165 FileData *fd = (FileData *)(work->data);
1166 const gchar *name = fd->name;
1168 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1169 (!is_dir_list && !filter_name_exists(name)) ||
1170 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1171 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1175 list = g_list_remove_link(list, link);
1176 file_data_unref(fd);
1187 *-----------------------------------------------------------------------------
1188 * filelist recursive
1189 *-----------------------------------------------------------------------------
1192 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1194 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1197 GList *filelist_sort_path(GList *list)
1199 return g_list_sort(list, filelist_sort_path_cb);
1202 static void filelist_recursive_append(GList **list, GList *dirs)
1209 FileData *fd = (FileData *)(work->data);
1213 if (filelist_read(fd, &f, &d))
1215 f = filelist_filter(f, FALSE);
1216 f = filelist_sort_path(f);
1217 *list = g_list_concat(*list, f);
1219 d = filelist_filter(d, TRUE);
1220 d = filelist_sort_path(d);
1221 filelist_recursive_append(list, d);
1229 GList *filelist_recursive(FileData *dir_fd)
1234 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1235 list = filelist_filter(list, FALSE);
1236 list = filelist_sort_path(list);
1238 d = filelist_filter(d, TRUE);
1239 d = filelist_sort_path(d);
1240 filelist_recursive_append(&list, d);
1248 * marks and orientation
1251 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1252 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1253 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1254 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1256 gboolean file_data_get_mark(FileData *fd, gint n)
1258 gboolean valid = (fd->valid_marks & (1 << n));
1260 if (file_data_get_mark_func[n] && !valid)
1262 guint old = fd->marks;
1263 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1265 if (!value != !(fd->marks & (1 << n)))
1267 fd->marks = fd->marks ^ (1 << n);
1270 fd->valid_marks |= (1 << n);
1271 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1273 file_data_unref(fd);
1275 else if (!old && fd->marks)
1281 return !!(fd->marks & (1 << n));
1284 guint file_data_get_marks(FileData *fd)
1287 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1291 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1294 if (!value == !file_data_get_mark(fd, n)) return;
1296 if (file_data_set_mark_func[n])
1298 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1303 fd->marks = fd->marks ^ (1 << n);
1305 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1307 file_data_unref(fd);
1309 else if (!old && fd->marks)
1314 file_data_increment_version(fd);
1315 file_data_send_notification(fd, NOTIFY_MARKS);
1318 gboolean file_data_filter_marks(FileData *fd, guint filter)
1321 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1322 return ((fd->marks & filter) == filter);
1325 GList *file_data_filter_marks_list(GList *list, guint filter)
1332 FileData *fd = work->data;
1336 if (!file_data_filter_marks(fd, filter))
1338 list = g_list_remove_link(list, link);
1339 file_data_unref(fd);
1347 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1349 FileData *fd = value;
1350 file_data_increment_version(fd);
1351 file_data_send_notification(fd, NOTIFY_MARKS);
1354 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1356 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1358 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1360 file_data_get_mark_func[n] = get_mark_func;
1361 file_data_set_mark_func[n] = set_mark_func;
1362 file_data_mark_func_data[n] = data;
1363 file_data_destroy_mark_func[n] = notify;
1367 /* this effectively changes all known files */
1368 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1374 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1376 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1377 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1378 if (data) *data = file_data_mark_func_data[n];
1381 gint file_data_get_user_orientation(FileData *fd)
1383 return fd->user_orientation;
1386 void file_data_set_user_orientation(FileData *fd, gint value)
1388 if (fd->user_orientation == value) return;
1390 fd->user_orientation = value;
1391 file_data_increment_version(fd);
1392 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1397 * file_data - operates on the given fd
1398 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1402 /* return list of sidecar file extensions in a string */
1403 gchar *file_data_sc_list_to_string(FileData *fd)
1406 GString *result = g_string_new("");
1408 work = fd->sidecar_files;
1411 FileData *sfd = work->data;
1413 result = g_string_append(result, "+ ");
1414 result = g_string_append(result, sfd->extension);
1416 if (work) result = g_string_append_c(result, ' ');
1419 return g_string_free(result, FALSE);
1425 * add FileDataChangeInfo (see typedefs.h) for the given operation
1426 * uses file_data_add_change_info
1428 * fails if the fd->change already exists - change operations can't run in parallel
1429 * fd->change_info works as a lock
1431 * dest can be NULL - in this case the current name is used for now, it will
1436 FileDataChangeInfo types:
1438 MOVE - path is changed, name may be changed too
1439 RENAME - path remains unchanged, name is changed
1440 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1441 sidecar names are changed too, extensions are not changed
1443 UPDATE - file size, date or grouping has been changed
1446 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1448 FileDataChangeInfo *fdci;
1450 if (fd->change) return FALSE;
1452 fdci = g_new0(FileDataChangeInfo, 1);
1457 fdci->source = g_strdup(src);
1459 fdci->source = g_strdup(fd->path);
1462 fdci->dest = g_strdup(dest);
1469 static void file_data_planned_change_remove(FileData *fd)
1471 if (file_data_planned_change_hash &&
1472 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1474 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1476 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1477 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1478 file_data_unref(fd);
1479 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1481 g_hash_table_destroy(file_data_planned_change_hash);
1482 file_data_planned_change_hash = NULL;
1483 DEBUG_1("planned change: empty");
1490 void file_data_free_ci(FileData *fd)
1492 FileDataChangeInfo *fdci = fd->change;
1496 file_data_planned_change_remove(fd);
1498 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1500 g_free(fdci->source);
1508 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1510 FileDataChangeInfo *fdci = fd->change;
1512 fdci->regroup_when_finished = enable;
1515 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1519 if (fd->parent) fd = fd->parent;
1521 if (fd->change) return FALSE;
1523 work = fd->sidecar_files;
1526 FileData *sfd = work->data;
1528 if (sfd->change) return FALSE;
1532 file_data_add_ci(fd, type, NULL, NULL);
1534 work = fd->sidecar_files;
1537 FileData *sfd = work->data;
1539 file_data_add_ci(sfd, type, NULL, NULL);
1546 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1550 if (fd->parent) fd = fd->parent;
1552 if (!fd->change || fd->change->type != type) return FALSE;
1554 work = fd->sidecar_files;
1557 FileData *sfd = work->data;
1559 if (!sfd->change || sfd->change->type != type) return FALSE;
1567 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1569 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1570 file_data_sc_update_ci_copy(fd, dest_path);
1574 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1576 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1577 file_data_sc_update_ci_move(fd, dest_path);
1581 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1583 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1584 file_data_sc_update_ci_rename(fd, dest_path);
1588 gboolean file_data_sc_add_ci_delete(FileData *fd)
1590 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1593 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1595 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1596 file_data_sc_update_ci_unspecified(fd, dest_path);
1600 gboolean file_data_add_ci_write_metadata(FileData *fd)
1602 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1605 void file_data_sc_free_ci(FileData *fd)
1609 if (fd->parent) fd = fd->parent;
1611 file_data_free_ci(fd);
1613 work = fd->sidecar_files;
1616 FileData *sfd = work->data;
1618 file_data_free_ci(sfd);
1623 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1626 gboolean ret = TRUE;
1631 FileData *fd = work->data;
1633 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1640 static void file_data_sc_revert_ci_list(GList *fd_list)
1647 FileData *fd = work->data;
1649 file_data_sc_free_ci(fd);
1654 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1661 FileData *fd = work->data;
1663 if (!func(fd, dest))
1665 file_data_sc_revert_ci_list(work->prev);
1674 gboolean file_data_sc_add_ci_copy_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_copy);
1679 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1681 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1684 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1686 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1689 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1691 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1694 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1697 gboolean ret = TRUE;
1702 FileData *fd = work->data;
1704 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1711 void file_data_free_ci_list(GList *fd_list)
1718 FileData *fd = work->data;
1720 file_data_free_ci(fd);
1725 void file_data_sc_free_ci_list(GList *fd_list)
1732 FileData *fd = work->data;
1734 file_data_sc_free_ci(fd);
1740 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1741 * fails if fd->change does not exist or the change type does not match
1744 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1746 FileDataChangeType type = fd->change->type;
1748 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1752 if (!file_data_planned_change_hash)
1753 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1755 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1757 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1758 g_hash_table_remove(file_data_planned_change_hash, old_path);
1759 file_data_unref(fd);
1762 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1767 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1768 g_hash_table_remove(file_data_planned_change_hash, new_path);
1769 file_data_unref(ofd);
1772 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1774 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1779 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1781 gchar *old_path = fd->change->dest;
1783 fd->change->dest = g_strdup(dest_path);
1784 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1788 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1790 const gchar *extension = extension_from_path(fd->change->source);
1791 gchar *base = remove_extension_from_path(dest_path);
1792 gchar *old_path = fd->change->dest;
1794 fd->change->dest = g_strconcat(base, extension, NULL);
1795 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1801 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1804 gchar *dest_path_full = NULL;
1806 if (fd->parent) fd = fd->parent;
1810 dest_path = fd->path;
1812 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1814 gchar *dir = remove_level_from_path(fd->path);
1816 dest_path_full = g_build_filename(dir, dest_path, NULL);
1818 dest_path = dest_path_full;
1820 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1822 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1823 dest_path = dest_path_full;
1826 file_data_update_ci_dest(fd, dest_path);
1828 work = fd->sidecar_files;
1831 FileData *sfd = work->data;
1833 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1837 g_free(dest_path_full);
1840 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1842 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1843 file_data_sc_update_ci(fd, dest_path);
1847 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1849 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1852 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1854 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1857 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1859 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1862 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1864 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1867 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1869 gboolean (*func)(FileData *, const gchar *))
1872 gboolean ret = TRUE;
1877 FileData *fd = work->data;
1879 if (!func(fd, dest)) ret = FALSE;
1886 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1888 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1891 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1893 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1896 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1898 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1903 * verify source and dest paths - dest image exists, etc.
1904 * it should detect all possible problems with the planned operation
1907 gint file_data_verify_ci(FileData *fd)
1909 gint ret = CHANGE_OK;
1914 DEBUG_1("Change checked: no change info: %s", fd->path);
1918 if (!isname(fd->path))
1920 /* this probably should not happen */
1921 ret |= CHANGE_NO_SRC;
1922 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1926 dir = remove_level_from_path(fd->path);
1928 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1929 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1930 fd->change->type != FILEDATA_CHANGE_RENAME &&
1931 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1934 ret |= CHANGE_WARN_UNSAVED_META;
1935 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1938 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1939 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1940 !access_file(fd->path, R_OK))
1942 ret |= CHANGE_NO_READ_PERM;
1943 DEBUG_1("Change checked: no read permission: %s", fd->path);
1945 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1946 !access_file(dir, W_OK))
1948 ret |= CHANGE_NO_WRITE_PERM_DIR;
1949 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1951 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1952 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1953 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1954 !access_file(fd->path, W_OK))
1956 ret |= CHANGE_WARN_NO_WRITE_PERM;
1957 DEBUG_1("Change checked: no write permission: %s", fd->path);
1959 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1960 - that means that there are no hard errors and warnings can be disabled
1961 - the destination is determined during the check
1963 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1965 /* determine destination file */
1966 gboolean have_dest = FALSE;
1967 gchar *dest_dir = NULL;
1969 if (options->metadata.save_in_image_file)
1971 if (file_data_can_write_directly(fd))
1973 /* we can write the file directly */
1974 if (access_file(fd->path, W_OK))
1980 if (options->metadata.warn_on_write_problems)
1982 ret |= CHANGE_WARN_NO_WRITE_PERM;
1983 DEBUG_1("Change checked: file is not writable: %s", fd->path);
1987 else if (file_data_can_write_sidecar(fd))
1989 /* we can write sidecar */
1990 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
1991 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
1993 file_data_update_ci_dest(fd, sidecar);
1998 if (options->metadata.warn_on_write_problems)
2000 ret |= CHANGE_WARN_NO_WRITE_PERM;
2001 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2010 /* write private metadata file under ~/.geeqie */
2012 /* If an existing metadata file exists, we will try writing to
2013 * it's location regardless of the user's preference.
2015 gchar *metadata_path = NULL;
2017 /* but ignore XMP if we are not able to write it */
2018 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2020 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2022 if (metadata_path && !access_file(metadata_path, W_OK))
2024 g_free(metadata_path);
2025 metadata_path = NULL;
2032 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2033 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2035 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2037 metadata_path = g_build_filename(dest_dir, filename, NULL);
2041 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2043 file_data_update_ci_dest(fd, metadata_path);
2048 ret |= CHANGE_NO_WRITE_PERM_DEST;
2049 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2051 g_free(metadata_path);
2056 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2061 same = (strcmp(fd->path, fd->change->dest) == 0);
2065 const gchar *dest_ext = extension_from_path(fd->change->dest);
2066 if (!dest_ext) dest_ext = "";
2068 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2070 ret |= CHANGE_WARN_CHANGED_EXT;
2071 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2076 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2078 ret |= CHANGE_WARN_SAME;
2079 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2083 dest_dir = remove_level_from_path(fd->change->dest);
2085 if (!isdir(dest_dir))
2087 ret |= CHANGE_NO_DEST_DIR;
2088 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2090 else if (!access_file(dest_dir, W_OK))
2092 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2093 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2097 if (isfile(fd->change->dest))
2099 if (!access_file(fd->change->dest, W_OK))
2101 ret |= CHANGE_NO_WRITE_PERM_DEST;
2102 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2106 ret |= CHANGE_WARN_DEST_EXISTS;
2107 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2110 else if (isdir(fd->change->dest))
2112 ret |= CHANGE_DEST_EXISTS;
2113 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2120 fd->change->error = ret;
2121 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2128 gint file_data_sc_verify_ci(FileData *fd)
2133 ret = file_data_verify_ci(fd);
2135 work = fd->sidecar_files;
2138 FileData *sfd = work->data;
2140 ret |= file_data_verify_ci(sfd);
2147 gchar *file_data_get_error_string(gint error)
2149 GString *result = g_string_new("");
2151 if (error & CHANGE_NO_SRC)
2153 if (result->len > 0) g_string_append(result, ", ");
2154 g_string_append(result, _("file or directory does not exist"));
2157 if (error & CHANGE_DEST_EXISTS)
2159 if (result->len > 0) g_string_append(result, ", ");
2160 g_string_append(result, _("destination already exists"));
2163 if (error & CHANGE_NO_WRITE_PERM_DEST)
2165 if (result->len > 0) g_string_append(result, ", ");
2166 g_string_append(result, _("destination can't be overwritten"));
2169 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2171 if (result->len > 0) g_string_append(result, ", ");
2172 g_string_append(result, _("destination directory is not writable"));
2175 if (error & CHANGE_NO_DEST_DIR)
2177 if (result->len > 0) g_string_append(result, ", ");
2178 g_string_append(result, _("destination directory does not exist"));
2181 if (error & CHANGE_NO_WRITE_PERM_DIR)
2183 if (result->len > 0) g_string_append(result, ", ");
2184 g_string_append(result, _("source directory is not writable"));
2187 if (error & CHANGE_NO_READ_PERM)
2189 if (result->len > 0) g_string_append(result, ", ");
2190 g_string_append(result, _("no read permission"));
2193 if (error & CHANGE_WARN_NO_WRITE_PERM)
2195 if (result->len > 0) g_string_append(result, ", ");
2196 g_string_append(result, _("file is readonly"));
2199 if (error & CHANGE_WARN_DEST_EXISTS)
2201 if (result->len > 0) g_string_append(result, ", ");
2202 g_string_append(result, _("destination already exists and will be overwritten"));
2205 if (error & CHANGE_WARN_SAME)
2207 if (result->len > 0) g_string_append(result, ", ");
2208 g_string_append(result, _("source and destination are the same"));
2211 if (error & CHANGE_WARN_CHANGED_EXT)
2213 if (result->len > 0) g_string_append(result, ", ");
2214 g_string_append(result, _("source and destination have different extension"));
2217 if (error & CHANGE_WARN_UNSAVED_META)
2219 if (result->len > 0) g_string_append(result, ", ");
2220 g_string_append(result, _("there are unsaved metadata changes for the file"));
2223 return g_string_free(result, FALSE);
2226 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2229 gint all_errors = 0;
2230 gint common_errors = ~0;
2235 if (!list) return 0;
2237 num = g_list_length(list);
2238 errors = g_new(int, num);
2249 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2250 all_errors |= error;
2251 common_errors &= error;
2258 if (desc && all_errors)
2261 GString *result = g_string_new("");
2265 gchar *str = file_data_get_error_string(common_errors);
2266 g_string_append(result, str);
2267 g_string_append(result, "\n");
2281 error = errors[i] & ~common_errors;
2285 gchar *str = file_data_get_error_string(error);
2286 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2291 *desc = g_string_free(result, FALSE);
2300 * perform the change described by FileFataChangeInfo
2301 * it is used for internal operations,
2302 * this function actually operates with files on the filesystem
2303 * it should implement safe delete
2306 static gboolean file_data_perform_move(FileData *fd)
2308 g_assert(!strcmp(fd->change->source, fd->path));
2309 return move_file(fd->change->source, fd->change->dest);
2312 static gboolean file_data_perform_copy(FileData *fd)
2314 g_assert(!strcmp(fd->change->source, fd->path));
2315 return copy_file(fd->change->source, fd->change->dest);
2318 static gboolean file_data_perform_delete(FileData *fd)
2320 if (isdir(fd->path) && !islink(fd->path))
2321 return rmdir_utf8(fd->path);
2323 if (options->file_ops.safe_delete_enable)
2324 return file_util_safe_unlink(fd->path);
2326 return unlink_file(fd->path);
2329 gboolean file_data_perform_ci(FileData *fd)
2331 FileDataChangeType type = fd->change->type;
2335 case FILEDATA_CHANGE_MOVE:
2336 return file_data_perform_move(fd);
2337 case FILEDATA_CHANGE_COPY:
2338 return file_data_perform_copy(fd);
2339 case FILEDATA_CHANGE_RENAME:
2340 return file_data_perform_move(fd); /* the same as move */
2341 case FILEDATA_CHANGE_DELETE:
2342 return file_data_perform_delete(fd);
2343 case FILEDATA_CHANGE_WRITE_METADATA:
2344 return metadata_write_perform(fd);
2345 case FILEDATA_CHANGE_UNSPECIFIED:
2346 /* nothing to do here */
2354 gboolean file_data_sc_perform_ci(FileData *fd)
2357 gboolean ret = TRUE;
2358 FileDataChangeType type = fd->change->type;
2360 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2362 work = fd->sidecar_files;
2365 FileData *sfd = work->data;
2367 if (!file_data_perform_ci(sfd)) ret = FALSE;
2371 if (!file_data_perform_ci(fd)) ret = FALSE;
2377 * updates FileData structure according to FileDataChangeInfo
2380 gboolean file_data_apply_ci(FileData *fd)
2382 FileDataChangeType type = fd->change->type;
2385 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2387 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2388 file_data_planned_change_remove(fd);
2390 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2392 /* this change overwrites another file which is already known to other modules
2393 renaming fd would create duplicate FileData structure
2394 the best thing we can do is nothing
2395 FIXME: maybe we could copy stuff like marks
2397 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2401 file_data_set_path(fd, fd->change->dest, NULL);
2404 file_data_increment_version(fd);
2405 file_data_send_notification(fd, NOTIFY_CHANGE);
2410 gboolean file_data_sc_apply_ci(FileData *fd)
2413 FileDataChangeType type = fd->change->type;
2415 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2417 work = fd->sidecar_files;
2420 FileData *sfd = work->data;
2422 file_data_apply_ci(sfd);
2426 file_data_apply_ci(fd);
2431 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2434 if (fd->parent) fd = fd->parent;
2435 if (!g_list_find(list, fd)) return FALSE;
2437 work = fd->sidecar_files;
2440 if (!g_list_find(list, work->data)) return FALSE;
2447 static gboolean file_data_list_dump(GList *list)
2449 GList *work, *work2;
2454 FileData *fd = work->data;
2455 printf("%s\n", fd->name);
2456 work2 = fd->sidecar_files;
2459 FileData *fd = work2->data;
2460 printf(" %s\n", fd->name);
2461 work2 = work2->next;
2469 GList *file_data_process_groups_in_selection(GList *list, GList **ungrouped_list)
2474 /* change partial groups to independent files */
2477 FileData *fd = work->data;
2480 if (!file_data_list_contains_whole_group(list, fd))
2482 file_data_disable_grouping(fd, TRUE);
2485 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2490 /* remove sidecars from the list,
2491 they can be still acessed via main_fd->sidecar_files */
2495 FileData *fd = work->data;
2500 out = g_list_prepend(out, fd);
2504 file_data_unref(fd);
2509 out = g_list_reverse(out);
2519 * notify other modules about the change described by FileDataChangeInfo
2522 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2523 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2524 implementation in view_file_list.c */
2529 typedef struct _NotifyData NotifyData;
2531 struct _NotifyData {
2532 FileDataNotifyFunc func;
2534 NotifyPriority priority;
2537 static GList *notify_func_list = NULL;
2539 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2541 NotifyData *nda = (NotifyData *)a;
2542 NotifyData *ndb = (NotifyData *)b;
2544 if (nda->priority < ndb->priority) return -1;
2545 if (nda->priority > ndb->priority) return 1;
2549 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2553 nd = g_new(NotifyData, 1);
2556 nd->priority = priority;
2558 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2559 DEBUG_2("Notify func registered: %p", nd);
2564 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2566 GList *work = notify_func_list;
2570 NotifyData *nd = (NotifyData *)work->data;
2572 if (nd->func == func && nd->data == data)
2574 notify_func_list = g_list_delete_link(notify_func_list, work);
2576 DEBUG_2("Notify func unregistered: %p", nd);
2586 void file_data_send_notification(FileData *fd, NotifyType type)
2588 GList *work = notify_func_list;
2592 NotifyData *nd = (NotifyData *)work->data;
2594 nd->func(fd, type, nd->data);
2599 static GHashTable *file_data_monitor_pool = NULL;
2600 static guint realtime_monitor_id = 0; /* event source id */
2602 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2606 file_data_check_changed_files(fd);
2608 DEBUG_1("monitor %s", fd->path);
2611 static gboolean realtime_monitor_cb(gpointer data)
2613 if (!options->update_on_time_change) return TRUE;
2614 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2618 gboolean file_data_register_real_time_monitor(FileData *fd)
2624 if (!file_data_monitor_pool)
2625 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2627 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2629 DEBUG_1("Register realtime %d %s", count, fd->path);
2632 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2634 if (!realtime_monitor_id)
2636 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2642 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2646 g_assert(file_data_monitor_pool);
2648 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2650 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2652 g_assert(count > 0);
2657 g_hash_table_remove(file_data_monitor_pool, fd);
2659 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2661 file_data_unref(fd);
2663 if (g_hash_table_size(file_data_monitor_pool) == 0)
2665 g_source_remove(realtime_monitor_id);
2666 realtime_monitor_id = 0;
2672 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */