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 disable_sidecars, GHashTable *basename_hash)
390 DEBUG_2("file_data_new: '%s' %d %d", path_utf8, disable_sidecars, !!basename_hash);
392 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
395 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
397 fd = g_hash_table_lookup(file_data_pool, path_utf8);
403 if (!fd && file_data_planned_change_hash)
405 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
408 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
410 file_data_apply_ci(fd);
418 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
422 file_data_basename_hash_insert(basename_hash, fd);
423 if (!disable_sidecars)
424 file_data_check_sidecars(fd, basename_hash);
428 changed = file_data_check_changed_files(fd);
430 changed = file_data_check_changed_files_recursive(fd, st);
431 if (changed && !disable_sidecars && sidecar_file_priority(fd->extension))
432 file_data_check_sidecars(fd, basename_hash);
433 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
438 fd = g_new0(FileData, 1);
440 fd->size = st->st_size;
441 fd->date = st->st_mtime;
442 fd->mode = st->st_mode;
444 fd->magick = 0x12345678;
446 if (disable_sidecars) fd->disable_grouping = TRUE;
448 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
449 if (basename_hash) file_data_basename_hash_insert(basename_hash, fd);
451 if (!disable_sidecars)
453 g_assert(basename_hash);
454 file_data_check_sidecars(fd, basename_hash);
461 static void file_data_check_sidecars(FileData *fd, GHashTable *basename_hash)
465 FileData *parent_fd = NULL;
467 const GList *basename_list = NULL;
468 GList *group_list = NULL;
469 if (fd->disable_grouping || !sidecar_file_priority(fd->extension))
472 base_len = fd->extension - fd->path;
473 fname = g_string_new_len(fd->path, base_len);
475 basename_list = g_hash_table_lookup(basename_hash, fname->str);
478 /* check for possible sidecar files;
479 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
480 they have fd->ref set to 0 and file_data unref must chack and free them all together
481 (using fd->ref would cause loops and leaks)
484 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
485 for case-only differences put lowercase first,
486 put the result to group_list
488 work = sidecar_ext_get_list();
491 gchar *ext = work->data;
494 const GList *work2 = basename_list;
498 FileData *sfd = work2->data;
500 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
502 group_list = g_list_append(group_list, file_data_ref(sfd));
507 g_string_free(fname, TRUE);
509 /* process the group list - the first one is the parent file, others are sidecars */
513 FileData *new_fd = work->data;
516 if (new_fd->disable_grouping)
518 file_data_unref(new_fd);
522 new_fd->ref--; /* do not use ref here */
525 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
527 file_data_merge_sidecar_files(parent_fd, new_fd);
529 g_list_free(group_list);
533 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars, GHashTable *basename_hash)
535 gchar *path_utf8 = path_to_utf8(path);
536 FileData *ret = file_data_new(path_utf8, st, disable_sidecars, basename_hash);
542 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
544 sfd->parent = target;
545 if (!g_list_find(target->sidecar_files, sfd))
546 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
547 file_data_increment_version(sfd); /* increments both sfd and target */
552 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
556 file_data_add_sidecar_file(target, source);
558 work = source->sidecar_files;
561 FileData *sfd = work->data;
562 file_data_add_sidecar_file(target, sfd);
566 g_list_free(source->sidecar_files);
567 source->sidecar_files = NULL;
569 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
574 #ifdef DEBUG_FILEDATA
575 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
577 FileData *file_data_ref(FileData *fd)
580 if (fd == NULL) return NULL;
581 #ifdef DEBUG_FILEDATA
582 if (fd->magick != 0x12345678)
583 DEBUG_0("fd magick mismatch at %s:%d", file, line);
585 g_assert(fd->magick == 0x12345678);
588 #ifdef DEBUG_FILEDATA
589 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
591 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
596 static void file_data_free(FileData *fd)
598 g_assert(fd->magick == 0x12345678);
599 g_assert(fd->ref == 0);
601 metadata_cache_free(fd);
602 g_hash_table_remove(file_data_pool, fd->original_path);
605 g_free(fd->original_path);
606 g_free(fd->collate_key_name);
607 g_free(fd->collate_key_name_nocase);
608 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
609 histmap_free(fd->histmap);
611 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
613 file_data_change_info_free(NULL, fd);
617 #ifdef DEBUG_FILEDATA
618 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
620 void file_data_unref(FileData *fd)
623 if (fd == NULL) return;
624 #ifdef DEBUG_FILEDATA
625 if (fd->magick != 0x12345678)
626 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
628 g_assert(fd->magick == 0x12345678);
631 #ifdef DEBUG_FILEDATA
632 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
634 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
639 FileData *parent = fd->parent ? fd->parent : fd;
641 if (parent->ref > 0) return;
643 work = parent->sidecar_files;
646 FileData *sfd = work->data;
647 if (sfd->ref > 0) return;
651 /* none of parent/children is referenced, we can free everything */
653 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
655 work = parent->sidecar_files;
658 FileData *sfd = work->data;
663 g_list_free(parent->sidecar_files);
664 parent->sidecar_files = NULL;
666 file_data_free(parent);
670 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
672 sfd->parent = target;
673 g_assert(g_list_find(target->sidecar_files, sfd));
675 file_data_increment_version(sfd); /* increments both sfd and target */
677 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
689 /* disables / enables grouping for particular file, sends UPDATE notification */
690 void file_data_disable_grouping(FileData *fd, gboolean disable)
692 if (!fd->disable_grouping == !disable) return;
694 fd->disable_grouping = !!disable;
700 FileData *parent = file_data_ref(fd->parent);
701 file_data_disconnect_sidecar_file(parent, fd);
702 file_data_send_notification(parent, NOTIFY_GROUPING);
703 file_data_unref(parent);
705 else if (fd->sidecar_files)
707 GList *sidecar_files = filelist_copy(fd->sidecar_files);
708 GList *work = sidecar_files;
711 FileData *sfd = work->data;
713 file_data_disconnect_sidecar_file(fd, sfd);
714 file_data_send_notification(sfd, NOTIFY_GROUPING);
716 file_data_check_sidecars((FileData *)sidecar_files->data, FALSE); /* this will group the sidecars back together */
717 filelist_free(sidecar_files);
721 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
726 file_data_increment_version(fd);
727 file_data_check_sidecars(fd, FALSE);
729 file_data_send_notification(fd, NOTIFY_GROUPING);
732 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
739 FileData *fd = work->data;
741 file_data_disable_grouping(fd, disable);
747 /* compare name without extension */
748 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
750 size_t len1 = fd1->extension - fd1->name;
751 size_t len2 = fd2->extension - fd2->name;
753 if (len1 < len2) return -1;
754 if (len1 > len2) return 1;
756 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
759 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
761 if (!fdci && fd) fdci = fd->change;
765 g_free(fdci->source);
770 if (fd) fd->change = NULL;
773 static gboolean file_data_can_write_directly(FileData *fd)
775 return filter_name_is_writable(fd->extension);
778 static gboolean file_data_can_write_sidecar(FileData *fd)
780 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
783 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
785 gchar *sidecar_path = NULL;
788 if (!file_data_can_write_sidecar(fd)) return NULL;
790 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
793 FileData *sfd = work->data;
795 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
797 sidecar_path = g_strdup(sfd->path);
802 if (!existing_only && !sidecar_path)
804 gchar *base = remove_extension_from_path(fd->path);
805 sidecar_path = g_strconcat(base, ".xmp", NULL);
814 *-----------------------------------------------------------------------------
815 * sidecar file info struct
816 *-----------------------------------------------------------------------------
821 static gint sidecar_file_priority(const gchar *path)
823 const gchar *extension = extension_from_path(path);
827 if (extension == NULL)
830 work = sidecar_ext_get_list();
833 gchar *ext = work->data;
836 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
844 *-----------------------------------------------------------------------------
846 *-----------------------------------------------------------------------------
849 static SortType filelist_sort_method = SORT_NONE;
850 static gboolean filelist_sort_ascend = TRUE;
853 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
855 if (!filelist_sort_ascend)
862 switch (filelist_sort_method)
867 if (fa->size < fb->size) return -1;
868 if (fa->size > fb->size) return 1;
869 /* fall back to name */
872 if (fa->date < fb->date) return -1;
873 if (fa->date > fb->date) return 1;
874 /* fall back to name */
876 #ifdef HAVE_STRVERSCMP
878 return strverscmp(fa->name, fb->name);
885 if (options->file_sort.case_sensitive)
886 return strcmp(fa->collate_key_name, fb->collate_key_name);
888 return strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
891 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
893 filelist_sort_method = method;
894 filelist_sort_ascend = ascend;
895 return filelist_sort_compare_filedata(fa, fb);
898 static gint filelist_sort_file_cb(gpointer a, gpointer b)
900 return filelist_sort_compare_filedata(a, b);
903 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
905 filelist_sort_method = method;
906 filelist_sort_ascend = ascend;
907 return g_list_sort(list, cb);
910 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
912 filelist_sort_method = method;
913 filelist_sort_ascend = ascend;
914 return g_list_insert_sorted(list, data, cb);
917 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
919 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
922 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
924 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
928 static GList *filelist_filter_out_sidecars(GList *flist)
931 GList *flist_filtered = NULL;
935 FileData *fd = work->data;
938 if (fd->parent) /* remove fd's that are children */
941 flist_filtered = g_list_prepend(flist_filtered, fd);
945 return flist_filtered;
948 static gboolean is_hidden_file(const gchar *name)
950 if (name[0] != '.') return FALSE;
951 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
955 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
962 gint (*stat_func)(const gchar *path, struct stat *buf);
963 GHashTable *basename_hash = NULL;
965 g_assert(files || dirs);
967 if (files) *files = NULL;
968 if (dirs) *dirs = NULL;
970 pathl = path_from_utf8(dir_path);
971 if (!pathl) return FALSE;
980 if (files) basename_hash = file_data_basename_hash_new();
987 while ((dir = readdir(dp)) != NULL)
989 struct stat ent_sbuf;
990 const gchar *name = dir->d_name;
993 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
996 filepath = g_build_filename(pathl, name, NULL);
997 if (stat_func(filepath, &ent_sbuf) >= 0)
999 if (S_ISDIR(ent_sbuf.st_mode))
1001 /* we ignore the .thumbnails dir for cleanliness */
1003 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1004 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1005 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1006 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1008 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE, NULL));
1013 if (files && filter_name_exists(name))
1015 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, FALSE, basename_hash));
1021 if (errno == EOVERFLOW)
1023 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1032 if (basename_hash) file_data_basename_hash_free(basename_hash);
1034 if (dirs) *dirs = dlist;
1035 if (files) *files = filelist_filter_out_sidecars(flist);
1040 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1042 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1045 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1047 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1050 FileData *file_data_new_simple(const gchar *path_utf8)
1057 if (!stat_utf8(path_utf8, &st))
1063 if (S_ISDIR(st.st_mode))
1064 return file_data_new(path_utf8, &st, TRUE, NULL);
1066 dir = remove_level_from_path(path_utf8);
1068 filelist_read_real(dir, &files, NULL, TRUE);
1070 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1074 filelist_free(files);
1079 FileData *file_data_new_no_grouping(const gchar *path_utf8)
1083 if (!stat_utf8(path_utf8, &st))
1089 return file_data_new(path_utf8, &st, TRUE, NULL);
1092 FileData *file_data_new_dir(const gchar *path_utf8)
1096 if (!stat_utf8(path_utf8, &st))
1102 g_assert(S_ISDIR(st.st_mode));
1103 return file_data_new(path_utf8, &st, TRUE, NULL);
1106 void filelist_free(GList *list)
1113 file_data_unref((FileData *)work->data);
1121 GList *filelist_copy(GList *list)
1123 GList *new_list = NULL;
1134 new_list = g_list_prepend(new_list, file_data_ref(fd));
1137 return g_list_reverse(new_list);
1140 GList *filelist_from_path_list(GList *list)
1142 GList *new_list = NULL;
1153 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1156 return g_list_reverse(new_list);
1159 GList *filelist_to_path_list(GList *list)
1161 GList *new_list = NULL;
1172 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1175 return g_list_reverse(new_list);
1178 GList *filelist_filter(GList *list, gboolean is_dir_list)
1182 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1187 FileData *fd = (FileData *)(work->data);
1188 const gchar *name = fd->name;
1190 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1191 (!is_dir_list && !filter_name_exists(name)) ||
1192 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1193 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1197 list = g_list_remove_link(list, link);
1198 file_data_unref(fd);
1209 *-----------------------------------------------------------------------------
1210 * filelist recursive
1211 *-----------------------------------------------------------------------------
1214 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1216 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1219 GList *filelist_sort_path(GList *list)
1221 return g_list_sort(list, filelist_sort_path_cb);
1224 static void filelist_recursive_append(GList **list, GList *dirs)
1231 FileData *fd = (FileData *)(work->data);
1235 if (filelist_read(fd, &f, &d))
1237 f = filelist_filter(f, FALSE);
1238 f = filelist_sort_path(f);
1239 *list = g_list_concat(*list, f);
1241 d = filelist_filter(d, TRUE);
1242 d = filelist_sort_path(d);
1243 filelist_recursive_append(list, d);
1251 GList *filelist_recursive(FileData *dir_fd)
1256 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1257 list = filelist_filter(list, FALSE);
1258 list = filelist_sort_path(list);
1260 d = filelist_filter(d, TRUE);
1261 d = filelist_sort_path(d);
1262 filelist_recursive_append(&list, d);
1270 * marks and orientation
1273 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1274 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1275 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1276 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1278 gboolean file_data_get_mark(FileData *fd, gint n)
1280 gboolean valid = (fd->valid_marks & (1 << n));
1282 if (file_data_get_mark_func[n] && !valid)
1284 guint old = fd->marks;
1285 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1287 if (!value != !(fd->marks & (1 << n)))
1289 fd->marks = fd->marks ^ (1 << n);
1292 fd->valid_marks |= (1 << n);
1293 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1295 file_data_unref(fd);
1297 else if (!old && fd->marks)
1303 return !!(fd->marks & (1 << n));
1306 guint file_data_get_marks(FileData *fd)
1309 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1313 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1316 if (!value == !file_data_get_mark(fd, n)) return;
1318 if (file_data_set_mark_func[n])
1320 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1325 fd->marks = fd->marks ^ (1 << n);
1327 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1329 file_data_unref(fd);
1331 else if (!old && fd->marks)
1336 file_data_increment_version(fd);
1337 file_data_send_notification(fd, NOTIFY_MARKS);
1340 gboolean file_data_filter_marks(FileData *fd, guint filter)
1343 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1344 return ((fd->marks & filter) == filter);
1347 GList *file_data_filter_marks_list(GList *list, guint filter)
1354 FileData *fd = work->data;
1358 if (!file_data_filter_marks(fd, filter))
1360 list = g_list_remove_link(list, link);
1361 file_data_unref(fd);
1369 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1371 FileData *fd = value;
1372 file_data_increment_version(fd);
1373 file_data_send_notification(fd, NOTIFY_MARKS);
1376 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1378 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1380 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1382 file_data_get_mark_func[n] = get_mark_func;
1383 file_data_set_mark_func[n] = set_mark_func;
1384 file_data_mark_func_data[n] = data;
1385 file_data_destroy_mark_func[n] = notify;
1389 /* this effectively changes all known files */
1390 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1396 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1398 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1399 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1400 if (data) *data = file_data_mark_func_data[n];
1403 gint file_data_get_user_orientation(FileData *fd)
1405 return fd->user_orientation;
1408 void file_data_set_user_orientation(FileData *fd, gint value)
1410 if (fd->user_orientation == value) return;
1412 fd->user_orientation = value;
1413 file_data_increment_version(fd);
1414 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1419 * file_data - operates on the given fd
1420 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1424 /* return list of sidecar file extensions in a string */
1425 gchar *file_data_sc_list_to_string(FileData *fd)
1428 GString *result = g_string_new("");
1430 work = fd->sidecar_files;
1433 FileData *sfd = work->data;
1435 result = g_string_append(result, "+ ");
1436 result = g_string_append(result, sfd->extension);
1438 if (work) result = g_string_append_c(result, ' ');
1441 return g_string_free(result, FALSE);
1447 * add FileDataChangeInfo (see typedefs.h) for the given operation
1448 * uses file_data_add_change_info
1450 * fails if the fd->change already exists - change operations can't run in parallel
1451 * fd->change_info works as a lock
1453 * dest can be NULL - in this case the current name is used for now, it will
1458 FileDataChangeInfo types:
1460 MOVE - path is changed, name may be changed too
1461 RENAME - path remains unchanged, name is changed
1462 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1463 sidecar names are changed too, extensions are not changed
1465 UPDATE - file size, date or grouping has been changed
1468 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1470 FileDataChangeInfo *fdci;
1472 if (fd->change) return FALSE;
1474 fdci = g_new0(FileDataChangeInfo, 1);
1479 fdci->source = g_strdup(src);
1481 fdci->source = g_strdup(fd->path);
1484 fdci->dest = g_strdup(dest);
1491 static void file_data_planned_change_remove(FileData *fd)
1493 if (file_data_planned_change_hash &&
1494 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1496 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1498 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1499 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1500 file_data_unref(fd);
1501 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1503 g_hash_table_destroy(file_data_planned_change_hash);
1504 file_data_planned_change_hash = NULL;
1505 DEBUG_1("planned change: empty");
1512 void file_data_free_ci(FileData *fd)
1514 FileDataChangeInfo *fdci = fd->change;
1518 file_data_planned_change_remove(fd);
1520 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1522 g_free(fdci->source);
1530 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1532 FileDataChangeInfo *fdci = fd->change;
1534 fdci->regroup_when_finished = enable;
1537 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1541 if (fd->parent) fd = fd->parent;
1543 if (fd->change) return FALSE;
1545 work = fd->sidecar_files;
1548 FileData *sfd = work->data;
1550 if (sfd->change) return FALSE;
1554 file_data_add_ci(fd, type, NULL, NULL);
1556 work = fd->sidecar_files;
1559 FileData *sfd = work->data;
1561 file_data_add_ci(sfd, type, NULL, NULL);
1568 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1572 if (fd->parent) fd = fd->parent;
1574 if (!fd->change || fd->change->type != type) return FALSE;
1576 work = fd->sidecar_files;
1579 FileData *sfd = work->data;
1581 if (!sfd->change || sfd->change->type != type) return FALSE;
1589 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1591 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1592 file_data_sc_update_ci_copy(fd, dest_path);
1596 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1598 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1599 file_data_sc_update_ci_move(fd, dest_path);
1603 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1605 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1606 file_data_sc_update_ci_rename(fd, dest_path);
1610 gboolean file_data_sc_add_ci_delete(FileData *fd)
1612 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1615 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1617 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1618 file_data_sc_update_ci_unspecified(fd, dest_path);
1622 gboolean file_data_add_ci_write_metadata(FileData *fd)
1624 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1627 void file_data_sc_free_ci(FileData *fd)
1631 if (fd->parent) fd = fd->parent;
1633 file_data_free_ci(fd);
1635 work = fd->sidecar_files;
1638 FileData *sfd = work->data;
1640 file_data_free_ci(sfd);
1645 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1648 gboolean ret = TRUE;
1653 FileData *fd = work->data;
1655 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1662 static void file_data_sc_revert_ci_list(GList *fd_list)
1669 FileData *fd = work->data;
1671 file_data_sc_free_ci(fd);
1676 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1683 FileData *fd = work->data;
1685 if (!func(fd, dest))
1687 file_data_sc_revert_ci_list(work->prev);
1696 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1698 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1701 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1703 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1706 gboolean file_data_sc_add_ci_rename_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_rename);
1711 gboolean file_data_sc_add_ci_unspecified_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_unspecified);
1716 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1719 gboolean ret = TRUE;
1724 FileData *fd = work->data;
1726 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1733 void file_data_free_ci_list(GList *fd_list)
1740 FileData *fd = work->data;
1742 file_data_free_ci(fd);
1747 void file_data_sc_free_ci_list(GList *fd_list)
1754 FileData *fd = work->data;
1756 file_data_sc_free_ci(fd);
1762 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1763 * fails if fd->change does not exist or the change type does not match
1766 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1768 FileDataChangeType type = fd->change->type;
1770 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1774 if (!file_data_planned_change_hash)
1775 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1777 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1779 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1780 g_hash_table_remove(file_data_planned_change_hash, old_path);
1781 file_data_unref(fd);
1784 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1789 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1790 g_hash_table_remove(file_data_planned_change_hash, new_path);
1791 file_data_unref(ofd);
1794 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1796 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1801 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1803 gchar *old_path = fd->change->dest;
1805 fd->change->dest = g_strdup(dest_path);
1806 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1810 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1812 const gchar *extension = extension_from_path(fd->change->source);
1813 gchar *base = remove_extension_from_path(dest_path);
1814 gchar *old_path = fd->change->dest;
1816 fd->change->dest = g_strconcat(base, extension, NULL);
1817 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1823 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1826 gchar *dest_path_full = NULL;
1828 if (fd->parent) fd = fd->parent;
1832 dest_path = fd->path;
1834 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1836 gchar *dir = remove_level_from_path(fd->path);
1838 dest_path_full = g_build_filename(dir, dest_path, NULL);
1840 dest_path = dest_path_full;
1842 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1844 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1845 dest_path = dest_path_full;
1848 file_data_update_ci_dest(fd, dest_path);
1850 work = fd->sidecar_files;
1853 FileData *sfd = work->data;
1855 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1859 g_free(dest_path_full);
1862 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1864 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1865 file_data_sc_update_ci(fd, dest_path);
1869 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1871 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1874 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1876 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1879 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1881 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1884 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1886 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1889 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1891 gboolean (*func)(FileData *, const gchar *))
1894 gboolean ret = TRUE;
1899 FileData *fd = work->data;
1901 if (!func(fd, dest)) ret = FALSE;
1908 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1910 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1913 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1915 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1918 gboolean file_data_sc_update_ci_unspecified_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_unspecified);
1925 * verify source and dest paths - dest image exists, etc.
1926 * it should detect all possible problems with the planned operation
1929 gint file_data_verify_ci(FileData *fd)
1931 gint ret = CHANGE_OK;
1936 DEBUG_1("Change checked: no change info: %s", fd->path);
1940 if (!isname(fd->path))
1942 /* this probably should not happen */
1943 ret |= CHANGE_NO_SRC;
1944 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1948 dir = remove_level_from_path(fd->path);
1950 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1951 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1952 fd->change->type != FILEDATA_CHANGE_RENAME &&
1953 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1956 ret |= CHANGE_WARN_UNSAVED_META;
1957 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1960 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1961 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1962 !access_file(fd->path, R_OK))
1964 ret |= CHANGE_NO_READ_PERM;
1965 DEBUG_1("Change checked: no read permission: %s", fd->path);
1967 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1968 !access_file(dir, W_OK))
1970 ret |= CHANGE_NO_WRITE_PERM_DIR;
1971 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1973 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1974 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1975 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1976 !access_file(fd->path, W_OK))
1978 ret |= CHANGE_WARN_NO_WRITE_PERM;
1979 DEBUG_1("Change checked: no write permission: %s", fd->path);
1981 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1982 - that means that there are no hard errors and warnings can be disabled
1983 - the destination is determined during the check
1985 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1987 /* determine destination file */
1988 gboolean have_dest = FALSE;
1989 gchar *dest_dir = NULL;
1991 if (options->metadata.save_in_image_file)
1993 if (file_data_can_write_directly(fd))
1995 /* we can write the file directly */
1996 if (access_file(fd->path, W_OK))
2002 if (options->metadata.warn_on_write_problems)
2004 ret |= CHANGE_WARN_NO_WRITE_PERM;
2005 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2009 else if (file_data_can_write_sidecar(fd))
2011 /* we can write sidecar */
2012 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2013 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2015 file_data_update_ci_dest(fd, sidecar);
2020 if (options->metadata.warn_on_write_problems)
2022 ret |= CHANGE_WARN_NO_WRITE_PERM;
2023 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2032 /* write private metadata file under ~/.geeqie */
2034 /* If an existing metadata file exists, we will try writing to
2035 * it's location regardless of the user's preference.
2037 gchar *metadata_path = NULL;
2039 /* but ignore XMP if we are not able to write it */
2040 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2042 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2044 if (metadata_path && !access_file(metadata_path, W_OK))
2046 g_free(metadata_path);
2047 metadata_path = NULL;
2054 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2055 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2057 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2059 metadata_path = g_build_filename(dest_dir, filename, NULL);
2063 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2065 file_data_update_ci_dest(fd, metadata_path);
2070 ret |= CHANGE_NO_WRITE_PERM_DEST;
2071 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2073 g_free(metadata_path);
2078 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2083 same = (strcmp(fd->path, fd->change->dest) == 0);
2087 const gchar *dest_ext = extension_from_path(fd->change->dest);
2088 if (!dest_ext) dest_ext = "";
2090 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2092 ret |= CHANGE_WARN_CHANGED_EXT;
2093 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2098 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2100 ret |= CHANGE_WARN_SAME;
2101 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2105 dest_dir = remove_level_from_path(fd->change->dest);
2107 if (!isdir(dest_dir))
2109 ret |= CHANGE_NO_DEST_DIR;
2110 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2112 else if (!access_file(dest_dir, W_OK))
2114 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2115 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2119 if (isfile(fd->change->dest))
2121 if (!access_file(fd->change->dest, W_OK))
2123 ret |= CHANGE_NO_WRITE_PERM_DEST;
2124 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2128 ret |= CHANGE_WARN_DEST_EXISTS;
2129 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2132 else if (isdir(fd->change->dest))
2134 ret |= CHANGE_DEST_EXISTS;
2135 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2142 fd->change->error = ret;
2143 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2150 gint file_data_sc_verify_ci(FileData *fd)
2155 ret = file_data_verify_ci(fd);
2157 work = fd->sidecar_files;
2160 FileData *sfd = work->data;
2162 ret |= file_data_verify_ci(sfd);
2169 gchar *file_data_get_error_string(gint error)
2171 GString *result = g_string_new("");
2173 if (error & CHANGE_NO_SRC)
2175 if (result->len > 0) g_string_append(result, ", ");
2176 g_string_append(result, _("file or directory does not exist"));
2179 if (error & CHANGE_DEST_EXISTS)
2181 if (result->len > 0) g_string_append(result, ", ");
2182 g_string_append(result, _("destination already exists"));
2185 if (error & CHANGE_NO_WRITE_PERM_DEST)
2187 if (result->len > 0) g_string_append(result, ", ");
2188 g_string_append(result, _("destination can't be overwritten"));
2191 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2193 if (result->len > 0) g_string_append(result, ", ");
2194 g_string_append(result, _("destination directory is not writable"));
2197 if (error & CHANGE_NO_DEST_DIR)
2199 if (result->len > 0) g_string_append(result, ", ");
2200 g_string_append(result, _("destination directory does not exist"));
2203 if (error & CHANGE_NO_WRITE_PERM_DIR)
2205 if (result->len > 0) g_string_append(result, ", ");
2206 g_string_append(result, _("source directory is not writable"));
2209 if (error & CHANGE_NO_READ_PERM)
2211 if (result->len > 0) g_string_append(result, ", ");
2212 g_string_append(result, _("no read permission"));
2215 if (error & CHANGE_WARN_NO_WRITE_PERM)
2217 if (result->len > 0) g_string_append(result, ", ");
2218 g_string_append(result, _("file is readonly"));
2221 if (error & CHANGE_WARN_DEST_EXISTS)
2223 if (result->len > 0) g_string_append(result, ", ");
2224 g_string_append(result, _("destination already exists and will be overwritten"));
2227 if (error & CHANGE_WARN_SAME)
2229 if (result->len > 0) g_string_append(result, ", ");
2230 g_string_append(result, _("source and destination are the same"));
2233 if (error & CHANGE_WARN_CHANGED_EXT)
2235 if (result->len > 0) g_string_append(result, ", ");
2236 g_string_append(result, _("source and destination have different extension"));
2239 if (error & CHANGE_WARN_UNSAVED_META)
2241 if (result->len > 0) g_string_append(result, ", ");
2242 g_string_append(result, _("there are unsaved metadata changes for the file"));
2245 return g_string_free(result, FALSE);
2248 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2251 gint all_errors = 0;
2252 gint common_errors = ~0;
2257 if (!list) return 0;
2259 num = g_list_length(list);
2260 errors = g_new(int, num);
2271 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2272 all_errors |= error;
2273 common_errors &= error;
2280 if (desc && all_errors)
2283 GString *result = g_string_new("");
2287 gchar *str = file_data_get_error_string(common_errors);
2288 g_string_append(result, str);
2289 g_string_append(result, "\n");
2303 error = errors[i] & ~common_errors;
2307 gchar *str = file_data_get_error_string(error);
2308 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2313 *desc = g_string_free(result, FALSE);
2322 * perform the change described by FileFataChangeInfo
2323 * it is used for internal operations,
2324 * this function actually operates with files on the filesystem
2325 * it should implement safe delete
2328 static gboolean file_data_perform_move(FileData *fd)
2330 g_assert(!strcmp(fd->change->source, fd->path));
2331 return move_file(fd->change->source, fd->change->dest);
2334 static gboolean file_data_perform_copy(FileData *fd)
2336 g_assert(!strcmp(fd->change->source, fd->path));
2337 return copy_file(fd->change->source, fd->change->dest);
2340 static gboolean file_data_perform_delete(FileData *fd)
2342 if (isdir(fd->path) && !islink(fd->path))
2343 return rmdir_utf8(fd->path);
2345 if (options->file_ops.safe_delete_enable)
2346 return file_util_safe_unlink(fd->path);
2348 return unlink_file(fd->path);
2351 gboolean file_data_perform_ci(FileData *fd)
2353 FileDataChangeType type = fd->change->type;
2357 case FILEDATA_CHANGE_MOVE:
2358 return file_data_perform_move(fd);
2359 case FILEDATA_CHANGE_COPY:
2360 return file_data_perform_copy(fd);
2361 case FILEDATA_CHANGE_RENAME:
2362 return file_data_perform_move(fd); /* the same as move */
2363 case FILEDATA_CHANGE_DELETE:
2364 return file_data_perform_delete(fd);
2365 case FILEDATA_CHANGE_WRITE_METADATA:
2366 return metadata_write_perform(fd);
2367 case FILEDATA_CHANGE_UNSPECIFIED:
2368 /* nothing to do here */
2376 gboolean file_data_sc_perform_ci(FileData *fd)
2379 gboolean ret = TRUE;
2380 FileDataChangeType type = fd->change->type;
2382 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2384 work = fd->sidecar_files;
2387 FileData *sfd = work->data;
2389 if (!file_data_perform_ci(sfd)) ret = FALSE;
2393 if (!file_data_perform_ci(fd)) ret = FALSE;
2399 * updates FileData structure according to FileDataChangeInfo
2402 gboolean file_data_apply_ci(FileData *fd)
2404 FileDataChangeType type = fd->change->type;
2407 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2409 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2410 file_data_planned_change_remove(fd);
2412 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2414 /* this change overwrites another file which is already known to other modules
2415 renaming fd would create duplicate FileData structure
2416 the best thing we can do is nothing
2417 FIXME: maybe we could copy stuff like marks
2419 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2423 file_data_set_path(fd, fd->change->dest);
2426 file_data_increment_version(fd);
2427 file_data_send_notification(fd, NOTIFY_CHANGE);
2432 gboolean file_data_sc_apply_ci(FileData *fd)
2435 FileDataChangeType type = fd->change->type;
2437 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2439 work = fd->sidecar_files;
2442 FileData *sfd = work->data;
2444 file_data_apply_ci(sfd);
2448 file_data_apply_ci(fd);
2453 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2456 if (fd->parent) fd = fd->parent;
2457 if (!g_list_find(list, fd)) return FALSE;
2459 work = fd->sidecar_files;
2462 if (!g_list_find(list, work->data)) return FALSE;
2469 static gboolean file_data_list_dump(GList *list)
2471 GList *work, *work2;
2476 FileData *fd = work->data;
2477 printf("%s\n", fd->name);
2478 work2 = fd->sidecar_files;
2481 FileData *fd = work2->data;
2482 printf(" %s\n", fd->name);
2483 work2 = work2->next;
2491 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2496 /* change partial groups to independent files */
2501 FileData *fd = work->data;
2504 if (!file_data_list_contains_whole_group(list, fd))
2506 file_data_disable_grouping(fd, TRUE);
2509 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2515 /* remove sidecars from the list,
2516 they can be still acessed via main_fd->sidecar_files */
2520 FileData *fd = work->data;
2524 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2526 out = g_list_prepend(out, file_data_ref(fd));
2530 filelist_free(list);
2531 out = g_list_reverse(out);
2541 * notify other modules about the change described by FileDataChangeInfo
2544 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2545 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2546 implementation in view_file_list.c */
2551 typedef struct _NotifyData NotifyData;
2553 struct _NotifyData {
2554 FileDataNotifyFunc func;
2556 NotifyPriority priority;
2559 static GList *notify_func_list = NULL;
2561 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2563 NotifyData *nda = (NotifyData *)a;
2564 NotifyData *ndb = (NotifyData *)b;
2566 if (nda->priority < ndb->priority) return -1;
2567 if (nda->priority > ndb->priority) return 1;
2571 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2574 GList *work = notify_func_list;
2578 NotifyData *nd = (NotifyData *)work->data;
2580 if (nd->func == func && nd->data == data)
2582 g_warning("Notify func already registered");
2588 nd = g_new(NotifyData, 1);
2591 nd->priority = priority;
2593 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2594 DEBUG_2("Notify func registered: %p", nd);
2599 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2601 GList *work = notify_func_list;
2605 NotifyData *nd = (NotifyData *)work->data;
2607 if (nd->func == func && nd->data == data)
2609 notify_func_list = g_list_delete_link(notify_func_list, work);
2611 DEBUG_2("Notify func unregistered: %p", nd);
2617 g_warning("Notify func not found");
2622 void file_data_send_notification(FileData *fd, NotifyType type)
2624 GList *work = notify_func_list;
2628 NotifyData *nd = (NotifyData *)work->data;
2630 nd->func(fd, type, nd->data);
2635 static GHashTable *file_data_monitor_pool = NULL;
2636 static guint realtime_monitor_id = 0; /* event source id */
2638 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2642 file_data_check_changed_files(fd);
2644 DEBUG_1("monitor %s", fd->path);
2647 static gboolean realtime_monitor_cb(gpointer data)
2649 if (!options->update_on_time_change) return TRUE;
2650 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2654 gboolean file_data_register_real_time_monitor(FileData *fd)
2660 if (!file_data_monitor_pool)
2661 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2663 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2665 DEBUG_1("Register realtime %d %s", count, fd->path);
2668 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2670 if (!realtime_monitor_id)
2672 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2678 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2682 g_assert(file_data_monitor_pool);
2684 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2686 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2688 g_assert(count > 0);
2693 g_hash_table_remove(file_data_monitor_pool, fd);
2695 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2697 file_data_unref(fd);
2699 if (g_hash_table_size(file_data_monitor_pool) == 0)
2701 g_source_remove(realtime_monitor_id);
2702 realtime_monitor_id = 0;
2708 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */