You've already forked AstralRinth
forked from didirus/AstralRinth
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:
@@ -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?;
|
||||
|
||||
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 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(|| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"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 {
|
||||
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?;
|
||||
Ok(credentials)
|
||||
|
||||
@@ -61,6 +61,7 @@ pub async fn get_optimal_jre_key(profile: &Profile) -> crate::Result<String> {
|
||||
version,
|
||||
profile.metadata.loader_version.as_ref(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let optimal_key = match version_info
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::config::MODRINTH_API_URL;
|
||||
use crate::data::ModLoader;
|
||||
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::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 async_zip::tokio::read::seek::ZipFileReader;
|
||||
@@ -75,17 +76,30 @@ enum PackDependency {
|
||||
|
||||
pub async fn install_pack_from_version_id(
|
||||
version_id: String,
|
||||
title: Option<String>,
|
||||
) -> crate::Result<PathBuf> {
|
||||
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(
|
||||
Method::GET,
|
||||
&format!("{}version/{}", MODRINTH_API_URL, version_id),
|
||||
None,
|
||||
None,
|
||||
&state.io_semaphore,
|
||||
&state.fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
let (url, hash) =
|
||||
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(
|
||||
Method::GET,
|
||||
&format!("{}project/{}", MODRINTH_API_URL, version.project_id),
|
||||
None,
|
||||
None,
|
||||
&state.io_semaphore,
|
||||
&state.fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
emit_loading(&loading_bar, 10.0, Some("Retrieving icon")).await?;
|
||||
let icon = if let Some(icon_url) = project.icon_url {
|
||||
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();
|
||||
|
||||
@@ -135,6 +160,7 @@ pub async fn install_pack_from_version_id(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
install_pack(
|
||||
file,
|
||||
@@ -142,6 +168,7 @@ pub async fn install_pack_from_version_id(
|
||||
Some(project.title),
|
||||
Some(version.project_id),
|
||||
Some(version.id),
|
||||
Some(loading_bar),
|
||||
)
|
||||
.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> {
|
||||
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(
|
||||
@@ -158,6 +185,7 @@ async fn install_pack(
|
||||
override_title: Option<String>,
|
||||
project_id: Option<String>,
|
||||
version_id: Option<String>,
|
||||
existing_loading_bar: Option<LoadingBarId>,
|
||||
) -> crate::Result<PathBuf> {
|
||||
let state = &State::get().await?;
|
||||
|
||||
@@ -242,14 +270,15 @@ async fn install_pack(
|
||||
.await?;
|
||||
let profile = profile_raw.clone();
|
||||
let result = async {
|
||||
let loading_bar = init_loading(
|
||||
let loading_bar = init_or_edit_loading(
|
||||
existing_loading_bar,
|
||||
LoadingBarType::PackDownload {
|
||||
pack_name: pack.name.clone(),
|
||||
pack_id: project_id,
|
||||
pack_version: version_id,
|
||||
},
|
||||
100.0,
|
||||
"Downloading modpack...",
|
||||
"Downloading modpack",
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -260,7 +289,7 @@ async fn install_pack(
|
||||
.map(Ok::<PackFile, crate::Error>),
|
||||
None,
|
||||
Some(&loading_bar),
|
||||
80.0,
|
||||
70.0,
|
||||
num_files,
|
||||
None,
|
||||
|project| {
|
||||
@@ -287,7 +316,7 @@ async fn install_pack(
|
||||
.hashes
|
||||
.get(&PackFileHash::Sha1)
|
||||
.map(|x| &**x),
|
||||
&state.io_semaphore,
|
||||
&state.fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -365,11 +394,11 @@ async fn install_pack(
|
||||
.await
|
||||
};
|
||||
|
||||
emit_loading(&loading_bar, 0.05, Some("Extracting overrides"))
|
||||
emit_loading(&loading_bar, 0.0, Some("Extracting overrides"))
|
||||
.await?;
|
||||
extract_overrides("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?;
|
||||
|
||||
if let Some(profile) = crate::api::profile::get(&profile).await? {
|
||||
@@ -380,13 +409,6 @@ async fn install_pack(
|
||||
Some(loading_bar)
|
||||
),
|
||||
)?;
|
||||
} else {
|
||||
emit_loading(
|
||||
&loading_bar,
|
||||
0.1,
|
||||
Some("Done extacting overrides"),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok::<PathBuf, crate::Error>(profile)
|
||||
|
||||
@@ -148,7 +148,7 @@ pub async fn update_all(profile_path: &Path) -> crate::Result<()> {
|
||||
profile_name: profile.metadata.name.clone(),
|
||||
},
|
||||
100.0,
|
||||
"Updating profile...",
|
||||
"Updating profile",
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -159,7 +159,7 @@ pub async fn update_all(profile_path: &Path) -> crate::Result<()> {
|
||||
None,
|
||||
Some(&loading_bar),
|
||||
100.0,
|
||||
profile.projects.len(),
|
||||
profile.projects.keys().len(),
|
||||
None,
|
||||
|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,
|
||||
/// 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>>> {
|
||||
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
|
||||
/// Returns Arc pointer to RwLock to Child
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tracing::instrument]
|
||||
pub async fn run_credentials(
|
||||
path: &Path,
|
||||
credentials: &auth::Credentials,
|
||||
@@ -398,6 +398,7 @@ pub async fn run_credentials(
|
||||
version,
|
||||
profile.metadata.loader_version.as_ref(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let pre_launch_hooks =
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Theseus profile management interface
|
||||
use crate::event::emit::emit_warning;
|
||||
use crate::state::LinkedData;
|
||||
use crate::{
|
||||
event::{emit::emit_profile, ProfilePayloadType},
|
||||
@@ -15,6 +16,7 @@ use futures::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use tokio::fs;
|
||||
use tokio_stream::wrappers::ReadDirStream;
|
||||
use tracing::{info, trace};
|
||||
use uuid::Uuid;
|
||||
|
||||
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
|
||||
skip_install_profile: Option<bool>,
|
||||
) -> crate::Result<PathBuf> {
|
||||
trace!("Creating new profile. {}", name);
|
||||
let state = State::get().await?;
|
||||
let metadata = state.metadata.read().await;
|
||||
|
||||
@@ -74,7 +77,7 @@ pub async fn profile_create(
|
||||
fs::create_dir_all(&path).await?;
|
||||
}
|
||||
|
||||
println!(
|
||||
info!(
|
||||
"Creating profile at path {}",
|
||||
&canonicalize(&path)?.display()
|
||||
);
|
||||
@@ -173,7 +176,7 @@ pub async fn profile_create(
|
||||
extra_arguments: None,
|
||||
});
|
||||
} 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(
|
||||
|
||||
@@ -19,8 +19,22 @@ pub async fn get() -> crate::Result<Settings> {
|
||||
pub async fn set(settings: Settings) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
// 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.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?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use super::LoadingBarId;
|
||||
use crate::event::{
|
||||
EventError, LoadingBar, LoadingBarType, ProcessPayloadType,
|
||||
ProfilePayloadType,
|
||||
};
|
||||
use futures::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use tracing::warn;
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
use crate::event::{
|
||||
@@ -13,6 +15,9 @@ use crate::event::{
|
||||
use tauri::Manager;
|
||||
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.
|
||||
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,
|
||||
total: f64,
|
||||
title: &str,
|
||||
) -> crate::Result<Uuid> {
|
||||
) -> crate::Result<LoadingBarId> {
|
||||
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(
|
||||
key,
|
||||
key.0,
|
||||
LoadingBar {
|
||||
loading_bar_id: key,
|
||||
loading_bar_uuid: key.0,
|
||||
message: title.to_string(),
|
||||
total,
|
||||
current: 0.0,
|
||||
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
|
||||
@@ -65,13 +86,13 @@ pub async fn init_loading(
|
||||
}
|
||||
|
||||
pub async fn init_or_edit_loading(
|
||||
id: Option<Uuid>,
|
||||
id: Option<LoadingBarId>,
|
||||
bar_type: LoadingBarType,
|
||||
total: f64,
|
||||
title: &str,
|
||||
) -> crate::Result<Uuid> {
|
||||
) -> crate::Result<LoadingBarId> {
|
||||
if let Some(id) = id {
|
||||
edit_loading(id, bar_type, total, title).await?;
|
||||
edit_loading(&id, bar_type, total, title).await?;
|
||||
|
||||
Ok(id)
|
||||
} else {
|
||||
@@ -80,21 +101,27 @@ pub async fn init_or_edit_loading(
|
||||
}
|
||||
|
||||
// Edits a loading bar's type
|
||||
// This also resets the bar's current progress to 0
|
||||
pub async fn edit_loading(
|
||||
id: Uuid,
|
||||
id: &LoadingBarId,
|
||||
bar_type: LoadingBarType,
|
||||
total: f64,
|
||||
title: &str,
|
||||
) -> crate::Result<()> {
|
||||
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.total = total;
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// By convention, fraction is the fraction of the progress bar that is filled
|
||||
#[allow(unused_variables)]
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub async fn emit_loading(
|
||||
key: &Uuid,
|
||||
key: &LoadingBarId,
|
||||
increment_frac: f64,
|
||||
message: Option<&str>,
|
||||
) -> crate::Result<()> {
|
||||
let event_state = crate::EventState::get().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,
|
||||
None => {
|
||||
return Err(EventError::NoLoadingBar(*key).into());
|
||||
return Err(EventError::NoLoadingBar(key.0).into());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -128,6 +156,22 @@ pub async fn emit_loading(
|
||||
} else {
|
||||
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
|
||||
#[cfg(feature = "tauri")]
|
||||
event_state
|
||||
@@ -138,7 +182,7 @@ pub async fn emit_loading(
|
||||
fraction: display_frac,
|
||||
message: message.unwrap_or(&loading_bar.message).to_string(),
|
||||
event: loading_bar.bar_type.clone(),
|
||||
loader_uuid: loading_bar.loading_bar_id,
|
||||
loader_uuid: loading_bar.loading_bar_uuid,
|
||||
},
|
||||
)
|
||||
.map_err(EventError::from)?;
|
||||
@@ -162,6 +206,7 @@ pub async fn emit_warning(message: &str) -> crate::Result<()> {
|
||||
)
|
||||
.map_err(EventError::from)?;
|
||||
}
|
||||
warn!("{}", message);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -235,7 +280,6 @@ macro_rules! count {
|
||||
() => (0usize);
|
||||
( $x:tt $($xs:tt)* ) => (1usize + $crate::count!($($xs)*));
|
||||
}
|
||||
#[cfg(feature = "tauri")]
|
||||
#[macro_export]
|
||||
macro_rules! loading_join {
|
||||
($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
|
||||
// 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).
|
||||
// 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
|
||||
#[cfg(feature = "tauri")]
|
||||
pub async fn loading_try_for_each_concurrent<I, F, Fut, T>(
|
||||
stream: I,
|
||||
limit: Option<usize>,
|
||||
key: Option<&Uuid>,
|
||||
key: Option<&LoadingBarId>,
|
||||
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>,
|
||||
@@ -316,31 +352,3 @@ where
|
||||
})
|
||||
.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
|
||||
}
|
||||
|
||||
@@ -48,11 +48,18 @@ impl EventState {
|
||||
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>>
|
||||
{
|
||||
let value = Self::get().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
|
||||
@@ -64,16 +71,84 @@ impl EventState {
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
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 total: f64,
|
||||
pub current: f64,
|
||||
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)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum LoadingBarType {
|
||||
StateInit,
|
||||
PackFileDownload {
|
||||
pack_name: Option<String>,
|
||||
pack_version: String,
|
||||
},
|
||||
PackDownload {
|
||||
pack_name: String,
|
||||
pack_id: Option<String>,
|
||||
@@ -124,6 +199,7 @@ pub struct ProfilePayload {
|
||||
pub event: ProfilePayloadType,
|
||||
}
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ProfilePayloadType {
|
||||
Created,
|
||||
Added, // also triggered when Created
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
//! 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 chrono::{prelude::*, Duration};
|
||||
use futures::prelude::*;
|
||||
use lazy_static::lazy_static;
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::{RwLock, Semaphore};
|
||||
use url::Url;
|
||||
|
||||
lazy_static! {
|
||||
@@ -95,7 +94,7 @@ impl HydraAuthFlow<ws::tokio::ConnectStream> {
|
||||
|
||||
pub async fn extract_credentials(
|
||||
&mut self,
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<Credentials> {
|
||||
// Minecraft bearer token
|
||||
let token_resp = self
|
||||
@@ -130,7 +129,7 @@ impl HydraAuthFlow<ws::tokio::ConnectStream> {
|
||||
|
||||
pub async fn refresh_credentials(
|
||||
credentials: &mut Credentials,
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<()> {
|
||||
let resp = fetch_json::<TokenJSON>(
|
||||
Method::POST,
|
||||
@@ -152,7 +151,7 @@ pub async fn refresh_credentials(
|
||||
// Helpers
|
||||
async fn fetch_info(
|
||||
token: &str,
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<ProfileInfoJSON> {
|
||||
let result = fetch_advanced(
|
||||
Method::GET,
|
||||
@@ -160,6 +159,7 @@ async fn fetch_info(
|
||||
None,
|
||||
None,
|
||||
Some(("Authorization", &format!("Bearer {token}"))),
|
||||
None,
|
||||
semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
//! Downloader for Minecraft data
|
||||
|
||||
use crate::{
|
||||
event::emit::{emit_loading, loading_try_for_each_concurrent},
|
||||
event::{
|
||||
emit::{emit_loading, loading_try_for_each_concurrent},
|
||||
LoadingBarId,
|
||||
},
|
||||
state::State,
|
||||
util::{fetch::*, platform::OsExt},
|
||||
};
|
||||
@@ -15,24 +18,26 @@ use daedalus::{
|
||||
};
|
||||
use futures::prelude::*;
|
||||
use tokio::{fs, sync::OnceCell};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn download_minecraft(
|
||||
st: &State,
|
||||
version: &GameVersionInfo,
|
||||
loading_bar: Uuid,
|
||||
loading_bar: &LoadingBarId,
|
||||
) -> crate::Result<()> {
|
||||
log::info!("Downloading Minecraft version {}", version.id);
|
||||
let assets_index = download_assets_index(st, version).await?;
|
||||
tracing::info!("Downloading Minecraft version {}", version.id);
|
||||
// 5
|
||||
let assets_index =
|
||||
download_assets_index(st, version, Some(loading_bar)).await?;
|
||||
|
||||
tokio::try_join! {
|
||||
download_client(st, version, Some(&loading_bar)),
|
||||
download_assets(st, version.assets == "legacy", &assets_index, Some(&loading_bar)),
|
||||
download_libraries(st, version.libraries.as_slice(), &version.id, Some(&loading_bar))
|
||||
// Total loading sums to 80
|
||||
download_client(st, version, Some(loading_bar)), // 10
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -42,10 +47,11 @@ pub async fn download_version_info(
|
||||
version: &GameVersion,
|
||||
loader: Option<&LoaderVersion>,
|
||||
force: Option<bool>,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
) -> crate::Result<GameVersionInfo> {
|
||||
let version_id = loader
|
||||
.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
|
||||
.directories
|
||||
.version_dir(&version_id)
|
||||
@@ -57,7 +63,7 @@ pub async fn download_version_info(
|
||||
.await
|
||||
.and_then(|ref it| Ok(serde_json::from_slice(it)?))
|
||||
} 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?;
|
||||
|
||||
if let Some(loader) = loader {
|
||||
@@ -70,7 +76,25 @@ pub async fn download_version_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)
|
||||
}
|
||||
|
||||
@@ -78,10 +102,10 @@ pub async fn download_version_info(
|
||||
pub async fn download_client(
|
||||
st: &State,
|
||||
version_info: &GameVersionInfo,
|
||||
loading_bar: Option<&Uuid>,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
) -> crate::Result<()> {
|
||||
let version = &version_info.id;
|
||||
log::debug!("Locating client for version {version}");
|
||||
tracing::debug!("Locating client for version {version}");
|
||||
let client_download = version_info
|
||||
.downloads
|
||||
.get(&d::minecraft::DownloadType::Client)
|
||||
@@ -100,17 +124,17 @@ pub async fn download_client(
|
||||
let bytes = fetch(
|
||||
&client_download.url,
|
||||
Some(&client_download.sha1),
|
||||
&st.io_semaphore,
|
||||
&st.fetch_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 {
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -118,8 +142,9 @@ pub async fn download_client(
|
||||
pub async fn download_assets_index(
|
||||
st: &State,
|
||||
version: &GameVersionInfo,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
) -> crate::Result<AssetsIndex> {
|
||||
log::debug!("Loading assets index");
|
||||
tracing::debug!("Loading assets index");
|
||||
let path = st
|
||||
.directories
|
||||
.assets_index_dir()
|
||||
@@ -133,11 +158,14 @@ pub async fn download_assets_index(
|
||||
} else {
|
||||
let index = d::minecraft::fetch_assets_index(version).await?;
|
||||
write(&path, &serde_json::to_vec(&index)?, &st.io_semaphore).await?;
|
||||
log::info!("Fetched assets index");
|
||||
tracing::info!("Fetched assets 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)
|
||||
}
|
||||
|
||||
@@ -146,9 +174,9 @@ pub async fn download_assets(
|
||||
st: &State,
|
||||
with_legacy: bool,
|
||||
index: &AssetsIndex,
|
||||
loading_bar: Option<&Uuid>,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
) -> crate::Result<()> {
|
||||
log::debug!("Loading assets");
|
||||
tracing::debug!("Loading assets");
|
||||
let num_futs = index.objects.len();
|
||||
let assets = stream::iter(index.objects.iter())
|
||||
.map(Ok::<(&String, &Asset), crate::Error>);
|
||||
@@ -156,7 +184,7 @@ pub async fn download_assets(
|
||||
loading_try_for_each_concurrent(assets,
|
||||
None,
|
||||
loading_bar,
|
||||
50.0,
|
||||
35.0,
|
||||
num_futs,
|
||||
None,
|
||||
|(name, asset)| async move {
|
||||
@@ -172,33 +200,33 @@ pub async fn download_assets(
|
||||
async {
|
||||
if !resource_path.exists() {
|
||||
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?;
|
||||
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>(())
|
||||
},
|
||||
async {
|
||||
if with_legacy {
|
||||
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?;
|
||||
let resource_path = st.directories.legacy_assets_dir().join(
|
||||
name.replace('/', &String::from(std::path::MAIN_SEPARATOR))
|
||||
);
|
||||
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>(())
|
||||
},
|
||||
}?;
|
||||
|
||||
log::debug!("Loaded asset with hash {hash}");
|
||||
tracing::trace!("Loaded asset with hash {hash}");
|
||||
Ok(())
|
||||
}).await?;
|
||||
|
||||
log::debug!("Done loading assets!");
|
||||
tracing::debug!("Done loading assets!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -207,9 +235,9 @@ pub async fn download_libraries(
|
||||
st: &State,
|
||||
libraries: &[Library],
|
||||
version: &str,
|
||||
loading_bar: Option<&Uuid>,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
) -> crate::Result<()> {
|
||||
log::debug!("Loading libraries");
|
||||
tracing::debug!("Loading libraries");
|
||||
|
||||
tokio::try_join! {
|
||||
fs::create_dir_all(st.directories.libraries_dir()),
|
||||
@@ -218,7 +246,7 @@ pub async fn download_libraries(
|
||||
let num_files = libraries.len();
|
||||
loading_try_for_each_concurrent(
|
||||
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 !rules.iter().all(super::parse_rule) {
|
||||
return Ok(());
|
||||
@@ -235,10 +263,10 @@ pub async fn download_libraries(
|
||||
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?;
|
||||
write(&path, &bytes, &st.io_semaphore).await?;
|
||||
log::info!("Fetched library {}", &library.name);
|
||||
tracing::trace!("Fetched library {}", &library.name);
|
||||
Ok::<_, crate::Error>(())
|
||||
}
|
||||
None => {
|
||||
@@ -250,9 +278,9 @@ pub async fn download_libraries(
|
||||
&artifact_path
|
||||
].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?;
|
||||
log::info!("Fetched library {}", &library.name);
|
||||
tracing::trace!("Fetched library {}", &library.name);
|
||||
Ok::<_, crate::Error>(())
|
||||
}
|
||||
_ => Ok(())
|
||||
@@ -277,15 +305,15 @@ pub async fn download_libraries(
|
||||
);
|
||||
|
||||
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);
|
||||
if let Ok(mut archive) = zip::ZipArchive::new(reader) {
|
||||
match archive.extract(&st.directories.version_natives_dir(version)) {
|
||||
Ok(_) => log::info!("Fetched native {}", &library.name),
|
||||
Err(err) => log::error!("Failed extracting native {}. err: {}", &library.name, err)
|
||||
Ok(_) => tracing::info!("Fetched native {}", &library.name),
|
||||
Err(err) => tracing::error!("Failed extracting native {}. err: {}", &library.name, err)
|
||||
}
|
||||
} 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(())
|
||||
}
|
||||
).await?;
|
||||
|
||||
log::debug!("Done loading libraries!");
|
||||
tracing::debug!("Done loading libraries!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Logic for launching Minecraft
|
||||
use crate::event::emit::{emit_loading, init_or_edit_loading};
|
||||
use crate::event::LoadingBarType;
|
||||
use crate::event::{LoadingBarId, LoadingBarType};
|
||||
use crate::{
|
||||
process,
|
||||
state::{self as st, MinecraftChild},
|
||||
@@ -53,9 +53,10 @@ macro_rules! processor_rules {
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(profile))]
|
||||
pub async fn install_minecraft(
|
||||
profile: &Profile,
|
||||
existing_loading_bar: Option<Uuid>,
|
||||
existing_loading_bar: Option<LoadingBarId>,
|
||||
) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let instance_path = &canonicalize(&profile.path)?;
|
||||
@@ -79,14 +80,6 @@ pub async fn install_minecraft(
|
||||
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(
|
||||
existing_loading_bar,
|
||||
LoadingBarType::MinecraftDownload {
|
||||
@@ -95,11 +88,22 @@ pub async fn install_minecraft(
|
||||
profile_uuid: profile.uuid,
|
||||
},
|
||||
100.0,
|
||||
"Downloading Minecraft...",
|
||||
"Downloading Minecraft",
|
||||
)
|
||||
.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
|
||||
.directories
|
||||
@@ -131,10 +135,11 @@ pub async fn install_minecraft(
|
||||
.await?;
|
||||
let total_length = processors.len();
|
||||
|
||||
// Forge processors (90-100)
|
||||
for (index, processor) in processors.iter().enumerate() {
|
||||
emit_loading(
|
||||
&loading_bar,
|
||||
index as f64 / total_length as f64,
|
||||
10.0 / total_length as f64,
|
||||
Some(&format!(
|
||||
"Running forge processor {}/{}",
|
||||
index, total_length
|
||||
@@ -223,7 +228,7 @@ pub async fn launch_minecraft(
|
||||
install_minecraft(profile, None).await?;
|
||||
}
|
||||
|
||||
let state = st::State::get().await?;
|
||||
let state = State::get().await?;
|
||||
let metadata = state.metadata.read().await;
|
||||
let instance_path = &canonicalize(&profile.path)?;
|
||||
|
||||
@@ -250,6 +255,7 @@ pub async fn launch_minecraft(
|
||||
version,
|
||||
profile.metadata.loader_version.as_ref(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -320,8 +326,12 @@ pub async fn launch_minecraft(
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
// Clear cargo-added env varaibles for debugging, and add settings env vars
|
||||
clear_cargo_env_vals(&mut command).envs(env_args);
|
||||
// CARGO-set DYLD_LIBRARY_PATH breaks Minecraft on macOS during testing on playground
|
||||
#[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
|
||||
let datetime_string =
|
||||
@@ -351,14 +361,3 @@ pub async fn launch_minecraft(
|
||||
)
|
||||
.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
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use tokio::process::Child;
|
||||
use tokio::process::Command;
|
||||
use tokio::process::{ChildStderr, ChildStdout};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::error;
|
||||
|
||||
use crate::event::emit::emit_process;
|
||||
use crate::event::ProcessPayloadType;
|
||||
@@ -55,7 +56,7 @@ impl Children {
|
||||
let stdout_clone = stdout.clone();
|
||||
tokio::spawn(async move {
|
||||
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();
|
||||
tokio::spawn(async move {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Theseus metadata
|
||||
use crate::data::DirectoryInfo;
|
||||
use crate::util::fetch::{read_json, write};
|
||||
use crate::util::fetch::{read_json, write, IoSemaphore};
|
||||
use crate::State;
|
||||
use daedalus::{
|
||||
minecraft::{fetch_version_manifest, VersionManifest as MinecraftManifest},
|
||||
@@ -9,7 +9,6 @@ use daedalus::{
|
||||
},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::{RwLock, Semaphore};
|
||||
|
||||
const METADATA_URL: &str = "https://meta.modrinth.com";
|
||||
|
||||
@@ -51,7 +50,7 @@ impl Metadata {
|
||||
// Attempt to fetch metadata and store in sled DB
|
||||
pub async fn init(
|
||||
dirs: &DirectoryInfo,
|
||||
io_semaphore: &RwLock<Semaphore>,
|
||||
io_semaphore: &IoSemaphore,
|
||||
) -> crate::Result<Self> {
|
||||
let mut metadata = None;
|
||||
let metadata_path = dirs.caches_meta_dir().join("metadata.json");
|
||||
@@ -79,7 +78,7 @@ impl Metadata {
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
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 {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("Unable to update launcher metadata: {err}")
|
||||
tracing::warn!("Unable to update launcher metadata: {err}")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::event::LoadingBarType;
|
||||
use crate::loading_join;
|
||||
|
||||
use crate::state::users::Users;
|
||||
use crate::util::fetch::{FetchSemaphore, IoSemaphore};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{OnceCell, RwLock, Semaphore};
|
||||
|
||||
@@ -44,10 +45,16 @@ static LAUNCHER_STATE: OnceCell<Arc<State>> = OnceCell::const_new();
|
||||
pub struct State {
|
||||
/// Information on the location of files used in the launcher
|
||||
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
|
||||
pub io_semaphore: RwLock<Semaphore>,
|
||||
pub io_semaphore: IoSemaphore,
|
||||
/// Stored maximum number of sempahores of current io_semaphore
|
||||
pub io_semaphore_max: RwLock<u32>,
|
||||
|
||||
/// Launcher metadata
|
||||
pub metadata: RwLock<Metadata>,
|
||||
/// Launcher configuration
|
||||
@@ -73,7 +80,7 @@ impl State {
|
||||
let loading_bar = init_loading(
|
||||
LoadingBarType::StateInit,
|
||||
100.0,
|
||||
"Initializing launcher...",
|
||||
"Initializing launcher",
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -83,20 +90,26 @@ impl State {
|
||||
// Settings
|
||||
let settings =
|
||||
Settings::init(&directories.settings_file()).await?;
|
||||
let io_semaphore = RwLock::new(Semaphore::new(
|
||||
settings.max_concurrent_downloads,
|
||||
let fetch_semaphore = FetchSemaphore(RwLock::new(
|
||||
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?;
|
||||
|
||||
let metadata_fut =
|
||||
Metadata::init(&directories, &io_semaphore);
|
||||
let profiles_fut =
|
||||
Profiles::init(&directories, &io_semaphore);
|
||||
let tags_fut = Tags::init(&directories, &io_semaphore);
|
||||
let profiles_fut = Profiles::init(&directories);
|
||||
let tags_fut = Tags::init(
|
||||
&directories,
|
||||
&io_semaphore,
|
||||
&fetch_semaphore,
|
||||
);
|
||||
let users_fut = Users::init(&directories, &io_semaphore);
|
||||
// Launcher data
|
||||
let (metadata, profiles, tags, users) = loading_join! {
|
||||
Some(&loading_bar), 70.0, Some("Initializing...");
|
||||
Some(&loading_bar), 70.0, Some("Loading metadata");
|
||||
metadata_fut,
|
||||
profiles_fut,
|
||||
tags_fut,
|
||||
@@ -109,9 +122,13 @@ impl State {
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
directories,
|
||||
fetch_semaphore,
|
||||
fetch_semaphore_max: RwLock::new(
|
||||
settings.max_concurrent_downloads as u32,
|
||||
),
|
||||
io_semaphore,
|
||||
io_semaphore_max: RwLock::new(
|
||||
settings.max_concurrent_downloads as u32,
|
||||
settings.max_concurrent_writes as u32,
|
||||
),
|
||||
metadata: RwLock::new(metadata),
|
||||
settings: RwLock::new(settings),
|
||||
@@ -169,17 +186,34 @@ impl State {
|
||||
.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
|
||||
/// 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 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;
|
||||
|
||||
// Wait to get all permits back
|
||||
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
|
||||
io_semaphore.close();
|
||||
*total_permits = settings.max_concurrent_downloads as u32;
|
||||
|
||||
@@ -5,7 +5,9 @@ use crate::event::emit::emit_profile;
|
||||
use crate::event::ProfilePayloadType;
|
||||
use crate::state::projects::Project;
|
||||
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 daedalus::modded::LoaderVersion;
|
||||
use dunce::canonicalize;
|
||||
@@ -17,8 +19,7 @@ use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio::{fs, sync::RwLock};
|
||||
use tokio::fs;
|
||||
use uuid::Uuid;
|
||||
|
||||
const PROFILE_JSON_PATH: &str = "profile.json";
|
||||
@@ -149,7 +150,7 @@ impl Profile {
|
||||
pub async fn set_icon<'a>(
|
||||
&'a mut self,
|
||||
cache_dir: &Path,
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
semaphore: &IoSemaphore,
|
||||
icon: bytes::Bytes,
|
||||
file_name: &str,
|
||||
) -> crate::Result<&'a mut Self> {
|
||||
@@ -168,6 +169,7 @@ impl Profile {
|
||||
paths,
|
||||
state.directories.caches_dir(),
|
||||
&state.io_semaphore,
|
||||
&state.fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -218,7 +220,7 @@ impl Profile {
|
||||
&format!("{MODRINTH_API_URL}version/{version_id}"),
|
||||
None,
|
||||
None,
|
||||
&state.io_semaphore,
|
||||
&state.fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -237,7 +239,7 @@ impl Profile {
|
||||
let bytes = fetch(
|
||||
&file.url,
|
||||
file.hashes.get("sha1").map(|x| &**x),
|
||||
&state.io_semaphore,
|
||||
&state.fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -345,10 +347,7 @@ impl Profile {
|
||||
|
||||
impl Profiles {
|
||||
#[tracing::instrument]
|
||||
pub async fn init(
|
||||
dirs: &DirectoryInfo,
|
||||
io_sempahore: &RwLock<Semaphore>,
|
||||
) -> crate::Result<Self> {
|
||||
pub async fn init(dirs: &DirectoryInfo) -> crate::Result<Self> {
|
||||
let mut profiles = HashMap::new();
|
||||
fs::create_dir_all(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 {
|
||||
Ok(prof) => Some(prof),
|
||||
Err(err) => {
|
||||
log::warn!("Error loading profile: {err}. Skipping...");
|
||||
tracing::warn!(
|
||||
"Error loading profile: {err}. Skipping..."
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
@@ -395,6 +396,7 @@ impl Profiles {
|
||||
files,
|
||||
state.directories.caches_dir(),
|
||||
&state.io_semaphore,
|
||||
&state.fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -417,7 +419,7 @@ impl Profiles {
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("Unable to fetch profile projects: {err}")
|
||||
tracing::warn!("Unable to fetch profile projects: {err}")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
use crate::config::MODRINTH_API_URL;
|
||||
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 chrono::{DateTime, Utc};
|
||||
use reqwest::Method;
|
||||
@@ -12,7 +14,6 @@ use sha2::Digest;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::sync::{RwLock, Semaphore};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@@ -203,7 +204,7 @@ async fn read_icon_from_file(
|
||||
icon_path: Option<String>,
|
||||
cache_dir: &Path,
|
||||
path: &PathBuf,
|
||||
io_semaphore: &RwLock<Semaphore>,
|
||||
io_semaphore: &IoSemaphore,
|
||||
) -> crate::Result<Option<PathBuf>> {
|
||||
if let Some(icon_path) = icon_path {
|
||||
// we have to repoen the zip twice here :(
|
||||
@@ -252,7 +253,8 @@ pub async fn infer_data_from_files(
|
||||
profile: Profile,
|
||||
paths: Vec<PathBuf>,
|
||||
cache_dir: PathBuf,
|
||||
io_semaphore: &RwLock<Semaphore>,
|
||||
io_semaphore: &IoSemaphore,
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<HashMap<PathBuf, Project>> {
|
||||
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<_>>(),
|
||||
"algorithm": "sha512",
|
||||
})),
|
||||
io_semaphore,
|
||||
fetch_semaphore,
|
||||
),
|
||||
fetch_json::<HashMap<String, ModrinthVersion>>(
|
||||
Method::POST,
|
||||
@@ -290,7 +292,7 @@ pub async fn infer_data_from_files(
|
||||
"loaders": [profile.metadata.loader],
|
||||
"game_versions": [profile.metadata.game_version]
|
||||
})),
|
||||
io_semaphore,
|
||||
fetch_semaphore,
|
||||
)
|
||||
)?;
|
||||
|
||||
@@ -308,7 +310,7 @@ pub async fn infer_data_from_files(
|
||||
),
|
||||
None,
|
||||
None,
|
||||
io_semaphore,
|
||||
fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -325,7 +327,7 @@ pub async fn infer_data_from_files(
|
||||
),
|
||||
None,
|
||||
None,
|
||||
io_semaphore,
|
||||
fetch_semaphore,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
|
||||
@@ -23,6 +23,7 @@ pub struct Settings {
|
||||
pub default_user: Option<uuid::Uuid>,
|
||||
pub hooks: Hooks,
|
||||
pub max_concurrent_downloads: usize,
|
||||
pub max_concurrent_writes: usize,
|
||||
pub version: u32,
|
||||
pub collapsed_navigation: bool,
|
||||
}
|
||||
@@ -39,6 +40,7 @@ impl Default for Settings {
|
||||
default_user: None,
|
||||
hooks: Hooks::default(),
|
||||
max_concurrent_downloads: 64,
|
||||
max_concurrent_writes: 100,
|
||||
version: CURRENT_FORMAT_VERSION,
|
||||
collapsed_navigation: false,
|
||||
}
|
||||
@@ -84,7 +86,7 @@ impl Settings {
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("Unable to update launcher java: {err}")
|
||||
tracing::warn!("Unable to update launcher java: {err}")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ use std::path::PathBuf;
|
||||
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::{RwLock, Semaphore};
|
||||
|
||||
use crate::config::MODRINTH_API_URL;
|
||||
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
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -21,7 +22,8 @@ pub struct Tags {
|
||||
impl Tags {
|
||||
pub async fn init(
|
||||
dirs: &DirectoryInfo,
|
||||
io_semaphore: &RwLock<Semaphore>,
|
||||
io_semaphore: &IoSemaphore,
|
||||
fetch_sempahore: &FetchSemaphore,
|
||||
) -> crate::Result<Self> {
|
||||
let mut tags = None;
|
||||
let tags_path = dirs.caches_meta_dir().join("tags.json");
|
||||
@@ -30,10 +32,10 @@ impl Tags {
|
||||
{
|
||||
tags = Some(tags_json);
|
||||
} else {
|
||||
match Self::fetch(io_semaphore).await {
|
||||
match Self::fetch(fetch_sempahore).await {
|
||||
Ok(tags_fetch) => tags = Some(tags_fetch),
|
||||
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() {
|
||||
let res = async {
|
||||
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 =
|
||||
state.directories.caches_meta_dir().join("tags.json");
|
||||
@@ -74,7 +76,7 @@ impl Tags {
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
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
|
||||
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 loaders = format!("{MODRINTH_API_URL}tag/loader");
|
||||
let game_versions = format!("{MODRINTH_API_URL}tag/game_version");
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
//! User login info
|
||||
use crate::auth::Credentials;
|
||||
use crate::data::DirectoryInfo;
|
||||
use crate::util::fetch::{read_json, write};
|
||||
use crate::util::fetch::{read_json, write, IoSemaphore};
|
||||
use crate::State;
|
||||
use std::collections::HashMap;
|
||||
use tokio::sync::{RwLock, Semaphore};
|
||||
use uuid::Uuid;
|
||||
|
||||
const USERS_JSON: &str = "users.json";
|
||||
@@ -16,7 +15,7 @@ pub(crate) struct Users(pub(crate) HashMap<Uuid, Credentials>);
|
||||
impl Users {
|
||||
pub async fn init(
|
||||
dirs: &DirectoryInfo,
|
||||
io_semaphore: &RwLock<Semaphore>,
|
||||
io_semaphore: &IoSemaphore,
|
||||
) -> crate::Result<Self> {
|
||||
let users_path = dirs.caches_meta_dir().join(USERS_JSON);
|
||||
let users = read_json(&users_path, io_semaphore).await.ok();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
//! Functions for fetching infromation from the Internet
|
||||
use crate::event::emit::emit_loading;
|
||||
use crate::event::LoadingBarId;
|
||||
use bytes::Bytes;
|
||||
use lazy_static::lazy_static;
|
||||
use reqwest::Method;
|
||||
@@ -12,6 +14,11 @@ use tokio::{
|
||||
io::AsyncWriteExt,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IoSemaphore(pub RwLock<Semaphore>);
|
||||
#[derive(Debug)]
|
||||
pub struct FetchSemaphore(pub RwLock<Semaphore>);
|
||||
|
||||
lazy_static! {
|
||||
static ref REQWEST_CLIENT: reqwest::Client = {
|
||||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
@@ -34,9 +41,9 @@ const FETCH_ATTEMPTS: usize = 3;
|
||||
pub async fn fetch(
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> 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))]
|
||||
@@ -45,13 +52,14 @@ pub async fn fetch_json<T>(
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
json_body: Option<serde_json::Value>,
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
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)?;
|
||||
Ok(value)
|
||||
}
|
||||
@@ -64,9 +72,10 @@ pub async fn fetch_advanced(
|
||||
sha1: Option<&str>,
|
||||
json_body: Option<serde_json::Value>,
|
||||
header: Option<(&str, &str)>,
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
loading_bar: Option<(&LoadingBarId, f64)>,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<Bytes> {
|
||||
let io_semaphore = semaphore.read().await;
|
||||
let io_semaphore = semaphore.0.read().await;
|
||||
let _permit = io_semaphore.acquire().await?;
|
||||
|
||||
for attempt in 1..=(FETCH_ATTEMPTS + 1) {
|
||||
@@ -83,7 +92,35 @@ pub async fn fetch_advanced(
|
||||
let result = req.send().await;
|
||||
match result {
|
||||
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 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);
|
||||
} else if attempt <= 3 {
|
||||
continue;
|
||||
@@ -124,7 +161,7 @@ pub async fn fetch_advanced(
|
||||
pub async fn fetch_mirrors(
|
||||
mirrors: &[&str],
|
||||
sha1: Option<&str>,
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<Bytes> {
|
||||
if mirrors.is_empty() {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
@@ -146,12 +183,12 @@ pub async fn fetch_mirrors(
|
||||
|
||||
pub async fn read_json<T>(
|
||||
path: &Path,
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
semaphore: &IoSemaphore,
|
||||
) -> crate::Result<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let io_semaphore = semaphore.read().await;
|
||||
let io_semaphore = semaphore.0.read().await;
|
||||
let _permit = io_semaphore.acquire().await?;
|
||||
|
||||
let json = fs::read(path).await?;
|
||||
@@ -164,9 +201,9 @@ where
|
||||
pub async fn write<'a>(
|
||||
path: &Path,
|
||||
bytes: &[u8],
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
semaphore: &IoSemaphore,
|
||||
) -> crate::Result<()> {
|
||||
let io_semaphore = semaphore.read().await;
|
||||
let io_semaphore = semaphore.0.read().await;
|
||||
let _permit = io_semaphore.acquire().await?;
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
@@ -175,7 +212,7 @@ pub async fn write<'a>(
|
||||
|
||||
let mut file = File::create(path).await?;
|
||||
file.write_all(bytes).await?;
|
||||
log::debug!("Done writing file {}", path.display());
|
||||
tracing::trace!("Done writing file {}", path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -184,7 +221,7 @@ pub async fn write_cached_icon(
|
||||
icon_path: &str,
|
||||
cache_dir: &Path,
|
||||
bytes: Bytes,
|
||||
semaphore: &RwLock<Semaphore>,
|
||||
semaphore: &IoSemaphore,
|
||||
) -> crate::Result<PathBuf> {
|
||||
let extension = Path::new(&icon_path).extension().and_then(OsStr::to_str);
|
||||
let hash = sha1_async(bytes.clone()).await?;
|
||||
|
||||
Reference in New Issue
Block a user