From 866507f3eb792c540bde0f03270ba0f2440fecf2 Mon Sep 17 00:00:00 2001 From: thesuzerain Date: Mon, 17 Jul 2023 14:50:40 -0700 Subject: [PATCH 01/10] temporary switch --- theseus/src/api/pack/import.rs | 175 +++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 theseus/src/api/pack/import.rs diff --git a/theseus/src/api/pack/import.rs b/theseus/src/api/pack/import.rs new file mode 100644 index 000000000..7f893b485 --- /dev/null +++ b/theseus/src/api/pack/import.rs @@ -0,0 +1,175 @@ +use std::{path::PathBuf, fs::read_to_string}; + +use serde::{Serialize, Deserialize, de}; +use tokio::{fs, io::BufReader}; + +use crate::{profile_create, pack::{install_from::{PackFormat, CreatePackDescription}, install}}; + +// instance.cfg +// https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/minecraft/MinecraftInstance.cpp +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +pub struct PrismInstance { + pub general : PrismInstanceGeneral, +} +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +pub struct PrismInstanceGeneral { + pub java_path : Option, + pub jvm_args : Option, + + #[serde(deserialize_with = "deserialize_bool")] + pub managed_pack : bool, + pub managed_pack_id : Option, + pub managed_pack_type : PrismManagedPackType, + pub managed_pack_version_id : Option, + pub managed_pack_version_name : Option, + + pub icon_key : Option, + pub name : Option, +} + +// serde_ini reads 'true' and 'false' as strings, so we need to convert them to booleans +fn deserialize_bool<'de, D>(deserializer: D) -> Result +where + D: de::Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + match s.as_str() { + "true" => Ok(true), + "false" => Ok(false), + _ => Err(de::Error::custom("expected 'true' or 'false'")), + } +} + + + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "lowercase")] +pub enum PrismManagedPackType { + Modrinth +} + + +// mmc-pack.json +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct PrismPack { + components : Vec, + format_version : u32, +} + +// https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/minecraft/Component.h +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct PrismComponent { + pub uid: String, + + #[serde(default)] + pub version : Option, + #[serde(default)] + pub dependency_only : bool, + + #[serde(default)] + pub important : bool, + #[serde(default)] + pub disabled : bool, + + #[serde(default)] + pub cached_name : String, + #[serde(default)] + pub cached_version : String, + + #[serde(default)] + pub cached_requires : Vec, + #[serde(default)] + pub cached_conflicts : Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct PrismComponentRequirement { + pub uid: String, + pub equals_version : Option, + pub suggests : Option, +} + +#[tracing::instrument] +#[theseus_macros::debug_pin] +pub async fn import_prism( + base_path: PathBuf, // path to prism instance + +) -> crate::Result<()> { + + // NOTE: Seems like mmc-packs are for meta-folder dependencies + // uid is the folder name, then version? + println!("import_prism({:?})", base_path); + let mmc_pack = fs::read_to_string(&base_path.join("mmc-pack.json")).await?; + let mmc_pack: PrismPack = serde_json::from_str::(&mmc_pack)?; + println!("mmc-pack.json: {:#?}", mmc_pack); + + let instance_cfg = fs::read_to_string(&base_path.join("instance.cfg")).await?; + let instance_cfg: PrismInstance = serde_ini::from_str::(&instance_cfg)?; + + // Managed pack + if instance_cfg.general.managed_pack { + match instance_cfg.general.managed_pack_type { + // MODRINTH MANAGED PACK + PrismManagedPackType::Modrinth => { + // Get overrides.txt in /mrpack + let file = fs::File::open(&base_path.join("mrpack").join("overrides.txt")).await?; + // add each line as a different String in a Vec + use tokio::io::AsyncBufReadExt; + let mut lines = BufReader::new(file).lines(); + let mut override_paths : Vec = Vec::new(); + while let Some(line) = lines.next_line().await? { + override_paths.push(base_path.join(line)); + } + + // Get mrpack.json in /mrpack + // let file = fs::File::open(&base_path.join("mrpack").join("mrpack.json")).await?; + let mrpack = serde_json::from_str::(&read_to_string(&base_path.join("mrpack").join("mrpack.json"))?)?; + + let description = CreatePackDescription { + // pub override_title: Option, + // pub project_id: Option, + // pub version_id: Option, + // pub existing_loading_bar: Option, + // pub profile: PathBuf, + icon: None, // TODO: cant just get icon directly, need to copy it to cache + override_title: instance_cfg.general.name, + project_id: instance_cfg.general.managed_pack_id, + version_id: instance_cfg.general.managed_pack_version_id, + existing_loading_bar: None, + profile: base_path.clone(), + }; + + install::import_pack(base_path, description, mrpack, override_paths).await?; + + } + // For flame, etc + _ => todo!("import non-modrinth managed pack: {:?}", instance_cfg.general.managed_pack_type) + } + } else { + let name = instance_cfg.general.name.unwrap_or("Imported Modpack".to_string()); + todo!("import non-managed pack: {}", name); + } + + panic!("hello"); + Ok(()) +} + + +#[cfg(test)] +mod tests { + use super::*; + + const PRISM_FOLDER : &'static str = r"/Users/wyattverchere/Library/Application Support/PrismLauncher/instances"; + + #[tokio::test] + async fn test_import_prism() { + let path = PathBuf::from(PRISM_FOLDER).join(r"Cobblemon [Fabric]"); + let result = import_prism(path).await.unwrap(); + + } +} \ No newline at end of file From 4f1f1ae83a185aee0b33c1c10738357c7caca8b4 Mon Sep 17 00:00:00 2001 From: Wyatt Date: Tue, 18 Jul 2023 17:26:19 -0700 Subject: [PATCH 02/10] draft; working unmanaged + modrinth --- Cargo.lock | 24 ++ theseus/Cargo.toml | 1 + theseus/src/api/pack/import.rs | 271 +++++++++++++++--- theseus/src/api/pack/install_from.rs | 133 +++++++-- .../pack/{install.rs => install_mrpack.rs} | 217 +++++++++----- theseus/src/api/pack/mod.rs | 3 +- theseus/src/api/profile_create.rs | 5 + theseus/src/error.rs | 3 + theseus/src/event/mod.rs | 2 +- theseus_gui/src-tauri/src/api/pack.rs | 5 +- 10 files changed, 534 insertions(+), 130 deletions(-) rename theseus/src/api/pack/{install.rs => install_mrpack.rs} (63%) diff --git a/Cargo.lock b/Cargo.lock index 0542cc180..de73d68ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3557,6 +3557,12 @@ dependencies = [ "winreg 0.10.1", ] +[[package]] +name = "result" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560" + [[package]] name = "rfd" version = "0.10.0" @@ -3862,6 +3868,17 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "serde_ini" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb236687e2bb073a7521c021949be944641e671b8505a94069ca37b656c81139" +dependencies = [ + "result", + "serde", + "void", +] + [[package]] name = "serde_json" version = "1.0.95" @@ -4631,6 +4648,7 @@ dependencies = [ "regex", "reqwest", "serde", + "serde_ini", "serde_json", "sha1 0.6.1", "sha2 0.9.9", @@ -5253,6 +5271,12 @@ version = "0.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b60dcd6a64dd45abf9bd426970c9843726da7fc08f44cd6fcebf68c21220a63" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "vswhom" version = "0.1.0" diff --git a/theseus/Cargo.toml b/theseus/Cargo.toml index c24cafca0..13444c8a8 100644 --- a/theseus/Cargo.toml +++ b/theseus/Cargo.toml @@ -12,6 +12,7 @@ theseus_macros = { path = "../theseus_macros" } bytes = "1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +serde_ini = "0.2.0" toml = "0.7.3" sha1 = { version = "0.6.1", features = ["std"]} sha2 = "0.9.9" diff --git a/theseus/src/api/pack/import.rs b/theseus/src/api/pack/import.rs index 7f893b485..0e8a6b53e 100644 --- a/theseus/src/api/pack/import.rs +++ b/theseus/src/api/pack/import.rs @@ -1,9 +1,11 @@ -use std::{path::PathBuf, fs::read_to_string}; +use std::{path::{PathBuf, Path}, fs::read_to_string}; use serde::{Serialize, Deserialize, de}; use tokio::{fs, io::BufReader}; -use crate::{profile_create, pack::{install_from::{PackFormat, CreatePackDescription}, install}}; +use crate::{profile_create, pack::{install_from::{PackFormat, CreatePackDescription}, install_mrpack}, event::LoadingBarId, util::fetch, State}; + +use super::install_from::{PackDependency, self}; // instance.cfg // https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/minecraft/MinecraftInstance.cpp @@ -20,12 +22,15 @@ pub struct PrismInstanceGeneral { #[serde(deserialize_with = "deserialize_bool")] pub managed_pack : bool, + pub managed_pack_id : Option, - pub managed_pack_type : PrismManagedPackType, + pub managed_pack_type : Option, pub managed_pack_version_id : Option, pub managed_pack_version_name : Option, + #[serde(rename = "iconKey")] pub icon_key : Option, + #[serde(rename = "name")] pub name : Option, } @@ -47,7 +52,9 @@ where #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "lowercase")] pub enum PrismManagedPackType { - Modrinth + Modrinth, + #[serde(rename = "")] + Unmanaged } @@ -97,79 +104,269 @@ pub struct PrismComponentRequirement { #[tracing::instrument] #[theseus_macros::debug_pin] pub async fn import_prism( - base_path: PathBuf, // path to prism instance + prism_base_path: PathBuf, // path to base prism folder + instance_folder: String, // instance folder in prism_base_path + profile_path : PathBuf, // path to profile + existing_loading_bar: Option, ) -> crate::Result<()> { + let state = crate::State::get().await?; + let prism_instance_path = prism_base_path.join("instances").join(instance_folder.clone()); + // NOTE: Seems like mmc-packs are for meta-folder dependencies // uid is the folder name, then version? - println!("import_prism({:?})", base_path); - let mmc_pack = fs::read_to_string(&base_path.join("mmc-pack.json")).await?; + println!("import_prism({:?})", prism_instance_path); + let mmc_pack = fs::read_to_string(&prism_instance_path.join("mmc-pack.json")).await?; let mmc_pack: PrismPack = serde_json::from_str::(&mmc_pack)?; println!("mmc-pack.json: {:#?}", mmc_pack); - let instance_cfg = fs::read_to_string(&base_path.join("instance.cfg")).await?; + let instance_cfg = fs::read_to_string(&prism_instance_path.join("instance.cfg")).await?; let instance_cfg: PrismInstance = serde_ini::from_str::(&instance_cfg)?; + println!("instance.cfg: {:#?}", &instance_cfg); + + // Re-cache icon + let icon = if let Some(icon_key) = instance_cfg.general.icon_key { + let icon_path = prism_base_path.join("icons").join(icon_key); + dbg!(&icon_path); + let bytes = tokio::fs::read(&icon_path).await; + if let Ok(bytes ) = bytes { + let bytes = bytes::Bytes::from(bytes); + let cache_dir = &state.directories.caches_dir(); + dbg!(&cache_dir); + let semaphore = &state.io_semaphore; + Some(fetch::write_cached_icon(&icon_path.to_string_lossy(), cache_dir, bytes, semaphore).await?) + } else { + // could not find icon (for instance, prism default icon, etc) + None + } + } else { + None + }; + + // Create description from instance.cfg + let description = CreatePackDescription { + icon, + override_title: instance_cfg.general.name, + project_id: instance_cfg.general.managed_pack_id, + version_id: instance_cfg.general.managed_pack_version_id, + existing_loading_bar: existing_loading_bar.clone(), + profile: profile_path.clone(), + }; + // Managed pack if instance_cfg.general.managed_pack { match instance_cfg.general.managed_pack_type { // MODRINTH MANAGED PACK - PrismManagedPackType::Modrinth => { + Some(PrismManagedPackType::Modrinth) => { // Get overrides.txt in /mrpack - let file = fs::File::open(&base_path.join("mrpack").join("overrides.txt")).await?; + println!("importing modrinth managed pack"); + let file = fs::File::open(&prism_instance_path.join("mrpack").join("overrides.txt")).await?; // add each line as a different String in a Vec use tokio::io::AsyncBufReadExt; let mut lines = BufReader::new(file).lines(); + println!("lines: {:?}", lines); let mut override_paths : Vec = Vec::new(); while let Some(line) = lines.next_line().await? { - override_paths.push(base_path.join(line)); + override_paths.push(line.into()); } // Get mrpack.json in /mrpack - // let file = fs::File::open(&base_path.join("mrpack").join("mrpack.json")).await?; - let mrpack = serde_json::from_str::(&read_to_string(&base_path.join("mrpack").join("mrpack.json"))?)?; - - let description = CreatePackDescription { - // pub override_title: Option, - // pub project_id: Option, - // pub version_id: Option, - // pub existing_loading_bar: Option, - // pub profile: PathBuf, - icon: None, // TODO: cant just get icon directly, need to copy it to cache - override_title: instance_cfg.general.name, - project_id: instance_cfg.general.managed_pack_id, - version_id: instance_cfg.general.managed_pack_version_id, - existing_loading_bar: None, - profile: base_path.clone(), - }; - - install::import_pack(base_path, description, mrpack, override_paths).await?; + let mrpack = serde_json::from_str::(&read_to_string(&prism_instance_path.join("mrpack").join("modrinth.index.json"))?)?; + println!("mrpack"); + + + install_mrpack::install_importable_mrpack(profile_path, description, mrpack, prism_instance_path.join(".minecraft"), override_paths).await?; } // For flame, etc - _ => todo!("import non-modrinth managed pack: {:?}", instance_cfg.general.managed_pack_type) + Some(x) => todo!("import non-modrinth managed pack: {:?}", x), + _ => return Err(crate::ErrorKind::InputError({ + format!("Managed pack type not specified in instance.cfg") + }).into()) } } else { - let name = instance_cfg.general.name.unwrap_or("Imported Modpack".to_string()); - todo!("import non-managed pack: {}", name); + let backup_name = "Imported Modpack".to_string(); + import_prism_unmanaged(profile_path, prism_base_path, instance_folder, backup_name, description, mmc_pack, existing_loading_bar).await?; + + } + Ok(()) +} + +async fn import_prism_unmanaged(profile_path: PathBuf, prism_base_path: PathBuf, instance_folder: String, backup_name : String, description: CreatePackDescription, mmc_pack: PrismPack, existing_loading_bar : Option)-> crate::Result<()> { + let state = crate::State::get().await?; + + + println!("loading dependencies {:?}", mmc_pack.components); + + // Pack dependencies stored in mmc-pack.json, we convert to .mrpack pack dependencies + let dependencies = mmc_pack.components.iter().filter_map(|component| { + if component.uid.starts_with("net.fabricmc.fabric-loader") { + return Some((PackDependency::FabricLoader, component.cached_version.clone())); + } + println!("Examining: {}", component.uid); + if component.uid.starts_with("net.minecraftforge") { + println!("Found forge: {}", component.uid); + return Some((PackDependency::Forge, component.cached_version.clone())); + } + if component.uid.starts_with("org.quiltmc.quilt-loader") { + return Some((PackDependency::QuiltLoader, component.cached_version.clone())); + } + if component.uid.starts_with("net.minecraft") { + return Some((PackDependency::Minecraft, component.cached_version.clone())); + } + + None + }).collect(); + + println!("dependencies: {:?}", dependencies); + println!("description: {:?}", description); + // Sets profile information to be that loaded from mmc-pack.json and instance.cfg + install_from::set_profile_information(profile_path.clone(), &description, &backup_name, &dependencies).await?; + + println!("mmc_pack.components: {:?}", mmc_pack.components); + // Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc) + import_dotminecraft(profile_path.clone(), prism_base_path.join("instances").join(instance_folder).join(".minecraft")).await?; + + if let Some(profile_val) = + crate::api::profile::get(&profile_path, None).await? + { + crate::launcher::install_minecraft( + &profile_val, + existing_loading_bar, + ) + .await?; + + State::sync().await?; + } +Ok(()) + + +} + + +async fn import_dotminecraft(profile_path : PathBuf, dotminecraft: PathBuf) -> crate::Result<()> { + + println!("here import_dotminecraft {:?} {:?}", profile_path, dotminecraft); + // std fs copy every file in dotminecraft to profile_path + for entry in std::fs::read_dir(dotminecraft)? { + let entry = entry?; + let path = entry.path(); + copy_dir_to(&path, &profile_path.join(path.file_name().ok_or_else(|| crate::ErrorKind::InputError(format!("Invalid file: {}", &path.display())))?)).await?; } - panic!("hello"); + Ok(()) +} + +// recursively fs::copy every file in src to dest +// uses async recursion +#[theseus_macros::debug_pin] +#[async_recursion::async_recursion] +#[tracing::instrument] +async fn copy_dir_to(src: &Path, dst: &Path) -> crate::Result<()> { + if !src.is_dir() { + fs::copy(src, dst).await?; + return Ok(()); + } + + // Create the destination directory + fs::create_dir_all(&dst).await?; + + // Iterate over the directory + let mut dir = fs::read_dir(src).await?; + while let Some(child) = dir.next_entry().await? { + let src_child = child.path(); + let dst_child = dst.join(src_child.file_name().ok_or_else(|| crate::ErrorKind::InputError(format!("Invalid file: {}", &src_child.display())))?); + + if child.metadata().await?.is_dir() { + // Recurse into sub-directory + copy_dir_to(&src_child, &dst_child).await?; + } else { + // Copy file + fs::copy(&src_child, &dst_child).await?; + } + } + Ok(()) } #[cfg(test)] mod tests { + use crate::pack::install_from::CreatePackProfile; + use super::*; - const PRISM_FOLDER : &'static str = r"/Users/wyattverchere/Library/Application Support/PrismLauncher/instances"; + const PRISM_FOLDER : &'static str = r"/home/thesuzerain/.local/share/PrismLauncher"; + + // #[tokio::test] + // async fn test_import_prism_cobblemon() { + + // setup_tests().await.unwrap(); + + // // Import cobblemon mrpack + // let profile_path = profile_create::profile_create_from_creator(CreatePackProfile::default()).await.unwrap(); + // let result = import_prism(PRISM_FOLDER.into(), r"Cobblemon [Fabric]".to_string(), profile_path.clone(), None).await.unwrap(); + // crate::profile::remove(&profile_path).await.unwrap(); + + // println!("Cobblemon result: {:?}", result); + + // panic!("hello"); + // } #[tokio::test] - async fn test_import_prism() { - let path = PathBuf::from(PRISM_FOLDER).join(r"Cobblemon [Fabric]"); - let result = import_prism(path).await.unwrap(); + async fn test_import_prism_tempor() { + + setup_tests().await.unwrap(); + + // Tempor test + let profile_path = profile_create::profile_create_from_creator(CreatePackProfile::default()).await.unwrap(); + let result = import_prism(PRISM_FOLDER.into(), r"tempor".to_string(), profile_path.clone(), None).await.unwrap(); + // crate::profile::remove(&profile_path).await.unwrap(); + + println!("tempor result: {:?}", result); + + panic!("hello"); + } + + // #[tokio::test] + // async fn test_import_prism_pixelmon() { + + // setup_tests().await.unwrap(); + + // // Tempor test + // let profile_path = profile_create::profile_create_from_creator(CreatePackProfile::default()).await.unwrap(); + // let result = import_prism(PRISM_FOLDER.into(), r"The Pixelmon Modpack".to_string(), profile_path.clone(), None).await.unwrap(); + // crate::profile::remove(&profile_path).await.unwrap(); + + // println!("The Pixelmon Modpack result: {:?}", result); + + // panic!("hello"); + // } + + + + async fn setup_tests() -> crate::Result<()> { + + // Initialize state + let state = crate::State::get().await?; + + // Set java globals to auto detected ones + { + let jres = crate::jre::get_all_jre().await?; + let java_8 = + crate::jre::find_filtered_jres("1.8", jres.clone(), false).await?; + let java_17 = + crate::jre::find_filtered_jres("1.17", jres.clone(), false).await?; + let java_18plus = + crate::jre::find_filtered_jres("1.18", jres.clone(), true).await?; + let java_globals = + crate::jre::autodetect_java_globals(java_8, java_17, java_18plus) + .await?; + state.settings.write().await.java_globals = java_globals; + } + Ok(()) } } \ No newline at end of file diff --git a/theseus/src/api/pack/install_from.rs b/theseus/src/api/pack/install_from.rs index 419bbf1f6..38a5fd597 100644 --- a/theseus/src/api/pack/install_from.rs +++ b/theseus/src/api/pack/install_from.rs @@ -2,7 +2,7 @@ use crate::config::MODRINTH_API_URL; use crate::data::ModLoader; use crate::event::emit::{emit_loading, init_loading}; use crate::event::{LoadingBarId, LoadingBarType}; -use crate::state::{LinkedData, ModrinthProject, ModrinthVersion, SideType}; +use crate::state::{LinkedData, ModrinthProject, ModrinthVersion, SideType, ProfileInstallStage}; use crate::util::fetch::{ fetch, fetch_advanced, fetch_json, write_cached_icon, }; @@ -62,7 +62,7 @@ pub enum EnvType { Server, } -#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Debug)] #[serde(rename_all = "kebab-case")] pub enum PackDependency { Forge, @@ -74,12 +74,14 @@ pub enum PackDependency { #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase", tag = "type")] pub enum CreatePackLocation { + // Create a pack from a modrinth version ID (such as a modpack) FromVersionId { project_id: String, version_id: String, title: String, icon_url: Option, }, + // Create a pack from a file (such as an .mrpack for installing from a file, or a folder name for importing) FromFile { path: PathBuf, }, @@ -98,8 +100,29 @@ pub struct CreatePackProfile { pub skip_install_profile: Option, } -pub struct CreatePackDescription { +// default +impl Default for CreatePackProfile { + fn default() -> Self { + CreatePackProfile { + name: "Untitled".to_string(), + game_version: "1.19.4".to_string(), + modloader: ModLoader::Vanilla, + loader_version: None, + icon: None, + icon_url: None, + linked_data: None, + skip_install_profile: Some(true), + } + } +} + +pub struct CreatePack { pub file: bytes::Bytes, + pub description: CreatePackDescription, +} + +#[derive(Clone, Debug)] +pub struct CreatePackDescription { pub icon: Option, pub override_title: Option, pub project_id: Option, @@ -119,16 +142,12 @@ pub fn get_profile_from_pack( icon_url, } => CreatePackProfile { name: title, - game_version: "1.19.4".to_string(), - modloader: ModLoader::Vanilla, - loader_version: None, - icon: None, icon_url, linked_data: Some(LinkedData { project_id: Some(project_id), version_id: Some(version_id), }), - skip_install_profile: Some(true), + ..Default::default() }, CreatePackLocation::FromFile { path } => { let file_name = path @@ -139,15 +158,9 @@ pub fn get_profile_from_pack( CreatePackProfile { name: file_name, - game_version: "1.19.4".to_string(), - modloader: ModLoader::Vanilla, - loader_version: None, - icon: None, - icon_url: None, - linked_data: None, - skip_install_profile: Some(true), + ..Default::default() } - } + }, } } @@ -159,7 +172,7 @@ pub async fn generate_pack_from_version_id( title: String, icon_url: Option, profile: PathBuf, -) -> crate::Result { +) -> crate::Result { let state = State::get().await?; let loading_bar = init_loading( @@ -246,15 +259,14 @@ pub async fn generate_pack_from_version_id( }; emit_loading(&loading_bar, 10.0, None).await?; - Ok(CreatePackDescription { - file, + Ok(CreatePack {file, description: CreatePackDescription { icon, override_title: None, project_id: Some(project_id), version_id: Some(version_id), existing_loading_bar: Some(loading_bar), profile, - }) + }}) } #[tracing::instrument] @@ -262,15 +274,88 @@ pub async fn generate_pack_from_version_id( pub async fn generate_pack_from_file( path: PathBuf, profile: PathBuf, -) -> crate::Result { +) -> crate::Result { let file = fs::read(&path).await?; - Ok(CreatePackDescription { - file: bytes::Bytes::from(file), + Ok(CreatePack { file: bytes::Bytes::from(file), description: CreatePackDescription { icon: None, override_title: None, project_id: None, version_id: None, existing_loading_bar: None, profile, - }) + }}) } + +/// Sets generated profile attributes to the pack ones (using profile::edit) +/// This includes the pack name, icon, game version, loader version, and loader +#[theseus_macros::debug_pin] +pub async fn set_profile_information(profile_path: PathBuf, description: &CreatePackDescription, backup_name: &str, dependencies: &HashMap) -> crate::Result<()> { + let mut game_version: Option<&String> = None; + let mut mod_loader = None; + let mut loader_version = None; + println!("here1"); + + + for (key, value) in dependencies { + match key { + PackDependency::Forge => { + mod_loader = Some(ModLoader::Forge); + loader_version = Some(value); + } + PackDependency::FabricLoader => { + mod_loader = Some(ModLoader::Fabric); + loader_version = Some(value); + } + PackDependency::QuiltLoader => { + mod_loader = Some(ModLoader::Quilt); + loader_version = Some(value); + } + PackDependency::Minecraft => game_version = Some(value), + } + } + + + let game_version = if let Some(game_version) = game_version { + game_version + } else { + return Err(crate::ErrorKind::InputError( + "Pack did not specify Minecraft version".to_string(), + ) + .into()); + }; + + println!("here4 - {:?}", mod_loader); + + let mod_loader = mod_loader.unwrap_or(ModLoader::Vanilla); + let loader_version = if mod_loader != ModLoader::Vanilla { + crate::profile_create::get_loader_version_from_loader( + game_version.clone(), + mod_loader, + loader_version.cloned(), + ) + .await? + } else { + None + }; + println!("her5 - {:?}", game_version); + println!("her5 - {:?}", loader_version); + println!("her5 - {:?}", description.override_title); + // Sets values in profile + crate::api::profile::edit(&profile_path, |prof| { + prof.metadata.name = + description.override_title.clone().unwrap_or_else(|| backup_name.to_string()); + prof.install_stage = ProfileInstallStage::PackInstalling; + prof.metadata.linked_data = Some(LinkedData { + project_id: description.project_id.clone(), + version_id: description.version_id.clone(), + }); + prof.metadata.icon = description.icon.clone(); + prof.metadata.game_version = game_version.clone(); + prof.metadata.loader_version = loader_version.clone(); + prof.metadata.loader = mod_loader; + + async { Ok(()) } + }) + .await?; + Ok(()) + } diff --git a/theseus/src/api/pack/install.rs b/theseus/src/api/pack/install_mrpack.rs similarity index 63% rename from theseus/src/api/pack/install.rs rename to theseus/src/api/pack/install_mrpack.rs index 097e2ea9c..b419abc68 100644 --- a/theseus/src/api/pack/install.rs +++ b/theseus/src/api/pack/install_mrpack.rs @@ -3,27 +3,30 @@ use crate::event::emit::{ emit_loading, init_or_edit_loading, loading_try_for_each_concurrent, }; use crate::event::LoadingBarType; -use crate::pack::install_from::{EnvType, PackFile, PackFileHash}; +use crate::pack::install_from::{EnvType, PackFile, PackFileHash, set_profile_information}; use crate::state::{LinkedData, ProfileInstallStage, SideType}; use crate::util::fetch::{fetch_mirrors, write}; use crate::State; use async_zip::tokio::read::seek::ZipFileReader; +use tokio::fs; +use std::collections::HashMap; use std::io::Cursor; use std::path::{Component, PathBuf}; use super::install_from::{ generate_pack_from_file, generate_pack_from_version_id, - CreatePackDescription, CreatePackLocation, PackDependency, PackFormat, + CreatePackDescription, CreatePackLocation, PackDependency, PackFormat, CreatePack, }; +/// Install a modpack from a mrpack file (a modrinth .zip format) #[theseus_macros::debug_pin] -pub async fn install_pack( +pub async fn install_zipped_mrpack( location: CreatePackLocation, profile: PathBuf, ) -> crate::Result { // Get file from description - let description: CreatePackDescription = match location { + let create_pack: CreatePack = match location { CreatePackLocation::FromVersionId { project_id, version_id, @@ -40,13 +43,13 @@ pub async fn install_pack( } }; - let file = description.file; - let icon = description.icon; - let override_title = description.override_title; - let project_id = description.project_id; - let version_id = description.version_id; - let existing_loading_bar = description.existing_loading_bar; - let profile = description.profile; + let file = create_pack.file; + let description = create_pack.description.clone(); // make a copy for profile edit function + let icon = create_pack.description.icon; + let project_id = create_pack.description.project_id; + let version_id = create_pack.description.version_id; + let existing_loading_bar = create_pack.description.existing_loading_bar; + let profile = create_pack.description.profile; let state = &State::get().await?; @@ -88,59 +91,8 @@ pub async fn install_pack( .into()); } - let mut game_version = None; - let mut mod_loader = None; - let mut loader_version = None; - for (key, value) in &pack.dependencies { - match key { - PackDependency::Forge => { - mod_loader = Some(ModLoader::Forge); - loader_version = Some(value); - } - PackDependency::FabricLoader => { - mod_loader = Some(ModLoader::Fabric); - loader_version = Some(value); - } - PackDependency::QuiltLoader => { - mod_loader = Some(ModLoader::Quilt); - loader_version = Some(value); - } - PackDependency::Minecraft => game_version = Some(value), - } - } - - let game_version = if let Some(game_version) = game_version { - game_version - } else { - return Err(crate::ErrorKind::InputError( - "Pack did not specify Minecraft version".to_string(), - ) - .into()); - }; - - let loader_version = - crate::profile_create::get_loader_version_from_loader( - game_version.clone(), - mod_loader.unwrap_or(ModLoader::Vanilla), - loader_version.cloned(), - ) - .await?; - crate::api::profile::edit(&profile, |prof| { - prof.metadata.name = - override_title.clone().unwrap_or_else(|| pack.name.clone()); - prof.install_stage = ProfileInstallStage::PackInstalling; - prof.metadata.linked_data = Some(LinkedData { - project_id: project_id.clone(), - version_id: version_id.clone(), - }); - prof.metadata.icon = icon.clone(); - prof.metadata.game_version = game_version.clone(); - prof.metadata.loader_version = loader_version.clone(); - prof.metadata.loader = mod_loader.unwrap_or(ModLoader::Vanilla); - - async { Ok(()) } - }) - .await?; + // Sets generated profile attributes to the pack ones (using profile::edit) + set_profile_information(profile.clone(), &description, &pack.name, &pack.dependencies).await?; let profile = profile.clone(); let result = async { @@ -325,3 +277,140 @@ pub async fn install_pack( } } } + + +/// Install a modpack from existing files on the system in .mrpack format +/// (This would be used to re-install an .mrpack from a different launcher) +pub async fn install_importable_mrpack(profile_path: PathBuf, description: CreatePackDescription, pack : PackFormat, overrides_base_path: PathBuf, override_paths : Vec ) -> crate::Result { + let state = &State::get().await?; + + println!("Hello123!"); + if &*pack.game != "minecraft" { + return Err(crate::ErrorKind::InputError( + "Pack does not support Minecraft".to_string(), + ) + .into()); + } + println!("Hello1233!"); + + // Sets generated profile attributes to the pack ones (using profile::edit) + set_profile_information(profile_path.clone(), &description, &pack.name, &pack.dependencies).await?; + + let base_path = profile_path.clone(); + let result = async { + let loading_bar = init_or_edit_loading( + description.existing_loading_bar, + LoadingBarType::PackDownload { + profile_path: base_path.clone(), + pack_name: pack.name.clone(), + icon: description.icon, + pack_id: description.project_id, + pack_version: description.version_id, + }, + 100.0, + "Downloading modpack", + ) + .await?; + println!("Hello1237!"); + + let num_files = pack.files.len(); + use futures::StreamExt; + loading_try_for_each_concurrent( + futures::stream::iter(pack.files.into_iter()) + .map(Ok::), + None, + Some(&loading_bar), + 70.0, + num_files, + None, + |project| { + let base_path = base_path.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(()); + } + } + println!("Hello128!"); + + let file = fetch_mirrors( + &project + .downloads + .iter() + .map(|x| &**x) + .collect::>(), + project + .hashes + .get(&PackFileHash::Sha1) + .map(|x| &**x), + &state.fetch_semaphore, + ) + .await?; + + let path = std::path::Path::new(&project.path) + .components() + .next(); + if let Some(path) = path { + match path { + Component::CurDir + | Component::Normal(_) => { + let path = base_path.join(project.path); + write( + &path, + &file, + &state.io_semaphore, + ) + .await?; + } + _ => {} + }; + } + Ok(()) + } + }, + ) + .await?; + println!("Hello129!"); + + emit_loading(&loading_bar, 0.0, Some("Extracting overrides")) + .await?; + + // Copy in overrides + for override_path in override_paths { + // ensures any parent directories exist + fs::create_dir_all(&base_path.join(&override_path).parent().ok_or_else(|| crate::ErrorKind::InputError(format!("Override file destionation is invalid: {}", &override_path.display())))?).await?; + fs::copy(&overrides_base_path.join(&override_path), &base_path.join(&override_path)).await?; + } + + if let Some(profile_val) = + crate::api::profile::get(&base_path, None).await? + { + crate::launcher::install_minecraft( + &profile_val, + Some(loading_bar), + ) + .await?; + + State::sync().await?; + } + + Ok::(base_path.clone()) + } + .await; +println!("Hello12--10!"); + + match result { + Ok(profile) => Ok(base_path), + Err(err) => { + let _ = crate::api::profile::remove(&base_path).await; + + Err(err) + } + } +} + diff --git a/theseus/src/api/pack/mod.rs b/theseus/src/api/pack/mod.rs index 9a7c1d341..e4fc146b6 100644 --- a/theseus/src/api/pack/mod.rs +++ b/theseus/src/api/pack/mod.rs @@ -1,2 +1,3 @@ -pub mod install; +pub mod import; +pub mod install_mrpack; pub mod install_from; diff --git a/theseus/src/api/profile_create.rs b/theseus/src/api/profile_create.rs index 88a802b61..1b0434d2d 100644 --- a/theseus/src/api/profile_create.rs +++ b/theseus/src/api/profile_create.rs @@ -1,4 +1,5 @@ //! Theseus profile management interface +use crate::pack::install_from::CreatePackProfile; use crate::state::LinkedData; use crate::{ event::{emit::emit_profile, ProfilePayloadType}, @@ -131,6 +132,10 @@ pub async fn profile_create( } } +pub async fn profile_create_from_creator(profile : CreatePackProfile) -> crate::Result { + profile_create(profile.name, profile.game_version, profile.modloader, profile.loader_version, profile.icon, profile.icon_url, profile.linked_data, profile.skip_install_profile).await +} + #[tracing::instrument] #[theseus_macros::debug_pin] pub(crate) async fn get_loader_version_from_loader( diff --git a/theseus/src/error.rs b/theseus/src/error.rs index 16056524a..84fbaa987 100644 --- a/theseus/src/error.rs +++ b/theseus/src/error.rs @@ -7,6 +7,9 @@ pub enum ErrorKind { #[error("Filesystem error: {0}")] FSError(String), + #[error("Serialization error (INI): {0}")] + INIError(#[from] serde_ini::de::Error), + #[error("Serialization error (JSON): {0}")] JSONError(#[from] serde_json::Error), diff --git a/theseus/src/event/mod.rs b/theseus/src/event/mod.rs index 27c3a0fd2..3569c0f60 100644 --- a/theseus/src/event/mod.rs +++ b/theseus/src/event/mod.rs @@ -93,7 +93,7 @@ pub struct LoadingBar { pub cli_progress_bar: indicatif::ProgressBar, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug ,Clone)] pub struct LoadingBarId(Uuid); // When Loading bar id is dropped, we should remove it from the hashmap diff --git a/theseus_gui/src-tauri/src/api/pack.rs b/theseus_gui/src-tauri/src/api/pack.rs index 1d13d8a18..c818aab5a 100644 --- a/theseus_gui/src-tauri/src/api/pack.rs +++ b/theseus_gui/src-tauri/src/api/pack.rs @@ -2,8 +2,7 @@ use crate::api::Result; use std::path::PathBuf; use theseus::{ pack::{ - install::install_pack, - install_from::{CreatePackLocation, CreatePackProfile}, + install_from::{CreatePackLocation, CreatePackProfile}, install_mrpack::install_zipped_mrpack, }, prelude::*, }; @@ -22,7 +21,7 @@ pub async fn pack_install( location: CreatePackLocation, profile: PathBuf, ) -> Result { - Ok(install_pack(location, profile).await?) + Ok(install_zipped_mrpack(location, profile).await?) } #[tauri::command] From bbb41b6bdd601950605960d44e55cbb991a1f760 Mon Sep 17 00:00:00 2001 From: Wyatt Date: Thu, 20 Jul 2023 02:16:04 -0700 Subject: [PATCH 03/10] working update --- .vscode/settings.json | 1 + theseus/src/api/pack/import.rs | 372 ---------------------- theseus/src/api/pack/import/atlauncher.rs | 235 ++++++++++++++ theseus/src/api/pack/import/curseforge.rs | 143 +++++++++ theseus/src/api/pack/import/gdlauncher.rs | 95 ++++++ theseus/src/api/pack/import/mmc.rs | 292 +++++++++++++++++ theseus/src/api/pack/import/mod.rs | 231 ++++++++++++++ theseus/src/api/pack/install_from.rs | 65 ++-- theseus/src/api/pack/install_mrpack.rs | 158 +-------- theseus/src/api/pack/mod.rs | 2 +- theseus/src/api/profile_create.rs | 16 +- theseus/src/error.rs | 2 +- theseus/src/event/mod.rs | 2 +- theseus_gui/src-tauri/src/api/pack.rs | 3 +- theseus_playground/src/main.rs | 136 +++----- 15 files changed, 1117 insertions(+), 636 deletions(-) delete mode 100644 theseus/src/api/pack/import.rs create mode 100644 theseus/src/api/pack/import/atlauncher.rs create mode 100644 theseus/src/api/pack/import/curseforge.rs create mode 100644 theseus/src/api/pack/import/gdlauncher.rs create mode 100644 theseus/src/api/pack/import/mmc.rs create mode 100644 theseus/src/api/pack/import/mod.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 526b84890..2d7302c8f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -56,4 +56,5 @@ "rust-analyzer.linkedProjects": [ "./theseus/Cargo.toml" ], + "rust-analyzer.showUnlinkedFileNotification": false, } \ No newline at end of file diff --git a/theseus/src/api/pack/import.rs b/theseus/src/api/pack/import.rs deleted file mode 100644 index 0e8a6b53e..000000000 --- a/theseus/src/api/pack/import.rs +++ /dev/null @@ -1,372 +0,0 @@ -use std::{path::{PathBuf, Path}, fs::read_to_string}; - -use serde::{Serialize, Deserialize, de}; -use tokio::{fs, io::BufReader}; - -use crate::{profile_create, pack::{install_from::{PackFormat, CreatePackDescription}, install_mrpack}, event::LoadingBarId, util::fetch, State}; - -use super::install_from::{PackDependency, self}; - -// instance.cfg -// https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/minecraft/MinecraftInstance.cpp -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "PascalCase")] -pub struct PrismInstance { - pub general : PrismInstanceGeneral, -} -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "PascalCase")] -pub struct PrismInstanceGeneral { - pub java_path : Option, - pub jvm_args : Option, - - #[serde(deserialize_with = "deserialize_bool")] - pub managed_pack : bool, - - pub managed_pack_id : Option, - pub managed_pack_type : Option, - pub managed_pack_version_id : Option, - pub managed_pack_version_name : Option, - - #[serde(rename = "iconKey")] - pub icon_key : Option, - #[serde(rename = "name")] - pub name : Option, -} - -// serde_ini reads 'true' and 'false' as strings, so we need to convert them to booleans -fn deserialize_bool<'de, D>(deserializer: D) -> Result -where - D: de::Deserializer<'de>, -{ - let s = String::deserialize(deserializer)?; - match s.as_str() { - "true" => Ok(true), - "false" => Ok(false), - _ => Err(de::Error::custom("expected 'true' or 'false'")), - } -} - - - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "lowercase")] -pub enum PrismManagedPackType { - Modrinth, - #[serde(rename = "")] - Unmanaged -} - - -// mmc-pack.json -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct PrismPack { - components : Vec, - format_version : u32, -} - -// https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/minecraft/Component.h -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct PrismComponent { - pub uid: String, - - #[serde(default)] - pub version : Option, - #[serde(default)] - pub dependency_only : bool, - - #[serde(default)] - pub important : bool, - #[serde(default)] - pub disabled : bool, - - #[serde(default)] - pub cached_name : String, - #[serde(default)] - pub cached_version : String, - - #[serde(default)] - pub cached_requires : Vec, - #[serde(default)] - pub cached_conflicts : Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct PrismComponentRequirement { - pub uid: String, - pub equals_version : Option, - pub suggests : Option, -} - -#[tracing::instrument] -#[theseus_macros::debug_pin] -pub async fn import_prism( - prism_base_path: PathBuf, // path to base prism folder - instance_folder: String, // instance folder in prism_base_path - profile_path : PathBuf, // path to profile - existing_loading_bar: Option, - -) -> crate::Result<()> { - let state = crate::State::get().await?; - let prism_instance_path = prism_base_path.join("instances").join(instance_folder.clone()); - - - // NOTE: Seems like mmc-packs are for meta-folder dependencies - // uid is the folder name, then version? - println!("import_prism({:?})", prism_instance_path); - let mmc_pack = fs::read_to_string(&prism_instance_path.join("mmc-pack.json")).await?; - let mmc_pack: PrismPack = serde_json::from_str::(&mmc_pack)?; - println!("mmc-pack.json: {:#?}", mmc_pack); - - let instance_cfg = fs::read_to_string(&prism_instance_path.join("instance.cfg")).await?; - let instance_cfg: PrismInstance = serde_ini::from_str::(&instance_cfg)?; - - println!("instance.cfg: {:#?}", &instance_cfg); - - // Re-cache icon - let icon = if let Some(icon_key) = instance_cfg.general.icon_key { - let icon_path = prism_base_path.join("icons").join(icon_key); - dbg!(&icon_path); - let bytes = tokio::fs::read(&icon_path).await; - if let Ok(bytes ) = bytes { - let bytes = bytes::Bytes::from(bytes); - let cache_dir = &state.directories.caches_dir(); - dbg!(&cache_dir); - let semaphore = &state.io_semaphore; - Some(fetch::write_cached_icon(&icon_path.to_string_lossy(), cache_dir, bytes, semaphore).await?) - } else { - // could not find icon (for instance, prism default icon, etc) - None - } - } else { - None - }; - - // Create description from instance.cfg - let description = CreatePackDescription { - icon, - override_title: instance_cfg.general.name, - project_id: instance_cfg.general.managed_pack_id, - version_id: instance_cfg.general.managed_pack_version_id, - existing_loading_bar: existing_loading_bar.clone(), - profile: profile_path.clone(), - }; - - // Managed pack - if instance_cfg.general.managed_pack { - match instance_cfg.general.managed_pack_type { - // MODRINTH MANAGED PACK - Some(PrismManagedPackType::Modrinth) => { - // Get overrides.txt in /mrpack - println!("importing modrinth managed pack"); - let file = fs::File::open(&prism_instance_path.join("mrpack").join("overrides.txt")).await?; - // add each line as a different String in a Vec - use tokio::io::AsyncBufReadExt; - let mut lines = BufReader::new(file).lines(); - println!("lines: {:?}", lines); - let mut override_paths : Vec = Vec::new(); - while let Some(line) = lines.next_line().await? { - override_paths.push(line.into()); - } - - // Get mrpack.json in /mrpack - let mrpack = serde_json::from_str::(&read_to_string(&prism_instance_path.join("mrpack").join("modrinth.index.json"))?)?; - println!("mrpack"); - - - install_mrpack::install_importable_mrpack(profile_path, description, mrpack, prism_instance_path.join(".minecraft"), override_paths).await?; - - } - // For flame, etc - Some(x) => todo!("import non-modrinth managed pack: {:?}", x), - _ => return Err(crate::ErrorKind::InputError({ - format!("Managed pack type not specified in instance.cfg") - }).into()) - } - } else { - let backup_name = "Imported Modpack".to_string(); - import_prism_unmanaged(profile_path, prism_base_path, instance_folder, backup_name, description, mmc_pack, existing_loading_bar).await?; - - } - Ok(()) -} - -async fn import_prism_unmanaged(profile_path: PathBuf, prism_base_path: PathBuf, instance_folder: String, backup_name : String, description: CreatePackDescription, mmc_pack: PrismPack, existing_loading_bar : Option)-> crate::Result<()> { - let state = crate::State::get().await?; - - - println!("loading dependencies {:?}", mmc_pack.components); - - // Pack dependencies stored in mmc-pack.json, we convert to .mrpack pack dependencies - let dependencies = mmc_pack.components.iter().filter_map(|component| { - if component.uid.starts_with("net.fabricmc.fabric-loader") { - return Some((PackDependency::FabricLoader, component.cached_version.clone())); - } - println!("Examining: {}", component.uid); - if component.uid.starts_with("net.minecraftforge") { - println!("Found forge: {}", component.uid); - return Some((PackDependency::Forge, component.cached_version.clone())); - } - if component.uid.starts_with("org.quiltmc.quilt-loader") { - return Some((PackDependency::QuiltLoader, component.cached_version.clone())); - } - if component.uid.starts_with("net.minecraft") { - return Some((PackDependency::Minecraft, component.cached_version.clone())); - } - - None - }).collect(); - - println!("dependencies: {:?}", dependencies); - println!("description: {:?}", description); - // Sets profile information to be that loaded from mmc-pack.json and instance.cfg - install_from::set_profile_information(profile_path.clone(), &description, &backup_name, &dependencies).await?; - - println!("mmc_pack.components: {:?}", mmc_pack.components); - // Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc) - import_dotminecraft(profile_path.clone(), prism_base_path.join("instances").join(instance_folder).join(".minecraft")).await?; - - if let Some(profile_val) = - crate::api::profile::get(&profile_path, None).await? - { - crate::launcher::install_minecraft( - &profile_val, - existing_loading_bar, - ) - .await?; - - State::sync().await?; - } -Ok(()) - - -} - - -async fn import_dotminecraft(profile_path : PathBuf, dotminecraft: PathBuf) -> crate::Result<()> { - - println!("here import_dotminecraft {:?} {:?}", profile_path, dotminecraft); - // std fs copy every file in dotminecraft to profile_path - for entry in std::fs::read_dir(dotminecraft)? { - let entry = entry?; - let path = entry.path(); - copy_dir_to(&path, &profile_path.join(path.file_name().ok_or_else(|| crate::ErrorKind::InputError(format!("Invalid file: {}", &path.display())))?)).await?; - } - - Ok(()) -} - -// recursively fs::copy every file in src to dest -// uses async recursion -#[theseus_macros::debug_pin] -#[async_recursion::async_recursion] -#[tracing::instrument] -async fn copy_dir_to(src: &Path, dst: &Path) -> crate::Result<()> { - if !src.is_dir() { - fs::copy(src, dst).await?; - return Ok(()); - } - - // Create the destination directory - fs::create_dir_all(&dst).await?; - - // Iterate over the directory - let mut dir = fs::read_dir(src).await?; - while let Some(child) = dir.next_entry().await? { - let src_child = child.path(); - let dst_child = dst.join(src_child.file_name().ok_or_else(|| crate::ErrorKind::InputError(format!("Invalid file: {}", &src_child.display())))?); - - if child.metadata().await?.is_dir() { - // Recurse into sub-directory - copy_dir_to(&src_child, &dst_child).await?; - } else { - // Copy file - fs::copy(&src_child, &dst_child).await?; - } - } - - Ok(()) -} - - -#[cfg(test)] -mod tests { - use crate::pack::install_from::CreatePackProfile; - - use super::*; - - const PRISM_FOLDER : &'static str = r"/home/thesuzerain/.local/share/PrismLauncher"; - - // #[tokio::test] - // async fn test_import_prism_cobblemon() { - - // setup_tests().await.unwrap(); - - // // Import cobblemon mrpack - // let profile_path = profile_create::profile_create_from_creator(CreatePackProfile::default()).await.unwrap(); - // let result = import_prism(PRISM_FOLDER.into(), r"Cobblemon [Fabric]".to_string(), profile_path.clone(), None).await.unwrap(); - // crate::profile::remove(&profile_path).await.unwrap(); - - // println!("Cobblemon result: {:?}", result); - - // panic!("hello"); - // } - - #[tokio::test] - async fn test_import_prism_tempor() { - - setup_tests().await.unwrap(); - - // Tempor test - let profile_path = profile_create::profile_create_from_creator(CreatePackProfile::default()).await.unwrap(); - let result = import_prism(PRISM_FOLDER.into(), r"tempor".to_string(), profile_path.clone(), None).await.unwrap(); - // crate::profile::remove(&profile_path).await.unwrap(); - - println!("tempor result: {:?}", result); - - panic!("hello"); - } - - // #[tokio::test] - // async fn test_import_prism_pixelmon() { - - // setup_tests().await.unwrap(); - - // // Tempor test - // let profile_path = profile_create::profile_create_from_creator(CreatePackProfile::default()).await.unwrap(); - // let result = import_prism(PRISM_FOLDER.into(), r"The Pixelmon Modpack".to_string(), profile_path.clone(), None).await.unwrap(); - // crate::profile::remove(&profile_path).await.unwrap(); - - // println!("The Pixelmon Modpack result: {:?}", result); - - // panic!("hello"); - // } - - - - async fn setup_tests() -> crate::Result<()> { - - // Initialize state - let state = crate::State::get().await?; - - // Set java globals to auto detected ones - { - let jres = crate::jre::get_all_jre().await?; - let java_8 = - crate::jre::find_filtered_jres("1.8", jres.clone(), false).await?; - let java_17 = - crate::jre::find_filtered_jres("1.17", jres.clone(), false).await?; - let java_18plus = - crate::jre::find_filtered_jres("1.18", jres.clone(), true).await?; - let java_globals = - crate::jre::autodetect_java_globals(java_8, java_17, java_18plus) - .await?; - state.settings.write().await.java_globals = java_globals; - } - - Ok(()) - } -} \ No newline at end of file diff --git a/theseus/src/api/pack/import/atlauncher.rs b/theseus/src/api/pack/import/atlauncher.rs new file mode 100644 index 000000000..7aff630ac --- /dev/null +++ b/theseus/src/api/pack/import/atlauncher.rs @@ -0,0 +1,235 @@ +use std::{collections::HashMap, path::PathBuf}; + +use serde::{Deserialize, Serialize}; +use tokio::fs; + +use crate::{ + event::LoadingBarId, + pack::{ + self, + import::{self, copy_dotminecraft}, + install_from::CreatePackDescription, + }, + prelude::ModLoader, + state::{LinkedData, ProfileInstallStage}, + State, +}; + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ATInstance { + pub id: String, // minecraft version id ie: 1.12.1, not a name + pub launcher: ATLauncher, + pub java_version: ATJavaVersion, +} +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ATLauncher { + pub name: String, + pub pack: String, + pub version: String, // ie: 1.6 + pub loader_version: ATLauncherLoaderVersion, + + pub modrinth_project: Option, + pub modrinth_version: Option, + pub modrinth_manifest: Option, + + pub mods: Vec, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ATJavaVersion { + pub major_version: u8, + pub component: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ATLauncherLoaderVersion { + pub r#type: String, + pub version: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ATLauncherModrinthProject { + pub id: String, + pub slug: String, + pub project_type: String, + pub team: String, + pub title: String, + pub description: String, + pub body: String, + pub client_side: Option, + pub server_side: Option, + pub categories: Vec, + pub icon_url: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ATLauncherModrinthVersion { + pub id: String, + pub project_id: String, + pub name: String, + pub version_number: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ATLauncherModrinthVersionFile { + pub hashes: HashMap, + pub url: String, + pub filename: String, + pub primary: bool, + pub size: u64, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ATLauncherModrinthVersionDependency { + pub project_id: Option, + pub version_id: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ATLauncherMod { + pub name: String, + pub version: String, + pub file: String, + + pub modrinth_project: Option, + pub modrinth_version: Option, +} + +#[tracing::instrument] +#[theseus_macros::debug_pin] +pub async fn import_atlauncher( + atlauncher_base_path: PathBuf, // path to base atlauncher folder + instance_folder: String, // instance folder in atlauncher_base_path + profile_path: PathBuf, // path to profile + existing_loading_bar: Option, +) -> crate::Result<()> { + let atlauncher_instance_path = atlauncher_base_path + .join("instances") + .join(instance_folder.clone()); + + // Load instance.json + let atinstance: String = + fs::read_to_string(&atlauncher_instance_path.join("instance.json")) + .await?; + let atinstance: ATInstance = + serde_json::from_str::(&atinstance)?; + + // Icon path should be {instance_folder}/instance.png if it exists, + // Second possibility is ATLauncher/configs/images/{safe_pack_name}.png (safe pack name is alphanumeric lowercase) + let icon_path_primary = atlauncher_instance_path.join("instance.png"); + let safe_pack_name = atinstance + .launcher + .pack + .replace(|c: char| !c.is_alphanumeric(), "") + .to_lowercase(); + let icon_path_secondary = atlauncher_base_path + .join("configs") + .join("images") + .join(safe_pack_name + ".png"); + let icon = match (icon_path_primary.exists(), icon_path_secondary.exists()) + { + (true, _) => import::recache_icon(icon_path_primary).await?, + (_, true) => import::recache_icon(icon_path_secondary).await?, + _ => None, + }; + + // Create description from instance.cfg + let description = CreatePackDescription { + icon, + override_title: Some(atinstance.launcher.name.clone()), + project_id: None, + version_id: None, + existing_loading_bar: existing_loading_bar.clone(), + profile: profile_path.clone(), + }; + + let backup_name = format!("ATLauncher-{}", instance_folder); + let minecraft_folder = atlauncher_instance_path; + + import_atlauncher_unmanaged( + profile_path, + minecraft_folder, + backup_name, + description, + atinstance, + existing_loading_bar, + ) + .await?; +println!("Done."); + + Ok(()) +} + +async fn import_atlauncher_unmanaged( + profile_path: PathBuf, + minecraft_folder: PathBuf, + backup_name: String, + description: CreatePackDescription, + atinstance: ATInstance, + existing_loading_bar: Option, +) -> crate::Result<()> { + let mod_loader = format!( + "\"{}\"", + atinstance.launcher.loader_version.r#type.to_lowercase() + ); + let mod_loader: ModLoader = serde_json::from_str::(&mod_loader) + .map_err(|_| { + crate::ErrorKind::InputError(format!( + "Could not parse mod loader type: {}", + mod_loader + )) + })?; + + let game_version = atinstance.id; + + let loader_version = if mod_loader != ModLoader::Vanilla { + crate::profile_create::get_loader_version_from_loader( + game_version.clone(), + mod_loader, + Some(atinstance.launcher.loader_version.version.clone()), + ) + .await? + } else { + None + }; + + // Set profile data to created default profile + crate::api::profile::edit(&profile_path, |prof| { + prof.metadata.name = description + .override_title + .clone() + .unwrap_or_else(|| backup_name.to_string()); + prof.install_stage = ProfileInstallStage::PackInstalling; + prof.metadata.linked_data = Some(LinkedData { + project_id: description.project_id.clone(), + version_id: description.version_id.clone(), + }); + prof.metadata.icon = description.icon.clone(); + prof.metadata.game_version = game_version.clone(); + prof.metadata.loader_version = loader_version.clone(); + prof.metadata.loader = mod_loader; + + async { Ok(()) } + }) + .await?; + + // Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc) + copy_dotminecraft(profile_path.clone(), minecraft_folder).await?; + + if let Some(profile_val) = + crate::api::profile::get(&profile_path, None).await? + { + crate::launcher::install_minecraft(&profile_val, existing_loading_bar) + .await?; + + State::sync().await?; + } + Ok(()) +} diff --git a/theseus/src/api/pack/import/curseforge.rs b/theseus/src/api/pack/import/curseforge.rs new file mode 100644 index 000000000..18fb7f4c8 --- /dev/null +++ b/theseus/src/api/pack/import/curseforge.rs @@ -0,0 +1,143 @@ +use std::path::PathBuf; + +use serde::{Serialize, Deserialize}; +use tokio::fs; + +use crate::{event::LoadingBarId, pack::install_from::CreatePackDescription, prelude::ModLoader, state::ProfileInstallStage, State}; + +use super::{copy_dir_to, copy_dotminecraft}; + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FlameManifest { + pub manifest_version : u8, + pub name : String, + pub minecraft : FlameMinecraft, +} +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FlameMinecraft { + pub version : String, + pub mod_loaders : Vec, + +} +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FlameModLoader { + pub id : String, + pub primary : bool +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MinecraftInstance { + pub name : Option, + pub game_version : String, // Minecraft game version. Non-prioritized, use this if Vanilla +} + +pub async fn import_curseforge( curseforge_instance_folder: PathBuf, // instance's folder + profile_path: PathBuf, // path to profile + existing_loading_bar: Option, +) -> crate::Result<()> { + // TODO: recache curseforge instance icon + let icon: Option = None; + + // Load minecraftinstance.json + let minecraft_instance: String = + fs::read_to_string(&curseforge_instance_folder.join("minecraftinstance.json")) + .await?; + let minecraft_instance: MinecraftInstance = serde_json::from_str::(&minecraft_instance)?; + let override_title: Option = minecraft_instance.name.clone(); + let backup_name = format!("Curseforge-{}", curseforge_instance_folder.file_name().map(|a| a.to_string_lossy().to_string()).unwrap_or("Unknown".to_string())); + + + // Curseforge vanilla profile may not have a manifest.json, so we allow it to not exist + if curseforge_instance_folder.join("manifest.json").exists() { + // Load manifest.json + let cf_manifest: String = + fs::read_to_string(&curseforge_instance_folder.join("manifest.json")) + .await?; + + let cf_manifest: FlameManifest = + serde_json::from_str::(&cf_manifest)?; + + let game_version = cf_manifest.minecraft.version; + + // CF allows Forge, Fabric, and Vanilla + let mut mod_loader = None; + let mut loader_version = None; + for loader in cf_manifest.minecraft.mod_loaders { + match loader.id.split_once('-') { + Some(("forge", version)) => { + mod_loader = Some(ModLoader::Forge); + loader_version = Some(version.to_string()); + }, + Some(("fabric", version)) => { + mod_loader = Some(ModLoader::Fabric); + loader_version = Some(version.to_string()); + } + _ => {}, + } + } + let mod_loader = mod_loader.unwrap_or(ModLoader::Vanilla); + + let loader_version = if mod_loader != ModLoader::Vanilla { + crate::profile_create::get_loader_version_from_loader( + game_version.clone(), + mod_loader, + loader_version, + ) + .await? + } else { + None + }; + +// Set profile data to created default profile +crate::api::profile::edit(&profile_path, |prof| { + prof.metadata.name = override_title + .clone() + .unwrap_or_else(|| backup_name.to_string()); + prof.install_stage = ProfileInstallStage::PackInstalling; + prof.metadata.icon = icon.clone(); + prof.metadata.game_version = game_version.clone(); + prof.metadata.loader_version = loader_version.clone(); + prof.metadata.loader = mod_loader; + + async { Ok(()) } +}) +.await?; + + } else { + // If no manifest is found, it's a vanilla profile +crate::api::profile::edit(&profile_path, |prof| { + prof.metadata.name = override_title + .clone() + .unwrap_or_else(|| backup_name.to_string()); + prof.metadata.icon = icon.clone(); + prof.metadata.game_version = minecraft_instance.game_version.clone(); + prof.metadata.loader_version = None; + prof.metadata.loader = ModLoader::Vanilla; + + async { Ok(()) } +}) +.await?; + + } + +// Copy in contained folders as overrides +copy_dotminecraft(profile_path.clone(), curseforge_instance_folder).await?; + +if let Some(profile_val) = + crate::api::profile::get(&profile_path, None).await? +{ + crate::launcher::install_minecraft(&profile_val, existing_loading_bar) + .await?; + + State::sync().await?; +} + + Ok(()) + + +} + diff --git a/theseus/src/api/pack/import/gdlauncher.rs b/theseus/src/api/pack/import/gdlauncher.rs new file mode 100644 index 000000000..6f9a5ee0f --- /dev/null +++ b/theseus/src/api/pack/import/gdlauncher.rs @@ -0,0 +1,95 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; +use tokio::fs; + +use crate::{event::LoadingBarId, prelude::ModLoader, state::ProfileInstallStage, State}; + +use super::{copy_dir_to, recache_icon, copy_dotminecraft}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GDLauncherConfig { + pub background : Option, + pub loader: GDLauncherLoader, + // pub mods: Vec, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GDLauncherLoader { + pub loader_type: ModLoader, + pub loader_version : Option, + pub mc_version: String, + pub source: Option, + pub source_name: Option, +} + +pub async fn import_gdlauncher( gdlauncher_instance_folder: PathBuf, // instance's folder + profile_path: PathBuf, // path to profile + existing_loading_bar: Option, +) -> crate::Result<()> { + + // Load config.json + let config: String = + fs::read_to_string(&gdlauncher_instance_folder.join("config.json")) + .await?; + let config: GDLauncherConfig = serde_json::from_str::(&config)?; + let override_title: Option = config.loader.source_name.clone(); + let backup_name = format!("GDLauncher-{}", gdlauncher_instance_folder.file_name().map(|a| a.to_string_lossy().to_string()).unwrap_or("Unknown".to_string())); + +// Re-cache icon + let icon = config.background.clone().map(|b| gdlauncher_instance_folder.join(b)); + let icon = if let Some(icon) = icon { + recache_icon(icon).await? + } else { + None + }; + + let game_version = config.loader.mc_version; + let mod_loader = config.loader.loader_type; + let loader_version = config.loader.loader_version; + + let loader_version = if mod_loader != ModLoader::Vanilla { + crate::profile_create::get_loader_version_from_loader( + game_version.clone(), + mod_loader, + loader_version, + ) + .await? + } else { + None + }; + +// Set profile data to created default profile +crate::api::profile::edit(&profile_path, |prof| { + prof.metadata.name = override_title + .clone() + .unwrap_or_else(|| backup_name.to_string()); + prof.install_stage = ProfileInstallStage::PackInstalling; + prof.metadata.icon = icon.clone(); + prof.metadata.game_version = game_version.clone(); + prof.metadata.loader_version = loader_version.clone(); + prof.metadata.loader = mod_loader; + + async { Ok(()) } +}) +.await?; + + +// Copy in contained folders as overrides +copy_dotminecraft(profile_path.clone(), gdlauncher_instance_folder).await?; + +if let Some(profile_val) = + crate::api::profile::get(&profile_path, None).await? +{ + crate::launcher::install_minecraft(&profile_val, existing_loading_bar) + .await?; + + State::sync().await?; +} + + Ok(()) + + +} + diff --git a/theseus/src/api/pack/import/mmc.rs b/theseus/src/api/pack/import/mmc.rs new file mode 100644 index 000000000..a8bf45790 --- /dev/null +++ b/theseus/src/api/pack/import/mmc.rs @@ -0,0 +1,292 @@ +use std::path::{Path, PathBuf}; + +use serde::{de, Deserialize, Serialize}; +use tokio::fs; + +use crate::{ + event::LoadingBarId, + pack::{ + import::{self, copy_dotminecraft}, + install_from::{self, CreatePackDescription, PackDependency}, + }, + State, +}; + +// instance.cfg +// https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/minecraft/MinecraftInstance.cpp +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +#[serde(untagged)] +enum MMCInstanceEnum { + General(MMCInstanceGeneral), + Instance(MMCInstance), +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +struct MMCInstanceGeneral { + pub general: MMCInstance, +} +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "PascalCase")] +pub struct MMCInstance { + pub java_path: Option, + pub jvm_args: Option, + + #[serde(default)] + #[serde(deserialize_with = "deserialize_optional_bool")] + pub managed_pack: Option, + + pub managed_pack_id: Option, + pub managed_pack_type: Option, + pub managed_pack_version_id: Option, + pub managed_pack_version_name: Option, + + #[serde(rename = "iconKey")] + pub icon_key: Option, + #[serde(rename = "name")] + pub name: Option, +} + +// serde_ini reads 'true' and 'false' as strings, so we need to convert them to booleans +fn deserialize_optional_bool<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: de::Deserializer<'de>, +{ + println!("deserialize_optional_bool"); + let s = Option::::deserialize(deserializer)?; + match s { + Some(string) => match string.as_str() { + "true" => Ok(Some(true)), + "false" => Ok(Some(false)), + _ => Err(de::Error::custom("expected 'true' or 'false'")), + }, + None => Ok(None), + } +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "lowercase")] +pub enum MMCManagedPackType { + Modrinth, + Flame, + ATLauncher, + #[serde(other)] + Unknown, +} + +// mmc-pack.json +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MMCPack { + components: Vec, + format_version: u32, +} + +// https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/minecraft/Component.h +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MMCComponent { + pub uid: String, + + #[serde(default)] + pub version: Option, + #[serde(default)] + pub dependency_only: bool, + + #[serde(default)] + pub important: bool, + #[serde(default)] + pub disabled: bool, + + pub cached_name: Option, + pub cached_version: Option, + + #[serde(default)] + pub cached_requires: Vec, + #[serde(default)] + pub cached_conflicts: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MMCComponentRequirement { + pub uid: String, + pub equals_version: Option, + pub suggests: Option, +} + +// Checks if if its a folder, and the folder contains instance.cfg and mmc-pack.json +#[tracing::instrument] +pub async fn is_mmc(path: PathBuf) -> bool { + let instance_cfg = path.join("instance.cfg"); + let mmc_pack = path.join("mmc-pack.json"); + instance_cfg.exists() && mmc_pack.exists() +} + +// Loading the INI (instance.cfg) file +async fn load_instance_cfg(file_path: &Path) -> crate::Result { + let instance_cfg = fs::read_to_string(file_path).await?; + let instance_cfg_enum: MMCInstanceEnum = + serde_ini::from_str::(&instance_cfg)?; + match instance_cfg_enum { + MMCInstanceEnum::General(instance_cfg) => Ok(instance_cfg.general), + MMCInstanceEnum::Instance(instance_cfg) => Ok(instance_cfg), + } +} + +#[tracing::instrument] +#[theseus_macros::debug_pin] +pub async fn import_mmc( + mmc_base_path: PathBuf, // path to base mmc folder + instance_folder: String, // instance folder in mmc_base_path + profile_path: PathBuf, // path to profile + existing_loading_bar: Option, +) -> crate::Result<()> { + let mmc_instance_path = mmc_base_path + .join("instances") + .join(instance_folder.clone()); + + let mmc_pack = + fs::read_to_string(&mmc_instance_path.join("mmc-pack.json")).await?; + let mmc_pack: MMCPack = serde_json::from_str::(&mmc_pack)?; + + let instance_cfg = + load_instance_cfg(&mmc_instance_path.join("instance.cfg")).await?; + + // Re-cache icon + let icon = if let Some(icon_key) = instance_cfg.icon_key { + let icon_path = mmc_base_path.join("icons").join(icon_key); + dbg!(&icon_path); + import::recache_icon(icon_path).await? + } else { + None + }; + + // Create description from instance.cfg + let description = CreatePackDescription { + icon, + override_title: instance_cfg.name, + project_id: instance_cfg.managed_pack_id, + version_id: instance_cfg.managed_pack_version_id, + existing_loading_bar: existing_loading_bar.clone(), + profile: profile_path.clone(), + }; + + // Managed pack + if instance_cfg.managed_pack.unwrap_or(false) { + match instance_cfg.managed_pack_type { + Some(MMCManagedPackType::Modrinth) => { + // Modrinth Managed Pack + // Kept separate as we may in the future want to add special handling for modrinth managed packs + let backup_name = "Imported Modrinth Modpack".to_string(); + let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join(".minecraft"); + import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack, existing_loading_bar).await?; + } + Some(MMCManagedPackType::Flame) => { + // For flame managed packs + // Treat as unmanaged, but with 'minecraft' folder instead of '.minecraft' + let backup_name = "Imported Flame Modpack".to_string(); + let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join("minecraft"); + import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack, existing_loading_bar).await?; + }, + Some(_) => { + // For managed packs that aren't modrinth or flame + // Treat as unmanaged + let backup_name = "ImportedModpack".to_string(); + let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join(".minecraft"); + import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack, existing_loading_bar).await?; + }, + _ => return Err(crate::ErrorKind::InputError({ + "Instance is managed, but managed pack type not specified in instance.cfg".to_string() + }).into()) + } + } else { + // Direclty import unmanaged pack + let backup_name = "Imported Modpack".to_string(); + let minecraft_folder = mmc_base_path + .join("instances") + .join(instance_folder) + .join(".minecraft"); + import_mmc_unmanaged( + profile_path, + minecraft_folder, + backup_name, + description, + mmc_pack, + existing_loading_bar, + ) + .await?; + } + Ok(()) +} + +async fn import_mmc_unmanaged( + profile_path: PathBuf, + minecraft_folder: PathBuf, + backup_name: String, + description: CreatePackDescription, + mmc_pack: MMCPack, + existing_loading_bar: Option, +) -> crate::Result<()> { + println!("import_mmc_unmanaged {:?} ", mmc_pack.components); + // Pack dependencies stored in mmc-pack.json, we convert to .mrpack pack dependencies + let dependencies = mmc_pack + .components + .iter() + .filter_map(|component| { + if component.uid.starts_with("net.fabricmc.fabric-loader") { + return Some(( + PackDependency::FabricLoader, + component.version.clone().unwrap_or_default(), + )); + } + if component.uid.starts_with("net.minecraftforge") { + return Some(( + PackDependency::Forge, + component.version.clone().unwrap_or_default(), + )); + } + if component.uid.starts_with("org.quiltmc.quilt-loader") { + return Some(( + PackDependency::QuiltLoader, + component.version.clone().unwrap_or_default(), + )); + } + if component.uid.starts_with("net.minecraft") { + return Some(( + PackDependency::Minecraft, + component.version.clone().unwrap_or_default(), + )); + } + + None + }) + .collect(); + println!("import_mmc_unmanaged222 {:?} ", dependencies); + + // Sets profile information to be that loaded from mmc-pack.json and instance.cfg + install_from::set_profile_information( + profile_path.clone(), + &description, + &backup_name, + &dependencies, + ) + .await?; + + println!("copying from {:?} to {:?}", minecraft_folder, profile_path); + // Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc) + copy_dotminecraft(profile_path.clone(), minecraft_folder).await?; + + if let Some(profile_val) = + crate::api::profile::get(&profile_path, None).await? + { + crate::launcher::install_minecraft(&profile_val, existing_loading_bar) + .await?; + + State::sync().await?; + } + Ok(()) +} diff --git a/theseus/src/api/pack/import/mod.rs b/theseus/src/api/pack/import/mod.rs new file mode 100644 index 000000000..9245bc398 --- /dev/null +++ b/theseus/src/api/pack/import/mod.rs @@ -0,0 +1,231 @@ +use std::path::{Path, PathBuf}; + +use tokio::fs; + +use crate::{util::fetch, event::LoadingBarId}; + +pub mod atlauncher; +pub mod curseforge; +pub mod gdlauncher; +pub mod mmc; + +#[derive(Debug, Clone, Copy)] +pub enum ImportLauncherType { + Modrinth, + MultiMC, + PrismLauncher, + ATLauncher, + GDLauncher, + Curseforge, + Unknown +} + +pub async fn get_importable_instances( + launcher_type: ImportLauncherType, + base_path: PathBuf, +) -> crate::Result> { + let instances_folder = match launcher_type { + ImportLauncherType::Modrinth => { + todo!() + }, + ImportLauncherType::GDLauncher | ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher | ImportLauncherType::ATLauncher => { + base_path.join("instances") + }, + ImportLauncherType::Curseforge => { + base_path.join("Instances") + }, + ImportLauncherType::Unknown => { + todo!() + } + }; + println!("Searching {:?} - instances_folder: {:?}", launcher_type, instances_folder); + let mut instances = Vec::new(); + let mut dir = fs::read_dir(instances_folder).await?; + while let Some(entry) = dir.next_entry().await? { + let path = entry.path(); + if path.is_dir() { + let name = path.file_name(); + if let Some(name) = name { + instances.push(name.to_string_lossy().to_string()); + } + } + } + Ok(instances) +} + +pub async fn import_instance( + profile_path: PathBuf, + launcher_type: ImportLauncherType, + base_path: PathBuf, + instance_folder: String, + existing_loading_bar: Option +) -> crate::Result<()> { + match launcher_type { + ImportLauncherType::Modrinth => { + todo!() + }, + ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => { + mmc::import_mmc( + base_path, // path to base mmc folder + instance_folder, // instance folder in mmc_base_path + profile_path, // path to profile + existing_loading_bar, + ).await?; + }, + ImportLauncherType::ATLauncher => { + atlauncher::import_atlauncher( + base_path, // path to atlauncher folder + instance_folder, // instance folder in atlauncher + profile_path, // path to profile + existing_loading_bar, + ).await?; + }, + ImportLauncherType::GDLauncher => { + gdlauncher::import_gdlauncher( + base_path.join("instances").join(instance_folder), // path to gdlauncher folder + profile_path, // path to profile + existing_loading_bar, + ).await?; + }, + ImportLauncherType::Curseforge => { + + curseforge::import_curseforge( + base_path.join("Instances").join(instance_folder), // path to curseforge folder + profile_path, // path to profile + existing_loading_bar, + ).await?; + }, + ImportLauncherType::Unknown => { + todo!() + } + } + Ok(()) +} + +pub async fn guess_launcher(filepath : &Path) -> crate::Result { + // search filepath for each launcher type + // if found, return that launcher type + // if not found, return unknown + let mut found_type = ImportLauncherType::Unknown; + + // search path as string for mmc + if filepath.to_string_lossy().to_lowercase().contains("multimc") { + found_type = ImportLauncherType::MultiMC; + } + + // search path as string for prism + if filepath.to_string_lossy().to_lowercase().contains("prism") { + found_type = ImportLauncherType::PrismLauncher; + } + + // search path as string for atlauncher + if filepath.to_string_lossy().to_lowercase().contains("atlauncher") { + found_type = ImportLauncherType::ATLauncher; + } + + // search path as string for curseforge + if filepath.to_string_lossy().to_lowercase().contains("curseforge") { + found_type = ImportLauncherType::Curseforge; + } + + // search path as string for modrinth + if filepath.to_string_lossy().to_lowercase().contains("modrinth") { + found_type = ImportLauncherType::Modrinth; + } + + // search path as string for gdlauncher + if filepath.to_string_lossy().to_lowercase().contains("gdlauncher") { + found_type = ImportLauncherType::GDLauncher; + } + + Ok(found_type) +} + + +/// Caches an image file in the filesystem into the cache directory, and returns the path to the cached file. +#[theseus_macros::debug_pin] +#[tracing::instrument] +pub async fn recache_icon( + icon_path: PathBuf, +) -> crate::Result> { + let state = crate::State::get().await?; + + let bytes = tokio::fs::read(&icon_path).await; + if let Ok(bytes) = bytes { + let bytes = bytes::Bytes::from(bytes); + let cache_dir = &state.directories.caches_dir(); + dbg!(&cache_dir); + let semaphore = &state.io_semaphore; + Ok(Some( + fetch::write_cached_icon( + &icon_path.to_string_lossy(), + cache_dir, + bytes, + semaphore, + ) + .await?, + )) + } else { + // could not find icon (for instance, prism default icon, etc) + Ok(None) + } +} + +async fn copy_dotminecraft( + profile_path: PathBuf, + dotminecraft: PathBuf, +) -> crate::Result<()> { + // std fs copy every file in dotminecraft to profile_path + for entry in std::fs::read_dir(dotminecraft)? { + let entry = entry?; + let path = entry.path(); + copy_dir_to( + &path, + &profile_path.join(path.file_name().ok_or_else(|| { + crate::ErrorKind::InputError(format!( + "Invalid file: {}", + &path.display() + )) + })?), + ) + .await?; + } + Ok(()) +} + +/// Recursively fs::copy every file in src to dest +/// uses async recursion +#[theseus_macros::debug_pin] +#[async_recursion::async_recursion] +#[tracing::instrument] +async fn copy_dir_to(src: &Path, dst: &Path) -> crate::Result<()> { + if !src.is_dir() { + fs::copy(src, dst).await?; + return Ok(()); + } + + // Create the destination directory + fs::create_dir_all(&dst).await?; + + // Iterate over the directory + let mut dir = fs::read_dir(src).await?; + while let Some(child) = dir.next_entry().await? { + let src_child = child.path(); + let dst_child = dst.join(src_child.file_name().ok_or_else(|| { + crate::ErrorKind::InputError(format!( + "Invalid file: {}", + &src_child.display() + )) + })?); + + if child.metadata().await?.is_dir() { + // Recurse into sub-directory + copy_dir_to(&src_child, &dst_child).await?; + } else { + // Copy file + fs::copy(&src_child, &dst_child).await?; + } + } + + Ok(()) +} diff --git a/theseus/src/api/pack/install_from.rs b/theseus/src/api/pack/install_from.rs index 38a5fd597..44c636993 100644 --- a/theseus/src/api/pack/install_from.rs +++ b/theseus/src/api/pack/install_from.rs @@ -2,7 +2,9 @@ use crate::config::MODRINTH_API_URL; use crate::data::ModLoader; use crate::event::emit::{emit_loading, init_loading}; use crate::event::{LoadingBarId, LoadingBarType}; -use crate::state::{LinkedData, ModrinthProject, ModrinthVersion, SideType, ProfileInstallStage}; +use crate::state::{ + LinkedData, ModrinthProject, ModrinthVersion, ProfileInstallStage, SideType, +}; use crate::util::fetch::{ fetch, fetch_advanced, fetch_json, write_cached_icon, }; @@ -160,7 +162,7 @@ pub fn get_profile_from_pack( name: file_name, ..Default::default() } - }, + } } } @@ -259,14 +261,17 @@ pub async fn generate_pack_from_version_id( }; emit_loading(&loading_bar, 10.0, None).await?; - Ok(CreatePack {file, description: CreatePackDescription { - icon, - override_title: None, - project_id: Some(project_id), - version_id: Some(version_id), - existing_loading_bar: Some(loading_bar), - profile, - }}) + Ok(CreatePack { + file, + description: CreatePackDescription { + icon, + override_title: None, + project_id: Some(project_id), + version_id: Some(version_id), + existing_loading_bar: Some(loading_bar), + profile, + }, + }) } #[tracing::instrument] @@ -276,25 +281,32 @@ pub async fn generate_pack_from_file( profile: PathBuf, ) -> crate::Result { let file = fs::read(&path).await?; - Ok(CreatePack { file: bytes::Bytes::from(file), description: CreatePackDescription { - icon: None, - override_title: None, - project_id: None, - version_id: None, - existing_loading_bar: None, - profile, - }}) + Ok(CreatePack { + file: bytes::Bytes::from(file), + description: CreatePackDescription { + icon: None, + override_title: None, + project_id: None, + version_id: None, + existing_loading_bar: None, + profile, + }, + }) } /// Sets generated profile attributes to the pack ones (using profile::edit) /// This includes the pack name, icon, game version, loader version, and loader #[theseus_macros::debug_pin] -pub async fn set_profile_information(profile_path: PathBuf, description: &CreatePackDescription, backup_name: &str, dependencies: &HashMap) -> crate::Result<()> { +pub async fn set_profile_information( + profile_path: PathBuf, + description: &CreatePackDescription, + backup_name: &str, + dependencies: &HashMap, +) -> crate::Result<()> { let mut game_version: Option<&String> = None; let mut mod_loader = None; let mut loader_version = None; - println!("here1"); - + println!("here1 {:?}", dependencies); for (key, value) in dependencies { match key { @@ -314,7 +326,6 @@ pub async fn set_profile_information(profile_path: PathBuf, description: &Create } } - let game_version = if let Some(game_version) = game_version { game_version } else { @@ -334,7 +345,7 @@ pub async fn set_profile_information(profile_path: PathBuf, description: &Create loader_version.cloned(), ) .await? - } else { + } else { None }; println!("her5 - {:?}", game_version); @@ -342,8 +353,10 @@ pub async fn set_profile_information(profile_path: PathBuf, description: &Create println!("her5 - {:?}", description.override_title); // Sets values in profile crate::api::profile::edit(&profile_path, |prof| { - prof.metadata.name = - description.override_title.clone().unwrap_or_else(|| backup_name.to_string()); + prof.metadata.name = description + .override_title + .clone() + .unwrap_or_else(|| backup_name.to_string()); prof.install_stage = ProfileInstallStage::PackInstalling; prof.metadata.linked_data = Some(LinkedData { project_id: description.project_id.clone(), @@ -358,4 +371,4 @@ pub async fn set_profile_information(profile_path: PathBuf, description: &Create }) .await?; Ok(()) - } +} diff --git a/theseus/src/api/pack/install_mrpack.rs b/theseus/src/api/pack/install_mrpack.rs index b419abc68..59ff34341 100644 --- a/theseus/src/api/pack/install_mrpack.rs +++ b/theseus/src/api/pack/install_mrpack.rs @@ -1,22 +1,21 @@ -use crate::data::ModLoader; use crate::event::emit::{ emit_loading, init_or_edit_loading, loading_try_for_each_concurrent, }; use crate::event::LoadingBarType; -use crate::pack::install_from::{EnvType, PackFile, PackFileHash, set_profile_information}; -use crate::state::{LinkedData, ProfileInstallStage, SideType}; +use crate::pack::install_from::{ + set_profile_information, EnvType, PackFile, PackFileHash, +}; +use crate::state::SideType; use crate::util::fetch::{fetch_mirrors, write}; use crate::State; use async_zip::tokio::read::seek::ZipFileReader; -use tokio::fs; -use std::collections::HashMap; use std::io::Cursor; use std::path::{Component, PathBuf}; use super::install_from::{ - generate_pack_from_file, generate_pack_from_version_id, - CreatePackDescription, CreatePackLocation, PackDependency, PackFormat, CreatePack, + generate_pack_from_file, generate_pack_from_version_id, CreatePack, + CreatePackLocation, PackFormat, }; /// Install a modpack from a mrpack file (a modrinth .zip format) @@ -92,7 +91,13 @@ pub async fn install_zipped_mrpack( } // Sets generated profile attributes to the pack ones (using profile::edit) - set_profile_information(profile.clone(), &description, &pack.name, &pack.dependencies).await?; + set_profile_information( + profile.clone(), + &description, + &pack.name, + &pack.dependencies, + ) + .await?; let profile = profile.clone(); let result = async { @@ -277,140 +282,3 @@ pub async fn install_zipped_mrpack( } } } - - -/// Install a modpack from existing files on the system in .mrpack format -/// (This would be used to re-install an .mrpack from a different launcher) -pub async fn install_importable_mrpack(profile_path: PathBuf, description: CreatePackDescription, pack : PackFormat, overrides_base_path: PathBuf, override_paths : Vec ) -> crate::Result { - let state = &State::get().await?; - - println!("Hello123!"); - if &*pack.game != "minecraft" { - return Err(crate::ErrorKind::InputError( - "Pack does not support Minecraft".to_string(), - ) - .into()); - } - println!("Hello1233!"); - - // Sets generated profile attributes to the pack ones (using profile::edit) - set_profile_information(profile_path.clone(), &description, &pack.name, &pack.dependencies).await?; - - let base_path = profile_path.clone(); - let result = async { - let loading_bar = init_or_edit_loading( - description.existing_loading_bar, - LoadingBarType::PackDownload { - profile_path: base_path.clone(), - pack_name: pack.name.clone(), - icon: description.icon, - pack_id: description.project_id, - pack_version: description.version_id, - }, - 100.0, - "Downloading modpack", - ) - .await?; - println!("Hello1237!"); - - let num_files = pack.files.len(); - use futures::StreamExt; - loading_try_for_each_concurrent( - futures::stream::iter(pack.files.into_iter()) - .map(Ok::), - None, - Some(&loading_bar), - 70.0, - num_files, - None, - |project| { - let base_path = base_path.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(()); - } - } - println!("Hello128!"); - - let file = fetch_mirrors( - &project - .downloads - .iter() - .map(|x| &**x) - .collect::>(), - project - .hashes - .get(&PackFileHash::Sha1) - .map(|x| &**x), - &state.fetch_semaphore, - ) - .await?; - - let path = std::path::Path::new(&project.path) - .components() - .next(); - if let Some(path) = path { - match path { - Component::CurDir - | Component::Normal(_) => { - let path = base_path.join(project.path); - write( - &path, - &file, - &state.io_semaphore, - ) - .await?; - } - _ => {} - }; - } - Ok(()) - } - }, - ) - .await?; - println!("Hello129!"); - - emit_loading(&loading_bar, 0.0, Some("Extracting overrides")) - .await?; - - // Copy in overrides - for override_path in override_paths { - // ensures any parent directories exist - fs::create_dir_all(&base_path.join(&override_path).parent().ok_or_else(|| crate::ErrorKind::InputError(format!("Override file destionation is invalid: {}", &override_path.display())))?).await?; - fs::copy(&overrides_base_path.join(&override_path), &base_path.join(&override_path)).await?; - } - - if let Some(profile_val) = - crate::api::profile::get(&base_path, None).await? - { - crate::launcher::install_minecraft( - &profile_val, - Some(loading_bar), - ) - .await?; - - State::sync().await?; - } - - Ok::(base_path.clone()) - } - .await; -println!("Hello12--10!"); - - match result { - Ok(profile) => Ok(base_path), - Err(err) => { - let _ = crate::api::profile::remove(&base_path).await; - - Err(err) - } - } -} - diff --git a/theseus/src/api/pack/mod.rs b/theseus/src/api/pack/mod.rs index e4fc146b6..be9ec8b53 100644 --- a/theseus/src/api/pack/mod.rs +++ b/theseus/src/api/pack/mod.rs @@ -1,3 +1,3 @@ pub mod import; -pub mod install_mrpack; pub mod install_from; +pub mod install_mrpack; diff --git a/theseus/src/api/profile_create.rs b/theseus/src/api/profile_create.rs index 1b0434d2d..e873ba371 100644 --- a/theseus/src/api/profile_create.rs +++ b/theseus/src/api/profile_create.rs @@ -132,8 +132,20 @@ pub async fn profile_create( } } -pub async fn profile_create_from_creator(profile : CreatePackProfile) -> crate::Result { - profile_create(profile.name, profile.game_version, profile.modloader, profile.loader_version, profile.icon, profile.icon_url, profile.linked_data, profile.skip_install_profile).await +pub async fn profile_create_from_creator( + profile: CreatePackProfile, +) -> crate::Result { + profile_create( + profile.name, + profile.game_version, + profile.modloader, + profile.loader_version, + profile.icon, + profile.icon_url, + profile.linked_data, + profile.skip_install_profile, + ) + .await } #[tracing::instrument] diff --git a/theseus/src/error.rs b/theseus/src/error.rs index 84fbaa987..2bd43b76a 100644 --- a/theseus/src/error.rs +++ b/theseus/src/error.rs @@ -9,7 +9,7 @@ pub enum ErrorKind { #[error("Serialization error (INI): {0}")] INIError(#[from] serde_ini::de::Error), - + #[error("Serialization error (JSON): {0}")] JSONError(#[from] serde_json::Error), diff --git a/theseus/src/event/mod.rs b/theseus/src/event/mod.rs index 3569c0f60..f3b114fd4 100644 --- a/theseus/src/event/mod.rs +++ b/theseus/src/event/mod.rs @@ -93,7 +93,7 @@ pub struct LoadingBar { pub cli_progress_bar: indicatif::ProgressBar, } -#[derive(Serialize, Debug ,Clone)] +#[derive(Serialize, Debug, Clone)] pub struct LoadingBarId(Uuid); // When Loading bar id is dropped, we should remove it from the hashmap diff --git a/theseus_gui/src-tauri/src/api/pack.rs b/theseus_gui/src-tauri/src/api/pack.rs index c818aab5a..e77af6944 100644 --- a/theseus_gui/src-tauri/src/api/pack.rs +++ b/theseus_gui/src-tauri/src/api/pack.rs @@ -2,7 +2,8 @@ use crate::api::Result; use std::path::PathBuf; use theseus::{ pack::{ - install_from::{CreatePackLocation, CreatePackProfile}, install_mrpack::install_zipped_mrpack, + install_from::{CreatePackLocation, CreatePackProfile}, + install_mrpack::install_zipped_mrpack, }, prelude::*, }; diff --git a/theseus_playground/src/main.rs b/theseus_playground/src/main.rs index 0542b4f0c..d6c80b43e 100644 --- a/theseus_playground/src/main.rs +++ b/theseus_playground/src/main.rs @@ -3,12 +3,15 @@ windows_subsystem = "windows" )] -use dunce::canonicalize; -use theseus::jre::autodetect_java_globals; +use std::path::PathBuf; + +use theseus::pack::import::ImportLauncherType; +use theseus::pack::import::atlauncher::import_atlauncher; +use theseus::pack::import::curseforge::import_curseforge; +use theseus::pack::install_from::CreatePackProfile; use theseus::prelude::*; -use theseus::profile_create::profile_create; -use tokio::time::{sleep, Duration}; +use tokio::fs; // A simple Rust implementation of the authentication run // 1) call the authenticate_begin_flow() function to get the URL to open (like you would in the frontend) @@ -35,95 +38,54 @@ async fn main() -> theseus::Result<()> { let _log_guard = theseus::start_logger(); // Initialize state - let st = State::get().await?; - //State::update(); - - // Autodetect java globals - let jres = jre::get_all_jre().await?; - let java_8 = jre::find_filtered_jres("1.8", jres.clone(), false).await?; - let java_17 = jre::find_filtered_jres("1.78", jres.clone(), false).await?; - let java_18plus = - jre::find_filtered_jres("1.18", jres.clone(), true).await?; - let java_globals = - autodetect_java_globals(java_8, java_17, java_18plus).await?; - st.settings.write().await.java_globals = java_globals; - - st.settings.write().await.max_concurrent_downloads = 50; - st.settings.write().await.hooks.post_exit = - Some("echo This is after Minecraft runs- global setting!".to_string()); - // Changed the settings, so need to reset the semaphore - st.reset_fetch_semaphore().await; - - // - // st.settings - // .write() - // .await - // .java_globals - // .insert(JAVA_8_KEY.to_string(), check_jre(path).await?.unwrap()); - // Clear profiles - println!("Clearing profiles."); + let state = crate::State::get().await?; + + // Set java globals to auto detected ones { - let h = profile::list(None).await?; - for (path, _) in h.into_iter() { - profile::remove(&path).await?; - } + let jres = crate::jre::get_all_jre().await?; + let java_8 = + crate::jre::find_filtered_jres("1.8", jres.clone(), false).await?; + let java_17 = + crate::jre::find_filtered_jres("1.17", jres.clone(), false).await?; + let java_18plus = + crate::jre::find_filtered_jres("1.18", jres.clone(), true).await?; + let java_globals = + crate::jre::autodetect_java_globals(java_8, java_17, java_18plus) + .await?; + state.settings.write().await.java_globals = java_globals; } + const ATLAUNCHER_FOLDER: &str = r"/home/thesuzerain/ATLauncher"; + const PRISM_FOLDER: &str = r"/home/thesuzerain/.local/share/PrismLauncher"; + const MMC_FOLDER: &str = r"/home/thesuzerain/MultiMC"; + const CURSEFORGE_FOLDER: &str = r"/home/thesuzerain/curseforge"; + const GD_LAUNCHER_FOLDER: &str = r"/home/thesuzerain/gdlauncher_next"; - println!("Creating/adding profile."); - - let name = "Example".to_string(); - let game_version = "1.19.2".to_string(); - let modloader = ModLoader::Vanilla; - let loader_version = "stable".to_string(); - - let profile_path = profile_create( - name.clone(), - game_version, - modloader, - Some(loader_version), - None, - None, - None, - None, - ) - .await?; - State::sync().await?; + test_batch_import(ATLAUNCHER_FOLDER, ImportLauncherType::ATLauncher).await?; + test_batch_import(PRISM_FOLDER, ImportLauncherType::PrismLauncher).await?; + test_batch_import(MMC_FOLDER, ImportLauncherType::MultiMC).await?; + test_batch_import(CURSEFORGE_FOLDER, ImportLauncherType::Curseforge).await?; + test_batch_import(GD_LAUNCHER_FOLDER, ImportLauncherType::GDLauncher).await?; - // Attempt to run game - if auth::users().await?.is_empty() { - println!("No users found, authenticating."); - authenticate_run().await?; // could take credentials from here direct, but also deposited in state users - } + // Iterate through all filenames in PRISM_FOLDER/instances + Ok(()) +} + +async fn test_batch_import(folder: &str, r#type : ImportLauncherType) -> theseus::Result<()> { + let instances = pack::import::get_importable_instances(r#type, folder.into()).await?; + for instance in instances { + println!("\n\n\nImporting {} for {:?}", instance, r#type); + let profile_path = profile_create::profile_create_from_creator( + CreatePackProfile::default(), + ) + .await + .unwrap(); - println!("running"); - // Run a profile, running minecraft and store the RwLock to the process - let proc_lock = profile::run(&canonicalize(&profile_path)?).await?; - let uuid = proc_lock.read().await.uuid; - let pid = proc_lock.read().await.current_child.read().await.id(); - - println!("Minecraft UUID: {}", uuid); - println!("Minecraft PID: {:?}", pid); - - // Wait 5 seconds - println!("Waiting 5 seconds to gather logs..."); - sleep(Duration::from_secs(5)).await; - let stdout = process::get_output_by_uuid(&uuid).await?; - println!("Logs after 5sec <<< {stdout} >>> end stdout"); - - println!( - "All running process UUID {:?}", - process::get_all_running_uuids().await? - ); - println!( - "All running process paths {:?}", - process::get_all_running_profile_paths().await? - ); - - // hold the lock to the process until it ends - println!("Waiting for process to end..."); - let mut proc = proc_lock.write().await; - process::wait_for(&mut proc).await?; + pack::import::import_instance(profile_path, r#type, PathBuf::from(folder), instance, None).await?; + println!("Completoooo"); + + } + println!("Done batch import."); Ok(()) } From 9e535877daf7816d8aada97ef1e7be0c1369fca1 Mon Sep 17 00:00:00 2001 From: Wyatt Date: Thu, 20 Jul 2023 08:31:41 -0700 Subject: [PATCH 04/10] added checkerg --- theseus/src/api/pack/import/atlauncher.rs | 11 +++++++ theseus/src/api/pack/import/curseforge.rs | 10 +++++++ theseus/src/api/pack/import/gdlauncher.rs | 10 +++++++ theseus/src/api/pack/import/mmc.rs | 26 ++++++++++------ theseus/src/api/pack/import/mod.rs | 36 +++++++++++++++++++++-- 5 files changed, 81 insertions(+), 12 deletions(-) diff --git a/theseus/src/api/pack/import/atlauncher.rs b/theseus/src/api/pack/import/atlauncher.rs index 7aff630ac..dca9b09c7 100644 --- a/theseus/src/api/pack/import/atlauncher.rs +++ b/theseus/src/api/pack/import/atlauncher.rs @@ -102,6 +102,17 @@ pub struct ATLauncherMod { pub modrinth_version: Option, } +// Check if folder has a instance.json that parses +pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool { + let instance: String = + fs::read_to_string(&instance_folder.join("instance.json")) + .await + .unwrap_or("".to_string()); + let instance: Result = + serde_json::from_str::(&instance); + instance.is_ok() +} + #[tracing::instrument] #[theseus_macros::debug_pin] pub async fn import_atlauncher( diff --git a/theseus/src/api/pack/import/curseforge.rs b/theseus/src/api/pack/import/curseforge.rs index 18fb7f4c8..66dab9800 100644 --- a/theseus/src/api/pack/import/curseforge.rs +++ b/theseus/src/api/pack/import/curseforge.rs @@ -35,6 +35,16 @@ pub struct MinecraftInstance { pub game_version : String, // Minecraft game version. Non-prioritized, use this if Vanilla } +// Check if folder has a minecraftinstance.json that parses +pub async fn is_valid_curseforge(instance_folder : PathBuf) -> bool { + let minecraftinstance: String = + fs::read_to_string(&instance_folder.join("minecraftinstance.json")) + .await + .unwrap_or("".to_string()); + let minecraftinstance: Result = serde_json::from_str::(&minecraftinstance); + minecraftinstance.is_ok() +} + pub async fn import_curseforge( curseforge_instance_folder: PathBuf, // instance's folder profile_path: PathBuf, // path to profile existing_loading_bar: Option, diff --git a/theseus/src/api/pack/import/gdlauncher.rs b/theseus/src/api/pack/import/gdlauncher.rs index 6f9a5ee0f..870bdf3c7 100644 --- a/theseus/src/api/pack/import/gdlauncher.rs +++ b/theseus/src/api/pack/import/gdlauncher.rs @@ -24,6 +24,16 @@ pub struct GDLauncherLoader { pub source_name: Option, } +// Check if folder has a config.json that parses +pub async fn is_valid_gdlauncher(instance_folder : PathBuf) -> bool { + let config: String = + fs::read_to_string(&instance_folder.join("config.json")) + .await + .unwrap_or("".to_string()); + let config: Result = serde_json::from_str::(&config); + config.is_ok() +} + pub async fn import_gdlauncher( gdlauncher_instance_folder: PathBuf, // instance's folder profile_path: PathBuf, // path to profile existing_loading_bar: Option, diff --git a/theseus/src/api/pack/import/mmc.rs b/theseus/src/api/pack/import/mmc.rs index a8bf45790..6056fdaf1 100644 --- a/theseus/src/api/pack/import/mmc.rs +++ b/theseus/src/api/pack/import/mmc.rs @@ -118,12 +118,19 @@ pub struct MMCComponentRequirement { pub suggests: Option, } -// Checks if if its a folder, and the folder contains instance.cfg and mmc-pack.json +// Checks if if its a folder, and the folder contains instance.cfg and mmc-pack.json, and they both parse #[tracing::instrument] -pub async fn is_mmc(path: PathBuf) -> bool { - let instance_cfg = path.join("instance.cfg"); - let mmc_pack = path.join("mmc-pack.json"); - instance_cfg.exists() && mmc_pack.exists() +pub async fn is_valid_mmc(instance_folder: PathBuf) -> bool { + let instance_cfg = instance_folder.join("instance.cfg"); + let mmc_pack = instance_folder.join("mmc-pack.json"); + + let mmc_pack = match fs::read_to_string(&mmc_pack).await { + Ok(mmc_pack) => mmc_pack, + Err(_) => return false, + }; + + load_instance_cfg(&instance_cfg).await.is_ok() && + serde_json::from_str::(&mmc_pack).is_ok() } // Loading the INI (instance.cfg) file @@ -176,6 +183,8 @@ pub async fn import_mmc( }; // Managed pack + let backup_name = "Imported Modpack".to_string(); + if instance_cfg.managed_pack.unwrap_or(false) { match instance_cfg.managed_pack_type { Some(MMCManagedPackType::Modrinth) => { @@ -185,15 +194,14 @@ pub async fn import_mmc( let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join(".minecraft"); import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack, existing_loading_bar).await?; } - Some(MMCManagedPackType::Flame) => { - // For flame managed packs + Some(MMCManagedPackType::Flame) | Some(MMCManagedPackType::ATLauncher) => { + // For flame/atlauncher managed packs // Treat as unmanaged, but with 'minecraft' folder instead of '.minecraft' - let backup_name = "Imported Flame Modpack".to_string(); let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join("minecraft"); import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack, existing_loading_bar).await?; }, Some(_) => { - // For managed packs that aren't modrinth or flame + // For managed packs that aren't modrinth, flame, atlauncher // Treat as unmanaged let backup_name = "ImportedModpack".to_string(); let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join(".minecraft"); diff --git a/theseus/src/api/pack/import/mod.rs b/theseus/src/api/pack/import/mod.rs index 9245bc398..a5f077d78 100644 --- a/theseus/src/api/pack/import/mod.rs +++ b/theseus/src/api/pack/import/mod.rs @@ -24,6 +24,8 @@ pub async fn get_importable_instances( launcher_type: ImportLauncherType, base_path: PathBuf, ) -> crate::Result> { + + // Some launchers have a different folder structure for instances let instances_folder = match launcher_type { ImportLauncherType::Modrinth => { todo!() @@ -44,9 +46,12 @@ pub async fn get_importable_instances( while let Some(entry) = dir.next_entry().await? { let path = entry.path(); if path.is_dir() { - let name = path.file_name(); - if let Some(name) = name { - instances.push(name.to_string_lossy().to_string()); + // Check instance is valid of this launcher type + if is_valid_importable_instance(path.clone(), launcher_type).await { + let name = path.file_name(); + if let Some(name) = name { + instances.push(name.to_string_lossy().to_string()); + } } } } @@ -141,6 +146,31 @@ pub async fn guess_launcher(filepath : &Path) -> crate::Result bool { + match r#type { + ImportLauncherType::Modrinth => { + todo!() + }, + ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => { + mmc::is_valid_mmc(instance_path).await + }, + ImportLauncherType::ATLauncher => { + atlauncher::is_valid_atlauncher(instance_path).await + }, + ImportLauncherType::GDLauncher => { + gdlauncher::is_valid_gdlauncher(instance_path).await + }, + ImportLauncherType::Curseforge => { + curseforge::is_valid_curseforge(instance_path).await + }, + ImportLauncherType::Unknown => { + todo!() + } + } +} /// Caches an image file in the filesystem into the cache directory, and returns the path to the cached file. #[theseus_macros::debug_pin] From 56d6aae3648a00cd5f01f8440f8e811a75490007 Mon Sep 17 00:00:00 2001 From: Wyatt Date: Thu, 20 Jul 2023 08:46:06 -0700 Subject: [PATCH 05/10] fixed io merge issue --- theseus/src/api/pack/import/atlauncher.rs | 5 +- theseus/src/api/pack/import/curseforge.rs | 208 +++++++++++----------- theseus/src/api/pack/import/gdlauncher.rs | 100 ++++++----- theseus/src/api/pack/import/mmc.rs | 11 +- theseus/src/api/pack/import/mod.rs | 154 ++++++++++------ theseus/src/util/io.rs | 15 ++ theseus_playground/src/main.rs | 32 ++-- 7 files changed, 306 insertions(+), 219 deletions(-) diff --git a/theseus/src/api/pack/import/atlauncher.rs b/theseus/src/api/pack/import/atlauncher.rs index dca9b09c7..b5dbb05bb 100644 --- a/theseus/src/api/pack/import/atlauncher.rs +++ b/theseus/src/api/pack/import/atlauncher.rs @@ -12,6 +12,7 @@ use crate::{ }, prelude::ModLoader, state::{LinkedData, ProfileInstallStage}, + util::io, State, }; @@ -127,7 +128,7 @@ pub async fn import_atlauncher( // Load instance.json let atinstance: String = - fs::read_to_string(&atlauncher_instance_path.join("instance.json")) + io::read_to_string(&atlauncher_instance_path.join("instance.json")) .await?; let atinstance: ATInstance = serde_json::from_str::(&atinstance)?; @@ -173,7 +174,7 @@ pub async fn import_atlauncher( existing_loading_bar, ) .await?; -println!("Done."); + println!("Done."); Ok(()) } diff --git a/theseus/src/api/pack/import/curseforge.rs b/theseus/src/api/pack/import/curseforge.rs index 66dab9800..63d5d09d2 100644 --- a/theseus/src/api/pack/import/curseforge.rs +++ b/theseus/src/api/pack/import/curseforge.rs @@ -1,153 +1,161 @@ use std::path::PathBuf; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use tokio::fs; -use crate::{event::LoadingBarId, pack::install_from::CreatePackDescription, prelude::ModLoader, state::ProfileInstallStage, State}; +use crate::{ + event::LoadingBarId, prelude::ModLoader, state::ProfileInstallStage, + util::io, State, +}; -use super::{copy_dir_to, copy_dotminecraft}; +use super::copy_dotminecraft; #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FlameManifest { - pub manifest_version : u8, - pub name : String, - pub minecraft : FlameMinecraft, + pub manifest_version: u8, + pub name: String, + pub minecraft: FlameMinecraft, } #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FlameMinecraft { - pub version : String, - pub mod_loaders : Vec, - + pub version: String, + pub mod_loaders: Vec, } #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FlameModLoader { - pub id : String, - pub primary : bool + pub id: String, + pub primary: bool, } #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MinecraftInstance { - pub name : Option, - pub game_version : String, // Minecraft game version. Non-prioritized, use this if Vanilla + pub name: Option, + pub game_version: String, // Minecraft game version. Non-prioritized, use this if Vanilla } // Check if folder has a minecraftinstance.json that parses -pub async fn is_valid_curseforge(instance_folder : PathBuf) -> bool { +pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool { let minecraftinstance: String = fs::read_to_string(&instance_folder.join("minecraftinstance.json")) .await .unwrap_or("".to_string()); - let minecraftinstance: Result = serde_json::from_str::(&minecraftinstance); + let minecraftinstance: Result = + serde_json::from_str::(&minecraftinstance); minecraftinstance.is_ok() } -pub async fn import_curseforge( curseforge_instance_folder: PathBuf, // instance's folder - profile_path: PathBuf, // path to profile +pub async fn import_curseforge( + curseforge_instance_folder: PathBuf, // instance's folder + profile_path: PathBuf, // path to profile existing_loading_bar: Option, -) -> crate::Result<()> { +) -> crate::Result<()> { // TODO: recache curseforge instance icon let icon: Option = None; - + // Load minecraftinstance.json - let minecraft_instance: String = - fs::read_to_string(&curseforge_instance_folder.join("minecraftinstance.json")) - .await?; - let minecraft_instance: MinecraftInstance = serde_json::from_str::(&minecraft_instance)?; + let minecraft_instance: String = io::read_to_string( + &curseforge_instance_folder.join("minecraftinstance.json"), + ) + .await?; + let minecraft_instance: MinecraftInstance = + serde_json::from_str::(&minecraft_instance)?; let override_title: Option = minecraft_instance.name.clone(); - let backup_name = format!("Curseforge-{}", curseforge_instance_folder.file_name().map(|a| a.to_string_lossy().to_string()).unwrap_or("Unknown".to_string())); - + let backup_name = format!( + "Curseforge-{}", + curseforge_instance_folder + .file_name() + .map(|a| a.to_string_lossy().to_string()) + .unwrap_or("Unknown".to_string()) + ); // Curseforge vanilla profile may not have a manifest.json, so we allow it to not exist if curseforge_instance_folder.join("manifest.json").exists() { - // Load manifest.json - let cf_manifest: String = - fs::read_to_string(&curseforge_instance_folder.join("manifest.json")) - .await?; + // Load manifest.json + let cf_manifest: String = io::read_to_string( + &curseforge_instance_folder.join("manifest.json"), + ) + .await?; let cf_manifest: FlameManifest = - serde_json::from_str::(&cf_manifest)?; - - let game_version = cf_manifest.minecraft.version; - - // CF allows Forge, Fabric, and Vanilla - let mut mod_loader = None; - let mut loader_version = None; - for loader in cf_manifest.minecraft.mod_loaders { - match loader.id.split_once('-') { - Some(("forge", version)) => { - mod_loader = Some(ModLoader::Forge); - loader_version = Some(version.to_string()); - }, - Some(("fabric", version)) => { - mod_loader = Some(ModLoader::Fabric); - loader_version = Some(version.to_string()); + serde_json::from_str::(&cf_manifest)?; + + let game_version = cf_manifest.minecraft.version; + + // CF allows Forge, Fabric, and Vanilla + let mut mod_loader = None; + let mut loader_version = None; + for loader in cf_manifest.minecraft.mod_loaders { + match loader.id.split_once('-') { + Some(("forge", version)) => { + mod_loader = Some(ModLoader::Forge); + loader_version = Some(version.to_string()); + } + Some(("fabric", version)) => { + mod_loader = Some(ModLoader::Fabric); + loader_version = Some(version.to_string()); + } + _ => {} } - _ => {}, } - } - let mod_loader = mod_loader.unwrap_or(ModLoader::Vanilla); - - let loader_version = if mod_loader != ModLoader::Vanilla { - crate::profile_create::get_loader_version_from_loader( - game_version.clone(), - mod_loader, - loader_version, - ) - .await? - } else { - None - }; - -// Set profile data to created default profile -crate::api::profile::edit(&profile_path, |prof| { - prof.metadata.name = override_title - .clone() - .unwrap_or_else(|| backup_name.to_string()); - prof.install_stage = ProfileInstallStage::PackInstalling; - prof.metadata.icon = icon.clone(); - prof.metadata.game_version = game_version.clone(); - prof.metadata.loader_version = loader_version.clone(); - prof.metadata.loader = mod_loader; - - async { Ok(()) } -}) -.await?; - + let mod_loader = mod_loader.unwrap_or(ModLoader::Vanilla); + + let loader_version = if mod_loader != ModLoader::Vanilla { + crate::profile_create::get_loader_version_from_loader( + game_version.clone(), + mod_loader, + loader_version, + ) + .await? + } else { + None + }; + + // Set profile data to created default profile + crate::api::profile::edit(&profile_path, |prof| { + prof.metadata.name = override_title + .clone() + .unwrap_or_else(|| backup_name.to_string()); + prof.install_stage = ProfileInstallStage::PackInstalling; + prof.metadata.icon = icon.clone(); + prof.metadata.game_version = game_version.clone(); + prof.metadata.loader_version = loader_version.clone(); + prof.metadata.loader = mod_loader; + + async { Ok(()) } + }) + .await?; } else { // If no manifest is found, it's a vanilla profile -crate::api::profile::edit(&profile_path, |prof| { - prof.metadata.name = override_title - .clone() - .unwrap_or_else(|| backup_name.to_string()); - prof.metadata.icon = icon.clone(); - prof.metadata.game_version = minecraft_instance.game_version.clone(); - prof.metadata.loader_version = None; - prof.metadata.loader = ModLoader::Vanilla; - - async { Ok(()) } -}) -.await?; - + crate::api::profile::edit(&profile_path, |prof| { + prof.metadata.name = override_title + .clone() + .unwrap_or_else(|| backup_name.to_string()); + prof.metadata.icon = icon.clone(); + prof.metadata.game_version = + minecraft_instance.game_version.clone(); + prof.metadata.loader_version = None; + prof.metadata.loader = ModLoader::Vanilla; + + async { Ok(()) } + }) + .await?; } -// Copy in contained folders as overrides -copy_dotminecraft(profile_path.clone(), curseforge_instance_folder).await?; + // Copy in contained folders as overrides + copy_dotminecraft(profile_path.clone(), curseforge_instance_folder).await?; -if let Some(profile_val) = - crate::api::profile::get(&profile_path, None).await? -{ - crate::launcher::install_minecraft(&profile_val, existing_loading_bar) - .await?; + if let Some(profile_val) = + crate::api::profile::get(&profile_path, None).await? + { + crate::launcher::install_minecraft(&profile_val, existing_loading_bar) + .await?; - State::sync().await?; -} + State::sync().await?; + } Ok(()) - - } - diff --git a/theseus/src/api/pack/import/gdlauncher.rs b/theseus/src/api/pack/import/gdlauncher.rs index 870bdf3c7..82c5170bf 100644 --- a/theseus/src/api/pack/import/gdlauncher.rs +++ b/theseus/src/api/pack/import/gdlauncher.rs @@ -3,14 +3,17 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; use tokio::fs; -use crate::{event::LoadingBarId, prelude::ModLoader, state::ProfileInstallStage, State}; +use crate::{ + event::LoadingBarId, prelude::ModLoader, state::ProfileInstallStage, + util::io, State, +}; -use super::{copy_dir_to, recache_icon, copy_dotminecraft}; +use super::{copy_dotminecraft, recache_icon}; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct GDLauncherConfig { - pub background : Option, + pub background: Option, pub loader: GDLauncherLoader, // pub mods: Vec, } @@ -18,37 +21,48 @@ pub struct GDLauncherConfig { #[serde(rename_all = "camelCase")] pub struct GDLauncherLoader { pub loader_type: ModLoader, - pub loader_version : Option, + pub loader_version: Option, pub mc_version: String, pub source: Option, pub source_name: Option, } // Check if folder has a config.json that parses -pub async fn is_valid_gdlauncher(instance_folder : PathBuf) -> bool { +pub async fn is_valid_gdlauncher(instance_folder: PathBuf) -> bool { let config: String = fs::read_to_string(&instance_folder.join("config.json")) .await .unwrap_or("".to_string()); - let config: Result = serde_json::from_str::(&config); + let config: Result = + serde_json::from_str::(&config); config.is_ok() } -pub async fn import_gdlauncher( gdlauncher_instance_folder: PathBuf, // instance's folder - profile_path: PathBuf, // path to profile +pub async fn import_gdlauncher( + gdlauncher_instance_folder: PathBuf, // instance's folder + profile_path: PathBuf, // path to profile existing_loading_bar: Option, -) -> crate::Result<()> { - +) -> crate::Result<()> { // Load config.json let config: String = - fs::read_to_string(&gdlauncher_instance_folder.join("config.json")) + io::read_to_string(&gdlauncher_instance_folder.join("config.json")) .await?; - let config: GDLauncherConfig = serde_json::from_str::(&config)?; + let config: GDLauncherConfig = + serde_json::from_str::(&config)?; let override_title: Option = config.loader.source_name.clone(); - let backup_name = format!("GDLauncher-{}", gdlauncher_instance_folder.file_name().map(|a| a.to_string_lossy().to_string()).unwrap_or("Unknown".to_string())); - -// Re-cache icon - let icon = config.background.clone().map(|b| gdlauncher_instance_folder.join(b)); + let backup_name = format!( + "GDLauncher-{}", + gdlauncher_instance_folder + .file_name() + .map(|a| a.to_string_lossy().to_string()) + .unwrap_or("Unknown".to_string()) + ); + + // Re-cache icon + let icon = config + .background + .clone() + .map(|b| gdlauncher_instance_folder.join(b)); let icon = if let Some(icon) = icon { recache_icon(icon).await? } else { @@ -70,36 +84,32 @@ pub async fn import_gdlauncher( gdlauncher_instance_folder: PathBuf, // insta None }; -// Set profile data to created default profile -crate::api::profile::edit(&profile_path, |prof| { - prof.metadata.name = override_title - .clone() - .unwrap_or_else(|| backup_name.to_string()); - prof.install_stage = ProfileInstallStage::PackInstalling; - prof.metadata.icon = icon.clone(); - prof.metadata.game_version = game_version.clone(); - prof.metadata.loader_version = loader_version.clone(); - prof.metadata.loader = mod_loader; - - async { Ok(()) } -}) -.await?; - - -// Copy in contained folders as overrides -copy_dotminecraft(profile_path.clone(), gdlauncher_instance_folder).await?; - -if let Some(profile_val) = - crate::api::profile::get(&profile_path, None).await? -{ - crate::launcher::install_minecraft(&profile_val, existing_loading_bar) - .await?; + // Set profile data to created default profile + crate::api::profile::edit(&profile_path, |prof| { + prof.metadata.name = override_title + .clone() + .unwrap_or_else(|| backup_name.to_string()); + prof.install_stage = ProfileInstallStage::PackInstalling; + prof.metadata.icon = icon.clone(); + prof.metadata.game_version = game_version.clone(); + prof.metadata.loader_version = loader_version.clone(); + prof.metadata.loader = mod_loader; + + async { Ok(()) } + }) + .await?; + + // Copy in contained folders as overrides + copy_dotminecraft(profile_path.clone(), gdlauncher_instance_folder).await?; + + if let Some(profile_val) = + crate::api::profile::get(&profile_path, None).await? + { + crate::launcher::install_minecraft(&profile_val, existing_loading_bar) + .await?; - State::sync().await?; -} + State::sync().await?; + } Ok(()) - - } - diff --git a/theseus/src/api/pack/import/mmc.rs b/theseus/src/api/pack/import/mmc.rs index 6056fdaf1..904b5b0aa 100644 --- a/theseus/src/api/pack/import/mmc.rs +++ b/theseus/src/api/pack/import/mmc.rs @@ -9,6 +9,7 @@ use crate::{ import::{self, copy_dotminecraft}, install_from::{self, CreatePackDescription, PackDependency}, }, + util::io, State, }; @@ -128,14 +129,14 @@ pub async fn is_valid_mmc(instance_folder: PathBuf) -> bool { Ok(mmc_pack) => mmc_pack, Err(_) => return false, }; - - load_instance_cfg(&instance_cfg).await.is_ok() && - serde_json::from_str::(&mmc_pack).is_ok() + + load_instance_cfg(&instance_cfg).await.is_ok() + && serde_json::from_str::(&mmc_pack).is_ok() } // Loading the INI (instance.cfg) file async fn load_instance_cfg(file_path: &Path) -> crate::Result { - let instance_cfg = fs::read_to_string(file_path).await?; + let instance_cfg = io::read_to_string(file_path).await?; let instance_cfg_enum: MMCInstanceEnum = serde_ini::from_str::(&instance_cfg)?; match instance_cfg_enum { @@ -157,7 +158,7 @@ pub async fn import_mmc( .join(instance_folder.clone()); let mmc_pack = - fs::read_to_string(&mmc_instance_path.join("mmc-pack.json")).await?; + io::read_to_string(&mmc_instance_path.join("mmc-pack.json")).await?; let mmc_pack: MMCPack = serde_json::from_str::(&mmc_pack)?; let instance_cfg = diff --git a/theseus/src/api/pack/import/mod.rs b/theseus/src/api/pack/import/mod.rs index a5f077d78..b86aa5a2f 100644 --- a/theseus/src/api/pack/import/mod.rs +++ b/theseus/src/api/pack/import/mod.rs @@ -1,8 +1,11 @@ use std::path::{Path, PathBuf}; -use tokio::fs; +use io::IOError; -use crate::{util::fetch, event::LoadingBarId}; +use crate::{ + event::LoadingBarId, + util::{fetch, io}, +}; pub mod atlauncher; pub mod curseforge; @@ -17,33 +20,38 @@ pub enum ImportLauncherType { ATLauncher, GDLauncher, Curseforge, - Unknown + Unknown, } -pub async fn get_importable_instances( +pub async fn get_importable_instances( launcher_type: ImportLauncherType, base_path: PathBuf, ) -> crate::Result> { - // Some launchers have a different folder structure for instances let instances_folder = match launcher_type { ImportLauncherType::Modrinth => { todo!() - }, - ImportLauncherType::GDLauncher | ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher | ImportLauncherType::ATLauncher => { - base_path.join("instances") - }, - ImportLauncherType::Curseforge => { - base_path.join("Instances") - }, + } + ImportLauncherType::GDLauncher + | ImportLauncherType::MultiMC + | ImportLauncherType::PrismLauncher + | ImportLauncherType::ATLauncher => base_path.join("instances"), + ImportLauncherType::Curseforge => base_path.join("Instances"), ImportLauncherType::Unknown => { todo!() } }; - println!("Searching {:?} - instances_folder: {:?}", launcher_type, instances_folder); + println!( + "Searching {:?} - instances_folder: {:?}", + launcher_type, instances_folder + ); let mut instances = Vec::new(); - let mut dir = fs::read_dir(instances_folder).await?; - while let Some(entry) = dir.next_entry().await? { + let mut dir = io::read_dir(&instances_folder).await?; + while let Some(entry) = dir + .next_entry() + .await + .map_err(|e| IOError::with_path(e, &instances_folder))? + { let path = entry.path(); if path.is_dir() { // Check instance is valid of this launcher type @@ -63,43 +71,46 @@ pub async fn import_instance( launcher_type: ImportLauncherType, base_path: PathBuf, instance_folder: String, - existing_loading_bar: Option + existing_loading_bar: Option, ) -> crate::Result<()> { match launcher_type { ImportLauncherType::Modrinth => { todo!() - }, - ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => { + } + ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => { mmc::import_mmc( - base_path, // path to base mmc folder + base_path, // path to base mmc folder instance_folder, // instance folder in mmc_base_path - profile_path, // path to profile + profile_path, // path to profile existing_loading_bar, - ).await?; - }, + ) + .await?; + } ImportLauncherType::ATLauncher => { atlauncher::import_atlauncher( - base_path, // path to atlauncher folder + base_path, // path to atlauncher folder instance_folder, // instance folder in atlauncher - profile_path, // path to profile + profile_path, // path to profile existing_loading_bar, - ).await?; - }, + ) + .await?; + } ImportLauncherType::GDLauncher => { gdlauncher::import_gdlauncher( - base_path.join("instances").join(instance_folder), // path to gdlauncher folder - profile_path, // path to profile + base_path.join("instances").join(instance_folder), // path to gdlauncher folder + profile_path, // path to profile existing_loading_bar, - ).await?; - }, + ) + .await?; + } ImportLauncherType::Curseforge => { - curseforge::import_curseforge( base_path.join("Instances").join(instance_folder), // path to curseforge folder - profile_path, // path to profile + profile_path, // path to profile existing_loading_bar, - ).await?; - }, + ) + .await?; + } ImportLauncherType::Unknown => { todo!() } @@ -107,14 +118,20 @@ pub async fn import_instance( Ok(()) } -pub async fn guess_launcher(filepath : &Path) -> crate::Result { +pub async fn guess_launcher( + filepath: &Path, +) -> crate::Result { // search filepath for each launcher type // if found, return that launcher type // if not found, return unknown let mut found_type = ImportLauncherType::Unknown; // search path as string for mmc - if filepath.to_string_lossy().to_lowercase().contains("multimc") { + if filepath + .to_string_lossy() + .to_lowercase() + .contains("multimc") + { found_type = ImportLauncherType::MultiMC; } @@ -124,22 +141,38 @@ pub async fn guess_launcher(filepath : &Path) -> crate::Result crate::Result bool { +pub async fn is_valid_importable_instance( + instance_path: PathBuf, + r#type: ImportLauncherType, +) -> bool { match r#type { ImportLauncherType::Modrinth => { todo!() - }, - ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => { + } + ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => { mmc::is_valid_mmc(instance_path).await - }, + } ImportLauncherType::ATLauncher => { atlauncher::is_valid_atlauncher(instance_path).await - }, + } ImportLauncherType::GDLauncher => { gdlauncher::is_valid_gdlauncher(instance_path).await - }, + } ImportLauncherType::Curseforge => { curseforge::is_valid_curseforge(instance_path).await - }, + } ImportLauncherType::Unknown => { todo!() } @@ -206,8 +242,12 @@ async fn copy_dotminecraft( dotminecraft: PathBuf, ) -> crate::Result<()> { // std fs copy every file in dotminecraft to profile_path - for entry in std::fs::read_dir(dotminecraft)? { - let entry = entry?; + let mut dir = io::read_dir(&dotminecraft).await?; + while let Some(entry) = dir + .next_entry() + .await + .map_err(|e| IOError::with_path(e, &dotminecraft))? + { let path = entry.path(); copy_dir_to( &path, @@ -230,16 +270,20 @@ async fn copy_dotminecraft( #[tracing::instrument] async fn copy_dir_to(src: &Path, dst: &Path) -> crate::Result<()> { if !src.is_dir() { - fs::copy(src, dst).await?; + io::copy(src, dst).await?; return Ok(()); } // Create the destination directory - fs::create_dir_all(&dst).await?; + io::create_dir_all(&dst).await?; // Iterate over the directory - let mut dir = fs::read_dir(src).await?; - while let Some(child) = dir.next_entry().await? { + let mut dir = io::read_dir(&src).await?; + while let Some(child) = dir + .next_entry() + .await + .map_err(|e| IOError::with_path(e, src))? + { let src_child = child.path(); let dst_child = dst.join(src_child.file_name().ok_or_else(|| { crate::ErrorKind::InputError(format!( @@ -248,12 +292,12 @@ async fn copy_dir_to(src: &Path, dst: &Path) -> crate::Result<()> { )) })?); - if child.metadata().await?.is_dir() { + if src_child.is_dir() { // Recurse into sub-directory copy_dir_to(&src_child, &dst_child).await?; } else { // Copy file - fs::copy(&src_child, &dst_child).await?; + io::copy(&src_child, &dst_child).await?; } } diff --git a/theseus/src/util/io.rs b/theseus/src/util/io.rs index 11162e94a..b915372a4 100644 --- a/theseus/src/util/io.rs +++ b/theseus/src/util/io.rs @@ -135,6 +135,21 @@ pub async fn rename( }) } +// copy +pub async fn copy( + from: impl AsRef, + to: impl AsRef, +) -> Result { + let from = from.as_ref(); + let to = to.as_ref(); + tokio::fs::copy(from, to) + .await + .map_err(|e| IOError::IOPathError { + source: e, + path: from.to_string_lossy().to_string(), + }) +} + // remove file pub async fn remove_file( path: impl AsRef, diff --git a/theseus_playground/src/main.rs b/theseus_playground/src/main.rs index 6710aba35..a74e07d27 100644 --- a/theseus_playground/src/main.rs +++ b/theseus_playground/src/main.rs @@ -6,13 +6,9 @@ use std::path::PathBuf; use theseus::pack::import::ImportLauncherType; -use theseus::pack::import::atlauncher::import_atlauncher; -use theseus::pack::import::curseforge::import_curseforge; use theseus::pack::install_from::CreatePackProfile; use theseus::prelude::*; -use tokio::fs; - // A simple Rust implementation of the authentication run // 1) call the authenticate_begin_flow() function to get the URL to open (like you would in the frontend) // 2) open the URL in a browser @@ -61,19 +57,25 @@ async fn main() -> theseus::Result<()> { const CURSEFORGE_FOLDER: &str = r"/home/thesuzerain/curseforge"; const GD_LAUNCHER_FOLDER: &str = r"/home/thesuzerain/gdlauncher_next"; - - test_batch_import(ATLAUNCHER_FOLDER, ImportLauncherType::ATLauncher).await?; + test_batch_import(ATLAUNCHER_FOLDER, ImportLauncherType::ATLauncher) + .await?; test_batch_import(PRISM_FOLDER, ImportLauncherType::PrismLauncher).await?; test_batch_import(MMC_FOLDER, ImportLauncherType::MultiMC).await?; - test_batch_import(CURSEFORGE_FOLDER, ImportLauncherType::Curseforge).await?; - test_batch_import(GD_LAUNCHER_FOLDER, ImportLauncherType::GDLauncher).await?; + test_batch_import(CURSEFORGE_FOLDER, ImportLauncherType::Curseforge) + .await?; + test_batch_import(GD_LAUNCHER_FOLDER, ImportLauncherType::GDLauncher) + .await?; // Iterate through all filenames in PRISM_FOLDER/instances Ok(()) } -async fn test_batch_import(folder: &str, r#type : ImportLauncherType) -> theseus::Result<()> { - let instances = pack::import::get_importable_instances(r#type, folder.into()).await?; +async fn test_batch_import( + folder: &str, + r#type: ImportLauncherType, +) -> theseus::Result<()> { + let instances = + pack::import::get_importable_instances(r#type, folder.into()).await?; for instance in instances { println!("\n\n\nImporting {} for {:?}", instance, r#type); let profile_path = profile_create::profile_create_from_creator( @@ -82,9 +84,15 @@ async fn test_batch_import(folder: &str, r#type : ImportLauncherType) -> theseus .await .unwrap(); - pack::import::import_instance(profile_path, r#type, PathBuf::from(folder), instance, None).await?; + pack::import::import_instance( + profile_path, + r#type, + PathBuf::from(folder), + instance, + None, + ) + .await?; println!("Completoooo"); - } println!("Done batch import."); From 69cbcf068dab49cf3986cc0ec0795a9f20f98e42 Mon Sep 17 00:00:00 2001 From: Wyatt Date: Thu, 20 Jul 2023 10:00:03 -0700 Subject: [PATCH 06/10] Added api handling --- theseus/src/api/pack/import/mod.rs | 118 ++++++++++-------------- theseus/src/event/mod.rs | 5 + theseus_gui/src-tauri/src/api/import.rs | 70 ++++++++++++++ theseus_gui/src-tauri/src/api/mod.rs | 1 + theseus_gui/src-tauri/src/main.rs | 1 + theseus_gui/src/helpers/import.js | 52 +++++++++++ theseus_playground/src/main.rs | 4 +- 7 files changed, 178 insertions(+), 73 deletions(-) create mode 100644 theseus_gui/src-tauri/src/api/import.rs create mode 100644 theseus_gui/src/helpers/import.js diff --git a/theseus/src/api/pack/import/mod.rs b/theseus/src/api/pack/import/mod.rs index b86aa5a2f..3a1c0f7c4 100644 --- a/theseus/src/api/pack/import/mod.rs +++ b/theseus/src/api/pack/import/mod.rs @@ -1,10 +1,12 @@ use std::path::{Path, PathBuf}; use io::IOError; +use serde::{Deserialize, Serialize}; use crate::{ - event::LoadingBarId, + event::{emit::init_or_edit_loading, LoadingBarId}, util::{fetch, io}, + LoadingBarType, }; pub mod atlauncher; @@ -12,9 +14,8 @@ pub mod curseforge; pub mod gdlauncher; pub mod mmc; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum ImportLauncherType { - Modrinth, MultiMC, PrismLauncher, ATLauncher, @@ -29,9 +30,6 @@ pub async fn get_importable_instances( ) -> crate::Result> { // Some launchers have a different folder structure for instances let instances_folder = match launcher_type { - ImportLauncherType::Modrinth => { - todo!() - } ImportLauncherType::GDLauncher | ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher @@ -66,6 +64,8 @@ pub async fn get_importable_instances( Ok(instances) } +#[theseus_macros::debug_pin] +#[tracing::instrument] pub async fn import_instance( profile_path: PathBuf, launcher_type: ImportLauncherType, @@ -73,10 +73,21 @@ pub async fn import_instance( instance_folder: String, existing_loading_bar: Option, ) -> crate::Result<()> { + let existing_loading_bar = Some( + init_or_edit_loading( + existing_loading_bar, + LoadingBarType::PackImport { + profile_path: profile_path.clone(), + instance_path: base_path.clone(), + instance_name: Some(instance_folder.clone()), + }, + 100.0, + "Downloading java version", + ) + .await?, + ); + match launcher_type { - ImportLauncherType::Modrinth => { - todo!() - } ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => { mmc::import_mmc( base_path, // path to base mmc folder @@ -118,68 +129,36 @@ pub async fn import_instance( Ok(()) } -pub async fn guess_launcher( - filepath: &Path, -) -> crate::Result { - // search filepath for each launcher type - // if found, return that launcher type - // if not found, return unknown - let mut found_type = ImportLauncherType::Unknown; - - // search path as string for mmc - if filepath - .to_string_lossy() - .to_lowercase() - .contains("multimc") - { - found_type = ImportLauncherType::MultiMC; - } - - // search path as string for prism - if filepath.to_string_lossy().to_lowercase().contains("prism") { - found_type = ImportLauncherType::PrismLauncher; - } - - // search path as string for atlauncher - if filepath - .to_string_lossy() - .to_lowercase() - .contains("atlauncher") - { - found_type = ImportLauncherType::ATLauncher; - } - - // search path as string for curseforge - if filepath - .to_string_lossy() - .to_lowercase() - .contains("curseforge") - { - found_type = ImportLauncherType::Curseforge; - } - - // search path as string for modrinth - if filepath - .to_string_lossy() - .to_lowercase() - .contains("modrinth") - { - found_type = ImportLauncherType::Modrinth; - } - - // search path as string for gdlauncher - if filepath - .to_string_lossy() - .to_lowercase() - .contains("gdlauncher") - { - found_type = ImportLauncherType::GDLauncher; +/// Returns the default path for the given launcher type +/// None if it can't be found or doesn't exist +pub fn get_default_launcher_path( + r#type: ImportLauncherType, +) -> Option { + let path = match r#type { + ImportLauncherType::MultiMC => None, // multimc data is *in* app dir + ImportLauncherType::PrismLauncher => { + Some(dirs::data_dir()?.join("PrismLauncher")) + } + ImportLauncherType::ATLauncher => { + Some(dirs::data_dir()?.join("ATLauncher")) + } + ImportLauncherType::GDLauncher => { + Some(dirs::data_dir()?.join("gdlauncher_next")) + } + ImportLauncherType::Curseforge => { + Some(dirs::home_dir()?.join("curseforge").join("minecraft")) + } + ImportLauncherType::Unknown => None, + }; + let path = path?; + if path.exists() { + Some(path) + } else { + None } - - Ok(found_type) } -// Checks if this PathBuf is a valid instance for the given launcher type +/// Checks if this PathBuf is a valid instance for the given launcher type #[theseus_macros::debug_pin] #[tracing::instrument] pub async fn is_valid_importable_instance( @@ -187,9 +166,6 @@ pub async fn is_valid_importable_instance( r#type: ImportLauncherType, ) -> bool { match r#type { - ImportLauncherType::Modrinth => { - todo!() - } ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => { mmc::is_valid_mmc(instance_path).await } diff --git a/theseus/src/event/mod.rs b/theseus/src/event/mod.rs index f3b114fd4..f6115bfd4 100644 --- a/theseus/src/event/mod.rs +++ b/theseus/src/event/mod.rs @@ -169,6 +169,11 @@ pub enum LoadingBarType { pack_id: Option, pack_version: Option, }, + PackImport { + profile_path: PathBuf, + instance_path: PathBuf, + instance_name: Option, + }, MinecraftDownload { profile_path: PathBuf, profile_name: String, diff --git a/theseus_gui/src-tauri/src/api/import.rs b/theseus_gui/src-tauri/src/api/import.rs new file mode 100644 index 000000000..394721332 --- /dev/null +++ b/theseus_gui/src-tauri/src/api/import.rs @@ -0,0 +1,70 @@ +use std::path::PathBuf; + +use crate::api::Result; +use theseus::pack::import::ImportLauncherType; + +use theseus::pack::import; + +pub fn init() -> tauri::plugin::TauriPlugin { + tauri::plugin::Builder::new("import") + .invoke_handler(tauri::generate_handler![ + import_get_importable_instances, + import_import_instance, + import_is_valid_importable_instance, + import_get_default_launcher_path, + ]) + .build() +} + +/// Gets a list of importable instances from a launcher type and base path +/// eg: get_importable_instances(ImportLauncherType::MultiMC, PathBuf::from("C:/MultiMC")) +/// returns ["Instance 1", "Instance 2"] +#[tauri::command] +pub async fn import_get_importable_instances( + launcher_type: ImportLauncherType, + base_path: PathBuf, +) -> Result> { + Ok(import::get_importable_instances(launcher_type, base_path).await?) +} + +/// Import an instance from a launcher type and base path +/// eg: import_instance(ImportLauncherType::MultiMC, PathBuf::from("C:/MultiMC"), "Instance 1") +#[tauri::command] +pub async fn import_import_instance( + profile_path: PathBuf, + launcher_type: ImportLauncherType, + base_path: PathBuf, + instance_folder: String, +) -> Result<()> { + import::import_instance( + profile_path, + launcher_type, + base_path, + instance_folder, + None, + ) + .await?; + Ok(()) +} + +/// Checks if this instance is valid for importing, given a certain launcher type +/// eg: is_valid_importable_instance(PathBuf::from("C:/MultiMC/Instance 1"), ImportLauncherType::MultiMC) +#[tauri::command] +pub async fn import_is_valid_importable_instance( + instance_folder: PathBuf, + launcher_type: ImportLauncherType, +) -> Result { + Ok( + import::is_valid_importable_instance(instance_folder, launcher_type) + .await, + ) +} + +/// Returns the default path for the given launcher type +/// None if it can't be found or doesn't exist +#[tauri::command] +pub async fn import_get_default_launcher_path( + launcher_type: ImportLauncherType, +) -> Result> { + Ok(import::get_default_launcher_path(launcher_type)) +} diff --git a/theseus_gui/src-tauri/src/api/mod.rs b/theseus_gui/src-tauri/src/api/mod.rs index 31ff2a90a..2bfb0c966 100644 --- a/theseus_gui/src-tauri/src/api/mod.rs +++ b/theseus_gui/src-tauri/src/api/mod.rs @@ -3,6 +3,7 @@ use serde::{Serialize, Serializer}; use thiserror::Error; pub mod auth; +pub mod import; pub mod jre; pub mod logs; pub mod metadata; diff --git a/theseus_gui/src-tauri/src/main.rs b/theseus_gui/src-tauri/src/main.rs index 99d90b9c1..d04d50dc4 100644 --- a/theseus_gui/src-tauri/src/main.rs +++ b/theseus_gui/src-tauri/src/main.rs @@ -118,6 +118,7 @@ fn main() { } let builder = builder .plugin(api::auth::init()) + .plugin(api::import::init()) .plugin(api::logs::init()) .plugin(api::jre::init()) .plugin(api::metadata::init()) diff --git a/theseus_gui/src/helpers/import.js b/theseus_gui/src/helpers/import.js new file mode 100644 index 000000000..cb1f46d50 --- /dev/null +++ b/theseus_gui/src/helpers/import.js @@ -0,0 +1,52 @@ +/** + * All theseus API calls return serialized values (both return values and errors); + * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, + * and deserialized into a usable JS object. + */ +import { invoke } from '@tauri-apps/api/tauri' + +/* + API for importing instances from other launchers + launcherType can be one of the following: + - MultiMC + - GDLauncher + - ATLauncher + - Curseforge + - PrismLauncher + - Unknown (shouldn't be used, but is used internally if the launcher type isn't recognized) + +*/ + +/// Gets a list of importable instances from a launcher type and base path +/// eg: get_importable_instances("MultiMC", "C:/MultiMC") +/// returns ["Instance 1", "Instance 2"] +export async function get_importable_instances(launcherType, basePath) { + return await invoke('plugin:import|import_get_importable_instances', { launcherType, basePath }) +} + +/// Import an instance from a launcher type and base path +/// eg: import_instance("profile-name-to-go-to", "MultiMC", "C:/MultiMC", "Instance 1") +export async function import_instance(profilePath, launcherType, basePath, instanceFolder) { + return await invoke('plugin:import|import_import_instance', { + profilePath, + launcherType, + basePath, + instanceFolder, + }) +} + +/// Checks if this instance is valid for importing, given a certain launcher type +/// eg: is_valid_importable_instance("C:/MultiMC/Instance 1", "MultiMC") +export async function is_valid_importable_instance(instanceFolder, launcherType) { + return await invoke('plugin:import|import_is_valid_importable_instance', { + instanceFolder, + launcherType, + }) +} + +/// Gets the default path for the given launcher type +/// null if it can't be found or doesn't exist +/// eg: get_default_launcher_path("MultiMC") +export async function get_default_launcher_path(launcherType) { + return await invoke('plugin:import|import_get_default_launcher_path', { launcher_type }) +} diff --git a/theseus_playground/src/main.rs b/theseus_playground/src/main.rs index a74e07d27..324879005 100644 --- a/theseus_playground/src/main.rs +++ b/theseus_playground/src/main.rs @@ -54,7 +54,7 @@ async fn main() -> theseus::Result<()> { const ATLAUNCHER_FOLDER: &str = r"/home/thesuzerain/ATLauncher"; const PRISM_FOLDER: &str = r"/home/thesuzerain/.local/share/PrismLauncher"; const MMC_FOLDER: &str = r"/home/thesuzerain/MultiMC"; - const CURSEFORGE_FOLDER: &str = r"/home/thesuzerain/curseforge"; + const CURSEFORGE_FOLDER: &str = r"/home/thesuzerain/curseforge/minecraft"; const GD_LAUNCHER_FOLDER: &str = r"/home/thesuzerain/gdlauncher_next"; test_batch_import(ATLAUNCHER_FOLDER, ImportLauncherType::ATLauncher) @@ -66,7 +66,7 @@ async fn main() -> theseus::Result<()> { test_batch_import(GD_LAUNCHER_FOLDER, ImportLauncherType::GDLauncher) .await?; - // Iterate through all filenames in PRISM_FOLDER/instances + println!("Done all!"); Ok(()) } From 010386f4927c06596cd42db0ecd5aa082cb6aa24 Mon Sep 17 00:00:00 2001 From: Wyatt Date: Thu, 20 Jul 2023 10:29:55 -0700 Subject: [PATCH 07/10] tidying up --- theseus/src/api/pack/import/atlauncher.rs | 7 +--- theseus/src/api/pack/import/curseforge.rs | 9 +---- theseus/src/api/pack/import/gdlauncher.rs | 9 +---- theseus/src/api/pack/import/mmc.rs | 20 +++------- theseus/src/api/pack/import/mod.rs | 47 +++++++---------------- theseus/src/api/pack/install_from.rs | 6 --- theseus/src/event/mod.rs | 5 --- theseus_gui/src-tauri/src/api/import.rs | 1 - theseus_gui/src/helpers/import.js | 7 +++- theseus_playground/src/main.rs | 1 - 10 files changed, 30 insertions(+), 82 deletions(-) diff --git a/theseus/src/api/pack/import/atlauncher.rs b/theseus/src/api/pack/import/atlauncher.rs index b5dbb05bb..2125aee28 100644 --- a/theseus/src/api/pack/import/atlauncher.rs +++ b/theseus/src/api/pack/import/atlauncher.rs @@ -120,7 +120,6 @@ pub async fn import_atlauncher( atlauncher_base_path: PathBuf, // path to base atlauncher folder instance_folder: String, // instance folder in atlauncher_base_path profile_path: PathBuf, // path to profile - existing_loading_bar: Option, ) -> crate::Result<()> { let atlauncher_instance_path = atlauncher_base_path .join("instances") @@ -158,7 +157,7 @@ pub async fn import_atlauncher( override_title: Some(atinstance.launcher.name.clone()), project_id: None, version_id: None, - existing_loading_bar: existing_loading_bar.clone(), + existing_loading_bar: None, profile: profile_path.clone(), }; @@ -171,11 +170,9 @@ pub async fn import_atlauncher( backup_name, description, atinstance, - existing_loading_bar, + None, ) .await?; - println!("Done."); - Ok(()) } diff --git a/theseus/src/api/pack/import/curseforge.rs b/theseus/src/api/pack/import/curseforge.rs index 63d5d09d2..4bfe6fabe 100644 --- a/theseus/src/api/pack/import/curseforge.rs +++ b/theseus/src/api/pack/import/curseforge.rs @@ -3,10 +3,7 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; use tokio::fs; -use crate::{ - event::LoadingBarId, prelude::ModLoader, state::ProfileInstallStage, - util::io, State, -}; +use crate::{prelude::ModLoader, state::ProfileInstallStage, util::io, State}; use super::copy_dotminecraft; @@ -51,7 +48,6 @@ pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool { pub async fn import_curseforge( curseforge_instance_folder: PathBuf, // instance's folder profile_path: PathBuf, // path to profile - existing_loading_bar: Option, ) -> crate::Result<()> { // TODO: recache curseforge instance icon let icon: Option = None; @@ -151,8 +147,7 @@ pub async fn import_curseforge( if let Some(profile_val) = crate::api::profile::get(&profile_path, None).await? { - crate::launcher::install_minecraft(&profile_val, existing_loading_bar) - .await?; + crate::launcher::install_minecraft(&profile_val, None).await?; State::sync().await?; } diff --git a/theseus/src/api/pack/import/gdlauncher.rs b/theseus/src/api/pack/import/gdlauncher.rs index 82c5170bf..ffc489e12 100644 --- a/theseus/src/api/pack/import/gdlauncher.rs +++ b/theseus/src/api/pack/import/gdlauncher.rs @@ -3,10 +3,7 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; use tokio::fs; -use crate::{ - event::LoadingBarId, prelude::ModLoader, state::ProfileInstallStage, - util::io, State, -}; +use crate::{prelude::ModLoader, state::ProfileInstallStage, util::io, State}; use super::{copy_dotminecraft, recache_icon}; @@ -41,7 +38,6 @@ pub async fn is_valid_gdlauncher(instance_folder: PathBuf) -> bool { pub async fn import_gdlauncher( gdlauncher_instance_folder: PathBuf, // instance's folder profile_path: PathBuf, // path to profile - existing_loading_bar: Option, ) -> crate::Result<()> { // Load config.json let config: String = @@ -105,8 +101,7 @@ pub async fn import_gdlauncher( if let Some(profile_val) = crate::api::profile::get(&profile_path, None).await? { - crate::launcher::install_minecraft(&profile_val, existing_loading_bar) - .await?; + crate::launcher::install_minecraft(&profile_val, None).await?; State::sync().await?; } diff --git a/theseus/src/api/pack/import/mmc.rs b/theseus/src/api/pack/import/mmc.rs index 904b5b0aa..8cd0e786e 100644 --- a/theseus/src/api/pack/import/mmc.rs +++ b/theseus/src/api/pack/import/mmc.rs @@ -4,7 +4,6 @@ use serde::{de, Deserialize, Serialize}; use tokio::fs; use crate::{ - event::LoadingBarId, pack::{ import::{self, copy_dotminecraft}, install_from::{self, CreatePackDescription, PackDependency}, @@ -56,7 +55,6 @@ fn deserialize_optional_bool<'de, D>( where D: de::Deserializer<'de>, { - println!("deserialize_optional_bool"); let s = Option::::deserialize(deserializer)?; match s { Some(string) => match string.as_str() { @@ -151,7 +149,6 @@ pub async fn import_mmc( mmc_base_path: PathBuf, // path to base mmc folder instance_folder: String, // instance folder in mmc_base_path profile_path: PathBuf, // path to profile - existing_loading_bar: Option, ) -> crate::Result<()> { let mmc_instance_path = mmc_base_path .join("instances") @@ -167,7 +164,6 @@ pub async fn import_mmc( // Re-cache icon let icon = if let Some(icon_key) = instance_cfg.icon_key { let icon_path = mmc_base_path.join("icons").join(icon_key); - dbg!(&icon_path); import::recache_icon(icon_path).await? } else { None @@ -179,7 +175,7 @@ pub async fn import_mmc( override_title: instance_cfg.name, project_id: instance_cfg.managed_pack_id, version_id: instance_cfg.managed_pack_version_id, - existing_loading_bar: existing_loading_bar.clone(), + existing_loading_bar: None, profile: profile_path.clone(), }; @@ -193,20 +189,20 @@ pub async fn import_mmc( // Kept separate as we may in the future want to add special handling for modrinth managed packs let backup_name = "Imported Modrinth Modpack".to_string(); let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join(".minecraft"); - import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack, existing_loading_bar).await?; + import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack).await?; } Some(MMCManagedPackType::Flame) | Some(MMCManagedPackType::ATLauncher) => { // For flame/atlauncher managed packs // Treat as unmanaged, but with 'minecraft' folder instead of '.minecraft' let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join("minecraft"); - import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack, existing_loading_bar).await?; + import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack).await?; }, Some(_) => { // For managed packs that aren't modrinth, flame, atlauncher // Treat as unmanaged let backup_name = "ImportedModpack".to_string(); let minecraft_folder = mmc_base_path.join("instances").join(instance_folder).join(".minecraft"); - import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack, existing_loading_bar).await?; + import_mmc_unmanaged(profile_path, minecraft_folder, backup_name, description, mmc_pack).await?; }, _ => return Err(crate::ErrorKind::InputError({ "Instance is managed, but managed pack type not specified in instance.cfg".to_string() @@ -225,7 +221,6 @@ pub async fn import_mmc( backup_name, description, mmc_pack, - existing_loading_bar, ) .await?; } @@ -238,9 +233,7 @@ async fn import_mmc_unmanaged( backup_name: String, description: CreatePackDescription, mmc_pack: MMCPack, - existing_loading_bar: Option, ) -> crate::Result<()> { - println!("import_mmc_unmanaged {:?} ", mmc_pack.components); // Pack dependencies stored in mmc-pack.json, we convert to .mrpack pack dependencies let dependencies = mmc_pack .components @@ -274,7 +267,6 @@ async fn import_mmc_unmanaged( None }) .collect(); - println!("import_mmc_unmanaged222 {:?} ", dependencies); // Sets profile information to be that loaded from mmc-pack.json and instance.cfg install_from::set_profile_information( @@ -285,15 +277,13 @@ async fn import_mmc_unmanaged( ) .await?; - println!("copying from {:?} to {:?}", minecraft_folder, profile_path); // Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc) copy_dotminecraft(profile_path.clone(), minecraft_folder).await?; if let Some(profile_val) = crate::api::profile::get(&profile_path, None).await? { - crate::launcher::install_minecraft(&profile_val, existing_loading_bar) - .await?; + crate::launcher::install_minecraft(&profile_val, None).await?; State::sync().await?; } diff --git a/theseus/src/api/pack/import/mod.rs b/theseus/src/api/pack/import/mod.rs index 3a1c0f7c4..413a59891 100644 --- a/theseus/src/api/pack/import/mod.rs +++ b/theseus/src/api/pack/import/mod.rs @@ -3,11 +3,7 @@ use std::path::{Path, PathBuf}; use io::IOError; use serde::{Deserialize, Serialize}; -use crate::{ - event::{emit::init_or_edit_loading, LoadingBarId}, - util::{fetch, io}, - LoadingBarType, -}; +use crate::util::{fetch, io}; pub mod atlauncher; pub mod curseforge; @@ -24,6 +20,7 @@ pub enum ImportLauncherType { Unknown, } +// Return a list of importable instances from a launcher type and base path, by iterating through the folder and checking pub async fn get_importable_instances( launcher_type: ImportLauncherType, base_path: PathBuf, @@ -36,13 +33,12 @@ pub async fn get_importable_instances( | ImportLauncherType::ATLauncher => base_path.join("instances"), ImportLauncherType::Curseforge => base_path.join("Instances"), ImportLauncherType::Unknown => { - todo!() + return Err(crate::ErrorKind::InputError( + "Launcher type Unknown".to_string(), + ) + .into()) } }; - println!( - "Searching {:?} - instances_folder: {:?}", - launcher_type, instances_folder - ); let mut instances = Vec::new(); let mut dir = io::read_dir(&instances_folder).await?; while let Some(entry) = dir @@ -64,6 +60,7 @@ pub async fn get_importable_instances( Ok(instances) } +// Import an instance from a launcher type and base path #[theseus_macros::debug_pin] #[tracing::instrument] pub async fn import_instance( @@ -71,29 +68,14 @@ pub async fn import_instance( launcher_type: ImportLauncherType, base_path: PathBuf, instance_folder: String, - existing_loading_bar: Option, ) -> crate::Result<()> { - let existing_loading_bar = Some( - init_or_edit_loading( - existing_loading_bar, - LoadingBarType::PackImport { - profile_path: profile_path.clone(), - instance_path: base_path.clone(), - instance_name: Some(instance_folder.clone()), - }, - 100.0, - "Downloading java version", - ) - .await?, - ); - + tracing::debug!("Importing instance from {instance_folder}"); match launcher_type { ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => { mmc::import_mmc( base_path, // path to base mmc folder instance_folder, // instance folder in mmc_base_path profile_path, // path to profile - existing_loading_bar, ) .await?; } @@ -102,7 +84,6 @@ pub async fn import_instance( base_path, // path to atlauncher folder instance_folder, // instance folder in atlauncher profile_path, // path to profile - existing_loading_bar, ) .await?; } @@ -110,7 +91,6 @@ pub async fn import_instance( gdlauncher::import_gdlauncher( base_path.join("instances").join(instance_folder), // path to gdlauncher folder profile_path, // path to profile - existing_loading_bar, ) .await?; } @@ -118,12 +98,14 @@ pub async fn import_instance( curseforge::import_curseforge( base_path.join("Instances").join(instance_folder), // path to curseforge folder profile_path, // path to profile - existing_loading_bar, ) .await?; } ImportLauncherType::Unknown => { - todo!() + return Err(crate::ErrorKind::InputError( + "Launcher type Unknown".to_string(), + ) + .into()); } } Ok(()) @@ -178,9 +160,7 @@ pub async fn is_valid_importable_instance( ImportLauncherType::Curseforge => { curseforge::is_valid_curseforge(instance_path).await } - ImportLauncherType::Unknown => { - todo!() - } + ImportLauncherType::Unknown => false, } } @@ -196,7 +176,6 @@ pub async fn recache_icon( if let Ok(bytes) = bytes { let bytes = bytes::Bytes::from(bytes); let cache_dir = &state.directories.caches_dir(); - dbg!(&cache_dir); let semaphore = &state.io_semaphore; Ok(Some( fetch::write_cached_icon( diff --git a/theseus/src/api/pack/install_from.rs b/theseus/src/api/pack/install_from.rs index ce104285f..2ea0ff659 100644 --- a/theseus/src/api/pack/install_from.rs +++ b/theseus/src/api/pack/install_from.rs @@ -306,7 +306,6 @@ pub async fn set_profile_information( let mut game_version: Option<&String> = None; let mut mod_loader = None; let mut loader_version = None; - println!("here1 {:?}", dependencies); for (key, value) in dependencies { match key { @@ -335,8 +334,6 @@ pub async fn set_profile_information( .into()); }; - println!("here4 - {:?}", mod_loader); - let mod_loader = mod_loader.unwrap_or(ModLoader::Vanilla); let loader_version = if mod_loader != ModLoader::Vanilla { crate::profile_create::get_loader_version_from_loader( @@ -348,9 +345,6 @@ pub async fn set_profile_information( } else { None }; - println!("her5 - {:?}", game_version); - println!("her5 - {:?}", loader_version); - println!("her5 - {:?}", description.override_title); // Sets values in profile crate::api::profile::edit(&profile_path, |prof| { prof.metadata.name = description diff --git a/theseus/src/event/mod.rs b/theseus/src/event/mod.rs index f6115bfd4..f3b114fd4 100644 --- a/theseus/src/event/mod.rs +++ b/theseus/src/event/mod.rs @@ -169,11 +169,6 @@ pub enum LoadingBarType { pack_id: Option, pack_version: Option, }, - PackImport { - profile_path: PathBuf, - instance_path: PathBuf, - instance_name: Option, - }, MinecraftDownload { profile_path: PathBuf, profile_name: String, diff --git a/theseus_gui/src-tauri/src/api/import.rs b/theseus_gui/src-tauri/src/api/import.rs index 394721332..7ddd43e61 100644 --- a/theseus_gui/src-tauri/src/api/import.rs +++ b/theseus_gui/src-tauri/src/api/import.rs @@ -41,7 +41,6 @@ pub async fn import_import_instance( launcher_type, base_path, instance_folder, - None, ) .await?; Ok(()) diff --git a/theseus_gui/src/helpers/import.js b/theseus_gui/src/helpers/import.js index cb1f46d50..54e59ced1 100644 --- a/theseus_gui/src/helpers/import.js +++ b/theseus_gui/src/helpers/import.js @@ -15,6 +15,11 @@ import { invoke } from '@tauri-apps/api/tauri' - PrismLauncher - Unknown (shouldn't be used, but is used internally if the launcher type isn't recognized) + For each launcher type, we can get a guess of the default path for the launcher, and a list of importable instances + For most launchers, this will be the application's data directory, with two exceptions: + - MultiMC: this goes to the app directory (wherever the app is) + - Curseforge: this goes to the 'minecraft' subdirectory of the data directory, as Curseforge has multiple games + */ /// Gets a list of importable instances from a launcher type and base path @@ -48,5 +53,5 @@ export async function is_valid_importable_instance(instanceFolder, launcherType) /// null if it can't be found or doesn't exist /// eg: get_default_launcher_path("MultiMC") export async function get_default_launcher_path(launcherType) { - return await invoke('plugin:import|import_get_default_launcher_path', { launcher_type }) + return await invoke('plugin:import|import_get_default_launcher_path', { launcherType }) } diff --git a/theseus_playground/src/main.rs b/theseus_playground/src/main.rs index 324879005..c16e24787 100644 --- a/theseus_playground/src/main.rs +++ b/theseus_playground/src/main.rs @@ -89,7 +89,6 @@ async fn test_batch_import( r#type, PathBuf::from(folder), instance, - None, ) .await?; println!("Completoooo"); From cafbbb000e0bf0169575fce9c54f648982e2f73e Mon Sep 17 00:00:00 2001 From: Wyatt Date: Thu, 20 Jul 2023 10:33:27 -0700 Subject: [PATCH 08/10] reverted playground changes --- theseus_playground/src/main.rs | 146 ++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 58 deletions(-) diff --git a/theseus_playground/src/main.rs b/theseus_playground/src/main.rs index c16e24787..56b3fac7e 100644 --- a/theseus_playground/src/main.rs +++ b/theseus_playground/src/main.rs @@ -3,12 +3,12 @@ windows_subsystem = "windows" )] -use std::path::PathBuf; - -use theseus::pack::import::ImportLauncherType; -use theseus::pack::install_from::CreatePackProfile; +use theseus::jre::autodetect_java_globals; use theseus::prelude::*; +use theseus::profile_create::profile_create; +use tokio::time::{sleep, Duration}; + // A simple Rust implementation of the authentication run // 1) call the authenticate_begin_flow() function to get the URL to open (like you would in the frontend) // 2) open the URL in a browser @@ -35,65 +35,95 @@ async fn main() -> theseus::Result<()> { let _log_guard = theseus::start_logger(); // Initialize state - let state = crate::State::get().await?; + let st = State::get().await?; + //State::update(); + + // Autodetect java globals + let jres = jre::get_all_jre().await?; + let java_8 = jre::find_filtered_jres("1.8", jres.clone(), false).await?; + let java_17 = jre::find_filtered_jres("1.78", jres.clone(), false).await?; + let java_18plus = + jre::find_filtered_jres("1.18", jres.clone(), true).await?; + let java_globals = + autodetect_java_globals(java_8, java_17, java_18plus).await?; + st.settings.write().await.java_globals = java_globals; + + st.settings.write().await.max_concurrent_downloads = 50; + st.settings.write().await.hooks.post_exit = + Some("echo This is after Minecraft runs- global setting!".to_string()); + // Changed the settings, so need to reset the semaphore + st.reset_fetch_semaphore().await; - // Set java globals to auto detected ones + // + // st.settings + // .write() + // .await + // .java_globals + // .insert(JAVA_8_KEY.to_string(), check_jre(path).await?.unwrap()); + // Clear profiles + println!("Clearing profiles."); { - let jres = crate::jre::get_all_jre().await?; - let java_8 = - crate::jre::find_filtered_jres("1.8", jres.clone(), false).await?; - let java_17 = - crate::jre::find_filtered_jres("1.17", jres.clone(), false).await?; - let java_18plus = - crate::jre::find_filtered_jres("1.18", jres.clone(), true).await?; - let java_globals = - crate::jre::autodetect_java_globals(java_8, java_17, java_18plus) - .await?; - state.settings.write().await.java_globals = java_globals; + let h = profile::list(None).await?; + for (path, _) in h.into_iter() { + profile::remove(&path).await?; + } } - const ATLAUNCHER_FOLDER: &str = r"/home/thesuzerain/ATLauncher"; - const PRISM_FOLDER: &str = r"/home/thesuzerain/.local/share/PrismLauncher"; - const MMC_FOLDER: &str = r"/home/thesuzerain/MultiMC"; - const CURSEFORGE_FOLDER: &str = r"/home/thesuzerain/curseforge/minecraft"; - const GD_LAUNCHER_FOLDER: &str = r"/home/thesuzerain/gdlauncher_next"; - - test_batch_import(ATLAUNCHER_FOLDER, ImportLauncherType::ATLauncher) - .await?; - test_batch_import(PRISM_FOLDER, ImportLauncherType::PrismLauncher).await?; - test_batch_import(MMC_FOLDER, ImportLauncherType::MultiMC).await?; - test_batch_import(CURSEFORGE_FOLDER, ImportLauncherType::Curseforge) - .await?; - test_batch_import(GD_LAUNCHER_FOLDER, ImportLauncherType::GDLauncher) - .await?; - - println!("Done all!"); - Ok(()) -} -async fn test_batch_import( - folder: &str, - r#type: ImportLauncherType, -) -> theseus::Result<()> { - let instances = - pack::import::get_importable_instances(r#type, folder.into()).await?; - for instance in instances { - println!("\n\n\nImporting {} for {:?}", instance, r#type); - let profile_path = profile_create::profile_create_from_creator( - CreatePackProfile::default(), - ) - .await - .unwrap(); - - pack::import::import_instance( - profile_path, - r#type, - PathBuf::from(folder), - instance, - ) - .await?; - println!("Completoooo"); + println!("Creating/adding profile."); + + let name = "Example".to_string(); + let game_version = "1.19.2".to_string(); + let modloader = ModLoader::Vanilla; + let loader_version = "stable".to_string(); + + let profile_path = profile_create( + name.clone(), + game_version, + modloader, + Some(loader_version), + None, + None, + None, + None, + ) + .await?; + + State::sync().await?; + + // Attempt to run game + if auth::users().await?.is_empty() { + println!("No users found, authenticating."); + authenticate_run().await?; // could take credentials from here direct, but also deposited in state users } - println!("Done batch import."); + + println!("running"); + // Run a profile, running minecraft and store the RwLock to the process + let proc_lock = profile::run(&canonicalize(&profile_path)?).await?; + let uuid = proc_lock.read().await.uuid; + let pid = proc_lock.read().await.current_child.read().await.id(); + + println!("Minecraft UUID: {}", uuid); + println!("Minecraft PID: {:?}", pid); + + // Wait 5 seconds + println!("Waiting 5 seconds to gather logs..."); + sleep(Duration::from_secs(5)).await; + let stdout = process::get_output_by_uuid(&uuid).await?; + println!("Logs after 5sec <<< {stdout} >>> end stdout"); + + println!( + "All running process UUID {:?}", + process::get_all_running_uuids().await? + ); + println!( + "All running process paths {:?}", + process::get_all_running_profile_paths().await? + ); + + // hold the lock to the process until it ends + println!("Waiting for process to end..."); + let mut proc = proc_lock.write().await; + process::wait_for(&mut proc).await?; Ok(()) } From ae1af2910c1c7063fdf07963ae32570eab040150 Mon Sep 17 00:00:00 2001 From: Wyatt Date: Thu, 20 Jul 2023 11:32:58 -0700 Subject: [PATCH 09/10] fixed js issue --- theseus/src/api/pack/import/mod.rs | 2 ++ theseus_gui/src/helpers/import.js | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/theseus/src/api/pack/import/mod.rs b/theseus/src/api/pack/import/mod.rs index 413a59891..03a504429 100644 --- a/theseus/src/api/pack/import/mod.rs +++ b/theseus/src/api/pack/import/mod.rs @@ -17,6 +17,7 @@ pub enum ImportLauncherType { ATLauncher, GDLauncher, Curseforge, + #[serde(other)] Unknown, } @@ -108,6 +109,7 @@ pub async fn import_instance( .into()); } } + tracing::debug!("Completed import."); Ok(()) } diff --git a/theseus_gui/src/helpers/import.js b/theseus_gui/src/helpers/import.js index 54e59ced1..c1e91d5c0 100644 --- a/theseus_gui/src/helpers/import.js +++ b/theseus_gui/src/helpers/import.js @@ -4,6 +4,7 @@ * and deserialized into a usable JS object. */ import { invoke } from '@tauri-apps/api/tauri' +import { create } from './profile' /* API for importing instances from other launchers @@ -31,7 +32,10 @@ export async function get_importable_instances(launcherType, basePath) { /// Import an instance from a launcher type and base path /// eg: import_instance("profile-name-to-go-to", "MultiMC", "C:/MultiMC", "Instance 1") -export async function import_instance(profilePath, launcherType, basePath, instanceFolder) { +export async function import_instance(launcherType, basePath, instanceFolder) { + // create a basic, empty instance (most properties will be filled in by the import process) + const profilePath = await create(instanceFolder, '1.19.4', 'vanilla', 'latest', null) + return await invoke('plugin:import|import_import_instance', { profilePath, launcherType, From 31a724a56d9c78ab6188f729f6b01432b95f68b4 Mon Sep 17 00:00:00 2001 From: thesuzerain Date: Sat, 22 Jul 2023 11:04:21 -0700 Subject: [PATCH 10/10] fixed merge issues --- theseus/src/api/pack/import/atlauncher.rs | 8 ++++---- theseus/src/api/pack/import/curseforge.rs | 9 +++++++-- theseus/src/api/pack/import/gdlauncher.rs | 9 +++++++-- theseus/src/api/pack/import/mmc.rs | 11 ++++++----- theseus/src/api/pack/import/mod.rs | 12 +++++++++--- theseus/src/api/pack/install_from.rs | 3 ++- theseus/src/api/pack/install_mrpack.rs | 19 +++++++++++-------- theseus/src/api/profile_create.rs | 3 ++- theseus_gui/src-tauri/src/api/import.rs | 3 ++- 9 files changed, 50 insertions(+), 27 deletions(-) diff --git a/theseus/src/api/pack/import/atlauncher.rs b/theseus/src/api/pack/import/atlauncher.rs index 2125aee28..be2508477 100644 --- a/theseus/src/api/pack/import/atlauncher.rs +++ b/theseus/src/api/pack/import/atlauncher.rs @@ -10,7 +10,7 @@ use crate::{ import::{self, copy_dotminecraft}, install_from::CreatePackDescription, }, - prelude::ModLoader, + prelude::{ModLoader, ProfilePathId}, state::{LinkedData, ProfileInstallStage}, util::io, State, @@ -119,7 +119,7 @@ pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool { pub async fn import_atlauncher( atlauncher_base_path: PathBuf, // path to base atlauncher folder instance_folder: String, // instance folder in atlauncher_base_path - profile_path: PathBuf, // path to profile + profile_path: ProfilePathId, // path to profile ) -> crate::Result<()> { let atlauncher_instance_path = atlauncher_base_path .join("instances") @@ -158,7 +158,7 @@ pub async fn import_atlauncher( project_id: None, version_id: None, existing_loading_bar: None, - profile: profile_path.clone(), + profile_path: profile_path.clone(), }; let backup_name = format!("ATLauncher-{}", instance_folder); @@ -177,7 +177,7 @@ pub async fn import_atlauncher( } async fn import_atlauncher_unmanaged( - profile_path: PathBuf, + profile_path: ProfilePathId, minecraft_folder: PathBuf, backup_name: String, description: CreatePackDescription, diff --git a/theseus/src/api/pack/import/curseforge.rs b/theseus/src/api/pack/import/curseforge.rs index 4bfe6fabe..27603e010 100644 --- a/theseus/src/api/pack/import/curseforge.rs +++ b/theseus/src/api/pack/import/curseforge.rs @@ -3,7 +3,12 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; use tokio::fs; -use crate::{prelude::ModLoader, state::ProfileInstallStage, util::io, State}; +use crate::{ + prelude::{ModLoader, ProfilePathId}, + state::ProfileInstallStage, + util::io, + State, +}; use super::copy_dotminecraft; @@ -47,7 +52,7 @@ pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool { pub async fn import_curseforge( curseforge_instance_folder: PathBuf, // instance's folder - profile_path: PathBuf, // path to profile + profile_path: ProfilePathId, // path to profile ) -> crate::Result<()> { // TODO: recache curseforge instance icon let icon: Option = None; diff --git a/theseus/src/api/pack/import/gdlauncher.rs b/theseus/src/api/pack/import/gdlauncher.rs index ffc489e12..6bf0f07cf 100644 --- a/theseus/src/api/pack/import/gdlauncher.rs +++ b/theseus/src/api/pack/import/gdlauncher.rs @@ -3,7 +3,12 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; use tokio::fs; -use crate::{prelude::ModLoader, state::ProfileInstallStage, util::io, State}; +use crate::{ + prelude::{ModLoader, ProfilePathId}, + state::ProfileInstallStage, + util::io, + State, +}; use super::{copy_dotminecraft, recache_icon}; @@ -37,7 +42,7 @@ pub async fn is_valid_gdlauncher(instance_folder: PathBuf) -> bool { pub async fn import_gdlauncher( gdlauncher_instance_folder: PathBuf, // instance's folder - profile_path: PathBuf, // path to profile + profile_path: ProfilePathId, // path to profile ) -> crate::Result<()> { // Load config.json let config: String = diff --git a/theseus/src/api/pack/import/mmc.rs b/theseus/src/api/pack/import/mmc.rs index 8cd0e786e..6cc87db61 100644 --- a/theseus/src/api/pack/import/mmc.rs +++ b/theseus/src/api/pack/import/mmc.rs @@ -8,6 +8,7 @@ use crate::{ import::{self, copy_dotminecraft}, install_from::{self, CreatePackDescription, PackDependency}, }, + prelude::ProfilePathId, util::io, State, }; @@ -146,9 +147,9 @@ async fn load_instance_cfg(file_path: &Path) -> crate::Result { #[tracing::instrument] #[theseus_macros::debug_pin] pub async fn import_mmc( - mmc_base_path: PathBuf, // path to base mmc folder - instance_folder: String, // instance folder in mmc_base_path - profile_path: PathBuf, // path to profile + mmc_base_path: PathBuf, // path to base mmc folder + instance_folder: String, // instance folder in mmc_base_path + profile_path: ProfilePathId, // path to profile ) -> crate::Result<()> { let mmc_instance_path = mmc_base_path .join("instances") @@ -176,7 +177,7 @@ pub async fn import_mmc( project_id: instance_cfg.managed_pack_id, version_id: instance_cfg.managed_pack_version_id, existing_loading_bar: None, - profile: profile_path.clone(), + profile_path: profile_path.clone(), }; // Managed pack @@ -228,7 +229,7 @@ pub async fn import_mmc( } async fn import_mmc_unmanaged( - profile_path: PathBuf, + profile_path: ProfilePathId, minecraft_folder: PathBuf, backup_name: String, description: CreatePackDescription, diff --git a/theseus/src/api/pack/import/mod.rs b/theseus/src/api/pack/import/mod.rs index 03a504429..0559f157e 100644 --- a/theseus/src/api/pack/import/mod.rs +++ b/theseus/src/api/pack/import/mod.rs @@ -3,7 +3,10 @@ use std::path::{Path, PathBuf}; use io::IOError; use serde::{Deserialize, Serialize}; -use crate::util::{fetch, io}; +use crate::{ + prelude::ProfilePathId, + util::{fetch, io}, +}; pub mod atlauncher; pub mod curseforge; @@ -65,7 +68,7 @@ pub async fn get_importable_instances( #[theseus_macros::debug_pin] #[tracing::instrument] pub async fn import_instance( - profile_path: PathBuf, + profile_path: ProfilePathId, launcher_type: ImportLauncherType, base_path: PathBuf, instance_folder: String, @@ -195,9 +198,12 @@ pub async fn recache_icon( } async fn copy_dotminecraft( - profile_path: PathBuf, + profile_path: ProfilePathId, dotminecraft: PathBuf, ) -> crate::Result<()> { + // Get full path to profile + let profile_path = profile_path.get_full_path().await?; + // std fs copy every file in dotminecraft to profile_path let mut dir = io::read_dir(&dotminecraft).await?; while let Some(entry) = dir diff --git a/theseus/src/api/pack/install_from.rs b/theseus/src/api/pack/install_from.rs index 02f9766fc..bfb31d71e 100644 --- a/theseus/src/api/pack/install_from.rs +++ b/theseus/src/api/pack/install_from.rs @@ -2,6 +2,7 @@ use crate::config::MODRINTH_API_URL; use crate::data::ModLoader; use crate::event::emit::{emit_loading, init_loading}; use crate::event::{LoadingBarId, LoadingBarType}; +use crate::prelude::ProfilePathId; use crate::state::{ LinkedData, ModrinthProject, ModrinthVersion, ProfileInstallStage, SideType, }; @@ -298,7 +299,7 @@ pub async fn generate_pack_from_file( /// This includes the pack name, icon, game version, loader version, and loader #[theseus_macros::debug_pin] pub async fn set_profile_information( - profile_path: PathBuf, + profile_path: ProfilePathId, description: &CreatePackDescription, backup_name: &str, dependencies: &HashMap, diff --git a/theseus/src/api/pack/install_mrpack.rs b/theseus/src/api/pack/install_mrpack.rs index 59ff34341..6ebb1e00f 100644 --- a/theseus/src/api/pack/install_mrpack.rs +++ b/theseus/src/api/pack/install_mrpack.rs @@ -5,6 +5,7 @@ use crate::event::LoadingBarType; use crate::pack::install_from::{ set_profile_information, EnvType, PackFile, PackFileHash, }; +use crate::prelude::ProfilePathId; use crate::state::SideType; use crate::util::fetch::{fetch_mirrors, write}; use crate::State; @@ -22,8 +23,8 @@ use super::install_from::{ #[theseus_macros::debug_pin] pub async fn install_zipped_mrpack( location: CreatePackLocation, - profile: PathBuf, -) -> crate::Result { + profile: ProfilePathId, +) -> crate::Result { // Get file from description let create_pack: CreatePack = match location { CreatePackLocation::FromVersionId { @@ -48,7 +49,7 @@ pub async fn install_zipped_mrpack( let project_id = create_pack.description.project_id; let version_id = create_pack.description.version_id; let existing_loading_bar = create_pack.description.existing_loading_bar; - let profile = create_pack.description.profile; + let profile = create_pack.description.profile_path; let state = &State::get().await?; @@ -99,12 +100,13 @@ pub async fn install_zipped_mrpack( ) .await?; + let profile_full_path = profile.get_full_path().await?; let profile = profile.clone(); let result = async { let loading_bar = init_or_edit_loading( existing_loading_bar, LoadingBarType::PackDownload { - profile_path: profile.clone(), + profile_path: profile_full_path.clone(), pack_name: pack.name.clone(), icon, pack_id: project_id, @@ -126,7 +128,7 @@ pub async fn install_zipped_mrpack( num_files, None, |project| { - let profile = profile.clone(); + let profile_full_path = profile_full_path.clone(); async move { //TODO: Future update: prompt user for optional files in a modpack if let Some(env) = project.env { @@ -160,7 +162,8 @@ pub async fn install_zipped_mrpack( match path { Component::CurDir | Component::Normal(_) => { - let path = profile.join(project.path); + let path = profile_full_path + .join(project.path); write( &path, &file, @@ -222,7 +225,7 @@ pub async fn install_zipped_mrpack( if new_path.file_name().is_some() { write( - &profile.join(new_path), + &profile_full_path.join(new_path), &content, &state.io_semaphore, ) @@ -253,7 +256,7 @@ pub async fn install_zipped_mrpack( State::sync().await?; } - Ok::(profile.clone()) + Ok::(profile.clone()) } .await; diff --git a/theseus/src/api/profile_create.rs b/theseus/src/api/profile_create.rs index d86485361..3e259cca2 100644 --- a/theseus/src/api/profile_create.rs +++ b/theseus/src/api/profile_create.rs @@ -1,5 +1,6 @@ //! Theseus profile management interface use crate::pack::install_from::CreatePackProfile; +use crate::prelude::ProfilePathId; use crate::state::LinkedData; use crate::util::io::{self, canonicalize}; use crate::{ @@ -131,7 +132,7 @@ pub async fn profile_create( pub async fn profile_create_from_creator( profile: CreatePackProfile, -) -> crate::Result { +) -> crate::Result { profile_create( profile.name, profile.game_version, diff --git a/theseus_gui/src-tauri/src/api/import.rs b/theseus_gui/src-tauri/src/api/import.rs index 7ddd43e61..f30e77d0a 100644 --- a/theseus_gui/src-tauri/src/api/import.rs +++ b/theseus_gui/src-tauri/src/api/import.rs @@ -4,6 +4,7 @@ use crate::api::Result; use theseus::pack::import::ImportLauncherType; use theseus::pack::import; +use theseus::prelude::ProfilePathId; pub fn init() -> tauri::plugin::TauriPlugin { tauri::plugin::Builder::new("import") @@ -31,7 +32,7 @@ pub async fn import_get_importable_instances( /// eg: import_instance(ImportLauncherType::MultiMC, PathBuf::from("C:/MultiMC"), "Instance 1") #[tauri::command] pub async fn import_import_instance( - profile_path: PathBuf, + profile_path: ProfilePathId, launcher_type: ImportLauncherType, base_path: PathBuf, instance_folder: String,