feat: Rewrote the system architecture (#5)
* refactor: Remove file locking functionality and associated references * refactor: Replace PathBuf with Path references across interfaces and remove redundant implementations * fix: UI missing "Comparison Mode" field, and set permission failed * refactor: Restructure core module into submodules for better organization * wip: Remove event system * refactor: Introduce actor model framework and reorganize module hierarchy * wip: Remove event system and restructure module hierarchy for actor-based architecture * refactor: Remove unused traits and modules, enhance GUI handling, and refine actor-driven architecture
This commit is contained in:
parent
9c16e05c8e
commit
fe5f8dd258
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
target
|
||||
logs
|
||||
.idea
|
||||
logs
|
||||
target
|
||||
devnote.txt
|
||||
mirrorSphere.db
|
||||
db.lock
|
||||
|
||||
4
build.rs
4
build.rs
@ -5,8 +5,8 @@ fn main() {
|
||||
.set("InternalName", "MirrorSphere.exe")
|
||||
.set_version_info(winres::VersionInfo::PRODUCTVERSION, 0x0001000000000000)
|
||||
.set_language(0x0409);
|
||||
if let Err(e) = res.compile() {
|
||||
eprintln!("winres error: {}", e);
|
||||
if let Err(err) = res.compile() {
|
||||
eprintln!("winres error: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,21 +1,20 @@
|
||||
use crate::core::app_config::AppConfig;
|
||||
use crate::core::event_bus::EventBus;
|
||||
use crate::core::io_manager::IOManager;
|
||||
use crate::core::progress_tracker::ProgressTracker;
|
||||
use crate::core::backup::progress_tracker::ProgressTracker;
|
||||
use crate::core::gui::gui_message_handler::GuiMessageHandler;
|
||||
use crate::core::infrastructure::actor_system::ActorSystem;
|
||||
use crate::core::infrastructure::app_config::AppConfig;
|
||||
use crate::core::infrastructure::io_manager::IOManager;
|
||||
use crate::interface::file_system::FileSystemTrait;
|
||||
use crate::interface::service_unit::ServiceUnit;
|
||||
use crate::model::backup::backup_execution::*;
|
||||
use crate::model::error::Error;
|
||||
use crate::model::core::backup::backup_execution::*;
|
||||
use crate::model::core::gui::message::GuiMessage;
|
||||
use crate::model::error::system::SystemError;
|
||||
use crate::model::error::task::TaskError;
|
||||
use crate::model::event::execution::*;
|
||||
use async_trait::async_trait;
|
||||
use crate::model::error::Error;
|
||||
use crossbeam_queue::SegQueue;
|
||||
use dashmap::DashMap;
|
||||
use futures::future::join_all;
|
||||
use macros::log;
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::task::JoinHandle;
|
||||
@ -24,8 +23,8 @@ use uuid::Uuid;
|
||||
|
||||
pub struct BackupEngine {
|
||||
app_config: Arc<AppConfig>,
|
||||
event_bus: Arc<EventBus>,
|
||||
io_manager: Arc<IOManager>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
progress_tracker: Arc<ProgressTracker>,
|
||||
executions: Arc<DashMap<Uuid, BackupExecution>>,
|
||||
running_executions: Arc<DashMap<Uuid, (oneshot::Sender<()>, JoinHandle<()>)>>,
|
||||
@ -34,14 +33,14 @@ pub struct BackupEngine {
|
||||
impl BackupEngine {
|
||||
pub fn new(
|
||||
app_config: Arc<AppConfig>,
|
||||
event_bus: Arc<EventBus>,
|
||||
io_manager: Arc<IOManager>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
progress_tracker: Arc<ProgressTracker>,
|
||||
) -> Self {
|
||||
Self {
|
||||
app_config,
|
||||
event_bus,
|
||||
io_manager,
|
||||
actor_system,
|
||||
progress_tracker,
|
||||
executions: Arc::new(DashMap::new()),
|
||||
running_executions: Arc::new(DashMap::new()),
|
||||
@ -52,7 +51,7 @@ impl BackupEngine {
|
||||
let keys: Vec<Uuid> = self
|
||||
.running_executions
|
||||
.iter()
|
||||
.map(|pair| pair.key().clone())
|
||||
.map(|pair| *pair.key())
|
||||
.collect();
|
||||
for uuid in keys {
|
||||
if let Some((_, (shutdown, handle))) = self.running_executions.remove(&uuid) {
|
||||
@ -67,10 +66,6 @@ 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()
|
||||
@ -158,12 +153,14 @@ impl BackupEngine {
|
||||
fn to_execution_runner(&self) -> ExecutionRunner {
|
||||
let config = self.app_config.clone();
|
||||
let io_manager = self.io_manager.clone();
|
||||
let actor_system = self.actor_system.clone();
|
||||
let progress_tracker = self.progress_tracker.clone();
|
||||
let executions = self.executions.clone();
|
||||
let running_executions = self.running_executions.clone();
|
||||
ExecutionRunner::new(
|
||||
config,
|
||||
io_manager,
|
||||
actor_system,
|
||||
progress_tracker,
|
||||
executions,
|
||||
running_executions,
|
||||
@ -174,6 +171,7 @@ impl BackupEngine {
|
||||
struct ExecutionRunner {
|
||||
app_config: Arc<AppConfig>,
|
||||
io_manager: Arc<IOManager>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
progress_tracker: Arc<ProgressTracker>,
|
||||
executions: Arc<DashMap<Uuid, BackupExecution>>,
|
||||
running_executions: Arc<DashMap<Uuid, (oneshot::Sender<()>, JoinHandle<()>)>>,
|
||||
@ -183,6 +181,7 @@ impl ExecutionRunner {
|
||||
pub fn new(
|
||||
app_config: Arc<AppConfig>,
|
||||
io_manager: Arc<IOManager>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
progress_tracker: Arc<ProgressTracker>,
|
||||
executions: Arc<DashMap<Uuid, BackupExecution>>,
|
||||
running_executions: Arc<DashMap<Uuid, (oneshot::Sender<()>, JoinHandle<()>)>>,
|
||||
@ -190,6 +189,7 @@ impl ExecutionRunner {
|
||||
Self {
|
||||
app_config,
|
||||
io_manager,
|
||||
actor_system,
|
||||
progress_tracker,
|
||||
executions,
|
||||
running_executions,
|
||||
@ -251,7 +251,21 @@ impl ExecutionRunner {
|
||||
match result {
|
||||
Ok((worker_next_level, worker_errors)) => {
|
||||
next_level.extend(worker_next_level);
|
||||
errors.extend(worker_errors);
|
||||
if !worker_errors.is_empty() {
|
||||
errors.extend(worker_errors.clone());
|
||||
if let Some(gui_ref) = self.actor_system.actor_of::<GuiMessageHandler>()
|
||||
{
|
||||
if let Err(err) = gui_ref
|
||||
.tell(GuiMessage::ExecutionErrors {
|
||||
uuid: execution.uuid,
|
||||
errors: worker_errors,
|
||||
})
|
||||
.await
|
||||
{
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => log!(SystemError::ThreadPanic(err)),
|
||||
}
|
||||
@ -261,7 +275,8 @@ impl ExecutionRunner {
|
||||
current_level.extend(next_level);
|
||||
if let Err(err) = progress_tracker
|
||||
.save_execution(execution.uuid, current_level, errors)
|
||||
.await {
|
||||
.await
|
||||
{
|
||||
error!("{}", err);
|
||||
}
|
||||
break;
|
||||
@ -297,9 +312,7 @@ struct Worker {
|
||||
|
||||
impl Worker {
|
||||
pub fn new(io_manager: Arc<IOManager>) -> Self {
|
||||
Self {
|
||||
io_manager
|
||||
}
|
||||
Self { io_manager }
|
||||
}
|
||||
|
||||
async fn run(
|
||||
@ -370,30 +383,30 @@ impl Worker {
|
||||
async fn process_entry(
|
||||
&self,
|
||||
execution: &BackupExecution,
|
||||
current_path: &PathBuf,
|
||||
current_path: &Path,
|
||||
) -> Result<Option<PathBuf>, Error> {
|
||||
let io_manager = &self.io_manager;
|
||||
|
||||
let source_root = &execution.source_path;
|
||||
let destination_root = &execution.destination_path;
|
||||
|
||||
let source_path = current_path.clone();
|
||||
let destination_path =
|
||||
self.calculate_destination_path(&source_path, &source_root, &destination_root)?;
|
||||
let source_path = current_path;
|
||||
let destination_path = self.calculate_destination_path(source_path, source_root, destination_root)?;
|
||||
let destination_path = destination_path.as_path();
|
||||
|
||||
let is_symlink = io_manager.is_symlink(&source_path).await.unwrap_or(false);
|
||||
let is_symlink = io_manager.is_symlink(source_path).await.unwrap_or(false);
|
||||
|
||||
if is_symlink {
|
||||
self.process_symlink(execution, &source_path, &destination_path)
|
||||
self.process_symlink(execution, source_path, destination_path)
|
||||
.await?;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if source_path.is_dir() {
|
||||
self.backup_directory(execution, &source_path, &destination_path)
|
||||
self.backup_directory(execution, source_path, destination_path)
|
||||
.await
|
||||
} else {
|
||||
self.backup_file(execution, &source_path, &destination_path)
|
||||
self.backup_file(execution, source_path, destination_path)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@ -401,13 +414,13 @@ impl Worker {
|
||||
async fn backup_directory(
|
||||
&self,
|
||||
execution: &BackupExecution,
|
||||
source_path: &PathBuf,
|
||||
destination_path: &PathBuf,
|
||||
source_path: &Path,
|
||||
destination_path: &Path,
|
||||
) -> Result<Option<PathBuf>, Error> {
|
||||
let io_manager = &self.io_manager;
|
||||
|
||||
if !destination_path.exists() {
|
||||
io_manager.create_directory(&destination_path).await?;
|
||||
io_manager.create_directory(destination_path).await?;
|
||||
}
|
||||
|
||||
io_manager
|
||||
@ -420,24 +433,17 @@ impl Worker {
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(Some(source_path.clone()))
|
||||
Ok(Some(source_path.to_path_buf()))
|
||||
}
|
||||
|
||||
async fn backup_file(
|
||||
&self,
|
||||
execution: &BackupExecution,
|
||||
source_path: &PathBuf,
|
||||
destination_path: &PathBuf,
|
||||
source_path: &Path,
|
||||
destination_path: &Path,
|
||||
) -> Result<Option<PathBuf>, Error> {
|
||||
let io_manager = &self.io_manager;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let mut file_lock = None;
|
||||
#[allow(unused_assignments)]
|
||||
if execution.options.lock_source {
|
||||
file_lock = Some(io_manager.acquire_file_lock(source_path).await?);
|
||||
}
|
||||
|
||||
match execution.backup_type {
|
||||
BackupType::Full => self.full_backup(source_path, destination_path).await?,
|
||||
BackupType::Incremental => {
|
||||
@ -457,8 +463,6 @@ impl Worker {
|
||||
.await?;
|
||||
}
|
||||
|
||||
drop(file_lock);
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@ -466,8 +470,8 @@ impl Worker {
|
||||
async fn process_symlink(
|
||||
&self,
|
||||
execution: &BackupExecution,
|
||||
source_path: &PathBuf,
|
||||
destination_path: &PathBuf,
|
||||
source_path: &Path,
|
||||
destination_path: &Path,
|
||||
) -> Result<(), Error> {
|
||||
if execution.options.follow_symlinks {
|
||||
self.follow_symlink(execution, source_path, destination_path)
|
||||
@ -481,15 +485,15 @@ impl Worker {
|
||||
async fn follow_symlink(
|
||||
&self,
|
||||
execution: &BackupExecution,
|
||||
source_path: &PathBuf,
|
||||
destination_path: &PathBuf,
|
||||
source_path: &Path,
|
||||
destination_path: &Path,
|
||||
) -> Result<(), Error> {
|
||||
let io_manager = &self.io_manager;
|
||||
|
||||
let mut queue = VecDeque::new();
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
queue.push_back((source_path.clone(), destination_path.clone()));
|
||||
queue.push_back((source_path.to_path_buf(), destination_path.to_path_buf()));
|
||||
|
||||
while let Some((current_source, current_dest)) = queue.pop_front() {
|
||||
let is_symlink = io_manager
|
||||
@ -502,7 +506,7 @@ impl Worker {
|
||||
Err(_) => continue,
|
||||
}
|
||||
} else {
|
||||
current_source.clone()
|
||||
current_source.to_path_buf()
|
||||
};
|
||||
|
||||
if visited.contains(&canonical_path) {
|
||||
@ -538,8 +542,8 @@ impl Worker {
|
||||
async fn copy_symlink(
|
||||
&self,
|
||||
execution: &BackupExecution,
|
||||
source_path: &PathBuf,
|
||||
destination_path: &PathBuf,
|
||||
source_path: &Path,
|
||||
destination_path: &Path,
|
||||
) -> Result<(), Error> {
|
||||
let io_manager = &self.io_manager;
|
||||
|
||||
@ -561,19 +565,15 @@ impl Worker {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
async fn full_backup(
|
||||
&self,
|
||||
source_path: &PathBuf,
|
||||
destination_path: &PathBuf,
|
||||
) -> Result<(), Error> {
|
||||
async fn full_backup(&self, source_path: &Path, destination_path: &Path) -> Result<(), Error> {
|
||||
let io_manager = &self.io_manager;
|
||||
io_manager.copy_file(source_path, destination_path).await
|
||||
}
|
||||
|
||||
async fn incremental_backup(
|
||||
&self,
|
||||
source_path: &PathBuf,
|
||||
destination_path: &PathBuf,
|
||||
source_path: &Path,
|
||||
destination_path: &Path,
|
||||
comparison_mode: ComparisonMode,
|
||||
) -> Result<(), Error> {
|
||||
let io_manager = &self.io_manager;
|
||||
@ -636,9 +636,9 @@ impl Worker {
|
||||
|
||||
fn calculate_destination_path(
|
||||
&self,
|
||||
source_path: &PathBuf,
|
||||
source_root: &PathBuf,
|
||||
destination_root: &PathBuf,
|
||||
source_path: &Path,
|
||||
source_root: &Path,
|
||||
destination_root: &Path,
|
||||
) -> Result<PathBuf, Error> {
|
||||
let relative_path = source_path
|
||||
.strip_prefix(source_root)
|
||||
@ -646,43 +646,3 @@ impl Worker {
|
||||
Ok(destination_root.join(relative_path))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ServiceUnit for BackupEngine {
|
||||
async fn run_impl(self: Arc<Self>, mut shutdown_rx: oneshot::Receiver<()>) {
|
||||
let backup_engine = self.clone();
|
||||
let event_bus = self.event_bus.clone();
|
||||
let add_execution = event_bus.subscribe::<ExecutionAddRequest>();
|
||||
let remove_execution = event_bus.subscribe::<ExecutionRemoveRequest>();
|
||||
let start_execution = event_bus.subscribe::<ExecutionStartRequest>();
|
||||
let resume_execution = event_bus.subscribe::<ExecutionResumeRequested>();
|
||||
let suspend_execution = event_bus.subscribe::<ExecutionSuspendRequest>();
|
||||
loop {
|
||||
if shutdown_rx.try_recv().is_ok() {
|
||||
break;
|
||||
}
|
||||
|
||||
while let Ok(event) = add_execution.try_recv() {
|
||||
backup_engine.add_execution(event.execution).await;
|
||||
}
|
||||
while let Ok(event) = remove_execution.try_recv() {
|
||||
backup_engine.remove_execution(&event.execution_id).await;
|
||||
}
|
||||
while let Ok(event) = start_execution.try_recv() {
|
||||
if let Err(err) = backup_engine.start_execution(event.execution_id).await {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
while let Ok(event) = resume_execution.try_recv() {
|
||||
if let Err(err) = backup_engine.resume_execution(event.execution_id).await {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
while let Ok(event) = suspend_execution.try_recv() {
|
||||
if let Err(err) = backup_engine.suspend_execution(event.execution_id).await {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/core/backup/backup_service.rs
Normal file
82
src/core/backup/backup_service.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use crate::core::backup::backup_engine::BackupEngine;
|
||||
use crate::core::backup::progress_tracker::ProgressTracker;
|
||||
use crate::core::infrastructure::actor_system::ActorSystem;
|
||||
use crate::core::infrastructure::app_config::AppConfig;
|
||||
use crate::core::infrastructure::io_manager::IOManager;
|
||||
use crate::interface::actor::actor::Actor;
|
||||
use crate::interface::actor::message::Message;
|
||||
use crate::model::core::backup::message::*;
|
||||
use crate::model::error::Error;
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct BackupService {
|
||||
backup_engine: Arc<BackupEngine>,
|
||||
}
|
||||
|
||||
impl BackupService {
|
||||
pub async fn init(
|
||||
app_config: Arc<AppConfig>,
|
||||
io_manager: Arc<IOManager>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
) {
|
||||
let progress_tracker = Arc::new(ProgressTracker::new(io_manager.clone()));
|
||||
let backup_engine = Arc::new(BackupEngine::new(
|
||||
app_config,
|
||||
io_manager,
|
||||
actor_system.clone(),
|
||||
progress_tracker,
|
||||
));
|
||||
let backup_service = Self {
|
||||
backup_engine,
|
||||
};
|
||||
actor_system.spawn(backup_service).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Actor for BackupService {
|
||||
type Message = BackupServiceMessage;
|
||||
|
||||
async fn pre_start(&mut self) {}
|
||||
|
||||
async fn post_stop(&mut self) {
|
||||
self.backup_engine.stop_all_executions().await;
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&mut self,
|
||||
message: Self::Message,
|
||||
) -> Result<<Self::Message as Message>::Response, Error> {
|
||||
match message {
|
||||
BackupServiceMessage::ServiceCall(service_call) => match service_call {
|
||||
ServiceCallMessage::AddExecution(execution) => {
|
||||
self.backup_engine.add_execution(execution).await;
|
||||
Ok(BackupServiceResponse::None)
|
||||
}
|
||||
ServiceCallMessage::RemoveExecution(uuid) => {
|
||||
self.backup_engine.resume_execution(uuid).await?;
|
||||
Ok(BackupServiceResponse::None)
|
||||
}
|
||||
ServiceCallMessage::StartExecution(uuid) => {
|
||||
self.backup_engine.start_execution(uuid).await?;
|
||||
Ok(BackupServiceResponse::None)
|
||||
}
|
||||
ServiceCallMessage::SuspendExecution(uuid) => {
|
||||
self.backup_engine.suspend_execution(uuid).await?;
|
||||
Ok(BackupServiceResponse::None)
|
||||
}
|
||||
ServiceCallMessage::ResumeExecution(uuid) => {
|
||||
self.backup_engine.resume_execution(uuid).await?;
|
||||
Ok(BackupServiceResponse::None)
|
||||
}
|
||||
ServiceCallMessage::GetExecutions => {
|
||||
let execution = self.backup_engine.get_all_executions();
|
||||
Ok(BackupServiceResponse::ServiceCall(
|
||||
ServiceCallResponse::GetExecutions(execution),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/core/backup/mod.rs
Normal file
3
src/core/backup/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod backup_engine;
|
||||
pub mod progress_tracker;
|
||||
pub mod backup_service;
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::core::io_manager::IOManager;
|
||||
use crate::core::infrastructure::io_manager::IOManager;
|
||||
use crate::interface::file_system::FileSystemTrait;
|
||||
use crate::model::backup::progress_data::ProgressData;
|
||||
use crate::model::core::backup::progress_data::ProgressData;
|
||||
use crate::model::error::Error;
|
||||
use crate::model::error::io::IOError;
|
||||
use crate::model::error::misc::MiscError;
|
||||
@ -1,44 +0,0 @@
|
||||
use crate::interface::event::Event;
|
||||
use dashmap::DashMap;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
|
||||
pub struct EventBus {
|
||||
channels: DashMap<TypeId, Vec<Box<dyn Fn(&dyn Any) + Send + Sync>>>,
|
||||
}
|
||||
|
||||
impl EventBus {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
channels: DashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe<E: Event>(&self) -> Receiver<E> {
|
||||
let (tx, rx) = channel();
|
||||
let type_id = TypeId::of::<E>();
|
||||
|
||||
let handler = Box::new(move |event: &dyn Any| {
|
||||
if let Some(typed_event) = event.downcast_ref::<E>() {
|
||||
let _ = tx.send(typed_event.clone());
|
||||
}
|
||||
});
|
||||
|
||||
self.channels
|
||||
.entry(type_id)
|
||||
.or_default()
|
||||
.push(handler);
|
||||
|
||||
rx
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn publish<E: Event>(&self, event: E) {
|
||||
let type_id = TypeId::of::<E>();
|
||||
if let Some(handlers) = self.channels.get(&type_id) {
|
||||
for handler in handlers.value() {
|
||||
handler(&event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/core/gui/gui_manager.rs
Normal file
61
src/core/gui/gui_manager.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use crate::core::gui::gui_message_handler::GuiMessageHandler;
|
||||
use crate::core::infrastructure::actor_system::ActorSystem;
|
||||
use crate::core::infrastructure::app_config::AppConfig;
|
||||
use crate::model::error::misc::MiscError;
|
||||
use crate::model::error::Error;
|
||||
use crate::ui::execution_page::ExecutionPage;
|
||||
use crate::ui::main_page::MainPage;
|
||||
use crate::ui::schedule_page::SchedulePage;
|
||||
use crate::utils::assets::Assets;
|
||||
use crate::utils::font;
|
||||
use eframe::egui;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct GuiManager {
|
||||
app_config: Arc<AppConfig>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
}
|
||||
|
||||
impl GuiManager {
|
||||
pub fn new(app_config: Arc<AppConfig>, actor_system: Arc<ActorSystem>) -> Self {
|
||||
Self {
|
||||
app_config,
|
||||
actor_system,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(&self) -> Result<(), Error> {
|
||||
let app_config = self.app_config.clone();
|
||||
let actor_system = self.actor_system.clone();
|
||||
|
||||
let mut handler = GuiMessageHandler::new();
|
||||
let message_rx = handler.subscribe();
|
||||
actor_system.spawn(handler).await;
|
||||
|
||||
let execution_page =
|
||||
ExecutionPage::new(app_config.clone(), actor_system.clone(), message_rx);
|
||||
let schedule_page = SchedulePage::new(app_config, actor_system)?;
|
||||
let main_page = MainPage::new(execution_page, schedule_page);
|
||||
|
||||
let icon_data = Assets::load_app_icon()?;
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([960.0, 540.0])
|
||||
.with_title("MirrorSphere")
|
||||
.with_icon(icon_data),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"MirrorSphere",
|
||||
options,
|
||||
Box::new(|cc| {
|
||||
font::setup_system_fonts(&cc.egui_ctx);
|
||||
Ok(Box::new(main_page))
|
||||
}),
|
||||
)
|
||||
.map_err(MiscError::UIPlatformError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
48
src/core/gui/gui_message_handler.rs
Normal file
48
src/core/gui/gui_message_handler.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use crate::interface::actor::actor::Actor;
|
||||
use crate::interface::actor::message::Message;
|
||||
use crate::model::core::gui::message::GuiMessage;
|
||||
use crate::model::error::Error;
|
||||
use async_trait::async_trait;
|
||||
use std::sync::mpsc;
|
||||
|
||||
pub struct GuiMessageHandler {
|
||||
subscriber: Vec<mpsc::Sender<GuiMessage>>,
|
||||
}
|
||||
|
||||
impl GuiMessageHandler {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
subscriber: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe(&mut self) -> mpsc::Receiver<GuiMessage> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
self.subscriber.push(tx);
|
||||
rx
|
||||
}
|
||||
|
||||
fn broadcast(&mut self, message: GuiMessage) {
|
||||
self.subscriber
|
||||
.retain(|tx| tx.send(message.clone()).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Actor for GuiMessageHandler {
|
||||
type Message = GuiMessage;
|
||||
|
||||
async fn pre_start(&mut self) {}
|
||||
|
||||
async fn post_stop(&mut self) {
|
||||
self.subscriber.clear();
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&mut self,
|
||||
message: Self::Message,
|
||||
) -> Result<<Self::Message as Message>::Response, Error> {
|
||||
self.broadcast(message);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
2
src/core/gui/mod.rs
Normal file
2
src/core/gui/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod gui_message_handler;
|
||||
pub mod gui_manager;
|
||||
@ -1,68 +0,0 @@
|
||||
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;
|
||||
|
||||
pub struct GuiManager {
|
||||
app_config: Arc<AppConfig>,
|
||||
event_bus: Arc<EventBus>,
|
||||
backup_engine: Arc<BackupEngine>,
|
||||
schedule_manager: Arc<ScheduleManager>,
|
||||
}
|
||||
|
||||
impl GuiManager {
|
||||
pub fn new(
|
||||
app_config: Arc<AppConfig>,
|
||||
event_bus: Arc<EventBus>,
|
||||
backup_engine: Arc<BackupEngine>,
|
||||
schedule_manager: Arc<ScheduleManager>,
|
||||
) -> Self {
|
||||
Self {
|
||||
app_config,
|
||||
event_bus,
|
||||
backup_engine,
|
||||
schedule_manager,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&self) -> Result<(), Error> {
|
||||
let config = self.app_config.clone();
|
||||
let event_bus = self.event_bus.clone();
|
||||
let backup_engine = self.backup_engine.clone();
|
||||
let schedule_manager = self.schedule_manager.clone();
|
||||
|
||||
let icon_data = Assets::load_app_icon()?;
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([800.0, 450.0])
|
||||
.with_title("MirrorSphere")
|
||||
.with_icon(icon_data),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eframe::run_native(
|
||||
"MirrorSphere",
|
||||
options,
|
||||
Box::new(|cc| {
|
||||
font::setup_system_fonts(&cc.egui_ctx);
|
||||
Ok(Box::new(MainPage::new(
|
||||
config,
|
||||
event_bus,
|
||||
backup_engine,
|
||||
schedule_manager,
|
||||
)))
|
||||
}),
|
||||
)
|
||||
.map_err(MiscError::UIPlatformError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
56
src/core/infrastructure/actor_system.rs
Normal file
56
src/core/infrastructure/actor_system.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use crate::interface::actor::actor::Actor;
|
||||
use crate::model::core::actor::actor_ref::ActorRef;
|
||||
use crate::model::core::actor::actor_runtime::ActorRuntime;
|
||||
use crate::model::error::system::SystemError;
|
||||
use dashmap::DashMap;
|
||||
use macros::log;
|
||||
use std::any::{Any, TypeId};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
pub struct ActorSystem {
|
||||
actors: DashMap<TypeId, Box<dyn Any + Send + Sync + 'static>>,
|
||||
shutdowns: DashMap<TypeId, oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl ActorSystem {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
actors: DashMap::new(),
|
||||
shutdowns: DashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn spawn<A>(&self, actor: A)
|
||||
where
|
||||
A: Actor + 'static,
|
||||
{
|
||||
let actor_id = TypeId::of::<A>();
|
||||
let (actor_runtime, actor_ref) = ActorRuntime::new(actor);
|
||||
let shutdown = actor_runtime.run().await;
|
||||
self.actors.insert(actor_id, Box::new(actor_ref));
|
||||
self.shutdowns.insert(actor_id, shutdown);
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) {
|
||||
let keys = self
|
||||
.shutdowns
|
||||
.iter()
|
||||
.map(|x| x.key().clone())
|
||||
.collect::<Vec<_>>();
|
||||
for key in keys {
|
||||
if let Some((_, shutdown)) = self.shutdowns.remove(&key) {
|
||||
if let Err(_) = shutdown.send(()) {
|
||||
log!(SystemError::ShutdownSignalFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn actor_of<A: Actor>(&self) -> Option<ActorRef<A::Message>> {
|
||||
let type_id = TypeId::of::<A>();
|
||||
self.actors
|
||||
.get(&type_id)?
|
||||
.downcast_ref::<ActorRef<A::Message>>()
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,7 @@ impl AppConfig {
|
||||
|
||||
fn load_config_file() -> Result<Config, Error> {
|
||||
let toml_string =
|
||||
fs::read_to_string("./config.toml").map_err(SystemError::ConfigNotFound)?;
|
||||
fs::read_to_string("config.toml").map_err(SystemError::ConfigNotFound)?;
|
||||
let config = toml::from_str::<ConfigTable>(&toml_string)
|
||||
.map_err(SystemError::InvalidConfig)?
|
||||
.config;
|
||||
@ -1,9 +1,10 @@
|
||||
use crate::interface::repository::schedule::ScheduleRepository;
|
||||
use crate::model::error::Error;
|
||||
use crate::model::error::database::DatabaseError;
|
||||
use crate::model::error::Error;
|
||||
use crate::model::log::database::DatabaseLog;
|
||||
use crate::model::log::system::SystemLog;
|
||||
use crate::platform::constants::*;
|
||||
use crate::utils::database_lock::DatabaseLock;
|
||||
use macros::log;
|
||||
use sqlx::SqlitePool;
|
||||
use tokio::fs;
|
||||
@ -12,11 +13,13 @@ use tokio::fs::File;
|
||||
#[derive(Debug)]
|
||||
pub struct DatabaseManager {
|
||||
pool: SqlitePool,
|
||||
_lock: DatabaseLock,
|
||||
}
|
||||
|
||||
impl DatabaseManager {
|
||||
pub async fn new() -> Result<Self, Error> {
|
||||
log!(SystemLog::Initializing);
|
||||
let lock = DatabaseLock::acquire().await?;
|
||||
if !Self::exist_database().await {
|
||||
Self::create_database().await?;
|
||||
}
|
||||
@ -24,7 +27,7 @@ impl DatabaseManager {
|
||||
.await
|
||||
.map_err(DatabaseError::DatabaseConnectFailed)?;
|
||||
log!(DatabaseLog::DatabaseConnectSuccess);
|
||||
let database_manager = Self { pool };
|
||||
let database_manager = Self { pool, _lock: lock };
|
||||
if !database_manager.exist_table("BackupSchedules").await {
|
||||
database_manager.create_backup_schedule_table().await?;
|
||||
}
|
||||
@ -36,11 +39,6 @@ impl DatabaseManager {
|
||||
self.pool.clone()
|
||||
}
|
||||
|
||||
pub async fn close_connection(&self) {
|
||||
let pool = self.get_pool();
|
||||
pool.close().await
|
||||
}
|
||||
|
||||
pub async fn exist_database() -> bool {
|
||||
fs::metadata(DATABASE_PATH).await.is_ok()
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::core::app_config::AppConfig;
|
||||
use crate::core::infrastructure::app_config::AppConfig;
|
||||
use crate::interface::file_system::FileSystemTrait;
|
||||
use crate::platform::file_system::FileSystem;
|
||||
use std::ops::Deref;
|
||||
@ -17,10 +17,6 @@ impl IOManager {
|
||||
file_system: FileSystem::new(semaphore),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn terminate(&self) {
|
||||
self.file_system.semaphore().close();
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for IOManager {
|
||||
@ -30,3 +26,9 @@ impl Deref for IOManager {
|
||||
&self.file_system
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IOManager {
|
||||
fn drop(&mut self) {
|
||||
self.file_system.semaphore().close();
|
||||
}
|
||||
}
|
||||
4
src/core/infrastructure/mod.rs
Normal file
4
src/core/infrastructure/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod app_config;
|
||||
pub mod database_manager;
|
||||
pub mod io_manager;
|
||||
pub mod actor_system;
|
||||
@ -1,9 +1,5 @@
|
||||
pub mod app_config;
|
||||
pub mod backup_engine;
|
||||
pub mod database_manager;
|
||||
pub mod event_bus;
|
||||
pub mod gui_manager;
|
||||
pub mod io_manager;
|
||||
pub mod progress_tracker;
|
||||
pub mod schedule_manager;
|
||||
pub mod backup;
|
||||
pub mod gui;
|
||||
pub mod infrastructure;
|
||||
pub mod schedule;
|
||||
pub mod system;
|
||||
|
||||
3
src/core/schedule/mod.rs
Normal file
3
src/core/schedule/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod schedule_manager;
|
||||
pub mod schedule_service;
|
||||
pub mod schedule_timer;
|
||||
145
src/core/schedule/schedule_manager.rs
Normal file
145
src/core/schedule/schedule_manager.rs
Normal file
@ -0,0 +1,145 @@
|
||||
use crate::core::backup::backup_service::BackupService;
|
||||
use crate::core::infrastructure::actor_system::ActorSystem;
|
||||
use crate::core::infrastructure::database_manager::DatabaseManager;
|
||||
use crate::interface::repository::schedule::ScheduleRepository;
|
||||
use crate::model::core::backup::message::{BackupServiceMessage, ServiceCallMessage};
|
||||
use crate::model::core::schedule::backup_schedule::*;
|
||||
use crate::model::error::Error;
|
||||
use chrono::{Duration, Months, Utc};
|
||||
use dashmap::DashMap;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct ScheduleManager {
|
||||
database_manager: Arc<DatabaseManager>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
schedules: DashMap<Uuid, BackupSchedule>,
|
||||
}
|
||||
|
||||
impl ScheduleManager {
|
||||
pub async fn new(
|
||||
database_manager: Arc<DatabaseManager>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
) -> Result<Self, Error> {
|
||||
let schedules = DashMap::new();
|
||||
let database_schedules = database_manager.get_all_backup_schedules().await?;
|
||||
for schedule in database_schedules {
|
||||
schedules.insert(schedule.uuid, schedule);
|
||||
}
|
||||
let schedule_manager = ScheduleManager {
|
||||
database_manager,
|
||||
actor_system,
|
||||
schedules,
|
||||
};
|
||||
Ok(schedule_manager)
|
||||
}
|
||||
|
||||
pub async fn get_all_schedules(&self) -> Vec<BackupSchedule> {
|
||||
self.schedules.iter().map(|x| x.value().clone()).collect()
|
||||
}
|
||||
|
||||
pub async fn create_schedule(&self, schedule: BackupSchedule) -> Result<(), Error> {
|
||||
self.database_manager
|
||||
.create_backup_schedule(&schedule)
|
||||
.await?;
|
||||
self.schedules.insert(schedule.uuid, schedule);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn modify_schedule(&self, schedule: BackupSchedule) -> Result<(), Error> {
|
||||
self.database_manager
|
||||
.modify_backup_schedule(&schedule)
|
||||
.await?;
|
||||
self.schedules.insert(schedule.uuid, schedule);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_schedule(&self, uuid: Uuid) -> Result<(), Error> {
|
||||
self.database_manager.remove_backup_schedule(uuid).await?;
|
||||
self.schedules.remove(&uuid);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn active_schedule(&self, uuid: Uuid) -> Result<(), Error> {
|
||||
if let Some(mut schedule) = self.database_manager.get_backup_schedule(uuid).await? {
|
||||
schedule.state = ScheduleState::Active;
|
||||
self.database_manager
|
||||
.modify_backup_schedule(&schedule)
|
||||
.await?;
|
||||
self.schedules.insert(schedule.uuid, schedule);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn pause_schedule(&self, uuid: Uuid) -> Result<(), Error> {
|
||||
if let Some(mut schedule) = self.database_manager.get_backup_schedule(uuid).await? {
|
||||
schedule.state = ScheduleState::Paused;
|
||||
self.database_manager
|
||||
.modify_backup_schedule(&schedule)
|
||||
.await?;
|
||||
self.schedules.insert(schedule.uuid, schedule);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn disable_schedule(&self, uuid: Uuid) -> Result<(), Error> {
|
||||
if let Some(mut schedule) = self.database_manager.get_backup_schedule(uuid).await? {
|
||||
schedule.state = ScheduleState::Disabled;
|
||||
self.database_manager
|
||||
.modify_backup_schedule(&schedule)
|
||||
.await?;
|
||||
self.schedules.insert(schedule.uuid, schedule);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn execute_ready_schedule(&self) -> Result<(), Error> {
|
||||
let database_manager = self.database_manager.clone();
|
||||
|
||||
let now = Utc::now().naive_utc();
|
||||
let mut schedules = self.get_all_schedules().await;
|
||||
|
||||
for schedule in schedules.iter_mut() {
|
||||
if schedule.state != ScheduleState::Active {
|
||||
continue;
|
||||
}
|
||||
if let Some(next_run_time) = schedule.next_run_time {
|
||||
if next_run_time >= now {
|
||||
continue;
|
||||
}
|
||||
let execution = schedule.to_execution();
|
||||
if let Some(service_ref) = self.actor_system.actor_of::<BackupService>() {
|
||||
service_ref
|
||||
.tell(BackupServiceMessage::ServiceCall(
|
||||
ServiceCallMessage::AddExecution(execution),
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
self.update_next_run_time(schedule);
|
||||
database_manager.modify_backup_schedule(schedule).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_next_run_time(&self, schedule: &mut BackupSchedule) {
|
||||
if schedule.next_run_time.is_none() {
|
||||
return;
|
||||
}
|
||||
let now = Utc::now().naive_utc();
|
||||
let old_next_run_time = schedule.next_run_time.unwrap();
|
||||
let new_next_run_time = match schedule.interval {
|
||||
ScheduleInterval::Once => None,
|
||||
ScheduleInterval::Daily => Some(old_next_run_time + Duration::days(1)),
|
||||
ScheduleInterval::Weekly => Some(old_next_run_time + Duration::days(7)),
|
||||
ScheduleInterval::Monthly => Some(
|
||||
old_next_run_time
|
||||
.checked_add_months(Months::new(1))
|
||||
.unwrap_or(old_next_run_time + Duration::days(30)),
|
||||
),
|
||||
};
|
||||
schedule.last_run_time = Some(now);
|
||||
schedule.next_run_time = new_next_run_time;
|
||||
}
|
||||
}
|
||||
123
src/core/schedule/schedule_service.rs
Normal file
123
src/core/schedule/schedule_service.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use crate::core::infrastructure::actor_system::ActorSystem;
|
||||
use crate::core::infrastructure::app_config::AppConfig;
|
||||
use crate::core::infrastructure::database_manager::DatabaseManager;
|
||||
use crate::core::schedule::schedule_manager::ScheduleManager;
|
||||
use crate::core::schedule::schedule_timer::ScheduleTimer;
|
||||
use crate::interface::actor::actor::Actor;
|
||||
use crate::interface::actor::message::Message;
|
||||
use crate::model::core::schedule::message::*;
|
||||
use crate::model::error::system::SystemError;
|
||||
use crate::model::error::Error;
|
||||
use async_trait::async_trait;
|
||||
use macros::log;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
pub struct ScheduleService {
|
||||
schedule_manager: Arc<ScheduleManager>,
|
||||
schedule_timer: Arc<ScheduleTimer>,
|
||||
timer_refresh: OnceLock<mpsc::UnboundedSender<()>>,
|
||||
shutdowns: Vec<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl ScheduleService {
|
||||
pub async fn init(
|
||||
app_config: Arc<AppConfig>,
|
||||
database_manager: Arc<DatabaseManager>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
) -> Result<(), Error> {
|
||||
let schedule_manager =
|
||||
Arc::new(ScheduleManager::new(database_manager, actor_system.clone()).await?);
|
||||
let schedule_timer = Arc::new(ScheduleTimer::new(app_config, actor_system.clone()));
|
||||
let schedule_service = Self {
|
||||
schedule_manager,
|
||||
schedule_timer,
|
||||
timer_refresh: OnceLock::new(),
|
||||
shutdowns: Vec::new(),
|
||||
};
|
||||
actor_system.spawn(schedule_service).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn refresh_timer(&self) {
|
||||
if let Some(timer_refresh) = self.timer_refresh.get() {
|
||||
let _ = timer_refresh.send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Actor for ScheduleService {
|
||||
type Message = ScheduleServiceMessage;
|
||||
|
||||
async fn pre_start(&mut self) {
|
||||
let schedule_timer = self.schedule_timer.clone();
|
||||
if let Ok((timer_refresh, shutdown)) = schedule_timer.run().await {
|
||||
self.timer_refresh.get_or_init(|| timer_refresh);
|
||||
self.shutdowns.push(shutdown);
|
||||
}
|
||||
}
|
||||
|
||||
async fn post_stop(&mut self) {
|
||||
let shutdowns = std::mem::take(&mut self.shutdowns);
|
||||
for shutdown in shutdowns {
|
||||
if let Err(_) = shutdown.send(()) {
|
||||
log!(SystemError::ShutdownSignalFailed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn receive(
|
||||
&mut self,
|
||||
message: Self::Message,
|
||||
) -> Result<<Self::Message as Message>::Response, Error> {
|
||||
match message {
|
||||
ScheduleServiceMessage::UnitNotification(unit_notification) => {
|
||||
match unit_notification {
|
||||
UnitNotificationMessage::CheckSchedule => {
|
||||
self.schedule_manager.execute_ready_schedule().await?;
|
||||
Ok(ScheduleServiceResponse::None)
|
||||
}
|
||||
}
|
||||
}
|
||||
ScheduleServiceMessage::ServiceCall(service_call) => match service_call {
|
||||
ServiceCallMessage::AddSchedule(schedule) => {
|
||||
self.schedule_manager.create_schedule(schedule).await?;
|
||||
self.refresh_timer();
|
||||
Ok(ScheduleServiceResponse::None)
|
||||
}
|
||||
ServiceCallMessage::ModifySchedule(schedule) => {
|
||||
self.schedule_manager.modify_schedule(schedule).await?;
|
||||
self.refresh_timer();
|
||||
Ok(ScheduleServiceResponse::None)
|
||||
}
|
||||
ServiceCallMessage::RemoveSchedule(uuid) => {
|
||||
self.schedule_manager.remove_schedule(uuid).await?;
|
||||
self.refresh_timer();
|
||||
Ok(ScheduleServiceResponse::None)
|
||||
}
|
||||
ServiceCallMessage::ActivateSchedule(uuid) => {
|
||||
self.schedule_manager.active_schedule(uuid).await?;
|
||||
self.refresh_timer();
|
||||
Ok(ScheduleServiceResponse::None)
|
||||
}
|
||||
ServiceCallMessage::PauseSchedule(uuid) => {
|
||||
self.schedule_manager.pause_schedule(uuid).await?;
|
||||
self.refresh_timer();
|
||||
Ok(ScheduleServiceResponse::None)
|
||||
}
|
||||
ServiceCallMessage::DisableSchedule(uuid) => {
|
||||
self.schedule_manager.disable_schedule(uuid).await?;
|
||||
self.refresh_timer();
|
||||
Ok(ScheduleServiceResponse::None)
|
||||
}
|
||||
ServiceCallMessage::GetSchedules => {
|
||||
let schedules = self.schedule_manager.get_all_schedules().await;
|
||||
Ok(ScheduleServiceResponse::ServiceCall(
|
||||
ServiceCallResponse::GetSchedules(schedules),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
107
src/core/schedule/schedule_timer.rs
Normal file
107
src/core/schedule/schedule_timer.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use crate::core::infrastructure::actor_system::ActorSystem;
|
||||
use crate::core::infrastructure::app_config::AppConfig;
|
||||
use crate::core::schedule::schedule_service::ScheduleService;
|
||||
use crate::model::core::schedule::backup_schedule::ScheduleState;
|
||||
use crate::model::core::schedule::message::*;
|
||||
use crate::model::error::actor::ActorError;
|
||||
use crate::model::error::Error;
|
||||
use chrono::{Duration, Utc};
|
||||
use std::sync::Arc;
|
||||
use tokio::select;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::time::sleep;
|
||||
use tracing::error;
|
||||
|
||||
pub struct ScheduleTimer {
|
||||
app_config: Arc<AppConfig>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
}
|
||||
|
||||
impl ScheduleTimer {
|
||||
pub fn new(app_config: Arc<AppConfig>, actor_system: Arc<ActorSystem>) -> Self {
|
||||
ScheduleTimer {
|
||||
app_config,
|
||||
actor_system,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
self: Arc<Self>,
|
||||
) -> Result<(mpsc::UnboundedSender<()>, oneshot::Sender<()>), Error> {
|
||||
let (refresh_tx, mut refresh_rx) = mpsc::unbounded_channel();
|
||||
let (shutdown_tx, mut shutdown_rx) = oneshot::channel();
|
||||
let service_ref = self
|
||||
.actor_system
|
||||
.actor_of::<ScheduleService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let mut sleep_time = match self.calculate_sleep_duration().await {
|
||||
Ok(Some(duration)) => duration,
|
||||
Ok(None) => Duration::seconds(self.app_config.default_wakeup_time),
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
Duration::seconds(self.app_config.default_wakeup_time)
|
||||
}
|
||||
};
|
||||
if sleep_time < Duration::seconds(0) {
|
||||
sleep_time = Duration::seconds(0);
|
||||
}
|
||||
select! {
|
||||
biased;
|
||||
_ = &mut shutdown_rx => { break; }
|
||||
_ = refresh_rx.recv() => { continue; }
|
||||
_ = sleep(sleep_time.to_std().unwrap()) => {}
|
||||
}
|
||||
if let Err(err) = service_ref
|
||||
.tell(ScheduleServiceMessage::UnitNotification(
|
||||
UnitNotificationMessage::CheckSchedule,
|
||||
))
|
||||
.await
|
||||
{
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok((refresh_tx, shutdown_tx))
|
||||
}
|
||||
|
||||
async fn calculate_sleep_duration(&self) -> Result<Option<Duration>, Error> {
|
||||
let mut next_time = None;
|
||||
let service_ref = self
|
||||
.actor_system
|
||||
.actor_of::<ScheduleService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let response = service_ref
|
||||
.ask(ScheduleServiceMessage::ServiceCall(
|
||||
ServiceCallMessage::GetSchedules,
|
||||
))
|
||||
.await?;
|
||||
let ScheduleServiceResponse::ServiceCall(service_call) = response else {
|
||||
return Ok(None);
|
||||
};
|
||||
let ServiceCallResponse::GetSchedules(schedules) = service_call;
|
||||
for schedule in schedules {
|
||||
if schedule.state != ScheduleState::Active {
|
||||
continue;
|
||||
}
|
||||
if let Some(schedule_next_time) = schedule.next_run_time {
|
||||
match next_time {
|
||||
Some(current_time) => {
|
||||
if schedule_next_time < current_time {
|
||||
next_time = Some(schedule_next_time);
|
||||
}
|
||||
}
|
||||
None => next_time = Some(schedule_next_time),
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(schedule_next_time) = next_time {
|
||||
let now = Utc::now().naive_utc();
|
||||
let duration = schedule_next_time.signed_duration_since(now);
|
||||
Ok(Some(Duration::seconds(duration.num_seconds().max(0))))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,304 +0,0 @@
|
||||
use crate::core::app_config::AppConfig;
|
||||
use crate::core::database_manager::DatabaseManager;
|
||||
use crate::core::event_bus::EventBus;
|
||||
use crate::interface::repository::schedule::ScheduleRepository;
|
||||
use crate::interface::service_unit::ServiceUnit;
|
||||
use crate::model::backup::backup_schedule::*;
|
||||
use crate::model::error::Error;
|
||||
use crate::model::error::system::SystemError;
|
||||
use crate::model::error::task::TaskError;
|
||||
use crate::model::event::execution::*;
|
||||
use crate::model::event::schedule::*;
|
||||
use crate::model::log::system::SystemLog;
|
||||
use async_trait::async_trait;
|
||||
use chrono::{Duration, Months, Utc};
|
||||
use macros::log;
|
||||
use std::sync::Arc;
|
||||
use tokio::select;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::time::sleep;
|
||||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct ScheduleManager {
|
||||
app_config: Arc<AppConfig>,
|
||||
event_bus: Arc<EventBus>,
|
||||
database_manager: Arc<DatabaseManager>,
|
||||
}
|
||||
|
||||
impl ScheduleManager {
|
||||
pub fn new(
|
||||
app_config: Arc<AppConfig>,
|
||||
event_bus: Arc<EventBus>,
|
||||
database_manager: Arc<DatabaseManager>,
|
||||
) -> Self {
|
||||
ScheduleManager {
|
||||
app_config,
|
||||
event_bus,
|
||||
database_manager,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_all_schedules(&self) -> Result<Vec<BackupSchedule>, Error> {
|
||||
self.database_manager.get_all_backup_schedules().await
|
||||
}
|
||||
|
||||
pub async fn create_schedule(&self, schedule: BackupSchedule) -> Result<(), Error> {
|
||||
self.database_manager
|
||||
.create_backup_schedule(&schedule)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn modify_schedule(&self, schedule: BackupSchedule) -> Result<(), Error> {
|
||||
self.database_manager
|
||||
.modify_backup_schedule(&schedule)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn remove_schedule(&self, uuid: Uuid) -> Result<(), Error> {
|
||||
self.database_manager.remove_backup_schedule(uuid).await
|
||||
}
|
||||
|
||||
pub async fn active_schedule(&self, uuid: Uuid) -> Result<(), Error> {
|
||||
if let Some(mut schedule) = self.database_manager.get_backup_schedule(uuid).await? {
|
||||
schedule.state = ScheduleState::Active;
|
||||
self.database_manager
|
||||
.modify_backup_schedule(&schedule)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn pause_schedule(&self, uuid: Uuid) -> Result<(), Error> {
|
||||
if let Some(mut schedule) = self.database_manager.get_backup_schedule(uuid).await? {
|
||||
schedule.state = ScheduleState::Paused;
|
||||
self.database_manager
|
||||
.modify_backup_schedule(&schedule)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn disable_schedule(&self, uuid: Uuid) -> Result<(), Error> {
|
||||
if let Some(mut schedule) = self.database_manager.get_backup_schedule(uuid).await? {
|
||||
schedule.state = ScheduleState::Disabled;
|
||||
self.database_manager
|
||||
.modify_backup_schedule(&schedule)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn execute_ready_schedule(&self) -> Result<(), Error> {
|
||||
let event_bus = self.event_bus.clone();
|
||||
let database_manager = self.database_manager.clone();
|
||||
|
||||
let now = Utc::now().naive_utc();
|
||||
let mut schedules = self.get_all_schedules().await?;
|
||||
|
||||
for schedule in schedules.iter_mut() {
|
||||
if schedule.state != ScheduleState::Active {
|
||||
continue;
|
||||
}
|
||||
if let Some(next_run_time) = schedule.next_run_time {
|
||||
if next_run_time < now {
|
||||
let execution = schedule.to_execution();
|
||||
event_bus.publish(ExecutionAddRequest { execution });
|
||||
self.update_next_run_time(schedule);
|
||||
database_manager.modify_backup_schedule(&schedule).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_next_run_time(&self, schedule: &mut BackupSchedule) {
|
||||
if schedule.next_run_time.is_none() {
|
||||
return;
|
||||
}
|
||||
let now = Utc::now().naive_utc();
|
||||
let old_next_run_time = schedule.next_run_time.unwrap();
|
||||
let new_next_run_time = match schedule.interval {
|
||||
ScheduleInterval::Once => None,
|
||||
ScheduleInterval::Daily => Some(old_next_run_time + Duration::days(1)),
|
||||
ScheduleInterval::Weekly => Some(old_next_run_time + Duration::days(7)),
|
||||
ScheduleInterval::Monthly => Some(
|
||||
old_next_run_time
|
||||
.checked_add_months(Months::new(1))
|
||||
.unwrap_or(old_next_run_time + Duration::days(30)),
|
||||
),
|
||||
};
|
||||
schedule.last_run_time = Some(now);
|
||||
schedule.next_run_time = new_next_run_time;
|
||||
}
|
||||
}
|
||||
|
||||
struct ScheduleTimer {
|
||||
app_config: Arc<AppConfig>,
|
||||
schedule_manager: Arc<ScheduleManager>,
|
||||
shutdown_rx: Option<oneshot::Receiver<()>>,
|
||||
refresh_rx: mpsc::UnboundedReceiver<()>,
|
||||
}
|
||||
|
||||
impl ScheduleTimer {
|
||||
pub fn new(
|
||||
app_config: Arc<AppConfig>,
|
||||
schedule_manager: Arc<ScheduleManager>,
|
||||
shutdown_rx: oneshot::Receiver<()>,
|
||||
refresh_rx: mpsc::UnboundedReceiver<()>,
|
||||
) -> Self {
|
||||
ScheduleTimer {
|
||||
app_config,
|
||||
schedule_manager,
|
||||
shutdown_rx: Some(shutdown_rx),
|
||||
refresh_rx,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(mut self) {
|
||||
let schedule_manager = self.schedule_manager.clone();
|
||||
match self.shutdown_rx.take() {
|
||||
Some(mut shutdown_rx) => loop {
|
||||
let mut sleep_time = match self.calculate_sleep_duration().await {
|
||||
Ok(Some(duration)) => duration,
|
||||
Ok(None) => Duration::seconds(self.app_config.default_wakeup_time),
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
Duration::seconds(self.app_config.default_wakeup_time)
|
||||
}
|
||||
};
|
||||
if sleep_time < Duration::seconds(0) {
|
||||
sleep_time = Duration::seconds(0);
|
||||
}
|
||||
select! {
|
||||
biased;
|
||||
_ = &mut shutdown_rx => { break; }
|
||||
_ = self.refresh_rx.recv() => {}
|
||||
_ = sleep(sleep_time.to_std().unwrap()) => {}
|
||||
}
|
||||
if let Err(err) = schedule_manager.execute_ready_schedule().await {
|
||||
error!("{}", err);
|
||||
}
|
||||
},
|
||||
None => log!(TaskError::IllegalRunState),
|
||||
}
|
||||
}
|
||||
|
||||
async fn calculate_sleep_duration(&self) -> Result<Option<Duration>, Error> {
|
||||
let mut next_time = None;
|
||||
|
||||
let schedules = self.schedule_manager.get_all_schedules().await?;
|
||||
|
||||
for schedule in schedules {
|
||||
if schedule.state != ScheduleState::Active {
|
||||
continue;
|
||||
}
|
||||
if let Some(schedule_next_time) = schedule.next_run_time {
|
||||
match next_time {
|
||||
Some(current_time) => {
|
||||
if schedule_next_time < current_time {
|
||||
next_time = Some(schedule_next_time);
|
||||
}
|
||||
}
|
||||
None => next_time = Some(schedule_next_time),
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(schedule_next_time) = next_time {
|
||||
let now = Utc::now().naive_utc();
|
||||
let duration = schedule_next_time.signed_duration_since(now);
|
||||
Ok(Some(Duration::seconds(duration.num_seconds().max(0))))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ServiceUnit for ScheduleManager {
|
||||
async fn run_impl(self: Arc<Self>, mut shutdown_rx: oneshot::Receiver<()>) {
|
||||
let (timer_shutdown_tx, timer_shutdown_rx) = oneshot::channel();
|
||||
let (timer_refresh_tx, timer_refresh_rx) = mpsc::unbounded_channel();
|
||||
|
||||
let schedule_timer = ScheduleTimer::new(
|
||||
self.app_config.clone(),
|
||||
self.clone(),
|
||||
timer_shutdown_rx,
|
||||
timer_refresh_rx,
|
||||
);
|
||||
|
||||
tokio::spawn(schedule_timer.run());
|
||||
|
||||
let event_bus = self.event_bus.clone();
|
||||
let schedule_manager = self.clone();
|
||||
let create_schedule = event_bus.subscribe::<ScheduleCreateRequest>();
|
||||
let modify_schedule = event_bus.subscribe::<ScheduleModifyRequest>();
|
||||
let remove_schedule = event_bus.subscribe::<ScheduleRemoveRequest>();
|
||||
let active_schedule = event_bus.subscribe::<ScheduleActiveRequest>();
|
||||
let pause_schedule = event_bus.subscribe::<SchedulePauseRequest>();
|
||||
let disable_schedule = event_bus.subscribe::<ScheduleDisableRequest>();
|
||||
let sleep_duration = Duration::milliseconds(self.app_config.internal_timestamp)
|
||||
.to_std()
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
if shutdown_rx.try_recv().is_ok() {
|
||||
log!(SystemLog::Terminating);
|
||||
if timer_shutdown_tx.send(()).is_err() {
|
||||
log!(SystemError::TerminateError(
|
||||
"Fail send shutdown signal to timer"
|
||||
))
|
||||
} else {
|
||||
log!(SystemLog::TerminateComplete);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let mut need_refresh = false;
|
||||
|
||||
while let Ok(event) = create_schedule.try_recv() {
|
||||
match schedule_manager.create_schedule(event.schedule).await {
|
||||
Ok(_) => need_refresh = true,
|
||||
Err(err) => error!("{}", err),
|
||||
}
|
||||
}
|
||||
while let Ok(event) = modify_schedule.try_recv() {
|
||||
match schedule_manager.modify_schedule(event.schedule).await {
|
||||
Ok(_) => need_refresh = true,
|
||||
Err(err) => error!("{}", err),
|
||||
}
|
||||
}
|
||||
while let Ok(event) = remove_schedule.try_recv() {
|
||||
match schedule_manager.remove_schedule(event.schedule_id).await {
|
||||
Ok(_) => need_refresh = true,
|
||||
Err(err) => error!("{}", err),
|
||||
}
|
||||
}
|
||||
while let Ok(event) = active_schedule.try_recv() {
|
||||
match schedule_manager.active_schedule(event.schedule_id).await {
|
||||
Ok(_) => need_refresh = true,
|
||||
Err(err) => error!("{}", err),
|
||||
}
|
||||
}
|
||||
while let Ok(event) = pause_schedule.try_recv() {
|
||||
match schedule_manager.pause_schedule(event.schedule_id).await {
|
||||
Ok(_) => need_refresh = true,
|
||||
Err(err) => error!("{}", err),
|
||||
}
|
||||
}
|
||||
while let Ok(event) = disable_schedule.try_recv() {
|
||||
match schedule_manager.disable_schedule(event.schedule_id).await {
|
||||
Ok(_) => need_refresh = true,
|
||||
Err(err) => error!("{}", err),
|
||||
}
|
||||
}
|
||||
|
||||
if need_refresh {
|
||||
let _ = timer_refresh_tx.send(());
|
||||
}
|
||||
|
||||
sleep(sleep_duration).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,99 +1,66 @@
|
||||
use crate::core::app_config::AppConfig;
|
||||
use crate::core::backup_engine::BackupEngine;
|
||||
use crate::core::database_manager::DatabaseManager;
|
||||
use crate::core::event_bus::EventBus;
|
||||
use crate::core::gui_manager::GuiManager;
|
||||
use crate::core::io_manager::IOManager;
|
||||
use crate::core::progress_tracker::ProgressTracker;
|
||||
use crate::core::schedule_manager::ScheduleManager;
|
||||
use crate::interface::service_unit::ServiceUnit;
|
||||
use crate::core::backup::backup_service::BackupService;
|
||||
use crate::core::gui::gui_manager::GuiManager;
|
||||
use crate::core::infrastructure::actor_system::ActorSystem;
|
||||
use crate::core::infrastructure::app_config::AppConfig;
|
||||
use crate::core::infrastructure::database_manager::DatabaseManager;
|
||||
use crate::core::infrastructure::io_manager::IOManager;
|
||||
use crate::core::schedule::schedule_service::ScheduleService;
|
||||
use crate::model::error::Error;
|
||||
use crate::model::error::system::SystemError;
|
||||
use crate::model::log::system::SystemLog;
|
||||
#[cfg(any(target_os = "windows", not(debug_assertions)))]
|
||||
use crate::platform::elevate;
|
||||
use crate::utils::database_lock::DatabaseLock;
|
||||
use crate::utils::logging::Logging;
|
||||
use macros::log;
|
||||
#[cfg(not(debug_assertions))]
|
||||
use privilege::user::privileged;
|
||||
use std::mem;
|
||||
#[cfg(not(debug_assertions))]
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
pub struct System {
|
||||
pub io_manager: Arc<IOManager>,
|
||||
pub database_manager: Arc<DatabaseManager>,
|
||||
pub backup_engine: Arc<BackupEngine>,
|
||||
pub schedule_manager: Arc<ScheduleManager>,
|
||||
pub gui_manager: Arc<GuiManager>,
|
||||
pub _database_lock: DatabaseLock,
|
||||
pub shutdowns: Vec<oneshot::Sender<()>>,
|
||||
app_config: Arc<AppConfig>,
|
||||
io_manager: Arc<IOManager>,
|
||||
database_manager: Arc<DatabaseManager>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
}
|
||||
|
||||
impl System {
|
||||
pub async fn new() -> Result<Self, Error> {
|
||||
let app_config = Arc::new(AppConfig::new()?);
|
||||
let io_manager = Arc::new(IOManager::new(app_config.clone()));
|
||||
let database_manager = Arc::new(DatabaseManager::new().await?);
|
||||
let actor_system = Arc::new(ActorSystem::new());
|
||||
let system = Self {
|
||||
app_config,
|
||||
io_manager,
|
||||
database_manager,
|
||||
actor_system,
|
||||
};
|
||||
Ok(system)
|
||||
}
|
||||
|
||||
pub async fn run(&self) -> Result<(), Error> {
|
||||
Logging::initialize().await;
|
||||
log!(SystemLog::Initializing);
|
||||
Self::elevate_privileges()?;
|
||||
let app_config = Arc::new(AppConfig::new()?);
|
||||
let event_bus = Arc::new(EventBus::new());
|
||||
let io_manager = Arc::new(IOManager::new(app_config.clone()));
|
||||
let _database_lock = DatabaseLock::acquire().await?;
|
||||
let database_manager = Arc::new(DatabaseManager::new().await?);
|
||||
let progress_tracker = Arc::new(ProgressTracker::new(io_manager.clone()));
|
||||
let backup_engine = Arc::new(BackupEngine::new(
|
||||
let app_config = self.app_config.clone();
|
||||
let io_manager = self.io_manager.clone();
|
||||
let database_manager = self.database_manager.clone();
|
||||
let actor_system = self.actor_system.clone();
|
||||
BackupService::init(app_config.clone(), io_manager.clone(), actor_system.clone()).await;
|
||||
ScheduleService::init(
|
||||
app_config.clone(),
|
||||
event_bus.clone(),
|
||||
io_manager.clone(),
|
||||
progress_tracker.clone(),
|
||||
));
|
||||
let schedule_manager = Arc::new(ScheduleManager::new(
|
||||
app_config.clone(),
|
||||
event_bus.clone(),
|
||||
database_manager.clone(),
|
||||
));
|
||||
let gui_manager = Arc::new(GuiManager::new(
|
||||
app_config.clone(),
|
||||
event_bus.clone(),
|
||||
backup_engine.clone(),
|
||||
schedule_manager.clone(),
|
||||
));
|
||||
actor_system.clone(),
|
||||
)
|
||||
.await?;
|
||||
let gui_manager = Arc::new(GuiManager::new(app_config.clone(), actor_system.clone()));
|
||||
log!(SystemLog::InitializeComplete);
|
||||
Ok(Self {
|
||||
io_manager,
|
||||
database_manager,
|
||||
backup_engine,
|
||||
schedule_manager,
|
||||
gui_manager,
|
||||
_database_lock,
|
||||
shutdowns: Vec::new(),
|
||||
})
|
||||
gui_manager.start().await
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<(), Error> {
|
||||
let gui_manager = self.gui_manager.clone();
|
||||
let backup_engine_shutdown = self.backup_engine.clone().run().await;
|
||||
let schedule_manager_shutdown = self.schedule_manager.clone().run().await;
|
||||
self.shutdowns.push(backup_engine_shutdown);
|
||||
self.shutdowns.push(schedule_manager_shutdown);
|
||||
gui_manager.start()
|
||||
}
|
||||
|
||||
pub async fn terminate(&mut self) {
|
||||
log!(SystemLog::Terminating);
|
||||
let shutdowns = mem::take(&mut self.shutdowns);
|
||||
for shutdown in shutdowns {
|
||||
if shutdown.send(()).is_err() {
|
||||
log!(SystemError::ShutdownSignalFailed);
|
||||
}
|
||||
}
|
||||
self.backup_engine.stop_all_executions().await;
|
||||
self.database_manager.close_connection().await;
|
||||
self.io_manager.terminate();
|
||||
log!(SystemLog::TerminateComplete);
|
||||
pub fn shutdown(&self) {
|
||||
self.actor_system.shutdown();
|
||||
}
|
||||
|
||||
fn elevate_privileges() -> Result<(), Error> {
|
||||
|
||||
14
src/interface/actor/actor.rs
Normal file
14
src/interface/actor/actor.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use crate::interface::actor::message::Message;
|
||||
use crate::model::error::Error;
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Actor: Send + 'static {
|
||||
type Message: Message;
|
||||
async fn pre_start(&mut self);
|
||||
async fn post_stop(&mut self);
|
||||
async fn receive(
|
||||
&mut self,
|
||||
message: Self::Message,
|
||||
) -> Result<<Self::Message as Message>::Response, Error>;
|
||||
}
|
||||
3
src/interface/actor/message.rs
Normal file
3
src/interface/actor/message.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub trait Message: Send + 'static {
|
||||
type Response: Send + 'static;
|
||||
}
|
||||
2
src/interface/actor/mod.rs
Normal file
2
src/interface/actor/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod actor;
|
||||
pub mod message;
|
||||
@ -1 +0,0 @@
|
||||
pub trait Event: Clone + Send + 'static {}
|
||||
@ -1,12 +1,11 @@
|
||||
use crate::model::error::io::IOError;
|
||||
use crate::model::error::system::SystemError;
|
||||
use crate::model::error::Error;
|
||||
use crate::model::backup::backup_execution::HashType;
|
||||
use crate::model::core::backup::backup_execution::HashType;
|
||||
use crate::platform::attributes::*;
|
||||
use crate::utils::file_hash::*;
|
||||
use crate::utils::file_lock::FileLock;
|
||||
use async_trait::async_trait;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use tokio::fs;
|
||||
use tokio::sync::Semaphore;
|
||||
@ -20,7 +19,7 @@ pub trait FileSystemTrait {
|
||||
|
||||
fn semaphore(&self) -> Arc<Semaphore>;
|
||||
|
||||
async fn is_symlink(&self, path: &PathBuf) -> Result<bool, Error> {
|
||||
async fn is_symlink(&self, path: &Path) -> Result<bool, Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
@ -29,18 +28,18 @@ pub trait FileSystemTrait {
|
||||
|
||||
let symlink_metadata = tokio::fs::symlink_metadata(path)
|
||||
.await
|
||||
.map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::GetMetadataFailed(path, err))?;
|
||||
|
||||
Ok(symlink_metadata.file_type().is_symlink())
|
||||
}
|
||||
|
||||
async fn copy_symlink(
|
||||
&self,
|
||||
source_link: &PathBuf,
|
||||
destination_link: &PathBuf,
|
||||
source_link: &Path,
|
||||
destination_link: &Path,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
async fn list_directory(&self, path: &PathBuf) -> Result<Vec<PathBuf>, Error> {
|
||||
async fn list_directory(&self, path: &Path) -> Result<Vec<PathBuf>, Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
@ -50,18 +49,18 @@ pub trait FileSystemTrait {
|
||||
let mut result = Vec::new();
|
||||
let reader = fs::read_dir(path)
|
||||
.await
|
||||
.map_err(|err| IOError::ReadDirectoryFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::ReadDirectoryFailed(path, err))?;
|
||||
let mut entries = ReadDirStream::new(reader);
|
||||
while let Some(entry) = entries.next().await {
|
||||
let path = entry
|
||||
.map_err(|err| IOError::ReadDirectoryFailed(path.clone(), err))?
|
||||
.map_err(|err| IOError::ReadDirectoryFailed(path, err))?
|
||||
.path();
|
||||
result.push(path);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn create_directory(&self, path: &PathBuf) -> Result<(), Error> {
|
||||
async fn create_directory(&self, path: &Path) -> Result<(), Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
@ -70,11 +69,11 @@ pub trait FileSystemTrait {
|
||||
|
||||
fs::create_dir_all(path)
|
||||
.await
|
||||
.map_err(|err| IOError::CreateDirectoryFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::CreateDirectoryFailed(path, err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_directory(&self, path: &PathBuf) -> Result<(), Error> {
|
||||
async fn delete_directory(&self, path: &Path) -> Result<(), Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
@ -83,11 +82,11 @@ pub trait FileSystemTrait {
|
||||
|
||||
fs::remove_dir_all(path)
|
||||
.await
|
||||
.map_err(|err| IOError::DeleteDirectoryFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::DeleteDirectoryFailed(path, err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn copy_file(&self, source: &PathBuf, destination: &PathBuf) -> Result<(), Error> {
|
||||
async fn copy_file(&self, source: &Path, destination: &Path) -> Result<(), Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
@ -96,11 +95,11 @@ pub trait FileSystemTrait {
|
||||
|
||||
fs::copy(source, destination)
|
||||
.await
|
||||
.map_err(|err| IOError::CopyFileFailed(source.clone(), destination.clone(), err))?;
|
||||
.map_err(|err| IOError::CopyFileFailed(source, destination, err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_file(&self, path: &PathBuf) -> Result<(), Error> {
|
||||
async fn delete_file(&self, path: &Path) -> Result<(), Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
@ -109,15 +108,15 @@ pub trait FileSystemTrait {
|
||||
|
||||
fs::remove_file(path)
|
||||
.await
|
||||
.map_err(|err| IOError::DeleteFileFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::DeleteFileFailed(path, err))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_attributes(&self, path: &PathBuf) -> Result<Attributes, Error>;
|
||||
async fn get_attributes(&self, path: &Path) -> Result<Attributes, Error>;
|
||||
|
||||
async fn set_attributes(&self, path: &PathBuf, attributes: Attributes) -> Result<(), Error>;
|
||||
async fn set_attributes(&self, path: &Path, attributes: Attributes) -> Result<(), Error>;
|
||||
|
||||
async fn copy_attributes(&self, source: &PathBuf, destination: &PathBuf) -> Result<(), Error> {
|
||||
async fn copy_attributes(&self, source: &Path, destination: &Path) -> Result<(), Error> {
|
||||
let source_attributes = self.get_attributes(source).await?;
|
||||
self.set_attributes(destination, source_attributes).await?;
|
||||
Ok(())
|
||||
@ -125,46 +124,33 @@ pub trait FileSystemTrait {
|
||||
|
||||
async fn compare_attributes(
|
||||
&self,
|
||||
source: &PathBuf,
|
||||
destination: &PathBuf,
|
||||
source: &Path,
|
||||
destination: &Path,
|
||||
) -> Result<bool, Error> {
|
||||
let source_attributes = self.get_attributes(source).await?;
|
||||
let destination_attributes = self.get_attributes(destination).await?;
|
||||
Ok(source_attributes == destination_attributes)
|
||||
}
|
||||
|
||||
async fn get_permission(&self, path: &PathBuf) -> Result<Permissions, Error>;
|
||||
async fn get_permission(&self, path: &Path) -> Result<Permissions, Error>;
|
||||
|
||||
async fn set_permission(&self, path: &PathBuf, permissions: Permissions) -> Result<(), Error>;
|
||||
async fn set_permission(&self, path: &Path, permissions: Permissions) -> Result<(), Error>;
|
||||
|
||||
async fn copy_permission(&self, source: &PathBuf, destination: &PathBuf) -> Result<(), Error> {
|
||||
async fn copy_permission(&self, source: &Path, destination: &Path) -> Result<(), Error> {
|
||||
let source_permissions = self.get_permission(source).await?;
|
||||
self.set_permission(destination, source_permissions).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn acquire_file_lock(&self, path: &PathBuf) -> Result<FileLock, Error> {
|
||||
async fn calculate_hash(&self, path: &Path, hash_type: HashType) -> Result<Vec<u8>, Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
.await
|
||||
.map_err(IOError::SemaphoreClosed)?;
|
||||
|
||||
let file_lock = FileLock::new(path).await?;
|
||||
|
||||
Ok(file_lock)
|
||||
}
|
||||
|
||||
async fn calculate_hash(&self, path: &PathBuf, hash_type: HashType) -> Result<Vec<u8>, Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
.await
|
||||
.map_err(IOError::SemaphoreClosed)?;
|
||||
|
||||
let path_clone = path.clone();
|
||||
let path = path.to_path_buf();
|
||||
let hash = spawn_blocking(move || {
|
||||
let path = path_clone;
|
||||
match hash_type {
|
||||
HashType::MD5 => md5(path),
|
||||
HashType::SHA3 => sha3(path),
|
||||
@ -181,8 +167,8 @@ pub trait FileSystemTrait {
|
||||
|
||||
async fn standard_compare(
|
||||
&self,
|
||||
source: &PathBuf,
|
||||
destination: &PathBuf,
|
||||
source: &Path,
|
||||
destination: &Path,
|
||||
) -> Result<bool, Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
@ -193,11 +179,11 @@ pub trait FileSystemTrait {
|
||||
let source_metadata =
|
||||
fs::metadata(source)
|
||||
.await
|
||||
.map_err(|err| IOError::GetMetadataFailed(source.clone(), err))?;
|
||||
.map_err(|err| IOError::GetMetadataFailed(source, err))?;
|
||||
let destination_metadata =
|
||||
fs::metadata(destination)
|
||||
.await
|
||||
.map_err(|err| IOError::GetMetadataFailed(destination.clone(), err))?;
|
||||
.map_err(|err| IOError::GetMetadataFailed(destination, err))?;
|
||||
|
||||
if source_metadata.len() != destination_metadata.len() {
|
||||
return Ok(false);
|
||||
@ -205,11 +191,11 @@ pub trait FileSystemTrait {
|
||||
let source_modified =
|
||||
source_metadata
|
||||
.modified()
|
||||
.map_err(|err| IOError::GetMetadataFailed(source.clone(), err))?;
|
||||
.map_err(|err| IOError::GetMetadataFailed(source, err))?;
|
||||
let destination_modified =
|
||||
destination_metadata
|
||||
.modified()
|
||||
.map_err(|err| IOError::GetMetadataFailed(destination.clone(), err))?;
|
||||
.map_err(|err| IOError::GetMetadataFailed(destination, err))?;
|
||||
if source_modified != destination_modified {
|
||||
return Ok(false);
|
||||
}
|
||||
@ -218,8 +204,8 @@ pub trait FileSystemTrait {
|
||||
|
||||
async fn advance_compare(
|
||||
&self,
|
||||
source: &PathBuf,
|
||||
destination: &PathBuf,
|
||||
source: &Path,
|
||||
destination: &Path,
|
||||
) -> Result<bool, Error> {
|
||||
if !self.standard_compare(source, destination).await? {
|
||||
return Ok(false);
|
||||
@ -234,8 +220,8 @@ pub trait FileSystemTrait {
|
||||
|
||||
async fn thorough_compare(
|
||||
&self,
|
||||
source: &PathBuf,
|
||||
destination: &PathBuf,
|
||||
source: &Path,
|
||||
destination: &Path,
|
||||
hash_type: HashType,
|
||||
) -> Result<bool, Error> {
|
||||
if !self.advance_compare(source, destination).await? {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
pub mod event;
|
||||
pub mod actor;
|
||||
pub mod file_system;
|
||||
pub mod repository;
|
||||
pub mod service_unit;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::core::database_manager::DatabaseManager;
|
||||
use crate::model::backup::backup_schedule::BackupSchedule;
|
||||
use crate::core::infrastructure::database_manager::DatabaseManager;
|
||||
use crate::model::core::schedule::backup_schedule::BackupSchedule;
|
||||
use crate::model::error::Error;
|
||||
use crate::model::error::database::DatabaseError;
|
||||
use crate::model::error::misc::MiscError;
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
#[async_trait]
|
||||
pub trait ServiceUnit: 'static {
|
||||
async fn run(self: Arc<Self>) -> oneshot::Sender<()> {
|
||||
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||
|
||||
tokio::spawn(self.run_impl(shutdown_rx));
|
||||
|
||||
shutdown_tx
|
||||
}
|
||||
|
||||
async fn run_impl(self: Arc<Self>, shutdown_rx: oneshot::Receiver<()>);
|
||||
}
|
||||
@ -11,8 +11,8 @@ mod utils;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut system = System::new().await?;
|
||||
let system = System::new().await?;
|
||||
system.run().await?;
|
||||
system.terminate().await;
|
||||
system.shutdown();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
pub mod backup_execution;
|
||||
pub mod backup_schedule;
|
||||
pub mod progress_data;
|
||||
44
src/model/core/actor/actor_ref.rs
Normal file
44
src/model/core/actor/actor_ref.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use crate::interface::actor::message::Message;
|
||||
use crate::model::core::actor::envelope::Envelope;
|
||||
use crate::model::error::actor::ActorError;
|
||||
use crate::model::error::Error;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
pub struct ActorRef<M: Message> {
|
||||
tx: mpsc::UnboundedSender<Envelope<M>>,
|
||||
}
|
||||
|
||||
impl<M: Message> ActorRef<M> {
|
||||
pub fn new(tx: mpsc::UnboundedSender<Envelope<M>>) -> Self {
|
||||
Self { tx }
|
||||
}
|
||||
|
||||
pub async fn tell(&self, message: M) -> Result<(), Error> {
|
||||
let envelope = Envelope::Tell(message);
|
||||
self.tx
|
||||
.send(envelope)
|
||||
.map_err(|_| ActorError::SendMessageError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn ask(&self, message: M) -> Result<M::Response, Error> {
|
||||
let (reply_tx, reply_rx) = oneshot::channel::<M::Response>();
|
||||
let envelope = Envelope::Ask {
|
||||
message,
|
||||
reply_to: reply_tx,
|
||||
};
|
||||
self.tx
|
||||
.send(envelope)
|
||||
.map_err(|_| ActorError::SendMessageError)?;
|
||||
let reply = reply_rx.await.map_err(|_| ActorError::ActorNotResponding)?;
|
||||
Ok(reply)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Message> Clone for ActorRef<M> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
tx: self.tx.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/model/core/actor/actor_runtime.rs
Normal file
59
src/model/core/actor/actor_runtime.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use crate::interface::actor::actor::Actor;
|
||||
use crate::model::core::actor::actor_ref::ActorRef;
|
||||
use crate::model::core::actor::envelope::Envelope;
|
||||
use crate::model::error::actor::ActorError;
|
||||
use macros::log;
|
||||
use tokio::select;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
pub struct ActorRuntime<A: Actor> {
|
||||
actor: A,
|
||||
rx: mpsc::UnboundedReceiver<Envelope<A::Message>>,
|
||||
}
|
||||
|
||||
impl<A: Actor> ActorRuntime<A> {
|
||||
pub fn new(actor: A) -> (Self, ActorRef<A::Message>) {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let actor_ref = ActorRef::new(tx);
|
||||
let runtime = Self { actor, rx };
|
||||
(runtime, actor_ref)
|
||||
}
|
||||
|
||||
pub async fn run(mut self) -> oneshot::Sender<()> {
|
||||
let (shutdown_tx, mut shutdown_rx) = oneshot::channel();
|
||||
tokio::spawn(async move {
|
||||
self.actor.pre_start().await;
|
||||
loop {
|
||||
select! {
|
||||
envelope = self.rx.recv() => {
|
||||
match envelope {
|
||||
Some(Envelope::Tell(message)) => {
|
||||
if self.actor.receive(message).await.is_err() {
|
||||
log!(ActorError::SendMessageError);
|
||||
}
|
||||
}
|
||||
Some(Envelope::Ask { message, reply_to }) => {
|
||||
match self.actor.receive(message).await {
|
||||
Ok(response) => {
|
||||
if reply_to.send(response).is_err() {
|
||||
log!(ActorError::SendMessageError);
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
log!(ActorError::SendMessageError);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
_ = &mut shutdown_rx => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.actor.post_stop().await;
|
||||
});
|
||||
shutdown_tx
|
||||
}
|
||||
}
|
||||
10
src/model/core/actor/envelope.rs
Normal file
10
src/model/core/actor/envelope.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use crate::interface::actor::message::Message;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
pub enum Envelope<M: Message> {
|
||||
Tell(M),
|
||||
Ask {
|
||||
message: M,
|
||||
reply_to: oneshot::Sender<M::Response>,
|
||||
}
|
||||
}
|
||||
3
src/model/core/actor/mod.rs
Normal file
3
src/model/core/actor/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod actor_ref;
|
||||
pub mod actor_runtime;
|
||||
pub mod envelope;
|
||||
@ -40,8 +40,7 @@ pub enum ComparisonMode {
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
pub struct BackupOptions {
|
||||
pub mirror: bool,
|
||||
pub lock_source: bool,
|
||||
pub mirror: bool,
|
||||
pub backup_permission: bool,
|
||||
pub follow_symlinks: bool,
|
||||
}
|
||||
29
src/model/core/backup/message.rs
Normal file
29
src/model/core/backup/message.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use crate::interface::actor::message::Message;
|
||||
use crate::model::core::backup::backup_execution::BackupExecution;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub enum BackupServiceMessage {
|
||||
ServiceCall(ServiceCallMessage),
|
||||
}
|
||||
|
||||
pub enum BackupServiceResponse {
|
||||
ServiceCall(ServiceCallResponse),
|
||||
None,
|
||||
}
|
||||
|
||||
impl Message for BackupServiceMessage {
|
||||
type Response = BackupServiceResponse;
|
||||
}
|
||||
|
||||
pub enum ServiceCallMessage {
|
||||
AddExecution(BackupExecution),
|
||||
RemoveExecution(Uuid),
|
||||
StartExecution(Uuid),
|
||||
SuspendExecution(Uuid),
|
||||
ResumeExecution(Uuid),
|
||||
GetExecutions,
|
||||
}
|
||||
|
||||
pub enum ServiceCallResponse {
|
||||
GetExecutions(Vec<(Uuid, BackupExecution)>)
|
||||
}
|
||||
3
src/model/core/backup/mod.rs
Normal file
3
src/model/core/backup/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod backup_execution;
|
||||
pub mod message;
|
||||
pub mod progress_data;
|
||||
25
src/model/core/gui/message.rs
Normal file
25
src/model/core/gui/message.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use crate::interface::actor::message::Message;
|
||||
use crate::model::error::Error;
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum GuiMessage {
|
||||
FolderProcess {
|
||||
uuid: Uuid,
|
||||
folder: PathBuf,
|
||||
},
|
||||
ExecutionProgress {
|
||||
uuid: Uuid,
|
||||
processed_files: usize,
|
||||
error_count: usize,
|
||||
},
|
||||
ExecutionErrors {
|
||||
uuid: Uuid,
|
||||
errors: Vec<Error>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Message for GuiMessage {
|
||||
type Response = ();
|
||||
}
|
||||
1
src/model/core/gui/mod.rs
Normal file
1
src/model/core/gui/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod message;
|
||||
4
src/model/core/mod.rs
Normal file
4
src/model/core/mod.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod actor;
|
||||
pub mod backup;
|
||||
pub mod gui;
|
||||
pub mod schedule;
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::model::backup::backup_execution::*;
|
||||
use crate::model::core::backup::backup_execution::*;
|
||||
use chrono::NaiveDateTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
35
src/model/core/schedule/message.rs
Normal file
35
src/model/core/schedule/message.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use uuid::Uuid;
|
||||
use crate::interface::actor::message::Message;
|
||||
use crate::model::core::schedule::backup_schedule::BackupSchedule;
|
||||
|
||||
pub enum ScheduleServiceMessage {
|
||||
UnitNotification(UnitNotificationMessage),
|
||||
ServiceCall(ServiceCallMessage),
|
||||
}
|
||||
|
||||
pub enum ScheduleServiceResponse {
|
||||
ServiceCall(ServiceCallResponse),
|
||||
None,
|
||||
}
|
||||
|
||||
pub enum UnitNotificationMessage {
|
||||
CheckSchedule,
|
||||
}
|
||||
|
||||
pub enum ServiceCallMessage {
|
||||
AddSchedule(BackupSchedule),
|
||||
ModifySchedule(BackupSchedule),
|
||||
RemoveSchedule(Uuid),
|
||||
ActivateSchedule(Uuid),
|
||||
PauseSchedule(Uuid),
|
||||
DisableSchedule(Uuid),
|
||||
GetSchedules,
|
||||
}
|
||||
|
||||
pub enum ServiceCallResponse {
|
||||
GetSchedules(Vec<BackupSchedule>),
|
||||
}
|
||||
|
||||
impl Message for ScheduleServiceMessage {
|
||||
type Response = ScheduleServiceResponse;
|
||||
}
|
||||
2
src/model/core/schedule/mod.rs
Normal file
2
src/model/core/schedule/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod backup_schedule;
|
||||
pub mod message;
|
||||
15
src/model/error/actor.rs
Normal file
15
src/model/error/actor.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use macros::traceable;
|
||||
|
||||
traceable! {
|
||||
ActorError {
|
||||
#[no_source]
|
||||
#[error("Actor not found")]
|
||||
ActorNotFound => tracing::Level::ERROR,
|
||||
#[no_source]
|
||||
#[error("Actor not responding")]
|
||||
ActorNotResponding => tracing::Level::WARN,
|
||||
#[no_source]
|
||||
#[error("Failed to send message to actor")]
|
||||
SendMessageError => tracing::Level::ERROR,
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
use macros::traceable;
|
||||
|
||||
traceable! {
|
||||
EventError {
|
||||
#[error("Placeholder")]
|
||||
Placeholder => tracing::Level::INFO,
|
||||
}
|
||||
}
|
||||
@ -1,24 +1,24 @@
|
||||
pub mod actor;
|
||||
pub mod database;
|
||||
pub mod event;
|
||||
pub mod io;
|
||||
pub mod misc;
|
||||
pub mod system;
|
||||
pub mod task;
|
||||
|
||||
use crate::model::error::actor::ActorError;
|
||||
use crate::model::error::database::DatabaseError;
|
||||
use crate::model::error::event::EventError;
|
||||
use crate::model::error::io::IOError;
|
||||
use crate::model::error::misc::MiscError;
|
||||
use crate::model::error::system::SystemError;
|
||||
use crate::model::error::task::TaskError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, thiserror::Error, Serialize, Deserialize, Clone)]
|
||||
#[derive(Clone, Debug, thiserror::Error, Serialize, Deserialize)]
|
||||
pub enum Error {
|
||||
#[error("{0}")]
|
||||
Database(DatabaseError),
|
||||
Actor(ActorError),
|
||||
#[error("{0}")]
|
||||
Event(EventError),
|
||||
Database(DatabaseError),
|
||||
#[error("{0}")]
|
||||
IO(IOError),
|
||||
#[error("{0}")]
|
||||
@ -29,15 +29,15 @@ pub enum Error {
|
||||
Task(TaskError),
|
||||
}
|
||||
|
||||
impl From<DatabaseError> for Error {
|
||||
fn from(error: DatabaseError) -> Self {
|
||||
Self::Database(error)
|
||||
impl From<ActorError> for Error {
|
||||
fn from(error: ActorError) -> Self {
|
||||
Self::Actor(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EventError> for Error {
|
||||
fn from(error: EventError) -> Self {
|
||||
Self::Event(error)
|
||||
impl From<DatabaseError> for Error {
|
||||
fn from(error: DatabaseError) -> Self {
|
||||
Self::Database(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,26 +9,5 @@ traceable! {
|
||||
#[no_source]
|
||||
#[error("Task not found")]
|
||||
ExecutionNotFound => tracing::Level::ERROR,
|
||||
|
||||
#[error("Failed to stop task")]
|
||||
StopExecutionFailed => tracing::Level::ERROR,
|
||||
|
||||
#[error("Failed to load schedule")]
|
||||
LoadScheduleFailed => tracing::Level::ERROR,
|
||||
|
||||
#[error("Failed to enable schedule")]
|
||||
EnableScheduleFailed => tracing::Level::ERROR,
|
||||
|
||||
#[error("Failed to pause schedule")]
|
||||
PauseScheduleFailed => tracing::Level::ERROR,
|
||||
|
||||
#[error("Failed to disable schedule")]
|
||||
DisableScheduleFailed => tracing::Level::ERROR,
|
||||
|
||||
#[error("Failed to resume schedule")]
|
||||
ResumeScheduleFailed => tracing::Level::ERROR,
|
||||
|
||||
#[error("Failed to remove schedule")]
|
||||
RemoveScheduleFailed => tracing::Level::ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
use crate::interface::event::Event;
|
||||
use crate::model::error::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BackupError {
|
||||
pub task_id: Uuid,
|
||||
pub error: Error,
|
||||
}
|
||||
impl Event for BackupError {}
|
||||
|
||||
//todo Need add global error event
|
||||
@ -1,41 +0,0 @@
|
||||
use crate::interface::event::Event;
|
||||
use crate::model::backup::backup_execution::BackupExecution;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExecutionAddRequest {
|
||||
pub execution: BackupExecution,
|
||||
}
|
||||
impl Event for ExecutionAddRequest {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExecutionRemoveRequest {
|
||||
pub execution_id: Uuid,
|
||||
}
|
||||
impl Event for ExecutionRemoveRequest {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExecutionStartRequest {
|
||||
pub execution_id: Uuid,
|
||||
}
|
||||
impl Event for ExecutionStartRequest {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExecutionSuspendRequest {
|
||||
pub execution_id: Uuid,
|
||||
}
|
||||
impl Event for ExecutionSuspendRequest {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExecutionResumeRequested {
|
||||
pub execution_id: Uuid,
|
||||
}
|
||||
impl Event for ExecutionResumeRequested {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExecutionProgress {
|
||||
pub task_id: Uuid,
|
||||
pub processed_files: usize,
|
||||
pub error_count: usize,
|
||||
}
|
||||
impl Event for ExecutionProgress {}
|
||||
@ -1,10 +0,0 @@
|
||||
use crate::interface::event::Event;
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FolderProcessing {
|
||||
pub execution_id: Uuid,
|
||||
pub current_folder: PathBuf,
|
||||
}
|
||||
impl Event for FolderProcessing {}
|
||||
@ -1,4 +0,0 @@
|
||||
pub mod error;
|
||||
pub mod execution;
|
||||
pub mod filesystem;
|
||||
pub mod schedule;
|
||||
@ -1,39 +0,0 @@
|
||||
use uuid::Uuid;
|
||||
use crate::interface::event::Event;
|
||||
use crate::model::backup::backup_schedule::BackupSchedule;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ScheduleCreateRequest {
|
||||
pub schedule: BackupSchedule,
|
||||
}
|
||||
impl Event for ScheduleCreateRequest {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ScheduleModifyRequest {
|
||||
pub schedule: BackupSchedule,
|
||||
}
|
||||
impl Event for ScheduleModifyRequest {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ScheduleRemoveRequest {
|
||||
pub schedule_id: Uuid,
|
||||
}
|
||||
impl Event for ScheduleRemoveRequest {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ScheduleActiveRequest {
|
||||
pub schedule_id: Uuid,
|
||||
}
|
||||
impl Event for ScheduleActiveRequest {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SchedulePauseRequest {
|
||||
pub schedule_id: Uuid,
|
||||
}
|
||||
impl Event for SchedulePauseRequest {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ScheduleDisableRequest {
|
||||
pub schedule_id: Uuid,
|
||||
}
|
||||
impl Event for ScheduleDisableRequest {}
|
||||
@ -1,5 +1,4 @@
|
||||
pub mod backup;
|
||||
pub mod core;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod event;
|
||||
pub mod log;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use libc::{gid_t, mode_t, uid_t};
|
||||
use libc::{gid_t, uid_t};
|
||||
use std::time::SystemTime;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
pub struct Attributes {
|
||||
pub attributes: u32,
|
||||
pub creation_time: SystemTime,
|
||||
@ -9,6 +9,14 @@ pub struct Attributes {
|
||||
pub change_time: SystemTime,
|
||||
}
|
||||
|
||||
impl PartialEq for Attributes {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.attributes == other.attributes
|
||||
&& self.creation_time == other.creation_time
|
||||
&& self.change_time == other.change_time
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Permissions {
|
||||
pub uid: uid_t,
|
||||
@ -18,28 +26,3 @@ pub struct Permissions {
|
||||
pub is_setuid: bool,
|
||||
pub is_setgid: bool,
|
||||
}
|
||||
|
||||
impl Permissions {
|
||||
pub fn new(uid: uid_t, gid: gid_t, mode: mode_t) -> Self {
|
||||
Self {
|
||||
uid,
|
||||
gid,
|
||||
mode: mode as u32,
|
||||
is_sticky: (mode & libc::S_ISVTX as mode_t) != 0,
|
||||
is_setuid: (mode & libc::S_ISUID as mode_t) != 0,
|
||||
is_setgid: (mode & libc::S_ISGID as mode_t) != 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ExtendedAttribute {
|
||||
pub name: String,
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ExtendedAttribute {
|
||||
pub fn new(name: String, value: Vec<u8>) -> Self {
|
||||
Self { name, value }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
pub const DATABASE_PATH: &'static str = "./mirrorSphere.db";
|
||||
pub const DATABASE_URL: &'static str = "sqlite://./mirrorSphere.db";
|
||||
pub const DATABASE_LOCK_PATH: &'static str = "./db.lock";
|
||||
pub const PROGRESS_SAVE_PATH: &'static str = "./progress";
|
||||
pub const DATABASE_PATH: &str = "./mirrorSphere.db";
|
||||
pub const DATABASE_URL: &str = "sqlite://./mirrorSphere.db";
|
||||
pub const DATABASE_LOCK_PATH: &str = "./db.lock";
|
||||
pub const PROGRESS_SAVE_PATH: &str = "./progress";
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
use crate::model::error::Error;
|
||||
use crate::model::error::system::SystemError;
|
||||
use std::process::Command;
|
||||
use std::{env, io};
|
||||
use std::env;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn elevate() -> Result<(), Error> {
|
||||
let exe = env::current_exe()
|
||||
.map_err(|_| SystemError::RunAsAdminFailed)?;
|
||||
|
||||
@ -7,7 +7,7 @@ use async_trait::async_trait;
|
||||
use libc::mode_t;
|
||||
use std::ffi::CString;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
use tokio::sync::Semaphore;
|
||||
@ -27,27 +27,21 @@ impl FileSystemTrait for FileSystem {
|
||||
self.semaphore.clone()
|
||||
}
|
||||
|
||||
async fn copy_symlink(
|
||||
&self,
|
||||
source_link: &PathBuf,
|
||||
destination_link: &PathBuf,
|
||||
) -> Result<(), Error> {
|
||||
async fn copy_symlink(&self, source_link: &Path, destination_link: &Path) -> Result<(), Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
.await
|
||||
.map_err(IOError::SemaphoreClosed)?;
|
||||
|
||||
tokio::fs::symlink(&source_link, destination_link)
|
||||
tokio::fs::symlink(source_link, destination_link)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
IOError::CreateSymbolLinkFailed(source_link.clone(), destination_link.clone(), err)
|
||||
})?;
|
||||
.map_err(|err| IOError::CreateSymbolLinkFailed(source_link, destination_link, err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_attributes(&self, path: &PathBuf) -> Result<Attributes, Error> {
|
||||
async fn get_attributes(&self, path: &Path) -> Result<Attributes, Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
@ -56,7 +50,7 @@ impl FileSystemTrait for FileSystem {
|
||||
|
||||
let metadata = tokio::fs::metadata(path)
|
||||
.await
|
||||
.map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::GetMetadataFailed(path, err))?;
|
||||
|
||||
let mode = metadata.mode();
|
||||
let file_type = metadata.file_type();
|
||||
@ -78,10 +72,10 @@ impl FileSystemTrait for FileSystem {
|
||||
.unwrap_or_else(|_| metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH));
|
||||
let last_access_time = metadata
|
||||
.accessed()
|
||||
.map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::GetMetadataFailed(path, err))?;
|
||||
let change_time = metadata
|
||||
.modified()
|
||||
.map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::GetMetadataFailed(path, err))?;
|
||||
|
||||
let attributes = Attributes {
|
||||
attributes,
|
||||
@ -93,14 +87,14 @@ impl FileSystemTrait for FileSystem {
|
||||
Ok(attributes)
|
||||
}
|
||||
|
||||
async fn set_attributes(&self, path: &PathBuf, attributes: Attributes) -> Result<(), Error> {
|
||||
async fn set_attributes(&self, path: &Path, attributes: Attributes) -> Result<(), Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
.await
|
||||
.map_err(IOError::SemaphoreClosed)?;
|
||||
|
||||
let path_clone = path.clone();
|
||||
let path_clone = path.to_path_buf();
|
||||
let mode = attributes.attributes & 0o7777;
|
||||
|
||||
spawn_blocking(move || {
|
||||
@ -127,14 +121,14 @@ impl FileSystemTrait for FileSystem {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_permission(&self, path: &PathBuf) -> Result<Permissions, Error> {
|
||||
async fn get_permission(&self, path: &Path) -> Result<Permissions, Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
.await
|
||||
.map_err(IOError::SemaphoreClosed)?;
|
||||
|
||||
let path_clone = path.clone();
|
||||
let path_clone = path.to_path_buf();
|
||||
|
||||
let permission = spawn_blocking(move || {
|
||||
let path = path_clone;
|
||||
@ -149,9 +143,9 @@ impl FileSystemTrait for FileSystem {
|
||||
uid,
|
||||
gid,
|
||||
mode,
|
||||
is_sticky: (mode & libc::S_ISVTX as u32) != 0,
|
||||
is_setuid: (mode & libc::S_ISUID as u32) != 0,
|
||||
is_setgid: (mode & libc::S_ISGID as u32) != 0,
|
||||
is_sticky: (mode & libc::S_ISVTX) != 0,
|
||||
is_setuid: (mode & libc::S_ISUID) != 0,
|
||||
is_setgid: (mode & libc::S_ISGID) != 0,
|
||||
})
|
||||
})
|
||||
.await
|
||||
@ -160,14 +154,14 @@ impl FileSystemTrait for FileSystem {
|
||||
Ok(permission)
|
||||
}
|
||||
|
||||
async fn set_permission(&self, path: &PathBuf, permissions: Permissions) -> Result<(), Error> {
|
||||
async fn set_permission(&self, path: &Path, permissions: Permissions) -> Result<(), Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
.await
|
||||
.map_err(IOError::SemaphoreClosed)?;
|
||||
|
||||
let path_clone = path.clone();
|
||||
let path_clone = path.to_path_buf();
|
||||
|
||||
spawn_blocking(move || {
|
||||
let path = path_clone;
|
||||
@ -176,26 +170,22 @@ impl FileSystemTrait for FileSystem {
|
||||
|
||||
unsafe {
|
||||
if libc::chown(c_path.as_ptr(), permissions.uid, permissions.gid) != 0 {
|
||||
return Err(
|
||||
IOError::SetMetadataFailed(path.clone(), "libc chmod failed").into(),
|
||||
);
|
||||
return Err(IOError::SetMetadataFailed(path, "libc chmod failed").into());
|
||||
}
|
||||
|
||||
let mut mode = permissions.mode & 0o7777;
|
||||
if permissions.is_sticky {
|
||||
mode |= libc::S_ISVTX as u32;
|
||||
mode |= libc::S_ISVTX;
|
||||
}
|
||||
if permissions.is_setuid {
|
||||
mode |= libc::S_ISUID as u32;
|
||||
mode |= libc::S_ISUID;
|
||||
}
|
||||
if permissions.is_setgid {
|
||||
mode |= libc::S_ISGID as u32;
|
||||
mode |= libc::S_ISGID;
|
||||
}
|
||||
|
||||
if libc::chmod(c_path.as_ptr(), mode as mode_t) != 0 {
|
||||
return Err(
|
||||
IOError::SetMetadataFailed(path.clone(), "libc chmod failed").into(),
|
||||
);
|
||||
return Err(IOError::SetMetadataFailed(path, "libc chmod failed").into());
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,9 +199,9 @@ impl FileSystemTrait for FileSystem {
|
||||
}
|
||||
|
||||
impl FileSystem {
|
||||
fn set_file_times(path: &PathBuf, attributes: &Attributes) -> Result<(), Error> {
|
||||
fn set_file_times(path: &Path, attributes: &Attributes) -> Result<(), Error> {
|
||||
let c_path = CString::new(path.to_string_lossy().as_bytes())
|
||||
.map_err(|err| IOError::SetMetadataFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::SetMetadataFailed(path, err))?;
|
||||
|
||||
let access_time = Self::system_time_to_timespec(attributes.last_access_time)?;
|
||||
let modify_time = Self::system_time_to_timespec(attributes.change_time)?;
|
||||
@ -220,10 +210,7 @@ impl FileSystem {
|
||||
|
||||
unsafe {
|
||||
if libc::utimensat(libc::AT_FDCWD, c_path.as_ptr(), times.as_ptr(), 0) != 0 {
|
||||
Err(IOError::SetMetadataFailed(
|
||||
path.clone(),
|
||||
"libc utimensat failed",
|
||||
))?;
|
||||
Err(IOError::SetMetadataFailed(path, "libc utimensat failed"))?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,8 @@ pub struct Attributes {
|
||||
impl PartialEq for Attributes {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.attributes == other.attributes
|
||||
&& self.creation_time == other.creation_time
|
||||
&& self.change_time == other.change_time
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ pub fn adjust_token_privileges() -> Result<(), Error> {
|
||||
LookupPrivilegeValueW(PCWSTR::null(), privilege_name, &mut luid)
|
||||
.map_err(SystemError::AdjustTokenPrivilegesFailed)?;
|
||||
|
||||
let mut token_privilege = TOKEN_PRIVILEGES {
|
||||
let token_privilege = TOKEN_PRIVILEGES {
|
||||
PrivilegeCount: 1,
|
||||
Privileges: [LUID_AND_ATTRIBUTES {
|
||||
Luid: luid,
|
||||
@ -96,7 +96,7 @@ pub fn adjust_token_privileges() -> Result<(), Error> {
|
||||
AdjustTokenPrivileges(
|
||||
token_handle,
|
||||
false,
|
||||
Some(&mut token_privilege),
|
||||
Some(&token_privilege),
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
@ -108,7 +108,7 @@ pub fn adjust_token_privileges() -> Result<(), Error> {
|
||||
|
||||
let last_error = GetLastError();
|
||||
if last_error == ERROR_NOT_ALL_ASSIGNED {
|
||||
Err(SystemError::AdjustTokenPrivilegesFailed(format!("{:?}", last_error)))?
|
||||
Err(SystemError::AdjustTokenPrivilegesFailed(format!("{last_error:?}")))?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -9,7 +9,7 @@ use async_trait::async_trait;
|
||||
use chrono::{DateTime, Datelike, Timelike};
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
use std::path::Path;
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
@ -20,7 +20,7 @@ use windows::Win32::Security::Authorization::{
|
||||
GetNamedSecurityInfoW, SE_FILE_OBJECT, SetNamedSecurityInfoW,
|
||||
};
|
||||
use windows::Win32::Security::{
|
||||
ACL, BACKUP_SECURITY_INFORMATION, DACL_SECURITY_INFORMATION, GROUP_SECURITY_INFORMATION,
|
||||
ACL, DACL_SECURITY_INFORMATION, GROUP_SECURITY_INFORMATION,
|
||||
OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, PSID, SACL_SECURITY_INFORMATION,
|
||||
};
|
||||
use windows::Win32::Storage::FileSystem::{
|
||||
@ -44,11 +44,7 @@ impl FileSystemTrait for FileSystem {
|
||||
self.semaphore.clone()
|
||||
}
|
||||
|
||||
async fn copy_symlink(
|
||||
&self,
|
||||
source_link: &PathBuf,
|
||||
destination_link: &PathBuf,
|
||||
) -> Result<(), Error> {
|
||||
async fn copy_symlink(&self, source_link: &Path, destination_link: &Path) -> Result<(), Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
@ -57,7 +53,7 @@ impl FileSystemTrait for FileSystem {
|
||||
|
||||
let link_target = tokio::fs::read_link(source_link)
|
||||
.await
|
||||
.map_err(|err| IOError::ReadSymbolLinkFailed(source_link.clone(), err))?;
|
||||
.map_err(|err| IOError::ReadSymbolLinkFailed(source_link, err))?;
|
||||
|
||||
if link_target.is_dir() {
|
||||
tokio::fs::symlink_dir(&link_target, destination_link).await
|
||||
@ -69,7 +65,7 @@ impl FileSystemTrait for FileSystem {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_attributes(&self, path: &PathBuf) -> Result<Attributes, Error> {
|
||||
async fn get_attributes(&self, path: &Path) -> Result<Attributes, Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
@ -78,19 +74,19 @@ impl FileSystemTrait for FileSystem {
|
||||
|
||||
let metadata = tokio::fs::metadata(path)
|
||||
.await
|
||||
.map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::GetMetadataFailed(path, err))?;
|
||||
|
||||
let attributes = metadata.file_attributes();
|
||||
|
||||
let creation_time = metadata
|
||||
.created()
|
||||
.map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::GetMetadataFailed(path, err))?;
|
||||
let last_access_time = metadata
|
||||
.accessed()
|
||||
.map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::GetMetadataFailed(path, err))?;
|
||||
let change_time = metadata
|
||||
.modified()
|
||||
.map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?;
|
||||
.map_err(|err| IOError::GetMetadataFailed(path, err))?;
|
||||
|
||||
let attributes = Attributes {
|
||||
attributes,
|
||||
@ -102,7 +98,7 @@ impl FileSystemTrait for FileSystem {
|
||||
Ok(attributes)
|
||||
}
|
||||
|
||||
async fn set_attributes(&self, path: &PathBuf, attributes: Attributes) -> Result<(), Error> {
|
||||
async fn set_attributes(&self, path: &Path, attributes: Attributes) -> Result<(), Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
@ -113,7 +109,7 @@ impl FileSystemTrait for FileSystem {
|
||||
|
||||
let file_attributes = attributes.attributes;
|
||||
|
||||
let path = path.clone();
|
||||
let path = path.to_path_buf();
|
||||
spawn_blocking(move || unsafe {
|
||||
SetFileAttributesW(
|
||||
PCWSTR(file_path_wild.as_ptr()),
|
||||
@ -155,10 +151,16 @@ impl FileSystemTrait for FileSystem {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_permission(&self, path: &PathBuf) -> Result<Permissions, Error> {
|
||||
async fn get_permission(&self, path: &Path) -> Result<Permissions, Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
.await
|
||||
.map_err(IOError::SemaphoreClosed)?;
|
||||
|
||||
let file_path_wild: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
|
||||
|
||||
let path = path.clone();
|
||||
let path = path.to_path_buf();
|
||||
let permission = spawn_blocking(move || unsafe {
|
||||
let security_info = OWNER_SECURITY_INFORMATION
|
||||
| GROUP_SECURITY_INFORMATION
|
||||
@ -182,7 +184,10 @@ impl FileSystemTrait for FileSystem {
|
||||
);
|
||||
|
||||
if result.is_err() {
|
||||
Err(IOError::GetMetadataFailed(path.clone(), format!("{result:?}")))?;
|
||||
Err(IOError::GetMetadataFailed(
|
||||
path.clone(),
|
||||
format!("{result:?}"),
|
||||
))?;
|
||||
}
|
||||
|
||||
Ok::<Permissions, Error>(Permissions {
|
||||
@ -199,10 +204,19 @@ impl FileSystemTrait for FileSystem {
|
||||
Ok(permission)
|
||||
}
|
||||
|
||||
async fn set_permission(&self, path: &PathBuf, permissions: Permissions) -> Result<(), Error> {
|
||||
async fn set_permission(&self, path: &Path, permissions: Permissions) -> Result<(), Error> {
|
||||
let semaphore = self.semaphore();
|
||||
let _permit = semaphore
|
||||
.acquire_owned()
|
||||
.await
|
||||
.map_err(IOError::SemaphoreClosed)?;
|
||||
|
||||
let file_path_wild: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
|
||||
|
||||
let security_info = BACKUP_SECURITY_INFORMATION;
|
||||
let security_info = OWNER_SECURITY_INFORMATION
|
||||
| GROUP_SECURITY_INFORMATION
|
||||
| DACL_SECURITY_INFORMATION
|
||||
| SACL_SECURITY_INFORMATION;
|
||||
let owner = permissions.owner;
|
||||
let primary_group = permissions.primary_group;
|
||||
let dacl = permissions.dacl;
|
||||
@ -221,7 +235,7 @@ impl FileSystemTrait for FileSystem {
|
||||
);
|
||||
|
||||
if result.is_err() {
|
||||
Err(IOError::SetMetadataFailed(path.clone(), format!("{result:?}")))?;
|
||||
Err(IOError::SetMetadataFailed(path, format!("{result:?}")))?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
39
src/ui/common.rs
Normal file
39
src/ui/common.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use crate::model::core::backup::backup_execution::BackupExecution;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PageType {
|
||||
Executions,
|
||||
Schedules,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FolderSelectionMode {
|
||||
Source,
|
||||
Destination,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ComparisonModeSelection {
|
||||
Standard,
|
||||
Advanced,
|
||||
Thorough,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExecutionDisplay {
|
||||
pub execution: BackupExecution,
|
||||
pub current_folder: String,
|
||||
pub processed_files: usize,
|
||||
pub error_count: usize,
|
||||
}
|
||||
|
||||
impl From<BackupExecution> for ExecutionDisplay {
|
||||
fn from(execution: BackupExecution) -> Self {
|
||||
Self {
|
||||
execution,
|
||||
current_folder: String::new(),
|
||||
processed_files: 0,
|
||||
error_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,54 +1,30 @@
|
||||
use crate::core::app_config::AppConfig;
|
||||
use crate::core::backup_engine::BackupEngine;
|
||||
use crate::core::event_bus::EventBus;
|
||||
use crate::model::backup::backup_execution::*;
|
||||
use crate::core::backup::backup_service::BackupService;
|
||||
use crate::core::infrastructure::actor_system::ActorSystem;
|
||||
use crate::core::infrastructure::app_config::AppConfig;
|
||||
use crate::model::core::backup::backup_execution::*;
|
||||
use crate::model::core::backup::message::{
|
||||
BackupServiceMessage, BackupServiceResponse, ServiceCallMessage, ServiceCallResponse,
|
||||
};
|
||||
use crate::model::core::gui::message::GuiMessage;
|
||||
use crate::model::error::actor::ActorError;
|
||||
use crate::model::error::Error;
|
||||
use crate::model::event::error::BackupError;
|
||||
use crate::model::event::execution::*;
|
||||
use crate::model::event::filesystem::FolderProcessing;
|
||||
use crate::ui::common::{ComparisonModeSelection, ExecutionDisplay, FolderSelectionMode};
|
||||
use dashmap::DashMap;
|
||||
use eframe::egui;
|
||||
use egui_file_dialog::FileDialog;
|
||||
use futures::executor::block_on;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::time::{Duration, Instant};
|
||||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ExecutionDisplay {
|
||||
execution: BackupExecution,
|
||||
current_folder: String,
|
||||
processed_files: usize,
|
||||
error_count: usize,
|
||||
}
|
||||
|
||||
impl From<BackupExecution> for ExecutionDisplay {
|
||||
fn from(execution: BackupExecution) -> Self {
|
||||
Self {
|
||||
execution,
|
||||
current_folder: String::new(),
|
||||
processed_files: 0,
|
||||
error_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum FolderSelectionMode {
|
||||
Source,
|
||||
Destination,
|
||||
}
|
||||
|
||||
pub struct ExecutionPage {
|
||||
app_config: Arc<AppConfig>,
|
||||
backup_engine: Arc<BackupEngine>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
|
||||
folder_processing_events: Receiver<FolderProcessing>,
|
||||
progress_events: Receiver<ExecutionProgress>,
|
||||
backup_error_events: Receiver<BackupError>,
|
||||
message_rx: mpsc::Receiver<GuiMessage>,
|
||||
|
||||
executions: DashMap<Uuid, ExecutionDisplay>,
|
||||
error_messages: DashMap<Uuid, Vec<Error>>,
|
||||
@ -56,9 +32,10 @@ pub struct ExecutionPage {
|
||||
new_task_source: String,
|
||||
new_task_destination: String,
|
||||
new_task_mirror: bool,
|
||||
new_task_lock_source: bool,
|
||||
new_task_backup_permission: bool,
|
||||
new_task_follow_symlinks: bool,
|
||||
new_task_comparison_mode: ComparisonModeSelection,
|
||||
new_task_hash_type: HashType,
|
||||
show_add_task_dialog: bool,
|
||||
|
||||
file_dialog: FileDialog,
|
||||
@ -73,27 +50,22 @@ pub struct ExecutionPage {
|
||||
impl ExecutionPage {
|
||||
pub fn new(
|
||||
app_config: Arc<AppConfig>,
|
||||
event_bus: Arc<EventBus>,
|
||||
backup_engine: Arc<BackupEngine>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
message_rx: mpsc::Receiver<GuiMessage>,
|
||||
) -> Self {
|
||||
let folder_processing_events = event_bus.subscribe::<FolderProcessing>();
|
||||
let progress_events = event_bus.subscribe::<ExecutionProgress>();
|
||||
let backup_error_events = event_bus.subscribe::<BackupError>();
|
||||
|
||||
Self {
|
||||
app_config,
|
||||
backup_engine,
|
||||
folder_processing_events,
|
||||
progress_events,
|
||||
backup_error_events,
|
||||
actor_system,
|
||||
message_rx,
|
||||
executions: DashMap::new(),
|
||||
error_messages: DashMap::new(),
|
||||
new_task_source: String::new(),
|
||||
new_task_destination: String::new(),
|
||||
new_task_mirror: false,
|
||||
new_task_lock_source: false,
|
||||
new_task_backup_permission: false,
|
||||
new_task_follow_symlinks: false,
|
||||
new_task_comparison_mode: ComparisonModeSelection::Standard,
|
||||
new_task_hash_type: HashType::BLAKE3,
|
||||
show_add_task_dialog: false,
|
||||
file_dialog: FileDialog::new(),
|
||||
folder_selection_mode: None,
|
||||
@ -105,90 +77,150 @@ impl ExecutionPage {
|
||||
}
|
||||
|
||||
fn process_events(&mut self) {
|
||||
while let Ok(event) = self.folder_processing_events.try_recv() {
|
||||
if let Some(mut task_display) = self.executions.get_mut(&event.execution_id) {
|
||||
task_display.current_folder = event.current_folder.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
|
||||
while let Ok(event) = self.progress_events.try_recv() {
|
||||
if let Some(mut task_display) = self.executions.get_mut(&event.task_id) {
|
||||
task_display.processed_files = event.processed_files;
|
||||
task_display.error_count = event.error_count;
|
||||
}
|
||||
}
|
||||
|
||||
while let Ok(event) = self.backup_error_events.try_recv() {
|
||||
match self.error_messages.get_mut(&event.task_id) {
|
||||
Some(mut errors) => errors.push(event.error),
|
||||
None => {
|
||||
self.error_messages.insert(event.task_id, vec![event.error]);
|
||||
while let Ok(message) = self.message_rx.try_recv() {
|
||||
match message {
|
||||
GuiMessage::FolderProcess { uuid, folder } => {
|
||||
if let Some(mut task_display) = self.executions.get_mut(&uuid) {
|
||||
task_display.current_folder = folder.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
GuiMessage::ExecutionProgress {
|
||||
uuid,
|
||||
processed_files,
|
||||
error_count,
|
||||
} => {
|
||||
if let Some(mut task_display) = self.executions.get_mut(&uuid) {
|
||||
task_display.processed_files = processed_files;
|
||||
task_display.error_count = error_count;
|
||||
}
|
||||
}
|
||||
GuiMessage::ExecutionErrors { uuid, errors } => {
|
||||
match self.error_messages.get_mut(&uuid) {
|
||||
Some(mut errors_ref) => errors_ref.extend(errors),
|
||||
None => {
|
||||
self.error_messages.insert(uuid, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if let Ok(response) = block_on(async {
|
||||
let backup_actor_ref = self
|
||||
.actor_system
|
||||
.actor_of::<BackupService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
backup_actor_ref
|
||||
.ask(BackupServiceMessage::ServiceCall(
|
||||
ServiceCallMessage::GetExecutions,
|
||||
))
|
||||
.await
|
||||
}) {
|
||||
let BackupServiceResponse::ServiceCall(service_call) = response else {
|
||||
return;
|
||||
};
|
||||
let ServiceCallResponse::GetExecutions(latest_executions) = service_call;
|
||||
let latest_ids: 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;
|
||||
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));
|
||||
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;
|
||||
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_add_execution(&mut self, execution: BackupExecution) -> Result<(), Error> {
|
||||
block_on(async {
|
||||
let backup_actor_ref = self
|
||||
.actor_system
|
||||
.actor_of::<BackupService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let message =
|
||||
BackupServiceMessage::ServiceCall(ServiceCallMessage::AddExecution(execution));
|
||||
backup_actor_ref
|
||||
.tell(message)
|
||||
.await
|
||||
.map_err(|_| ActorError::SendMessageError)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
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_start_execution(&mut self, uuid: Uuid) -> Result<(), Error> {
|
||||
block_on(async {
|
||||
let backup_actor_ref = self
|
||||
.actor_system
|
||||
.actor_of::<BackupService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let message =
|
||||
BackupServiceMessage::ServiceCall(ServiceCallMessage::StartExecution(uuid));
|
||||
backup_actor_ref
|
||||
.tell(message)
|
||||
.await
|
||||
.map_err(|_| ActorError::SendMessageError)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
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_suspend_execution(&mut self, uuid: Uuid) -> Result<(), Error> {
|
||||
block_on(async {
|
||||
let backup_actor_ref = self
|
||||
.actor_system
|
||||
.actor_of::<BackupService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let message =
|
||||
BackupServiceMessage::ServiceCall(ServiceCallMessage::SuspendExecution(uuid));
|
||||
backup_actor_ref
|
||||
.tell(message)
|
||||
.await
|
||||
.map_err(|_| ActorError::SendMessageError)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
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_resume_execution(&mut self, uuid: Uuid) -> Result<(), Error> {
|
||||
block_on(async {
|
||||
let backup_actor_ref = self
|
||||
.actor_system
|
||||
.actor_of::<BackupService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let message =
|
||||
BackupServiceMessage::ServiceCall(ServiceCallMessage::ResumeExecution(uuid));
|
||||
backup_actor_ref
|
||||
.tell(message)
|
||||
.await
|
||||
.map_err(|_| ActorError::SendMessageError)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_remove_execution(&mut self, task_id: Uuid) {
|
||||
block_on(self.backup_engine.remove_execution(&task_id));
|
||||
self.executions.remove(&task_id);
|
||||
fn handle_remove_execution(&mut self, uuid: Uuid) -> Result<(), Error> {
|
||||
block_on(async {
|
||||
let backup_actor_ref = self
|
||||
.actor_system
|
||||
.actor_of::<BackupService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let message =
|
||||
BackupServiceMessage::ServiceCall(ServiceCallMessage::RemoveExecution(uuid));
|
||||
backup_actor_ref
|
||||
.tell(message)
|
||||
.await
|
||||
.map_err(|_| ActorError::SendMessageError)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, ctx: &egui::Context) {
|
||||
@ -288,7 +320,7 @@ impl ExecutionPage {
|
||||
fn draw_execution_item(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
task_id: Uuid,
|
||||
uuid: Uuid,
|
||||
task_display: &ExecutionDisplay,
|
||||
) {
|
||||
egui::Frame::new()
|
||||
@ -345,10 +377,10 @@ impl ExecutionPage {
|
||||
});
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if let Some(errors) = self.error_messages.get(&task_id) {
|
||||
if let Some(errors) = self.error_messages.get(&uuid) {
|
||||
if !errors.is_empty() {
|
||||
if ui.small_button("👁 View Errors").clicked() {
|
||||
self.viewing_errors_for_task = Some(task_id);
|
||||
self.viewing_errors_for_task = Some(uuid);
|
||||
}
|
||||
ui.separator();
|
||||
}
|
||||
@ -357,24 +389,32 @@ impl ExecutionPage {
|
||||
match task_display.execution.state {
|
||||
BackupState::Pending => {
|
||||
if ui.button("▶ Start").clicked() {
|
||||
self.handle_start_execution(task_id);
|
||||
if let Err(err) = self.handle_start_execution(uuid) {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
BackupState::Suspended => {
|
||||
if ui.button("▶ Resume").clicked() {
|
||||
self.handle_resume_execution(task_id);
|
||||
if let Err(err) = self.handle_resume_execution(uuid) {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
BackupState::Running => {
|
||||
if ui.button("⏸ Pause").clicked() {
|
||||
self.handle_suspend_execution(task_id);
|
||||
if let Err(err) = self.handle_suspend_execution(uuid) {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if ui.button("🗑").clicked() {
|
||||
self.handle_remove_execution(task_id);
|
||||
if let Err(err) = self.handle_remove_execution(uuid) {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -392,7 +432,10 @@ impl ExecutionPage {
|
||||
.spacing([10.0, 4.0])
|
||||
.show(ui, |ui| {
|
||||
ui.label("Source Path:");
|
||||
ui.text_edit_singleline(&mut self.new_task_source);
|
||||
ui.add_sized(
|
||||
[300.0, 20.0],
|
||||
egui::TextEdit::singleline(&mut self.new_task_source),
|
||||
);
|
||||
if ui.button("📁 Browse").clicked() {
|
||||
self.folder_selection_mode = Some(FolderSelectionMode::Source);
|
||||
self.file_dialog.pick_directory();
|
||||
@ -400,7 +443,10 @@ impl ExecutionPage {
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Destination Path:");
|
||||
ui.text_edit_singleline(&mut self.new_task_destination);
|
||||
ui.add_sized(
|
||||
[300.0, 20.0],
|
||||
egui::TextEdit::singleline(&mut self.new_task_destination),
|
||||
);
|
||||
if ui.button("📁 Browse").clicked() {
|
||||
self.folder_selection_mode = Some(FolderSelectionMode::Destination);
|
||||
self.file_dialog.pick_directory();
|
||||
@ -410,13 +456,73 @@ impl ExecutionPage {
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("Options:");
|
||||
ui.label("File Comparison Mode:");
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(
|
||||
&mut self.new_task_comparison_mode,
|
||||
ComparisonModeSelection::Standard,
|
||||
"⚡ Standard (Size + Time)",
|
||||
);
|
||||
ui.radio_value(
|
||||
&mut self.new_task_comparison_mode,
|
||||
ComparisonModeSelection::Advanced,
|
||||
"🔧 Advanced (+ Attributes)",
|
||||
);
|
||||
ui.radio_value(
|
||||
&mut self.new_task_comparison_mode,
|
||||
ComparisonModeSelection::Thorough,
|
||||
"🔍 Thorough (+ Checksum)",
|
||||
);
|
||||
});
|
||||
|
||||
if self.new_task_comparison_mode == ComparisonModeSelection::Thorough {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(" Hash Algorithm:");
|
||||
egui::ComboBox::from_id_salt("hash_type")
|
||||
.selected_text(format!("{:?}", self.new_task_hash_type))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(
|
||||
&mut self.new_task_hash_type,
|
||||
HashType::BLAKE3,
|
||||
"BLAKE3 (Recommended)",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut self.new_task_hash_type,
|
||||
HashType::SHA256,
|
||||
"SHA256",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut self.new_task_hash_type,
|
||||
HashType::SHA3,
|
||||
"SHA3",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut self.new_task_hash_type,
|
||||
HashType::BLAKE2B,
|
||||
"BLAKE2B",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut self.new_task_hash_type,
|
||||
HashType::BLAKE2S,
|
||||
"BLAKE2S",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut self.new_task_hash_type,
|
||||
HashType::MD5,
|
||||
"MD5 (Legacy)",
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("Additional Options:");
|
||||
ui.checkbox(&mut self.new_task_follow_symlinks, "Follow Symlinks");
|
||||
ui.checkbox(
|
||||
&mut self.new_task_mirror,
|
||||
"Mirror Mode (Delete extra files in destination)",
|
||||
);
|
||||
ui.checkbox(&mut self.new_task_lock_source, "Lock Source Files");
|
||||
ui.checkbox(
|
||||
&mut self.new_task_backup_permission,
|
||||
"Backup File Permissions",
|
||||
@ -429,25 +535,38 @@ impl ExecutionPage {
|
||||
&& !self.new_task_source.is_empty()
|
||||
&& !self.new_task_destination.is_empty()
|
||||
{
|
||||
let comparison_mode = match self.new_task_comparison_mode {
|
||||
ComparisonModeSelection::Standard => Some(ComparisonMode::Standard),
|
||||
ComparisonModeSelection::Advanced => Some(ComparisonMode::Advanced),
|
||||
ComparisonModeSelection::Thorough => {
|
||||
Some(ComparisonMode::Thorough(self.new_task_hash_type))
|
||||
}
|
||||
};
|
||||
|
||||
let execution = BackupExecution {
|
||||
uuid: Uuid::new_v4(),
|
||||
state: BackupState::Pending,
|
||||
source_path: PathBuf::from(&self.new_task_source),
|
||||
destination_path: PathBuf::from(&self.new_task_destination),
|
||||
backup_type: BackupType::Full,
|
||||
comparison_mode: None,
|
||||
comparison_mode,
|
||||
options: BackupOptions {
|
||||
mirror: self.new_task_mirror,
|
||||
lock_source: self.new_task_lock_source,
|
||||
backup_permission: self.new_task_backup_permission,
|
||||
follow_symlinks: self.new_task_follow_symlinks,
|
||||
},
|
||||
};
|
||||
|
||||
block_on(self.backup_engine.add_execution(execution.clone()));
|
||||
let execution_display = ExecutionDisplay::from(execution.clone());
|
||||
self.executions.insert(execution.uuid, execution_display);
|
||||
self.reset_form();
|
||||
match self.handle_add_execution(execution.clone()) {
|
||||
Ok(_) => {
|
||||
let execution_display = ExecutionDisplay::from(execution.clone());
|
||||
self.executions.insert(execution.uuid, execution_display);
|
||||
self.reset_form();
|
||||
}
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("Cancel").clicked() {
|
||||
@ -559,9 +678,10 @@ impl ExecutionPage {
|
||||
self.new_task_source.clear();
|
||||
self.new_task_destination.clear();
|
||||
self.new_task_mirror = false;
|
||||
self.new_task_lock_source = false;
|
||||
self.new_task_backup_permission = false;
|
||||
self.new_task_follow_symlinks = false;
|
||||
self.new_task_comparison_mode = ComparisonModeSelection::Standard;
|
||||
self.new_task_hash_type = HashType::BLAKE3;
|
||||
self.show_add_task_dialog = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,20 +1,10 @@
|
||||
use crate::core::backup_engine::BackupEngine;
|
||||
use crate::core::event_bus::EventBus;
|
||||
use crate::core::schedule_manager::ScheduleManager;
|
||||
use crate::model::log::system::SystemLog;
|
||||
use crate::ui::common::PageType;
|
||||
use crate::ui::execution_page::ExecutionPage;
|
||||
use crate::ui::schedule_page::SchedulePage;
|
||||
use crate::model::log::system::SystemLog;
|
||||
use eframe::egui;
|
||||
use eframe::{App, Frame};
|
||||
use macros::log;
|
||||
use std::sync::Arc;
|
||||
use crate::core::app_config::AppConfig;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum PageType {
|
||||
Executions,
|
||||
Schedules,
|
||||
}
|
||||
|
||||
pub struct MainPage {
|
||||
current_page: PageType,
|
||||
@ -23,37 +13,38 @@ pub struct MainPage {
|
||||
}
|
||||
|
||||
impl MainPage {
|
||||
pub fn new(
|
||||
app_config: Arc<AppConfig>,
|
||||
event_bus: Arc<EventBus>,
|
||||
backup_engine: Arc<BackupEngine>,
|
||||
schedule_manager: Arc<ScheduleManager>,
|
||||
) -> Self {
|
||||
pub fn new(execution_page: ExecutionPage, schedule_page: SchedulePage) -> Self {
|
||||
Self {
|
||||
current_page: PageType::Executions,
|
||||
execution_page: ExecutionPage::new(app_config.clone(), event_bus, backup_engine),
|
||||
schedule_page: SchedulePage::new(app_config, schedule_manager),
|
||||
execution_page,
|
||||
schedule_page,
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_top_panel(&mut self, ctx: &egui::Context) {
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
egui::MenuBar::new().ui(ui, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("Exit").clicked() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("View", |ui| {
|
||||
match self.current_page {
|
||||
PageType::Executions => {
|
||||
ui.checkbox(&mut self.execution_page.show_completed_tasks, "Show Completed Tasks");
|
||||
ui.checkbox(&mut self.execution_page.auto_scroll_errors, "Auto-scroll Error Messages");
|
||||
}
|
||||
PageType::Schedules => {
|
||||
ui.checkbox(&mut self.schedule_page.show_disabled_schedules, "Show Disabled Schedules");
|
||||
}
|
||||
ui.menu_button("View", |ui| match self.current_page {
|
||||
PageType::Executions => {
|
||||
ui.checkbox(
|
||||
&mut self.execution_page.show_completed_tasks,
|
||||
"Show Completed Tasks",
|
||||
);
|
||||
ui.checkbox(
|
||||
&mut self.execution_page.auto_scroll_errors,
|
||||
"Auto-scroll Error Messages",
|
||||
);
|
||||
}
|
||||
PageType::Schedules => {
|
||||
ui.checkbox(
|
||||
&mut self.schedule_page.show_disabled_schedules,
|
||||
"Show Disabled Schedules",
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -63,7 +54,11 @@ impl MainPage {
|
||||
fn draw_tabs(&mut self, ctx: &egui::Context) {
|
||||
egui::TopBottomPanel::top("tabs_panel").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(&mut self.current_page, PageType::Executions, "📋 Executions");
|
||||
ui.selectable_value(
|
||||
&mut self.current_page,
|
||||
PageType::Executions,
|
||||
"📋 Executions",
|
||||
);
|
||||
ui.selectable_value(&mut self.current_page, PageType::Schedules, "⏰ Schedules");
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod common;
|
||||
pub mod execution_page;
|
||||
pub mod main_page;
|
||||
pub mod schedule_page;
|
||||
|
||||
@ -1,26 +1,27 @@
|
||||
use crate::core::app_config::AppConfig;
|
||||
use crate::core::schedule_manager::ScheduleManager;
|
||||
use crate::model::backup::backup_execution::*;
|
||||
use crate::model::backup::backup_schedule::*;
|
||||
use crate::model::error::task::TaskError;
|
||||
use crate::core::infrastructure::actor_system::ActorSystem;
|
||||
use crate::core::infrastructure::app_config::AppConfig;
|
||||
use crate::core::schedule::schedule_service::ScheduleService;
|
||||
use crate::model::core::actor::actor_ref::ActorRef;
|
||||
use crate::model::core::backup::backup_execution::*;
|
||||
use crate::model::core::schedule::backup_schedule::*;
|
||||
use crate::model::core::schedule::message::*;
|
||||
use crate::model::error::actor::ActorError;
|
||||
use crate::model::error::Error;
|
||||
use crate::ui::common::{ComparisonModeSelection, FolderSelectionMode};
|
||||
use eframe::egui;
|
||||
use egui_file_dialog::FileDialog;
|
||||
use futures::executor::block_on;
|
||||
use macros::log;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum FolderSelectionMode {
|
||||
Source,
|
||||
Destination,
|
||||
}
|
||||
|
||||
pub struct SchedulePage {
|
||||
app_config: Arc<AppConfig>,
|
||||
schedule_manager: Arc<ScheduleManager>,
|
||||
actor_system: Arc<ActorSystem>,
|
||||
|
||||
schedule_service_ref: ActorRef<ScheduleServiceMessage>,
|
||||
|
||||
schedules: Vec<BackupSchedule>,
|
||||
|
||||
@ -29,9 +30,10 @@ pub struct SchedulePage {
|
||||
new_schedule_destination: String,
|
||||
new_schedule_interval: ScheduleInterval,
|
||||
new_schedule_mirror: bool,
|
||||
new_schedule_lock_source: bool,
|
||||
new_schedule_backup_permission: bool,
|
||||
new_schedule_follow_symlinks: bool,
|
||||
new_schedule_comparison_mode: ComparisonModeSelection,
|
||||
new_schedule_hash_type: HashType,
|
||||
show_add_schedule_dialog: bool,
|
||||
|
||||
file_dialog: FileDialog,
|
||||
@ -43,35 +45,132 @@ pub struct SchedulePage {
|
||||
}
|
||||
|
||||
impl SchedulePage {
|
||||
pub fn new(app_config: Arc<AppConfig>, schedule_manager: Arc<ScheduleManager>) -> Self {
|
||||
Self {
|
||||
pub fn new(app_config: Arc<AppConfig>, actor_system: Arc<ActorSystem>) -> Result<Self, Error> {
|
||||
let schedule_service_ref = actor_system
|
||||
.actor_of::<ScheduleService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let schedule_page = Self {
|
||||
app_config,
|
||||
schedule_manager,
|
||||
actor_system,
|
||||
schedule_service_ref,
|
||||
schedules: Vec::new(),
|
||||
new_schedule_name: String::new(),
|
||||
new_schedule_source: String::new(),
|
||||
new_schedule_destination: String::new(),
|
||||
new_schedule_interval: ScheduleInterval::Daily,
|
||||
new_schedule_mirror: false,
|
||||
new_schedule_lock_source: false,
|
||||
new_schedule_backup_permission: false,
|
||||
new_schedule_follow_symlinks: false,
|
||||
new_schedule_comparison_mode: ComparisonModeSelection::Standard,
|
||||
new_schedule_hash_type: HashType::BLAKE3,
|
||||
show_add_schedule_dialog: false,
|
||||
file_dialog: FileDialog::new(),
|
||||
folder_selection_mode: None,
|
||||
show_disabled_schedules: true,
|
||||
viewing_schedule_details: None,
|
||||
last_refresh: None,
|
||||
}
|
||||
};
|
||||
Ok(schedule_page)
|
||||
}
|
||||
|
||||
fn load_schedules(&mut self) {
|
||||
match block_on(self.schedule_manager.get_all_schedules()) {
|
||||
Ok(schedules) => self.schedules = schedules,
|
||||
Err(err) => log!(TaskError::LoadScheduleFailed(err)),
|
||||
match block_on(async {
|
||||
self.schedule_service_ref
|
||||
.ask(ScheduleServiceMessage::ServiceCall(
|
||||
ServiceCallMessage::GetSchedules,
|
||||
))
|
||||
.await
|
||||
}) {
|
||||
Ok(ScheduleServiceResponse::ServiceCall(ServiceCallResponse::GetSchedules(
|
||||
schedules,
|
||||
))) => {
|
||||
self.schedules = schedules;
|
||||
}
|
||||
Ok(ScheduleServiceResponse::None) => {}
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_add_schedule(&self, schedule: BackupSchedule) -> Result<(), Error> {
|
||||
block_on(async {
|
||||
let backup_actor_ref = self
|
||||
.actor_system
|
||||
.actor_of::<ScheduleService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let message =
|
||||
ScheduleServiceMessage::ServiceCall(ServiceCallMessage::AddSchedule(schedule));
|
||||
backup_actor_ref.tell(message).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_modify_schedule(&self, schedule: BackupSchedule) -> Result<(), Error> {
|
||||
block_on(async {
|
||||
let backup_actor_ref = self
|
||||
.actor_system
|
||||
.actor_of::<ScheduleService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let message =
|
||||
ScheduleServiceMessage::ServiceCall(ServiceCallMessage::ModifySchedule(schedule));
|
||||
backup_actor_ref.tell(message).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_remove_schedule(&self, uuid: Uuid) -> Result<(), Error> {
|
||||
block_on(async {
|
||||
let backup_actor_ref = self
|
||||
.actor_system
|
||||
.actor_of::<ScheduleService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let message =
|
||||
ScheduleServiceMessage::ServiceCall(ServiceCallMessage::RemoveSchedule(uuid));
|
||||
backup_actor_ref.tell(message).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_active_schedule(&self, uuid: Uuid) -> Result<(), Error> {
|
||||
block_on(async {
|
||||
let backup_actor_ref = self
|
||||
.actor_system
|
||||
.actor_of::<ScheduleService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let message =
|
||||
ScheduleServiceMessage::ServiceCall(ServiceCallMessage::ActivateSchedule(uuid));
|
||||
backup_actor_ref.tell(message).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_pause_schedule(&self, uuid: Uuid) -> Result<(), Error> {
|
||||
block_on(async {
|
||||
let backup_actor_ref = self
|
||||
.actor_system
|
||||
.actor_of::<ScheduleService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let message =
|
||||
ScheduleServiceMessage::ServiceCall(ServiceCallMessage::PauseSchedule(uuid));
|
||||
backup_actor_ref.tell(message).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_disable_schedule(&self, uuid: Uuid) -> Result<(), Error> {
|
||||
block_on(async {
|
||||
let backup_actor_ref = self
|
||||
.actor_system
|
||||
.actor_of::<ScheduleService>()
|
||||
.ok_or(ActorError::ActorNotFound)?;
|
||||
let message =
|
||||
ScheduleServiceMessage::ServiceCall(ServiceCallMessage::DisableSchedule(uuid));
|
||||
backup_actor_ref.tell(message).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, ctx: &egui::Context) {
|
||||
let should_refresh = match self.last_refresh {
|
||||
None => true,
|
||||
@ -165,17 +264,27 @@ impl SchedulePage {
|
||||
ui.label(format!("📅 {}", schedule.name));
|
||||
ui.label(format!("🗂️ {}", schedule.source_path.display()));
|
||||
ui.label(format!("📁 {}", schedule.destination_path.display()));
|
||||
ui.label(format!("⏱️ {:?}", schedule.interval));
|
||||
ui.label(format!("⏱ {:?}", schedule.interval));
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let (color, symbol, status_text) = match schedule.state {
|
||||
ScheduleState::Active => (egui::Color32::GREEN, "✅", "Active"),
|
||||
ScheduleState::Paused => (egui::Color32::YELLOW, "⏸️", "Paused"),
|
||||
ScheduleState::Paused => (egui::Color32::YELLOW, "⏸", "Paused"),
|
||||
ScheduleState::Disabled => (egui::Color32::GRAY, "❌", "Disabled"),
|
||||
};
|
||||
|
||||
ui.colored_label(color, format!("{symbol} {status_text}"));
|
||||
|
||||
if let Some(comparison_mode) = &schedule.comparison_mode {
|
||||
ui.separator();
|
||||
let mode_text = match comparison_mode {
|
||||
ComparisonMode::Standard => "⚡ Standard",
|
||||
ComparisonMode::Advanced => "🔧 Advanced",
|
||||
ComparisonMode::Thorough(_) => "🔍 Thorough",
|
||||
};
|
||||
ui.label(mode_text);
|
||||
}
|
||||
|
||||
if let Some(last_run) = schedule.last_run_time {
|
||||
ui.separator();
|
||||
ui.label(format!(
|
||||
@ -203,29 +312,23 @@ impl SchedulePage {
|
||||
|
||||
match schedule.state {
|
||||
ScheduleState::Active => {
|
||||
if ui.button("⏸️ Pause").clicked() {
|
||||
if let Err(err) = block_on(
|
||||
self.schedule_manager.pause_schedule(schedule.uuid),
|
||||
) {
|
||||
log!(TaskError::PauseScheduleFailed(err));
|
||||
if ui.button("⏸ Pause").clicked() {
|
||||
if let Err(err) = self.handle_pause_schedule(schedule.uuid) {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
ScheduleState::Paused => {
|
||||
if ui.button("▶️ Resume").clicked() {
|
||||
if let Err(err) = block_on(
|
||||
self.schedule_manager.active_schedule(schedule.uuid),
|
||||
) {
|
||||
log!(TaskError::EnableScheduleFailed(err));
|
||||
if ui.button("▶ Resume").clicked() {
|
||||
if let Err(err) = self.handle_active_schedule(schedule.uuid) {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
ScheduleState::Disabled => {
|
||||
if ui.button("▶️ Enable").clicked() {
|
||||
if let Err(err) = block_on(
|
||||
self.schedule_manager.active_schedule(schedule.uuid),
|
||||
) {
|
||||
log!(TaskError::EnableScheduleFailed(err));
|
||||
if ui.button("▶ Enable").clicked() {
|
||||
if let Err(err) = self.handle_active_schedule(schedule.uuid) {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -234,18 +337,14 @@ impl SchedulePage {
|
||||
if schedule.state != ScheduleState::Disabled
|
||||
&& ui.button("❌ Disable").clicked()
|
||||
{
|
||||
if let Err(err) =
|
||||
block_on(self.schedule_manager.disable_schedule(schedule.uuid))
|
||||
{
|
||||
log!(TaskError::DisableScheduleFailed(err));
|
||||
if let Err(err) = self.handle_disable_schedule(schedule.uuid) {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("🗑️").clicked() {
|
||||
if let Err(err) =
|
||||
block_on(self.schedule_manager.remove_schedule(schedule.uuid))
|
||||
{
|
||||
log!(TaskError::RemoveScheduleFailed(err));
|
||||
if ui.button("🗑").clicked() {
|
||||
if let Err(err) = self.handle_remove_schedule(schedule.uuid) {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -264,10 +363,35 @@ impl SchedulePage {
|
||||
.spacing([10.0, 4.0])
|
||||
.show(ui, |ui| {
|
||||
ui.label("Schedule Name:");
|
||||
ui.text_edit_singleline(&mut self.new_schedule_name);
|
||||
ui.add_sized(
|
||||
[300.0, 20.0],
|
||||
egui::TextEdit::singleline(&mut self.new_schedule_name),
|
||||
);
|
||||
ui.label("");
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Source Path:");
|
||||
ui.add_sized(
|
||||
[300.0, 20.0],
|
||||
egui::TextEdit::singleline(&mut self.new_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.new_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.new_schedule_interval))
|
||||
@ -295,33 +419,77 @@ impl SchedulePage {
|
||||
});
|
||||
ui.label("");
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Source Path:");
|
||||
ui.text_edit_singleline(&mut self.new_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.text_edit_singleline(&mut self.new_schedule_destination);
|
||||
if ui.button("📁 Browse").clicked() {
|
||||
self.folder_selection_mode = Some(FolderSelectionMode::Destination);
|
||||
self.file_dialog.pick_directory();
|
||||
}
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("Options:");
|
||||
ui.label("File Comparison Mode:");
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(
|
||||
&mut self.new_schedule_comparison_mode,
|
||||
ComparisonModeSelection::Standard,
|
||||
"⚡ Standard (Size + Time)",
|
||||
);
|
||||
ui.radio_value(
|
||||
&mut self.new_schedule_comparison_mode,
|
||||
ComparisonModeSelection::Advanced,
|
||||
"🔧 Advanced (+ Attributes)",
|
||||
);
|
||||
ui.radio_value(
|
||||
&mut self.new_schedule_comparison_mode,
|
||||
ComparisonModeSelection::Thorough,
|
||||
"🔍 Thorough (+ Checksum)",
|
||||
);
|
||||
});
|
||||
|
||||
if self.new_schedule_comparison_mode == ComparisonModeSelection::Thorough {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(" Hash Algorithm:");
|
||||
egui::ComboBox::from_id_salt("schedule_hash_type")
|
||||
.selected_text(format!("{:?}", self.new_schedule_hash_type))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(
|
||||
&mut self.new_schedule_hash_type,
|
||||
HashType::BLAKE3,
|
||||
"BLAKE3 (Recommended)",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut self.new_schedule_hash_type,
|
||||
HashType::SHA256,
|
||||
"SHA256",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut self.new_schedule_hash_type,
|
||||
HashType::SHA3,
|
||||
"SHA3",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut self.new_schedule_hash_type,
|
||||
HashType::BLAKE2B,
|
||||
"BLAKE2B",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut self.new_schedule_hash_type,
|
||||
HashType::BLAKE2S,
|
||||
"BLAKE2S",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut self.new_schedule_hash_type,
|
||||
HashType::MD5,
|
||||
"MD5 (Legacy)",
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("Additional Options:");
|
||||
ui.checkbox(&mut self.new_schedule_follow_symlinks, "Follow Symlinks");
|
||||
ui.checkbox(
|
||||
&mut self.new_schedule_mirror,
|
||||
"Mirror Mode (Delete extra files in destination)",
|
||||
);
|
||||
ui.checkbox(&mut self.new_schedule_lock_source, "Lock Source Files");
|
||||
ui.checkbox(
|
||||
&mut self.new_schedule_backup_permission,
|
||||
"Backup File Permissions",
|
||||
@ -335,6 +503,14 @@ impl SchedulePage {
|
||||
&& !self.new_schedule_source.is_empty()
|
||||
&& !self.new_schedule_destination.is_empty()
|
||||
{
|
||||
let comparison_mode = match self.new_schedule_comparison_mode {
|
||||
ComparisonModeSelection::Standard => Some(ComparisonMode::Standard),
|
||||
ComparisonModeSelection::Advanced => Some(ComparisonMode::Advanced),
|
||||
ComparisonModeSelection::Thorough => {
|
||||
Some(ComparisonMode::Thorough(self.new_schedule_hash_type))
|
||||
}
|
||||
};
|
||||
|
||||
let schedule = BackupSchedule {
|
||||
uuid: Uuid::new_v4(),
|
||||
name: self.new_schedule_name.clone(),
|
||||
@ -342,10 +518,9 @@ impl SchedulePage {
|
||||
source_path: PathBuf::from(&self.new_schedule_source),
|
||||
destination_path: PathBuf::from(&self.new_schedule_destination),
|
||||
backup_type: BackupType::Full,
|
||||
comparison_mode: None,
|
||||
comparison_mode,
|
||||
options: BackupOptions {
|
||||
mirror: self.new_schedule_mirror,
|
||||
lock_source: self.new_schedule_lock_source,
|
||||
backup_permission: self.new_schedule_backup_permission,
|
||||
follow_symlinks: self.new_schedule_follow_symlinks,
|
||||
},
|
||||
@ -356,10 +531,8 @@ impl SchedulePage {
|
||||
updated_at: chrono::Utc::now().naive_utc(),
|
||||
};
|
||||
|
||||
if let Err(e) =
|
||||
block_on(self.schedule_manager.create_schedule(schedule))
|
||||
{
|
||||
eprintln!("Failed to create schedule: {e:?}");
|
||||
if let Err(err) = self.handle_add_schedule(schedule) {
|
||||
error!("{}", err);
|
||||
}
|
||||
|
||||
self.reset_schedule_form();
|
||||
@ -424,6 +597,19 @@ impl SchedulePage {
|
||||
ui.label(format!("{:?}", schedule.backup_type));
|
||||
ui.end_row();
|
||||
|
||||
if let Some(comparison_mode) = &schedule.comparison_mode {
|
||||
ui.label("Comparison Mode:");
|
||||
let mode_text = match comparison_mode {
|
||||
ComparisonMode::Standard => "Standard (Size + Time)",
|
||||
ComparisonMode::Advanced => "Advanced (+ Attributes)",
|
||||
ComparisonMode::Thorough(hash_type) => {
|
||||
&format!("Thorough (+ Checksum: {hash_type:?})")
|
||||
}
|
||||
};
|
||||
ui.label(mode_text);
|
||||
ui.end_row();
|
||||
}
|
||||
|
||||
ui.label("Interval:");
|
||||
ui.label(format!("{:?}", schedule.interval));
|
||||
ui.end_row();
|
||||
@ -460,9 +646,6 @@ impl SchedulePage {
|
||||
if schedule.options.mirror {
|
||||
ui.label("✅ Mirror Mode");
|
||||
}
|
||||
if schedule.options.lock_source {
|
||||
ui.label("✅ Lock Source");
|
||||
}
|
||||
if schedule.options.backup_permission {
|
||||
ui.label("✅ Backup Permissions");
|
||||
}
|
||||
@ -475,13 +658,9 @@ impl SchedulePage {
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Run Now").clicked() {
|
||||
// 這裡可以觸發立即執行排程
|
||||
// 可能需要新增一個 API 方法來立即執行排程
|
||||
println!("Would run schedule {} now", schedule.name);
|
||||
}
|
||||
|
||||
if ui.button("Edit").clicked() {
|
||||
// 這裡可以開啟編輯對話框
|
||||
println!("Would edit schedule {}", schedule.name);
|
||||
}
|
||||
});
|
||||
@ -500,9 +679,10 @@ impl SchedulePage {
|
||||
self.new_schedule_destination.clear();
|
||||
self.new_schedule_interval = ScheduleInterval::Daily;
|
||||
self.new_schedule_mirror = false;
|
||||
self.new_schedule_lock_source = false;
|
||||
self.new_schedule_backup_permission = false;
|
||||
self.new_schedule_follow_symlinks = false;
|
||||
self.new_schedule_comparison_mode = ComparisonModeSelection::Standard;
|
||||
self.new_schedule_hash_type = HashType::BLAKE3;
|
||||
self.show_add_schedule_dialog = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ use crate::platform::constants::DATABASE_LOCK_PATH;
|
||||
use std::fs;
|
||||
use tokio::fs::File;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DatabaseLock {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
use crate::model::error::io::IOError;
|
||||
use crate::model::error::Error;
|
||||
use fs4::tokio::AsyncFileExt;
|
||||
use macros::log;
|
||||
use std::path::PathBuf;
|
||||
use tokio::fs::File;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileLock {
|
||||
file: File,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl FileLock {
|
||||
pub async fn new(path: &PathBuf) -> Result<Self, Error> {
|
||||
let file = File::open(path)
|
||||
.await
|
||||
.map_err(|err| IOError::ReadFileFailed(path.clone(), err))?;
|
||||
file.try_lock_exclusive()
|
||||
.map_err(|err| IOError::LockFileFailed(path.clone(), err))?;
|
||||
Ok(Self {
|
||||
file,
|
||||
path: path.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FileLock {
|
||||
fn drop(&mut self) {
|
||||
let path = self.path.clone();
|
||||
if let Err(err) = self.file.unlock() {
|
||||
log!(IOError::UnlockFileFailed(path, err));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,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