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 *extension);
31 static void file_data_check_sidecars(const GList *basename_list);
32 static FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd);
37 *-----------------------------------------------------------------------------
38 * text conversion utils
39 *-----------------------------------------------------------------------------
42 gchar *text_from_size(gint64 size)
48 /* what I would like to use is printf("%'d", size)
49 * BUT: not supported on every libc :(
53 /* the %lld conversion is not valid in all libcs, so use a simple work-around */
54 a = g_strdup_printf("%d%09d", (guint)(size / 1000000000), (guint)(size % 1000000000));
58 a = g_strdup_printf("%d", (guint)size);
64 b = g_new(gchar, l + n + 1);
89 gchar *text_from_size_abrev(gint64 size)
91 if (size < (gint64)1024)
93 return g_strdup_printf(_("%d bytes"), (gint)size);
95 if (size < (gint64)1048576)
97 return g_strdup_printf(_("%.1f K"), (gdouble)size / 1024.0);
99 if (size < (gint64)1073741824)
101 return g_strdup_printf(_("%.1f MB"), (gdouble)size / 1048576.0);
104 /* to avoid overflowing the gdouble, do division in two steps */
106 return g_strdup_printf(_("%.1f GB"), (gdouble)size / 1024.0);
109 /* note: returned string is valid until next call to text_from_time() */
110 const gchar *text_from_time(time_t t)
112 static gchar *ret = NULL;
116 GError *error = NULL;
118 btime = localtime(&t);
120 /* the %x warning about 2 digit years is not an error */
121 buflen = strftime(buf, sizeof(buf), "%x %X", btime);
122 if (buflen < 1) return "";
125 ret = g_locale_to_utf8(buf, buflen, NULL, NULL, &error);
128 log_printf("Error converting locale strftime to UTF-8: %s\n", error->message);
137 *-----------------------------------------------------------------------------
138 * changed files detection and notification
139 *-----------------------------------------------------------------------------
142 void file_data_increment_version(FileData *fd)
148 fd->parent->version++;
149 fd->parent->valid_marks = 0;
153 static gboolean file_data_check_changed_single_file(FileData *fd, struct stat *st)
155 if (fd->size != st->st_size ||
156 fd->date != st->st_mtime)
158 fd->size = st->st_size;
159 fd->date = st->st_mtime;
160 fd->mode = st->st_mode;
161 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
162 fd->thumb_pixbuf = NULL;
163 file_data_increment_version(fd);
164 file_data_send_notification(fd, NOTIFY_REREAD);
170 static gboolean file_data_check_changed_files_recursive(FileData *fd, struct stat *st)
172 gboolean ret = FALSE;
175 ret = file_data_check_changed_single_file(fd, st);
177 work = fd->sidecar_files;
180 FileData *sfd = work->data;
184 if (!stat_utf8(sfd->path, &st))
188 file_data_disconnect_sidecar_file(fd, sfd);
193 ret |= file_data_check_changed_files_recursive(sfd, &st);
199 gboolean file_data_check_changed_files(FileData *fd)
201 gboolean ret = FALSE;
204 if (fd->parent) fd = fd->parent;
206 if (!stat_utf8(fd->path, &st))
210 FileData *sfd = NULL;
212 /* parent is missing, we have to rebuild whole group */
217 /* file_data_disconnect_sidecar_file might delete the file,
218 we have to keep the reference to prevent this */
219 sidecars = filelist_copy(fd->sidecar_files);
226 file_data_disconnect_sidecar_file(fd, sfd);
228 file_data_check_sidecars(sidecars); /* this will group the sidecars back together */
229 /* now we can release the sidecars */
230 filelist_free(sidecars);
231 file_data_send_notification(fd, NOTIFY_REREAD);
235 ret |= file_data_check_changed_files_recursive(fd, &st);
242 *-----------------------------------------------------------------------------
243 * file name, extension, sorting, ...
244 *-----------------------------------------------------------------------------
247 static void file_data_set_collate_keys(FileData *fd)
249 gchar *caseless_name;
251 caseless_name = g_utf8_casefold(fd->name, -1);
253 g_free(fd->collate_key_name);
254 g_free(fd->collate_key_name_nocase);
256 #if 0 && GLIB_CHECK_VERSION(2, 8, 0)
257 fd->collate_key_name = g_utf8_collate_key_for_filename(fd->name, -1);
258 fd->collate_key_name_nocase = g_utf8_collate_key_for_filename(caseless_name, -1);
260 fd->collate_key_name = g_utf8_collate_key(fd->name, -1);
261 fd->collate_key_name_nocase = g_utf8_collate_key(caseless_name, -1);
263 g_free(caseless_name);
266 static void file_data_set_path(FileData *fd, const gchar *path)
268 g_assert(path /* && *path*/); /* view_dir_tree uses FileData with zero length path */
269 g_assert(file_data_pool);
273 if (fd->original_path)
275 g_hash_table_remove(file_data_pool, fd->original_path);
276 g_free(fd->original_path);
279 g_assert(!g_hash_table_lookup(file_data_pool, path));
281 fd->original_path = g_strdup(path);
282 g_hash_table_insert(file_data_pool, fd->original_path, fd);
284 if (strcmp(path, G_DIR_SEPARATOR_S) == 0)
286 fd->path = g_strdup(path);
288 fd->extension = fd->name + 1;
289 file_data_set_collate_keys(fd);
293 fd->path = g_strdup(path);
294 fd->name = filename_from_path(fd->path);
296 if (strcmp(fd->name, "..") == 0)
298 gchar *dir = remove_level_from_path(path);
300 fd->path = remove_level_from_path(dir);
303 fd->extension = fd->name + 2;
304 file_data_set_collate_keys(fd);
307 else if (strcmp(fd->name, ".") == 0)
310 fd->path = remove_level_from_path(path);
312 fd->extension = fd->name + 1;
313 file_data_set_collate_keys(fd);
317 fd->extension = registered_extension_from_path(fd->path);
318 if (fd->extension == NULL)
320 fd->extension = fd->name + strlen(fd->name);
323 fd->sidecar_priority = sidecar_file_priority(fd->extension);
324 file_data_set_collate_keys(fd);
328 *-----------------------------------------------------------------------------
329 * create or reuse Filedata
330 *-----------------------------------------------------------------------------
333 static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean disable_sidecars)
337 DEBUG_2("file_data_new: '%s' %d", path_utf8, disable_sidecars);
339 if (S_ISDIR(st->st_mode)) disable_sidecars = TRUE;
342 file_data_pool = g_hash_table_new(g_str_hash, g_str_equal);
344 fd = g_hash_table_lookup(file_data_pool, path_utf8);
350 if (!fd && file_data_planned_change_hash)
352 fd = g_hash_table_lookup(file_data_planned_change_hash, path_utf8);
355 DEBUG_1("planned change: using %s -> %s", path_utf8, fd->path);
357 file_data_apply_ci(fd);
365 if (disable_sidecars) file_data_disable_grouping(fd, TRUE);
368 changed = file_data_check_changed_single_file(fd, st);
370 DEBUG_2("file_data_pool hit: '%s' %s", fd->path, changed ? "(changed)" : "");
375 fd = g_new0(FileData, 1);
377 fd->size = st->st_size;
378 fd->date = st->st_mtime;
379 fd->mode = st->st_mode;
381 fd->magick = 0x12345678;
383 if (disable_sidecars) fd->disable_grouping = TRUE;
385 file_data_set_path(fd, path_utf8); /* set path, name, collate_key_*, original_path */
390 static FileData *file_data_new_local(const gchar *path, struct stat *st, gboolean disable_sidecars)
392 gchar *path_utf8 = path_to_utf8(path);
393 FileData *ret = file_data_new(path_utf8, st, disable_sidecars);
399 FileData *file_data_new_no_grouping(const gchar *path_utf8)
403 if (!stat_utf8(path_utf8, &st))
409 return file_data_new(path_utf8, &st, TRUE);
412 FileData *file_data_new_dir(const gchar *path_utf8)
416 if (!stat_utf8(path_utf8, &st))
422 /* dir or non-existing yet */
423 g_assert(S_ISDIR(st.st_mode));
425 return file_data_new(path_utf8, &st, TRUE);
429 *-----------------------------------------------------------------------------
431 *-----------------------------------------------------------------------------
434 #ifdef DEBUG_FILEDATA
435 FileData *file_data_ref_debug(const gchar *file, gint line, FileData *fd)
437 FileData *file_data_ref(FileData *fd)
440 if (fd == NULL) return NULL;
441 #ifdef DEBUG_FILEDATA
442 if (fd->magick != 0x12345678)
443 DEBUG_0("fd magick mismatch at %s:%d", file, line);
445 g_assert(fd->magick == 0x12345678);
448 #ifdef DEBUG_FILEDATA
449 DEBUG_2("file_data_ref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
451 DEBUG_2("file_data_ref (%d): '%s'", fd->ref, fd->path);
456 static void file_data_free(FileData *fd)
458 g_assert(fd->magick == 0x12345678);
459 g_assert(fd->ref == 0);
461 metadata_cache_free(fd);
462 g_hash_table_remove(file_data_pool, fd->original_path);
465 g_free(fd->original_path);
466 g_free(fd->collate_key_name);
467 g_free(fd->collate_key_name_nocase);
468 if (fd->thumb_pixbuf) g_object_unref(fd->thumb_pixbuf);
469 histmap_free(fd->histmap);
471 g_assert(fd->sidecar_files == NULL); /* sidecar files must be freed before calling this */
473 file_data_change_info_free(NULL, fd);
477 #ifdef DEBUG_FILEDATA
478 void file_data_unref_debug(const gchar *file, gint line, FileData *fd)
480 void file_data_unref(FileData *fd)
483 if (fd == NULL) return;
484 #ifdef DEBUG_FILEDATA
485 if (fd->magick != 0x12345678)
486 DEBUG_0("fd magick mismatch @ %s:%d", file, line);
488 g_assert(fd->magick == 0x12345678);
491 #ifdef DEBUG_FILEDATA
492 DEBUG_2("file_data_unref (%d): '%s' @ %s:%d", fd->ref, fd->path, file, line);
494 DEBUG_2("file_data_unref (%d): '%s'", fd->ref, fd->path);
499 FileData *parent = fd->parent ? fd->parent : fd;
501 if (parent->ref > 0) return;
503 work = parent->sidecar_files;
506 FileData *sfd = work->data;
507 if (sfd->ref > 0) return;
511 /* none of parent/children is referenced, we can free everything */
513 DEBUG_2("file_data_unref: deleting '%s', parent '%s'", fd->path, fd->parent ? parent->path : "-");
515 work = parent->sidecar_files;
518 FileData *sfd = work->data;
523 g_list_free(parent->sidecar_files);
524 parent->sidecar_files = NULL;
526 file_data_free(parent);
533 *-----------------------------------------------------------------------------
534 * sidecar file info struct
535 *-----------------------------------------------------------------------------
538 static gint file_data_sort_by_ext(gconstpointer a, gconstpointer b)
540 const FileData *fda = a;
541 const FileData *fdb = b;
543 if (fda->sidecar_priority < fdb->sidecar_priority) return -1;
544 if (fda->sidecar_priority > fdb->sidecar_priority) return 1;
546 return strcmp(fdb->extension, fda->extension);
550 static gint sidecar_file_priority(const gchar *extension)
555 if (extension == NULL)
558 work = sidecar_ext_get_list();
561 gchar *ext = work->data;
564 if (g_ascii_strcasecmp(extension, ext) == 0) return i;
570 static FileData *file_data_add_sidecar_file(FileData *target, FileData *sfd)
572 sfd->parent = target;
573 if (!g_list_find(target->sidecar_files, sfd))
574 target->sidecar_files = g_list_insert_sorted(target->sidecar_files, sfd, file_data_sort_by_ext);
575 file_data_increment_version(sfd); /* increments both sfd and target */
580 static FileData *file_data_merge_sidecar_files(FileData *target, FileData *source)
584 file_data_add_sidecar_file(target, source);
586 work = source->sidecar_files;
589 FileData *sfd = work->data;
590 file_data_add_sidecar_file(target, sfd);
594 g_list_free(source->sidecar_files);
595 source->sidecar_files = NULL;
600 static void file_data_check_sidecars(const GList *basename_list)
604 if (!basename_list) return;
605 /* process the group list - the first one is the parent file, others are sidecars */
606 parent_fd = basename_list->data;
607 work = basename_list->next;
610 FileData *sfd = work->data;
613 file_data_merge_sidecar_files(parent_fd, sfd);
616 /* there may be some sidecars that are already deleted - disconnect them */
617 work = parent_fd->sidecar_files;
620 FileData *sfd = work->data;
623 if (!g_list_find((GList *)basename_list, sfd))
625 printf("removing unknown %s: %s \n", parent_fd->path, sfd->path);
626 file_data_disconnect_sidecar_file(parent_fd, sfd);
627 file_data_send_notification(sfd, NOTIFY_REREAD);
628 file_data_send_notification(parent_fd, NOTIFY_REREAD);
633 static FileData *file_data_disconnect_sidecar_file(FileData *target, FileData *sfd)
635 sfd->parent = target;
636 g_assert(g_list_find(target->sidecar_files, sfd));
638 file_data_increment_version(sfd); /* increments both sfd and target */
640 target->sidecar_files = g_list_remove(target->sidecar_files, sfd);
652 /* disables / enables grouping for particular file, sends UPDATE notification */
653 void file_data_disable_grouping(FileData *fd, gboolean disable)
655 if (!fd->disable_grouping == !disable) return;
657 fd->disable_grouping = !!disable;
663 FileData *parent = file_data_ref(fd->parent);
664 file_data_disconnect_sidecar_file(parent, fd);
665 file_data_send_notification(parent, NOTIFY_GROUPING);
666 file_data_unref(parent);
668 else if (fd->sidecar_files)
670 GList *sidecar_files = filelist_copy(fd->sidecar_files);
671 GList *work = sidecar_files;
674 FileData *sfd = work->data;
676 file_data_disconnect_sidecar_file(fd, sfd);
677 file_data_send_notification(sfd, NOTIFY_GROUPING);
679 file_data_check_sidecars(sidecar_files); /* this will group the sidecars back together */
680 filelist_free(sidecar_files);
684 file_data_increment_version(fd); /* the functions called in the cases above increments the version too */
689 file_data_increment_version(fd);
690 /* file_data_check_sidecars call is not necessary - the file will be re-grouped on next dir read */
692 file_data_send_notification(fd, NOTIFY_GROUPING);
695 void file_data_disable_grouping_list(GList *fd_list, gboolean disable)
702 FileData *fd = work->data;
704 file_data_disable_grouping(fd, disable);
712 *-----------------------------------------------------------------------------
714 *-----------------------------------------------------------------------------
717 static SortType filelist_sort_method = SORT_NONE;
718 static gboolean filelist_sort_ascend = TRUE;
721 gint filelist_sort_compare_filedata(FileData *fa, FileData *fb)
724 if (!filelist_sort_ascend)
731 switch (filelist_sort_method)
736 if (fa->size < fb->size) return -1;
737 if (fa->size > fb->size) return 1;
738 /* fall back to name */
741 if (fa->date < fb->date) return -1;
742 if (fa->date > fb->date) return 1;
743 /* fall back to name */
745 #ifdef HAVE_STRVERSCMP
747 ret = strverscmp(fa->name, fb->name);
748 if (ret != 0) return ret;
755 if (options->file_sort.case_sensitive)
756 ret = strcmp(fa->collate_key_name, fb->collate_key_name);
758 ret = strcmp(fa->collate_key_name_nocase, fb->collate_key_name_nocase);
760 if (ret != 0) return ret;
762 /* do not return 0 unless the files are really the same
763 file_data_pool ensures that original_path is unique
765 return strcmp(fa->original_path, fb->original_path);
768 gint filelist_sort_compare_filedata_full(FileData *fa, FileData *fb, SortType method, gboolean ascend)
770 filelist_sort_method = method;
771 filelist_sort_ascend = ascend;
772 return filelist_sort_compare_filedata(fa, fb);
775 static gint filelist_sort_file_cb(gpointer a, gpointer b)
777 return filelist_sort_compare_filedata(a, b);
780 GList *filelist_sort_full(GList *list, SortType method, gboolean ascend, GCompareFunc cb)
782 filelist_sort_method = method;
783 filelist_sort_ascend = ascend;
784 return g_list_sort(list, cb);
787 GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gboolean ascend, GCompareFunc cb)
789 filelist_sort_method = method;
790 filelist_sort_ascend = ascend;
791 return g_list_insert_sorted(list, data, cb);
794 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
796 return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
799 GList *filelist_insert_sort(GList *list, FileData *fd, SortType method, gboolean ascend)
801 return filelist_insert_sort_full(list, fd, method, ascend, (GCompareFunc) filelist_sort_file_cb);
805 *-----------------------------------------------------------------------------
806 * basename hash - grouping of sidecars in filelist
807 *-----------------------------------------------------------------------------
811 static GHashTable *file_data_basename_hash_new(void)
813 return g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
816 static GList * file_data_basename_hash_insert(GHashTable *basename_hash, FileData *fd)
819 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
821 list = g_hash_table_lookup(basename_hash, basename);
823 if (!g_list_find(list, fd))
825 list = g_list_insert_sorted(list, file_data_ref(fd), file_data_sort_by_ext);
826 g_hash_table_insert(basename_hash, basename, list);
836 static void file_data_basename_hash_remove(GHashTable *basename_hash, FileData *fd)
839 gchar *basename = g_strndup(fd->path, fd->extension - fd->path);
841 list = g_hash_table_lookup(basename_hash, basename);
843 if (!g_list_find(list, fd)) return;
845 list = g_list_remove(list, fd);
850 g_hash_table_insert(basename_hash, basename, list);
854 g_hash_table_remove(basename_hash, basename);
860 static void file_data_basename_hash_remove_list(gpointer key, gpointer value, gpointer data)
862 filelist_free((GList *)value);
865 static void file_data_basename_hash_free(GHashTable *basename_hash)
867 g_hash_table_foreach(basename_hash, file_data_basename_hash_remove_list, NULL);
868 g_hash_table_destroy(basename_hash);
872 *-----------------------------------------------------------------------------
873 * handling sidecars in filelist
874 *-----------------------------------------------------------------------------
877 static GList *filelist_filter_out_sidecars(GList *flist)
880 GList *flist_filtered = NULL;
884 FileData *fd = work->data;
887 if (fd->parent) /* remove fd's that are children */
890 flist_filtered = g_list_prepend(flist_filtered, fd);
894 return flist_filtered;
897 static void file_data_basename_hash_to_sidecars(gpointer key, gpointer value, gpointer data)
899 GList *basename_list = (GList *)value;
900 file_data_check_sidecars(basename_list);
904 static gboolean is_hidden_file(const gchar *name)
906 if (name[0] != '.') return FALSE;
907 if (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')) return FALSE;
912 *-----------------------------------------------------------------------------
913 * the main filelist function
914 *-----------------------------------------------------------------------------
917 static gboolean filelist_read_real(const gchar *dir_path, GList **files, GList **dirs, gboolean follow_symlinks)
924 gint (*stat_func)(const gchar *path, struct stat *buf);
925 GHashTable *basename_hash = NULL;
927 g_assert(files || dirs);
929 if (files) *files = NULL;
930 if (dirs) *dirs = NULL;
932 pathl = path_from_utf8(dir_path);
933 if (!pathl) return FALSE;
942 if (files) basename_hash = file_data_basename_hash_new();
949 while ((dir = readdir(dp)) != NULL)
951 struct stat ent_sbuf;
952 const gchar *name = dir->d_name;
955 if (!options->file_filter.show_hidden_files && is_hidden_file(name))
958 filepath = g_build_filename(pathl, name, NULL);
959 if (stat_func(filepath, &ent_sbuf) >= 0)
961 if (S_ISDIR(ent_sbuf.st_mode))
963 /* we ignore the .thumbnails dir for cleanliness */
965 !(name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) &&
966 strcmp(name, GQ_CACHE_LOCAL_THUMB) != 0 &&
967 strcmp(name, GQ_CACHE_LOCAL_METADATA) != 0 &&
968 strcmp(name, THUMB_FOLDER_LOCAL) != 0)
970 dlist = g_list_prepend(dlist, file_data_new_local(filepath, &ent_sbuf, TRUE));
975 if (files && filter_name_exists(name))
977 FileData *fd = file_data_new_local(filepath, &ent_sbuf, FALSE);
978 flist = g_list_prepend(flist, fd);
979 if (fd->sidecar_priority && !fd->disable_grouping)
981 file_data_basename_hash_insert(basename_hash, fd);
988 if (errno == EOVERFLOW)
990 log_printf("stat(): EOVERFLOW, skip '%s'", filepath);
1000 if (dirs) *dirs = dlist;
1003 g_hash_table_foreach(basename_hash, file_data_basename_hash_to_sidecars, NULL);
1005 *files = filelist_filter_out_sidecars(flist);
1007 if (basename_hash) file_data_basename_hash_free(basename_hash);
1012 gboolean filelist_read(FileData *dir_fd, GList **files, GList **dirs)
1014 return filelist_read_real(dir_fd->path, files, dirs, TRUE);
1017 gboolean filelist_read_lstat(FileData *dir_fd, GList **files, GList **dirs)
1019 return filelist_read_real(dir_fd->path, files, dirs, FALSE);
1022 FileData *file_data_new_group(const gchar *path_utf8)
1029 if (!stat_utf8(path_utf8, &st))
1035 if (S_ISDIR(st.st_mode))
1036 return file_data_new(path_utf8, &st, TRUE);
1038 dir = remove_level_from_path(path_utf8);
1040 filelist_read_real(dir, &files, NULL, TRUE);
1042 fd = g_hash_table_lookup(file_data_pool, path_utf8);
1046 filelist_free(files);
1052 void filelist_free(GList *list)
1059 file_data_unref((FileData *)work->data);
1067 GList *filelist_copy(GList *list)
1069 GList *new_list = NULL;
1080 new_list = g_list_prepend(new_list, file_data_ref(fd));
1083 return g_list_reverse(new_list);
1086 GList *filelist_from_path_list(GList *list)
1088 GList *new_list = NULL;
1099 new_list = g_list_prepend(new_list, file_data_new_group(path));
1102 return g_list_reverse(new_list);
1105 GList *filelist_to_path_list(GList *list)
1107 GList *new_list = NULL;
1118 new_list = g_list_prepend(new_list, g_strdup(fd->path));
1121 return g_list_reverse(new_list);
1124 GList *filelist_filter(GList *list, gboolean is_dir_list)
1128 if (!is_dir_list && options->file_filter.disable && options->file_filter.show_hidden_files) return list;
1133 FileData *fd = (FileData *)(work->data);
1134 const gchar *name = fd->name;
1136 if ((!options->file_filter.show_hidden_files && is_hidden_file(name)) ||
1137 (!is_dir_list && !filter_name_exists(name)) ||
1138 (is_dir_list && name[0] == '.' && (strcmp(name, GQ_CACHE_LOCAL_THUMB) == 0 ||
1139 strcmp(name, GQ_CACHE_LOCAL_METADATA) == 0)) )
1143 list = g_list_remove_link(list, link);
1144 file_data_unref(fd);
1155 *-----------------------------------------------------------------------------
1156 * filelist recursive
1157 *-----------------------------------------------------------------------------
1160 static gint filelist_sort_path_cb(gconstpointer a, gconstpointer b)
1162 return CASE_SORT(((FileData *)a)->path, ((FileData *)b)->path);
1165 GList *filelist_sort_path(GList *list)
1167 return g_list_sort(list, filelist_sort_path_cb);
1170 static void filelist_recursive_append(GList **list, GList *dirs)
1177 FileData *fd = (FileData *)(work->data);
1181 if (filelist_read(fd, &f, &d))
1183 f = filelist_filter(f, FALSE);
1184 f = filelist_sort_path(f);
1185 *list = g_list_concat(*list, f);
1187 d = filelist_filter(d, TRUE);
1188 d = filelist_sort_path(d);
1189 filelist_recursive_append(list, d);
1197 GList *filelist_recursive(FileData *dir_fd)
1202 if (!filelist_read(dir_fd, &list, &d)) return NULL;
1203 list = filelist_filter(list, FALSE);
1204 list = filelist_sort_path(list);
1206 d = filelist_filter(d, TRUE);
1207 d = filelist_sort_path(d);
1208 filelist_recursive_append(&list, d);
1215 *-----------------------------------------------------------------------------
1216 * file modification support
1217 *-----------------------------------------------------------------------------
1221 void file_data_change_info_free(FileDataChangeInfo *fdci, FileData *fd)
1223 if (!fdci && fd) fdci = fd->change;
1227 g_free(fdci->source);
1232 if (fd) fd->change = NULL;
1235 static gboolean file_data_can_write_directly(FileData *fd)
1237 return filter_name_is_writable(fd->extension);
1240 static gboolean file_data_can_write_sidecar(FileData *fd)
1242 return filter_name_allow_sidecar(fd->extension) && !filter_name_is_writable(fd->extension);
1245 gchar *file_data_get_sidecar_path(FileData *fd, gboolean existing_only)
1247 gchar *sidecar_path = NULL;
1250 if (!file_data_can_write_sidecar(fd)) return NULL;
1252 work = fd->parent ? fd->parent->sidecar_files : fd->sidecar_files;
1255 FileData *sfd = work->data;
1257 if (g_ascii_strcasecmp(sfd->extension, ".xmp") == 0)
1259 sidecar_path = g_strdup(sfd->path);
1264 if (!existing_only && !sidecar_path)
1266 gchar *base = g_strndup(fd->path, fd->extension - fd->path);
1267 sidecar_path = g_strconcat(base, ".xmp", NULL);
1271 return sidecar_path;
1275 * marks and orientation
1278 static FileDataGetMarkFunc file_data_get_mark_func[FILEDATA_MARKS_SIZE];
1279 static FileDataSetMarkFunc file_data_set_mark_func[FILEDATA_MARKS_SIZE];
1280 static gpointer file_data_mark_func_data[FILEDATA_MARKS_SIZE];
1281 static GDestroyNotify file_data_destroy_mark_func[FILEDATA_MARKS_SIZE];
1283 gboolean file_data_get_mark(FileData *fd, gint n)
1285 gboolean valid = (fd->valid_marks & (1 << n));
1287 if (file_data_get_mark_func[n] && !valid)
1289 guint old = fd->marks;
1290 gboolean value = (file_data_get_mark_func[n])(fd, n, file_data_mark_func_data[n]);
1292 if (!value != !(fd->marks & (1 << n)))
1294 fd->marks = fd->marks ^ (1 << n);
1297 fd->valid_marks |= (1 << n);
1298 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1300 file_data_unref(fd);
1302 else if (!old && fd->marks)
1308 return !!(fd->marks & (1 << n));
1311 guint file_data_get_marks(FileData *fd)
1314 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) file_data_get_mark(fd, i);
1318 void file_data_set_mark(FileData *fd, gint n, gboolean value)
1321 if (!value == !file_data_get_mark(fd, n)) return;
1323 if (file_data_set_mark_func[n])
1325 (file_data_set_mark_func[n])(fd, n, value, file_data_mark_func_data[n]);
1330 fd->marks = fd->marks ^ (1 << n);
1332 if (old && !fd->marks) /* keep files with non-zero marks in memory */
1334 file_data_unref(fd);
1336 else if (!old && fd->marks)
1341 file_data_increment_version(fd);
1342 file_data_send_notification(fd, NOTIFY_MARKS);
1345 gboolean file_data_filter_marks(FileData *fd, guint filter)
1348 for (i = 0; i < FILEDATA_MARKS_SIZE; i++) if (filter & (1 << i)) file_data_get_mark(fd, i);
1349 return ((fd->marks & filter) == filter);
1352 GList *file_data_filter_marks_list(GList *list, guint filter)
1359 FileData *fd = work->data;
1363 if (!file_data_filter_marks(fd, filter))
1365 list = g_list_remove_link(list, link);
1366 file_data_unref(fd);
1374 static void file_data_notify_mark_func(gpointer key, gpointer value, gpointer user_data)
1376 FileData *fd = value;
1377 file_data_increment_version(fd);
1378 file_data_send_notification(fd, NOTIFY_MARKS);
1381 gboolean file_data_register_mark_func(gint n, FileDataGetMarkFunc get_mark_func, FileDataSetMarkFunc set_mark_func, gpointer data, GDestroyNotify notify)
1383 if (n < 0 || n >= FILEDATA_MARKS_SIZE) return FALSE;
1385 if (file_data_destroy_mark_func[n]) (file_data_destroy_mark_func[n])(file_data_mark_func_data[n]);
1387 file_data_get_mark_func[n] = get_mark_func;
1388 file_data_set_mark_func[n] = set_mark_func;
1389 file_data_mark_func_data[n] = data;
1390 file_data_destroy_mark_func[n] = notify;
1394 /* this effectively changes all known files */
1395 g_hash_table_foreach(file_data_pool, file_data_notify_mark_func, NULL);
1401 void file_data_get_registered_mark_func(gint n, FileDataGetMarkFunc *get_mark_func, FileDataSetMarkFunc *set_mark_func, gpointer *data)
1403 if (get_mark_func) *get_mark_func = file_data_get_mark_func[n];
1404 if (set_mark_func) *set_mark_func = file_data_set_mark_func[n];
1405 if (data) *data = file_data_mark_func_data[n];
1408 gint file_data_get_user_orientation(FileData *fd)
1410 return fd->user_orientation;
1413 void file_data_set_user_orientation(FileData *fd, gint value)
1415 if (fd->user_orientation == value) return;
1417 fd->user_orientation = value;
1418 file_data_increment_version(fd);
1419 file_data_send_notification(fd, NOTIFY_ORIENTATION);
1424 * file_data - operates on the given fd
1425 * file_data_sc - operates on the given fd + sidecars - all fds linked via fd->sidecar_files or fd->parent
1429 /* return list of sidecar file extensions in a string */
1430 gchar *file_data_sc_list_to_string(FileData *fd)
1433 GString *result = g_string_new("");
1435 work = fd->sidecar_files;
1438 FileData *sfd = work->data;
1440 result = g_string_append(result, "+ ");
1441 result = g_string_append(result, sfd->extension);
1443 if (work) result = g_string_append_c(result, ' ');
1446 return g_string_free(result, FALSE);
1452 * add FileDataChangeInfo (see typedefs.h) for the given operation
1453 * uses file_data_add_change_info
1455 * fails if the fd->change already exists - change operations can't run in parallel
1456 * fd->change_info works as a lock
1458 * dest can be NULL - in this case the current name is used for now, it will
1463 FileDataChangeInfo types:
1465 MOVE - path is changed, name may be changed too
1466 RENAME - path remains unchanged, name is changed
1467 extension should remain (FIXME should we allow editing extension? it will make problems wth grouping)
1468 sidecar names are changed too, extensions are not changed
1470 UPDATE - file size, date or grouping has been changed
1473 gboolean file_data_add_ci(FileData *fd, FileDataChangeType type, const gchar *src, const gchar *dest)
1475 FileDataChangeInfo *fdci;
1477 if (fd->change) return FALSE;
1479 fdci = g_new0(FileDataChangeInfo, 1);
1484 fdci->source = g_strdup(src);
1486 fdci->source = g_strdup(fd->path);
1489 fdci->dest = g_strdup(dest);
1496 static void file_data_planned_change_remove(FileData *fd)
1498 if (file_data_planned_change_hash &&
1499 (fd->change->type == FILEDATA_CHANGE_MOVE || fd->change->type == FILEDATA_CHANGE_RENAME))
1501 if (g_hash_table_lookup(file_data_planned_change_hash, fd->change->dest) == fd)
1503 DEBUG_1("planned change: removing %s -> %s", fd->change->dest, fd->path);
1504 g_hash_table_remove(file_data_planned_change_hash, fd->change->dest);
1505 file_data_unref(fd);
1506 if (g_hash_table_size(file_data_planned_change_hash) == 0)
1508 g_hash_table_destroy(file_data_planned_change_hash);
1509 file_data_planned_change_hash = NULL;
1510 DEBUG_1("planned change: empty");
1517 void file_data_free_ci(FileData *fd)
1519 FileDataChangeInfo *fdci = fd->change;
1523 file_data_planned_change_remove(fd);
1525 if (fdci->regroup_when_finished) file_data_disable_grouping(fd, FALSE);
1527 g_free(fdci->source);
1535 void file_data_set_regroup_when_finished(FileData *fd, gboolean enable)
1537 FileDataChangeInfo *fdci = fd->change;
1539 fdci->regroup_when_finished = enable;
1542 static gboolean file_data_sc_add_ci(FileData *fd, FileDataChangeType type)
1546 if (fd->parent) fd = fd->parent;
1548 if (fd->change) return FALSE;
1550 work = fd->sidecar_files;
1553 FileData *sfd = work->data;
1555 if (sfd->change) return FALSE;
1559 file_data_add_ci(fd, type, NULL, NULL);
1561 work = fd->sidecar_files;
1564 FileData *sfd = work->data;
1566 file_data_add_ci(sfd, type, NULL, NULL);
1573 static gboolean file_data_sc_check_ci(FileData *fd, FileDataChangeType type)
1577 if (fd->parent) fd = fd->parent;
1579 if (!fd->change || fd->change->type != type) return FALSE;
1581 work = fd->sidecar_files;
1584 FileData *sfd = work->data;
1586 if (!sfd->change || sfd->change->type != type) return FALSE;
1594 gboolean file_data_sc_add_ci_copy(FileData *fd, const gchar *dest_path)
1596 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_COPY)) return FALSE;
1597 file_data_sc_update_ci_copy(fd, dest_path);
1601 gboolean file_data_sc_add_ci_move(FileData *fd, const gchar *dest_path)
1603 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_MOVE)) return FALSE;
1604 file_data_sc_update_ci_move(fd, dest_path);
1608 gboolean file_data_sc_add_ci_rename(FileData *fd, const gchar *dest_path)
1610 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_RENAME)) return FALSE;
1611 file_data_sc_update_ci_rename(fd, dest_path);
1615 gboolean file_data_sc_add_ci_delete(FileData *fd)
1617 return file_data_sc_add_ci(fd, FILEDATA_CHANGE_DELETE);
1620 gboolean file_data_sc_add_ci_unspecified(FileData *fd, const gchar *dest_path)
1622 if (!file_data_sc_add_ci(fd, FILEDATA_CHANGE_UNSPECIFIED)) return FALSE;
1623 file_data_sc_update_ci_unspecified(fd, dest_path);
1627 gboolean file_data_add_ci_write_metadata(FileData *fd)
1629 return file_data_add_ci(fd, FILEDATA_CHANGE_WRITE_METADATA, NULL, NULL);
1632 void file_data_sc_free_ci(FileData *fd)
1636 if (fd->parent) fd = fd->parent;
1638 file_data_free_ci(fd);
1640 work = fd->sidecar_files;
1643 FileData *sfd = work->data;
1645 file_data_free_ci(sfd);
1650 gboolean file_data_sc_add_ci_delete_list(GList *fd_list)
1653 gboolean ret = TRUE;
1658 FileData *fd = work->data;
1660 if (!file_data_sc_add_ci_delete(fd)) ret = FALSE;
1667 static void file_data_sc_revert_ci_list(GList *fd_list)
1674 FileData *fd = work->data;
1676 file_data_sc_free_ci(fd);
1681 static gboolean file_data_sc_add_ci_list_call_func(GList *fd_list, const gchar *dest, gboolean (*func)(FileData *, const gchar *))
1688 FileData *fd = work->data;
1690 if (!func(fd, dest))
1692 file_data_sc_revert_ci_list(work->prev);
1701 gboolean file_data_sc_add_ci_copy_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_copy);
1706 gboolean file_data_sc_add_ci_move_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_move);
1711 gboolean file_data_sc_add_ci_rename_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_rename);
1716 gboolean file_data_sc_add_ci_unspecified_list(GList *fd_list, const gchar *dest)
1718 return file_data_sc_add_ci_list_call_func(fd_list, dest, file_data_sc_add_ci_unspecified);
1721 gboolean file_data_add_ci_write_metadata_list(GList *fd_list)
1724 gboolean ret = TRUE;
1729 FileData *fd = work->data;
1731 if (!file_data_add_ci_write_metadata(fd)) ret = FALSE;
1738 void file_data_free_ci_list(GList *fd_list)
1745 FileData *fd = work->data;
1747 file_data_free_ci(fd);
1752 void file_data_sc_free_ci_list(GList *fd_list)
1759 FileData *fd = work->data;
1761 file_data_sc_free_ci(fd);
1767 * update existing fd->change, it will be used from dialog callbacks for interactive editing
1768 * fails if fd->change does not exist or the change type does not match
1771 static void file_data_update_planned_change_hash(FileData *fd, const gchar *old_path, gchar *new_path)
1773 FileDataChangeType type = fd->change->type;
1775 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
1779 if (!file_data_planned_change_hash)
1780 file_data_planned_change_hash = g_hash_table_new(g_str_hash, g_str_equal);
1782 if (old_path && g_hash_table_lookup(file_data_planned_change_hash, old_path) == fd)
1784 DEBUG_1("planned change: removing %s -> %s", old_path, fd->path);
1785 g_hash_table_remove(file_data_planned_change_hash, old_path);
1786 file_data_unref(fd);
1789 ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
1794 DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
1795 g_hash_table_remove(file_data_planned_change_hash, new_path);
1796 file_data_unref(ofd);
1799 DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
1801 g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
1806 static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
1808 gchar *old_path = fd->change->dest;
1810 fd->change->dest = g_strdup(dest_path);
1811 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1815 static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
1817 const gchar *extension = extension_from_path(fd->change->source);
1818 gchar *base = remove_extension_from_path(dest_path);
1819 gchar *old_path = fd->change->dest;
1821 fd->change->dest = g_strconcat(base, extension, NULL);
1822 file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
1828 static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
1831 gchar *dest_path_full = NULL;
1833 if (fd->parent) fd = fd->parent;
1837 dest_path = fd->path;
1839 else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
1841 gchar *dir = remove_level_from_path(fd->path);
1843 dest_path_full = g_build_filename(dir, dest_path, NULL);
1845 dest_path = dest_path_full;
1847 else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
1849 dest_path_full = g_build_filename(dest_path, fd->name, NULL);
1850 dest_path = dest_path_full;
1853 file_data_update_ci_dest(fd, dest_path);
1855 work = fd->sidecar_files;
1858 FileData *sfd = work->data;
1860 file_data_update_ci_dest_preserve_ext(sfd, dest_path);
1864 g_free(dest_path_full);
1867 static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
1869 if (!file_data_sc_check_ci(fd, type)) return FALSE;
1870 file_data_sc_update_ci(fd, dest_path);
1874 gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
1876 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
1879 gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
1881 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
1884 gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
1886 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
1889 gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
1891 return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
1894 static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
1896 gboolean (*func)(FileData *, const gchar *))
1899 gboolean ret = TRUE;
1904 FileData *fd = work->data;
1906 if (!func(fd, dest)) ret = FALSE;
1913 gboolean file_data_sc_update_ci_move_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_move);
1918 gboolean file_data_sc_update_ci_copy_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_copy);
1923 gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
1925 return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
1930 * verify source and dest paths - dest image exists, etc.
1931 * it should detect all possible problems with the planned operation
1934 gint file_data_verify_ci(FileData *fd)
1936 gint ret = CHANGE_OK;
1941 DEBUG_1("Change checked: no change info: %s", fd->path);
1945 if (!isname(fd->path))
1947 /* this probably should not happen */
1948 ret |= CHANGE_NO_SRC;
1949 DEBUG_1("Change checked: file does not exist: %s", fd->path);
1953 dir = remove_level_from_path(fd->path);
1955 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1956 fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
1957 fd->change->type != FILEDATA_CHANGE_RENAME &&
1958 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1961 ret |= CHANGE_WARN_UNSAVED_META;
1962 DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
1965 if (fd->change->type != FILEDATA_CHANGE_DELETE &&
1966 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1967 !access_file(fd->path, R_OK))
1969 ret |= CHANGE_NO_READ_PERM;
1970 DEBUG_1("Change checked: no read permission: %s", fd->path);
1972 else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
1973 !access_file(dir, W_OK))
1975 ret |= CHANGE_NO_WRITE_PERM_DIR;
1976 DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
1978 else if (fd->change->type != FILEDATA_CHANGE_COPY &&
1979 fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
1980 fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
1981 !access_file(fd->path, W_OK))
1983 ret |= CHANGE_WARN_NO_WRITE_PERM;
1984 DEBUG_1("Change checked: no write permission: %s", fd->path);
1986 /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
1987 - that means that there are no hard errors and warnings can be disabled
1988 - the destination is determined during the check
1990 else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
1992 /* determine destination file */
1993 gboolean have_dest = FALSE;
1994 gchar *dest_dir = NULL;
1996 if (options->metadata.save_in_image_file)
1998 if (file_data_can_write_directly(fd))
2000 /* we can write the file directly */
2001 if (access_file(fd->path, W_OK))
2007 if (options->metadata.warn_on_write_problems)
2009 ret |= CHANGE_WARN_NO_WRITE_PERM;
2010 DEBUG_1("Change checked: file is not writable: %s", fd->path);
2014 else if (file_data_can_write_sidecar(fd))
2016 /* we can write sidecar */
2017 gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
2018 if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
2020 file_data_update_ci_dest(fd, sidecar);
2025 if (options->metadata.warn_on_write_problems)
2027 ret |= CHANGE_WARN_NO_WRITE_PERM;
2028 DEBUG_1("Change checked: file is not writable: %s", sidecar);
2037 /* write private metadata file under ~/.geeqie */
2039 /* If an existing metadata file exists, we will try writing to
2040 * it's location regardless of the user's preference.
2042 gchar *metadata_path = NULL;
2044 /* but ignore XMP if we are not able to write it */
2045 metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
2047 if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
2049 if (metadata_path && !access_file(metadata_path, W_OK))
2051 g_free(metadata_path);
2052 metadata_path = NULL;
2059 dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
2060 if (recursive_mkdir_if_not_exists(dest_dir, mode))
2062 gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
2064 metadata_path = g_build_filename(dest_dir, filename, NULL);
2068 if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
2070 file_data_update_ci_dest(fd, metadata_path);
2075 ret |= CHANGE_NO_WRITE_PERM_DEST;
2076 DEBUG_1("Change checked: file is not writable: %s", metadata_path);
2078 g_free(metadata_path);
2083 if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
2088 same = (strcmp(fd->path, fd->change->dest) == 0);
2092 const gchar *dest_ext = extension_from_path(fd->change->dest);
2093 if (!dest_ext) dest_ext = "";
2095 if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
2097 ret |= CHANGE_WARN_CHANGED_EXT;
2098 DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
2103 if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
2105 ret |= CHANGE_WARN_SAME;
2106 DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
2110 dest_dir = remove_level_from_path(fd->change->dest);
2112 if (!isdir(dest_dir))
2114 ret |= CHANGE_NO_DEST_DIR;
2115 DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
2117 else if (!access_file(dest_dir, W_OK))
2119 ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
2120 DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
2124 if (isfile(fd->change->dest))
2126 if (!access_file(fd->change->dest, W_OK))
2128 ret |= CHANGE_NO_WRITE_PERM_DEST;
2129 DEBUG_1("Change checked: destination file exists and is readonly: %s -> %s", fd->path, fd->change->dest);
2133 ret |= CHANGE_WARN_DEST_EXISTS;
2134 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2137 else if (isdir(fd->change->dest))
2139 ret |= CHANGE_DEST_EXISTS;
2140 DEBUG_1("Change checked: destination exists: %s -> %s", fd->path, fd->change->dest);
2147 fd->change->error = ret;
2148 if (ret == 0) DEBUG_1("Change checked: OK: %s", fd->path);
2155 gint file_data_sc_verify_ci(FileData *fd)
2160 ret = file_data_verify_ci(fd);
2162 work = fd->sidecar_files;
2165 FileData *sfd = work->data;
2167 ret |= file_data_verify_ci(sfd);
2174 gchar *file_data_get_error_string(gint error)
2176 GString *result = g_string_new("");
2178 if (error & CHANGE_NO_SRC)
2180 if (result->len > 0) g_string_append(result, ", ");
2181 g_string_append(result, _("file or directory does not exist"));
2184 if (error & CHANGE_DEST_EXISTS)
2186 if (result->len > 0) g_string_append(result, ", ");
2187 g_string_append(result, _("destination already exists"));
2190 if (error & CHANGE_NO_WRITE_PERM_DEST)
2192 if (result->len > 0) g_string_append(result, ", ");
2193 g_string_append(result, _("destination can't be overwritten"));
2196 if (error & CHANGE_WARN_NO_WRITE_PERM_DEST_DIR)
2198 if (result->len > 0) g_string_append(result, ", ");
2199 g_string_append(result, _("destination directory is not writable"));
2202 if (error & CHANGE_NO_DEST_DIR)
2204 if (result->len > 0) g_string_append(result, ", ");
2205 g_string_append(result, _("destination directory does not exist"));
2208 if (error & CHANGE_NO_WRITE_PERM_DIR)
2210 if (result->len > 0) g_string_append(result, ", ");
2211 g_string_append(result, _("source directory is not writable"));
2214 if (error & CHANGE_NO_READ_PERM)
2216 if (result->len > 0) g_string_append(result, ", ");
2217 g_string_append(result, _("no read permission"));
2220 if (error & CHANGE_WARN_NO_WRITE_PERM)
2222 if (result->len > 0) g_string_append(result, ", ");
2223 g_string_append(result, _("file is readonly"));
2226 if (error & CHANGE_WARN_DEST_EXISTS)
2228 if (result->len > 0) g_string_append(result, ", ");
2229 g_string_append(result, _("destination already exists and will be overwritten"));
2232 if (error & CHANGE_WARN_SAME)
2234 if (result->len > 0) g_string_append(result, ", ");
2235 g_string_append(result, _("source and destination are the same"));
2238 if (error & CHANGE_WARN_CHANGED_EXT)
2240 if (result->len > 0) g_string_append(result, ", ");
2241 g_string_append(result, _("source and destination have different extension"));
2244 if (error & CHANGE_WARN_UNSAVED_META)
2246 if (result->len > 0) g_string_append(result, ", ");
2247 g_string_append(result, _("there are unsaved metadata changes for the file"));
2250 return g_string_free(result, FALSE);
2253 gint file_data_verify_ci_list(GList *list, gchar **desc, gboolean with_sidecars)
2256 gint all_errors = 0;
2257 gint common_errors = ~0;
2262 if (!list) return 0;
2264 num = g_list_length(list);
2265 errors = g_new(int, num);
2276 error = with_sidecars ? file_data_sc_verify_ci(fd) : file_data_verify_ci(fd);
2277 all_errors |= error;
2278 common_errors &= error;
2285 if (desc && all_errors)
2288 GString *result = g_string_new("");
2292 gchar *str = file_data_get_error_string(common_errors);
2293 g_string_append(result, str);
2294 g_string_append(result, "\n");
2308 error = errors[i] & ~common_errors;
2312 gchar *str = file_data_get_error_string(error);
2313 g_string_append_printf(result, "%s: %s\n", fd->name, str);
2318 *desc = g_string_free(result, FALSE);
2327 * perform the change described by FileFataChangeInfo
2328 * it is used for internal operations,
2329 * this function actually operates with files on the filesystem
2330 * it should implement safe delete
2333 static gboolean file_data_perform_move(FileData *fd)
2335 g_assert(!strcmp(fd->change->source, fd->path));
2336 return move_file(fd->change->source, fd->change->dest);
2339 static gboolean file_data_perform_copy(FileData *fd)
2341 g_assert(!strcmp(fd->change->source, fd->path));
2342 return copy_file(fd->change->source, fd->change->dest);
2345 static gboolean file_data_perform_delete(FileData *fd)
2347 if (isdir(fd->path) && !islink(fd->path))
2348 return rmdir_utf8(fd->path);
2350 if (options->file_ops.safe_delete_enable)
2351 return file_util_safe_unlink(fd->path);
2353 return unlink_file(fd->path);
2356 gboolean file_data_perform_ci(FileData *fd)
2358 FileDataChangeType type = fd->change->type;
2362 case FILEDATA_CHANGE_MOVE:
2363 return file_data_perform_move(fd);
2364 case FILEDATA_CHANGE_COPY:
2365 return file_data_perform_copy(fd);
2366 case FILEDATA_CHANGE_RENAME:
2367 return file_data_perform_move(fd); /* the same as move */
2368 case FILEDATA_CHANGE_DELETE:
2369 return file_data_perform_delete(fd);
2370 case FILEDATA_CHANGE_WRITE_METADATA:
2371 return metadata_write_perform(fd);
2372 case FILEDATA_CHANGE_UNSPECIFIED:
2373 /* nothing to do here */
2381 gboolean file_data_sc_perform_ci(FileData *fd)
2384 gboolean ret = TRUE;
2385 FileDataChangeType type = fd->change->type;
2387 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2389 work = fd->sidecar_files;
2392 FileData *sfd = work->data;
2394 if (!file_data_perform_ci(sfd)) ret = FALSE;
2398 if (!file_data_perform_ci(fd)) ret = FALSE;
2404 * updates FileData structure according to FileDataChangeInfo
2407 gboolean file_data_apply_ci(FileData *fd)
2409 FileDataChangeType type = fd->change->type;
2412 if (type == FILEDATA_CHANGE_MOVE || type == FILEDATA_CHANGE_RENAME)
2414 DEBUG_1("planned change: applying %s -> %s", fd->change->dest, fd->path);
2415 file_data_planned_change_remove(fd);
2417 if (g_hash_table_lookup(file_data_pool, fd->change->dest))
2419 /* this change overwrites another file which is already known to other modules
2420 renaming fd would create duplicate FileData structure
2421 the best thing we can do is nothing
2422 FIXME: maybe we could copy stuff like marks
2424 DEBUG_1("can't rename fd, target exists %s -> %s", fd->change->dest, fd->path);
2428 file_data_set_path(fd, fd->change->dest);
2431 file_data_increment_version(fd);
2432 file_data_send_notification(fd, NOTIFY_CHANGE);
2437 gboolean file_data_sc_apply_ci(FileData *fd)
2440 FileDataChangeType type = fd->change->type;
2442 if (!file_data_sc_check_ci(fd, type)) return FALSE;
2444 work = fd->sidecar_files;
2447 FileData *sfd = work->data;
2449 file_data_apply_ci(sfd);
2453 file_data_apply_ci(fd);
2458 static gboolean file_data_list_contains_whole_group(GList *list, FileData *fd)
2461 if (fd->parent) fd = fd->parent;
2462 if (!g_list_find(list, fd)) return FALSE;
2464 work = fd->sidecar_files;
2467 if (!g_list_find(list, work->data)) return FALSE;
2474 static gboolean file_data_list_dump(GList *list)
2476 GList *work, *work2;
2481 FileData *fd = work->data;
2482 printf("%s\n", fd->name);
2483 work2 = fd->sidecar_files;
2486 FileData *fd = work2->data;
2487 printf(" %s\n", fd->name);
2488 work2 = work2->next;
2496 GList *file_data_process_groups_in_selection(GList *list, gboolean ungroup, GList **ungrouped_list)
2501 /* change partial groups to independent files */
2506 FileData *fd = work->data;
2509 if (!file_data_list_contains_whole_group(list, fd))
2511 file_data_disable_grouping(fd, TRUE);
2514 *ungrouped_list = g_list_prepend(*ungrouped_list, file_data_ref(fd));
2520 /* remove sidecars from the list,
2521 they can be still acessed via main_fd->sidecar_files */
2525 FileData *fd = work->data;
2529 (!ungroup && !file_data_list_contains_whole_group(list, fd)))
2531 out = g_list_prepend(out, file_data_ref(fd));
2535 filelist_free(list);
2536 out = g_list_reverse(out);
2546 * notify other modules about the change described by FileDataChangeInfo
2549 /* might use file_maint_ functions for now, later it should be changed to a system of callbacks
2550 FIXME do we need the ignore_list? It looks like a workaround for ineffective
2551 implementation in view_file_list.c */
2554 typedef struct _NotifyIdleData NotifyIdleData;
2556 struct _NotifyIdleData {
2562 typedef struct _NotifyData NotifyData;
2564 struct _NotifyData {
2565 FileDataNotifyFunc func;
2567 NotifyPriority priority;
2570 static GList *notify_func_list = NULL;
2572 static gint file_data_notify_sort(gconstpointer a, gconstpointer b)
2574 NotifyData *nda = (NotifyData *)a;
2575 NotifyData *ndb = (NotifyData *)b;
2577 if (nda->priority < ndb->priority) return -1;
2578 if (nda->priority > ndb->priority) return 1;
2582 gboolean file_data_register_notify_func(FileDataNotifyFunc func, gpointer data, NotifyPriority priority)
2585 GList *work = notify_func_list;
2589 NotifyData *nd = (NotifyData *)work->data;
2591 if (nd->func == func && nd->data == data)
2593 g_warning("Notify func already registered");
2599 nd = g_new(NotifyData, 1);
2602 nd->priority = priority;
2604 notify_func_list = g_list_insert_sorted(notify_func_list, nd, file_data_notify_sort);
2605 DEBUG_2("Notify func registered: %p", nd);
2610 gboolean file_data_unregister_notify_func(FileDataNotifyFunc func, gpointer data)
2612 GList *work = notify_func_list;
2616 NotifyData *nd = (NotifyData *)work->data;
2618 if (nd->func == func && nd->data == data)
2620 notify_func_list = g_list_delete_link(notify_func_list, work);
2622 DEBUG_2("Notify func unregistered: %p", nd);
2628 g_warning("Notify func not found");
2633 gboolean file_data_send_notification_idle_cb(gpointer data)
2635 NotifyIdleData *nid = (NotifyIdleData *)data;
2636 GList *work = notify_func_list;
2640 NotifyData *nd = (NotifyData *)work->data;
2642 nd->func(nid->fd, nid->type, nd->data);
2645 file_data_unref(nid->fd);
2650 void file_data_send_notification(FileData *fd, NotifyType type)
2652 NotifyIdleData *nid = g_new0(NotifyIdleData, 1);
2653 nid->fd = file_data_ref(fd);
2655 g_idle_add_full(G_PRIORITY_HIGH, file_data_send_notification_idle_cb, nid, NULL);
2658 static GHashTable *file_data_monitor_pool = NULL;
2659 static guint realtime_monitor_id = 0; /* event source id */
2661 static void realtime_monitor_check_cb(gpointer key, gpointer value, gpointer data)
2665 file_data_check_changed_files(fd);
2667 DEBUG_1("monitor %s", fd->path);
2670 static gboolean realtime_monitor_cb(gpointer data)
2672 if (!options->update_on_time_change) return TRUE;
2673 g_hash_table_foreach(file_data_monitor_pool, realtime_monitor_check_cb, NULL);
2677 gboolean file_data_register_real_time_monitor(FileData *fd)
2683 if (!file_data_monitor_pool)
2684 file_data_monitor_pool = g_hash_table_new(g_direct_hash, g_direct_equal);
2686 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2688 DEBUG_1("Register realtime %d %s", count, fd->path);
2691 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2693 if (!realtime_monitor_id)
2695 realtime_monitor_id = g_timeout_add(5000, realtime_monitor_cb, NULL);
2701 gboolean file_data_unregister_real_time_monitor(FileData *fd)
2705 g_assert(file_data_monitor_pool);
2707 count = GPOINTER_TO_INT(g_hash_table_lookup(file_data_monitor_pool, fd));
2709 DEBUG_1("Unregister realtime %d %s", count, fd->path);
2711 g_assert(count > 0);
2716 g_hash_table_remove(file_data_monitor_pool, fd);
2718 g_hash_table_insert(file_data_monitor_pool, fd, GINT_TO_POINTER(count));
2720 file_data_unref(fd);
2722 if (g_hash_table_size(file_data_monitor_pool) == 0)
2724 g_source_remove(realtime_monitor_id);
2725 realtime_monitor_id = 0;
2731 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */