Debug pin macro (#118)

* debug pin macro

* Added debug pinning macro

* working on windows

* removed remaining box pins
This commit is contained in:
Wyatt Verchere
2023-05-18 10:31:52 -07:00
committed by GitHub
parent 0801d7a145
commit 16407060f0
35 changed files with 1133 additions and 990 deletions

10
Cargo.lock generated
View File

@@ -3764,6 +3764,7 @@ dependencies = [
"sys-info", "sys-info",
"tauri", "tauri",
"tempfile", "tempfile",
"theseus_macros",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
@@ -3817,6 +3818,7 @@ dependencies = [
"tauri", "tauri",
"tauri-build", "tauri-build",
"theseus", "theseus",
"theseus_macros",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
@@ -3827,6 +3829,14 @@ dependencies = [
"uuid 1.3.0", "uuid 1.3.0",
] ]
[[package]]
name = "theseus_macros"
version = "0.1.0"
dependencies = [
"quote",
"syn 1.0.109",
]
[[package]] [[package]]
name = "theseus_playground" name = "theseus_playground"
version = "0.1.0" version = "0.1.0"

View File

@@ -4,7 +4,8 @@ members = [
"theseus", "theseus",
"theseus_cli", "theseus_cli",
"theseus_playground", "theseus_playground",
"theseus_gui/src-tauri" "theseus_gui/src-tauri",
"theseus_macros"
] ]
# Optimize for speed and reduce size on release builds # Optimize for speed and reduce size on release builds

View File

@@ -7,6 +7,8 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
theseus_macros = { path = "../theseus_macros" }
bytes = "1" bytes = "1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View File

@@ -32,6 +32,7 @@ pub async fn authenticate_await_complete_flow() -> crate::Result<Credentials> {
/// open a browser to the given URL and finally wait on the spawned future /// 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 /// with the ability to cancel in case the browser is closed before finishing
#[tracing::instrument] #[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn authenticate( pub async fn authenticate(
browser_url: oneshot::Sender<url::Url>, browser_url: oneshot::Sender<url::Url>,
) -> crate::Result<Credentials> { ) -> crate::Result<Credentials> {
@@ -60,6 +61,7 @@ pub async fn authenticate(
/// Refresh some credentials using Hydra, if needed /// Refresh some credentials using Hydra, if needed
/// This is the primary desired way to get credentials, as it will also refresh them. /// This is the primary desired way to get credentials, as it will also refresh them.
#[tracing::instrument] #[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn refresh(user: uuid::Uuid) -> crate::Result<Credentials> { pub async fn refresh(user: uuid::Uuid) -> crate::Result<Credentials> {
let state = State::get().await?; let state = State::get().await?;
let mut users = state.users.write().await; let mut users = state.users.write().await;

View File

@@ -137,28 +137,27 @@ pub async fn find_java17_jres() -> crate::Result<Vec<JavaVersion>> {
.collect()) .collect())
} }
#[theseus_macros::debug_pin]
pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> { pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
Box::pin( let state = State::get().await?;
async move {
let state = State::get().await?;
let loading_bar = init_loading( let loading_bar = init_loading(
LoadingBarType::JavaDownload { LoadingBarType::JavaDownload {
version: java_version, version: java_version,
}, },
100.0, 100.0,
"Downloading java version", "Downloading java version",
) )
.await?; .await?;
#[derive(Deserialize)] #[derive(Deserialize)]
struct Package { struct Package {
pub download_url: String, pub download_url: String,
pub name: PathBuf, pub name: PathBuf,
} }
emit_loading(&loading_bar, 0.0, Some("Fetching java version")).await?; emit_loading(&loading_bar, 0.0, Some("Fetching java version")).await?;
let packages = fetch_json::<Vec<Package>>( let packages = fetch_json::<Vec<Package>>(
Method::GET, Method::GET,
&format!( &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", "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<PathBuf> {
None, None,
&state.fetch_semaphore, &state.fetch_semaphore,
).await?; ).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() { if let Some(download) = packages.first() {
let file = fetch_advanced( let file = fetch_advanced(
Method::GET, Method::GET,
&download.download_url, &download.download_url,
None, None,
None, None,
None, None,
Some((&loading_bar, 80.0)), Some((&loading_bar, 80.0)),
&state.fetch_semaphore, &state.fetch_semaphore,
) )
.await?; .await?;
let path = state.directories.java_versions_dir(); let path = state.directories.java_versions_dir();
if path.exists() { if path.exists() {
tokio::fs::remove_dir_all(&path).await?; tokio::fs::remove_dir_all(&path).await?;
} }
let mut archive = zip::ZipArchive::new(std::io::Cursor::new(file)) let mut archive = zip::ZipArchive::new(std::io::Cursor::new(file))
.map_err(|_| { .map_err(|_| {
crate::Error::from(crate::ErrorKind::InputError( crate::Error::from(crate::ErrorKind::InputError(
"Failed to read java zip".to_string(), "Failed to read java zip".to_string(),
)) ))
})?; })?;
emit_loading(&loading_bar, 0.0, Some("Extracting java")).await?; emit_loading(&loading_bar, 0.0, Some("Extracting java")).await?;
archive.extract(&path).map_err(|_| { archive.extract(&path).map_err(|_| {
crate::Error::from(crate::ErrorKind::InputError( crate::Error::from(crate::ErrorKind::InputError(
"Failed to extract java zip".to_string(), "Failed to extract java zip".to_string(),
)) ))
})?; })?;
emit_loading(&loading_bar, 100.0, Some("Done extracting java")).await?; emit_loading(&loading_bar, 100.0, Some("Done extracting java")).await?;
Ok(path Ok(path
.join( .join(
download download
.name .name
.file_stem() .file_stem()
.unwrap_or_default() .unwrap_or_default()
.to_string_lossy() .to_string_lossy()
.to_string(), .to_string(),
) )
.join(format!("zulu-{}.jre/Contents/Home/bin/java", java_version))) .join(format!("zulu-{}.jre/Contents/Home/bin/java", java_version)))
} else { } else {
Err(crate::ErrorKind::LauncherError(format!( Err(crate::ErrorKind::LauncherError(format!(
"No Java Version found for Java version {}, OS {}, and Architecture {}", "No Java Version found for Java version {}, OS {}, and Architecture {}",
java_version, std::env::consts::OS, std::env::consts::ARCH, java_version, std::env::consts::OS, std::env::consts::ARCH,
)).into()) )).into())
} }
}
).await
} }
// Get all JREs that exist on the system // Get all JREs that exist on the system

View File

@@ -76,125 +76,124 @@ enum PackDependency {
Minecraft, Minecraft,
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn install_pack_from_version_id( pub async fn install_pack_from_version_id(
version_id: String, version_id: String,
title: String, title: String,
icon_url: Option<String>, icon_url: Option<String>,
) -> crate::Result<PathBuf> { ) -> crate::Result<PathBuf> {
let state = State::get().await?; let state = State::get().await?;
Box::pin(async move { let profile = crate::api::profile_create::profile_create(
let profile = crate::api::profile_create::profile_create( title.clone(),
title.clone(), "1.19.4".to_string(),
"1.19.4".to_string(), ModLoader::Vanilla,
ModLoader::Vanilla, None,
None, None,
None, icon_url.clone(),
icon_url.clone(), None,
None, Some(true),
Some(true), )
) .await?;
.await?;
let loading_bar = init_loading( let loading_bar = init_loading(
LoadingBarType::PackFileDownload { LoadingBarType::PackFileDownload {
profile_path: profile.clone(), profile_path: profile.clone(),
pack_name: title, pack_name: title,
icon: icon_url, icon: icon_url,
pack_version: version_id.clone(), pack_version: version_id.clone(),
}, },
100.0, 100.0,
"Downloading pack file", "Downloading pack file",
) )
.await?; .await?;
emit_loading(&loading_bar, 0.0, Some("Fetching version")).await?; emit_loading(&loading_bar, 0.0, Some("Fetching version")).await?;
let version: ModrinthVersion = fetch_json( let version: ModrinthVersion = fetch_json(
Method::GET, Method::GET,
&format!("{}version/{}", MODRINTH_API_URL, version_id), &format!("{}version/{}", MODRINTH_API_URL, version_id),
None, None,
None, None,
&state.fetch_semaphore, &state.fetch_semaphore,
) )
.await?; .await?;
emit_loading(&loading_bar, 10.0, None).await?; emit_loading(&loading_bar, 10.0, None).await?;
let (url, hash) = let (url, hash) =
if let Some(file) = version.files.iter().find(|x| x.primary) { if let Some(file) = version.files.iter().find(|x| x.primary) {
Some((file.url.clone(), file.hashes.get("sha1"))) Some((file.url.clone(), file.hashes.get("sha1")))
} else { } else {
version version
.files .files
.first() .first()
.map(|file| (file.url.clone(), file.hashes.get("sha1"))) .map(|file| (file.url.clone(), file.hashes.get("sha1")))
} }
.ok_or_else(|| { .ok_or_else(|| {
crate::ErrorKind::InputError( crate::ErrorKind::InputError(
"Specified version has no files".to_string(), "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,
) )
})?; .await?,
)
let file = fetch_advanced(
Method::GET,
&url,
hash.map(|x| &**x),
None,
None,
Some((&loading_bar, 70.0)),
&state.fetch_semaphore,
)
.await?;
emit_loading(&loading_bar, 0.0, Some("Fetching project metadata"))
.await?;
let project: ModrinthProject = fetch_json(
Method::GET,
&format!("{}project/{}", MODRINTH_API_URL, version.project_id),
None,
None,
&state.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
}
} else { } else {
None None
}; }
emit_loading(&loading_bar, 10.0, None).await?; } else {
None
};
emit_loading(&loading_bar, 10.0, None).await?;
install_pack( install_pack(
file, file,
icon, icon,
Some(project.title), Some(project.title),
Some(version.project_id), Some(version.project_id),
Some(version.id), Some(version.id),
Some(loading_bar), Some(loading_bar),
profile, profile,
) )
.await
})
.await .await
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn install_pack_from_file(path: PathBuf) -> crate::Result<PathBuf> { pub async fn install_pack_from_file(path: PathBuf) -> crate::Result<PathBuf> {
let file = fs::read(&path).await?; let file = fs::read(&path).await?;
@@ -228,6 +227,8 @@ pub async fn install_pack_from_file(path: PathBuf) -> crate::Result<PathBuf> {
.await .await
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
async fn install_pack( async fn install_pack(
file: bytes::Bytes, file: bytes::Bytes,
icon: Option<PathBuf>, icon: Option<PathBuf>,
@@ -239,270 +240,261 @@ async fn install_pack(
) -> crate::Result<PathBuf> { ) -> crate::Result<PathBuf> {
let state = &State::get().await?; 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 // Create zip reader around file
let mut zip_reader = let mut zip_reader = ZipFileReader::new(reader).await.map_err(|_| {
ZipFileReader::new(reader).await.map_err(|_| { crate::Error::from(crate::ErrorKind::InputError(
crate::Error::from(crate::ErrorKind::InputError( "Failed to read input modpack zip".to_string(),
"Failed to read input modpack zip".to_string(), ))
)) })?;
})?;
// Extract index of modrinth.index.json // Extract index of modrinth.index.json
let zip_index_option = zip_reader 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() .file()
.entries() .entries()
.iter() .get(zip_index)
.position(|f| f.entry().filename() == "modrinth.index.json"); .unwrap()
if let Some(zip_index) = zip_index_option { .entry()
let mut manifest = String::new(); .clone();
let entry = zip_reader let mut reader = zip_reader.entry(zip_index).await?;
.file() reader.read_to_string_checked(&mut manifest, &entry).await?;
.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?;
let pack: PackFormat = serde_json::from_str(&manifest)?; let pack: PackFormat = serde_json::from_str(&manifest)?;
if &*pack.game != "minecraft" { if &*pack.game != "minecraft" {
return Err(crate::ErrorKind::InputError( return Err(crate::ErrorKind::InputError(
"Pack does not support Minecraft".to_string(), "Pack does not support Minecraft".to_string(),
) )
.into()); .into());
} }
let mut game_version = None; let mut game_version = None;
let mut mod_loader = None; let mut mod_loader = None;
let mut loader_version = None; let mut loader_version = None;
for (key, value) in &pack.dependencies { for (key, value) in &pack.dependencies {
match key { match key {
PackDependency::Forge => { PackDependency::Forge => {
mod_loader = Some(ModLoader::Forge); mod_loader = Some(ModLoader::Forge);
loader_version = Some(value); 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),
} }
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 { let game_version = if let Some(game_version) = game_version {
game_version game_version
} else { } else {
return Err(crate::ErrorKind::InputError( return Err(crate::ErrorKind::InputError(
"Pack did not specify Minecraft version".to_string(), "Pack did not specify Minecraft version".to_string(),
) )
.into()); .into());
}; };
let loader_version = let loader_version =
crate::profile_create::get_loader_version_from_loader( crate::profile_create::get_loader_version_from_loader(
game_version.clone(), game_version.clone(),
mod_loader.unwrap_or(ModLoader::Vanilla), mod_loader.unwrap_or(ModLoader::Vanilla),
loader_version.cloned(), 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(()) }
})
.await?; .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(); async { Ok(()) }
let result = async { })
let loading_bar = init_or_edit_loading( .await?;
existing_loading_bar, State::sync().await?;
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(); let profile = profile.clone();
use futures::StreamExt; let result = async {
loading_try_for_each_concurrent( let loading_bar = init_or_edit_loading(
futures::stream::iter(pack.files.into_iter()) existing_loading_bar,
.map(Ok::<PackFile, crate::Error>), LoadingBarType::PackDownload {
None, profile_path: profile.clone(),
Some(&loading_bar), pack_name: pack.name.clone(),
70.0, icon,
num_files, pack_id: project_id,
None, pack_version: version_id,
|project| { },
let profile = profile.clone(); 100.0,
async move { "Downloading modpack",
//TODO: Future update: prompt user for optional files in a modpack )
if let Some(env) = project.env { .await?;
if env
.get(&EnvType::Client) let num_files = pack.files.len();
.map(|x| x == &SideType::Unsupported) use futures::StreamExt;
.unwrap_or(false) loading_try_for_each_concurrent(
{ futures::stream::iter(pack.files.into_iter())
return Ok(()); .map(Ok::<PackFile, crate::Error>),
} 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( let file = fetch_mirrors(
&project &project
.downloads .downloads
.iter() .iter()
.map(|x| &**x) .map(|x| &**x)
.collect::<Vec<&str>>(), .collect::<Vec<&str>>(),
project project
.hashes .hashes
.get(&PackFileHash::Sha1) .get(&PackFileHash::Sha1)
.map(|x| &**x), .map(|x| &**x),
&state.fetch_semaphore, &state.fetch_semaphore,
) )
.await?; .await?;
let path = std::path::Path::new(&project.path) let path = std::path::Path::new(&project.path)
.components() .components()
.next(); .next();
if let Some(path) = path { if let Some(path) = path {
match path { match path {
Component::CurDir Component::CurDir | Component::Normal(_) => {
| Component::Normal(_) => { let path = profile.join(project.path);
let path = profile.join(project.path); write(&path, &file, &state.io_semaphore)
write(
&path,
&file,
&state.io_semaphore,
)
.await?; .await?;
} }
_ => {} _ => {}
}; };
}
Ok(())
} }
}, Ok(())
) }
},
)
.await?;
emit_loading(&loading_bar, 0.0, Some("Extracting overrides"))
.await?; .await?;
emit_loading(&loading_bar, 0.0, Some("Extracting overrides")) let mut total_len = 0;
.await?;
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() { if (file.filename().starts_with("overrides")
let file = || file.filename().starts_with("client_overrides"))
zip_reader.file().entries().get(index).unwrap().entry(); && !file.filename().ends_with('/')
{
if (file.filename().starts_with("overrides") total_len += 1;
|| file.filename().starts_with("client_overrides"))
&& !file.filename().ends_with('/')
{
total_len += 1;
}
} }
}
for index in 0..zip_reader.file().entries().len() { for index in 0..zip_reader.file().entries().len() {
let file = zip_reader let file = zip_reader
.file() .file()
.entries() .entries()
.get(index) .get(index)
.unwrap() .unwrap()
.entry() .entry()
.clone(); .clone();
let file_path = PathBuf::from(file.filename()); let file_path = PathBuf::from(file.filename());
if (file.filename().starts_with("overrides") if (file.filename().starts_with("overrides")
|| file.filename().starts_with("client_overrides")) || file.filename().starts_with("client_overrides"))
&& !file.filename().ends_with('/') && !file.filename().ends_with('/')
{ {
// Reads the file into the 'content' variable // Reads the file into the 'content' variable
let mut content = Vec::new(); let mut content = Vec::new();
let mut reader = zip_reader.entry(index).await?; let mut reader = zip_reader.entry(index).await?;
reader.read_to_end_checked(&mut content, &file).await?; reader.read_to_end_checked(&mut content, &file).await?;
let mut new_path = PathBuf::new(); let mut new_path = PathBuf::new();
let components = file_path.components().skip(1); let components = file_path.components().skip(1);
for component in components { for component in components {
new_path.push(component); new_path.push(component);
} }
if new_path.file_name().is_some() { if new_path.file_name().is_some() {
write( write(
&profile.join(new_path), &profile.join(new_path),
&content, &content,
&state.io_semaphore, &state.io_semaphore,
)
.await?;
}
emit_loading(
&loading_bar,
30.0 / total_len as f64,
Some(&format!(
"Extracting override {}/{}",
index, total_len
)),
) )
.await?; .await?;
} }
}
if let Some(profile_val) = emit_loading(
crate::api::profile::get(&profile, None).await? &loading_bar,
{ 30.0 / total_len as f64,
crate::launcher::install_minecraft( Some(&format!(
&profile_val, "Extracting override {}/{}",
Some(loading_bar), index, total_len
)),
) )
.await?; .await?;
} }
Ok::<PathBuf, crate::Error>(profile.clone())
} }
.await;
match result { if let Some(profile_val) =
Ok(profile) => Ok(profile), crate::api::profile::get(&profile, None).await?
Err(err) => { {
let _ = crate::api::profile::remove(&profile).await; crate::launcher::install_minecraft(
&profile_val,
Err(err) Some(loading_bar),
} )
.await?;
} }
} else {
let _ = crate::api::profile::remove(&profile).await;
Err(crate::Error::from(crate::ErrorKind::InputError( Ok::<PathBuf, crate::Error>(profile.clone())
"No pack manifest found in mrpack".to_string(),
)))
} }
}) .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(),
)))
}
} }

View File

@@ -123,47 +123,48 @@ pub async fn install(path: &Path) -> crate::Result<()> {
Ok(()) Ok(())
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn update_all(profile_path: &Path) -> crate::Result<()> { pub async fn update_all(profile_path: &Path) -> crate::Result<()> {
Box::pin(async move { if let Some(profile) = get(profile_path, None).await? {
if let Some(profile) = get(profile_path, None).await? { let loading_bar = init_loading(
let loading_bar = init_loading( LoadingBarType::ProfileUpdate {
LoadingBarType::ProfileUpdate { profile_path: profile.path.clone(),
profile_path: profile.path.clone(), profile_name: profile.metadata.name.clone(),
profile_name: profile.metadata.name.clone(), },
}, 100.0,
100.0, "Updating profile",
"Updating profile", )
) .await?;
.await?;
use futures::StreamExt; use futures::StreamExt;
loading_try_for_each_concurrent( loading_try_for_each_concurrent(
futures::stream::iter(profile.projects.keys()) futures::stream::iter(profile.projects.keys())
.map(Ok::<&PathBuf, crate::Error>), .map(Ok::<&PathBuf, crate::Error>),
None, None,
Some(&loading_bar), Some(&loading_bar),
100.0, 100.0,
profile.projects.keys().len(), profile.projects.keys().len(),
None, None,
|project| async move { |project| async move {
let _ = update_project(profile_path, project).await?; let _ = update_project(profile_path, project).await?;
Ok(()) Ok(())
}, },
) )
.await?; .await?;
Ok(()) Ok(())
} else { } else {
Err(crate::ErrorKind::UnmanagedProfileError( Err(crate::ErrorKind::UnmanagedProfileError(
profile_path.display().to_string(), profile_path.display().to_string(),
) )
.as_error()) .as_error())
} }
})
.await
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn update_project( pub async fn update_project(
profile_path: &Path, profile_path: &Path,
project_path: &Path, project_path: &Path,
@@ -304,113 +305,108 @@ pub async fn remove_project(
/// failing with an error if no credentials are available /// failing with an error if no credentials are available
#[tracing::instrument] #[tracing::instrument]
pub async fn run(path: &Path) -> crate::Result<Arc<RwLock<MinecraftChild>>> { pub async fn run(path: &Path) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
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) // Get default account and refresh credentials (preferred way to log in)
let default_account = state.settings.read().await.default_user; let default_account = state.settings.read().await.default_user;
let credentials = if let Some(default_account) = default_account { let credentials = if let Some(default_account) = default_account {
refresh(default_account).await? 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 { } else {
// If no default account, try to use a logged in account return Err(crate::ErrorKind::NoCredentialsError.as_error());
let users = auth::users().await?; }
let last_account = users.first(); };
if let Some(last_account) = last_account { run_credentials(path, &credentials).await
refresh(last_account.id).await?
} else {
return Err(crate::ErrorKind::NoCredentialsError.as_error());
}
};
run_credentials(path, &credentials).await
})
.await
} }
/// Run Minecraft using a profile, and credentials for authentication /// Run Minecraft using a profile, and credentials for authentication
/// Returns Arc pointer to RwLock to Child /// Returns Arc pointer to RwLock to Child
#[tracing::instrument] #[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn run_credentials( pub async fn run_credentials(
path: &Path, path: &Path,
credentials: &auth::Credentials, credentials: &auth::Credentials,
) -> crate::Result<Arc<RwLock<MinecraftChild>>> { ) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
Box::pin(async move { let state = State::get().await?;
let state = State::get().await?; let settings = state.settings.read().await;
let settings = state.settings.read().await; let profile = get(path, None).await?.ok_or_else(|| {
let profile = get(path, None).await?.ok_or_else(|| { crate::ErrorKind::OtherError(format!(
crate::ErrorKind::OtherError(format!( "Tried to run a nonexistent or unloaded profile at path {}!",
"Tried to run a nonexistent or unloaded profile at path {}!", path.display()
path.display() ))
)) })?;
})?;
let pre_launch_hooks = let pre_launch_hooks =
&profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch; &profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch;
if let Some(hook) = pre_launch_hooks { if let Some(hook) = pre_launch_hooks {
// TODO: hook parameters // TODO: hook parameters
let mut cmd = hook.split(' '); let mut cmd = hook.split(' ');
if let Some(command) = cmd.next() { if let Some(command) = cmd.next() {
let result = Command::new(command) let result = Command::new(command)
.args(&cmd.collect::<Vec<&str>>()) .args(&cmd.collect::<Vec<&str>>())
.current_dir(path) .current_dir(path)
.spawn()? .spawn()?
.wait() .wait()
.await?; .await?;
if !result.success() { if !result.success() {
return Err(crate::ErrorKind::LauncherError(format!( return Err(crate::ErrorKind::LauncherError(format!(
"Non-zero exit code for pre-launch hook: {}", "Non-zero exit code for pre-launch hook: {}",
result.code().unwrap_or(-1) result.code().unwrap_or(-1)
)) ))
.as_error()); .as_error());
}
} }
} }
}
let java_args = profile let java_args = profile
.java .java
.as_ref() .as_ref()
.and_then(|it| it.extra_arguments.as_ref()) .and_then(|it| it.extra_arguments.as_ref())
.unwrap_or(&settings.custom_java_args); .unwrap_or(&settings.custom_java_args);
let wrapper = profile let wrapper = profile
.hooks .hooks
.as_ref() .as_ref()
.map_or(&settings.hooks.wrapper, |it| &it.wrapper); .map_or(&settings.hooks.wrapper, |it| &it.wrapper);
let memory = profile.memory.unwrap_or(settings.memory); let memory = profile.memory.unwrap_or(settings.memory);
let resolution = profile.resolution.unwrap_or(settings.game_resolution); 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 // Post post exit hooks
let post_exit_hook = let post_exit_hook =
&profile.hooks.as_ref().unwrap_or(&settings.hooks).post_exit; &profile.hooks.as_ref().unwrap_or(&settings.hooks).post_exit;
let post_exit_hook = if let Some(hook) = post_exit_hook { let post_exit_hook = if let Some(hook) = post_exit_hook {
let mut cmd = hook.split(' '); let mut cmd = hook.split(' ');
if let Some(command) = cmd.next() { if let Some(command) = cmd.next() {
let mut command = Command::new(command); let mut command = Command::new(command);
command.args(&cmd.collect::<Vec<&str>>()).current_dir(path); command.args(&cmd.collect::<Vec<&str>>()).current_dir(path);
Some(command) Some(command)
} else {
None
}
} else { } else {
None None
}; }
} else {
None
};
let mc_process = crate::launcher::launch_minecraft( let mc_process = crate::launcher::launch_minecraft(
java_args, java_args,
env_args, env_args,
wrapper, wrapper,
&memory, &memory,
&resolution, &resolution,
credentials, credentials,
post_exit_hook, post_exit_hook,
&profile, &profile,
) )
.await?; .await?;
Ok(mc_process) Ok(mc_process)
})
.await
} }

View File

@@ -39,6 +39,7 @@ pub async fn profile_create_empty() -> crate::Result<PathBuf> {
// Creates a profile at the given filepath and adds it to the in-memory state // 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 // Returns filepath at which it can be accessed in the State
#[tracing::instrument] #[tracing::instrument]
#[theseus_macros::debug_pin]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub async fn profile_create( pub async fn profile_create(
name: String, // the name of the profile, and relative path name: String, // the name of the profile, and relative path
@@ -52,92 +53,91 @@ pub async fn profile_create(
) -> crate::Result<PathBuf> { ) -> crate::Result<PathBuf> {
trace!("Creating new profile. {}", name); trace!("Creating new profile. {}", name);
let state = State::get().await?; let state = State::get().await?;
Box::pin(async move { let uuid = Uuid::new_v4();
let uuid = Uuid::new_v4(); let path = state.directories.profiles_dir().join(uuid.to_string());
let path = state.directories.profiles_dir().join(uuid.to_string()); if path.exists() {
if path.exists() { if !path.is_dir() {
if !path.is_dir() { return Err(ProfileCreationError::NotFolder.into());
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?;
} }
if path.join("profile.json").exists() {
info!( return Err(ProfileCreationError::ProfileExistsError(
"Creating profile at path {}", path.join("profile.json"),
&canonicalize(&path)?.display()
);
let loader = if modloader != ModLoader::Vanilla {
get_loader_version_from_loader(
game_version.clone(),
modloader,
loader_version,
) )
.await? .into());
} 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);
} }
profile.metadata.linked_data = linked_data; if ReadDirStream::new(fs::read_dir(&path).await?)
.next()
emit_profile( .await
uuid, .is_some()
path.clone(),
&profile.metadata.name,
ProfilePayloadType::Created,
)
.await?;
{ {
let mut profiles = state.profiles.write().await; return Err(ProfileCreationError::NotEmptyFolder.into());
profiles.insert(profile.clone()).await?;
} }
} else {
fs::create_dir_all(&path).await?;
}
if !skip_install_profile.unwrap_or(false) { info!(
crate::launcher::install_minecraft(&profile, None).await?; "Creating profile at path {}",
} &canonicalize(&path)?.display()
State::sync().await?; );
let loader = if modloader != ModLoader::Vanilla {
get_loader_version_from_loader(
game_version.clone(),
modloader,
loader_version,
)
.await?
} else {
None
};
Ok(path) // Fully canonicalize now that its created for storing purposes
}) let path = canonicalize(&path)?;
.await 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( pub(crate) async fn get_loader_version_from_loader(
game_version: String, game_version: String,
loader: ModLoader, loader: ModLoader,

View File

@@ -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. // 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) // 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 // title is the title of the loading bar
#[theseus_macros::debug_pin]
pub async fn init_loading( pub async fn init_loading(
bar_type: LoadingBarType, bar_type: LoadingBarType,
total: f64, total: f64,
@@ -134,6 +135,7 @@ pub async fn edit_loading(
// By convention, fraction is the fraction of the progress bar that is filled // By convention, fraction is the fraction of the progress bar that is filled
#[allow(unused_variables)] #[allow(unused_variables)]
#[tracing::instrument(level = "debug")] #[tracing::instrument(level = "debug")]
#[theseus_macros::debug_pin]
pub async fn emit_loading( pub async fn emit_loading(
key: &LoadingBarId, key: &LoadingBarId,
increment_frac: f64, 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). // Total is the total amount of progress that the loading bar should take up by all futures in this (will be split evenly amongst them).
// If message is Some(t) you will overwrite this loading bar's message with a custom one // If message is Some(t) you will overwrite this loading bar's message with a custom one
// num_futs is the number of futures that will be run, which is needed as we allow Iterator to be passed in, which doesn't have a size // num_futs is the number of futures that will be run, which is needed as we allow Iterator to be passed in, which doesn't have a size
#[tracing::instrument(skip(stream, f))]
#[theseus_macros::debug_pin]
pub async fn loading_try_for_each_concurrent<I, F, Fut, T>( pub async fn loading_try_for_each_concurrent<I, F, Fut, T>(
stream: I, stream: I,
limit: Option<usize>, limit: Option<usize>,

View File

@@ -54,6 +54,7 @@ pub async fn download_minecraft(
} }
#[tracing::instrument(skip_all, fields(version = version.id.as_str(), loader = ?loader))] #[tracing::instrument(skip_all, fields(version = version.id.as_str(), loader = ?loader))]
#[theseus_macros::debug_pin]
pub async fn download_version_info( pub async fn download_version_info(
st: &State, st: &State,
version: &GameVersion, version: &GameVersion,
@@ -61,93 +62,84 @@ pub async fn download_version_info(
force: Option<bool>, force: Option<bool>,
loading_bar: Option<&LoadingBarId>, loading_bar: Option<&LoadingBarId>,
) -> crate::Result<GameVersionInfo> { ) -> crate::Result<GameVersionInfo> {
Box::pin(async move { let version_id = loader
let version_id = loader.map_or(version.id.clone(), |it| { .map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id));
format!("{}-{}", version.id, it.id) tracing::debug!("Loading version info for Minecraft {version_id}");
}); let path = st
tracing::debug!("Loading version info for Minecraft {version_id}"); .directories
let path = st .version_dir(&version_id)
.directories .join(format!("{version_id}.json"));
.version_dir(&version_id)
.join(format!("{version_id}.json"));
let res = if path.exists() && !force.unwrap_or(false) { let res = if path.exists() && !force.unwrap_or(false) {
fs::read(path) fs::read(path)
.err_into::<crate::Error>() .err_into::<crate::Error>()
.await .await
.and_then(|ref it| Ok(serde_json::from_slice(it)?)) .and_then(|ref it| Ok(serde_json::from_slice(it)?))
} else { } else {
tracing::info!( tracing::info!("Downloading version info for version {}", &version.id);
"Downloading version info for version {}", let mut info = d::minecraft::fetch_version_info(version).await?;
&version.id
);
let mut info = d::minecraft::fetch_version_info(version).await?;
if let Some(loader) = loader { if let Some(loader) = loader {
let partial = let partial = d::modded::fetch_partial_version(&loader.url).await?;
d::modded::fetch_partial_version(&loader.url).await?; info = d::modded::merge_partial_version(partial, info);
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?;
} }
info.id = version_id.clone();
tracing::debug!("Loaded version info for Minecraft {version_id}"); write(&path, &serde_json::to_vec(&info)?, &st.io_semaphore).await?;
Ok(res) Ok(info)
}) }?;
.await
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)] #[tracing::instrument(skip_all)]
#[theseus_macros::debug_pin]
pub async fn download_client( pub async fn download_client(
st: &State, st: &State,
version_info: &GameVersionInfo, version_info: &GameVersionInfo,
loading_bar: Option<&LoadingBarId>, loading_bar: Option<&LoadingBarId>,
) -> crate::Result<()> { ) -> crate::Result<()> {
Box::pin(async move { let version = &version_info.id;
let version = &version_info.id; tracing::debug!("Locating client for version {version}");
tracing::debug!("Locating client for version {version}"); let client_download = version_info
let client_download = version_info .downloads
.downloads .get(&d::minecraft::DownloadType::Client)
.get(&d::minecraft::DownloadType::Client) .ok_or(
.ok_or( crate::ErrorKind::LauncherError(format!(
crate::ErrorKind::LauncherError(format!( "No client downloads exist for version {version}"
"No client downloads exist for version {version}" ))
)) .as_error(),
.as_error(), )?;
)?; let path = st
let path = st .directories
.directories .version_dir(version)
.version_dir(version) .join(format!("{version}.jar"));
.join(format!("{version}.jar"));
if !path.exists() { if !path.exists() {
let bytes = fetch( let bytes = fetch(
&client_download.url, &client_download.url,
Some(&client_download.sha1), Some(&client_download.sha1),
&st.fetch_semaphore, &st.fetch_semaphore,
) )
.await?; .await?;
write(&path, &bytes, &st.io_semaphore).await?; write(&path, &bytes, &st.io_semaphore).await?;
tracing::trace!("Fetched client version {version}"); tracing::trace!("Fetched client version {version}");
} }
if let Some(loading_bar) = loading_bar { if let Some(loading_bar) = loading_bar {
emit_loading(loading_bar, 9.0, None).await?; emit_loading(loading_bar, 9.0, None).await?;
} }
tracing::debug!("Client loaded for version {version}!"); tracing::debug!("Client loaded for version {version}!");
Ok(()) Ok(())
})
.await
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
#[theseus_macros::debug_pin]
pub async fn download_assets_index( pub async fn download_assets_index(
st: &State, st: &State,
version: &GameVersionInfo, version: &GameVersionInfo,
@@ -179,6 +171,7 @@ pub async fn download_assets_index(
} }
#[tracing::instrument(skip(st, index))] #[tracing::instrument(skip(st, index))]
#[theseus_macros::debug_pin]
pub async fn download_assets( pub async fn download_assets(
st: &State, st: &State,
with_legacy: bool, with_legacy: bool,
@@ -186,13 +179,12 @@ pub async fn download_assets(
loading_bar: Option<&LoadingBarId>, loading_bar: Option<&LoadingBarId>,
loading_amount: f64, loading_amount: f64,
) -> crate::Result<()> { ) -> crate::Result<()> {
Box::pin(async move { tracing::debug!("Loading assets");
tracing::debug!("Loading assets"); let num_futs = index.objects.len();
let num_futs = index.objects.len(); let assets = stream::iter(index.objects.iter())
let assets = stream::iter(index.objects.iter()) .map(Ok::<(&String, &Asset), crate::Error>);
.map(Ok::<(&String, &Asset), crate::Error>);
loading_try_for_each_concurrent(assets, loading_try_for_each_concurrent(assets,
None, None,
loading_bar, loading_bar,
loading_amount, loading_amount,
@@ -236,12 +228,12 @@ pub async fn download_assets(
tracing::trace!("Loaded asset with hash {hash}"); tracing::trace!("Loaded asset with hash {hash}");
Ok(()) Ok(())
}).await?; }).await?;
tracing::debug!("Done loading assets!"); tracing::debug!("Done loading assets!");
Ok(()) Ok(())
}).await
} }
#[tracing::instrument(skip(st, libraries))] #[tracing::instrument(skip(st, libraries))]
#[theseus_macros::debug_pin]
pub async fn download_libraries( pub async fn download_libraries(
st: &State, st: &State,
libraries: &[Library], libraries: &[Library],
@@ -250,15 +242,14 @@ pub async fn download_libraries(
loading_amount: f64, loading_amount: f64,
java_arch: &str, java_arch: &str,
) -> crate::Result<()> { ) -> crate::Result<()> {
Box::pin(async move { tracing::debug!("Loading libraries");
tracing::debug!("Loading libraries");
tokio::try_join! { tokio::try_join! {
fs::create_dir_all(st.directories.libraries_dir()), fs::create_dir_all(st.directories.libraries_dir()),
fs::create_dir_all(st.directories.version_natives_dir(version)) fs::create_dir_all(st.directories.version_natives_dir(version))
}?; }?;
let num_files = libraries.len(); let num_files = libraries.len();
loading_try_for_each_concurrent( loading_try_for_each_concurrent(
stream::iter(libraries.iter()) stream::iter(libraries.iter())
.map(Ok::<&Library, crate::Error>), None, loading_bar,loading_amount,num_files, None,|library| async move { .map(Ok::<&Library, crate::Error>), None, loading_bar,loading_amount,num_files, None,|library| async move {
if let Some(rules) = &library.rules { if let Some(rules) = &library.rules {
@@ -342,8 +333,6 @@ pub async fn download_libraries(
} }
).await?; ).await?;
tracing::debug!("Done loading libraries!"); tracing::debug!("Done loading libraries!");
Ok(()) Ok(())
}).await
} }

View File

@@ -92,183 +92,186 @@ async fn get_java_version_from_profile(
} }
#[tracing::instrument(skip(profile))] #[tracing::instrument(skip(profile))]
#[theseus_macros::debug_pin]
pub async fn install_minecraft( pub async fn install_minecraft(
profile: &Profile, profile: &Profile,
existing_loading_bar: Option<LoadingBarId>, existing_loading_bar: Option<LoadingBarId>,
) -> crate::Result<()> { ) -> crate::Result<()> {
Box::pin(async move { let loading_bar = init_or_edit_loading(
let loading_bar = init_or_edit_loading( existing_loading_bar,
existing_loading_bar, LoadingBarType::MinecraftDownload {
LoadingBarType::MinecraftDownload { // If we are downloading minecraft for a profile, provide its name and uuid
// If we are downloading minecraft for a profile, provide its name and uuid profile_name: profile.metadata.name.clone(),
profile_name: profile.metadata.name.clone(), profile_path: profile.path.clone(),
profile_path: profile.path.clone(), },
}, 100.0,
100.0, "Downloading Minecraft",
"Downloading Minecraft", )
) .await?;
.await?;
crate::api::profile::edit(&profile.path, |prof| { crate::api::profile::edit(&profile.path, |prof| {
prof.install_stage = ProfileInstallStage::Installing; prof.install_stage = ProfileInstallStage::Installing;
async { Ok(()) } async { Ok(()) }
}) })
.await?; .await?;
State::sync().await?; State::sync().await?;
let state = State::get().await?; let state = State::get().await?;
let instance_path = &canonicalize(&profile.path)?; let instance_path = &canonicalize(&profile.path)?;
let metadata = state.metadata.read().await; let metadata = state.metadata.read().await;
let version = metadata let version = metadata
.minecraft .minecraft
.versions .versions
.iter() .iter()
.find(|it| it.id == profile.metadata.game_version) .find(|it| it.id == profile.metadata.game_version)
.ok_or(crate::ErrorKind::LauncherError(format!( .ok_or(crate::ErrorKind::LauncherError(format!(
"Invalid game version: {}", "Invalid game version: {}",
profile.metadata.game_version profile.metadata.game_version
)))?; )))?;
let version_jar = profile let version_jar = profile
.metadata .metadata
.loader_version .loader_version
.as_ref() .as_ref()
.map_or(version.id.clone(), |it| { .map_or(version.id.clone(), |it| {
format!("{}-{}", version.id.clone(), it.id.clone()) format!("{}-{}", version.id.clone(), it.id.clone())
}); });
// Download version info (5) // Download version info (5)
let mut version_info = download::download_version_info( let mut version_info = download::download_version_info(
&state, &state,
version, version,
profile.metadata.loader_version.as_ref(), profile.metadata.loader_version.as_ref(),
None, None,
Some(&loading_bar), Some(&loading_bar),
) )
.await?; .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 minecraft (5-90)
download::download_minecraft(&state, &version_info, &loading_bar, &java_version.architecture).await?; download::download_minecraft(
&state,
&version_info,
&loading_bar,
&java_version.architecture,
)
.await?;
if let Some(processors) = &version_info.processors { if let Some(processors) = &version_info.processors {
let client_path = state let client_path = state
.directories .directories
.version_dir(&version_jar) .version_dir(&version_jar)
.join(format!("{version_jar}.jar")); .join(format!("{version_jar}.jar"));
if let Some(ref mut data) = version_info.data { if let Some(ref mut data) = version_info.data {
processor_rules! { processor_rules! {
data; data;
"SIDE": "SIDE":
client => "client", client => "client",
server => ""; server => "";
"MINECRAFT_JAR" : "MINECRAFT_JAR" :
client => client_path.to_string_lossy(), client => client_path.to_string_lossy(),
server => ""; server => "";
"MINECRAFT_VERSION": "MINECRAFT_VERSION":
client => profile.metadata.game_version.clone(), client => profile.metadata.game_version.clone(),
server => ""; server => "";
"ROOT": "ROOT":
client => instance_path.to_string_lossy(), client => instance_path.to_string_lossy(),
server => ""; server => "";
"LIBRARY_DIR": "LIBRARY_DIR":
client => state.directories.libraries_dir().to_string_lossy(), client => state.directories.libraries_dir().to_string_lossy(),
server => ""; 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")) let cp = wrap_ref_builder!(cp = processor.classpath.clone() => {
.await?; cp.push(processor.jar.clone())
let total_length = processors.len(); });
// Forge processors (90-100) let child = Command::new(&java_version.path)
for (index, processor) in processors.iter().enumerate() { .arg("-cp")
if let Some(sides) = &processor.sides { .arg(args::get_class_paths_jar(
if !sides.contains(&String::from("client")) { &state.directories.libraries_dir(),
continue; &cp,
} &java_version.architecture,
} )?)
.arg(
let cp = wrap_ref_builder!(cp = processor.classpath.clone() => { args::get_processor_main_class(args::get_lib_path(
cp.push(processor.jar.clone())
});
let child = Command::new(&java_version.path)
.arg("-cp")
.arg(args::get_class_paths_jar(
&state.directories.libraries_dir(), &state.directories.libraries_dir(),
&cp, &processor.jar,
&java_version.architecture false,
)?) )?)
.arg( .await?
args::get_processor_main_class(args::get_lib_path( .ok_or_else(|| {
&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| {
crate::ErrorKind::LauncherError(format!( 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| { crate::api::profile::edit(&profile.path, |prof| {
prof.install_stage = ProfileInstallStage::Installed; prof.install_stage = ProfileInstallStage::Installed;
async { Ok(()) } async { Ok(()) }
}) })
.await?; .await?;
State::sync().await?; State::sync().await?;
emit_loading( emit_loading(&loading_bar, 1.0, Some("Finished installing")).await?;
&loading_bar,
1.0,
Some("Finished installing"),
)
.await?;
Ok(()) Ok(())
}).await
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub async fn launch_minecraft( pub async fn launch_minecraft(
java_args: &[String], java_args: &[String],
@@ -280,156 +283,155 @@ pub async fn launch_minecraft(
post_exit_hook: Option<Command>, post_exit_hook: Option<Command>,
profile: &Profile, profile: &Profile,
) -> crate::Result<Arc<tokio::sync::RwLock<MinecraftChild>>> { ) -> crate::Result<Arc<tokio::sync::RwLock<MinecraftChild>>> {
Box::pin(async move { if profile.install_stage == ProfileInstallStage::PackInstalling
if profile.install_stage == ProfileInstallStage::PackInstalling || profile.install_stage == ProfileInstallStage::Installing
|| profile.install_stage == ProfileInstallStage::Installing {
{ return Err(crate::ErrorKind::LauncherError(
return Err(crate::ErrorKind::LauncherError( "Profile is still installing".to_string(),
"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,
) )
.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 let state = State::get().await?;
.directories let metadata = state.metadata.read().await;
.version_dir(&version_jar) let instance_path = &canonicalize(&profile.path)?;
.join(format!("{version_jar}.jar"));
let args = version_info.arguments.clone().unwrap_or_default(); let version = metadata
let mut command = match wrapper { .minecraft
Some(hook) => { .versions
wrap_ref_builder!(it = Command::new(hook) => {it.arg(&java_version.path)}) .iter()
} .find(|it| it.id == profile.metadata.game_version)
None => Command::new(&java_version.path), .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 let version_info = download::download_version_info(
// Done late so a quick double call doesn't launch two instances &state,
let existing_processes = version,
process::get_uuids_by_profile_path(instance_path).await?; profile.metadata.loader_version.as_ref(),
if let Some(uuid) = existing_processes.first() { None,
return Err(crate::ErrorKind::LauncherError(format!( None,
"Profile {} is already running at UUID: {uuid}", )
instance_path.display() .await?;
))
.as_error()); 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 let env_args = Vec::from(env_args);
.args(
args::get_jvm_arguments( // Check if profile has a running profile, and reject running the command if it does
args.get(&d::minecraft::ArgumentType::Jvm) // Done late so a quick double call doesn't launch two instances
.map(|x| x.as_slice()), let existing_processes =
&state.directories.version_natives_dir(&version_jar), 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(), &state.directories.libraries_dir(),
&args::get_class_paths( version_info.libraries.as_slice(),
&state.directories.libraries_dir(), &client_path,
version_info.libraries.as_slice(), &java_version.architecture,
&client_path, )?,
&java_version.architecture &version_jar,
)?, *memory,
&version_jar, Vec::from(java_args),
*memory, &java_version.architecture,
Vec::from(java_args), )?
&java_version.architecture .into_iter()
)? .collect::<Vec<_>>(),
.into_iter() )
.collect::<Vec<_>>(), .arg(version_info.main_class.clone())
) .args(
.arg(version_info.main_class.clone()) args::get_minecraft_arguments(
.args( args.get(&d::minecraft::ArgumentType::Game)
args::get_minecraft_arguments( .map(|x| x.as_slice()),
args.get(&d::minecraft::ArgumentType::Game) version_info.minecraft_arguments.as_deref(),
.map(|x| x.as_slice()), credentials,
version_info.minecraft_arguments.as_deref(), &version.id,
credentials, &version_info.asset_index.id,
&version.id, instance_path,
&version_info.asset_index.id, &state.directories.assets_dir(),
instance_path, &version.type_,
&state.directories.assets_dir(), *resolution,
&version.type_, &java_version.architecture,
*resolution, )?
&java_version.architecture .into_iter()
)? .collect::<Vec<_>>(),
.into_iter() )
.collect::<Vec<_>>(), .current_dir(instance_path.clone())
) .stdout(Stdio::piped())
.current_dir(instance_path.clone()) .stderr(Stdio::piped());
.stdout(Stdio::piped())
.stderr(Stdio::piped());
// CARGO-set DYLD_LIBRARY_PATH breaks Minecraft on macOS during testing on playground // CARGO-set DYLD_LIBRARY_PATH breaks Minecraft on macOS during testing on playground
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
if std::env::var("CARGO").is_ok() { if std::env::var("CARGO").is_ok() {
command.env_remove("DYLD_FALLBACK_LIBRARY_PATH"); command.env_remove("DYLD_FALLBACK_LIBRARY_PATH");
} }
command.envs(env_args); command.envs(env_args);
// Get Modrinth logs directories // Get Modrinth logs directories
let datetime_string = let datetime_string =
chrono::Local::now().format("%Y%m%d_%H%M%S").to_string(); chrono::Local::now().format("%Y%m%d_%H%M%S").to_string();
let logs_dir = { let logs_dir = {
let st = State::get().await?; let st = State::get().await?;
st.directories st.directories
.profile_logs_dir(profile.uuid) .profile_logs_dir(profile.uuid)
.join(&datetime_string) .join(&datetime_string)
}; };
fs::create_dir_all(&logs_dir)?; fs::create_dir_all(&logs_dir)?;
let stdout_log_path = logs_dir.join("stdout.log"); let stdout_log_path = logs_dir.join("stdout.log");
let stderr_log_path = logs_dir.join("stderr.log"); let stderr_log_path = logs_dir.join("stderr.log");
// Create Minecraft child by inserting it into the state // Create Minecraft child by inserting it into the state
// This also spawns the process and prepares the subsequent processes // This also spawns the process and prepares the subsequent processes
let mut state_children = state.children.write().await; let mut state_children = state.children.write().await;
state_children state_children
.insert_process( .insert_process(
Uuid::new_v4(), Uuid::new_v4(),
instance_path.to_path_buf(), instance_path.to_path_buf(),
stdout_log_path, stdout_log_path,
stderr_log_path, stderr_log_path,
command, command,
post_exit_hook, post_exit_hook,
) )
.await .await
}).await
} }

View File

@@ -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 // 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 // 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 // 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( pub async fn insert_process(
&mut self, &mut self,
uuid: Uuid, uuid: Uuid,
@@ -110,6 +113,8 @@ impl Children {
// Spawns a new child process and inserts it into the hashmap // 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 // 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 // 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( async fn sequential_process_manager(
uuid: Uuid, uuid: Uuid,
post_command: Option<Command>, post_command: Option<Command>,

View File

@@ -54,6 +54,8 @@ impl Metadata {
} }
// Attempt to fetch metadata and store in sled DB // Attempt to fetch metadata and store in sled DB
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn init( pub async fn init(
dirs: &DirectoryInfo, dirs: &DirectoryInfo,
io_semaphore: &IoSemaphore, io_semaphore: &IoSemaphore,

View File

@@ -82,6 +82,8 @@ pub struct State {
impl State { impl State {
/// Get the current launcher state, initializing it if needed /// Get the current launcher state, initializing it if needed
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn get() -> crate::Result<Arc<Self>> { pub async fn get() -> crate::Result<Arc<Self>> {
LAUNCHER_STATE LAUNCHER_STATE
.get_or_try_init(|| { .get_or_try_init(|| {
@@ -166,37 +168,35 @@ impl State {
} }
#[tracing::instrument] #[tracing::instrument]
#[theseus_macros::debug_pin]
/// Synchronize in-memory state with persistent state /// Synchronize in-memory state with persistent state
pub async fn sync() -> crate::Result<()> { pub async fn sync() -> crate::Result<()> {
Box::pin(async move { let state = Self::get().await?;
let state = Self::get().await?; let sync_settings = async {
let sync_settings = async { let state = Arc::clone(&state);
let state = Arc::clone(&state);
tokio::spawn(async move { tokio::spawn(async move {
let reader = state.settings.read().await; let reader = state.settings.read().await;
reader.sync(&state.directories.settings_file()).await?; reader.sync(&state.directories.settings_file()).await?;
Ok::<_, crate::Error>(()) Ok::<_, crate::Error>(())
}) })
.await? .await?
}; };
let sync_profiles = async { let sync_profiles = async {
let state = Arc::clone(&state); let state = Arc::clone(&state);
tokio::spawn(async move { tokio::spawn(async move {
let profiles = state.profiles.read().await; let profiles = state.profiles.read().await;
profiles.sync().await?; profiles.sync().await?;
Ok::<_, crate::Error>(()) Ok::<_, crate::Error>(())
}) })
.await? .await?
}; };
tokio::try_join!(sync_settings, sync_profiles)?; tokio::try_join!(sync_settings, sync_profiles)?;
Ok(()) Ok(())
})
.await
} }
/// Reset IO semaphore to default values /// Reset IO semaphore to default values

View File

@@ -249,6 +249,8 @@ impl Profile {
Ok(files) Ok(files)
} }
#[tracing::instrument(skip(watcher))]
#[theseus_macros::debug_pin]
pub async fn watch_fs( pub async fn watch_fs(
profile_path: &Path, profile_path: &Path,
watcher: &mut Debouncer<RecommendedWatcher>, watcher: &mut Debouncer<RecommendedWatcher>,
@@ -285,6 +287,8 @@ impl Profile {
Ok(()) Ok(())
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn add_project_version( pub async fn add_project_version(
&self, &self,
version_id: String, version_id: String,
@@ -330,6 +334,8 @@ impl Profile {
Ok((path, version)) Ok((path, version))
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn add_project_bytes( pub async fn add_project_bytes(
&self, &self,
file_name: &str, file_name: &str,
@@ -398,6 +404,8 @@ impl Profile {
Ok(path) Ok(path)
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn toggle_disable_project( pub async fn toggle_disable_project(
&self, &self,
path: &Path, path: &Path,
@@ -471,6 +479,7 @@ impl Profile {
impl Profiles { impl Profiles {
#[tracing::instrument(skip(file_watcher))] #[tracing::instrument(skip(file_watcher))]
#[theseus_macros::debug_pin]
pub async fn init( pub async fn init(
dirs: &DirectoryInfo, dirs: &DirectoryInfo,
file_watcher: &mut Debouncer<RecommendedWatcher>, file_watcher: &mut Debouncer<RecommendedWatcher>,
@@ -501,6 +510,8 @@ impl Profiles {
Ok(Self(profiles)) Ok(Self(profiles))
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn update_projects() { pub async fn update_projects() {
let res = async { let res = async {
let state = State::get().await?; let state = State::get().await?;
@@ -553,6 +564,7 @@ impl Profiles {
} }
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
#[theseus_macros::debug_pin]
pub async fn insert(&mut self, profile: Profile) -> crate::Result<&Self> { pub async fn insert(&mut self, profile: Profile) -> crate::Result<&Self> {
emit_profile( emit_profile(
profile.uuid, profile.uuid,

View File

@@ -200,6 +200,8 @@ pub enum ProjectMetadata {
Unknown, Unknown,
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
async fn read_icon_from_file( async fn read_icon_from_file(
icon_path: Option<String>, icon_path: Option<String>,
cache_dir: &Path, cache_dir: &Path,
@@ -249,6 +251,8 @@ async fn read_icon_from_file(
Ok(None) Ok(None)
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn infer_data_from_files( pub async fn infer_data_from_files(
profile: Profile, profile: Profile,
paths: Vec<PathBuf>, paths: Vec<PathBuf>,

View File

@@ -68,6 +68,8 @@ impl Settings {
} }
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn update_java() { pub async fn update_java() {
let res = async { let res = async {
let state = State::get().await?; let state = State::get().await?;

View File

@@ -20,6 +20,8 @@ pub struct Tags {
} }
impl Tags { impl Tags {
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn init( pub async fn init(
dirs: &DirectoryInfo, dirs: &DirectoryInfo,
io_semaphore: &IoSemaphore, io_semaphore: &IoSemaphore,
@@ -50,6 +52,8 @@ impl Tags {
} }
} }
#[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn update() { pub async fn update() {
let res = async { let res = async {
let state = crate::State::get().await?; let state = crate::State::get().await?;

View File

@@ -66,6 +66,7 @@ where
/// Downloads a file with retry and checksum functionality /// Downloads a file with retry and checksum functionality
#[tracing::instrument(skip(json_body, semaphore))] #[tracing::instrument(skip(json_body, semaphore))]
#[theseus_macros::debug_pin]
pub async fn fetch_advanced( pub async fn fetch_advanced(
method: Method, method: Method,
url: &str, url: &str,
@@ -158,6 +159,7 @@ pub async fn fetch_advanced(
/// Downloads a file from specified mirrors /// Downloads a file from specified mirrors
#[tracing::instrument(skip(semaphore))] #[tracing::instrument(skip(semaphore))]
#[theseus_macros::debug_pin]
pub async fn fetch_mirrors( pub async fn fetch_mirrors(
mirrors: &[&str], mirrors: &[&str],
sha1: Option<&str>, sha1: Option<&str>,

View File

@@ -185,6 +185,7 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
// Gets all JREs from the PATH env variable // Gets all JREs from the PATH env variable
#[tracing::instrument] #[tracing::instrument]
#[theseus_macros::debug_pin]
async fn get_all_autoinstalled_jre_path() -> Result<HashSet<PathBuf>, JREError> async fn get_all_autoinstalled_jre_path() -> Result<HashSet<PathBuf>, JREError>
{ {
Box::pin(async move { 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 // 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 // If no such path exists, or no such valid java at this path exists, returns None
#[tracing::instrument] #[tracing::instrument]
#[theseus_macros::debug_pin]
pub async fn check_java_at_filepath(path: &Path) -> Option<JavaVersion> { pub async fn check_java_at_filepath(path: &Path) -> Option<JavaVersion> {
// Attempt to canonicalize the potential java filepath // Attempt to canonicalize the potential java filepath
// If it fails, this path does not exist and None is returned (no Java here) // If it fails, this path does not exist and None is returned (no Java here)

View File

@@ -16,6 +16,7 @@ regex = "1.5"
[dependencies] [dependencies]
theseus = { path = "../../theseus", features = ["tauri"] } theseus = { path = "../../theseus", features = ["tauri"] }
theseus_macros = { path = "../../theseus_macros" }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View File

@@ -4,6 +4,7 @@ use theseus::prelude::*;
/// Authenticate a user with Hydra - part 1 /// 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) /// This begins the authentication flow quasi-synchronously, returning a URL to visit (that the user will sign in at)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn auth_authenticate_begin_flow() -> Result<url::Url> { pub async fn auth_authenticate_begin_flow() -> Result<url::Url> {
Ok(auth::authenticate_begin_flow().await?) Ok(auth::authenticate_begin_flow().await?)
} }
@@ -12,6 +13,7 @@ pub async fn auth_authenticate_begin_flow() -> Result<url::Url> {
/// This completes the authentication flow quasi-synchronously, returning the sign-in credentials /// This completes the authentication flow quasi-synchronously, returning the sign-in credentials
/// (and also adding the credentials to the state) /// (and also adding the credentials to the state)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn auth_authenticate_await_completion() -> Result<Credentials> { pub async fn auth_authenticate_await_completion() -> Result<Credentials> {
Ok(auth::authenticate_await_complete_flow().await?) Ok(auth::authenticate_await_complete_flow().await?)
} }
@@ -19,13 +21,13 @@ pub async fn auth_authenticate_await_completion() -> Result<Credentials> {
/// Refresh some credentials using Hydra, if needed /// Refresh some credentials using Hydra, if needed
// invoke('auth_refresh',user) // invoke('auth_refresh',user)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn auth_refresh(user: uuid::Uuid) -> Result<Credentials> { pub async fn auth_refresh(user: uuid::Uuid) -> Result<Credentials> {
Ok(auth::refresh(user).await?) Ok(auth::refresh(user).await?)
} }
/// Remove a user account from the database
// invoke('auth_remove_user',user)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn auth_remove_user(user: uuid::Uuid) -> Result<()> { pub async fn auth_remove_user(user: uuid::Uuid) -> Result<()> {
Ok(auth::remove_user(user).await?) 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 /// Check if a user exists in Theseus
// invoke('auth_has_user',user) // invoke('auth_has_user',user)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn auth_has_user(user: uuid::Uuid) -> Result<bool> { pub async fn auth_has_user(user: uuid::Uuid) -> Result<bool> {
Ok(auth::has_user(user).await?) Ok(auth::has_user(user).await?)
} }
@@ -40,6 +43,7 @@ pub async fn auth_has_user(user: uuid::Uuid) -> Result<bool> {
/// Get a copy of the list of all user credentials /// Get a copy of the list of all user credentials
// invoke('auth_users',user) // invoke('auth_users',user)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn auth_users() -> Result<Vec<Credentials>> { pub async fn auth_users() -> Result<Vec<Credentials>> {
Ok(auth::users().await?) Ok(auth::users().await?)
} }
@@ -48,6 +52,7 @@ pub async fn auth_users() -> Result<Vec<Credentials>> {
/// Prefer to use refresh instead, as it will refresh the credentials as well /// Prefer to use refresh instead, as it will refresh the credentials as well
// invoke('auth_users',user) // invoke('auth_users',user)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn auth_get_user(user: uuid::Uuid) -> Result<Credentials> { pub async fn auth_get_user(user: uuid::Uuid) -> Result<Credentials> {
Ok(auth::get_user(user).await?) Ok(auth::get_user(user).await?)
} }

View File

@@ -6,24 +6,28 @@ use theseus::prelude::*;
/// Get all JREs that exist on the system /// Get all JREs that exist on the system
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn jre_get_all_jre() -> Result<Vec<JavaVersion>> { pub async fn jre_get_all_jre() -> Result<Vec<JavaVersion>> {
Ok(jre::get_all_jre().await?) Ok(jre::get_all_jre().await?)
} }
// Finds the isntallation of Java 7, if it exists // Finds the isntallation of Java 7, if it exists
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn jre_find_jre_8_jres() -> Result<Vec<JavaVersion>> { pub async fn jre_find_jre_8_jres() -> Result<Vec<JavaVersion>> {
Ok(jre::find_java8_jres().await?) Ok(jre::find_java8_jres().await?)
} }
// finds the installation of Java 17, if it exists // finds the installation of Java 17, if it exists
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn jre_find_jre_17_jres() -> Result<Vec<JavaVersion>> { pub async fn jre_find_jre_17_jres() -> Result<Vec<JavaVersion>> {
Ok(jre::find_java17_jres().await?) Ok(jre::find_java17_jres().await?)
} }
// Finds the highest version of Java 18+, if it exists // Finds the highest version of Java 18+, if it exists
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn jre_find_jre_18plus_jres() -> Result<Vec<JavaVersion>> { pub async fn jre_find_jre_18plus_jres() -> Result<Vec<JavaVersion>> {
Ok(jre::find_java18plus_jres().await?) Ok(jre::find_java18plus_jres().await?)
} }
@@ -31,6 +35,7 @@ pub async fn jre_find_jre_18plus_jres() -> Result<Vec<JavaVersion>> {
// Autodetect Java globals, by searching the users computer. // Autodetect Java globals, by searching the users computer.
// Returns a *NEW* JavaGlobals that can be put into Settings // Returns a *NEW* JavaGlobals that can be put into Settings
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn jre_autodetect_java_globals() -> Result<JavaGlobals> { pub async fn jre_autodetect_java_globals() -> Result<JavaGlobals> {
Ok(jre::autodetect_java_globals().await?) Ok(jre::autodetect_java_globals().await?)
} }
@@ -38,6 +43,7 @@ pub async fn jre_autodetect_java_globals() -> Result<JavaGlobals> {
// Validates java globals, by checking if the paths exist // Validates java globals, by checking if the paths exist
// If false, recommend to direct them to reassign, or to re-guess // If false, recommend to direct them to reassign, or to re-guess
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn jre_validate_globals() -> Result<bool> { pub async fn jre_validate_globals() -> Result<bool> {
Ok(jre::validate_globals().await?) Ok(jre::validate_globals().await?)
} }
@@ -45,18 +51,21 @@ pub async fn jre_validate_globals() -> Result<bool> {
// Validates JRE at a given path // Validates JRE at a given path
// Returns None if the path is not a valid JRE // Returns None if the path is not a valid JRE
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn jre_get_jre(path: PathBuf) -> Result<Option<JavaVersion>> { pub async fn jre_get_jre(path: PathBuf) -> Result<Option<JavaVersion>> {
jre::check_jre(path).await.map_err(|e| e.into()) jre::check_jre(path).await.map_err(|e| e.into())
} }
// Auto installs java for the given java version // Auto installs java for the given java version
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn jre_auto_install_java(java_version: u32) -> Result<PathBuf> { pub async fn jre_auto_install_java(java_version: u32) -> Result<PathBuf> {
Ok(jre::auto_install_java(java_version).await?) Ok(jre::auto_install_java(java_version).await?)
} }
// Gets the maximum memory a system has available. // Gets the maximum memory a system has available.
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn jre_get_max_memory() -> Result<u64> { pub async fn jre_get_max_memory() -> Result<u64> {
Ok(jre::get_max_memory().await?) Ok(jre::get_max_memory().await?)
} }

View File

@@ -14,6 +14,7 @@ pub struct Logs {
/// Get all Logs for a profile, sorted by datetime /// Get all Logs for a profile, sorted by datetime
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn logs_get_logs( pub async fn logs_get_logs(
profile_uuid: Uuid, profile_uuid: Uuid,
clear_contents: Option<bool>, clear_contents: Option<bool>,
@@ -29,6 +30,7 @@ pub async fn logs_get_logs(
/// Get a Log struct for a profile by profile id and datetime string /// Get a Log struct for a profile by profile id and datetime string
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn logs_get_logs_by_datetime( pub async fn logs_get_logs_by_datetime(
profile_uuid: Uuid, profile_uuid: Uuid,
datetime_string: String, 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 /// Get the stdout for a profile by profile id and datetime string
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn logs_get_stdout_by_datetime( pub async fn logs_get_stdout_by_datetime(
profile_uuid: Uuid, profile_uuid: Uuid,
datetime_string: String, 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 /// Get the stderr for a profile by profile id and datetime string
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn logs_get_stderr_by_datetime( pub async fn logs_get_stderr_by_datetime(
profile_uuid: Uuid, profile_uuid: Uuid,
datetime_string: String, datetime_string: String,
@@ -56,12 +60,14 @@ pub async fn logs_get_stderr_by_datetime(
/// Delete all logs for a profile by profile id /// Delete all logs for a profile by profile id
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn logs_delete_logs(profile_uuid: Uuid) -> Result<()> { pub async fn logs_delete_logs(profile_uuid: Uuid) -> Result<()> {
Ok(logs::delete_logs(profile_uuid).await?) Ok(logs::delete_logs(profile_uuid).await?)
} }
/// Delete a log for a profile by profile id and datetime string /// Delete a log for a profile by profile id and datetime string
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn logs_delete_logs_by_datetime( pub async fn logs_delete_logs_by_datetime(
profile_uuid: Uuid, profile_uuid: Uuid,
datetime_string: String, datetime_string: String,

View File

@@ -4,24 +4,28 @@ use daedalus::modded::Manifest;
/// Gets the game versions from daedalus /// Gets the game versions from daedalus
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn metadata_get_game_versions() -> Result<VersionManifest> { pub async fn metadata_get_game_versions() -> Result<VersionManifest> {
Ok(theseus::metadata::get_minecraft_versions().await?) Ok(theseus::metadata::get_minecraft_versions().await?)
} }
/// Gets the fabric versions from daedalus /// Gets the fabric versions from daedalus
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn metadata_get_fabric_versions() -> Result<Manifest> { pub async fn metadata_get_fabric_versions() -> Result<Manifest> {
Ok(theseus::metadata::get_fabric_versions().await?) Ok(theseus::metadata::get_fabric_versions().await?)
} }
/// Gets the forge versions from daedalus /// Gets the forge versions from daedalus
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn metadata_get_forge_versions() -> Result<Manifest> { pub async fn metadata_get_forge_versions() -> Result<Manifest> {
Ok(theseus::metadata::get_forge_versions().await?) Ok(theseus::metadata::get_forge_versions().await?)
} }
/// Gets the quilt versions from daedalus /// Gets the quilt versions from daedalus
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn metadata_get_quilt_versions() -> Result<Manifest> { pub async fn metadata_get_quilt_versions() -> Result<Manifest> {
Ok(theseus::metadata::get_quilt_versions().await?) Ok(theseus::metadata::get_quilt_versions().await?)
} }

View File

@@ -47,6 +47,7 @@ where
// Create a new HashMap with the same keys // 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 // Values provided should not be used directly, as they are not guaranteed to be up-to-date
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn progress_bars_list( pub async fn progress_bars_list(
) -> Result<std::collections::HashMap<uuid::Uuid, theseus::LoadingBar>> { ) -> Result<std::collections::HashMap<uuid::Uuid, theseus::LoadingBar>> {
let res = theseus::EventState::list_progress_bars().await?; let res = theseus::EventState::list_progress_bars().await?;

View File

@@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
use theseus::prelude::*; use theseus::prelude::*;
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn pack_install_version_id( pub async fn pack_install_version_id(
version_id: String, version_id: String,
pack_title: String, pack_title: String,
@@ -15,6 +16,7 @@ pub async fn pack_install_version_id(
} }
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn pack_install_file(path: &Path) -> Result<PathBuf> { pub async fn pack_install_file(path: &Path) -> Result<PathBuf> {
let res = pack::install_pack_from_file(path.to_path_buf()).await?; let res = pack::install_pack_from_file(path.to_path_buf()).await?;
Ok(res) Ok(res)

View File

@@ -6,12 +6,14 @@ use uuid::Uuid;
// Checks if a process has finished by process UUID // Checks if a process has finished by process UUID
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn process_has_finished_by_uuid(uuid: Uuid) -> Result<bool> { pub async fn process_has_finished_by_uuid(uuid: Uuid) -> Result<bool> {
Ok(process::has_finished_by_uuid(&uuid).await?) Ok(process::has_finished_by_uuid(&uuid).await?)
} }
// Gets process exit status by process UUID // Gets process exit status by process UUID
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn process_get_exit_status_by_uuid( pub async fn process_get_exit_status_by_uuid(
uuid: Uuid, uuid: Uuid,
) -> Result<Option<i32>> { ) -> Result<Option<i32>> {
@@ -20,18 +22,21 @@ pub async fn process_get_exit_status_by_uuid(
// Gets all process UUIDs // Gets all process UUIDs
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn process_get_all_uuids() -> Result<Vec<Uuid>> { pub async fn process_get_all_uuids() -> Result<Vec<Uuid>> {
Ok(process::get_all_uuids().await?) Ok(process::get_all_uuids().await?)
} }
// Gets all running process UUIDs // Gets all running process UUIDs
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn process_get_all_running_uuids() -> Result<Vec<Uuid>> { pub async fn process_get_all_running_uuids() -> Result<Vec<Uuid>> {
Ok(process::get_all_running_uuids().await?) Ok(process::get_all_running_uuids().await?)
} }
// Gets all process UUIDs by profile path // Gets all process UUIDs by profile path
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn process_get_uuids_by_profile_path( pub async fn process_get_uuids_by_profile_path(
profile_path: &Path, profile_path: &Path,
) -> Result<Vec<Uuid>> { ) -> Result<Vec<Uuid>> {
@@ -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 // Gets the Profile paths of each *running* stored process in the state
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn process_get_all_running_profile_paths() -> Result<Vec<PathBuf>> { pub async fn process_get_all_running_profile_paths() -> Result<Vec<PathBuf>> {
Ok(process::get_all_running_profile_paths().await?) Ok(process::get_all_running_profile_paths().await?)
} }
// Gets the Profiles (cloned) of each *running* stored process in the state // Gets the Profiles (cloned) of each *running* stored process in the state
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn process_get_all_running_profiles() -> Result<Vec<Profile>> { pub async fn process_get_all_running_profiles() -> Result<Vec<Profile>> {
Ok(process::get_all_running_profiles().await?) Ok(process::get_all_running_profiles().await?)
} }
// Gets process stderr by process UUID // Gets process stderr by process UUID
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn process_get_stderr_by_uuid(uuid: Uuid) -> Result<String> { pub async fn process_get_stderr_by_uuid(uuid: Uuid) -> Result<String> {
Ok(process::get_stderr_by_uuid(&uuid).await?) Ok(process::get_stderr_by_uuid(&uuid).await?)
} }
// Gets process stdout by process UUID // Gets process stdout by process UUID
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn process_get_stdout_by_uuid(uuid: Uuid) -> Result<String> { pub async fn process_get_stdout_by_uuid(uuid: Uuid) -> Result<String> {
Ok(process::get_stdout_by_uuid(&uuid).await?) Ok(process::get_stdout_by_uuid(&uuid).await?)
} }
// Kill a process by process UUID // Kill a process by process UUID
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn process_kill_by_uuid(uuid: Uuid) -> Result<()> { pub async fn process_kill_by_uuid(uuid: Uuid) -> Result<()> {
Ok(process::kill_by_uuid(&uuid).await?) Ok(process::kill_by_uuid(&uuid).await?)
} }
// Wait for a process to finish by process UUID // Wait for a process to finish by process UUID
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn process_wait_for_by_uuid(uuid: Uuid) -> Result<()> { pub async fn process_wait_for_by_uuid(uuid: Uuid) -> Result<()> {
Ok(process::wait_for_by_uuid(&uuid).await?) Ok(process::wait_for_by_uuid(&uuid).await?)
} }

View File

@@ -8,6 +8,7 @@ use uuid::Uuid;
// Remove a profile // Remove a profile
// invoke('profile_add_path',path) // invoke('profile_add_path',path)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_remove(path: &Path) -> Result<()> { pub async fn profile_remove(path: &Path) -> Result<()> {
profile::remove(path).await?; profile::remove(path).await?;
Ok(()) Ok(())
@@ -16,6 +17,7 @@ pub async fn profile_remove(path: &Path) -> Result<()> {
// Get a profile by path // Get a profile by path
// invoke('profile_add_path',path) // invoke('profile_add_path',path)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_get( pub async fn profile_get(
path: &Path, path: &Path,
clear_projects: Option<bool>, clear_projects: Option<bool>,
@@ -27,6 +29,7 @@ pub async fn profile_get(
// Get a copy of the profile set // Get a copy of the profile set
// invoke('profile_list') // invoke('profile_list')
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_list( pub async fn profile_list(
clear_projects: Option<bool>, clear_projects: Option<bool>,
) -> Result<std::collections::HashMap<PathBuf, Profile>> { ) -> Result<std::collections::HashMap<PathBuf, Profile>> {
@@ -35,6 +38,7 @@ pub async fn profile_list(
} }
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_check_installed( pub async fn profile_check_installed(
path: &Path, path: &Path,
project_id: String, project_id: String,
@@ -57,6 +61,7 @@ pub async fn profile_check_installed(
/// Installs/Repairs a profile /// Installs/Repairs a profile
/// invoke('profile_install') /// invoke('profile_install')
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_install(path: &Path) -> Result<()> { pub async fn profile_install(path: &Path) -> Result<()> {
profile::install(path).await?; profile::install(path).await?;
Ok(()) Ok(())
@@ -65,6 +70,7 @@ pub async fn profile_install(path: &Path) -> Result<()> {
/// Updates all of the profile's projects /// Updates all of the profile's projects
/// invoke('profile_update_all') /// invoke('profile_update_all')
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_update_all(path: &Path) -> Result<()> { pub async fn profile_update_all(path: &Path) -> Result<()> {
profile::update_all(path).await?; profile::update_all(path).await?;
Ok(()) Ok(())
@@ -73,6 +79,7 @@ pub async fn profile_update_all(path: &Path) -> Result<()> {
/// Updates a specified project /// Updates a specified project
/// invoke('profile_update_project') /// invoke('profile_update_project')
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_update_project( pub async fn profile_update_project(
path: &Path, path: &Path,
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 // Adds a project to a profile from a version ID
// invoke('profile_add_project_from_version') // invoke('profile_add_project_from_version')
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_add_project_from_version( pub async fn profile_add_project_from_version(
path: &Path, path: &Path,
version_id: String, version_id: String,
@@ -95,6 +103,7 @@ pub async fn profile_add_project_from_version(
// Adds a project to a profile from a path // Adds a project to a profile from a path
// invoke('profile_add_project_from_path') // invoke('profile_add_project_from_path')
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_add_project_from_path( pub async fn profile_add_project_from_path(
path: &Path, path: &Path,
project_path: &Path, project_path: &Path,
@@ -108,6 +117,7 @@ pub async fn profile_add_project_from_path(
// Toggles disabling a project from its path // Toggles disabling a project from its path
// invoke('profile_toggle_disable_project') // invoke('profile_toggle_disable_project')
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_toggle_disable_project( pub async fn profile_toggle_disable_project(
path: &Path, path: &Path,
project_path: &Path, project_path: &Path,
@@ -119,6 +129,7 @@ pub async fn profile_toggle_disable_project(
// Removes a project from a profile // Removes a project from a profile
// invoke('profile_remove_project') // invoke('profile_remove_project')
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_remove_project( pub async fn profile_remove_project(
path: &Path, path: &Path,
project_path: &Path, project_path: &Path,
@@ -131,6 +142,7 @@ pub async fn profile_remove_project(
// for the actual Child in the state. // for the actual Child in the state.
// invoke('profile_run', path) // invoke('profile_run', path)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_run(path: &Path) -> Result<Uuid> { pub async fn profile_run(path: &Path) -> Result<Uuid> {
let minecraft_child = profile::run(path).await?; let minecraft_child = profile::run(path).await?;
let uuid = minecraft_child.read().await.uuid; let uuid = minecraft_child.read().await.uuid;
@@ -140,6 +152,7 @@ pub async fn profile_run(path: &Path) -> Result<Uuid> {
// Run Minecraft using a profile using the default credentials, and wait for the result // Run Minecraft using a profile using the default credentials, and wait for the result
// invoke('profile_run_wait', path) // invoke('profile_run_wait', path)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_run_wait(path: &Path) -> Result<()> { pub async fn profile_run_wait(path: &Path) -> Result<()> {
let proc_lock = profile::run(path).await?; let proc_lock = profile::run(path).await?;
let mut proc = proc_lock.write().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. // for the actual Child in the state.
// invoke('profile_run_credentials', {path, credentials})') // invoke('profile_run_credentials', {path, credentials})')
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_run_credentials( pub async fn profile_run_credentials(
path: &Path, path: &Path,
credentials: Credentials, 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 // Run Minecraft using a profile using the chosen credentials, and wait for the result
// invoke('profile_run_wait', {path, credentials) // invoke('profile_run_wait', {path, credentials)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_run_wait_credentials( pub async fn profile_run_wait_credentials(
path: &Path, path: &Path,
credentials: Credentials, credentials: Credentials,
@@ -192,6 +207,7 @@ pub struct EditProfileMetadata {
// Edits a profile // Edits a profile
// invoke('profile_edit', {path, editProfile}) // invoke('profile_edit', {path, editProfile})
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_edit( pub async fn profile_edit(
path: &Path, path: &Path,
edit_profile: EditProfile, edit_profile: EditProfile,

View File

@@ -5,6 +5,7 @@ use theseus::prelude::*;
// Generic basic profile creation tool. // Generic basic profile creation tool.
// Creates an essentially empty dummy profile with profile_create // Creates an essentially empty dummy profile with profile_create
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_create_empty() -> Result<PathBuf> { pub async fn profile_create_empty() -> Result<PathBuf> {
let res = profile_create::profile_create_empty().await?; let res = profile_create::profile_create_empty().await?;
Ok(res) Ok(res)
@@ -13,6 +14,7 @@ pub async fn profile_create_empty() -> Result<PathBuf> {
// Creates a profile at the given filepath and adds it to the in-memory state // Creates a profile at the given filepath and adds it to the in-memory state
// invoke('profile_add',profile) // invoke('profile_add',profile)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn profile_create( pub async fn profile_create(
name: String, // the name of the profile, and relative path name: String, // the name of the profile, and relative path
game_version: String, // the game version of the profile game_version: String, // the game version of the profile

View File

@@ -23,6 +23,7 @@ pub struct FrontendSettings {
// Get full settings // Get full settings
// invoke('settings_get') // invoke('settings_get')
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn settings_get() -> Result<FrontendSettings> { pub async fn settings_get() -> Result<FrontendSettings> {
let backend_settings = settings::get().await?; let backend_settings = settings::get().await?;
let frontend_settings = FrontendSettings { let frontend_settings = FrontendSettings {
@@ -50,6 +51,7 @@ pub async fn settings_get() -> Result<FrontendSettings> {
// Set full settings // Set full settings
// invoke('settings_set', settings) // invoke('settings_set', settings)
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn settings_set(settings: FrontendSettings) -> Result<()> { pub async fn settings_set(settings: FrontendSettings) -> Result<()> {
let custom_env_args: Vec<(String, String)> = settings let custom_env_args: Vec<(String, String)> = settings
.custom_env_args .custom_env_args

View File

@@ -3,36 +3,42 @@ use theseus::tags::{Category, DonationPlatform, GameVersion, Loader, Tags};
/// Gets cached category tags from the database /// Gets cached category tags from the database
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn tags_get_categories() -> Result<Vec<Category>> { pub async fn tags_get_categories() -> Result<Vec<Category>> {
Ok(theseus::tags::get_category_tags().await?) Ok(theseus::tags::get_category_tags().await?)
} }
/// Gets cached report type tags from the database /// Gets cached report type tags from the database
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn tags_get_report_types() -> Result<Vec<String>> { pub async fn tags_get_report_types() -> Result<Vec<String>> {
Ok(theseus::tags::get_report_type_tags().await?) Ok(theseus::tags::get_report_type_tags().await?)
} }
/// Gets cached loader tags from the database /// Gets cached loader tags from the database
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn tags_get_loaders() -> Result<Vec<Loader>> { pub async fn tags_get_loaders() -> Result<Vec<Loader>> {
Ok(theseus::tags::get_loader_tags().await?) Ok(theseus::tags::get_loader_tags().await?)
} }
/// Gets cached game version tags from the database /// Gets cached game version tags from the database
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn tags_get_game_versions() -> Result<Vec<GameVersion>> { pub async fn tags_get_game_versions() -> Result<Vec<GameVersion>> {
Ok(theseus::tags::get_game_version_tags().await?) Ok(theseus::tags::get_game_version_tags().await?)
} }
/// Gets cached donation platform tags from the database /// Gets cached donation platform tags from the database
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn tags_get_donation_platforms() -> Result<Vec<DonationPlatform>> { pub async fn tags_get_donation_platforms() -> Result<Vec<DonationPlatform>> {
Ok(theseus::tags::get_donation_platform_tags().await?) Ok(theseus::tags::get_donation_platform_tags().await?)
} }
/// Gets cached tag bundle from the database /// Gets cached tag bundle from the database
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
pub async fn tags_get_tag_bundle() -> Result<Tags> { pub async fn tags_get_tag_bundle() -> Result<Tags> {
Ok(theseus::tags::get_tag_bundle().await?) Ok(theseus::tags::get_tag_bundle().await?)
} }

View File

@@ -13,6 +13,7 @@ mod error;
// Should be called in launcher initialization // Should be called in launcher initialization
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> { async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
theseus::EventState::init(app).await?; theseus::EventState::init(app).await?;
State::get().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 // disables mouseover and fixes a random crash error only fixed by recent versions of macos
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
async fn should_disable_mouseover() -> bool { 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 // 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(); let os = os_info::get();
@@ -37,6 +39,7 @@ async fn should_disable_mouseover() -> bool {
} }
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
#[tauri::command] #[tauri::command]
#[theseus_macros::debug_pin]
async fn should_disable_mouseover() -> bool { async fn should_disable_mouseover() -> bool {
false false
} }

11
theseus_macros/Cargo.toml Normal file
View File

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

36
theseus_macros/src/lib.rs Normal file
View File

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