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
33 changed files with 726 additions and 294 deletions

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?;
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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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 =

View File

@@ -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(

View File

@@ -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(())
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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?;

View File

@@ -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(())
}

View File

@@ -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
}

View File

@@ -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);
}
});
}

View File

@@ -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}")
}
};
}

View File

@@ -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;

View File

@@ -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}")
}
};
}

View File

@@ -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()

View File

@@ -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}")
}
};
}

View File

@@ -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");

View File

@@ -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();

View File

@@ -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?;