diff --git a/Cargo.lock b/Cargo.lock index bf594c2..53dec01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "MirrorSphere" -version = "0.0.9" +version = "1.0.0-Alpha1" dependencies = [ "async-trait", "bincode", @@ -17,7 +17,6 @@ dependencies = [ "eframe", "egui-file-dialog", "font-kit", - "fs4", "futures", "image", "libc", @@ -32,7 +31,7 @@ dependencies = [ "sha2", "sha3", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.16", "tokio", "tokio-stream", "toml 0.9.4", @@ -430,9 +429,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -1612,17 +1611,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "fs4" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" -dependencies = [ - "rustix 1.0.7", - "tokio", - "windows-sys 0.59.0", -] - [[package]] name = "futures" version = "0.3.31" @@ -2426,9 +2414,9 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" dependencies = [ "libc", ] @@ -2489,7 +2477,7 @@ dependencies = [ "once_cell", "rustc-hash 1.1.0", "strum", - "thiserror 2.0.12", + "thiserror 2.0.16", "unicode-ident", ] @@ -3473,7 +3461,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.16", ] [[package]] @@ -3723,9 +3711,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -3926,12 +3914,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3994,7 +3982,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.16", "tokio", "tokio-stream", "tracing", @@ -4078,7 +4066,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.16", "tracing", "uuid", "whoami", @@ -4117,7 +4105,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.16", "tracing", "uuid", "whoami", @@ -4143,7 +4131,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.12", + "thiserror 2.0.16", "tracing", "url", "uuid", @@ -4292,11 +4280,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.16", ] [[package]] @@ -4312,9 +4300,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", @@ -4424,9 +4412,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.46.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1140bb80481756a8cbe10541f37433b459c5aa1e727b4c020fbfebdc25bf3ec4" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -4439,7 +4427,7 @@ dependencies = [ "slab", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4716,9 +4704,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -5070,7 +5058,7 @@ dependencies = [ "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.16", "wgpu-core-deps-windows-linux-android", "wgpu-hal", "wgpu-types", @@ -5100,7 +5088,7 @@ dependencies = [ "portable-atomic", "raw-window-handle", "renderdoc-sys", - "thiserror 2.0.12", + "thiserror 2.0.16", "wgpu-types", ] @@ -5114,7 +5102,7 @@ dependencies = [ "bytemuck", "js-sys", "log", - "thiserror 2.0.12", + "thiserror 2.0.16", "web-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 811e63f..470579f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,51 +1,50 @@ [package] name = "MirrorSphere" -version = "0.0.9" +version = "1.0.0-Alpha1" edition = "2024" build = "build.rs" authors = ["DaLaw2"] [dependencies] -async-trait = "0.1.88" +async-trait = "0.1.89" bincode = { version = "2.0.1", features = ["serde"] } blake2 = "0.10.6" blake3 = { version = "1.8.2", features = ["traits-preview"] } chrono = { version = "0.4.41", features = ["serde"] } -crossbeam-queue = "0.3.11" +crossbeam-queue = "0.3.12" dashmap = "6.1.0" digest = "0.10.7" eframe = "0.32.0" egui-file-dialog = "0.11.0" -fs4 = { version = "0.13.1", features = ["sync", "tokio"] } font-kit = "0.14.3" futures = "0.3.31" image = "0.25.6" -libc = "0.2.169" +libc = "0.2.174" log-panics = { version = "2.1.0", features = ["with-backtrace"] } macros = { path = "macros" } md-5 = "0.10.6" -memmap2 = "0.9.5" +memmap2 = "0.9.8" privilege = "0.3.0" rust-embed = "8.7.2" serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.140" -sha2 = "0.10.8" +serde_json = "1.0.143" +sha2 = "0.10.9" sha3 = "0.10.8" sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite", "uuid", "derive", "chrono"] } -thiserror = "2.0.12" +thiserror = "2.0.16" tokio-stream = "0.1.17" -tokio = { version = "1.45.1", features = ["full"] } +tokio = { version = "1.47.1", features = ["full"] } toml = "0.9.4" tracing = "0.1.41" tracing-appender = "0.2.3" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } -uuid = { version = "1.17.0", features = ["v4", "fast-rng", "serde"] } +uuid = { version = "1.18.0", features = ["v4", "fast-rng", "serde"] } [target.'cfg(windows)'.dependencies.windows-acl] version = "0.3.0" [target.'cfg(windows)'.dependencies.windows] -version = "0.61.1" +version = "0.61.3" features = [ "Win32", "Win32_System", "Win32_System_Memory", "Win32_System_IO", "Win32_System_Threading", "Win32_System_Time", "Win32_System_Registry", "Win32_System_Com", "Win32_Security", "Win32_Security_Authorization", "Win32_Storage", diff --git a/config.toml b/config.toml index df5a141..38d0ede 100644 --- a/config.toml +++ b/config.toml @@ -4,3 +4,4 @@ ui_refresh_time = 2 default_wakeup_time = 10 max_concurrency = 16 max_file_operations = 100 +channel_capacity = 100 diff --git a/src/core/backup/backup_engine.rs b/src/core/backup/backup_engine.rs index 2492771..2bc5ec3 100644 --- a/src/core/backup/backup_engine.rs +++ b/src/core/backup/backup_engine.rs @@ -22,7 +22,7 @@ use tokio::sync::oneshot; use tokio::task::JoinHandle; use tracing::error; use uuid::Uuid; -use crate::model::core::gui::communication::ExecutionErrors; +use crate::model::core::gui::communication::{ExecutionErrors, ExecutionProgress, FolderProcess}; pub struct BackupEngine { app_config: Arc, @@ -56,6 +56,8 @@ impl BackupEngine { .with_service(self) .command::() .query::() + .event::() + .event::() .event::() .build(); } diff --git a/src/core/infrastructure/communication_manager.rs b/src/core/infrastructure/communication_manager.rs index 8842751..f7b5378 100644 --- a/src/core/infrastructure/communication_manager.rs +++ b/src/core/infrastructure/communication_manager.rs @@ -121,6 +121,12 @@ impl CommunicationManager { .ok_or(MiscError::TypeNotRegistered)?; broadcaster.broadcast_event(Box::new(event)) } + + pub fn clear_handlers(&self) { + self.command_handlers.clear(); + self.query_handlers.clear(); + self.event_broadcasters.clear(); + } } pub struct ServiceRegistrar { diff --git a/src/core/infrastructure/database_manager.rs b/src/core/infrastructure/database_manager.rs index d36f5b4..3befe37 100644 --- a/src/core/infrastructure/database_manager.rs +++ b/src/core/infrastructure/database_manager.rs @@ -39,6 +39,11 @@ impl DatabaseManager { self.pool.clone() } + pub async fn shutdown(&self) { + self.pool.close().await; + self._lock.release(); + } + pub async fn exist_database() -> bool { fs::metadata(DATABASE_PATH).await.is_ok() } diff --git a/src/core/infrastructure/io_manager.rs b/src/core/infrastructure/io_manager.rs index c929ce6..dd72709 100644 --- a/src/core/infrastructure/io_manager.rs +++ b/src/core/infrastructure/io_manager.rs @@ -17,6 +17,10 @@ impl IOManager { file_system: FileSystem::new(semaphore), } } + + pub fn shutdown(&self) { + self.file_system.semaphore().close(); + } } impl Deref for IOManager { @@ -26,9 +30,3 @@ impl Deref for IOManager { &self.file_system } } - -impl Drop for IOManager { - fn drop(&mut self) { - self.file_system.semaphore().close(); - } -} diff --git a/src/core/system.rs b/src/core/system.rs index c84f086..67d454e 100644 --- a/src/core/system.rs +++ b/src/core/system.rs @@ -21,6 +21,9 @@ use std::sync::Arc; use tokio::sync::oneshot; pub struct System { + io_manager: Arc, + database_manager: Arc, + communication_manager: Arc, backup_service: Arc, schedule_service: Arc, gui_manager: Arc, @@ -49,8 +52,16 @@ impl System { ) .await?, ); - let gui_manager = Arc::new(GuiManager::new(app_config, communication_manager)); + let gui_manager = Arc::new( + GuiManager::new( + app_config, + communication_manager.clone() + ) + ); let system = Self { + io_manager, + database_manager, + communication_manager, backup_service, schedule_service, gui_manager, @@ -75,10 +86,13 @@ impl System { } pub async fn shutdown(&self) { - self.backup_service.shutdown().await; while let Some(shutdown) = self.shutdowns.pop() { let _ = shutdown.send(()); } + self.backup_service.shutdown().await; + self.communication_manager.clear_handlers(); + self.database_manager.shutdown().await; + self.io_manager.shutdown(); } fn elevate_privileges() -> Result<(), Error> { diff --git a/src/model/core/schedule/schedule.rs b/src/model/core/schedule/schedule.rs index 27a7a59..d006347 100644 --- a/src/model/core/schedule/schedule.rs +++ b/src/model/core/schedule/schedule.rs @@ -39,7 +39,7 @@ pub struct Schedule { impl Schedule { pub fn to_execution(&self) -> Execution { Execution { - uuid: self.uuid, + uuid: Uuid::new_v4(), state: BackupState::Pending, source_path: self.source_path.clone(), destination_path: self.destination_path.clone(), diff --git a/src/ui/execution_page.rs b/src/ui/execution_page.rs index e2e3aa8..694d51a 100644 --- a/src/ui/execution_page.rs +++ b/src/ui/execution_page.rs @@ -115,9 +115,8 @@ impl ExecutionPage { let latest_ids: HashSet = latest_executions.iter().map(|(id, _)| *id).collect(); for (task_id, latest_execution) in latest_executions { - if let Some(mut display) = self.executions.get_mut(&task_id) { - display.execution = latest_execution; - } + let execution_display = ExecutionDisplay::from(latest_execution); + self.executions.insert(task_id, execution_display); } self.executions diff --git a/src/ui/schedule_page.rs b/src/ui/schedule_page.rs index ec100e8..5426748 100644 --- a/src/ui/schedule_page.rs +++ b/src/ui/schedule_page.rs @@ -1,9 +1,8 @@ use crate::core::infrastructure::app_config::AppConfig; use crate::core::infrastructure::communication_manager::CommunicationManager; +use crate::model::core::backup::communication::BackupCommand; use crate::model::core::backup::execution::*; -use crate::model::core::schedule::communication::{ - ScheduleManagerCommand, ScheduleManagerQuery, ScheduleManagerQueryResponse, -}; +use crate::model::core::schedule::communication::*; use crate::model::core::schedule::schedule::*; use crate::model::error::Error; use crate::ui::common::{ComparisonModeSelection, FolderSelectionMode}; @@ -33,6 +32,19 @@ pub struct SchedulePage { new_schedule_hash_type: HashType, show_add_schedule_dialog: bool, + // Edit functionality + editing_schedule: Option, + show_edit_schedule_dialog: bool, + edit_schedule_name: String, + edit_schedule_source: String, + edit_schedule_destination: String, + edit_schedule_interval: ScheduleInterval, + edit_schedule_mirror: bool, + edit_schedule_backup_permission: bool, + edit_schedule_follow_symlinks: bool, + edit_schedule_comparison_mode: ComparisonModeSelection, + edit_schedule_hash_type: HashType, + file_dialog: FileDialog, folder_selection_mode: Option, @@ -60,6 +72,20 @@ impl SchedulePage { new_schedule_comparison_mode: ComparisonModeSelection::Standard, new_schedule_hash_type: HashType::BLAKE3, show_add_schedule_dialog: false, + + // Initialize edit fields + editing_schedule: None, + show_edit_schedule_dialog: false, + edit_schedule_name: String::new(), + edit_schedule_source: String::new(), + edit_schedule_destination: String::new(), + edit_schedule_interval: ScheduleInterval::Daily, + edit_schedule_mirror: false, + edit_schedule_backup_permission: false, + edit_schedule_follow_symlinks: false, + edit_schedule_comparison_mode: ComparisonModeSelection::Standard, + edit_schedule_hash_type: HashType::BLAKE3, + file_dialog: FileDialog::new(), folder_selection_mode: None, show_disabled_schedules: true, @@ -138,6 +164,20 @@ impl SchedulePage { }) } + fn handle_run_schedule_now(&self, schedule: Schedule) -> Result<(), Error> { + block_on(async { + let execution = schedule.to_execution(); + let uuid = execution.uuid; + self.communication_manager + .send_command(BackupCommand::AddExecution(execution)) + .await?; + self.communication_manager + .send_command(BackupCommand::StartExecution(uuid)) + .await?; + Ok(()) + }) + } + pub fn update(&mut self, ctx: &egui::Context) { let should_refresh = match self.last_refresh { None => true, @@ -218,6 +258,7 @@ impl SchedulePage { }); self.draw_add_schedule_dialog(ctx); + self.draw_edit_schedule_dialog(ctx); self.draw_schedule_details_window(ctx); } @@ -512,28 +553,224 @@ impl SchedulePage { }); } - self.file_dialog.update(ctx); + // Handle file dialog for both add and edit modes + if !self.show_edit_schedule_dialog { + self.handle_file_dialog_for_add_mode(ctx); + } + } - if let Some(path) = self.file_dialog.take_picked() { - if let Some(mode) = &self.folder_selection_mode { - match mode { - FolderSelectionMode::Source => { - self.new_schedule_source = path.to_string_lossy().to_string(); - } - FolderSelectionMode::Destination => { - self.new_schedule_destination = path.to_string_lossy().to_string(); + // New function to draw edit schedule dialog + fn draw_edit_schedule_dialog(&mut self, ctx: &egui::Context) { + if self.show_edit_schedule_dialog { + egui::Window::new("Edit Backup Schedule") + .collapsible(false) + .resizable(false) + .show(ctx, |ui| { + egui::Grid::new("edit_schedule_grid") + .num_columns(3) + .spacing([10.0, 4.0]) + .show(ui, |ui| { + ui.label("Schedule Name:"); + ui.add_sized( + [300.0, 20.0], + egui::TextEdit::singleline(&mut self.edit_schedule_name), + ); + ui.label(""); + ui.end_row(); + + ui.label("Source Path:"); + ui.add_sized( + [300.0, 20.0], + egui::TextEdit::singleline(&mut self.edit_schedule_source), + ); + if ui.button("📁 Browse").clicked() { + self.folder_selection_mode = Some(FolderSelectionMode::Source); + self.file_dialog.pick_directory(); + } + ui.end_row(); + + ui.label("Destination Path:"); + ui.add_sized( + [300.0, 20.0], + egui::TextEdit::singleline(&mut self.edit_schedule_destination), + ); + if ui.button("📁 Browse").clicked() { + self.folder_selection_mode = Some(FolderSelectionMode::Destination); + self.file_dialog.pick_directory(); + } + ui.end_row(); + + ui.label("Interval:"); + egui::ComboBox::from_label("") + .selected_text(format!("{:?}", self.edit_schedule_interval)) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.edit_schedule_interval, + ScheduleInterval::Once, + "Once", + ); + ui.selectable_value( + &mut self.edit_schedule_interval, + ScheduleInterval::Daily, + "Daily", + ); + ui.selectable_value( + &mut self.edit_schedule_interval, + ScheduleInterval::Weekly, + "Weekly", + ); + ui.selectable_value( + &mut self.edit_schedule_interval, + ScheduleInterval::Monthly, + "Monthly", + ); + }); + ui.label(""); + ui.end_row(); + }); + + ui.separator(); + + ui.label("File Comparison Mode:"); + ui.horizontal(|ui| { + ui.radio_value( + &mut self.edit_schedule_comparison_mode, + ComparisonModeSelection::Standard, + "⚡ Standard (Size + Time)", + ); + ui.radio_value( + &mut self.edit_schedule_comparison_mode, + ComparisonModeSelection::Advanced, + "🔧 Advanced (+ Attributes)", + ); + ui.radio_value( + &mut self.edit_schedule_comparison_mode, + ComparisonModeSelection::Thorough, + "🔍 Thorough (+ Checksum)", + ); + }); + + if self.edit_schedule_comparison_mode == ComparisonModeSelection::Thorough { + ui.horizontal(|ui| { + ui.label(" Hash Algorithm:"); + egui::ComboBox::from_id_salt("edit_schedule_hash_type") + .selected_text(format!("{:?}", self.edit_schedule_hash_type)) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.edit_schedule_hash_type, + HashType::BLAKE3, + "BLAKE3 (Recommended)", + ); + ui.selectable_value( + &mut self.edit_schedule_hash_type, + HashType::SHA256, + "SHA256", + ); + ui.selectable_value( + &mut self.edit_schedule_hash_type, + HashType::SHA3, + "SHA3", + ); + ui.selectable_value( + &mut self.edit_schedule_hash_type, + HashType::BLAKE2B, + "BLAKE2B", + ); + ui.selectable_value( + &mut self.edit_schedule_hash_type, + HashType::BLAKE2S, + "BLAKE2S", + ); + ui.selectable_value( + &mut self.edit_schedule_hash_type, + HashType::MD5, + "MD5 (Legacy)", + ); + }); + }); } - } - } - self.folder_selection_mode = None; + + ui.separator(); + + ui.label("Additional Options:"); + ui.checkbox(&mut self.edit_schedule_follow_symlinks, "Follow Symlinks"); + ui.checkbox( + &mut self.edit_schedule_mirror, + "Mirror Mode (Delete extra files in destination)", + ); + ui.checkbox( + &mut self.edit_schedule_backup_permission, + "Backup File Permissions", + ); + + ui.separator(); + + ui.horizontal(|ui| { + if ui.button("Update Schedule").clicked() + && !self.edit_schedule_name.is_empty() + && !self.edit_schedule_source.is_empty() + && !self.edit_schedule_destination.is_empty() + { + if let Some(mut editing_schedule) = self.editing_schedule.clone() { + let comparison_mode = match self.edit_schedule_comparison_mode { + ComparisonModeSelection::Standard => { + Some(ComparisonMode::Standard) + } + ComparisonModeSelection::Advanced => { + Some(ComparisonMode::Advanced) + } + ComparisonModeSelection::Thorough => { + Some(ComparisonMode::Thorough(self.edit_schedule_hash_type)) + } + }; + + editing_schedule.name = self.edit_schedule_name.clone(); + editing_schedule.source_path = + PathBuf::from(&self.edit_schedule_source); + editing_schedule.destination_path = + PathBuf::from(&self.edit_schedule_destination); + editing_schedule.interval = self.edit_schedule_interval; + editing_schedule.comparison_mode = comparison_mode; + editing_schedule.options = BackupOptions { + mirror: self.edit_schedule_mirror, + backup_permission: self.edit_schedule_backup_permission, + follow_symlinks: self.edit_schedule_follow_symlinks, + }; + editing_schedule.updated_at = chrono::Utc::now().naive_utc(); + + if let Err(err) = self.handle_modify_schedule(editing_schedule) { + error!("{}", err); + } + + self.reset_edit_schedule_form(); + } + } + + if ui.button("Cancel").clicked() { + self.reset_edit_schedule_form(); + } + }); + }); + } + + // Handle file dialog for edit mode + if self.show_edit_schedule_dialog { + self.handle_file_dialog_for_edit_mode(ctx); } } fn draw_schedule_details_window(&mut self, ctx: &egui::Context) { if let Some(schedule_id) = self.viewing_schedule_details { let mut show_window = true; + let mut run_now_clicked = false; + let mut edit_clicked = false; - if let Some(schedule) = self.schedules.iter().find(|s| s.uuid == schedule_id) { + // Clone the schedule data we need before entering the closure + let schedule_data = self.schedules.iter() + .find(|s| s.uuid == schedule_id) + .cloned(); + + if let Some(schedule) = schedule_data { egui::Window::new(format!("Schedule Details - {}", schedule.name)) .open(&mut show_window) .resizable(true) @@ -624,22 +861,115 @@ impl SchedulePage { ui.separator(); ui.horizontal(|ui| { - if ui.button("Run Now").clicked() { - println!("Would run schedule {} now", schedule.name); + // Use local flags to track button clicks + if ui.button("▶ Run Now").clicked() { + run_now_clicked = true; } - if ui.button("Edit").clicked() { - println!("Would edit schedule {}", schedule.name); + + if ui.button("✏ Edit").clicked() { + edit_clicked = true; } }); }); - } + if run_now_clicked { + if let Err(err) = self.handle_run_schedule_now(schedule.clone()) { + error!("Failed to run schedule now: {}", err); + } else { + self.load_schedules(); + } + } + if edit_clicked { + self.start_editing_schedule(schedule); + } + } if !show_window { self.viewing_schedule_details = None; } } } + fn start_editing_schedule(&mut self, schedule: Schedule) { + self.editing_schedule = Some(schedule.clone()); + self.edit_schedule_name = schedule.name.clone(); + self.edit_schedule_source = schedule.source_path.to_string_lossy().to_string(); + self.edit_schedule_destination = schedule.destination_path.to_string_lossy().to_string(); + self.edit_schedule_interval = schedule.interval; + self.edit_schedule_mirror = schedule.options.mirror; + self.edit_schedule_backup_permission = schedule.options.backup_permission; + self.edit_schedule_follow_symlinks = schedule.options.follow_symlinks; + + if let Some(comparison_mode) = &schedule.comparison_mode { + match comparison_mode { + ComparisonMode::Standard => { + self.edit_schedule_comparison_mode = ComparisonModeSelection::Standard; + } + ComparisonMode::Advanced => { + self.edit_schedule_comparison_mode = ComparisonModeSelection::Advanced; + } + ComparisonMode::Thorough(hash_type) => { + self.edit_schedule_comparison_mode = ComparisonModeSelection::Thorough; + self.edit_schedule_hash_type = *hash_type; + } + } + } else { + self.edit_schedule_comparison_mode = ComparisonModeSelection::Standard; + } + + self.show_edit_schedule_dialog = true; + self.viewing_schedule_details = None; + } + + fn reset_edit_schedule_form(&mut self) { + self.editing_schedule = None; + self.edit_schedule_name.clear(); + self.edit_schedule_source.clear(); + self.edit_schedule_destination.clear(); + self.edit_schedule_interval = ScheduleInterval::Daily; + self.edit_schedule_mirror = false; + self.edit_schedule_backup_permission = false; + self.edit_schedule_follow_symlinks = false; + self.edit_schedule_comparison_mode = ComparisonModeSelection::Standard; + self.edit_schedule_hash_type = HashType::BLAKE3; + self.show_edit_schedule_dialog = false; + } + + fn handle_file_dialog_for_add_mode(&mut self, ctx: &egui::Context) { + self.file_dialog.update(ctx); + + if let Some(path) = self.file_dialog.take_picked() { + if let Some(mode) = &self.folder_selection_mode { + match mode { + FolderSelectionMode::Source => { + self.new_schedule_source = path.to_string_lossy().to_string(); + } + FolderSelectionMode::Destination => { + self.new_schedule_destination = path.to_string_lossy().to_string(); + } + } + } + self.folder_selection_mode = None; + } + } + + fn handle_file_dialog_for_edit_mode(&mut self, ctx: &egui::Context) { + self.file_dialog.update(ctx); + + if let Some(path) = self.file_dialog.take_picked() { + if let Some(mode) = &self.folder_selection_mode { + match mode { + FolderSelectionMode::Source => { + self.edit_schedule_source = path.to_string_lossy().to_string(); + } + FolderSelectionMode::Destination => { + self.edit_schedule_destination = path.to_string_lossy().to_string(); + } + } + } + self.folder_selection_mode = None; + } + } + fn reset_schedule_form(&mut self) { self.new_schedule_name.clear(); self.new_schedule_source.clear(); diff --git a/src/utils/database_lock.rs b/src/utils/database_lock.rs index 14f35b7..7485378 100644 --- a/src/utils/database_lock.rs +++ b/src/utils/database_lock.rs @@ -21,6 +21,10 @@ impl DatabaseLock { Err(DatabaseError::LockDatabaseFailed("Lock file already exists."))? } } + + pub fn release(&self) { + let _ = fs::remove_file(DATABASE_LOCK_PATH); + } } impl Drop for DatabaseLock {