4 * Copyright (C) 2008 - 2010 The Geeqie Team
8 * This software is released under the GNU General Public License (GNU GPL).
9 * Please read the included file COPYING for more information.
10 * This software comes with no warranty of any kind, use at your own risk!
17 #include "filefilter.h"
19 #include "thumb_standard.h"
20 #include "ui_fileops.h"
23 #include "histogram.h"
27 static GHashTable *file_data_pool = NULL;
28 static GHashTable *file_data_planned_change_hash = NULL;
30 static gint sidecar_file_priority(const gchar *path);
31 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean check_sidecars, GHashTable *basename_hash);
35 *-----------------------------------------------------------------------------
36 * text conversion utils
37 *-----------------------------------------------------------------------------
40 gchar *text_from_size(gint64 size)
46 /* what I would like to use is printf("%'d", size)
47 * BUT: not supported on every libc :(
51 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
52 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
56 a = g_strdup_printf("%d", (guint)size);
62 b = g_new(gchar, l + n + 1);
87 gchar *text_from_size_abrev(gint64 size)
89 if (size < (gint64)1024)
91 return g_strdup_printf(_("%d bytes"), (gint)size);
93 if (size < (gint64)1048576)
95 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
97 if (size < (gint64)1073741824)
99 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
102 /* to avoid overflowing the gdouble, do division in two steps */
104 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
107 /* note: returned string is valid until next call to text_from_time() */
108 const gchar *text_from_time(time_t t)
110 static gchar *ret = NULL;
114 GError *error = NULL;
116 btime = localtime(&t);
118 /* the %x warning about 2 digit years is not an error */
119 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
120 if (buflen < 1) return "";
123 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
126 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
135 *-----------------------------------------------------------------------------
137 *-----------------------------------------------------------------------------
140 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source);
141 static void file_data_check_sidecars(const GList *basename_list);
142 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
145 void file_data_increment_version(FileData *fd)
151 fd->parent->version++;
152 fd->parent->valid_marks = 0;
156 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
158 const FileData *fda = a;
159 const FileData *fdb = b;
161 return strcmp(fdb->extension, fda->extension);
164 static GHashTable *file_data_basename_hash_new(void)
166 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
169 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
172 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
174 list = g_hash_table_lookup(basename_hash, basename);
176 if (!g_list_find(list, fd))
178 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
179 g_hash_table_insert(basename_hash, basename, list);
189 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
192 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
194 list = g_hash_table_lookup(basename_hash, basename);
196 if (!g_list_find(list, fd)) return;
198 list = g_list_remove(list, fd);
203 g_hash_table_insert(basename_hash, basename, list);
207 g_hash_table_remove(basename_hash, basename);
213 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
215 filelist_free((GList *)value);
218 static void file_data_basename_hash_free(GHashTable *basename_hash)
220 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
221 g_hash_table_destroy(basename_hash);
224 static void file_data_set_collate_keys(FileData *fd)
226 gchar *caseless_name;
228 caseless_name = g_utf8_casefold(fd->name, -1);
230 g_free(fd->collate_key_name);
231 g_free(fd->collate_key_name_nocase);
233 #if 0 && GLIB_CHECK_VERSION(2, 8, 0)
234 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
235 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
237 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
238 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
240 g_free(caseless_name);
243 static void file_data_set_path(FileData *fd, const gchar *path)
245 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
246 g_assert(file_data_pool);
250 if (fd->original_path)
252 g_hash_table_remove(file_data_pool, fd->original_path);
253 g_free(fd->original_path);
256 g_assert(!g_hash_table_lookup(file_data_pool, path));
258 fd->original_path = g_strdup(path);
259 g_hash_table_insert(file_data_pool, fd->original_path, fd);
261 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
263 fd->path = g_strdup(path);
265 fd->extension = fd->name + 1;
266 file_data_set_collate_keys(fd);
270 fd->path = g_strdup(path);
271 fd->name = filename_from_path(fd->path);
273 if (strcmp(fd->name, "..") == 0)
275 gchar *dir = remove_level_from_path(path);
277 fd->path = remove_level_from_path(dir);
280 fd->extension = fd->name + 2;
281 file_data_set_collate_keys(fd);
284 else if (strcmp(fd->name, ".") == 0)
287 fd->path = remove_level_from_path(path);
289 fd->extension = fd->name + 1;
290 file_data_set_collate_keys(fd);
294 fd->extension = extension_from_path(fd->path);
295 if (fd->extension == NULL)
297 fd->extension = fd->name + strlen(fd->name);
300 file_data_set_collate_keys(fd);
303 static gboolean file_data_check_changed(FileData *fd, struct stat *st)
307 if (fd->size != st->st_size ||
308 fd->date != st->st_mtime)
310 fd->size = st->st_size;
311 fd->date = st->st_mtime;
312 fd->mode = st->st_mode;
313 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
314 fd->thumb_pixbuf = NULL;
315 file_data_increment_version(fd);
316 file_data_send_notification(fd, NOTIFY_REREAD);
322 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
324 gboolean ret = FALSE;
327 ret = file_data_check_changed(fd, st);
329 work = fd->sidecar_files;
332 FileData *sfd = work->data;
336 if (!stat_utf8(sfd->path, &st))
340 file_data_disconnect_sidecar_file(fd, sfd);
345 ret |= file_data_check_changed_files_recursive(sfd, &st);
351 gboolean file_data_check_changed_files(FileData *fd)
353 gboolean ret = FALSE;
356 if (fd->parent) fd = fd->parent;
358 if (!stat_utf8(fd->path, &st))
362 FileData *sfd = NULL;
364 /* parent is missing, we have to rebuild whole group */
369 /* file_data_disconnect_sidecar_file might delete the file,
370 we have to keep the reference to prevent this */
371 sidecars = filelist_copy(fd->sidecar_files);
378 file_data_disconnect_sidecar_file(fd, sfd);
380 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
381 /* now we can release the sidecars */
382 filelist_free(sidecars);
383 file_data_send_notification(fd, NOTIFY_REREAD);
387 ret |= file_data_check_changed_files_recursive(fd, &st);
393 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars, GHashTable *basename_hash)
397 DEBUG_2("file_data_new: '%s' %d %d", path_utf8, disable_sidecars, !!basename_hash);
399 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
402 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
404 fd = g_hash_table_lookup(file_data_pool, path_utf8);
410 if (!fd && file_data_planned_change_hash)
412 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
415 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
417 file_data_apply_ci(fd);
425 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
429 changed = file_data_check_changed_files(fd);
431 changed = file_data_check_changed_files_recursive(fd, st);
435 GList *list = file_data_basename_hash_insert(basename_hash, fd);
436 if (!disable_sidecars)
437 file_data_check_sidecars(list);
439 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
444 fd = g_new0(FileData, 1);
446 fd->size = st->st_size;
447 fd->date = st->st_mtime;
448 fd->mode = st->st_mode;
450 fd->magick = 0x12345678;
452 if (disable_sidecars) fd->disable_grouping = TRUE;
454 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
456 if (!disable_sidecars && !fd->disable_grouping && sidecar_file_priority(fd->extension))
458 GList *list = file_data_basename_hash_insert(basename_hash, fd);
459 file_data_check_sidecars(list);
466 static void file_data_check_sidecars(const GList *basename_list)
468 FileData *parent_fd = NULL;
470 GList *group_list = NULL;
472 /* check for possible sidecar files;
473 the sidecar files created here are referenced only via fd->sidecar_files or fd->parent,
474 they have fd->ref set to 0 and file_data unref must chack and free them all together
475 (using fd->ref would cause loops and leaks)
478 /* find all possible sidecar files and order them according to sidecar_ext_get_list,
479 for case-only differences put lowercase first,
480 put the result to group_list
482 work = sidecar_ext_get_list();
485 gchar *ext = work->data;
488 const GList *work2 = basename_list;
492 FileData *sfd = work2->data;
494 if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
496 group_list = g_list_append(group_list, file_data_ref(sfd));
502 /* process the group list - the first one is the parent file, others are sidecars */
506 FileData *new_fd = work->data;
509 if (new_fd->disable_grouping)
511 file_data_unref(new_fd);
515 new_fd->ref--; /* do not use ref here */
518 parent_fd = new_fd; /* parent is the one with the highest prio, found first */
520 file_data_merge_sidecar_files(parent_fd, new_fd);
522 g_list_free(group_list);
526 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars, GHashTable *basename_hash)
528 gchar *path_utf8 = path_to_utf8(path);
529 FileData *ret = file_data_new(path_utf8, st, disable_sidecars, basename_hash);
535 FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
537 sfd->parent = target;
538 if (!g_list_find(target->sidecar_files, sfd))
539 target->sidecar_files = g_list_prepend(target->sidecar_files, sfd);
540 file_data_increment_version(sfd); /* increments both sfd and target */
545 FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
549 file_data_add_sidecar_file(target, source);
551 work = source->sidecar_files;
554 FileData *sfd = work->data;
555 file_data_add_sidecar_file(target, sfd);
559 g_list_free(source->sidecar_files);
560 source->sidecar_files = NULL;
562 target->sidecar_files = filelist_sort(target->sidecar_files, SORT_NAME, TRUE);
567 #ifdef DEBUG_FILEDATA
568 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
570 FileData *file_data_ref(FileData *fd)
573 if (fd == NULL) return NULL;
574 #ifdef DEBUG_FILEDATA
575 if (fd->magick != 0x12345678)
576 DEBUG_0("fd magick mismatch at %s:%d", file, line);
578 g_assert(fd->magick == 0x12345678);
581 #ifdef DEBUG_FILEDATA
582 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
584 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
589 static void file_data_free(FileData *fd)
591 g_assert(fd->magick == 0x12345678);
592 g_assert(fd->ref == 0);
594 metadata_cache_free(fd);
595 g_hash_table_remove(file_data_pool, fd->original_path);
598 g_free(fd->original_path);
599 g_free(fd->collate_key_name);
600 g_free(fd->collate_key_name_nocase);
601 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
602 histmap_free(fd->histmap);
604 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
606 file_data_change_info_free(NULL, fd);
610 #ifdef DEBUG_FILEDATA
611 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
613 void file_data_unref(FileData *fd)
616 if (fd == NULL) return;
617 #ifdef DEBUG_FILEDATA
618 if (fd->magick != 0x12345678)
619 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
621 g_assert(fd->magick == 0x12345678);
624 #ifdef DEBUG_FILEDATA
625 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
627 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
632 FileData *parent = fd->parent ? fd->parent : fd;
634 if (parent->ref > 0) return;
636 work = parent->sidecar_files;
639 FileData *sfd = work->data;
640 if (sfd->ref > 0) return;
644 /* none of parent/children is referenced, we can free everything */
646 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
648 work = parent->sidecar_files;
651 FileData *sfd = work->data;
656 g_list_free(parent->sidecar_files);
657 parent->sidecar_files = NULL;
659 file_data_free(parent);
663 FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
665 sfd->parent = target;
666 g_assert(g_list_find(target->sidecar_files, sfd));
668 file_data_increment_version(sfd); /* increments both sfd and target */
670 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
682 /* disables / enables grouping for particular file, sends UPDATE notification */
683 void file_data_disable_grouping(FileData *fd, gboolean disable)
685 if (!fd->disable_grouping == !disable) return;
687 fd->disable_grouping = !!disable;
693 FileData *parent = file_data_ref(fd->parent);
694 file_data_disconnect_sidecar_file(parent, fd);
695 file_data_send_notification(parent, NOTIFY_GROUPING);
696 file_data_unref(parent);
698 else if (fd->sidecar_files)
700 GList *sidecar_files = filelist_copy(fd->sidecar_files);
701 GList *work = sidecar_files;
704 FileData *sfd = work->data;
706 file_data_disconnect_sidecar_file(fd, sfd);
707 file_data_send_notification(sfd, NOTIFY_GROUPING);
709 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
710 filelist_free(sidecar_files);
714 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
719 file_data_increment_version(fd);
720 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
722 file_data_send_notification(fd, NOTIFY_GROUPING);
725 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
732 FileData *fd = work->data;
734 file_data_disable_grouping(fd, disable);
740 /* compare name without extension */
741 gint file_data_compare_name_without_ext(FileData *fd1, FileData *fd2)
743 size_t len1 = fd1->extension - fd1->name;
744 size_t len2 = fd2->extension - fd2->name;
746 if (len1 < len2) return -1;
747 if (len1 > len2) return 1;
749 return strncmp(fd1->name, fd2->name, len1); /* FIXME: utf8 */
752 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
754 if (!fdci && fd) fdci = fd->change;
758 g_free(fdci->source);
763 if (fd) fd->change = NULL;
766 static gboolean file_data_can_write_directly(FileData *fd)
768 return filter_name_is_writable(fd->extension);
771 static gboolean file_data_can_write_sidecar(FileData *fd)
773 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
776 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
778 gchar *sidecar_path = NULL;
781 if (!file_data_can_write_sidecar(fd)) return NULL;
783 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
786 FileData *sfd = work->data;
788 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
790 sidecar_path = g_strdup(sfd->path);
795 if (!existing_only && !sidecar_path)
797 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
798 sidecar_path = g_strconcat(base, ".xmp", NULL);
807 *-----------------------------------------------------------------------------
808 * sidecar file info struct
809 *-----------------------------------------------------------------------------
814 static gint sidecar_file_priority(const gchar *path)
816 const gchar *extension = extension_from_path(path);
820 if (extension == NULL)
823 work = sidecar_ext_get_list();
826 gchar *ext = work->data;
829 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
837 *-----------------------------------------------------------------------------
839 *-----------------------------------------------------------------------------
842 static SortType filelist_sort_method = SORT_NONE;
843 static gboolean filelist_sort_ascend = TRUE;
846 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
849 if (!filelist_sort_ascend)
856 switch (filelist_sort_method)
861 if (fa->size < fb->size) return -1;
862 if (fa->size > fb->size) return 1;
863 /* fall back to name */
866 if (fa->date < fb->date) return -1;
867 if (fa->date > fb->date) return 1;
868 /* fall back to name */
870 #ifdef HAVE_STRVERSCMP
872 ret = strverscmp(fa->name, fb->name);
873 if (ret != 0) return ret;
880 if (options->file_sort.case_sensitive)
881 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
883 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
885 if (ret != 0) return ret;
887 /* do not return 0 unless the files are really the same
888 file_data_pool ensures that original_path is unique
890 return strcmp(fa->original_path, fb->original_path);
893 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
895 filelist_sort_method = method;
896 filelist_sort_ascend = ascend;
897 return filelist_sort_compare_filedata(fa, fb);
900 static gint filelist_sort_file_cb(gpointer a, gpointer b)
902 return filelist_sort_compare_filedata(a, b);
905 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
907 filelist_sort_method = method;
908 filelist_sort_ascend = ascend;
909 return g_list_sort(list, cb);
912 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
914 filelist_sort_method = method;
915 filelist_sort_ascend = ascend;
916 return g_list_insert_sorted(list, data, cb);
919 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
921 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
924 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
926 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
930 static GList *filelist_filter_out_sidecars(GList *flist)
933 GList *flist_filtered = NULL;
937 FileData *fd = work->data;
940 if (fd->parent) /* remove fd's that are children */
943 flist_filtered = g_list_prepend(flist_filtered, fd);
947 return flist_filtered;
950 static gboolean is_hidden_file(const gchar *name)
952 if (name[0] != '.') return FALSE;
953 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
957 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
964 gint (*stat_func)(const gchar *path, struct stat *buf);
965 GHashTable *basename_hash = NULL;
967 g_assert(files || dirs);
969 if (files) *files = NULL;
970 if (dirs) *dirs = NULL;
972 pathl = path_from_utf8(dir_path);
973 if (!pathl) return FALSE;
982 if (files) basename_hash = file_data_basename_hash_new();
989 while ((dir = readdir(dp)) != NULL)
991 struct stat ent_sbuf;
992 const gchar *name = dir->d_name;
995 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
998 filepath = g_build_filename(pathl, name, NULL);
999 if (stat_func(filepath, &ent_sbuf) >= 0)
1001 if (S_ISDIR(ent_sbuf.st_mode))
1003 /* we ignore the .thumbnails dir for cleanliness */
1005 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
1006 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
1007 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
1008 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
1010 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE, NULL));
1015 if (files && filter_name_exists(name))
1017 flist = g_list_prepend(flist, file_data_new_local(filepath, &ent_sbuf, FALSE, basename_hash));
1023 if (errno == EOVERFLOW)
1025 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1034 if (basename_hash) file_data_basename_hash_free(basename_hash);
1036 if (dirs) *dirs = dlist;
1037 if (files) *files = filelist_filter_out_sidecars(flist);
1042 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1044 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1047 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1049 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1052 FileData *file_data_new_simple(const gchar *path_utf8)
1059 if (!stat_utf8(path_utf8, &st))
1065 if (S_ISDIR(st.st_mode))
1066 return file_data_new(path_utf8, &st, TRUE, NULL);
1068 dir = remove_level_from_path(path_utf8);
1070 filelist_read_real(dir, &files, NULL, TRUE);
1072 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1076 filelist_free(files);
1081 FileData *file_data_new_no_grouping(const gchar *path_utf8)
1085 if (!stat_utf8(path_utf8, &st))
1091 return file_data_new(path_utf8, &st, TRUE, NULL);
1094 FileData *file_data_new_dir(const gchar *path_utf8)
1098 if (!stat_utf8(path_utf8, &st))
1104 g_assert(S_ISDIR(st.st_mode));
1105 return file_data_new(path_utf8, &st, TRUE, NULL);
1108 void filelist_free(GList *list)
1115 file_data_unref((FileData *)work->data);
1123 GList *filelist_copy(GList *list)
1125 GList *new_list = NULL;
1136 new_list = g_list_prepend(new_list, file_data_ref(fd));
1139 return g_list_reverse(new_list);
1142 GList *filelist_from_path_list(GList *list)
1144 GList *new_list = NULL;
1155 new_list = g_list_prepend(new_list, file_data_new_simple(path));
1158 return g_list_reverse(new_list);
1161 GList *filelist_to_path_list(GList *list)
1163 GList *new_list = NULL;
1174 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1177 return g_list_reverse(new_list);
1180 GList *filelist_filter(GList *list, gboolean is_dir_list)
1184 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1189 FileData *fd = (FileData *)(work->data);
1190 const gchar *name = fd->name;
1192 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1193 (!is_dir_list && !filter_name_exists(name)) ||
1194 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1195 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1199 list = g_list_remove_link(list, link);
1200 file_data_unref(fd);
1211 *-----------------------------------------------------------------------------
1212 * filelist recursive
1213 *-----------------------------------------------------------------------------
1216 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1218 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1221 GList *filelist_sort_path(GList *list)
1223 return g_list_sort(list, filelist_sort_path_cb);
1226 static void filelist_recursive_append(GList **list, GList *dirs)
1233 FileData *fd = (FileData *)(work->data);
1237 if (filelist_read(fd, &f, &d))
1239 f = filelist_filter(f, FALSE);
1240 f = filelist_sort_path(f);
1241 *list = g_list_concat(*list, f);
1243 d = filelist_filter(d, TRUE);
1244 d = filelist_sort_path(d);
1245 filelist_recursive_append(list, d);
1253 GList *filelist_recursive(FileData *dir_fd)
1258 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1259 list = filelist_filter(list, FALSE);
1260 list = filelist_sort_path(list);
1262 d = filelist_filter(d, TRUE);
1263 d = filelist_sort_path(d);
1264 filelist_recursive_append(&list, d);
1272 * marks and orientation
1275 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1276 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1277 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1278 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1280 gboolean file_data_get_mark(FileData *fd, gint n)
1282 gboolean valid = (fd->valid_marks & (1 << n));
1284 if (file_data_get_mark_func[n] && !valid)
1286 guint old = fd->marks;
1287 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1289 if (!value != !(fd->marks & (1 << n)))
1291 fd->marks = fd->marks ^ (1 << n);
1294 fd->valid_marks |= (1 << n);
1295 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1297 file_data_unref(fd);
1299 else if (!old && fd->marks)
1305 return !!(fd->marks & (1 << n));
1308 guint file_data_get_marks(FileData *fd)
1311 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1315 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1318 if (!value == !file_data_get_mark(fd, n)) return;
1320 if (file_data_set_mark_func[n])
1322 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1327 fd->marks = fd->marks ^ (1 << n);
1329 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1331 file_data_unref(fd);
1333 else if (!old && fd->marks)
1338 file_data_increment_version(fd);
1339 file_data_send_notification(fd, NOTIFY_MARKS);
1342 gboolean file_data_filter_marks(FileData *fd, guint filter)
1345 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1346 return ((fd->marks & filter) == filter);
1349 GList *file_data_filter_marks_list(GList *list, guint filter)
1356 FileData *fd = work->data;
1360 if (!file_data_filter_marks(fd, filter))
1362 list = g_list_remove_link(list, link);
1363 file_data_unref(fd);
1371 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1373 FileData *fd = value;
1374 file_data_increment_version(fd);
1375 file_data_send_notification(fd, NOTIFY_MARKS);
1378 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1380 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1382 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1384 file_data_get_mark_func[n] = get_mark_func;
1385 file_data_set_mark_func[n] = set_mark_func;
1386 file_data_mark_func_data[n] = data;
1387 file_data_destroy_mark_func[n] = notify;
1391 /* this effectively changes all known files */
1392 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1398 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1400 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1401 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1402 if (data) *data = file_data_mark_func_data[n];
1405 gint file_data_get_user_orientation(FileData *fd)
1407 return fd->user_orientation;
1410 void file_data_set_user_orientation(FileData *fd, gint value)
1412 if (fd->user_orientation == value) return;
1414 fd->user_orientation = value;
1415 file_data_increment_version(fd);
1416 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1421 * file_data - operates on the given fd
1422 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1426 /* return list of sidecar file extensions in a string */
1427 gchar *file_data_sc_list_to_string(FileData *fd)
1430 GString *result = g_string_new("");
1432 work = fd->sidecar_files;
1435 FileData *sfd = work->data;
1437 result = g_string_append(result, "+ ");
1438 result = g_string_append(result, sfd->extension);
1440 if (work) result = g_string_append_c(result, ' ');
1443 return g_string_free(result, FALSE);
1449 * add FileDataChangeInfo (see typedefs.h) for the given operation
1450 * uses file_data_add_change_info
1452 * fails if the fd->change already exists - change operations can't run in parallel
1453 * fd->change_info works as a lock
1455 * dest can be NULL - in this case the current name is used for now, it will
1460 FileDataChangeInfo types:
1462 MOVE - path is changed, name may be changed too
1463 RENAME - path remains unchanged, name is changed
1464 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1465 sidecar names are changed too, extensions are not changed
1467 UPDATE - file size, date or grouping has been changed
1470 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1472 FileDataChangeInfo *fdci;
1474 if (fd->change) return FALSE;
1476 fdci = g_new0(FileDataChangeInfo, 1);
1481 fdci->source = g_strdup(src);
1483 fdci->source = g_strdup(fd->path);
1486 fdci->dest = g_strdup(dest);
1493 static void file_data_planned_change_remove(FileData *fd)
1495 if (file_data_planned_change_hash &&
1496 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1498 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1500 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1501 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1502 file_data_unref(fd);
1503 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1505 g_hash_table_destroy(file_data_planned_change_hash);
1506 file_data_planned_change_hash = NULL;
1507 DEBUG_1("planned change: empty");
1514 void file_data_free_ci(FileData *fd)
1516 FileDataChangeInfo *fdci = fd->change;
1520 file_data_planned_change_remove(fd);
1522 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1524 g_free(fdci->source);
1532 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1534 FileDataChangeInfo *fdci = fd->change;
1536 fdci->regroup_when_finished = enable;
1539 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1543 if (fd->parent) fd = fd->parent;
1545 if (fd->change) return FALSE;
1547 work = fd->sidecar_files;
1550 FileData *sfd = work->data;
1552 if (sfd->change) return FALSE;
1556 file_data_add_ci(fd, type, NULL, NULL);
1558 work = fd->sidecar_files;
1561 FileData *sfd = work->data;
1563 file_data_add_ci(sfd, type, NULL, NULL);
1570 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1574 if (fd->parent) fd = fd->parent;
1576 if (!fd->change || fd->change->type != type) return FALSE;
1578 work = fd->sidecar_files;
1581 FileData *sfd = work->data;
1583 if (!sfd->change || sfd->change->type != type) return FALSE;
1591 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1593 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1594 file_data_sc_update_ci_copy(fd, dest_path);
1598 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1600 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1601 file_data_sc_update_ci_move(fd, dest_path);
1605 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1607 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1608 file_data_sc_update_ci_rename(fd, dest_path);
1612 gboolean file_data_sc_add_ci_delete(FileData *fd)
1614 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1617 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1619 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1620 file_data_sc_update_ci_unspecified(fd, dest_path);
1624 gboolean file_data_add_ci_write_metadata(FileData *fd)
1626 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1629 void file_data_sc_free_ci(FileData *fd)
1633 if (fd->parent) fd = fd->parent;
1635 file_data_free_ci(fd);
1637 work = fd->sidecar_files;
1640 FileData *sfd = work->data;
1642 file_data_free_ci(sfd);
1647 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1650 gboolean ret = TRUE;
1655 FileData *fd = work->data;
1657 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1664 static void file_data_sc_revert_ci_list(GList *fd_list)
1671 FileData *fd = work->data;
1673 file_data_sc_free_ci(fd);
1678 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1685 FileData *fd = work->data;
1687 if (!func(fd, dest))
1689 file_data_sc_revert_ci_list(work->prev);
1698 gboolean file_data_sc_add_ci_copy_list(GList *fd_list, const gchar *dest)
1700 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_copy);
1703 gboolean file_data_sc_add_ci_move_list(GList *fd_list, const gchar *dest)
1705 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_move);
1708 gboolean file_data_sc_add_ci_rename_list(GList *fd_list, const gchar *dest)
1710 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_rename);
1713 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1715 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1718 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1721 gboolean ret = TRUE;
1726 FileData *fd = work->data;
1728 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1735 void file_data_free_ci_list(GList *fd_list)
1742 FileData *fd = work->data;
1744 file_data_free_ci(fd);
1749 void file_data_sc_free_ci_list(GList *fd_list)
1756 FileData *fd = work->data;
1758 file_data_sc_free_ci(fd);
1764 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1765 * fails if fd->change does not exist or the change type does not match
1768 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1770 FileDataChangeType type = fd->change->type;
1772 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1776 if (!file_data_planned_change_hash)
1777 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1779 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1781 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1782 g_hash_table_remove(file_data_planned_change_hash, old_path);
1783 file_data_unref(fd);
1786 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1791 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1792 g_hash_table_remove(file_data_planned_change_hash, new_path);
1793 file_data_unref(ofd);
1796 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1798 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1803 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1805 gchar *old_path = fd->change->dest;
1807 fd->change->dest = g_strdup(dest_path);
1808 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1812 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1814 const gchar *extension = extension_from_path(fd->change->source);
1815 gchar *base = remove_extension_from_path(dest_path);
1816 gchar *old_path = fd->change->dest;
1818 fd->change->dest = g_strconcat(base, extension, NULL);
1819 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1825 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1828 gchar *dest_path_full = NULL;
1830 if (fd->parent) fd = fd->parent;
1834 dest_path = fd->path;
1836 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1838 gchar *dir = remove_level_from_path(fd->path);
1840 dest_path_full = g_build_filename(dir, dest_path, NULL);
1842 dest_path = dest_path_full;
1844 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1846 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1847 dest_path = dest_path_full;
1850 file_data_update_ci_dest(fd, dest_path);
1852 work = fd->sidecar_files;
1855 FileData *sfd = work->data;
1857 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1861 g_free(dest_path_full);
1864 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1866 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1867 file_data_sc_update_ci(fd, dest_path);
1871 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1873 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1876 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1878 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1881 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1883 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1886 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1888 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1891 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1893 gboolean (*func)(FileData *, const gchar *))
1896 gboolean ret = TRUE;
1901 FileData *fd = work->data;
1903 if (!func(fd, dest)) ret = FALSE;
1910 gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
1912 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
1915 gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
1917 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
1920 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1922 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1927 * verify source and dest paths - dest image exists, etc.
1928 * it should detect all possible problems with the planned operation
1931 gint file_data_verify_ci(FileData *fd)
1933 gint ret = CHANGE_OK;
1938 DEBUG_1("Change checked: no change info: %s", fd->path);
1942 if (!isname(fd->path))
1944 /* this probably should not happen */
1945 ret |= CHANGE_NO_SRC;
1946 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1950 dir = remove_level_from_path(fd->path);
1952 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1953 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1954 fd->change->type != FILEDATA_CHANGE_RENAME &&
1955 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1958 ret |= CHANGE_WARN_UNSAVED_META;
1959 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1962 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1963 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1964 !access_file(fd->path, R_OK))
1966 ret |= CHANGE_NO_READ_PERM;
1967 DEBUG_1("Change checked: no read permission: %s", fd->path);
1969 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1970 !access_file(dir, W_OK))
1972 ret |= CHANGE_NO_WRITE_PERM_DIR;
1973 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1975 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1976 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1977 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1978 !access_file(fd->path, W_OK))
1980 ret |= CHANGE_WARN_NO_WRITE_PERM;
1981 DEBUG_1("Change checked: no write permission: %s", fd->path);
1983 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1984 - that means that there are no hard errors and warnings can be disabled
1985 - the destination is determined during the check
1987 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1989 /* determine destination file */
1990 gboolean have_dest = FALSE;
1991 gchar *dest_dir = NULL;
1993 if (options->metadata.save_in_image_file)
1995 if (file_data_can_write_directly(fd))
1997 /* we can write the file directly */
1998 if (access_file(fd->path, W_OK))
2004 if (options->metadata.warn_on_write_problems)
2006 ret |= CHANGE_WARN_NO_WRITE_PERM;
2007 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2011 else if (file_data_can_write_sidecar(fd))
2013 /* we can write sidecar */
2014 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2015 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2017 file_data_update_ci_dest(fd, sidecar);
2022 if (options->metadata.warn_on_write_problems)
2024 ret |= CHANGE_WARN_NO_WRITE_PERM;
2025 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2034 /* write private metadata file under ~/.geeqie */
2036 /* If an existing metadata file exists, we will try writing to
2037 * it's location regardless of the user's preference.
2039 gchar *metadata_path = NULL;
2041 /* but ignore XMP if we are not able to write it */
2042 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2044 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2046 if (metadata_path && !access_file(metadata_path, W_OK))
2048 g_free(metadata_path);
2049 metadata_path = NULL;
2056 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2057 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2059 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2061 metadata_path = g_build_filename(dest_dir, filename, NULL);
2065 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2067 file_data_update_ci_dest(fd, metadata_path);
2072 ret |= CHANGE_NO_WRITE_PERM_DEST;
2073 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2075 g_free(metadata_path);
2080 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2085 same = (strcmp(fd->path, fd->change->dest) == 0);
2089 const gchar *dest_ext = extension_from_path(fd->change->dest);
2090 if (!dest_ext) dest_ext = "";
2092 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2094 ret |= CHANGE_WARN_CHANGED_EXT;
2095 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2100 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2102 ret |= CHANGE_WARN_SAME;
2103 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2107 dest_dir = remove_level_from_path(fd->change->dest);
2109 if (!isdir(dest_dir))
2111 ret |= CHANGE_NO_DEST_DIR;
2112 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2114 else if (!access_file(dest_dir, W_OK))
2116 ret |= CHANGE_NO_WRITE_PERM_DEST_DIR;
2117 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2121 if (isfile(fd->change->dest))
2123 if (!access_file(fd->change->dest, W_OK))
2125 ret |= CHANGE_NO_WRITE_PERM_DEST;
2126 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2130 ret |= CHANGE_WARN_DEST_EXISTS;
2131 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2134 else if (isdir(fd->change->dest))
2136 ret |= CHANGE_DEST_EXISTS;
2137 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2144 fd->change->error = ret;
2145 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2152 gint file_data_sc_verify_ci(FileData *fd)
2157 ret = file_data_verify_ci(fd);
2159 work = fd->sidecar_files;
2162 FileData *sfd = work->data;
2164 ret |= file_data_verify_ci(sfd);
2171 gchar *file_data_get_error_string(gint error)
2173 GString *result = g_string_new("");
2175 if (error & CHANGE_NO_SRC)
2177 if (result->len > 0) g_string_append(result, ", ");
2178 g_string_append(result, _("file or directory does not exist"));
2181 if (error & CHANGE_DEST_EXISTS)
2183 if (result->len > 0) g_string_append(result, ", ");
2184 g_string_append(result, _("destination already exists"));
2187 if (error & CHANGE_NO_WRITE_PERM_DEST)
2189 if (result->len > 0) g_string_append(result, ", ");
2190 g_string_append(result, _("destination can't be overwritten"));
2193 if (error & CHANGE_NO_WRITE_PERM_DEST_DIR)
2195 if (result->len > 0) g_string_append(result, ", ");
2196 g_string_append(result, _("destination directory is not writable"));
2199 if (error & CHANGE_NO_DEST_DIR)
2201 if (result->len > 0) g_string_append(result, ", ");
2202 g_string_append(result, _("destination directory does not exist"));
2205 if (error & CHANGE_NO_WRITE_PERM_DIR)
2207 if (result->len > 0) g_string_append(result, ", ");
2208 g_string_append(result, _("source directory is not writable"));
2211 if (error & CHANGE_NO_READ_PERM)
2213 if (result->len > 0) g_string_append(result, ", ");
2214 g_string_append(result, _("no read permission"));
2217 if (error & CHANGE_WARN_NO_WRITE_PERM)
2219 if (result->len > 0) g_string_append(result, ", ");
2220 g_string_append(result, _("file is readonly"));
2223 if (error & CHANGE_WARN_DEST_EXISTS)
2225 if (result->len > 0) g_string_append(result, ", ");
2226 g_string_append(result, _("destination already exists and will be overwritten"));
2229 if (error & CHANGE_WARN_SAME)
2231 if (result->len > 0) g_string_append(result, ", ");
2232 g_string_append(result, _("source and destination are the same"));
2235 if (error & CHANGE_WARN_CHANGED_EXT)
2237 if (result->len > 0) g_string_append(result, ", ");
2238 g_string_append(result, _("source and destination have different extension"));
2241 if (error & CHANGE_WARN_UNSAVED_META)
2243 if (result->len > 0) g_string_append(result, ", ");
2244 g_string_append(result, _("there are unsaved metadata changes for the file"));
2247 return g_string_free(result, FALSE);
2250 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2253 gint all_errors = 0;
2254 gint common_errors = ~0;
2259 if (!list) return 0;
2261 num = g_list_length(list);
2262 errors = g_new(int, num);
2273 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2274 all_errors |= error;
2275 common_errors &= error;
2282 if (desc && all_errors)
2285 GString *result = g_string_new("");
2289 gchar *str = file_data_get_error_string(common_errors);
2290 g_string_append(result, str);
2291 g_string_append(result, "\n");
2305 error = errors[i] & ~common_errors;
2309 gchar *str = file_data_get_error_string(error);
2310 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2315 *desc = g_string_free(result, FALSE);
2324 * perform the change described by FileFataChangeInfo
2325 * it is used for internal operations,
2326 * this function actually operates with files on the filesystem
2327 * it should implement safe delete
2330 static gboolean file_data_perform_move(FileData *fd)
2332 g_assert(!strcmp(fd->change->source, fd->path));
2333 return move_file(fd->change->source, fd->change->dest);
2336 static gboolean file_data_perform_copy(FileData *fd)
2338 g_assert(!strcmp(fd->change->source, fd->path));
2339 return copy_file(fd->change->source, fd->change->dest);
2342 static gboolean file_data_perform_delete(FileData *fd)
2344 if (isdir(fd->path) && !islink(fd->path))
2345 return rmdir_utf8(fd->path);
2347 if (options->file_ops.safe_delete_enable)
2348 return file_util_safe_unlink(fd->path);
2350 return unlink_file(fd->path);
2353 gboolean file_data_perform_ci(FileData *fd)
2355 FileDataChangeType type = fd->change->type;
2359 case FILEDATA_CHANGE_MOVE:
2360 return file_data_perform_move(fd);
2361 case FILEDATA_CHANGE_COPY:
2362 return file_data_perform_copy(fd);
2363 case FILEDATA_CHANGE_RENAME:
2364 return file_data_perform_move(fd); /* the same as move */
2365 case FILEDATA_CHANGE_DELETE:
2366 return file_data_perform_delete(fd);
2367 case FILEDATA_CHANGE_WRITE_METADATA:
2368 return metadata_write_perform(fd);
2369 case FILEDATA_CHANGE_UNSPECIFIED:
2370 /* nothing to do here */
2378 gboolean file_data_sc_perform_ci(FileData *fd)
2381 gboolean ret = TRUE;
2382 FileDataChangeType type = fd->change->type;
2384 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2386 work = fd->sidecar_files;
2389 FileData *sfd = work->data;
2391 if (!file_data_perform_ci(sfd)) ret = FALSE;
2395 if (!file_data_perform_ci(fd)) ret = FALSE;
2401 * updates FileData structure according to FileDataChangeInfo
2404 gboolean file_data_apply_ci(FileData *fd)
2406 FileDataChangeType type = fd->change->type;
2409 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2411 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2412 file_data_planned_change_remove(fd);
2414 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2416 /* this change overwrites another file which is already known to other modules
2417 renaming fd would create duplicate FileData structure
2418 the best thing we can do is nothing
2419 FIXME: maybe we could copy stuff like marks
2421 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2425 file_data_set_path(fd, fd->change->dest);
2428 file_data_increment_version(fd);
2429 file_data_send_notification(fd, NOTIFY_CHANGE);
2434 gboolean file_data_sc_apply_ci(FileData *fd)
2437 FileDataChangeType type = fd->change->type;
2439 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2441 work = fd->sidecar_files;
2444 FileData *sfd = work->data;
2446 file_data_apply_ci(sfd);
2450 file_data_apply_ci(fd);
2455 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2458 if (fd->parent) fd = fd->parent;
2459 if (!g_list_find(list, fd)) return FALSE;
2461 work = fd->sidecar_files;
2464 if (!g_list_find(list, work->data)) return FALSE;
2471 static gboolean file_data_list_dump(GList *list)
2473 GList *work, *work2;
2478 FileData *fd = work->data;
2479 printf("%s\n", fd->name);
2480 work2 = fd->sidecar_files;
2483 FileData *fd = work2->data;
2484 printf(" %s\n", fd->name);
2485 work2 = work2->next;
2493 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2498 /* change partial groups to independent files */
2503 FileData *fd = work->data;
2506 if (!file_data_list_contains_whole_group(list, fd))
2508 file_data_disable_grouping(fd, TRUE);
2511 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2517 /* remove sidecars from the list,
2518 they can be still acessed via main_fd->sidecar_files */
2522 FileData *fd = work->data;
2526 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2528 out = g_list_prepend(out, file_data_ref(fd));
2532 filelist_free(list);
2533 out = g_list_reverse(out);
2543 * notify other modules about the change described by FileDataChangeInfo
2546 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2547 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2548 implementation in view_file_list.c */
2553 typedef struct _NotifyData NotifyData;
2555 struct _NotifyData {
2556 FileDataNotifyFunc func;
2558 NotifyPriority priority;
2561 static GList *notify_func_list = NULL;
2563 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2565 NotifyData *nda = (NotifyData *)a;
2566 NotifyData *ndb = (NotifyData *)b;
2568 if (nda->priority < ndb->priority) return -1;
2569 if (nda->priority > ndb->priority) return 1;
2573 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2576 GList *work = notify_func_list;
2580 NotifyData *nd = (NotifyData *)work->data;
2582 if (nd->func == func && nd->data == data)
2584 g_warning("Notify func already registered");
2590 nd = g_new(NotifyData, 1);
2593 nd->priority = priority;
2595 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2596 DEBUG_2("Notify func registered: %p", nd);
2601 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2603 GList *work = notify_func_list;
2607 NotifyData *nd = (NotifyData *)work->data;
2609 if (nd->func == func && nd->data == data)
2611 notify_func_list = g_list_delete_link(notify_func_list, work);
2613 DEBUG_2("Notify func unregistered: %p", nd);
2619 g_warning("Notify func not found");
2624 void file_data_send_notification(FileData *fd, NotifyType type)
2626 GList *work = notify_func_list;
2630 NotifyData *nd = (NotifyData *)work->data;
2632 nd->func(fd, type, nd->data);
2637 static GHashTable *file_data_monitor_pool = NULL;
2638 static guint realtime_monitor_id = 0; /* event source id */
2640 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2644 file_data_check_changed_files(fd);
2646 DEBUG_1("monitor %s", fd->path);
2649 static gboolean realtime_monitor_cb(gpointer data)
2651 if (!options->update_on_time_change) return TRUE;
2652 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2656 gboolean file_data_register_real_time_monitor(FileData *fd)
2662 if (!file_data_monitor_pool)
2663 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2665 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2667 DEBUG_1("Register realtime %d %s", count, fd->path);
2670 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2672 if (!realtime_monitor_id)
2674 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2680 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2684 g_assert(file_data_monitor_pool);
2686 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2688 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2690 g_assert(count > 0);
2695 g_hash_table_remove(file_data_monitor_pool, fd);
2697 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2699 file_data_unref(fd);
2701 if (g_hash_table_size(file_data_monitor_pool) == 0)
2703 g_source_remove(realtime_monitor_id);
2704 realtime_monitor_id = 0;
2710 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */