From 65b919015359f9dfef470763ebd6193fda537c3e Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Mon, 3 Jul 2023 12:30:38 +0200 Subject: [PATCH 01/61] merge commit --- .gitignore | 4 +- rnote-ui/data/app.gschema.xml.in | 8 + rnote-ui/data/ui/settingspanel.ui | 87 +++++---- rnote-ui/src/appwindow/imp.rs | 67 +++++++ rnote-ui/src/appwindow/mod.rs | 14 ++ rnote-ui/src/canvas/imexport.rs | 13 +- rnote-ui/src/canvas/mod.rs | 123 ++++++++++-- rnote-ui/src/mainheader.rs | 27 ++- rnote-ui/src/settingspanel/mod.rs | 308 ++++++++++++++---------------- 9 files changed, 434 insertions(+), 217 deletions(-) diff --git a/.gitignore b/.gitignore index e227fb0425..e3779ab0e7 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ *.code-workspace .idea/ .toggletasks.json -*.flatpak \ No newline at end of file +*.flatpak +justfile +rust-toolchain \ No newline at end of file diff --git a/rnote-ui/data/app.gschema.xml.in b/rnote-ui/data/app.gschema.xml.in index c88615da17..ba39c529a0 100644 --- a/rnote-ui/data/app.gschema.xml.in +++ b/rnote-ui/data/app.gschema.xml.in @@ -125,6 +125,14 @@ 120 the sec interval for the autosave + + true + true when document recovery is enabled + + + 80 + the sec interval for the recovery saves + false Whether the canvas scrollbars are shown diff --git a/rnote-ui/data/ui/settingspanel.ui b/rnote-ui/data/ui/settingspanel.ui index 7586bcdfdf..bdf6141510 100644 --- a/rnote-ui/data/ui/settingspanel.ui +++ b/rnote-ui/data/ui/settingspanel.ui @@ -72,24 +72,57 @@ - - Show Scrollbars - Set whether the scrollbars on the canvas are shown + + Recovery + Enable or disable document recovery + + + center + + + + + + + Recoevry Interval (secs) + Set the recovery save interval in seconds + + + 1 + 9999 + 5 + 120 + + + general_recovery_interval_secs_adj + horizontal + false + center + 0 + + + + + + + Format Border Color + Set the format border color center + + + - - Inertial Touch Scrolling - Set whether touch scrolling on the canvas is inertial. -An application restart is required when this option -gets disabled. + + Show Scrollbars + Set whether the scrollbars on the canvas are shown - + center @@ -284,33 +317,12 @@ gets disabled. - + - Document + Document Background - - Format Border Color - Set the format border color - - - horizontal - 6 - false - false - center - - - - doc_format_border_color_dialog - - - - - - - - + Color Set the background color @@ -321,17 +333,14 @@ gets disabled. false center - - - doc_background_color_dialog - + - + Pattern Choose a background pattern @@ -530,4 +539,4 @@ on a drawing pad - \ No newline at end of file + diff --git a/rnote-ui/src/appwindow/imp.rs b/rnote-ui/src/appwindow/imp.rs index 475b6f1268..501da5673c 100644 --- a/rnote-ui/src/appwindow/imp.rs +++ b/rnote-ui/src/appwindow/imp.rs @@ -21,10 +21,13 @@ pub(crate) struct RnAppWindow { pub(crate) app_settings: gio::Settings, pub(crate) drawing_pad_controller: RefCell>, pub(crate) autosave_source_id: RefCell>, + pub(crate) recovery_source_id: RefCell>, pub(crate) periodic_configsave_source_id: RefCell>, pub(crate) autosave: Cell, pub(crate) autosave_interval_secs: Cell, + pub(crate) recovery: Cell, + pub(crate) recovery_interval_secs: Cell, pub(crate) righthanded: Cell, pub(crate) block_pinch_zoom: Cell, pub(crate) touch_drawing: Cell, @@ -66,10 +69,13 @@ impl Default for RnAppWindow { app_settings: gio::Settings::new(config::APP_ID), drawing_pad_controller: RefCell::new(None), autosave_source_id: RefCell::new(None), + recovery_source_id: RefCell::new(None), periodic_configsave_source_id: RefCell::new(None), autosave: Cell::new(true), autosave_interval_secs: Cell::new(super::RnAppWindow::AUTOSAVE_INTERVAL_DEFAULT), + recovery: Cell::new(true), + recovery_interval_secs: Cell::new(super::RnAppWindow::RECOVERY_INTERVAL_DEFAULT), righthanded: Cell::new(true), block_pinch_zoom: Cell::new(false), touch_drawing: Cell::new(false), @@ -152,6 +158,14 @@ impl ObjectImpl for RnAppWindow { .maximum(u32::MAX) .default_value(super::RnAppWindow::AUTOSAVE_INTERVAL_DEFAULT) .build(), + glib::ParamSpecBoolean::builder("recovery") + .default_value(true) + .build(), + glib::ParamSpecUInt::builder("recovery-interval-secs") + .minimum(5) + .maximum(u32::MAX) + .default_value(super::RnAppWindow::RECOVERY_INTERVAL_DEFAULT) + .build(), glib::ParamSpecBoolean::builder("righthanded") .default_value(false) .build(), @@ -173,6 +187,8 @@ impl ObjectImpl for RnAppWindow { match pspec.name() { "autosave" => self.autosave.get().to_value(), "autosave-interval-secs" => self.autosave_interval_secs.get().to_value(), + "recovery" => self.recovery.get().to_value(), + "recovery-interval-secs" => self.recovery_interval_secs.get().to_value(), "righthanded" => self.righthanded.get().to_value(), "block-pinch-zoom" => self.block_pinch_zoom.get().to_value(), "touch-drawing" => self.touch_drawing.get().to_value(), @@ -208,6 +224,31 @@ impl ObjectImpl for RnAppWindow { self.update_autosave_handler(); } } + "recovery" => { + let recovery = value + .get::() + .expect("The value needs to be of type `bool`"); + + self.recovery.replace(recovery); + + if recovery { + self.update_recovery_handler(); + } else if let Some(recovery_source_id) = self.recovery_source_id.borrow_mut().take() + { + recovery_source_id.remove(); + } + } + "recovery-interval-secs" => { + let recovery_interval_secs = value + .get::() + .expect("The value needs to be of type `u32`"); + + self.recovery_interval_secs.replace(recovery_interval_secs); + + if self.autosave.get() { + self.update_autosave_handler(); + } + } "righthanded" => { let righthanded = value .get::() @@ -284,7 +325,33 @@ impl RnAppWindow { } )); } + glib::source::Continue(true) + }))) { + removed_id.remove(); + } + } + fn update_recovery_handler(&self) { + let obj = self.obj(); + + if let Some(removed_id) = self.recovery_source_id.borrow_mut().replace(glib::source::timeout_add_seconds_local(self.recovery_interval_secs.get(), + clone!(@weak obj as appwindow => @default-return glib::source::Continue(false), move || { + let canvas = appwindow.active_tab().canvas(); + + glib::MainContext::default().spawn_local(clone!(@weak canvas, @weak appwindow => async move { + let tmp_file = canvas.get_or_generate_tmp_file(); + appwindow.overlays().start_pulsing_progressbar(); + canvas.set_recovery_in_progress(true); + canvas.imp().output_file_cache.replace(canvas.imp().output_file.take()); + if let Err(e) = canvas.save_document_to_file(&tmp_file).await { + log::error!("saving document failed, Error: `{e:?}`"); + appwindow.overlays().dispatch_toast_error(&gettext("Saving document failed")); + } + canvas.imp().output_file.replace(canvas.imp().output_file_cache.take()); + canvas.set_recovery_in_progress(false); + appwindow.overlays().finish_progressbar(); + } + )); glib::source::Continue(true) }))) { removed_id.remove(); diff --git a/rnote-ui/src/appwindow/mod.rs b/rnote-ui/src/appwindow/mod.rs index 699416239b..e0184ad998 100644 --- a/rnote-ui/src/appwindow/mod.rs +++ b/rnote-ui/src/appwindow/mod.rs @@ -27,6 +27,7 @@ glib::wrapper! { impl RnAppWindow { const AUTOSAVE_INTERVAL_DEFAULT: u32 = 30; + const RECOVERY_INTERVAL_DEFAULT: u32 = 20; const PERIODIC_CONFIGSAVE_INTERVAL: u32 = 10; const FLAP_FOLDED_RESIZE_MARGIN: u32 = 64; @@ -44,6 +45,16 @@ impl RnAppWindow { self.set_property("autosave", autosave.to_value()); } + #[allow(unused)] + pub(crate) fn recovery(&self) -> bool { + self.property::("recovery") + } + + #[allow(unused)] + pub(crate) fn set_recovery(&self, recovery: bool) { + self.set_property("recovery", recovery.to_value()); + } + #[allow(unused)] pub(crate) fn autosave_interval_secs(&self) -> u32 { self.property::("autosave-interval-secs") @@ -433,6 +444,9 @@ impl RnAppWindow { self.mainheader() .main_title_unsaved_indicator() .set_visible(canvas.unsaved_changes()); + self.mainheader() + .main_title_unsaved_recovery_indicator() + .set_visible(canvas.unsaved_changes_recovery()); if canvas.unsaved_changes() { self.mainheader() .main_title() diff --git a/rnote-ui/src/canvas/imexport.rs b/rnote-ui/src/canvas/imexport.rs index 8e35def88a..415d3a0d80 100644 --- a/rnote-ui/src/canvas/imexport.rs +++ b/rnote-ui/src/canvas/imexport.rs @@ -234,6 +234,7 @@ impl RnCanvas { /// Returns Ok(true) if saved successfully, Ok(false) when a save is already in progress and no file operatiosn were executed, /// Err(e) when saving failed in any way. pub(crate) async fn save_document_to_file(&self, file: &gio::File) -> anyhow::Result { + let recovery_in_progress = || self.recovery_in_progress(); // skip saving when it is already in progress if self.save_in_progress() { log::debug!("saving file already in progress"); @@ -281,6 +282,9 @@ impl RnCanvas { if let Err(e) = res { self.set_save_in_progress(false); + if recovery_in_progress() { + self.set_recovery_in_progress(false) + } // If the file operations failed in any way, we make sure to clear the expect_write flag // because we can't know for sure if the output_file monitor will be able to. @@ -288,8 +292,15 @@ impl RnCanvas { return Err(e); } - self.set_unsaved_changes(false); + if self.recovery_in_progress() { + self.set_unsaved_changes_recovery(false); + } else { + self.set_unsaved_changes(false); + } self.set_save_in_progress(false); + if recovery_in_progress() { + self.set_recovery_in_progress(false) + } Ok(true) } diff --git a/rnote-ui/src/canvas/mod.rs b/rnote-ui/src/canvas/mod.rs index e382eeaa1a..3b35874583 100644 --- a/rnote-ui/src/canvas/mod.rs +++ b/rnote-ui/src/canvas/mod.rs @@ -70,6 +70,9 @@ mod imp { pub(crate) engine: RefCell, pub(crate) engine_task_handler_handle: RefCell>>, + pub(crate) recovery_in_progress: Cell, + pub(crate) recovery_file: RefCell>, + pub(crate) output_file_cache: RefCell>, pub(crate) output_file: RefCell>, pub(crate) output_file_monitor: RefCell>, pub(crate) output_file_monitor_changed_handler: RefCell>, @@ -77,6 +80,7 @@ mod imp { pub(crate) output_file_expect_write: Cell, pub(crate) save_in_progress: Cell, pub(crate) unsaved_changes: Cell, + pub(crate) unsaved_changes_recovery: Cell, pub(crate) empty: Cell, pub(crate) touch_drawing: Cell, pub(crate) show_drawing_cursor: Cell, @@ -162,6 +166,9 @@ mod imp { engine: RefCell::new(engine), engine_task_handler_handle: RefCell::new(None), + recovery_in_progress: Cell::new(false), + recovery_file: RefCell::new(None), + output_file_cache: RefCell::new(None), output_file: RefCell::new(None), output_file_monitor: RefCell::new(None), output_file_monitor_changed_handler: RefCell::new(None), @@ -169,6 +176,7 @@ mod imp { output_file_expect_write: Cell::new(false), save_in_progress: Cell::new(false), unsaved_changes: Cell::new(false), + unsaved_changes_recovery: Cell::new(false), empty: Cell::new(true), touch_drawing: Cell::new(false), show_drawing_cursor: Cell::new(false), @@ -253,6 +261,9 @@ mod imp { glib::ParamSpecBoolean::builder("unsaved-changes") .default_value(false) .build(), + glib::ParamSpecBoolean::builder("unsaved-changes-recovery") + .default_value(false) + .build(), glib::ParamSpecBoolean::builder("empty") .default_value(true) .build(), @@ -282,6 +293,7 @@ mod imp { match pspec.name() { "output-file" => self.output_file.borrow().to_value(), "unsaved-changes" => self.unsaved_changes.get().to_value(), + "unsaved-changes-recovery" => self.unsaved_changes.get().to_value(), "empty" => self.empty.get().to_value(), "hadjustment" => self.hadjustment.borrow().to_value(), "vadjustment" => self.vadjustment.borrow().to_value(), @@ -310,6 +322,12 @@ mod imp { value.get().expect("The value needs to be of type `bool`"); self.unsaved_changes.replace(unsaved_changes); } + "unsaved-changes-recovery" => { + let unsaved_changes_recovery: bool = + value.get().expect("The value needs to be of type `bool`"); + self.unsaved_changes_recovery + .replace(unsaved_changes_recovery); + } "empty" => { let empty: bool = value.get().expect("The value needs to be of type `bool`"); self.empty.replace(empty); @@ -567,6 +585,49 @@ impl RnCanvas { self.set_property("drawing-cursor", drawing_cursor.to_value()); } + #[allow(unused)] + pub(crate) fn tmp_file(&self) -> Option { + self.imp().recovery_file.borrow().clone() + } + + #[allow(unused)] + pub(crate) fn set_tmp_file(&self, tmp_file: Option) { + self.imp().recovery_file.replace(tmp_file); + } + + #[allow(unused)] + pub(crate) fn output_file_cache(&self) -> Option { + self.imp().recovery_file.borrow().clone() + } + + #[allow(unused)] + pub(crate) fn set_output_file_cache(&self) -> Option { + self.imp().recovery_file.borrow().clone() + } + + pub(crate) fn get_or_generate_tmp_file(&self) -> gio::File { + // if !recovery { + // return None; + // } + if self.imp().recovery_file.borrow().is_none() { + let mut path = std::path::PathBuf::from("/tmp/rnote-recovery/"); + let time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + + if !path.exists() { + std::fs::create_dir(&path).expect("Failed to create tmp dir"); + } + let name = format!("draft-{time}.rnote"); + path.push(name); + self.imp() + .recovery_file + .replace(Some(gio::File::for_path(path))); + } + self.imp().recovery_file.borrow().as_ref().unwrap().clone() + } + #[allow(unused)] pub(crate) fn output_file(&self) -> Option { self.property::>("output-file") @@ -592,6 +653,16 @@ impl RnCanvas { self.imp().save_in_progress.set(save_in_progress); } + #[allow(unused)] + pub(crate) fn recovery_in_progress(&self) -> bool { + self.imp().recovery_in_progress.get() + } + + #[allow(unused)] + pub(crate) fn set_recovery_in_progress(&self, save_in_progress: bool) { + self.imp().recovery_in_progress.set(save_in_progress); + } + #[allow(unused)] pub(crate) fn set_output_file(&self, output_file: Option) { self.set_property("output-file", output_file.to_value()); @@ -605,10 +676,28 @@ impl RnCanvas { #[allow(unused)] pub(crate) fn set_unsaved_changes(&self, unsaved_changes: bool) { if self.imp().unsaved_changes.get() != unsaved_changes { + if unsaved_changes { + self.set_unsaved_changes_recovery(true); + } self.set_property("unsaved-changes", unsaved_changes.to_value()); } } + #[allow(unused)] + pub(crate) fn unsaved_changes_recovery(&self) -> bool { + self.property::("unsaved-changes-recovery") + } + + #[allow(unused)] + pub(crate) fn set_unsaved_changes_recovery(&self, unsaved_changes_recovery: bool) { + if self.imp().unsaved_changes_recovery.get() != unsaved_changes_recovery { + self.set_property( + "unsaved-changes-recovery", + unsaved_changes_recovery.to_value(), + ); + } + } + #[allow(unused)] pub(crate) fn empty(&self) -> bool { self.property::("empty") @@ -761,26 +850,32 @@ impl RnCanvas { /// /// When there is no output-file, falls back to the "New document" string pub(crate) fn doc_title_display(&self) -> String { - self.output_file() - .map(|f| { - f.basename() - .and_then(|t| Some(t.file_stem()?.to_string_lossy().to_string())) - .unwrap_or_else(|| gettext("- invalid file name -")) - }) - .unwrap_or_else(|| OUTPUT_FILE_NEW_TITLE.to_string()) + match self.recovery_in_progress() { + true => self.output_file_cache(), + false => self.output_file(), + } + .map(|f| { + f.basename() + .and_then(|t| Some(t.file_stem()?.to_string_lossy().to_string())) + .unwrap_or_else(|| gettext("- invalid file name -")) + }) + .unwrap_or_else(|| OUTPUT_FILE_NEW_TITLE.to_string()) } /// The document folder path for display. To get the actual path, use output-file /// /// When there is no output-file, falls back to the "Draft" string pub(crate) fn doc_folderpath_display(&self) -> String { - self.output_file() - .map(|f| { - f.parent() - .and_then(|p| Some(p.path()?.display().to_string())) - .unwrap_or_else(|| gettext("- invalid folder path -")) - }) - .unwrap_or_else(|| OUTPUT_FILE_NEW_SUBTITLE.to_string()) + match self.recovery_in_progress() { + true => self.output_file_cache(), + false => self.output_file(), + } + .map(|f| { + f.parent() + .and_then(|p| Some(p.path()?.display().to_string())) + .unwrap_or_else(|| gettext("- invalid folder path -")) + }) + .unwrap_or_else(|| OUTPUT_FILE_NEW_SUBTITLE.to_string()) } pub(crate) fn create_output_file_monitor(&self, file: &gio::File, appwindow: &RnAppWindow) { diff --git a/rnote-ui/src/mainheader.rs b/rnote-ui/src/mainheader.rs index 6e51a43eab..e06c28f955 100644 --- a/rnote-ui/src/mainheader.rs +++ b/rnote-ui/src/mainheader.rs @@ -1,7 +1,6 @@ -// Imports use crate::{appmenu::RnAppMenu, appwindow::RnAppWindow, canvasmenu::RnCanvasMenu}; use gtk4::{ - glib, prelude::*, subclass::prelude::*, CompositeTemplate, Label, ToggleButton, Widget, + glib, prelude::*, subclass::prelude::*, Button, CompositeTemplate, Label, ToggleButton, Widget, }; mod imp { @@ -17,6 +16,14 @@ mod imp { #[template_child] pub(crate) main_title_unsaved_indicator: TemplateChild - - - Format Border Color - Set the format border color - - - center - - - - - - - Show Scrollbars From 9b394f10ed8d6766ee9f41ceda31e37ef5db32e3 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Mon, 5 Jun 2023 12:10:58 +0200 Subject: [PATCH 06/61] add recovery metadata --- Cargo.lock | 10 +++++ rnote-engine/src/fileformats/mod.rs | 4 ++ .../src/fileformats/recovery_metadata.rs | 43 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 rnote-engine/src/fileformats/recovery_metadata.rs diff --git a/Cargo.lock b/Cargo.lock index 6dc6a470dd..be9e14e1fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1032,6 +1032,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.4.1" @@ -3469,6 +3478,7 @@ dependencies = [ "anyhow", "cairo-rs", "directories", + "dirs", "fs_extra", "futures", "gettext-rs", diff --git a/rnote-engine/src/fileformats/mod.rs b/rnote-engine/src/fileformats/mod.rs index b7b5ec4009..30b9c6f470 100644 --- a/rnote-engine/src/fileformats/mod.rs +++ b/rnote-engine/src/fileformats/mod.rs @@ -1,7 +1,11 @@ // Modules pub mod rnoteformat; +/// The Xournal++ `.xopp` file format. pub mod xoppformat; +// Renames +extern crate nalgebra as na; + // Imports use roxmltree::Node; diff --git a/rnote-engine/src/fileformats/recovery_metadata.rs b/rnote-engine/src/fileformats/recovery_metadata.rs new file mode 100644 index 0000000000..1772ee2267 --- /dev/null +++ b/rnote-engine/src/fileformats/recovery_metadata.rs @@ -0,0 +1,43 @@ +// Imports +use serde::{Deserialize, Serialize}; +use std::{cell::Cell, path::PathBuf}; + +#[derive(Debug, Serialize, Deserialize)] +/// Metadata of a revovery save +pub struct RecoveryMetadata { + last_changed: Cell, + rnote_path: PathBuf, + #[serde(skip_serializing)] + metdata_path: PathBuf, +} + +impl RecoveryMetadata { + /// Create new Cargo metadata + pub fn new(metadata_path: impl Into, rnote_path: impl Into) -> Self { + let out = Self { + last_changed: Cell::new(0), + rnote_path: rnote_path.into(), + metdata_path: metadata_path.into(), + }; + out.update_last_changed(); + out + } + /// Save recovery data + pub fn save(&self) { + std::fs::write( + &self.metdata_path, + serde_json::to_string(self).expect("Failed to parse recovery format"), + ) + .expect("Failed to write file") + } + + /// Replace last_changed with the current unix time + pub fn update_last_changed(&self) { + self.last_changed.replace( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Failed to get unix time") + .as_secs(), + ); + } +} From bda4853d7cecd8e649815c9fddfa1599adcf0ae9 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Mon, 5 Jun 2023 12:11:24 +0200 Subject: [PATCH 07/61] remove dublicate code --- rnote-ui/src/settingspanel/mod.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/rnote-ui/src/settingspanel/mod.rs b/rnote-ui/src/settingspanel/mod.rs index b95c6c8c5a..a75883fa25 100644 --- a/rnote-ui/src/settingspanel/mod.rs +++ b/rnote-ui/src/settingspanel/mod.rs @@ -501,15 +501,6 @@ impl RnSettingsPanel { .sync_create() .build(); - imp.general_autosave_interval_secs_spinbutton - .get() - .bind_property("value", appwindow, "autosave-interval-secs") - .transform_to(|_, val: f64| Some((val.round() as u32).to_value())) - .transform_from(|_, val: u32| Some(f64::from(val).to_value())) - .sync_create() - .bidirectional() - .build(); - imp.general_recovery_interval_secs_spinbutton .get() .bind_property("value", appwindow, "recovery-interval-secs") From fc9fd75344b1654698403a57836b55f0ee3d5c55 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Mon, 5 Jun 2023 12:19:16 +0200 Subject: [PATCH 08/61] add dirs dependency --- rnote-ui/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/rnote-ui/Cargo.toml b/rnote-ui/Cargo.toml index 803567af35..565b5a6ef1 100644 --- a/rnote-ui/Cargo.toml +++ b/rnote-ui/Cargo.toml @@ -49,3 +49,4 @@ fs_extra = "1" same-file = "1" regex = "1.7" directories = "5" +dirs = "5.0.1" From 2fea104c7577bbf2e8474cd06567a7d4722e458e Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Mon, 5 Jun 2023 12:21:43 +0200 Subject: [PATCH 09/61] remove output_file_cache --- rnote-ui/src/appwindow/imp.rs | 8 ++-- rnote-ui/src/canvas/imexport.rs | 2 +- rnote-ui/src/canvas/mod.rs | 85 ++++++++++++++++----------------- 3 files changed, 46 insertions(+), 49 deletions(-) diff --git a/rnote-ui/src/appwindow/imp.rs b/rnote-ui/src/appwindow/imp.rs index 501da5673c..532bac055d 100644 --- a/rnote-ui/src/appwindow/imp.rs +++ b/rnote-ui/src/appwindow/imp.rs @@ -245,8 +245,8 @@ impl ObjectImpl for RnAppWindow { self.recovery_interval_secs.replace(recovery_interval_secs); - if self.autosave.get() { - self.update_autosave_handler(); + if self.recovery.get() { + self.update_recovery_handler(); } } "righthanded" => { @@ -342,12 +342,12 @@ impl RnAppWindow { let tmp_file = canvas.get_or_generate_tmp_file(); appwindow.overlays().start_pulsing_progressbar(); canvas.set_recovery_in_progress(true); - canvas.imp().output_file_cache.replace(canvas.imp().output_file.take()); + // canvas.imp().output_file_cache.replace(canvas.imp().output_file.take()); if let Err(e) = canvas.save_document_to_file(&tmp_file).await { log::error!("saving document failed, Error: `{e:?}`"); appwindow.overlays().dispatch_toast_error(&gettext("Saving document failed")); } - canvas.imp().output_file.replace(canvas.imp().output_file_cache.take()); + // canvas.imp().output_file.replace(canvas.imp().output_file_cache.take()); canvas.set_recovery_in_progress(false); appwindow.overlays().finish_progressbar(); } diff --git a/rnote-ui/src/canvas/imexport.rs b/rnote-ui/src/canvas/imexport.rs index 6dae6ea587..3c8751de25 100644 --- a/rnote-ui/src/canvas/imexport.rs +++ b/rnote-ui/src/canvas/imexport.rs @@ -267,7 +267,7 @@ impl RnCanvas { // this **must** come before actually saving the file to disk, // else the event might not be caught by the monitor for new or changed files - if !skip_set_output_file { + if !skip_set_output_file && !self.recovery_in_progress() { self.set_output_file(Some(file.to_owned())); } diff --git a/rnote-ui/src/canvas/mod.rs b/rnote-ui/src/canvas/mod.rs index 3b35874583..77c4d439f5 100644 --- a/rnote-ui/src/canvas/mod.rs +++ b/rnote-ui/src/canvas/mod.rs @@ -46,6 +46,7 @@ pub(crate) struct Handlers { } mod imp { + use super::*; #[derive(Debug)] @@ -72,7 +73,9 @@ mod imp { pub(crate) recovery_in_progress: Cell, pub(crate) recovery_file: RefCell>, - pub(crate) output_file_cache: RefCell>, + // pub(crate) recovery_file_monitor: RefCell>, + pub(crate) recovery_file_metadata: RefCell>, + // pub(crate) output_file_cache: RefCell>, pub(crate) output_file: RefCell>, pub(crate) output_file_monitor: RefCell>, pub(crate) output_file_monitor_changed_handler: RefCell>, @@ -168,7 +171,9 @@ mod imp { recovery_in_progress: Cell::new(false), recovery_file: RefCell::new(None), - output_file_cache: RefCell::new(None), + // recovery_file_monitor: RefCell::new(None), + recovery_file_metadata: RefCell::new(None), + // output_file_cache: RefCell::new(None), output_file: RefCell::new(None), output_file_monitor: RefCell::new(None), output_file_monitor_changed_handler: RefCell::new(None), @@ -595,35 +600,33 @@ impl RnCanvas { self.imp().recovery_file.replace(tmp_file); } - #[allow(unused)] - pub(crate) fn output_file_cache(&self) -> Option { - self.imp().recovery_file.borrow().clone() - } - - #[allow(unused)] - pub(crate) fn set_output_file_cache(&self) -> Option { - self.imp().recovery_file.borrow().clone() - } - pub(crate) fn get_or_generate_tmp_file(&self) -> gio::File { - // if !recovery { - // return None; - // } if self.imp().recovery_file.borrow().is_none() { - let mut path = std::path::PathBuf::from("/tmp/rnote-recovery/"); + let imp = self.imp(); + let mut rnote_path = dirs::data_dir().expect("Failed to get data dir"); + rnote_path.push("rnote"); + rnote_path.push("recovery"); + if !rnote_path.exists() { + std::fs::create_dir_all(&rnote_path).expect("Failed to create directory") + }; let time = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .unwrap() + .expect("Failed to get unix time") .as_secs(); - if !path.exists() { - std::fs::create_dir(&path).expect("Failed to create tmp dir"); + if !rnote_path.exists() { + std::fs::create_dir(&rnote_path).expect("Failed to create tmp dir"); } - let name = format!("draft-{time}.rnote"); - path.push(name); + let name = format!("{time}.rnote"); + rnote_path.push(name); + let mut metadata_path = rnote_path.clone(); self.imp() .recovery_file - .replace(Some(gio::File::for_path(path))); + .replace(Some(gio::File::for_path(&rnote_path))); + + metadata_path.set_extension("json"); + let metadata = RecoveryMetadata::new(metadata_path, rnote_path); + imp.recovery_file_metadata.replace(Some(metadata)); } self.imp().recovery_file.borrow().as_ref().unwrap().clone() } @@ -659,8 +662,8 @@ impl RnCanvas { } #[allow(unused)] - pub(crate) fn set_recovery_in_progress(&self, save_in_progress: bool) { - self.imp().recovery_in_progress.set(save_in_progress); + pub(crate) fn set_recovery_in_progress(&self, recovery_in_progress: bool) { + self.imp().recovery_in_progress.set(recovery_in_progress); } #[allow(unused)] @@ -850,32 +853,26 @@ impl RnCanvas { /// /// When there is no output-file, falls back to the "New document" string pub(crate) fn doc_title_display(&self) -> String { - match self.recovery_in_progress() { - true => self.output_file_cache(), - false => self.output_file(), - } - .map(|f| { - f.basename() - .and_then(|t| Some(t.file_stem()?.to_string_lossy().to_string())) - .unwrap_or_else(|| gettext("- invalid file name -")) - }) - .unwrap_or_else(|| OUTPUT_FILE_NEW_TITLE.to_string()) + self.output_file() + .map(|f| { + f.basename() + .and_then(|t| Some(t.file_stem()?.to_string_lossy().to_string())) + .unwrap_or_else(|| gettext("- invalid file name -")) + }) + .unwrap_or_else(|| OUTPUT_FILE_NEW_TITLE.to_string()) } /// The document folder path for display. To get the actual path, use output-file /// /// When there is no output-file, falls back to the "Draft" string pub(crate) fn doc_folderpath_display(&self) -> String { - match self.recovery_in_progress() { - true => self.output_file_cache(), - false => self.output_file(), - } - .map(|f| { - f.parent() - .and_then(|p| Some(p.path()?.display().to_string())) - .unwrap_or_else(|| gettext("- invalid folder path -")) - }) - .unwrap_or_else(|| OUTPUT_FILE_NEW_SUBTITLE.to_string()) + self.output_file() + .map(|f| { + f.parent() + .and_then(|p| Some(p.path()?.display().to_string())) + .unwrap_or_else(|| gettext("- invalid folder path -")) + }) + .unwrap_or_else(|| OUTPUT_FILE_NEW_SUBTITLE.to_string()) } pub(crate) fn create_output_file_monitor(&self, file: &gio::File, appwindow: &RnAppWindow) { From fa6d29081bc3645ed9587847207dd7311f25e905 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Mon, 5 Jun 2023 12:22:13 +0200 Subject: [PATCH 10/61] import RecoveryMetadata --- rnote-ui/src/canvas/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rnote-ui/src/canvas/mod.rs b/rnote-ui/src/canvas/mod.rs index 77c4d439f5..faa4dfc553 100644 --- a/rnote-ui/src/canvas/mod.rs +++ b/rnote-ui/src/canvas/mod.rs @@ -5,6 +5,7 @@ mod input; // Re-exports pub(crate) use canvaslayout::RnCanvasLayout; +use rnote_fileformats::recovery_metadata::RecoveryMetadata; // Imports use crate::{config, RnAppWindow}; @@ -46,6 +47,7 @@ pub(crate) struct Handlers { } mod imp { + use rnote_fileformats::recovery_metadata::RecoveryMetadata; use super::*; From 518e4302ea933abf7598ee87228c5d434b5ca630 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Mon, 5 Jun 2023 12:26:53 +0200 Subject: [PATCH 11/61] skip serializing and deserializing --- rnote-engine/src/fileformats/recovery_metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rnote-engine/src/fileformats/recovery_metadata.rs b/rnote-engine/src/fileformats/recovery_metadata.rs index 1772ee2267..e5575206f6 100644 --- a/rnote-engine/src/fileformats/recovery_metadata.rs +++ b/rnote-engine/src/fileformats/recovery_metadata.rs @@ -7,7 +7,7 @@ use std::{cell::Cell, path::PathBuf}; pub struct RecoveryMetadata { last_changed: Cell, rnote_path: PathBuf, - #[serde(skip_serializing)] + #[serde(skip)] metdata_path: PathBuf, } From 515525c19bb7b829b3c708fbf9f2683ebaf00d16 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Wed, 7 Jun 2023 08:45:52 +0200 Subject: [PATCH 12/61] update last changed metadata when saving --- .gitignore | 1 - rnote-ui/src/appwindow/imp.rs | 2 -- rnote-ui/src/canvas/imexport.rs | 1 + rnote-ui/src/canvas/mod.rs | 5 +++++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index e3779ab0e7..37368f95d6 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,3 @@ .toggletasks.json *.flatpak justfile -rust-toolchain \ No newline at end of file diff --git a/rnote-ui/src/appwindow/imp.rs b/rnote-ui/src/appwindow/imp.rs index 532bac055d..9b21fed245 100644 --- a/rnote-ui/src/appwindow/imp.rs +++ b/rnote-ui/src/appwindow/imp.rs @@ -342,12 +342,10 @@ impl RnAppWindow { let tmp_file = canvas.get_or_generate_tmp_file(); appwindow.overlays().start_pulsing_progressbar(); canvas.set_recovery_in_progress(true); - // canvas.imp().output_file_cache.replace(canvas.imp().output_file.take()); if let Err(e) = canvas.save_document_to_file(&tmp_file).await { log::error!("saving document failed, Error: `{e:?}`"); appwindow.overlays().dispatch_toast_error(&gettext("Saving document failed")); } - // canvas.imp().output_file.replace(canvas.imp().output_file_cache.take()); canvas.set_recovery_in_progress(false); appwindow.overlays().finish_progressbar(); } diff --git a/rnote-ui/src/canvas/imexport.rs b/rnote-ui/src/canvas/imexport.rs index 3c8751de25..e094cc9291 100644 --- a/rnote-ui/src/canvas/imexport.rs +++ b/rnote-ui/src/canvas/imexport.rs @@ -290,6 +290,7 @@ impl RnCanvas { self.set_output_file_expect_write(false); return Err(e); } + self.update_recovery_file_metadata_last_changed(); if self.recovery_in_progress() { self.set_unsaved_changes_recovery(false); diff --git a/rnote-ui/src/canvas/mod.rs b/rnote-ui/src/canvas/mod.rs index faa4dfc553..9e9a218f96 100644 --- a/rnote-ui/src/canvas/mod.rs +++ b/rnote-ui/src/canvas/mod.rs @@ -1324,4 +1324,9 @@ impl RnCanvas { self.engine_mut().background_regenerate_pattern(); self.queue_draw(); } + pub(crate) fn update_recovery_file_metadata_last_changed(&self) { + if let Some(m) = self.imp().recovery_file_metadata.borrow().as_ref() { + m.update_last_changed() + } + } } From d40e2bd2b3c23d52b2d8610f172abc8cff203b1a Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Wed, 7 Jun 2023 12:38:37 +0200 Subject: [PATCH 13/61] switch to directories --- Cargo.lock | 10 ---------- rnote-ui/Cargo.toml | 1 - rnote-ui/src/canvas/mod.rs | 6 ++++-- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be9e14e1fe..6dc6a470dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1032,15 +1032,6 @@ dependencies = [ "dirs-sys", ] -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs-sys" version = "0.4.1" @@ -3478,7 +3469,6 @@ dependencies = [ "anyhow", "cairo-rs", "directories", - "dirs", "fs_extra", "futures", "gettext-rs", diff --git a/rnote-ui/Cargo.toml b/rnote-ui/Cargo.toml index 565b5a6ef1..803567af35 100644 --- a/rnote-ui/Cargo.toml +++ b/rnote-ui/Cargo.toml @@ -49,4 +49,3 @@ fs_extra = "1" same-file = "1" regex = "1.7" directories = "5" -dirs = "5.0.1" diff --git a/rnote-ui/src/canvas/mod.rs b/rnote-ui/src/canvas/mod.rs index 9e9a218f96..76a24d69f2 100644 --- a/rnote-ui/src/canvas/mod.rs +++ b/rnote-ui/src/canvas/mod.rs @@ -605,8 +605,10 @@ impl RnCanvas { pub(crate) fn get_or_generate_tmp_file(&self) -> gio::File { if self.imp().recovery_file.borrow().is_none() { let imp = self.imp(); - let mut rnote_path = dirs::data_dir().expect("Failed to get data dir"); - rnote_path.push("rnote"); + let mut rnote_path = directories::ProjectDirs::from("com.gthub", "flxt", "rnote") + .expect("Failed to get ProjectDirs") + .data_dir() + .to_path_buf(); rnote_path.push("recovery"); if !rnote_path.exists() { std::fs::create_dir_all(&rnote_path).expect("Failed to create directory") From f736261f2682974f72b736f5818cbb7ea03d9288 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Wed, 7 Jun 2023 12:39:17 +0200 Subject: [PATCH 14/61] move recovery indicator --- rnote-ui/data/ui/settingspanel.ui | 4 ++++ rnote-ui/src/appwindow/mod.rs | 5 +++-- rnote-ui/src/mainheader.rs | 6 ------ rnote-ui/src/settingspanel/mod.rs | 8 +++++++- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/rnote-ui/data/ui/settingspanel.ui b/rnote-ui/data/ui/settingspanel.ui index 135ed42ca5..8d433b70af 100644 --- a/rnote-ui/data/ui/settingspanel.ui +++ b/rnote-ui/data/ui/settingspanel.ui @@ -76,6 +76,10 @@ Recovery Enable or disable document recovery + + • + false + center diff --git a/rnote-ui/src/appwindow/mod.rs b/rnote-ui/src/appwindow/mod.rs index e0184ad998..041615b5e0 100644 --- a/rnote-ui/src/appwindow/mod.rs +++ b/rnote-ui/src/appwindow/mod.rs @@ -444,8 +444,9 @@ impl RnAppWindow { self.mainheader() .main_title_unsaved_indicator() .set_visible(canvas.unsaved_changes()); - self.mainheader() - .main_title_unsaved_recovery_indicator() + // This is not in the title bar, but I will keep the logic here to keep it close to the other unsaved indicator + self.settings_panel() + .general_recovery_unsaved_indicator() .set_visible(canvas.unsaved_changes_recovery()); if canvas.unsaved_changes() { self.mainheader() diff --git a/rnote-ui/src/mainheader.rs b/rnote-ui/src/mainheader.rs index 9a4fde56ad..9a68c5ec4b 100644 --- a/rnote-ui/src/mainheader.rs +++ b/rnote-ui/src/mainheader.rs @@ -16,8 +16,6 @@ mod imp { #[template_child] pub(crate) main_title_unsaved_indicator: TemplateChild + + + + 800 + 600 + true + false + fill + fill + + + vertical + 24 + 12 + 12 + 12 + 12 + + + + + + + + + + \ No newline at end of file diff --git a/rnote-ui/src/appwindow/imp.rs b/rnote-ui/src/appwindow/imp.rs index 9b21fed245..f86491d823 100644 --- a/rnote-ui/src/appwindow/imp.rs +++ b/rnote-ui/src/appwindow/imp.rs @@ -339,6 +339,7 @@ impl RnAppWindow { let canvas = appwindow.active_tab().canvas(); glib::MainContext::default().spawn_local(clone!(@weak canvas, @weak appwindow => async move { + if !canvas.unsaved_changes_recovery() {return;} let tmp_file = canvas.get_or_generate_tmp_file(); appwindow.overlays().start_pulsing_progressbar(); canvas.set_recovery_in_progress(true); diff --git a/rnote-ui/src/appwindow/mod.rs b/rnote-ui/src/appwindow/mod.rs index 2ab87ebc81..8110500645 100644 --- a/rnote-ui/src/appwindow/mod.rs +++ b/rnote-ui/src/appwindow/mod.rs @@ -54,6 +54,15 @@ impl RnAppWindow { pub(crate) fn set_recovery(&self, recovery: bool) { self.set_property("recovery", recovery.to_value()); } + #[allow(unused)] + pub(crate) fn recovery_interval_secs(&self) -> u32 { + self.property::("recovery-interval-secs") + } + + #[allow(unused)] + pub(crate) fn set_recovery_interval_secs(&self, autosave_interval_secs: u32) { + self.set_property("recovery-interval-secs", autosave_interval_secs.to_value()); + } #[allow(unused)] pub(crate) fn autosave_interval_secs(&self) -> u32 { @@ -187,6 +196,9 @@ impl RnAppWindow { self.overlays().undo_button().set_sensitive(false); self.overlays().redo_button().set_sensitive(false); self.refresh_ui_from_engine(&self.active_tab()); + glib::MainContext::default().spawn_local(clone!(@weak self as appwindow => async move { + dialogs::dialog_recover_documents(&appwindow).await; + })); } /// Called to close the window diff --git a/rnote-ui/src/dialogs/mod.rs b/rnote-ui/src/dialogs/mod.rs index bfa178eb44..1adbc9d473 100644 --- a/rnote-ui/src/dialogs/mod.rs +++ b/rnote-ui/src/dialogs/mod.rs @@ -4,6 +4,7 @@ // Modules pub(crate) mod export; pub(crate) mod import; +mod recovery; // Imports use crate::appwindow::RnAppWindow; @@ -19,6 +20,9 @@ use gtk4::{ Label, MenuButton, ResponseType, ShortcutsWindow, StringList, }; +// Re-exports +pub(crate) use recovery::dialog_recover_documents; + // About Dialog pub(crate) fn dialog_about(appwindow: &RnAppWindow) { let app_icon_name = if config::PROFILE == "devel" { @@ -559,10 +563,6 @@ pub(crate) async fn dialog_edit_selected_workspace(appwindow: &RnAppWindow) { } } -pub(crate) async fn dialog_recover_documents() { - todo!() -} - const WORKSPACELISTENTRY_ICONS_LIST: &[&str] = &[ "workspacelistentryicon-bandaid-symbolic", "workspacelistentryicon-bank-symbolic", From 477af0cf06f0106dd662fc62cb559811c0242e31 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Mon, 3 Jul 2023 12:08:39 +0200 Subject: [PATCH 21/61] add ui files to grecource --- rnote-ui/data/resources.gresource.xml.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rnote-ui/data/resources.gresource.xml.in b/rnote-ui/data/resources.gresource.xml.in index 02cc4950a6..ae674d479a 100644 --- a/rnote-ui/data/resources.gresource.xml.in +++ b/rnote-ui/data/resources.gresource.xml.in @@ -15,6 +15,7 @@ ui/workspacesbar/workspacesbar.ui ui/workspacesbar/workspacerow.ui ui/filerow.ui + ui/recoveryrow.ui ui/unitentry.ui ui/iconpicker.ui ui/groupediconpicker/groupediconpicker.ui @@ -30,6 +31,7 @@ ui/penssidebar/toolspage.ui ui/dialogs/dialogs.ui ui/dialogs/import.ui + ui/dialogs/recovery.ui ui/dialogs/export.ui icons/scalable/apps/@APP_NAME@.svg icons/scalable/apps/@APP_NAME@-devel.svg From d5b93d97ab919c0d2881fcd1269910d191fb94df Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Tue, 4 Jul 2023 09:03:58 +0200 Subject: [PATCH 22/61] revert changes from merge --- rnote-engine/src/fileformats/mod.rs | 5 +- rnote-ui/data/ui/settingspanel.ui | 79 +++++++++++++++-------------- 2 files changed, 41 insertions(+), 43 deletions(-) diff --git a/rnote-engine/src/fileformats/mod.rs b/rnote-engine/src/fileformats/mod.rs index 30b9c6f470..9195948034 100644 --- a/rnote-engine/src/fileformats/mod.rs +++ b/rnote-engine/src/fileformats/mod.rs @@ -1,10 +1,7 @@ // Modules pub mod rnoteformat; -/// The Xournal++ `.xopp` file format. pub mod xoppformat; - -// Renames -extern crate nalgebra as na; +pub mod recovery_metadata; // Imports use roxmltree::Node; diff --git a/rnote-ui/data/ui/settingspanel.ui b/rnote-ui/data/ui/settingspanel.ui index 8d433b70af..7586bcdfdf 100644 --- a/rnote-ui/data/ui/settingspanel.ui +++ b/rnote-ui/data/ui/settingspanel.ui @@ -72,47 +72,24 @@ - - Recovery - Enable or disable document recovery - - - • - false - - - center - - - - - - - Recoevry Interval (secs) - Set the recovery save interval in seconds + + Show Scrollbars + Set whether the scrollbars on the canvas are shown - - 1 - 9999 - 5 - 120 - - - general_recovery_interval_secs_adj - horizontal - false + center - 0 - - Show Scrollbars - Set whether the scrollbars on the canvas are shown + + Inertial Touch Scrolling + Set whether touch scrolling on the canvas is inertial. +An application restart is required when this option +gets disabled. - + center @@ -307,12 +284,33 @@ - + - Document Background + Document + + + Format Border Color + Set the format border color + + + horizontal + 6 + false + false + center + + + + doc_format_border_color_dialog + + + + + + - + Color Set the background color @@ -323,14 +321,17 @@ false center - + + + doc_background_color_dialog + - + Pattern Choose a background pattern @@ -529,4 +530,4 @@ on a drawing pad - + \ No newline at end of file From c866d79d85b3916340c83186ee5a4f3a90fef6f3 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Tue, 4 Jul 2023 09:52:05 +0200 Subject: [PATCH 23/61] undo changed in settingspanel/mod.rs --- rnote-ui/data/ui/settingspanel.ui | 32 ++++ rnote-ui/src/settingspanel/mod.rs | 285 +++++++++++++++++------------- 2 files changed, 191 insertions(+), 126 deletions(-) diff --git a/rnote-ui/data/ui/settingspanel.ui b/rnote-ui/data/ui/settingspanel.ui index 7586bcdfdf..233604253b 100644 --- a/rnote-ui/data/ui/settingspanel.ui +++ b/rnote-ui/data/ui/settingspanel.ui @@ -71,6 +71,38 @@ + + + Recovery + Enable or disable document recovery + + + center + + + + + + + Recovery Interval (secs) + Set the recovery interval in seconds + + + 1 + 9999 + 5 + 120 + + + general_recovery_interval_secs_adj + horizontal + false + center + 0 + + + + Show Scrollbars diff --git a/rnote-ui/src/settingspanel/mod.rs b/rnote-ui/src/settingspanel/mod.rs index d7183b69a3..8733f8e7b1 100644 --- a/rnote-ui/src/settingspanel/mod.rs +++ b/rnote-ui/src/settingspanel/mod.rs @@ -1,3 +1,4 @@ +// Modules mod penshortcutmodels; mod penshortcutrow; @@ -7,7 +8,7 @@ pub(crate) use penshortcutrow::RnPenShortcutRow; // Imports use crate::{RnAppWindow, RnCanvasWrapper, RnIconPicker, RnUnitEntry}; use adw::prelude::*; -use gettextrs::gettext; +use gettextrs::{gettext, pgettext}; use gtk4::{ gdk, glib, glib::clone, subclass::prelude::*, Adjustment, Button, ColorDialogButton, CompositeTemplate, MenuButton, ScrolledWindow, SpinButton, StringList, Switch, ToggleButton, @@ -19,7 +20,6 @@ use rnote_engine::document::background::PatternStyle; use rnote_engine::document::format::{self, Format, PredefinedFormat}; use rnote_engine::utils::GdkRGBAHelpers; use std::cell::RefCell; -use std::rc::Rc; mod imp { use super::*; @@ -27,7 +27,8 @@ mod imp { #[derive(Debug, Default, CompositeTemplate)] #[template(resource = "/com/github/flxzt/rnote/ui/settingspanel.ui")] pub(crate) struct RnSettingsPanel { - pub(crate) temporary_format: Rc>, + pub(crate) temporary_format: RefCell, + pub(crate) app_restart_toast_singleton: RefCell>, #[template_child] pub(crate) settings_scroller: TemplateChild, @@ -38,8 +39,6 @@ mod imp { #[template_child] pub(crate) general_autosave_interval_secs_spinbutton: TemplateChild, #[template_child] - pub(crate) general_recovery_unsaved_indicator: TemplateChild - - - - 800 - 600 - true - false - fill - fill - - - vertical - 24 - 12 - 12 - 12 - 12 - - - - - - - - - - + \ No newline at end of file diff --git a/rnote-ui/data/ui/dialogs/recovery.ui b/rnote-ui/data/ui/dialogs/recovery.ui new file mode 100644 index 0000000000..25885c1bee --- /dev/null +++ b/rnote-ui/data/ui/dialogs/recovery.ui @@ -0,0 +1,31 @@ + + + + + 800 + 600 + true + false + fill + fill + + + vertical + 24 + 12 + 12 + 12 + 12 + + + + + + + + + + + \ No newline at end of file diff --git a/rnote-ui/src/app/mod.rs b/rnote-ui/src/app/mod.rs index d5df3fbf73..1b85a3e938 100644 --- a/rnote-ui/src/app/mod.rs +++ b/rnote-ui/src/app/mod.rs @@ -5,19 +5,18 @@ mod appactions; use crate::{ colorpicker::RnColorPad, colorpicker::RnColorSetter, config, globals, penssidebar::RnBrushPage, penssidebar::RnEraserPage, penssidebar::RnSelectorPage, penssidebar::RnShaperPage, - penssidebar::RnToolsPage, penssidebar::RnTypewriterPage, settingspanel::RnPenShortcutRow, - strokewidthpicker::RnStrokeWidthPreview, strokewidthpicker::RnStrokeWidthSetter, - strokewidthpicker::StrokeWidthPreviewStyle, workspacebrowser::workspacesbar::RnWorkspaceRow, - workspacebrowser::RnFileRow, workspacebrowser::RnWorkspacesBar, RnAppMenu, RnAppWindow, - RnCanvas, RnCanvasMenu, RnCanvasWrapper, RnColorPicker, RnIconPicker, RnMainHeader, RnOverlays, - RnPensSideBar, RnSettingsPanel, RnStrokeWidthPicker, RnUnitEntry, RnWorkspaceBrowser, + penssidebar::RnToolsPage, penssidebar::RnTypewriterPage, recoveryrow::RnRecoveryRow, + settingspanel::RnPenShortcutRow, strokewidthpicker::RnStrokeWidthPreview, + strokewidthpicker::RnStrokeWidthSetter, strokewidthpicker::StrokeWidthPreviewStyle, + workspacebrowser::workspacesbar::RnWorkspaceRow, workspacebrowser::RnFileRow, + workspacebrowser::RnWorkspacesBar, RnAppMenu, RnAppWindow, RnCanvas, RnCanvasMenu, + RnCanvasWrapper, RnColorPicker, RnIconPicker, RnMainHeader, RnOverlays, RnPensSideBar, + RnSettingsPanel, RnStrokeWidthPicker, RnUnitEntry, RnWorkspaceBrowser, }; use adw::subclass::prelude::AdwApplicationImpl; use gtk4::{gio, glib, prelude::*, subclass::prelude::*}; mod imp { - use crate::recoveryrow::RnRecoveryRow; - use super::*; #[derive(Debug, Default)] From 7ee25cbabbcc56c0dfe0318549246b881ac3e214 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Wed, 12 Jul 2023 11:34:21 +0200 Subject: [PATCH 26/61] use loader and saver for RecoveryMetadata --- .../src/fileformats/recovery_metadata.rs | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/rnote-engine/src/fileformats/recovery_metadata.rs b/rnote-engine/src/fileformats/recovery_metadata.rs index 18fcd096e2..e3a0a8ba0b 100644 --- a/rnote-engine/src/fileformats/recovery_metadata.rs +++ b/rnote-engine/src/fileformats/recovery_metadata.rs @@ -1,11 +1,13 @@ -// Imports +use anyhow::Context; use serde::{Deserialize, Serialize}; use std::{ cell::{Cell, RefCell}, - path::PathBuf, + path::{Path, PathBuf}, }; -#[derive(Debug, Serialize, Deserialize)] +use super::{FileFormatLoader, FileFormatSaver}; + +#[derive(Debug, Serialize, Deserialize, Clone)] /// Metadata of a revovery save pub struct RecoveryMetadata { title: RefCell>, @@ -16,6 +18,23 @@ pub struct RecoveryMetadata { metdata_path: PathBuf, } +impl FileFormatSaver for RecoveryMetadata { + fn save_as_bytes(&self, file_name: &str) -> anyhow::Result> { + let data = serde_json::to_string(self).expect("Failed to parse recovery format"); + let bytes = data.as_bytes(); + std::fs::write(file_name, bytes).expect("Failed to write file"); + Ok(bytes.to_vec()) + } +} +impl FileFormatLoader for RecoveryMetadata { + fn load_from_bytes(bytes: &[u8]) -> anyhow::Result + where + Self: Sized, + { + serde_json::from_slice(bytes).context("failed to parse bytes") + } +} + impl RecoveryMetadata { /// Create new Cargo metadata pub fn new(metadata_path: impl Into, rnote_path: impl Into) -> Self { @@ -29,13 +48,14 @@ impl RecoveryMetadata { out.update_last_changed(); out } + pub fn load_from_path(path: impl AsRef) -> anyhow::Result { + let path = path.as_ref(); + let bytes = std::fs::read(path).context("Failed to read file")?; + Self::load_from_bytes(&bytes) + } /// Save recovery data - pub fn save(&self) { - std::fs::write( - &self.metdata_path, - serde_json::to_string(self).expect("Failed to parse recovery format"), - ) - .expect("Failed to write file") + pub fn save(&self) -> anyhow::Result> { + self.save_as_bytes(self.metdata_path.to_str().unwrap()) } /// Update Metadate based of the given document option pub fn update(&self, document_path: &Option) { From 2df16f2fc6de8378e9d48a53cc39b3f76d923bf7 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Wed, 12 Jul 2023 11:34:41 +0200 Subject: [PATCH 27/61] update recoveryrow --- rnote-ui/data/ui/recoveryrow.ui | 32 +++++++------ rnote-ui/src/recoveryrow/actions/discard.rs | 2 +- rnote-ui/src/recoveryrow/actions/recover.rs | 4 +- rnote-ui/src/recoveryrow/actions/save_as.rs | 37 +++++++++++++-- rnote-ui/src/recoveryrow/mod.rs | 52 +++++++++++++-------- 5 files changed, 86 insertions(+), 41 deletions(-) diff --git a/rnote-ui/data/ui/recoveryrow.ui b/rnote-ui/data/ui/recoveryrow.ui index 2a53d92b1e..67a323cce4 100644 --- a/rnote-ui/data/ui/recoveryrow.ui +++ b/rnote-ui/data/ui/recoveryrow.ui @@ -18,24 +18,26 @@ vertical - - document_name - start - true - end - - - document_path + + + document_name start true end - - - last_changed - start - true - end - + + + document_path + start + true + end + + + last_changed + start + true + end + + Recover Document diff --git a/rnote-ui/src/recoveryrow/actions/discard.rs b/rnote-ui/src/recoveryrow/actions/discard.rs index 440dd5f8d5..34f88ac9bd 100644 --- a/rnote-ui/src/recoveryrow/actions/discard.rs +++ b/rnote-ui/src/recoveryrow/actions/discard.rs @@ -4,7 +4,7 @@ use cairo::glib::{self, clone}; use gtk4::{gio, prelude::FileExt, subclass::prelude::ObjectSubclassIsExt}; use std::fs::remove_file; -pub(crate) fn discard(recoveryrow: &RnRecoveryRow) /*-> gio::SimpleAction*/ +pub(crate) async fn discard(recoveryrow: &RnRecoveryRow) /*-> gio::SimpleAction*/ { let action_discard_file = gio::SimpleAction::new("discard", None); action_discard_file.connect_activate( diff --git a/rnote-ui/src/recoveryrow/actions/recover.rs b/rnote-ui/src/recoveryrow/actions/recover.rs index 92bc289313..9d098fb7ae 100644 --- a/rnote-ui/src/recoveryrow/actions/recover.rs +++ b/rnote-ui/src/recoveryrow/actions/recover.rs @@ -1,5 +1,5 @@ -use crate::recoveryrow::RnRecoveryRow; +use crate::{appwindow::RnAppWindow, recoveryrow::RnRecoveryRow}; -pub(crate) fn recover(recoveryrow: &RnRecoveryRow) { +pub(crate) async fn recover(recoveryrow: &RnRecoveryRow, appwindow: &RnAppWindow) { todo!() } diff --git a/rnote-ui/src/recoveryrow/actions/save_as.rs b/rnote-ui/src/recoveryrow/actions/save_as.rs index 61c39530b5..34fc54da99 100644 --- a/rnote-ui/src/recoveryrow/actions/save_as.rs +++ b/rnote-ui/src/recoveryrow/actions/save_as.rs @@ -1,5 +1,36 @@ -use crate::recoveryrow::RnRecoveryRow; +use gettextrs::gettext; +use gtk4::{prelude::FileExt, subclass::prelude::ObjectSubclassIsExt, FileDialog, FileFilter}; -pub(crate) fn save_as(recoveryrow: &RnRecoveryRow) { - todo!() +use crate::{appwindow::RnAppWindow, recoveryrow::RnRecoveryRow}; + +pub(crate) async fn save_as(recoveryrow: &RnRecoveryRow, appwindow: &RnAppWindow) { + let filter = FileFilter::new(); + filter.add_mime_type("application/rnote"); + filter.add_suffix("rnote"); + filter.set_name(Some(&gettext(".rnote"))); + + let filedialog = FileDialog::builder() + .title("Save recovered file as...") + .accept_label(gettext("Save")) + .modal(true) + .default_filter(&filter) + .build(); + match filedialog.save_future(Some(appwindow)).await { + Ok(f) => { + std::fs::copy( + recoveryrow + .imp() + .meta + .borrow() + .as_ref() + .unwrap() + .recovery_file_path(), + f.path().unwrap(), + ) + .unwrap(); + } + Err(e) => { + log::error!("Failed to save revovery file as: {e}") + } + } } diff --git a/rnote-ui/src/recoveryrow/mod.rs b/rnote-ui/src/recoveryrow/mod.rs index 858e0ce2f7..4ac96aad83 100644 --- a/rnote-ui/src/recoveryrow/mod.rs +++ b/rnote-ui/src/recoveryrow/mod.rs @@ -1,4 +1,3 @@ -#![warn(clippy::todo)] // modules mod actions; @@ -31,7 +30,7 @@ mod imp { use super::*; #[derive(Debug, CompositeTemplate, Default)] - #[template(resource = "/com/github/flxzt/rnote/ui/recoveryentry.ui")] + #[template(resource = "/com/github/flxzt/rnote/ui/recoveryrow.ui")] pub(crate) struct RnRecoveryRow { pub(crate) last_changed_format: String, pub(crate) meta: RefCell>, @@ -51,19 +50,19 @@ mod imp { pub(crate) discard_button: TemplateChild + \ No newline at end of file diff --git a/rnote-ui/data/ui/dialogs/recovery.ui b/rnote-ui/data/ui/dialogs/recovery.ui index 25885c1bee..a253660893 100644 --- a/rnote-ui/data/ui/dialogs/recovery.ui +++ b/rnote-ui/data/ui/dialogs/recovery.ui @@ -1,31 +1,18 @@ - - - 800 - 600 - true - false - fill - fill - - - vertical - 24 - 12 - 12 - 12 - 12 - - - - - - - - - + + Recover Documents + Look like Rnote crashed last time. + We recovered some documents.Please select what you want to do with them: + save + cancel + + + Recovered Documents + + + + Confirm + \ No newline at end of file diff --git a/rnote-ui/src/appwindow/imp.rs b/rnote-ui/src/appwindow/imp.rs index f86491d823..8574f9520c 100644 --- a/rnote-ui/src/appwindow/imp.rs +++ b/rnote-ui/src/appwindow/imp.rs @@ -1,6 +1,7 @@ // Imports use crate::{ - config, RnOverlays, RnSettingsPanel, RnWorkspaceBrowser, {dialogs, RnMainHeader}, + config, RnOverlays, RnRecoveryAction, RnSettingsPanel, RnWorkspaceBrowser, + {dialogs, RnMainHeader}, }; use adw::{prelude::*, subclass::prelude::*}; use gettextrs::gettext; @@ -28,6 +29,7 @@ pub(crate) struct RnAppWindow { pub(crate) autosave_interval_secs: Cell, pub(crate) recovery: Cell, pub(crate) recovery_interval_secs: Cell, + pub(crate) recovery_actions: RefCell>, pub(crate) righthanded: Cell, pub(crate) block_pinch_zoom: Cell, pub(crate) touch_drawing: Cell, @@ -76,6 +78,7 @@ impl Default for RnAppWindow { autosave_interval_secs: Cell::new(super::RnAppWindow::AUTOSAVE_INTERVAL_DEFAULT), recovery: Cell::new(true), recovery_interval_secs: Cell::new(super::RnAppWindow::RECOVERY_INTERVAL_DEFAULT), + recovery_actions: RefCell::new(Vec::new()), righthanded: Cell::new(true), block_pinch_zoom: Cell::new(false), touch_drawing: Cell::new(false), diff --git a/rnote-ui/src/appwindow/mod.rs b/rnote-ui/src/appwindow/mod.rs index 31742ed147..226ffb32f9 100644 --- a/rnote-ui/src/appwindow/mod.rs +++ b/rnote-ui/src/appwindow/mod.rs @@ -5,8 +5,8 @@ mod imp; // Imports use crate::{ - config, RnApp, RnCanvas, RnCanvasWrapper, RnOverlays, RnSettingsPanel, RnWorkspaceBrowser, - {dialogs, RnMainHeader}, + config, RnApp, RnCanvas, RnCanvasWrapper, RnOverlays, RnRecoveryAction, RnSettingsPanel, + RnWorkspaceBrowser, {dialogs, RnMainHeader}, }; use adw::{prelude::*, subclass::prelude::*}; use gettextrs::gettext; @@ -35,6 +35,10 @@ impl RnAppWindow { glib::Object::builder().property("application", app).build() } + pub(crate) fn set_recovery_action(&self, i: usize, action: RnRecoveryAction) { + self.imp().recovery_actions.borrow_mut()[i] = dbg!(action) + } + #[allow(unused)] pub(crate) fn autosave(&self) -> bool { self.property::("autosave") diff --git a/rnote-ui/src/canvas/mod.rs b/rnote-ui/src/canvas/mod.rs index 085a11e4fd..455780a516 100644 --- a/rnote-ui/src/canvas/mod.rs +++ b/rnote-ui/src/canvas/mod.rs @@ -1328,7 +1328,9 @@ impl RnCanvas { .clone() .map(|f| f.path().unwrap()), ); - m.save(); + if let Err(e) = m.save() { + log::error!("Failed to save recovery metadata: {e}") + }; } } } diff --git a/rnote-ui/src/dialogs/mod.rs b/rnote-ui/src/dialogs/mod.rs index 1adbc9d473..69f5e35b9a 100644 --- a/rnote-ui/src/dialogs/mod.rs +++ b/rnote-ui/src/dialogs/mod.rs @@ -4,7 +4,7 @@ // Modules pub(crate) mod export; pub(crate) mod import; -mod recovery; +pub(crate) mod recovery; // Imports use crate::appwindow::RnAppWindow; diff --git a/rnote-ui/src/dialogs/recovery.rs b/rnote-ui/src/dialogs/recovery.rs index 2de3c5c3cc..f9418e2190 100644 --- a/rnote-ui/src/dialogs/recovery.rs +++ b/rnote-ui/src/dialogs/recovery.rs @@ -1,27 +1,179 @@ -use cairo::glib::{self, clone, Cast, StaticType}; -use gtk4::{ConstantExpression, ListItem, PropertyExpression, SignalListItemFactory}; +use adw::{ + prelude::MessageDialogExtManual, + traits::{ActionRowExt, PreferencesGroupExt}, +}; +use cairo::glib::{self, clone}; +use gettextrs::gettext; +use gtk4::{prelude::FileExt, traits::ToggleButtonExt}; +use gtk4::{ + subclass::prelude::ObjectSubclassIsExt, traits::GtkWindowExt, Builder, FileDialog, ToggleButton, +}; +use std::{ffi::OsStr, fs::read_dir, path::PathBuf}; +use time::{format_description::well_known::Rfc2822, OffsetDateTime}; -use crate::{appwindow::RnAppWindow, recoveryrow::RnRecoveryRow}; +use crate::{appwindow::RnAppWindow, config, env::recovery_dir}; +use rnote_engine::fileformats::recovery_metadata::RecoveryMetadata; -pub(crate) async fn dialog_recover_documents(appwindow: &RnAppWindow) { - setup_recovery_rows(appwindow); +#[derive(Clone, Debug)] +pub(crate) enum RnRecoveryAction { + Discard, + SaveAs(PathBuf), + Keep, + Open, } -fn setup_recovery_rows(appwindow: &RnAppWindow) { - let primary_list_factory = SignalListItemFactory::new(); - primary_list_factory.connect_setup(clone!(@weak appwindow => move |_, list_item| { - let list_item = list_item.downcast_ref::().unwrap(); +pub(crate) async fn dialog_recover_documents(appwindow: &RnAppWindow) { + let files = get_files(); + if files.is_empty() { + log::debug!("No recovery files found"); + // return; + } + let builder = Builder::from_resource( + (String::from(config::APP_IDPATH) + "ui/dialogs/recovery.ui").as_str(), + ); + let mut rows = Vec::new(); + let dialog: adw::MessageDialog = builder.object("dialog_recover_documents").unwrap(); + let recover_documents_group: adw::PreferencesGroup = + builder.object("recover_documents_group").unwrap(); + dialog.set_transient_for(Some(appwindow)); + appwindow + .imp() + .recovery_actions + .replace([(); 4].map(|_| RnRecoveryAction::Discard).to_vec()); + for (i, metadata) in files.iter().enumerate() { + // let recovery_row: RnRecoveryRow = RnRecoveryRow::new(); + // recovery_row.init(appwindow, metadata.clone()); + let row: adw::ActionRow = adw::ActionRow::builder() + .title(metadata.title().unwrap_or_else(|| String::from("Unsaved"))) + .subtitle(format_unix_timestamp(metadata.last_changed())) + .subtitle_lines(2) + .build(); + let open_button = ToggleButton::builder() + .icon_name("tab-new-filled-symbolic") + .tooltip_text("Recover document in new tab") + .build(); + let save_as_button = ToggleButton::builder() + .icon_name("doc-save-symbolic") + .tooltip_text("Save file to selected path") + .group(&open_button) + .build(); + let keep_button = ToggleButton::builder() + .icon_name("workspacelistentryicon-clock-symbolic") + .tooltip_text("Ask me again next session") + .group(&open_button) + .build(); + let discard_button = ToggleButton::builder() + .icon_name("trash-empty") + .tooltip_text("Discard document") + .active(true) + .group(&open_button) + .build(); + discard_button.connect_toggled(clone!(@weak appwindow => move |button| { + if button.is_active() { + appwindow.set_recovery_action(i, RnRecoveryAction::Discard) + } + })); + open_button.connect_toggled(clone!(@weak appwindow => move |button| { + if button.is_active(){ + appwindow.set_recovery_action(i, RnRecoveryAction::Open) + } + })); + save_as_button.connect_toggled(clone!(@weak appwindow => move |button| { + if !button.is_active(){ + return; + } + glib::MainContext::default().spawn_local(clone!(@weak appwindow => async move { + let filedialog = FileDialog::builder() + .title("Save recovered file as...") + .accept_label(gettext("Save")) + .modal(true) + .build(); - let recoveryrow = RnRecoveryRow::new(); - recoveryrow.init(&appwindow); - list_item.set_child(Some(&recoveryrow)); + match filedialog.save_future(Some(&appwindow)).await { + Ok(f) => { + let path = f.path().unwrap(); + // if path.extension().ne(Some("rnote")){ + // path.set_extension() + // } + appwindow.set_recovery_action(i, RnRecoveryAction::SaveAs(path)) + } + Err(e) => { + log::error!("Failed to get save path for revovery file: {e}") + } + } + })); + })); + keep_button.connect_toggled(clone!(@weak appwindow => move |button| { + if button.is_active(){ + appwindow.set_recovery_action(i, RnRecoveryAction::Keep) + } + })); + // recover_document_button.connect_clicked(); + // save_as_button.connect_clicked(); + // discard_button.connect_clicked(clone!(@weak appwindow => move |button|{ + // b - let list_item_expr = ConstantExpression::new(list_item); - let recoveryinfo_expr = - PropertyExpression::new(ListItem::static_type(), Some(&list_item_expr), "item"); + // })); + row.add_suffix(&open_button); + row.add_suffix(&save_as_button); + row.add_suffix(&discard_button); + recover_documents_group.add(&row); + rows.push(row); + } + dialog.choose_future().await; +} - // recoveryrow. - // recoveryinfo_expr.chain_closure(|| ()) +fn get_files() -> Vec { + let mut recovery_files = Vec::new(); + let recovery_ext: &OsStr = OsStr::new("json"); + for file in read_dir(recovery_dir().expect("Failed to get recovery dir")) + .expect("failed to read recovery dir") + { + let file = file.expect("Failed to get DirEntry"); + if file.path().extension().ne(&Some(recovery_ext)) { + continue; + } + let metadata = + RecoveryMetadata::load_from_path(&file.path()).expect("Failed to load recovery file"); + recovery_files.push(metadata); + } + recovery_files +} - })); +fn format_unix_timestamp(unix: i64) -> String { + // Shows occuring errors in timesptamp label field instead of crashing + match OffsetDateTime::from_unix_timestamp(unix) { + Err(e) => { + log::error!("Failed to get time from unix time: {e}"); + String::from("Error getting time") + } + Ok(ts) => ts.format(&Rfc2822).unwrap_or_else(|e| { + log::error!("Failed to format time: {e}"); + String::from("Error formatting time") + }), + } } + +// pub(crate) async fn discard(appwindow: &RnAppWindow, i: usize) /*-> gio::SimpleAction*/ +// { +// let action_discard_file = gio::SimpleAction::new("discard", None); +// action_discard_file.connect_activate( +// clone!(@weak appwindow => move |_action_discard_file, _| { +// let medata = appwindow.imp().recovered_documents.borrow().get(i); +// if metadata.is_some() && imp.meta.borrow().is_some() { +// // Unwrapping should be safe here since the condition makes sure they're not None +// let meta = imp.meta.replace(None).unwrap(); +// let meta_path = imp.meta_path.replace(None).unwrap(); + +// if let Err(e) = remove_file(meta.recovery_file_path()){ +// log::error!("Failed to remove recovery file {}: {e}", meta.recovery_file_path().display()) +// }; +// if let Err(e) = remove_file(meta_path.path().unwrap()){ +// log::error!("Failed to remove recovery file {}: {e}", meta_path) +// }; +// } +// }), +// ); + +// // action_discard_file +// } diff --git a/rnote-ui/src/main.rs b/rnote-ui/src/main.rs index d2cae59926..e6c6d8679f 100644 --- a/rnote-ui/src/main.rs +++ b/rnote-ui/src/main.rs @@ -1,4 +1,4 @@ -#![warn(missing_debug_implementations)] +#![warn(missing_debug_implementations, clippy::todo)] #![allow(clippy::single_match)] // Hides console window on windows #![windows_subsystem = "windows"] @@ -35,6 +35,7 @@ pub(crate) use canvas::RnCanvas; pub(crate) use canvasmenu::RnCanvasMenu; pub(crate) use canvaswrapper::RnCanvasWrapper; pub(crate) use colorpicker::RnColorPicker; +pub(crate) use dialogs::recovery::RnRecoveryAction; pub(crate) use groupediconpicker::RnGroupedIconPicker; pub(crate) use iconpicker::RnIconPicker; pub(crate) use mainheader::RnMainHeader; From 09cf022f04ab36f7c1f94bb0610cf9698422b196 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Wed, 12 Jul 2023 17:30:02 +0200 Subject: [PATCH 30/61] finish recovery dialog --- .../src/fileformats/recovery_metadata.rs | 67 ++++++----- rnote-ui/data/ui/dialogs/recovery.ui | 13 ++- rnote-ui/src/appwindow/imp.rs | 13 ++- rnote-ui/src/appwindow/mod.rs | 6 +- rnote-ui/src/dialogs/recovery.rs | 106 +++++++++++------- 5 files changed, 131 insertions(+), 74 deletions(-) diff --git a/rnote-engine/src/fileformats/recovery_metadata.rs b/rnote-engine/src/fileformats/recovery_metadata.rs index e3a0a8ba0b..a6b7258bc1 100644 --- a/rnote-engine/src/fileformats/recovery_metadata.rs +++ b/rnote-engine/src/fileformats/recovery_metadata.rs @@ -2,6 +2,7 @@ use anyhow::Context; use serde::{Deserialize, Serialize}; use std::{ cell::{Cell, RefCell}, + fs::remove_file, path::{Path, PathBuf}, }; @@ -36,6 +37,40 @@ impl FileFormatLoader for RecoveryMetadata { } impl RecoveryMetadata { + /// Get the path to the file being backed up + pub fn document_path(&self) -> Option { + self.document_path.borrow().clone() + } + /// Remove recovery file and metadata from disk + pub fn delete(&self) { + if let Err(e) = remove_file(&self.recovery_file_path) { + log::error!( + "Failed to delete recovery file {}: {e}", + self.recovery_file_path.display() + ) + }; + if let Err(e) = remove_file(&self.metdata_path) { + log::error!( + "Failed to delete recovery metadata {}: {e}", + self.metdata_path.display() + ) + } + } + /// Get the last changed date as unix timestamp + pub fn last_changed(&self) -> i64 { + self.last_changed.get() + } + /// Load instance from given Path + pub fn load_from_path(path: impl AsRef) -> anyhow::Result { + let path = path.as_ref(); + let bytes = std::fs::read(path).context("Failed to read file")?; + Self::load_from_bytes(&bytes) + } + /// Get the metadata path + pub fn metadata_path(&self) -> PathBuf { + self.metdata_path.clone() + } + /// Create new Cargo metadata pub fn new(metadata_path: impl Into, rnote_path: impl Into) -> Self { let out = Self { @@ -48,15 +83,18 @@ impl RecoveryMetadata { out.update_last_changed(); out } - pub fn load_from_path(path: impl AsRef) -> anyhow::Result { - let path = path.as_ref(); - let bytes = std::fs::read(path).context("Failed to read file")?; - Self::load_from_bytes(&bytes) + /// Get the path to Recovery file + pub fn recovery_file_path(&self) -> PathBuf { + self.recovery_file_path.clone() } /// Save recovery data pub fn save(&self) -> anyhow::Result> { self.save_as_bytes(self.metdata_path.to_str().unwrap()) } + /// Get the document title + pub fn title(&self) -> Option { + self.title.borrow().clone() + } /// Update Metadate based of the given document option pub fn update(&self, document_path: &Option) { self.update_last_changed(); @@ -71,7 +109,6 @@ impl RecoveryMetadata { None => (), }; } - /// Replace last_changed with the current unix time pub(crate) fn update_last_changed(&self) { self.last_changed.replace( @@ -81,24 +118,4 @@ impl RecoveryMetadata { .as_secs() as i64, ); } - /// Get the metadata path - pub fn metadata_path(&self) -> PathBuf { - self.metdata_path.clone() - } - /// Get the path to the file being backed up - pub fn document_path(&self) -> Option { - self.document_path.borrow().clone() - } - /// Get the path to Recovery file - pub fn recovery_file_path(&self) -> PathBuf { - self.recovery_file_path.clone() - } - /// Get the last changed date as unix timestamp - pub fn last_changed(&self) -> i64 { - self.last_changed.get() - } - /// Get the document title - pub fn title(&self) -> Option { - self.title.borrow().clone() - } } diff --git a/rnote-ui/data/ui/dialogs/recovery.ui b/rnote-ui/data/ui/dialogs/recovery.ui index a253660893..5433e85a0f 100644 --- a/rnote-ui/data/ui/dialogs/recovery.ui +++ b/rnote-ui/data/ui/dialogs/recovery.ui @@ -2,17 +2,20 @@ Recover Documents - Look like Rnote crashed last time. - We recovered some documents.Please select what you want to do with them: - save - cancel + Oops, looks like Rnote crashed last time! + We recovered documents. + Please select what you want to do with them: + confirm + show_later Recovered Documents - Confirm + Show Later + Discard All + Apply \ No newline at end of file diff --git a/rnote-ui/src/appwindow/imp.rs b/rnote-ui/src/appwindow/imp.rs index 8574f9520c..1261eadd4e 100644 --- a/rnote-ui/src/appwindow/imp.rs +++ b/rnote-ui/src/appwindow/imp.rs @@ -29,7 +29,7 @@ pub(crate) struct RnAppWindow { pub(crate) autosave_interval_secs: Cell, pub(crate) recovery: Cell, pub(crate) recovery_interval_secs: Cell, - pub(crate) recovery_actions: RefCell>, + pub(crate) recovery_actions: RefCell>>, pub(crate) righthanded: Cell, pub(crate) block_pinch_zoom: Cell, pub(crate) touch_drawing: Cell, @@ -78,7 +78,7 @@ impl Default for RnAppWindow { autosave_interval_secs: Cell::new(super::RnAppWindow::AUTOSAVE_INTERVAL_DEFAULT), recovery: Cell::new(true), recovery_interval_secs: Cell::new(super::RnAppWindow::RECOVERY_INTERVAL_DEFAULT), - recovery_actions: RefCell::new(Vec::new()), + recovery_actions: RefCell::new(None), righthanded: Cell::new(true), block_pinch_zoom: Cell::new(false), touch_drawing: Cell::new(false), @@ -340,6 +340,15 @@ impl RnAppWindow { if let Some(removed_id) = self.recovery_source_id.borrow_mut().replace(glib::source::timeout_add_seconds_local(self.recovery_interval_secs.get(), clone!(@weak obj as appwindow => @default-return glib::source::Continue(false), move || { let canvas = appwindow.active_tab().canvas(); + if canvas.output_file().is_some() && appwindow.autosave() { + // Delete recovery files from disk to avoid suggesting the user an outdated file on next boot + canvas.imp().recovery_file_metadata.borrow_mut().iter_mut().for_each(|meta| { + meta.delete(); + }); + // We keep the metadata path in the canvas to make sure it doesnt change when the user toggles autosave. + // This would otherwise lead to confusing timestamps on next launch. + return glib::source::Continue(true); + } glib::MainContext::default().spawn_local(clone!(@weak canvas, @weak appwindow => async move { if !canvas.unsaved_changes_recovery() {return;} diff --git a/rnote-ui/src/appwindow/mod.rs b/rnote-ui/src/appwindow/mod.rs index 226ffb32f9..e77869e2b5 100644 --- a/rnote-ui/src/appwindow/mod.rs +++ b/rnote-ui/src/appwindow/mod.rs @@ -36,7 +36,11 @@ impl RnAppWindow { } pub(crate) fn set_recovery_action(&self, i: usize, action: RnRecoveryAction) { - self.imp().recovery_actions.borrow_mut()[i] = dbg!(action) + self.imp() + .recovery_actions + .borrow_mut() + .as_mut() + .expect("Recovery actions not set")[i] = action } #[allow(unused)] diff --git a/rnote-ui/src/dialogs/recovery.rs b/rnote-ui/src/dialogs/recovery.rs index f9418e2190..9b9498a6f8 100644 --- a/rnote-ui/src/dialogs/recovery.rs +++ b/rnote-ui/src/dialogs/recovery.rs @@ -8,23 +8,28 @@ use gtk4::{prelude::FileExt, traits::ToggleButtonExt}; use gtk4::{ subclass::prelude::ObjectSubclassIsExt, traits::GtkWindowExt, Builder, FileDialog, ToggleButton, }; -use std::{ffi::OsStr, fs::read_dir, path::PathBuf}; +use std::{ + ffi::OsStr, + fs::remove_file, + path::{Path, PathBuf}, +}; use time::{format_description::well_known::Rfc2822, OffsetDateTime}; use crate::{appwindow::RnAppWindow, config, env::recovery_dir}; use rnote_engine::fileformats::recovery_metadata::RecoveryMetadata; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub(crate) enum RnRecoveryAction { Discard, SaveAs(PathBuf), - Keep, + ShowLater, + #[default] Open, } pub(crate) async fn dialog_recover_documents(appwindow: &RnAppWindow) { - let files = get_files(); - if files.is_empty() { + let metadata_found = find_metadata(); + if metadata_found.is_empty() { log::debug!("No recovery files found"); // return; } @@ -36,11 +41,11 @@ pub(crate) async fn dialog_recover_documents(appwindow: &RnAppWindow) { let recover_documents_group: adw::PreferencesGroup = builder.object("recover_documents_group").unwrap(); dialog.set_transient_for(Some(appwindow)); - appwindow - .imp() - .recovery_actions - .replace([(); 4].map(|_| RnRecoveryAction::Discard).to_vec()); - for (i, metadata) in files.iter().enumerate() { + appwindow.imp().recovery_actions.replace(Some(vec![ + RnRecoveryAction::default(); + metadata_found.len() + ])); + for (i, metadata) in metadata_found.iter().enumerate() { // let recovery_row: RnRecoveryRow = RnRecoveryRow::new(); // recovery_row.init(appwindow, metadata.clone()); let row: adw::ActionRow = adw::ActionRow::builder() @@ -51,21 +56,21 @@ pub(crate) async fn dialog_recover_documents(appwindow: &RnAppWindow) { let open_button = ToggleButton::builder() .icon_name("tab-new-filled-symbolic") .tooltip_text("Recover document in new tab") + .active(true) .build(); let save_as_button = ToggleButton::builder() .icon_name("doc-save-symbolic") .tooltip_text("Save file to selected path") .group(&open_button) .build(); - let keep_button = ToggleButton::builder() + let show_later_button = ToggleButton::builder() .icon_name("workspacelistentryicon-clock-symbolic") - .tooltip_text("Ask me again next session") + .tooltip_text("Show option again next launch") .group(&open_button) .build(); let discard_button = ToggleButton::builder() .icon_name("trash-empty") .tooltip_text("Discard document") - .active(true) .group(&open_button) .build(); discard_button.connect_toggled(clone!(@weak appwindow => move |button| { @@ -103,9 +108,9 @@ pub(crate) async fn dialog_recover_documents(appwindow: &RnAppWindow) { } })); })); - keep_button.connect_toggled(clone!(@weak appwindow => move |button| { + show_later_button.connect_toggled(clone!(@weak appwindow => move |button| { if button.is_active(){ - appwindow.set_recovery_action(i, RnRecoveryAction::Keep) + appwindow.set_recovery_action(i, RnRecoveryAction::ShowLater) } })); // recover_document_button.connect_clicked(); @@ -116,17 +121,36 @@ pub(crate) async fn dialog_recover_documents(appwindow: &RnAppWindow) { // })); row.add_suffix(&open_button); row.add_suffix(&save_as_button); + row.add_suffix(&show_later_button); row.add_suffix(&discard_button); recover_documents_group.add(&row); rows.push(row); } - dialog.choose_future().await; + let choice = dialog.choose_future().await; + let mut actions = appwindow.imp().recovery_actions.replace(None).unwrap(); + assert_eq!(metadata_found.len(), actions.len()); + match choice.as_str() { + "discard_all" => actions.fill(RnRecoveryAction::Discard), + "show_later" => actions.fill(RnRecoveryAction::ShowLater), + "apply" => actions.fill(RnRecoveryAction::ShowLater), + c => unimplemented!("unknown coice {}", c), + }; + for (i, meta) in metadata_found.iter().enumerate() { + match &actions[i] { + RnRecoveryAction::Discard => discard(meta), + RnRecoveryAction::ShowLater => (), + RnRecoveryAction::Open => todo!(), + RnRecoveryAction::SaveAs(target) => save_as(meta, target), + } + } } -fn get_files() -> Vec { +fn find_metadata() -> Vec { let mut recovery_files = Vec::new(); let recovery_ext: &OsStr = OsStr::new("json"); - for file in read_dir(recovery_dir().expect("Failed to get recovery dir")) + for file in recovery_dir() + .expect("Failed to get recovery dir") + .read_dir() .expect("failed to read recovery dir") { let file = file.expect("Failed to get DirEntry"); @@ -154,26 +178,26 @@ fn format_unix_timestamp(unix: i64) -> String { } } -// pub(crate) async fn discard(appwindow: &RnAppWindow, i: usize) /*-> gio::SimpleAction*/ -// { -// let action_discard_file = gio::SimpleAction::new("discard", None); -// action_discard_file.connect_activate( -// clone!(@weak appwindow => move |_action_discard_file, _| { -// let medata = appwindow.imp().recovered_documents.borrow().get(i); -// if metadata.is_some() && imp.meta.borrow().is_some() { -// // Unwrapping should be safe here since the condition makes sure they're not None -// let meta = imp.meta.replace(None).unwrap(); -// let meta_path = imp.meta_path.replace(None).unwrap(); - -// if let Err(e) = remove_file(meta.recovery_file_path()){ -// log::error!("Failed to remove recovery file {}: {e}", meta.recovery_file_path().display()) -// }; -// if let Err(e) = remove_file(meta_path.path().unwrap()){ -// log::error!("Failed to remove recovery file {}: {e}", meta_path) -// }; -// } -// }), -// ); - -// // action_discard_file -// } +pub(crate) fn discard(meta: &RecoveryMetadata) { + if let Err(e) = remove_file(meta.recovery_file_path()) { + log::error!( + "Failed to remove recovery file {}: {e}", + meta.recovery_file_path().display() + ) + }; + if let Err(e) = remove_file(meta.metadata_path()) { + log::error!( + "Failed to remove recovery file {}: {e}", + meta.metadata_path().display() + ) + }; +} +pub(crate) fn save_as(meta: &RecoveryMetadata, target: &Path) { + if let Err(e) = std::fs::rename(meta.recovery_file_path(), target) { + log::error!( + "Failed to move recovered document from {} to {}, because {e}", + meta.recovery_file_path().display(), + target.display() + ) + } +} From d3b0d1a417da94fd562dd67355f0929f2d345a5c Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Wed, 12 Jul 2023 23:06:50 +0200 Subject: [PATCH 31/61] removed recoveryrow --- rnote-ui/data/ui/recoveryrow.ui | 59 ------ rnote-ui/src/recoveryrow/actions/discard.rs | 29 --- rnote-ui/src/recoveryrow/actions/mod.rs | 7 - rnote-ui/src/recoveryrow/actions/recover.rs | 5 - rnote-ui/src/recoveryrow/actions/save_as.rs | 36 ---- rnote-ui/src/recoveryrow/mod.rs | 188 -------------------- 6 files changed, 324 deletions(-) delete mode 100644 rnote-ui/data/ui/recoveryrow.ui delete mode 100644 rnote-ui/src/recoveryrow/actions/discard.rs delete mode 100644 rnote-ui/src/recoveryrow/actions/mod.rs delete mode 100644 rnote-ui/src/recoveryrow/actions/recover.rs delete mode 100644 rnote-ui/src/recoveryrow/actions/save_as.rs delete mode 100644 rnote-ui/src/recoveryrow/mod.rs diff --git a/rnote-ui/data/ui/recoveryrow.ui b/rnote-ui/data/ui/recoveryrow.ui deleted file mode 100644 index 67a323cce4..0000000000 --- a/rnote-ui/data/ui/recoveryrow.ui +++ /dev/null @@ -1,59 +0,0 @@ - - - - - diff --git a/rnote-ui/src/recoveryrow/actions/discard.rs b/rnote-ui/src/recoveryrow/actions/discard.rs deleted file mode 100644 index 34f88ac9bd..0000000000 --- a/rnote-ui/src/recoveryrow/actions/discard.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Imports -use crate::recoveryrow::RnRecoveryRow; -use cairo::glib::{self, clone}; -use gtk4::{gio, prelude::FileExt, subclass::prelude::ObjectSubclassIsExt}; -use std::fs::remove_file; - -pub(crate) async fn discard(recoveryrow: &RnRecoveryRow) /*-> gio::SimpleAction*/ -{ - let action_discard_file = gio::SimpleAction::new("discard", None); - action_discard_file.connect_activate( - clone!(@weak recoveryrow => move |_action_discard_file, _| { - let imp = recoveryrow.imp(); - if imp.meta_path.borrow().is_some() && imp.meta.borrow().is_some() { - // Unwrapping should be safe here since the condition makes sure they're not None - let meta = imp.meta.replace(None).unwrap(); - let meta_path = imp.meta_path.replace(None).unwrap(); - - if let Err(e) = remove_file(meta.recovery_file_path()){ - log::error!("Failed to remove recovery file {}: {e}", meta.recovery_file_path().display()) - }; - if let Err(e) = remove_file(meta_path.path().unwrap()){ - log::error!("Failed to remove recovery file {}: {e}", meta_path) - }; - } - }), - ); - - // action_discard_file -} diff --git a/rnote-ui/src/recoveryrow/actions/mod.rs b/rnote-ui/src/recoveryrow/actions/mod.rs deleted file mode 100644 index 63e5a036fd..0000000000 --- a/rnote-ui/src/recoveryrow/actions/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod discard; -mod recover; -mod save_as; - -pub(crate) use discard::discard; -pub(crate) use recover::recover; -pub(crate) use save_as::save_as; diff --git a/rnote-ui/src/recoveryrow/actions/recover.rs b/rnote-ui/src/recoveryrow/actions/recover.rs deleted file mode 100644 index 9d098fb7ae..0000000000 --- a/rnote-ui/src/recoveryrow/actions/recover.rs +++ /dev/null @@ -1,5 +0,0 @@ -use crate::{appwindow::RnAppWindow, recoveryrow::RnRecoveryRow}; - -pub(crate) async fn recover(recoveryrow: &RnRecoveryRow, appwindow: &RnAppWindow) { - todo!() -} diff --git a/rnote-ui/src/recoveryrow/actions/save_as.rs b/rnote-ui/src/recoveryrow/actions/save_as.rs deleted file mode 100644 index 34fc54da99..0000000000 --- a/rnote-ui/src/recoveryrow/actions/save_as.rs +++ /dev/null @@ -1,36 +0,0 @@ -use gettextrs::gettext; -use gtk4::{prelude::FileExt, subclass::prelude::ObjectSubclassIsExt, FileDialog, FileFilter}; - -use crate::{appwindow::RnAppWindow, recoveryrow::RnRecoveryRow}; - -pub(crate) async fn save_as(recoveryrow: &RnRecoveryRow, appwindow: &RnAppWindow) { - let filter = FileFilter::new(); - filter.add_mime_type("application/rnote"); - filter.add_suffix("rnote"); - filter.set_name(Some(&gettext(".rnote"))); - - let filedialog = FileDialog::builder() - .title("Save recovered file as...") - .accept_label(gettext("Save")) - .modal(true) - .default_filter(&filter) - .build(); - match filedialog.save_future(Some(appwindow)).await { - Ok(f) => { - std::fs::copy( - recoveryrow - .imp() - .meta - .borrow() - .as_ref() - .unwrap() - .recovery_file_path(), - f.path().unwrap(), - ) - .unwrap(); - } - Err(e) => { - log::error!("Failed to save revovery file as: {e}") - } - } -} diff --git a/rnote-ui/src/recoveryrow/mod.rs b/rnote-ui/src/recoveryrow/mod.rs deleted file mode 100644 index 4ac96aad83..0000000000 --- a/rnote-ui/src/recoveryrow/mod.rs +++ /dev/null @@ -1,188 +0,0 @@ -// modules -mod actions; - -// Imports -use adw::{ - prelude::{ButtonExt, WidgetExt}, - subclass::prelude::CompositeTemplateDisposeExt, -}; -use cairo::glib::{self, clone, ToValue}; -use gtk4::{ - gio, - subclass::{ - prelude::{ - ObjectImpl, ObjectImplExt, ObjectSubclass, ObjectSubclassExt, ObjectSubclassIsExt, - WidgetClassSubclassExt, - }, - widget::{CompositeTemplate, CompositeTemplateInitializingExt, WidgetImpl}, - }, - Button, CompositeTemplate, Label, TemplateChild, Widget, -}; -use once_cell::sync::Lazy; -use std::cell::RefCell; -use time::{format_description::well_known::Rfc2822, OffsetDateTime}; - -use crate::appwindow::RnAppWindow; -use rnote_engine::fileformats::recovery_metadata::RecoveryMetadata; - -mod imp { - - use super::*; - - #[derive(Debug, CompositeTemplate, Default)] - #[template(resource = "/com/github/flxzt/rnote/ui/recoveryrow.ui")] - pub(crate) struct RnRecoveryRow { - pub(crate) last_changed_format: String, - pub(crate) meta: RefCell>, - pub(crate) meta_path: RefCell>, - - #[template_child] - pub(crate) document_name_label: TemplateChild + + Overwrite File + + overwrite + cancel + + Cancel + Overwrite + Select new .. + + + New Document Creating a new document will discard any unsaved changes. diff --git a/rnote-ui/src/dialogs/mod.rs b/rnote-ui/src/dialogs/mod.rs index d04760ef11..68b96ff028 100644 --- a/rnote-ui/src/dialogs/mod.rs +++ b/rnote-ui/src/dialogs/mod.rs @@ -19,6 +19,7 @@ use gtk4::{ gio, glib, glib::clone, Builder, Button, CheckButton, ColorDialogButton, Dialog, FileDialog, Label, MenuButton, ResponseType, ShortcutsWindow, StringList, }; +use std::path::Path; // Re-exports pub(crate) use recovery::dialog_recover_documents; @@ -96,6 +97,26 @@ pub(crate) async fn dialog_clear_doc(appwindow: &RnAppWindow, canvas: &RnCanvas) } } +/// Display a dialog confirming a file overwrite. +/// +/// The file needs to be supplied as Path or PathBuf. +/// The return value is "cancel", "selectnew" or "overwrite" +pub(crate) async fn dialog_confirm_overwrite_document( + appwindow: &RnAppWindow, + path: impl AsRef, +) -> glib::GString { + let builder = Builder::from_resource( + (String::from(config::APP_IDPATH) + "ui/dialogs/dialogs.ui").as_str(), + ); + let dialog: adw::MessageDialog = builder.object("dialog_confirm_overwrite_file").unwrap(); + dialog.set_transient_for(Some(appwindow)); + dialog.set_body(&format!( + "File {} already exits!\n Overwrite existing file?", + path.as_ref().display() + )); + dialog.choose_future().await +} + pub(crate) async fn dialog_new_doc(appwindow: &RnAppWindow, canvas: &RnCanvas) { let builder = Builder::from_resource( (String::from(config::APP_IDPATH) + "ui/dialogs/dialogs.ui").as_str(), From ce348b95a8e713445666477c554526136115e374 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Sat, 15 Jul 2023 18:56:53 +0200 Subject: [PATCH 46/61] only remove metadata when recovery_file moved/invalid --- rnote-engine/src/fileformats/recovery_metadata.rs | 5 +++++ rnote-ui/src/dialogs/recovery.rs | 9 +++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/rnote-engine/src/fileformats/recovery_metadata.rs b/rnote-engine/src/fileformats/recovery_metadata.rs index 4caa3557df..91cdb5f538 100644 --- a/rnote-engine/src/fileformats/recovery_metadata.rs +++ b/rnote-engine/src/fileformats/recovery_metadata.rs @@ -66,6 +66,10 @@ impl RnRecoveryMetadata { self.recovery_file_path.display() ) }; + self.delete_meta() + } + /// Remove recovery metadata from disk + pub fn delete_meta(&self) { if let Err(e) = remove_file(&self.metadata_path) { log::error!( "Failed to delete recovery metadata {}: {e}", @@ -73,6 +77,7 @@ impl RnRecoveryMetadata { ) } } + /// Get the creation date as unix timestamp pub fn crated(&self) -> u64 { self.created diff --git a/rnote-ui/src/dialogs/recovery.rs b/rnote-ui/src/dialogs/recovery.rs index 907cafec15..e037e48103 100644 --- a/rnote-ui/src/dialogs/recovery.rs +++ b/rnote-ui/src/dialogs/recovery.rs @@ -221,13 +221,8 @@ pub(crate) async fn dialog_recover_documents(appwindow: &RnAppWindow) { RnRecoveryAction::Open => open(appwindow, meta), RnRecoveryAction::SaveAs(target) => { save_as(&meta, target); - discard(meta) - } - RnRecoveryAction::CleanInvalid => { - if let Err(e) = remove_file(&meta.metadata_path()) { - log::error!("Failedro delete {}, {e}", meta.metadata_path().display()) - } } + RnRecoveryAction::CleanInvalid => meta.delete_meta(), } } } @@ -296,6 +291,8 @@ pub(crate) fn save_as(meta: &RnRecoveryMetadata, target: &Path) { meta.recovery_file_path().display(), target.display() ) + } else { + meta.delete_meta(); } } From 834810ed4f6bcd7c325710273ded29c515d35328 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Sat, 15 Jul 2023 19:19:32 +0200 Subject: [PATCH 47/61] ask before overwriting when saving recovered file --- rnote-ui/src/dialogs/recovery.rs | 75 ++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/rnote-ui/src/dialogs/recovery.rs b/rnote-ui/src/dialogs/recovery.rs index e037e48103..2f8e7f2d3c 100644 --- a/rnote-ui/src/dialogs/recovery.rs +++ b/rnote-ui/src/dialogs/recovery.rs @@ -152,24 +152,11 @@ pub(crate) async fn dialog_recover_documents(appwindow: &RnAppWindow) { if !button.is_active(){ return; } - glib::MainContext::default().spawn_local(clone!(@weak appwindow => async move { - let filedialog = FileDialog::builder() - .title("Save recovered file as...") - .accept_label(gettext("Save")) - .modal(true) - .build(); - - match filedialog.save_future(Some(&appwindow)).await { - Ok(f) => { - let mut path = f.path().unwrap(); - if path.extension() != Some(OsStr::new("rnote")){ - path.set_extension("rnote"); - } - appwindow.set_recovery_action(i, RnRecoveryAction::SaveAs(path)) - } - Err(e) => { - log::error!("Failed to get save path for revovery file: {e}") - } + glib::MainContext::default().spawn_local(clone!(@weak appwindow, @weak button => async move { + if let Some(path) = get_save_as_path(&appwindow).await { + appwindow.set_recovery_action(i, RnRecoveryAction::SaveAs(path)) + }else{ + button.set_active(false); } })); })); @@ -284,6 +271,58 @@ fn format_unix_timestamp(unix: u64) -> String { pub(crate) fn discard(meta: RnRecoveryMetadata) { meta.delete() } + +pub(crate) async fn get_save_as_path(appwindow: &RnAppWindow) -> Option { + let filedialog = FileDialog::builder() + .title("Save recovered file as...") + .accept_label(gettext("Save")) + .modal(true) + .build(); + if let Some(dir) = appwindow.workspacebrowser().dirlist_dir() { + filedialog.set_initial_folder(Some(&gio::File::for_path(dir))); + } + let mut initial_name: Option = None; + let mut cancel = false; + let mut out = None; + while out.is_none() && !cancel { + filedialog.set_initial_name(initial_name.as_deref()); + match filedialog.save_future(Some(appwindow)).await { + Ok(f) => { + let Some(mut path) = f.path() else { + log::error!("Failed to parse defined file no path"); + // Cancel + return None; + }; + if path.extension() != Some(OsStr::new("rnote")) { + path.set_extension("rnote"); + } + if path.exists() { + match super::dialog_confirm_overwrite_document(appwindow, &path) + .await + .as_str() + { + "overwrite" => out = Some(path), + "cancel" => cancel = true, + "selectnew" => { + if let Some(name) = path.file_name().and_then(|name| name.to_str()) { + initial_name = Some(name.to_string()) + } + } + r => unimplemented!("{r}"), + } + } else { + out = Some(path) + } + } + Err(e) => { + log::error!("Failed to get save path for revovery file: {e}"); + cancel = true; + } + } + } + out +} + pub(crate) fn save_as(meta: &RnRecoveryMetadata, target: &Path) { if let Err(e) = std::fs::rename(meta.recovery_file_path(), target) { log::error!( From 40a7d29e4d69e7e8945e3df79937307ebc0e55fb Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Sun, 16 Jul 2023 17:49:05 +0200 Subject: [PATCH 48/61] allow meson to monitor changes in recovery.rs --- rnote-ui/src/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/rnote-ui/src/meson.build b/rnote-ui/src/meson.build index 6681dbf3eb..8ef577c3e3 100644 --- a/rnote-ui/src/meson.build +++ b/rnote-ui/src/meson.build @@ -78,6 +78,7 @@ rnote_ui_sources = files( 'dialogs/mod.rs', 'dialogs/import.rs', 'dialogs/export.rs', + 'dialogs/recovery.rs', 'appmenu.rs', 'canvasmenu.rs', 'canvaswrapper.rs', From ec3c77941d823949063beffffee1089dde839765 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Sun, 16 Jul 2023 22:40:13 +0200 Subject: [PATCH 49/61] adaot to upstream changes --- .gitignore | 3 +- Cargo.lock | 62 ++++++++++++++++++++++++++++---- Cargo.toml | 1 + rnote-ui/Cargo.toml | 1 + rnote-ui/src/appwindow/imp.rs | 6 ++-- rnote-ui/src/appwindow/mod.rs | 2 +- rnote-ui/src/dialogs/recovery.rs | 24 +++++-------- 7 files changed, 71 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 37368f95d6..e227fb0425 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,4 @@ *.code-workspace .idea/ .toggletasks.json -*.flatpak -justfile +*.flatpak \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 19ceca07e4..602d5880f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1028,6 +1028,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "downcast-rs" version = "1.2.0" @@ -2694,6 +2715,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "optional" version = "0.5.0" @@ -2806,7 +2833,7 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", "windows-targets 0.48.1", ] @@ -3370,6 +3397,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -3379,6 +3415,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.10", + "redox_syscall 0.2.16", + "thiserror", +] + [[package]] name = "regex" version = "1.9.1" @@ -3424,6 +3471,7 @@ dependencies = [ "anyhow", "base64", "cairo-rs", + "directories", "fs_extra", "futures", "gettext-rs", @@ -3457,7 +3505,7 @@ dependencies = [ "serde_json", "svg", "thiserror", - "time 0.3.22", + "time 0.3.23", "unicode-segmentation", "url", "winresource", @@ -4186,7 +4234,7 @@ dependencies = [ "autocfg", "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix 0.37.23", "windows-sys 0.48.0", ] @@ -4276,9 +4324,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" dependencies = [ "itoa", "libc", @@ -4296,9 +4344,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" dependencies = [ "time-core", ] diff --git a/Cargo.toml b/Cargo.toml index 916a0c48d7..185ffc9804 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,7 @@ gettext-rs = { version = "0.7", features = ["gettext-system"] } gtk4 = { version = "0.6", features = ["v4_10"] } adw = { version = "0.4", package="libadwaita", features = ["v1_3"] } time = { version = "0.3.22", default-features = false, features = ["formatting", "local-offset"] } +directories = "5.0.1" [patch.crates-io] # once a new piet (current v0.6.2) is released with the updated cairo, this can be removed diff --git a/rnote-ui/Cargo.toml b/rnote-ui/Cargo.toml index a3009fe02e..e833ae181f 100644 --- a/rnote-ui/Cargo.toml +++ b/rnote-ui/Cargo.toml @@ -50,6 +50,7 @@ gettext-rs = {workspace = true } gtk4 = { workspace = true } adw = { workspace = true } time = { workspace = true } +directories = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/rnote-ui/src/appwindow/imp.rs b/rnote-ui/src/appwindow/imp.rs index 56b489df74..3dc766e504 100644 --- a/rnote-ui/src/appwindow/imp.rs +++ b/rnote-ui/src/appwindow/imp.rs @@ -340,7 +340,7 @@ impl RnAppWindow { if let Some(removed_id) = self.recovery_source_id.borrow_mut().replace(glib::source::timeout_add_seconds_local(self.recovery_interval_secs.get(), clone!(@weak obj as appwindow => @default-return glib::source::Continue(false), move || { - let canvas = appwindow.active_tab().canvas(); + let canvas = appwindow.active_tab_wrapper().canvas(); glib::MainContext::default().spawn_local(clone!(@weak canvas, @weak appwindow => async move { // Doing both recovery and autosaves of saved files serves little advatage but could lead to slowdowns or conflicts if canvas.output_file().is_some() && appwindow.autosave() { @@ -354,14 +354,14 @@ impl RnAppWindow { } else if canvas.unsaved_changes_recovery() { canvas.set_recovery_paused(false); let recovery_file = canvas.get_or_generate_recovery_file(); - appwindow.overlays().start_pulsing_progressbar(); + appwindow.overlays().progressbar_start_pulsing(); canvas.set_recovery_in_progress(true); if let Err(e) = canvas.save_document_to_file(&recovery_file).await { log::error!("saving document failed, Error: `{e:?}`"); appwindow.overlays().dispatch_toast_error(&gettext("Saving document failed")); } canvas.set_recovery_in_progress(false); - appwindow.overlays().finish_progressbar(); + appwindow.overlays().progressbar_finish(); } })); glib::source::Continue(true) diff --git a/rnote-ui/src/appwindow/mod.rs b/rnote-ui/src/appwindow/mod.rs index 9010197613..cc11aec954 100644 --- a/rnote-ui/src/appwindow/mod.rs +++ b/rnote-ui/src/appwindow/mod.rs @@ -532,7 +532,7 @@ impl RnAppWindow { let (bytes, _) = input_file.load_bytes_future().await?; wrapper .canvas() - .load_in_rnote_bytes(bytes.to_vec(), input_file.path()) + .load_in_rnote_bytes(bytes.to_vec(), input_file.path(), None) .await?; if rnote_file_new_tab { appwindow.append_wrapper_new_tab(&wrapper); diff --git a/rnote-ui/src/dialogs/recovery.rs b/rnote-ui/src/dialogs/recovery.rs index 2f8e7f2d3c..f7aca0ac64 100644 --- a/rnote-ui/src/dialogs/recovery.rs +++ b/rnote-ui/src/dialogs/recovery.rs @@ -2,7 +2,7 @@ use adw::{ prelude::MessageDialogExtManual, traits::{ActionRowExt, MessageDialogExt, PreferencesGroupExt}, }; -use cairo::glib::{self, clone, Cast}; +use cairo::glib::{self, clone}; use gettextrs::gettext; use gtk4::{ gdk::Display, @@ -20,7 +20,7 @@ use std::{ }; use time::{format_description::well_known::Rfc2822, OffsetDateTime}; -use crate::{appwindow::RnAppWindow, canvaswrapper::RnCanvasWrapper, config, env::recovery_dir}; +use crate::{appwindow::RnAppWindow, config, env::recovery_dir}; use rnote_engine::RnRecoveryMetadata; #[derive(Clone, Debug, Default)] @@ -40,7 +40,7 @@ pub(crate) async fn dialog_recovery_info(appwindow: &RnAppWindow) { let dialog: adw::MessageDialog = builder.object("dialog_recovery_info").unwrap(); dialog.set_transient_for(Some(appwindow)); dialog.set_modal(true); - let canvas = appwindow.active_tab().canvas(); + let canvas = appwindow.active_tab_wrapper().canvas(); let info = { let recovery = appwindow.recovery(); let autosave = appwindow.autosave(); @@ -118,7 +118,7 @@ pub(crate) async fn dialog_recover_documents(appwindow: &RnAppWindow) { .build(); if valid { let open_button = ToggleButton::builder() - .icon_name("tab-new-filled-symbolic") + .icon_name("tab-new-symbolic") .tooltip_text("Recover document in new tab") .active(true) .build(); @@ -337,18 +337,11 @@ pub(crate) fn save_as(meta: &RnRecoveryMetadata, target: &Path) { pub(crate) fn open(appwindow: &RnAppWindow, meta: RnRecoveryMetadata) { let file = gio::File::for_path(meta.recovery_file_path()); - let canvas = { - // open a new tab for rnote files - let new_tab = appwindow.new_tab(); - new_tab - .child() - .downcast::() - .unwrap() - .canvas() - }; + let wrapper = appwindow.new_canvas_wrapper(); + let canvas = wrapper.canvas(); glib::MainContext::default().spawn_local(clone!(@weak canvas, @weak appwindow => async move { - appwindow.overlays().start_pulsing_progressbar(); + appwindow.overlays().progressbar_start_pulsing(); match file.load_bytes_future().await { Ok((bytes, _)) => { if let Err(e) = canvas.load_in_rnote_bytes(bytes.to_vec(), file.path(), Some(meta)).await { @@ -358,6 +351,7 @@ pub(crate) fn open(appwindow: &RnAppWindow, meta: RnRecoveryMetadata) { } Err(e) => log::error!("failed to load bytes, Err: {e:?}"), } - appwindow.overlays().finish_progressbar(); + appwindow.overlays().progressbar_finish(); })); + appwindow.append_wrapper_new_tab(&wrapper); } From e77f802bc1b43b1c95dbf5ae568829ea1a8d51a3 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Sun, 16 Jul 2023 23:09:36 +0200 Subject: [PATCH 50/61] renamed overwrite dialog fn to match ui definiton --- rnote-ui/src/dialogs/mod.rs | 2 +- rnote-ui/src/dialogs/recovery.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rnote-ui/src/dialogs/mod.rs b/rnote-ui/src/dialogs/mod.rs index bd51d195c5..f688005851 100644 --- a/rnote-ui/src/dialogs/mod.rs +++ b/rnote-ui/src/dialogs/mod.rs @@ -101,7 +101,7 @@ pub(crate) async fn dialog_clear_doc(appwindow: &RnAppWindow, canvas: &RnCanvas) /// /// The file needs to be supplied as Path or PathBuf. /// The return value is "cancel", "selectnew" or "overwrite" -pub(crate) async fn dialog_confirm_overwrite_document( +pub(crate) async fn dialog_confirm_overwrite_file( appwindow: &RnAppWindow, path: impl AsRef, ) -> glib::GString { diff --git a/rnote-ui/src/dialogs/recovery.rs b/rnote-ui/src/dialogs/recovery.rs index f7aca0ac64..a4c34b4ceb 100644 --- a/rnote-ui/src/dialogs/recovery.rs +++ b/rnote-ui/src/dialogs/recovery.rs @@ -297,7 +297,7 @@ pub(crate) async fn get_save_as_path(appwindow: &RnAppWindow) -> Option path.set_extension("rnote"); } if path.exists() { - match super::dialog_confirm_overwrite_document(appwindow, &path) + match super::dialog_confirm_overwrite_file(appwindow, &path) .await .as_str() { From 84878df241916d22521a0e1a97b45446e7e350a1 Mon Sep 17 00:00:00 2001 From: LeSnake Date: Sun, 30 Jul 2023 00:24:04 +0200 Subject: [PATCH 51/61] fix merge --- Cargo.lock | 24 +++++++++++------------- crates/rnote-ui/src/canvas/mod.rs | 1 - 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8819380db2..5ddd6de178 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -567,9 +567,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "215c0072ecc28f92eeb0eea38ba63ddfcb65c2828c46311d646f1a3ff5f9841c" +checksum = "b40ccee03b5175c18cde8f37e7d2a33bcef6f8ec8f7cc0d81090d1bb380949c9" dependencies = [ "smallvec", "target-lexicon", @@ -3432,9 +3432,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" dependencies = [ "aho-corasick", "memchr", @@ -3792,18 +3792,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.177" +version = "1.0.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63ba2516aa6bf82e0b19ca8b50019d52df58455d3cf9bdaf6315225fdd0c560a" +checksum = "60363bdd39a7be0266a520dab25fdc9241d2f987b08a01e01f0ec6d06a981348" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.177" +version = "1.0.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "401797fe7833d72109fedec6bfcbe67c0eed9b99772f26eb8afd261f0abc6fd3" +checksum = "f28482318d6641454cb273da158647922d1be6b5a2fcc6165cd89ebdd7ed576b" dependencies = [ "proc-macro2", "quote", @@ -4224,11 +4224,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" dependencies = [ "cfg-if", -==== BASE ==== - "fastrand", - "redox_syscall", - "rustix 0.37.23", -==== BASE ==== + "fastrand 2.0.0", + "redox_syscall 0.3.5", + "rustix 0.38.4", "windows-sys 0.48.0", ] diff --git a/crates/rnote-ui/src/canvas/mod.rs b/crates/rnote-ui/src/canvas/mod.rs index 4c4faf8bb0..fc3e900a08 100644 --- a/crates/rnote-ui/src/canvas/mod.rs +++ b/crates/rnote-ui/src/canvas/mod.rs @@ -22,7 +22,6 @@ use p2d::bounding_volume::Aabb; use rnote_compose::ext::AabbExt; use rnote_compose::penevents::PenState; use rnote_engine::ext::GrapheneRectExt; -use rnote_engine::Document; use rnote_engine::{Document, RnRecoveryMetadata, RnoteEngine, WidgetFlags}; use std::cell::{Cell, Ref, RefCell, RefMut}; From b8a0e1a8f9f01827c7e7e148012b34c5a31173c3 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Mon, 2 Oct 2023 22:41:45 +0200 Subject: [PATCH 52/61] update lockfile --- Cargo.lock | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 730aeb6732..5838f7dffc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -966,6 +966,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41b319d1b62ffbd002e057f36bebd1f42b9f97927c9577461d855f3513c4289f" +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + [[package]] name = "derive_builder" version = "0.11.2" @@ -1020,6 +1026,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "downcast-rs" version = "1.2.0" @@ -2600,6 +2627,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -2684,6 +2720,12 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "optional" version = "0.5.0" @@ -2794,7 +2836,7 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", "windows-targets 0.48.5", ] @@ -3240,6 +3282,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -3249,6 +3300,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall 0.2.16", + "thiserror", +] + [[package]] name = "regex" version = "1.9.5" @@ -3294,6 +3356,7 @@ dependencies = [ "anyhow", "base64", "cairo-rs", + "directories", "fs_extra", "futures", "gettext-rs", @@ -3327,6 +3390,7 @@ dependencies = [ "serde_json", "svg", "thiserror", + "time", "unicode-segmentation", "url", "winresource", @@ -4059,7 +4123,7 @@ checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand 2.0.1", - "redox_syscall", + "redox_syscall 0.3.5", "rustix 0.38.14", "windows-sys 0.48.0", ] @@ -4136,6 +4200,36 @@ dependencies = [ "weezl", ] +[[package]] +name = "time" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +dependencies = [ + "deranged", + "itoa", + "libc", + "num_threads", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + [[package]] name = "tiny-skia-path" version = "0.10.0" From 513e4dc15d72f2ddb77b72fdd7fb8cc680a1ba36 Mon Sep 17 00:00:00 2001 From: LeSnake Date: Sun, 5 Nov 2023 14:01:54 +0100 Subject: [PATCH 53/61] added crash item to developer menu --- crates/rnote-ui/data/ui/appmenu.ui | 4 ++++ crates/rnote-ui/src/appwindow/appwindowactions.rs | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/crates/rnote-ui/data/ui/appmenu.ui b/crates/rnote-ui/data/ui/appmenu.ui index 8b79fe237f..3cdfb735e8 100644 --- a/crates/rnote-ui/data/ui/appmenu.ui +++ b/crates/rnote-ui/data/ui/appmenu.ui @@ -64,6 +64,10 @@ Get _Recovery Info win.debug-recovery-info + + Crash App + win.crash-app + diff --git a/crates/rnote-ui/src/appwindow/appwindowactions.rs b/crates/rnote-ui/src/appwindow/appwindowactions.rs index 887bc64730..c28eae0ba6 100644 --- a/crates/rnote-ui/src/appwindow/appwindowactions.rs +++ b/crates/rnote-ui/src/appwindow/appwindowactions.rs @@ -57,6 +57,8 @@ impl RnAppWindow { let action_debug_display_recovery_info = gio::SimpleAction::new("debug-recovery-info", None); self.add_action(&action_debug_display_recovery_info); + let action_crash_app = gio::SimpleAction::new("crash-app", None); + self.add_action(&action_crash_app); let action_righthanded = gio::PropertyAction::new("righthanded", self, "righthanded"); self.add_action(&action_righthanded); let action_touch_drawing = gio::PropertyAction::new("touch-drawing", self, "touch-drawing"); @@ -270,6 +272,11 @@ impl RnAppWindow { }), ); + // Crash App + action_crash_app.connect_activate( + clone!(@weak self as appwindow => move |_, _| panic!("Test Crash") ), + ); + // Doc layout action_doc_layout.connect_activate( clone!(@weak self as appwindow => move |action_doc_layout, target| { From 2a4a1145ec4ca858d992e6f278bcd4b703ee486e Mon Sep 17 00:00:00 2001 From: LeSnake Date: Mon, 13 Nov 2023 16:28:54 +0100 Subject: [PATCH 54/61] small changes --- crates/rnote-ui/src/appwindow/mod.rs | 5 +++-- crates/rnote-ui/src/canvas/imexport.rs | 10 +++------- crates/rnote-ui/src/canvas/mod.rs | 6 +++++- crates/rnote-ui/src/dialogs/mod.rs | 1 + crates/rnote-ui/src/dialogs/recovery.rs | 4 ++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/crates/rnote-ui/src/appwindow/mod.rs b/crates/rnote-ui/src/appwindow/mod.rs index 0f334a2d24..3627f557b3 100644 --- a/crates/rnote-ui/src/appwindow/mod.rs +++ b/crates/rnote-ui/src/appwindow/mod.rs @@ -5,8 +5,9 @@ mod imp; // Imports use crate::{ - config, dialogs, FileType, RnApp, RnCanvas, RnCanvasWrapper, RnMainHeader, RnOverlays, - RnSidebar, + config, + dialogs::{self, RnRecoveryAction}, + FileType, RnApp, RnCanvas, RnCanvasWrapper, RnMainHeader, RnOverlays, RnSidebar, }; use adw::{prelude::*, subclass::prelude::*}; use gettextrs::gettext; diff --git a/crates/rnote-ui/src/canvas/imexport.rs b/crates/rnote-ui/src/canvas/imexport.rs index c1db48a680..a99a309a57 100644 --- a/crates/rnote-ui/src/canvas/imexport.rs +++ b/crates/rnote-ui/src/canvas/imexport.rs @@ -30,18 +30,13 @@ impl RnCanvas { let engine_snapshot = EngineSnapshot::load_from_rnote_bytes(bytes).await?; let widget_flags = self.engine_mut().load_snapshot(engine_snapshot); - let mut widget_flags = self.engine_mut().load_snapshot(engine_snapshot); - let mut unsaved_changes = false; if let Some(meta) = &recovery_metadata { self.set_output_file(meta.document_path().map(gio::File::for_path)); unsaved_changes = true; - self.dismiss_output_file_modified_toast(); self.set_unsaved_changes_recovery(false); - } else if let Some(file_path) = file_path { - let file = gio::File::for_path(file_path); - self.dismiss_output_file_modified_toast(); } + self.dismiss_output_file_modified_toast(); self.set_output_file(file_path.map(gio::File::for_path)); self.set_recovery_metadata(recovery_metadata); @@ -54,6 +49,7 @@ impl RnCanvas { /// /// If the origin file is set to None, this does nothing and returns an error. pub(crate) async fn reload_from_disk(&self) -> anyhow::Result<()> { + let recovery_metadata = self.recovery_metadata(); let Some(output_file) = self.output_file() else { return Err(anyhow::anyhow!( "Failed to reload file from disk, no file path saved." @@ -61,7 +57,7 @@ impl RnCanvas { }; let (bytes, _) = output_file.load_bytes_future().await?; let widget_flags = self - .load_in_rnote_bytes(bytes.to_vec(), output_file.path()) + .load_in_rnote_bytes(bytes.to_vec(), output_file.path(), recovery_metadata) .await?; self.emit_handle_widget_flags(widget_flags); Ok(()) diff --git a/crates/rnote-ui/src/canvas/mod.rs b/crates/rnote-ui/src/canvas/mod.rs index b54d6f594b..ae6df4d720 100644 --- a/crates/rnote-ui/src/canvas/mod.rs +++ b/crates/rnote-ui/src/canvas/mod.rs @@ -22,7 +22,7 @@ use rnote_compose::ext::AabbExt; use rnote_compose::penevent::PenState; use rnote_engine::ext::GraphenePointExt; use rnote_engine::ext::GrapheneRectExt; -use rnote_engine::{Camera, RnRecoveryMetadata, Engine, WidgetFlags}; +use rnote_engine::{Camera, Engine, RnRecoveryMetadata, WidgetFlags}; use std::cell::{Cell, Ref, RefCell, RefMut}; #[derive(Debug, Default)] @@ -667,6 +667,10 @@ impl RnCanvas { } } + pub fn recovery_metadata(&self) -> Option { + self.imp().recovery_metadata.borrow().as_ref().cloned() + } + #[allow(unused)] pub(crate) fn output_file(&self) -> Option { self.property::>("output-file") diff --git a/crates/rnote-ui/src/dialogs/mod.rs b/crates/rnote-ui/src/dialogs/mod.rs index 3eb26f6cee..49edb3bee7 100644 --- a/crates/rnote-ui/src/dialogs/mod.rs +++ b/crates/rnote-ui/src/dialogs/mod.rs @@ -24,6 +24,7 @@ use std::path::Path; // Re-exports pub(crate) use recovery::dialog_recover_documents; pub(crate) use recovery::dialog_recovery_info; +pub(crate) use recovery::RnRecoveryAction; // About Dialog pub(crate) fn dialog_about(appwindow: &RnAppWindow) { diff --git a/crates/rnote-ui/src/dialogs/recovery.rs b/crates/rnote-ui/src/dialogs/recovery.rs index a4c34b4ceb..fd54745ba1 100644 --- a/crates/rnote-ui/src/dialogs/recovery.rs +++ b/crates/rnote-ui/src/dialogs/recovery.rs @@ -223,7 +223,7 @@ fn find_metadata() -> Vec { { let Ok(file) = file else { log::error!("failed to get DirEntry"); - continue + continue; }; // clean up .rnote files without metadata in the recovery dir // they are usally a result of broken recovery metadata @@ -278,7 +278,7 @@ pub(crate) async fn get_save_as_path(appwindow: &RnAppWindow) -> Option .accept_label(gettext("Save")) .modal(true) .build(); - if let Some(dir) = appwindow.workspacebrowser().dirlist_dir() { + if let Some(dir) = appwindow.sidebar().workspacebrowser().dirlist_dir() { filedialog.set_initial_folder(Some(&gio::File::for_path(dir))); } let mut initial_name: Option = None; From b92f5ba2701bee388ec18ea6a26af4f7bc9b777a Mon Sep 17 00:00:00 2001 From: LeSnake Date: Mon, 13 Nov 2023 17:29:00 +0100 Subject: [PATCH 55/61] fix merge --- Cargo.lock | 104 +++++++++++++---------- crates/rnote-ui/src/appwindow/imp.rs | 23 ++--- crates/rnote-ui/src/settingspanel/mod.rs | 12 ++- 3 files changed, 77 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85fe46816f..e1dfb470f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,6 +183,19 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-channel" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37875bd9915b7d67c2f117ea2c30a0989874d0b2cb694fe25403c85763c0c9e" +dependencies = [ + "concurrent-queue", + "event-listener 3.1.0", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-executor" version = "1.6.0" @@ -235,7 +248,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41ed9d5715c2d329bf1b4da8d60455b99b187f27ba726df2883799af9af60997" dependencies = [ - "async-lock 3.0.0", + "async-lock 3.1.0", "cfg-if", "concurrent-queue", "futures-io", @@ -260,11 +273,11 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e900cdcd39bb94a14487d3f7ef92ca222162e6c7c3fe7cb3550ea75fb486ed" +checksum = "deb2ab2aa8a746e221ab826c73f48bc6ba41be6763f0855cb249eb6d154cf1d7" dependencies = [ - "event-listener 3.0.1", + "event-listener 3.1.0", "event-listener-strategy", "pin-project-lite", ] @@ -291,7 +304,7 @@ dependencies = [ "async-signal", "blocking", "cfg-if", - "event-listener 3.0.1", + "event-listener 3.1.0", "futures-lite 1.13.0", "rustix 0.38.21", "windows-sys 0.48.0", @@ -538,16 +551,16 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "blocking" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ - "async-channel", - "async-lock 2.8.0", + "async-channel 2.1.0", + "async-lock 3.1.0", "async-task", "fastrand 2.0.1", "futures-io", - "futures-lite 1.13.0", + "futures-lite 2.0.1", "piper", "tracing", ] @@ -609,11 +622,10 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" dependencies = [ - "jobserver", "libc", ] @@ -1010,9 +1022,12 @@ checksum = "41b319d1b62ffbd002e057f36bebd1f42b9f97927c9577461d855f3513c4289f" [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] [[package]] name = "derive_builder" @@ -1200,9 +1215,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "3.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cec0252c2afff729ee6f00e903d479fba81784c8e2bd77447673471fdfaea1" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" dependencies = [ "concurrent-queue", "parking", @@ -1215,7 +1230,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d96b852f1345da36d551b9473fa1e2b1eb5c5195585c6c018118bc92a8d91160" dependencies = [ - "event-listener 3.0.1", + "event-listener 3.1.0", "pin-project-lite", ] @@ -2168,15 +2183,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jobserver" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" -dependencies = [ - "libc", -] - [[package]] name = "jpeg-decoder" version = "0.3.0" @@ -2282,6 +2288,17 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", +] + [[package]] name = "librsvg" version = "2.57.0" @@ -2796,12 +2813,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "optional" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978aa494585d3ca4ad74929863093e87cac9790d81fe7aba2b3dc2890643a0fc" - [[package]] name = "owo-colors" version = "3.5.0" @@ -2906,7 +2917,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "smallvec", "windows-targets 0.48.5", ] @@ -3186,6 +3197,12 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -3377,9 +3394,7 @@ checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" [[package]] name = "redox_syscall" -==== BASE ==== -version = "0.3.5" -==== BASE ==== +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ @@ -3388,12 +3403,12 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] @@ -3916,7 +3931,7 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-executor", "async-fs", "async-io 1.13.0", @@ -4205,7 +4220,7 @@ checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand 2.0.1", - "redox_syscall 0.3.5", + "redox_syscall", "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -4284,14 +4299,15 @@ dependencies = [ [[package]] name = "time" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", "libc", "num_threads", + "powerfmt", "serde", "time-core", "time-macros", diff --git a/crates/rnote-ui/src/appwindow/imp.rs b/crates/rnote-ui/src/appwindow/imp.rs index 6355354a7d..ce85374f2c 100644 --- a/crates/rnote-ui/src/appwindow/imp.rs +++ b/crates/rnote-ui/src/appwindow/imp.rs @@ -1,5 +1,5 @@ // Imports -use crate::{config, dialogs,RnRecoveryAction, RnMainHeader, RnOverlays, RnSidebar}; +use crate::{config, dialogs, RnMainHeader, RnOverlays, RnRecoveryAction, RnSidebar}; use adw::{prelude::*, subclass::prelude::*}; use gettextrs::gettext; use gtk4::{ @@ -293,11 +293,11 @@ impl RnAppWindow { log::error!("saving document failed, Error: `{e:?}`"); appwindow.overlays().dispatch_toast_error(&gettext("Saving document failed")); } - } - )); - } - glib::ControlFlow::Continue(true) - }))) { + })); + } + glib::ControlFlow::Continue + }) + )) { removed_id.remove(); } } @@ -306,7 +306,7 @@ impl RnAppWindow { let obj = self.obj(); if let Some(removed_id) = self.recovery_source_id.borrow_mut().replace(glib::source::timeout_add_seconds_local(self.recovery_interval_secs.get(), - clone!(@weak obj as appwindow => @default-return glib::source::Continue(false), move || { + clone!(@weak obj as appwindow => @default-return glib::ControlFlow::Break, move || { let canvas = appwindow.active_tab_wrapper().canvas(); glib::MainContext::default().spawn_local(clone!(@weak canvas, @weak appwindow => async move { // Doing both recovery and autosaves of saved files serves little advatage but could lead to slowdowns or conflicts @@ -331,10 +331,11 @@ impl RnAppWindow { appwindow.overlays().progressbar_finish(); } })); - glib::ControlFlow:::Continue(true) - }))) { - removed_id.remove(); - } + glib::ControlFlow::Continue + }) + )) { + removed_id.remove(); + } } fn setup_input(&self) { diff --git a/crates/rnote-ui/src/settingspanel/mod.rs b/crates/rnote-ui/src/settingspanel/mod.rs index a95642f63d..ac5835c0bf 100644 --- a/crates/rnote-ui/src/settingspanel/mod.rs +++ b/crates/rnote-ui/src/settingspanel/mod.rs @@ -38,11 +38,9 @@ mod imp { #[template_child] pub(crate) general_show_scrollbars_row: TemplateChild, #[template_child] - pub(crate) general_recovery_enable_switch: TemplateChild, + pub(crate) general_recovery_row: TemplateChild, #[template_child] - pub(crate) general_recovery_interval_secs_row: TemplateChild, - #[template_child] - pub(crate) general_recovery_interval_secs_spinbutton: TemplateChild, + pub(crate) general_recovery_interval_secs_row: TemplateChild, #[template_child] pub(crate) general_inertial_scrolling_row: TemplateChild, #[template_child] @@ -475,13 +473,13 @@ impl RnSettingsPanel { .build(); // recovery enable switch - imp.general_recovery_enable_switch + imp.general_recovery_row .bind_property("state", appwindow, "recovery") .sync_create() .bidirectional() .build(); - imp.general_recovery_enable_switch + imp.general_recovery_row .get() .bind_property( "state", @@ -491,7 +489,7 @@ impl RnSettingsPanel { .sync_create() .build(); - imp.general_recovery_interval_secs_spinbutton + imp.general_autosave_interval_secs_row .get() .bind_property("value", appwindow, "recovery-interval-secs") .transform_to(|_, val: f64| Some((val.round() as u32).to_value())) From e7d2c8c2f11ab798b5924708875babf08f36c363 Mon Sep 17 00:00:00 2001 From: LeSnake Date: Mon, 13 Nov 2023 17:35:53 +0100 Subject: [PATCH 56/61] fmt --- crates/rnote-ui/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rnote-ui/src/main.rs b/crates/rnote-ui/src/main.rs index c15d95c095..245ee4e691 100644 --- a/crates/rnote-ui/src/main.rs +++ b/crates/rnote-ui/src/main.rs @@ -39,8 +39,8 @@ pub(crate) use canvas::RnCanvas; pub(crate) use canvasmenu::RnCanvasMenu; pub(crate) use canvaswrapper::RnCanvasWrapper; pub(crate) use colorpicker::RnColorPicker; -pub(crate) use filetype::FileType; pub(crate) use dialogs::recovery::RnRecoveryAction; +pub(crate) use filetype::FileType; pub(crate) use groupediconpicker::RnGroupedIconPicker; pub(crate) use iconpicker::RnIconPicker; pub(crate) use mainheader::RnMainHeader; From 3154c03ea891d79958983aa7cdd2e4e194b434c1 Mon Sep 17 00:00:00 2001 From: LeSnake Date: Mon, 13 Nov 2023 17:45:16 +0100 Subject: [PATCH 57/61] move recoverymetadata --- crates/rnote-engine/src/fileformats/mod.rs | 2 +- .../fileformats/{recovery_metadata.rs => recoverymetadata.rs} | 0 crates/rnote-engine/src/lib.rs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename crates/rnote-engine/src/fileformats/{recovery_metadata.rs => recoverymetadata.rs} (100%) diff --git a/crates/rnote-engine/src/fileformats/mod.rs b/crates/rnote-engine/src/fileformats/mod.rs index 9fab4c61ca..1cf0072290 100644 --- a/crates/rnote-engine/src/fileformats/mod.rs +++ b/crates/rnote-engine/src/fileformats/mod.rs @@ -1,5 +1,5 @@ // Modules -pub mod recovery_metadata; +pub mod recoverymetadata; pub mod rnoteformat; pub mod xoppformat; diff --git a/crates/rnote-engine/src/fileformats/recovery_metadata.rs b/crates/rnote-engine/src/fileformats/recoverymetadata.rs similarity index 100% rename from crates/rnote-engine/src/fileformats/recovery_metadata.rs rename to crates/rnote-engine/src/fileformats/recoverymetadata.rs diff --git a/crates/rnote-engine/src/lib.rs b/crates/rnote-engine/src/lib.rs index 0954059c0a..257fdecaf2 100644 --- a/crates/rnote-engine/src/lib.rs +++ b/crates/rnote-engine/src/lib.rs @@ -33,7 +33,7 @@ pub use document::Document; pub use drawable::Drawable; pub use drawable::DrawableOnDoc; pub use engine::Engine; -pub use fileformats::recovery_metadata::RnRecoveryMetadata; +pub use fileformats::recoverymetadata::RnRecoveryMetadata; pub use pens::PenHolder; pub use selectioncollision::SelectionCollision; pub use store::StrokeStore; From 8be57fed4c0330c1e9821e233003a6410429591f Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Fri, 17 Nov 2023 12:25:45 +0100 Subject: [PATCH 58/61] renamed recovery metadata --- crates/rnote-engine/src/fileformats/mod.rs | 2 +- .../{recoverymetadata.rs => recoverymetadataformat.rs} | 0 crates/rnote-engine/src/lib.rs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename crates/rnote-engine/src/fileformats/{recoverymetadata.rs => recoverymetadataformat.rs} (100%) diff --git a/crates/rnote-engine/src/fileformats/mod.rs b/crates/rnote-engine/src/fileformats/mod.rs index 1cf0072290..6c836af5b9 100644 --- a/crates/rnote-engine/src/fileformats/mod.rs +++ b/crates/rnote-engine/src/fileformats/mod.rs @@ -1,5 +1,5 @@ // Modules -pub mod recoverymetadata; +pub mod recoverymetadataformat; pub mod rnoteformat; pub mod xoppformat; diff --git a/crates/rnote-engine/src/fileformats/recoverymetadata.rs b/crates/rnote-engine/src/fileformats/recoverymetadataformat.rs similarity index 100% rename from crates/rnote-engine/src/fileformats/recoverymetadata.rs rename to crates/rnote-engine/src/fileformats/recoverymetadataformat.rs diff --git a/crates/rnote-engine/src/lib.rs b/crates/rnote-engine/src/lib.rs index 257fdecaf2..c2e1816b66 100644 --- a/crates/rnote-engine/src/lib.rs +++ b/crates/rnote-engine/src/lib.rs @@ -33,7 +33,7 @@ pub use document::Document; pub use drawable::Drawable; pub use drawable::DrawableOnDoc; pub use engine::Engine; -pub use fileformats::recoverymetadata::RnRecoveryMetadata; +pub use fileformats::recoverymetadataformat::RnRecoveryMetadata; pub use pens::PenHolder; pub use selectioncollision::SelectionCollision; pub use store::StrokeStore; From 364849be0ff05990e21d4fa982352c09f4d78be4 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Mon, 27 Nov 2023 08:30:30 +0100 Subject: [PATCH 59/61] add rnoterecoveryformat based on bincode --- Cargo.lock | 10 +++++ Cargo.toml | 1 + crates/rnote-engine/Cargo.toml | 1 + crates/rnote-engine/src/fileformats/mod.rs | 1 + .../src/fileformats/rnoterecoveryformat.rs | 43 +++++++++++++++++++ 5 files changed, 56 insertions(+) create mode 100644 crates/rnote-engine/src/fileformats/rnoterecoveryformat.rs diff --git a/Cargo.lock b/Cargo.lock index e1dfb470f8..6a13f6efac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -505,6 +505,15 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.68.1" @@ -3552,6 +3561,7 @@ dependencies = [ "anyhow", "approx", "base64", + "bincode", "cairo-rs", "chrono", "clap", diff --git a/Cargo.toml b/Cargo.toml index 58da8be3b4..e88f407acb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,6 +82,7 @@ adw = { version = "0.5.3", package="libadwaita", features = ["v1_4"] } numeric-sort = "0.1" time = { version = "0.3.22", default-features = false, features = ["formatting", "local-offset"] } directories = "5.0.1" +bincode = "1.3.3" [patch.crates-io] # once a new piet (current v0.6.2) is released with the updated cairo, this can be removed diff --git a/crates/rnote-engine/Cargo.toml b/crates/rnote-engine/Cargo.toml index 9fba942434..649e39d2ec 100644 --- a/crates/rnote-engine/Cargo.toml +++ b/crates/rnote-engine/Cargo.toml @@ -56,6 +56,7 @@ poppler-rs = { workspace = true } # the long-term plan is to remove the gtk4 dependency entirely after switching to another renderer. gtk4 = { workspace = true, optional = true } clap = { workspace = true, optional = true } +bincode = { workspace = true } [dev-dependencies] approx = { workspace = true } diff --git a/crates/rnote-engine/src/fileformats/mod.rs b/crates/rnote-engine/src/fileformats/mod.rs index 6c836af5b9..05cbe20869 100644 --- a/crates/rnote-engine/src/fileformats/mod.rs +++ b/crates/rnote-engine/src/fileformats/mod.rs @@ -1,6 +1,7 @@ // Modules pub mod recoverymetadataformat; pub mod rnoteformat; +pub mod rnoterecoveryformat; pub mod xoppformat; // Imports diff --git a/crates/rnote-engine/src/fileformats/rnoterecoveryformat.rs b/crates/rnote-engine/src/fileformats/rnoterecoveryformat.rs new file mode 100644 index 0000000000..33f42fafab --- /dev/null +++ b/crates/rnote-engine/src/fileformats/rnoterecoveryformat.rs @@ -0,0 +1,43 @@ +use anyhow::Context; + +use crate::engine::EngineSnapshot; + +use super::{FileFormatLoader, FileFormatSaver}; + +#[derive(Debug)] +pub struct RnoteRecoveryFile { + pub engine_snapshot: EngineSnapshot, +} + +// impl From<&Engine> for RnoteRecoveryFile { +// fn from(value: &Engine) -> Self { +// Self { +// engine_snapshot: bincode::serialize(value).unwrap(), +// } +// } +// } + +impl FileFormatSaver for RnoteRecoveryFile { + fn save_as_bytes(&self, _file_name: &str) -> anyhow::Result> { + let bytes = bincode::serialize(&self.engine_snapshot)?; + Ok(bytes) + } +} + +impl FileFormatLoader for RnoteRecoveryFile { + fn load_from_bytes(bytes: &[u8]) -> anyhow::Result + where + Self: Sized, + { + Ok(Self { + engine_snapshot: bincode::deserialize(bytes) + .context("Failed to load recovery snapshot")?, + }) + } +} + +// impl From for Engine { +// fn from(val: RnoteRecoveryFile) -> Self { +// bincode::deserialize(&val.engine_snapshot).unwrap() +// } +// } From f0316fe205dd07271e205568d518db41b9d14007 Mon Sep 17 00:00:00 2001 From: LeSnake04 Date: Mon, 27 Nov 2023 09:07:00 +0100 Subject: [PATCH 60/61] fix: import clone --- crates/rnote-ui/src/appwindow/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/rnote-ui/src/appwindow/mod.rs b/crates/rnote-ui/src/appwindow/mod.rs index faef1db911..62bf3ec199 100644 --- a/crates/rnote-ui/src/appwindow/mod.rs +++ b/crates/rnote-ui/src/appwindow/mod.rs @@ -10,6 +10,7 @@ use crate::{ FileType, RnApp, RnCanvas, RnCanvasWrapper, RnMainHeader, RnOverlays, RnSidebar, }; use adw::{prelude::*, subclass::prelude::*}; +use cairo::glib::clone; use gettextrs::gettext; use gtk4::{gdk, gio, glib, Application, IconTheme}; use rnote_compose::Color; From c1f504c3fab344385b1af4b47b3fac53f1ccb689 Mon Sep 17 00:00:00 2001 From: LeSnake Date: Thu, 1 Feb 2024 23:03:36 +0100 Subject: [PATCH 61/61] use recovery format when restoring --- crates/rnote-engine/src/engine/export.rs | 19 +++++ crates/rnote-engine/src/engine/snapshot.rs | 21 +++++- .../src/fileformats/recoverymetadataformat.rs | 2 +- crates/rnote-ui/data/ui/settingspanel.ui | 32 +++----- crates/rnote-ui/src/appwindow/imp.rs | 74 +++++++++---------- crates/rnote-ui/src/canvas/imexport.rs | 20 +++-- crates/rnote-ui/src/canvas/mod.rs | 65 ++++++++-------- crates/rnote-ui/src/settingspanel/mod.rs | 5 +- 8 files changed, 134 insertions(+), 104 deletions(-) diff --git a/crates/rnote-engine/src/engine/export.rs b/crates/rnote-engine/src/engine/export.rs index 11beae7c4a..58a1086244 100644 --- a/crates/rnote-engine/src/engine/export.rs +++ b/crates/rnote-engine/src/engine/export.rs @@ -1,6 +1,7 @@ // Imports use super::{Engine, EngineConfig, StrokeContent}; use crate::fileformats::rnoteformat::RnoteFile; +use crate::fileformats::rnoterecoveryformat::RnoteRecoveryFile; use crate::fileformats::{xoppformat, FileFormatSaver}; use anyhow::Context; use futures::channel::oneshot; @@ -334,6 +335,24 @@ impl Engine { }); oneshot_receiver } + /// Save the current document as a .rnote~ file. + pub fn save_as_rnote_recovery_bytes( + &self, + file_name: String, + ) -> oneshot::Receiver>> { + let (oneshot_sender, oneshot_receiver) = oneshot::channel::>>(); + let engine_snapshot = self.take_snapshot(); + rayon::spawn(move || { + let result = || -> anyhow::Result> { + let rnote_file = RnoteRecoveryFile { engine_snapshot }; + rnote_file.save_as_bytes(&file_name) + }; + if let Err(_data) = oneshot_sender.send(result()) { + log::error!("Sending result to receiver in save_as_rnote_bytes() failed. Receiver was already dropped."); + } + }); + oneshot_receiver + } /// Extract the current engine configuration. pub fn extract_engine_config(&self) -> EngineConfig { diff --git a/crates/rnote-engine/src/engine/snapshot.rs b/crates/rnote-engine/src/engine/snapshot.rs index 109757bd41..747b9a8db9 100644 --- a/crates/rnote-engine/src/engine/snapshot.rs +++ b/crates/rnote-engine/src/engine/snapshot.rs @@ -1,7 +1,7 @@ // Imports use crate::document::background; use crate::engine::import::XoppImportPrefs; -use crate::fileformats::{rnoteformat, xoppformat, FileFormatLoader}; +use crate::fileformats::{rnoteformat, rnoterecoveryformat, xoppformat, FileFormatLoader}; use crate::store::{ChronoComponent, StrokeKey}; use crate::strokes::Stroke; use crate::{Camera, Document, Engine}; @@ -60,6 +60,25 @@ impl EngineSnapshot { snapshot_receiver.await? } + + pub async fn load_from_rnote_recovery_bytes(bytes: Vec) -> anyhow::Result { + let (snapshot_sender, snapshot_receiver) = oneshot::channel::>(); + + rayon::spawn(move || { + let result = || -> anyhow::Result { + let rnote_recovery_file = + rnoterecoveryformat::RnoteRecoveryFile::load_from_bytes(&bytes) + .context("loading RnoteRecoveryFile failed")?; + Ok(rnote_recovery_file.engine_snapshot) + }; + + if let Err(_data) = snapshot_sender.send(result()) { + log::error!("Sending result to receiver in open_from_rnote_bytes() failed. Receiver was already dropped."); + } + }); + + snapshot_receiver.await? + } /// Loads from the bytes of a Xournal++ .xopp file. /// /// To import this snapshot into the current engine, use [`Engine::load_snapshot()`]. diff --git a/crates/rnote-engine/src/fileformats/recoverymetadataformat.rs b/crates/rnote-engine/src/fileformats/recoverymetadataformat.rs index 91cdb5f538..0e55102c99 100644 --- a/crates/rnote-engine/src/fileformats/recoverymetadataformat.rs +++ b/crates/rnote-engine/src/fileformats/recoverymetadataformat.rs @@ -128,7 +128,7 @@ impl RnRecoveryMetadata { pub fn title(&self) -> Option { self.title.borrow().clone() } - /// Update Metadate based of the given document option + /// Update Metadata based of the given document option pub fn update(&self, document_path: &Option) { self.update_last_changed(); match document_path { diff --git a/crates/rnote-ui/data/ui/settingspanel.ui b/crates/rnote-ui/data/ui/settingspanel.ui index 199a968faa..ec0f227251 100644 --- a/crates/rnote-ui/data/ui/settingspanel.ui +++ b/crates/rnote-ui/data/ui/settingspanel.ui @@ -54,35 +54,17 @@ - + Recovery Enable or disable document recovery - - - center - - - + Recovery Interval (secs) Set the recovery interval in seconds - - - 1 - 9999 - 5 - 120 - - - general_recovery_interval_secs_adj - horizontal - false - center - 0 - - + general_recovery_interval_secs_adj + 0 @@ -561,5 +543,11 @@ on a drawing pad 5 120 + + 1 + 9999 + 5 + 120 + diff --git a/crates/rnote-ui/src/appwindow/imp.rs b/crates/rnote-ui/src/appwindow/imp.rs index 4760f3a56d..096119729e 100644 --- a/crates/rnote-ui/src/appwindow/imp.rs +++ b/crates/rnote-ui/src/appwindow/imp.rs @@ -280,21 +280,21 @@ impl RnAppWindow { let obj = self.obj(); if let Some(removed_id) = self.autosave_source_id.borrow_mut().replace(glib::source::timeout_add_seconds_local(self.autosave_interval_secs.get(), - clone!(@weak obj as appwindow => @default-return glib::ControlFlow::Break, move || { - let canvas = appwindow.active_tab_wrapper().canvas(); - - if let Some(output_file) = canvas.output_file() { - glib::MainContext::default().spawn_local(clone!(@weak canvas, @weak appwindow => async move { - if let Err(e) = canvas.save_document_to_file(&output_file).await { - canvas.set_output_file(None); - - log::error!("saving document failed, Error: `{e:?}`"); - appwindow.overlays().dispatch_toast_error(&gettext("Saving document failed")); - } - })); - } - glib::ControlFlow::Continue - }) + clone!(@weak obj as appwindow => @default-return glib::ControlFlow::Break, move || { + let canvas = appwindow.active_tab_wrapper().canvas(); + + if let Some(output_file) = canvas.output_file() { + glib::MainContext::default().spawn_local(clone!(@weak canvas, @weak appwindow => async move { + if let Err(e) = canvas.save_document_to_file(&output_file).await { + canvas.set_output_file(None); + + log::error!("saving document failed, Error: `{e:?}`"); + appwindow.overlays().dispatch_toast_error(&gettext("Saving document failed")); + } + })); + } + glib::ControlFlow::Continue + }) )) { removed_id.remove(); } @@ -305,31 +305,31 @@ impl RnAppWindow { if let Some(removed_id) = self.recovery_source_id.borrow_mut().replace(glib::source::timeout_add_seconds_local(self.recovery_interval_secs.get(), clone!(@weak obj as appwindow => @default-return glib::ControlFlow::Break, move || { - let canvas = appwindow.active_tab_wrapper().canvas(); + let canvas = appwindow.active_tab_wrapper().canvas(); + // Doing both recovery and autosaves of saved files serves little advantage and could lead to slowdowns or io conflicts + if canvas.output_file().is_some() && appwindow.autosave() { + // Delete recovery files from disk to avoid suggesting the user an outdated file on next boot + if let Some(meta) = &*canvas.imp().recovery_metadata.borrow_mut() { + meta.delete(); + }; + canvas.set_recovery_paused(true); + // We keep the metadata itself in the canvas to make sure the path doesn't change when the user toggles autosave + // which would lead to wrong timestamps in the recovery menu + } else if dbg!(canvas.unsaved_changes_recovery()) { glib::MainContext::default().spawn_local(clone!(@weak canvas, @weak appwindow => async move { - // Doing both recovery and autosaves of saved files serves little advatage but could lead to slowdowns or conflicts - if canvas.output_file().is_some() && appwindow.autosave() { - // Delete recovery files from disk to avoid suggesting the user an outdated file on next boot - if let Some(meta) = &*canvas.imp().recovery_metadata.borrow_mut() { - meta.delete(); - }; - canvas.set_recovery_paused(true); - // We keep the metadata itself in the canvas to make sure the path doesnt change when the user toggles autosave - // which would lead to confusing timestamps on next launch. - } else if canvas.unsaved_changes_recovery() { - canvas.set_recovery_paused(false); - let recovery_file = canvas.get_or_generate_recovery_file(); - appwindow.overlays().progressbar_start_pulsing(); - canvas.set_recovery_in_progress(true); - if let Err(e) = canvas.save_document_to_file(&recovery_file).await { - log::error!("saving document failed, Error: `{e:?}`"); - appwindow.overlays().dispatch_toast_error(&gettext("Saving document failed")); - } - canvas.set_recovery_in_progress(false); - appwindow.overlays().progressbar_finish(); + canvas.set_recovery_paused(false); + let recovery_file = canvas.get_or_generate_recovery_file(); + appwindow.overlays().progressbar_start_pulsing(); + canvas.set_recovery_in_progress(true); + if let Err(e) = canvas.save_document_to_file(&recovery_file).await { + log::error!("saving document failed, Error: `{e:?}`"); + appwindow.overlays().dispatch_toast_error(&gettext("Saving document failed")); } + canvas.set_recovery_in_progress(false); + appwindow.overlays().progressbar_finish(); })); - glib::ControlFlow::Continue + } + glib::ControlFlow::Continue }) )) { removed_id.remove(); diff --git a/crates/rnote-ui/src/canvas/imexport.rs b/crates/rnote-ui/src/canvas/imexport.rs index d8e89fbd4b..cb29fcf7bd 100644 --- a/crates/rnote-ui/src/canvas/imexport.rs +++ b/crates/rnote-ui/src/canvas/imexport.rs @@ -27,7 +27,12 @@ impl RnCanvas { where P: AsRef, { - let engine_snapshot = EngineSnapshot::load_from_rnote_bytes(bytes).await?; + dbg!(&recovery_metadata); + let engine_snapshot = if recovery_metadata.is_some() { + EngineSnapshot::load_from_rnote_recovery_bytes(bytes).await? + } else { + EngineSnapshot::load_from_rnote_bytes(bytes).await? + }; let mut widget_flags = self.engine_mut().load_snapshot(engine_snapshot); widget_flags |= self .engine_mut() @@ -268,10 +273,13 @@ impl RnCanvas { self.set_save_in_progress(true); - let rnote_bytes_receiver = self - .engine_ref() - .save_as_rnote_bytes(basename.to_string_lossy().to_string()); - + let rnote_bytes_receiver = if self.recovery_in_progress() { + self.engine_ref() + .save_as_rnote_recovery_bytes(basename.to_string_lossy().to_string()) + } else { + self.engine_ref() + .save_as_rnote_bytes(basename.to_string_lossy().to_string()) + }; let mut skip_set_output_file = false; if let Some(current_file_path) = self.output_file().and_then(|f| f.path()) { if same_file::is_same_file(current_file_path, file_path).unwrap_or(false) { @@ -304,7 +312,7 @@ impl RnCanvas { self.set_output_file_expect_write(false); return Err(e); } - self.update_recovery_file(); + self.update_recovery_metadata(); if self.recovery_in_progress() { self.set_unsaved_changes_recovery(false); diff --git a/crates/rnote-ui/src/canvas/mod.rs b/crates/rnote-ui/src/canvas/mod.rs index 21320d0a9a..09c5292e18 100644 --- a/crates/rnote-ui/src/canvas/mod.rs +++ b/crates/rnote-ui/src/canvas/mod.rs @@ -6,6 +6,7 @@ mod widgetflagsboxed; // Re-exports pub(crate) use canvaslayout::RnCanvasLayout; +use rand::Rng; pub(crate) use widgetflagsboxed::WidgetFlagsBoxed; // Imports @@ -68,6 +69,7 @@ mod imp { pub(crate) recovery_in_progress: Cell, pub(crate) recovery_metadata: RefCell>, + /// Only used for recovery info dialog pub(crate) recovery_paused: Cell, pub(crate) output_file: RefCell>, pub(crate) output_file_saved_modified_date_time: RefCell>, @@ -330,13 +332,17 @@ mod imp { "unsaved-changes" => { let unsaved_changes: bool = value.get().expect("The value needs to be of type `bool`"); + if unsaved_changes { + dbg!("ucr!"); + self.unsaved_changes_recovery.replace(true); + } self.unsaved_changes.replace(unsaved_changes); } "unsaved-changes-recovery" => { let unsaved_changes_recovery: bool = value.get().expect("The value needs to be of type `bool`"); self.unsaved_changes_recovery - .replace(unsaved_changes_recovery); + .replace(dbg!(unsaved_changes_recovery)); } "empty" => { let empty: bool = value.get().expect("The value needs to be of type `bool`"); @@ -637,37 +643,29 @@ impl RnCanvas { } pub(crate) fn get_or_generate_recovery_file(&self) -> gio::File { - if self.imp().recovery_metadata.borrow().is_none() { - let imp = self.imp(); - let recovery_path = crate::env::recovery_dir().expect("Failed to get recovery path"); - if !recovery_path.exists() { - std::fs::create_dir_all(&recovery_path).expect("Failed to create directory") - }; - let time = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("Failed to get unix time") - .as_secs(); - let name = format!("{time}.rnote"); - let mut recovery_file_path = recovery_path.join(&name); - // add incrementing suffix if nessary - let mut suffix = 1; - while recovery_file_path.exists() { - recovery_file_path = recovery_path.join(format!("{name}_{suffix}.rnote")); - suffix += 1; - } - let mut metadata_path = recovery_file_path.clone(); - metadata_path.set_extension("json"); - let metadata = RnRecoveryMetadata::new(time, metadata_path, recovery_file_path); - imp.recovery_metadata.replace(Some(metadata)); + match self.imp().recovery_metadata.borrow().as_ref() { + Some(meta) => return gio::File::for_path(meta.recovery_file_path()), + _ => (), } - gio::File::for_path( - self.imp() - .recovery_metadata - .borrow() - .as_ref() - .unwrap() - .recovery_file_path(), - ) + let recovery_path = crate::env::recovery_dir().expect("Failed to get recovery path"); + if !recovery_path.exists() { + std::fs::create_dir_all(&recovery_path).expect("Failed to create directory") + }; + let time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Failed to get unix time") + .as_secs(); + // Random Suffix to avoid 2 opened documents created in the same second overwriting each others recovery saves + // the chance of 2 documents being created in the same second and having the same suffix is neglegable + let suffix: u16 = rand::thread_rng().gen_range(1000..=9999); + let name = format!("{time}_{suffix}.rnoterecovery"); + let recovery_file_path = recovery_path.join(name); + let mut metadata_path = recovery_file_path.clone(); + metadata_path.set_extension("json"); + let metadata = RnRecoveryMetadata::new(time, metadata_path, recovery_file_path); + let recovery_file_path = metadata.recovery_file_path(); + self.imp().recovery_metadata.borrow_mut().replace(metadata); + gio::File::for_path(recovery_file_path) } /// Deletes recovery save and metadate from disk @@ -740,9 +738,6 @@ impl RnCanvas { #[allow(unused)] pub(crate) fn set_unsaved_changes(&self, unsaved_changes: bool) { - if unsaved_changes { - self.set_unsaved_changes_recovery(true); - } self.set_property("unsaved-changes", unsaved_changes.to_value()); } @@ -1403,7 +1398,7 @@ impl RnCanvas { na::point![f64::from(self.width()), f64::from(self.height())], ) } - pub(crate) fn update_recovery_file(&self) { + pub(crate) fn update_recovery_metadata(&self) { if let Some(m) = self.imp().recovery_metadata.borrow().as_ref() { m.update( &self diff --git a/crates/rnote-ui/src/settingspanel/mod.rs b/crates/rnote-ui/src/settingspanel/mod.rs index a1ad35ae33..e6f1449aa7 100644 --- a/crates/rnote-ui/src/settingspanel/mod.rs +++ b/crates/rnote-ui/src/settingspanel/mod.rs @@ -474,7 +474,8 @@ impl RnSettingsPanel { // recovery enable switch imp.general_recovery_row - .bind_property("state", appwindow, "recovery") + .get() + .bind_property("active", appwindow, "recovery") .sync_create() .bidirectional() .build(); @@ -482,7 +483,7 @@ impl RnSettingsPanel { imp.general_recovery_row .get() .bind_property( - "state", + "active", &*imp.general_recovery_interval_secs_row, "sensitive", )