diff --git a/theseus/src/api/auth.rs b/theseus/src/api/auth.rs index 362b8cd26..389d62c9b 100644 --- a/theseus/src/api/auth.rs +++ b/theseus/src/api/auth.rs @@ -57,7 +57,6 @@ pub async fn authenticate( Ok(credentials) } - /// Refresh some credentials using Hydra, if needed /// This is the primary desired way to get credentials, as it will also refresh them. #[tracing::instrument] diff --git a/theseus/src/api/jre.rs b/theseus/src/api/jre.rs index b8398a360..21af1f6ec 100644 --- a/theseus/src/api/jre.rs +++ b/theseus/src/api/jre.rs @@ -54,8 +54,12 @@ pub async fn get_optimal_jre_key(profile: &Profile) -> crate::Result { })?; // Get detailed manifest info from Daedalus - let version_info = - download::download_version_info(&state, version, profile.metadata.loader_version.as_ref()).await?; + let version_info = download::download_version_info( + &state, + version, + profile.metadata.loader_version.as_ref(), + ) + .await?; let optimal_key = match version_info .java_version .as_ref() diff --git a/theseus/src/api/profile.rs b/theseus/src/api/profile.rs index bf22233af..d7c9c0c19 100644 --- a/theseus/src/api/profile.rs +++ b/theseus/src/api/profile.rs @@ -146,8 +146,12 @@ pub async fn run_credentials( profile.metadata.game_version )) })?; - let version_info = - download::download_version_info(&state, version, profile.metadata.loader_version.as_ref()).await?; + let version_info = download::download_version_info( + &state, + version, + profile.metadata.loader_version.as_ref(), + ) + .await?; let pre_launch_hooks = &profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch; for hook in pre_launch_hooks.iter() { diff --git a/theseus/src/api/settings.rs b/theseus/src/api/settings.rs index a839f2c88..fdf3b92c6 100644 --- a/theseus/src/api/settings.rs +++ b/theseus/src/api/settings.rs @@ -20,5 +20,6 @@ pub async fn set(settings: Settings) -> crate::Result<()> { let state = State::get().await?; // Replaces the settings struct in the RwLock with the passed argument *state.settings.write().await = settings; + state.reset_semaphore().await; // reset semaphore to new max Ok(()) } diff --git a/theseus/src/state/mod.rs b/theseus/src/state/mod.rs index 8cb27300b..4e7d3bded 100644 --- a/theseus/src/state/mod.rs +++ b/theseus/src/state/mod.rs @@ -41,7 +41,9 @@ pub struct State { /// Information on the location of files used in the launcher pub directories: DirectoryInfo, /// Semaphore used to limit concurrent I/O and avoid errors - pub io_semaphore: Semaphore, + pub io_semaphore: RwLock, + /// Stored maximum number of sempahores of current io_semaphore + pub io_semaphore_max: RwLock, /// Launcher metadata pub metadata: Metadata, // TODO: settings API @@ -80,8 +82,10 @@ impl State { Settings::init(&directories.settings_file()).await?; // Loose initializations + let io_semaphore_max = settings.max_concurrent_downloads; + let io_semaphore = - Semaphore::new(settings.max_concurrent_downloads); + RwLock::new(Semaphore::new(io_semaphore_max)); // Launcher data let (metadata, profiles) = tokio::try_join! { @@ -112,6 +116,7 @@ impl State { Ok(Arc::new(Self { directories, io_semaphore, + io_semaphore_max: RwLock::new(io_semaphore_max as u32), metadata, settings: RwLock::new(settings), profiles: RwLock::new(profiles), @@ -158,4 +163,21 @@ impl State { Ok(()) } + + /// Reset semaphores 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) { + let settings = self.settings.read().await; + let mut io_semaphore = self.io_semaphore.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_downloads as u32; + *io_semaphore = Semaphore::new(settings.max_concurrent_downloads); + } } diff --git a/theseus/src/state/profiles.rs b/theseus/src/state/profiles.rs index 2eabb9c3c..50633ace6 100644 --- a/theseus/src/state/profiles.rs +++ b/theseus/src/state/profiles.rs @@ -10,8 +10,8 @@ use std::{ collections::HashMap, path::{Path, PathBuf}, }; -use tokio::fs; use tokio::sync::Semaphore; +use tokio::{fs, sync::RwLock}; const PROFILE_JSON_PATH: &str = "profile.json"; @@ -119,7 +119,7 @@ impl Profile { pub async fn set_icon<'a>( &'a mut self, cache_dir: &Path, - semaphore: &Semaphore, + semaphore: &RwLock, icon: bytes::Bytes, file_name: &str, ) -> crate::Result<&'a mut Self> { @@ -166,11 +166,11 @@ impl Profiles { #[tracing::instrument] pub async fn init( dirs: &DirectoryInfo, - io_sempahore: &Semaphore, + io_sempahore: &RwLock, ) -> crate::Result { let mut profiles = HashMap::new(); + fs::create_dir_all(dirs.profiles_dir()).await?; let mut entries = fs::read_dir(dirs.profiles_dir()).await?; - while let Some(entry) = entries.next_entry().await? { let path = entry.path(); if path.is_dir() { diff --git a/theseus/src/state/projects.rs b/theseus/src/state/projects.rs index d69c73841..6ad8e90de 100644 --- a/theseus/src/state/projects.rs +++ b/theseus/src/state/projects.rs @@ -10,7 +10,7 @@ use sha2::Digest; use std::collections::HashMap; use std::path::{Path, PathBuf}; use tokio::io::AsyncReadExt; -use tokio::sync::Semaphore; +use tokio::sync::{RwLock, Semaphore}; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Project { @@ -160,7 +160,7 @@ async fn read_icon_from_file( icon_path: Option, cache_dir: &Path, path: &PathBuf, - io_semaphore: &Semaphore, + io_semaphore: &RwLock, ) -> crate::Result> { if let Some(icon_path) = icon_path { // we have to repoen the zip twice here :( @@ -208,7 +208,7 @@ async fn read_icon_from_file( pub async fn infer_data_from_files( paths: Vec, cache_dir: PathBuf, - io_semaphore: &Semaphore, + io_semaphore: &RwLock, ) -> crate::Result> { let mut file_path_hashes = HashMap::new(); diff --git a/theseus/src/util/fetch.rs b/theseus/src/util/fetch.rs index 5b1a11010..c5384e285 100644 --- a/theseus/src/util/fetch.rs +++ b/theseus/src/util/fetch.rs @@ -5,7 +5,7 @@ use reqwest::Method; use serde::de::DeserializeOwned; use std::ffi::OsStr; use std::path::{Path, PathBuf}; -use tokio::sync::Semaphore; +use tokio::sync::{RwLock, Semaphore}; use tokio::{ fs::{self, File}, io::AsyncWriteExt, @@ -16,7 +16,7 @@ const FETCH_ATTEMPTS: usize = 3; pub async fn fetch( url: &str, sha1: Option<&str>, - semaphore: &Semaphore, + semaphore: &RwLock, ) -> crate::Result { fetch_advanced(Method::GET, url, sha1, semaphore).await } @@ -25,7 +25,7 @@ pub async fn fetch_json( method: Method, url: &str, sha1: Option<&str>, - semaphore: &Semaphore, + semaphore: &RwLock, ) -> crate::Result where T: DeserializeOwned, @@ -41,9 +41,10 @@ pub async fn fetch_advanced( method: Method, url: &str, sha1: Option<&str>, - semaphore: &Semaphore, + semaphore: &RwLock, ) -> crate::Result { - let _permit = semaphore.acquire().await?; + let io_semaphore = semaphore.read().await; + let _permit = io_semaphore.acquire().await?; for attempt in 1..=(FETCH_ATTEMPTS + 1) { let result = REQWEST_CLIENT.request(method.clone(), url).send().await; @@ -90,7 +91,7 @@ pub async fn fetch_advanced( pub async fn fetch_mirrors( mirrors: &[&str], sha1: Option<&str>, - semaphore: &Semaphore, + semaphore: &RwLock, ) -> crate::Result { if mirrors.is_empty() { return Err(crate::ErrorKind::InputError( @@ -114,9 +115,11 @@ pub async fn fetch_mirrors( pub async fn write<'a>( path: &Path, bytes: &[u8], - semaphore: &Semaphore, + semaphore: &RwLock, ) -> crate::Result<()> { - let _permit = semaphore.acquire().await?; + let io_semaphore = semaphore.read().await; + let _permit = io_semaphore.acquire().await?; + if let Some(parent) = path.parent() { fs::create_dir_all(parent).await?; } @@ -132,7 +135,7 @@ pub async fn write_cached_icon( icon_path: &str, cache_dir: &Path, bytes: Bytes, - semaphore: &Semaphore, + semaphore: &RwLock, ) -> crate::Result { let extension = Path::new(&icon_path).extension().and_then(OsStr::to_str); let hash = sha1_async(bytes.clone()).await?; diff --git a/theseus_gui/src-tauri/src/api/profile.rs b/theseus_gui/src-tauri/src/api/profile.rs index 353bcf474..a311884bb 100644 --- a/theseus_gui/src-tauri/src/api/profile.rs +++ b/theseus_gui/src-tauri/src/api/profile.rs @@ -56,7 +56,10 @@ pub async fn profile_run_wait(path: &Path) -> Result<()> { // for the actual Child in the state. // invoke('profile_run_credentials', {path, credentials})') #[tauri::command] -pub async fn profile_run_credentials(path: &Path, credentials: Credentials) -> Result { +pub async fn profile_run_credentials( + path: &Path, + credentials: Credentials, +) -> Result { let proc_lock = profile::run_credentials(path, &credentials).await?; let pid = proc_lock.read().await.child.id().ok_or_else(|| { theseus::Error::from(theseus::ErrorKind::LauncherError( diff --git a/theseus_playground/src/main.rs b/theseus_playground/src/main.rs index bbd950d9b..76d4b304b 100644 --- a/theseus_playground/src/main.rs +++ b/theseus_playground/src/main.rs @@ -31,7 +31,9 @@ async fn main() -> theseus::Result<()> { // Initialize state let st = State::get().await?; - st.settings.write().await.max_concurrent_downloads = 10; + st.settings.write().await.max_concurrent_downloads = 5; + // Changed the settings, so need to reset the semaphore + st.reset_semaphore().await; // Clear profiles println!("Clearing profiles.");