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
This commit is contained in:
parent
f72c66d348
commit
9c16e05c8e
2
.github/workflows/master-build.yml
vendored
2
.github/workflows/master-build.yml
vendored
@ -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
|
||||
|
||||
2
.github/workflows/pr-check.yml
vendored
2
.github/workflows/pr-check.yml
vendored
@ -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
|
||||
|
||||
121
Cargo.lock
generated
121
Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -67,6 +67,17 @@ impl BackupEngine {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_execution(&self, uuid: &Uuid) -> Option<BackupExecution> {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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<AppConfig>,
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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::<Vec<_>>();
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
@ -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<Semaphore>,
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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<Uuid>,
|
||||
last_refresh: Option<Instant>,
|
||||
}
|
||||
|
||||
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<Uuid> = 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");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
80
src/utils/font.rs
Normal file
80
src/utils/font.rs
Normal file
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user