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 gchar *basename = g_strndup(fd->path, fd->extension - 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);
189 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
192 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
194 list = g_hash_table_lookup(basename_hash, basename);
196 if (!g_list_find(list, fd)) return;
198 list = g_list_remove(list, fd);
203 g_hash_table_insert(basename_hash, basename, list);
207 g_hash_table_remove(basename_hash, basename);
213 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
215 filelist_free((GList *)value);
218 static void file_data_basename_hash_free(GHashTable *basename_hash)
220 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
221 g_hash_table_destroy(basename_hash);
224 static void file_data_set_collate_keys(FileData *fd)
226 gchar *caseless_name;
228 caseless_name = g_utf8_casefold(fd->name, -1);
230 g_free(fd->collate_key_name);
231 g_free(fd->collate_key_name_nocase);
233 #if 0 && GLIB_CHECK_VERSION(2, 8, 0)
234 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
235 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
237 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
238 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
240 g_free(caseless_name);
243 static void file_data_set_path(FileData *fd, const gchar *path)
245 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
246 g_assert(file_data_pool);
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 file_data_set_collate_keys(fd);
303 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
305 gboolean ret = FALSE;
308 if (fd->size != st->st_size ||
309 fd->date != st->st_mtime)
311 fd->size = st->st_size;
312 fd->date = st->st_mtime;
313 fd->mode = st->st_mode;
314 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
315 fd->thumb_pixbuf = NULL;
316 file_data_increment_version(fd);
317 file_data_send_notification(fd, NOTIFY_REREAD);
321 work = fd->sidecar_files;
324 FileData *sfd = work->data;
328 if (!stat_utf8(sfd->path, &st))
332 file_data_disconnect_sidecar_file(fd, sfd);
337 ret |= file_data_check_changed_files_recursive(sfd, &st);
343 gboolean file_data_check_changed_files(FileData *fd)
345 gboolean ret = FALSE;
348 if (fd->parent) fd = fd->parent;
350 if (!stat_utf8(fd->path, &st))
354 FileData *sfd = NULL;
356 /* parent is missing, we have to rebuild whole group */
361 /* file_data_disconnect_sidecar_file might delete the file,
362 we have to keep the reference to prevent this */
363 sidecars = filelist_copy(fd->sidecar_files);
370 file_data_disconnect_sidecar_file(fd, sfd);
372 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
373 /* now we can release the sidecars */
374 filelist_free(sidecars);
375 file_data_send_notification(fd, NOTIFY_REREAD);
379 ret |= file_data_check_changed_files_recursive(fd, &st);
385 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars, GHashTable *basename_hash)
389 DEBUG_2("file_data_new: '%s' %d %d", path_utf8, disable_sidecars, !!basename_hash);
391 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
394 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
396 fd = g_hash_table_lookup(file_data_pool, path_utf8);
402 if (!fd && file_data_planned_change_hash)
404 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
407 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
409 file_data_apply_ci(fd);
417 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
421 changed = file_data_check_changed_files(fd);
423 changed = file_data_check_changed_files_recursive(fd, st);
427 GList *list = file_data_basename_hash_insert(basename_hash, fd);
428 if (!disable_sidecars)
429 file_data_check_sidecars(list);
431 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
436 fd = g_new0(FileData, 1);
438 fd->size = st->st_size;
439 fd->date = st->st_mtime;
440 fd->mode = st->st_mode;
442 fd->magick = 0x12345678;
444 if (disable_sidecars) fd->disable_grouping = TRUE;
446 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
448 if (!disable_sidecars && !fd->disable_grouping && sidecar_file_priority(fd->extension))
450 GList *list = file_data_basename_hash_insert(basename_hash, fd);
451 file_data_check_sidecars(list);
458 static void file_data_check_sidecars(const GList *basename_list)
460 FileData *parent_fd = NULL;
462 GList *group_list = NULL;
464 /* check for possible sidecar files;
465 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
466 they have fd->ref set to 0 and file_data unref must chack and free them all together
467 (using fd->ref would cause loops and leaks)
470 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
471 for case-only differences put lowercase first,
472 put the result to group_list
474 work = sidecar_ext_get_list();
477 gchar *ext = work->data;
480 const GList *work2 = basename_list;
484 FileData *sfd = work2->data;
486 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
488 group_list = g_list_append(group_list, file_data_ref(sfd));
494 /* process the group list - the first one is the parent file, others are sidecars */
498 FileData *new_fd = work->data;
501 if (new_fd->disable_grouping)
503 file_data_unref(new_fd);
507 new_fd->ref--; /* do not use ref here */
510 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
512 file_data_merge_sidecar_files(parent_fd, new_fd);
514 g_list_free(group_list);
518 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars, GHashTable *basename_hash)
520 gchar *path_utf8 = path_to_utf8(path);
521 FileData *ret = file_data_new(path_utf8, st, disable_sidecars, basename_hash);
527 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
529 sfd->parent = target;
530 if (!g_list_find(target->sidecar_files, sfd))
531 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
532 file_data_increment_version(sfd); /* increments both sfd and target */
537 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
541 file_data_add_sidecar_file(target, source);
543 work = source->sidecar_files;
546 FileData *sfd = work->data;
547 file_data_add_sidecar_file(target, sfd);
551 g_list_free(source->sidecar_files);
552 source->sidecar_files = NULL;
554 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
559 #ifdef DEBUG_FILEDATA
560 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
562 FileData *file_data_ref(FileData *fd)
565 if (fd == NULL) return NULL;
566 #ifdef DEBUG_FILEDATA
567 if (fd->magick != 0x12345678)
568 DEBUG_0("fd magick mismatch at %s:%d", file, line);
570 g_assert(fd->magick == 0x12345678);
573 #ifdef DEBUG_FILEDATA
574 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
576 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
581 static void file_data_free(FileData *fd)
583 g_assert(fd->magick == 0x12345678);
584 g_assert(fd->ref == 0);
586 metadata_cache_free(fd);
587 g_hash_table_remove(file_data_pool, fd->original_path);
590 g_free(fd->original_path);
591 g_free(fd->collate_key_name);
592 g_free(fd->collate_key_name_nocase);
593 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
594 histmap_free(fd->histmap);
596 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
598 file_data_change_info_free(NULL, fd);
602 #ifdef DEBUG_FILEDATA
603 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
605 void file_data_unref(FileData *fd)
608 if (fd == NULL) return;
609 #ifdef DEBUG_FILEDATA
610 if (fd->magick != 0x12345678)
611 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
613 g_assert(fd->magick == 0x12345678);
616 #ifdef DEBUG_FILEDATA
617 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
619 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
624 FileData *parent = fd->parent ? fd->parent : fd;
626 if (parent->ref > 0) return;
628 work = parent->sidecar_files;
631 FileData *sfd = work->data;
632 if (sfd->ref > 0) return;
636 /* none of parent/children is referenced, we can free everything */
638 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
640 work = parent->sidecar_files;
643 FileData *sfd = work->data;
648 g_list_free(parent->sidecar_files);
649 parent->sidecar_files = NULL;
651 file_data_free(parent);
655 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
657 sfd->parent = target;
658 g_assert(g_list_find(target->sidecar_files, sfd));
660 file_data_increment_version(sfd); /* increments both sfd and target */
662 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
674 /* disables / enables grouping for particular file, sends UPDATE notification */
675 void file_data_disable_grouping(FileData *fd, gboolean disable)
677 if (!fd->disable_grouping == !disable) return;
679 fd->disable_grouping = !!disable;
685 FileData *parent = file_data_ref(fd->parent);
686 file_data_disconnect_sidecar_file(parent, fd);
687 file_data_send_notification(parent, NOTIFY_GROUPING);
688 file_data_unref(parent);
690 else if (fd->sidecar_files)
692 GList *sidecar_files = filelist_copy(fd->sidecar_files);
693 GList *work = sidecar_files;
696 FileData *sfd = work->data;
698 file_data_disconnect_sidecar_file(fd, sfd);
699 file_data_send_notification(sfd, NOTIFY_GROUPING);
701 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
702 filelist_free(sidecar_files);
706 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
711 file_data_increment_version(fd);
712 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
714 file_data_send_notification(fd, NOTIFY_GROUPING);
717 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
724 FileData *fd = work->data;
726 file_data_disable_grouping(fd, disable);
732 /* compare name without extension */
733 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
735 size_t len1 = fd1->extension - fd1->name;
736 size_t len2 = fd2->extension - fd2->name;
738 if (len1 < len2) return -1;
739 if (len1 > len2) return 1;
741 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
744 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
746 if (!fdci && fd) fdci = fd->change;
750 g_free(fdci->source);
755 if (fd) fd->change = NULL;
758 static gboolean file_data_can_write_directly(FileData *fd)
760 return filter_name_is_writable(fd->extension);
763 static gboolean file_data_can_write_sidecar(FileData *fd)
765 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
768 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
770 gchar *sidecar_path = NULL;
773 if (!file_data_can_write_sidecar(fd)) return NULL;
775 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
778 FileData *sfd = work->data;
780 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
782 sidecar_path = g_strdup(sfd->path);
787 if (!existing_only && !sidecar_path)
789 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
790 sidecar_path = g_strconcat(base, ".xmp", NULL);
799 *-----------------------------------------------------------------------------
800 * sidecar file info struct
801 *-----------------------------------------------------------------------------
806 static gint sidecar_file_priority(const gchar *path)
808 const gchar *extension = extension_from_path(path);
812 if (extension == NULL)
815 work = sidecar_ext_get_list();
818 gchar *ext = work->data;
821 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
829 *-----------------------------------------------------------------------------
831 *-----------------------------------------------------------------------------
834 static SortType filelist_sort_method = SORT_NONE;
835 static gboolean filelist_sort_ascend = TRUE;
838 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
841 if (!filelist_sort_ascend)
848 switch (filelist_sort_method)
853 if (fa->size < fb->size) return -1;
854 if (fa->size > fb->size) return 1;
855 /* fall back to name */
858 if (fa->date < fb->date) return -1;
859 if (fa->date > fb->date) return 1;
860 /* fall back to name */
862 #ifdef HAVE_STRVERSCMP
864 ret = strverscmp(fa->name, fb->name);
865 if (ret != 0) return ret;
872 if (options->file_sort.case_sensitive)
873 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
875 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
877 if (ret != 0) return ret;
879 /* do not return 0 unless the files are really the same
880 file_data_pool ensures that original_path is unique
882 return strcmp(fa->original_path, fb->original_path);
885 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
887 filelist_sort_method = method;
888 filelist_sort_ascend = ascend;
889 return filelist_sort_compare_filedata(fa, fb);
892 static gint filelist_sort_file_cb(gpointer a, gpointer b)
894 return filelist_sort_compare_filedata(a, b);
897 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
899 filelist_sort_method = method;
900 filelist_sort_ascend = ascend;
901 return g_list_sort(list, cb);
904 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
906 filelist_sort_method = method;
907 filelist_sort_ascend = ascend;
908 return g_list_insert_sorted(list, data, cb);
911 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
913 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
916 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
918 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
922 static GList *filelist_filter_out_sidecars(GList *flist)
925 GList *flist_filtered = NULL;
929 FileData *fd = work->data;
932 if (fd->parent) /* remove fd's that are children */
935 flist_filtered = g_list_prepend(flist_filtered, fd);
939 return flist_filtered;
942 static gboolean is_hidden_file(const gchar *name)
944 if (name[0] != '.') return FALSE;
945 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
949 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
956 gint (*stat_func)(const gchar *path, struct stat *buf);
957 GHashTable *basename_hash = NULL;
959 g_assert(files || dirs);
961 if (files) *files = NULL;
962 if (dirs) *dirs = NULL;
964 pathl = path_from_utf8(dir_path);
965 if (!pathl) return FALSE;
974 if (files) basename_hash = file_data_basename_hash_new();
981 while ((dir = readdir(dp)) != NULL)
983 struct stat ent_sbuf;
984 const gchar *name = dir->d_name;
987 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
990 filepath = g_build_filename(pathl, name, NULL);
991 if (stat_func(filepath, &ent_sbuf) >= 0)
993 if (S_ISDIR(ent_sbuf.st_mode))
995 /* we ignore the .thumbnails dir for cleanliness */
997 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
998 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
999 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1000 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1002 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE, NULL));
1007 if (files && filter_name_exists(name))
1009 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, FALSE, basename_hash));
1015 if (errno == EOVERFLOW)
1017 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1026 if (basename_hash) file_data_basename_hash_free(basename_hash);
1028 if (dirs) *dirs = dlist;
1029 if (files) *files = filelist_filter_out_sidecars(flist);
1034 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1036 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1039 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1041 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1044 FileData *file_data_new_simple(const gchar *path_utf8)
1051 if (!stat_utf8(path_utf8, &st))
1057 if (S_ISDIR(st.st_mode))
1058 return file_data_new(path_utf8, &st, TRUE, NULL);
1060 dir = remove_level_from_path(path_utf8);
1062 filelist_read_real(dir, &files, NULL, TRUE);
1064 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1068 filelist_free(files);
1073 FileData *file_data_new_no_grouping(const gchar *path_utf8)
1077 if (!stat_utf8(path_utf8, &st))
1083 return file_data_new(path_utf8, &st, TRUE, NULL);
1086 FileData *file_data_new_dir(const gchar *path_utf8)
1090 if (!stat_utf8(path_utf8, &st))
1096 g_assert(S_ISDIR(st.st_mode));
1097 return file_data_new(path_utf8, &st, TRUE, NULL);
1100 void filelist_free(GList *list)
1107 file_data_unref((FileData *)work->data);
1115 GList *filelist_copy(GList *list)
1117 GList *new_list = NULL;
1128 new_list = g_list_prepend(new_list, file_data_ref(fd));
1131 return g_list_reverse(new_list);
1134 GList *filelist_from_path_list(GList *list)
1136 GList *new_list = NULL;
1147 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1150 return g_list_reverse(new_list);
1153 GList *filelist_to_path_list(GList *list)
1155 GList *new_list = NULL;
1166 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1169 return g_list_reverse(new_list);
1172 GList *filelist_filter(GList *list, gboolean is_dir_list)
1176 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1181 FileData *fd = (FileData *)(work->data);
1182 const gchar *name = fd->name;
1184 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1185 (!is_dir_list && !filter_name_exists(name)) ||
1186 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1187 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1191 list = g_list_remove_link(list, link);
1192 file_data_unref(fd);
1203 *-----------------------------------------------------------------------------
1204 * filelist recursive
1205 *-----------------------------------------------------------------------------
1208 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1210 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1213 GList *filelist_sort_path(GList *list)
1215 return g_list_sort(list, filelist_sort_path_cb);
1218 static void filelist_recursive_append(GList **list, GList *dirs)
1225 FileData *fd = (FileData *)(work->data);
1229 if (filelist_read(fd, &f, &d))
1231 f = filelist_filter(f, FALSE);
1232 f = filelist_sort_path(f);
1233 *list = g_list_concat(*list, f);
1235 d = filelist_filter(d, TRUE);
1236 d = filelist_sort_path(d);
1237 filelist_recursive_append(list, d);
1245 GList *filelist_recursive(FileData *dir_fd)
1250 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1251 list = filelist_filter(list, FALSE);
1252 list = filelist_sort_path(list);
1254 d = filelist_filter(d, TRUE);
1255 d = filelist_sort_path(d);
1256 filelist_recursive_append(&list, d);
1264 * marks and orientation
1267 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1268 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1269 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1270 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1272 gboolean file_data_get_mark(FileData *fd, gint n)
1274 gboolean valid = (fd->valid_marks & (1 << n));
1276 if (file_data_get_mark_func[n] && !valid)
1278 guint old = fd->marks;
1279 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1281 if (!value != !(fd->marks & (1 << n)))
1283 fd->marks = fd->marks ^ (1 << n);
1286 fd->valid_marks |= (1 << n);
1287 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1289 file_data_unref(fd);
1291 else if (!old && fd->marks)
1297 return !!(fd->marks & (1 << n));
1300 guint file_data_get_marks(FileData *fd)
1303 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1307 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1310 if (!value == !file_data_get_mark(fd, n)) return;
1312 if (file_data_set_mark_func[n])
1314 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1319 fd->marks = fd->marks ^ (1 << n);
1321 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1323 file_data_unref(fd);
1325 else if (!old && fd->marks)
1330 file_data_increment_version(fd);
1331 file_data_send_notification(fd, NOTIFY_MARKS);
1334 gboolean file_data_filter_marks(FileData *fd, guint filter)
1337 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1338 return ((fd->marks & filter) == filter);
1341 GList *file_data_filter_marks_list(GList *list, guint filter)
1348 FileData *fd = work->data;
1352 if (!file_data_filter_marks(fd, filter))
1354 list = g_list_remove_link(list, link);
1355 file_data_unref(fd);
1363 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1365 FileData *fd = value;
1366 file_data_increment_version(fd);
1367 file_data_send_notification(fd, NOTIFY_MARKS);
1370 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1372 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1374 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1376 file_data_get_mark_func[n] = get_mark_func;
1377 file_data_set_mark_func[n] = set_mark_func;
1378 file_data_mark_func_data[n] = data;
1379 file_data_destroy_mark_func[n] = notify;
1383 /* this effectively changes all known files */
1384 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1390 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1392 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1393 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1394 if (data) *data = file_data_mark_func_data[n];
1397 gint file_data_get_user_orientation(FileData *fd)
1399 return fd->user_orientation;
1402 void file_data_set_user_orientation(FileData *fd, gint value)
1404 if (fd->user_orientation == value) return;
1406 fd->user_orientation = value;
1407 file_data_increment_version(fd);
1408 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1413 * file_data - operates on the given fd
1414 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1418 /* return list of sidecar file extensions in a string */
1419 gchar *file_data_sc_list_to_string(FileData *fd)
1422 GString *result = g_string_new("");
1424 work = fd->sidecar_files;
1427 FileData *sfd = work->data;
1429 result = g_string_append(result, "+ ");
1430 result = g_string_append(result, sfd->extension);
1432 if (work) result = g_string_append_c(result, ' ');
1435 return g_string_free(result, FALSE);
1441 * add FileDataChangeInfo (see typedefs.h) for the given operation
1442 * uses file_data_add_change_info
1444 * fails if the fd->change already exists - change operations can't run in parallel
1445 * fd->change_info works as a lock
1447 * dest can be NULL - in this case the current name is used for now, it will
1452 FileDataChangeInfo types:
1454 MOVE - path is changed, name may be changed too
1455 RENAME - path remains unchanged, name is changed
1456 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1457 sidecar names are changed too, extensions are not changed
1459 UPDATE - file size, date or grouping has been changed
1462 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1464 FileDataChangeInfo *fdci;
1466 if (fd->change) return FALSE;
1468 fdci = g_new0(FileDataChangeInfo, 1);
1473 fdci->source = g_strdup(src);
1475 fdci->source = g_strdup(fd->path);
1478 fdci->dest = g_strdup(dest);
1485 static void file_data_planned_change_remove(FileData *fd)
1487 if (file_data_planned_change_hash &&
1488 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1490 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1492 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1493 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1494 file_data_unref(fd);
1495 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1497 g_hash_table_destroy(file_data_planned_change_hash);
1498 file_data_planned_change_hash = NULL;
1499 DEBUG_1("planned change: empty");
1506 void file_data_free_ci(FileData *fd)
1508 FileDataChangeInfo *fdci = fd->change;
1512 file_data_planned_change_remove(fd);
1514 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1516 g_free(fdci->source);
1524 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1526 FileDataChangeInfo *fdci = fd->change;
1528 fdci->regroup_when_finished = enable;
1531 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1535 if (fd->parent) fd = fd->parent;
1537 if (fd->change) return FALSE;
1539 work = fd->sidecar_files;
1542 FileData *sfd = work->data;
1544 if (sfd->change) return FALSE;
1548 file_data_add_ci(fd, type, NULL, NULL);
1550 work = fd->sidecar_files;
1553 FileData *sfd = work->data;
1555 file_data_add_ci(sfd, type, NULL, NULL);
1562 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1566 if (fd->parent) fd = fd->parent;
1568 if (!fd->change || fd->change->type != type) return FALSE;
1570 work = fd->sidecar_files;
1573 FileData *sfd = work->data;
1575 if (!sfd->change || sfd->change->type != type) return FALSE;
1583 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1585 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1586 file_data_sc_update_ci_copy(fd, dest_path);
1590 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1592 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1593 file_data_sc_update_ci_move(fd, dest_path);
1597 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1599 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1600 file_data_sc_update_ci_rename(fd, dest_path);
1604 gboolean file_data_sc_add_ci_delete(FileData *fd)
1606 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1609 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1611 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1612 file_data_sc_update_ci_unspecified(fd, dest_path);
1616 gboolean file_data_add_ci_write_metadata(FileData *fd)
1618 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1621 void file_data_sc_free_ci(FileData *fd)
1625 if (fd->parent) fd = fd->parent;
1627 file_data_free_ci(fd);
1629 work = fd->sidecar_files;
1632 FileData *sfd = work->data;
1634 file_data_free_ci(sfd);
1639 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1642 gboolean ret = TRUE;
1647 FileData *fd = work->data;
1649 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1656 static void file_data_sc_revert_ci_list(GList *fd_list)
1663 FileData *fd = work->data;
1665 file_data_sc_free_ci(fd);
1670 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1677 FileData *fd = work->data;
1679 if (!func(fd, dest))
1681 file_data_sc_revert_ci_list(work->prev);
1690 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1692 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1695 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1697 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1700 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1702 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1705 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1707 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1710 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1713 gboolean ret = TRUE;
1718 FileData *fd = work->data;
1720 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1727 void file_data_free_ci_list(GList *fd_list)
1734 FileData *fd = work->data;
1736 file_data_free_ci(fd);
1741 void file_data_sc_free_ci_list(GList *fd_list)
1748 FileData *fd = work->data;
1750 file_data_sc_free_ci(fd);
1756 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1757 * fails if fd->change does not exist or the change type does not match
1760 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1762 FileDataChangeType type = fd->change->type;
1764 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1768 if (!file_data_planned_change_hash)
1769 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1771 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1773 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1774 g_hash_table_remove(file_data_planned_change_hash, old_path);
1775 file_data_unref(fd);
1778 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1783 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1784 g_hash_table_remove(file_data_planned_change_hash, new_path);
1785 file_data_unref(ofd);
1788 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1790 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1795 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1797 gchar *old_path = fd->change->dest;
1799 fd->change->dest = g_strdup(dest_path);
1800 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1804 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1806 const gchar *extension = extension_from_path(fd->change->source);
1807 gchar *base = remove_extension_from_path(dest_path);
1808 gchar *old_path = fd->change->dest;
1810 fd->change->dest = g_strconcat(base, extension, NULL);
1811 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1817 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1820 gchar *dest_path_full = NULL;
1822 if (fd->parent) fd = fd->parent;
1826 dest_path = fd->path;
1828 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1830 gchar *dir = remove_level_from_path(fd->path);
1832 dest_path_full = g_build_filename(dir, dest_path, NULL);
1834 dest_path = dest_path_full;
1836 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1838 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1839 dest_path = dest_path_full;
1842 file_data_update_ci_dest(fd, dest_path);
1844 work = fd->sidecar_files;
1847 FileData *sfd = work->data;
1849 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1853 g_free(dest_path_full);
1856 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1858 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1859 file_data_sc_update_ci(fd, dest_path);
1863 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1865 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1868 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1870 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1873 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1875 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1878 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1880 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1883 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1885 gboolean (*func)(FileData *, const gchar *))
1888 gboolean ret = TRUE;
1893 FileData *fd = work->data;
1895 if (!func(fd, dest)) ret = FALSE;
1902 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1904 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1907 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1909 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1912 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1914 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1919 * verify source and dest paths - dest image exists, etc.
1920 * it should detect all possible problems with the planned operation
1923 gint file_data_verify_ci(FileData *fd)
1925 gint ret = CHANGE_OK;
1930 DEBUG_1("Change checked: no change info: %s", fd->path);
1934 if (!isname(fd->path))
1936 /* this probably should not happen */
1937 ret |= CHANGE_NO_SRC;
1938 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1942 dir = remove_level_from_path(fd->path);
1944 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1945 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1946 fd->change->type != FILEDATA_CHANGE_RENAME &&
1947 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1950 ret |= CHANGE_WARN_UNSAVED_META;
1951 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1954 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1955 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1956 !access_file(fd->path, R_OK))
1958 ret |= CHANGE_NO_READ_PERM;
1959 DEBUG_1("Change checked: no read permission: %s", fd->path);
1961 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1962 !access_file(dir, W_OK))
1964 ret |= CHANGE_NO_WRITE_PERM_DIR;
1965 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1967 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1968 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1969 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1970 !access_file(fd->path, W_OK))
1972 ret |= CHANGE_WARN_NO_WRITE_PERM;
1973 DEBUG_1("Change checked: no write permission: %s", fd->path);
1975 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1976 - that means that there are no hard errors and warnings can be disabled
1977 - the destination is determined during the check
1979 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1981 /* determine destination file */
1982 gboolean have_dest = FALSE;
1983 gchar *dest_dir = NULL;
1985 if (options->metadata.save_in_image_file)
1987 if (file_data_can_write_directly(fd))
1989 /* we can write the file directly */
1990 if (access_file(fd->path, W_OK))
1996 if (options->metadata.warn_on_write_problems)
1998 ret |= CHANGE_WARN_NO_WRITE_PERM;
1999 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2003 else if (file_data_can_write_sidecar(fd))
2005 /* we can write sidecar */
2006 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2007 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2009 file_data_update_ci_dest(fd, sidecar);
2014 if (options->metadata.warn_on_write_problems)
2016 ret |= CHANGE_WARN_NO_WRITE_PERM;
2017 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2026 /* write private metadata file under ~/.geeqie */
2028 /* If an existing metadata file exists, we will try writing to
2029 * it's location regardless of the user's preference.
2031 gchar *metadata_path = NULL;
2033 /* but ignore XMP if we are not able to write it */
2034 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2036 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2038 if (metadata_path && !access_file(metadata_path, W_OK))
2040 g_free(metadata_path);
2041 metadata_path = NULL;
2048 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2049 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2051 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2053 metadata_path = g_build_filename(dest_dir, filename, NULL);
2057 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2059 file_data_update_ci_dest(fd, metadata_path);
2064 ret |= CHANGE_NO_WRITE_PERM_DEST;
2065 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2067 g_free(metadata_path);
2072 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2077 same = (strcmp(fd->path, fd->change->dest) == 0);
2081 const gchar *dest_ext = extension_from_path(fd->change->dest);
2082 if (!dest_ext) dest_ext = "";
2084 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2086 ret |= CHANGE_WARN_CHANGED_EXT;
2087 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2092 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2094 ret |= CHANGE_WARN_SAME;
2095 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2099 dest_dir = remove_level_from_path(fd->change->dest);
2101 if (!isdir(dest_dir))
2103 ret |= CHANGE_NO_DEST_DIR;
2104 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2106 else if (!access_file(dest_dir, W_OK))
2108 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2109 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2113 if (isfile(fd->change->dest))
2115 if (!access_file(fd->change->dest, W_OK))
2117 ret |= CHANGE_NO_WRITE_PERM_DEST;
2118 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2122 ret |= CHANGE_WARN_DEST_EXISTS;
2123 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2126 else if (isdir(fd->change->dest))
2128 ret |= CHANGE_DEST_EXISTS;
2129 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2136 fd->change->error = ret;
2137 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2144 gint file_data_sc_verify_ci(FileData *fd)
2149 ret = file_data_verify_ci(fd);
2151 work = fd->sidecar_files;
2154 FileData *sfd = work->data;
2156 ret |= file_data_verify_ci(sfd);
2163 gchar *file_data_get_error_string(gint error)
2165 GString *result = g_string_new("");
2167 if (error & CHANGE_NO_SRC)
2169 if (result->len > 0) g_string_append(result, ", ");
2170 g_string_append(result, _("file or directory does not exist"));
2173 if (error & CHANGE_DEST_EXISTS)
2175 if (result->len > 0) g_string_append(result, ", ");
2176 g_string_append(result, _("destination already exists"));
2179 if (error & CHANGE_NO_WRITE_PERM_DEST)
2181 if (result->len > 0) g_string_append(result, ", ");
2182 g_string_append(result, _("destination can't be overwritten"));
2185 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2187 if (result->len > 0) g_string_append(result, ", ");
2188 g_string_append(result, _("destination directory is not writable"));
2191 if (error & CHANGE_NO_DEST_DIR)
2193 if (result->len > 0) g_string_append(result, ", ");
2194 g_string_append(result, _("destination directory does not exist"));
2197 if (error & CHANGE_NO_WRITE_PERM_DIR)
2199 if (result->len > 0) g_string_append(result, ", ");
2200 g_string_append(result, _("source directory is not writable"));
2203 if (error & CHANGE_NO_READ_PERM)
2205 if (result->len > 0) g_string_append(result, ", ");
2206 g_string_append(result, _("no read permission"));
2209 if (error & CHANGE_WARN_NO_WRITE_PERM)
2211 if (result->len > 0) g_string_append(result, ", ");
2212 g_string_append(result, _("file is readonly"));
2215 if (error & CHANGE_WARN_DEST_EXISTS)
2217 if (result->len > 0) g_string_append(result, ", ");
2218 g_string_append(result, _("destination already exists and will be overwritten"));
2221 if (error & CHANGE_WARN_SAME)
2223 if (result->len > 0) g_string_append(result, ", ");
2224 g_string_append(result, _("source and destination are the same"));
2227 if (error & CHANGE_WARN_CHANGED_EXT)
2229 if (result->len > 0) g_string_append(result, ", ");
2230 g_string_append(result, _("source and destination have different extension"));
2233 if (error & CHANGE_WARN_UNSAVED_META)
2235 if (result->len > 0) g_string_append(result, ", ");
2236 g_string_append(result, _("there are unsaved metadata changes for the file"));
2239 return g_string_free(result, FALSE);
2242 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2245 gint all_errors = 0;
2246 gint common_errors = ~0;
2251 if (!list) return 0;
2253 num = g_list_length(list);
2254 errors = g_new(int, num);
2265 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2266 all_errors |= error;
2267 common_errors &= error;
2274 if (desc && all_errors)
2277 GString *result = g_string_new("");
2281 gchar *str = file_data_get_error_string(common_errors);
2282 g_string_append(result, str);
2283 g_string_append(result, "\n");
2297 error = errors[i] & ~common_errors;
2301 gchar *str = file_data_get_error_string(error);
2302 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2307 *desc = g_string_free(result, FALSE);
2316 * perform the change described by FileFataChangeInfo
2317 * it is used for internal operations,
2318 * this function actually operates with files on the filesystem
2319 * it should implement safe delete
2322 static gboolean file_data_perform_move(FileData *fd)
2324 g_assert(!strcmp(fd->change->source, fd->path));
2325 return move_file(fd->change->source, fd->change->dest);
2328 static gboolean file_data_perform_copy(FileData *fd)
2330 g_assert(!strcmp(fd->change->source, fd->path));
2331 return copy_file(fd->change->source, fd->change->dest);
2334 static gboolean file_data_perform_delete(FileData *fd)
2336 if (isdir(fd->path) && !islink(fd->path))
2337 return rmdir_utf8(fd->path);
2339 if (options->file_ops.safe_delete_enable)
2340 return file_util_safe_unlink(fd->path);
2342 return unlink_file(fd->path);
2345 gboolean file_data_perform_ci(FileData *fd)
2347 FileDataChangeType type = fd->change->type;
2351 case FILEDATA_CHANGE_MOVE:
2352 return file_data_perform_move(fd);
2353 case FILEDATA_CHANGE_COPY:
2354 return file_data_perform_copy(fd);
2355 case FILEDATA_CHANGE_RENAME:
2356 return file_data_perform_move(fd); /* the same as move */
2357 case FILEDATA_CHANGE_DELETE:
2358 return file_data_perform_delete(fd);
2359 case FILEDATA_CHANGE_WRITE_METADATA:
2360 return metadata_write_perform(fd);
2361 case FILEDATA_CHANGE_UNSPECIFIED:
2362 /* nothing to do here */
2370 gboolean file_data_sc_perform_ci(FileData *fd)
2373 gboolean ret = TRUE;
2374 FileDataChangeType type = fd->change->type;
2376 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2378 work = fd->sidecar_files;
2381 FileData *sfd = work->data;
2383 if (!file_data_perform_ci(sfd)) ret = FALSE;
2387 if (!file_data_perform_ci(fd)) ret = FALSE;
2393 * updates FileData structure according to FileDataChangeInfo
2396 gboolean file_data_apply_ci(FileData *fd)
2398 FileDataChangeType type = fd->change->type;
2401 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2403 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2404 file_data_planned_change_remove(fd);
2406 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2408 /* this change overwrites another file which is already known to other modules
2409 renaming fd would create duplicate FileData structure
2410 the best thing we can do is nothing
2411 FIXME: maybe we could copy stuff like marks
2413 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2417 file_data_set_path(fd, fd->change->dest);
2420 file_data_increment_version(fd);
2421 file_data_send_notification(fd, NOTIFY_CHANGE);
2426 gboolean file_data_sc_apply_ci(FileData *fd)
2429 FileDataChangeType type = fd->change->type;
2431 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2433 work = fd->sidecar_files;
2436 FileData *sfd = work->data;
2438 file_data_apply_ci(sfd);
2442 file_data_apply_ci(fd);
2447 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2450 if (fd->parent) fd = fd->parent;
2451 if (!g_list_find(list, fd)) return FALSE;
2453 work = fd->sidecar_files;
2456 if (!g_list_find(list, work->data)) return FALSE;
2463 static gboolean file_data_list_dump(GList *list)
2465 GList *work, *work2;
2470 FileData *fd = work->data;
2471 printf("%s\n", fd->name);
2472 work2 = fd->sidecar_files;
2475 FileData *fd = work2->data;
2476 printf(" %s\n", fd->name);
2477 work2 = work2->next;
2485 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2490 /* change partial groups to independent files */
2495 FileData *fd = work->data;
2498 if (!file_data_list_contains_whole_group(list, fd))
2500 file_data_disable_grouping(fd, TRUE);
2503 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2509 /* remove sidecars from the list,
2510 they can be still acessed via main_fd->sidecar_files */
2514 FileData *fd = work->data;
2518 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2520 out = g_list_prepend(out, file_data_ref(fd));
2524 filelist_free(list);
2525 out = g_list_reverse(out);
2535 * notify other modules about the change described by FileDataChangeInfo
2538 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2539 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2540 implementation in view_file_list.c */
2545 typedef struct _NotifyData NotifyData;
2547 struct _NotifyData {
2548 FileDataNotifyFunc func;
2550 NotifyPriority priority;
2553 static GList *notify_func_list = NULL;
2555 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2557 NotifyData *nda = (NotifyData *)a;
2558 NotifyData *ndb = (NotifyData *)b;
2560 if (nda->priority < ndb->priority) return -1;
2561 if (nda->priority > ndb->priority) return 1;
2565 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2568 GList *work = notify_func_list;
2572 NotifyData *nd = (NotifyData *)work->data;
2574 if (nd->func == func && nd->data == data)
2576 g_warning("Notify func already registered");
2582 nd = g_new(NotifyData, 1);
2585 nd->priority = priority;
2587 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2588 DEBUG_2("Notify func registered: %p", nd);
2593 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2595 GList *work = notify_func_list;
2599 NotifyData *nd = (NotifyData *)work->data;
2601 if (nd->func == func && nd->data == data)
2603 notify_func_list = g_list_delete_link(notify_func_list, work);
2605 DEBUG_2("Notify func unregistered: %p", nd);
2611 g_warning("Notify func not found");
2616 void file_data_send_notification(FileData *fd, NotifyType type)
2618 GList *work = notify_func_list;
2622 NotifyData *nd = (NotifyData *)work->data;
2624 nd->func(fd, type, nd->data);
2629 static GHashTable *file_data_monitor_pool = NULL;
2630 static guint realtime_monitor_id = 0; /* event source id */
2632 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2636 file_data_check_changed_files(fd);
2638 DEBUG_1("monitor %s", fd->path);
2641 static gboolean realtime_monitor_cb(gpointer data)
2643 if (!options->update_on_time_change) return TRUE;
2644 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2648 gboolean file_data_register_real_time_monitor(FileData *fd)
2654 if (!file_data_monitor_pool)
2655 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2657 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2659 DEBUG_1("Register realtime %d %s", count, fd->path);
2662 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2664 if (!realtime_monitor_id)
2666 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2672 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2676 g_assert(file_data_monitor_pool);
2678 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2680 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2682 g_assert(count > 0);
2687 g_hash_table_remove(file_data_monitor_pool, fd);
2689 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2691 file_data_unref(fd);
2693 if (g_hash_table_size(file_data_monitor_pool) == 0)
2695 g_source_remove(realtime_monitor_id);
2696 realtime_monitor_id = 0;
2702 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */