4 * Copyright (C) 2008 - 2009 The Geeqie Team
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
17 #include "filefilter.h"
19 #include "thumb_standard.h"
20 #include "ui_fileops.h"
23 #include "histogram.h"
26 static GHashTable *file_data_pool = NULL;
27 static GHashTable *file_data_planned_change_hash = NULL;
29 static gint sidecar_file_priority(const gchar *path);
30 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, GHashTable *basename_hash);
34 *-----------------------------------------------------------------------------
35 * text conversion utils
36 *-----------------------------------------------------------------------------
39 gchar *text_from_size(gint64 size)
45 /* what I would like to use is printf("%'d", size)
46 * BUT: not supported on every libc :(
50 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
51 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
55 a = g_strdup_printf("%d", (guint)size);
61 b = g_new(gchar, l + n + 1);
86 gchar *text_from_size_abrev(gint64 size)
88 if (size < (gint64)1024)
90 return g_strdup_printf(_("%d bytes"), (gint)size);
92 if (size < (gint64)1048576)
94 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
96 if (size < (gint64)1073741824)
98 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
101 /* to avoid overflowing the gdouble, do division in two steps */
103 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
106 /* note: returned string is valid until next call to text_from_time() */
107 const gchar *text_from_time(time_t t)
109 static gchar *ret = NULL;
113 GError *error = NULL;
115 btime = localtime(&t);
117 /* the %x warning about 2 digit years is not an error */
118 buflen = strftime(buf, sizeof(buf), "%x %H:%M", btime);
119 if (buflen < 1) return "";
122 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
125 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
134 *-----------------------------------------------------------------------------
136 *-----------------------------------------------------------------------------
139 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
140 static void file_data_check_sidecars(FileData *fd, GHashTable *basename_hash);
141 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
144 void file_data_increment_version(FileData *fd)
150 fd->parent->version++;
151 fd->parent->valid_marks = 0;
155 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
157 const FileData *fda = a;
158 const FileData *fdb = b;
160 return strcmp(fdb->extension, fda->extension);
163 static GHashTable *file_data_basename_hash_new(void)
165 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
168 static void file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
171 const gchar *ext = extension_from_path(fd->path);
172 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
174 list = g_hash_table_lookup(basename_hash, basename);
176 if (!g_list_find(list, fd))
178 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
179 g_hash_table_insert(basename_hash, basename, list);
188 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
191 const gchar *ext = extension_from_path(fd->path);
192 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(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 if (sfd) file_data_check_sidecars(sfd, NULL); /* 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 check_sidecars, GHashTable *basename_hash)
389 DEBUG_2("file_data_new: '%s' %d %d", path_utf8, check_sidecars, !!basename_hash);
392 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
394 fd = g_hash_table_lookup(file_data_pool, path_utf8);
400 if (!fd && file_data_planned_change_hash)
402 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
405 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
407 file_data_apply_ci(fd);
416 file_data_basename_hash_insert(basename_hash, fd);
418 file_data_check_sidecars(fd, basename_hash);
422 changed = file_data_check_changed_files(fd);
424 changed = file_data_check_changed_files_recursive(fd, st);
425 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
426 file_data_check_sidecars(fd, basename_hash);
427 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
432 fd = g_new0(FileData, 1);
434 fd->size = st->st_size;
435 fd->date = st->st_mtime;
436 fd->mode = st->st_mode;
438 fd->magick = 0x12345678;
440 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
441 if (basename_hash) file_data_basename_hash_insert(basename_hash, fd);
444 file_data_check_sidecars(fd, basename_hash);
449 /* extension must contain only ASCII characters */
450 static GList *check_case_insensitive_ext(gchar *path)
457 sl = path_from_utf8(path);
459 extl = strrchr(sl, '.');
463 extl++; /* the first char after . */
464 ext_len = strlen(extl);
466 for (i = 0; i < (1 << ext_len); i++)
469 gboolean skip = FALSE;
470 for (j = 0; j < ext_len; j++)
472 if (i & (1 << (ext_len - 1 - j)))
474 extl[j] = g_ascii_tolower(extl[j]);
475 /* make sure the result does not contain duplicates */
476 if (extl[j] == g_ascii_toupper(extl[j]))
478 /* no change, probably a number, we have already tested this combination */
484 extl[j] = g_ascii_toupper(extl[j]);
488 if (stat(sl, &st) == 0)
490 list = g_list_prepend(list, file_data_new_local(sl, &st, FALSE, FALSE));
499 static void file_data_check_sidecars(FileData *fd, GHashTable *basename_hash)
503 FileData *parent_fd = NULL;
505 const GList *basename_list = NULL;
506 GList *group_list = NULL;
507 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
510 base_len = fd->extension - fd->path;
511 fname = g_string_new_len(fd->path, base_len);
515 basename_list = g_hash_table_lookup(basename_hash, fname->str);
519 /* check for possible sidecar files;
520 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
521 they have fd->ref set to 0 and file_data unref must chack and free them all together
522 (using fd->ref would cause loops and leaks)
525 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
526 for case-only differences put lowercase first,
527 put the result to group_list
529 work = sidecar_ext_get_list();
532 gchar *ext = work->data;
538 g_string_truncate(fname, base_len);
539 g_string_append(fname, ext);
540 new_list = check_case_insensitive_ext(fname->str);
541 group_list = g_list_concat(group_list, new_list);
545 const GList *work2 = basename_list;
549 FileData *sfd = work2->data;
551 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
553 group_list = g_list_append(group_list, file_data_ref(sfd));
559 g_string_free(fname, TRUE);
561 /* process the group list - the first one is the parent file, others are sidecars */
565 FileData *new_fd = work->data;
568 if (new_fd->disable_grouping)
570 file_data_unref(new_fd);
574 new_fd->ref--; /* do not use ref here */
577 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
579 file_data_merge_sidecar_files(parent_fd, new_fd);
581 g_list_free(group_list);
585 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, GHashTable *basename_hash)
587 gchar *path_utf8 = path_to_utf8(path);
588 FileData *ret = file_data_new(path_utf8, st, check_sidecars, basename_hash);
594 FileData *file_data_new_simple(const gchar *path_utf8)
598 if (!stat_utf8(path_utf8, &st))
604 return file_data_new(path_utf8, &st, TRUE, NULL);
607 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
609 sfd->parent = target;
610 if (!g_list_find(target->sidecar_files, sfd))
611 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
612 file_data_increment_version(sfd); /* increments both sfd and target */
617 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
621 file_data_add_sidecar_file(target, source);
623 work = source->sidecar_files;
626 FileData *sfd = work->data;
627 file_data_add_sidecar_file(target, sfd);
631 g_list_free(source->sidecar_files);
632 source->sidecar_files = NULL;
634 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
639 #ifdef DEBUG_FILEDATA
640 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
642 FileData *file_data_ref(FileData *fd)
645 if (fd == NULL) return NULL;
646 #ifdef DEBUG_FILEDATA
647 if (fd->magick != 0x12345678)
648 DEBUG_0("fd magick mismatch at %s:%d", file, line);
650 g_assert(fd->magick == 0x12345678);
653 #ifdef DEBUG_FILEDATA
654 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
656 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
661 static void file_data_free(FileData *fd)
663 g_assert(fd->magick == 0x12345678);
664 g_assert(fd->ref == 0);
666 g_hash_table_remove(file_data_pool, fd->original_path);
669 g_free(fd->original_path);
670 g_free(fd->collate_key_name);
671 g_free(fd->collate_key_name_nocase);
672 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
673 histmap_free(fd->histmap);
675 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
677 file_data_change_info_free(NULL, fd);
681 #ifdef DEBUG_FILEDATA
682 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
684 void file_data_unref(FileData *fd)
687 if (fd == NULL) return;
688 #ifdef DEBUG_FILEDATA
689 if (fd->magick != 0x12345678)
690 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
692 g_assert(fd->magick == 0x12345678);
695 #ifdef DEBUG_FILEDATA
696 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
698 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
703 FileData *parent = fd->parent ? fd->parent : fd;
705 if (parent->ref > 0) return;
707 work = parent->sidecar_files;
710 FileData *sfd = work->data;
711 if (sfd->ref > 0) return;
715 /* none of parent/children is referenced, we can free everything */
717 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
719 work = parent->sidecar_files;
722 FileData *sfd = work->data;
727 g_list_free(parent->sidecar_files);
728 parent->sidecar_files = NULL;
730 file_data_free(parent);
734 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
736 sfd->parent = target;
737 g_assert(g_list_find(target->sidecar_files, sfd));
739 file_data_increment_version(sfd); /* increments both sfd and target */
741 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
753 /* disables / enables grouping for particular file, sends UPDATE notification */
754 void file_data_disable_grouping(FileData *fd, gboolean disable)
756 if (!fd->disable_grouping == !disable) return;
758 fd->disable_grouping = !!disable;
764 FileData *parent = file_data_ref(fd->parent);
765 file_data_disconnect_sidecar_file(parent, fd);
766 file_data_send_notification(parent, NOTIFY_GROUPING);
767 file_data_unref(parent);
769 else if (fd->sidecar_files)
771 GList *sidecar_files = filelist_copy(fd->sidecar_files);
772 GList *work = sidecar_files;
775 FileData *sfd = work->data;
777 file_data_disconnect_sidecar_file(fd, sfd);
778 file_data_send_notification(sfd, NOTIFY_GROUPING);
780 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
781 filelist_free(sidecar_files);
785 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
790 file_data_increment_version(fd);
791 file_data_check_sidecars(fd, FALSE);
793 file_data_send_notification(fd, NOTIFY_GROUPING);
796 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
803 FileData *fd = work->data;
805 file_data_disable_grouping(fd, disable);
811 /* compare name without extension */
812 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
814 size_t len1 = fd1->extension - fd1->name;
815 size_t len2 = fd2->extension - fd2->name;
817 if (len1 < len2) return -1;
818 if (len1 > len2) return 1;
820 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
823 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
825 if (!fdci && fd) fdci = fd->change;
829 g_free(fdci->source);
834 if (fd) fd->change = NULL;
837 static gboolean file_data_can_write_directly(FileData *fd)
839 return filter_name_is_writable(fd->extension);
842 static gboolean file_data_can_write_sidecar(FileData *fd)
844 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
847 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
849 gchar *sidecar_path = NULL;
852 if (!file_data_can_write_sidecar(fd)) return NULL;
854 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
857 FileData *sfd = work->data;
859 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
861 sidecar_path = g_strdup(sfd->path);
866 if (!existing_only && !sidecar_path)
868 gchar *base = remove_extension_from_path(fd->path);
869 sidecar_path = g_strconcat(base, ".xmp", NULL);
878 *-----------------------------------------------------------------------------
879 * sidecar file info struct
880 *-----------------------------------------------------------------------------
885 static gint sidecar_file_priority(const gchar *path)
887 const gchar *extension = extension_from_path(path);
891 if (extension == NULL)
894 work = sidecar_ext_get_list();
897 gchar *ext = work->data;
900 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
908 *-----------------------------------------------------------------------------
910 *-----------------------------------------------------------------------------
913 static SortType filelist_sort_method = SORT_NONE;
914 static gboolean filelist_sort_ascend = TRUE;
917 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
919 if (!filelist_sort_ascend)
926 switch (filelist_sort_method)
931 if (fa->size < fb->size) return -1;
932 if (fa->size > fb->size) return 1;
933 /* fall back to name */
936 if (fa->date < fb->date) return -1;
937 if (fa->date > fb->date) return 1;
938 /* fall back to name */
940 #ifdef HAVE_STRVERSCMP
942 return strverscmp(fa->name, fb->name);
949 if (options->file_sort.case_sensitive)
950 return strcmp(fa->collate_key_name, fb->collate_key_name);
952 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
955 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
957 filelist_sort_method = method;
958 filelist_sort_ascend = ascend;
959 return filelist_sort_compare_filedata(fa, fb);
962 static gint filelist_sort_file_cb(gpointer a, gpointer b)
964 return filelist_sort_compare_filedata(a, b);
967 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
969 filelist_sort_method = method;
970 filelist_sort_ascend = ascend;
971 return g_list_sort(list, cb);
974 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
976 filelist_sort_method = method;
977 filelist_sort_ascend = ascend;
978 return g_list_insert_sorted(list, data, cb);
981 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
983 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
986 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
988 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
992 static GList *filelist_filter_out_sidecars(GList *flist)
995 GList *flist_filtered = NULL;
999 FileData *fd = work->data;
1002 if (fd->parent) /* remove fd's that are children */
1003 file_data_unref(fd);
1005 flist_filtered = g_list_prepend(flist_filtered, fd);
1009 return flist_filtered;
1012 static gboolean is_hidden_file(const gchar *name)
1014 if (name[0] != '.') return FALSE;
1015 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1019 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
1024 GList *dlist = NULL;
1025 GList *flist = NULL;
1026 gint (*stat_func)(const gchar *path, struct stat *buf);
1027 GHashTable *basename_hash = NULL;
1029 g_assert(files || dirs);
1031 if (files) *files = NULL;
1032 if (dirs) *dirs = NULL;
1034 pathl = path_from_utf8(dir_fd->path);
1035 if (!pathl) return FALSE;
1037 dp = opendir(pathl);
1044 if (files) basename_hash = file_data_basename_hash_new();
1046 if (follow_symlinks)
1051 while ((dir = readdir(dp)) != NULL)
1053 struct stat ent_sbuf;
1054 const gchar *name = dir->d_name;
1057 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1060 filepath = g_build_filename(pathl, name, NULL);
1061 if (stat_func(filepath, &ent_sbuf) >= 0)
1063 if (S_ISDIR(ent_sbuf.st_mode))
1065 /* we ignore the .thumbnails dir for cleanliness */
1067 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1068 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1069 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1070 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1072 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, NULL));
1077 if (files && filter_name_exists(name))
1079 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, basename_hash));
1089 if (basename_hash) file_data_basename_hash_free(basename_hash);
1091 if (dirs) *dirs = dlist;
1092 if (files) *files = filelist_filter_out_sidecars(flist);
1097 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1099 return filelist_read_real(dir_fd, files, dirs, TRUE);
1102 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1104 return filelist_read_real(dir_fd, files, dirs, FALSE);
1107 void filelist_free(GList *list)
1114 file_data_unref((FileData *)work->data);
1122 GList *filelist_copy(GList *list)
1124 GList *new_list = NULL;
1135 new_list = g_list_prepend(new_list, file_data_ref(fd));
1138 return g_list_reverse(new_list);
1141 GList *filelist_from_path_list(GList *list)
1143 GList *new_list = NULL;
1154 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1157 return g_list_reverse(new_list);
1160 GList *filelist_to_path_list(GList *list)
1162 GList *new_list = NULL;
1173 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1176 return g_list_reverse(new_list);
1179 GList *filelist_filter(GList *list, gboolean is_dir_list)
1183 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1188 FileData *fd = (FileData *)(work->data);
1189 const gchar *name = fd->name;
1191 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1192 (!is_dir_list && !filter_name_exists(name)) ||
1193 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1194 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1198 list = g_list_remove_link(list, link);
1199 file_data_unref(fd);
1210 *-----------------------------------------------------------------------------
1211 * filelist recursive
1212 *-----------------------------------------------------------------------------
1215 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1217 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1220 GList *filelist_sort_path(GList *list)
1222 return g_list_sort(list, filelist_sort_path_cb);
1225 static void filelist_recursive_append(GList **list, GList *dirs)
1232 FileData *fd = (FileData *)(work->data);
1236 if (filelist_read(fd, &f, &d))
1238 f = filelist_filter(f, FALSE);
1239 f = filelist_sort_path(f);
1240 *list = g_list_concat(*list, f);
1242 d = filelist_filter(d, TRUE);
1243 d = filelist_sort_path(d);
1244 filelist_recursive_append(list, d);
1252 GList *filelist_recursive(FileData *dir_fd)
1257 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1258 list = filelist_filter(list, FALSE);
1259 list = filelist_sort_path(list);
1261 d = filelist_filter(d, TRUE);
1262 d = filelist_sort_path(d);
1263 filelist_recursive_append(&list, d);
1271 * marks and orientation
1274 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1275 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1276 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1277 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1279 gboolean file_data_get_mark(FileData *fd, gint n)
1281 gboolean valid = (fd->valid_marks & (1 << n));
1283 if (file_data_get_mark_func[n] && !valid)
1285 guint old = fd->marks;
1286 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1288 if (!value != !(fd->marks & (1 << n)))
1290 fd->marks = fd->marks ^ (1 << n);
1293 fd->valid_marks |= (1 << n);
1294 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1296 file_data_unref(fd);
1298 else if (!old && fd->marks)
1304 return !!(fd->marks & (1 << n));
1307 guint file_data_get_marks(FileData *fd)
1310 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1314 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1317 if (!value == !file_data_get_mark(fd, n)) return;
1319 if (file_data_set_mark_func[n])
1321 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1326 fd->marks = fd->marks ^ (1 << n);
1328 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1330 file_data_unref(fd);
1332 else if (!old && fd->marks)
1337 file_data_increment_version(fd);
1338 file_data_send_notification(fd, NOTIFY_MARKS);
1341 gboolean file_data_filter_marks(FileData *fd, guint filter)
1344 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1345 return ((fd->marks & filter) == filter);
1348 GList *file_data_filter_marks_list(GList *list, guint filter)
1355 FileData *fd = work->data;
1359 if (!file_data_filter_marks(fd, filter))
1361 list = g_list_remove_link(list, link);
1362 file_data_unref(fd);
1370 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1372 FileData *fd = value;
1373 file_data_increment_version(fd);
1374 file_data_send_notification(fd, NOTIFY_MARKS);
1377 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1379 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1381 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1383 file_data_get_mark_func[n] = get_mark_func;
1384 file_data_set_mark_func[n] = set_mark_func;
1385 file_data_mark_func_data[n] = data;
1386 file_data_destroy_mark_func[n] = notify;
1390 /* this effectively changes all known files */
1391 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1397 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1399 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1400 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1401 if (data) *data = file_data_mark_func_data[n];
1404 gint file_data_get_user_orientation(FileData *fd)
1406 return fd->user_orientation;
1409 void file_data_set_user_orientation(FileData *fd, gint value)
1411 if (fd->user_orientation == value) return;
1413 fd->user_orientation = value;
1414 file_data_increment_version(fd);
1415 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1420 * file_data - operates on the given fd
1421 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1425 /* return list of sidecar file extensions in a string */
1426 gchar *file_data_sc_list_to_string(FileData *fd)
1429 GString *result = g_string_new("");
1431 work = fd->sidecar_files;
1434 FileData *sfd = work->data;
1436 result = g_string_append(result, "+ ");
1437 result = g_string_append(result, sfd->extension);
1439 if (work) result = g_string_append_c(result, ' ');
1442 return g_string_free(result, FALSE);
1448 * add FileDataChangeInfo (see typedefs.h) for the given operation
1449 * uses file_data_add_change_info
1451 * fails if the fd->change already exists - change operations can't run in parallel
1452 * fd->change_info works as a lock
1454 * dest can be NULL - in this case the current name is used for now, it will
1459 FileDataChangeInfo types:
1461 MOVE - path is changed, name may be changed too
1462 RENAME - path remains unchanged, name is changed
1463 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1464 sidecar names are changed too, extensions are not changed
1466 UPDATE - file size, date or grouping has been changed
1469 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1471 FileDataChangeInfo *fdci;
1473 if (fd->change) return FALSE;
1475 fdci = g_new0(FileDataChangeInfo, 1);
1480 fdci->source = g_strdup(src);
1482 fdci->source = g_strdup(fd->path);
1485 fdci->dest = g_strdup(dest);
1492 static void file_data_planned_change_remove(FileData *fd)
1494 if (file_data_planned_change_hash &&
1495 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1497 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1499 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1500 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1501 file_data_unref(fd);
1502 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1504 g_hash_table_destroy(file_data_planned_change_hash);
1505 file_data_planned_change_hash = NULL;
1506 DEBUG_1("planned change: empty");
1513 void file_data_free_ci(FileData *fd)
1515 FileDataChangeInfo *fdci = fd->change;
1519 file_data_planned_change_remove(fd);
1521 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1523 g_free(fdci->source);
1531 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1533 FileDataChangeInfo *fdci = fd->change;
1535 fdci->regroup_when_finished = enable;
1538 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1542 if (fd->parent) fd = fd->parent;
1544 if (fd->change) return FALSE;
1546 work = fd->sidecar_files;
1549 FileData *sfd = work->data;
1551 if (sfd->change) return FALSE;
1555 file_data_add_ci(fd, type, NULL, NULL);
1557 work = fd->sidecar_files;
1560 FileData *sfd = work->data;
1562 file_data_add_ci(sfd, type, NULL, NULL);
1569 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1573 if (fd->parent) fd = fd->parent;
1575 if (!fd->change || fd->change->type != type) return FALSE;
1577 work = fd->sidecar_files;
1580 FileData *sfd = work->data;
1582 if (!sfd->change || sfd->change->type != type) return FALSE;
1590 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1592 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1593 file_data_sc_update_ci_copy(fd, dest_path);
1597 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1599 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1600 file_data_sc_update_ci_move(fd, dest_path);
1604 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1606 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1607 file_data_sc_update_ci_rename(fd, dest_path);
1611 gboolean file_data_sc_add_ci_delete(FileData *fd)
1613 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1616 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1618 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1619 file_data_sc_update_ci_unspecified(fd, dest_path);
1623 gboolean file_data_add_ci_write_metadata(FileData *fd)
1625 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1628 void file_data_sc_free_ci(FileData *fd)
1632 if (fd->parent) fd = fd->parent;
1634 file_data_free_ci(fd);
1636 work = fd->sidecar_files;
1639 FileData *sfd = work->data;
1641 file_data_free_ci(sfd);
1646 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1649 gboolean ret = TRUE;
1654 FileData *fd = work->data;
1656 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1663 static void file_data_sc_revert_ci_list(GList *fd_list)
1670 FileData *fd = work->data;
1672 file_data_sc_free_ci(fd);
1677 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1684 FileData *fd = work->data;
1686 if (!func(fd, dest))
1688 file_data_sc_revert_ci_list(work->prev);
1697 gboolean file_data_sc_add_ci_copy_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_copy);
1702 gboolean file_data_sc_add_ci_move_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_move);
1707 gboolean file_data_sc_add_ci_rename_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_rename);
1712 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1714 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1717 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1720 gboolean ret = TRUE;
1725 FileData *fd = work->data;
1727 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1734 void file_data_free_ci_list(GList *fd_list)
1741 FileData *fd = work->data;
1743 file_data_free_ci(fd);
1748 void file_data_sc_free_ci_list(GList *fd_list)
1755 FileData *fd = work->data;
1757 file_data_sc_free_ci(fd);
1763 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1764 * fails if fd->change does not exist or the change type does not match
1767 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1769 FileDataChangeType type = fd->change->type;
1771 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1775 if (!file_data_planned_change_hash)
1776 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1778 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1780 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1781 g_hash_table_remove(file_data_planned_change_hash, old_path);
1782 file_data_unref(fd);
1785 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1790 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1791 g_hash_table_remove(file_data_planned_change_hash, new_path);
1792 file_data_unref(ofd);
1795 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1797 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1802 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1804 gchar *old_path = fd->change->dest;
1806 fd->change->dest = g_strdup(dest_path);
1807 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1811 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1813 const gchar *extension = extension_from_path(fd->change->source);
1814 gchar *base = remove_extension_from_path(dest_path);
1815 gchar *old_path = fd->change->dest;
1817 fd->change->dest = g_strconcat(base, extension, NULL);
1818 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1824 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1827 gchar *dest_path_full = NULL;
1829 if (fd->parent) fd = fd->parent;
1833 dest_path = fd->path;
1835 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1837 gchar *dir = remove_level_from_path(fd->path);
1839 dest_path_full = g_build_filename(dir, dest_path, NULL);
1841 dest_path = dest_path_full;
1843 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1845 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1846 dest_path = dest_path_full;
1849 file_data_update_ci_dest(fd, dest_path);
1851 work = fd->sidecar_files;
1854 FileData *sfd = work->data;
1856 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1860 g_free(dest_path_full);
1863 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1865 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1866 file_data_sc_update_ci(fd, dest_path);
1870 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1872 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1875 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1877 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1880 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1882 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1885 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1887 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1890 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1892 gboolean (*func)(FileData *, const gchar *))
1895 gboolean ret = TRUE;
1900 FileData *fd = work->data;
1902 if (!func(fd, dest)) ret = FALSE;
1909 gboolean file_data_sc_update_ci_move_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_move);
1914 gboolean file_data_sc_update_ci_copy_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_copy);
1919 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1921 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1926 * verify source and dest paths - dest image exists, etc.
1927 * it should detect all possible problems with the planned operation
1930 gint file_data_verify_ci(FileData *fd)
1932 gint ret = CHANGE_OK;
1937 DEBUG_1("Change checked: no change info: %s", fd->path);
1941 if (!isname(fd->path))
1943 /* this probably should not happen */
1944 ret |= CHANGE_NO_SRC;
1945 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1949 dir = remove_level_from_path(fd->path);
1951 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1952 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1953 fd->change->type != FILEDATA_CHANGE_RENAME &&
1954 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1957 ret |= CHANGE_WARN_UNSAVED_META;
1958 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1961 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1962 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1963 !access_file(fd->path, R_OK))
1965 ret |= CHANGE_NO_READ_PERM;
1966 DEBUG_1("Change checked: no read permission: %s", fd->path);
1968 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1969 !access_file(dir, W_OK))
1971 ret |= CHANGE_NO_WRITE_PERM_DIR;
1972 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1974 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1975 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1976 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1977 !access_file(fd->path, W_OK))
1979 ret |= CHANGE_WARN_NO_WRITE_PERM;
1980 DEBUG_1("Change checked: no write permission: %s", fd->path);
1982 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1983 - that means that there are no hard errors and warnings can be disabled
1984 - the destination is determined during the check
1986 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1988 /* determine destination file */
1989 gboolean have_dest = FALSE;
1990 gchar *dest_dir = NULL;
1992 if (options->metadata.save_in_image_file)
1994 if (file_data_can_write_directly(fd))
1996 /* we can write the file directly */
1997 if (access_file(fd->path, W_OK))
2003 if (options->metadata.warn_on_write_problems)
2005 ret |= CHANGE_WARN_NO_WRITE_PERM;
2006 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2010 else if (file_data_can_write_sidecar(fd))
2012 /* we can write sidecar */
2013 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2014 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2016 file_data_update_ci_dest(fd, sidecar);
2021 if (options->metadata.warn_on_write_problems)
2023 ret |= CHANGE_WARN_NO_WRITE_PERM;
2024 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2033 /* write private metadata file under ~/.geeqie */
2035 /* If an existing metadata file exists, we will try writing to
2036 * it's location regardless of the user's preference.
2038 gchar *metadata_path = NULL;
2040 /* but ignore XMP if we are not able to write it */
2041 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2043 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2045 if (metadata_path && !access_file(metadata_path, W_OK))
2047 g_free(metadata_path);
2048 metadata_path = NULL;
2055 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2056 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2058 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2060 metadata_path = g_build_filename(dest_dir, filename, NULL);
2064 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2066 file_data_update_ci_dest(fd, metadata_path);
2071 ret |= CHANGE_NO_WRITE_PERM_DEST;
2072 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2074 g_free(metadata_path);
2079 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2084 same = (strcmp(fd->path, fd->change->dest) == 0);
2088 const gchar *dest_ext = extension_from_path(fd->change->dest);
2089 if (!dest_ext) dest_ext = "";
2091 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2093 ret |= CHANGE_WARN_CHANGED_EXT;
2094 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2099 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2101 ret |= CHANGE_WARN_SAME;
2102 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2106 dest_dir = remove_level_from_path(fd->change->dest);
2108 if (!isdir(dest_dir))
2110 ret |= CHANGE_NO_DEST_DIR;
2111 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2113 else if (!access_file(dest_dir, W_OK))
2115 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2116 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2120 if (isfile(fd->change->dest))
2122 if (!access_file(fd->change->dest, W_OK))
2124 ret |= CHANGE_NO_WRITE_PERM_DEST;
2125 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2129 ret |= CHANGE_WARN_DEST_EXISTS;
2130 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2133 else if (isdir(fd->change->dest))
2135 ret |= CHANGE_DEST_EXISTS;
2136 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2143 fd->change->error = ret;
2144 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2151 gint file_data_sc_verify_ci(FileData *fd)
2156 ret = file_data_verify_ci(fd);
2158 work = fd->sidecar_files;
2161 FileData *sfd = work->data;
2163 ret |= file_data_verify_ci(sfd);
2170 gchar *file_data_get_error_string(gint error)
2172 GString *result = g_string_new("");
2174 if (error & CHANGE_NO_SRC)
2176 if (result->len > 0) g_string_append(result, ", ");
2177 g_string_append(result, _("file or directory does not exist"));
2180 if (error & CHANGE_DEST_EXISTS)
2182 if (result->len > 0) g_string_append(result, ", ");
2183 g_string_append(result, _("destination already exists"));
2186 if (error & CHANGE_NO_WRITE_PERM_DEST)
2188 if (result->len > 0) g_string_append(result, ", ");
2189 g_string_append(result, _("destination can't be overwritten"));
2192 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2194 if (result->len > 0) g_string_append(result, ", ");
2195 g_string_append(result, _("destination directory is not writable"));
2198 if (error & CHANGE_NO_DEST_DIR)
2200 if (result->len > 0) g_string_append(result, ", ");
2201 g_string_append(result, _("destination directory does not exist"));
2204 if (error & CHANGE_NO_WRITE_PERM_DIR)
2206 if (result->len > 0) g_string_append(result, ", ");
2207 g_string_append(result, _("source directory is not writable"));
2210 if (error & CHANGE_NO_READ_PERM)
2212 if (result->len > 0) g_string_append(result, ", ");
2213 g_string_append(result, _("no read permission"));
2216 if (error & CHANGE_WARN_NO_WRITE_PERM)
2218 if (result->len > 0) g_string_append(result, ", ");
2219 g_string_append(result, _("file is readonly"));
2222 if (error & CHANGE_WARN_DEST_EXISTS)
2224 if (result->len > 0) g_string_append(result, ", ");
2225 g_string_append(result, _("destination already exists and will be overwritten"));
2228 if (error & CHANGE_WARN_SAME)
2230 if (result->len > 0) g_string_append(result, ", ");
2231 g_string_append(result, _("source and destination are the same"));
2234 if (error & CHANGE_WARN_CHANGED_EXT)
2236 if (result->len > 0) g_string_append(result, ", ");
2237 g_string_append(result, _("source and destination have different extension"));
2240 if (error & CHANGE_WARN_UNSAVED_META)
2242 if (result->len > 0) g_string_append(result, ", ");
2243 g_string_append(result, _("there are unsaved metadata changes for the file"));
2246 return g_string_free(result, FALSE);
2249 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2252 gint all_errors = 0;
2253 gint common_errors = ~0;
2258 if (!list) return 0;
2260 num = g_list_length(list);
2261 errors = g_new(int, num);
2272 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2273 all_errors |= error;
2274 common_errors &= error;
2281 if (desc && all_errors)
2284 GString *result = g_string_new("");
2288 gchar *str = file_data_get_error_string(common_errors);
2289 g_string_append(result, str);
2290 g_string_append(result, "\n");
2304 error = errors[i] & ~common_errors;
2308 gchar *str = file_data_get_error_string(error);
2309 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2314 *desc = g_string_free(result, FALSE);
2323 * perform the change described by FileFataChangeInfo
2324 * it is used for internal operations,
2325 * this function actually operates with files on the filesystem
2326 * it should implement safe delete
2329 static gboolean file_data_perform_move(FileData *fd)
2331 g_assert(!strcmp(fd->change->source, fd->path));
2332 return move_file(fd->change->source, fd->change->dest);
2335 static gboolean file_data_perform_copy(FileData *fd)
2337 g_assert(!strcmp(fd->change->source, fd->path));
2338 return copy_file(fd->change->source, fd->change->dest);
2341 static gboolean file_data_perform_delete(FileData *fd)
2343 if (isdir(fd->path) && !islink(fd->path))
2344 return rmdir_utf8(fd->path);
2346 if (options->file_ops.safe_delete_enable)
2347 return file_util_safe_unlink(fd->path);
2349 return unlink_file(fd->path);
2352 gboolean file_data_perform_ci(FileData *fd)
2354 FileDataChangeType type = fd->change->type;
2358 case FILEDATA_CHANGE_MOVE:
2359 return file_data_perform_move(fd);
2360 case FILEDATA_CHANGE_COPY:
2361 return file_data_perform_copy(fd);
2362 case FILEDATA_CHANGE_RENAME:
2363 return file_data_perform_move(fd); /* the same as move */
2364 case FILEDATA_CHANGE_DELETE:
2365 return file_data_perform_delete(fd);
2366 case FILEDATA_CHANGE_WRITE_METADATA:
2367 return metadata_write_perform(fd);
2368 case FILEDATA_CHANGE_UNSPECIFIED:
2369 /* nothing to do here */
2377 gboolean file_data_sc_perform_ci(FileData *fd)
2380 gboolean ret = TRUE;
2381 FileDataChangeType type = fd->change->type;
2383 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2385 work = fd->sidecar_files;
2388 FileData *sfd = work->data;
2390 if (!file_data_perform_ci(sfd)) ret = FALSE;
2394 if (!file_data_perform_ci(fd)) ret = FALSE;
2400 * updates FileData structure according to FileDataChangeInfo
2403 gboolean file_data_apply_ci(FileData *fd)
2405 FileDataChangeType type = fd->change->type;
2408 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2410 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2411 file_data_planned_change_remove(fd);
2413 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2415 /* this change overwrites another file which is already known to other modules
2416 renaming fd would create duplicate FileData structure
2417 the best thing we can do is nothing
2418 FIXME: maybe we could copy stuff like marks
2420 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2424 file_data_set_path(fd, fd->change->dest);
2427 file_data_increment_version(fd);
2428 file_data_send_notification(fd, NOTIFY_CHANGE);
2433 gboolean file_data_sc_apply_ci(FileData *fd)
2436 FileDataChangeType type = fd->change->type;
2438 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2440 work = fd->sidecar_files;
2443 FileData *sfd = work->data;
2445 file_data_apply_ci(sfd);
2449 file_data_apply_ci(fd);
2454 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2457 if (fd->parent) fd = fd->parent;
2458 if (!g_list_find(list, fd)) return FALSE;
2460 work = fd->sidecar_files;
2463 if (!g_list_find(list, work->data)) return FALSE;
2470 static gboolean file_data_list_dump(GList *list)
2472 GList *work, *work2;
2477 FileData *fd = work->data;
2478 printf("%s\n", fd->name);
2479 work2 = fd->sidecar_files;
2482 FileData *fd = work2->data;
2483 printf(" %s\n", fd->name);
2484 work2 = work2->next;
2492 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2497 /* change partial groups to independent files */
2502 FileData *fd = work->data;
2505 if (!file_data_list_contains_whole_group(list, fd))
2507 file_data_disable_grouping(fd, TRUE);
2510 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2516 /* remove sidecars from the list,
2517 they can be still acessed via main_fd->sidecar_files */
2521 FileData *fd = work->data;
2525 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2527 out = g_list_prepend(out, file_data_ref(fd));
2531 filelist_free(list);
2532 out = g_list_reverse(out);
2542 * notify other modules about the change described by FileDataChangeInfo
2545 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2546 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2547 implementation in view_file_list.c */
2552 typedef struct _NotifyData NotifyData;
2554 struct _NotifyData {
2555 FileDataNotifyFunc func;
2557 NotifyPriority priority;
2560 static GList *notify_func_list = NULL;
2562 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2564 NotifyData *nda = (NotifyData *)a;
2565 NotifyData *ndb = (NotifyData *)b;
2567 if (nda->priority < ndb->priority) return -1;
2568 if (nda->priority > ndb->priority) return 1;
2572 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2575 GList *work = notify_func_list;
2579 NotifyData *nd = (NotifyData *)work->data;
2581 if (nd->func == func && nd->data == data)
2583 g_warning("Notify func already registered");
2589 nd = g_new(NotifyData, 1);
2592 nd->priority = priority;
2594 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2595 DEBUG_2("Notify func registered: %p", nd);
2600 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2602 GList *work = notify_func_list;
2606 NotifyData *nd = (NotifyData *)work->data;
2608 if (nd->func == func && nd->data == data)
2610 notify_func_list = g_list_delete_link(notify_func_list, work);
2612 DEBUG_2("Notify func unregistered: %p", nd);
2618 g_warning("Notify func not found");
2623 void file_data_send_notification(FileData *fd, NotifyType type)
2625 GList *work = notify_func_list;
2629 NotifyData *nd = (NotifyData *)work->data;
2631 nd->func(fd, type, nd->data);
2636 static GHashTable *file_data_monitor_pool = NULL;
2637 static guint realtime_monitor_id = 0; /* event source id */
2639 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2643 file_data_check_changed_files(fd);
2645 DEBUG_1("monitor %s", fd->path);
2648 static gboolean realtime_monitor_cb(gpointer data)
2650 if (!options->update_on_time_change) return TRUE;
2651 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2655 gboolean file_data_register_real_time_monitor(FileData *fd)
2661 if (!file_data_monitor_pool)
2662 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2664 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2666 DEBUG_1("Register realtime %d %s", count, fd->path);
2669 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2671 if (!realtime_monitor_id)
2673 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2679 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2683 g_assert(file_data_monitor_pool);
2685 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2687 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2689 g_assert(count > 0);
2694 g_hash_table_remove(file_data_monitor_pool, fd);
2696 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2698 file_data_unref(fd);
2700 if (g_hash_table_size(file_data_monitor_pool) == 0)
2702 g_source_remove(realtime_monitor_id);
2703 realtime_monitor_id = 0;
2709 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */