From 9c16e05c8e1357819b9bfbdfb8acbe8e3d1a4597 Mon Sep 17 00:00:00 2001 From: DaLaw2 Date: Sat, 2 Aug 2025 02:54:24 +0800 Subject: [PATCH] feat: Add font, find system fonts (#4) * feat: Add font, find system fonts * fix: File time set error and missing token permission to set file time and acl * fix: File permission get and set not working * fix: Missing font library in linux env --- .github/workflows/master-build.yml | 2 +- .github/workflows/pr-check.yml | 2 +- Cargo.lock | 121 +++++++++++++++++++++++ Cargo.toml | 3 +- src/core/backup_engine.rs | 11 +++ src/core/gui_manager.rs | 8 +- src/platform/windows/attributes.rs | 3 +- src/platform/windows/elevate.rs | 76 ++++++++------- src/platform/windows/file_system.rs | 38 ++++---- src/platform/windows/raii_guard.rs | 1 + src/ui/execution_page.rs | 146 ++++++++++++++++++++-------- src/utils/font.rs | 80 +++++++++++++++ src/utils/mod.rs | 1 + 13 files changed, 390 insertions(+), 102 deletions(-) create mode 100644 src/utils/font.rs diff --git a/.github/workflows/master-build.yml b/.github/workflows/master-build.yml index 91b1a5a..0d28431 100644 --- a/.github/workflows/master-build.yml +++ b/.github/workflows/master-build.yml @@ -15,7 +15,7 @@ jobs: - name: Install system dependencies run: | apt-get update - apt-get install -y curl build-essential mingw-w64 git + apt-get install -y curl build-essential mingw-w64 git libfontconfig1-dev pkg-config - name: Checkout code uses: actions/checkout@v4 diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index bbaf150..bb1e070 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -15,7 +15,7 @@ jobs: - name: Install system dependencies run: | apt-get update - apt-get install -y curl build-essential mingw-w64 git + apt-get install -y curl build-essential mingw-w64 git libfontconfig1-dev pkg-config - name: Checkout code uses: actions/checkout@v4 diff --git a/Cargo.lock b/Cargo.lock index 3b380be..bf594c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,7 @@ dependencies = [ "digest", "eframe", "egui-file-dialog", + "font-kit", "fs4", "futures", "image", @@ -921,6 +922,18 @@ dependencies = [ "libc", ] +[[package]] +name = "core-text" +version = "20.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" +dependencies = [ + "core-foundation 0.9.4", + "core-graphics", + "foreign-types", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1074,6 +1087,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.5.0" @@ -1155,6 +1177,18 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dwrote" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe1f192fcce01590bd8d839aca53ce0d11d803bf291b2a6c4ad925a8f0024be" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + [[package]] name = "ecolor" version = "0.32.0" @@ -1483,6 +1517,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + [[package]] name = "flume" version = "0.11.1" @@ -1500,6 +1540,31 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "font-kit" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3" +dependencies = [ + "bitflags 2.9.1", + "byteorder", + "core-foundation 0.9.4", + "core-graphics", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1536,6 +1601,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "fs4" version = "0.13.1" @@ -2970,6 +3046,31 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57" +dependencies = [ + "rustc_version", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -5556,6 +5657,15 @@ dependencies = [ "toml 0.5.11", ] +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -5634,6 +5744,17 @@ version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + [[package]] name = "yoke" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 8ea2ea0..811e63f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,8 @@ name = "MirrorSphere" version = "0.0.9" edition = "2024" -authors = ["DaLaw2"] build = "build.rs" +authors = ["DaLaw2"] [dependencies] async-trait = "0.1.88" @@ -17,6 +17,7 @@ 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" diff --git a/src/core/backup_engine.rs b/src/core/backup_engine.rs index a47a8bb..18a3221 100644 --- a/src/core/backup_engine.rs +++ b/src/core/backup_engine.rs @@ -67,6 +67,17 @@ impl BackupEngine { } } + pub fn get_execution(&self, uuid: &Uuid) -> Option { + self.executions.get(uuid).map(|entry| entry.clone()) + } + + pub fn get_all_executions(&self) -> Vec<(Uuid, BackupExecution)> { + self.executions + .iter() + .map(|entry| (*entry.key(), entry.value().clone())) + .collect() + } + pub async fn add_execution(&self, execution: BackupExecution) { self.executions.insert(execution.uuid, execution); } diff --git a/src/core/gui_manager.rs b/src/core/gui_manager.rs index 1733a63..dd93862 100644 --- a/src/core/gui_manager.rs +++ b/src/core/gui_manager.rs @@ -1,13 +1,14 @@ +use crate::core::app_config::AppConfig; use crate::core::backup_engine::BackupEngine; use crate::core::event_bus::EventBus; use crate::core::schedule_manager::ScheduleManager; use crate::model::error::Error; use crate::model::error::misc::MiscError; use crate::ui::main_page::MainPage; +use crate::utils::assets::Assets; +use crate::utils::font; use eframe::egui; use std::sync::Arc; -use crate::core::app_config::AppConfig; -use crate::utils::assets::Assets; pub struct GuiManager { app_config: Arc, @@ -50,7 +51,8 @@ impl GuiManager { eframe::run_native( "MirrorSphere", options, - Box::new(|_| { + Box::new(|cc| { + font::setup_system_fonts(&cc.egui_ctx); Ok(Box::new(MainPage::new( config, event_bus, diff --git a/src/platform/windows/attributes.rs b/src/platform/windows/attributes.rs index 65055fc..015ee86 100644 --- a/src/platform/windows/attributes.rs +++ b/src/platform/windows/attributes.rs @@ -2,7 +2,7 @@ use crate::platform::raii_guard::SecurityDescriptorGuard; use std::time::SystemTime; use windows::Win32::Security::{ACL, PSID}; -#[derive(Clone, Eq)] +#[derive(Debug, Clone, Eq)] pub struct Attributes { pub attributes: u32, pub creation_time: SystemTime, @@ -16,6 +16,7 @@ impl PartialEq for Attributes { } } +#[derive(Debug)] pub struct Permissions { pub owner: PSID, pub primary_group: PSID, diff --git a/src/platform/windows/elevate.rs b/src/platform/windows/elevate.rs index 6268771..3141d68 100644 --- a/src/platform/windows/elevate.rs +++ b/src/platform/windows/elevate.rs @@ -1,21 +1,22 @@ +use crate::model::error::Error; use crate::model::error::misc::MiscError; use crate::model::error::system::SystemError; -use crate::model::error::Error; use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use std::{env, mem}; -use windows::core::PCWSTR; -use windows::Win32::Foundation::{CloseHandle, HANDLE, LUID}; +use windows::Win32::Foundation::{CloseHandle, GetLastError, ERROR_NOT_ALL_ASSIGNED, HANDLE, LUID}; use windows::Win32::Security::{ - AdjustTokenPrivileges, LookupPrivilegeValueW, LUID_AND_ATTRIBUTES, SE_PRIVILEGE_ENABLED, - SE_SECURITY_NAME, TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES, TOKEN_QUERY, + AdjustTokenPrivileges, LUID_AND_ATTRIBUTES, LookupPrivilegeValueW, SE_BACKUP_NAME, + SE_PRIVILEGE_ENABLED, SE_RESTORE_NAME, SE_SECURITY_NAME, TOKEN_ADJUST_PRIVILEGES, + TOKEN_PRIVILEGES, TOKEN_QUERY, }; use windows::Win32::System::Com::{ - CoInitializeEx, COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE, + COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE, CoInitializeEx, }; use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken}; -use windows::Win32::UI::Shell::{ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW}; +use windows::Win32::UI::Shell::{SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW, ShellExecuteExW}; use windows::Win32::UI::WindowsAndMessaging::SW_NORMAL; +use windows::core::PCWSTR; #[allow(dead_code)] pub fn elevate() -> Result<(), Error> { @@ -34,9 +35,7 @@ pub fn elevate() -> Result<(), Error> { .chain(Some(0)) .collect::>(); - unsafe { - win_runas(file, params) - } + unsafe { win_runas(file, params) } } #[allow(dead_code)] @@ -75,37 +74,42 @@ pub fn adjust_token_privileges() -> Result<(), Error> { ) .map_err(SystemError::AdjustTokenPrivilegesFailed)?; - let mut luid = LUID { - LowPart: 0, - HighPart: 0, - }; + let privileges = [SE_SECURITY_NAME, SE_BACKUP_NAME, SE_RESTORE_NAME]; - LookupPrivilegeValueW(PCWSTR::null(), SE_SECURITY_NAME, &mut luid) + for privilege_name in privileges { + let mut luid = LUID { + LowPart: 0, + HighPart: 0, + }; + + LookupPrivilegeValueW(PCWSTR::null(), privilege_name, &mut luid) + .map_err(SystemError::AdjustTokenPrivilegesFailed)?; + + let mut token_privilege = TOKEN_PRIVILEGES { + PrivilegeCount: 1, + Privileges: [LUID_AND_ATTRIBUTES { + Luid: luid, + Attributes: SE_PRIVILEGE_ENABLED, + }], + }; + + AdjustTokenPrivileges( + token_handle, + false, + Some(&mut token_privilege), + 0, + None, + None, + ) .map_err(SystemError::AdjustTokenPrivilegesFailed)?; - - let mut token_privilege = TOKEN_PRIVILEGES { - PrivilegeCount: 1, - Privileges: [LUID_AND_ATTRIBUTES { - Luid: luid, - Attributes: SE_PRIVILEGE_ENABLED, - }], - }; - - AdjustTokenPrivileges( - token_handle, - false, - Some(&mut token_privilege), - 0, - None, - None, - ) - .map_err(SystemError::AdjustTokenPrivilegesFailed)?; + } CloseHandle(token_handle).map_err(MiscError::ObjectFreeFailed)?; - // if GetLastError().is_err() { - // Err(SystemError::AdjustTokenPrivilegesFailed)? - // } + let last_error = GetLastError(); + if last_error == ERROR_NOT_ALL_ASSIGNED { + Err(SystemError::AdjustTokenPrivilegesFailed(format!("{:?}", last_error)))? + } Ok(()) } diff --git a/src/platform/windows/file_system.rs b/src/platform/windows/file_system.rs index f11d5a3..9266f05 100644 --- a/src/platform/windows/file_system.rs +++ b/src/platform/windows/file_system.rs @@ -1,8 +1,8 @@ use crate::interface::file_system::FileSystemTrait; +use crate::model::error::Error; use crate::model::error::io::IOError; use crate::model::error::misc::MiscError; use crate::model::error::system::SystemError; -use crate::model::error::Error; use crate::platform::attributes::{Attributes, Permissions}; use crate::platform::raii_guard::SecurityDescriptorGuard; use async_trait::async_trait; @@ -15,17 +15,20 @@ use std::sync::Arc; use std::time::SystemTime; use tokio::sync::Semaphore; use tokio::task::spawn_blocking; -use windows::core::PCWSTR; -use windows::Win32::Foundation::{CloseHandle, FILETIME, GENERIC_ALL, SYSTEMTIME}; +use windows::Win32::Foundation::{CloseHandle, FILETIME, GENERIC_WRITE, SYSTEMTIME}; use windows::Win32::Security::Authorization::{ - GetNamedSecurityInfoW, SetNamedSecurityInfoW, SE_FILE_OBJECT, + GetNamedSecurityInfoW, SE_FILE_OBJECT, SetNamedSecurityInfoW, +}; +use windows::Win32::Security::{ + ACL, BACKUP_SECURITY_INFORMATION, DACL_SECURITY_INFORMATION, GROUP_SECURITY_INFORMATION, + OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, PSID, SACL_SECURITY_INFORMATION, }; -use windows::Win32::Security::{ACL, BACKUP_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, PSID}; use windows::Win32::Storage::FileSystem::{ - CreateFileW, SetFileAttributesW, SetFileTime, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_DELETE, - FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, + CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_WRITE, + OPEN_EXISTING, SetFileAttributesW, SetFileTime, }; use windows::Win32::System::Time::SystemTimeToFileTime; +use windows::core::PCWSTR; pub struct FileSystem { semaphore: Arc, @@ -52,10 +55,9 @@ impl FileSystemTrait for FileSystem { .await .map_err(IOError::SemaphoreClosed)?; - let link_target = - tokio::fs::read_link(source_link) - .await - .map_err(|err| IOError::ReadSymbolLinkFailed(source_link.clone(), err))?; + let link_target = tokio::fs::read_link(source_link) + .await + .map_err(|err| IOError::ReadSymbolLinkFailed(source_link.clone(), err))?; if link_target.is_dir() { tokio::fs::symlink_dir(&link_target, destination_link).await @@ -121,11 +123,11 @@ impl FileSystemTrait for FileSystem { let handle = CreateFileW( PCWSTR(file_path_wild.as_ptr()), - GENERIC_ALL.0, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + GENERIC_WRITE.0, + FILE_SHARE_WRITE, None, OPEN_EXISTING, - FILE_FLAGS_AND_ATTRIBUTES(file_attributes), + FILE_FLAGS_AND_ATTRIBUTES(FILE_FLAG_BACKUP_SEMANTICS.0), None, ) .map_err(|err| IOError::SetMetadataFailed(path.clone(), err))?; @@ -158,7 +160,10 @@ impl FileSystemTrait for FileSystem { let path = path.clone(); let permission = spawn_blocking(move || unsafe { - let security_info = BACKUP_SECURITY_INFORMATION; + let security_info = OWNER_SECURITY_INFORMATION + | GROUP_SECURITY_INFORMATION + | DACL_SECURITY_INFORMATION + | SACL_SECURITY_INFORMATION; let mut owner = PSID::default(); let mut primary_group = PSID::default(); let mut dacl: *mut ACL = ptr::null_mut(); @@ -249,8 +254,7 @@ impl FileSystem { let mut file_time = FILETIME::default(); unsafe { - SystemTimeToFileTime(&sys_time, &mut file_time) - .map_err(SystemError::UnexpectError)?; + SystemTimeToFileTime(&sys_time, &mut file_time).map_err(SystemError::UnexpectError)?; Ok(file_time) } } diff --git a/src/platform/windows/raii_guard.rs b/src/platform/windows/raii_guard.rs index 0bfbea6..187a632 100644 --- a/src/platform/windows/raii_guard.rs +++ b/src/platform/windows/raii_guard.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use windows::Win32::Foundation::{LocalFree, HLOCAL}; use windows::Win32::Security::PSECURITY_DESCRIPTOR; +#[derive(Debug)] pub struct SecurityDescriptorGuard { descriptor: PSECURITY_DESCRIPTOR, } diff --git a/src/ui/execution_page.rs b/src/ui/execution_page.rs index 108a01b..f2ecb88 100644 --- a/src/ui/execution_page.rs +++ b/src/ui/execution_page.rs @@ -13,6 +13,8 @@ use futures::executor::block_on; use std::path::PathBuf; use std::sync::Arc; use std::sync::mpsc::Receiver; +use std::time::{Duration, Instant}; +use tracing::error; use uuid::Uuid; #[derive(Debug, Clone)] @@ -65,6 +67,7 @@ pub struct ExecutionPage { pub auto_scroll_errors: bool, pub show_completed_tasks: bool, viewing_errors_for_task: Option, + last_refresh: Option, } impl ExecutionPage { @@ -97,6 +100,7 @@ impl ExecutionPage { auto_scroll_errors: true, show_completed_tasks: true, viewing_errors_for_task: None, + last_refresh: None, } } @@ -124,13 +128,93 @@ impl ExecutionPage { } } + fn sync_all_execution_states(&mut self) { + let latest_executions = self.backup_engine.get_all_executions(); + let latest_ids: std::collections::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; + } + } + + self.executions.retain(|task_id, _| latest_ids.contains(task_id)); + self.error_messages.retain(|task_id, _| latest_ids.contains(task_id)); + + if let Some(viewing_id) = self.viewing_errors_for_task { + if !latest_ids.contains(&viewing_id) { + self.viewing_errors_for_task = None; + } + } + } + + fn sync_execution_state(&mut self, task_id: Uuid) { + if let Some(latest_execution) = self.backup_engine.get_execution(&task_id) { + if let Some(mut display) = self.executions.get_mut(&task_id) { + display.execution = latest_execution; + } + } + } + + fn handle_start_execution(&mut self, task_id: Uuid) { + match block_on(self.backup_engine.start_execution(task_id)) { + Ok(_) => self.sync_execution_state(task_id), + Err(err) => { + self.sync_execution_state(task_id); + error!("{}", err); + } + } + } + + fn handle_suspend_execution(&mut self, task_id: Uuid) { + match block_on(self.backup_engine.suspend_execution(task_id)) { + Ok(_) => self.sync_execution_state(task_id), + Err(err) => { + self.sync_execution_state(task_id); + error!("{}", err); + } + } + } + + fn handle_resume_execution(&mut self, task_id: Uuid) { + match block_on(self.backup_engine.resume_execution(task_id)) { + Ok(_) => self.sync_execution_state(task_id), + Err(err) => { + self.sync_execution_state(task_id); + error!("{}", err); + } + } + } + + fn handle_remove_execution(&mut self, task_id: Uuid) { + block_on(self.backup_engine.remove_execution(&task_id)); + self.executions.remove(&task_id); + } + pub fn update(&mut self, ctx: &egui::Context) { self.process_events(); + let should_refresh = match self.last_refresh { + None => true, + Some(last) => { + last.elapsed() > Duration::from_secs(self.app_config.ui_refresh_time as u64) + } + }; + + if should_refresh { + self.sync_all_execution_states(); + self.last_refresh = Some(Instant::now()); + } + egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Backup Executions"); ui.horizontal(|ui| { + if ui.button("🔄 Refresh").clicked() { + self.sync_all_execution_states(); + self.last_refresh = Some(Instant::now()); + } + if ui.button("➕ Add Execution").clicked() { self.show_add_task_dialog = true; } @@ -214,7 +298,7 @@ impl ExecutionPage { ui.horizontal(|ui| { ui.vertical(|ui| { ui.label(format!( - "đŸ—‚ī¸ {}", + "📁 {}", task_display.execution.source_path.display() )); ui.label(format!( @@ -224,12 +308,12 @@ impl ExecutionPage { ui.horizontal(|ui| { let (color, symbol) = match task_display.execution.state { - BackupState::Running => (egui::Color32::GREEN, "â–ļī¸"), - BackupState::Suspended => (egui::Color32::YELLOW, "â¸ī¸"), + BackupState::Running => (egui::Color32::GREEN, "â–ļ"), + BackupState::Suspended => (egui::Color32::YELLOW, "⏸"), BackupState::Completed => (egui::Color32::BLUE, "✅"), BackupState::Failed => (egui::Color32::RED, "❌"), - BackupState::Canceled => (egui::Color32::GRAY, "âšī¸"), - BackupState::Pending => (egui::Color32::GRAY, "â¸ī¸"), + BackupState::Canceled => (egui::Color32::GRAY, "⏚"), + BackupState::Pending => (egui::Color32::GRAY, "⏸"), }; ui.colored_label( @@ -271,46 +355,26 @@ impl ExecutionPage { } match task_display.execution.state { - BackupState::Pending | BackupState::Suspended => { - if ui.button("â–ļī¸ Start").clicked() { - if let Err(e) = - block_on(self.backup_engine.start_execution(task_id)) - { - eprintln!("Failed to start execution: {e:?}"); - } + BackupState::Pending => { + if ui.button("â–ļ Start").clicked() { + self.handle_start_execution(task_id); + } + } + BackupState::Suspended => { + if ui.button("â–ļ Resume").clicked() { + self.handle_resume_execution(task_id); } } BackupState::Running => { - if ui.button("â¸ī¸ Pause").clicked() { - if let Err(e) = - block_on(self.backup_engine.suspend_execution(task_id)) - { - eprintln!("Failed to suspend execution: {e:?}"); - } + if ui.button("⏸ Pause").clicked() { + self.handle_suspend_execution(task_id); } } _ => {} } - if task_display.execution.state == BackupState::Suspended - && ui.button("â–ļī¸ Resume").clicked() - { - let rt = tokio::runtime::Handle::current(); - if let Err(e) = - rt.block_on(self.backup_engine.resume_execution(task_id)) - { - eprintln!("Failed to resume execution: {e:?}"); - } - } - - if ui.button("đŸ—‘ī¸").clicked() { - let rt = tokio::runtime::Handle::current(); - rt.block_on(self.backup_engine.remove_execution(&task_id)); - self.executions.remove(&task_id); - self.error_messages.remove(&task_id); - if self.viewing_errors_for_task == Some(task_id) { - self.viewing_errors_for_task = None; - } + if ui.button("🗑").clicked() { + self.handle_remove_execution(task_id); } }); }); @@ -380,11 +444,9 @@ impl ExecutionPage { }, }; + block_on(self.backup_engine.add_execution(execution.clone())); let execution_display = ExecutionDisplay::from(execution.clone()); self.executions.insert(execution.uuid, execution_display); - - let rt = tokio::runtime::Handle::current(); - rt.block_on(self.backup_engine.add_execution(execution)); self.reset_form(); } @@ -442,7 +504,7 @@ impl ExecutionPage { ui.with_layout( egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.button("đŸ—‘ī¸ Clear All Errors").clicked() { + if ui.button("🗑 Clear All Errors").clicked() { self.error_messages.remove(&task_id); self.viewing_errors_for_task = None; } @@ -482,7 +544,7 @@ impl ExecutionPage { }); } else { ui.vertical_centered(|ui| { - ui.label("âš ī¸ Cannot find error information for this execution"); + ui.label("⚠ Cannot find error information for this execution"); }); } }); diff --git a/src/utils/font.rs b/src/utils/font.rs new file mode 100644 index 0000000..65f7e4d --- /dev/null +++ b/src/utils/font.rs @@ -0,0 +1,80 @@ +use std::sync::Arc; +use eframe::egui; +use font_kit::source::SystemSource; +use font_kit::family_name::FamilyName; +use font_kit::properties::{Properties, Weight, Style, Stretch}; + +pub fn setup_system_fonts(ctx: &egui::Context) { + let mut fonts = egui::FontDefinitions::default(); + let system_source = SystemSource::new(); + + if let Ok(font_handle) = system_source.select_best_match( + &[FamilyName::Title("Times New Roman".to_string())], + &Properties { + weight: Weight::NORMAL, + style: Style::Normal, + stretch: Stretch::NORMAL, + } + ) { + if let Ok(font) = font_handle.load() { + if let Some(font_data) = font.copy_font_data() { + fonts.font_data.insert( + "times_new_roman".to_owned(), + Arc::from(egui::FontData::from_owned(font_data.to_vec())) + ); + } + } + } + + if let Ok(font_handle) = system_source.select_best_match( + &[ + FamilyName::Title("DFKai-SB".to_string()), + FamilyName::Title("BiauKai".to_string()), + ], + &Properties::default() + ) { + if let Ok(font) = font_handle.load() { + if let Some(font_data) = font.copy_font_data() { + fonts.font_data.insert( + "kaiti".to_owned(), + Arc::from(egui::FontData::from_owned(font_data.to_vec())) + ); + } + } + } + + if let Ok(emoji_handle) = system_source.select_best_match( + &[ + FamilyName::Title("Segoe UI Symbol".to_string()), + FamilyName::Title("Segoe UI Emoji".to_string()), + ], + &Properties::default() + ) { + if let Ok(font) = emoji_handle.load() { + if let Some(font_data) = font.copy_font_data() { + fonts.font_data.insert( + "system_emoji".to_owned(), + Arc::from(egui::FontData::from_owned(font_data.to_vec())) + ); + } + } + } + + fonts.families.get_mut(&egui::FontFamily::Proportional) + .unwrap() + .insert(0, "times_new_roman".to_owned()); + fonts.families.get_mut(&egui::FontFamily::Proportional) + .unwrap() + .insert(1, "kaiti".to_owned()); + fonts.families.get_mut(&egui::FontFamily::Proportional) + .unwrap() + .insert(2, "system_emoji".to_owned()); + fonts.families.get_mut(&egui::FontFamily::Monospace) + .unwrap() + .insert(0, "times_new_roman".to_owned()); + fonts.families.get_mut(&egui::FontFamily::Monospace) + .unwrap() + .insert(1, "kaiti".to_owned()); + + ctx.set_fonts(fonts); +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 992b4b8..3466fff 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,4 +2,5 @@ pub mod assets; pub mod database_lock; pub mod file_hash; pub mod file_lock; +pub mod font; pub mod logging;