+
+ ofd = g_hash_table_lookup(file_data_planned_change_hash, new_path);
+ if (ofd != fd)
+ {
+ if (ofd)
+ {
+ DEBUG_1("planned change: replacing %s -> %s", new_path, ofd->path);
+ g_hash_table_remove(file_data_planned_change_hash, new_path);
+ file_data_unref(ofd);
+ }
+
+ DEBUG_1("planned change: inserting %s -> %s", new_path, fd->path);
+ file_data_ref(fd);
+ g_hash_table_insert(file_data_planned_change_hash, new_path, fd);
+ }
+ }
+}
+
+static void file_data_update_ci_dest(FileData *fd, const gchar *dest_path)
+{
+ gchar *old_path = fd->change->dest;
+
+ fd->change->dest = g_strdup(dest_path);
+ file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
+ g_free(old_path);
+}
+
+static void file_data_update_ci_dest_preserve_ext(FileData *fd, const gchar *dest_path)
+{
+ const gchar *extension = extension_from_path(fd->change->source);
+ gchar *base = remove_extension_from_path(dest_path);
+ gchar *old_path = fd->change->dest;
+
+ fd->change->dest = g_strconcat(base, extension, NULL);
+ file_data_update_planned_change_hash(fd, old_path, fd->change->dest);
+
+ g_free(old_path);
+ g_free(base);
+}
+
+static void file_data_sc_update_ci(FileData *fd, const gchar *dest_path)
+{
+ GList *work;
+ gchar *dest_path_full = NULL;
+
+ if (fd->parent) fd = fd->parent;
+
+ if (!dest_path)
+ {
+ dest_path = fd->path;
+ }
+ else if (!strchr(dest_path, G_DIR_SEPARATOR)) /* we got only filename, not a full path */
+ {
+ gchar *dir = remove_level_from_path(fd->path);
+
+ dest_path_full = g_build_filename(dir, dest_path, NULL);
+ g_free(dir);
+ dest_path = dest_path_full;
+ }
+ else if (fd->change->type != FILEDATA_CHANGE_RENAME && isdir(dest_path)) /* rename should not move files between directories */
+ {
+ dest_path_full = g_build_filename(dest_path, fd->name, NULL);
+ dest_path = dest_path_full;
+ }
+
+ file_data_update_ci_dest(fd, dest_path);
+
+ work = fd->sidecar_files;
+ while (work)
+ {
+ FileData *sfd = work->data;
+
+ file_data_update_ci_dest_preserve_ext(sfd, dest_path);
+ work = work->next;
+ }
+
+ g_free(dest_path_full);
+}
+
+static gboolean file_data_sc_check_update_ci(FileData *fd, const gchar *dest_path, FileDataChangeType type)
+{
+ if (!file_data_sc_check_ci(fd, type)) return FALSE;
+ file_data_sc_update_ci(fd, dest_path);
+ return TRUE;
+}
+
+gboolean file_data_sc_update_ci_copy(FileData *fd, const gchar *dest_path)
+{
+ return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_COPY);
+}
+
+gboolean file_data_sc_update_ci_move(FileData *fd, const gchar *dest_path)
+{
+ return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_MOVE);
+}
+
+gboolean file_data_sc_update_ci_rename(FileData *fd, const gchar *dest_path)
+{
+ return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_RENAME);
+}
+
+gboolean file_data_sc_update_ci_unspecified(FileData *fd, const gchar *dest_path)
+{
+ return file_data_sc_check_update_ci(fd, dest_path, FILEDATA_CHANGE_UNSPECIFIED);
+}
+
+static gboolean file_data_sc_update_ci_list_call_func(GList *fd_list,
+ const gchar *dest,
+ gboolean (*func)(FileData *, const gchar *))
+{
+ GList *work;
+ gboolean ret = TRUE;
+
+ work = fd_list;
+ while (work)
+ {
+ FileData *fd = work->data;
+
+ if (!func(fd, dest)) ret = FALSE;
+ work = work->next;
+ }
+
+ return ret;
+}
+
+gboolean file_data_sc_update_ci_move_list(GList *fd_list, const gchar *dest)
+{
+ return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_move);
+}
+
+gboolean file_data_sc_update_ci_copy_list(GList *fd_list, const gchar *dest)
+{
+ return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_copy);
+}
+
+gboolean file_data_sc_update_ci_unspecified_list(GList *fd_list, const gchar *dest)
+{
+ return file_data_sc_update_ci_list_call_func(fd_list, dest, file_data_sc_update_ci_unspecified);
+}
+
+
+/*
+ * verify source and dest paths - dest image exists, etc.
+ * it should detect all possible problems with the planned operation
+ */
+
+gint file_data_verify_ci(FileData *fd)
+{
+ gint ret = CHANGE_OK;
+ gchar *dir;
+
+ if (!fd->change)
+ {
+ DEBUG_1("Change checked: no change info: %s", fd->path);
+ return ret;
+ }
+
+ if (!isname(fd->path))
+ {
+ /* this probably should not happen */
+ ret |= CHANGE_NO_SRC;
+ DEBUG_1("Change checked: file does not exist: %s", fd->path);
+ return ret;
+ }
+
+ dir = remove_level_from_path(fd->path);
+
+ if (fd->change->type != FILEDATA_CHANGE_DELETE &&
+ fd->change->type != FILEDATA_CHANGE_MOVE && /* the unsaved metadata should survive move and rename operations */
+ fd->change->type != FILEDATA_CHANGE_RENAME &&
+ fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
+ fd->modified_xmp)
+ {
+ ret |= CHANGE_WARN_UNSAVED_META;
+ DEBUG_1("Change checked: unsaved metadata: %s", fd->path);
+ }
+
+ if (fd->change->type != FILEDATA_CHANGE_DELETE &&
+ fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
+ !access_file(fd->path, R_OK))
+ {
+ ret |= CHANGE_NO_READ_PERM;
+ DEBUG_1("Change checked: no read permission: %s", fd->path);
+ }
+ else if ((fd->change->type == FILEDATA_CHANGE_DELETE || fd->change->type == FILEDATA_CHANGE_MOVE) &&
+ !access_file(dir, W_OK))
+ {
+ ret |= CHANGE_NO_WRITE_PERM_DIR;
+ DEBUG_1("Change checked: source dir is readonly: %s", fd->path);
+ }
+ else if (fd->change->type != FILEDATA_CHANGE_COPY &&
+ fd->change->type != FILEDATA_CHANGE_UNSPECIFIED &&
+ fd->change->type != FILEDATA_CHANGE_WRITE_METADATA &&
+ !access_file(fd->path, W_OK))
+ {
+ ret |= CHANGE_WARN_NO_WRITE_PERM;
+ DEBUG_1("Change checked: no write permission: %s", fd->path);
+ }
+ /* WRITE_METADATA is special because it can be configured to silently write to ~/.geeqie/...
+ - that means that there are no hard errors and warnings can be disabled
+ - the destination is determined during the check
+ */
+ else if (fd->change->type == FILEDATA_CHANGE_WRITE_METADATA)
+ {
+ /* determine destination file */
+ gboolean have_dest = FALSE;
+ gchar *dest_dir = NULL;
+
+ if (options->metadata.save_in_image_file)
+ {
+ if (file_data_can_write_directly(fd))
+ {
+ /* we can write the file directly */
+ if (access_file(fd->path, W_OK))
+ {
+ have_dest = TRUE;
+ }
+ else
+ {
+ if (options->metadata.warn_on_write_problems)
+ {
+ ret |= CHANGE_WARN_NO_WRITE_PERM;
+ DEBUG_1("Change checked: file is not writable: %s", fd->path);
+ }
+ }
+ }
+ else if (file_data_can_write_sidecar(fd))
+ {
+ /* we can write sidecar */
+ gchar *sidecar = file_data_get_sidecar_path(fd, FALSE);
+ if (access_file(sidecar, W_OK) || (!isname(sidecar) && access_file(dir, W_OK)))
+ {
+ file_data_update_ci_dest(fd, sidecar);
+ have_dest = TRUE;
+ }
+ else
+ {
+ if (options->metadata.warn_on_write_problems)
+ {
+ ret |= CHANGE_WARN_NO_WRITE_PERM;
+ DEBUG_1("Change checked: file is not writable: %s", sidecar);
+ }
+ }
+ g_free(sidecar);
+ }
+ }
+
+ if (!have_dest)
+ {
+ /* write private metadata file under ~/.geeqie */
+
+ /* If an existing metadata file exists, we will try writing to
+ * it's location regardless of the user's preference.
+ */
+ gchar *metadata_path = NULL;
+#ifdef HAVE_EXIV2
+ /* but ignore XMP if we are not able to write it */
+ metadata_path = cache_find_location(CACHE_TYPE_XMP_METADATA, fd->path);
+#endif
+ if (!metadata_path) metadata_path = cache_find_location(CACHE_TYPE_METADATA, fd->path);
+
+ if (metadata_path && !access_file(metadata_path, W_OK))
+ {
+ g_free(metadata_path);
+ metadata_path = NULL;
+ }
+
+ if (!metadata_path)
+ {
+ mode_t mode = 0755;
+
+ dest_dir = cache_get_location(CACHE_TYPE_METADATA, fd->path, FALSE, &mode);
+ if (recursive_mkdir_if_not_exists(dest_dir, mode))
+ {
+ gchar *filename = g_strconcat(fd->name, options->metadata.save_legacy_format ? GQ_CACHE_EXT_METADATA : GQ_CACHE_EXT_XMP_METADATA, NULL);
+
+ metadata_path = g_build_filename(dest_dir, filename, NULL);
+ g_free(filename);
+ }
+ }
+ if (access_file(metadata_path, W_OK) || (!isname(metadata_path) && access_file(dest_dir, W_OK)))
+ {
+ file_data_update_ci_dest(fd, metadata_path);
+ have_dest = TRUE;
+ }
+ else
+ {
+ ret |= CHANGE_NO_WRITE_PERM_DEST;
+ DEBUG_1("Change checked: file is not writable: %s", metadata_path);
+ }
+ g_free(metadata_path);
+ }
+ g_free(dest_dir);
+ }
+
+ if (fd->change->dest && fd->change->type != FILEDATA_CHANGE_WRITE_METADATA)
+ {
+ gboolean same;
+ gchar *dest_dir;
+
+ same = (strcmp(fd->path, fd->change->dest) == 0);
+
+ if (!same)
+ {
+ const gchar *dest_ext = extension_from_path(fd->change->dest);
+ if (!dest_ext) dest_ext = "";
+
+ if (g_ascii_strcasecmp(fd->extension, dest_ext) != 0)
+ {
+ ret |= CHANGE_WARN_CHANGED_EXT;
+ DEBUG_1("Change checked: source and destination have different extensions: %s -> %s", fd->path, fd->change->dest);
+ }
+ }
+ else
+ {
+ if (fd->change->type != FILEDATA_CHANGE_UNSPECIFIED) /* FIXME this is now needed for running editors */
+ {
+ ret |= CHANGE_WARN_SAME;
+ DEBUG_1("Change checked: source and destination are the same: %s -> %s", fd->path, fd->change->dest);
+ }
+ }
+
+ dest_dir = remove_level_from_path(fd->change->dest);
+
+ if (!isdir(dest_dir))
+ {
+ ret |= CHANGE_NO_DEST_DIR;
+ DEBUG_1("Change checked: destination dir does not exist: %s -> %s", fd->path, fd->change->dest);
+ }
+ else if (!access_file(dest_dir, W_OK))
+ {
+ ret |= CHANGE_WARN_NO_WRITE_PERM_DEST_DIR;
+ DEBUG_1("Change checked: destination dir is readonly: %s -> %s", fd->path, fd->change->dest);
+ }
+ else if (!same)