Misc improvements and fixes (#109)

* now utilizing tracing better

* better tracing

* fix mac vs pc oppositional env var issue

* modified loading package

* added droppable loadingbarid that sends completion message

* loading bar

* regressed bug on mac

* fixed non-updated loading bar on playground

* Loading bar improvements

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
Wyatt Verchere 2023-05-08 12:14:08 -07:00 committed by GitHub
parent c79d5c32a6
commit 65c1942037
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 726 additions and 294 deletions

154
Cargo.lock generated
View File

@ -50,6 +50,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.70" version = "1.0.70"
@ -468,7 +477,7 @@ dependencies = [
"indenter", "indenter",
"once_cell", "once_cell",
"owo-colors", "owo-colors",
"tracing-error", "tracing-error 0.2.0",
] ]
[[package]] [[package]]
@ -480,7 +489,7 @@ dependencies = [
"once_cell", "once_cell",
"owo-colors", "owo-colors",
"tracing-core", "tracing-core",
"tracing-error", "tracing-error 0.2.0",
] ]
[[package]] [[package]]
@ -1001,9 +1010,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.27" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@ -1011,9 +1020,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.27" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
@ -1028,38 +1037,38 @@ dependencies = [
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.27" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.27" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.109", "syn 2.0.11",
] ]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.27" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.27" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.27" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -1608,6 +1617,18 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "indicatif"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729"
dependencies = [
"console",
"number_prefix",
"portable-atomic",
"unicode-width",
]
[[package]] [[package]]
name = "infer" name = "infer"
version = "0.7.0" version = "0.7.0"
@ -1820,7 +1841,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber 0.3.16",
] ]
[[package]] [[package]]
@ -1863,6 +1884,15 @@ dependencies = [
"tendril", "tendril",
] ]
[[package]]
name = "matchers"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
dependencies = [
"regex-automata",
]
[[package]] [[package]]
name = "matchers" name = "matchers"
version = "0.1.0" version = "0.1.0"
@ -2077,6 +2107,12 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "number_prefix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "objc" name = "objc"
version = "0.2.7" version = "0.2.7"
@ -2458,6 +2494,12 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "portable-atomic"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26f6a7b87c2e435a3241addceeeff740ff8b7e76b74c13bf9acb17fa454ea00b"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.17"
@ -2699,10 +2741,12 @@ dependencies = [
"serde_urlencoded", "serde_urlencoded",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tokio-util",
"tower-service", "tower-service",
"url", "url",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-streams",
"web-sys", "web-sys",
"winreg 0.10.1", "winreg 0.10.1",
] ]
@ -3516,8 +3560,8 @@ dependencies = [
"dirs", "dirs",
"dunce", "dunce",
"futures", "futures",
"indicatif",
"lazy_static", "lazy_static",
"log",
"paste", "paste",
"regex", "regex",
"reqwest", "reqwest",
@ -3532,7 +3576,8 @@ dependencies = [
"tokio-stream", "tokio-stream",
"toml 0.7.3", "toml 0.7.3",
"tracing", "tracing",
"tracing-error", "tracing-error 0.1.2",
"tracing-subscriber 0.2.25",
"url", "url",
"uuid 1.3.0", "uuid 1.3.0",
"winreg 0.11.0", "winreg 0.11.0",
@ -3557,9 +3602,9 @@ dependencies = [
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tracing", "tracing",
"tracing-error", "tracing-error 0.2.0",
"tracing-futures", "tracing-futures",
"tracing-subscriber", "tracing-subscriber 0.3.16",
"url", "url",
"uuid 1.3.0", "uuid 1.3.0",
"webbrowser", "webbrowser",
@ -3582,6 +3627,9 @@ dependencies = [
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tracing",
"tracing-error 0.1.2",
"tracing-subscriber 0.2.25",
"url", "url",
"uuid 1.3.0", "uuid 1.3.0",
] ]
@ -3600,6 +3648,9 @@ dependencies = [
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tracing",
"tracing-error 0.1.2",
"tracing-subscriber 0.2.25",
"url", "url",
"uuid 1.3.0", "uuid 1.3.0",
"webbrowser", "webbrowser",
@ -3842,6 +3893,16 @@ dependencies = [
"valuable", "valuable",
] ]
[[package]]
name = "tracing-error"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24"
dependencies = [
"tracing",
"tracing-subscriber 0.2.25",
]
[[package]] [[package]]
name = "tracing-error" name = "tracing-error"
version = "0.2.0" version = "0.2.0"
@ -3849,7 +3910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber 0.3.16",
] ]
[[package]] [[package]]
@ -3873,13 +3934,45 @@ dependencies = [
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-serde"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
dependencies = [
"serde",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
dependencies = [
"ansi_term",
"chrono",
"lazy_static",
"matchers 0.0.1",
"regex",
"serde",
"serde_json",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
"tracing-serde",
]
[[package]] [[package]]
name = "tracing-subscriber" name = "tracing-subscriber"
version = "0.3.16" version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
dependencies = [ dependencies = [
"matchers", "matchers 0.1.0",
"nu-ansi-term", "nu-ansi-term",
"once_cell", "once_cell",
"regex", "regex",
@ -4139,6 +4232,19 @@ version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "wasm-streams"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.61" version = "0.3.61"

View File

@ -22,19 +22,21 @@ chrono = { version = "0.4.19", features = ["serde"] }
daedalus = { version = "0.1.20" } daedalus = { version = "0.1.20" }
dirs = "4.0" dirs = "4.0"
log = "0.4.14"
regex = "1.5" regex = "1.5"
sys-info = "0.9.0" sys-info = "0.9.0"
thiserror = "1.0" thiserror = "1.0"
tracing = "0.1" tracing = "0.1.37"
tracing-error = "0.2" tracing-subscriber = "0.2"
tracing-error = "0.1"
paste = { version = "1.0"}
tauri = { version = "1.2", optional = true} tauri = { version = "1.2", optional = true}
paste = { version = "1.0", optional = true} indicatif = { version = "0.17.3", optional = true }
async-tungstenite = { version = "0.20.0", features = ["tokio-runtime", "tokio-native-tls"] } async-tungstenite = { version = "0.20.0", features = ["tokio-runtime", "tokio-native-tls"] }
futures = "0.3" futures = "0.3"
reqwest = { version = "0.11", features = ["json"] } reqwest = { version = "0.11", features = ["json", "stream"] }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tokio-stream = { version = "0.1", features = ["fs"] } tokio-stream = { version = "0.1", features = ["fs"] }
@ -45,4 +47,5 @@ dunce = "1.0.3"
winreg = "0.11.0" winreg = "0.11.0"
[features] [features]
tauri = ["dep:tauri", "dep:paste"] tauri = ["dep:tauri"]
cli = ["dep:indicatif"]

View File

@ -46,7 +46,7 @@ pub async fn authenticate(
)) ))
})?; })?;
let credentials = flow.extract_credentials(&state.io_semaphore).await?; let credentials = flow.extract_credentials(&state.fetch_semaphore).await?;
users.insert(&credentials).await?; users.insert(&credentials).await?;
if state.settings.read().await.default_user.is_none() { if state.settings.read().await.default_user.is_none() {
@ -64,7 +64,7 @@ pub async fn refresh(user: uuid::Uuid) -> crate::Result<Credentials> {
let state = State::get().await?; let state = State::get().await?;
let mut users = state.users.write().await; let mut users = state.users.write().await;
let io_sempahore = &state.io_semaphore; let fetch_semaphore = &state.fetch_semaphore;
futures::future::ready(users.get(user).ok_or_else(|| { futures::future::ready(users.get(user).ok_or_else(|| {
crate::ErrorKind::OtherError(format!( crate::ErrorKind::OtherError(format!(
"Tried to refresh nonexistent user with ID {user}" "Tried to refresh nonexistent user with ID {user}"
@ -73,7 +73,8 @@ pub async fn refresh(user: uuid::Uuid) -> crate::Result<Credentials> {
})) }))
.and_then(|mut credentials| async move { .and_then(|mut credentials| async move {
if chrono::offset::Utc::now() > credentials.expires { if chrono::offset::Utc::now() > credentials.expires {
inner::refresh_credentials(&mut credentials, io_sempahore).await?; inner::refresh_credentials(&mut credentials, fetch_semaphore)
.await?;
} }
users.insert(&credentials).await?; users.insert(&credentials).await?;
Ok(credentials) Ok(credentials)

View File

@ -61,6 +61,7 @@ pub async fn get_optimal_jre_key(profile: &Profile) -> crate::Result<String> {
version, version,
profile.metadata.loader_version.as_ref(), profile.metadata.loader_version.as_ref(),
None, None,
None,
) )
.await?; .await?;
let optimal_key = match version_info let optimal_key = match version_info

View File

@ -1,12 +1,13 @@
use crate::config::MODRINTH_API_URL; use crate::config::MODRINTH_API_URL;
use crate::data::ModLoader; use crate::data::ModLoader;
use crate::event::emit::{ use crate::event::emit::{
emit_loading, init_loading, loading_try_for_each_concurrent, emit_loading, init_loading, init_or_edit_loading,
loading_try_for_each_concurrent,
}; };
use crate::event::LoadingBarType; use crate::event::{LoadingBarId, LoadingBarType};
use crate::state::{LinkedData, ModrinthProject, ModrinthVersion, SideType}; use crate::state::{LinkedData, ModrinthProject, ModrinthVersion, SideType};
use crate::util::fetch::{ use crate::util::fetch::{
fetch, fetch_json, fetch_mirrors, write, write_cached_icon, fetch, fetch_advanced, fetch_json, fetch_mirrors, write, write_cached_icon,
}; };
use crate::State; use crate::State;
use async_zip::tokio::read::seek::ZipFileReader; use async_zip::tokio::read::seek::ZipFileReader;
@ -75,17 +76,30 @@ enum PackDependency {
pub async fn install_pack_from_version_id( pub async fn install_pack_from_version_id(
version_id: String, version_id: String,
title: Option<String>,
) -> crate::Result<PathBuf> { ) -> crate::Result<PathBuf> {
let state = State::get().await?; let state = State::get().await?;
let loading_bar = init_loading(
LoadingBarType::PackFileDownload {
pack_name: title,
pack_version: version_id.clone(),
},
100.0,
"Downloading pack file",
)
.await?;
emit_loading(&loading_bar, 0.0, Some("Fetching version")).await?;
let version: ModrinthVersion = fetch_json( let version: ModrinthVersion = fetch_json(
Method::GET, Method::GET,
&format!("{}version/{}", MODRINTH_API_URL, version_id), &format!("{}version/{}", MODRINTH_API_URL, version_id),
None, None,
None, None,
&state.io_semaphore, &state.fetch_semaphore,
) )
.await?; .await?;
emit_loading(&loading_bar, 10.0, None).await?;
let (url, hash) = let (url, hash) =
if let Some(file) = version.files.iter().find(|x| x.primary) { if let Some(file) = version.files.iter().find(|x| x.primary) {
@ -102,20 +116,31 @@ pub async fn install_pack_from_version_id(
) )
})?; })?;
let file = fetch(&url, hash.map(|x| &**x), &state.io_semaphore).await?; let file = fetch_advanced(
Method::GET,
&url,
hash.map(|x| &**x),
None,
None,
Some((&loading_bar, 70.0)),
&state.fetch_semaphore,
)
.await?;
emit_loading(&loading_bar, 0.0, Some("Fetching project metadata")).await?;
let project: ModrinthProject = fetch_json( let project: ModrinthProject = fetch_json(
Method::GET, Method::GET,
&format!("{}project/{}", MODRINTH_API_URL, version.project_id), &format!("{}project/{}", MODRINTH_API_URL, version.project_id),
None, None,
None, None,
&state.io_semaphore, &state.fetch_semaphore,
) )
.await?; .await?;
emit_loading(&loading_bar, 10.0, Some("Retrieving icon")).await?;
let icon = if let Some(icon_url) = project.icon_url { let icon = if let Some(icon_url) = project.icon_url {
let state = State::get().await?; let state = State::get().await?;
let icon_bytes = fetch(&icon_url, None, &state.io_semaphore).await?; let icon_bytes = fetch(&icon_url, None, &state.fetch_semaphore).await?;
let filename = icon_url.rsplit('/').next(); let filename = icon_url.rsplit('/').next();
@ -135,6 +160,7 @@ pub async fn install_pack_from_version_id(
} else { } else {
None None
}; };
emit_loading(&loading_bar, 10.0, None).await?;
install_pack( install_pack(
file, file,
@ -142,6 +168,7 @@ pub async fn install_pack_from_version_id(
Some(project.title), Some(project.title),
Some(version.project_id), Some(version.project_id),
Some(version.id), Some(version.id),
Some(loading_bar),
) )
.await .await
} }
@ -149,7 +176,7 @@ pub async fn install_pack_from_version_id(
pub async fn install_pack_from_file(path: PathBuf) -> crate::Result<PathBuf> { pub async fn install_pack_from_file(path: PathBuf) -> crate::Result<PathBuf> {
let file = fs::read(path).await?; let file = fs::read(path).await?;
install_pack(bytes::Bytes::from(file), None, None, None, None).await install_pack(bytes::Bytes::from(file), None, None, None, None, None).await
} }
async fn install_pack( async fn install_pack(
@ -158,6 +185,7 @@ async fn install_pack(
override_title: Option<String>, override_title: Option<String>,
project_id: Option<String>, project_id: Option<String>,
version_id: Option<String>, version_id: Option<String>,
existing_loading_bar: Option<LoadingBarId>,
) -> crate::Result<PathBuf> { ) -> crate::Result<PathBuf> {
let state = &State::get().await?; let state = &State::get().await?;
@ -242,14 +270,15 @@ async fn install_pack(
.await?; .await?;
let profile = profile_raw.clone(); let profile = profile_raw.clone();
let result = async { let result = async {
let loading_bar = init_loading( let loading_bar = init_or_edit_loading(
existing_loading_bar,
LoadingBarType::PackDownload { LoadingBarType::PackDownload {
pack_name: pack.name.clone(), pack_name: pack.name.clone(),
pack_id: project_id, pack_id: project_id,
pack_version: version_id, pack_version: version_id,
}, },
100.0, 100.0,
"Downloading modpack...", "Downloading modpack",
) )
.await?; .await?;
@ -260,7 +289,7 @@ async fn install_pack(
.map(Ok::<PackFile, crate::Error>), .map(Ok::<PackFile, crate::Error>),
None, None,
Some(&loading_bar), Some(&loading_bar),
80.0, 70.0,
num_files, num_files,
None, None,
|project| { |project| {
@ -287,7 +316,7 @@ async fn install_pack(
.hashes .hashes
.get(&PackFileHash::Sha1) .get(&PackFileHash::Sha1)
.map(|x| &**x), .map(|x| &**x),
&state.io_semaphore, &state.fetch_semaphore,
) )
.await?; .await?;
@ -365,11 +394,11 @@ async fn install_pack(
.await .await
}; };
emit_loading(&loading_bar, 0.05, Some("Extracting overrides")) emit_loading(&loading_bar, 0.0, Some("Extracting overrides"))
.await?; .await?;
extract_overrides("overrides".to_string()).await?; extract_overrides("overrides".to_string()).await?;
extract_overrides("client_overrides".to_string()).await?; extract_overrides("client_overrides".to_string()).await?;
emit_loading(&loading_bar, 0.1, Some("Done extacting overrides")) emit_loading(&loading_bar, 29.9, Some("Done extacting overrides"))
.await?; .await?;
if let Some(profile) = crate::api::profile::get(&profile).await? { if let Some(profile) = crate::api::profile::get(&profile).await? {
@ -380,13 +409,6 @@ async fn install_pack(
Some(loading_bar) Some(loading_bar)
), ),
)?; )?;
} else {
emit_loading(
&loading_bar,
0.1,
Some("Done extacting overrides"),
)
.await?;
} }
Ok::<PathBuf, crate::Error>(profile) Ok::<PathBuf, crate::Error>(profile)

View File

@ -148,7 +148,7 @@ pub async fn update_all(profile_path: &Path) -> crate::Result<()> {
profile_name: profile.metadata.name.clone(), profile_name: profile.metadata.name.clone(),
}, },
100.0, 100.0,
"Updating profile...", "Updating profile",
) )
.await?; .await?;
@ -159,7 +159,7 @@ pub async fn update_all(profile_path: &Path) -> crate::Result<()> {
None, None,
Some(&loading_bar), Some(&loading_bar),
100.0, 100.0,
profile.projects.len(), profile.projects.keys().len(),
None, None,
|project| update_project(profile_path, project, Some(true)), |project| update_project(profile_path, project, Some(true)),
) )
@ -344,7 +344,7 @@ pub async fn remove_project(
} }
/// Run Minecraft using a profile and the default credentials, logged in credentials, /// Run Minecraft using a profile and the default credentials, logged in credentials,
/// failing with an error if no credentials are available /// failing with an error if no credentials are available
#[tracing::instrument(skip_all)] #[tracing::instrument]
pub async fn run(path: &Path) -> crate::Result<Arc<RwLock<MinecraftChild>>> { pub async fn run(path: &Path) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
let state = State::get().await?; let state = State::get().await?;
@ -367,7 +367,7 @@ pub async fn run(path: &Path) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
/// Run Minecraft using a profile, and credentials for authentication /// Run Minecraft using a profile, and credentials for authentication
/// Returns Arc pointer to RwLock to Child /// Returns Arc pointer to RwLock to Child
#[tracing::instrument(skip_all)] #[tracing::instrument]
pub async fn run_credentials( pub async fn run_credentials(
path: &Path, path: &Path,
credentials: &auth::Credentials, credentials: &auth::Credentials,
@ -398,6 +398,7 @@ pub async fn run_credentials(
version, version,
profile.metadata.loader_version.as_ref(), profile.metadata.loader_version.as_ref(),
None, None,
None,
) )
.await?; .await?;
let pre_launch_hooks = let pre_launch_hooks =

View File

@ -1,4 +1,5 @@
//! Theseus profile management interface //! Theseus profile management interface
use crate::event::emit::emit_warning;
use crate::state::LinkedData; use crate::state::LinkedData;
use crate::{ use crate::{
event::{emit::emit_profile, ProfilePayloadType}, event::{emit::emit_profile, ProfilePayloadType},
@ -15,6 +16,7 @@ use futures::prelude::*;
use std::path::PathBuf; use std::path::PathBuf;
use tokio::fs; use tokio::fs;
use tokio_stream::wrappers::ReadDirStream; use tokio_stream::wrappers::ReadDirStream;
use tracing::{info, trace};
use uuid::Uuid; use uuid::Uuid;
const DEFAULT_NAME: &str = "Untitled Instance"; const DEFAULT_NAME: &str = "Untitled Instance";
@ -47,6 +49,7 @@ pub async fn profile_create(
linked_data: Option<LinkedData>, // the linked project ID (mainly for modpacks)- used for updating linked_data: Option<LinkedData>, // the linked project ID (mainly for modpacks)- used for updating
skip_install_profile: Option<bool>, skip_install_profile: Option<bool>,
) -> crate::Result<PathBuf> { ) -> crate::Result<PathBuf> {
trace!("Creating new profile. {}", name);
let state = State::get().await?; let state = State::get().await?;
let metadata = state.metadata.read().await; let metadata = state.metadata.read().await;
@ -74,7 +77,7 @@ pub async fn profile_create(
fs::create_dir_all(&path).await?; fs::create_dir_all(&path).await?;
} }
println!( info!(
"Creating profile at path {}", "Creating profile at path {}",
&canonicalize(&path)?.display() &canonicalize(&path)?.display()
); );
@ -173,7 +176,7 @@ pub async fn profile_create(
extra_arguments: None, extra_arguments: None,
}); });
} else { } else {
println!("Could not detect optimal JRE: {optimal_version_key}, falling back to system default."); emit_warning(&format!("Could not detect optimal JRE: {optimal_version_key}, falling back to system default.")).await?;
} }
emit_profile( emit_profile(

View File

@ -19,8 +19,22 @@ pub async fn get() -> crate::Result<Settings> {
pub async fn set(settings: Settings) -> crate::Result<()> { pub async fn set(settings: Settings) -> crate::Result<()> {
let state = State::get().await?; let state = State::get().await?;
// Replaces the settings struct in the RwLock with the passed argument // Replaces the settings struct in the RwLock with the passed argument
let (reset_io, reset_fetch) = async {
let read = state.settings.read().await;
(
settings.max_concurrent_writes != read.max_concurrent_writes,
settings.max_concurrent_downloads != read.max_concurrent_downloads,
)
}
.await;
*state.settings.write().await = settings; *state.settings.write().await = settings;
state.reset_semaphore().await; // reset semaphore to new max if reset_io {
state.reset_io_semaphore().await;
}
if reset_fetch {
state.reset_fetch_semaphore().await;
}
State::sync().await?; State::sync().await?;
Ok(()) Ok(())
} }

View File

@ -1,9 +1,11 @@
use super::LoadingBarId;
use crate::event::{ use crate::event::{
EventError, LoadingBar, LoadingBarType, ProcessPayloadType, EventError, LoadingBar, LoadingBarType, ProcessPayloadType,
ProfilePayloadType, ProfilePayloadType,
}; };
use futures::prelude::*; use futures::prelude::*;
use std::path::PathBuf; use std::path::PathBuf;
use tracing::warn;
#[cfg(feature = "tauri")] #[cfg(feature = "tauri")]
use crate::event::{ use crate::event::{
@ -13,6 +15,9 @@ use crate::event::{
use tauri::Manager; use tauri::Manager;
use uuid::Uuid; use uuid::Uuid;
#[cfg(feature = "cli")]
const CLI_PROGRESS_BAR_TOTAL: u64 = 1000;
/* /*
Events are a way we can communciate with the Tauri frontend from the Rust backend. Events are a way we can communciate with the Tauri frontend from the Rust backend.
We include a feature flag for Tauri, so that we can compile this code without Tauri. We include a feature flag for Tauri, so that we can compile this code without Tauri.
@ -45,18 +50,34 @@ pub async fn init_loading(
bar_type: LoadingBarType, bar_type: LoadingBarType,
total: f64, total: f64,
title: &str, title: &str,
) -> crate::Result<Uuid> { ) -> crate::Result<LoadingBarId> {
let event_state = crate::EventState::get().await?; let event_state = crate::EventState::get().await?;
let key = Uuid::new_v4(); let key = LoadingBarId(Uuid::new_v4());
event_state.loading_bars.write().await.insert( event_state.loading_bars.write().await.insert(
key, key.0,
LoadingBar { LoadingBar {
loading_bar_id: key, loading_bar_uuid: key.0,
message: title.to_string(), message: title.to_string(),
total, total,
current: 0.0, current: 0.0,
bar_type, bar_type,
#[cfg(feature = "cli")]
cli_progress_bar: {
let pb = indicatif::ProgressBar::new(CLI_PROGRESS_BAR_TOTAL);
pb.set_position(0);
pb.set_style(
indicatif::ProgressStyle::default_bar()
.template(
"{spinner:.green} [{elapsed_precise}] [{bar:.lime/green}] {pos}/{len} {msg}",
).unwrap()
.progress_chars("#>-"),
);
//pb.set_message(title);
pb
},
}, },
); );
// attempt an initial loading_emit event to the frontend // attempt an initial loading_emit event to the frontend
@ -65,13 +86,13 @@ pub async fn init_loading(
} }
pub async fn init_or_edit_loading( pub async fn init_or_edit_loading(
id: Option<Uuid>, id: Option<LoadingBarId>,
bar_type: LoadingBarType, bar_type: LoadingBarType,
total: f64, total: f64,
title: &str, title: &str,
) -> crate::Result<Uuid> { ) -> crate::Result<LoadingBarId> {
if let Some(id) = id { if let Some(id) = id {
edit_loading(id, bar_type, total, title).await?; edit_loading(&id, bar_type, total, title).await?;
Ok(id) Ok(id)
} else { } else {
@ -80,21 +101,27 @@ pub async fn init_or_edit_loading(
} }
// Edits a loading bar's type // Edits a loading bar's type
// This also resets the bar's current progress to 0
pub async fn edit_loading( pub async fn edit_loading(
id: Uuid, id: &LoadingBarId,
bar_type: LoadingBarType, bar_type: LoadingBarType,
total: f64, total: f64,
title: &str, title: &str,
) -> crate::Result<()> { ) -> crate::Result<()> {
let event_state = crate::EventState::get().await?; let event_state = crate::EventState::get().await?;
if let Some(bar) = event_state.loading_bars.write().await.get_mut(&id) { if let Some(bar) = event_state.loading_bars.write().await.get_mut(&id.0) {
bar.bar_type = bar_type; bar.bar_type = bar_type;
bar.total = total; bar.total = total;
bar.message = title.to_string(); bar.message = title.to_string();
bar.current = 0.0;
#[cfg(feature = "cli")]
{
bar.cli_progress_bar.reset(); // indicatif::ProgressBar::new(CLI_PROGRESS_BAR_TOTAL as u64);
}
}; };
emit_loading(&id, 0.0, None).await?; emit_loading(id, 0.0, None).await?;
Ok(()) Ok(())
} }
@ -104,18 +131,19 @@ pub async fn edit_loading(
// message is the message to display on the loading bar- if None, use the loading bar's default one // message is the message to display on the loading bar- if None, use the loading bar's default one
// By convention, fraction is the fraction of the progress bar that is filled // By convention, fraction is the fraction of the progress bar that is filled
#[allow(unused_variables)] #[allow(unused_variables)]
#[tracing::instrument(level = "debug")]
pub async fn emit_loading( pub async fn emit_loading(
key: &Uuid, key: &LoadingBarId,
increment_frac: f64, increment_frac: f64,
message: Option<&str>, message: Option<&str>,
) -> crate::Result<()> { ) -> crate::Result<()> {
let event_state = crate::EventState::get().await?; let event_state = crate::EventState::get().await?;
let mut loading_bar = event_state.loading_bars.write().await; let mut loading_bar = event_state.loading_bars.write().await;
let loading_bar = match loading_bar.get_mut(key) { let loading_bar = match loading_bar.get_mut(&key.0) {
Some(f) => f, Some(f) => f,
None => { None => {
return Err(EventError::NoLoadingBar(*key).into()); return Err(EventError::NoLoadingBar(key.0).into());
} }
}; };
@ -128,6 +156,22 @@ pub async fn emit_loading(
} else { } else {
Some(display_frac) Some(display_frac)
}; };
// Emit event to indicatif progress bar
#[cfg(feature = "cli")]
{
loading_bar.cli_progress_bar.set_message(
message
.map(|x| x.to_string())
.unwrap_or(loading_bar.message.clone()),
);
loading_bar.cli_progress_bar.set_position(
((loading_bar.current / loading_bar.total)
* CLI_PROGRESS_BAR_TOTAL as f64)
.round() as u64,
);
}
// Emit event to tauri // Emit event to tauri
#[cfg(feature = "tauri")] #[cfg(feature = "tauri")]
event_state event_state
@ -138,7 +182,7 @@ pub async fn emit_loading(
fraction: display_frac, fraction: display_frac,
message: message.unwrap_or(&loading_bar.message).to_string(), message: message.unwrap_or(&loading_bar.message).to_string(),
event: loading_bar.bar_type.clone(), event: loading_bar.bar_type.clone(),
loader_uuid: loading_bar.loading_bar_id, loader_uuid: loading_bar.loading_bar_uuid,
}, },
) )
.map_err(EventError::from)?; .map_err(EventError::from)?;
@ -162,6 +206,7 @@ pub async fn emit_warning(message: &str) -> crate::Result<()> {
) )
.map_err(EventError::from)?; .map_err(EventError::from)?;
} }
warn!("{}", message);
Ok(()) Ok(())
} }
@ -235,7 +280,6 @@ macro_rules! count {
() => (0usize); () => (0usize);
( $x:tt $($xs:tt)* ) => (1usize + $crate::count!($($xs)*)); ( $x:tt $($xs:tt)* ) => (1usize + $crate::count!($($xs)*));
} }
#[cfg(feature = "tauri")]
#[macro_export] #[macro_export]
macro_rules! loading_join { macro_rules! loading_join {
($key:expr, $total:expr, $message:expr; $($task:expr $(,)?)+) => { ($key:expr, $total:expr, $message:expr; $($task:expr $(,)?)+) => {
@ -272,24 +316,16 @@ macro_rules! loading_join {
}; };
} }
#[cfg(not(feature = "tauri"))]
#[macro_export]
macro_rules! loading_join {
($start:expr, $end:expr, $message:expr; $($future:expr $(,)?)+) => {{
tokio::try_join!($($future),+)
}};
}
// A drop in replacement to try_for_each_concurrent that emits loading events as it goes // A drop in replacement to try_for_each_concurrent that emits loading events as it goes
// Key is the key to use for which loading bar- a LoadingBarId. If None, does nothing // Key is the key to use for which loading bar- a LoadingBarId. If None, does nothing
// Total is the total amount of progress that the loading bar should take up by all futures in this (will be split evenly amongst them). // Total is the total amount of progress that the loading bar should take up by all futures in this (will be split evenly amongst them).
// If message is Some(t) you will overwrite this loading bar's message with a custom one // If message is Some(t) you will overwrite this loading bar's message with a custom one
// num_futs is the number of futures that will be run, which is needed as we allow Iterator to be passed in, which doesn't have a size // num_futs is the number of futures that will be run, which is needed as we allow Iterator to be passed in, which doesn't have a size
#[cfg(feature = "tauri")]
pub async fn loading_try_for_each_concurrent<I, F, Fut, T>( pub async fn loading_try_for_each_concurrent<I, F, Fut, T>(
stream: I, stream: I,
limit: Option<usize>, limit: Option<usize>,
key: Option<&Uuid>, key: Option<&LoadingBarId>,
total: f64, total: f64,
num_futs: usize, // num is in here as we allow Iterator to be passed in, which doesn't have a size num_futs: usize, // num is in here as we allow Iterator to be passed in, which doesn't have a size
message: Option<&str>, message: Option<&str>,
@ -316,31 +352,3 @@ where
}) })
.await .await
} }
#[cfg(not(feature = "tauri"))]
pub async fn loading_try_for_each_concurrent<I, F, Fut, T>(
stream: I,
limit: Option<usize>,
_key: Option<&Uuid>,
_total: f64,
_num_futs: usize, // num is in here as we allow Iterator to be passed in, which doesn't have a size
_message: Option<&str>,
f: F,
) -> crate::Result<()>
where
I: futures::TryStreamExt<Error = crate::Error> + TryStream<Ok = T>,
F: FnMut(T) -> Fut + Send,
Fut: Future<Output = crate::Result<()>> + Send,
T: Send,
{
let mut f = f;
stream
.try_for_each_concurrent(limit, |item| {
let f = f(item);
async move {
f.await?;
Ok(())
}
})
.await
}

View File

@ -48,11 +48,18 @@ impl EventState {
Ok(EVENT_STATE.get().ok_or(EventError::NotInitialized)?.clone()) Ok(EVENT_STATE.get().ok_or(EventError::NotInitialized)?.clone())
} }
// Values provided should not be used directly, as they are clones and are not guaranteed to be up-to-date
pub async fn list_progress_bars() -> crate::Result<HashMap<Uuid, LoadingBar>> pub async fn list_progress_bars() -> crate::Result<HashMap<Uuid, LoadingBar>>
{ {
let value = Self::get().await?; let value = Self::get().await?;
let read = value.loading_bars.read().await; let read = value.loading_bars.read().await;
Ok(read.clone())
let mut display_list: HashMap<Uuid, LoadingBar> = HashMap::new();
for (uuid, loading_bar) in read.iter() {
display_list.insert(*uuid, loading_bar.clone());
}
Ok(display_list)
} }
// Initialization requires no app handle in non-tauri mode, so we can just use the same function // Initialization requires no app handle in non-tauri mode, so we can just use the same function
@ -64,16 +71,84 @@ impl EventState {
#[derive(Serialize, Debug, Clone)] #[derive(Serialize, Debug, Clone)]
pub struct LoadingBar { pub struct LoadingBar {
pub loading_bar_id: Uuid, // loading_bar_uuid not be used directly by external functions as it may not reflect the current state of the loading bar/hashmap
pub loading_bar_uuid: Uuid,
pub message: String, pub message: String,
pub total: f64, pub total: f64,
pub current: f64, pub current: f64,
pub bar_type: LoadingBarType, pub bar_type: LoadingBarType,
#[cfg(feature = "cli")]
#[serde(skip)]
pub cli_progress_bar: indicatif::ProgressBar,
}
#[derive(Serialize, Debug)]
pub struct LoadingBarId(Uuid);
// When Loading bar id is dropped, we should remove it from the hashmap
impl Drop for LoadingBarId {
fn drop(&mut self) {
let loader_uuid = self.0;
let _event = LoadingBarType::StateInit;
let _message = "finished".to_string();
tokio::spawn(async move {
if let Ok(event_state) = crate::EventState::get().await {
{
let mut bars = event_state.loading_bars.write().await;
bars.remove(&loader_uuid);
}
}
});
}
}
// When Loading bar is dropped, should attempt to throw out one last event to indicate that the loading bar is done
#[cfg(feature = "tauri")]
impl Drop for LoadingBar {
fn drop(&mut self) {
let loader_uuid = self.loading_bar_uuid;
let event = self.bar_type.clone();
let fraction = self.current / self.total;
let cli_progress_bar = self.cli_progress_bar.clone();
tokio::spawn(async move {
#[cfg(feature = "tauri")]
{
use tauri::Manager;
if let Ok(event_state) = crate::EventState::get().await {
let _ = event_state.app.emit_all(
"loading",
LoadingPayload {
fraction: None,
message: "Completed".to_string(),
event,
loader_uuid,
},
);
tracing::debug!(
"Exited at {fraction} for loading bar: {:?}",
loader_uuid
);
}
}
// Emit event to indicatif progress bar arc
#[cfg(feature = "cli")]
{
cli_progress_bar.finish();
}
});
}
} }
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)] #[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum LoadingBarType { pub enum LoadingBarType {
StateInit, StateInit,
PackFileDownload {
pack_name: Option<String>,
pack_version: String,
},
PackDownload { PackDownload {
pack_name: String, pack_name: String,
pack_id: Option<String>, pack_id: Option<String>,
@ -124,6 +199,7 @@ pub struct ProfilePayload {
pub event: ProfilePayloadType, pub event: ProfilePayloadType,
} }
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum ProfilePayloadType { pub enum ProfilePayloadType {
Created, Created,
Added, // also triggered when Created Added, // also triggered when Created

View File

@ -1,12 +1,11 @@
//! Authentication flow based on Hydra //! Authentication flow based on Hydra
use crate::util::fetch::{fetch_advanced, fetch_json}; use crate::util::fetch::{fetch_advanced, fetch_json, FetchSemaphore};
use async_tungstenite as ws; use async_tungstenite as ws;
use chrono::{prelude::*, Duration}; use chrono::{prelude::*, Duration};
use futures::prelude::*; use futures::prelude::*;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use reqwest::Method; use reqwest::Method;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::{RwLock, Semaphore};
use url::Url; use url::Url;
lazy_static! { lazy_static! {
@ -95,7 +94,7 @@ impl HydraAuthFlow<ws::tokio::ConnectStream> {
pub async fn extract_credentials( pub async fn extract_credentials(
&mut self, &mut self,
semaphore: &RwLock<Semaphore>, semaphore: &FetchSemaphore,
) -> crate::Result<Credentials> { ) -> crate::Result<Credentials> {
// Minecraft bearer token // Minecraft bearer token
let token_resp = self let token_resp = self
@ -130,7 +129,7 @@ impl HydraAuthFlow<ws::tokio::ConnectStream> {
pub async fn refresh_credentials( pub async fn refresh_credentials(
credentials: &mut Credentials, credentials: &mut Credentials,
semaphore: &RwLock<Semaphore>, semaphore: &FetchSemaphore,
) -> crate::Result<()> { ) -> crate::Result<()> {
let resp = fetch_json::<TokenJSON>( let resp = fetch_json::<TokenJSON>(
Method::POST, Method::POST,
@ -152,7 +151,7 @@ pub async fn refresh_credentials(
// Helpers // Helpers
async fn fetch_info( async fn fetch_info(
token: &str, token: &str,
semaphore: &RwLock<Semaphore>, semaphore: &FetchSemaphore,
) -> crate::Result<ProfileInfoJSON> { ) -> crate::Result<ProfileInfoJSON> {
let result = fetch_advanced( let result = fetch_advanced(
Method::GET, Method::GET,
@ -160,6 +159,7 @@ async fn fetch_info(
None, None,
None, None,
Some(("Authorization", &format!("Bearer {token}"))), Some(("Authorization", &format!("Bearer {token}"))),
None,
semaphore, semaphore,
) )
.await?; .await?;

View File

@ -1,7 +1,10 @@
//! Downloader for Minecraft data //! Downloader for Minecraft data
use crate::{ use crate::{
event::emit::{emit_loading, loading_try_for_each_concurrent}, event::{
emit::{emit_loading, loading_try_for_each_concurrent},
LoadingBarId,
},
state::State, state::State,
util::{fetch::*, platform::OsExt}, util::{fetch::*, platform::OsExt},
}; };
@ -15,24 +18,26 @@ use daedalus::{
}; };
use futures::prelude::*; use futures::prelude::*;
use tokio::{fs, sync::OnceCell}; use tokio::{fs, sync::OnceCell};
use uuid::Uuid;
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn download_minecraft( pub async fn download_minecraft(
st: &State, st: &State,
version: &GameVersionInfo, version: &GameVersionInfo,
loading_bar: Uuid, loading_bar: &LoadingBarId,
) -> crate::Result<()> { ) -> crate::Result<()> {
log::info!("Downloading Minecraft version {}", version.id); tracing::info!("Downloading Minecraft version {}", version.id);
let assets_index = download_assets_index(st, version).await?; // 5
let assets_index =
download_assets_index(st, version, Some(loading_bar)).await?;
tokio::try_join! { tokio::try_join! {
download_client(st, version, Some(&loading_bar)), // Total loading sums to 80
download_assets(st, version.assets == "legacy", &assets_index, Some(&loading_bar)), download_client(st, version, Some(loading_bar)), // 10
download_libraries(st, version.libraries.as_slice(), &version.id, Some(&loading_bar)) download_assets(st, version.assets == "legacy", &assets_index, Some(loading_bar)), // 35
download_libraries(st, version.libraries.as_slice(), &version.id, Some(loading_bar)) // 35
}?; }?;
log::info!("Done downloading Minecraft!"); tracing::info!("Done downloading Minecraft!");
Ok(()) Ok(())
} }
@ -42,10 +47,11 @@ pub async fn download_version_info(
version: &GameVersion, version: &GameVersion,
loader: Option<&LoaderVersion>, loader: Option<&LoaderVersion>,
force: Option<bool>, force: Option<bool>,
loading_bar: Option<&LoadingBarId>,
) -> crate::Result<GameVersionInfo> { ) -> crate::Result<GameVersionInfo> {
let version_id = loader let version_id = loader
.map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id)); .map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id));
log::debug!("Loading version info for Minecraft {version_id}"); tracing::debug!("Loading version info for Minecraft {version_id}");
let path = st let path = st
.directories .directories
.version_dir(&version_id) .version_dir(&version_id)
@ -57,7 +63,7 @@ pub async fn download_version_info(
.await .await
.and_then(|ref it| Ok(serde_json::from_slice(it)?)) .and_then(|ref it| Ok(serde_json::from_slice(it)?))
} else { } else {
log::info!("Downloading version info for version {}", &version.id); tracing::info!("Downloading version info for version {}", &version.id);
let mut info = d::minecraft::fetch_version_info(version).await?; let mut info = d::minecraft::fetch_version_info(version).await?;
if let Some(loader) = loader { if let Some(loader) = loader {
@ -70,7 +76,25 @@ pub async fn download_version_info(
Ok(info) Ok(info)
}?; }?;
log::debug!("Loaded version info for Minecraft {version_id}"); if let Some(loading_bar) = loading_bar {
emit_loading(
loading_bar,
if res
.processors
.as_ref()
.map(|x| !x.is_empty())
.unwrap_or(false)
{
5.0
} else {
15.0
},
None,
)
.await?;
}
tracing::debug!("Loaded version info for Minecraft {version_id}");
Ok(res) Ok(res)
} }
@ -78,10 +102,10 @@ pub async fn download_version_info(
pub async fn download_client( pub async fn download_client(
st: &State, st: &State,
version_info: &GameVersionInfo, version_info: &GameVersionInfo,
loading_bar: Option<&Uuid>, loading_bar: Option<&LoadingBarId>,
) -> crate::Result<()> { ) -> crate::Result<()> {
let version = &version_info.id; let version = &version_info.id;
log::debug!("Locating client for version {version}"); tracing::debug!("Locating client for version {version}");
let client_download = version_info let client_download = version_info
.downloads .downloads
.get(&d::minecraft::DownloadType::Client) .get(&d::minecraft::DownloadType::Client)
@ -100,17 +124,17 @@ pub async fn download_client(
let bytes = fetch( let bytes = fetch(
&client_download.url, &client_download.url,
Some(&client_download.sha1), Some(&client_download.sha1),
&st.io_semaphore, &st.fetch_semaphore,
) )
.await?; .await?;
write(&path, &bytes, &st.io_semaphore).await?; write(&path, &bytes, &st.io_semaphore).await?;
log::info!("Fetched client version {version}"); tracing::trace!("Fetched client version {version}");
} }
if let Some(loading_bar) = loading_bar { if let Some(loading_bar) = loading_bar {
emit_loading(loading_bar, 20.0, None).await?; emit_loading(loading_bar, 10.0, None).await?;
} }
log::debug!("Client loaded for version {version}!"); tracing::debug!("Client loaded for version {version}!");
Ok(()) Ok(())
} }
@ -118,8 +142,9 @@ pub async fn download_client(
pub async fn download_assets_index( pub async fn download_assets_index(
st: &State, st: &State,
version: &GameVersionInfo, version: &GameVersionInfo,
loading_bar: Option<&LoadingBarId>,
) -> crate::Result<AssetsIndex> { ) -> crate::Result<AssetsIndex> {
log::debug!("Loading assets index"); tracing::debug!("Loading assets index");
let path = st let path = st
.directories .directories
.assets_index_dir() .assets_index_dir()
@ -133,11 +158,14 @@ pub async fn download_assets_index(
} else { } else {
let index = d::minecraft::fetch_assets_index(version).await?; let index = d::minecraft::fetch_assets_index(version).await?;
write(&path, &serde_json::to_vec(&index)?, &st.io_semaphore).await?; write(&path, &serde_json::to_vec(&index)?, &st.io_semaphore).await?;
log::info!("Fetched assets index"); tracing::info!("Fetched assets index");
Ok(index) Ok(index)
}?; }?;
log::debug!("Assets index successfully loaded!"); if let Some(loading_bar) = loading_bar {
emit_loading(loading_bar, 5.0, None).await?;
}
tracing::debug!("Assets index successfully loaded!");
Ok(res) Ok(res)
} }
@ -146,9 +174,9 @@ pub async fn download_assets(
st: &State, st: &State,
with_legacy: bool, with_legacy: bool,
index: &AssetsIndex, index: &AssetsIndex,
loading_bar: Option<&Uuid>, loading_bar: Option<&LoadingBarId>,
) -> crate::Result<()> { ) -> crate::Result<()> {
log::debug!("Loading assets"); tracing::debug!("Loading assets");
let num_futs = index.objects.len(); let num_futs = index.objects.len();
let assets = stream::iter(index.objects.iter()) let assets = stream::iter(index.objects.iter())
.map(Ok::<(&String, &Asset), crate::Error>); .map(Ok::<(&String, &Asset), crate::Error>);
@ -156,7 +184,7 @@ pub async fn download_assets(
loading_try_for_each_concurrent(assets, loading_try_for_each_concurrent(assets,
None, None,
loading_bar, loading_bar,
50.0, 35.0,
num_futs, num_futs,
None, None,
|(name, asset)| async move { |(name, asset)| async move {
@ -172,33 +200,33 @@ pub async fn download_assets(
async { async {
if !resource_path.exists() { if !resource_path.exists() {
let resource = fetch_cell let resource = fetch_cell
.get_or_try_init(|| fetch(&url, Some(hash), &st.io_semaphore)) .get_or_try_init(|| fetch(&url, Some(hash), &st.fetch_semaphore))
.await?; .await?;
write(&resource_path, resource, &st.io_semaphore).await?; write(&resource_path, resource, &st.io_semaphore).await?;
log::info!("Fetched asset with hash {hash}"); tracing::trace!("Fetched asset with hash {hash}");
} }
Ok::<_, crate::Error>(()) Ok::<_, crate::Error>(())
}, },
async { async {
if with_legacy { if with_legacy {
let resource = fetch_cell let resource = fetch_cell
.get_or_try_init(|| fetch(&url, Some(hash), &st.io_semaphore)) .get_or_try_init(|| fetch(&url, Some(hash), &st.fetch_semaphore))
.await?; .await?;
let resource_path = st.directories.legacy_assets_dir().join( let resource_path = st.directories.legacy_assets_dir().join(
name.replace('/', &String::from(std::path::MAIN_SEPARATOR)) name.replace('/', &String::from(std::path::MAIN_SEPARATOR))
); );
write(&resource_path, resource, &st.io_semaphore).await?; write(&resource_path, resource, &st.io_semaphore).await?;
log::info!("Fetched legacy asset with hash {hash}"); tracing::trace!("Fetched legacy asset with hash {hash}");
} }
Ok::<_, crate::Error>(()) Ok::<_, crate::Error>(())
}, },
}?; }?;
log::debug!("Loaded asset with hash {hash}"); tracing::trace!("Loaded asset with hash {hash}");
Ok(()) Ok(())
}).await?; }).await?;
log::debug!("Done loading assets!"); tracing::debug!("Done loading assets!");
Ok(()) Ok(())
} }
@ -207,9 +235,9 @@ pub async fn download_libraries(
st: &State, st: &State,
libraries: &[Library], libraries: &[Library],
version: &str, version: &str,
loading_bar: Option<&Uuid>, loading_bar: Option<&LoadingBarId>,
) -> crate::Result<()> { ) -> crate::Result<()> {
log::debug!("Loading libraries"); tracing::debug!("Loading libraries");
tokio::try_join! { tokio::try_join! {
fs::create_dir_all(st.directories.libraries_dir()), fs::create_dir_all(st.directories.libraries_dir()),
@ -218,7 +246,7 @@ pub async fn download_libraries(
let num_files = libraries.len(); let num_files = libraries.len();
loading_try_for_each_concurrent( loading_try_for_each_concurrent(
stream::iter(libraries.iter()) stream::iter(libraries.iter())
.map(Ok::<&Library, crate::Error>), None, loading_bar,50.0,num_files, None,|library| async move { .map(Ok::<&Library, crate::Error>), None, loading_bar,35.0,num_files, None,|library| async move {
if let Some(rules) = &library.rules { if let Some(rules) = &library.rules {
if !rules.iter().all(super::parse_rule) { if !rules.iter().all(super::parse_rule) {
return Ok(()); return Ok(());
@ -235,10 +263,10 @@ pub async fn download_libraries(
artifact: Some(ref artifact), artifact: Some(ref artifact),
.. ..
}) => { }) => {
let bytes = fetch(&artifact.url, Some(&artifact.sha1), &st.io_semaphore) let bytes = fetch(&artifact.url, Some(&artifact.sha1), &st.fetch_semaphore)
.await?; .await?;
write(&path, &bytes, &st.io_semaphore).await?; write(&path, &bytes, &st.io_semaphore).await?;
log::info!("Fetched library {}", &library.name); tracing::trace!("Fetched library {}", &library.name);
Ok::<_, crate::Error>(()) Ok::<_, crate::Error>(())
} }
None => { None => {
@ -250,9 +278,9 @@ pub async fn download_libraries(
&artifact_path &artifact_path
].concat(); ].concat();
let bytes = fetch(&url, None, &st.io_semaphore).await?; let bytes = fetch(&url, None, &st.fetch_semaphore).await?;
write(&path, &bytes, &st.io_semaphore).await?; write(&path, &bytes, &st.io_semaphore).await?;
log::info!("Fetched library {}", &library.name); tracing::trace!("Fetched library {}", &library.name);
Ok::<_, crate::Error>(()) Ok::<_, crate::Error>(())
} }
_ => Ok(()) _ => Ok(())
@ -277,15 +305,15 @@ pub async fn download_libraries(
); );
if let Some(native) = classifiers.get(&parsed_key) { if let Some(native) = classifiers.get(&parsed_key) {
let data = fetch(&native.url, Some(&native.sha1), &st.io_semaphore).await?; let data = fetch(&native.url, Some(&native.sha1), &st.fetch_semaphore).await?;
let reader = std::io::Cursor::new(&data); let reader = std::io::Cursor::new(&data);
if let Ok(mut archive) = zip::ZipArchive::new(reader) { if let Ok(mut archive) = zip::ZipArchive::new(reader) {
match archive.extract(&st.directories.version_natives_dir(version)) { match archive.extract(&st.directories.version_natives_dir(version)) {
Ok(_) => log::info!("Fetched native {}", &library.name), Ok(_) => tracing::info!("Fetched native {}", &library.name),
Err(err) => log::error!("Failed extracting native {}. err: {}", &library.name, err) Err(err) => tracing::error!("Failed extracting native {}. err: {}", &library.name, err)
} }
} else { } else {
log::error!("Failed extracting native {}", &library.name) tracing::error!("Failed extracting native {}", &library.name)
} }
} }
} }
@ -294,11 +322,11 @@ pub async fn download_libraries(
} }
}?; }?;
log::debug!("Loaded library {}", library.name); tracing::debug!("Loaded library {}", library.name);
Ok(()) Ok(())
} }
).await?; ).await?;
log::debug!("Done loading libraries!"); tracing::debug!("Done loading libraries!");
Ok(()) Ok(())
} }

View File

@ -1,6 +1,6 @@
//! Logic for launching Minecraft //! Logic for launching Minecraft
use crate::event::emit::{emit_loading, init_or_edit_loading}; use crate::event::emit::{emit_loading, init_or_edit_loading};
use crate::event::LoadingBarType; use crate::event::{LoadingBarId, LoadingBarType};
use crate::{ use crate::{
process, process,
state::{self as st, MinecraftChild}, state::{self as st, MinecraftChild},
@ -53,9 +53,10 @@ macro_rules! processor_rules {
} }
} }
#[tracing::instrument(skip(profile))]
pub async fn install_minecraft( pub async fn install_minecraft(
profile: &Profile, profile: &Profile,
existing_loading_bar: Option<Uuid>, existing_loading_bar: Option<LoadingBarId>,
) -> crate::Result<()> { ) -> crate::Result<()> {
let state = State::get().await?; let state = State::get().await?;
let instance_path = &canonicalize(&profile.path)?; let instance_path = &canonicalize(&profile.path)?;
@ -79,14 +80,6 @@ pub async fn install_minecraft(
format!("{}-{}", version.id.clone(), it.id.clone()) format!("{}-{}", version.id.clone(), it.id.clone())
}); });
let mut version_info = download::download_version_info(
&state,
version,
profile.metadata.loader_version.as_ref(),
None,
)
.await?;
let loading_bar = init_or_edit_loading( let loading_bar = init_or_edit_loading(
existing_loading_bar, existing_loading_bar,
LoadingBarType::MinecraftDownload { LoadingBarType::MinecraftDownload {
@ -95,11 +88,22 @@ pub async fn install_minecraft(
profile_uuid: profile.uuid, profile_uuid: profile.uuid,
}, },
100.0, 100.0,
"Downloading Minecraft...", "Downloading Minecraft",
) )
.await?; .await?;
download::download_minecraft(&state, &version_info, loading_bar).await?; // Download version info
let mut version_info = download::download_version_info(
&state,
version,
profile.metadata.loader_version.as_ref(),
None,
Some(&loading_bar),
)
.await?;
// Download minecraft (5-90)
download::download_minecraft(&state, &version_info, &loading_bar).await?;
let client_path = state let client_path = state
.directories .directories
@ -131,10 +135,11 @@ pub async fn install_minecraft(
.await?; .await?;
let total_length = processors.len(); let total_length = processors.len();
// Forge processors (90-100)
for (index, processor) in processors.iter().enumerate() { for (index, processor) in processors.iter().enumerate() {
emit_loading( emit_loading(
&loading_bar, &loading_bar,
index as f64 / total_length as f64, 10.0 / total_length as f64,
Some(&format!( Some(&format!(
"Running forge processor {}/{}", "Running forge processor {}/{}",
index, total_length index, total_length
@ -223,7 +228,7 @@ pub async fn launch_minecraft(
install_minecraft(profile, None).await?; install_minecraft(profile, None).await?;
} }
let state = st::State::get().await?; let state = State::get().await?;
let metadata = state.metadata.read().await; let metadata = state.metadata.read().await;
let instance_path = &canonicalize(&profile.path)?; let instance_path = &canonicalize(&profile.path)?;
@ -250,6 +255,7 @@ pub async fn launch_minecraft(
version, version,
profile.metadata.loader_version.as_ref(), profile.metadata.loader_version.as_ref(),
None, None,
None,
) )
.await?; .await?;
@ -320,8 +326,12 @@ pub async fn launch_minecraft(
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::piped()); .stderr(Stdio::piped());
// Clear cargo-added env varaibles for debugging, and add settings env vars // CARGO-set DYLD_LIBRARY_PATH breaks Minecraft on macOS during testing on playground
clear_cargo_env_vals(&mut command).envs(env_args); #[cfg(target_os = "macos")]
if std::env::var("CARGO").is_ok() {
command.env_remove("DYLD_FALLBACK_LIBRARY_PATH");
}
command.envs(env_args);
// Get Modrinth logs directories // Get Modrinth logs directories
let datetime_string = let datetime_string =
@ -351,14 +361,3 @@ pub async fn launch_minecraft(
) )
.await .await
} }
fn clear_cargo_env_vals(command: &mut Command) -> &mut Command {
for (key, _) in std::env::vars() {
command.env_remove(key);
// if key.starts_with("CARGO") {
// command.env_remove(key);
// }
}
command
}

View File

@ -8,6 +8,7 @@ use tokio::process::Child;
use tokio::process::Command; use tokio::process::Command;
use tokio::process::{ChildStderr, ChildStdout}; use tokio::process::{ChildStderr, ChildStdout};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tracing::error;
use crate::event::emit::emit_process; use crate::event::emit::emit_process;
use crate::event::ProcessPayloadType; use crate::event::ProcessPayloadType;
@ -55,7 +56,7 @@ impl Children {
let stdout_clone = stdout.clone(); let stdout_clone = stdout.clone();
tokio::spawn(async move { tokio::spawn(async move {
if let Err(e) = stdout_clone.read_stdout(child_stdout).await { if let Err(e) = stdout_clone.read_stdout(child_stdout).await {
eprintln!("Stdout process died with error: {}", e); error!("Stdout process died with error: {}", e);
} }
}); });
} }
@ -64,7 +65,7 @@ impl Children {
let stderr_clone = stderr.clone(); let stderr_clone = stderr.clone();
tokio::spawn(async move { tokio::spawn(async move {
if let Err(e) = stderr_clone.read_stderr(child_stderr).await { if let Err(e) = stderr_clone.read_stderr(child_stderr).await {
eprintln!("Stderr process died with error: {}", e); error!("Stderr process died with error: {}", e);
} }
}); });
} }

View File

@ -1,6 +1,6 @@
//! Theseus metadata //! Theseus metadata
use crate::data::DirectoryInfo; use crate::data::DirectoryInfo;
use crate::util::fetch::{read_json, write}; use crate::util::fetch::{read_json, write, IoSemaphore};
use crate::State; use crate::State;
use daedalus::{ use daedalus::{
minecraft::{fetch_version_manifest, VersionManifest as MinecraftManifest}, minecraft::{fetch_version_manifest, VersionManifest as MinecraftManifest},
@ -9,7 +9,6 @@ use daedalus::{
}, },
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::{RwLock, Semaphore};
const METADATA_URL: &str = "https://meta.modrinth.com"; const METADATA_URL: &str = "https://meta.modrinth.com";
@ -51,7 +50,7 @@ impl Metadata {
// Attempt to fetch metadata and store in sled DB // Attempt to fetch metadata and store in sled DB
pub async fn init( pub async fn init(
dirs: &DirectoryInfo, dirs: &DirectoryInfo,
io_semaphore: &RwLock<Semaphore>, io_semaphore: &IoSemaphore,
) -> crate::Result<Self> { ) -> crate::Result<Self> {
let mut metadata = None; let mut metadata = None;
let metadata_path = dirs.caches_meta_dir().join("metadata.json"); let metadata_path = dirs.caches_meta_dir().join("metadata.json");
@ -79,7 +78,7 @@ impl Metadata {
match res { match res {
Ok(()) => {} Ok(()) => {}
Err(err) => { Err(err) => {
log::warn!("Unable to fetch launcher metadata: {err}") tracing::warn!("Unable to fetch launcher metadata: {err}")
} }
} }
} }
@ -120,7 +119,7 @@ impl Metadata {
match res { match res {
Ok(()) => {} Ok(()) => {}
Err(err) => { Err(err) => {
log::warn!("Unable to update launcher metadata: {err}") tracing::warn!("Unable to update launcher metadata: {err}")
} }
}; };
} }

View File

@ -6,6 +6,7 @@ use crate::event::LoadingBarType;
use crate::loading_join; use crate::loading_join;
use crate::state::users::Users; use crate::state::users::Users;
use crate::util::fetch::{FetchSemaphore, IoSemaphore};
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::{OnceCell, RwLock, Semaphore}; use tokio::sync::{OnceCell, RwLock, Semaphore};
@ -44,10 +45,16 @@ static LAUNCHER_STATE: OnceCell<Arc<State>> = OnceCell::const_new();
pub struct State { pub struct State {
/// Information on the location of files used in the launcher /// Information on the location of files used in the launcher
pub directories: DirectoryInfo, pub directories: DirectoryInfo,
/// Semaphore used to limit concurrent network requests and avoid errors
pub fetch_semaphore: FetchSemaphore,
/// Stored maximum number of sempahores of current fetch_semaphore
pub fetch_semaphore_max: RwLock<u32>,
/// Semaphore used to limit concurrent I/O and avoid errors /// Semaphore used to limit concurrent I/O and avoid errors
pub io_semaphore: RwLock<Semaphore>, pub io_semaphore: IoSemaphore,
/// Stored maximum number of sempahores of current io_semaphore /// Stored maximum number of sempahores of current io_semaphore
pub io_semaphore_max: RwLock<u32>, pub io_semaphore_max: RwLock<u32>,
/// Launcher metadata /// Launcher metadata
pub metadata: RwLock<Metadata>, pub metadata: RwLock<Metadata>,
/// Launcher configuration /// Launcher configuration
@ -73,7 +80,7 @@ impl State {
let loading_bar = init_loading( let loading_bar = init_loading(
LoadingBarType::StateInit, LoadingBarType::StateInit,
100.0, 100.0,
"Initializing launcher...", "Initializing launcher",
) )
.await?; .await?;
@ -83,20 +90,26 @@ impl State {
// Settings // Settings
let settings = let settings =
Settings::init(&directories.settings_file()).await?; Settings::init(&directories.settings_file()).await?;
let io_semaphore = RwLock::new(Semaphore::new( let fetch_semaphore = FetchSemaphore(RwLock::new(
settings.max_concurrent_downloads, Semaphore::new(settings.max_concurrent_downloads),
));
let io_semaphore = IoSemaphore(RwLock::new(
Semaphore::new(settings.max_concurrent_writes),
)); ));
emit_loading(&loading_bar, 10.0, None).await?; emit_loading(&loading_bar, 10.0, None).await?;
let metadata_fut = let metadata_fut =
Metadata::init(&directories, &io_semaphore); Metadata::init(&directories, &io_semaphore);
let profiles_fut = let profiles_fut = Profiles::init(&directories);
Profiles::init(&directories, &io_semaphore); let tags_fut = Tags::init(
let tags_fut = Tags::init(&directories, &io_semaphore); &directories,
&io_semaphore,
&fetch_semaphore,
);
let users_fut = Users::init(&directories, &io_semaphore); let users_fut = Users::init(&directories, &io_semaphore);
// Launcher data // Launcher data
let (metadata, profiles, tags, users) = loading_join! { let (metadata, profiles, tags, users) = loading_join! {
Some(&loading_bar), 70.0, Some("Initializing..."); Some(&loading_bar), 70.0, Some("Loading metadata");
metadata_fut, metadata_fut,
profiles_fut, profiles_fut,
tags_fut, tags_fut,
@ -109,9 +122,13 @@ impl State {
Ok(Arc::new(Self { Ok(Arc::new(Self {
directories, directories,
fetch_semaphore,
fetch_semaphore_max: RwLock::new(
settings.max_concurrent_downloads as u32,
),
io_semaphore, io_semaphore,
io_semaphore_max: RwLock::new( io_semaphore_max: RwLock::new(
settings.max_concurrent_downloads as u32, settings.max_concurrent_writes as u32,
), ),
metadata: RwLock::new(metadata), metadata: RwLock::new(metadata),
settings: RwLock::new(settings), settings: RwLock::new(settings),
@ -169,17 +186,34 @@ impl State {
.await .await
} }
/// Reset semaphores to default values /// Reset IO semaphore to default values
/// This will block until all uses of the semaphore are complete, so it should only be called /// This will block until all uses of the semaphore are complete, so it should only be called
/// when we are not in the middle of downloading something (ie: changing the settings!) /// when we are not in the middle of downloading something (ie: changing the settings!)
pub async fn reset_semaphore(&self) { pub async fn reset_io_semaphore(&self) {
let settings = self.settings.read().await; let settings = self.settings.read().await;
let mut io_semaphore = self.io_semaphore.write().await; let mut io_semaphore = self.io_semaphore.0.write().await;
let mut total_permits = self.io_semaphore_max.write().await; let mut total_permits = self.io_semaphore_max.write().await;
// Wait to get all permits back // Wait to get all permits back
let _ = io_semaphore.acquire_many(*total_permits).await; let _ = io_semaphore.acquire_many(*total_permits).await;
// Reset the semaphore
io_semaphore.close();
*total_permits = settings.max_concurrent_writes as u32;
*io_semaphore = Semaphore::new(settings.max_concurrent_writes);
}
/// Reset IO semaphore to default values
/// This will block until all uses of the semaphore are complete, so it should only be called
/// when we are not in the middle of downloading something (ie: changing the settings!)
pub async fn reset_fetch_semaphore(&self) {
let settings = self.settings.read().await;
let mut io_semaphore = self.fetch_semaphore.0.write().await;
let mut total_permits = self.fetch_semaphore_max.write().await;
// Wait to get all permits back
let _ = io_semaphore.acquire_many(*total_permits).await;
// Reset the semaphore // Reset the semaphore
io_semaphore.close(); io_semaphore.close();
*total_permits = settings.max_concurrent_downloads as u32; *total_permits = settings.max_concurrent_downloads as u32;

View File

@ -5,7 +5,9 @@ use crate::event::emit::emit_profile;
use crate::event::ProfilePayloadType; use crate::event::ProfilePayloadType;
use crate::state::projects::Project; use crate::state::projects::Project;
use crate::state::{ModrinthVersion, ProjectType}; use crate::state::{ModrinthVersion, ProjectType};
use crate::util::fetch::{fetch, fetch_json, write, write_cached_icon}; use crate::util::fetch::{
fetch, fetch_json, write, write_cached_icon, IoSemaphore,
};
use crate::State; use crate::State;
use daedalus::modded::LoaderVersion; use daedalus::modded::LoaderVersion;
use dunce::canonicalize; use dunce::canonicalize;
@ -17,8 +19,7 @@ use std::{
collections::HashMap, collections::HashMap,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use tokio::sync::Semaphore; use tokio::fs;
use tokio::{fs, sync::RwLock};
use uuid::Uuid; use uuid::Uuid;
const PROFILE_JSON_PATH: &str = "profile.json"; const PROFILE_JSON_PATH: &str = "profile.json";
@ -149,7 +150,7 @@ impl Profile {
pub async fn set_icon<'a>( pub async fn set_icon<'a>(
&'a mut self, &'a mut self,
cache_dir: &Path, cache_dir: &Path,
semaphore: &RwLock<Semaphore>, semaphore: &IoSemaphore,
icon: bytes::Bytes, icon: bytes::Bytes,
file_name: &str, file_name: &str,
) -> crate::Result<&'a mut Self> { ) -> crate::Result<&'a mut Self> {
@ -168,6 +169,7 @@ impl Profile {
paths, paths,
state.directories.caches_dir(), state.directories.caches_dir(),
&state.io_semaphore, &state.io_semaphore,
&state.fetch_semaphore,
) )
.await?; .await?;
@ -218,7 +220,7 @@ impl Profile {
&format!("{MODRINTH_API_URL}version/{version_id}"), &format!("{MODRINTH_API_URL}version/{version_id}"),
None, None,
None, None,
&state.io_semaphore, &state.fetch_semaphore,
) )
.await?; .await?;
@ -237,7 +239,7 @@ impl Profile {
let bytes = fetch( let bytes = fetch(
&file.url, &file.url,
file.hashes.get("sha1").map(|x| &**x), file.hashes.get("sha1").map(|x| &**x),
&state.io_semaphore, &state.fetch_semaphore,
) )
.await?; .await?;
@ -345,10 +347,7 @@ impl Profile {
impl Profiles { impl Profiles {
#[tracing::instrument] #[tracing::instrument]
pub async fn init( pub async fn init(dirs: &DirectoryInfo) -> crate::Result<Self> {
dirs: &DirectoryInfo,
io_sempahore: &RwLock<Semaphore>,
) -> crate::Result<Self> {
let mut profiles = HashMap::new(); let mut profiles = HashMap::new();
fs::create_dir_all(dirs.profiles_dir()).await?; fs::create_dir_all(dirs.profiles_dir()).await?;
let mut entries = fs::read_dir(dirs.profiles_dir()).await?; let mut entries = fs::read_dir(dirs.profiles_dir()).await?;
@ -358,7 +357,9 @@ impl Profiles {
let prof = match Self::read_profile_from_dir(&path).await { let prof = match Self::read_profile_from_dir(&path).await {
Ok(prof) => Some(prof), Ok(prof) => Some(prof),
Err(err) => { Err(err) => {
log::warn!("Error loading profile: {err}. Skipping..."); tracing::warn!(
"Error loading profile: {err}. Skipping..."
);
None None
} }
}; };
@ -395,6 +396,7 @@ impl Profiles {
files, files,
state.directories.caches_dir(), state.directories.caches_dir(),
&state.io_semaphore, &state.io_semaphore,
&state.fetch_semaphore,
) )
.await?; .await?;
@ -417,7 +419,7 @@ impl Profiles {
match res { match res {
Ok(()) => {} Ok(()) => {}
Err(err) => { Err(err) => {
log::warn!("Unable to fetch profile projects: {err}") tracing::warn!("Unable to fetch profile projects: {err}")
} }
}; };
} }

View File

@ -2,7 +2,9 @@
use crate::config::MODRINTH_API_URL; use crate::config::MODRINTH_API_URL;
use crate::state::Profile; use crate::state::Profile;
use crate::util::fetch::{fetch_json, write_cached_icon}; use crate::util::fetch::{
fetch_json, write_cached_icon, FetchSemaphore, IoSemaphore,
};
use async_zip::tokio::read::fs::ZipFileReader; use async_zip::tokio::read::fs::ZipFileReader;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use reqwest::Method; use reqwest::Method;
@ -12,7 +14,6 @@ use sha2::Digest;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
use tokio::sync::{RwLock, Semaphore};
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
@ -203,7 +204,7 @@ async fn read_icon_from_file(
icon_path: Option<String>, icon_path: Option<String>,
cache_dir: &Path, cache_dir: &Path,
path: &PathBuf, path: &PathBuf,
io_semaphore: &RwLock<Semaphore>, io_semaphore: &IoSemaphore,
) -> crate::Result<Option<PathBuf>> { ) -> crate::Result<Option<PathBuf>> {
if let Some(icon_path) = icon_path { if let Some(icon_path) = icon_path {
// we have to repoen the zip twice here :( // we have to repoen the zip twice here :(
@ -252,7 +253,8 @@ pub async fn infer_data_from_files(
profile: Profile, profile: Profile,
paths: Vec<PathBuf>, paths: Vec<PathBuf>,
cache_dir: PathBuf, cache_dir: PathBuf,
io_semaphore: &RwLock<Semaphore>, io_semaphore: &IoSemaphore,
fetch_semaphore: &FetchSemaphore,
) -> crate::Result<HashMap<PathBuf, Project>> { ) -> crate::Result<HashMap<PathBuf, Project>> {
let mut file_path_hashes = HashMap::new(); let mut file_path_hashes = HashMap::new();
@ -278,7 +280,7 @@ pub async fn infer_data_from_files(
"hashes": file_path_hashes.keys().collect::<Vec<_>>(), "hashes": file_path_hashes.keys().collect::<Vec<_>>(),
"algorithm": "sha512", "algorithm": "sha512",
})), })),
io_semaphore, fetch_semaphore,
), ),
fetch_json::<HashMap<String, ModrinthVersion>>( fetch_json::<HashMap<String, ModrinthVersion>>(
Method::POST, Method::POST,
@ -290,7 +292,7 @@ pub async fn infer_data_from_files(
"loaders": [profile.metadata.loader], "loaders": [profile.metadata.loader],
"game_versions": [profile.metadata.game_version] "game_versions": [profile.metadata.game_version]
})), })),
io_semaphore, fetch_semaphore,
) )
)?; )?;
@ -308,7 +310,7 @@ pub async fn infer_data_from_files(
), ),
None, None,
None, None,
io_semaphore, fetch_semaphore,
) )
.await?; .await?;
@ -325,7 +327,7 @@ pub async fn infer_data_from_files(
), ),
None, None,
None, None,
io_semaphore, fetch_semaphore,
) )
.await? .await?
.into_iter() .into_iter()

View File

@ -23,6 +23,7 @@ pub struct Settings {
pub default_user: Option<uuid::Uuid>, pub default_user: Option<uuid::Uuid>,
pub hooks: Hooks, pub hooks: Hooks,
pub max_concurrent_downloads: usize, pub max_concurrent_downloads: usize,
pub max_concurrent_writes: usize,
pub version: u32, pub version: u32,
pub collapsed_navigation: bool, pub collapsed_navigation: bool,
} }
@ -39,6 +40,7 @@ impl Default for Settings {
default_user: None, default_user: None,
hooks: Hooks::default(), hooks: Hooks::default(),
max_concurrent_downloads: 64, max_concurrent_downloads: 64,
max_concurrent_writes: 100,
version: CURRENT_FORMAT_VERSION, version: CURRENT_FORMAT_VERSION,
collapsed_navigation: false, collapsed_navigation: false,
} }
@ -84,7 +86,7 @@ impl Settings {
match res { match res {
Ok(()) => {} Ok(()) => {}
Err(err) => { Err(err) => {
log::warn!("Unable to update launcher java: {err}") tracing::warn!("Unable to update launcher java: {err}")
} }
}; };
} }

View File

@ -2,11 +2,12 @@ use std::path::PathBuf;
use reqwest::Method; use reqwest::Method;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::{RwLock, Semaphore};
use crate::config::MODRINTH_API_URL; use crate::config::MODRINTH_API_URL;
use crate::data::DirectoryInfo; use crate::data::DirectoryInfo;
use crate::util::fetch::{fetch_json, read_json, write}; use crate::util::fetch::{
fetch_json, read_json, write, FetchSemaphore, IoSemaphore,
};
// Serializeable struct for all tags to be fetched together by the frontend // Serializeable struct for all tags to be fetched together by the frontend
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -21,7 +22,8 @@ pub struct Tags {
impl Tags { impl Tags {
pub async fn init( pub async fn init(
dirs: &DirectoryInfo, dirs: &DirectoryInfo,
io_semaphore: &RwLock<Semaphore>, io_semaphore: &IoSemaphore,
fetch_sempahore: &FetchSemaphore,
) -> crate::Result<Self> { ) -> crate::Result<Self> {
let mut tags = None; let mut tags = None;
let tags_path = dirs.caches_meta_dir().join("tags.json"); let tags_path = dirs.caches_meta_dir().join("tags.json");
@ -30,10 +32,10 @@ impl Tags {
{ {
tags = Some(tags_json); tags = Some(tags_json);
} else { } else {
match Self::fetch(io_semaphore).await { match Self::fetch(fetch_sempahore).await {
Ok(tags_fetch) => tags = Some(tags_fetch), Ok(tags_fetch) => tags = Some(tags_fetch),
Err(err) => { Err(err) => {
log::warn!("Unable to fetch launcher tags: {err}") tracing::warn!("Unable to fetch launcher tags: {err}")
} }
} }
} }
@ -51,7 +53,7 @@ impl Tags {
pub async fn update() { pub async fn update() {
let res = async { let res = async {
let state = crate::State::get().await?; let state = crate::State::get().await?;
let tags_fetch = Tags::fetch(&state.io_semaphore).await?; let tags_fetch = Tags::fetch(&state.fetch_semaphore).await?;
let tags_path = let tags_path =
state.directories.caches_meta_dir().join("tags.json"); state.directories.caches_meta_dir().join("tags.json");
@ -74,7 +76,7 @@ impl Tags {
match res { match res {
Ok(()) => {} Ok(()) => {}
Err(err) => { Err(err) => {
log::warn!("Unable to update launcher tags: {err}") tracing::warn!("Unable to update launcher tags: {err}")
} }
}; };
} }
@ -116,7 +118,7 @@ impl Tags {
} }
// Fetches the tags from the Modrinth API and stores them in the database // Fetches the tags from the Modrinth API and stores them in the database
pub async fn fetch(semaphore: &RwLock<Semaphore>) -> crate::Result<Self> { pub async fn fetch(semaphore: &FetchSemaphore) -> crate::Result<Self> {
let categories = format!("{MODRINTH_API_URL}tag/category"); let categories = format!("{MODRINTH_API_URL}tag/category");
let loaders = format!("{MODRINTH_API_URL}tag/loader"); let loaders = format!("{MODRINTH_API_URL}tag/loader");
let game_versions = format!("{MODRINTH_API_URL}tag/game_version"); let game_versions = format!("{MODRINTH_API_URL}tag/game_version");

View File

@ -1,10 +1,9 @@
//! User login info //! User login info
use crate::auth::Credentials; use crate::auth::Credentials;
use crate::data::DirectoryInfo; use crate::data::DirectoryInfo;
use crate::util::fetch::{read_json, write}; use crate::util::fetch::{read_json, write, IoSemaphore};
use crate::State; use crate::State;
use std::collections::HashMap; use std::collections::HashMap;
use tokio::sync::{RwLock, Semaphore};
use uuid::Uuid; use uuid::Uuid;
const USERS_JSON: &str = "users.json"; const USERS_JSON: &str = "users.json";
@ -16,7 +15,7 @@ pub(crate) struct Users(pub(crate) HashMap<Uuid, Credentials>);
impl Users { impl Users {
pub async fn init( pub async fn init(
dirs: &DirectoryInfo, dirs: &DirectoryInfo,
io_semaphore: &RwLock<Semaphore>, io_semaphore: &IoSemaphore,
) -> crate::Result<Self> { ) -> crate::Result<Self> {
let users_path = dirs.caches_meta_dir().join(USERS_JSON); let users_path = dirs.caches_meta_dir().join(USERS_JSON);
let users = read_json(&users_path, io_semaphore).await.ok(); let users = read_json(&users_path, io_semaphore).await.ok();

View File

@ -1,4 +1,6 @@
//! Functions for fetching infromation from the Internet //! Functions for fetching infromation from the Internet
use crate::event::emit::emit_loading;
use crate::event::LoadingBarId;
use bytes::Bytes; use bytes::Bytes;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use reqwest::Method; use reqwest::Method;
@ -12,6 +14,11 @@ use tokio::{
io::AsyncWriteExt, io::AsyncWriteExt,
}; };
#[derive(Debug)]
pub struct IoSemaphore(pub RwLock<Semaphore>);
#[derive(Debug)]
pub struct FetchSemaphore(pub RwLock<Semaphore>);
lazy_static! { lazy_static! {
static ref REQWEST_CLIENT: reqwest::Client = { static ref REQWEST_CLIENT: reqwest::Client = {
let mut headers = reqwest::header::HeaderMap::new(); let mut headers = reqwest::header::HeaderMap::new();
@ -34,9 +41,9 @@ const FETCH_ATTEMPTS: usize = 3;
pub async fn fetch( pub async fn fetch(
url: &str, url: &str,
sha1: Option<&str>, sha1: Option<&str>,
semaphore: &RwLock<Semaphore>, semaphore: &FetchSemaphore,
) -> crate::Result<Bytes> { ) -> crate::Result<Bytes> {
fetch_advanced(Method::GET, url, sha1, None, None, semaphore).await fetch_advanced(Method::GET, url, sha1, None, None, None, semaphore).await
} }
#[tracing::instrument(skip(json_body, semaphore))] #[tracing::instrument(skip(json_body, semaphore))]
@ -45,13 +52,14 @@ pub async fn fetch_json<T>(
url: &str, url: &str,
sha1: Option<&str>, sha1: Option<&str>,
json_body: Option<serde_json::Value>, json_body: Option<serde_json::Value>,
semaphore: &RwLock<Semaphore>, semaphore: &FetchSemaphore,
) -> crate::Result<T> ) -> crate::Result<T>
where where
T: DeserializeOwned, T: DeserializeOwned,
{ {
let result = let result =
fetch_advanced(method, url, sha1, json_body, None, semaphore).await?; fetch_advanced(method, url, sha1, json_body, None, None, semaphore)
.await?;
let value = serde_json::from_slice(&result)?; let value = serde_json::from_slice(&result)?;
Ok(value) Ok(value)
} }
@ -64,9 +72,10 @@ pub async fn fetch_advanced(
sha1: Option<&str>, sha1: Option<&str>,
json_body: Option<serde_json::Value>, json_body: Option<serde_json::Value>,
header: Option<(&str, &str)>, header: Option<(&str, &str)>,
semaphore: &RwLock<Semaphore>, loading_bar: Option<(&LoadingBarId, f64)>,
semaphore: &FetchSemaphore,
) -> crate::Result<Bytes> { ) -> crate::Result<Bytes> {
let io_semaphore = semaphore.read().await; let io_semaphore = semaphore.0.read().await;
let _permit = io_semaphore.acquire().await?; let _permit = io_semaphore.acquire().await?;
for attempt in 1..=(FETCH_ATTEMPTS + 1) { for attempt in 1..=(FETCH_ATTEMPTS + 1) {
@ -83,7 +92,35 @@ pub async fn fetch_advanced(
let result = req.send().await; let result = req.send().await;
match result { match result {
Ok(x) => { Ok(x) => {
let bytes = x.bytes().await; let bytes = if let Some((bar, total)) = &loading_bar {
let length = x.content_length();
if let Some(total_size) = length {
use futures::StreamExt;
let mut stream = x.bytes_stream();
let mut bytes = Vec::new();
while let Some(item) = stream.next().await {
let chunk = item.or(Err(
crate::error::ErrorKind::NoValueFor(
"fetch bytes".to_string(),
),
))?;
bytes.append(&mut chunk.to_vec());
emit_loading(
bar,
(chunk.len() as f64 / total_size as f64)
* total,
None,
)
.await?;
}
Ok(bytes::Bytes::from(bytes))
} else {
x.bytes().await
}
} else {
x.bytes().await
};
if let Ok(bytes) = bytes { if let Ok(bytes) = bytes {
if let Some(sha1) = sha1 { if let Some(sha1) = sha1 {
@ -101,7 +138,7 @@ pub async fn fetch_advanced(
} }
} }
log::debug!("Done downloading URL {url}"); tracing::trace!("Done downloading URL {url}");
return Ok(bytes); return Ok(bytes);
} else if attempt <= 3 { } else if attempt <= 3 {
continue; continue;
@ -124,7 +161,7 @@ pub async fn fetch_advanced(
pub async fn fetch_mirrors( pub async fn fetch_mirrors(
mirrors: &[&str], mirrors: &[&str],
sha1: Option<&str>, sha1: Option<&str>,
semaphore: &RwLock<Semaphore>, semaphore: &FetchSemaphore,
) -> crate::Result<Bytes> { ) -> crate::Result<Bytes> {
if mirrors.is_empty() { if mirrors.is_empty() {
return Err(crate::ErrorKind::InputError( return Err(crate::ErrorKind::InputError(
@ -146,12 +183,12 @@ pub async fn fetch_mirrors(
pub async fn read_json<T>( pub async fn read_json<T>(
path: &Path, path: &Path,
semaphore: &RwLock<Semaphore>, semaphore: &IoSemaphore,
) -> crate::Result<T> ) -> crate::Result<T>
where where
T: DeserializeOwned, T: DeserializeOwned,
{ {
let io_semaphore = semaphore.read().await; let io_semaphore = semaphore.0.read().await;
let _permit = io_semaphore.acquire().await?; let _permit = io_semaphore.acquire().await?;
let json = fs::read(path).await?; let json = fs::read(path).await?;
@ -164,9 +201,9 @@ where
pub async fn write<'a>( pub async fn write<'a>(
path: &Path, path: &Path,
bytes: &[u8], bytes: &[u8],
semaphore: &RwLock<Semaphore>, semaphore: &IoSemaphore,
) -> crate::Result<()> { ) -> crate::Result<()> {
let io_semaphore = semaphore.read().await; let io_semaphore = semaphore.0.read().await;
let _permit = io_semaphore.acquire().await?; let _permit = io_semaphore.acquire().await?;
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
@ -175,7 +212,7 @@ pub async fn write<'a>(
let mut file = File::create(path).await?; let mut file = File::create(path).await?;
file.write_all(bytes).await?; file.write_all(bytes).await?;
log::debug!("Done writing file {}", path.display()); tracing::trace!("Done writing file {}", path.display());
Ok(()) Ok(())
} }
@ -184,7 +221,7 @@ pub async fn write_cached_icon(
icon_path: &str, icon_path: &str,
cache_dir: &Path, cache_dir: &Path,
bytes: Bytes, bytes: Bytes,
semaphore: &RwLock<Semaphore>, semaphore: &IoSemaphore,
) -> crate::Result<PathBuf> { ) -> crate::Result<PathBuf> {
let extension = Path::new(&icon_path).extension().and_then(OsStr::to_str); let extension = Path::new(&icon_path).extension().and_then(OsStr::to_str);
let hash = sha1_async(bytes.clone()).await?; let hash = sha1_async(bytes.clone()).await?;

View File

@ -7,7 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
theseus = { path = "../theseus" } theseus = { path = "../theseus", features = ["cli"] }
daedalus = {version = "0.1.15", features = ["bincode"]} daedalus = {version = "0.1.15", features = ["bincode"]}
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tokio-stream = { version = "0.1", features = ["fs"] } tokio-stream = { version = "0.1", features = ["fs"] }

View File

@ -15,7 +15,7 @@ tauri-build = { version = "1.2", features = [] }
regex = "1.5" regex = "1.5"
[dependencies] [dependencies]
theseus = { path = "../../theseus", features = ["tauri"] } theseus = { path = "../../theseus", features = ["tauri", "cli"] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
@ -30,6 +30,10 @@ url = "2.2"
uuid = { version = "1.1", features = ["serde", "v4"] } uuid = { version = "1.1", features = ["serde", "v4"] }
os_info = "3.7.0" os_info = "3.7.0"
tracing = "0.1.37"
tracing-subscriber = "0.2"
tracing-error = "0.1"
[features] [features]
# by default Tauri runs in production mode # by default Tauri runs in production mode
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL

View File

@ -47,6 +47,8 @@ where
} }
// Lists active progress bars // Lists active progress bars
// Create a new HashMap with the same keys
// Values provided should not be used directly, as they are not guaranteed to be up-to-date
#[tauri::command] #[tauri::command]
pub async fn progress_bars_list( pub async fn progress_bars_list(
) -> Result<std::collections::HashMap<uuid::Uuid, theseus::LoadingBar>> { ) -> Result<std::collections::HashMap<uuid::Uuid, theseus::LoadingBar>> {
@ -64,6 +66,16 @@ macro_rules! impl_serialize {
S: Serializer, S: Serializer,
{ {
match self { match self {
// For the Theseus variant, we add a special display for the error,
// to view the spans if subscribed to them (which is information that is lost when serializing)
TheseusSerializableError::Theseus(theseus_error) => {
$crate::error::display_tracing_error(theseus_error);
let mut state = serializer.serialize_struct("Theseus", 2)?;
state.serialize_field("field_name", "Theseus")?;
state.serialize_field("message", &theseus_error.to_string())?;
state.end()
}
$( $(
TheseusSerializableError::$variant(message) => { TheseusSerializableError::$variant(message) => {
let mut state = serializer.serialize_struct(stringify!($variant), 2)?; let mut state = serializer.serialize_struct(stringify!($variant), 2)?;
@ -80,7 +92,6 @@ macro_rules! impl_serialize {
// Use the macro to implement Serialize for TheseusSerializableError // Use the macro to implement Serialize for TheseusSerializableError
impl_serialize! { impl_serialize! {
Theseus,
IO, IO,
NoProfileFound, NoProfileFound,
} }

View File

@ -5,8 +5,12 @@ use theseus::prelude::*;
// Creates a pack from a version ID (returns a path to the created profile) // Creates a pack from a version ID (returns a path to the created profile)
// invoke('pack_install_version_id', version_id) // invoke('pack_install_version_id', version_id)
#[tauri::command] #[tauri::command]
pub async fn pack_install_version_id(version_id: String) -> Result<PathBuf> { pub async fn pack_install_version_id(
let res = pack::install_pack_from_version_id(version_id).await?; version_id: String,
pack_title: Option<String>,
) -> Result<PathBuf> {
let res =
pack::install_pack_from_version_id(version_id, pack_title).await?;
Ok(res) Ok(res)
} }

View File

@ -15,6 +15,7 @@ pub struct FrontendSettings {
pub default_user: Option<uuid::Uuid>, pub default_user: Option<uuid::Uuid>,
pub hooks: Hooks, pub hooks: Hooks,
pub max_concurrent_downloads: usize, pub max_concurrent_downloads: usize,
pub max_concurrent_writes: usize,
pub version: u32, pub version: u32,
pub collapsed_navigation: bool, pub collapsed_navigation: bool,
} }
@ -39,6 +40,7 @@ pub async fn settings_get() -> Result<FrontendSettings> {
default_user: backend_settings.default_user, default_user: backend_settings.default_user,
hooks: backend_settings.hooks, hooks: backend_settings.hooks,
max_concurrent_downloads: backend_settings.max_concurrent_downloads, max_concurrent_downloads: backend_settings.max_concurrent_downloads,
max_concurrent_writes: backend_settings.max_concurrent_writes,
version: backend_settings.version, version: backend_settings.version,
collapsed_navigation: backend_settings.collapsed_navigation, collapsed_navigation: backend_settings.collapsed_navigation,
}; };
@ -77,6 +79,7 @@ pub async fn settings_set(settings: FrontendSettings) -> Result<()> {
default_user: settings.default_user, default_user: settings.default_user,
hooks: settings.hooks, hooks: settings.hooks,
max_concurrent_downloads: settings.max_concurrent_downloads, max_concurrent_downloads: settings.max_concurrent_downloads,
max_concurrent_writes: settings.max_concurrent_writes,
version: settings.version, version: settings.version,
collapsed_navigation: settings.collapsed_navigation, collapsed_navigation: settings.collapsed_navigation,
}; };

View File

@ -0,0 +1,18 @@
use tracing_error::ExtractSpanTrace;
pub fn display_tracing_error(err: &theseus::Error) {
match get_span_trace(err) {
Some(span_trace) => {
tracing::error!(error = %err, span_trace = %span_trace);
}
None => {
tracing::error!(error = %err);
}
}
}
pub fn get_span_trace<'a>(
error: &'a (dyn std::error::Error + 'static),
) -> Option<&'a tracing_error::SpanTrace> {
error.source().and_then(|e| e.span_trace())
}

View File

@ -5,7 +5,11 @@
use theseus::prelude::*; use theseus::prelude::*;
use tracing_error::ErrorLayer;
use tracing_subscriber::EnvFilter;
mod api; mod api;
mod error;
// Should be called in launcher initialization // Should be called in launcher initialization
#[tauri::command] #[tauri::command]
@ -37,7 +41,34 @@ async fn should_disable_mouseover() -> bool {
false false
} }
use tracing_subscriber::prelude::*;
fn main() { fn main() {
/*
tracing is set basd on the environment variable RUST_LOG=xxx, depending on the amount of logs to show
ERROR > WARN > INFO > DEBUG > TRACE
eg. RUST_LOG=info will show info, warn, and error logs
RUST_LOG="theseus=trace" will show *all* messages but from theseus only (and not dependencies using similar crates)
RUST_LOG="theseus=trace" will show *all* messages but from theseus only (and not dependencies using similar crates)
Error messages returned to Tauri will display as traced error logs if they return an error.
This will also include an attached span trace if the error is from a tracing error, and the level is set to info, debug, or trace
on unix:
RUST_LOG="theseus=trace" {run command}
*/
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("theseus=info"));
let subscriber = tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(filter)
.with(ErrorLayer::default());
tracing::subscriber::set_global_default(subscriber)
.expect("setting default subscriber failed");
tauri::Builder::default() tauri::Builder::default()
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
initialize_state, initialize_state,

View File

@ -25,15 +25,16 @@ import { listen } from '@tauri-apps/api/event'
/// Payload for the 'loading' event /// Payload for the 'loading' event
/* /*
LoadingPayload { LoadingPayload {
event: "StateInit", "PackDownload", etc event: {
- Certain states have additional fields: type: string, one of "StateInit", "PackDownload", etc
- PackDownload: { (Optional fields depending on event type)
pack_name: name of the pack pack_name: name of the pack
pack_id, optional, the id of the modpack pack_id, optional, the id of the modpack
pack_version, optional, the version of the modpack pack_version, optional, the version of the modpack
- MinecraftDownload: { profile_name: name of the profile
profile_name: name of the profile profile_uuid: unique identification of the profile
profile_uuid: unique identification of the profile
}
loader_uuid: unique identification of the loading bar loader_uuid: unique identification of the loading bar
fraction: number, (as a fraction of 1, how much we'vel oaded so far). If null, by convention, loading is finished fraction: number, (as a fraction of 1, how much we'vel oaded so far). If null, by convention, loading is finished
message: message to display to the user message: message to display to the user

View File

@ -6,8 +6,8 @@
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/tauri'
// Installs pack from a version ID // Installs pack from a version ID
export async function install(versionId) { export async function install(versionId, packTitle) {
return await invoke('pack_install_version_id', { versionId }) return await invoke('pack_install_version_id', { versionId, packTitle })
} }
// Installs pack from a path // Installs pack from a path

View File

@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
theseus = { path = "../theseus" } theseus = { path = "../theseus", features = ["cli"] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
@ -21,3 +21,7 @@ tokio-stream = { version = "0.1", features = ["fs"] }
futures = "0.3" futures = "0.3"
daedalus = {version = "0.1.15", features = ["bincode"] } daedalus = {version = "0.1.15", features = ["bincode"] }
uuid = { version = "1.1", features = ["serde", "v4"] } uuid = { version = "1.1", features = ["serde", "v4"] }
tracing = "0.1.37"
tracing-subscriber = "0.2"
tracing-error = "0.1"

View File

@ -8,6 +8,9 @@ use theseus::jre::autodetect_java_globals;
use theseus::prelude::*; use theseus::prelude::*;
use theseus::profile_create::profile_create; use theseus::profile_create::profile_create;
use tokio::time::{sleep, Duration}; use tokio::time::{sleep, Duration};
use tracing_error::ErrorLayer;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::EnvFilter;
// A simple Rust implementation of the authentication run // A simple Rust implementation of the authentication run
// 1) call the authenticate_begin_flow() function to get the URL to open (like you would in the frontend) // 1) call the authenticate_begin_flow() function to get the URL to open (like you would in the frontend)
@ -31,16 +34,27 @@ pub async fn authenticate_run() -> theseus::Result<Credentials> {
async fn main() -> theseus::Result<()> { async fn main() -> theseus::Result<()> {
println!("Starting."); println!("Starting.");
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("theseus=info"));
let subscriber = tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.with(filter)
.with(ErrorLayer::default());
tracing::subscriber::set_global_default(subscriber)
.expect("setting default subscriber failed");
// Initialize state // Initialize state
let st = State::get().await?; let st = State::get().await?;
//State::update(); //State::update();
st.settings.write().await.java_globals = autodetect_java_globals().await?; st.settings.write().await.java_globals = autodetect_java_globals().await?;
st.settings.write().await.max_concurrent_downloads = 5; st.settings.write().await.max_concurrent_downloads = 50;
st.settings.write().await.hooks.post_exit = st.settings.write().await.hooks.post_exit =
Some("echo This is after Minecraft runs- global setting!".to_string()); Some("echo This is after Minecraft runs- global setting!".to_string());
// Changed the settings, so need to reset the semaphore // Changed the settings, so need to reset the semaphore
st.reset_semaphore().await; st.reset_fetch_semaphore().await;
// Clear profiles // Clear profiles
println!("Clearing profiles."); println!("Clearing profiles.");
@ -53,21 +67,21 @@ async fn main() -> theseus::Result<()> {
println!("Creating/adding profile."); println!("Creating/adding profile.");
let name = "Example".to_string(); // let name = "Example".to_string();
let game_version = "1.19.2".to_string(); // let game_version = "1.19.2".to_string();
let modloader = ModLoader::Vanilla; // let modloader = ModLoader::Vanilla;
let loader_version = "stable".to_string(); // let loader_version = "stable".to_string();
//
let profile_path = profile_create( // let profile_path = profile_create(
name.clone(), // name.clone(),
game_version, // game_version,
modloader, // modloader,
Some(loader_version), // Some(loader_version),
None, // None,
None, // None,
None, // None,
) // )
.await?; // .await?;
// let mut value = list().await?; // let mut value = list().await?;
// let profile_path = value.iter().next().map(|x| x.0).unwrap(); // let profile_path = value.iter().next().map(|x| x.0).unwrap();
@ -89,10 +103,12 @@ async fn main() -> theseus::Result<()> {
// profile::toggle_disable_project(&profile_path, &sodium_path).await?; // profile::toggle_disable_project(&profile_path, &sodium_path).await?;
// //
// profile::remove_project(&profile_path, &mod_menu_path).await?; // profile::remove_project(&profile_path, &mod_menu_path).await?;
// let profile_path = let profile_path = pack::install_pack_from_version_id(
// pack::install_pack_from_version_id("zroFQG1k".to_string()) "zroFQG1k".to_string(),
// .await Some("Technical Electrical".to_string()),
// .unwrap(); )
.await
.unwrap();
// async closure for testing any desired edits // async closure for testing any desired edits
// (ie: changing the java runtime of an added profile) // (ie: changing the java runtime of an added profile)