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(FileData *fd, GHashTable *basename_hash);
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 void file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
172 const gchar *ext = extension_from_path(fd->path);
173 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
175 list = g_hash_table_lookup(basename_hash, basename);
177 if (!g_list_find(list, fd))
179 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
180 g_hash_table_insert(basename_hash, basename, list);
189 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
192 const gchar *ext = extension_from_path(fd->path);
193 gchar *basename = ext ? g_strndup(fd->path, ext - fd->path) : g_strdup(fd->path);
195 list = g_hash_table_lookup(basename_hash, basename);
197 if (!g_list_find(list, fd)) return;
199 list = g_list_remove(list, fd);
204 g_hash_table_insert(basename_hash, basename, list);
208 g_hash_table_remove(basename_hash, basename);
214 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
216 filelist_free((GList *)value);
219 static void file_data_basename_hash_free(GHashTable *basename_hash)
221 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
222 g_hash_table_destroy(basename_hash);
225 static void file_data_set_collate_keys(FileData *fd)
227 gchar *caseless_name;
229 caseless_name = g_utf8_casefold(fd->name, -1);
231 g_free(fd->collate_key_name);
232 g_free(fd->collate_key_name_nocase);
234 #if 0 && GLIB_CHECK_VERSION(2, 8, 0)
235 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
236 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
238 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
239 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
241 g_free(caseless_name);
244 static void file_data_set_path(FileData *fd, const gchar *path)
246 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
247 g_assert(file_data_pool);
251 if (fd->original_path)
253 g_hash_table_remove(file_data_pool, fd->original_path);
254 g_free(fd->original_path);
257 g_assert(!g_hash_table_lookup(file_data_pool, path));
259 fd->original_path = g_strdup(path);
260 g_hash_table_insert(file_data_pool, fd->original_path, fd);
262 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
264 fd->path = g_strdup(path);
266 fd->extension = fd->name + 1;
267 file_data_set_collate_keys(fd);
271 fd->path = g_strdup(path);
272 fd->name = filename_from_path(fd->path);
274 if (strcmp(fd->name, "..") == 0)
276 gchar *dir = remove_level_from_path(path);
278 fd->path = remove_level_from_path(dir);
281 fd->extension = fd->name + 2;
282 file_data_set_collate_keys(fd);
285 else if (strcmp(fd->name, ".") == 0)
288 fd->path = remove_level_from_path(path);
290 fd->extension = fd->name + 1;
291 file_data_set_collate_keys(fd);
295 fd->extension = extension_from_path(fd->path);
296 if (fd->extension == NULL)
298 fd->extension = fd->name + strlen(fd->name);
301 file_data_set_collate_keys(fd);
304 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
306 gboolean ret = FALSE;
309 if (fd->size != st->st_size ||
310 fd->date != st->st_mtime)
312 fd->size = st->st_size;
313 fd->date = st->st_mtime;
314 fd->mode = st->st_mode;
315 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
316 fd->thumb_pixbuf = NULL;
317 file_data_increment_version(fd);
318 file_data_send_notification(fd, NOTIFY_REREAD);
322 work = fd->sidecar_files;
325 FileData *sfd = work->data;
329 if (!stat_utf8(sfd->path, &st))
333 file_data_disconnect_sidecar_file(fd, sfd);
338 ret |= file_data_check_changed_files_recursive(sfd, &st);
344 gboolean file_data_check_changed_files(FileData *fd)
346 gboolean ret = FALSE;
349 if (fd->parent) fd = fd->parent;
351 if (!stat_utf8(fd->path, &st))
355 FileData *sfd = NULL;
357 /* parent is missing, we have to rebuild whole group */
362 /* file_data_disconnect_sidecar_file might delete the file,
363 we have to keep the reference to prevent this */
364 sidecars = filelist_copy(fd->sidecar_files);
371 file_data_disconnect_sidecar_file(fd, sfd);
373 if (sfd) file_data_check_sidecars(sfd, NULL); /* this will group the sidecars back together */
374 /* now we can release the sidecars */
375 filelist_free(sidecars);
376 file_data_send_notification(fd, NOTIFY_REREAD);
380 ret |= file_data_check_changed_files_recursive(fd, &st);
386 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean check_sidecars, GHashTable *basename_hash)
390 DEBUG_2("file_data_new: '%s' %d %d", path_utf8, check_sidecars, !!basename_hash);
393 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
395 fd = g_hash_table_lookup(file_data_pool, path_utf8);
401 if (!fd && file_data_planned_change_hash)
403 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
406 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
408 file_data_apply_ci(fd);
417 file_data_basename_hash_insert(basename_hash, fd);
419 file_data_check_sidecars(fd, basename_hash);
423 changed = file_data_check_changed_files(fd);
425 changed = file_data_check_changed_files_recursive(fd, st);
426 if (changed && check_sidecars && sidecar_file_priority(fd->extension))
427 file_data_check_sidecars(fd, basename_hash);
428 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
433 fd = g_new0(FileData, 1);
435 fd->size = st->st_size;
436 fd->date = st->st_mtime;
437 fd->mode = st->st_mode;
439 fd->magick = 0x12345678;
441 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
442 if (basename_hash) file_data_basename_hash_insert(basename_hash, fd);
445 file_data_check_sidecars(fd, basename_hash);
450 /* extension must contain only ASCII characters */
451 static GList *check_case_insensitive_ext(gchar *path)
458 sl = path_from_utf8(path);
460 extl = strrchr(sl, '.');
464 extl++; /* the first char after . */
465 ext_len = strlen(extl);
467 for (i = 0; i < (1 << ext_len); i++)
470 gboolean skip = FALSE;
471 for (j = 0; j < ext_len; j++)
473 if (i & (1 << (ext_len - 1 - j)))
475 extl[j] = g_ascii_tolower(extl[j]);
476 /* make sure the result does not contain duplicates */
477 if (extl[j] == g_ascii_toupper(extl[j]))
479 /* no change, probably a number, we have already tested this combination */
485 extl[j] = g_ascii_toupper(extl[j]);
489 if (stat(sl, &st) == 0)
491 list = g_list_prepend(list, file_data_new_local(sl, &st, FALSE, FALSE));
500 static void file_data_check_sidecars(FileData *fd, GHashTable *basename_hash)
504 FileData *parent_fd = NULL;
506 const GList *basename_list = NULL;
507 GList *group_list = NULL;
508 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
511 base_len = fd->extension - fd->path;
512 fname = g_string_new_len(fd->path, base_len);
516 basename_list = g_hash_table_lookup(basename_hash, fname->str);
520 /* check for possible sidecar files;
521 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
522 they have fd->ref set to 0 and file_data unref must chack and free them all together
523 (using fd->ref would cause loops and leaks)
526 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
527 for case-only differences put lowercase first,
528 put the result to group_list
530 work = sidecar_ext_get_list();
533 gchar *ext = work->data;
539 g_string_truncate(fname, base_len);
540 g_string_append(fname, ext);
541 new_list = check_case_insensitive_ext(fname->str);
542 group_list = g_list_concat(group_list, new_list);
546 const GList *work2 = basename_list;
550 FileData *sfd = work2->data;
552 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
554 group_list = g_list_append(group_list, file_data_ref(sfd));
560 g_string_free(fname, TRUE);
562 /* process the group list - the first one is the parent file, others are sidecars */
566 FileData *new_fd = work->data;
569 if (new_fd->disable_grouping)
571 file_data_unref(new_fd);
575 new_fd->ref--; /* do not use ref here */
578 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
580 file_data_merge_sidecar_files(parent_fd, new_fd);
582 g_list_free(group_list);
586 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, GHashTable *basename_hash)
588 gchar *path_utf8 = path_to_utf8(path);
589 FileData *ret = file_data_new(path_utf8, st, check_sidecars, basename_hash);
595 FileData *file_data_new_simple(const gchar *path_utf8)
599 if (!stat_utf8(path_utf8, &st))
605 return file_data_new(path_utf8, &st, TRUE, NULL);
608 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
610 sfd->parent = target;
611 if (!g_list_find(target->sidecar_files, sfd))
612 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
613 file_data_increment_version(sfd); /* increments both sfd and target */
618 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
622 file_data_add_sidecar_file(target, source);
624 work = source->sidecar_files;
627 FileData *sfd = work->data;
628 file_data_add_sidecar_file(target, sfd);
632 g_list_free(source->sidecar_files);
633 source->sidecar_files = NULL;
635 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
640 #ifdef DEBUG_FILEDATA
641 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
643 FileData *file_data_ref(FileData *fd)
646 if (fd == NULL) return NULL;
647 #ifdef DEBUG_FILEDATA
648 if (fd->magick != 0x12345678)
649 DEBUG_0("fd magick mismatch at %s:%d", file, line);
651 g_assert(fd->magick == 0x12345678);
654 #ifdef DEBUG_FILEDATA
655 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
657 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
662 static void file_data_free(FileData *fd)
664 g_assert(fd->magick == 0x12345678);
665 g_assert(fd->ref == 0);
667 metadata_cache_free(fd);
668 g_hash_table_remove(file_data_pool, fd->original_path);
671 g_free(fd->original_path);
672 g_free(fd->collate_key_name);
673 g_free(fd->collate_key_name_nocase);
674 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
675 histmap_free(fd->histmap);
677 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
679 file_data_change_info_free(NULL, fd);
683 #ifdef DEBUG_FILEDATA
684 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
686 void file_data_unref(FileData *fd)
689 if (fd == NULL) return;
690 #ifdef DEBUG_FILEDATA
691 if (fd->magick != 0x12345678)
692 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
694 g_assert(fd->magick == 0x12345678);
697 #ifdef DEBUG_FILEDATA
698 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
700 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
705 FileData *parent = fd->parent ? fd->parent : fd;
707 if (parent->ref > 0) return;
709 work = parent->sidecar_files;
712 FileData *sfd = work->data;
713 if (sfd->ref > 0) return;
717 /* none of parent/children is referenced, we can free everything */
719 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
721 work = parent->sidecar_files;
724 FileData *sfd = work->data;
729 g_list_free(parent->sidecar_files);
730 parent->sidecar_files = NULL;
732 file_data_free(parent);
736 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
738 sfd->parent = target;
739 g_assert(g_list_find(target->sidecar_files, sfd));
741 file_data_increment_version(sfd); /* increments both sfd and target */
743 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
755 /* disables / enables grouping for particular file, sends UPDATE notification */
756 void file_data_disable_grouping(FileData *fd, gboolean disable)
758 if (!fd->disable_grouping == !disable) return;
760 fd->disable_grouping = !!disable;
766 FileData *parent = file_data_ref(fd->parent);
767 file_data_disconnect_sidecar_file(parent, fd);
768 file_data_send_notification(parent, NOTIFY_GROUPING);
769 file_data_unref(parent);
771 else if (fd->sidecar_files)
773 GList *sidecar_files = filelist_copy(fd->sidecar_files);
774 GList *work = sidecar_files;
777 FileData *sfd = work->data;
779 file_data_disconnect_sidecar_file(fd, sfd);
780 file_data_send_notification(sfd, NOTIFY_GROUPING);
782 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
783 filelist_free(sidecar_files);
787 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
792 file_data_increment_version(fd);
793 file_data_check_sidecars(fd, FALSE);
795 file_data_send_notification(fd, NOTIFY_GROUPING);
798 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
805 FileData *fd = work->data;
807 file_data_disable_grouping(fd, disable);
813 /* compare name without extension */
814 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
816 size_t len1 = fd1->extension - fd1->name;
817 size_t len2 = fd2->extension - fd2->name;
819 if (len1 < len2) return -1;
820 if (len1 > len2) return 1;
822 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
825 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
827 if (!fdci && fd) fdci = fd->change;
831 g_free(fdci->source);
836 if (fd) fd->change = NULL;
839 static gboolean file_data_can_write_directly(FileData *fd)
841 return filter_name_is_writable(fd->extension);
844 static gboolean file_data_can_write_sidecar(FileData *fd)
846 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
849 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
851 gchar *sidecar_path = NULL;
854 if (!file_data_can_write_sidecar(fd)) return NULL;
856 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
859 FileData *sfd = work->data;
861 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
863 sidecar_path = g_strdup(sfd->path);
868 if (!existing_only && !sidecar_path)
870 gchar *base = remove_extension_from_path(fd->path);
871 sidecar_path = g_strconcat(base, ".xmp", NULL);
880 *-----------------------------------------------------------------------------
881 * sidecar file info struct
882 *-----------------------------------------------------------------------------
887 static gint sidecar_file_priority(const gchar *path)
889 const gchar *extension = extension_from_path(path);
893 if (extension == NULL)
896 work = sidecar_ext_get_list();
899 gchar *ext = work->data;
902 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
910 *-----------------------------------------------------------------------------
912 *-----------------------------------------------------------------------------
915 static SortType filelist_sort_method = SORT_NONE;
916 static gboolean filelist_sort_ascend = TRUE;
919 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
921 if (!filelist_sort_ascend)
928 switch (filelist_sort_method)
933 if (fa->size < fb->size) return -1;
934 if (fa->size > fb->size) return 1;
935 /* fall back to name */
938 if (fa->date < fb->date) return -1;
939 if (fa->date > fb->date) return 1;
940 /* fall back to name */
942 #ifdef HAVE_STRVERSCMP
944 return strverscmp(fa->name, fb->name);
951 if (options->file_sort.case_sensitive)
952 return strcmp(fa->collate_key_name, fb->collate_key_name);
954 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
957 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
959 filelist_sort_method = method;
960 filelist_sort_ascend = ascend;
961 return filelist_sort_compare_filedata(fa, fb);
964 static gint filelist_sort_file_cb(gpointer a, gpointer b)
966 return filelist_sort_compare_filedata(a, b);
969 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
971 filelist_sort_method = method;
972 filelist_sort_ascend = ascend;
973 return g_list_sort(list, cb);
976 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
978 filelist_sort_method = method;
979 filelist_sort_ascend = ascend;
980 return g_list_insert_sorted(list, data, cb);
983 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
985 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
988 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
990 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
994 static GList *filelist_filter_out_sidecars(GList *flist)
997 GList *flist_filtered = NULL;
1001 FileData *fd = work->data;
1004 if (fd->parent) /* remove fd's that are children */
1005 file_data_unref(fd);
1007 flist_filtered = g_list_prepend(flist_filtered, fd);
1011 return flist_filtered;
1014 static gboolean is_hidden_file(const gchar *name)
1016 if (name[0] != '.') return FALSE;
1017 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
1021 static gboolean filelist_read_real(FileData *dir_fd, GList **files, GList **dirs, gboolean follow_symlinks)
1026 GList *dlist = NULL;
1027 GList *flist = NULL;
1028 gint (*stat_func)(const gchar *path, struct stat *buf);
1029 GHashTable *basename_hash = NULL;
1031 g_assert(files || dirs);
1033 if (files) *files = NULL;
1034 if (dirs) *dirs = NULL;
1036 pathl = path_from_utf8(dir_fd->path);
1037 if (!pathl) return FALSE;
1039 dp = opendir(pathl);
1046 if (files) basename_hash = file_data_basename_hash_new();
1048 if (follow_symlinks)
1053 while ((dir = readdir(dp)) != NULL)
1055 struct stat ent_sbuf;
1056 const gchar *name = dir->d_name;
1059 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
1062 filepath = g_build_filename(pathl, name, NULL);
1063 if (stat_func(filepath, &ent_sbuf) >= 0)
1065 if (S_ISDIR(ent_sbuf.st_mode))
1067 /* we ignore the .thumbnails dir for cleanliness */
1069 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1070 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1071 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1072 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1074 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, FALSE, NULL));
1079 if (files && filter_name_exists(name))
1081 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, TRUE, basename_hash));
1087 if (errno == EOVERFLOW)
1089 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1098 if (basename_hash) file_data_basename_hash_free(basename_hash);
1100 if (dirs) *dirs = dlist;
1101 if (files) *files = filelist_filter_out_sidecars(flist);
1106 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1108 return filelist_read_real(dir_fd, files, dirs, TRUE);
1111 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1113 return filelist_read_real(dir_fd, files, dirs, FALSE);
1116 void filelist_free(GList *list)
1123 file_data_unref((FileData *)work->data);
1131 GList *filelist_copy(GList *list)
1133 GList *new_list = NULL;
1144 new_list = g_list_prepend(new_list, file_data_ref(fd));
1147 return g_list_reverse(new_list);
1150 GList *filelist_from_path_list(GList *list)
1152 GList *new_list = NULL;
1163 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1166 return g_list_reverse(new_list);
1169 GList *filelist_to_path_list(GList *list)
1171 GList *new_list = NULL;
1182 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1185 return g_list_reverse(new_list);
1188 GList *filelist_filter(GList *list, gboolean is_dir_list)
1192 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1197 FileData *fd = (FileData *)(work->data);
1198 const gchar *name = fd->name;
1200 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1201 (!is_dir_list && !filter_name_exists(name)) ||
1202 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1203 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1207 list = g_list_remove_link(list, link);
1208 file_data_unref(fd);
1219 *-----------------------------------------------------------------------------
1220 * filelist recursive
1221 *-----------------------------------------------------------------------------
1224 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1226 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1229 GList *filelist_sort_path(GList *list)
1231 return g_list_sort(list, filelist_sort_path_cb);
1234 static void filelist_recursive_append(GList **list, GList *dirs)
1241 FileData *fd = (FileData *)(work->data);
1245 if (filelist_read(fd, &f, &d))
1247 f = filelist_filter(f, FALSE);
1248 f = filelist_sort_path(f);
1249 *list = g_list_concat(*list, f);
1251 d = filelist_filter(d, TRUE);
1252 d = filelist_sort_path(d);
1253 filelist_recursive_append(list, d);
1261 GList *filelist_recursive(FileData *dir_fd)
1266 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1267 list = filelist_filter(list, FALSE);
1268 list = filelist_sort_path(list);
1270 d = filelist_filter(d, TRUE);
1271 d = filelist_sort_path(d);
1272 filelist_recursive_append(&list, d);
1280 * marks and orientation
1283 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1284 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1285 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1286 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1288 gboolean file_data_get_mark(FileData *fd, gint n)
1290 gboolean valid = (fd->valid_marks & (1 << n));
1292 if (file_data_get_mark_func[n] && !valid)
1294 guint old = fd->marks;
1295 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1297 if (!value != !(fd->marks & (1 << n)))
1299 fd->marks = fd->marks ^ (1 << n);
1302 fd->valid_marks |= (1 << n);
1303 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1305 file_data_unref(fd);
1307 else if (!old && fd->marks)
1313 return !!(fd->marks & (1 << n));
1316 guint file_data_get_marks(FileData *fd)
1319 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1323 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1326 if (!value == !file_data_get_mark(fd, n)) return;
1328 if (file_data_set_mark_func[n])
1330 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1335 fd->marks = fd->marks ^ (1 << n);
1337 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1339 file_data_unref(fd);
1341 else if (!old && fd->marks)
1346 file_data_increment_version(fd);
1347 file_data_send_notification(fd, NOTIFY_MARKS);
1350 gboolean file_data_filter_marks(FileData *fd, guint filter)
1353 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1354 return ((fd->marks & filter) == filter);
1357 GList *file_data_filter_marks_list(GList *list, guint filter)
1364 FileData *fd = work->data;
1368 if (!file_data_filter_marks(fd, filter))
1370 list = g_list_remove_link(list, link);
1371 file_data_unref(fd);
1379 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1381 FileData *fd = value;
1382 file_data_increment_version(fd);
1383 file_data_send_notification(fd, NOTIFY_MARKS);
1386 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1388 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1390 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1392 file_data_get_mark_func[n] = get_mark_func;
1393 file_data_set_mark_func[n] = set_mark_func;
1394 file_data_mark_func_data[n] = data;
1395 file_data_destroy_mark_func[n] = notify;
1399 /* this effectively changes all known files */
1400 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1406 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1408 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1409 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1410 if (data) *data = file_data_mark_func_data[n];
1413 gint file_data_get_user_orientation(FileData *fd)
1415 return fd->user_orientation;
1418 void file_data_set_user_orientation(FileData *fd, gint value)
1420 if (fd->user_orientation == value) return;
1422 fd->user_orientation = value;
1423 file_data_increment_version(fd);
1424 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1429 * file_data - operates on the given fd
1430 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1434 /* return list of sidecar file extensions in a string */
1435 gchar *file_data_sc_list_to_string(FileData *fd)
1438 GString *result = g_string_new("");
1440 work = fd->sidecar_files;
1443 FileData *sfd = work->data;
1445 result = g_string_append(result, "+ ");
1446 result = g_string_append(result, sfd->extension);
1448 if (work) result = g_string_append_c(result, ' ');
1451 return g_string_free(result, FALSE);
1457 * add FileDataChangeInfo (see typedefs.h) for the given operation
1458 * uses file_data_add_change_info
1460 * fails if the fd->change already exists - change operations can't run in parallel
1461 * fd->change_info works as a lock
1463 * dest can be NULL - in this case the current name is used for now, it will
1468 FileDataChangeInfo types:
1470 MOVE - path is changed, name may be changed too
1471 RENAME - path remains unchanged, name is changed
1472 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1473 sidecar names are changed too, extensions are not changed
1475 UPDATE - file size, date or grouping has been changed
1478 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1480 FileDataChangeInfo *fdci;
1482 if (fd->change) return FALSE;
1484 fdci = g_new0(FileDataChangeInfo, 1);
1489 fdci->source = g_strdup(src);
1491 fdci->source = g_strdup(fd->path);
1494 fdci->dest = g_strdup(dest);
1501 static void file_data_planned_change_remove(FileData *fd)
1503 if (file_data_planned_change_hash &&
1504 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1506 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1508 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1509 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1510 file_data_unref(fd);
1511 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1513 g_hash_table_destroy(file_data_planned_change_hash);
1514 file_data_planned_change_hash = NULL;
1515 DEBUG_1("planned change: empty");
1522 void file_data_free_ci(FileData *fd)
1524 FileDataChangeInfo *fdci = fd->change;
1528 file_data_planned_change_remove(fd);
1530 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1532 g_free(fdci->source);
1540 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1542 FileDataChangeInfo *fdci = fd->change;
1544 fdci->regroup_when_finished = enable;
1547 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1551 if (fd->parent) fd = fd->parent;
1553 if (fd->change) return FALSE;
1555 work = fd->sidecar_files;
1558 FileData *sfd = work->data;
1560 if (sfd->change) return FALSE;
1564 file_data_add_ci(fd, type, NULL, NULL);
1566 work = fd->sidecar_files;
1569 FileData *sfd = work->data;
1571 file_data_add_ci(sfd, type, NULL, NULL);
1578 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1582 if (fd->parent) fd = fd->parent;
1584 if (!fd->change || fd->change->type != type) return FALSE;
1586 work = fd->sidecar_files;
1589 FileData *sfd = work->data;
1591 if (!sfd->change || sfd->change->type != type) return FALSE;
1599 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1601 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1602 file_data_sc_update_ci_copy(fd, dest_path);
1606 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1608 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1609 file_data_sc_update_ci_move(fd, dest_path);
1613 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1615 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1616 file_data_sc_update_ci_rename(fd, dest_path);
1620 gboolean file_data_sc_add_ci_delete(FileData *fd)
1622 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1625 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1627 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1628 file_data_sc_update_ci_unspecified(fd, dest_path);
1632 gboolean file_data_add_ci_write_metadata(FileData *fd)
1634 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1637 void file_data_sc_free_ci(FileData *fd)
1641 if (fd->parent) fd = fd->parent;
1643 file_data_free_ci(fd);
1645 work = fd->sidecar_files;
1648 FileData *sfd = work->data;
1650 file_data_free_ci(sfd);
1655 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1658 gboolean ret = TRUE;
1663 FileData *fd = work->data;
1665 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1672 static void file_data_sc_revert_ci_list(GList *fd_list)
1679 FileData *fd = work->data;
1681 file_data_sc_free_ci(fd);
1686 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1693 FileData *fd = work->data;
1695 if (!func(fd, dest))
1697 file_data_sc_revert_ci_list(work->prev);
1706 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1708 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1711 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1713 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1716 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1718 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1721 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1723 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1726 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1729 gboolean ret = TRUE;
1734 FileData *fd = work->data;
1736 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1743 void file_data_free_ci_list(GList *fd_list)
1750 FileData *fd = work->data;
1752 file_data_free_ci(fd);
1757 void file_data_sc_free_ci_list(GList *fd_list)
1764 FileData *fd = work->data;
1766 file_data_sc_free_ci(fd);
1772 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1773 * fails if fd->change does not exist or the change type does not match
1776 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1778 FileDataChangeType type = fd->change->type;
1780 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1784 if (!file_data_planned_change_hash)
1785 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1787 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1789 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1790 g_hash_table_remove(file_data_planned_change_hash, old_path);
1791 file_data_unref(fd);
1794 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1799 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1800 g_hash_table_remove(file_data_planned_change_hash, new_path);
1801 file_data_unref(ofd);
1804 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1806 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1811 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1813 gchar *old_path = fd->change->dest;
1815 fd->change->dest = g_strdup(dest_path);
1816 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1820 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1822 const gchar *extension = extension_from_path(fd->change->source);
1823 gchar *base = remove_extension_from_path(dest_path);
1824 gchar *old_path = fd->change->dest;
1826 fd->change->dest = g_strconcat(base, extension, NULL);
1827 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1833 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1836 gchar *dest_path_full = NULL;
1838 if (fd->parent) fd = fd->parent;
1842 dest_path = fd->path;
1844 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1846 gchar *dir = remove_level_from_path(fd->path);
1848 dest_path_full = g_build_filename(dir, dest_path, NULL);
1850 dest_path = dest_path_full;
1852 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1854 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1855 dest_path = dest_path_full;
1858 file_data_update_ci_dest(fd, dest_path);
1860 work = fd->sidecar_files;
1863 FileData *sfd = work->data;
1865 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1869 g_free(dest_path_full);
1872 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1874 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1875 file_data_sc_update_ci(fd, dest_path);
1879 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1881 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1884 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1886 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1889 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1891 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1894 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1896 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1899 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1901 gboolean (*func)(FileData *, const gchar *))
1904 gboolean ret = TRUE;
1909 FileData *fd = work->data;
1911 if (!func(fd, dest)) ret = FALSE;
1918 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1920 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1923 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1925 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1928 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1930 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1935 * verify source and dest paths - dest image exists, etc.
1936 * it should detect all possible problems with the planned operation
1939 gint file_data_verify_ci(FileData *fd)
1941 gint ret = CHANGE_OK;
1946 DEBUG_1("Change checked: no change info: %s", fd->path);
1950 if (!isname(fd->path))
1952 /* this probably should not happen */
1953 ret |= CHANGE_NO_SRC;
1954 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1958 dir = remove_level_from_path(fd->path);
1960 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1961 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1962 fd->change->type != FILEDATA_CHANGE_RENAME &&
1963 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1966 ret |= CHANGE_WARN_UNSAVED_META;
1967 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1970 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1971 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1972 !access_file(fd->path, R_OK))
1974 ret |= CHANGE_NO_READ_PERM;
1975 DEBUG_1("Change checked: no read permission: %s", fd->path);
1977 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1978 !access_file(dir, W_OK))
1980 ret |= CHANGE_NO_WRITE_PERM_DIR;
1981 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1983 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1984 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1985 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1986 !access_file(fd->path, W_OK))
1988 ret |= CHANGE_WARN_NO_WRITE_PERM;
1989 DEBUG_1("Change checked: no write permission: %s", fd->path);
1991 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1992 - that means that there are no hard errors and warnings can be disabled
1993 - the destination is determined during the check
1995 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1997 /* determine destination file */
1998 gboolean have_dest = FALSE;
1999 gchar *dest_dir = NULL;
2001 if (options->metadata.save_in_image_file)
2003 if (file_data_can_write_directly(fd))
2005 /* we can write the file directly */
2006 if (access_file(fd->path, W_OK))
2012 if (options->metadata.warn_on_write_problems)
2014 ret |= CHANGE_WARN_NO_WRITE_PERM;
2015 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2019 else if (file_data_can_write_sidecar(fd))
2021 /* we can write sidecar */
2022 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2023 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2025 file_data_update_ci_dest(fd, sidecar);
2030 if (options->metadata.warn_on_write_problems)
2032 ret |= CHANGE_WARN_NO_WRITE_PERM;
2033 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2042 /* write private metadata file under ~/.geeqie */
2044 /* If an existing metadata file exists, we will try writing to
2045 * it's location regardless of the user's preference.
2047 gchar *metadata_path = NULL;
2049 /* but ignore XMP if we are not able to write it */
2050 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2052 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2054 if (metadata_path && !access_file(metadata_path, W_OK))
2056 g_free(metadata_path);
2057 metadata_path = NULL;
2064 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2065 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2067 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2069 metadata_path = g_build_filename(dest_dir, filename, NULL);
2073 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2075 file_data_update_ci_dest(fd, metadata_path);
2080 ret |= CHANGE_NO_WRITE_PERM_DEST;
2081 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2083 g_free(metadata_path);
2088 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2093 same = (strcmp(fd->path, fd->change->dest) == 0);
2097 const gchar *dest_ext = extension_from_path(fd->change->dest);
2098 if (!dest_ext) dest_ext = "";
2100 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2102 ret |= CHANGE_WARN_CHANGED_EXT;
2103 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2108 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2110 ret |= CHANGE_WARN_SAME;
2111 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2115 dest_dir = remove_level_from_path(fd->change->dest);
2117 if (!isdir(dest_dir))
2119 ret |= CHANGE_NO_DEST_DIR;
2120 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2122 else if (!access_file(dest_dir, W_OK))
2124 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2125 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2129 if (isfile(fd->change->dest))
2131 if (!access_file(fd->change->dest, W_OK))
2133 ret |= CHANGE_NO_WRITE_PERM_DEST;
2134 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2138 ret |= CHANGE_WARN_DEST_EXISTS;
2139 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2142 else if (isdir(fd->change->dest))
2144 ret |= CHANGE_DEST_EXISTS;
2145 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2152 fd->change->error = ret;
2153 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2160 gint file_data_sc_verify_ci(FileData *fd)
2165 ret = file_data_verify_ci(fd);
2167 work = fd->sidecar_files;
2170 FileData *sfd = work->data;
2172 ret |= file_data_verify_ci(sfd);
2179 gchar *file_data_get_error_string(gint error)
2181 GString *result = g_string_new("");
2183 if (error & CHANGE_NO_SRC)
2185 if (result->len > 0) g_string_append(result, ", ");
2186 g_string_append(result, _("file or directory does not exist"));
2189 if (error & CHANGE_DEST_EXISTS)
2191 if (result->len > 0) g_string_append(result, ", ");
2192 g_string_append(result, _("destination already exists"));
2195 if (error & CHANGE_NO_WRITE_PERM_DEST)
2197 if (result->len > 0) g_string_append(result, ", ");
2198 g_string_append(result, _("destination can't be overwritten"));
2201 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2203 if (result->len > 0) g_string_append(result, ", ");
2204 g_string_append(result, _("destination directory is not writable"));
2207 if (error & CHANGE_NO_DEST_DIR)
2209 if (result->len > 0) g_string_append(result, ", ");
2210 g_string_append(result, _("destination directory does not exist"));
2213 if (error & CHANGE_NO_WRITE_PERM_DIR)
2215 if (result->len > 0) g_string_append(result, ", ");
2216 g_string_append(result, _("source directory is not writable"));
2219 if (error & CHANGE_NO_READ_PERM)
2221 if (result->len > 0) g_string_append(result, ", ");
2222 g_string_append(result, _("no read permission"));
2225 if (error & CHANGE_WARN_NO_WRITE_PERM)
2227 if (result->len > 0) g_string_append(result, ", ");
2228 g_string_append(result, _("file is readonly"));
2231 if (error & CHANGE_WARN_DEST_EXISTS)
2233 if (result->len > 0) g_string_append(result, ", ");
2234 g_string_append(result, _("destination already exists and will be overwritten"));
2237 if (error & CHANGE_WARN_SAME)
2239 if (result->len > 0) g_string_append(result, ", ");
2240 g_string_append(result, _("source and destination are the same"));
2243 if (error & CHANGE_WARN_CHANGED_EXT)
2245 if (result->len > 0) g_string_append(result, ", ");
2246 g_string_append(result, _("source and destination have different extension"));
2249 if (error & CHANGE_WARN_UNSAVED_META)
2251 if (result->len > 0) g_string_append(result, ", ");
2252 g_string_append(result, _("there are unsaved metadata changes for the file"));
2255 return g_string_free(result, FALSE);
2258 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2261 gint all_errors = 0;
2262 gint common_errors = ~0;
2267 if (!list) return 0;
2269 num = g_list_length(list);
2270 errors = g_new(int, num);
2281 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2282 all_errors |= error;
2283 common_errors &= error;
2290 if (desc && all_errors)
2293 GString *result = g_string_new("");
2297 gchar *str = file_data_get_error_string(common_errors);
2298 g_string_append(result, str);
2299 g_string_append(result, "\n");
2313 error = errors[i] & ~common_errors;
2317 gchar *str = file_data_get_error_string(error);
2318 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2323 *desc = g_string_free(result, FALSE);
2332 * perform the change described by FileFataChangeInfo
2333 * it is used for internal operations,
2334 * this function actually operates with files on the filesystem
2335 * it should implement safe delete
2338 static gboolean file_data_perform_move(FileData *fd)
2340 g_assert(!strcmp(fd->change->source, fd->path));
2341 return move_file(fd->change->source, fd->change->dest);
2344 static gboolean file_data_perform_copy(FileData *fd)
2346 g_assert(!strcmp(fd->change->source, fd->path));
2347 return copy_file(fd->change->source, fd->change->dest);
2350 static gboolean file_data_perform_delete(FileData *fd)
2352 if (isdir(fd->path) && !islink(fd->path))
2353 return rmdir_utf8(fd->path);
2355 if (options->file_ops.safe_delete_enable)
2356 return file_util_safe_unlink(fd->path);
2358 return unlink_file(fd->path);
2361 gboolean file_data_perform_ci(FileData *fd)
2363 FileDataChangeType type = fd->change->type;
2367 case FILEDATA_CHANGE_MOVE:
2368 return file_data_perform_move(fd);
2369 case FILEDATA_CHANGE_COPY:
2370 return file_data_perform_copy(fd);
2371 case FILEDATA_CHANGE_RENAME:
2372 return file_data_perform_move(fd); /* the same as move */
2373 case FILEDATA_CHANGE_DELETE:
2374 return file_data_perform_delete(fd);
2375 case FILEDATA_CHANGE_WRITE_METADATA:
2376 return metadata_write_perform(fd);
2377 case FILEDATA_CHANGE_UNSPECIFIED:
2378 /* nothing to do here */
2386 gboolean file_data_sc_perform_ci(FileData *fd)
2389 gboolean ret = TRUE;
2390 FileDataChangeType type = fd->change->type;
2392 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2394 work = fd->sidecar_files;
2397 FileData *sfd = work->data;
2399 if (!file_data_perform_ci(sfd)) ret = FALSE;
2403 if (!file_data_perform_ci(fd)) ret = FALSE;
2409 * updates FileData structure according to FileDataChangeInfo
2412 gboolean file_data_apply_ci(FileData *fd)
2414 FileDataChangeType type = fd->change->type;
2417 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2419 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2420 file_data_planned_change_remove(fd);
2422 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2424 /* this change overwrites another file which is already known to other modules
2425 renaming fd would create duplicate FileData structure
2426 the best thing we can do is nothing
2427 FIXME: maybe we could copy stuff like marks
2429 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2433 file_data_set_path(fd, fd->change->dest);
2436 file_data_increment_version(fd);
2437 file_data_send_notification(fd, NOTIFY_CHANGE);
2442 gboolean file_data_sc_apply_ci(FileData *fd)
2445 FileDataChangeType type = fd->change->type;
2447 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2449 work = fd->sidecar_files;
2452 FileData *sfd = work->data;
2454 file_data_apply_ci(sfd);
2458 file_data_apply_ci(fd);
2463 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2466 if (fd->parent) fd = fd->parent;
2467 if (!g_list_find(list, fd)) return FALSE;
2469 work = fd->sidecar_files;
2472 if (!g_list_find(list, work->data)) return FALSE;
2479 static gboolean file_data_list_dump(GList *list)
2481 GList *work, *work2;
2486 FileData *fd = work->data;
2487 printf("%s\n", fd->name);
2488 work2 = fd->sidecar_files;
2491 FileData *fd = work2->data;
2492 printf(" %s\n", fd->name);
2493 work2 = work2->next;
2501 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2506 /* change partial groups to independent files */
2511 FileData *fd = work->data;
2514 if (!file_data_list_contains_whole_group(list, fd))
2516 file_data_disable_grouping(fd, TRUE);
2519 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2525 /* remove sidecars from the list,
2526 they can be still acessed via main_fd->sidecar_files */
2530 FileData *fd = work->data;
2534 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2536 out = g_list_prepend(out, file_data_ref(fd));
2540 filelist_free(list);
2541 out = g_list_reverse(out);
2551 * notify other modules about the change described by FileDataChangeInfo
2554 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2555 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2556 implementation in view_file_list.c */
2561 typedef struct _NotifyData NotifyData;
2563 struct _NotifyData {
2564 FileDataNotifyFunc func;
2566 NotifyPriority priority;
2569 static GList *notify_func_list = NULL;
2571 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2573 NotifyData *nda = (NotifyData *)a;
2574 NotifyData *ndb = (NotifyData *)b;
2576 if (nda->priority < ndb->priority) return -1;
2577 if (nda->priority > ndb->priority) return 1;
2581 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2584 GList *work = notify_func_list;
2588 NotifyData *nd = (NotifyData *)work->data;
2590 if (nd->func == func && nd->data == data)
2592 g_warning("Notify func already registered");
2598 nd = g_new(NotifyData, 1);
2601 nd->priority = priority;
2603 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2604 DEBUG_2("Notify func registered: %p", nd);
2609 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2611 GList *work = notify_func_list;
2615 NotifyData *nd = (NotifyData *)work->data;
2617 if (nd->func == func && nd->data == data)
2619 notify_func_list = g_list_delete_link(notify_func_list, work);
2621 DEBUG_2("Notify func unregistered: %p", nd);
2627 g_warning("Notify func not found");
2632 void file_data_send_notification(FileData *fd, NotifyType type)
2634 GList *work = notify_func_list;
2638 NotifyData *nd = (NotifyData *)work->data;
2640 nd->func(fd, type, nd->data);
2645 static GHashTable *file_data_monitor_pool = NULL;
2646 static guint realtime_monitor_id = 0; /* event source id */
2648 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2652 file_data_check_changed_files(fd);
2654 DEBUG_1("monitor %s", fd->path);
2657 static gboolean realtime_monitor_cb(gpointer data)
2659 if (!options->update_on_time_change) return TRUE;
2660 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2664 gboolean file_data_register_real_time_monitor(FileData *fd)
2670 if (!file_data_monitor_pool)
2671 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2673 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2675 DEBUG_1("Register realtime %d %s", count, fd->path);
2678 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2680 if (!realtime_monitor_id)
2682 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2688 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2692 g_assert(file_data_monitor_pool);
2694 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2696 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2698 g_assert(count > 0);
2703 g_hash_table_remove(file_data_monitor_pool, fd);
2705 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2707 file_data_unref(fd);
2709 if (g_hash_table_size(file_data_monitor_pool) == 0)
2711 g_source_remove(realtime_monitor_id);
2712 realtime_monitor_id = 0;
2718 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */