Compare commits

..

2 Commits

Author SHA1 Message Date
62a2561539 fix: Remote tauri dependency from process
Signed-off-by: quexeky <git@quexeky.dev>
2025-10-11 09:51:04 +11:00
59f040bc8b chore: Major refactoring
Still needs a massive go-over because there shouldn't be anything referencing tauri in any of the workspaces except the original one. Process manager has been refactored as an example

Signed-off-by: quexeky <git@quexeky.dev>
2025-10-11 09:28:41 +11:00
152 changed files with 2486 additions and 8524 deletions

4
.gitignore vendored
View File

@ -29,4 +29,6 @@ src-tauri/flamegraph.svg
src-tauri/perf* src-tauri/perf*
/*.AppImage /*.AppImage
/squashfs-root /squashfs-root
/target/

File diff suppressed because it is too large Load Diff

14
Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[workspace]
members = [
"client",
"database",
"src-tauri",
"process",
"remote",
"utils",
"cloud_saves",
"download_manager",
"games",
]
resolver = "3"

47
client/src/compat.rs Normal file
View File

@ -0,0 +1,47 @@
use std::{ffi::OsStr, path::PathBuf, process::{Command, Stdio}, sync::LazyLock};
use log::info;
pub static COMPAT_INFO: LazyLock<Option<CompatInfo>> = LazyLock::new(create_new_compat_info);
pub static UMU_LAUNCHER_EXECUTABLE: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
let x = get_umu_executable();
info!("{:?}", &x);
x
});
#[derive(Clone)]
pub struct CompatInfo {
pub umu_installed: bool,
}
fn create_new_compat_info() -> Option<CompatInfo> {
#[cfg(target_os = "windows")]
return None;
let has_umu_installed = UMU_LAUNCHER_EXECUTABLE.is_some();
Some(CompatInfo {
umu_installed: has_umu_installed,
})
}
const UMU_BASE_LAUNCHER_EXECUTABLE: &str = "umu-run";
const UMU_INSTALL_DIRS: [&str; 4] = ["/app/share", "/use/local/share", "/usr/share", "/opt"];
fn get_umu_executable() -> Option<PathBuf> {
if check_executable_exists(UMU_BASE_LAUNCHER_EXECUTABLE) {
return Some(PathBuf::from(UMU_BASE_LAUNCHER_EXECUTABLE));
}
for dir in UMU_INSTALL_DIRS {
let p = PathBuf::from(dir).join(UMU_BASE_LAUNCHER_EXECUTABLE);
if check_executable_exists(&p) {
return Some(p);
}
}
None
}
fn check_executable_exists<P: AsRef<OsStr>>(exec: P) -> bool {
let has_umu_installed = Command::new(exec).stdout(Stdio::null()).output();
has_umu_installed.is_ok()
}

4
client/src/lib.rs Normal file
View File

@ -0,0 +1,4 @@
pub mod autostart;
pub mod user;
pub mod app_status;
pub mod compat;

View File

@ -11,7 +11,3 @@ pub struct User {
profile_picture_object_id: String, profile_picture_object_id: String,
} }
#[derive(Clone)]
pub struct CompatInfo {
umu_installed: bool,
}

View File

@ -11,6 +11,7 @@ regex = "1.11.3"
rustix = "1.1.2" rustix = "1.1.2"
serde = "1.0.228" serde = "1.0.228"
serde_json = "1.0.145" serde_json = "1.0.145"
serde_with = "3.15.0"
tar = "0.4.44" tar = "0.4.44"
tempfile = "3.23.0" tempfile = "3.23.0"
uuid = "1.18.1" uuid = "1.18.1"

27
cloud_saves/src/error.rs Normal file
View File

@ -0,0 +1,27 @@
use std::fmt::Display;
use serde_with::SerializeDisplay;
#[derive(Debug, SerializeDisplay, Clone, Copy)]
pub enum BackupError {
InvalidSystem,
NotFound,
ParseError,
}
impl Display for BackupError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
BackupError::InvalidSystem => "Attempted to generate path for invalid system",
BackupError::NotFound => "Could not generate or find path",
BackupError::ParseError => "Failed to parse path",
};
write!(f, "{}", s)
}
}

View File

@ -6,7 +6,7 @@ use database::{borrow_db_checked, borrow_db_mut_checked, ApplicationTransientSta
use log::{debug, error, warn}; use log::{debug, error, warn};
use remote::{auth::generate_authorization_header, error::RemoteAccessError, requests::generate_url, utils::DROP_CLIENT_SYNC}; use remote::{auth::generate_authorization_header, error::RemoteAccessError, requests::generate_url, utils::DROP_CLIENT_SYNC};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tauri::AppHandle; use tauri::{AppHandle, Emitter};
use utils::app_emit; use utils::app_emit;
use crate::{downloads::error::LibraryError, state::{GameStatusManager, GameStatusWithTransient}}; use crate::{downloads::error::LibraryError, state::{GameStatusManager, GameStatusWithTransient}};

View File

@ -8,10 +8,11 @@ chrono = "0.4.42"
client = { version = "0.1.0", path = "../client" } client = { version = "0.1.0", path = "../client" }
database = { version = "0.1.0", path = "../database" } database = { version = "0.1.0", path = "../database" }
dynfmt = "0.1.5" dynfmt = "0.1.5"
games = { version = "0.1.0", path = "../games" }
log = "0.4.28" log = "0.4.28"
page_size = "0.6.0"
serde = "1.0.228" serde = "1.0.228"
serde_with = "3.15.0" serde_with = "3.15.0"
shared_child = "1.1.1" shared_child = "1.1.1"
tauri = "2.8.5"
tauri-plugin-opener = "2.5.0" tauri-plugin-opener = "2.5.0"
utils = { version = "0.1.0", path = "../utils" } utils = { version = "0.1.0", path = "../utils" }

View File

@ -12,7 +12,8 @@ pub enum ProcessError {
FormatError(String), // String errors supremacy FormatError(String), // String errors supremacy
InvalidPlatform, InvalidPlatform,
OpenerError(tauri_plugin_opener::Error), OpenerError(tauri_plugin_opener::Error),
InvalidArguments(String) InvalidArguments(String),
FailedLaunch(String),
} }
impl Display for ProcessError { impl Display for ProcessError {
@ -26,8 +27,13 @@ impl Display for ProcessError {
ProcessError::InvalidPlatform => "This game cannot be played on the current platform", ProcessError::InvalidPlatform => "This game cannot be played on the current platform",
ProcessError::FormatError(error) => &format!("Could not format template: {error:?}"), ProcessError::FormatError(error) => &format!("Could not format template: {error:?}"),
ProcessError::OpenerError(error) => &format!("Could not open directory: {error:?}"), ProcessError::OpenerError(error) => &format!("Could not open directory: {error:?}"),
ProcessError::InvalidArguments(arguments) => &format!("Invalid arguments in command {arguments}"), ProcessError::InvalidArguments(arguments) => {
}; &format!("Invalid arguments in command {arguments}")
}
ProcessError::FailedLaunch(game_id) => {
&format!("Drop detected that the game {game_id} may have failed to launch properly")
}
};
write!(f, "{s}") write!(f, "{s}")
} }
} }

14
process/src/lib.rs Normal file
View File

@ -0,0 +1,14 @@
#![feature(nonpoison_mutex)]
#![feature(sync_nonpoison)]
use std::sync::{LazyLock, nonpoison::Mutex};
use crate::process_manager::ProcessManager;
pub static PROCESS_MANAGER: LazyLock<Mutex<ProcessManager>> =
LazyLock::new(|| Mutex::new(ProcessManager::new()));
pub mod error;
pub mod format;
pub mod process_handlers;
pub mod process_manager;

View File

@ -1,12 +1,6 @@
use std::{ use client::compat::{COMPAT_INFO, UMU_LAUNCHER_EXECUTABLE};
ffi::OsStr,
path::PathBuf,
process::{Command, Stdio},
sync::LazyLock,
};
use database::{platform::Platform, Database, DownloadableMetadata, GameVersion}; use database::{platform::Platform, Database, DownloadableMetadata, GameVersion};
use log::{debug, info}; use log::debug;
use crate::{error::ProcessError, process_manager::ProcessHandler}; use crate::{error::ProcessError, process_manager::ProcessHandler};
@ -24,36 +18,11 @@ impl ProcessHandler for NativeGameLauncher {
Ok(format!("\"{}\" {}", launch_command, args.join(" "))) Ok(format!("\"{}\" {}", launch_command, args.join(" ")))
} }
fn valid_for_platform(&self, _db: &Database, _state: &AppState, _target: &Platform) -> bool { fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
true true
} }
} }
pub static UMU_LAUNCHER_EXECUTABLE: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
let x = get_umu_executable();
info!("{:?}", &x);
x
});
const UMU_BASE_LAUNCHER_EXECUTABLE: &str = "umu-run";
const UMU_INSTALL_DIRS: [&str; 4] = ["/app/share", "/use/local/share", "/usr/share", "/opt"];
fn get_umu_executable() -> Option<PathBuf> {
if check_executable_exists(UMU_BASE_LAUNCHER_EXECUTABLE) {
return Some(PathBuf::from(UMU_BASE_LAUNCHER_EXECUTABLE));
}
for dir in UMU_INSTALL_DIRS {
let p = PathBuf::from(dir).join(UMU_BASE_LAUNCHER_EXECUTABLE);
if check_executable_exists(&p) {
return Some(p);
}
}
None
}
fn check_executable_exists<P: AsRef<OsStr>>(exec: P) -> bool {
let has_umu_installed = Command::new(exec).stdout(Stdio::null()).output();
has_umu_installed.is_ok()
}
pub struct UMULauncher; pub struct UMULauncher;
impl ProcessHandler for UMULauncher { impl ProcessHandler for UMULauncher {
fn create_launch_process( fn create_launch_process(
@ -83,8 +52,8 @@ impl ProcessHandler for UMULauncher {
)) ))
} }
fn valid_for_platform(&self, _db: &Database, state: &AppState, _target: &Platform) -> bool { fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
let Some(ref compat_info) = state.compat_info else { let Some(compat_info) = &*COMPAT_INFO else {
return false; return false;
}; };
compat_info.umu_installed compat_info.umu_installed
@ -124,7 +93,7 @@ impl ProcessHandler for AsahiMuvmLauncher {
#[allow(unreachable_code)] #[allow(unreachable_code)]
#[allow(unused_variables)] #[allow(unused_variables)]
fn valid_for_platform(&self, _db: &Database, state: &AppState, _target: &Platform) -> bool { fn valid_for_platform(&self, _db: &Database, _target: &Platform) -> bool {
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
return false; return false;
@ -136,7 +105,7 @@ impl ProcessHandler for AsahiMuvmLauncher {
return false; return false;
} }
let Some(ref compat_info) = state.compat_info else { let Some(compat_info) = &*COMPAT_INFO else {
return false; return false;
}; };

View File

@ -1,27 +1,31 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
fs::{OpenOptions, create_dir_all}, fs::{OpenOptions, create_dir_all},
io::{self}, io,
path::PathBuf, path::PathBuf,
process::{Command, ExitStatus}, process::{Command, ExitStatus},
str::FromStr, str::FromStr,
sync::{Arc, Mutex}, sync::Arc,
thread::spawn,
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
use database::{borrow_db_checked, borrow_db_mut_checked, db::DATA_ROOT_DIR, platform::Platform, ApplicationTransientStatus, Database, DownloadType, DownloadableMetadata, GameDownloadStatus, GameVersion}; use database::{
ApplicationTransientStatus, Database, DownloadType, DownloadableMetadata, GameDownloadStatus,
GameVersion, borrow_db_checked, borrow_db_mut_checked, db::DATA_ROOT_DIR, platform::Platform,
};
use dynfmt::Format; use dynfmt::Format;
use dynfmt::SimpleCurlyFormat; use dynfmt::SimpleCurlyFormat;
use games::state::GameStatusManager;
use log::{debug, info, warn}; use log::{debug, info, warn};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use shared_child::SharedChild; use shared_child::SharedChild;
use tauri::{AppHandle, Emitter, Manager};
use tauri_plugin_opener::OpenerExt;
use utils::lock;
use crate::{error::ProcessError, format::DropFormatArgs, process_handlers::{AsahiMuvmLauncher, NativeGameLauncher, UMULauncher}};
use crate::{
PROCESS_MANAGER,
error::ProcessError,
format::DropFormatArgs,
process_handlers::{AsahiMuvmLauncher, NativeGameLauncher, UMULauncher},
};
pub struct RunningProcess { pub struct RunningProcess {
handle: Arc<SharedChild>, handle: Arc<SharedChild>,
@ -33,7 +37,6 @@ pub struct ProcessManager<'a> {
current_platform: Platform, current_platform: Platform,
log_output_dir: PathBuf, log_output_dir: PathBuf,
processes: HashMap<String, RunningProcess>, processes: HashMap<String, RunningProcess>,
app_handle: AppHandle,
game_launchers: Vec<( game_launchers: Vec<(
(Platform, Platform), (Platform, Platform),
&'a (dyn ProcessHandler + Sync + Send + 'static), &'a (dyn ProcessHandler + Sync + Send + 'static),
@ -41,7 +44,7 @@ pub struct ProcessManager<'a> {
} }
impl ProcessManager<'_> { impl ProcessManager<'_> {
pub fn new(app_handle: AppHandle) -> Self { pub fn new() -> Self {
let log_output_dir = DATA_ROOT_DIR.join("logs"); let log_output_dir = DATA_ROOT_DIR.join("logs");
ProcessManager { ProcessManager {
@ -54,7 +57,6 @@ impl ProcessManager<'_> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
current_platform: Platform::Linux, current_platform: Platform::Linux,
app_handle,
processes: HashMap::new(), processes: HashMap::new(),
log_output_dir, log_output_dir,
game_launchers: vec![ game_launchers: vec![
@ -102,21 +104,16 @@ impl ProcessManager<'_> {
self.log_output_dir.join(game_id) self.log_output_dir.join(game_id)
} }
pub fn open_process_logs(&mut self, game_id: String) -> Result<(), ProcessError> { fn on_process_finish(
let dir = self.get_log_dir(game_id); &mut self,
self.app_handle game_id: String,
.opener() result: Result<ExitStatus, std::io::Error>,
.open_path(dir.display().to_string(), None::<&str>) ) -> Result<(), ProcessError> {
.map_err(ProcessError::OpenerError)?;
Ok(())
}
fn on_process_finish(&mut self, game_id: String, result: Result<ExitStatus, std::io::Error>) {
if !self.processes.contains_key(&game_id) { if !self.processes.contains_key(&game_id) {
warn!( warn!(
"process on_finish was called, but game_id is no longer valid. finished with result: {result:?}" "process on_finish was called, but game_id is no longer valid. finished with result: {result:?}"
); );
return; return Ok(());
} }
debug!("process for {:?} exited with {:?}", &game_id, result); debug!("process for {:?} exited with {:?}", &game_id, result);
@ -125,7 +122,7 @@ impl ProcessManager<'_> {
Some(process) => process, Some(process) => process,
None => { None => {
info!("Attempted to stop process {game_id} which didn't exist"); info!("Attempted to stop process {game_id} which didn't exist");
return; return Ok(());
} }
}; };
@ -163,7 +160,8 @@ impl ProcessManager<'_> {
&& (elapsed.as_secs() <= 2 || result.map_or(true, |r| !r.success())) && (elapsed.as_secs() <= 2 || result.map_or(true, |r| !r.success()))
{ {
warn!("drop detected that the game {game_id} may have failed to launch properly"); warn!("drop detected that the game {game_id} may have failed to launch properly");
let _ = self.app_handle.emit("launch_external_error", &game_id); return Err(ProcessError::FailedLaunch(game_id));
// let _ = self.app_handle.emit("launch_external_error", &game_id);
} }
let version_data = match db_handle.applications.game_versions.get(&game_id) { let version_data = match db_handle.applications.game_versions.get(&game_id) {
@ -174,18 +172,19 @@ impl ProcessManager<'_> {
let status = GameStatusManager::fetch_state(&game_id, &db_handle); let status = GameStatusManager::fetch_state(&game_id, &db_handle);
push_game_update( // TODO
&self.app_handle, // push_game_update(
&game_id, // &self.app_handle,
Some(version_data.clone()), // &game_id,
status, // Some(version_data.clone()),
); // status,
// );
Ok(())
} }
fn fetch_process_handler( fn fetch_process_handler(
&self, &self,
db_lock: &Database, db_lock: &Database,
state: &AppState,
target_platform: &Platform, target_platform: &Platform,
) -> Result<&(dyn ProcessHandler + Send + Sync), ProcessError> { ) -> Result<&(dyn ProcessHandler + Send + Sync), ProcessError> {
Ok(self Ok(self
@ -195,23 +194,20 @@ impl ProcessManager<'_> {
let (e_current, e_target) = e.0; let (e_current, e_target) = e.0;
e_current == self.current_platform e_current == self.current_platform
&& e_target == *target_platform && e_target == *target_platform
&& e.1.valid_for_platform(db_lock, state, target_platform) && e.1.valid_for_platform(db_lock, target_platform)
}) })
.ok_or(ProcessError::InvalidPlatform)? .ok_or(ProcessError::InvalidPlatform)?
.1) .1)
} }
pub fn valid_platform(&self, platform: &Platform, state: &AppState) -> bool { pub fn valid_platform(&self, platform: &Platform) -> bool {
let db_lock = borrow_db_checked(); let db_lock = borrow_db_checked();
let process_handler = self.fetch_process_handler(&db_lock, state, platform); let process_handler = self.fetch_process_handler(&db_lock, platform);
process_handler.is_ok() process_handler.is_ok()
} }
pub fn launch_process( /// Must be called through spawn as it is currently blocking
&mut self, pub fn launch_process(&mut self, game_id: String) -> Result<(), ProcessError> {
game_id: String,
state: &AppState,
) -> Result<(), ProcessError> {
if self.processes.contains_key(&game_id) { if self.processes.contains_key(&game_id) {
return Err(ProcessError::AlreadyRunning); return Err(ProcessError::AlreadyRunning);
} }
@ -293,7 +289,7 @@ impl ProcessManager<'_> {
let target_platform = game_version.platform; let target_platform = game_version.platform;
let process_handler = self.fetch_process_handler(&db_lock, state, &target_platform)?; let process_handler = self.fetch_process_handler(&db_lock, &target_platform)?;
let (launch, args) = match game_status { let (launch, args) = match game_status {
GameDownloadStatus::Installed { GameDownloadStatus::Installed {
@ -367,35 +363,17 @@ impl ProcessManager<'_> {
.transient_statuses .transient_statuses
.insert(meta.clone(), ApplicationTransientStatus::Running {}); .insert(meta.clone(), ApplicationTransientStatus::Running {});
push_game_update( // TODO
&self.app_handle, // push_game_update(
&meta.id, // &self.app_handle,
None, // &meta.id,
(None, Some(ApplicationTransientStatus::Running {})), // None,
); // (None, Some(ApplicationTransientStatus::Running {})),
// );
let wait_thread_handle = launch_process_handle.clone(); let wait_thread_handle = launch_process_handle.clone();
let wait_thread_apphandle = self.app_handle.clone();
let wait_thread_game_id = meta.clone(); let wait_thread_game_id = meta.clone();
spawn(move || {
let result: Result<ExitStatus, std::io::Error> = launch_process_handle.wait();
let app_state = wait_thread_apphandle.state::<Mutex<AppState>>();
let app_state_handle = lock!(app_state);
let mut process_manager_handle = app_state_handle
.process_manager
.lock()
.expect("Failed to lock onto process manager");
process_manager_handle.on_process_finish(wait_thread_game_id.id, result);
// As everything goes out of scope, they should get dropped
// But just to explicit about it
drop(process_manager_handle);
drop(app_state_handle);
});
self.processes.insert( self.processes.insert(
meta.id, meta.id,
RunningProcess { RunningProcess {
@ -404,7 +382,12 @@ impl ProcessManager<'_> {
manually_killed: false, manually_killed: false,
}, },
); );
Ok(())
let result: Result<ExitStatus, std::io::Error> = launch_process_handle.wait();
PROCESS_MANAGER
.lock()
.on_process_finish(wait_thread_game_id.id, result)
} }
} }
@ -418,5 +401,5 @@ pub trait ProcessHandler: Send + 'static {
current_dir: &str, current_dir: &str,
) -> Result<String, ProcessError>; ) -> Result<String, ProcessError>;
fn valid_for_platform(&self, db: &Database, state: &AppState, target: &Platform) -> bool; fn valid_for_platform(&self, db: &Database, target: &Platform) -> bool;
} }

2136
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,137 @@
[workspace] [package]
members = [ name = "drop-app"
"client", version = "0.3.3"
"database", description = "The client application for the open-source, self-hosted game distribution platform Drop"
"src-tauri", authors = ["Drop OSS"]
"process", edition = "2024"
"remote",
"utils", # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
"cloud_saves",
"download_manager", [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies]
"games", tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] }
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "drop_app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
rustflags = ["-C", "target-feature=+aes,+sse2"]
[build-dependencies]
tauri-build = { version = "2.0.0", features = [] }
[dependencies]
tauri-plugin-shell = "2.2.1"
serde_json = "1"
rayon = "1.10.0"
webbrowser = "1.0.2"
url = "2.5.2"
tauri-plugin-deep-link = "2"
log = "0.4.22"
hex = "0.4.3"
tauri-plugin-dialog = "2"
http = "1.1.0"
urlencoding = "2.1.3"
md5 = "0.7.0"
chrono = "0.4.38"
tauri-plugin-os = "2"
boxcar = "0.2.7"
umu-wrapper-lib = "0.1.0"
tauri-plugin-autostart = "2.0.0"
shared_child = "1.0.1"
serde_with = "3.12.0"
slice-deque = "0.3.0"
throttle_my_fn = "0.2.6"
parking_lot = "0.12.3"
atomic-instant-full = "0.1.0"
cacache = "13.1.0"
http-serde = "2.1.1"
reqwest-middleware = "0.4.0"
reqwest-middleware-cache = "0.1.1"
deranged = "=0.4.0"
droplet-rs = "0.7.3"
gethostname = "1.0.1"
zstd = "0.13.3"
tar = "0.4.44"
rand = "0.9.1"
regex = "1.11.1"
tempfile = "3.19.1"
schemars = "0.8.22"
sha1 = "0.10.6"
dirs = "6.0.0"
whoami = "1.6.0"
filetime = "0.2.25"
walkdir = "2.5.0"
known-folders = "1.2.0"
native_model = { version = "0.6.4", features = ["rmp_serde_1_3"], git = "https://github.com/Drop-OSS/native_model.git"}
tauri-plugin-opener = "2.4.0"
bitcode = "0.6.6"
reqwest-websocket = "0.5.0"
futures-lite = "2.6.0"
page_size = "0.6.0"
sysinfo = "0.36.1"
humansize = "2.1.3"
tokio-util = { version = "0.7.16", features = ["io"] }
futures-core = "0.3.31"
bytes = "1.10.1"
# tailscale = { path = "./tailscale" }
# Workspaces
client = { path = "../client" }
database = { path = "../database" }
process = { path = "../process" }
remote = { path = "../remote" }
utils = { path = "../utils" }
[dependencies.dynfmt]
version = "0.1.5"
features = ["curly"]
[dependencies.tauri]
version = "2.7.0"
features = ["protocol-asset", "tray-icon"]
[dependencies.tokio]
version = "1.40.0"
features = ["rt", "tokio-macros", "signal"]
[dependencies.log4rs]
version = "1.3.0"
features = ["console_appender", "file_appender"]
[dependencies.rustix]
version = "0.38.37"
features = ["fs"]
[dependencies.uuid]
version = "1.10.0"
features = ["v4", "fast-rng", "macro-diagnostics"]
[dependencies.rustbreak]
version = "2"
features = ["other_errors"] # You can also use "yaml_enc" or "bin_enc"
[dependencies.reqwest]
version = "0.12.22"
default-features = false
features = [
"json",
"http2",
"blocking",
"rustls-tls",
"native-tls-alpn",
"rustls-tls-native-roots",
"stream",
] ]
resolver = "3" [dependencies.serde]
version = "1"
features = ["derive", "rc"]
[profile.release]
lto = true
codegen-units = 1
panic = 'abort'

View File

@ -1,3 +0,0 @@
pub mod autostart;
pub mod user;
pub mod app_status;

View File

@ -1,5 +0,0 @@
pub enum BackupError {
InvalidSystem,
ParseError,
NotFound
}

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 911 B

After

Width:  |  Height:  |  Size: 911 B

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Before

Width:  |  Height:  |  Size: 803 B

After

Width:  |  Height:  |  Size: 803 B

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Some files were not shown because too many files have changed in this diff Show More