refactor: Base stuff done

This commit is contained in:
LynithDev 2025-03-29 10:26:01 +01:00
parent 5f4d4013b3
commit 2815c45409
32 changed files with 1181 additions and 180 deletions

436
Cargo.lock generated
View file

@ -429,6 +429,15 @@ dependencies = [
"system-deps",
]
[[package]]
name = "atoi"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
dependencies = [
"num-traits",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
@ -1124,6 +1133,15 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
@ -1340,42 +1358,6 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "diesel"
version = "2.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "470eb10efc8646313634c99bb1593f402a6434cbd86e266770c6e39219adb86a"
dependencies = [
"chrono",
"diesel_derives",
"libsqlite3-sys",
"serde_json",
"time",
"uuid 0.8.2",
]
[[package]]
name = "diesel_derives"
version = "2.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a93958254b70bea63b4187ff73d10180599d9d8d177071b7f91e6da4e0c0ad55"
dependencies = [
"diesel_table_macro_syntax",
"dsl_auto_type",
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "diesel_table_macro_syntax"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25"
dependencies = [
"syn 2.0.87",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -1520,6 +1502,12 @@ dependencies = [
"const-random",
]
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dotenvy"
version = "0.15.7"
@ -1540,20 +1528,6 @@ dependencies = [
"serde",
]
[[package]]
name = "dsl_auto_type"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139ae9aca7527f85f26dd76483eb38533fd84bd571065da1739656ef71c5ff5b"
dependencies = [
"darling",
"either",
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "dtoa"
version = "1.0.9"
@ -1624,6 +1598,9 @@ name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
dependencies = [
"serde",
]
[[package]]
name = "elliptic-curve"
@ -1739,6 +1716,17 @@ version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
[[package]]
name = "etcetera"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
dependencies = [
"cfg-if",
"home",
"windows-sys 0.48.0",
]
[[package]]
name = "event-listener"
version = "5.3.1"
@ -1854,6 +1842,8 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
dependencies = [
"futures-core",
"futures-sink",
"spin",
]
@ -1863,6 +1853,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.5.0"
@ -1960,6 +1956,17 @@ dependencies = [
"futures-util",
]
[[package]]
name = "futures-intrusive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
dependencies = [
"futures-core",
"lock_api",
"parking_lot",
]
[[package]]
name = "futures-io"
version = "0.3.30"
@ -2403,6 +2410,26 @@ dependencies = [
"allocator-api2",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown 0.15.2",
]
[[package]]
name = "heck"
version = "0.4.1"
@ -3031,6 +3058,9 @@ name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
"spin",
]
[[package]]
name = "lebe"
@ -3113,6 +3143,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "libm"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
name = "libmimalloc-sys"
version = "0.1.39"
@ -3153,10 +3189,11 @@ dependencies = [
[[package]]
name = "libsqlite3-sys"
version = "0.31.0"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8935b44e7c13394a179a438e0cebba0fe08fe01b54f152e29a93b5cf993fd4"
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
@ -3272,6 +3309,16 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "md5"
version = "0.7.0"
@ -3548,6 +3595,23 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
dependencies = [
"byteorder",
"lazy_static",
"libm",
"num-integer",
"num-iter",
"num-traits",
"rand 0.8.5",
"smallvec",
"zeroize",
]
[[package]]
name = "num-conv"
version = "0.1.0"
@ -3574,6 +3638,17 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
@ -3592,6 +3667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@ -3878,7 +3954,7 @@ dependencies = [
"chrono",
"dirs 5.0.1",
"discord-rich-presence",
"dotenvy",
"dotenvy 0.15.7 (git+https://github.com/allan2/dotenvy/?rev=db0d6aa8fc8d1b0ddbfe94b5c833372e463eb1c5)",
"flate2",
"futures",
"indicatif",
@ -3981,14 +4057,16 @@ dependencies = [
name = "onelauncher_new"
version = "0.1.0-alpha.2"
dependencies = [
"anyhow",
"async-compression",
"async-trait",
"async_zip",
"base64 0.22.1",
"bytes",
"chrono",
"diesel",
"dirs 5.0.1",
"discord-rich-presence",
"flate2",
"dunce",
"futures",
"indicatif",
"interpulse",
@ -4007,6 +4085,7 @@ dependencies = [
"sha1_smol",
"sha2",
"specta",
"sqlx",
"sysinfo",
"tauri",
"tauri-specta",
@ -4395,6 +4474,17 @@ dependencies = [
"futures-io",
]
[[package]]
name = "pkcs1"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
dependencies = [
"der",
"pkcs8",
"spki",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
@ -5018,6 +5108,26 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
[[package]]
name = "rsa"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
dependencies = [
"const-oid",
"digest",
"num-bigint-dig",
"num-integer",
"num-traits",
"pkcs1",
"pkcs8",
"rand_core 0.6.4",
"signature",
"spki",
"subtle",
"zeroize",
]
[[package]]
name = "rust-argon2"
version = "1.0.0"
@ -5505,6 +5615,9 @@ name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
dependencies = [
"serde",
]
[[package]]
name = "socket2"
@ -5643,6 +5756,201 @@ dependencies = [
"der",
]
[[package]]
name = "sqlx"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f"
dependencies = [
"sqlx-core",
"sqlx-macros",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
]
[[package]]
name = "sqlx-core"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0"
dependencies = [
"bytes",
"chrono",
"crc",
"crossbeam-queue",
"either",
"event-listener",
"futures-core",
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown 0.15.2",
"hashlink",
"indexmap 2.5.0",
"log",
"memchr",
"once_cell",
"percent-encoding",
"serde",
"serde_json",
"sha2",
"smallvec",
"thiserror 2.0.12",
"tokio",
"tokio-stream",
"tracing",
"url",
"uuid 1.10.0",
]
[[package]]
name = "sqlx-macros"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310"
dependencies = [
"proc-macro2",
"quote",
"sqlx-core",
"sqlx-macros-core",
"syn 2.0.87",
]
[[package]]
name = "sqlx-macros-core"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad"
dependencies = [
"dotenvy 0.15.7 (registry+https://github.com/rust-lang/crates.io-index)",
"either",
"heck 0.5.0",
"hex",
"once_cell",
"proc-macro2",
"quote",
"serde",
"serde_json",
"sha2",
"sqlx-core",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
"syn 2.0.87",
"tempfile",
"tokio",
"url",
]
[[package]]
name = "sqlx-mysql"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233"
dependencies = [
"atoi",
"base64 0.22.1",
"bitflags 2.6.0",
"byteorder",
"bytes",
"chrono",
"crc",
"digest",
"dotenvy 0.15.7 (registry+https://github.com/rust-lang/crates.io-index)",
"either",
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"generic-array",
"hex",
"hkdf",
"hmac",
"itoa 1.0.11",
"log",
"md-5",
"memchr",
"once_cell",
"percent-encoding",
"rand 0.8.5",
"rsa",
"serde",
"sha1",
"sha2",
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 2.0.12",
"tracing",
"uuid 1.10.0",
"whoami",
]
[[package]]
name = "sqlx-postgres"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613"
dependencies = [
"atoi",
"base64 0.22.1",
"bitflags 2.6.0",
"byteorder",
"chrono",
"crc",
"dotenvy 0.15.7 (registry+https://github.com/rust-lang/crates.io-index)",
"etcetera",
"futures-channel",
"futures-core",
"futures-util",
"hex",
"hkdf",
"hmac",
"home",
"itoa 1.0.11",
"log",
"md-5",
"memchr",
"once_cell",
"rand 0.8.5",
"serde",
"serde_json",
"sha2",
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 2.0.12",
"tracing",
"uuid 1.10.0",
"whoami",
]
[[package]]
name = "sqlx-sqlite"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540"
dependencies = [
"atoi",
"chrono",
"flume",
"futures-channel",
"futures-core",
"futures-executor",
"futures-intrusive",
"futures-util",
"libsqlite3-sys",
"log",
"percent-encoding",
"serde",
"serde_urlencoded",
"sqlx-core",
"tracing",
"url",
"uuid 1.10.0",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@ -5681,6 +5989,17 @@ dependencies = [
"quote",
]
[[package]]
name = "stringprep"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
dependencies = [
"unicode-bidi",
"unicode-normalization",
"unicode-properties",
]
[[package]]
name = "stronghold-derive"
version = "1.0.0"
@ -6636,6 +6955,7 @@ version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@ -6861,6 +7181,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-properties"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"

View file

@ -126,10 +126,12 @@ md5 = { version = "0.7.0" }
bytes = { version = "1.7" }
# # ============= UTILITY ============= # #
diesel = { version = "2.2.0", features = [
sqlx = { version = "0.8", features = [
"runtime-tokio",
"sqlite",
"returning_clauses_for_sqlite_3_35",
"serde_json",
"macros",
"derive",
"migrate",
"uuid",
"chrono"
] }
@ -149,6 +151,7 @@ lazy_static = { version = "1.5" }
indicatif = { version = "0.17" }
futures = { version = "0.3" }
async-recursion = { version = "1.1" }
async-trait = { version = "0.1" }
sysinfo = { version = "0.31", features = [ "serde" ] }
sys-info = { version = "0.9" }
whoami = { version = "1.5" }

View file

@ -14,10 +14,16 @@ authors = { workspace = true }
path = "src/lib.rs"
[features]
default = []
tauri = [ "dep:tauri", "dep:tauri-specta", "specta" ]
default = [ "cli" ]
tauri = [
"dep:tauri",
"dep:tauri-specta",
"specta",
"gui"
]
specta = [ "dep:specta" ]
cli = [ "dep:indicatif" ]
gui = []
[lints]
workspace = true
@ -39,12 +45,13 @@ serde_repr = { workspace = true }
serde_with = { workspace = true }
# utils
diesel = { workspace = true }
sqlx = { workspace = true }
tokio = { workspace = true }
tokio-stream = { workspace = true }
futures = { workspace = true }
uuid = { workspace = true }
thiserror = { workspace = true }
anyhow = { workspace = true }
chrono = { workspace = true }
reqwest = { workspace = true }
regex = { workspace = true }
@ -53,6 +60,7 @@ whoami = { workspace = true }
lazy_static = { workspace = true }
rand = { workspace = true }
url = { workspace = true }
async-trait = { workspace = true }
# integration
discord-rich-presence = { workspace = true }
@ -69,7 +77,8 @@ async_zip = { workspace = true }
dirs = { workspace = true }
bytes = { workspace = true }
base64 = { workspace = true }
flate2 = { workspace = true }
async-compression = { workspace = true }
dunce = { workspace = true }
# features[tauri]
tauri = { workspace = true, optional = true }

View file

@ -1,8 +0,0 @@
# For documentation on how to configure this file,
# see https://diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"
custom_type_derives = [ "diesel::query_builder::QueryId", "Clone" ]

View file

@ -1 +0,0 @@
-- this is the init migration, it is not reversible

View file

@ -13,6 +13,6 @@
"scripts": {
"cargo": "cargo",
"binary": "cargo run --bin",
"diesel": "diesel"
"sqlx": "sqlx"
}
}

View file

@ -0,0 +1,14 @@
# Connecting to database on *nix
```bash
export DATABASE_URL=sqlite:/path/to/user_data.db
```
# Connecting to database on Windows
```cmd
set DATABASE_URL=sqlite:C:\\path\\to\\user_data.db
```
# Connecting to database on Windows (PowerShell)
```powershell
$env:DATABASE_URL="sqlite:C:\\path\\to\\user_data.db"
```

View file

@ -0,0 +1,37 @@
use crate::{store::{ingress::{IngressId, IngressType}, State}, LauncherResult};
pub async fn init_ingress(
ingress_type: IngressType,
message: &str,
total: f64,
) -> LauncherResult<IngressId> {
let state = State::get().await?;
let processor = &state.ingress_processor;
let id = processor.create(ingress_type, message.to_string(), total).await?;
Ok(id)
}
pub async fn send_ingress(id: IngressId) -> LauncherResult<()> {
let state = State::get().await?;
let processor = &state.ingress_processor;
processor.send(id.0).await?;
Ok(())
}
pub async fn update_ingress(id: IngressId, current: f64) -> LauncherResult<()> {
let state = State::get().await?;
let processor = &state.ingress_processor;
processor.update(id.0, current).await
}
pub async fn finish_ingress(id: IngressId) -> LauncherResult<()> {
let state = State::get().await?;
let processor = &state.ingress_processor;
processor.finish(id.0).await?;
Ok(())
}

View file

@ -0,0 +1,2 @@
pub mod ingress;
pub mod proxy;

View file

@ -0,0 +1,59 @@
use serde::Serialize;
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Debug, Clone)]
pub struct MessagePayload {
pub level: MessageLevel,
pub message: String,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Debug, Clone)]
pub enum MessageLevel {
Info,
Warning,
Error,
}
#[macro_export]
macro_rules! send_warning {
($($message:tt)*) => {{
tokio::spawn(async move {
let _ = match $crate::store::proxy::ProxyState::get() {
Ok(proxy) => {
let payload = $crate::api::proxy::message::MessagePayload {
level: $crate::api::proxy::message::MessageLevel::Warning,
message: format!($($message)*),
};
let _ = proxy.send_message(payload).await;
},
Err(err) => {
tracing::warn!($($message)*);
tracing::warn!("failed to send warning: {}", err);
},
};
})
}};
}
#[macro_export]
macro_rules! send_error {
($($message:tt)*) => {{
tokio::spawn(async move {
let _ = match $crate::store::proxy::ProxyState::get() {
Ok(proxy) => {
let payload = $crate::api::proxy::message::MessagePayload {
level: $crate::api::proxy::message::MessageLevel::Error,
message: format!($($message)*),
};
let _ = proxy.send_message(payload).await;
},
Err(err) => {
tracing::error!($($message)*);
tracing::error!("failed to send error: {}", err);
},
};
})
}};
}

View file

@ -0,0 +1,20 @@
pub mod message;
#[cfg(feature = "tauri")]
pub mod proxy_tauri;
#[cfg(feature = "cli")]
pub mod proxy_cli;
#[async_trait::async_trait]
pub trait LauncherProxy: Send + Sync {
async fn send_ingress(&self, ingress: crate::store::ingress::IngressPayload) -> crate::LauncherResult<()>;
async fn send_message(&self, message: message::MessagePayload) -> crate::LauncherResult<()>;
#[cfg(feature = "gui")]
fn hide_main_window(&self) -> crate::LauncherResult<()>;
#[cfg(feature = "gui")]
fn show_main_window(&self) -> crate::LauncherResult<()>;
}

View file

@ -0,0 +1,45 @@
use std::collections::HashMap;
use indicatif::ProgressBar;
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::{store::{ingress::IngressPayload, State}, LauncherResult};
use super::LauncherProxy;
#[derive(Default)]
pub struct ProxyCli {
ingress_feeds: RwLock<HashMap<Uuid, ProgressBar>>,
}
impl ProxyCli {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
#[async_trait::async_trait]
impl LauncherProxy for ProxyCli {
async fn send_ingress(&self, ingress: IngressPayload) -> LauncherResult<()> {
let feeds = self.ingress_feeds.write().await;
if let Some(progress) = feeds.get(&ingress.id) {
// progress.set_position(ingress.percent as u64);
// TODO: customize progress bar
} else {
let progress = ProgressBar::new(ingress.total as u64);
progress.set_message(ingress.message.clone());
progress.set_position(0);
}
Ok(())
}
async fn send_message(&self, message: crate::api::proxy::message::MessagePayload) -> LauncherResult<()> {
println!("[{:?}] {:?}", message.level, message.message);
Ok(())
}
}

View file

@ -0,0 +1,63 @@
use tauri::{AppHandle, Emitter, Manager};
use tauri_specta::Event;
use tracing::{error, warn};
use crate::{store::ingress::IngressPayload, LauncherResult};
use super::{message::{MessageLevel, MessagePayload}, LauncherProxy};
#[derive(Debug)]
pub struct ProxyTauri {
handle: AppHandle,
}
impl ProxyTauri {
#[must_use]
pub const fn new(handle: AppHandle) -> Self {
Self {
handle
}
}
}
#[async_trait::async_trait]
impl LauncherProxy for ProxyTauri {
async fn send_ingress(&self, ingress: IngressPayload) -> LauncherResult<()> {
self.handle
.emit(IngressPayload::NAME, ingress)
.map_err(Into::into)
}
async fn send_message(&self, message: MessagePayload) -> LauncherResult<()> {
match message.level {
MessageLevel::Info => {},
MessageLevel::Warning => warn!("{}", message.message),
MessageLevel::Error => error!("{}", message.message),
};
self.handle
.emit(MessagePayload::NAME, message)
.map_err(Into::into)
}
#[tracing::instrument]
fn hide_main_window(&self) -> LauncherResult<()> {
if let Some(window) = self.handle.get_webview_window("main") {
Ok(window.hide()?)
} else {
warn!("main window not found");
Ok(())
}
}
#[tracing::instrument]
fn show_main_window(&self) -> LauncherResult<()> {
if let Some(window) = self.handle.get_webview_window("main") {
Ok(window.show()?)
} else {
warn!("main window not found");
Ok(())
}
}
}

View file

@ -1,4 +1,4 @@
use onelauncher_new::io::Dirs;
use onelauncher_new::store::Dirs;
#[tokio::main]
async fn main() {

View file

@ -1,7 +1,6 @@
//! **`OneLauncher` Constants**
//!
//! Public constant variables and strings that are used throughout the launcher.
//! Can be shared publically, any secrets should be stored in [`crate::store::credentials`].
// =========== Core Metadata ===========
/// The name of the launcher.

View file

@ -1,7 +1,24 @@
#[derive(thiserror::Error, Debug)]
pub enum LauncherError {
#[error(transparent)]
DirError(#[from] crate::io::DirectoryError),
DirError(#[from] crate::store::DirectoryError),
#[error(transparent)]
IOError(#[from] crate::utils::io::IOError),
#[error(transparent)]
IngressError(#[from] crate::store::ingress::IngressError),
#[error(transparent)]
SerdeError(#[from] serde_json::Error),
#[error(transparent)]
DbError(#[from] sqlx::Error),
#[error(transparent)]
DbMigrationError(#[from] sqlx::migrate::MigrateError),
#[error(transparent)]
AnyhowError(#[from] anyhow::Error),
#[cfg(feature = "tauri")]
#[error(transparent)]
TauriError(#[from] tauri::Error),
}
pub type LauncherResult<T> = Result<T, LauncherError>;

View file

@ -1,3 +0,0 @@
mod dirs;
pub use dirs::*;

View file

@ -1,16 +1,10 @@
// Private (accessible by crate only) modules
// Modules which are not re-exported
pub mod io;
pub mod constants;
pub mod schema;
pub mod utils;
pub mod store;
pub mod api;
// Modules which are re-exported
mod state;
mod error;
mod logger;
// Re-exported modules
pub use state::State;
pub use error::*;
pub use error::{LauncherResult, LauncherError};
pub use logger::start_logger;

View file

@ -1,80 +0,0 @@
// @generated automatically by Diesel CLI.
diesel::table! {
cluster_groups (id) {
id -> Integer,
name -> Text,
}
}
diesel::table! {
clusters (path) {
path -> Text,
created_at -> Integer,
updated_at -> Integer,
group_id -> Nullable<Integer>,
name -> Text,
mc_version -> Text,
mc_loader -> Text,
mc_loader_version -> Nullable<Text>,
last_played -> Nullable<Integer>,
overall_played -> Nullable<Integer>,
icon_url -> Nullable<Text>,
setting_profile_name -> Nullable<Text>,
linked_pack_id -> Nullable<Text>,
linked_pack_version -> Nullable<Integer>,
}
}
diesel::table! {
java_versions (id) {
id -> Integer,
major_version -> Integer,
display_name -> Text,
absolute_path -> Text,
full_version -> Text,
}
}
diesel::table! {
packages (file_name, type_id) {
file_name -> Text,
display_name -> Text,
display_version -> Text,
type_id -> Integer,
provider_id -> Integer,
provider_version -> Text,
mc_versions -> Text,
mc_loader -> Text,
hash -> Text,
icon_url -> Nullable<Text>,
}
}
diesel::table! {
setting_profiles (name) {
name -> Text,
java_id -> Nullable<Integer>,
res_w -> Nullable<Integer>,
res_h -> Nullable<Integer>,
force_fullscreen -> Nullable<Integer>,
mem_max -> Nullable<Integer>,
launch_args -> Nullable<Text>,
launch_env -> Nullable<Text>,
hook_pre -> Nullable<Text>,
hook_wrapper -> Nullable<Text>,
hook_post -> Nullable<Text>,
}
}
diesel::joinable!(clusters -> cluster_groups (group_id));
diesel::joinable!(clusters -> setting_profiles (setting_profile_name));
diesel::joinable!(setting_profiles -> java_versions (java_id));
diesel::allow_tables_to_appear_in_same_query!(
cluster_groups,
clusters,
java_versions,
packages,
setting_profiles,
);

View file

@ -0,0 +1,32 @@
use std::path::PathBuf;
use chrono::Duration;
use sqlx::{migrate::MigrateDatabase, sqlite::SqlitePoolOptions, Sqlite, SqlitePool};
use crate::LauncherResult;
use super::Dirs;
pub async fn create_pool_from_path(path: PathBuf) -> LauncherResult<SqlitePool> {
let protocol = String::from("sqlite:");
let connection_str = protocol + path.to_str().expect("invalid path used for sqlite connection");
if !Sqlite::database_exists(&connection_str).await? {
Sqlite::create_database(&connection_str).await?;
}
let pool = SqlitePoolOptions::new()
.max_connections(40)
.idle_timeout(Some(Duration::hours(6).to_std().unwrap()))
.connect(&connection_str).await?;
sqlx::migrate!().run(&pool).await?;
Ok(pool)
}
pub async fn create_pool() -> LauncherResult<SqlitePool> {
let path = Dirs::get().await?.db_file();
create_pool_from_path(path).await
}

View file

@ -2,7 +2,7 @@ use std::path::PathBuf;
use tokio::sync::OnceCell;
use crate::{constants, LauncherResult};
use crate::{constants, utils, LauncherResult};
/// The static [`OnceCell<RwLock<Dirs>>`] for storing the global directory state.
/// Should be initialized as soon as possible (preferably before logging)
@ -20,7 +20,7 @@ impl Dirs {
}
fn initialize() -> LauncherResult<Self> {
let base_dir = env_path("LAUNCHER_DIR")
let base_dir = utils::io::env_path("LAUNCHER_DIR")
.or_else(|| Some(dirs::data_dir()?.join(constants::NAME)))
.ok_or(DirectoryError::BaseDir)?;
@ -41,11 +41,20 @@ impl Dirs {
self.base_dir().join("logs")
}
// --- FILES ---
#[must_use]
pub fn db_file(&self) -> PathBuf {
self.base_dir().join("user_data.db")
}
#[must_use]
pub fn settings_file(&self) -> PathBuf {
self.base_dir().join("settings.json")
}
// --- GAME DIRECTORIES ---
/// Get the `config_dir/metadata` folder within the core config directory.
#[inline]
@ -85,6 +94,3 @@ pub enum DirectoryError {
BaseDir,
}
pub fn env_path(name: &str) -> Option<PathBuf> {
std::env::var_os(name).map(PathBuf::from)
}

View file

@ -0,0 +1,144 @@
use std::collections::HashMap;
use serde::Serialize;
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::LauncherResult;
use super::{proxy::ProxyState, State};
#[derive(Default)]
pub struct IngressProcessor {
ingress_feeds: RwLock<HashMap<Uuid, Ingress>>,
}
#[derive(Debug, thiserror::Error)]
pub enum IngressError {
#[error("ingress not found")]
NotFound,
}
impl IngressProcessor {
#[must_use]
pub fn new() -> Self {
Self {
ingress_feeds: RwLock::new(HashMap::new()),
}
}
pub async fn create(&self, ingress_type: IngressType, message: String, total: f64) -> LauncherResult<IngressId> {
let mut feeds = self.ingress_feeds.write().await;
let uuid = Uuid::new_v4();
let ingress = Ingress {
id: IngressId(uuid),
message,
ingress_type,
total,
current: 0.0,
last_sent: 0.0,
};
feeds.insert(uuid, ingress);
self.send(uuid).await?;
Ok(IngressId(uuid))
}
pub async fn update(&self, id: Uuid, current: f64) -> LauncherResult<()> {
let mut feeds = self.ingress_feeds.write().await;
let ingress = feeds.get_mut(&id).ok_or_else(|| IngressError::NotFound)?;
ingress.current = current;
Ok(())
}
pub async fn send(&self, id: Uuid) -> LauncherResult<()> {
let feeds = self.ingress_feeds.read().await;
let ingress = feeds.get(&id).ok_or_else(|| IngressError::NotFound)?;
let proxy = ProxyState::get()?;
proxy.send_ingress(IngressPayload {
id: ingress.id.0,
message: ingress.message.clone(),
ingress_type: ingress.ingress_type.clone(),
percent: Some(ingress.current / ingress.total),
total: ingress.total,
}).await?;
Ok(())
}
pub async fn finish(&self, uuid: Uuid) -> LauncherResult<Ingress> {
let mut feeds = self.ingress_feeds.write().await;
Ok(feeds.remove(&uuid).ok_or_else(|| IngressError::NotFound)?)
}
}
#[derive(Debug, Clone)]
pub struct IngressId(pub Uuid);
#[derive(Debug, Clone)]
pub struct Ingress {
pub id: IngressId,
pub message: String,
pub ingress_type: IngressType,
pub total: f64,
pub current: f64,
pub last_sent: f64,
}
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Debug, Clone)]
pub struct IngressPayload {
pub id: Uuid,
pub message: String,
pub ingress_type: IngressType,
pub percent: Option<f64>,
pub total: f64,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Serialize, Debug, Clone)]
pub enum IngressType {
Download {
file_name: String,
}
}
impl Drop for IngressId {
#[clippy::has_significant_drop]
fn drop(&mut self) {
let ingress_uuid = self.0;
tokio::spawn(async move {
let result: LauncherResult<()> = async {
let state = State::get().await?;
let processor = &state.ingress_processor;
let ingress = processor.finish(ingress_uuid).await?;
let proxy = ProxyState::get()?;
proxy.send_ingress(IngressPayload {
id: ingress.id.0,
message: "Completed".into(),
ingress_type: ingress.ingress_type,
percent: None,
total: ingress.total,
}).await?;
tracing::trace!("exited at {}% for ingress {:?}", ingress.current / ingress.total * 100.0, ingress_uuid);
Ok(())
}.await;
if let Err(e) = result {
tracing::error!("failed to finish ingress: {:?}", e);
}
});
}
}

View file

@ -0,0 +1,10 @@
pub(super) mod db;
pub mod ingress;
pub mod proxy;
mod dirs;
mod settings;
mod state;
pub use dirs::*;
pub use settings::*;
pub use state::*;

View file

@ -0,0 +1,40 @@
use std::{ops::Deref, sync::Arc};
use anyhow::anyhow;
use tokio::sync::OnceCell;
use crate::{api::proxy::LauncherProxy, LauncherResult};
static PROXY_STATE: OnceCell<Arc<ProxyState>> = OnceCell::const_new();
pub struct ProxyState {
inner: Box<dyn LauncherProxy>,
}
impl Deref for ProxyState {
type Target = Box<dyn LauncherProxy>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl ProxyState {
pub async fn initialize(proxy: Box<dyn LauncherProxy>) -> LauncherResult<Arc<Self>> {
PROXY_STATE
.get_or_try_init(|| async {
Ok(Arc::new(Self {
inner: proxy,
}))
})
.await
.cloned()
}
pub fn get() -> LauncherResult<Arc<Self>> {
Ok(PROXY_STATE
.get()
.ok_or_else(|| anyhow!("proxy state not initialized"))
.cloned()?)
}
}

View file

@ -0,0 +1,74 @@
use serde::{Deserialize, Serialize};
use sqlx::prelude::FromRow;
use crate::{send_warning, utils::io, LauncherResult};
use super::Dirs;
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct Settings {
pub global_game_settings: SettingsProfile,
pub allow_parallel_running_clusters: bool,
pub enable_gamemode: bool,
pub discord_enabled: bool,
pub settings_version: u32,
}
impl Default for Settings {
fn default() -> Self {
Self {
global_game_settings: SettingsProfile {
name: "Global".into(),
..Default::default()
},
allow_parallel_running_clusters: false,
discord_enabled: false,
enable_gamemode: false,
settings_version: 2,
}
}
}
impl Settings {
pub async fn new() -> Self {
match Self::read().await {
Ok(settings) => settings,
Err(err) => {
send_warning!("Failed to read settings file: {}", err);
Self::default()
},
}
}
async fn read() -> LauncherResult<Self> {
let path = Dirs::get().await?.settings_file();
let data = io::read(path).await?;
Ok(serde_json::from_slice(&data)?)
}
pub async fn save(&self) -> LauncherResult<()> {
let path = Dirs::get().await?.settings_file();
let data = serde_json::to_string_pretty(self)?;
io::write(path, data).await?;
Ok(())
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Default, Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct SettingsProfile {
pub name: String,
pub java_id: Option<usize>,
pub res_w: Option<usize>,
pub res_h: Option<usize>,
pub force_fullscreen: Option<bool>,
pub mem_max: Option<usize>,
pub launch_args: Option<String>,
pub launch_env: Option<String>,
pub hook_pre: Option<String>,
pub hook_wrapper: Option<String>,
pub hook_post: Option<String>,
}

View file

@ -1,14 +1,19 @@
use std::sync::Arc;
use sqlx::SqlitePool;
use tokio::sync::{OnceCell, RwLock};
use crate::LauncherResult;
use super::{ingress::IngressProcessor, Settings};
/// The static [`OnceCell<RwLock<State>>`] for storing the global runtime launcher state.
static LAUNCHER_STATE: OnceCell<RwLock<State>> = OnceCell::const_new();
pub struct State {
pub ingress_processor: IngressProcessor,
pub settings: Settings,
pub db: SqlitePool,
}
impl State {
@ -32,9 +37,19 @@ impl State {
.await)
}
async fn initialize() -> LauncherResult<RwLock<Self>> {
Ok(RwLock::new(Self {
pub fn initialized() -> bool {
LAUNCHER_STATE.initialized()
}
async fn initialize() -> LauncherResult<RwLock<Self>> {
let ingress_processor = IngressProcessor::new();
let settings = Settings::new().await;
let db = super::db::create_pool().await?;
Ok(RwLock::new(Self {
ingress_processor,
settings,
db,
}))
}
}

View file

@ -0,0 +1,185 @@
use async_compression::tokio::bufread::GzipDecoder;
use tokio::io::AsyncReadExt;
/// A wrapper around generic and unhelpful [`std::io::Error`] messages.
#[derive(Debug, thiserror::Error)]
pub enum IOError {
/// A wrapped [`std::io::Error`] along with the path involved in the error.
#[error("error acessing path: {source}, path: {path}")]
IOErrorWrapper {
#[source]
source: std::io::Error,
path: String,
},
/// A wrapped [`zip::result::ZipError`].
#[error(transparent)]
ZipError(#[from] zip::result::ZipError),
/// A wrapped [`std::io::Error`].
#[error(transparent)]
IOError(#[from] std::io::Error),
}
impl<P: AsRef<std::path::Path>> From<(P, std::io::Error)> for IOError {
fn from((path, source): (P, std::io::Error)) -> Self {
Self::IOErrorWrapper {
source,
path: path.as_ref().to_string_lossy().to_string(),
}
}
}
#[derive(Debug, thiserror::Error)]
#[error("received a non UTF-8 path: <lossy_path='{}'>", .0.to_string_lossy())]
pub struct NonUtf8PathError(pub Box<std::path::Path>);
/// Attempts to parse a path taken from an environment variable. Returns `None` if the variable is not set.
pub fn env_path(name: &str) -> Option<std::path::PathBuf> {
std::env::var_os(name).map(std::path::PathBuf::from)
}
/// An OS specific wrapper of [`std::fs::canonicalize`], but on Windows it outputs the most compatible form of a path instead of UNC.
pub fn canonicalize(path: impl AsRef<std::path::Path>) -> Result<std::path::PathBuf, IOError> {
let path = path.as_ref();
dunce::canonicalize(path).map_err(|e| IOError::IOErrorWrapper {
source: e,
path: path.to_string_lossy().to_string(),
})
}
/// Returns a stream over the entries within a directory.
pub async fn read_dir(path: impl AsRef<std::path::Path>) -> Result<tokio::fs::ReadDir, IOError> {
let path = path.as_ref();
tokio::fs::read_dir(path)
.await
.map_err(|e| IOError::IOErrorWrapper {
source: e,
path: path.to_string_lossy().to_string(),
})
}
/// Creates a directory if they are missing.
pub async fn create_dir(path: impl AsRef<std::path::Path>) -> Result<(), IOError> {
let path = path.as_ref();
if path.exists() {
return Ok(());
}
tokio::fs::create_dir(path)
.await
.map_err(|e| IOError::IOErrorWrapper {
source: e,
path: path.to_string_lossy().to_string(),
})
}
/// Recursively creates a directory and all of its parent components if they are missing.
pub async fn create_dir_all(path: impl AsRef<std::path::Path>) -> Result<(), IOError> {
let path = path.as_ref();
tokio::fs::create_dir_all(path)
.await
.map_err(|e| IOError::IOErrorWrapper {
source: e,
path: path.to_string_lossy().to_string(),
})
}
/// Removes a directory at this path, after removing all its contents. Use carefully!
pub async fn remove_dir_all(path: impl AsRef<std::path::Path>) -> Result<(), IOError> {
let path = path.as_ref();
tokio::fs::remove_dir_all(path)
.await
.map_err(|e| IOError::IOErrorWrapper {
source: e,
path: path.to_string_lossy().to_string(),
})
}
/// Creates a future which will open a gzip compressed file for reading and read the entire contents into a string and return said string.
pub async fn read_gz_to_string(path: impl AsRef<std::path::Path>) -> Result<String, IOError> {
let mut f = tokio::fs::File::open(path).await?;
let mut buf = vec![];
f.read_to_end(&mut buf).await?;
let mut decoder = GzipDecoder::new(buf.as_slice());
let mut dst = String::new();
decoder.read_to_string(&mut dst).await?;
Ok(dst)
}
/// Creates a future which will open a file for reading and read the entire contents into a string and return said string.
pub async fn read_to_string(path: impl AsRef<std::path::Path>) -> Result<String, IOError> {
let path = path.as_ref();
tokio::fs::read_to_string(path)
.await
.map_err(|e| IOError::IOErrorWrapper {
source: e,
path: path.to_string_lossy().to_string(),
})
}
/// Reads the entire contents of a file into a bytes vector.
pub async fn read(path: impl AsRef<std::path::Path>) -> Result<Vec<u8>, IOError> {
let path = path.as_ref();
tokio::fs::read(path)
.await
.map_err(|e| IOError::IOErrorWrapper {
source: e,
path: path.to_string_lossy().to_string(),
})
}
/// Asynchrously write to a tempfile that is then transferred to an official [`AsRef<Path>`].
pub async fn write(
path: impl AsRef<std::path::Path>,
data: impl AsRef<[u8]>,
) -> Result<(), IOError> {
let path = path.as_ref();
tokio::fs::write(path, data)
.await
.map_err(|e| IOError::IOErrorWrapper {
source: e,
path: path.to_string_lossy().to_string(),
})
}
/// Renames a file or directory to a new name, replacing the original file if `to` already exists.
pub async fn rename(
from: impl AsRef<std::path::Path>,
to: impl AsRef<std::path::Path>,
) -> Result<(), IOError> {
let from = from.as_ref();
let to = to.as_ref();
tokio::fs::rename(from, to)
.await
.map_err(|e| IOError::IOErrorWrapper {
source: e,
path: from.to_string_lossy().to_string(),
})
}
/// Copies the contents of one file to another. This function will also copy the permission bits of the original file to the destination file. This function will overwrite the contents of to.
pub async fn copy(
from: impl AsRef<std::path::Path>,
to: impl AsRef<std::path::Path>,
) -> Result<u64, IOError> {
let from = from.as_ref();
let to = to.as_ref();
tokio::fs::copy(from, to)
.await
.map_err(|e| IOError::IOErrorWrapper {
source: e,
path: from.to_string_lossy().to_string(),
})
}
/// Removes a file from the filesystem.
pub async fn remove_file(path: impl AsRef<std::path::Path>) -> Result<(), IOError> {
let path = path.as_ref();
tokio::fs::remove_file(path)
.await
.map_err(|e| IOError::IOErrorWrapper {
source: e,
path: path.to_string_lossy().to_string(),
})
}

View file

@ -0,0 +1 @@
pub mod io;

View file

@ -199,7 +199,7 @@ https://learn.microsoft.com/windows/package-manager/winget/
Write-Host
Write-Host 'Installing Rust tools...' -ForegroundColor Yellow
cargo install cargo-watch
cargo install diesel_cli --no-default-features --features sqlite
cargo install sqlx-cli
if ($LASTEXITCODE -ne 0) {
Exit-WithError 'Failed to install Rust tools'
}

View file

@ -183,10 +183,8 @@ esac
# installs cargo-watch for development purposes
if [ "${CI:-}" != "true" ]; then
echo "installing rust tools..."
_tools="cargo-watch"
_tools="cargo-watch sqlx-cli"
echo "$_tools" | xargs cargo install
cargo install diesel_cli --no-default-features --features sqlite
fi
echo 'your machine has been setup for onelauncher development!'