diff --git a/Cargo.lock b/Cargo.lock index fd0a26814..49b44440d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3764,6 +3764,7 @@ dependencies = [ "sys-info", "tauri", "tempfile", + "theseus_macros", "thiserror", "tokio", "tokio-stream", @@ -3817,6 +3818,7 @@ dependencies = [ "tauri", "tauri-build", "theseus", + "theseus_macros", "thiserror", "tokio", "tokio-stream", @@ -3827,6 +3829,14 @@ dependencies = [ "uuid 1.3.0", ] +[[package]] +name = "theseus_macros" +version = "0.1.0" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "theseus_playground" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index af7b943af..3d0c76f31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,8 @@ members = [ "theseus", "theseus_cli", "theseus_playground", - "theseus_gui/src-tauri" + "theseus_gui/src-tauri", + "theseus_macros" ] # Optimize for speed and reduce size on release builds diff --git a/theseus/Cargo.toml b/theseus/Cargo.toml index 112769577..e25c8b657 100644 --- a/theseus/Cargo.toml +++ b/theseus/Cargo.toml @@ -7,6 +7,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +theseus_macros = { path = "../theseus_macros" } + bytes = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/theseus/src/api/auth.rs b/theseus/src/api/auth.rs index 01d338138..0a356fe48 100644 --- a/theseus/src/api/auth.rs +++ b/theseus/src/api/auth.rs @@ -32,6 +32,7 @@ pub async fn authenticate_await_complete_flow() -> crate::Result { /// open a browser to the given URL and finally wait on the spawned future /// with the ability to cancel in case the browser is closed before finishing #[tracing::instrument] +#[theseus_macros::debug_pin] pub async fn authenticate( browser_url: oneshot::Sender, ) -> crate::Result { @@ -60,6 +61,7 @@ pub async fn authenticate( /// Refresh some credentials using Hydra, if needed /// This is the primary desired way to get credentials, as it will also refresh them. #[tracing::instrument] +#[theseus_macros::debug_pin] pub async fn refresh(user: uuid::Uuid) -> crate::Result { let state = State::get().await?; let mut users = state.users.write().await; diff --git a/theseus/src/api/jre.rs b/theseus/src/api/jre.rs index f4bb9a1e0..d874a2ff1 100644 --- a/theseus/src/api/jre.rs +++ b/theseus/src/api/jre.rs @@ -137,28 +137,27 @@ pub async fn find_java17_jres() -> crate::Result> { .collect()) } +#[theseus_macros::debug_pin] pub async fn auto_install_java(java_version: u32) -> crate::Result { - Box::pin( - async move { - let state = State::get().await?; + let state = State::get().await?; - let loading_bar = init_loading( - LoadingBarType::JavaDownload { - version: java_version, - }, - 100.0, - "Downloading java version", - ) - .await?; + let loading_bar = init_loading( + LoadingBarType::JavaDownload { + version: java_version, + }, + 100.0, + "Downloading java version", + ) + .await?; - #[derive(Deserialize)] - struct Package { - pub download_url: String, - pub name: PathBuf, - } + #[derive(Deserialize)] + struct Package { + pub download_url: String, + pub name: PathBuf, + } - emit_loading(&loading_bar, 0.0, Some("Fetching java version")).await?; - let packages = fetch_json::>( + emit_loading(&loading_bar, 0.0, Some("Fetching java version")).await?; + let packages = fetch_json::>( Method::GET, &format!( "https://api.azul.com/metadata/v1/zulu/packages?arch={}&java_version={}&os={}&archive_type=zip&javafx_bundled=false&java_package_type=jre&page_size=1", @@ -168,58 +167,56 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result { None, &state.fetch_semaphore, ).await?; - emit_loading(&loading_bar, 10.0, Some("Downloading java version")).await?; + emit_loading(&loading_bar, 10.0, Some("Downloading java version")).await?; - if let Some(download) = packages.first() { - let file = fetch_advanced( - Method::GET, - &download.download_url, - None, - None, - None, - Some((&loading_bar, 80.0)), - &state.fetch_semaphore, - ) - .await?; + if let Some(download) = packages.first() { + let file = fetch_advanced( + Method::GET, + &download.download_url, + None, + None, + None, + Some((&loading_bar, 80.0)), + &state.fetch_semaphore, + ) + .await?; - let path = state.directories.java_versions_dir(); + let path = state.directories.java_versions_dir(); - if path.exists() { - tokio::fs::remove_dir_all(&path).await?; - } + if path.exists() { + tokio::fs::remove_dir_all(&path).await?; + } - let mut archive = zip::ZipArchive::new(std::io::Cursor::new(file)) - .map_err(|_| { - crate::Error::from(crate::ErrorKind::InputError( - "Failed to read java zip".to_string(), - )) - })?; + let mut archive = zip::ZipArchive::new(std::io::Cursor::new(file)) + .map_err(|_| { + crate::Error::from(crate::ErrorKind::InputError( + "Failed to read java zip".to_string(), + )) + })?; - emit_loading(&loading_bar, 0.0, Some("Extracting java")).await?; - archive.extract(&path).map_err(|_| { - crate::Error::from(crate::ErrorKind::InputError( - "Failed to extract java zip".to_string(), - )) - })?; - emit_loading(&loading_bar, 100.0, Some("Done extracting java")).await?; - Ok(path - .join( - download - .name - .file_stem() - .unwrap_or_default() - .to_string_lossy() - .to_string(), - ) - .join(format!("zulu-{}.jre/Contents/Home/bin/java", java_version))) - } else { - Err(crate::ErrorKind::LauncherError(format!( + emit_loading(&loading_bar, 0.0, Some("Extracting java")).await?; + archive.extract(&path).map_err(|_| { + crate::Error::from(crate::ErrorKind::InputError( + "Failed to extract java zip".to_string(), + )) + })?; + emit_loading(&loading_bar, 100.0, Some("Done extracting java")).await?; + Ok(path + .join( + download + .name + .file_stem() + .unwrap_or_default() + .to_string_lossy() + .to_string(), + ) + .join(format!("zulu-{}.jre/Contents/Home/bin/java", java_version))) + } else { + Err(crate::ErrorKind::LauncherError(format!( "No Java Version found for Java version {}, OS {}, and Architecture {}", java_version, std::env::consts::OS, std::env::consts::ARCH, )).into()) - } - } - ).await + } } // Get all JREs that exist on the system diff --git a/theseus/src/api/pack.rs b/theseus/src/api/pack.rs index 680ae5ed3..cba87f59b 100644 --- a/theseus/src/api/pack.rs +++ b/theseus/src/api/pack.rs @@ -76,125 +76,124 @@ enum PackDependency { Minecraft, } +#[tracing::instrument] +#[theseus_macros::debug_pin] pub async fn install_pack_from_version_id( version_id: String, title: String, icon_url: Option, ) -> crate::Result { let state = State::get().await?; - Box::pin(async move { - let profile = crate::api::profile_create::profile_create( - title.clone(), - "1.19.4".to_string(), - ModLoader::Vanilla, - None, - None, - icon_url.clone(), - None, - Some(true), - ) - .await?; + let profile = crate::api::profile_create::profile_create( + title.clone(), + "1.19.4".to_string(), + ModLoader::Vanilla, + None, + None, + icon_url.clone(), + None, + Some(true), + ) + .await?; - let loading_bar = init_loading( - LoadingBarType::PackFileDownload { - profile_path: profile.clone(), - pack_name: title, - icon: icon_url, - pack_version: version_id.clone(), - }, - 100.0, - "Downloading pack file", - ) - .await?; + let loading_bar = init_loading( + LoadingBarType::PackFileDownload { + profile_path: profile.clone(), + pack_name: title, + icon: icon_url, + 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.fetch_semaphore, - ) - .await?; - emit_loading(&loading_bar, 10.0, None).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.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) { - Some((file.url.clone(), file.hashes.get("sha1"))) - } else { - version - .files - .first() - .map(|file| (file.url.clone(), file.hashes.get("sha1"))) - } - .ok_or_else(|| { - crate::ErrorKind::InputError( - "Specified version has no files".to_string(), + let (url, hash) = + if let Some(file) = version.files.iter().find(|x| x.primary) { + Some((file.url.clone(), file.hashes.get("sha1"))) + } else { + version + .files + .first() + .map(|file| (file.url.clone(), file.hashes.get("sha1"))) + } + .ok_or_else(|| { + crate::ErrorKind::InputError( + "Specified version has no files".to_string(), + ) + })?; + + 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.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.fetch_semaphore).await?; + + let filename = icon_url.rsplit('/').next(); + + if let Some(filename) = filename { + Some( + write_cached_icon( + filename, + &state.directories.caches_dir(), + icon_bytes, + &state.io_semaphore, ) - })?; - - 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.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.fetch_semaphore).await?; - - let filename = icon_url.rsplit('/').next(); - - if let Some(filename) = filename { - Some( - write_cached_icon( - filename, - &state.directories.caches_dir(), - icon_bytes, - &state.io_semaphore, - ) - .await?, - ) - } else { - None - } + .await?, + ) } else { None - }; - emit_loading(&loading_bar, 10.0, None).await?; + } + } else { + None + }; + emit_loading(&loading_bar, 10.0, None).await?; - install_pack( - file, - icon, - Some(project.title), - Some(version.project_id), - Some(version.id), - Some(loading_bar), - profile, - ) - .await - }) + install_pack( + file, + icon, + Some(project.title), + Some(version.project_id), + Some(version.id), + Some(loading_bar), + profile, + ) .await } +#[tracing::instrument] +#[theseus_macros::debug_pin] pub async fn install_pack_from_file(path: PathBuf) -> crate::Result { let file = fs::read(&path).await?; @@ -228,6 +227,8 @@ pub async fn install_pack_from_file(path: PathBuf) -> crate::Result { .await } +#[tracing::instrument] +#[theseus_macros::debug_pin] async fn install_pack( file: bytes::Bytes, icon: Option, @@ -239,270 +240,261 @@ async fn install_pack( ) -> crate::Result { let state = &State::get().await?; - Box::pin(async move { - let reader: Cursor<&bytes::Bytes> = Cursor::new(&file); + let reader: Cursor<&bytes::Bytes> = Cursor::new(&file); - // Create zip reader around file - let mut zip_reader = - ZipFileReader::new(reader).await.map_err(|_| { - crate::Error::from(crate::ErrorKind::InputError( - "Failed to read input modpack zip".to_string(), - )) - })?; + // Create zip reader around file + let mut zip_reader = ZipFileReader::new(reader).await.map_err(|_| { + crate::Error::from(crate::ErrorKind::InputError( + "Failed to read input modpack zip".to_string(), + )) + })?; - // Extract index of modrinth.index.json - let zip_index_option = zip_reader + // Extract index of modrinth.index.json + let zip_index_option = zip_reader + .file() + .entries() + .iter() + .position(|f| f.entry().filename() == "modrinth.index.json"); + if let Some(zip_index) = zip_index_option { + let mut manifest = String::new(); + let entry = zip_reader .file() .entries() - .iter() - .position(|f| f.entry().filename() == "modrinth.index.json"); - if let Some(zip_index) = zip_index_option { - let mut manifest = String::new(); - let entry = zip_reader - .file() - .entries() - .get(zip_index) - .unwrap() - .entry() - .clone(); - let mut reader = zip_reader.entry(zip_index).await?; - reader.read_to_string_checked(&mut manifest, &entry).await?; + .get(zip_index) + .unwrap() + .entry() + .clone(); + let mut reader = zip_reader.entry(zip_index).await?; + reader.read_to_string_checked(&mut manifest, &entry).await?; - let pack: PackFormat = serde_json::from_str(&manifest)?; + let pack: PackFormat = serde_json::from_str(&manifest)?; - if &*pack.game != "minecraft" { - return Err(crate::ErrorKind::InputError( - "Pack does not support Minecraft".to_string(), - ) - .into()); - } + if &*pack.game != "minecraft" { + return Err(crate::ErrorKind::InputError( + "Pack does not support Minecraft".to_string(), + ) + .into()); + } - let mut game_version = None; - let mut mod_loader = None; - let mut loader_version = None; - for (key, value) in &pack.dependencies { - match key { - PackDependency::Forge => { - mod_loader = Some(ModLoader::Forge); - loader_version = Some(value); - } - PackDependency::FabricLoader => { - mod_loader = Some(ModLoader::Fabric); - loader_version = Some(value); - } - PackDependency::QuiltLoader => { - mod_loader = Some(ModLoader::Quilt); - loader_version = Some(value); - } - PackDependency::Minecraft => game_version = Some(value), + let mut game_version = None; + let mut mod_loader = None; + let mut loader_version = None; + for (key, value) in &pack.dependencies { + match key { + PackDependency::Forge => { + mod_loader = Some(ModLoader::Forge); + loader_version = Some(value); } + PackDependency::FabricLoader => { + mod_loader = Some(ModLoader::Fabric); + loader_version = Some(value); + } + PackDependency::QuiltLoader => { + mod_loader = Some(ModLoader::Quilt); + loader_version = Some(value); + } + PackDependency::Minecraft => game_version = Some(value), } + } - let game_version = if let Some(game_version) = game_version { - game_version - } else { - return Err(crate::ErrorKind::InputError( - "Pack did not specify Minecraft version".to_string(), - ) - .into()); - }; + let game_version = if let Some(game_version) = game_version { + game_version + } else { + return Err(crate::ErrorKind::InputError( + "Pack did not specify Minecraft version".to_string(), + ) + .into()); + }; - let loader_version = - crate::profile_create::get_loader_version_from_loader( - game_version.clone(), - mod_loader.unwrap_or(ModLoader::Vanilla), - loader_version.cloned(), - ) - .await?; - crate::api::profile::edit(&profile, |prof| { - prof.metadata.name = - override_title.clone().unwrap_or_else(|| pack.name.clone()); - prof.install_stage = ProfileInstallStage::PackInstalling; - prof.metadata.linked_data = Some(LinkedData { - project_id: project_id.clone(), - version_id: version_id.clone(), - }); - prof.metadata.icon = icon.clone(); - prof.metadata.game_version = game_version.clone(); - prof.metadata.loader_version = loader_version.clone(); - prof.metadata.loader = mod_loader.unwrap_or(ModLoader::Vanilla); - - async { Ok(()) } - }) + let loader_version = + crate::profile_create::get_loader_version_from_loader( + game_version.clone(), + mod_loader.unwrap_or(ModLoader::Vanilla), + loader_version.cloned(), + ) .await?; - State::sync().await?; + crate::api::profile::edit(&profile, |prof| { + prof.metadata.name = + override_title.clone().unwrap_or_else(|| pack.name.clone()); + prof.install_stage = ProfileInstallStage::PackInstalling; + prof.metadata.linked_data = Some(LinkedData { + project_id: project_id.clone(), + version_id: version_id.clone(), + }); + prof.metadata.icon = icon.clone(); + prof.metadata.game_version = game_version.clone(); + prof.metadata.loader_version = loader_version.clone(); + prof.metadata.loader = mod_loader.unwrap_or(ModLoader::Vanilla); - let profile = profile.clone(); - let result = async { - let loading_bar = init_or_edit_loading( - existing_loading_bar, - LoadingBarType::PackDownload { - profile_path: profile.clone(), - pack_name: pack.name.clone(), - icon, - pack_id: project_id, - pack_version: version_id, - }, - 100.0, - "Downloading modpack", - ) - .await?; + async { Ok(()) } + }) + .await?; + State::sync().await?; - let num_files = pack.files.len(); - use futures::StreamExt; - loading_try_for_each_concurrent( - futures::stream::iter(pack.files.into_iter()) - .map(Ok::), - None, - Some(&loading_bar), - 70.0, - num_files, - None, - |project| { - let profile = profile.clone(); - async move { - //TODO: Future update: prompt user for optional files in a modpack - if let Some(env) = project.env { - if env - .get(&EnvType::Client) - .map(|x| x == &SideType::Unsupported) - .unwrap_or(false) - { - return Ok(()); - } + let profile = profile.clone(); + let result = async { + let loading_bar = init_or_edit_loading( + existing_loading_bar, + LoadingBarType::PackDownload { + profile_path: profile.clone(), + pack_name: pack.name.clone(), + icon, + pack_id: project_id, + pack_version: version_id, + }, + 100.0, + "Downloading modpack", + ) + .await?; + + let num_files = pack.files.len(); + use futures::StreamExt; + loading_try_for_each_concurrent( + futures::stream::iter(pack.files.into_iter()) + .map(Ok::), + None, + Some(&loading_bar), + 70.0, + num_files, + None, + |project| { + let profile = profile.clone(); + async move { + //TODO: Future update: prompt user for optional files in a modpack + if let Some(env) = project.env { + if env + .get(&EnvType::Client) + .map(|x| x == &SideType::Unsupported) + .unwrap_or(false) + { + return Ok(()); } + } - let file = fetch_mirrors( - &project - .downloads - .iter() - .map(|x| &**x) - .collect::>(), - project - .hashes - .get(&PackFileHash::Sha1) - .map(|x| &**x), - &state.fetch_semaphore, - ) - .await?; + let file = fetch_mirrors( + &project + .downloads + .iter() + .map(|x| &**x) + .collect::>(), + project + .hashes + .get(&PackFileHash::Sha1) + .map(|x| &**x), + &state.fetch_semaphore, + ) + .await?; - let path = std::path::Path::new(&project.path) - .components() - .next(); - if let Some(path) = path { - match path { - Component::CurDir - | Component::Normal(_) => { - let path = profile.join(project.path); - write( - &path, - &file, - &state.io_semaphore, - ) + let path = std::path::Path::new(&project.path) + .components() + .next(); + if let Some(path) = path { + match path { + Component::CurDir | Component::Normal(_) => { + let path = profile.join(project.path); + write(&path, &file, &state.io_semaphore) .await?; - } - _ => {} - }; - } - Ok(()) + } + _ => {} + }; } - }, - ) + Ok(()) + } + }, + ) + .await?; + + emit_loading(&loading_bar, 0.0, Some("Extracting overrides")) .await?; - emit_loading(&loading_bar, 0.0, Some("Extracting overrides")) - .await?; + let mut total_len = 0; - let mut total_len = 0; + for index in 0..zip_reader.file().entries().len() { + let file = + zip_reader.file().entries().get(index).unwrap().entry(); - for index in 0..zip_reader.file().entries().len() { - let file = - zip_reader.file().entries().get(index).unwrap().entry(); - - if (file.filename().starts_with("overrides") - || file.filename().starts_with("client_overrides")) - && !file.filename().ends_with('/') - { - total_len += 1; - } + if (file.filename().starts_with("overrides") + || file.filename().starts_with("client_overrides")) + && !file.filename().ends_with('/') + { + total_len += 1; } + } - for index in 0..zip_reader.file().entries().len() { - let file = zip_reader - .file() - .entries() - .get(index) - .unwrap() - .entry() - .clone(); + for index in 0..zip_reader.file().entries().len() { + let file = zip_reader + .file() + .entries() + .get(index) + .unwrap() + .entry() + .clone(); - let file_path = PathBuf::from(file.filename()); - if (file.filename().starts_with("overrides") - || file.filename().starts_with("client_overrides")) - && !file.filename().ends_with('/') - { - // Reads the file into the 'content' variable - let mut content = Vec::new(); - let mut reader = zip_reader.entry(index).await?; - reader.read_to_end_checked(&mut content, &file).await?; + let file_path = PathBuf::from(file.filename()); + if (file.filename().starts_with("overrides") + || file.filename().starts_with("client_overrides")) + && !file.filename().ends_with('/') + { + // Reads the file into the 'content' variable + let mut content = Vec::new(); + let mut reader = zip_reader.entry(index).await?; + reader.read_to_end_checked(&mut content, &file).await?; - let mut new_path = PathBuf::new(); - let components = file_path.components().skip(1); + let mut new_path = PathBuf::new(); + let components = file_path.components().skip(1); - for component in components { - new_path.push(component); - } + for component in components { + new_path.push(component); + } - if new_path.file_name().is_some() { - write( - &profile.join(new_path), - &content, - &state.io_semaphore, - ) - .await?; - } - - emit_loading( - &loading_bar, - 30.0 / total_len as f64, - Some(&format!( - "Extracting override {}/{}", - index, total_len - )), + if new_path.file_name().is_some() { + write( + &profile.join(new_path), + &content, + &state.io_semaphore, ) .await?; } - } - if let Some(profile_val) = - crate::api::profile::get(&profile, None).await? - { - crate::launcher::install_minecraft( - &profile_val, - Some(loading_bar), + emit_loading( + &loading_bar, + 30.0 / total_len as f64, + Some(&format!( + "Extracting override {}/{}", + index, total_len + )), ) .await?; } - - Ok::(profile.clone()) } - .await; - match result { - Ok(profile) => Ok(profile), - Err(err) => { - let _ = crate::api::profile::remove(&profile).await; - - Err(err) - } + if let Some(profile_val) = + crate::api::profile::get(&profile, None).await? + { + crate::launcher::install_minecraft( + &profile_val, + Some(loading_bar), + ) + .await?; } - } else { - let _ = crate::api::profile::remove(&profile).await; - Err(crate::Error::from(crate::ErrorKind::InputError( - "No pack manifest found in mrpack".to_string(), - ))) + Ok::(profile.clone()) } - }) - .await + .await; + + match result { + Ok(profile) => Ok(profile), + Err(err) => { + let _ = crate::api::profile::remove(&profile).await; + + Err(err) + } + } + } else { + let _ = crate::api::profile::remove(&profile).await; + + Err(crate::Error::from(crate::ErrorKind::InputError( + "No pack manifest found in mrpack".to_string(), + ))) + } } diff --git a/theseus/src/api/profile.rs b/theseus/src/api/profile.rs index c1417ed8f..fad638d05 100644 --- a/theseus/src/api/profile.rs +++ b/theseus/src/api/profile.rs @@ -123,47 +123,48 @@ pub async fn install(path: &Path) -> crate::Result<()> { Ok(()) } +#[tracing::instrument] +#[theseus_macros::debug_pin] pub async fn update_all(profile_path: &Path) -> crate::Result<()> { - Box::pin(async move { - if let Some(profile) = get(profile_path, None).await? { - let loading_bar = init_loading( - LoadingBarType::ProfileUpdate { - profile_path: profile.path.clone(), - profile_name: profile.metadata.name.clone(), - }, - 100.0, - "Updating profile", - ) - .await?; + if let Some(profile) = get(profile_path, None).await? { + let loading_bar = init_loading( + LoadingBarType::ProfileUpdate { + profile_path: profile.path.clone(), + profile_name: profile.metadata.name.clone(), + }, + 100.0, + "Updating profile", + ) + .await?; - use futures::StreamExt; - loading_try_for_each_concurrent( - futures::stream::iter(profile.projects.keys()) - .map(Ok::<&PathBuf, crate::Error>), - None, - Some(&loading_bar), - 100.0, - profile.projects.keys().len(), - None, - |project| async move { - let _ = update_project(profile_path, project).await?; + use futures::StreamExt; + loading_try_for_each_concurrent( + futures::stream::iter(profile.projects.keys()) + .map(Ok::<&PathBuf, crate::Error>), + None, + Some(&loading_bar), + 100.0, + profile.projects.keys().len(), + None, + |project| async move { + let _ = update_project(profile_path, project).await?; - Ok(()) - }, - ) - .await?; + Ok(()) + }, + ) + .await?; - Ok(()) - } else { - Err(crate::ErrorKind::UnmanagedProfileError( - profile_path.display().to_string(), - ) - .as_error()) - } - }) - .await + Ok(()) + } else { + Err(crate::ErrorKind::UnmanagedProfileError( + profile_path.display().to_string(), + ) + .as_error()) + } } +#[tracing::instrument] +#[theseus_macros::debug_pin] pub async fn update_project( profile_path: &Path, project_path: &Path, @@ -304,113 +305,108 @@ pub async fn remove_project( /// failing with an error if no credentials are available #[tracing::instrument] pub async fn run(path: &Path) -> crate::Result>> { - Box::pin(async move { - let state = State::get().await?; + let state = State::get().await?; - // Get default account and refresh credentials (preferred way to log in) - let default_account = state.settings.read().await.default_user; - let credentials = if let Some(default_account) = default_account { - refresh(default_account).await? + // Get default account and refresh credentials (preferred way to log in) + let default_account = state.settings.read().await.default_user; + let credentials = if let Some(default_account) = default_account { + refresh(default_account).await? + } else { + // If no default account, try to use a logged in account + let users = auth::users().await?; + let last_account = users.first(); + if let Some(last_account) = last_account { + refresh(last_account.id).await? } else { - // If no default account, try to use a logged in account - let users = auth::users().await?; - let last_account = users.first(); - if let Some(last_account) = last_account { - refresh(last_account.id).await? - } else { - return Err(crate::ErrorKind::NoCredentialsError.as_error()); - } - }; - run_credentials(path, &credentials).await - }) - .await + return Err(crate::ErrorKind::NoCredentialsError.as_error()); + } + }; + run_credentials(path, &credentials).await } /// Run Minecraft using a profile, and credentials for authentication /// Returns Arc pointer to RwLock to Child #[tracing::instrument] +#[theseus_macros::debug_pin] pub async fn run_credentials( path: &Path, credentials: &auth::Credentials, ) -> crate::Result>> { - Box::pin(async move { - let state = State::get().await?; - let settings = state.settings.read().await; - let profile = get(path, None).await?.ok_or_else(|| { - crate::ErrorKind::OtherError(format!( - "Tried to run a nonexistent or unloaded profile at path {}!", - path.display() - )) - })?; + let state = State::get().await?; + let settings = state.settings.read().await; + let profile = get(path, None).await?.ok_or_else(|| { + crate::ErrorKind::OtherError(format!( + "Tried to run a nonexistent or unloaded profile at path {}!", + path.display() + )) + })?; - let pre_launch_hooks = - &profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch; - if let Some(hook) = pre_launch_hooks { - // TODO: hook parameters - let mut cmd = hook.split(' '); - if let Some(command) = cmd.next() { - let result = Command::new(command) - .args(&cmd.collect::>()) - .current_dir(path) - .spawn()? - .wait() - .await?; + let pre_launch_hooks = + &profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch; + if let Some(hook) = pre_launch_hooks { + // TODO: hook parameters + let mut cmd = hook.split(' '); + if let Some(command) = cmd.next() { + let result = Command::new(command) + .args(&cmd.collect::>()) + .current_dir(path) + .spawn()? + .wait() + .await?; - if !result.success() { - return Err(crate::ErrorKind::LauncherError(format!( - "Non-zero exit code for pre-launch hook: {}", - result.code().unwrap_or(-1) - )) - .as_error()); - } + if !result.success() { + return Err(crate::ErrorKind::LauncherError(format!( + "Non-zero exit code for pre-launch hook: {}", + result.code().unwrap_or(-1) + )) + .as_error()); } } + } - let java_args = profile - .java - .as_ref() - .and_then(|it| it.extra_arguments.as_ref()) - .unwrap_or(&settings.custom_java_args); + let java_args = profile + .java + .as_ref() + .and_then(|it| it.extra_arguments.as_ref()) + .unwrap_or(&settings.custom_java_args); - let wrapper = profile - .hooks - .as_ref() - .map_or(&settings.hooks.wrapper, |it| &it.wrapper); + let wrapper = profile + .hooks + .as_ref() + .map_or(&settings.hooks.wrapper, |it| &it.wrapper); - let memory = profile.memory.unwrap_or(settings.memory); - let resolution = profile.resolution.unwrap_or(settings.game_resolution); + let memory = profile.memory.unwrap_or(settings.memory); + let resolution = profile.resolution.unwrap_or(settings.game_resolution); - let env_args = &settings.custom_env_args; + let env_args = &settings.custom_env_args; - // Post post exit hooks - let post_exit_hook = - &profile.hooks.as_ref().unwrap_or(&settings.hooks).post_exit; + // Post post exit hooks + let post_exit_hook = + &profile.hooks.as_ref().unwrap_or(&settings.hooks).post_exit; - let post_exit_hook = if let Some(hook) = post_exit_hook { - let mut cmd = hook.split(' '); - if let Some(command) = cmd.next() { - let mut command = Command::new(command); - command.args(&cmd.collect::>()).current_dir(path); - Some(command) - } else { - None - } + let post_exit_hook = if let Some(hook) = post_exit_hook { + let mut cmd = hook.split(' '); + if let Some(command) = cmd.next() { + let mut command = Command::new(command); + command.args(&cmd.collect::>()).current_dir(path); + Some(command) } else { None - }; + } + } else { + None + }; - let mc_process = crate::launcher::launch_minecraft( - java_args, - env_args, - wrapper, - &memory, - &resolution, - credentials, - post_exit_hook, - &profile, - ) - .await?; - Ok(mc_process) - }) - .await + let mc_process = crate::launcher::launch_minecraft( + java_args, + env_args, + wrapper, + &memory, + &resolution, + credentials, + post_exit_hook, + &profile, + ) + .await?; + Ok(mc_process) } diff --git a/theseus/src/api/profile_create.rs b/theseus/src/api/profile_create.rs index 8daac110e..5f35d3f9d 100644 --- a/theseus/src/api/profile_create.rs +++ b/theseus/src/api/profile_create.rs @@ -39,6 +39,7 @@ pub async fn profile_create_empty() -> crate::Result { // Creates a profile at the given filepath and adds it to the in-memory state // Returns filepath at which it can be accessed in the State #[tracing::instrument] +#[theseus_macros::debug_pin] #[allow(clippy::too_many_arguments)] pub async fn profile_create( name: String, // the name of the profile, and relative path @@ -52,92 +53,91 @@ pub async fn profile_create( ) -> crate::Result { trace!("Creating new profile. {}", name); let state = State::get().await?; - Box::pin(async move { - let uuid = Uuid::new_v4(); - let path = state.directories.profiles_dir().join(uuid.to_string()); - if path.exists() { - if !path.is_dir() { - return Err(ProfileCreationError::NotFolder.into()); - } - if path.join("profile.json").exists() { - return Err(ProfileCreationError::ProfileExistsError( - path.join("profile.json"), - ) - .into()); - } - - if ReadDirStream::new(fs::read_dir(&path).await?) - .next() - .await - .is_some() - { - return Err(ProfileCreationError::NotEmptyFolder.into()); - } - } else { - fs::create_dir_all(&path).await?; + let uuid = Uuid::new_v4(); + let path = state.directories.profiles_dir().join(uuid.to_string()); + if path.exists() { + if !path.is_dir() { + return Err(ProfileCreationError::NotFolder.into()); } - - info!( - "Creating profile at path {}", - &canonicalize(&path)?.display() - ); - let loader = if modloader != ModLoader::Vanilla { - get_loader_version_from_loader( - game_version.clone(), - modloader, - loader_version, + if path.join("profile.json").exists() { + return Err(ProfileCreationError::ProfileExistsError( + path.join("profile.json"), ) - .await? - } else { - None - }; - - // Fully canonicalize now that its created for storing purposes - let path = canonicalize(&path)?; - let mut profile = - Profile::new(uuid, name, game_version, path.clone()).await?; - if let Some(ref icon) = icon { - let bytes = tokio::fs::read(icon).await?; - profile - .set_icon( - &state.directories.caches_dir(), - &state.io_semaphore, - bytes::Bytes::from(bytes), - &icon.to_string_lossy(), - ) - .await?; - } - profile.metadata.icon_url = icon_url; - if let Some(loader_version) = loader { - profile.metadata.loader = modloader; - profile.metadata.loader_version = Some(loader_version); + .into()); } - profile.metadata.linked_data = linked_data; - - emit_profile( - uuid, - path.clone(), - &profile.metadata.name, - ProfilePayloadType::Created, - ) - .await?; - + if ReadDirStream::new(fs::read_dir(&path).await?) + .next() + .await + .is_some() { - let mut profiles = state.profiles.write().await; - profiles.insert(profile.clone()).await?; + return Err(ProfileCreationError::NotEmptyFolder.into()); } + } else { + fs::create_dir_all(&path).await?; + } - if !skip_install_profile.unwrap_or(false) { - crate::launcher::install_minecraft(&profile, None).await?; - } - State::sync().await?; + info!( + "Creating profile at path {}", + &canonicalize(&path)?.display() + ); + let loader = if modloader != ModLoader::Vanilla { + get_loader_version_from_loader( + game_version.clone(), + modloader, + loader_version, + ) + .await? + } else { + None + }; - Ok(path) - }) - .await + // Fully canonicalize now that its created for storing purposes + let path = canonicalize(&path)?; + let mut profile = + Profile::new(uuid, name, game_version, path.clone()).await?; + if let Some(ref icon) = icon { + let bytes = tokio::fs::read(icon).await?; + profile + .set_icon( + &state.directories.caches_dir(), + &state.io_semaphore, + bytes::Bytes::from(bytes), + &icon.to_string_lossy(), + ) + .await?; + } + profile.metadata.icon_url = icon_url; + if let Some(loader_version) = loader { + profile.metadata.loader = modloader; + profile.metadata.loader_version = Some(loader_version); + } + + profile.metadata.linked_data = linked_data; + + emit_profile( + uuid, + path.clone(), + &profile.metadata.name, + ProfilePayloadType::Created, + ) + .await?; + + { + let mut profiles = state.profiles.write().await; + profiles.insert(profile.clone()).await?; + } + + if !skip_install_profile.unwrap_or(false) { + crate::launcher::install_minecraft(&profile, None).await?; + } + State::sync().await?; + + Ok(path) } +#[tracing::instrument] +#[theseus_macros::debug_pin] pub(crate) async fn get_loader_version_from_loader( game_version: String, loader: ModLoader, diff --git a/theseus/src/event/emit.rs b/theseus/src/event/emit.rs index 49aaea192..80d1a60dd 100644 --- a/theseus/src/event/emit.rs +++ b/theseus/src/event/emit.rs @@ -46,6 +46,7 @@ const CLI_PROGRESS_BAR_TOTAL: u64 = 1000; // This will generate a LoadingBarId, which is used to refer to the loading bar uniquely. // total is the total amount of work to be done- all emissions will be considered a fraction of this value (should be 1 or 100 for simplicity) // title is the title of the loading bar +#[theseus_macros::debug_pin] pub async fn init_loading( bar_type: LoadingBarType, total: f64, @@ -134,6 +135,7 @@ pub async fn edit_loading( // By convention, fraction is the fraction of the progress bar that is filled #[allow(unused_variables)] #[tracing::instrument(level = "debug")] +#[theseus_macros::debug_pin] pub async fn emit_loading( key: &LoadingBarId, increment_frac: f64, @@ -329,6 +331,8 @@ macro_rules! loading_join { // 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 +#[tracing::instrument(skip(stream, f))] +#[theseus_macros::debug_pin] pub async fn loading_try_for_each_concurrent( stream: I, limit: Option, diff --git a/theseus/src/launcher/download.rs b/theseus/src/launcher/download.rs index feb4366d5..2ab99866c 100644 --- a/theseus/src/launcher/download.rs +++ b/theseus/src/launcher/download.rs @@ -54,6 +54,7 @@ pub async fn download_minecraft( } #[tracing::instrument(skip_all, fields(version = version.id.as_str(), loader = ?loader))] +#[theseus_macros::debug_pin] pub async fn download_version_info( st: &State, version: &GameVersion, @@ -61,93 +62,84 @@ pub async fn download_version_info( force: Option, loading_bar: Option<&LoadingBarId>, ) -> crate::Result { - Box::pin(async move { - let version_id = loader.map_or(version.id.clone(), |it| { - format!("{}-{}", version.id, it.id) - }); - tracing::debug!("Loading version info for Minecraft {version_id}"); - let path = st - .directories - .version_dir(&version_id) - .join(format!("{version_id}.json")); + let version_id = loader + .map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id)); + tracing::debug!("Loading version info for Minecraft {version_id}"); + let path = st + .directories + .version_dir(&version_id) + .join(format!("{version_id}.json")); - let res = if path.exists() && !force.unwrap_or(false) { - fs::read(path) - .err_into::() - .await - .and_then(|ref it| Ok(serde_json::from_slice(it)?)) - } else { - tracing::info!( - "Downloading version info for version {}", - &version.id - ); - let mut info = d::minecraft::fetch_version_info(version).await?; + let res = if path.exists() && !force.unwrap_or(false) { + fs::read(path) + .err_into::() + .await + .and_then(|ref it| Ok(serde_json::from_slice(it)?)) + } else { + tracing::info!("Downloading version info for version {}", &version.id); + let mut info = d::minecraft::fetch_version_info(version).await?; - if let Some(loader) = loader { - let partial = - d::modded::fetch_partial_version(&loader.url).await?; - info = d::modded::merge_partial_version(partial, info); - } - info.id = version_id.clone(); - - write(&path, &serde_json::to_vec(&info)?, &st.io_semaphore).await?; - Ok(info) - }?; - - if let Some(loading_bar) = loading_bar { - emit_loading(loading_bar, 5.0, None).await?; + if let Some(loader) = loader { + let partial = d::modded::fetch_partial_version(&loader.url).await?; + info = d::modded::merge_partial_version(partial, info); } + info.id = version_id.clone(); - tracing::debug!("Loaded version info for Minecraft {version_id}"); - Ok(res) - }) - .await + write(&path, &serde_json::to_vec(&info)?, &st.io_semaphore).await?; + Ok(info) + }?; + + if let Some(loading_bar) = loading_bar { + emit_loading(loading_bar, 5.0, None).await?; + } + + tracing::debug!("Loaded version info for Minecraft {version_id}"); + Ok(res) } #[tracing::instrument(skip_all)] +#[theseus_macros::debug_pin] pub async fn download_client( st: &State, version_info: &GameVersionInfo, loading_bar: Option<&LoadingBarId>, ) -> crate::Result<()> { - Box::pin(async move { - let version = &version_info.id; - tracing::debug!("Locating client for version {version}"); - let client_download = version_info - .downloads - .get(&d::minecraft::DownloadType::Client) - .ok_or( - crate::ErrorKind::LauncherError(format!( - "No client downloads exist for version {version}" - )) - .as_error(), - )?; - let path = st - .directories - .version_dir(version) - .join(format!("{version}.jar")); + let version = &version_info.id; + tracing::debug!("Locating client for version {version}"); + let client_download = version_info + .downloads + .get(&d::minecraft::DownloadType::Client) + .ok_or( + crate::ErrorKind::LauncherError(format!( + "No client downloads exist for version {version}" + )) + .as_error(), + )?; + let path = st + .directories + .version_dir(version) + .join(format!("{version}.jar")); - if !path.exists() { - let bytes = fetch( - &client_download.url, - Some(&client_download.sha1), - &st.fetch_semaphore, - ) - .await?; - write(&path, &bytes, &st.io_semaphore).await?; - tracing::trace!("Fetched client version {version}"); - } - if let Some(loading_bar) = loading_bar { - emit_loading(loading_bar, 9.0, None).await?; - } + if !path.exists() { + let bytes = fetch( + &client_download.url, + Some(&client_download.sha1), + &st.fetch_semaphore, + ) + .await?; + write(&path, &bytes, &st.io_semaphore).await?; + tracing::trace!("Fetched client version {version}"); + } + if let Some(loading_bar) = loading_bar { + emit_loading(loading_bar, 9.0, None).await?; + } - tracing::debug!("Client loaded for version {version}!"); - Ok(()) - }) - .await + tracing::debug!("Client loaded for version {version}!"); + Ok(()) } #[tracing::instrument(skip_all)] +#[theseus_macros::debug_pin] pub async fn download_assets_index( st: &State, version: &GameVersionInfo, @@ -179,6 +171,7 @@ pub async fn download_assets_index( } #[tracing::instrument(skip(st, index))] +#[theseus_macros::debug_pin] pub async fn download_assets( st: &State, with_legacy: bool, @@ -186,13 +179,12 @@ pub async fn download_assets( loading_bar: Option<&LoadingBarId>, loading_amount: f64, ) -> crate::Result<()> { - Box::pin(async move { - tracing::debug!("Loading assets"); - let num_futs = index.objects.len(); - let assets = stream::iter(index.objects.iter()) - .map(Ok::<(&String, &Asset), crate::Error>); + tracing::debug!("Loading assets"); + let num_futs = index.objects.len(); + let assets = stream::iter(index.objects.iter()) + .map(Ok::<(&String, &Asset), crate::Error>); - loading_try_for_each_concurrent(assets, + loading_try_for_each_concurrent(assets, None, loading_bar, loading_amount, @@ -236,12 +228,12 @@ pub async fn download_assets( tracing::trace!("Loaded asset with hash {hash}"); Ok(()) }).await?; - tracing::debug!("Done loading assets!"); - Ok(()) - }).await + tracing::debug!("Done loading assets!"); + Ok(()) } #[tracing::instrument(skip(st, libraries))] +#[theseus_macros::debug_pin] pub async fn download_libraries( st: &State, libraries: &[Library], @@ -250,15 +242,14 @@ pub async fn download_libraries( loading_amount: f64, java_arch: &str, ) -> crate::Result<()> { - Box::pin(async move { - tracing::debug!("Loading libraries"); + tracing::debug!("Loading libraries"); - tokio::try_join! { - fs::create_dir_all(st.directories.libraries_dir()), - fs::create_dir_all(st.directories.version_natives_dir(version)) - }?; - let num_files = libraries.len(); - loading_try_for_each_concurrent( + tokio::try_join! { + fs::create_dir_all(st.directories.libraries_dir()), + fs::create_dir_all(st.directories.version_natives_dir(version)) + }?; + let num_files = libraries.len(); + loading_try_for_each_concurrent( stream::iter(libraries.iter()) .map(Ok::<&Library, crate::Error>), None, loading_bar,loading_amount,num_files, None,|library| async move { if let Some(rules) = &library.rules { @@ -342,8 +333,6 @@ pub async fn download_libraries( } ).await?; - tracing::debug!("Done loading libraries!"); - Ok(()) - - }).await + tracing::debug!("Done loading libraries!"); + Ok(()) } diff --git a/theseus/src/launcher/mod.rs b/theseus/src/launcher/mod.rs index c7e073570..da81f1d3d 100644 --- a/theseus/src/launcher/mod.rs +++ b/theseus/src/launcher/mod.rs @@ -92,183 +92,186 @@ async fn get_java_version_from_profile( } #[tracing::instrument(skip(profile))] +#[theseus_macros::debug_pin] pub async fn install_minecraft( profile: &Profile, existing_loading_bar: Option, ) -> crate::Result<()> { - Box::pin(async move { - let loading_bar = init_or_edit_loading( - existing_loading_bar, - LoadingBarType::MinecraftDownload { - // If we are downloading minecraft for a profile, provide its name and uuid - profile_name: profile.metadata.name.clone(), - profile_path: profile.path.clone(), - }, - 100.0, - "Downloading Minecraft", - ) - .await?; + let loading_bar = init_or_edit_loading( + existing_loading_bar, + LoadingBarType::MinecraftDownload { + // If we are downloading minecraft for a profile, provide its name and uuid + profile_name: profile.metadata.name.clone(), + profile_path: profile.path.clone(), + }, + 100.0, + "Downloading Minecraft", + ) + .await?; - crate::api::profile::edit(&profile.path, |prof| { - prof.install_stage = ProfileInstallStage::Installing; + crate::api::profile::edit(&profile.path, |prof| { + prof.install_stage = ProfileInstallStage::Installing; - async { Ok(()) } - }) - .await?; - State::sync().await?; + async { Ok(()) } + }) + .await?; + State::sync().await?; - let state = State::get().await?; - let instance_path = &canonicalize(&profile.path)?; - let metadata = state.metadata.read().await; + let state = State::get().await?; + let instance_path = &canonicalize(&profile.path)?; + let metadata = state.metadata.read().await; - let version = metadata - .minecraft - .versions - .iter() - .find(|it| it.id == profile.metadata.game_version) - .ok_or(crate::ErrorKind::LauncherError(format!( - "Invalid game version: {}", - profile.metadata.game_version - )))?; + let version = metadata + .minecraft + .versions + .iter() + .find(|it| it.id == profile.metadata.game_version) + .ok_or(crate::ErrorKind::LauncherError(format!( + "Invalid game version: {}", + profile.metadata.game_version + )))?; - let version_jar = profile - .metadata - .loader_version - .as_ref() - .map_or(version.id.clone(), |it| { - format!("{}-{}", version.id.clone(), it.id.clone()) - }); + let version_jar = profile + .metadata + .loader_version + .as_ref() + .map_or(version.id.clone(), |it| { + format!("{}-{}", version.id.clone(), it.id.clone()) + }); - // Download version info (5) - let mut version_info = download::download_version_info( - &state, - version, - profile.metadata.loader_version.as_ref(), - None, - Some(&loading_bar), - ) - .await?; + // Download version info (5) + let mut version_info = download::download_version_info( + &state, + version, + profile.metadata.loader_version.as_ref(), + None, + Some(&loading_bar), + ) + .await?; - let java_version = get_java_version_from_profile(profile, &version_info).await?; + let java_version = + get_java_version_from_profile(profile, &version_info).await?; - // Download minecraft (5-90) - download::download_minecraft(&state, &version_info, &loading_bar, &java_version.architecture).await?; + // Download minecraft (5-90) + download::download_minecraft( + &state, + &version_info, + &loading_bar, + &java_version.architecture, + ) + .await?; - if let Some(processors) = &version_info.processors { - let client_path = state - .directories - .version_dir(&version_jar) - .join(format!("{version_jar}.jar")); + if let Some(processors) = &version_info.processors { + let client_path = state + .directories + .version_dir(&version_jar) + .join(format!("{version_jar}.jar")); - if let Some(ref mut data) = version_info.data { - processor_rules! { - data; - "SIDE": - client => "client", - server => ""; - "MINECRAFT_JAR" : - client => client_path.to_string_lossy(), - server => ""; - "MINECRAFT_VERSION": - client => profile.metadata.game_version.clone(), - server => ""; - "ROOT": - client => instance_path.to_string_lossy(), - server => ""; - "LIBRARY_DIR": - client => state.directories.libraries_dir().to_string_lossy(), - server => ""; + if let Some(ref mut data) = version_info.data { + processor_rules! { + data; + "SIDE": + client => "client", + server => ""; + "MINECRAFT_JAR" : + client => client_path.to_string_lossy(), + server => ""; + "MINECRAFT_VERSION": + client => profile.metadata.game_version.clone(), + server => ""; + "ROOT": + client => instance_path.to_string_lossy(), + server => ""; + "LIBRARY_DIR": + client => state.directories.libraries_dir().to_string_lossy(), + server => ""; + } + + emit_loading(&loading_bar, 0.0, Some("Running forge processors")) + .await?; + let total_length = processors.len(); + + // Forge processors (90-100) + for (index, processor) in processors.iter().enumerate() { + if let Some(sides) = &processor.sides { + if !sides.contains(&String::from("client")) { + continue; + } } - emit_loading(&loading_bar, 0.0, Some("Running forge processors")) - .await?; - let total_length = processors.len(); + let cp = wrap_ref_builder!(cp = processor.classpath.clone() => { + cp.push(processor.jar.clone()) + }); - // Forge processors (90-100) - for (index, processor) in processors.iter().enumerate() { - if let Some(sides) = &processor.sides { - if !sides.contains(&String::from("client")) { - continue; - } - } - - let cp = wrap_ref_builder!(cp = processor.classpath.clone() => { - cp.push(processor.jar.clone()) - }); - - let child = Command::new(&java_version.path) - .arg("-cp") - .arg(args::get_class_paths_jar( + let child = Command::new(&java_version.path) + .arg("-cp") + .arg(args::get_class_paths_jar( + &state.directories.libraries_dir(), + &cp, + &java_version.architecture, + )?) + .arg( + args::get_processor_main_class(args::get_lib_path( &state.directories.libraries_dir(), - &cp, - &java_version.architecture + &processor.jar, + false, )?) - .arg( - args::get_processor_main_class(args::get_lib_path( - &state.directories.libraries_dir(), - &processor.jar, - false, - )?) - .await? - .ok_or_else(|| { - crate::ErrorKind::LauncherError(format!( - "Could not find processor main class for {}", - processor.jar - )) - })?, - ) - .args(args::get_processor_arguments( - &state.directories.libraries_dir(), - &processor.args, - data, - )?) - .output() - .await - .map_err(|err| { + .await? + .ok_or_else(|| { crate::ErrorKind::LauncherError(format!( - "Error running processor: {err}", + "Could not find processor main class for {}", + processor.jar )) - })?; - - if !child.status.success() { - return Err(crate::ErrorKind::LauncherError(format!( - "Processor error: {}", - String::from_utf8_lossy(&child.stderr) - )) - .as_error()); - } - - emit_loading( - &loading_bar, - 30.0 / total_length as f64, - Some(&format!( - "Running forge processor {}/{}", - index, total_length - )), + })?, ) - .await?; + .args(args::get_processor_arguments( + &state.directories.libraries_dir(), + &processor.args, + data, + )?) + .output() + .await + .map_err(|err| { + crate::ErrorKind::LauncherError(format!( + "Error running processor: {err}", + )) + })?; + + if !child.status.success() { + return Err(crate::ErrorKind::LauncherError(format!( + "Processor error: {}", + String::from_utf8_lossy(&child.stderr) + )) + .as_error()); } + + emit_loading( + &loading_bar, + 30.0 / total_length as f64, + Some(&format!( + "Running forge processor {}/{}", + index, total_length + )), + ) + .await?; } } + } - crate::api::profile::edit(&profile.path, |prof| { - prof.install_stage = ProfileInstallStage::Installed; + crate::api::profile::edit(&profile.path, |prof| { + prof.install_stage = ProfileInstallStage::Installed; - async { Ok(()) } - }) - .await?; - State::sync().await?; - emit_loading( - &loading_bar, - 1.0, - Some("Finished installing"), - ) - .await?; + async { Ok(()) } + }) + .await?; + State::sync().await?; + emit_loading(&loading_bar, 1.0, Some("Finished installing")).await?; - Ok(()) - }).await + Ok(()) } +#[tracing::instrument] +#[theseus_macros::debug_pin] #[allow(clippy::too_many_arguments)] pub async fn launch_minecraft( java_args: &[String], @@ -280,156 +283,155 @@ pub async fn launch_minecraft( post_exit_hook: Option, profile: &Profile, ) -> crate::Result>> { - Box::pin(async move { - if profile.install_stage == ProfileInstallStage::PackInstalling - || profile.install_stage == ProfileInstallStage::Installing - { - return Err(crate::ErrorKind::LauncherError( - "Profile is still installing".to_string(), - ) - .into()); - } - - if profile.install_stage != ProfileInstallStage::Installed { - install_minecraft(profile, None).await?; - } - - let state = State::get().await?; - let metadata = state.metadata.read().await; - let instance_path = &canonicalize(&profile.path)?; - - let version = metadata - .minecraft - .versions - .iter() - .find(|it| it.id == profile.metadata.game_version) - .ok_or(crate::ErrorKind::LauncherError(format!( - "Invalid game version: {}", - profile.metadata.game_version - )))?; - - let version_jar = profile - .metadata - .loader_version - .as_ref() - .map_or(version.id.clone(), |it| { - format!("{}-{}", version.id.clone(), it.id.clone()) - }); - - let version_info = download::download_version_info( - &state, - version, - profile.metadata.loader_version.as_ref(), - None, - None, + if profile.install_stage == ProfileInstallStage::PackInstalling + || profile.install_stage == ProfileInstallStage::Installing + { + return Err(crate::ErrorKind::LauncherError( + "Profile is still installing".to_string(), ) - .await?; + .into()); + } - let java_version = get_java_version_from_profile(profile, &version_info).await?; + if profile.install_stage != ProfileInstallStage::Installed { + install_minecraft(profile, None).await?; + } - let client_path = state - .directories - .version_dir(&version_jar) - .join(format!("{version_jar}.jar")); + let state = State::get().await?; + let metadata = state.metadata.read().await; + let instance_path = &canonicalize(&profile.path)?; - let args = version_info.arguments.clone().unwrap_or_default(); - let mut command = match wrapper { - Some(hook) => { - wrap_ref_builder!(it = Command::new(hook) => {it.arg(&java_version.path)}) - } - None => Command::new(&java_version.path), - }; + let version = metadata + .minecraft + .versions + .iter() + .find(|it| it.id == profile.metadata.game_version) + .ok_or(crate::ErrorKind::LauncherError(format!( + "Invalid game version: {}", + profile.metadata.game_version + )))?; - let env_args = Vec::from(env_args); + let version_jar = profile + .metadata + .loader_version + .as_ref() + .map_or(version.id.clone(), |it| { + format!("{}-{}", version.id.clone(), it.id.clone()) + }); - // Check if profile has a running profile, and reject running the command if it does - // Done late so a quick double call doesn't launch two instances - let existing_processes = - process::get_uuids_by_profile_path(instance_path).await?; - if let Some(uuid) = existing_processes.first() { - return Err(crate::ErrorKind::LauncherError(format!( - "Profile {} is already running at UUID: {uuid}", - instance_path.display() - )) - .as_error()); + let version_info = download::download_version_info( + &state, + version, + profile.metadata.loader_version.as_ref(), + None, + None, + ) + .await?; + + let java_version = + get_java_version_from_profile(profile, &version_info).await?; + + let client_path = state + .directories + .version_dir(&version_jar) + .join(format!("{version_jar}.jar")); + + let args = version_info.arguments.clone().unwrap_or_default(); + let mut command = match wrapper { + Some(hook) => { + wrap_ref_builder!(it = Command::new(hook) => {it.arg(&java_version.path)}) } + None => Command::new(&java_version.path), + }; - command - .args( - args::get_jvm_arguments( - args.get(&d::minecraft::ArgumentType::Jvm) - .map(|x| x.as_slice()), - &state.directories.version_natives_dir(&version_jar), + let env_args = Vec::from(env_args); + + // Check if profile has a running profile, and reject running the command if it does + // Done late so a quick double call doesn't launch two instances + let existing_processes = + process::get_uuids_by_profile_path(instance_path).await?; + if let Some(uuid) = existing_processes.first() { + return Err(crate::ErrorKind::LauncherError(format!( + "Profile {} is already running at UUID: {uuid}", + instance_path.display() + )) + .as_error()); + } + + command + .args( + args::get_jvm_arguments( + args.get(&d::minecraft::ArgumentType::Jvm) + .map(|x| x.as_slice()), + &state.directories.version_natives_dir(&version_jar), + &state.directories.libraries_dir(), + &args::get_class_paths( &state.directories.libraries_dir(), - &args::get_class_paths( - &state.directories.libraries_dir(), - version_info.libraries.as_slice(), - &client_path, - &java_version.architecture - )?, - &version_jar, - *memory, - Vec::from(java_args), - &java_version.architecture - )? - .into_iter() - .collect::>(), - ) - .arg(version_info.main_class.clone()) - .args( - args::get_minecraft_arguments( - args.get(&d::minecraft::ArgumentType::Game) - .map(|x| x.as_slice()), - version_info.minecraft_arguments.as_deref(), - credentials, - &version.id, - &version_info.asset_index.id, - instance_path, - &state.directories.assets_dir(), - &version.type_, - *resolution, - &java_version.architecture - )? - .into_iter() - .collect::>(), - ) - .current_dir(instance_path.clone()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); + version_info.libraries.as_slice(), + &client_path, + &java_version.architecture, + )?, + &version_jar, + *memory, + Vec::from(java_args), + &java_version.architecture, + )? + .into_iter() + .collect::>(), + ) + .arg(version_info.main_class.clone()) + .args( + args::get_minecraft_arguments( + args.get(&d::minecraft::ArgumentType::Game) + .map(|x| x.as_slice()), + version_info.minecraft_arguments.as_deref(), + credentials, + &version.id, + &version_info.asset_index.id, + instance_path, + &state.directories.assets_dir(), + &version.type_, + *resolution, + &java_version.architecture, + )? + .into_iter() + .collect::>(), + ) + .current_dir(instance_path.clone()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); - // 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); + // 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 = - chrono::Local::now().format("%Y%m%d_%H%M%S").to_string(); - let logs_dir = { - let st = State::get().await?; - st.directories - .profile_logs_dir(profile.uuid) - .join(&datetime_string) - }; - fs::create_dir_all(&logs_dir)?; + // Get Modrinth logs directories + let datetime_string = + chrono::Local::now().format("%Y%m%d_%H%M%S").to_string(); + let logs_dir = { + let st = State::get().await?; + st.directories + .profile_logs_dir(profile.uuid) + .join(&datetime_string) + }; + fs::create_dir_all(&logs_dir)?; - let stdout_log_path = logs_dir.join("stdout.log"); - let stderr_log_path = logs_dir.join("stderr.log"); + let stdout_log_path = logs_dir.join("stdout.log"); + let stderr_log_path = logs_dir.join("stderr.log"); - // Create Minecraft child by inserting it into the state - // This also spawns the process and prepares the subsequent processes - let mut state_children = state.children.write().await; - state_children - .insert_process( - Uuid::new_v4(), - instance_path.to_path_buf(), - stdout_log_path, - stderr_log_path, - command, - post_exit_hook, - ) - .await - }).await + // Create Minecraft child by inserting it into the state + // This also spawns the process and prepares the subsequent processes + let mut state_children = state.children.write().await; + state_children + .insert_process( + Uuid::new_v4(), + instance_path.to_path_buf(), + stdout_log_path, + stderr_log_path, + command, + post_exit_hook, + ) + .await } diff --git a/theseus/src/state/children.rs b/theseus/src/state/children.rs index e0ca2e404..0c957b049 100644 --- a/theseus/src/state/children.rs +++ b/theseus/src/state/children.rs @@ -38,6 +38,9 @@ impl Children { // Runs the command in process, inserts a child process to keep track of, and returns a reference to the container struct MinecraftChild // The threads for stdout and stderr are spawned here // Unlike a Hashmap's 'insert', this directly returns the reference to the MinecraftChild rather than any previously stored MinecraftChild that may exist + + #[tracing::instrument(skip(self))] + #[theseus_macros::debug_pin] pub async fn insert_process( &mut self, uuid: Uuid, @@ -110,6 +113,8 @@ impl Children { // Spawns a new child process and inserts it into the hashmap // Also, as the process ends, it spawns the follow-up process if it exists // By convention, ExitStatus is last command's exit status, and we exit on the first non-zero exit status + #[tracing::instrument] + #[theseus_macros::debug_pin] async fn sequential_process_manager( uuid: Uuid, post_command: Option, diff --git a/theseus/src/state/metadata.rs b/theseus/src/state/metadata.rs index 8dc8bac37..276d993c6 100644 --- a/theseus/src/state/metadata.rs +++ b/theseus/src/state/metadata.rs @@ -54,6 +54,8 @@ impl Metadata { } // Attempt to fetch metadata and store in sled DB + #[tracing::instrument] + #[theseus_macros::debug_pin] pub async fn init( dirs: &DirectoryInfo, io_semaphore: &IoSemaphore, diff --git a/theseus/src/state/mod.rs b/theseus/src/state/mod.rs index e969106e6..c7114da69 100644 --- a/theseus/src/state/mod.rs +++ b/theseus/src/state/mod.rs @@ -82,6 +82,8 @@ pub struct State { impl State { /// Get the current launcher state, initializing it if needed + #[tracing::instrument] + #[theseus_macros::debug_pin] pub async fn get() -> crate::Result> { LAUNCHER_STATE .get_or_try_init(|| { @@ -166,37 +168,35 @@ impl State { } #[tracing::instrument] + #[theseus_macros::debug_pin] /// Synchronize in-memory state with persistent state pub async fn sync() -> crate::Result<()> { - Box::pin(async move { - let state = Self::get().await?; - let sync_settings = async { - let state = Arc::clone(&state); + let state = Self::get().await?; + let sync_settings = async { + let state = Arc::clone(&state); - tokio::spawn(async move { - let reader = state.settings.read().await; - reader.sync(&state.directories.settings_file()).await?; - Ok::<_, crate::Error>(()) - }) - .await? - }; + tokio::spawn(async move { + let reader = state.settings.read().await; + reader.sync(&state.directories.settings_file()).await?; + Ok::<_, crate::Error>(()) + }) + .await? + }; - let sync_profiles = async { - let state = Arc::clone(&state); + let sync_profiles = async { + let state = Arc::clone(&state); - tokio::spawn(async move { - let profiles = state.profiles.read().await; + tokio::spawn(async move { + let profiles = state.profiles.read().await; - profiles.sync().await?; - Ok::<_, crate::Error>(()) - }) - .await? - }; + profiles.sync().await?; + Ok::<_, crate::Error>(()) + }) + .await? + }; - tokio::try_join!(sync_settings, sync_profiles)?; - Ok(()) - }) - .await + tokio::try_join!(sync_settings, sync_profiles)?; + Ok(()) } /// Reset IO semaphore to default values diff --git a/theseus/src/state/profiles.rs b/theseus/src/state/profiles.rs index 0e463377d..5082c3cb9 100644 --- a/theseus/src/state/profiles.rs +++ b/theseus/src/state/profiles.rs @@ -249,6 +249,8 @@ impl Profile { Ok(files) } + #[tracing::instrument(skip(watcher))] + #[theseus_macros::debug_pin] pub async fn watch_fs( profile_path: &Path, watcher: &mut Debouncer, @@ -285,6 +287,8 @@ impl Profile { Ok(()) } + #[tracing::instrument] + #[theseus_macros::debug_pin] pub async fn add_project_version( &self, version_id: String, @@ -330,6 +334,8 @@ impl Profile { Ok((path, version)) } + #[tracing::instrument] + #[theseus_macros::debug_pin] pub async fn add_project_bytes( &self, file_name: &str, @@ -398,6 +404,8 @@ impl Profile { Ok(path) } + #[tracing::instrument] + #[theseus_macros::debug_pin] pub async fn toggle_disable_project( &self, path: &Path, @@ -471,6 +479,7 @@ impl Profile { impl Profiles { #[tracing::instrument(skip(file_watcher))] + #[theseus_macros::debug_pin] pub async fn init( dirs: &DirectoryInfo, file_watcher: &mut Debouncer, @@ -501,6 +510,8 @@ impl Profiles { Ok(Self(profiles)) } + #[tracing::instrument] + #[theseus_macros::debug_pin] pub async fn update_projects() { let res = async { let state = State::get().await?; @@ -553,6 +564,7 @@ impl Profiles { } #[tracing::instrument(skip(self))] + #[theseus_macros::debug_pin] pub async fn insert(&mut self, profile: Profile) -> crate::Result<&Self> { emit_profile( profile.uuid, diff --git a/theseus/src/state/projects.rs b/theseus/src/state/projects.rs index ea8fbb977..cef956348 100644 --- a/theseus/src/state/projects.rs +++ b/theseus/src/state/projects.rs @@ -200,6 +200,8 @@ pub enum ProjectMetadata { Unknown, } +#[tracing::instrument] +#[theseus_macros::debug_pin] async fn read_icon_from_file( icon_path: Option, cache_dir: &Path, @@ -249,6 +251,8 @@ async fn read_icon_from_file( Ok(None) } +#[tracing::instrument] +#[theseus_macros::debug_pin] pub async fn infer_data_from_files( profile: Profile, paths: Vec, diff --git a/theseus/src/state/settings.rs b/theseus/src/state/settings.rs index 658af2d95..a93205c77 100644 --- a/theseus/src/state/settings.rs +++ b/theseus/src/state/settings.rs @@ -68,6 +68,8 @@ impl Settings { } } + #[tracing::instrument] + #[theseus_macros::debug_pin] pub async fn update_java() { let res = async { let state = State::get().await?; diff --git a/theseus/src/state/tags.rs b/theseus/src/state/tags.rs index b6b351192..a03036fbe 100644 --- a/theseus/src/state/tags.rs +++ b/theseus/src/state/tags.rs @@ -20,6 +20,8 @@ pub struct Tags { } impl Tags { + #[tracing::instrument] + #[theseus_macros::debug_pin] pub async fn init( dirs: &DirectoryInfo, io_semaphore: &IoSemaphore, @@ -50,6 +52,8 @@ impl Tags { } } + #[tracing::instrument] + #[theseus_macros::debug_pin] pub async fn update() { let res = async { let state = crate::State::get().await?; diff --git a/theseus/src/util/fetch.rs b/theseus/src/util/fetch.rs index 58e6270d3..b60fe1f1e 100644 --- a/theseus/src/util/fetch.rs +++ b/theseus/src/util/fetch.rs @@ -66,6 +66,7 @@ where /// Downloads a file with retry and checksum functionality #[tracing::instrument(skip(json_body, semaphore))] +#[theseus_macros::debug_pin] pub async fn fetch_advanced( method: Method, url: &str, @@ -158,6 +159,7 @@ pub async fn fetch_advanced( /// Downloads a file from specified mirrors #[tracing::instrument(skip(semaphore))] +#[theseus_macros::debug_pin] pub async fn fetch_mirrors( mirrors: &[&str], sha1: Option<&str>, diff --git a/theseus/src/util/jre.rs b/theseus/src/util/jre.rs index 7bfa5ace5..09dd6a7f1 100644 --- a/theseus/src/util/jre.rs +++ b/theseus/src/util/jre.rs @@ -185,6 +185,7 @@ pub async fn get_all_jre() -> Result, JREError> { // Gets all JREs from the PATH env variable #[tracing::instrument] +#[theseus_macros::debug_pin] async fn get_all_autoinstalled_jre_path() -> Result, JREError> { Box::pin(async move { @@ -247,6 +248,7 @@ pub async fn check_java_at_filepaths( // For example filepath 'path', attempt to resolve it and get a Java version at this path // If no such path exists, or no such valid java at this path exists, returns None #[tracing::instrument] +#[theseus_macros::debug_pin] pub async fn check_java_at_filepath(path: &Path) -> Option { // Attempt to canonicalize the potential java filepath // If it fails, this path does not exist and None is returned (no Java here) diff --git a/theseus_gui/src-tauri/Cargo.toml b/theseus_gui/src-tauri/Cargo.toml index 962110f14..e01113dbb 100644 --- a/theseus_gui/src-tauri/Cargo.toml +++ b/theseus_gui/src-tauri/Cargo.toml @@ -16,6 +16,7 @@ regex = "1.5" [dependencies] theseus = { path = "../../theseus", features = ["tauri"] } +theseus_macros = { path = "../../theseus_macros" } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } diff --git a/theseus_gui/src-tauri/src/api/auth.rs b/theseus_gui/src-tauri/src/api/auth.rs index d2ec6bc3c..ffcedd892 100644 --- a/theseus_gui/src-tauri/src/api/auth.rs +++ b/theseus_gui/src-tauri/src/api/auth.rs @@ -4,6 +4,7 @@ use theseus::prelude::*; /// Authenticate a user with Hydra - part 1 /// This begins the authentication flow quasi-synchronously, returning a URL to visit (that the user will sign in at) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn auth_authenticate_begin_flow() -> Result { Ok(auth::authenticate_begin_flow().await?) } @@ -12,6 +13,7 @@ pub async fn auth_authenticate_begin_flow() -> Result { /// This completes the authentication flow quasi-synchronously, returning the sign-in credentials /// (and also adding the credentials to the state) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn auth_authenticate_await_completion() -> Result { Ok(auth::authenticate_await_complete_flow().await?) } @@ -19,13 +21,13 @@ pub async fn auth_authenticate_await_completion() -> Result { /// Refresh some credentials using Hydra, if needed // invoke('auth_refresh',user) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn auth_refresh(user: uuid::Uuid) -> Result { Ok(auth::refresh(user).await?) } -/// Remove a user account from the database -// invoke('auth_remove_user',user) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn auth_remove_user(user: uuid::Uuid) -> Result<()> { Ok(auth::remove_user(user).await?) } @@ -33,6 +35,7 @@ pub async fn auth_remove_user(user: uuid::Uuid) -> Result<()> { /// Check if a user exists in Theseus // invoke('auth_has_user',user) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn auth_has_user(user: uuid::Uuid) -> Result { Ok(auth::has_user(user).await?) } @@ -40,6 +43,7 @@ pub async fn auth_has_user(user: uuid::Uuid) -> Result { /// Get a copy of the list of all user credentials // invoke('auth_users',user) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn auth_users() -> Result> { Ok(auth::users().await?) } @@ -48,6 +52,7 @@ pub async fn auth_users() -> Result> { /// Prefer to use refresh instead, as it will refresh the credentials as well // invoke('auth_users',user) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn auth_get_user(user: uuid::Uuid) -> Result { Ok(auth::get_user(user).await?) } diff --git a/theseus_gui/src-tauri/src/api/jre.rs b/theseus_gui/src-tauri/src/api/jre.rs index e9bfe293e..4337e48fa 100644 --- a/theseus_gui/src-tauri/src/api/jre.rs +++ b/theseus_gui/src-tauri/src/api/jre.rs @@ -6,24 +6,28 @@ use theseus::prelude::*; /// Get all JREs that exist on the system #[tauri::command] +#[theseus_macros::debug_pin] pub async fn jre_get_all_jre() -> Result> { Ok(jre::get_all_jre().await?) } // Finds the isntallation of Java 7, if it exists #[tauri::command] +#[theseus_macros::debug_pin] pub async fn jre_find_jre_8_jres() -> Result> { Ok(jre::find_java8_jres().await?) } // finds the installation of Java 17, if it exists #[tauri::command] +#[theseus_macros::debug_pin] pub async fn jre_find_jre_17_jres() -> Result> { Ok(jre::find_java17_jres().await?) } // Finds the highest version of Java 18+, if it exists #[tauri::command] +#[theseus_macros::debug_pin] pub async fn jre_find_jre_18plus_jres() -> Result> { Ok(jre::find_java18plus_jres().await?) } @@ -31,6 +35,7 @@ pub async fn jre_find_jre_18plus_jres() -> Result> { // Autodetect Java globals, by searching the users computer. // Returns a *NEW* JavaGlobals that can be put into Settings #[tauri::command] +#[theseus_macros::debug_pin] pub async fn jre_autodetect_java_globals() -> Result { Ok(jre::autodetect_java_globals().await?) } @@ -38,6 +43,7 @@ pub async fn jre_autodetect_java_globals() -> Result { // Validates java globals, by checking if the paths exist // If false, recommend to direct them to reassign, or to re-guess #[tauri::command] +#[theseus_macros::debug_pin] pub async fn jre_validate_globals() -> Result { Ok(jre::validate_globals().await?) } @@ -45,18 +51,21 @@ pub async fn jre_validate_globals() -> Result { // Validates JRE at a given path // Returns None if the path is not a valid JRE #[tauri::command] +#[theseus_macros::debug_pin] pub async fn jre_get_jre(path: PathBuf) -> Result> { jre::check_jre(path).await.map_err(|e| e.into()) } // Auto installs java for the given java version #[tauri::command] +#[theseus_macros::debug_pin] pub async fn jre_auto_install_java(java_version: u32) -> Result { Ok(jre::auto_install_java(java_version).await?) } // Gets the maximum memory a system has available. #[tauri::command] +#[theseus_macros::debug_pin] pub async fn jre_get_max_memory() -> Result { Ok(jre::get_max_memory().await?) } diff --git a/theseus_gui/src-tauri/src/api/logs.rs b/theseus_gui/src-tauri/src/api/logs.rs index 8e038a59c..ba5f4f298 100644 --- a/theseus_gui/src-tauri/src/api/logs.rs +++ b/theseus_gui/src-tauri/src/api/logs.rs @@ -14,6 +14,7 @@ pub struct Logs { /// Get all Logs for a profile, sorted by datetime #[tauri::command] +#[theseus_macros::debug_pin] pub async fn logs_get_logs( profile_uuid: Uuid, clear_contents: Option, @@ -29,6 +30,7 @@ pub async fn logs_get_logs( /// Get a Log struct for a profile by profile id and datetime string #[tauri::command] +#[theseus_macros::debug_pin] pub async fn logs_get_logs_by_datetime( profile_uuid: Uuid, datetime_string: String, @@ -38,6 +40,7 @@ pub async fn logs_get_logs_by_datetime( /// Get the stdout for a profile by profile id and datetime string #[tauri::command] +#[theseus_macros::debug_pin] pub async fn logs_get_stdout_by_datetime( profile_uuid: Uuid, datetime_string: String, @@ -47,6 +50,7 @@ pub async fn logs_get_stdout_by_datetime( /// Get the stderr for a profile by profile id and datetime string #[tauri::command] +#[theseus_macros::debug_pin] pub async fn logs_get_stderr_by_datetime( profile_uuid: Uuid, datetime_string: String, @@ -56,12 +60,14 @@ pub async fn logs_get_stderr_by_datetime( /// Delete all logs for a profile by profile id #[tauri::command] +#[theseus_macros::debug_pin] pub async fn logs_delete_logs(profile_uuid: Uuid) -> Result<()> { Ok(logs::delete_logs(profile_uuid).await?) } /// Delete a log for a profile by profile id and datetime string #[tauri::command] +#[theseus_macros::debug_pin] pub async fn logs_delete_logs_by_datetime( profile_uuid: Uuid, datetime_string: String, diff --git a/theseus_gui/src-tauri/src/api/metadata.rs b/theseus_gui/src-tauri/src/api/metadata.rs index 807f5ec3c..9d1d2baf7 100644 --- a/theseus_gui/src-tauri/src/api/metadata.rs +++ b/theseus_gui/src-tauri/src/api/metadata.rs @@ -4,24 +4,28 @@ use daedalus::modded::Manifest; /// Gets the game versions from daedalus #[tauri::command] +#[theseus_macros::debug_pin] pub async fn metadata_get_game_versions() -> Result { Ok(theseus::metadata::get_minecraft_versions().await?) } /// Gets the fabric versions from daedalus #[tauri::command] +#[theseus_macros::debug_pin] pub async fn metadata_get_fabric_versions() -> Result { Ok(theseus::metadata::get_fabric_versions().await?) } /// Gets the forge versions from daedalus #[tauri::command] +#[theseus_macros::debug_pin] pub async fn metadata_get_forge_versions() -> Result { Ok(theseus::metadata::get_forge_versions().await?) } /// Gets the quilt versions from daedalus #[tauri::command] +#[theseus_macros::debug_pin] pub async fn metadata_get_quilt_versions() -> Result { Ok(theseus::metadata::get_quilt_versions().await?) } diff --git a/theseus_gui/src-tauri/src/api/mod.rs b/theseus_gui/src-tauri/src/api/mod.rs index 3905de054..4e739ecb3 100644 --- a/theseus_gui/src-tauri/src/api/mod.rs +++ b/theseus_gui/src-tauri/src/api/mod.rs @@ -47,6 +47,7 @@ where // Create a new HashMap with the same keys // Values provided should not be used directly, as they are not guaranteed to be up-to-date #[tauri::command] +#[theseus_macros::debug_pin] pub async fn progress_bars_list( ) -> Result> { let res = theseus::EventState::list_progress_bars().await?; diff --git a/theseus_gui/src-tauri/src/api/pack.rs b/theseus_gui/src-tauri/src/api/pack.rs index 8872c8b9d..0bff85e9d 100644 --- a/theseus_gui/src-tauri/src/api/pack.rs +++ b/theseus_gui/src-tauri/src/api/pack.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use theseus::prelude::*; #[tauri::command] +#[theseus_macros::debug_pin] pub async fn pack_install_version_id( version_id: String, pack_title: String, @@ -15,6 +16,7 @@ pub async fn pack_install_version_id( } #[tauri::command] +#[theseus_macros::debug_pin] pub async fn pack_install_file(path: &Path) -> Result { let res = pack::install_pack_from_file(path.to_path_buf()).await?; Ok(res) diff --git a/theseus_gui/src-tauri/src/api/process.rs b/theseus_gui/src-tauri/src/api/process.rs index 87cd08133..6dbf7634b 100644 --- a/theseus_gui/src-tauri/src/api/process.rs +++ b/theseus_gui/src-tauri/src/api/process.rs @@ -6,12 +6,14 @@ use uuid::Uuid; // Checks if a process has finished by process UUID #[tauri::command] +#[theseus_macros::debug_pin] pub async fn process_has_finished_by_uuid(uuid: Uuid) -> Result { Ok(process::has_finished_by_uuid(&uuid).await?) } // Gets process exit status by process UUID #[tauri::command] +#[theseus_macros::debug_pin] pub async fn process_get_exit_status_by_uuid( uuid: Uuid, ) -> Result> { @@ -20,18 +22,21 @@ pub async fn process_get_exit_status_by_uuid( // Gets all process UUIDs #[tauri::command] +#[theseus_macros::debug_pin] pub async fn process_get_all_uuids() -> Result> { Ok(process::get_all_uuids().await?) } // Gets all running process UUIDs #[tauri::command] +#[theseus_macros::debug_pin] pub async fn process_get_all_running_uuids() -> Result> { Ok(process::get_all_running_uuids().await?) } // Gets all process UUIDs by profile path #[tauri::command] +#[theseus_macros::debug_pin] pub async fn process_get_uuids_by_profile_path( profile_path: &Path, ) -> Result> { @@ -40,36 +45,42 @@ pub async fn process_get_uuids_by_profile_path( // Gets the Profile paths of each *running* stored process in the state #[tauri::command] +#[theseus_macros::debug_pin] pub async fn process_get_all_running_profile_paths() -> Result> { Ok(process::get_all_running_profile_paths().await?) } // Gets the Profiles (cloned) of each *running* stored process in the state #[tauri::command] +#[theseus_macros::debug_pin] pub async fn process_get_all_running_profiles() -> Result> { Ok(process::get_all_running_profiles().await?) } // Gets process stderr by process UUID #[tauri::command] +#[theseus_macros::debug_pin] pub async fn process_get_stderr_by_uuid(uuid: Uuid) -> Result { Ok(process::get_stderr_by_uuid(&uuid).await?) } // Gets process stdout by process UUID #[tauri::command] +#[theseus_macros::debug_pin] pub async fn process_get_stdout_by_uuid(uuid: Uuid) -> Result { Ok(process::get_stdout_by_uuid(&uuid).await?) } // Kill a process by process UUID #[tauri::command] +#[theseus_macros::debug_pin] pub async fn process_kill_by_uuid(uuid: Uuid) -> Result<()> { Ok(process::kill_by_uuid(&uuid).await?) } // Wait for a process to finish by process UUID #[tauri::command] +#[theseus_macros::debug_pin] pub async fn process_wait_for_by_uuid(uuid: Uuid) -> Result<()> { Ok(process::wait_for_by_uuid(&uuid).await?) } diff --git a/theseus_gui/src-tauri/src/api/profile.rs b/theseus_gui/src-tauri/src/api/profile.rs index 351c31476..ac2a11b8e 100644 --- a/theseus_gui/src-tauri/src/api/profile.rs +++ b/theseus_gui/src-tauri/src/api/profile.rs @@ -8,6 +8,7 @@ use uuid::Uuid; // Remove a profile // invoke('profile_add_path',path) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_remove(path: &Path) -> Result<()> { profile::remove(path).await?; Ok(()) @@ -16,6 +17,7 @@ pub async fn profile_remove(path: &Path) -> Result<()> { // Get a profile by path // invoke('profile_add_path',path) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_get( path: &Path, clear_projects: Option, @@ -27,6 +29,7 @@ pub async fn profile_get( // Get a copy of the profile set // invoke('profile_list') #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_list( clear_projects: Option, ) -> Result> { @@ -35,6 +38,7 @@ pub async fn profile_list( } #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_check_installed( path: &Path, project_id: String, @@ -57,6 +61,7 @@ pub async fn profile_check_installed( /// Installs/Repairs a profile /// invoke('profile_install') #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_install(path: &Path) -> Result<()> { profile::install(path).await?; Ok(()) @@ -65,6 +70,7 @@ pub async fn profile_install(path: &Path) -> Result<()> { /// Updates all of the profile's projects /// invoke('profile_update_all') #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_update_all(path: &Path) -> Result<()> { profile::update_all(path).await?; Ok(()) @@ -73,6 +79,7 @@ pub async fn profile_update_all(path: &Path) -> Result<()> { /// Updates a specified project /// invoke('profile_update_project') #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_update_project( path: &Path, project_path: &Path, @@ -84,6 +91,7 @@ pub async fn profile_update_project( // Adds a project to a profile from a version ID // invoke('profile_add_project_from_version') #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_add_project_from_version( path: &Path, version_id: String, @@ -95,6 +103,7 @@ pub async fn profile_add_project_from_version( // Adds a project to a profile from a path // invoke('profile_add_project_from_path') #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_add_project_from_path( path: &Path, project_path: &Path, @@ -108,6 +117,7 @@ pub async fn profile_add_project_from_path( // Toggles disabling a project from its path // invoke('profile_toggle_disable_project') #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_toggle_disable_project( path: &Path, project_path: &Path, @@ -119,6 +129,7 @@ pub async fn profile_toggle_disable_project( // Removes a project from a profile // invoke('profile_remove_project') #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_remove_project( path: &Path, project_path: &Path, @@ -131,6 +142,7 @@ pub async fn profile_remove_project( // for the actual Child in the state. // invoke('profile_run', path) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_run(path: &Path) -> Result { let minecraft_child = profile::run(path).await?; let uuid = minecraft_child.read().await.uuid; @@ -140,6 +152,7 @@ pub async fn profile_run(path: &Path) -> Result { // Run Minecraft using a profile using the default credentials, and wait for the result // invoke('profile_run_wait', path) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_run_wait(path: &Path) -> Result<()> { let proc_lock = profile::run(path).await?; let mut proc = proc_lock.write().await; @@ -151,6 +164,7 @@ pub async fn profile_run_wait(path: &Path) -> Result<()> { // for the actual Child in the state. // invoke('profile_run_credentials', {path, credentials})') #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_run_credentials( path: &Path, credentials: Credentials, @@ -163,6 +177,7 @@ pub async fn profile_run_credentials( // Run Minecraft using a profile using the chosen credentials, and wait for the result // invoke('profile_run_wait', {path, credentials) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_run_wait_credentials( path: &Path, credentials: Credentials, @@ -192,6 +207,7 @@ pub struct EditProfileMetadata { // Edits a profile // invoke('profile_edit', {path, editProfile}) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_edit( path: &Path, edit_profile: EditProfile, diff --git a/theseus_gui/src-tauri/src/api/profile_create.rs b/theseus_gui/src-tauri/src/api/profile_create.rs index fb49f3dd7..70e0e5927 100644 --- a/theseus_gui/src-tauri/src/api/profile_create.rs +++ b/theseus_gui/src-tauri/src/api/profile_create.rs @@ -5,6 +5,7 @@ use theseus::prelude::*; // Generic basic profile creation tool. // Creates an essentially empty dummy profile with profile_create #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_create_empty() -> Result { let res = profile_create::profile_create_empty().await?; Ok(res) @@ -13,6 +14,7 @@ pub async fn profile_create_empty() -> Result { // Creates a profile at the given filepath and adds it to the in-memory state // invoke('profile_add',profile) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn profile_create( name: String, // the name of the profile, and relative path game_version: String, // the game version of the profile diff --git a/theseus_gui/src-tauri/src/api/settings.rs b/theseus_gui/src-tauri/src/api/settings.rs index d19970c0c..ffd9f3f94 100644 --- a/theseus_gui/src-tauri/src/api/settings.rs +++ b/theseus_gui/src-tauri/src/api/settings.rs @@ -23,6 +23,7 @@ pub struct FrontendSettings { // Get full settings // invoke('settings_get') #[tauri::command] +#[theseus_macros::debug_pin] pub async fn settings_get() -> Result { let backend_settings = settings::get().await?; let frontend_settings = FrontendSettings { @@ -50,6 +51,7 @@ pub async fn settings_get() -> Result { // Set full settings // invoke('settings_set', settings) #[tauri::command] +#[theseus_macros::debug_pin] pub async fn settings_set(settings: FrontendSettings) -> Result<()> { let custom_env_args: Vec<(String, String)> = settings .custom_env_args diff --git a/theseus_gui/src-tauri/src/api/tags.rs b/theseus_gui/src-tauri/src/api/tags.rs index 0ae2bad65..1d6b3b844 100644 --- a/theseus_gui/src-tauri/src/api/tags.rs +++ b/theseus_gui/src-tauri/src/api/tags.rs @@ -3,36 +3,42 @@ use theseus::tags::{Category, DonationPlatform, GameVersion, Loader, Tags}; /// Gets cached category tags from the database #[tauri::command] +#[theseus_macros::debug_pin] pub async fn tags_get_categories() -> Result> { Ok(theseus::tags::get_category_tags().await?) } /// Gets cached report type tags from the database #[tauri::command] +#[theseus_macros::debug_pin] pub async fn tags_get_report_types() -> Result> { Ok(theseus::tags::get_report_type_tags().await?) } /// Gets cached loader tags from the database #[tauri::command] +#[theseus_macros::debug_pin] pub async fn tags_get_loaders() -> Result> { Ok(theseus::tags::get_loader_tags().await?) } /// Gets cached game version tags from the database #[tauri::command] +#[theseus_macros::debug_pin] pub async fn tags_get_game_versions() -> Result> { Ok(theseus::tags::get_game_version_tags().await?) } /// Gets cached donation platform tags from the database #[tauri::command] +#[theseus_macros::debug_pin] pub async fn tags_get_donation_platforms() -> Result> { Ok(theseus::tags::get_donation_platform_tags().await?) } /// Gets cached tag bundle from the database #[tauri::command] +#[theseus_macros::debug_pin] pub async fn tags_get_tag_bundle() -> Result { Ok(theseus::tags::get_tag_bundle().await?) } diff --git a/theseus_gui/src-tauri/src/main.rs b/theseus_gui/src-tauri/src/main.rs index 39afbeb3d..e4f38a7d2 100644 --- a/theseus_gui/src-tauri/src/main.rs +++ b/theseus_gui/src-tauri/src/main.rs @@ -13,6 +13,7 @@ mod error; // Should be called in launcher initialization #[tauri::command] +#[theseus_macros::debug_pin] async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> { theseus::EventState::init(app).await?; State::get().await?; @@ -24,6 +25,7 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> { // disables mouseover and fixes a random crash error only fixed by recent versions of macos #[cfg(target_os = "macos")] #[tauri::command] +#[theseus_macros::debug_pin] async fn should_disable_mouseover() -> bool { // We try to match version to 12.2 or higher. If unrecognizable to pattern or lower, we default to the css with disabled mouseover for safety let os = os_info::get(); @@ -37,6 +39,7 @@ async fn should_disable_mouseover() -> bool { } #[cfg(not(target_os = "macos"))] #[tauri::command] +#[theseus_macros::debug_pin] async fn should_disable_mouseover() -> bool { false } diff --git a/theseus_macros/Cargo.toml b/theseus_macros/Cargo.toml new file mode 100644 index 000000000..6c31f3cab --- /dev/null +++ b/theseus_macros/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "theseus_macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0", features = ["full"] } +quote = "1.0" \ No newline at end of file diff --git a/theseus_macros/src/lib.rs b/theseus_macros/src/lib.rs new file mode 100644 index 000000000..dd39499b0 --- /dev/null +++ b/theseus_macros/src/lib.rs @@ -0,0 +1,36 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, ItemFn}; + +#[proc_macro_attribute] +pub fn debug_pin(_attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse the input tokens into a syntax tree + let input = parse_macro_input!(item as ItemFn); + + let attrs = &input.attrs; + let vis = &input.vis; // visibility modifier + let sig = &input.sig; // function signature + let body = &input.block; + + // Use cfg attribute for conditional compilation + let result = quote! { + #[cfg(debug_assertions)] + #(#attrs) * + #vis #sig { + Box::pin(async move { + #body + }).await + } + + #[cfg(not(debug_assertions))] + #(#attrs) * + #vis #sig { + #body + } + }; + + // Hand the output tokens back to the compiler + TokenStream::from(result) +}