4 * Copyright (C) 2008 - 2010 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"
27 static GHashTable *file_data_pool = NULL;
28 static GHashTable *file_data_planned_change_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, GHashTable *basename_hash);
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 %X", 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(const GList *basename_list);
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 GHashTable *file_data_basename_hash_new(void)
166 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
169 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
172 const gchar *ext = extension_from_path(fd->path);
173 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
175 list = g_hash_table_lookup(basename_hash, basename);
177 if (!g_list_find(list, fd))
179 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
180 g_hash_table_insert(basename_hash, basename, list);
190 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
193 const gchar *ext = extension_from_path(fd->path);
194 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
196 list = g_hash_table_lookup(basename_hash, basename);
198 if (!g_list_find(list, fd)) return;
200 list = g_list_remove(list, fd);
205 g_hash_table_insert(basename_hash, basename, list);
209 g_hash_table_remove(basename_hash, basename);
215 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
217 filelist_free((GList *)value);
220 static void file_data_basename_hash_free(GHashTable *basename_hash)
222 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
223 g_hash_table_destroy(basename_hash);
226 static void file_data_set_collate_keys(FileData *fd)
228 gchar *caseless_name;
230 caseless_name = g_utf8_casefold(fd->name, -1);
232 g_free(fd->collate_key_name);
233 g_free(fd->collate_key_name_nocase);
235 #if 0 && GLIB_CHECK_VERSION(2, 8, 0)
236 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
237 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
239 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
240 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
242 g_free(caseless_name);
245 static void file_data_set_path(FileData *fd, const gchar *path)
247 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
248 g_assert(file_data_pool);
252 if (fd->original_path)
254 g_hash_table_remove(file_data_pool, fd->original_path);
255 g_free(fd->original_path);
258 g_assert(!g_hash_table_lookup(file_data_pool, path));
260 fd->original_path = g_strdup(path);
261 g_hash_table_insert(file_data_pool, fd->original_path, fd);
263 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
265 fd->path = g_strdup(path);
267 fd->extension = fd->name + 1;
268 file_data_set_collate_keys(fd);
272 fd->path = g_strdup(path);
273 fd->name = filename_from_path(fd->path);
275 if (strcmp(fd->name, "..") == 0)
277 gchar *dir = remove_level_from_path(path);
279 fd->path = remove_level_from_path(dir);
282 fd->extension = fd->name + 2;
283 file_data_set_collate_keys(fd);
286 else if (strcmp(fd->name, ".") == 0)
289 fd->path = remove_level_from_path(path);
291 fd->extension = fd->name + 1;
292 file_data_set_collate_keys(fd);
296 fd->extension = extension_from_path(fd->path);
297 if (fd->extension == NULL)
299 fd->extension = fd->name + strlen(fd->name);
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))
356 FileData *sfd = NULL;
358 /* parent is missing, we have to rebuild whole group */
363 /* file_data_disconnect_sidecar_file might delete the file,
364 we have to keep the reference to prevent this */
365 sidecars = filelist_copy(fd->sidecar_files);
372 file_data_disconnect_sidecar_file(fd, sfd);
374 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
375 /* now we can release the sidecars */
376 filelist_free(sidecars);
377 file_data_send_notification(fd, NOTIFY_REREAD);
381 ret |= file_data_check_changed_files_recursive(fd, &st);
387 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars, GHashTable *basename_hash)
391 DEBUG_2("file_data_new: '%s' %d %d", path_utf8, disable_sidecars, !!basename_hash);
393 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
396 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
398 fd = g_hash_table_lookup(file_data_pool, path_utf8);
404 if (!fd && file_data_planned_change_hash)
406 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
409 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
411 file_data_apply_ci(fd);
419 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
423 changed = file_data_check_changed_files(fd);
425 changed = file_data_check_changed_files_recursive(fd, st);
429 GList *list = file_data_basename_hash_insert(basename_hash, fd);
430 if (!disable_sidecars)
431 file_data_check_sidecars(list);
433 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
438 fd = g_new0(FileData, 1);
440 fd->size = st->st_size;
441 fd->date = st->st_mtime;
442 fd->mode = st->st_mode;
444 fd->magick = 0x12345678;
446 if (disable_sidecars) fd->disable_grouping = TRUE;
448 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
450 if (!disable_sidecars && !fd->disable_grouping && sidecar_file_priority(fd->extension))
452 GList *list = file_data_basename_hash_insert(basename_hash, fd);
453 file_data_check_sidecars(list);
460 static void file_data_check_sidecars(const GList *basename_list)
462 FileData *parent_fd = NULL;
464 GList *group_list = NULL;
466 /* check for possible sidecar files;
467 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
468 they have fd->ref set to 0 and file_data unref must chack and free them all together
469 (using fd->ref would cause loops and leaks)
472 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
473 for case-only differences put lowercase first,
474 put the result to group_list
476 work = sidecar_ext_get_list();
479 gchar *ext = work->data;
482 const GList *work2 = basename_list;
486 FileData *sfd = work2->data;
488 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
490 group_list = g_list_append(group_list, file_data_ref(sfd));
496 /* process the group list - the first one is the parent file, others are sidecars */
500 FileData *new_fd = work->data;
503 if (new_fd->disable_grouping)
505 file_data_unref(new_fd);
509 new_fd->ref--; /* do not use ref here */
512 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
514 file_data_merge_sidecar_files(parent_fd, new_fd);
516 g_list_free(group_list);
520 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars, GHashTable *basename_hash)
522 gchar *path_utf8 = path_to_utf8(path);
523 FileData *ret = file_data_new(path_utf8, st, disable_sidecars, basename_hash);
529 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
531 sfd->parent = target;
532 if (!g_list_find(target->sidecar_files, sfd))
533 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
534 file_data_increment_version(sfd); /* increments both sfd and target */
539 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
543 file_data_add_sidecar_file(target, source);
545 work = source->sidecar_files;
548 FileData *sfd = work->data;
549 file_data_add_sidecar_file(target, sfd);
553 g_list_free(source->sidecar_files);
554 source->sidecar_files = NULL;
556 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
561 #ifdef DEBUG_FILEDATA
562 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
564 FileData *file_data_ref(FileData *fd)
567 if (fd == NULL) return NULL;
568 #ifdef DEBUG_FILEDATA
569 if (fd->magick != 0x12345678)
570 DEBUG_0("fd magick mismatch at %s:%d", file, line);
572 g_assert(fd->magick == 0x12345678);
575 #ifdef DEBUG_FILEDATA
576 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
578 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
583 static void file_data_free(FileData *fd)
585 g_assert(fd->magick == 0x12345678);
586 g_assert(fd->ref == 0);
588 metadata_cache_free(fd);
589 g_hash_table_remove(file_data_pool, fd->original_path);
592 g_free(fd->original_path);
593 g_free(fd->collate_key_name);
594 g_free(fd->collate_key_name_nocase);
595 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
596 histmap_free(fd->histmap);
598 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
600 file_data_change_info_free(NULL, fd);
604 #ifdef DEBUG_FILEDATA
605 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
607 void file_data_unref(FileData *fd)
610 if (fd == NULL) return;
611 #ifdef DEBUG_FILEDATA
612 if (fd->magick != 0x12345678)
613 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
615 g_assert(fd->magick == 0x12345678);
618 #ifdef DEBUG_FILEDATA
619 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
621 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
626 FileData *parent = fd->parent ? fd->parent : fd;
628 if (parent->ref > 0) return;
630 work = parent->sidecar_files;
633 FileData *sfd = work->data;
634 if (sfd->ref > 0) return;
638 /* none of parent/children is referenced, we can free everything */
640 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
642 work = parent->sidecar_files;
645 FileData *sfd = work->data;
650 g_list_free(parent->sidecar_files);
651 parent->sidecar_files = NULL;
653 file_data_free(parent);
657 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
659 sfd->parent = target;
660 g_assert(g_list_find(target->sidecar_files, sfd));
662 file_data_increment_version(sfd); /* increments both sfd and target */
664 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
676 /* disables / enables grouping for particular file, sends UPDATE notification */
677 void file_data_disable_grouping(FileData *fd, gboolean disable)
679 if (!fd->disable_grouping == !disable) return;
681 fd->disable_grouping = !!disable;
687 FileData *parent = file_data_ref(fd->parent);
688 file_data_disconnect_sidecar_file(parent, fd);
689 file_data_send_notification(parent, NOTIFY_GROUPING);
690 file_data_unref(parent);
692 else if (fd->sidecar_files)
694 GList *sidecar_files = filelist_copy(fd->sidecar_files);
695 GList *work = sidecar_files;
698 FileData *sfd = work->data;
700 file_data_disconnect_sidecar_file(fd, sfd);
701 file_data_send_notification(sfd, NOTIFY_GROUPING);
703 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
704 filelist_free(sidecar_files);
708 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
713 file_data_increment_version(fd);
714 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
716 file_data_send_notification(fd, NOTIFY_GROUPING);
719 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
726 FileData *fd = work->data;
728 file_data_disable_grouping(fd, disable);
734 /* compare name without extension */
735 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
737 size_t len1 = fd1->extension - fd1->name;
738 size_t len2 = fd2->extension - fd2->name;
740 if (len1 < len2) return -1;
741 if (len1 > len2) return 1;
743 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
746 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
748 if (!fdci && fd) fdci = fd->change;
752 g_free(fdci->source);
757 if (fd) fd->change = NULL;
760 static gboolean file_data_can_write_directly(FileData *fd)
762 return filter_name_is_writable(fd->extension);
765 static gboolean file_data_can_write_sidecar(FileData *fd)
767 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
770 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
772 gchar *sidecar_path = NULL;
775 if (!file_data_can_write_sidecar(fd)) return NULL;
777 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
780 FileData *sfd = work->data;
782 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
784 sidecar_path = g_strdup(sfd->path);
789 if (!existing_only && !sidecar_path)
791 gchar *base = remove_extension_from_path(fd->path);
792 sidecar_path = g_strconcat(base, ".xmp", NULL);
801 *-----------------------------------------------------------------------------
802 * sidecar file info struct
803 *-----------------------------------------------------------------------------
808 static gint sidecar_file_priority(const gchar *path)
810 const gchar *extension = extension_from_path(path);
814 if (extension == NULL)
817 work = sidecar_ext_get_list();
820 gchar *ext = work->data;
823 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
831 *-----------------------------------------------------------------------------
833 *-----------------------------------------------------------------------------
836 static SortType filelist_sort_method = SORT_NONE;
837 static gboolean filelist_sort_ascend = TRUE;
840 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
843 if (!filelist_sort_ascend)
850 switch (filelist_sort_method)
855 if (fa->size < fb->size) return -1;
856 if (fa->size > fb->size) return 1;
857 /* fall back to name */
860 if (fa->date < fb->date) return -1;
861 if (fa->date > fb->date) return 1;
862 /* fall back to name */
864 #ifdef HAVE_STRVERSCMP
866 ret = strverscmp(fa->name, fb->name);
867 if (ret != 0) return ret;
874 if (options->file_sort.case_sensitive)
875 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
877 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
879 if (ret != 0) return ret;
881 /* do not return 0 unless the files are really the same
882 file_data_pool ensures that original_path is unique
884 return strcmp(fa->original_path, fb->original_path);
887 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
889 filelist_sort_method = method;
890 filelist_sort_ascend = ascend;
891 return filelist_sort_compare_filedata(fa, fb);
894 static gint filelist_sort_file_cb(gpointer a, gpointer b)
896 return filelist_sort_compare_filedata(a, b);
899 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
901 filelist_sort_method = method;
902 filelist_sort_ascend = ascend;
903 return g_list_sort(list, cb);
906 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
908 filelist_sort_method = method;
909 filelist_sort_ascend = ascend;
910 return g_list_insert_sorted(list, data, cb);
913 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
915 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
918 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
920 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
924 static GList *filelist_filter_out_sidecars(GList *flist)
927 GList *flist_filtered = NULL;
931 FileData *fd = work->data;
934 if (fd->parent) /* remove fd's that are children */
937 flist_filtered = g_list_prepend(flist_filtered, fd);
941 return flist_filtered;
944 static gboolean is_hidden_file(const gchar *name)
946 if (name[0] != '.') return FALSE;
947 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
951 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
958 gint (*stat_func)(const gchar *path, struct stat *buf);
959 GHashTable *basename_hash = NULL;
961 g_assert(files || dirs);
963 if (files) *files = NULL;
964 if (dirs) *dirs = NULL;
966 pathl = path_from_utf8(dir_path);
967 if (!pathl) return FALSE;
976 if (files) basename_hash = file_data_basename_hash_new();
983 while ((dir = readdir(dp)) != NULL)
985 struct stat ent_sbuf;
986 const gchar *name = dir->d_name;
989 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
992 filepath = g_build_filename(pathl, name, NULL);
993 if (stat_func(filepath, &ent_sbuf) >= 0)
995 if (S_ISDIR(ent_sbuf.st_mode))
997 /* we ignore the .thumbnails dir for cleanliness */
999 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1000 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1001 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1002 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1004 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE, NULL));
1009 if (files && filter_name_exists(name))
1011 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, FALSE, basename_hash));
1017 if (errno == EOVERFLOW)
1019 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1028 if (basename_hash) file_data_basename_hash_free(basename_hash);
1030 if (dirs) *dirs = dlist;
1031 if (files) *files = filelist_filter_out_sidecars(flist);
1036 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1038 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1041 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1043 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1046 FileData *file_data_new_simple(const gchar *path_utf8)
1053 if (!stat_utf8(path_utf8, &st))
1059 if (S_ISDIR(st.st_mode))
1060 return file_data_new(path_utf8, &st, TRUE, NULL);
1062 dir = remove_level_from_path(path_utf8);
1064 filelist_read_real(dir, &files, NULL, TRUE);
1066 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1070 filelist_free(files);
1075 FileData *file_data_new_no_grouping(const gchar *path_utf8)
1079 if (!stat_utf8(path_utf8, &st))
1085 return file_data_new(path_utf8, &st, TRUE, NULL);
1088 FileData *file_data_new_dir(const gchar *path_utf8)
1092 if (!stat_utf8(path_utf8, &st))
1098 g_assert(S_ISDIR(st.st_mode));
1099 return file_data_new(path_utf8, &st, TRUE, NULL);
1102 void filelist_free(GList *list)
1109 file_data_unref((FileData *)work->data);
1117 GList *filelist_copy(GList *list)
1119 GList *new_list = NULL;
1130 new_list = g_list_prepend(new_list, file_data_ref(fd));
1133 return g_list_reverse(new_list);
1136 GList *filelist_from_path_list(GList *list)
1138 GList *new_list = NULL;
1149 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1152 return g_list_reverse(new_list);
1155 GList *filelist_to_path_list(GList *list)
1157 GList *new_list = NULL;
1168 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1171 return g_list_reverse(new_list);
1174 GList *filelist_filter(GList *list, gboolean is_dir_list)
1178 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1183 FileData *fd = (FileData *)(work->data);
1184 const gchar *name = fd->name;
1186 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1187 (!is_dir_list && !filter_name_exists(name)) ||
1188 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1189 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1193 list = g_list_remove_link(list, link);
1194 file_data_unref(fd);
1205 *-----------------------------------------------------------------------------
1206 * filelist recursive
1207 *-----------------------------------------------------------------------------
1210 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1212 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1215 GList *filelist_sort_path(GList *list)
1217 return g_list_sort(list, filelist_sort_path_cb);
1220 static void filelist_recursive_append(GList **list, GList *dirs)
1227 FileData *fd = (FileData *)(work->data);
1231 if (filelist_read(fd, &f, &d))
1233 f = filelist_filter(f, FALSE);
1234 f = filelist_sort_path(f);
1235 *list = g_list_concat(*list, f);
1237 d = filelist_filter(d, TRUE);
1238 d = filelist_sort_path(d);
1239 filelist_recursive_append(list, d);
1247 GList *filelist_recursive(FileData *dir_fd)
1252 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1253 list = filelist_filter(list, FALSE);
1254 list = filelist_sort_path(list);
1256 d = filelist_filter(d, TRUE);
1257 d = filelist_sort_path(d);
1258 filelist_recursive_append(&list, d);
1266 * marks and orientation
1269 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1270 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1271 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1272 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1274 gboolean file_data_get_mark(FileData *fd, gint n)
1276 gboolean valid = (fd->valid_marks & (1 << n));
1278 if (file_data_get_mark_func[n] && !valid)
1280 guint old = fd->marks;
1281 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1283 if (!value != !(fd->marks & (1 << n)))
1285 fd->marks = fd->marks ^ (1 << n);
1288 fd->valid_marks |= (1 << n);
1289 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1291 file_data_unref(fd);
1293 else if (!old && fd->marks)
1299 return !!(fd->marks & (1 << n));
1302 guint file_data_get_marks(FileData *fd)
1305 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1309 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1312 if (!value == !file_data_get_mark(fd, n)) return;
1314 if (file_data_set_mark_func[n])
1316 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1321 fd->marks = fd->marks ^ (1 << n);
1323 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1325 file_data_unref(fd);
1327 else if (!old && fd->marks)
1332 file_data_increment_version(fd);
1333 file_data_send_notification(fd, NOTIFY_MARKS);
1336 gboolean file_data_filter_marks(FileData *fd, guint filter)
1339 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1340 return ((fd->marks & filter) == filter);
1343 GList *file_data_filter_marks_list(GList *list, guint filter)
1350 FileData *fd = work->data;
1354 if (!file_data_filter_marks(fd, filter))
1356 list = g_list_remove_link(list, link);
1357 file_data_unref(fd);
1365 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1367 FileData *fd = value;
1368 file_data_increment_version(fd);
1369 file_data_send_notification(fd, NOTIFY_MARKS);
1372 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1374 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1376 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1378 file_data_get_mark_func[n] = get_mark_func;
1379 file_data_set_mark_func[n] = set_mark_func;
1380 file_data_mark_func_data[n] = data;
1381 file_data_destroy_mark_func[n] = notify;
1385 /* this effectively changes all known files */
1386 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1392 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1394 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1395 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1396 if (data) *data = file_data_mark_func_data[n];
1399 gint file_data_get_user_orientation(FileData *fd)
1401 return fd->user_orientation;
1404 void file_data_set_user_orientation(FileData *fd, gint value)
1406 if (fd->user_orientation == value) return;
1408 fd->user_orientation = value;
1409 file_data_increment_version(fd);
1410 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1415 * file_data - operates on the given fd
1416 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1420 /* return list of sidecar file extensions in a string */
1421 gchar *file_data_sc_list_to_string(FileData *fd)
1424 GString *result = g_string_new("");
1426 work = fd->sidecar_files;
1429 FileData *sfd = work->data;
1431 result = g_string_append(result, "+ ");
1432 result = g_string_append(result, sfd->extension);
1434 if (work) result = g_string_append_c(result, ' ');
1437 return g_string_free(result, FALSE);
1443 * add FileDataChangeInfo (see typedefs.h) for the given operation
1444 * uses file_data_add_change_info
1446 * fails if the fd->change already exists - change operations can't run in parallel
1447 * fd->change_info works as a lock
1449 * dest can be NULL - in this case the current name is used for now, it will
1454 FileDataChangeInfo types:
1456 MOVE - path is changed, name may be changed too
1457 RENAME - path remains unchanged, name is changed
1458 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1459 sidecar names are changed too, extensions are not changed
1461 UPDATE - file size, date or grouping has been changed
1464 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1466 FileDataChangeInfo *fdci;
1468 if (fd->change) return FALSE;
1470 fdci = g_new0(FileDataChangeInfo, 1);
1475 fdci->source = g_strdup(src);
1477 fdci->source = g_strdup(fd->path);
1480 fdci->dest = g_strdup(dest);
1487 static void file_data_planned_change_remove(FileData *fd)
1489 if (file_data_planned_change_hash &&
1490 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1492 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1494 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1495 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1496 file_data_unref(fd);
1497 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1499 g_hash_table_destroy(file_data_planned_change_hash);
1500 file_data_planned_change_hash = NULL;
1501 DEBUG_1("planned change: empty");
1508 void file_data_free_ci(FileData *fd)
1510 FileDataChangeInfo *fdci = fd->change;
1514 file_data_planned_change_remove(fd);
1516 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1518 g_free(fdci->source);
1526 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1528 FileDataChangeInfo *fdci = fd->change;
1530 fdci->regroup_when_finished = enable;
1533 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1537 if (fd->parent) fd = fd->parent;
1539 if (fd->change) return FALSE;
1541 work = fd->sidecar_files;
1544 FileData *sfd = work->data;
1546 if (sfd->change) return FALSE;
1550 file_data_add_ci(fd, type, NULL, NULL);
1552 work = fd->sidecar_files;
1555 FileData *sfd = work->data;
1557 file_data_add_ci(sfd, type, NULL, NULL);
1564 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1568 if (fd->parent) fd = fd->parent;
1570 if (!fd->change || fd->change->type != type) return FALSE;
1572 work = fd->sidecar_files;
1575 FileData *sfd = work->data;
1577 if (!sfd->change || sfd->change->type != type) return FALSE;
1585 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1587 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1588 file_data_sc_update_ci_copy(fd, dest_path);
1592 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1594 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1595 file_data_sc_update_ci_move(fd, dest_path);
1599 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1601 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1602 file_data_sc_update_ci_rename(fd, dest_path);
1606 gboolean file_data_sc_add_ci_delete(FileData *fd)
1608 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1611 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1613 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1614 file_data_sc_update_ci_unspecified(fd, dest_path);
1618 gboolean file_data_add_ci_write_metadata(FileData *fd)
1620 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1623 void file_data_sc_free_ci(FileData *fd)
1627 if (fd->parent) fd = fd->parent;
1629 file_data_free_ci(fd);
1631 work = fd->sidecar_files;
1634 FileData *sfd = work->data;
1636 file_data_free_ci(sfd);
1641 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1644 gboolean ret = TRUE;
1649 FileData *fd = work->data;
1651 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1658 static void file_data_sc_revert_ci_list(GList *fd_list)
1665 FileData *fd = work->data;
1667 file_data_sc_free_ci(fd);
1672 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1679 FileData *fd = work->data;
1681 if (!func(fd, dest))
1683 file_data_sc_revert_ci_list(work->prev);
1692 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1694 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1697 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1699 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1702 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1704 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1707 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1709 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1712 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1715 gboolean ret = TRUE;
1720 FileData *fd = work->data;
1722 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1729 void file_data_free_ci_list(GList *fd_list)
1736 FileData *fd = work->data;
1738 file_data_free_ci(fd);
1743 void file_data_sc_free_ci_list(GList *fd_list)
1750 FileData *fd = work->data;
1752 file_data_sc_free_ci(fd);
1758 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1759 * fails if fd->change does not exist or the change type does not match
1762 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1764 FileDataChangeType type = fd->change->type;
1766 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1770 if (!file_data_planned_change_hash)
1771 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1773 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1775 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1776 g_hash_table_remove(file_data_planned_change_hash, old_path);
1777 file_data_unref(fd);
1780 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1785 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1786 g_hash_table_remove(file_data_planned_change_hash, new_path);
1787 file_data_unref(ofd);
1790 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1792 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1797 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1799 gchar *old_path = fd->change->dest;
1801 fd->change->dest = g_strdup(dest_path);
1802 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1806 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1808 const gchar *extension = extension_from_path(fd->change->source);
1809 gchar *base = remove_extension_from_path(dest_path);
1810 gchar *old_path = fd->change->dest;
1812 fd->change->dest = g_strconcat(base, extension, NULL);
1813 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1819 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1822 gchar *dest_path_full = NULL;
1824 if (fd->parent) fd = fd->parent;
1828 dest_path = fd->path;
1830 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1832 gchar *dir = remove_level_from_path(fd->path);
1834 dest_path_full = g_build_filename(dir, dest_path, NULL);
1836 dest_path = dest_path_full;
1838 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1840 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1841 dest_path = dest_path_full;
1844 file_data_update_ci_dest(fd, dest_path);
1846 work = fd->sidecar_files;
1849 FileData *sfd = work->data;
1851 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1855 g_free(dest_path_full);
1858 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1860 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1861 file_data_sc_update_ci(fd, dest_path);
1865 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1867 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1870 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1872 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1875 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1877 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1880 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1882 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1885 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1887 gboolean (*func)(FileData *, const gchar *))
1890 gboolean ret = TRUE;
1895 FileData *fd = work->data;
1897 if (!func(fd, dest)) ret = FALSE;
1904 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1906 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1909 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1911 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1914 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1916 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1921 * verify source and dest paths - dest image exists, etc.
1922 * it should detect all possible problems with the planned operation
1925 gint file_data_verify_ci(FileData *fd)
1927 gint ret = CHANGE_OK;
1932 DEBUG_1("Change checked: no change info: %s", fd->path);
1936 if (!isname(fd->path))
1938 /* this probably should not happen */
1939 ret |= CHANGE_NO_SRC;
1940 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1944 dir = remove_level_from_path(fd->path);
1946 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1947 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1948 fd->change->type != FILEDATA_CHANGE_RENAME &&
1949 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1952 ret |= CHANGE_WARN_UNSAVED_META;
1953 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1956 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1957 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1958 !access_file(fd->path, R_OK))
1960 ret |= CHANGE_NO_READ_PERM;
1961 DEBUG_1("Change checked: no read permission: %s", fd->path);
1963 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1964 !access_file(dir, W_OK))
1966 ret |= CHANGE_NO_WRITE_PERM_DIR;
1967 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1969 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1970 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1971 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1972 !access_file(fd->path, W_OK))
1974 ret |= CHANGE_WARN_NO_WRITE_PERM;
1975 DEBUG_1("Change checked: no write permission: %s", fd->path);
1977 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1978 - that means that there are no hard errors and warnings can be disabled
1979 - the destination is determined during the check
1981 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1983 /* determine destination file */
1984 gboolean have_dest = FALSE;
1985 gchar *dest_dir = NULL;
1987 if (options->metadata.save_in_image_file)
1989 if (file_data_can_write_directly(fd))
1991 /* we can write the file directly */
1992 if (access_file(fd->path, W_OK))
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", fd->path);
2005 else if (file_data_can_write_sidecar(fd))
2007 /* we can write sidecar */
2008 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2009 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2011 file_data_update_ci_dest(fd, sidecar);
2016 if (options->metadata.warn_on_write_problems)
2018 ret |= CHANGE_WARN_NO_WRITE_PERM;
2019 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2028 /* write private metadata file under ~/.geeqie */
2030 /* If an existing metadata file exists, we will try writing to
2031 * it's location regardless of the user's preference.
2033 gchar *metadata_path = NULL;
2035 /* but ignore XMP if we are not able to write it */
2036 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2038 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2040 if (metadata_path && !access_file(metadata_path, W_OK))
2042 g_free(metadata_path);
2043 metadata_path = NULL;
2050 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2051 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2053 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2055 metadata_path = g_build_filename(dest_dir, filename, NULL);
2059 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2061 file_data_update_ci_dest(fd, metadata_path);
2066 ret |= CHANGE_NO_WRITE_PERM_DEST;
2067 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2069 g_free(metadata_path);
2074 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2079 same = (strcmp(fd->path, fd->change->dest) == 0);
2083 const gchar *dest_ext = extension_from_path(fd->change->dest);
2084 if (!dest_ext) dest_ext = "";
2086 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2088 ret |= CHANGE_WARN_CHANGED_EXT;
2089 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2094 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2096 ret |= CHANGE_WARN_SAME;
2097 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2101 dest_dir = remove_level_from_path(fd->change->dest);
2103 if (!isdir(dest_dir))
2105 ret |= CHANGE_NO_DEST_DIR;
2106 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2108 else if (!access_file(dest_dir, W_OK))
2110 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2111 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2115 if (isfile(fd->change->dest))
2117 if (!access_file(fd->change->dest, W_OK))
2119 ret |= CHANGE_NO_WRITE_PERM_DEST;
2120 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2124 ret |= CHANGE_WARN_DEST_EXISTS;
2125 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2128 else if (isdir(fd->change->dest))
2130 ret |= CHANGE_DEST_EXISTS;
2131 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2138 fd->change->error = ret;
2139 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2146 gint file_data_sc_verify_ci(FileData *fd)
2151 ret = file_data_verify_ci(fd);
2153 work = fd->sidecar_files;
2156 FileData *sfd = work->data;
2158 ret |= file_data_verify_ci(sfd);
2165 gchar *file_data_get_error_string(gint error)
2167 GString *result = g_string_new("");
2169 if (error & CHANGE_NO_SRC)
2171 if (result->len > 0) g_string_append(result, ", ");
2172 g_string_append(result, _("file or directory does not exist"));
2175 if (error & CHANGE_DEST_EXISTS)
2177 if (result->len > 0) g_string_append(result, ", ");
2178 g_string_append(result, _("destination already exists"));
2181 if (error & CHANGE_NO_WRITE_PERM_DEST)
2183 if (result->len > 0) g_string_append(result, ", ");
2184 g_string_append(result, _("destination can't be overwritten"));
2187 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2189 if (result->len > 0) g_string_append(result, ", ");
2190 g_string_append(result, _("destination directory is not writable"));
2193 if (error & CHANGE_NO_DEST_DIR)
2195 if (result->len > 0) g_string_append(result, ", ");
2196 g_string_append(result, _("destination directory does not exist"));
2199 if (error & CHANGE_NO_WRITE_PERM_DIR)
2201 if (result->len > 0) g_string_append(result, ", ");
2202 g_string_append(result, _("source directory is not writable"));
2205 if (error & CHANGE_NO_READ_PERM)
2207 if (result->len > 0) g_string_append(result, ", ");
2208 g_string_append(result, _("no read permission"));
2211 if (error & CHANGE_WARN_NO_WRITE_PERM)
2213 if (result->len > 0) g_string_append(result, ", ");
2214 g_string_append(result, _("file is readonly"));
2217 if (error & CHANGE_WARN_DEST_EXISTS)
2219 if (result->len > 0) g_string_append(result, ", ");
2220 g_string_append(result, _("destination already exists and will be overwritten"));
2223 if (error & CHANGE_WARN_SAME)
2225 if (result->len > 0) g_string_append(result, ", ");
2226 g_string_append(result, _("source and destination are the same"));
2229 if (error & CHANGE_WARN_CHANGED_EXT)
2231 if (result->len > 0) g_string_append(result, ", ");
2232 g_string_append(result, _("source and destination have different extension"));
2235 if (error & CHANGE_WARN_UNSAVED_META)
2237 if (result->len > 0) g_string_append(result, ", ");
2238 g_string_append(result, _("there are unsaved metadata changes for the file"));
2241 return g_string_free(result, FALSE);
2244 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2247 gint all_errors = 0;
2248 gint common_errors = ~0;
2253 if (!list) return 0;
2255 num = g_list_length(list);
2256 errors = g_new(int, num);
2267 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2268 all_errors |= error;
2269 common_errors &= error;
2276 if (desc && all_errors)
2279 GString *result = g_string_new("");
2283 gchar *str = file_data_get_error_string(common_errors);
2284 g_string_append(result, str);
2285 g_string_append(result, "\n");
2299 error = errors[i] & ~common_errors;
2303 gchar *str = file_data_get_error_string(error);
2304 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2309 *desc = g_string_free(result, FALSE);
2318 * perform the change described by FileFataChangeInfo
2319 * it is used for internal operations,
2320 * this function actually operates with files on the filesystem
2321 * it should implement safe delete
2324 static gboolean file_data_perform_move(FileData *fd)
2326 g_assert(!strcmp(fd->change->source, fd->path));
2327 return move_file(fd->change->source, fd->change->dest);
2330 static gboolean file_data_perform_copy(FileData *fd)
2332 g_assert(!strcmp(fd->change->source, fd->path));
2333 return copy_file(fd->change->source, fd->change->dest);
2336 static gboolean file_data_perform_delete(FileData *fd)
2338 if (isdir(fd->path) && !islink(fd->path))
2339 return rmdir_utf8(fd->path);
2341 if (options->file_ops.safe_delete_enable)
2342 return file_util_safe_unlink(fd->path);
2344 return unlink_file(fd->path);
2347 gboolean file_data_perform_ci(FileData *fd)
2349 FileDataChangeType type = fd->change->type;
2353 case FILEDATA_CHANGE_MOVE:
2354 return file_data_perform_move(fd);
2355 case FILEDATA_CHANGE_COPY:
2356 return file_data_perform_copy(fd);
2357 case FILEDATA_CHANGE_RENAME:
2358 return file_data_perform_move(fd); /* the same as move */
2359 case FILEDATA_CHANGE_DELETE:
2360 return file_data_perform_delete(fd);
2361 case FILEDATA_CHANGE_WRITE_METADATA:
2362 return metadata_write_perform(fd);
2363 case FILEDATA_CHANGE_UNSPECIFIED:
2364 /* nothing to do here */
2372 gboolean file_data_sc_perform_ci(FileData *fd)
2375 gboolean ret = TRUE;
2376 FileDataChangeType type = fd->change->type;
2378 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2380 work = fd->sidecar_files;
2383 FileData *sfd = work->data;
2385 if (!file_data_perform_ci(sfd)) ret = FALSE;
2389 if (!file_data_perform_ci(fd)) ret = FALSE;
2395 * updates FileData structure according to FileDataChangeInfo
2398 gboolean file_data_apply_ci(FileData *fd)
2400 FileDataChangeType type = fd->change->type;
2403 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2405 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2406 file_data_planned_change_remove(fd);
2408 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2410 /* this change overwrites another file which is already known to other modules
2411 renaming fd would create duplicate FileData structure
2412 the best thing we can do is nothing
2413 FIXME: maybe we could copy stuff like marks
2415 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2419 file_data_set_path(fd, fd->change->dest);
2422 file_data_increment_version(fd);
2423 file_data_send_notification(fd, NOTIFY_CHANGE);
2428 gboolean file_data_sc_apply_ci(FileData *fd)
2431 FileDataChangeType type = fd->change->type;
2433 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2435 work = fd->sidecar_files;
2438 FileData *sfd = work->data;
2440 file_data_apply_ci(sfd);
2444 file_data_apply_ci(fd);
2449 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2452 if (fd->parent) fd = fd->parent;
2453 if (!g_list_find(list, fd)) return FALSE;
2455 work = fd->sidecar_files;
2458 if (!g_list_find(list, work->data)) return FALSE;
2465 static gboolean file_data_list_dump(GList *list)
2467 GList *work, *work2;
2472 FileData *fd = work->data;
2473 printf("%s\n", fd->name);
2474 work2 = fd->sidecar_files;
2477 FileData *fd = work2->data;
2478 printf(" %s\n", fd->name);
2479 work2 = work2->next;
2487 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2492 /* change partial groups to independent files */
2497 FileData *fd = work->data;
2500 if (!file_data_list_contains_whole_group(list, fd))
2502 file_data_disable_grouping(fd, TRUE);
2505 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2511 /* remove sidecars from the list,
2512 they can be still acessed via main_fd->sidecar_files */
2516 FileData *fd = work->data;
2520 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2522 out = g_list_prepend(out, file_data_ref(fd));
2526 filelist_free(list);
2527 out = g_list_reverse(out);
2537 * notify other modules about the change described by FileDataChangeInfo
2540 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2541 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2542 implementation in view_file_list.c */
2547 typedef struct _NotifyData NotifyData;
2549 struct _NotifyData {
2550 FileDataNotifyFunc func;
2552 NotifyPriority priority;
2555 static GList *notify_func_list = NULL;
2557 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2559 NotifyData *nda = (NotifyData *)a;
2560 NotifyData *ndb = (NotifyData *)b;
2562 if (nda->priority < ndb->priority) return -1;
2563 if (nda->priority > ndb->priority) return 1;
2567 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2570 GList *work = notify_func_list;
2574 NotifyData *nd = (NotifyData *)work->data;
2576 if (nd->func == func && nd->data == data)
2578 g_warning("Notify func already registered");
2584 nd = g_new(NotifyData, 1);
2587 nd->priority = priority;
2589 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2590 DEBUG_2("Notify func registered: %p", nd);
2595 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2597 GList *work = notify_func_list;
2601 NotifyData *nd = (NotifyData *)work->data;
2603 if (nd->func == func && nd->data == data)
2605 notify_func_list = g_list_delete_link(notify_func_list, work);
2607 DEBUG_2("Notify func unregistered: %p", nd);
2613 g_warning("Notify func not found");
2618 void file_data_send_notification(FileData *fd, NotifyType type)
2620 GList *work = notify_func_list;
2624 NotifyData *nd = (NotifyData *)work->data;
2626 nd->func(fd, type, nd->data);
2631 static GHashTable *file_data_monitor_pool = NULL;
2632 static guint realtime_monitor_id = 0; /* event source id */
2634 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2638 file_data_check_changed_files(fd);
2640 DEBUG_1("monitor %s", fd->path);
2643 static gboolean realtime_monitor_cb(gpointer data)
2645 if (!options->update_on_time_change) return TRUE;
2646 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2650 gboolean file_data_register_real_time_monitor(FileData *fd)
2656 if (!file_data_monitor_pool)
2657 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2659 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2661 DEBUG_1("Register realtime %d %s", count, fd->path);
2664 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2666 if (!realtime_monitor_id)
2668 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2674 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2678 g_assert(file_data_monitor_pool);
2680 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2682 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2684 g_assert(count > 0);
2689 g_hash_table_remove(file_data_monitor_pool, fd);
2691 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2693 file_data_unref(fd);
2695 if (g_hash_table_size(file_data_monitor_pool) == 0)
2697 g_source_remove(realtime_monitor_id);
2698 realtime_monitor_id = 0;
2704 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */