refactor: Switch to sea-orm
This commit is contained in:
parent
7b82ad6887
commit
33ac7b4397
129 changed files with 2787 additions and 4219 deletions
1
.env.template
Normal file
1
.env.template
Normal file
|
|
@ -0,0 +1 @@
|
|||
DATABASE_URL=sqlite://path/to/user_data.db
|
||||
2899
Cargo.lock
generated
2899
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
98
Cargo.toml
98
Cargo.toml
|
|
@ -1,37 +1,32 @@
|
|||
[workspace]
|
||||
resolver = "2" # virtual workspace resolver so we don't need a root package
|
||||
members = [
|
||||
"apps/testing", # onelauncher_test -- test the core crate in a cli setting
|
||||
"apps/desktop", # onelauncher_gui -- the rust code for our tauri app
|
||||
"packages/core", # onelauncher -- the rust core for onelauncher
|
||||
"packages/core_new",
|
||||
"packages/macros", # onelauncher_macros -- utility procedural macros for onelauncher
|
||||
"packages/gamemode", # onelauncher_gamemode -- rust wrapper for feral gamemode
|
||||
"packages/utils" # onelauncher_utils -- standard utilities for use within all crates
|
||||
"packages/core",
|
||||
"packages/entity",
|
||||
"packages/gamemode",
|
||||
"packages/macro",
|
||||
"packages/migration",
|
||||
]
|
||||
|
||||
[workspace.package] # sets default [package] settings for workspace members
|
||||
[workspace.package]
|
||||
license = "GPL-3.0-only"
|
||||
edition = "2021" # latest rust edition
|
||||
edition = "2024"
|
||||
repository = "https://github.com/Polyfrost/OneLauncher"
|
||||
documentation = "https://docs.polyfrost.org/onelauncher"
|
||||
readme = "README.md"
|
||||
homepage = "https://polyfrost.org/projects/onelauncher"
|
||||
authors = [ "Polyfrost", "Polyfrost Contributors" ]
|
||||
version = "0.1.0-alpha.2" # bump this manually for now
|
||||
version = "0.2.0-alpha.2"
|
||||
|
||||
[workspace.dependencies]
|
||||
# # ============= WORKSPACE ============= # #
|
||||
# best practice to define all workspace crates in the root
|
||||
# even if they are not use, with no features enabled.
|
||||
onelauncher = { path = "./packages/core" }
|
||||
onelauncher_macros = { path = "./packages/macros" }
|
||||
onelauncher_gui = { path = "./apps/desktop" }
|
||||
onelauncher_test = { path = "./apps/testing" }
|
||||
# workspace
|
||||
onelauncher_core = { path = "./packages/core" }
|
||||
onelauncher_entity = { path = "./packages/entity" }
|
||||
onelauncher_gamemode = { path = "./packages/gamemode" }
|
||||
onelauncher_utils = { path = "./packages/utils" }
|
||||
onelauncher_macro = { path = "./packages/macro" }
|
||||
onelauncher_migration = { path = "./packages/migration" }
|
||||
|
||||
# # ============= TAURI ============= # #
|
||||
# tauri
|
||||
tauri = { version = "2.0", features = [
|
||||
"rustls-tls",
|
||||
"tracing",
|
||||
|
|
@ -48,7 +43,10 @@ tauri-plugin-updater = { version = "2.0" }
|
|||
tauri-plugin-dialog = { version = "2.0" }
|
||||
tauri-plugin-deep-link = { version = "2.0" }
|
||||
|
||||
# # ============= CORE ============= # #
|
||||
# core
|
||||
interpulse = { version = "=1.2.2", features = [ "specta" ] }
|
||||
|
||||
# code gen
|
||||
specta = { version = "2.0.0-rc.20", features = [
|
||||
"chrono",
|
||||
"uuid",
|
||||
|
|
@ -60,19 +58,18 @@ specta = { version = "2.0.0-rc.20", features = [
|
|||
] }
|
||||
tauri-specta = { version = "2.0.0-rc.20", features = [ "javascript", "typescript", "derive" ] }
|
||||
specta-typescript = { version = "=0.0.7" }
|
||||
interpulse = { version = "=1.2.2", features = [ "specta" ] }
|
||||
|
||||
# # ============= SECURITY ============= # #
|
||||
# security
|
||||
iota_stronghold = { version = "2.1" }
|
||||
zeroize = { version = "1.8", features = [ "zeroize_derive" ] }
|
||||
|
||||
# # ============= LOGGING ============= # #
|
||||
# logging
|
||||
tracing = { version = "0.1" }
|
||||
tracing-subscriber = { version = "0.3", features = [ "chrono", "env-filter" ] }
|
||||
tracing-error = { version = "0.2" }
|
||||
tracing-appender = { version = "0.2" }
|
||||
|
||||
# # ============= SERIALIZATION ============= # #
|
||||
# serialization & text
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
serde_json = { version = "1.0" }
|
||||
serde_ini = { version = "0.2" }
|
||||
|
|
@ -90,7 +87,7 @@ uuid = { version = "1.10", features = [ "serde", "v4" ] }
|
|||
toml = { version = "0.8" }
|
||||
nom = { version = "7.1" }
|
||||
|
||||
# # ============= NETWORK ============= # #
|
||||
# network
|
||||
reqwest = { version = "0.12", default-features = false, features = [
|
||||
"json",
|
||||
"stream",
|
||||
|
|
@ -107,7 +104,7 @@ reqwest = { version = "0.12", default-features = false, features = [
|
|||
async-tungstenite = { version = "0.28", features = [ "tokio-runtime", "tokio-rustls-webpki-roots" ] }
|
||||
webbrowser = { version = "1.0" }
|
||||
|
||||
# # ============= SYSTEM ============= # #
|
||||
# io
|
||||
tokio = { version = "1.40", features = [ "full" ] }
|
||||
tokio-stream = { version = "0.1.16", features = [ "fs" ] }
|
||||
notify = { version = "6.1", default-features = false }
|
||||
|
|
@ -119,49 +116,40 @@ dirs = { version = "5.0" }
|
|||
async_zip = { version = "0.0.17", features = [ "full" ] }
|
||||
async-compression = { version = "0.4.12", features = [ "flate2", "tokio" ] }
|
||||
flate2 = { version = "1.0" }
|
||||
bytes = { version = "1.7" }
|
||||
|
||||
# crypto
|
||||
sha1_smol = { version = "1.0", features = [ "std" ] }
|
||||
sha2 = { version = "0.10" }
|
||||
murmur2 = { version = "0.1.0" }
|
||||
md5 = { version = "0.7.0" }
|
||||
bytes = { version = "1.7" }
|
||||
|
||||
# # ============= UTILITY ============= # #
|
||||
sqlx = { version = "0.8", features = [
|
||||
"runtime-tokio",
|
||||
"sqlite",
|
||||
# db
|
||||
sea-orm = { version = "1.1", features = [
|
||||
"sqlx-sqlite",
|
||||
"runtime-tokio-rustls",
|
||||
"macros",
|
||||
"derive",
|
||||
"migrate",
|
||||
"uuid",
|
||||
"chrono"
|
||||
"with-chrono",
|
||||
"with-json",
|
||||
"with-uuid"
|
||||
] }
|
||||
sea-orm-migration = { version = "1.1", features = [ "sqlx-sqlite", "runtime-tokio-rustls" ] }
|
||||
|
||||
# utils
|
||||
thiserror = { version = "1.0" }
|
||||
anyhow = { version = "1.0" }
|
||||
async-channel = { version = "2.3" }
|
||||
mimalloc = { version = "0.1" }
|
||||
pin-project-lite = { version = "0.2" }
|
||||
once_cell = { version = "1.19" }
|
||||
quote = { version = "1.0" }
|
||||
syn = { version = "2.0", features = [ "full" ] }
|
||||
sysinfo = { version = "0.31", features = [ "serde" ] }
|
||||
discord-rich-presence = { version = "0.2" }
|
||||
winreg = { version = "0.52" }
|
||||
paste = { version = "1.0" }
|
||||
dashmap = { version = "6.1", features = [ "serde" ] }
|
||||
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" }
|
||||
cocoa = { version = "0.26" }
|
||||
objc = { version = "0.2" }
|
||||
cc = { version = "1.1" }
|
||||
libc = { version = "0.2" }
|
||||
dotenvy = { git = "https://github.com/allan2/dotenvy/", rev = "db0d6aa8fc8d1b0ddbfe94b5c833372e463eb1c5" }
|
||||
# https://github.com/tauri-apps/tauri/blob/dev/crates/tauri/Cargo.toml#L102
|
||||
webkit2gtk = { version = "=2.0.1", features = [ "v2_40" ] }
|
||||
|
||||
# macro
|
||||
syn = { version = "2" }
|
||||
quote = { version = "1" }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
all = { level = "warn", priority = -1 }
|
||||
|
|
@ -197,10 +185,6 @@ deprecated = "warn"
|
|||
deprecated_in_future = "forbid"
|
||||
unused_must_use = "deny"
|
||||
|
||||
[patch.crates-io]
|
||||
# rspc = { git = "https://github.com/oscartbeaumont/rspc", rev = "9f26d6400affdb501837cb023678268ad72feb9b" }
|
||||
# rspc-tauri2 = { git = "https://github.com/specta-rs/tauri2", rev = "6b9419566e2a826fb2a62538c7647f4524ab0fea" }
|
||||
|
||||
[profile.dev]
|
||||
# add debug symbols/logic and optimize for reduced build time (parallel)
|
||||
debug = true # add debug info
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -28,15 +28,17 @@
|
|||
"test": "pnpm test:vi && pnpm --filter=./* run test",
|
||||
"format": "pnpm format:es && pnpm format:rs",
|
||||
"lint": "pnpm lint:es && pnpm lint:rs",
|
||||
|
||||
"migrate": "sea-orm-cli migrate -d ./packages/migration",
|
||||
"entity:gen": "sea-orm-cli generate entity -o packages/entity/src/entities --with-serde both --model-extra-attributes 'onelauncher_macro::specta'",
|
||||
|
||||
"frontend": "pnpm --filter @onelauncher/frontend -- ",
|
||||
"client": "pnpm --filter @onelauncher/client -- ",
|
||||
"desktop": "pnpm --filter @onelauncher/desktop -- ",
|
||||
"core_new": "pnpm --filter @onelauncher/core_new -- ",
|
||||
"core": "pnpm --filter @onelauncher/core -- ",
|
||||
"scripts": "pnpm --filter @onelauncher/scripts -- ",
|
||||
"testing": "pnpm --filter @onelauncher/testing --",
|
||||
"tauri": "pnpm desktop tauri-ext",
|
||||
|
||||
"postupdate": "cargo update",
|
||||
"----------------------": "----------------------",
|
||||
"test:vi": "pnpm vitest run",
|
||||
"test:update": "pnpm vitest -u",
|
||||
"test:ui": "pnpm vitest --ui",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "onelauncher"
|
||||
name = "onelauncher_core"
|
||||
description = "The core of OneLauncher"
|
||||
version = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
|
@ -10,67 +10,78 @@ readme = { workspace = true }
|
|||
homepage = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
# feature flag allowing us to interact with tauri (for loading animations, errors showing on gui, etc)
|
||||
# also allows us to compile onelauncher core without tauri
|
||||
tauri = [ "dep:tauri", "dep:tauri-specta", "specta" ]
|
||||
default = [ "tauri" ]
|
||||
tauri = [
|
||||
"dep:tauri",
|
||||
"dep:tauri-specta",
|
||||
"specta",
|
||||
"gui"
|
||||
]
|
||||
specta = [ "dep:specta" ]
|
||||
cli = [ "dep:indicatif" ]
|
||||
gui = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
# features[default]
|
||||
onelauncher_macros = { workspace = true }
|
||||
onelauncher_utils = { workspace = true }
|
||||
# core
|
||||
interpulse = { workspace = true }
|
||||
onelauncher_migration = { workspace = true }
|
||||
onelauncher_entity = { workspace = true }
|
||||
onelauncher_macro = { workspace = true }
|
||||
|
||||
# logging
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
tracing-error = { workspace = true }
|
||||
tracing-appender = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
notify-debouncer-mini = { workspace = true }
|
||||
iota_stronghold = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
# serialization
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_ini = { workspace = true }
|
||||
serde_repr = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
|
||||
# utils
|
||||
sea-orm = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
zip = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
sysinfo = { workspace = true }
|
||||
whoami = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
url = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
|
||||
# integration
|
||||
discord-rich-presence = { workspace = true }
|
||||
|
||||
# crypto
|
||||
sha1_smol = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
murmur2 = { workspace = true }
|
||||
md5 = { workspace = true }
|
||||
|
||||
# io
|
||||
zip = { workspace = true }
|
||||
async_zip = { workspace = true }
|
||||
discord-rich-presence = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
sysinfo = { workspace = true }
|
||||
whoami = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
async-tungstenite = { workspace = true }
|
||||
async-recursion = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
p256 = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
flate2 = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
url = { workspace = true }
|
||||
async-compression = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
|
||||
# features[tauri]
|
||||
tauri = { workspace = true, optional = true }
|
||||
|
|
@ -82,11 +93,5 @@ specta = { workspace = true, optional = true }
|
|||
# features[cli]
|
||||
indicatif = { workspace = true, optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
dotenvy = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winreg = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
onelauncher_gamemode = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
{
|
||||
"name": "@onelauncher/core",
|
||||
"version": "0.1.0-alpha.2",
|
||||
"version": "0.2.0-alpha.1",
|
||||
"description": "Next-generation open source Minecraft launcher core",
|
||||
"license": "GPL-3.0-only",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:polyfrost/onelauncher",
|
||||
"directory": "packages/core"
|
||||
"directory": "packages/core-new"
|
||||
},
|
||||
"bugs": "https://github.com/polyfrost/onelauncher/issues",
|
||||
"sideEffects": false,
|
||||
"scripts": {}
|
||||
"scripts": {
|
||||
"cargo": "cargo",
|
||||
"binary": "cargo run --bin"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,56 @@
|
|||
use crate::store::{IngressProcessType, IngressProcessor};
|
||||
use crate::{store::{ingress::{IngressId, IngressType}, State}, LauncherResult};
|
||||
|
||||
pub async fn check_ingress_feeds() -> crate::Result<bool> {
|
||||
IngressProcessor::finished(IngressProcessType::IngressFeed).await
|
||||
#[tracing::instrument]
|
||||
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)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn send_ingress(id: &IngressId, increment: f64) -> LauncherResult<()> {
|
||||
let state = State::get().await?;
|
||||
let processor = &state.ingress_processor;
|
||||
|
||||
processor.send(id.0, increment).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
#[tokio::test]
|
||||
pub async fn create_and_update_ingress() -> LauncherResult<()> {
|
||||
assert!(cfg!(feature = "cli"));
|
||||
|
||||
use std::time::Duration;
|
||||
use crate::{api::{ingress::{init_ingress, send_ingress}, proxy::proxy_cli::ProxyCli}, initialize_core, store::ingress::IngressType, LauncherResult};
|
||||
|
||||
initialize_core(ProxyCli::new()).await?;
|
||||
|
||||
let id = init_ingress(IngressType::Download { file_name: "Some-Mod-1.8.9.jar".into() }, "This is a test message", 100.0).await?;
|
||||
tokio::time::sleep(Duration::from_millis(2350)).await;
|
||||
|
||||
send_ingress(&id, 10.0).await?;
|
||||
tokio::time::sleep(Duration::from_millis(2500)).await;
|
||||
|
||||
send_ingress(&id, 20.0).await?;
|
||||
tokio::time::sleep(Duration::from_millis(2500)).await;
|
||||
|
||||
send_ingress(&id, 70.0).await?;
|
||||
tokio::time::sleep(Duration::from_millis(2500)).await;
|
||||
|
||||
drop(id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,31 +1,2 @@
|
|||
//! **`OneLauncher` API**
|
||||
//!
|
||||
//! API for interacting with our Rust core.
|
||||
|
||||
pub mod cluster;
|
||||
pub mod handler;
|
||||
pub mod ingress;
|
||||
pub mod java;
|
||||
pub mod metadata;
|
||||
pub mod minecraft;
|
||||
pub mod package;
|
||||
pub mod processor;
|
||||
pub mod proxy;
|
||||
pub mod settings;
|
||||
|
||||
pub mod data {
|
||||
pub use crate::store::{
|
||||
ClusterMeta, Credentials, Directories, InitHooks, JavaOptions, Loader, ManagedDependency,
|
||||
ManagedPackage, ManagedUser, ManagedVersion, Memory, MinecraftCredentials, PackageData,
|
||||
PackageType, Resolution, Settings,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::cluster::{self, create, Cluster};
|
||||
pub use crate::data::*;
|
||||
pub use crate::proxy::InternetPayload;
|
||||
pub use crate::store::{ClusterPath, JavaVersions, PackageDependency, PackagePath};
|
||||
pub use crate::utils::java::JavaVersion;
|
||||
pub use crate::{java, metadata, minecraft, package, processor, settings, State};
|
||||
}
|
||||
pub mod proxy;
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
use serde::Serialize;
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[onelauncher_macro::specta(with_event)]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct MessagePayload {
|
||||
pub level: MessageLevel,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub enum MessageLevel {
|
||||
Info,
|
||||
|
|
@ -1,277 +1,20 @@
|
|||
//! Proxy for Tauri/CLI specific features and communication
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{OnceCell, RwLock};
|
||||
use uuid::Uuid;
|
||||
pub mod message;
|
||||
|
||||
use crate::store::{ClusterPath, IngressProcessor};
|
||||
#[cfg(feature = "tauri")]
|
||||
pub mod proxy_tauri;
|
||||
|
||||
pub mod send;
|
||||
pub mod utils;
|
||||
pub use utils::*;
|
||||
#[cfg(feature = "cli")]
|
||||
pub mod proxy_cli;
|
||||
|
||||
static PROXY_STATE: OnceCell<Arc<ProxyState>> = OnceCell::const_new();
|
||||
pub struct ProxyState {
|
||||
#[cfg(feature = "tauri")]
|
||||
pub app: tauri::AppHandle<tauri::Wry>,
|
||||
pub ingress_feeds: RwLock<HashMap<Uuid, Ingress>>,
|
||||
#[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<()>;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct Ingress {
|
||||
pub ingress_uuid: Uuid,
|
||||
pub message: String,
|
||||
pub ingress_type: IngressType,
|
||||
pub total: f64,
|
||||
pub current: f64,
|
||||
#[serde(skip)]
|
||||
pub last_sent: f64,
|
||||
#[cfg(feature = "cli")]
|
||||
#[serde(skip)]
|
||||
pub cli: indicatif::ProgressBar,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct IngressId(Uuid);
|
||||
|
||||
impl Drop for IngressId {
|
||||
#[clippy::has_significant_drop]
|
||||
fn drop(&mut self) {
|
||||
let ingress_uuid = self.0;
|
||||
tokio::spawn(async move {
|
||||
if let Ok(proxy_state) = ProxyState::get().await {
|
||||
let mut ingress_feeds = proxy_state.ingress_feeds.write().await;
|
||||
#[cfg(any(feature = "tauri", feature = "cli"))]
|
||||
if let Some(ingress) = ingress_feeds.remove(&ingress_uuid) {
|
||||
#[cfg(feature = "cli")]
|
||||
{
|
||||
let cli = ingress.cli;
|
||||
cli.finish();
|
||||
}
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
use tauri::Emitter;
|
||||
let event = ingress.ingress_type.clone();
|
||||
let fraction = ingress.current / ingress.total;
|
||||
|
||||
let _ = proxy_state.app.emit(
|
||||
"ingress",
|
||||
IngressPayload {
|
||||
fraction: None,
|
||||
message: "Complete".to_string(),
|
||||
event,
|
||||
ingress_uuid,
|
||||
},
|
||||
);
|
||||
|
||||
tracing::trace!("exited at {fraction} for ingress: {:?}", ingress_uuid);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "tauri", feature = "cli")))]
|
||||
ingress_feeds.remove(&ingress_uuid);
|
||||
}
|
||||
|
||||
if crate::State::initalized() {
|
||||
let _ = IngressProcessor::finish(
|
||||
crate::store::IngressProcessType::IngressFeed,
|
||||
ingress_uuid,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ProxyState {
|
||||
#[cfg(feature = "tauri")]
|
||||
pub async fn initialize(handle: &tauri::AppHandle<tauri::Wry>) -> crate::Result<Arc<Self>> {
|
||||
PROXY_STATE
|
||||
.get_or_try_init(|| async {
|
||||
Ok(Arc::new(Self {
|
||||
app: handle.clone(),
|
||||
ingress_feeds: RwLock::new(HashMap::new()),
|
||||
}))
|
||||
})
|
||||
.await
|
||||
.cloned()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tauri"))]
|
||||
pub async fn initialize() -> crate::Result<Arc<Self>> {
|
||||
PROXY_STATE
|
||||
.get_or_try_init(|| async {
|
||||
Ok(Arc::new(Self {
|
||||
ingress_feeds: RwLock::new(HashMap::new()),
|
||||
}))
|
||||
})
|
||||
.await
|
||||
.cloned()
|
||||
}
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
pub async fn get_main_window() -> crate::Result<tauri::WebviewWindow> {
|
||||
use tauri::Manager;
|
||||
let value = Self::get().await?;
|
||||
Ok(value.app.get_webview_window("main").unwrap())
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
#[cfg(feature = "tauri")]
|
||||
pub async fn get() -> crate::Result<Arc<Self>> {
|
||||
Ok(PROXY_STATE.get().ok_or(ProxyError::NotInitialized)?.clone())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tauri"))]
|
||||
pub async fn get() -> crate::Result<Arc<Self>> {
|
||||
Self::initialize().await
|
||||
}
|
||||
|
||||
pub async fn list_ingress_feeds() -> crate::Result<HashMap<Uuid, Ingress>> {
|
||||
let value = Self::get().await?;
|
||||
let read = value.ingress_feeds.read().await;
|
||||
|
||||
let mut display_list: HashMap<Uuid, Ingress> = HashMap::new();
|
||||
for (uuid, ingress) in read.iter() {
|
||||
display_list.insert(*uuid, ingress.clone());
|
||||
}
|
||||
|
||||
Ok(display_list)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum IngressType {
|
||||
Initialize,
|
||||
DownloadJava {
|
||||
version: u32,
|
||||
},
|
||||
DownloadLoader {
|
||||
cluster_path: PathBuf,
|
||||
cluster_name: String,
|
||||
},
|
||||
SyncCluster {
|
||||
cluster_path: PathBuf,
|
||||
cluster_name: String,
|
||||
},
|
||||
InferPackages,
|
||||
CopyCluster {
|
||||
import: PathBuf,
|
||||
cluster_name: String,
|
||||
},
|
||||
SyncConfig {
|
||||
new_path: PathBuf,
|
||||
},
|
||||
Archival {
|
||||
cluster_path: PathBuf,
|
||||
cluster_name: String,
|
||||
},
|
||||
DownloadPackage {
|
||||
cluster_path: PathBuf,
|
||||
package_name: String,
|
||||
icon: Option<PathBuf>,
|
||||
package_id: Option<String>,
|
||||
package_version: Option<String>,
|
||||
},
|
||||
DownloadPack {
|
||||
cluster_path: PathBuf,
|
||||
package_name: String,
|
||||
icon: Option<String>,
|
||||
package_version: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct IngressPayload {
|
||||
pub event: IngressType,
|
||||
pub ingress_uuid: Uuid,
|
||||
pub fraction: Option<f64>,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct OfflinePayload {
|
||||
pub offline: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct MessagePayload {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(tag = "event")]
|
||||
pub enum InternetPayload {
|
||||
InstallPackage { id: String },
|
||||
InstallPack { id: String },
|
||||
InstallPath { path: PathBuf },
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct ProcessPayload {
|
||||
pub uuid: Uuid,
|
||||
pub pid: u32,
|
||||
pub event: ProcessPayloadType,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ProcessPayloadType {
|
||||
Started,
|
||||
Modified,
|
||||
Finished,
|
||||
Logging,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct ClusterPayload {
|
||||
pub uuid: Uuid,
|
||||
pub cluster_path: ClusterPath,
|
||||
pub path: PathBuf,
|
||||
pub name: String,
|
||||
pub event: ClusterPayloadType,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ClusterPayloadType {
|
||||
Created,
|
||||
Inserted,
|
||||
Synced,
|
||||
Edited,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ProxyError {
|
||||
#[error("event state was not initialized!")]
|
||||
NotInitialized,
|
||||
|
||||
#[error("non-existant ingress of key: {0}")]
|
||||
NoIngressFound(Uuid),
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
#[error("Tauri error: {0}")]
|
||||
TauriError(#[from] tauri::Error),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use onelauncher_new::store::Dirs;
|
||||
use onelauncher_core::store::Dirs;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
//! **`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.
|
||||
pub const NAME: &str = "OneLauncher";
|
||||
pub const NAME: &str = match option_env!("LAUNCHER_NAME") {
|
||||
Some(name) => name,
|
||||
None => "OneLauncher",
|
||||
};
|
||||
|
||||
/// The version of the launcher (from `../Cargo.toml` env).
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
|
|
@ -38,7 +41,7 @@ pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2";
|
|||
pub const MODRINTH_V3_API_URL: &str = "https://api.modrinth.com/v3";
|
||||
/// The Curseforge API base url.
|
||||
pub const CURSEFORGE_API_URL: &str = "https://api.curseforge.com";
|
||||
/// The Curseforge API key. Reserved for use in OneLauncher projects only. Do not use in other projects without permission from the OneLauncher team.
|
||||
/// The Curseforge API key. Reserved for use in Polyfrost projects only. Do not use in other projects without permission from the Polyfrost team.
|
||||
pub const CURSEFORGE_API_KEY: &str = "$2a$10$6utA1UNSmFPrE/Lh7b7ndeeGmiOkjKNY8kpFB0fsmE/d42ZAfFgCe";
|
||||
/// The Minecraft game ID on Curseforge.
|
||||
pub const CURSEFORGE_GAME_ID: u32 = 432;
|
||||
|
|
@ -48,7 +51,7 @@ pub const METADATA_API_URL: &str = "https://meta.polyfrost.org";
|
|||
pub const FEATURED_PACKAGES_URL: &str = "https://polyfrost.org/meta/onelauncher/featured.json";
|
||||
/// <https://mclo.gs>/ API base url.
|
||||
pub const MCLOGS_API_URL: &str = "https://api.mclo.gs/1";
|
||||
/// https://skyclient.co/ metadata base url.
|
||||
/// <https://skyclient.co/> metadata base url.
|
||||
pub const SKYCLIENT_BASE_URL: &str = "https://raw.githubusercontent.com/SkyblockClient/SkyblockClient-REPO/refs/heads/main/v1";
|
||||
|
||||
// =========== Paths ===========
|
||||
|
|
@ -56,10 +59,6 @@ pub const SKYCLIENT_BASE_URL: &str = "https://raw.githubusercontent.com/Skyblock
|
|||
///
|
||||
/// [`Settings`]: crate::store::Settings
|
||||
pub const SETTINGS_FILE: &str = "settings.json";
|
||||
/// The public `cluster.json` file used to store the global [`Clusters`] state.
|
||||
///
|
||||
/// [`Clusters`]: crate::store::Clusters
|
||||
pub const CLUSTER_FILE: &str = "cluster.json";
|
||||
/// The public `authentication.json` file used to store the global [`MinecraftAuth`] state.
|
||||
///
|
||||
/// [`MinecraftAuth`]: crate::store::MinecraftAuth
|
||||
|
|
@ -75,15 +74,6 @@ pub const PROCESSOR_FILE: &str = "processor.json";
|
|||
/// [`Settings`]: crate::store::Settings
|
||||
pub const CURRENT_SETTINGS_FORMAT_VERSION: u32 = 1;
|
||||
|
||||
/// The constant core caches folder.
|
||||
pub const CACHES_FOLDER: &str = "caches";
|
||||
/// The constant core crash reports folder.
|
||||
pub const CRASH_PATH: &str = "crash-reports";
|
||||
/// The constant core clusters folder.
|
||||
pub const CLUSTERS_FOLDER: &str = "clusters";
|
||||
/// The constant core metadata folder.
|
||||
pub const METADATA_FOLDER: &str = "metadata";
|
||||
|
||||
// =========== Hacky Mojang-spec OS constants ===========
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const TARGET_OS: &str = "windows";
|
||||
|
|
|
|||
|
|
@ -1,180 +1,22 @@
|
|||
//! **`OneLauncher` Errors**
|
||||
//!
|
||||
//! Public wrappers around common `OneLauncher` errors.
|
||||
|
||||
use tracing_error::InstrumentError;
|
||||
|
||||
/// A standardized [`thiserror::Error`] type that is used across the launcher
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ErrorKind {
|
||||
/// Wrapper around [`tokio::sync::AcquireError`] to handle sempahore errors.
|
||||
#[error("failed to acquire a tokio semaphore: {0}")]
|
||||
AcquireError(#[from] tokio::sync::AcquireError),
|
||||
pub enum LauncherError {
|
||||
#[error(transparent)]
|
||||
DirError(#[from] crate::store::DirectoryError),
|
||||
#[error(transparent)]
|
||||
IOError(#[from] crate::utils::io::IOError),
|
||||
#[error(transparent)]
|
||||
IngressError(#[from] crate::store::ingress::IngressError),
|
||||
|
||||
/// Wrapper around [`tokio::sync::oneshot::error::RecvError`] to handle recieve errors.
|
||||
#[error("failed to handle async reciever: {0}")]
|
||||
RecvError(#[from] tokio::sync::oneshot::error::RecvError),
|
||||
|
||||
/// Wrapper around [`tokio::task::JoinError`] to handle tokio join handle errors.
|
||||
#[error("failed to handle an async task: {0}")]
|
||||
JoinError(#[from] tokio::task::JoinError),
|
||||
|
||||
/// Wrapper around [`flate2::CompressError`] to handle flate compression errors.
|
||||
#[error("failed to compress a file with flate: {0}")]
|
||||
FlateError(#[from] flate2::CompressError),
|
||||
|
||||
/// Wrapper around [`flate2::DecompressError`] to handle flate decompression errors.
|
||||
#[error("failed to decompress a file with flate: {0}")]
|
||||
DeflateError(#[from] flate2::DecompressError),
|
||||
|
||||
/// Wrapper around [`uuid::Error`] to handle UUID parsing errors.
|
||||
#[error("failed to parse and handle uuids: {0}")]
|
||||
UUIDError(#[from] uuid::Error),
|
||||
|
||||
/// Wrapper around [`interpulse::Error`] to handle interpulse errors.
|
||||
#[error("failed to parse and handle metadata: {0}")]
|
||||
MetadataError(#[from] interpulse::Error),
|
||||
|
||||
/// Wrapper around [`serde_json::Error`] to handle Serde JSON parsing errors.
|
||||
#[error("failed to manage serialization of json: {0}")]
|
||||
#[error(transparent)]
|
||||
SerdeError(#[from] serde_json::Error),
|
||||
|
||||
/// Wrapper around [`serde_ini::de::Error`] to handle Serde INI parsing errors.
|
||||
#[error("failed to manage deserialization of ini: {0}")]
|
||||
INIError(#[from] serde_ini::de::Error),
|
||||
|
||||
/// Wrapper around [`reqwest::Error`] to handle HTTP errors.
|
||||
#[error("failed to establish an http connection: {0}")]
|
||||
HTTPError(#[from] reqwest::Error),
|
||||
|
||||
/// Wrapper around [`zip::result::ZipError`] to handle zip errors.
|
||||
#[error("failed to manage zip files: {0}")]
|
||||
ZipError(#[from] zip::result::ZipError),
|
||||
|
||||
/// Wrapper around [`async_zip::error::ZipError`] to handle async zip errors.
|
||||
#[error("failed to manage zip files asynchronously: {0}")]
|
||||
AsyncZipError(#[from] async_zip::error::ZipError),
|
||||
|
||||
/// Wrapper around [`anyhow::Error`] to handle generic errors.
|
||||
#[error(transparent)]
|
||||
AnyhowError(#[from] anyhow::Error),
|
||||
#[error(transparent)]
|
||||
DbError(#[from] sea_orm::DbErr),
|
||||
|
||||
/// Wrapper around [`chrono::ParseError`] to handle date parsing errors.
|
||||
#[error("failed to parse and handle datetime: {0}")]
|
||||
ChronoError(#[from] chrono::ParseError),
|
||||
|
||||
/// Wrapper around [`crate::state::DirectoryError`] to handle directory errors.
|
||||
#[error("failed to get directory: {0}")]
|
||||
DirectoryError(#[from] crate::store::DirectoryError),
|
||||
|
||||
/// Wrapper around [`crate::api::proxy::ProxyError`] to handle ingress errors.
|
||||
#[error("failed to handle api communication: {0}")]
|
||||
ProxyError(#[from] crate::api::proxy::ProxyError),
|
||||
|
||||
/// Wrapper around [`crate::store::MinecraftAuthError`] to handle Minecraft auth errors.
|
||||
#[error("failed to authenticate with microsoft: {0}")]
|
||||
AuthError(#[from] crate::store::MinecraftAuthError),
|
||||
|
||||
/// Wrapper around [`crate::api::cluster::create::CreateClusterError`] to handle Cluster creation errors.
|
||||
#[error("failed to create clusters: {0}")]
|
||||
CreateClusterError(#[from] crate::api::cluster::create::CreateClusterError),
|
||||
|
||||
/// Wrapper around [`notify:Error`] to handle file watching errors.
|
||||
#[error("failed to watch file {0}")]
|
||||
NotifyError(#[from] notify::Error),
|
||||
|
||||
/// Wrapper around [`regex::Error`] to handle `RegExp` errors.
|
||||
#[error("string verification with regex failed: {0}")]
|
||||
RegexError(#[from] regex::Error),
|
||||
|
||||
/// Wrapper around [`reqwest::header::ToStrError`] to handle header conversion errors.
|
||||
#[error("failed to convert header to string: {0}")]
|
||||
HeaderToStrError(#[from] reqwest::header::ToStrError),
|
||||
|
||||
/// Wrapper around [`async_tungstenite::tungstenite::Error`] to handle WebSocket errors.
|
||||
#[error("failed to handle websocket connection: {0}")]
|
||||
WebSocketError(#[from] async_tungstenite::tungstenite::Error),
|
||||
|
||||
/// Indicates an error with SHA checksum.
|
||||
#[error("checksum {0} did not match {1}!")]
|
||||
HashError(String, String),
|
||||
|
||||
/// Wrapper around [`onelauncher_utils::io::IOError`] to handle wrapped `std::io` errors.
|
||||
#[error("error handling IO operations: {0}")]
|
||||
IOError(#[from] onelauncher_utils::io::IOError),
|
||||
|
||||
/// Wrapper around [`std::io::Error`] to handle non-wrapped `std::io` errors.
|
||||
#[error("error handling standard IO operations: {0}")]
|
||||
StdIOError(#[from] std::io::Error),
|
||||
|
||||
/// Wrapper around [`crate::utils::java::JavaError`] to handle java errors.
|
||||
#[error("failed to manage java installations: {0}")]
|
||||
JavaError(#[from] crate::utils::java::JavaError),
|
||||
|
||||
/// Wrapper around [`crate::store::StrongholdError`] to handle stronghold errors.
|
||||
#[error("failed to manage credentials with IOTA stronghold: {0}")]
|
||||
StrongholdError(#[from] crate::store::StrongholdError),
|
||||
|
||||
/// Wrapper around [`url::ParseError`] to handle URL parsing errors
|
||||
#[error("failed to parse and handle URLs: {0}")]
|
||||
URLError(#[from] url::ParseError),
|
||||
|
||||
/// Wrapper around [`tauri::Error`] to handle Tauri errors when the feature flag is enabled
|
||||
#[cfg(feature = "tauri")]
|
||||
#[error("error handling tauri: {0}")]
|
||||
#[error(transparent)]
|
||||
TauriError(#[from] tauri::Error),
|
||||
}
|
||||
|
||||
/// A safe, accessible error structure wrapper around [`ErrorKind`] for use with [`tracing_error`].
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
/// The raw, inner [`ErrorKind`], wrapped in an [`Arc`].
|
||||
pub raw: std::sync::Arc<ErrorKind>,
|
||||
/// The traced [`ErrorKind`] ([`tracing_error`]) to be parsed and logged.
|
||||
pub source: tracing_error::TracedError<std::sync::Arc<ErrorKind>>,
|
||||
}
|
||||
|
||||
impl From<Error> for String {
|
||||
/// Converts an [`Error`] into a [`String`] value.
|
||||
fn from(value: Error) -> Self {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
/// Get the source for the inner traced [`Error`].
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
self.source.source()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
/// Format the [`Error`] and output it into the formatter buffer.
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(fmt, "{}", self.source)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Into<ErrorKind>> From<E> for Error {
|
||||
/// Converts an [`ErrorKind`] error into a traced [`Error`].
|
||||
/// Useful for reducing error mapping and standardizing error types.
|
||||
fn from(source: E) -> Self {
|
||||
let error = Into::<ErrorKind>::into(source);
|
||||
let boxed_error = std::sync::Arc::new(error);
|
||||
|
||||
Self {
|
||||
raw: boxed_error.clone(),
|
||||
source: boxed_error.in_current_span(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorKind {
|
||||
/// Converts the current [`ErrorKind`] into a traced [`Error`].
|
||||
pub fn as_error(self) -> Error {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Alias for a [`core::result::Result`] with the error type [`Error`].
|
||||
pub type Result<T> = anyhow::Result<T, Error>;
|
||||
pub type LauncherResult<T> = Result<T, LauncherError>;
|
||||
|
|
@ -1,20 +1,25 @@
|
|||
#![allow(clippy::all)]
|
||||
//! # `OneLauncher`
|
||||
//!
|
||||
//! A library used as a core for our launcher and Rust APIs.
|
||||
use api::proxy::LauncherProxy;
|
||||
use store::{proxy::ProxyState, Dirs, State};
|
||||
use error::LauncherResult;
|
||||
use logger::start_logger;
|
||||
|
||||
#[macro_use]
|
||||
pub mod utils;
|
||||
|
||||
pub mod api;
|
||||
pub mod constants;
|
||||
pub mod error;
|
||||
pub mod game;
|
||||
pub mod logger;
|
||||
pub mod utils;
|
||||
pub mod store;
|
||||
pub mod api;
|
||||
pub mod error;
|
||||
|
||||
pub use api::proxy::{Ingress, IngressType, ProxyState};
|
||||
pub use api::*;
|
||||
pub use error::*;
|
||||
pub use logger::start_logger;
|
||||
pub use store::{InnerPathLinux, State};
|
||||
mod logger;
|
||||
|
||||
pub use onelauncher_entity as entity;
|
||||
pub use onelauncher_migration as migration;
|
||||
|
||||
pub async fn initialize_core(proxy_backend: impl LauncherProxy + 'static) -> LauncherResult<()> {
|
||||
Dirs::get().await?;
|
||||
start_logger().await;
|
||||
|
||||
ProxyState::initialize(proxy_backend).await?;
|
||||
State::get().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,74 +1,72 @@
|
|||
//! **`OneLauncher` Logger**
|
||||
//!
|
||||
//! Public utilities for [`tracing`] logging in `OneLauncher`.
|
||||
//!
|
||||
//! - `RUST_LOG=xxx` is a variable which defines tracing configuration (`ERROR > WARN > INFO > DEBUG > TRACE`)
|
||||
//! - `RUST_LOG="onelauncher=trace"` will show **all** mesages from `OneLauncher` and not other dependencies.
|
||||
//! - `RUST_LOG="info"` will show info, warn, and error logs.
|
||||
//!
|
||||
//! Errors returned to [`tauri`] are displayed as tracing error logs.
|
||||
//! These errors also include an attached span trace if the error is from a [`tracing_error`] and the level is info, debug, or trace.
|
||||
//! Tracing errors also provide Sentry metadata to be handled.
|
||||
//! To set the environment variable on `unix` you can use the following command: `RUST_LOG="xxx" {your command}`
|
||||
//!
|
||||
//! The default `RUST_LOG` is `onelauncher=show`, meaning only logs from `OneLauncher` will be displayed at the info, warn, and error levels.
|
||||
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
|
||||
// TODO: refactor logging and add more env filters.
|
||||
|
||||
/// Initialize the global [`tracing`] logger in development.
|
||||
// Handling for the live development logging
|
||||
// This will log to the console, and will not log to a file
|
||||
#[cfg(debug_assertions)]
|
||||
#[must_use]
|
||||
pub fn start_logger() -> Option<WorkerGuard> {
|
||||
use tracing_subscriber::prelude::*;
|
||||
pub async fn start_logger() {
|
||||
use tracing_subscriber::{prelude::*, util::SubscriberInitExt};
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
tracing_subscriber::EnvFilter::new("onelauncher=info,onelauncher_gui=info")
|
||||
});
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(format!("{}=info", env!("CARGO_PKG_NAME"))));
|
||||
|
||||
let subscriber = tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default());
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Initialize the global [`tracing`] logger in production.
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub fn start_logger() -> Option<WorkerGuard> {
|
||||
use crate::store::Directories;
|
||||
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||
use tracing_subscriber::fmt::time::ChronoLocal;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
let logs_dir = if let Some(directory) = Directories::logs_dir() {
|
||||
directory
|
||||
} else {
|
||||
eprintln!("failed to start logger");
|
||||
return None;
|
||||
};
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("onelauncher=info"));
|
||||
|
||||
let file_appender = RollingFileAppender::new(Rotation::DAILY, logs_dir, "onelauncher.log");
|
||||
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
|
||||
|
||||
let subscriber = tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_writer(non_blocking)
|
||||
.with_ansi(false)
|
||||
.with_timer(ChronoLocal::rfc_3339()),
|
||||
.with_ansi(true)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_level(true)
|
||||
.with_thread_names(true)
|
||||
.pretty()
|
||||
)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default());
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
||||
|
||||
Some(guard)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default())
|
||||
.init();
|
||||
}
|
||||
|
||||
// Handling for the live production logging
|
||||
// This will log to a file in the logs directory, and will not show any logs in the console
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub async fn start_logger() {
|
||||
use chrono::Local;
|
||||
use std::fs::OpenOptions;
|
||||
use tracing_subscriber::fmt::time::ChronoLocal;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
use crate::io::Dirs;
|
||||
|
||||
// Initialize and get logs directory path
|
||||
let logs_dir = Dirs::get().await?.launcher_logs_dir();
|
||||
|
||||
let log_file_name = format!("launcher_{}.log", Local::now().format("%Y%m%d_%H%M%S"));
|
||||
let log_file_path = logs_dir.join(log_file_name);
|
||||
|
||||
if let Err(err) = std::fs::create_dir_all(&logs_dir) {
|
||||
eprintln!("Could not create logs directory: {err}");
|
||||
}
|
||||
|
||||
let file = match OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file_path)
|
||||
{
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
eprintln!("Could not start open log file: {e}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(format!("{}=info", env!("CARGO_PKG_NAME"))));
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.compact()
|
||||
.with_writer(file)
|
||||
.with_ansi(false)
|
||||
.with_timer(ChronoLocal::rfc_3339()),
|
||||
)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default())
|
||||
.init();
|
||||
}
|
||||
|
|
|
|||
30
packages/core/src/store/db.rs
Normal file
30
packages/core/src/store/db.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use std::{path::PathBuf, time::Duration};
|
||||
|
||||
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
|
||||
|
||||
use crate::{utils::io::IOError, LauncherResult};
|
||||
|
||||
use super::Dirs;
|
||||
|
||||
pub async fn create_pool_from_path(path: PathBuf) -> LauncherResult<DatabaseConnection> {
|
||||
if !path.is_absolute() {
|
||||
return Err(IOError::InvalidAbsolutePath(path).into());
|
||||
}
|
||||
|
||||
let address = format!("sqlite:/{}?mode=rwc", path.to_string_lossy());
|
||||
let mut opts = ConnectOptions::new(address);
|
||||
opts.min_connections(5)
|
||||
.max_connections(40)
|
||||
.connect_timeout(Duration::from_secs(5))
|
||||
.idle_timeout(Duration::from_secs(6 * 60 * 60 /* 6 hours */));
|
||||
|
||||
let db = Database::connect(opts).await?;
|
||||
|
||||
Ok(db)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn create_pool() -> LauncherResult<DatabaseConnection> {
|
||||
let path = Dirs::get().await?.db_file();
|
||||
create_pool_from_path(path).await
|
||||
}
|
||||
|
|
@ -1,191 +1,96 @@
|
|||
//! Handles core directory management
|
||||
use std::path::PathBuf;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use super::Settings;
|
||||
use crate::constants;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
/// Directory management and utilities.
|
||||
#[derive(Debug)]
|
||||
pub struct Directories {
|
||||
/// Settings: All small caches and the core settings.json
|
||||
pub settings_dir: PathBuf,
|
||||
/// Base Configuration: Clusters and all Assets (should be changable as a setting that can go across file systems)
|
||||
/// By default this should be the same as the `settings_dir`
|
||||
pub config_dir: RwLock<PathBuf>,
|
||||
/// The current working directory of the directory manager.
|
||||
pub cwd: PathBuf,
|
||||
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)
|
||||
static DIRS_GLOBAL: OnceCell<Dirs> = OnceCell::const_new();
|
||||
|
||||
pub struct Dirs {
|
||||
base_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl Directories {
|
||||
/// Bootstrap the initial settings directory on first startup.
|
||||
#[must_use]
|
||||
pub fn init_settings_dir() -> Option<PathBuf> {
|
||||
Self::env_path("ONELAUNCHER_CONFIG")
|
||||
impl Dirs {
|
||||
pub async fn get() -> LauncherResult<&'static Self> {
|
||||
DIRS_GLOBAL.get_or_try_init(async || {
|
||||
Self::initialize()
|
||||
}).await
|
||||
}
|
||||
|
||||
fn initialize() -> LauncherResult<Self> {
|
||||
let base_dir = utils::io::env_path("LAUNCHER_DIR")
|
||||
.or_else(|| Some(dirs::data_dir()?.join(constants::NAME)))
|
||||
}
|
||||
.ok_or(DirectoryError::BaseDir)?;
|
||||
|
||||
/// Bootstrap the initial settings directory on first startup.
|
||||
#[inline]
|
||||
pub fn init_settings_file() -> crate::Result<PathBuf> {
|
||||
let settings_dir = Self::init_settings_dir().ok_or(DirectoryError::ConfigDir)?;
|
||||
Ok(settings_dir.join("settings.json"))
|
||||
}
|
||||
|
||||
/// Initialize the core directory manager
|
||||
#[tracing::instrument]
|
||||
pub fn initalize(settings: &Settings) -> crate::Result<Self> {
|
||||
let cwd = std::env::current_dir().map_err(DirectoryError::WorkingDir)?;
|
||||
let settings_dir = Self::init_settings_dir().ok_or(DirectoryError::ConfigDir)?;
|
||||
let config_dir = settings
|
||||
.config_dir
|
||||
.clone()
|
||||
.ok_or(DirectoryError::ConfigDir)?;
|
||||
Ok(Self {
|
||||
settings_dir,
|
||||
config_dir: RwLock::new(config_dir),
|
||||
cwd,
|
||||
base_dir
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the base directory for the launcher.
|
||||
#[must_use]
|
||||
pub const fn base_dir(&self) -> &PathBuf {
|
||||
&self.base_dir
|
||||
}
|
||||
|
||||
/// Get the launcher's logs directory.
|
||||
#[must_use]
|
||||
pub fn launcher_logs_dir(&self) -> PathBuf {
|
||||
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]
|
||||
pub async fn metadata_dir(&self) -> PathBuf {
|
||||
self.config_dir
|
||||
.read()
|
||||
.await
|
||||
.join(constants::METADATA_FOLDER)
|
||||
#[must_use]
|
||||
pub fn metadata_dir(&self) -> PathBuf {
|
||||
self.base_dir().join("metadata")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/libraries` folder for Minecraft libraries.
|
||||
#[inline]
|
||||
pub async fn libraries_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("libraries")
|
||||
#[must_use]
|
||||
pub fn libraries_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("libraries")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/natives` folder for Minecraft natives.
|
||||
#[must_use]
|
||||
pub fn natives_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("natives")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/java` directory.
|
||||
#[inline]
|
||||
pub async fn java_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("java_versions")
|
||||
#[must_use]
|
||||
pub fn java_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("java")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/caches` directory.
|
||||
#[inline]
|
||||
pub async fn caches_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("caches")
|
||||
}
|
||||
|
||||
/// Bootstrap the core logs directory and get it as a [`PathBuf`].
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn logs_dir() -> Option<PathBuf> {
|
||||
Self::init_settings_dir().map(|d| d.join("logs"))
|
||||
}
|
||||
|
||||
/// Get the `settings_dir/settings.json` file as a [`PathBuf`].
|
||||
#[inline]
|
||||
pub fn settings_file(&self) -> PathBuf {
|
||||
self.settings_dir.join(constants::SETTINGS_FILE)
|
||||
}
|
||||
|
||||
/// Get a [`PathBuf`] from a provided environment variable.
|
||||
#[inline]
|
||||
fn env_path(name: &str) -> Option<PathBuf> {
|
||||
std::env::var_os(name).map(PathBuf::from)
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/assets` directory.
|
||||
#[inline]
|
||||
pub async fn assets_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("assets")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/assets/indexes` directory.
|
||||
#[inline]
|
||||
pub async fn index_dir(&self) -> PathBuf {
|
||||
self.assets_dir().await.join("indexes")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/assets/objects` directory.
|
||||
#[inline]
|
||||
pub async fn objects_dir(&self) -> PathBuf {
|
||||
self.assets_dir().await.join("objects")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/assets/objects/{hash}` directory.
|
||||
#[inline]
|
||||
pub async fn object_dir(&self, hash: &str) -> PathBuf {
|
||||
self.objects_dir().await.join(&hash[..2]).join(hash)
|
||||
}
|
||||
|
||||
/// Get the `config_dir/clusters` directory.
|
||||
#[inline]
|
||||
pub async fn clusters_dir(&self) -> PathBuf {
|
||||
self.config_dir.read().await.join("clusters")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/clusters/{uuid}` directory.
|
||||
#[inline]
|
||||
pub async fn cluster_dir(&self, uuid: uuid::Uuid) -> PathBuf {
|
||||
self.clusters_dir().await.join(uuid.to_string())
|
||||
}
|
||||
|
||||
/// Get the `{cluster_path}/logs` directory.
|
||||
#[inline]
|
||||
pub async fn cluster_logs_dir(cluster_path: &super::ClusterPath) -> crate::Result<PathBuf> {
|
||||
Ok(cluster_path.full_path().await?.join("logs"))
|
||||
}
|
||||
|
||||
/// Get the `{cluster_path}/crash_reports` directory.
|
||||
#[inline]
|
||||
pub async fn crash_reports_dir(cluster_path: &super::ClusterPath) -> crate::Result<PathBuf> {
|
||||
Ok(cluster_path.full_path().await?.join("crash-reports"))
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/resources` directory.
|
||||
#[inline]
|
||||
pub async fn legacy_assets_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("resources")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/natives` directory.
|
||||
#[inline]
|
||||
pub async fn natives_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("natives")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/natives/{version}` directory.
|
||||
#[inline]
|
||||
pub async fn version_natives_dir(&self, version: &str) -> PathBuf {
|
||||
self.natives_dir().await.join(version)
|
||||
}
|
||||
|
||||
/// Get the Minecraft `config_dir/metadata/versions` directory.
|
||||
#[inline]
|
||||
pub async fn versions_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("versions")
|
||||
}
|
||||
|
||||
/// Get the Minecraft `config_dir/metadata/versions/{version}` directory.
|
||||
#[inline]
|
||||
pub async fn version_dir(&self, version: &str) -> PathBuf {
|
||||
self.versions_dir().await.join(version)
|
||||
}
|
||||
|
||||
/// Get the `config_dir/icons` directory.
|
||||
#[inline]
|
||||
pub async fn icons_dir(&self) -> PathBuf {
|
||||
self.config_dir.read().await.join("icons")
|
||||
pub fn caches_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("caches")
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a core directory management error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DirectoryError {
|
||||
/// An error for when a config directory cannot be initialized.
|
||||
#[error("failed to get the user's config directory")]
|
||||
ConfigDir,
|
||||
/// A wrapper over [`std::io::Error`] for cwd fetching errors.
|
||||
#[error("could not open working directory {0}")]
|
||||
WorkingDir(std::io::Error),
|
||||
#[error("Failed to get the base directory for the launcher.")]
|
||||
BaseDir,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,8 +93,7 @@ pub struct Ingress {
|
|||
pub last_sent: f64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[onelauncher_macro::specta(with_event)]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct IngressPayload {
|
||||
pub id: Uuid,
|
||||
|
|
@ -104,7 +103,7 @@ pub struct IngressPayload {
|
|||
pub total: f64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub enum IngressType {
|
||||
Download {
|
||||
14
packages/core/src/store/java.rs
Normal file
14
packages/core/src/store/java.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::LauncherResult;
|
||||
|
||||
// pub async fn get_java_versions() -> LauncherResult<Vec<JavaVersion>> {
|
||||
// // let java_versions = sqlx::query_as!(
|
||||
// // JavaVersion,
|
||||
// // r#"
|
||||
// // SELECT id, major_version, display_name, absolute_path, full_version FROM java_versions
|
||||
// // "#
|
||||
// // );
|
||||
|
||||
// Ok(vec![])
|
||||
// }
|
||||
|
|
@ -1,289 +1,11 @@
|
|||
//! **`OneLauncher` Store**
|
||||
//!
|
||||
//! Core state and storage management for the launcher, managing all states.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use notify::RecommendedWatcher;
|
||||
use notify_debouncer_mini::Debouncer;
|
||||
use tokio::sync::{OnceCell, RwLock, Semaphore};
|
||||
|
||||
use crate::proxy::send::{init_ingress_internal, send_ingress, send_offline};
|
||||
use crate::utils::http;
|
||||
use crate::utils::http::{FetchSemaphore, IoSemaphore};
|
||||
|
||||
pub(super) mod db;
|
||||
pub mod ingress;
|
||||
pub mod proxy;
|
||||
pub mod java;
|
||||
mod dirs;
|
||||
pub use self::dirs::*;
|
||||
|
||||
mod minecraft;
|
||||
pub use self::minecraft::*;
|
||||
|
||||
mod credentials;
|
||||
pub use self::credentials::*;
|
||||
|
||||
mod discord;
|
||||
pub use self::discord::*;
|
||||
|
||||
mod metadata;
|
||||
pub use self::metadata::*;
|
||||
|
||||
mod settings;
|
||||
pub use self::settings::*;
|
||||
mod state;
|
||||
|
||||
mod java_versions;
|
||||
pub use self::java_versions::*;
|
||||
|
||||
mod processor;
|
||||
pub use self::processor::*;
|
||||
|
||||
mod clusters;
|
||||
pub use self::clusters::*;
|
||||
|
||||
mod package;
|
||||
pub use self::package::*;
|
||||
|
||||
mod oneconfig;
|
||||
pub use self::oneconfig::*;
|
||||
|
||||
/// The static [`OnceCell<RwLock<State>>`] for storing the global runtime launcher state.
|
||||
static LAUNCHER_STATE: OnceCell<RwLock<State>> = OnceCell::const_new();
|
||||
|
||||
/// The public `OneLauncher` state structure.
|
||||
pub struct State {
|
||||
/// Handles a boolean to check if the launcher is connected to the internet
|
||||
pub offline: RwLock<bool>,
|
||||
/// Handles core launcher directory and files
|
||||
pub directories: Directories,
|
||||
|
||||
/// Semaphore used to handle concurrent network requests
|
||||
pub fetch_semaphore: FetchSemaphore,
|
||||
/// Maximum number of semaphore permits to limit the amount of network requests at one time
|
||||
pub fetch_semaphore_max: RwLock<u32>,
|
||||
/// Semaphore used to handle concurrent IO requests
|
||||
pub io_semaphore: IoSemaphore,
|
||||
/// Maximum number of semaphore permits to limit the amount of IO requests at one time
|
||||
pub io_semaphore_max: RwLock<u32>,
|
||||
|
||||
/// Handles launcher metadata via [`interpulse`]
|
||||
pub metadata: RwLock<Metadata>,
|
||||
/// Handles launcher configuration and settings
|
||||
pub settings: RwLock<Settings>,
|
||||
/// Handles minecraft auth flows and users
|
||||
pub users: RwLock<MinecraftState>,
|
||||
/// Handles processes
|
||||
pub processor: RwLock<Processor>,
|
||||
/// Handles `OneConfig` integration with [`async-tungstenite`]
|
||||
pub oneconfig: RwLock<Option<OneConfig>>,
|
||||
/// Handles secure credential management with [`iota_stronghold`].
|
||||
pub credentials: RwLock<Option<Credentials>>,
|
||||
/// Handles clusters
|
||||
pub(crate) clusters: RwLock<Clusters>,
|
||||
/// Handles packages
|
||||
pub(crate) packages: RwLock<Packages>,
|
||||
/// Handles internal ingress processes
|
||||
pub(crate) ingress_processor: RwLock<IngressProcessor>,
|
||||
/// Handles file system watching for cluster manager
|
||||
pub(crate) watcher: RwLock<Debouncer<RecommendedWatcher>>,
|
||||
/// Handles Discord rich prescence
|
||||
pub discord_rpc: DiscordRPC,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Get the current global launcher state (or initialize it)
|
||||
pub async fn get() -> crate::Result<Arc<tokio::sync::RwLockReadGuard<'static, Self>>> {
|
||||
Ok(Arc::new(
|
||||
LAUNCHER_STATE
|
||||
.get_or_try_init(Self::initialize)
|
||||
.await?
|
||||
.read()
|
||||
.await,
|
||||
))
|
||||
}
|
||||
|
||||
/// Get and lock writing to the current global launcher state (or initalize it)
|
||||
pub async fn get_and_write() -> crate::Result<tokio::sync::RwLockWriteGuard<'static, Self>> {
|
||||
Ok(LAUNCHER_STATE
|
||||
.get_or_try_init(Self::initialize)
|
||||
.await?
|
||||
.write()
|
||||
.await)
|
||||
}
|
||||
|
||||
/// Initializes the core OneLauncher state fully.
|
||||
#[tracing::instrument]
|
||||
#[onelauncher_macros::memory]
|
||||
async fn initialize() -> crate::Result<RwLock<Self>> {
|
||||
let ingress =
|
||||
init_ingress_internal(crate::IngressType::Initialize, 100.0, "initializing state")
|
||||
.await?;
|
||||
|
||||
let settings = Settings::initialize(&Directories::init_settings_file()?).await?;
|
||||
let directories = Directories::initalize(&settings)?;
|
||||
send_ingress(&ingress, 10.0, None).await?;
|
||||
let mut watcher = crate::utils::watcher::initialize_watcher()?;
|
||||
let fetch_semaphore =
|
||||
FetchSemaphore(RwLock::new(Semaphore::new(settings.max_async_fetches)));
|
||||
let io_semaphore = IoSemaphore(RwLock::new(Semaphore::new(
|
||||
settings.max_async_io_operations,
|
||||
)));
|
||||
send_ingress(&ingress, 10.0, None).await?;
|
||||
|
||||
let is_offline = !http::check_internet_connection(3).await;
|
||||
|
||||
// TODO: Make this run in the background, this delays launcher startup by a couple seconds
|
||||
let metadata_in =
|
||||
Metadata::initialize(&directories, !is_offline, &io_semaphore, &fetch_semaphore);
|
||||
let clusters_in = Clusters::initialize(&directories, &mut watcher);
|
||||
let users_in = MinecraftState::initialize(&directories, &io_semaphore);
|
||||
let (metadata, clusters, users) = crate::ingress_join! {
|
||||
Some(&ingress), 70.0, Some("loading core");
|
||||
metadata_in,
|
||||
clusters_in,
|
||||
users_in,
|
||||
}?;
|
||||
|
||||
send_ingress(&ingress, 5.0, Some("loading packages")).await?;
|
||||
let packages = Packages::initialize(&clusters);
|
||||
|
||||
let ingress_processor = IngressProcessor::new();
|
||||
let discord_rpc = DiscordRPC::initialize(is_offline || settings.disable_discord)?;
|
||||
if !settings.disable_discord && !is_offline {
|
||||
let _ = discord_rpc.apply_activity("Idling...", true).await;
|
||||
}
|
||||
|
||||
let processor = Processor::new();
|
||||
|
||||
Self::status_loop();
|
||||
send_ingress(&ingress, 10.0, None).await?;
|
||||
|
||||
Ok::<RwLock<Self>, crate::Error>(RwLock::new(Self {
|
||||
offline: RwLock::new(is_offline),
|
||||
directories,
|
||||
fetch_semaphore,
|
||||
fetch_semaphore_max: RwLock::new(settings.max_async_fetches as u32),
|
||||
io_semaphore,
|
||||
io_semaphore_max: RwLock::new(settings.max_async_io_operations as u32),
|
||||
metadata: RwLock::new(metadata),
|
||||
settings: RwLock::new(settings),
|
||||
users: RwLock::new(users),
|
||||
processor: RwLock::new(processor),
|
||||
oneconfig: RwLock::new(None),
|
||||
credentials: RwLock::new(None),
|
||||
clusters: RwLock::new(clusters),
|
||||
packages: RwLock::new(packages),
|
||||
ingress_processor: RwLock::new(ingress_processor),
|
||||
watcher: RwLock::new(watcher),
|
||||
discord_rpc,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Initalizes a "game loop" which checks for updates and if we are online.
|
||||
pub fn status_loop() {
|
||||
tokio::task::spawn(async {
|
||||
loop {
|
||||
let state = Self::get().await;
|
||||
if let Ok(state) = state {
|
||||
let _ = state.check_status().await;
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Updates all data if we are connected to the internet.
|
||||
pub fn update() {
|
||||
tokio::task::spawn(async {
|
||||
let state = Self::get();
|
||||
if let Ok(state) = state.await {
|
||||
if !*state.offline.read().await {
|
||||
let version_up = Clusters::update_versions();
|
||||
let meta_up = Metadata::update();
|
||||
let package_up = Clusters::update_packages();
|
||||
|
||||
let _ = tokio::join!(version_up, meta_up, package_up);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Synchronizes data that can change outside of our control.
|
||||
#[tracing::instrument]
|
||||
#[onelauncher_macros::memory]
|
||||
pub async fn sync() -> crate::Result<()> {
|
||||
let state = Self::get().await?;
|
||||
let sync_settings = async {
|
||||
let state = Arc::clone(&state);
|
||||
tokio::spawn(async move {
|
||||
let reader = state.settings.read().await;
|
||||
reader.sync(&state.directories.settings_file()).await?;
|
||||
Ok::<_, crate::Error>(())
|
||||
})
|
||||
.await?
|
||||
};
|
||||
|
||||
let sync_clusters = async {
|
||||
let state = Arc::clone(&state);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let clusters = state.clusters.read().await;
|
||||
|
||||
clusters.sync().await?;
|
||||
Ok::<_, crate::Error>(())
|
||||
})
|
||||
.await?
|
||||
};
|
||||
|
||||
tokio::try_join!(sync_settings, sync_clusters)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resets the IO [`Semaphore`] to clear all tasks.
|
||||
/// # This should ONLY be called when we can ensure
|
||||
/// # that there are no current tasks running!!
|
||||
pub async fn reset_io_semaphore(&self) {
|
||||
let settings = self.settings.read().await;
|
||||
let mut io_semaphore = self.io_semaphore.0.write().await;
|
||||
let mut total_permits = self.io_semaphore_max.write().await;
|
||||
// block ALL semaphore permits - this can be destructive
|
||||
let _ = io_semaphore.acquire_many(*total_permits).await;
|
||||
|
||||
io_semaphore.close();
|
||||
*total_permits = settings.max_async_io_operations as u32;
|
||||
*io_semaphore = Semaphore::new(settings.max_async_io_operations);
|
||||
}
|
||||
|
||||
/// Resets the Fetch [`Semaphore`] to clear all tasks.
|
||||
/// # This should ONLY be called when we can ensure
|
||||
/// # that there are no current tasks running!!
|
||||
pub async fn reset_fetch_semaphore(&self) {
|
||||
let settings = self.settings.read().await;
|
||||
let mut fetch_semaphore = self.fetch_semaphore.0.write().await;
|
||||
let mut total_permits = self.fetch_semaphore_max.write().await;
|
||||
// block ALL semaphore permits - this can be destructive
|
||||
let _ = fetch_semaphore.acquire_many(*total_permits).await;
|
||||
|
||||
fetch_semaphore.close();
|
||||
*total_permits = settings.max_async_io_operations as u32;
|
||||
*fetch_semaphore = Semaphore::new(settings.max_async_fetches);
|
||||
}
|
||||
|
||||
/// Checks if we are online used in a loop.
|
||||
pub async fn check_status(&self) -> crate::Result<()> {
|
||||
let is_online = http::check_internet_connection(3).await;
|
||||
let mut offline = self.offline.write().await;
|
||||
if *offline != is_online {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
send_offline(!is_online).await?;
|
||||
*offline = !is_online;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determine if the global state is initalized or not (wrapper)
|
||||
pub fn initalized() -> bool {
|
||||
LAUNCHER_STATE.initialized()
|
||||
}
|
||||
}
|
||||
pub use dirs::*;
|
||||
pub use settings::*;
|
||||
pub use state::*;
|
||||
|
|
@ -1,209 +1,72 @@
|
|||
//! Handles user-inputted settings and global values
|
||||
|
||||
use super::{Directories, JavaVersions};
|
||||
use crate::constants::CURRENT_SETTINGS_FORMAT_VERSION;
|
||||
use onelauncher_utils::io;
|
||||
use onelauncher_entity::setting_profiles;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// A global settings state for the launcher.
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
use crate::{send_warning, utils::io, LauncherResult};
|
||||
|
||||
use super::Dirs;
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(default)]
|
||||
pub struct Settings {
|
||||
/// A `OneLauncher` theme managed by the `OneLauncher` GUI.
|
||||
#[serde(default = "theme_default")]
|
||||
pub theme: String,
|
||||
/// A global browser list view for the `OneLauncher` GUI.
|
||||
#[serde(default)]
|
||||
pub browser_list_view: BrowserListView,
|
||||
/// Does not ask for confirmation when closing the `OneLauncher` GUI
|
||||
#[serde(default)]
|
||||
pub hide_close_prompt: bool,
|
||||
/// Disables animations in the `OneLauncher` GUI
|
||||
#[serde(default)]
|
||||
pub disable_animations: bool,
|
||||
/// A global fullscreen Minecraft state.
|
||||
#[serde(default)]
|
||||
pub force_fullscreen: bool,
|
||||
/// Whether to allow launching the same cluster under the same account.
|
||||
#[serde(default)]
|
||||
pub global_game_settings: setting_profiles::Model,
|
||||
pub allow_parallel_running_clusters: bool,
|
||||
/// Whether to launch Feral Gamemode on Linux systems.
|
||||
#[serde(default)]
|
||||
pub enable_gamemode: bool,
|
||||
/// A global default [`Resolution`] for Minecraft.
|
||||
pub resolution: Resolution,
|
||||
/// A global [`JavaVersions`] list and default version.
|
||||
pub java_versions: JavaVersions,
|
||||
/// A global [`Memory`] settings store for Java memory settings.
|
||||
pub memory: Memory,
|
||||
/// Global and default initialization hooks .
|
||||
pub init_hooks: InitHooks,
|
||||
/// Global and default custom Java arguments.
|
||||
pub custom_java_args: Vec<String>,
|
||||
/// Global and default custom environment variables.
|
||||
pub custom_env_args: Vec<(String, String)>,
|
||||
/// Global and default maximum [`Semaphore`] I/O operations.
|
||||
pub max_async_io_operations: usize,
|
||||
/// Global and default maximum [`Semaphore`] HTTP operations.
|
||||
pub max_async_fetches: usize,
|
||||
/// The [`CURRENT_FORMAT_VERSION`] of this settings file.
|
||||
pub version: u32,
|
||||
/// Whether or not to disable Discord IPC.
|
||||
#[serde(default)]
|
||||
pub disable_discord: bool,
|
||||
/// Whether or not to enable a debug mode in the launcher.
|
||||
#[serde(default)]
|
||||
pub debug_mode: bool,
|
||||
/// Whether or not to disable Plausible and Sentry analytics.
|
||||
#[serde(default)]
|
||||
pub disable_analytics: bool,
|
||||
/// The core global config directory stored as a [`PathBuf`].
|
||||
#[serde(default = "Directories::init_settings_dir")]
|
||||
pub config_dir: Option<PathBuf>,
|
||||
/// Whether or not to minimize the launcher upon a game launch.
|
||||
#[serde(default)]
|
||||
pub hide_on_launch: bool,
|
||||
/// Enable/disable custom window decorations.
|
||||
#[serde(default)]
|
||||
pub custom_frame: bool,
|
||||
/// Completed onboarding.
|
||||
#[serde(default)]
|
||||
pub onboarding_completed: bool,
|
||||
pub discord_enabled: bool,
|
||||
pub settings_version: u32,
|
||||
}
|
||||
|
||||
fn theme_default() -> String {
|
||||
"dark".to_string()
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
global_game_settings: default_global_game_settings(),
|
||||
allow_parallel_running_clusters: false,
|
||||
discord_enabled: false,
|
||||
enable_gamemode: false,
|
||||
settings_version: 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
/// Initializes the global settings state.
|
||||
#[tracing::instrument]
|
||||
pub async fn initialize(file: &Path) -> crate::Result<Self> {
|
||||
let mut recovered_corruption = false;
|
||||
|
||||
let settings = if file.exists() {
|
||||
let read_settings = io::read(&file)
|
||||
.await
|
||||
.map_err(|err| anyhow::anyhow!("error reading settings file: {0}", err).into())
|
||||
.and_then(|it| serde_json::from_slice::<Self>(&it).map_err(crate::Error::from));
|
||||
|
||||
if let Err(ref err) = read_settings {
|
||||
tracing::error!("failed to load settings file: {err}.");
|
||||
let backup = file.with_extension("json.bak");
|
||||
tracing::error!(
|
||||
"corrupted settings will be backed up as {}, and a new one will be created",
|
||||
backup.display()
|
||||
);
|
||||
let _ = io::rename(file, backup).await;
|
||||
recovered_corruption = true;
|
||||
}
|
||||
|
||||
read_settings.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(settings) = settings {
|
||||
Ok(settings)
|
||||
} else {
|
||||
let settings = Self {
|
||||
theme: "dark".to_string(),
|
||||
browser_list_view: BrowserListView::Grid,
|
||||
hide_close_prompt: true,
|
||||
disable_animations: false,
|
||||
force_fullscreen: false,
|
||||
allow_parallel_running_clusters: false,
|
||||
#[cfg(target_os = "linux")]
|
||||
enable_gamemode: true,
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
enable_gamemode: false,
|
||||
resolution: Resolution::default(),
|
||||
java_versions: JavaVersions::new(),
|
||||
memory: Memory::default(),
|
||||
init_hooks: InitHooks::default(),
|
||||
custom_env_args: Vec::new(),
|
||||
custom_java_args: Vec::new(),
|
||||
max_async_fetches: 10,
|
||||
max_async_io_operations: 10,
|
||||
version: CURRENT_SETTINGS_FORMAT_VERSION,
|
||||
disable_analytics: false,
|
||||
disable_discord: false,
|
||||
debug_mode: false,
|
||||
config_dir: Directories::init_settings_dir(),
|
||||
hide_on_launch: false,
|
||||
custom_frame: true,
|
||||
onboarding_completed: false,
|
||||
};
|
||||
|
||||
if recovered_corruption {
|
||||
settings.sync(file).await?;
|
||||
}
|
||||
|
||||
Ok(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()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronizes the current settings from a file.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn sync(&self, to: &Path) -> crate::Result<()> {
|
||||
io::write(to, serde_json::to_vec(self)?).await?;
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
/// `OneLauncher` browser list type.
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BrowserListView {
|
||||
#[default]
|
||||
Grid,
|
||||
List,
|
||||
}
|
||||
|
||||
/// Global memory settings across all clusters.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
pub struct Memory {
|
||||
/// Maximum amount of Java memory available globally.
|
||||
pub maximum: u32,
|
||||
/// Minimum amount of Java memory available globally.
|
||||
pub minimum: u32,
|
||||
}
|
||||
|
||||
impl Default for Memory {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
maximum: 2048,
|
||||
minimum: 256,
|
||||
}
|
||||
fn default_global_game_settings() -> setting_profiles::Model {
|
||||
setting_profiles::Model {
|
||||
name: "Global".into(),
|
||||
force_fullscreen: Some(false),
|
||||
hook_post: None,
|
||||
hook_pre: None,
|
||||
hook_wrapper: None,
|
||||
java_id: None,
|
||||
launch_args: None,
|
||||
launch_env: None,
|
||||
mem_max: Some(3072),
|
||||
res_h: None,
|
||||
res_w: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Global Minecraft resolution.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
pub struct Resolution(pub u16, pub u16);
|
||||
|
||||
impl Default for Resolution {
|
||||
fn default() -> Self {
|
||||
Self(854, 480)
|
||||
}
|
||||
}
|
||||
|
||||
/// Global initialization hooks for all Minecraft clusters.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[serde(default)]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
pub struct InitHooks {
|
||||
/// Pre-launch hook.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pre: Option<String>,
|
||||
/// Wrapper hook for the runtime of the game instance.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub wrapper: Option<String>,
|
||||
/// Post-launch hook.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub post: Option<String>,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use sqlx::SqlitePool;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use tokio::sync::{OnceCell, RwLock};
|
||||
|
||||
use crate::LauncherResult;
|
||||
|
|
@ -13,7 +13,7 @@ static LAUNCHER_STATE: OnceCell<RwLock<State>> = OnceCell::const_new();
|
|||
pub struct State {
|
||||
pub ingress_processor: IngressProcessor,
|
||||
pub settings: Settings,
|
||||
pub db: SqlitePool,
|
||||
pub db: DatabaseConnection,
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
|
@ -4,17 +4,17 @@ 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("invalid absolute path '{0}'")]
|
||||
InvalidAbsolutePath(std::path::PathBuf),
|
||||
|
||||
#[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),
|
||||
}
|
||||
|
|
@ -1,15 +1 @@
|
|||
//! **`OneLauncher` Core Utilities**
|
||||
//! Standard asynchronous utilities and wrappers for use within the launcher core.
|
||||
//!
|
||||
//! - [`http`]: Async extensions and wrappers around [`reqwest`] functions.
|
||||
//! - [`java`]: Async utilities for managing and downloading Java versions.
|
||||
//! - [`watcher`]: Async utilities for watching files with [`notify`].
|
||||
|
||||
pub mod http;
|
||||
pub mod java;
|
||||
pub mod pagination;
|
||||
pub mod watcher;
|
||||
pub mod crypto;
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
pub mod window;
|
||||
pub mod io;
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"name": "@onelauncher/core_new",
|
||||
"version": "0.2.0-alpha.1",
|
||||
"description": "Next-generation open source Minecraft launcher core",
|
||||
"license": "GPL-3.0-only",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:polyfrost/onelauncher",
|
||||
"directory": "packages/core-new"
|
||||
},
|
||||
"bugs": "https://github.com/polyfrost/onelauncher/issues",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"cargo": "cargo",
|
||||
"binary": "cargo run --bin",
|
||||
"sqlx": "sqlx"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
# 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"
|
||||
```
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
use crate::{store::{ingress::{IngressId, IngressType}, State}, LauncherResult};
|
||||
|
||||
#[tracing::instrument]
|
||||
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)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn send_ingress(id: &IngressId, increment: f64) -> LauncherResult<()> {
|
||||
let state = State::get().await?;
|
||||
let processor = &state.ingress_processor;
|
||||
|
||||
processor.send(id.0, increment).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{api::{ingress::{init_ingress, send_ingress}, proxy::proxy_cli::ProxyCli}, initialize_core, store::ingress::IngressType, LauncherResult};
|
||||
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn create_and_update_ingress() -> LauncherResult<()> {
|
||||
assert!(cfg!(feature = "cli"));
|
||||
|
||||
initialize_core(ProxyCli::new()).await?;
|
||||
|
||||
let id = init_ingress(IngressType::Download { file_name: "Some-Mod-1.8.9.jar".into() }, "This is a test message", 100.0).await?;
|
||||
tokio::time::sleep(Duration::from_millis(2350)).await;
|
||||
|
||||
send_ingress(&id, 10.0).await?;
|
||||
tokio::time::sleep(Duration::from_millis(2500)).await;
|
||||
|
||||
send_ingress(&id, 20.0).await?;
|
||||
tokio::time::sleep(Duration::from_millis(2500)).await;
|
||||
|
||||
send_ingress(&id, 70.0).await?;
|
||||
tokio::time::sleep(Duration::from_millis(2500)).await;
|
||||
|
||||
drop(id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
pub mod ingress;
|
||||
pub mod proxy;
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
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<()>;
|
||||
}
|
||||
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum LauncherError {
|
||||
#[error(transparent)]
|
||||
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>;
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
pub mod constants;
|
||||
pub mod utils;
|
||||
pub mod store;
|
||||
pub mod api;
|
||||
|
||||
mod error;
|
||||
mod logger;
|
||||
|
||||
use api::proxy::LauncherProxy;
|
||||
pub use error::{LauncherResult, LauncherError};
|
||||
pub use logger::start_logger;
|
||||
use store::{proxy::ProxyState, Dirs, State};
|
||||
|
||||
pub async fn initialize_core(proxy_backend: impl LauncherProxy + 'static) -> LauncherResult<()> {
|
||||
Dirs::get().await?;
|
||||
start_logger().await;
|
||||
|
||||
ProxyState::initialize(proxy_backend).await?;
|
||||
State::get().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
// Handling for the live development logging
|
||||
// This will log to the console, and will not log to a file
|
||||
#[cfg(debug_assertions)]
|
||||
pub async fn start_logger() {
|
||||
use tracing_subscriber::{prelude::*, util::SubscriberInitExt};
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(format!("{}=info", env!("CARGO_PKG_NAME"))));
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_ansi(true)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_level(true)
|
||||
.with_thread_names(true)
|
||||
.pretty()
|
||||
)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default())
|
||||
.init();
|
||||
}
|
||||
|
||||
// Handling for the live production logging
|
||||
// This will log to a file in the logs directory, and will not show any logs in the console
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub async fn start_logger() {
|
||||
use chrono::Local;
|
||||
use std::fs::OpenOptions;
|
||||
use tracing_subscriber::fmt::time::ChronoLocal;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
use crate::io::Dirs;
|
||||
|
||||
// Initialize and get logs directory path
|
||||
let logs_dir = Dirs::get().await?.launcher_logs_dir();
|
||||
|
||||
let log_file_name = format!("launcher_{}.log", Local::now().format("%Y%m%d_%H%M%S"));
|
||||
let log_file_path = logs_dir.join(log_file_name);
|
||||
|
||||
if let Err(err) = std::fs::create_dir_all(&logs_dir) {
|
||||
eprintln!("Could not create logs directory: {err}");
|
||||
}
|
||||
|
||||
let file = match OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file_path)
|
||||
{
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
eprintln!("Could not start open log file: {e}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(format!("{}=info", env!("CARGO_PKG_NAME"))));
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.compact()
|
||||
.with_writer(file)
|
||||
.with_ansi(false)
|
||||
.with_timer(ChronoLocal::rfc_3339()),
|
||||
)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default())
|
||||
.init();
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
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)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn create_pool() -> LauncherResult<SqlitePool> {
|
||||
let path = Dirs::get().await?.db_file();
|
||||
create_pool_from_path(path).await
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
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)
|
||||
static DIRS_GLOBAL: OnceCell<Dirs> = OnceCell::const_new();
|
||||
|
||||
pub struct Dirs {
|
||||
base_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl Dirs {
|
||||
pub async fn get() -> LauncherResult<&'static Self> {
|
||||
DIRS_GLOBAL.get_or_try_init(async || {
|
||||
Self::initialize()
|
||||
}).await
|
||||
}
|
||||
|
||||
fn initialize() -> LauncherResult<Self> {
|
||||
let base_dir = utils::io::env_path("LAUNCHER_DIR")
|
||||
.or_else(|| Some(dirs::data_dir()?.join(constants::NAME)))
|
||||
.ok_or(DirectoryError::BaseDir)?;
|
||||
|
||||
Ok(Self {
|
||||
base_dir
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the base directory for the launcher.
|
||||
#[must_use]
|
||||
pub const fn base_dir(&self) -> &PathBuf {
|
||||
&self.base_dir
|
||||
}
|
||||
|
||||
/// Get the launcher's logs directory.
|
||||
#[must_use]
|
||||
pub fn launcher_logs_dir(&self) -> PathBuf {
|
||||
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]
|
||||
#[must_use]
|
||||
pub fn metadata_dir(&self) -> PathBuf {
|
||||
self.base_dir().join("metadata")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/libraries` folder for Minecraft libraries.
|
||||
#[must_use]
|
||||
pub fn libraries_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("libraries")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/natives` folder for Minecraft natives.
|
||||
#[must_use]
|
||||
pub fn natives_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("natives")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/java` directory.
|
||||
#[must_use]
|
||||
pub fn java_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("java")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/caches` directory.
|
||||
#[must_use]
|
||||
pub fn caches_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("caches")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DirectoryError {
|
||||
#[error("Failed to get the base directory for the launcher.")]
|
||||
BaseDir,
|
||||
}
|
||||
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
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::*;
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
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 {
|
||||
#[tracing::instrument]
|
||||
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>,
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub mod io;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "onelauncher_new"
|
||||
name = "onelauncher"
|
||||
description = "The core of OneLauncher"
|
||||
version = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
|
@ -10,75 +10,67 @@ readme = { workspace = true }
|
|||
homepage = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = [ "cli" ]
|
||||
tauri = [
|
||||
"dep:tauri",
|
||||
"dep:tauri-specta",
|
||||
"specta",
|
||||
"gui"
|
||||
]
|
||||
default = []
|
||||
# feature flag allowing us to interact with tauri (for loading animations, errors showing on gui, etc)
|
||||
# also allows us to compile onelauncher core without tauri
|
||||
tauri = [ "dep:tauri", "dep:tauri-specta", "specta" ]
|
||||
specta = [ "dep:specta" ]
|
||||
cli = [ "dep:indicatif" ]
|
||||
gui = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
# features[default]
|
||||
onelauncher_macros = { workspace = true }
|
||||
onelauncher_utils = { workspace = true }
|
||||
interpulse = { workspace = true }
|
||||
|
||||
# logging
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
tracing-error = { workspace = true }
|
||||
tracing-appender = { workspace = true }
|
||||
|
||||
# serialization
|
||||
notify = { workspace = true }
|
||||
notify-debouncer-mini = { workspace = true }
|
||||
iota_stronghold = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_ini = { workspace = true }
|
||||
serde_repr = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
|
||||
# utils
|
||||
sqlx = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
zip = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
sysinfo = { workspace = true }
|
||||
whoami = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
url = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
|
||||
# integration
|
||||
discord-rich-presence = { workspace = true }
|
||||
|
||||
# crypto
|
||||
dirs = { workspace = true }
|
||||
sha1_smol = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
murmur2 = { workspace = true }
|
||||
md5 = { workspace = true }
|
||||
|
||||
# io
|
||||
zip = { workspace = true }
|
||||
async_zip = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
discord-rich-presence = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
sysinfo = { workspace = true }
|
||||
whoami = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
async-tungstenite = { workspace = true }
|
||||
async-recursion = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
p256 = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
async-compression = { workspace = true }
|
||||
dunce = { workspace = true }
|
||||
flate2 = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
# features[tauri]
|
||||
tauri = { workspace = true, optional = true }
|
||||
|
|
@ -90,5 +82,11 @@ specta = { workspace = true, optional = true }
|
|||
# features[cli]
|
||||
indicatif = { workspace = true, optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
dotenvy = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winreg = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
onelauncher_gamemode = { workspace = true }
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "@onelauncher/macros",
|
||||
"name": "@onelauncher/core_old",
|
||||
"version": "0.1.0-alpha.2",
|
||||
"description": "Next-generation open source Minecraft launcher macros",
|
||||
"description": "Next-generation open source Minecraft launcher core",
|
||||
"license": "GPL-3.0-only",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:polyfrost/onelauncher",
|
||||
"directory": "packages/macros"
|
||||
"directory": "packages/core_old"
|
||||
},
|
||||
"bugs": "https://github.com/polyfrost/onelauncher/issues",
|
||||
"sideEffects": false,
|
||||
5
packages/core_old/src/api/ingress.rs
Normal file
5
packages/core_old/src/api/ingress.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
use crate::store::{IngressProcessType, IngressProcessor};
|
||||
|
||||
pub async fn check_ingress_feeds() -> crate::Result<bool> {
|
||||
IngressProcessor::finished(IngressProcessType::IngressFeed).await
|
||||
}
|
||||
31
packages/core_old/src/api/mod.rs
Normal file
31
packages/core_old/src/api/mod.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
//! **`OneLauncher` API**
|
||||
//!
|
||||
//! API for interacting with our Rust core.
|
||||
|
||||
pub mod cluster;
|
||||
pub mod handler;
|
||||
pub mod ingress;
|
||||
pub mod java;
|
||||
pub mod metadata;
|
||||
pub mod minecraft;
|
||||
pub mod package;
|
||||
pub mod processor;
|
||||
pub mod proxy;
|
||||
pub mod settings;
|
||||
|
||||
pub mod data {
|
||||
pub use crate::store::{
|
||||
ClusterMeta, Credentials, Directories, InitHooks, JavaOptions, Loader, ManagedDependency,
|
||||
ManagedPackage, ManagedUser, ManagedVersion, Memory, MinecraftCredentials, PackageData,
|
||||
PackageType, Resolution, Settings,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::cluster::{self, create, Cluster};
|
||||
pub use crate::data::*;
|
||||
pub use crate::proxy::InternetPayload;
|
||||
pub use crate::store::{ClusterPath, JavaVersions, PackageDependency, PackagePath};
|
||||
pub use crate::utils::java::JavaVersion;
|
||||
pub use crate::{java, metadata, minecraft, package, processor, settings, State};
|
||||
}
|
||||
277
packages/core_old/src/api/proxy/mod.rs
Normal file
277
packages/core_old/src/api/proxy/mod.rs
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
//! Proxy for Tauri/CLI specific features and communication
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{OnceCell, RwLock};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::store::{ClusterPath, IngressProcessor};
|
||||
|
||||
pub mod send;
|
||||
pub mod utils;
|
||||
pub use utils::*;
|
||||
|
||||
static PROXY_STATE: OnceCell<Arc<ProxyState>> = OnceCell::const_new();
|
||||
pub struct ProxyState {
|
||||
#[cfg(feature = "tauri")]
|
||||
pub app: tauri::AppHandle<tauri::Wry>,
|
||||
pub ingress_feeds: RwLock<HashMap<Uuid, Ingress>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct Ingress {
|
||||
pub ingress_uuid: Uuid,
|
||||
pub message: String,
|
||||
pub ingress_type: IngressType,
|
||||
pub total: f64,
|
||||
pub current: f64,
|
||||
#[serde(skip)]
|
||||
pub last_sent: f64,
|
||||
#[cfg(feature = "cli")]
|
||||
#[serde(skip)]
|
||||
pub cli: indicatif::ProgressBar,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct IngressId(Uuid);
|
||||
|
||||
impl Drop for IngressId {
|
||||
#[clippy::has_significant_drop]
|
||||
fn drop(&mut self) {
|
||||
let ingress_uuid = self.0;
|
||||
tokio::spawn(async move {
|
||||
if let Ok(proxy_state) = ProxyState::get().await {
|
||||
let mut ingress_feeds = proxy_state.ingress_feeds.write().await;
|
||||
#[cfg(any(feature = "tauri", feature = "cli"))]
|
||||
if let Some(ingress) = ingress_feeds.remove(&ingress_uuid) {
|
||||
#[cfg(feature = "cli")]
|
||||
{
|
||||
let cli = ingress.cli;
|
||||
cli.finish();
|
||||
}
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
use tauri::Emitter;
|
||||
let event = ingress.ingress_type.clone();
|
||||
let fraction = ingress.current / ingress.total;
|
||||
|
||||
let _ = proxy_state.app.emit(
|
||||
"ingress",
|
||||
IngressPayload {
|
||||
fraction: None,
|
||||
message: "Complete".to_string(),
|
||||
event,
|
||||
ingress_uuid,
|
||||
},
|
||||
);
|
||||
|
||||
tracing::trace!("exited at {fraction} for ingress: {:?}", ingress_uuid);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "tauri", feature = "cli")))]
|
||||
ingress_feeds.remove(&ingress_uuid);
|
||||
}
|
||||
|
||||
if crate::State::initalized() {
|
||||
let _ = IngressProcessor::finish(
|
||||
crate::store::IngressProcessType::IngressFeed,
|
||||
ingress_uuid,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ProxyState {
|
||||
#[cfg(feature = "tauri")]
|
||||
pub async fn initialize(handle: &tauri::AppHandle<tauri::Wry>) -> crate::Result<Arc<Self>> {
|
||||
PROXY_STATE
|
||||
.get_or_try_init(|| async {
|
||||
Ok(Arc::new(Self {
|
||||
app: handle.clone(),
|
||||
ingress_feeds: RwLock::new(HashMap::new()),
|
||||
}))
|
||||
})
|
||||
.await
|
||||
.cloned()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tauri"))]
|
||||
pub async fn initialize() -> crate::Result<Arc<Self>> {
|
||||
PROXY_STATE
|
||||
.get_or_try_init(|| async {
|
||||
Ok(Arc::new(Self {
|
||||
ingress_feeds: RwLock::new(HashMap::new()),
|
||||
}))
|
||||
})
|
||||
.await
|
||||
.cloned()
|
||||
}
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
pub async fn get_main_window() -> crate::Result<tauri::WebviewWindow> {
|
||||
use tauri::Manager;
|
||||
let value = Self::get().await?;
|
||||
Ok(value.app.get_webview_window("main").unwrap())
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
#[cfg(feature = "tauri")]
|
||||
pub async fn get() -> crate::Result<Arc<Self>> {
|
||||
Ok(PROXY_STATE.get().ok_or(ProxyError::NotInitialized)?.clone())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tauri"))]
|
||||
pub async fn get() -> crate::Result<Arc<Self>> {
|
||||
Self::initialize().await
|
||||
}
|
||||
|
||||
pub async fn list_ingress_feeds() -> crate::Result<HashMap<Uuid, Ingress>> {
|
||||
let value = Self::get().await?;
|
||||
let read = value.ingress_feeds.read().await;
|
||||
|
||||
let mut display_list: HashMap<Uuid, Ingress> = HashMap::new();
|
||||
for (uuid, ingress) in read.iter() {
|
||||
display_list.insert(*uuid, ingress.clone());
|
||||
}
|
||||
|
||||
Ok(display_list)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum IngressType {
|
||||
Initialize,
|
||||
DownloadJava {
|
||||
version: u32,
|
||||
},
|
||||
DownloadLoader {
|
||||
cluster_path: PathBuf,
|
||||
cluster_name: String,
|
||||
},
|
||||
SyncCluster {
|
||||
cluster_path: PathBuf,
|
||||
cluster_name: String,
|
||||
},
|
||||
InferPackages,
|
||||
CopyCluster {
|
||||
import: PathBuf,
|
||||
cluster_name: String,
|
||||
},
|
||||
SyncConfig {
|
||||
new_path: PathBuf,
|
||||
},
|
||||
Archival {
|
||||
cluster_path: PathBuf,
|
||||
cluster_name: String,
|
||||
},
|
||||
DownloadPackage {
|
||||
cluster_path: PathBuf,
|
||||
package_name: String,
|
||||
icon: Option<PathBuf>,
|
||||
package_id: Option<String>,
|
||||
package_version: Option<String>,
|
||||
},
|
||||
DownloadPack {
|
||||
cluster_path: PathBuf,
|
||||
package_name: String,
|
||||
icon: Option<String>,
|
||||
package_version: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct IngressPayload {
|
||||
pub event: IngressType,
|
||||
pub ingress_uuid: Uuid,
|
||||
pub fraction: Option<f64>,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct OfflinePayload {
|
||||
pub offline: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct MessagePayload {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(tag = "event")]
|
||||
pub enum InternetPayload {
|
||||
InstallPackage { id: String },
|
||||
InstallPack { id: String },
|
||||
InstallPath { path: PathBuf },
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct ProcessPayload {
|
||||
pub uuid: Uuid,
|
||||
pub pid: u32,
|
||||
pub event: ProcessPayloadType,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ProcessPayloadType {
|
||||
Started,
|
||||
Modified,
|
||||
Finished,
|
||||
Logging,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", derive(tauri_specta::Event))]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct ClusterPayload {
|
||||
pub uuid: Uuid,
|
||||
pub cluster_path: ClusterPath,
|
||||
pub path: PathBuf,
|
||||
pub name: String,
|
||||
pub event: ClusterPayloadType,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ClusterPayloadType {
|
||||
Created,
|
||||
Inserted,
|
||||
Synced,
|
||||
Edited,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ProxyError {
|
||||
#[error("event state was not initialized!")]
|
||||
NotInitialized,
|
||||
|
||||
#[error("non-existant ingress of key: {0}")]
|
||||
NoIngressFound(Uuid),
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
#[error("Tauri error: {0}")]
|
||||
TauriError(#[from] tauri::Error),
|
||||
}
|
||||
|
|
@ -1,14 +1,11 @@
|
|||
//! **`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.
|
||||
pub const NAME: &str = match option_env!("LAUNCHER_NAME") {
|
||||
Some(name) => name,
|
||||
None => "OneLauncher",
|
||||
};
|
||||
|
||||
pub const NAME: &str = "OneLauncher";
|
||||
/// The version of the launcher (from `../Cargo.toml` env).
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
|
|
@ -41,7 +38,7 @@ pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2";
|
|||
pub const MODRINTH_V3_API_URL: &str = "https://api.modrinth.com/v3";
|
||||
/// The Curseforge API base url.
|
||||
pub const CURSEFORGE_API_URL: &str = "https://api.curseforge.com";
|
||||
/// The Curseforge API key. Reserved for use in Polyfrost projects only. Do not use in other projects without permission from the Polyfrost team.
|
||||
/// The Curseforge API key. Reserved for use in OneLauncher projects only. Do not use in other projects without permission from the OneLauncher team.
|
||||
pub const CURSEFORGE_API_KEY: &str = "$2a$10$6utA1UNSmFPrE/Lh7b7ndeeGmiOkjKNY8kpFB0fsmE/d42ZAfFgCe";
|
||||
/// The Minecraft game ID on Curseforge.
|
||||
pub const CURSEFORGE_GAME_ID: u32 = 432;
|
||||
|
|
@ -51,7 +48,7 @@ pub const METADATA_API_URL: &str = "https://meta.polyfrost.org";
|
|||
pub const FEATURED_PACKAGES_URL: &str = "https://polyfrost.org/meta/onelauncher/featured.json";
|
||||
/// <https://mclo.gs>/ API base url.
|
||||
pub const MCLOGS_API_URL: &str = "https://api.mclo.gs/1";
|
||||
/// <https://skyclient.co/> metadata base url.
|
||||
/// https://skyclient.co/ metadata base url.
|
||||
pub const SKYCLIENT_BASE_URL: &str = "https://raw.githubusercontent.com/SkyblockClient/SkyblockClient-REPO/refs/heads/main/v1";
|
||||
|
||||
// =========== Paths ===========
|
||||
|
|
@ -59,6 +56,10 @@ pub const SKYCLIENT_BASE_URL: &str = "https://raw.githubusercontent.com/Skyblock
|
|||
///
|
||||
/// [`Settings`]: crate::store::Settings
|
||||
pub const SETTINGS_FILE: &str = "settings.json";
|
||||
/// The public `cluster.json` file used to store the global [`Clusters`] state.
|
||||
///
|
||||
/// [`Clusters`]: crate::store::Clusters
|
||||
pub const CLUSTER_FILE: &str = "cluster.json";
|
||||
/// The public `authentication.json` file used to store the global [`MinecraftAuth`] state.
|
||||
///
|
||||
/// [`MinecraftAuth`]: crate::store::MinecraftAuth
|
||||
|
|
@ -74,6 +75,15 @@ pub const PROCESSOR_FILE: &str = "processor.json";
|
|||
/// [`Settings`]: crate::store::Settings
|
||||
pub const CURRENT_SETTINGS_FORMAT_VERSION: u32 = 1;
|
||||
|
||||
/// The constant core caches folder.
|
||||
pub const CACHES_FOLDER: &str = "caches";
|
||||
/// The constant core crash reports folder.
|
||||
pub const CRASH_PATH: &str = "crash-reports";
|
||||
/// The constant core clusters folder.
|
||||
pub const CLUSTERS_FOLDER: &str = "clusters";
|
||||
/// The constant core metadata folder.
|
||||
pub const METADATA_FOLDER: &str = "metadata";
|
||||
|
||||
// =========== Hacky Mojang-spec OS constants ===========
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const TARGET_OS: &str = "windows";
|
||||
180
packages/core_old/src/error.rs
Normal file
180
packages/core_old/src/error.rs
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
//! **`OneLauncher` Errors**
|
||||
//!
|
||||
//! Public wrappers around common `OneLauncher` errors.
|
||||
|
||||
use tracing_error::InstrumentError;
|
||||
|
||||
/// A standardized [`thiserror::Error`] type that is used across the launcher
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ErrorKind {
|
||||
/// Wrapper around [`tokio::sync::AcquireError`] to handle sempahore errors.
|
||||
#[error("failed to acquire a tokio semaphore: {0}")]
|
||||
AcquireError(#[from] tokio::sync::AcquireError),
|
||||
|
||||
/// Wrapper around [`tokio::sync::oneshot::error::RecvError`] to handle recieve errors.
|
||||
#[error("failed to handle async reciever: {0}")]
|
||||
RecvError(#[from] tokio::sync::oneshot::error::RecvError),
|
||||
|
||||
/// Wrapper around [`tokio::task::JoinError`] to handle tokio join handle errors.
|
||||
#[error("failed to handle an async task: {0}")]
|
||||
JoinError(#[from] tokio::task::JoinError),
|
||||
|
||||
/// Wrapper around [`flate2::CompressError`] to handle flate compression errors.
|
||||
#[error("failed to compress a file with flate: {0}")]
|
||||
FlateError(#[from] flate2::CompressError),
|
||||
|
||||
/// Wrapper around [`flate2::DecompressError`] to handle flate decompression errors.
|
||||
#[error("failed to decompress a file with flate: {0}")]
|
||||
DeflateError(#[from] flate2::DecompressError),
|
||||
|
||||
/// Wrapper around [`uuid::Error`] to handle UUID parsing errors.
|
||||
#[error("failed to parse and handle uuids: {0}")]
|
||||
UUIDError(#[from] uuid::Error),
|
||||
|
||||
/// Wrapper around [`interpulse::Error`] to handle interpulse errors.
|
||||
#[error("failed to parse and handle metadata: {0}")]
|
||||
MetadataError(#[from] interpulse::Error),
|
||||
|
||||
/// Wrapper around [`serde_json::Error`] to handle Serde JSON parsing errors.
|
||||
#[error("failed to manage serialization of json: {0}")]
|
||||
SerdeError(#[from] serde_json::Error),
|
||||
|
||||
/// Wrapper around [`serde_ini::de::Error`] to handle Serde INI parsing errors.
|
||||
#[error("failed to manage deserialization of ini: {0}")]
|
||||
INIError(#[from] serde_ini::de::Error),
|
||||
|
||||
/// Wrapper around [`reqwest::Error`] to handle HTTP errors.
|
||||
#[error("failed to establish an http connection: {0}")]
|
||||
HTTPError(#[from] reqwest::Error),
|
||||
|
||||
/// Wrapper around [`zip::result::ZipError`] to handle zip errors.
|
||||
#[error("failed to manage zip files: {0}")]
|
||||
ZipError(#[from] zip::result::ZipError),
|
||||
|
||||
/// Wrapper around [`async_zip::error::ZipError`] to handle async zip errors.
|
||||
#[error("failed to manage zip files asynchronously: {0}")]
|
||||
AsyncZipError(#[from] async_zip::error::ZipError),
|
||||
|
||||
/// Wrapper around [`anyhow::Error`] to handle generic errors.
|
||||
#[error(transparent)]
|
||||
AnyhowError(#[from] anyhow::Error),
|
||||
|
||||
/// Wrapper around [`chrono::ParseError`] to handle date parsing errors.
|
||||
#[error("failed to parse and handle datetime: {0}")]
|
||||
ChronoError(#[from] chrono::ParseError),
|
||||
|
||||
/// Wrapper around [`crate::state::DirectoryError`] to handle directory errors.
|
||||
#[error("failed to get directory: {0}")]
|
||||
DirectoryError(#[from] crate::store::DirectoryError),
|
||||
|
||||
/// Wrapper around [`crate::api::proxy::ProxyError`] to handle ingress errors.
|
||||
#[error("failed to handle api communication: {0}")]
|
||||
ProxyError(#[from] crate::api::proxy::ProxyError),
|
||||
|
||||
/// Wrapper around [`crate::store::MinecraftAuthError`] to handle Minecraft auth errors.
|
||||
#[error("failed to authenticate with microsoft: {0}")]
|
||||
AuthError(#[from] crate::store::MinecraftAuthError),
|
||||
|
||||
/// Wrapper around [`crate::api::cluster::create::CreateClusterError`] to handle Cluster creation errors.
|
||||
#[error("failed to create clusters: {0}")]
|
||||
CreateClusterError(#[from] crate::api::cluster::create::CreateClusterError),
|
||||
|
||||
/// Wrapper around [`notify:Error`] to handle file watching errors.
|
||||
#[error("failed to watch file {0}")]
|
||||
NotifyError(#[from] notify::Error),
|
||||
|
||||
/// Wrapper around [`regex::Error`] to handle `RegExp` errors.
|
||||
#[error("string verification with regex failed: {0}")]
|
||||
RegexError(#[from] regex::Error),
|
||||
|
||||
/// Wrapper around [`reqwest::header::ToStrError`] to handle header conversion errors.
|
||||
#[error("failed to convert header to string: {0}")]
|
||||
HeaderToStrError(#[from] reqwest::header::ToStrError),
|
||||
|
||||
/// Wrapper around [`async_tungstenite::tungstenite::Error`] to handle WebSocket errors.
|
||||
#[error("failed to handle websocket connection: {0}")]
|
||||
WebSocketError(#[from] async_tungstenite::tungstenite::Error),
|
||||
|
||||
/// Indicates an error with SHA checksum.
|
||||
#[error("checksum {0} did not match {1}!")]
|
||||
HashError(String, String),
|
||||
|
||||
/// Wrapper around [`onelauncher_utils::io::IOError`] to handle wrapped `std::io` errors.
|
||||
#[error("error handling IO operations: {0}")]
|
||||
IOError(#[from] onelauncher_utils::io::IOError),
|
||||
|
||||
/// Wrapper around [`std::io::Error`] to handle non-wrapped `std::io` errors.
|
||||
#[error("error handling standard IO operations: {0}")]
|
||||
StdIOError(#[from] std::io::Error),
|
||||
|
||||
/// Wrapper around [`crate::utils::java::JavaError`] to handle java errors.
|
||||
#[error("failed to manage java installations: {0}")]
|
||||
JavaError(#[from] crate::utils::java::JavaError),
|
||||
|
||||
/// Wrapper around [`crate::store::StrongholdError`] to handle stronghold errors.
|
||||
#[error("failed to manage credentials with IOTA stronghold: {0}")]
|
||||
StrongholdError(#[from] crate::store::StrongholdError),
|
||||
|
||||
/// Wrapper around [`url::ParseError`] to handle URL parsing errors
|
||||
#[error("failed to parse and handle URLs: {0}")]
|
||||
URLError(#[from] url::ParseError),
|
||||
|
||||
/// Wrapper around [`tauri::Error`] to handle Tauri errors when the feature flag is enabled
|
||||
#[cfg(feature = "tauri")]
|
||||
#[error("error handling tauri: {0}")]
|
||||
TauriError(#[from] tauri::Error),
|
||||
}
|
||||
|
||||
/// A safe, accessible error structure wrapper around [`ErrorKind`] for use with [`tracing_error`].
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
/// The raw, inner [`ErrorKind`], wrapped in an [`Arc`].
|
||||
pub raw: std::sync::Arc<ErrorKind>,
|
||||
/// The traced [`ErrorKind`] ([`tracing_error`]) to be parsed and logged.
|
||||
pub source: tracing_error::TracedError<std::sync::Arc<ErrorKind>>,
|
||||
}
|
||||
|
||||
impl From<Error> for String {
|
||||
/// Converts an [`Error`] into a [`String`] value.
|
||||
fn from(value: Error) -> Self {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
/// Get the source for the inner traced [`Error`].
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
self.source.source()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
/// Format the [`Error`] and output it into the formatter buffer.
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(fmt, "{}", self.source)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Into<ErrorKind>> From<E> for Error {
|
||||
/// Converts an [`ErrorKind`] error into a traced [`Error`].
|
||||
/// Useful for reducing error mapping and standardizing error types.
|
||||
fn from(source: E) -> Self {
|
||||
let error = Into::<ErrorKind>::into(source);
|
||||
let boxed_error = std::sync::Arc::new(error);
|
||||
|
||||
Self {
|
||||
raw: boxed_error.clone(),
|
||||
source: boxed_error.in_current_span(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorKind {
|
||||
/// Converts the current [`ErrorKind`] into a traced [`Error`].
|
||||
pub fn as_error(self) -> Error {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Alias for a [`core::result::Result`] with the error type [`Error`].
|
||||
pub type Result<T> = anyhow::Result<T, Error>;
|
||||
20
packages/core_old/src/lib.rs
Normal file
20
packages/core_old/src/lib.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#![allow(clippy::all)]
|
||||
//! # `OneLauncher`
|
||||
//!
|
||||
//! A library used as a core for our launcher and Rust APIs.
|
||||
|
||||
#[macro_use]
|
||||
pub mod utils;
|
||||
|
||||
pub mod api;
|
||||
pub mod constants;
|
||||
pub mod error;
|
||||
pub mod game;
|
||||
pub mod logger;
|
||||
pub mod store;
|
||||
|
||||
pub use api::proxy::{Ingress, IngressType, ProxyState};
|
||||
pub use api::*;
|
||||
pub use error::*;
|
||||
pub use logger::start_logger;
|
||||
pub use store::{InnerPathLinux, State};
|
||||
74
packages/core_old/src/logger.rs
Normal file
74
packages/core_old/src/logger.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
//! **`OneLauncher` Logger**
|
||||
//!
|
||||
//! Public utilities for [`tracing`] logging in `OneLauncher`.
|
||||
//!
|
||||
//! - `RUST_LOG=xxx` is a variable which defines tracing configuration (`ERROR > WARN > INFO > DEBUG > TRACE`)
|
||||
//! - `RUST_LOG="onelauncher=trace"` will show **all** mesages from `OneLauncher` and not other dependencies.
|
||||
//! - `RUST_LOG="info"` will show info, warn, and error logs.
|
||||
//!
|
||||
//! Errors returned to [`tauri`] are displayed as tracing error logs.
|
||||
//! These errors also include an attached span trace if the error is from a [`tracing_error`] and the level is info, debug, or trace.
|
||||
//! Tracing errors also provide Sentry metadata to be handled.
|
||||
//! To set the environment variable on `unix` you can use the following command: `RUST_LOG="xxx" {your command}`
|
||||
//!
|
||||
//! The default `RUST_LOG` is `onelauncher=show`, meaning only logs from `OneLauncher` will be displayed at the info, warn, and error levels.
|
||||
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
|
||||
// TODO: refactor logging and add more env filters.
|
||||
|
||||
/// Initialize the global [`tracing`] logger in development.
|
||||
#[cfg(debug_assertions)]
|
||||
#[must_use]
|
||||
pub fn start_logger() -> Option<WorkerGuard> {
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
tracing_subscriber::EnvFilter::new("onelauncher=info,onelauncher_gui=info")
|
||||
});
|
||||
|
||||
let subscriber = tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default());
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Initialize the global [`tracing`] logger in production.
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub fn start_logger() -> Option<WorkerGuard> {
|
||||
use crate::store::Directories;
|
||||
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||
use tracing_subscriber::fmt::time::ChronoLocal;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
let logs_dir = if let Some(directory) = Directories::logs_dir() {
|
||||
directory
|
||||
} else {
|
||||
eprintln!("failed to start logger");
|
||||
return None;
|
||||
};
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("onelauncher=info"));
|
||||
|
||||
let file_appender = RollingFileAppender::new(Rotation::DAILY, logs_dir, "onelauncher.log");
|
||||
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
|
||||
|
||||
let subscriber = tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_writer(non_blocking)
|
||||
.with_ansi(false)
|
||||
.with_timer(ChronoLocal::rfc_3339()),
|
||||
)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default());
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
||||
|
||||
Some(guard)
|
||||
}
|
||||
191
packages/core_old/src/store/dirs.rs
Normal file
191
packages/core_old/src/store/dirs.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
//! Handles core directory management
|
||||
use std::path::PathBuf;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use super::Settings;
|
||||
use crate::constants;
|
||||
|
||||
/// Directory management and utilities.
|
||||
#[derive(Debug)]
|
||||
pub struct Directories {
|
||||
/// Settings: All small caches and the core settings.json
|
||||
pub settings_dir: PathBuf,
|
||||
/// Base Configuration: Clusters and all Assets (should be changable as a setting that can go across file systems)
|
||||
/// By default this should be the same as the `settings_dir`
|
||||
pub config_dir: RwLock<PathBuf>,
|
||||
/// The current working directory of the directory manager.
|
||||
pub cwd: PathBuf,
|
||||
}
|
||||
|
||||
impl Directories {
|
||||
/// Bootstrap the initial settings directory on first startup.
|
||||
#[must_use]
|
||||
pub fn init_settings_dir() -> Option<PathBuf> {
|
||||
Self::env_path("ONELAUNCHER_CONFIG")
|
||||
.or_else(|| Some(dirs::data_dir()?.join(constants::NAME)))
|
||||
}
|
||||
|
||||
/// Bootstrap the initial settings directory on first startup.
|
||||
#[inline]
|
||||
pub fn init_settings_file() -> crate::Result<PathBuf> {
|
||||
let settings_dir = Self::init_settings_dir().ok_or(DirectoryError::ConfigDir)?;
|
||||
Ok(settings_dir.join("settings.json"))
|
||||
}
|
||||
|
||||
/// Initialize the core directory manager
|
||||
#[tracing::instrument]
|
||||
pub fn initalize(settings: &Settings) -> crate::Result<Self> {
|
||||
let cwd = std::env::current_dir().map_err(DirectoryError::WorkingDir)?;
|
||||
let settings_dir = Self::init_settings_dir().ok_or(DirectoryError::ConfigDir)?;
|
||||
let config_dir = settings
|
||||
.config_dir
|
||||
.clone()
|
||||
.ok_or(DirectoryError::ConfigDir)?;
|
||||
Ok(Self {
|
||||
settings_dir,
|
||||
config_dir: RwLock::new(config_dir),
|
||||
cwd,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata` folder within the core config directory.
|
||||
#[inline]
|
||||
pub async fn metadata_dir(&self) -> PathBuf {
|
||||
self.config_dir
|
||||
.read()
|
||||
.await
|
||||
.join(constants::METADATA_FOLDER)
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/libraries` folder for Minecraft libraries.
|
||||
#[inline]
|
||||
pub async fn libraries_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("libraries")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/java` directory.
|
||||
#[inline]
|
||||
pub async fn java_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("java_versions")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/caches` directory.
|
||||
#[inline]
|
||||
pub async fn caches_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("caches")
|
||||
}
|
||||
|
||||
/// Bootstrap the core logs directory and get it as a [`PathBuf`].
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn logs_dir() -> Option<PathBuf> {
|
||||
Self::init_settings_dir().map(|d| d.join("logs"))
|
||||
}
|
||||
|
||||
/// Get the `settings_dir/settings.json` file as a [`PathBuf`].
|
||||
#[inline]
|
||||
pub fn settings_file(&self) -> PathBuf {
|
||||
self.settings_dir.join(constants::SETTINGS_FILE)
|
||||
}
|
||||
|
||||
/// Get a [`PathBuf`] from a provided environment variable.
|
||||
#[inline]
|
||||
fn env_path(name: &str) -> Option<PathBuf> {
|
||||
std::env::var_os(name).map(PathBuf::from)
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/assets` directory.
|
||||
#[inline]
|
||||
pub async fn assets_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("assets")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/assets/indexes` directory.
|
||||
#[inline]
|
||||
pub async fn index_dir(&self) -> PathBuf {
|
||||
self.assets_dir().await.join("indexes")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/assets/objects` directory.
|
||||
#[inline]
|
||||
pub async fn objects_dir(&self) -> PathBuf {
|
||||
self.assets_dir().await.join("objects")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/assets/objects/{hash}` directory.
|
||||
#[inline]
|
||||
pub async fn object_dir(&self, hash: &str) -> PathBuf {
|
||||
self.objects_dir().await.join(&hash[..2]).join(hash)
|
||||
}
|
||||
|
||||
/// Get the `config_dir/clusters` directory.
|
||||
#[inline]
|
||||
pub async fn clusters_dir(&self) -> PathBuf {
|
||||
self.config_dir.read().await.join("clusters")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/clusters/{uuid}` directory.
|
||||
#[inline]
|
||||
pub async fn cluster_dir(&self, uuid: uuid::Uuid) -> PathBuf {
|
||||
self.clusters_dir().await.join(uuid.to_string())
|
||||
}
|
||||
|
||||
/// Get the `{cluster_path}/logs` directory.
|
||||
#[inline]
|
||||
pub async fn cluster_logs_dir(cluster_path: &super::ClusterPath) -> crate::Result<PathBuf> {
|
||||
Ok(cluster_path.full_path().await?.join("logs"))
|
||||
}
|
||||
|
||||
/// Get the `{cluster_path}/crash_reports` directory.
|
||||
#[inline]
|
||||
pub async fn crash_reports_dir(cluster_path: &super::ClusterPath) -> crate::Result<PathBuf> {
|
||||
Ok(cluster_path.full_path().await?.join("crash-reports"))
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/resources` directory.
|
||||
#[inline]
|
||||
pub async fn legacy_assets_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("resources")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/natives` directory.
|
||||
#[inline]
|
||||
pub async fn natives_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("natives")
|
||||
}
|
||||
|
||||
/// Get the `config_dir/metadata/natives/{version}` directory.
|
||||
#[inline]
|
||||
pub async fn version_natives_dir(&self, version: &str) -> PathBuf {
|
||||
self.natives_dir().await.join(version)
|
||||
}
|
||||
|
||||
/// Get the Minecraft `config_dir/metadata/versions` directory.
|
||||
#[inline]
|
||||
pub async fn versions_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().await.join("versions")
|
||||
}
|
||||
|
||||
/// Get the Minecraft `config_dir/metadata/versions/{version}` directory.
|
||||
#[inline]
|
||||
pub async fn version_dir(&self, version: &str) -> PathBuf {
|
||||
self.versions_dir().await.join(version)
|
||||
}
|
||||
|
||||
/// Get the `config_dir/icons` directory.
|
||||
#[inline]
|
||||
pub async fn icons_dir(&self) -> PathBuf {
|
||||
self.config_dir.read().await.join("icons")
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a core directory management error.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DirectoryError {
|
||||
/// An error for when a config directory cannot be initialized.
|
||||
#[error("failed to get the user's config directory")]
|
||||
ConfigDir,
|
||||
/// A wrapper over [`std::io::Error`] for cwd fetching errors.
|
||||
#[error("could not open working directory {0}")]
|
||||
WorkingDir(std::io::Error),
|
||||
}
|
||||
289
packages/core_old/src/store/mod.rs
Normal file
289
packages/core_old/src/store/mod.rs
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
//! **`OneLauncher` Store**
|
||||
//!
|
||||
//! Core state and storage management for the launcher, managing all states.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use notify::RecommendedWatcher;
|
||||
use notify_debouncer_mini::Debouncer;
|
||||
use tokio::sync::{OnceCell, RwLock, Semaphore};
|
||||
|
||||
use crate::proxy::send::{init_ingress_internal, send_ingress, send_offline};
|
||||
use crate::utils::http;
|
||||
use crate::utils::http::{FetchSemaphore, IoSemaphore};
|
||||
|
||||
mod dirs;
|
||||
pub use self::dirs::*;
|
||||
|
||||
mod minecraft;
|
||||
pub use self::minecraft::*;
|
||||
|
||||
mod credentials;
|
||||
pub use self::credentials::*;
|
||||
|
||||
mod discord;
|
||||
pub use self::discord::*;
|
||||
|
||||
mod metadata;
|
||||
pub use self::metadata::*;
|
||||
|
||||
mod settings;
|
||||
pub use self::settings::*;
|
||||
|
||||
mod java_versions;
|
||||
pub use self::java_versions::*;
|
||||
|
||||
mod processor;
|
||||
pub use self::processor::*;
|
||||
|
||||
mod clusters;
|
||||
pub use self::clusters::*;
|
||||
|
||||
mod package;
|
||||
pub use self::package::*;
|
||||
|
||||
mod oneconfig;
|
||||
pub use self::oneconfig::*;
|
||||
|
||||
/// The static [`OnceCell<RwLock<State>>`] for storing the global runtime launcher state.
|
||||
static LAUNCHER_STATE: OnceCell<RwLock<State>> = OnceCell::const_new();
|
||||
|
||||
/// The public `OneLauncher` state structure.
|
||||
pub struct State {
|
||||
/// Handles a boolean to check if the launcher is connected to the internet
|
||||
pub offline: RwLock<bool>,
|
||||
/// Handles core launcher directory and files
|
||||
pub directories: Directories,
|
||||
|
||||
/// Semaphore used to handle concurrent network requests
|
||||
pub fetch_semaphore: FetchSemaphore,
|
||||
/// Maximum number of semaphore permits to limit the amount of network requests at one time
|
||||
pub fetch_semaphore_max: RwLock<u32>,
|
||||
/// Semaphore used to handle concurrent IO requests
|
||||
pub io_semaphore: IoSemaphore,
|
||||
/// Maximum number of semaphore permits to limit the amount of IO requests at one time
|
||||
pub io_semaphore_max: RwLock<u32>,
|
||||
|
||||
/// Handles launcher metadata via [`interpulse`]
|
||||
pub metadata: RwLock<Metadata>,
|
||||
/// Handles launcher configuration and settings
|
||||
pub settings: RwLock<Settings>,
|
||||
/// Handles minecraft auth flows and users
|
||||
pub users: RwLock<MinecraftState>,
|
||||
/// Handles processes
|
||||
pub processor: RwLock<Processor>,
|
||||
/// Handles `OneConfig` integration with [`async-tungstenite`]
|
||||
pub oneconfig: RwLock<Option<OneConfig>>,
|
||||
/// Handles secure credential management with [`iota_stronghold`].
|
||||
pub credentials: RwLock<Option<Credentials>>,
|
||||
/// Handles clusters
|
||||
pub(crate) clusters: RwLock<Clusters>,
|
||||
/// Handles packages
|
||||
pub(crate) packages: RwLock<Packages>,
|
||||
/// Handles internal ingress processes
|
||||
pub(crate) ingress_processor: RwLock<IngressProcessor>,
|
||||
/// Handles file system watching for cluster manager
|
||||
pub(crate) watcher: RwLock<Debouncer<RecommendedWatcher>>,
|
||||
/// Handles Discord rich prescence
|
||||
pub discord_rpc: DiscordRPC,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Get the current global launcher state (or initialize it)
|
||||
pub async fn get() -> crate::Result<Arc<tokio::sync::RwLockReadGuard<'static, Self>>> {
|
||||
Ok(Arc::new(
|
||||
LAUNCHER_STATE
|
||||
.get_or_try_init(Self::initialize)
|
||||
.await?
|
||||
.read()
|
||||
.await,
|
||||
))
|
||||
}
|
||||
|
||||
/// Get and lock writing to the current global launcher state (or initalize it)
|
||||
pub async fn get_and_write() -> crate::Result<tokio::sync::RwLockWriteGuard<'static, Self>> {
|
||||
Ok(LAUNCHER_STATE
|
||||
.get_or_try_init(Self::initialize)
|
||||
.await?
|
||||
.write()
|
||||
.await)
|
||||
}
|
||||
|
||||
/// Initializes the core OneLauncher state fully.
|
||||
#[tracing::instrument]
|
||||
#[onelauncher_macros::memory]
|
||||
async fn initialize() -> crate::Result<RwLock<Self>> {
|
||||
let ingress =
|
||||
init_ingress_internal(crate::IngressType::Initialize, 100.0, "initializing state")
|
||||
.await?;
|
||||
|
||||
let settings = Settings::initialize(&Directories::init_settings_file()?).await?;
|
||||
let directories = Directories::initalize(&settings)?;
|
||||
send_ingress(&ingress, 10.0, None).await?;
|
||||
let mut watcher = crate::utils::watcher::initialize_watcher()?;
|
||||
let fetch_semaphore =
|
||||
FetchSemaphore(RwLock::new(Semaphore::new(settings.max_async_fetches)));
|
||||
let io_semaphore = IoSemaphore(RwLock::new(Semaphore::new(
|
||||
settings.max_async_io_operations,
|
||||
)));
|
||||
send_ingress(&ingress, 10.0, None).await?;
|
||||
|
||||
let is_offline = !http::check_internet_connection(3).await;
|
||||
|
||||
// TODO: Make this run in the background, this delays launcher startup by a couple seconds
|
||||
let metadata_in =
|
||||
Metadata::initialize(&directories, !is_offline, &io_semaphore, &fetch_semaphore);
|
||||
let clusters_in = Clusters::initialize(&directories, &mut watcher);
|
||||
let users_in = MinecraftState::initialize(&directories, &io_semaphore);
|
||||
let (metadata, clusters, users) = crate::ingress_join! {
|
||||
Some(&ingress), 70.0, Some("loading core");
|
||||
metadata_in,
|
||||
clusters_in,
|
||||
users_in,
|
||||
}?;
|
||||
|
||||
send_ingress(&ingress, 5.0, Some("loading packages")).await?;
|
||||
let packages = Packages::initialize(&clusters);
|
||||
|
||||
let ingress_processor = IngressProcessor::new();
|
||||
let discord_rpc = DiscordRPC::initialize(is_offline || settings.disable_discord)?;
|
||||
if !settings.disable_discord && !is_offline {
|
||||
let _ = discord_rpc.apply_activity("Idling...", true).await;
|
||||
}
|
||||
|
||||
let processor = Processor::new();
|
||||
|
||||
Self::status_loop();
|
||||
send_ingress(&ingress, 10.0, None).await?;
|
||||
|
||||
Ok::<RwLock<Self>, crate::Error>(RwLock::new(Self {
|
||||
offline: RwLock::new(is_offline),
|
||||
directories,
|
||||
fetch_semaphore,
|
||||
fetch_semaphore_max: RwLock::new(settings.max_async_fetches as u32),
|
||||
io_semaphore,
|
||||
io_semaphore_max: RwLock::new(settings.max_async_io_operations as u32),
|
||||
metadata: RwLock::new(metadata),
|
||||
settings: RwLock::new(settings),
|
||||
users: RwLock::new(users),
|
||||
processor: RwLock::new(processor),
|
||||
oneconfig: RwLock::new(None),
|
||||
credentials: RwLock::new(None),
|
||||
clusters: RwLock::new(clusters),
|
||||
packages: RwLock::new(packages),
|
||||
ingress_processor: RwLock::new(ingress_processor),
|
||||
watcher: RwLock::new(watcher),
|
||||
discord_rpc,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Initalizes a "game loop" which checks for updates and if we are online.
|
||||
pub fn status_loop() {
|
||||
tokio::task::spawn(async {
|
||||
loop {
|
||||
let state = Self::get().await;
|
||||
if let Ok(state) = state {
|
||||
let _ = state.check_status().await;
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Updates all data if we are connected to the internet.
|
||||
pub fn update() {
|
||||
tokio::task::spawn(async {
|
||||
let state = Self::get();
|
||||
if let Ok(state) = state.await {
|
||||
if !*state.offline.read().await {
|
||||
let version_up = Clusters::update_versions();
|
||||
let meta_up = Metadata::update();
|
||||
let package_up = Clusters::update_packages();
|
||||
|
||||
let _ = tokio::join!(version_up, meta_up, package_up);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Synchronizes data that can change outside of our control.
|
||||
#[tracing::instrument]
|
||||
#[onelauncher_macros::memory]
|
||||
pub async fn sync() -> crate::Result<()> {
|
||||
let state = Self::get().await?;
|
||||
let sync_settings = async {
|
||||
let state = Arc::clone(&state);
|
||||
tokio::spawn(async move {
|
||||
let reader = state.settings.read().await;
|
||||
reader.sync(&state.directories.settings_file()).await?;
|
||||
Ok::<_, crate::Error>(())
|
||||
})
|
||||
.await?
|
||||
};
|
||||
|
||||
let sync_clusters = async {
|
||||
let state = Arc::clone(&state);
|
||||
|
||||
tokio::spawn(async move {
|
||||
let clusters = state.clusters.read().await;
|
||||
|
||||
clusters.sync().await?;
|
||||
Ok::<_, crate::Error>(())
|
||||
})
|
||||
.await?
|
||||
};
|
||||
|
||||
tokio::try_join!(sync_settings, sync_clusters)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resets the IO [`Semaphore`] to clear all tasks.
|
||||
/// # This should ONLY be called when we can ensure
|
||||
/// # that there are no current tasks running!!
|
||||
pub async fn reset_io_semaphore(&self) {
|
||||
let settings = self.settings.read().await;
|
||||
let mut io_semaphore = self.io_semaphore.0.write().await;
|
||||
let mut total_permits = self.io_semaphore_max.write().await;
|
||||
// block ALL semaphore permits - this can be destructive
|
||||
let _ = io_semaphore.acquire_many(*total_permits).await;
|
||||
|
||||
io_semaphore.close();
|
||||
*total_permits = settings.max_async_io_operations as u32;
|
||||
*io_semaphore = Semaphore::new(settings.max_async_io_operations);
|
||||
}
|
||||
|
||||
/// Resets the Fetch [`Semaphore`] to clear all tasks.
|
||||
/// # This should ONLY be called when we can ensure
|
||||
/// # that there are no current tasks running!!
|
||||
pub async fn reset_fetch_semaphore(&self) {
|
||||
let settings = self.settings.read().await;
|
||||
let mut fetch_semaphore = self.fetch_semaphore.0.write().await;
|
||||
let mut total_permits = self.fetch_semaphore_max.write().await;
|
||||
// block ALL semaphore permits - this can be destructive
|
||||
let _ = fetch_semaphore.acquire_many(*total_permits).await;
|
||||
|
||||
fetch_semaphore.close();
|
||||
*total_permits = settings.max_async_io_operations as u32;
|
||||
*fetch_semaphore = Semaphore::new(settings.max_async_fetches);
|
||||
}
|
||||
|
||||
/// Checks if we are online used in a loop.
|
||||
pub async fn check_status(&self) -> crate::Result<()> {
|
||||
let is_online = http::check_internet_connection(3).await;
|
||||
let mut offline = self.offline.write().await;
|
||||
if *offline != is_online {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
send_offline(!is_online).await?;
|
||||
*offline = !is_online;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determine if the global state is initalized or not (wrapper)
|
||||
pub fn initalized() -> bool {
|
||||
LAUNCHER_STATE.initialized()
|
||||
}
|
||||
}
|
||||
209
packages/core_old/src/store/settings.rs
Normal file
209
packages/core_old/src/store/settings.rs
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
//! Handles user-inputted settings and global values
|
||||
|
||||
use super::{Directories, JavaVersions};
|
||||
use crate::constants::CURRENT_SETTINGS_FORMAT_VERSION;
|
||||
use onelauncher_utils::io;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// A global settings state for the launcher.
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Settings {
|
||||
/// A `OneLauncher` theme managed by the `OneLauncher` GUI.
|
||||
#[serde(default = "theme_default")]
|
||||
pub theme: String,
|
||||
/// A global browser list view for the `OneLauncher` GUI.
|
||||
#[serde(default)]
|
||||
pub browser_list_view: BrowserListView,
|
||||
/// Does not ask for confirmation when closing the `OneLauncher` GUI
|
||||
#[serde(default)]
|
||||
pub hide_close_prompt: bool,
|
||||
/// Disables animations in the `OneLauncher` GUI
|
||||
#[serde(default)]
|
||||
pub disable_animations: bool,
|
||||
/// A global fullscreen Minecraft state.
|
||||
#[serde(default)]
|
||||
pub force_fullscreen: bool,
|
||||
/// Whether to allow launching the same cluster under the same account.
|
||||
#[serde(default)]
|
||||
pub allow_parallel_running_clusters: bool,
|
||||
/// Whether to launch Feral Gamemode on Linux systems.
|
||||
#[serde(default)]
|
||||
pub enable_gamemode: bool,
|
||||
/// A global default [`Resolution`] for Minecraft.
|
||||
pub resolution: Resolution,
|
||||
/// A global [`JavaVersions`] list and default version.
|
||||
pub java_versions: JavaVersions,
|
||||
/// A global [`Memory`] settings store for Java memory settings.
|
||||
pub memory: Memory,
|
||||
/// Global and default initialization hooks .
|
||||
pub init_hooks: InitHooks,
|
||||
/// Global and default custom Java arguments.
|
||||
pub custom_java_args: Vec<String>,
|
||||
/// Global and default custom environment variables.
|
||||
pub custom_env_args: Vec<(String, String)>,
|
||||
/// Global and default maximum [`Semaphore`] I/O operations.
|
||||
pub max_async_io_operations: usize,
|
||||
/// Global and default maximum [`Semaphore`] HTTP operations.
|
||||
pub max_async_fetches: usize,
|
||||
/// The [`CURRENT_FORMAT_VERSION`] of this settings file.
|
||||
pub version: u32,
|
||||
/// Whether or not to disable Discord IPC.
|
||||
#[serde(default)]
|
||||
pub disable_discord: bool,
|
||||
/// Whether or not to enable a debug mode in the launcher.
|
||||
#[serde(default)]
|
||||
pub debug_mode: bool,
|
||||
/// Whether or not to disable Plausible and Sentry analytics.
|
||||
#[serde(default)]
|
||||
pub disable_analytics: bool,
|
||||
/// The core global config directory stored as a [`PathBuf`].
|
||||
#[serde(default = "Directories::init_settings_dir")]
|
||||
pub config_dir: Option<PathBuf>,
|
||||
/// Whether or not to minimize the launcher upon a game launch.
|
||||
#[serde(default)]
|
||||
pub hide_on_launch: bool,
|
||||
/// Enable/disable custom window decorations.
|
||||
#[serde(default)]
|
||||
pub custom_frame: bool,
|
||||
/// Completed onboarding.
|
||||
#[serde(default)]
|
||||
pub onboarding_completed: bool,
|
||||
}
|
||||
|
||||
fn theme_default() -> String {
|
||||
"dark".to_string()
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
/// Initializes the global settings state.
|
||||
#[tracing::instrument]
|
||||
pub async fn initialize(file: &Path) -> crate::Result<Self> {
|
||||
let mut recovered_corruption = false;
|
||||
|
||||
let settings = if file.exists() {
|
||||
let read_settings = io::read(&file)
|
||||
.await
|
||||
.map_err(|err| anyhow::anyhow!("error reading settings file: {0}", err).into())
|
||||
.and_then(|it| serde_json::from_slice::<Self>(&it).map_err(crate::Error::from));
|
||||
|
||||
if let Err(ref err) = read_settings {
|
||||
tracing::error!("failed to load settings file: {err}.");
|
||||
let backup = file.with_extension("json.bak");
|
||||
tracing::error!(
|
||||
"corrupted settings will be backed up as {}, and a new one will be created",
|
||||
backup.display()
|
||||
);
|
||||
let _ = io::rename(file, backup).await;
|
||||
recovered_corruption = true;
|
||||
}
|
||||
|
||||
read_settings.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(settings) = settings {
|
||||
Ok(settings)
|
||||
} else {
|
||||
let settings = Self {
|
||||
theme: "dark".to_string(),
|
||||
browser_list_view: BrowserListView::Grid,
|
||||
hide_close_prompt: true,
|
||||
disable_animations: false,
|
||||
force_fullscreen: false,
|
||||
allow_parallel_running_clusters: false,
|
||||
#[cfg(target_os = "linux")]
|
||||
enable_gamemode: true,
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
enable_gamemode: false,
|
||||
resolution: Resolution::default(),
|
||||
java_versions: JavaVersions::new(),
|
||||
memory: Memory::default(),
|
||||
init_hooks: InitHooks::default(),
|
||||
custom_env_args: Vec::new(),
|
||||
custom_java_args: Vec::new(),
|
||||
max_async_fetches: 10,
|
||||
max_async_io_operations: 10,
|
||||
version: CURRENT_SETTINGS_FORMAT_VERSION,
|
||||
disable_analytics: false,
|
||||
disable_discord: false,
|
||||
debug_mode: false,
|
||||
config_dir: Directories::init_settings_dir(),
|
||||
hide_on_launch: false,
|
||||
custom_frame: true,
|
||||
onboarding_completed: false,
|
||||
};
|
||||
|
||||
if recovered_corruption {
|
||||
settings.sync(file).await?;
|
||||
}
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronizes the current settings from a file.
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn sync(&self, to: &Path) -> crate::Result<()> {
|
||||
io::write(to, serde_json::to_vec(self)?).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// `OneLauncher` browser list type.
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BrowserListView {
|
||||
#[default]
|
||||
Grid,
|
||||
List,
|
||||
}
|
||||
|
||||
/// Global memory settings across all clusters.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
pub struct Memory {
|
||||
/// Maximum amount of Java memory available globally.
|
||||
pub maximum: u32,
|
||||
/// Minimum amount of Java memory available globally.
|
||||
pub minimum: u32,
|
||||
}
|
||||
|
||||
impl Default for Memory {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
maximum: 2048,
|
||||
minimum: 256,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Global Minecraft resolution.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
pub struct Resolution(pub u16, pub u16);
|
||||
|
||||
impl Default for Resolution {
|
||||
fn default() -> Self {
|
||||
Self(854, 480)
|
||||
}
|
||||
}
|
||||
|
||||
/// Global initialization hooks for all Minecraft clusters.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[serde(default)]
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
pub struct InitHooks {
|
||||
/// Pre-launch hook.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pre: Option<String>,
|
||||
/// Wrapper hook for the runtime of the game instance.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub wrapper: Option<String>,
|
||||
/// Post-launch hook.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub post: Option<String>,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue