From 84e58f28df6057f8dd441a48e32807375fc60c8b Mon Sep 17 00:00:00 2001 From: Sophie Herold Date: Wed, 15 Jan 2025 21:45:13 +0100 Subject: [PATCH] edit_window: Add "Replace Original" option --- src/widgets/edit_window.rs | 160 +++++++++++++++++++++++++--------- src/widgets/edit_window.ui | 5 +- src/widgets/image/metadata.rs | 34 ++++++++ 3 files changed, 156 insertions(+), 43 deletions(-) diff --git a/src/widgets/edit_window.rs b/src/widgets/edit_window.rs index 0238e75..7a0b7af 100644 --- a/src/widgets/edit_window.rs +++ b/src/widgets/edit_window.rs @@ -40,6 +40,8 @@ mod imp { toolbar_view: TemplateChild, #[template_child] cancel: TemplateChild, + #[template_child] + done: TemplateChild, #[property(get, construct_only)] original_image: OnceCell, @@ -60,7 +62,15 @@ mod imp { glib::spawn_future_local(glib::clone!( #[weak] obj, - async move { obj.imp().save_image().await } + async move { obj.imp().save_copy().await } + )); + }); + + klass.install_action("edit.save-overwrite", None, |obj, _, _| { + glib::spawn_future_local(glib::clone!( + #[weak] + obj, + async move { obj.imp().save_overwrite().await } )); }); } @@ -87,6 +97,15 @@ mod imp { obj.window().show_image(); } )); + + glib::spawn_future_local(glib::clone!( + #[weak] + obj, + async move { + let can_trash = obj.original_image().can_trash().await; + obj.action_set_enabled("edit.save-overwrite", can_trash); + } + )); } } @@ -94,8 +113,9 @@ mod imp { impl BinImpl for LpEditWindow {} impl LpEditWindow { - async fn save_image(&self) { + async fn save_copy(&self) { let obj = self.obj(); + self.done.popdown(); if let Some(current_file) = obj.original_image().file() { let file_dialog = gtk::FileDialog::new(); @@ -107,55 +127,113 @@ mod imp { log::error!("{}", err); } Ok(new_file) => { - let editor = glycin::Editor::new(current_file); - if let Some(operations) = obj.operations() { - log::debug!("Computing edited image."); - let result = editor.apply_complete(&operations).await; - match result { - Err(err) => { - log::warn!("Failed to edit image: {err}"); - obj.window().show_error( - &gettext("Failed to edit image."), + self.save(current_file, new_file).await; + } + } + } + } + + async fn save_overwrite(&self) { + let obj = self.obj(); + self.done.popdown(); + + if let Some(current_file) = obj.original_image().file() { + if let Some(current_path) = current_file.path() { + if let Some(mut file_stem) = current_path.file_stem().map(|x| x.to_os_string()) + { + let mut tmp_path = current_path.clone(); + file_stem.push(".tmp"); + if let Some(ext) = current_path.extension() { + file_stem.push("."); + file_stem.push(ext); + } + tmp_path.set_file_name(file_stem); + + let new_file = gio::File::for_path(&tmp_path); + let written = self.save(current_file.clone(), new_file.clone()).await; + + if written { + if let Err(err) = + current_file.trash_future(glib::Priority::DEFAULT).await + { + obj.window().show_error( + &gettext("Failed to save image."), + &format!("Failed to move image {current_path:?} to trash and therefore couldn't save image: {err}"), + ErrorType::General, + ); + } else if let Err(err) = new_file + .move_future( + ¤t_file, + gio::FileCopyFlags::NONE, + glib::Priority::DEFAULT, + ) + .0 + .await + { + obj.window().show_error( + &gettext("Failed to save image."), &format!( - "Failed to edit image:\n\n{err}\n\n{operations:#?}" + "Failed to move image image to {current_path:?}: {err} move {:?} to {:?}", new_file.path().unwrap(), current_file.path().unwrap() ), ErrorType::General, + ); + } + } + } + } + } + } + + async fn save(&self, current_file: gio::File, new_file: gio::File) -> bool { + let obj = self.obj(); + let editor = glycin::Editor::new(current_file); + if let Some(operations) = obj.operations() { + log::debug!("Computing edited image."); + let result = editor.apply_complete(&operations).await; + match result { + Err(err) => { + log::warn!("Failed to edit image: {err}"); + obj.window().show_error( + &gettext("Failed to edit image."), + &format!("Failed to edit image:\n\n{err}\n\n{operations:#?}"), + ErrorType::General, + ) + } + Ok(binary_data) => { + log::debug!("Saving edited image to '{}'", new_file.uri()); + match binary_data.get() { + Err(err) => { + obj.window().show_error( + &gettext("Failed to Save Image"), + &format!("Failed to get binary data: {err}"), + ErrorType::General, + ); + } + Ok(data) => { + if let Err(err) = new_file + .replace_contents_future( + data, + None, + true, + gio::FileCreateFlags::NONE, ) - } - Ok(binary_data) => { - log::debug!("Saving edited image to '{}'", new_file.uri()); - match binary_data.get() { - Err(err) => { - obj.window().show_error( - &gettext("Failed to Save Image"), - &format!("Failed to get binary data: {err}"), - ErrorType::General, - ); - } - Ok(data) => { - if let Err(err) = new_file - .replace_contents_future( - data, - None, - true, - gio::FileCreateFlags::NONE, - ) - .await - { - obj.window().show_error( - &gettext("Failed to Save Image"), - &format!("Failed to write file:\n\n{err:?}"), - ErrorType::General, - ); - } - } - } + .await + { + obj.window().show_error( + &gettext("Failed to Save Image"), + &format!("Failed to write file:\n\n{err:?}"), + ErrorType::General, + ); + } else { + return true; } } } } } } + + false } } } diff --git a/src/widgets/edit_window.ui b/src/widgets/edit_window.ui index e2e7714..5991766 100644 --- a/src/widgets/edit_window.ui +++ b/src/widgets/edit_window.ui @@ -16,7 +16,7 @@ - + _Done true @@ -57,12 +57,13 @@ + edit.save-overwrite vertical - _Replace Original… + _Replace Original true 0 save_overwrite diff --git a/src/widgets/image/metadata.rs b/src/widgets/image/metadata.rs index 1e60819..f1dcc3f 100644 --- a/src/widgets/image/metadata.rs +++ b/src/widgets/image/metadata.rs @@ -115,4 +115,38 @@ impl LpImage { self.imp().untransformed_dimensions() } } + + pub async fn can_trash(&self) -> bool { + let Some(file) = self.file() else { + return false; + }; + + if file.path().is_none() { + // The trash portal only supports fds anyway + return false; + } + + let file_info = file + .query_info_future( + gio::FILE_ATTRIBUTE_ACCESS_CAN_TRASH, + gio::FileQueryInfoFlags::NONE, + glib::Priority::DEFAULT, + ) + .await; + + match file_info { + Err(err) => { + log::error!( + "Failed to obtain CAN_TRASH attribute for '{}': {err}", + file.uri() + ); + // Assume we can't trash if we don't have any infos + false + } + Ok(file_info) => { + file_info.has_attribute(gio::FILE_ATTRIBUTE_ACCESS_CAN_TRASH) + && file_info.boolean(gio::FILE_ATTRIBUTE_ACCESS_CAN_TRASH) + } + } + } }