diff --git a/Cargo.lock b/Cargo.lock index f223de853..588936200 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -587,19 +587,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-epoch" -version = "0.9.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - [[package]] name = "crossbeam-utils" version = "0.8.15" @@ -987,16 +974,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "futf" version = "0.1.5" @@ -2278,17 +2255,6 @@ version = "1.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eaf2319cd71dd9ff38c72bebde61b9ea657134abcf26ae4205f54f772a32810" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -2296,21 +2262,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.7", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -3120,23 +3072,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "sled" -version = "0.34.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" -dependencies = [ - "crc32fast", - "crossbeam-epoch", - "crossbeam-utils", - "fs2", - "fxhash", - "libc", - "log", - "parking_lot 0.11.2", - "zstd", -] - [[package]] name = "smallvec" version = "1.10.0" @@ -3204,7 +3139,7 @@ checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", - "parking_lot 0.12.1", + "parking_lot", "phf_shared 0.10.0", "precomputed-hash", "serde", @@ -3340,7 +3275,7 @@ dependencies = [ "ndk-sys", "objc", "once_cell", - "parking_lot 0.12.1", + "parking_lot", "paste", "png", "raw-window-handle", @@ -3575,7 +3510,6 @@ version = "0.1.0" dependencies = [ "async-tungstenite", "async_zip", - "bincode", "bytes", "chrono", "daedalus", @@ -3591,7 +3525,6 @@ dependencies = [ "serde_json", "sha1 0.6.1", "sha2 0.9.9", - "sled", "sys-info", "tauri", "thiserror", @@ -3772,7 +3705,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", diff --git a/theseus/Cargo.toml b/theseus/Cargo.toml index 5c1e28c0e..a2a02d005 100644 --- a/theseus/Cargo.toml +++ b/theseus/Cargo.toml @@ -8,22 +8,20 @@ edition = "2018" [dependencies] bytes = "1" -bincode = { version = "2.0.0-rc.1", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" toml = "0.7.3" sha1 = { version = "0.6.1", features = ["std"]} sha2 = "0.9.9" -sled = { version = "0.34.7", features = ["compression"] } url = "2.2" uuid = { version = "1.1", features = ["serde", "v4"] } zip = "0.5" async_zip = { version = "0.0.13", features = ["full"] } chrono = { version = "0.4.19", features = ["serde"] } -daedalus = { version = "0.1.18", features = ["bincode"] } +daedalus = { version = "0.1.18" } dirs = "4.0" -# TODO: possibly replace with tracing to have structured logging + log = "0.4.14" regex = "1.5" sys-info = "0.9.0" diff --git a/theseus/src/api/auth.rs b/theseus/src/api/auth.rs index 3ec7b60e1..fa38c7922 100644 --- a/theseus/src/api/auth.rs +++ b/theseus/src/api/auth.rs @@ -47,7 +47,7 @@ pub async fn authenticate( })?; let credentials = flow.extract_credentials(&state.io_semaphore).await?; - users.insert(&credentials)?; + users.insert(&credentials).await?; if state.settings.read().await.default_user.is_none() { let mut settings = state.settings.write().await; @@ -65,7 +65,7 @@ pub async fn refresh(user: uuid::Uuid) -> crate::Result { let mut users = state.users.write().await; let io_sempahore = &state.io_semaphore; - futures::future::ready(users.get(user)?.ok_or_else(|| { + futures::future::ready(users.get(user).ok_or_else(|| { crate::ErrorKind::OtherError(format!( "Tried to refresh nonexistent user with ID {user}" )) @@ -75,7 +75,7 @@ pub async fn refresh(user: uuid::Uuid) -> crate::Result { if chrono::offset::Utc::now() > credentials.expires { inner::refresh_credentials(&mut credentials, io_sempahore).await?; } - users.insert(&credentials)?; + users.insert(&credentials).await?; Ok(credentials) }) .await @@ -89,14 +89,10 @@ pub async fn remove_user(user: uuid::Uuid) -> crate::Result<()> { if state.settings.read().await.default_user == Some(user) { let mut settings = state.settings.write().await; - settings.default_user = users - .0 - .first()? - .map(|it| uuid::Uuid::from_slice(&it.0)) - .transpose()?; + settings.default_user = users.0.values().next().map(|it| it.id); } - users.remove(user)?; + users.remove(user).await?; Ok(()) } @@ -106,15 +102,15 @@ pub async fn has_user(user: uuid::Uuid) -> crate::Result { let state = State::get().await?; let users = state.users.read().await; - users.contains(user) + Ok(users.contains(user)) } /// Get a copy of the list of all user credentials #[tracing::instrument] -pub async fn users() -> crate::Result> { +pub async fn users() -> crate::Result> { let state = State::get().await?; let users = state.users.read().await; - users.iter().collect() + Ok(users.0.values().cloned().collect()) } /// Get a specific user by user ID @@ -123,7 +119,7 @@ pub async fn users() -> crate::Result> { pub async fn get_user(user: uuid::Uuid) -> crate::Result { let state = State::get().await?; let users = state.users.read().await; - let user = users.get(user)?.ok_or_else(|| { + let user = users.get(user).ok_or_else(|| { crate::ErrorKind::OtherError(format!( "Tried to get nonexistent user with ID {user}" )) diff --git a/theseus/src/api/jre.rs b/theseus/src/api/jre.rs index 74042f25f..a29f44469 100644 --- a/theseus/src/api/jre.rs +++ b/theseus/src/api/jre.rs @@ -40,10 +40,10 @@ pub async fn autodetect_java_globals() -> crate::Result { // this can be overwritten by the user a profile-by-profile basis pub async fn get_optimal_jre_key(profile: &Profile) -> crate::Result { let state = State::get().await?; + let metadata = state.metadata.read().await; // Fetch version info from stored profile game_version - let version = state - .metadata + let version = metadata .minecraft .versions .iter() @@ -60,6 +60,7 @@ pub async fn get_optimal_jre_key(profile: &Profile) -> crate::Result { &state, version, profile.metadata.loader_version.as_ref(), + None, ) .await?; let optimal_key = match version_info diff --git a/theseus/src/api/metadata.rs b/theseus/src/api/metadata.rs index 06ef07ecb..4b90f738f 100644 --- a/theseus/src/api/metadata.rs +++ b/theseus/src/api/metadata.rs @@ -5,7 +5,7 @@ pub use daedalus::modded::Manifest; #[tracing::instrument] pub async fn get_minecraft_versions() -> crate::Result { let state = State::get().await?; - let tags = state.metadata.minecraft.clone(); + let tags = state.metadata.read().await.minecraft.clone(); Ok(tags) } @@ -13,7 +13,7 @@ pub async fn get_minecraft_versions() -> crate::Result { #[tracing::instrument] pub async fn get_fabric_versions() -> crate::Result { let state = State::get().await?; - let tags = state.metadata.fabric.clone(); + let tags = state.metadata.read().await.fabric.clone(); Ok(tags) } @@ -21,7 +21,7 @@ pub async fn get_fabric_versions() -> crate::Result { #[tracing::instrument] pub async fn get_forge_versions() -> crate::Result { let state = State::get().await?; - let tags = state.metadata.forge.clone(); + let tags = state.metadata.read().await.forge.clone(); Ok(tags) } diff --git a/theseus/src/api/pack.rs b/theseus/src/api/pack.rs index 36fb6b114..83befd22f 100644 --- a/theseus/src/api/pack.rs +++ b/theseus/src/api/pack.rs @@ -1,8 +1,10 @@ use crate::config::MODRINTH_API_URL; use crate::data::ModLoader; -use crate::event::emit::{init_loading, loading_try_for_each_concurrent}; +use crate::event::emit::{ + emit_loading, init_loading, loading_try_for_each_concurrent, +}; use crate::event::LoadingBarType; -use crate::state::{ModrinthProject, ModrinthVersion, SideType}; +use crate::state::{LinkedData, ModrinthProject, ModrinthVersion, SideType}; use crate::util::fetch::{ fetch, fetch_json, fetch_mirrors, write, write_cached_icon, }; @@ -218,13 +220,18 @@ async fn install_pack( }; let pack_name = pack.name.clone(); + let profile = crate::api::profile_create::profile_create( pack.name, game_version.clone(), mod_loader.unwrap_or(ModLoader::Vanilla), loader_version, icon, - project_id.clone(), + Some(LinkedData { + project_id: project_id.clone(), + version_id: version_id.clone(), + }), + Some(true), ) .await?; @@ -238,6 +245,7 @@ async fn install_pack( "Downloading modpack...", ) .await?; + let num_files = pack.files.len(); use futures::StreamExt; loading_try_for_each_concurrent( @@ -245,7 +253,7 @@ async fn install_pack( .map(Ok::), None, Some(&loading_bar), - 100.0, + 80.0, num_files, None, |project| { @@ -344,11 +352,22 @@ async fn install_pack( .await }; + emit_loading(&loading_bar, 0.05, Some("Extracting overrides")).await?; extract_overrides("overrides".to_string()).await?; extract_overrides("client_overrides".to_string()).await?; + emit_loading(&loading_bar, 0.1, Some("Done extacting overrides")) + .await?; super::profile::sync(&profile).await?; + if let Some(profile) = crate::api::profile::get(&profile).await? { + crate::launcher::install_minecraft(&profile, Some(loading_bar)) + .await?; + } else { + emit_loading(&loading_bar, 0.1, Some("Done extacting overrides")) + .await?; + } + Ok(profile) } else { Err(crate::Error::from(crate::ErrorKind::InputError( diff --git a/theseus/src/api/profile.rs b/theseus/src/api/profile.rs index a1092245d..1497dbd3e 100644 --- a/theseus/src/api/profile.rs +++ b/theseus/src/api/profile.rs @@ -1,4 +1,7 @@ //! Theseus profile management interface +use crate::event::emit::{init_loading, loading_try_for_each_concurrent}; +use crate::event::LoadingBarType; +use crate::state::ProjectMetadata; use crate::{ auth::{self, refresh}, event::{emit::emit_profile, ProfilePayloadType}, @@ -22,7 +25,7 @@ pub async fn remove(path: &Path) -> crate::Result<()> { let state = State::get().await?; let mut profiles = state.profiles.write().await; - if let Some(profile) = profiles.0.get(path) { + if let Some(profile) = profiles.remove(path).await? { emit_profile( profile.uuid, profile.path.clone(), @@ -32,8 +35,6 @@ pub async fn remove(path: &Path) -> crate::Result<()> { .await?; } - profiles.remove(path).await?; - Ok(()) } @@ -108,20 +109,151 @@ pub async fn sync(path: &Path) -> crate::Result<()> { result } -/// Add a project from a version +/// Installs/Repairs a profile #[tracing::instrument] -pub async fn add_project_from_version( - profile: &Path, +pub async fn install(path: &Path) -> crate::Result<()> { + let state = State::get().await?; + let result = { + let mut profiles: tokio::sync::RwLockWriteGuard< + crate::state::Profiles, + > = state.profiles.write().await; + + if let Some(profile) = profiles.0.get_mut(path) { + crate::launcher::install_minecraft(profile, None).await?; + Ok(()) + } else { + Err(crate::ErrorKind::UnmanagedProfileError( + path.display().to_string(), + ) + .as_error()) + } + }; + State::sync().await?; + result +} + +pub async fn update_all(profile_path: &Path) -> crate::Result<()> { + let state = State::get().await?; + let mut profiles = state.profiles.write().await; + + if let Some(profile) = profiles.0.get_mut(profile_path) { + let loading_bar = init_loading( + LoadingBarType::ProfileUpdate { + profile_uuid: profile.uuid, + profile_name: profile.metadata.name.clone(), + }, + 100.0, + "Updating profile...", + ) + .await?; + + use futures::StreamExt; + loading_try_for_each_concurrent( + futures::stream::iter(profile.projects.keys()) + .map(Ok::<&PathBuf, crate::Error>), + None, + Some(&loading_bar), + 100.0, + profile.projects.len(), + None, + |project| update_project(profile_path, project, Some(true)), + ) + .await?; + + profile.sync().await?; + + Ok(()) + } else { + Err(crate::ErrorKind::UnmanagedProfileError( + profile_path.display().to_string(), + ) + .as_error()) + } +} + +pub async fn update_project( + profile_path: &Path, + project_path: &Path, + should_not_sync: Option, +) -> crate::Result<()> { + let state = State::get().await?; + let mut profiles = state.profiles.write().await; + + if let Some(profile) = profiles.0.get_mut(profile_path) { + if let Some(project) = profile.projects.get(project_path) { + if let ProjectMetadata::Modrinth { + update_version: Some(update_version), + .. + } = &project.metadata + { + let path = profile + .add_project_version(update_version.id.clone()) + .await?; + + if path != project_path { + profile.remove_project(project_path).await?; + } + + if !should_not_sync.unwrap_or(false) { + profile.sync().await?; + } + } + } + + Ok(()) + } else { + Err(crate::ErrorKind::UnmanagedProfileError( + profile_path.display().to_string(), + ) + .as_error()) + } +} + +/// Replaces a project given a new version ID +pub async fn replace_project( + profile_path: &Path, + project: &Path, version_id: String, ) -> crate::Result { let state = State::get().await?; let mut profiles = state.profiles.write().await; - if let Some(profile) = profiles.0.get_mut(profile) { - profile.add_project_version(version_id).await + if let Some(profile) = profiles.0.get_mut(profile_path) { + let path = profile.add_project_version(version_id).await?; + + if path != project { + profile.remove_project(project).await?; + } + + profile.sync().await?; + + Ok(path) } else { Err(crate::ErrorKind::UnmanagedProfileError( - profile.display().to_string(), + profile_path.display().to_string(), + ) + .as_error()) + } +} + +/// Add a project from a version +#[tracing::instrument] +pub async fn add_project_from_version( + profile_path: &Path, + version_id: String, +) -> crate::Result { + let state = State::get().await?; + let mut profiles = state.profiles.write().await; + + if let Some(profile) = profiles.0.get_mut(profile_path) { + let path = profile.add_project_version(version_id).await?; + + profile.sync().await?; + + Ok(path) + } else { + Err(crate::ErrorKind::UnmanagedProfileError( + profile_path.display().to_string(), ) .as_error()) } @@ -130,14 +262,14 @@ pub async fn add_project_from_version( /// Add a project from an FS path #[tracing::instrument] pub async fn add_project_from_path( - profile: &Path, + profile_path: &Path, path: &Path, project_type: Option, ) -> crate::Result { let state = State::get().await?; let mut profiles = state.profiles.write().await; - if let Some(profile) = profiles.0.get_mut(profile) { + if let Some(profile) = profiles.0.get_mut(profile_path) { let file = fs::read(path).await?; let file_name = path .file_name() @@ -145,16 +277,20 @@ pub async fn add_project_from_path( .to_string_lossy() .to_string(); - profile + let path = profile .add_project_bytes( &file_name, bytes::Bytes::from(file), project_type.and_then(|x| serde_json::from_str(&x).ok()), ) - .await + .await?; + + profile.sync().await?; + + Ok(path) } else { Err(crate::ErrorKind::UnmanagedProfileError( - profile.display().to_string(), + profile_path.display().to_string(), ) .as_error()) } @@ -214,7 +350,7 @@ pub async fn run(path: &Path) -> crate::Result>> { } else { // If no default account, try to use a logged in account let users = auth::users().await?; - let last_account = users.iter().next(); + let last_account = users.first(); if let Some(last_account) = last_account { refresh(last_account.id).await? } else { @@ -233,6 +369,7 @@ pub async fn run_credentials( ) -> crate::Result>> { let state = State::get().await?; let settings = state.settings.read().await; + let metadata = state.metadata.read().await; let profile = get(path).await?.ok_or_else(|| { crate::ErrorKind::OtherError(format!( "Tried to run a nonexistent or unloaded profile at path {}!", @@ -240,8 +377,7 @@ pub async fn run_credentials( )) })?; - let version = state - .metadata + let version = metadata .minecraft .versions .iter() @@ -256,6 +392,7 @@ pub async fn run_credentials( &state, version, profile.metadata.loader_version.as_ref(), + None, ) .await?; let pre_launch_hooks = @@ -358,9 +495,6 @@ pub async fn run_credentials( }; let mc_process = crate::launcher::launch_minecraft( - &profile.metadata.game_version, - &profile.metadata.loader_version, - &profile.path, java_install, java_args, env_args, diff --git a/theseus/src/api/profile_create.rs b/theseus/src/api/profile_create.rs index baeb5f405..fa10761d5 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::state::LinkedData; use crate::{ event::{emit::emit_profile, ProfilePayloadType}, jre, @@ -29,6 +30,7 @@ pub async fn profile_create_empty() -> crate::Result { None, // the modloader version to use, set to "latest", "stable", or the ID of your chosen loader None, // the icon for the profile None, + None, ) .await } @@ -42,9 +44,11 @@ pub async fn profile_create( modloader: ModLoader, // the modloader to use loader_version: Option, // the modloader version to use, set to "latest", "stable", or the ID of your chosen loader. defaults to latest icon: Option, // the icon for the profile - linked_project_id: Option, // the linked project ID (mainly for modpacks)- used for updating + linked_data: Option, // the linked project ID (mainly for modpacks)- used for updating + skip_install_profile: Option, ) -> crate::Result { let state = State::get().await?; + let metadata = state.metadata.read().await; let uuid = Uuid::new_v4(); let path = state.directories.profiles_dir().join(uuid.to_string()); @@ -86,8 +90,8 @@ pub async fn profile_create( }; let loader_data = match loader { - ModLoader::Forge => &state.metadata.forge, - ModLoader::Fabric => &state.metadata.fabric, + ModLoader::Forge => &metadata.forge, + ModLoader::Fabric => &metadata.fabric, _ => { return Err(ProfileCreationError::NoManifest( loader.to_string(), @@ -157,7 +161,7 @@ pub async fn profile_create( profile.metadata.loader_version = Some(loader_version); } - profile.metadata.linked_project_id = linked_project_id; + profile.metadata.linked_data = linked_data; // Attempts to find optimal JRE for the profile from the JavaGlobals // Finds optimal key, and see if key has been set in JavaGlobals @@ -179,11 +183,15 @@ pub async fn profile_create( ProfilePayloadType::Created, ) .await?; + { let mut profiles = state.profiles.write().await; - profiles.insert(profile).await?; + profiles.insert(profile.clone()).await?; } + if !skip_install_profile.unwrap_or(false) { + crate::launcher::install_minecraft(&profile, None).await?; + } State::sync().await?; Ok(path) diff --git a/theseus/src/api/tags.rs b/theseus/src/api/tags.rs index 595344984..3ec2c9b97 100644 --- a/theseus/src/api/tags.rs +++ b/theseus/src/api/tags.rs @@ -1,18 +1,16 @@ //! Theseus tag management interface pub use crate::{ - state::{ - Category, DonationPlatform, GameVersion, License, Loader, TagBundle, - }, + state::{Category, DonationPlatform, GameVersion, Loader, Tags}, State, }; // Get bundled set of tags #[tracing::instrument] -pub async fn get_tag_bundle() -> crate::Result { +pub async fn get_tag_bundle() -> crate::Result { let state = State::get().await?; let tags = state.tags.read().await; - tags.get_tag_bundle() + Ok(tags.get_tag_bundle()) } /// Get category tags @@ -21,7 +19,7 @@ pub async fn get_category_tags() -> crate::Result> { let state = State::get().await?; let tags = state.tags.read().await; - tags.get_categories() + Ok(tags.get_categories()) } /// Get report type tags @@ -30,7 +28,7 @@ pub async fn get_report_type_tags() -> crate::Result> { let state = State::get().await?; let tags = state.tags.read().await; - tags.get_report_types() + Ok(tags.get_report_types()) } /// Get loader tags @@ -39,7 +37,7 @@ pub async fn get_loader_tags() -> crate::Result> { let state = State::get().await?; let tags = state.tags.read().await; - tags.get_loaders() + Ok(tags.get_loaders()) } /// Get game version tags @@ -48,16 +46,7 @@ pub async fn get_game_version_tags() -> crate::Result> { let state = State::get().await?; let tags = state.tags.read().await; - tags.get_game_versions() -} - -/// Get license tags -#[tracing::instrument] -pub async fn get_license_tags() -> crate::Result> { - let state = State::get().await?; - let tags = state.tags.read().await; - - tags.get_licenses() + Ok(tags.get_game_versions()) } /// Get donation platform tags @@ -67,5 +56,5 @@ pub async fn get_donation_platform_tags() -> crate::Result let state = State::get().await?; let tags = state.tags.read().await; - tags.get_donation_platforms() + Ok(tags.get_donation_platforms()) } diff --git a/theseus/src/config.rs b/theseus/src/config.rs index b21c6017e..760f9eeda 100644 --- a/theseus/src/config.rs +++ b/theseus/src/config.rs @@ -1,16 +1,3 @@ //! Configuration structs -use lazy_static::lazy_static; - -lazy_static! { - pub static ref BINCODE_CONFIG: bincode::config::Configuration = - bincode::config::standard() - .with_little_endian() - .with_no_limit(); -} - pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2/"; - -pub fn sled_config() -> sled::Config { - sled::Config::default().use_compression(true) -} diff --git a/theseus/src/error.rs b/theseus/src/error.rs index f295e669c..bf27b7eeb 100644 --- a/theseus/src/error.rs +++ b/theseus/src/error.rs @@ -13,18 +13,9 @@ pub enum ErrorKind { #[error("Error parsing UUID: {0}")] UUIDError(#[from] uuid::Error), - #[error("Serialization error (Bincode): {0}")] - EncodeError(#[from] bincode::error::EncodeError), - - #[error("Deserialization error (Bincode): {0}")] - DecodeError(#[from] bincode::error::DecodeError), - #[error("Error parsing URL: {0}")] URLError(#[from] url::ParseError), - #[error("Database error: {0}")] - DBError(#[from] sled::Error), - #[error("Unable to read {0} from any source")] NoValueFor(String), diff --git a/theseus/src/event/emit.rs b/theseus/src/event/emit.rs index eea03c541..121729ddd 100644 --- a/theseus/src/event/emit.rs +++ b/theseus/src/event/emit.rs @@ -1,5 +1,5 @@ use crate::event::{ - EventError, LoadingBar, LoadingBarId, LoadingBarType, ProcessPayloadType, + EventError, LoadingBar, LoadingBarType, ProcessPayloadType, ProfilePayloadType, }; use futures::prelude::*; @@ -11,6 +11,7 @@ use crate::event::{ }; #[cfg(feature = "tauri")] use tauri::Manager; +use uuid::Uuid; /* Events are a way we can communciate with the Tauri frontend from the Rust backend. @@ -39,22 +40,23 @@ use tauri::Manager; // Initialize a loading bar for use in emit_loading // This will generate a LoadingBarId, which is used to refer to the loading bar uniquely. // total is the total amount of work to be done- all emissions will be considered a fraction of this value (should be 1 or 100 for simplicity) -// default_message is the message to display on the loading bar if no message is passed to emit_loading +// title is the title of the loading bar pub async fn init_loading( bar_type: LoadingBarType, total: f64, - default_message: &str, -) -> crate::Result { + title: &str, +) -> crate::Result { let event_state = crate::EventState::get().await?; - let key = LoadingBarId::new(bar_type); + let key = Uuid::new_v4(); event_state.loading_bars.write().await.insert( - key.clone(), + key, LoadingBar { - loading_bar_id: key.clone(), - message: default_message.to_string(), + loading_bar_id: key, + message: title.to_string(), total, current: 0.0, + bar_type, }, ); // attempt an initial loading_emit event to the frontend @@ -62,6 +64,40 @@ pub async fn init_loading( Ok(key) } +pub async fn init_or_edit_loading( + id: Option, + bar_type: LoadingBarType, + total: f64, + title: &str, +) -> crate::Result { + if let Some(id) = id { + edit_loading(id, bar_type, total, title).await?; + + Ok(id) + } else { + init_loading(bar_type, total, title).await + } +} + +// Edits a loading bar's type +pub async fn edit_loading( + id: Uuid, + bar_type: LoadingBarType, + total: f64, + title: &str, +) -> crate::Result<()> { + let event_state = crate::EventState::get().await?; + + if let Some(bar) = event_state.loading_bars.write().await.get_mut(&id) { + bar.bar_type = bar_type; + bar.total = total; + bar.message = title.to_string(); + }; + + emit_loading(&id, 0.0, None).await?; + Ok(()) +} + // emit_loading emits a loading event to the frontend // key refers to the loading bar to update // increment refers to by what relative increment to the loading struct's total to update @@ -69,7 +105,7 @@ pub async fn init_loading( // By convention, fraction is the fraction of the progress bar that is filled #[allow(unused_variables)] pub async fn emit_loading( - key: &LoadingBarId, + key: &Uuid, increment_frac: f64, message: Option<&str>, ) -> crate::Result<()> { @@ -79,14 +115,14 @@ pub async fn emit_loading( let loading_bar = match loading_bar.get_mut(key) { Some(f) => f, None => { - return Err(EventError::NoLoadingBar(key.clone()).into()); + return Err(EventError::NoLoadingBar(*key).into()); } }; // Tick up loading bar loading_bar.current += increment_frac; let display_frac = loading_bar.current / loading_bar.total; - let display_frac = if display_frac > 1.0 { + let display_frac = if display_frac >= 1.0 { None // by convention, when its done, we submit None // any further updates will be ignored (also sending None) } else { @@ -101,8 +137,8 @@ pub async fn emit_loading( LoadingPayload { fraction: display_frac, message: message.unwrap_or(&loading_bar.message).to_string(), - event: key.key.clone(), - loader_uuid: key.uuid, + event: loading_bar.bar_type.clone(), + loader_uuid: loading_bar.loading_bar_id, }, ) .map_err(EventError::from)?; @@ -132,7 +168,7 @@ pub async fn emit_warning(message: &str) -> crate::Result<()> { // emit_process(uuid, pid, event, message) #[allow(unused_variables)] pub async fn emit_process( - uuid: uuid::Uuid, + uuid: Uuid, pid: u32, event: ProcessPayloadType, message: &str, @@ -159,7 +195,7 @@ pub async fn emit_process( // emit_profile(path, event) #[allow(unused_variables)] pub async fn emit_profile( - uuid: uuid::Uuid, + uuid: Uuid, path: PathBuf, name: &str, event: ProfilePayloadType, @@ -253,7 +289,7 @@ macro_rules! loading_join { pub async fn loading_try_for_each_concurrent( stream: I, limit: Option, - key: Option<&LoadingBarId>, + key: Option<&Uuid>, total: f64, num_futs: usize, // num is in here as we allow Iterator to be passed in, which doesn't have a size message: Option<&str>, @@ -285,7 +321,7 @@ where pub async fn loading_try_for_each_concurrent( stream: I, limit: Option, - _key: Option<&LoadingBarId>, + _key: Option<&Uuid>, _total: f64, _num_futs: usize, // num is in here as we allow Iterator to be passed in, which doesn't have a size _message: Option<&str>, diff --git a/theseus/src/event/mod.rs b/theseus/src/event/mod.rs index 4d3ed130c..bec43dab2 100644 --- a/theseus/src/event/mod.rs +++ b/theseus/src/event/mod.rs @@ -1,6 +1,6 @@ //! Theseus state management system use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fmt, path::PathBuf, sync::Arc}; +use std::{collections::HashMap, path::PathBuf, sync::Arc}; use tokio::sync::OnceCell; use tokio::sync::RwLock; use uuid::Uuid; @@ -14,7 +14,7 @@ pub struct EventState { /// Tauri app #[cfg(feature = "tauri")] pub app: tauri::AppHandle, - pub loading_bars: RwLock>, + pub loading_bars: RwLock>, } impl EventState { @@ -48,6 +48,13 @@ impl EventState { Ok(EVENT_STATE.get().ok_or(EventError::NotInitialized)?.clone()) } + pub async fn list_progress_bars() -> crate::Result> + { + let value = Self::get().await?; + let read = value.loading_bars.read().await; + Ok(read.clone()) + } + // Initialization requires no app handle in non-tauri mode, so we can just use the same function #[cfg(not(feature = "tauri"))] pub async fn get() -> crate::Result> { @@ -55,35 +62,13 @@ impl EventState { } } -#[derive(Debug, Clone)] +#[derive(Serialize, Debug, Clone)] pub struct LoadingBar { - pub loading_bar_id: LoadingBarId, + pub loading_bar_id: Uuid, pub message: String, pub total: f64, pub current: f64, -} - -// Loading Bar Id lets us uniquely identify loading bars stored in the state -// the uuid lets us identify loading bars across threads -#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)] -pub struct LoadingBarId { - pub key: LoadingBarType, - pub uuid: Uuid, -} - -impl LoadingBarId { - pub fn new(key: LoadingBarType) -> Self { - Self { - key, - uuid: Uuid::new_v4(), - } - } -} - -impl fmt::Display for LoadingBarId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}-{}", self.key, self.uuid) - } + pub bar_type: LoadingBarType, } #[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)] @@ -98,7 +83,10 @@ pub enum LoadingBarType { profile_uuid: Uuid, profile_name: String, }, - ProfileSync, + ProfileUpdate { + profile_uuid: Uuid, + profile_name: String, + }, } #[derive(Serialize, Clone)] @@ -139,6 +127,7 @@ pub struct ProfilePayload { pub enum ProfilePayloadType { Created, Added, // also triggered when Created + Synced, Edited, Removed, } @@ -149,7 +138,7 @@ pub enum EventError { NotInitialized, #[error("Non-existent loading bar of key: {0}")] - NoLoadingBar(LoadingBarId), + NoLoadingBar(Uuid), #[cfg(feature = "tauri")] #[error("Tauri error: {0}")] diff --git a/theseus/src/launcher/auth.rs b/theseus/src/launcher/auth.rs index e6f9c523e..5da747965 100644 --- a/theseus/src/launcher/auth.rs +++ b/theseus/src/launcher/auth.rs @@ -1,7 +1,6 @@ //! Authentication flow based on Hydra use crate::util::fetch::{fetch_advanced, fetch_json}; use async_tungstenite as ws; -use bincode::{Decode, Encode}; use chrono::{prelude::*, Duration}; use futures::prelude::*; use lazy_static::lazy_static; @@ -50,14 +49,12 @@ struct ProfileInfoJSON { } // Login information -#[derive(Encode, Decode, Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct Credentials { - #[bincode(with_serde)] pub id: uuid::Uuid, pub username: String, pub access_token: String, pub refresh_token: String, - #[bincode(with_serde)] pub expires: DateTime, _ctor_scope: std::marker::PhantomData<()>, } diff --git a/theseus/src/launcher/download.rs b/theseus/src/launcher/download.rs index 6b0ea5936..9a8b457df 100644 --- a/theseus/src/launcher/download.rs +++ b/theseus/src/launcher/download.rs @@ -1,11 +1,7 @@ //! Downloader for Minecraft data use crate::{ - event::{ - emit::{emit_loading, init_loading, loading_try_for_each_concurrent}, - LoadingBarId, LoadingBarType, - }, - process::Profile, + event::emit::{emit_loading, loading_try_for_each_concurrent}, state::State, util::{fetch::*, platform::OsExt}, }; @@ -19,26 +15,17 @@ use daedalus::{ }; use futures::prelude::*; use tokio::{fs, sync::OnceCell}; +use uuid::Uuid; #[tracing::instrument(skip_all)] pub async fn download_minecraft( st: &State, version: &GameVersionInfo, - profile: &Profile, + loading_bar: Uuid, ) -> crate::Result<()> { log::info!("Downloading Minecraft version {}", version.id); let assets_index = download_assets_index(st, version).await?; - let loading_bar = init_loading( - LoadingBarType::MinecraftDownload { - // If we are downloading minecraft for a profile, provide its name and uuid - profile_name: profile.metadata.name.clone(), - profile_uuid: profile.uuid, - }, - 100.0, - "Downloading Minecraft...", - ) - .await?; tokio::try_join! { download_client(st, version, Some(&loading_bar)), download_assets(st, version.assets == "legacy", &assets_index, Some(&loading_bar)), @@ -54,6 +41,7 @@ pub async fn download_version_info( st: &State, version: &GameVersion, loader: Option<&LoaderVersion>, + force: Option, ) -> crate::Result { let version_id = loader .map_or(version.id.clone(), |it| format!("{}-{}", version.id, it.id)); @@ -63,7 +51,7 @@ pub async fn download_version_info( .version_dir(&version_id) .join(format!("{version_id}.json")); - let res = if path.exists() { + let res = if path.exists() && !force.unwrap_or(false) { fs::read(path) .err_into::() .await @@ -90,7 +78,7 @@ pub async fn download_version_info( pub async fn download_client( st: &State, version_info: &GameVersionInfo, - loading_bar: Option<&LoadingBarId>, + loading_bar: Option<&Uuid>, ) -> crate::Result<()> { let version = &version_info.id; log::debug!("Locating client for version {version}"); @@ -158,7 +146,7 @@ pub async fn download_assets( st: &State, with_legacy: bool, index: &AssetsIndex, - loading_bar: Option<&LoadingBarId>, + loading_bar: Option<&Uuid>, ) -> crate::Result<()> { log::debug!("Loading assets"); let num_futs = index.objects.len(); @@ -219,7 +207,7 @@ pub async fn download_libraries( st: &State, libraries: &[Library], version: &str, - loading_bar: Option<&LoadingBarId>, + loading_bar: Option<&Uuid>, ) -> crate::Result<()> { log::debug!("Loading libraries"); diff --git a/theseus/src/launcher/mod.rs b/theseus/src/launcher/mod.rs index 1b914f355..8f23921c2 100644 --- a/theseus/src/launcher/mod.rs +++ b/theseus/src/launcher/mod.rs @@ -1,4 +1,6 @@ //! Logic for launching Minecraft +use crate::event::emit::{emit_loading, init_or_edit_loading}; +use crate::event::LoadingBarType; use crate::{ process, state::{self as st, MinecraftChild}, @@ -8,6 +10,7 @@ use dunce::canonicalize; use st::Profile; use std::{path::Path, process::Stdio, sync::Arc}; use tokio::process::Command; +use uuid::Uuid; mod args; @@ -48,55 +51,60 @@ macro_rules! processor_rules { } } -#[allow(clippy::too_many_arguments)] -#[tracing::instrument(skip_all, fields(path = ?instance_path))] -pub async fn launch_minecraft( - game_version: &str, - loader_version: &Option, - instance_path: &Path, - java_install: &Path, - java_args: &[String], - env_args: &[(String, String)], - wrapper: &Option, - memory: &st::MemorySettings, - resolution: &st::WindowSize, - credentials: &auth::Credentials, - post_exit_hook: Option, - profile: &Profile, // optional ref to Profile for event tracking -) -> crate::Result>> { +pub async fn install_minecraft( + profile: &Profile, + existing_loading_bar: Option, +) -> crate::Result<()> { let state = st::State::get().await?; - let instance_path = &canonicalize(instance_path)?; + let instance_path = &canonicalize(&profile.path)?; + let metadata = state.metadata.read().await; - let version = state - .metadata + let version = metadata .minecraft .versions .iter() - .find(|it| it.id == game_version) + .find(|it| it.id == profile.metadata.game_version) .ok_or(crate::ErrorKind::LauncherError(format!( - "Invalid game version: {game_version}" + "Invalid game version: {}", + profile.metadata.game_version )))?; - let version_jar = - loader_version.as_ref().map_or(version.id.clone(), |it| { + let version_jar = profile + .metadata + .loader_version + .as_ref() + .map_or(version.id.clone(), |it| { format!("{}-{}", version.id.clone(), it.id.clone()) }); let mut version_info = download::download_version_info( &state, version, - loader_version.as_ref(), + profile.metadata.loader_version.as_ref(), + None, ) .await?; + let loading_bar = init_or_edit_loading( + existing_loading_bar, + LoadingBarType::MinecraftDownload { + // If we are downloading minecraft for a profile, provide its name and uuid + profile_name: profile.metadata.name.clone(), + profile_uuid: profile.uuid, + }, + 100.0, + "Downloading Minecraft...", + ) + .await?; + + download::download_minecraft(&state, &version_info, loading_bar).await?; + st::State::sync().await?; + let client_path = state .directories .version_dir(&version_jar) .join(format!("{version_jar}.jar")); - download::download_minecraft(&state, &version_info, profile).await?; - st::State::sync().await?; - if let Some(processors) = &version_info.processors { if let Some(ref mut data) = version_info.data { processor_rules! { @@ -108,7 +116,7 @@ pub async fn launch_minecraft( client => client_path.to_string_lossy(), server => ""; "MINECRAFT_VERSION": - client => game_version, + client => profile.metadata.game_version.clone(), server => ""; "ROOT": client => instance_path.to_string_lossy(), @@ -118,7 +126,21 @@ pub async fn launch_minecraft( server => ""; } - for processor in processors { + emit_loading(&loading_bar, 0.0, Some("Running forge processors")) + .await?; + let total_length = processors.len(); + + for (index, processor) in processors.iter().enumerate() { + emit_loading( + &loading_bar, + index as f64 / total_length as f64, + Some(&format!( + "Running forge processor {}/{}", + index, total_length + )), + ) + .await?; + if let Some(sides) = &processor.sides { if !sides.contains(&String::from("client")) { continue; @@ -173,6 +195,68 @@ pub async fn launch_minecraft( } } + crate::api::profile::edit(&profile.path, |prof| { + prof.installed = true; + + async { Ok(()) } + }) + .await?; + crate::api::profile::sync(&profile.path).await?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +pub async fn launch_minecraft( + java_install: &Path, + java_args: &[String], + env_args: &[(String, String)], + wrapper: &Option, + memory: &st::MemorySettings, + resolution: &st::WindowSize, + credentials: &auth::Credentials, + post_exit_hook: Option, + profile: &Profile, +) -> crate::Result>> { + if !profile.installed { + install_minecraft(profile, None).await?; + } + + let state = st::State::get().await?; + let metadata = state.metadata.read().await; + let instance_path = &canonicalize(&profile.path)?; + + let version = metadata + .minecraft + .versions + .iter() + .find(|it| it.id == profile.metadata.game_version) + .ok_or(crate::ErrorKind::LauncherError(format!( + "Invalid game version: {}", + profile.metadata.game_version + )))?; + + let version_jar = profile + .metadata + .loader_version + .as_ref() + .map_or(version.id.clone(), |it| { + format!("{}-{}", version.id.clone(), it.id.clone()) + }); + + let version_info = download::download_version_info( + &state, + version, + profile.metadata.loader_version.as_ref(), + None, + ) + .await?; + + let client_path = state + .directories + .version_dir(&version_jar) + .join(format!("{version_jar}.jar")); + let args = version_info.arguments.clone().unwrap_or_default(); let mut command = match wrapper { Some(hook) => { diff --git a/theseus/src/lib.rs b/theseus/src/lib.rs index 0daf2d1c4..5268455bd 100644 --- a/theseus/src/lib.rs +++ b/theseus/src/lib.rs @@ -19,5 +19,5 @@ mod state; pub use api::*; pub use error::*; -pub use event::EventState; +pub use event::{EventState, LoadingBar, LoadingBarType}; pub use state::State; diff --git a/theseus/src/state/dirs.rs b/theseus/src/state/dirs.rs index 4e5c0769e..b91aafcea 100644 --- a/theseus/src/state/dirs.rs +++ b/theseus/src/state/dirs.rs @@ -134,6 +134,11 @@ impl DirectoryInfo { self.config_dir.join("caches") } + #[inline] + pub fn caches_meta_dir(&self) -> PathBuf { + self.config_dir.join("caches").join("metadata") + } + /// Get path from environment variable #[inline] fn env_path(name: &str) -> Option { diff --git a/theseus/src/state/metadata.rs b/theseus/src/state/metadata.rs index a3c2e694b..fdcc0b93f 100644 --- a/theseus/src/state/metadata.rs +++ b/theseus/src/state/metadata.rs @@ -1,21 +1,18 @@ //! Theseus metadata -use crate::config::BINCODE_CONFIG; -use bincode::{Decode, Encode}; +use crate::data::DirectoryInfo; +use crate::util::fetch::{read_json, write}; use daedalus::{ minecraft::{fetch_version_manifest, VersionManifest as MinecraftManifest}, modded::{ fetch_manifest as fetch_loader_manifest, Manifest as LoaderManifest, }, }; -use futures::prelude::*; -use std::collections::LinkedList; +use serde::{Deserialize, Serialize}; +use tokio::sync::{RwLock, Semaphore}; const METADATA_URL: &str = "https://meta.modrinth.com"; -const METADATA_DB_FIELD: &[u8] = b"metadata"; -const RETRY_ATTEMPTS: i32 = 3; -// TODO: store as subtree in database -#[derive(Encode, Decode, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Metadata { pub minecraft: MinecraftManifest, pub forge: LoaderManifest, @@ -27,7 +24,7 @@ impl Metadata { format!("{METADATA_URL}/{name}/v0/manifest.json") } - async fn fetch() -> crate::Result { + pub async fn fetch() -> crate::Result { let (minecraft, forge, fabric) = tokio::try_join! { async { let url = Self::get_manifest("minecraft"); @@ -51,41 +48,42 @@ impl Metadata { } // Attempt to fetch metadata and store in sled DB - #[tracing::instrument(skip_all)] - pub async fn init(db: &sled::Db) -> crate::Result { + pub async fn init( + dirs: &DirectoryInfo, + io_semaphore: &RwLock, + ) -> crate::Result { let mut metadata = None; + let metadata_path = dirs.caches_meta_dir().join("metadata.json"); - if let Some(ref meta_bin) = db.get(METADATA_DB_FIELD)? { - match bincode::decode_from_slice::( - meta_bin, - *BINCODE_CONFIG, - ) { - Ok((meta, _)) => metadata = Some(meta), + if let Ok(metadata_json) = + read_json::(&metadata_path, io_semaphore).await + { + metadata = Some(metadata_json); + } else { + let res = async { + let metadata_fetch = Self::fetch().await?; + + write( + &metadata_path, + &serde_json::to_vec(&metadata_fetch).unwrap_or_default(), + io_semaphore, + ) + .await?; + + metadata = Some(metadata_fetch); + Ok::<(), crate::Error>(()) + } + .await; + + match res { + Ok(()) => {} Err(err) => { - log::warn!("Could not read launcher metadata: {err}") + log::warn!("Unable to fetch launcher metadata: {err}") } } } - let mut fetch_futures = LinkedList::new(); - for _ in 0..RETRY_ATTEMPTS { - fetch_futures.push_back(Self::fetch().boxed()); - } - - match future::select_ok(fetch_futures).await { - Ok(meta) => metadata = Some(meta.0), - Err(err) => log::warn!("Unable to fetch launcher metadata: {err}"), - } - if let Some(meta) = metadata { - db.insert( - METADATA_DB_FIELD, - sled::IVec::from(bincode::encode_to_vec( - &meta, - *BINCODE_CONFIG, - )?), - )?; - db.flush_async().await?; Ok(meta) } else { Err( @@ -94,4 +92,35 @@ impl Metadata { ) } } + + pub async fn update() { + let res = async { + let metadata_fetch = Metadata::fetch().await?; + let state = crate::State::get().await?; + + let metadata_path = + state.directories.caches_meta_dir().join("metadata.json"); + + write( + &metadata_path, + &serde_json::to_vec(&metadata_fetch)?, + &state.io_semaphore, + ) + .await + .unwrap(); + + let mut old_metadata = state.metadata.write().await; + *old_metadata = metadata_fetch; + + Ok::<(), crate::Error>(()) + } + .await; + + match res { + Ok(()) => {} + Err(err) => { + log::warn!("Unable to update launcher metadata: {err}") + } + }; + } } diff --git a/theseus/src/state/mod.rs b/theseus/src/state/mod.rs index 421553aaf..dc2b3743f 100644 --- a/theseus/src/state/mod.rs +++ b/theseus/src/state/mod.rs @@ -1,12 +1,11 @@ //! Theseus state management system -use crate::config::sled_config; use crate::event::emit::emit_loading; use crate::event::emit::init_loading; use crate::event::LoadingBarType; -use crate::jre; use crate::loading_join; +use crate::state::users::Users; use std::sync::Arc; use tokio::sync::{OnceCell, RwLock, Semaphore}; @@ -27,7 +26,6 @@ mod projects; pub use self::projects::*; mod users; -pub use self::users::*; mod children; pub use self::children::*; @@ -51,8 +49,7 @@ pub struct State { /// Stored maximum number of sempahores of current io_semaphore pub io_semaphore_max: RwLock, /// Launcher metadata - pub metadata: Metadata, - // TODO: settings API + pub metadata: RwLock, /// Launcher configuration pub settings: RwLock, /// Reference to minecraft process children @@ -68,76 +65,55 @@ pub struct State { } impl State { - #[tracing::instrument] /// Get the current launcher state, initializing it if needed pub async fn get() -> crate::Result> { LAUNCHER_STATE .get_or_try_init(|| { async { + let loading_bar = init_loading( + LoadingBarType::StateInit, + 100.0, + "Initializing launcher...", + ) + .await?; - let loading_bar = init_loading(LoadingBarType::StateInit, 100.0, "Initializing launcher...").await?; - // Directories let directories = DirectoryInfo::init().await?; - - // Database - // TODO: make database versioned - let database = sled_config() - .path(directories.database_file()) - .open()?; - emit_loading(&loading_bar, 10.0, None).await?; // Settings - let mut settings = + let settings = Settings::init(&directories.settings_file()).await?; + let io_semaphore = RwLock::new(Semaphore::new( + settings.max_concurrent_downloads, + )); + emit_loading(&loading_bar, 10.0, None).await?; - // Loose initializations - let io_semaphore_max = settings.max_concurrent_downloads; - - let io_semaphore = - RwLock::new(Semaphore::new(io_semaphore_max)); - - let metadata_fut = Metadata::init(&database); + let metadata_fut = + Metadata::init(&directories, &io_semaphore); let profiles_fut = Profiles::init(&directories, &io_semaphore); - + let tags_fut = Tags::init(&directories, &io_semaphore); + let users_fut = Users::init(&directories, &io_semaphore); // Launcher data - let (metadata, profiles) = loading_join! { - Some(&loading_bar), 20.0, Some("Initializing metadata and profiles..."); - metadata_fut, profiles_fut + let (metadata, profiles, tags, users) = loading_join! { + Some(&loading_bar), 70.0, Some("Initializing..."); + metadata_fut, + profiles_fut, + tags_fut, + users_fut, }?; - emit_loading(&loading_bar, 10.0, None).await?; - let users = Users::init(&database)?; - let children = Children::new(); - let auth_flow = AuthTask::new(); - - // On launcher initialization, attempt a tag fetch after tags init - let mut tags = Tags::init(&database)?; - if let Err(tag_fetch_err) = - tags.fetch_update(&io_semaphore,Some(&loading_bar)).await - { - tracing::error!( - "Failed to fetch tags on launcher init: {}", - tag_fetch_err - ); - }; - emit_loading(&loading_bar, 10.0, None).await?; - // On launcher initialization, if global java variables are unset, try to find and set them - // (they are required for the game to launch) - if settings.java_globals.count() == 0 { - settings.java_globals = jre::autodetect_java_globals().await?; - } - Ok(Arc::new(Self { directories, io_semaphore, - io_semaphore_max: RwLock::new(io_semaphore_max as u32), - metadata, + io_semaphore_max: RwLock::new( + settings.max_concurrent_downloads as u32, + ), + metadata: RwLock::new(metadata), settings: RwLock::new(settings), profiles: RwLock::new(profiles), users: RwLock::new(users), @@ -151,6 +127,14 @@ impl State { .map(Arc::clone) } + /// Updates state with data from the web + pub fn update() { + tokio::task::spawn(Metadata::update()); + tokio::task::spawn(Tags::update()); + tokio::task::spawn(Profiles::update_projects()); + tokio::task::spawn(Settings::update_java()); + } + #[tracing::instrument] /// Synchronize in-memory state with persistent state pub async fn sync() -> crate::Result<()> { diff --git a/theseus/src/state/profiles.rs b/theseus/src/state/profiles.rs index 7d3722cb5..297f2ceb4 100644 --- a/theseus/src/state/profiles.rs +++ b/theseus/src/state/profiles.rs @@ -1,10 +1,8 @@ use super::settings::{Hooks, MemorySettings, WindowSize}; use crate::config::MODRINTH_API_URL; use crate::data::DirectoryInfo; -use crate::event::emit::{ - emit_profile, init_loading, loading_try_for_each_concurrent, -}; -use crate::event::{LoadingBarType, ProfilePayloadType}; +use crate::event::emit::emit_profile; +use crate::event::ProfilePayloadType; use crate::state::projects::Project; use crate::state::{ModrinthVersion, ProjectType}; use crate::util::fetch::{fetch, fetch_json, write, write_cached_icon}; @@ -34,6 +32,8 @@ pub const CURRENT_FORMAT_VERSION: u32 = 1; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Profile { pub uuid: Uuid, // todo: will be used in restructure to refer to profiles + #[serde(default)] + pub installed: bool, pub path: PathBuf, pub metadata: ProfileMetadata, #[serde(skip_serializing_if = "Option::is_none")] @@ -58,10 +58,15 @@ pub struct ProfileMetadata { #[serde(skip_serializing_if = "Option::is_none")] pub loader_version: Option, pub format_version: u32, - pub linked_project_id: Option, + pub linked_data: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct LinkedData { + pub project_id: Option, + pub version_id: Option, } -// TODO: Quilt? #[derive( Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize, Default, )] @@ -85,6 +90,17 @@ impl std::fmt::Display for ModLoader { } } +impl ModLoader { + pub(crate) fn as_api_str(&self) -> &'static str { + match *self { + Self::Vanilla => "vanilla", + Self::Forge => "forge", + Self::Fabric => "fabric", + Self::Quilt => "quilt", + } + } +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct JavaSettings { #[serde(skip_serializing_if = "Option::is_none")] @@ -110,6 +126,7 @@ impl Profile { Ok(Self { uuid, + installed: false, path: canonicalize(path)?, metadata: ProfileMetadata { name, @@ -118,7 +135,7 @@ impl Profile { loader: ModLoader::Vanilla, loader_version: None, format_version: CURRENT_FORMAT_VERSION, - linked_project_id: None, + linked_data: None, }, projects: HashMap::new(), java: None, @@ -147,7 +164,7 @@ impl Profile { let paths = self.get_profile_project_paths()?; let projects = crate::state::infer_data_from_files( - paths, + &[(self.clone(), paths)], state.directories.caches_dir(), &state.io_semaphore, ) @@ -155,6 +172,14 @@ impl Profile { self.projects = projects; + emit_profile( + self.uuid, + self.path.clone(), + &self.metadata.name, + ProfilePayloadType::Synced, + ) + .await?; + Ok(()) } @@ -266,8 +291,6 @@ impl Profile { let path = self.path.join(project_type.get_folder()).join(file_name); write(&path, &bytes, &state.io_semaphore).await?; - self.sync().await?; - Ok(path) } @@ -345,36 +368,62 @@ impl Profiles { } } - // project path, parent profile path - let mut files: HashMap = HashMap::new(); - { - for (profile_path, profile) in profiles.iter() { - let paths = profile.get_profile_project_paths()?; - - for path in paths { - files.insert(path, profile_path.clone()); - } - } - } - - let inferred = super::projects::infer_data_from_files( - files.keys().cloned().collect(), - dirs.caches_dir(), - io_sempahore, - ) - .await?; - - for (key, value) in inferred { - if let Some(profile_path) = files.get(&key) { - if let Some(profile) = profiles.get_mut(profile_path) { - profile.projects.insert(key, value); - } - } - } - Ok(Self(profiles)) } + pub async fn update_projects() { + let res = async { + let state = State::get().await?; + + // profile, child paths + let mut files: Vec<(Profile, Vec)> = Vec::new(); + { + let profiles = state.profiles.read().await; + for (_profile_path, profile) in profiles.0.iter() { + let paths = profile.get_profile_project_paths()?; + + files.push((profile.clone(), paths)); + } + } + + if !files.is_empty() { + let inferred = super::projects::infer_data_from_files( + &files, + state.directories.caches_dir(), + &state.io_semaphore, + ) + .await?; + let mut wipe_profiles = Vec::new(); + for (key, value) in inferred { + if let Some((profile, _)) = + files.iter().find(|(_, files)| files.contains(&key)) + { + let mut new_profiles = state.profiles.write().await; + if let Some(profile) = + new_profiles.0.get_mut(&profile.path) + { + if !wipe_profiles.contains(&profile.path) { + profile.projects = HashMap::new(); + wipe_profiles.push(profile.path.clone()); + } + profile.projects.insert(key, value); + } + } + } + } + + Ok::<(), crate::Error>(()) + } + .await; + + match res { + Ok(()) => {} + Err(err) => { + log::warn!("Unable to fetch profile projects: {err}") + } + }; + } + #[tracing::instrument(skip(self))] pub async fn insert(&mut self, profile: Profile) -> crate::Result<&Self> { emit_profile( @@ -406,35 +455,26 @@ impl Profiles { } #[tracing::instrument(skip(self))] - pub async fn remove(&mut self, path: &Path) -> crate::Result<&Self> { + pub async fn remove( + &mut self, + path: &Path, + ) -> crate::Result> { let path = PathBuf::from(&canonicalize(path)?.to_string_lossy().to_string()); - self.0.remove(&path); + let profile = self.0.remove(&path); if path.exists() { fs::remove_dir_all(path).await?; } - Ok(self) + Ok(profile) } #[tracing::instrument(skip_all)] pub async fn sync(&self) -> crate::Result<&Self> { - let loading_bar = init_loading( - LoadingBarType::ProfileSync, - 100.0, - "Syncing profiles...", - ) - .await?; - let num_futs = self.0.len(); - loading_try_for_each_concurrent( - stream::iter(self.0.iter()).map(Ok::<_, crate::Error>), - None, - Some(&loading_bar), - 100.0, - num_futs, - None, - |(path, profile)| async move { + stream::iter(self.0.iter()) + .map(Ok::<_, crate::Error>) + .try_for_each_concurrent(None, |(path, profile)| async move { let json = serde_json::to_vec(&profile)?; let json_path = Path::new(&path.to_string_lossy().to_string()) @@ -442,9 +482,8 @@ impl Profiles { fs::write(json_path, json).await?; Ok::<_, crate::Error>(()) - }, - ) - .await?; + }) + .await?; Ok(self) } diff --git a/theseus/src/state/projects.rs b/theseus/src/state/projects.rs index a9d31d6e3..85da2f26a 100644 --- a/theseus/src/state/projects.rs +++ b/theseus/src/state/projects.rs @@ -1,6 +1,8 @@ //! Project management + inference use crate::config::MODRINTH_API_URL; +use crate::data::ModLoader; +use crate::state::Profile; use crate::util::fetch::{fetch_json, write_cached_icon}; use async_zip::tokio::read::fs::ZipFileReader; use chrono::{DateTime, Utc}; @@ -185,6 +187,8 @@ pub enum ProjectMetadata { project: Box, version: Box, members: Vec, + update_version: Option>, + incompatible: bool, }, Inferred { title: Option, @@ -246,21 +250,23 @@ async fn read_icon_from_file( } pub async fn infer_data_from_files( - paths: Vec, + paths: &[(Profile, Vec)], cache_dir: PathBuf, io_semaphore: &RwLock, ) -> crate::Result> { let mut file_path_hashes = HashMap::new(); // TODO: Make this concurrent and use progressive hashing to avoid loading each JAR in memory - for path in paths.clone() { - let mut file = tokio::fs::File::open(path.clone()).await?; + for set in paths { + for path in &set.1 { + let mut file = tokio::fs::File::open(path.clone()).await?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer).await?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer).await?; - let hash = format!("{:x}", sha2::Sha512::digest(&buffer)); - file_path_hashes.insert(hash, path.clone()); + let hash = format!("{:x}", sha2::Sha512::digest(&buffer)); + file_path_hashes.insert(hash, path.clone()); + } } let files: HashMap = fetch_json( @@ -291,7 +297,6 @@ pub async fn infer_data_from_files( io_semaphore, ) .await?; - let teams: Vec = fetch_json::< Vec>, >( @@ -312,6 +317,26 @@ pub async fn infer_data_from_files( .flatten() .collect(); + let mut update_versions: Vec = fetch_json( + Method::GET, + &format!( + "{}versions?ids={}", + MODRINTH_API_URL, + serde_json::to_string( + &projects + .iter() + .flat_map(|x| x.versions.clone()) + .collect::>() + )? + ), + None, + None, + io_semaphore, + ) + .await?; + + update_versions.sort_by(|a, b| b.date_published.cmp(&a.date_published)); + let mut return_projects = HashMap::new(); let mut further_analyze_projects: Vec<(String, PathBuf)> = Vec::new(); @@ -320,18 +345,14 @@ pub async fn infer_data_from_files( if let Some(project) = projects.iter().find(|x| version.project_id == x.id) { + let profile = paths.iter().find(|x| x.1.contains(&path)); + let file_name = path .file_name() .unwrap_or_default() .to_string_lossy() .to_string(); - let team_members = teams - .iter() - .filter(|x| x.team_id == project.team) - .cloned() - .collect::>(); - return_projects.insert( path, Project { @@ -340,7 +361,52 @@ pub async fn infer_data_from_files( metadata: ProjectMetadata::Modrinth { project: Box::new(project.clone()), version: Box::new(version.clone()), - members: team_members, + members: teams + .iter() + .filter(|x| x.team_id == project.team) + .cloned() + .collect::>(), + update_version: if let Some((profile, _)) = &profile + { + update_versions + .iter() + .find(|x| { + x.project_id == project.id + && x.game_versions.contains( + &profile.metadata.game_version, + ) + && if profile.metadata.loader + == ModLoader::Vanilla + { + true + } else { + x.loaders.contains( + &profile + .metadata + .loader + .as_api_str() + .to_string(), + ) + } + }) + .cloned() + .map(Box::new) + } else { + None + }, + incompatible: if let Some((profile, _)) = &profile { + !version.loaders.contains( + &profile + .metadata + .loader + .as_api_str() + .to_string(), + ) || version + .game_versions + .contains(&profile.metadata.game_version) + } else { + false + }, }, file_name, }, diff --git a/theseus/src/state/settings.rs b/theseus/src/state/settings.rs index 0bf0ba939..ddc32f991 100644 --- a/theseus/src/state/settings.rs +++ b/theseus/src/state/settings.rs @@ -1,4 +1,5 @@ //! Theseus settings file +use crate::{jre, State}; use serde::{Deserialize, Serialize}; use std::path::Path; use tokio::fs; @@ -63,6 +64,29 @@ impl Settings { } } + pub async fn update_java() { + let res = async { + let state = State::get().await?; + let settings_read = state.settings.write().await; + + if settings_read.java_globals.count() == 0 { + drop(settings_read); + let java_globals = jre::autodetect_java_globals().await?; + state.settings.write().await.java_globals = java_globals; + } + + Ok::<(), crate::Error>(()) + } + .await; + + match res { + Ok(()) => {} + Err(err) => { + log::warn!("Unable to update launcher java: {err}") + } + }; + } + #[tracing::instrument(skip(self))] pub async fn sync(&self, to: &Path) -> crate::Result<()> { fs::write(to, serde_json::to_vec(self)?) diff --git a/theseus/src/state/tags.rs b/theseus/src/state/tags.rs index ec6e31e31..55805e81c 100644 --- a/theseus/src/state/tags.rs +++ b/theseus/src/state/tags.rs @@ -1,152 +1,125 @@ use std::path::PathBuf; -use bincode::{Decode, Encode}; use reqwest::Method; use serde::{Deserialize, Serialize}; use tokio::sync::{RwLock, Semaphore}; -use crate::config::{BINCODE_CONFIG, MODRINTH_API_URL}; -use crate::event::LoadingBarId; -use crate::loading_join; -use crate::util::fetch::fetch_json; +use crate::config::MODRINTH_API_URL; +use crate::data::DirectoryInfo; +use crate::util::fetch::{fetch_json, read_json, write}; -const CATEGORIES_DB_TREE: &[u8] = b"categories"; -const LOADERS_DB_TREE: &[u8] = b"loaders"; -const GAME_VERSIONS_DB_TREE: &[u8] = b"game_versions"; -const LICENSES_DB_TREE: &[u8] = b"licenses"; -const DONATION_PLATFORMS_DB_TREE: &[u8] = b"donation_platforms"; -const REPORT_TYPES_DB_TREE: &[u8] = b"report_types"; - -#[derive(Clone)] -pub(crate) struct Tags(pub(crate) TagsInner); - -#[derive(Debug, Clone)] -pub struct TagsInner { - pub categories: sled::Tree, - pub loaders: sled::Tree, - pub game_versions: sled::Tree, - pub licenses: sled::Tree, - pub donation_platforms: sled::Tree, - pub report_types: sled::Tree, +// Serializeable struct for all tags to be fetched together by the frontend +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Tags { + pub categories: Vec, + pub loaders: Vec, + pub game_versions: Vec, + pub donation_platforms: Vec, + pub report_types: Vec, } impl Tags { - #[tracing::instrument(skip(db))] - pub fn init(db: &sled::Db) -> crate::Result { - Ok(Tags(TagsInner { - categories: db.open_tree(CATEGORIES_DB_TREE)?, - loaders: db.open_tree(LOADERS_DB_TREE)?, - game_versions: db.open_tree(GAME_VERSIONS_DB_TREE)?, - licenses: db.open_tree(LICENSES_DB_TREE)?, - donation_platforms: db.open_tree(DONATION_PLATFORMS_DB_TREE)?, - report_types: db.open_tree(REPORT_TYPES_DB_TREE)?, - })) + pub async fn init( + dirs: &DirectoryInfo, + io_semaphore: &RwLock, + ) -> crate::Result { + let mut tags = None; + let tags_path = dirs.caches_meta_dir().join("tags.json"); + + if let Ok(tags_json) = read_json::(&tags_path, io_semaphore).await + { + tags = Some(tags_json); + } else { + match Self::fetch(io_semaphore).await { + Ok(tags_fetch) => tags = Some(tags_fetch), + Err(err) => { + log::warn!("Unable to fetch launcher tags: {err}") + } + } + } + + if let Some(tags_data) = tags { + write(&tags_path, &serde_json::to_vec(&tags_data)?, io_semaphore) + .await?; + Ok(tags_data) + } else { + Err(crate::ErrorKind::NoValueFor(String::from("launcher tags")) + .as_error()) + } + } + + pub async fn update() { + let res = async { + let state = crate::State::get().await?; + let tags_fetch = Tags::fetch(&state.io_semaphore).await?; + + let tags_path = + state.directories.caches_meta_dir().join("tags.json"); + + write( + &tags_path, + &serde_json::to_vec(&tags_fetch)?, + &state.io_semaphore, + ) + .await + .unwrap(); + + let mut old_tags = state.tags.write().await; + *old_tags = tags_fetch; + + Ok::<(), crate::Error>(()) + } + .await; + + match res { + Ok(()) => {} + Err(err) => { + log::warn!("Unable to update launcher tags: {err}") + } + }; } // Checks the database for categories tag, returns a Vec::new() if it doesnt exist, otherwise returns the categories #[tracing::instrument(skip(self))] - pub fn get_categories(&self) -> crate::Result> { - self.0.categories.get("categories")?.map_or( - Ok(Vec::new()), - |categories| { - bincode::decode_from_slice(&categories, *BINCODE_CONFIG) - .map_err(crate::Error::from) - .map(|it| it.0) - }, - ) + pub fn get_categories(&self) -> Vec { + self.categories.clone() } // Checks the database for loaders tag, returns a Vec::new() if it doesnt exist, otherwise returns the loaders #[tracing::instrument(skip(self))] - pub fn get_loaders(&self) -> crate::Result> { - self.0 - .loaders - .get("loaders")? - .map_or(Ok(Vec::new()), |loaders| { - bincode::decode_from_slice(&loaders, *BINCODE_CONFIG) - .map_err(crate::Error::from) - .map(|it| it.0) - }) + pub fn get_loaders(&self) -> Vec { + self.loaders.clone() } // Checks the database for game_versions tag, returns a Vec::new() if it doesnt exist, otherwise returns the game_versions #[tracing::instrument(skip(self))] - pub fn get_game_versions(&self) -> crate::Result> { - self.0.game_versions.get("game_versions")?.map_or( - Ok(Vec::new()), - |game_versions| { - bincode::decode_from_slice(&game_versions, *BINCODE_CONFIG) - .map_err(crate::Error::from) - .map(|it| it.0) - }, - ) - } - - // Checks the database for licenses tag, returns a Vec::new() if it doesnt exist, otherwise returns the licenses - #[tracing::instrument(skip(self))] - pub fn get_licenses(&self) -> crate::Result> { - self.0 - .licenses - .get("licenses")? - .map_or(Ok(Vec::new()), |licenses| { - bincode::decode_from_slice(&licenses, *BINCODE_CONFIG) - .map_err(crate::Error::from) - .map(|it| it.0) - }) + pub fn get_game_versions(&self) -> Vec { + self.game_versions.clone() } // Checks the database for donation_platforms tag, returns a Vec::new() if it doesnt exist, otherwise returns the donation_platforms #[tracing::instrument(skip(self))] - pub fn get_donation_platforms( - &self, - ) -> crate::Result> { - self.0.donation_platforms.get("donation_platforms")?.map_or( - Ok(Vec::new()), - |donation_platforms| { - bincode::decode_from_slice(&donation_platforms, *BINCODE_CONFIG) - .map_err(crate::Error::from) - .map(|it| it.0) - }, - ) + pub fn get_donation_platforms(&self) -> Vec { + self.donation_platforms.clone() } // Checks the database for report_types tag, returns a Vec::new() if it doesnt exist, otherwise returns the report_types #[tracing::instrument(skip(self))] - pub fn get_report_types(&self) -> crate::Result> { - self.0.report_types.get("report_types")?.map_or( - Ok(Vec::new()), - |report_types| { - bincode::decode_from_slice(&report_types, *BINCODE_CONFIG) - .map_err(crate::Error::from) - .map(|it| it.0) - }, - ) + pub fn get_report_types(&self) -> Vec { + self.report_types.clone() } // Gets all tags together as a serializable bundle #[tracing::instrument(skip(self))] - pub fn get_tag_bundle(&self) -> crate::Result { - Ok(TagBundle { - categories: self.get_categories()?, - loaders: self.get_loaders()?, - game_versions: self.get_game_versions()?, - licenses: self.get_licenses()?, - donation_platforms: self.get_donation_platforms()?, - report_types: self.get_report_types()?, - }) + pub fn get_tag_bundle(&self) -> Tags { + self.clone() } // Fetches the tags from the Modrinth API and stores them in the database - #[tracing::instrument(skip(self))] - pub async fn fetch_update( - &mut self, - semaphore: &RwLock, - loading_bar: Option<&LoadingBarId>, - ) -> crate::Result<()> { + pub async fn fetch(semaphore: &RwLock) -> crate::Result { let categories = format!("{MODRINTH_API_URL}tag/category"); let loaders = format!("{MODRINTH_API_URL}tag/loader"); let game_versions = format!("{MODRINTH_API_URL}tag/game_version"); - let licenses = format!("{MODRINTH_API_URL}tag/license"); let donation_platforms = format!("{MODRINTH_API_URL}tag/donation_platform"); let report_types = format!("{MODRINTH_API_URL}tag/report_type"); @@ -172,13 +145,6 @@ impl Tags { None, semaphore, ); - let licenses_fut = fetch_json::>( - Method::GET, - &licenses, - None, - None, - semaphore, - ); let donation_platforms_fut = fetch_json::>( Method::GET, &donation_platforms, @@ -198,60 +164,27 @@ impl Tags { categories, loaders, game_versions, - licenses, donation_platforms, report_types, - ) = loading_join!(loading_bar, 0.5, None; + ) = tokio::try_join!( categories_fut, loaders_fut, game_versions_fut, - licenses_fut, donation_platforms_fut, report_types_fut )?; - // Store the tags in the database - self.0.categories.insert( - "categories", - bincode::encode_to_vec(categories, *BINCODE_CONFIG)?, - )?; - self.0.loaders.insert( - "loaders", - bincode::encode_to_vec(loaders, *BINCODE_CONFIG)?, - )?; - self.0.game_versions.insert( - "game_versions", - bincode::encode_to_vec(game_versions, *BINCODE_CONFIG)?, - )?; - self.0.licenses.insert( - "licenses", - bincode::encode_to_vec(licenses, *BINCODE_CONFIG)?, - )?; - self.0.donation_platforms.insert( - "donation_platforms", - bincode::encode_to_vec(donation_platforms, *BINCODE_CONFIG)?, - )?; - self.0.report_types.insert( - "report_types", - bincode::encode_to_vec(report_types, *BINCODE_CONFIG)?, - )?; - - Ok(()) + Ok(Self { + categories, + loaders, + game_versions, + donation_platforms, + report_types, + }) } } -// Serializeable struct for all tags to be fetched together by the frontend #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TagBundle { - pub categories: Vec, - pub loaders: Vec, - pub game_versions: Vec, - pub licenses: Vec, - pub donation_platforms: Vec, - pub report_types: Vec, -} - -#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)] pub struct Category { pub name: String, pub project_type: String, @@ -259,26 +192,20 @@ pub struct Category { pub icon: PathBuf, } -#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Loader { pub name: String, pub icon: PathBuf, pub supported_project_types: Vec, } -#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)] -pub struct License { - pub short: String, - pub name: String, -} - -#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct DonationPlatform { pub short: String, pub name: String, } -#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct GameVersion { pub version: String, pub version_type: String, diff --git a/theseus/src/state/users.rs b/theseus/src/state/users.rs index 84d49ffe5..6c58a3c41 100644 --- a/theseus/src/state/users.rs +++ b/theseus/src/state/users.rs @@ -1,79 +1,70 @@ //! User login info -use crate::{auth::Credentials, config::BINCODE_CONFIG}; +use crate::auth::Credentials; +use crate::data::DirectoryInfo; +use crate::util::fetch::{read_json, write}; +use crate::State; +use std::collections::HashMap; +use tokio::sync::{RwLock, Semaphore}; +use uuid::Uuid; -const USER_DB_TREE: &[u8] = b"users"; +const USERS_JSON: &str = "users.json"; /// The set of users stored in the launcher #[derive(Clone)] -pub(crate) struct Users(pub(crate) sled::Tree); +pub(crate) struct Users(pub(crate) HashMap); impl Users { - #[tracing::instrument(skip(db))] - pub fn init(db: &sled::Db) -> crate::Result { - Ok(Self(db.open_tree(USER_DB_TREE)?)) + pub async fn init( + dirs: &DirectoryInfo, + io_semaphore: &RwLock, + ) -> crate::Result { + let users_path = dirs.caches_meta_dir().join(USERS_JSON); + let users = read_json(&users_path, io_semaphore).await.ok(); + + if let Some(users) = users { + Ok(Self(users)) + } else { + Ok(Self(HashMap::new())) + } + } + + pub async fn save(&self) -> crate::Result<()> { + let state = State::get().await?; + let users_path = state.directories.caches_meta_dir().join(USERS_JSON); + write( + &users_path, + &serde_json::to_vec(&self.0)?, + &state.io_semaphore, + ) + .await?; + + Ok(()) } #[tracing::instrument(skip_all)] - pub fn insert( + pub async fn insert( &mut self, credentials: &Credentials, ) -> crate::Result<&Self> { - let id = credentials.id.as_bytes(); - self.0.insert( - id, - bincode::encode_to_vec(credentials, *BINCODE_CONFIG)?, - )?; + self.0.insert(credentials.id, credentials.clone()); + self.save().await?; Ok(self) } #[tracing::instrument(skip(self))] - pub fn contains(&self, id: uuid::Uuid) -> crate::Result { - Ok(self.0.contains_key(id.as_bytes())?) + pub fn contains(&self, id: Uuid) -> bool { + self.0.contains_key(&id) } #[tracing::instrument(skip(self))] - pub fn get(&self, id: uuid::Uuid) -> crate::Result> { - self.0.get(id.as_bytes())?.map_or(Ok(None), |prof| { - bincode::decode_from_slice(&prof, *BINCODE_CONFIG) - .map_err(crate::Error::from) - .map(|it| Some(it.0)) - }) + pub fn get(&self, id: Uuid) -> Option { + self.0.get(&id).cloned() } #[tracing::instrument(skip(self))] - pub fn remove(&mut self, id: uuid::Uuid) -> crate::Result<&Self> { - self.0.remove(id.as_bytes())?; + pub async fn remove(&mut self, id: Uuid) -> crate::Result<&Self> { + self.0.remove(&id); + self.save().await?; Ok(self) } - - pub fn iter(&self) -> UserIter { - UserIter(self.0.iter().values(), false) - } -} - -alias_trait! { - pub UserInnerIter: Iterator>, Send, Sync -} - -/// An iterator over the set of users -#[derive(Debug)] -pub struct UserIter(I, bool); - -impl Iterator for UserIter { - type Item = crate::Result; - - #[tracing::instrument(skip(self))] - fn next(&mut self) -> Option { - if self.1 { - return None; - } - - let it = self.0.next()?; - let res = it.map_err(crate::Error::from).and_then(|it| { - Ok(bincode::decode_from_slice(&it, *BINCODE_CONFIG)?.0) - }); - - self.1 = res.is_err(); - Some(res) - } } diff --git a/theseus/src/util/fetch.rs b/theseus/src/util/fetch.rs index 4bff0aeed..2bb286742 100644 --- a/theseus/src/util/fetch.rs +++ b/theseus/src/util/fetch.rs @@ -144,6 +144,22 @@ pub async fn fetch_mirrors( unreachable!() } +pub async fn read_json( + path: &Path, + semaphore: &RwLock, +) -> crate::Result +where + T: DeserializeOwned, +{ + let io_semaphore = semaphore.read().await; + let _permit = io_semaphore.acquire().await?; + + let json = fs::read(path).await?; + let json = serde_json::from_slice::(&json)?; + + Ok(json) +} + #[tracing::instrument(skip(bytes, semaphore))] pub async fn write<'a>( path: &Path, diff --git a/theseus/src/util/mod.rs b/theseus/src/util/mod.rs index 6f8731bfe..a3a4c7937 100644 --- a/theseus/src/util/mod.rs +++ b/theseus/src/util/mod.rs @@ -14,11 +14,3 @@ macro_rules! wrap_ref_builder { it }}; } - -/// Alias a trait, used to avoid needing nightly features -macro_rules! alias_trait { - ($scope:vis $name:ident : $bound:path $(, $bounds:path)*) => { - $scope trait $name: $bound $(+ $bounds)* {} - impl $name for T {} - } -} diff --git a/theseus_cli/src/subcommands/profile.rs b/theseus_cli/src/subcommands/profile.rs index 10125ecce..edcb678b8 100644 --- a/theseus_cli/src/subcommands/profile.rs +++ b/theseus_cli/src/subcommands/profile.rs @@ -64,6 +64,7 @@ impl ProfileInit { ) -> Result<()> { // TODO: validate inputs from args early let state = State::get().await?; + let metadata = state.metadata.read().await; if self.path.exists() { ensure!( @@ -114,7 +115,7 @@ impl ProfileInit { let game_version = match &self.game_version { Some(version) => version.clone(), None => { - let default = &state.metadata.minecraft.latest.release; + let default = &metadata.minecraft.latest.release; prompt_async( String::from("Game version"), @@ -163,8 +164,8 @@ impl ProfileInit { }; let loader_data = match loader { - ModLoader::Forge => &state.metadata.forge, - ModLoader::Fabric => &state.metadata.fabric, + ModLoader::Forge => &metadata.forge, + ModLoader::Fabric => &metadata.fabric, _ => eyre::bail!("Could not get manifest for loader {loader}. This is a bug in the CLI!"), }; @@ -193,6 +194,7 @@ impl ProfileInit { loader.map(|x| x.0.id), None, None, + None, ) .await?; diff --git a/theseus_cli/src/subcommands/user.rs b/theseus_cli/src/subcommands/user.rs index 36c813c94..e866b982f 100644 --- a/theseus_cli/src/subcommands/user.rs +++ b/theseus_cli/src/subcommands/user.rs @@ -151,7 +151,6 @@ impl UserDefault { ) -> Result<()> { info!("Setting user {} as default", self.user.as_hyphenated()); - // TODO: settings API let state: std::sync::Arc = State::get().await?; let mut settings = state.settings.write().await; diff --git a/theseus_gui/.gitignore b/theseus_gui/.gitignore index 6541bae65..bedd035f5 100644 --- a/theseus_gui/.gitignore +++ b/theseus_gui/.gitignore @@ -9,8 +9,6 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* -generated.js - node_modules *.local @@ -24,5 +22,3 @@ node_modules *.njsproj *.sln *.sw? - -generated.js diff --git a/theseus_gui/jsconfig.json b/theseus_gui/jsconfig.json new file mode 100644 index 000000000..686295e60 --- /dev/null +++ b/theseus_gui/jsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/theseus_gui/src-tauri/Cargo.toml b/theseus_gui/src-tauri/Cargo.toml index 82c826cee..2d7c32896 100644 --- a/theseus_gui/src-tauri/Cargo.toml +++ b/theseus_gui/src-tauri/Cargo.toml @@ -19,7 +19,7 @@ theseus = { path = "../../theseus", features = ["tauri"] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.2", features = ["dialog", "dialog-all", "protocol-asset", "window-close", "window-create"] } +tauri = { version = "1.2", features = ["dialog", "dialog-open", "protocol-asset", "window-close", "window-create"] } tokio = { version = "1", features = ["full"] } thiserror = "1.0" tokio-stream = { version = "0.1", features = ["fs"] } diff --git a/theseus_gui/src-tauri/src/api/auth.rs b/theseus_gui/src-tauri/src/api/auth.rs index 9e7857563..d2ec6bc3c 100644 --- a/theseus_gui/src-tauri/src/api/auth.rs +++ b/theseus_gui/src-tauri/src/api/auth.rs @@ -40,7 +40,7 @@ pub async fn auth_has_user(user: uuid::Uuid) -> Result { /// Get a copy of the list of all user credentials // invoke('auth_users',user) #[tauri::command] -pub async fn auth_users() -> Result> { +pub async fn auth_users() -> Result> { Ok(auth::users().await?) } diff --git a/theseus_gui/src-tauri/src/api/mod.rs b/theseus_gui/src-tauri/src/api/mod.rs index 88762feab..8e693f871 100644 --- a/theseus_gui/src-tauri/src/api/mod.rs +++ b/theseus_gui/src-tauri/src/api/mod.rs @@ -46,6 +46,14 @@ where } } +// Lists active progress bars +#[tauri::command] +pub async fn progress_bars_list( +) -> Result> { + let res = theseus::EventState::list_progress_bars().await?; + Ok(res) +} + // This is a very simple macro that implements a very basic Serializable for each variant of TheseusSerializableError, // where the field is the string. (This allows easy extension to errors without many match arms) macro_rules! impl_serialize { diff --git a/theseus_gui/src-tauri/src/api/profile.rs b/theseus_gui/src-tauri/src/api/profile.rs index 3a59d0e0e..b00a19821 100644 --- a/theseus_gui/src-tauri/src/api/profile.rs +++ b/theseus_gui/src-tauri/src/api/profile.rs @@ -28,6 +28,53 @@ pub async fn profile_list( Ok(res) } +/// Syncs a profile's in memory state with the state on the disk +/// // invoke('profile_sync') +#[tauri::command] +pub async fn profile_sync(path: &Path) -> Result<()> { + profile::sync(path).await?; + Ok(()) +} + +/// Installs/Repairs a profile +/// invoke('profile_install') +#[tauri::command] +pub async fn profile_install(path: &Path) -> Result<()> { + profile::install(path).await?; + Ok(()) +} + +/// Updates all of the profile's projects +/// invoke('profile_update_all') +#[tauri::command] +pub async fn profile_update_all(path: &Path) -> Result<()> { + profile::update_all(path).await?; + Ok(()) +} + +/// Updates a specified project +/// invoke('profile_update_project') +#[tauri::command] +pub async fn profile_update_project( + path: &Path, + project_path: &Path, +) -> Result<()> { + profile::update_project(path, project_path, None).await?; + Ok(()) +} + +/// Replaces a project with the given version ID +/// invoke('profile_replace_project') +#[tauri::command] +pub async fn profile_replace_project( + path: &Path, + project: &Path, + version_id: String, +) -> Result { + let res = profile::replace_project(path, project, version_id).await?; + Ok(res) +} + // Adds a project to a profile from a version ID // invoke('profile_add_project_from_version') #[tauri::command] diff --git a/theseus_gui/src-tauri/src/api/profile_create.rs b/theseus_gui/src-tauri/src/api/profile_create.rs index f2a4f2e67..5cc865dda 100644 --- a/theseus_gui/src-tauri/src/api/profile_create.rs +++ b/theseus_gui/src-tauri/src/api/profile_create.rs @@ -27,6 +27,7 @@ pub async fn profile_create( Some(loader_version), icon, None, + None, ) .await?; Ok(res) diff --git a/theseus_gui/src-tauri/src/api/tags.rs b/theseus_gui/src-tauri/src/api/tags.rs index 8748b31d7..0ae2bad65 100644 --- a/theseus_gui/src-tauri/src/api/tags.rs +++ b/theseus_gui/src-tauri/src/api/tags.rs @@ -1,7 +1,5 @@ use crate::api::Result; -use theseus::tags::{ - Category, DonationPlatform, GameVersion, License, Loader, TagBundle, -}; +use theseus::tags::{Category, DonationPlatform, GameVersion, Loader, Tags}; /// Gets cached category tags from the database #[tauri::command] @@ -27,12 +25,6 @@ pub async fn tags_get_game_versions() -> Result> { Ok(theseus::tags::get_game_version_tags().await?) } -/// Gets cached license tags from the database -#[tauri::command] -pub async fn tags_get_licenses() -> Result> { - Ok(theseus::tags::get_license_tags().await?) -} - /// Gets cached donation platform tags from the database #[tauri::command] pub async fn tags_get_donation_platforms() -> Result> { @@ -41,6 +33,6 @@ pub async fn tags_get_donation_platforms() -> Result> { /// Gets cached tag bundle from the database #[tauri::command] -pub async fn tags_get_tag_bundle() -> Result { +pub async fn tags_get_tag_bundle() -> Result { Ok(theseus::tags::get_tag_bundle().await?) } diff --git a/theseus_gui/src-tauri/src/main.rs b/theseus_gui/src-tauri/src/main.rs index cec576510..e63d9c750 100644 --- a/theseus_gui/src-tauri/src/main.rs +++ b/theseus_gui/src-tauri/src/main.rs @@ -12,6 +12,7 @@ mod api; async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> { theseus::EventState::init(app).await?; State::get().await?; + State::update(); Ok(()) } @@ -41,11 +42,17 @@ fn main() { .invoke_handler(tauri::generate_handler![ initialize_state, should_disable_mouseover, + api::progress_bars_list, api::profile_create::profile_create_empty, api::profile_create::profile_create, api::profile::profile_remove, api::profile::profile_get, api::profile::profile_list, + api::profile::profile_sync, + api::profile::profile_install, + api::profile::profile_update_all, + api::profile::profile_update_project, + api::profile::profile_replace_project, api::profile::profile_add_project_from_version, api::profile::profile_add_project_from_path, api::profile::profile_toggle_disable_project, @@ -67,7 +74,6 @@ fn main() { api::tags::tags_get_donation_platforms, api::tags::tags_get_game_versions, api::tags::tags_get_loaders, - api::tags::tags_get_licenses, api::tags::tags_get_report_types, api::tags::tags_get_tag_bundle, api::settings::settings_get, diff --git a/theseus_gui/src-tauri/tauri.conf.json b/theseus_gui/src-tauri/tauri.conf.json index 68ecc7703..f0dee4419 100644 --- a/theseus_gui/src-tauri/tauri.conf.json +++ b/theseus_gui/src-tauri/tauri.conf.json @@ -13,7 +13,7 @@ "tauri": { "allowlist": { "dialog": { - "all": true + "open": true }, "protocol": { "asset": true, @@ -62,7 +62,7 @@ } }, "security": { - "csp": "default-src 'self'; connect-src https://modrinth.com https://*.modrinth.com; style-src https://rsms.me/inter/ 'unsafe-inline'; font-src https://rsms.me/inter/; img-src tauri: https: data: blob: 'unsafe-inline' asset: https://asset.localhost" + "csp": "default-src 'self'; connect-src https://modrinth.com https://*.modrinth.com; font-src https://cdn-raw.modrinth.com/fonts/inter/; img-src tauri: https: data: blob: 'unsafe-inline' asset: https://asset.localhost" }, "updater": { "active": false diff --git a/theseus_gui/src/assets/stylesheets/global.scss b/theseus_gui/src/assets/stylesheets/global.scss index 4813b0b23..39229d43f 100644 --- a/theseus_gui/src/assets/stylesheets/global.scss +++ b/theseus_gui/src/assets/stylesheets/global.scss @@ -1,4 +1,4 @@ -@import url('https://rsms.me/inter/inter.css'); +@import 'inter.scss'; :root { font-family: var(--font-standard); diff --git a/theseus_gui/src/assets/stylesheets/inter.scss b/theseus_gui/src/assets/stylesheets/inter.scss new file mode 100644 index 000000000..bbd8fa344 --- /dev/null +++ b/theseus_gui/src/assets/stylesheets/inter.scss @@ -0,0 +1,41 @@ +// TODO: move to omorphia +@font-face { + font-family: inter; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Regular.woff2?v=3.19') format('woff2'), + url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Regular.woff?v=3.19') format('woff'); +} +@font-face { + font-family: inter; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Medium.woff2?v=3.19') format('woff2'), + url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Medium.woff?v=3.19') format('woff'); +} +@font-face { + font-family: inter; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-SemiBold.woff2?v=3.19') format('woff2'), + url('https://cdn-raw.modrinth.com/fonts/inter/Inter-SemiBold.woff?v=3.19') format('woff'); +} +@font-face { + font-family: inter; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Bold.woff2?v=3.19') format('woff2'), + url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Bold.woff?v=3.19') format('woff'); +} +@font-face { + font-family: inter; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-ExtraBold.woff2?v=3.19') format('woff2'), + url('https://cdn-raw.modrinth.com/fonts/inter/Inter-ExtraBold.woff?v=3.19') format('woff'); +} diff --git a/theseus_gui/src/helpers/profile.js b/theseus_gui/src/helpers/profile.js index 95749abdd..0ff91d5ce 100644 --- a/theseus_gui/src/helpers/profile.js +++ b/theseus_gui/src/helpers/profile.js @@ -42,26 +42,52 @@ export async function list() { return await invoke('profile_list') } +// Syncs a profile with the disk +export async function sync(path) { + return await invoke('profile_sync', { path }) +} + +// Installs/Repairs a profile +export async function install(path) { + return await invoke('profile_install', { path }) +} + +// Updates all of a profile's projects +export async function update_all(path) { + return await invoke('profile_update_all', { path }) +} + +// Updates a specified project +export async function update_project(path, projectPath) { + return await invoke('profile_update_project', { path, projectPath }) +} + +// Replaces a given project with the specified version ID +// Returns a path to the new project file +export async function replace_project(path, projectPath, versionId) { + return await invoke('profile_replace_project', { path, projectPath, versionId }) +} + // Add a project to a profile from a version // Returns a path to the new project file -export async function add_project_from_version(path, version_id) { - return await invoke('profile_add_project_from_version', { path, version_id }) +export async function add_project_from_version(path, versionId) { + return await invoke('profile_add_project_from_version', { path, versionId }) } // Add a project to a profile from a path + project_type // Returns a path to the new project file -export async function add_project_from_path(path, project_path, project_type) { - return await invoke('profile_add_project_from_path', { path, project_path, project_type }) +export async function add_project_from_path(path, projectPath, projectType) { + return await invoke('profile_add_project_from_path', { path, projectPath, projectType }) } // Toggle disabling a project -export async function toggle_disable_project(path, project_path) { - return await invoke('profile_toggle_disable_project', { path, project_path }) +export async function toggle_disable_project(path, projectPath) { + return await invoke('profile_toggle_disable_project', { path, projectPath }) } // Remove a project -export async function remove_project(path, project_path) { - return await invoke('profile_remove_project', { path, project_path }) +export async function remove_project(path, projectPath) { + return await invoke('profile_remove_project', { path, projectPath }) } // Run Minecraft using a pathed profile diff --git a/theseus_gui/src/helpers/state.js b/theseus_gui/src/helpers/state.js index 1b65402a1..c4d768c2e 100644 --- a/theseus_gui/src/helpers/state.js +++ b/theseus_gui/src/helpers/state.js @@ -10,3 +10,8 @@ import { invoke } from '@tauri-apps/api/tauri' export async function initialize_state() { return await invoke('initialize_state') } + +// Gets active progress bars +export async function progress_bars_list() { + return await invoke('progress_bars_list') +} diff --git a/theseus_gui/src/helpers/tags.js b/theseus_gui/src/helpers/tags.js index 6bdd36e82..2ce3f234b 100644 --- a/theseus_gui/src/helpers/tags.js +++ b/theseus_gui/src/helpers/tags.js @@ -25,11 +25,6 @@ export async function get_game_versions() { return await invoke('tags_get_game_versions') } -// Gets cached licenses tags -export async function get_licenses() { - return await invoke('tags_get_licenses') -} - // Gets cached donation_platforms tags export async function get_donation_platforms() { return await invoke('tags_get_donation_platforms') diff --git a/theseus_gui/src/main.js b/theseus_gui/src/main.js index a8c2fcde0..e79e06b15 100644 --- a/theseus_gui/src/main.js +++ b/theseus_gui/src/main.js @@ -2,7 +2,7 @@ import { createApp } from 'vue' import router from '@/routes' import App from '@/App.vue' import { createPinia } from 'pinia' -import '../node_modules/omorphia/dist/style.css' +import 'omorphia/dist/style.css' import '@/assets/stylesheets/global.scss' import FloatingVue from 'floating-vue' import { initialize_state } from '@/helpers/state' @@ -10,8 +10,14 @@ import loadCssMixin from './mixins/macCssFix.js' const pinia = createPinia() +let app = createApp(App) +app.use(router) +app.use(pinia) +app.use(FloatingVue) +app.mixin(loadCssMixin) + initialize_state() - .then(() => { - createApp(App).use(router).use(pinia).use(FloatingVue).mixin(loadCssMixin).mount('#app') + .then(() => app.mount('#app')) + .catch((err) => { + console.error(err) }) - .catch((err) => console.error(err)) diff --git a/theseus_gui/vite.config.js b/theseus_gui/vite.config.js index 549e8583b..e1560bc53 100644 --- a/theseus_gui/vite.config.js +++ b/theseus_gui/vite.config.js @@ -19,7 +19,6 @@ export default defineConfig({ }, ], }), - eslint(), svgLoader({ svgoConfig: { plugins: [ @@ -34,6 +33,7 @@ export default defineConfig({ ], }, }), + eslint(), ], // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` diff --git a/theseus_gui/yarn.lock b/theseus_gui/yarn.lock index 5bdad4f3a..77f9b2ba8 100644 --- a/theseus_gui/yarn.lock +++ b/theseus_gui/yarn.lock @@ -7,115 +7,115 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17" integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw== -"@esbuild/android-arm64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.17.tgz#164b054d58551f8856285f386e1a8f45d9ba3a31" - integrity sha512-jaJ5IlmaDLFPNttv0ofcwy/cfeY4bh/n705Tgh+eLObbGtQBK3EPAu+CzL95JVE4nFAliyrnEu0d32Q5foavqg== +"@esbuild/android-arm64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.18.tgz#4aa8d8afcffb4458736ca9b32baa97d7cb5861ea" + integrity sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw== -"@esbuild/android-arm@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.17.tgz#1b3b5a702a69b88deef342a7a80df4c894e4f065" - integrity sha512-E6VAZwN7diCa3labs0GYvhEPL2M94WLF8A+czO8hfjREXxba8Ng7nM5VxV+9ihNXIY1iQO1XxUU4P7hbqbICxg== +"@esbuild/android-arm@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.18.tgz#74a7e95af4ee212ebc9db9baa87c06a594f2a427" + integrity sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw== -"@esbuild/android-x64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.17.tgz#6781527e3c4ea4de532b149d18a2167f06783e7f" - integrity sha512-446zpfJ3nioMC7ASvJB1pszHVskkw4u/9Eu8s5yvvsSDTzYh4p4ZIRj0DznSl3FBF0Z/mZfrKXTtt0QCoFmoHA== +"@esbuild/android-x64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.18.tgz#1dcd13f201997c9fe0b204189d3a0da4eb4eb9b6" + integrity sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg== -"@esbuild/darwin-arm64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.17.tgz#c5961ef4d3c1cc80dafe905cc145b5a71d2ac196" - integrity sha512-m/gwyiBwH3jqfUabtq3GH31otL/0sE0l34XKpSIqR7NjQ/XHQ3lpmQHLHbG8AHTGCw8Ao059GvV08MS0bhFIJQ== +"@esbuild/darwin-arm64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.18.tgz#444f3b961d4da7a89eb9bd35cfa4415141537c2a" + integrity sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ== -"@esbuild/darwin-x64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.17.tgz#b81f3259cc349691f67ae30f7b333a53899b3c20" - integrity sha512-4utIrsX9IykrqYaXR8ob9Ha2hAY2qLc6ohJ8c0CN1DR8yWeMrTgYFjgdeQ9LIoTOfLetXjuCu5TRPHT9yKYJVg== +"@esbuild/darwin-x64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.18.tgz#a6da308d0ac8a498c54d62e0b2bfb7119b22d315" + integrity sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A== -"@esbuild/freebsd-arm64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.17.tgz#db846ad16cf916fd3acdda79b85ea867cb100e87" - integrity sha512-4PxjQII/9ppOrpEwzQ1b0pXCsFLqy77i0GaHodrmzH9zq2/NEhHMAMJkJ635Ns4fyJPFOlHMz4AsklIyRqFZWA== +"@esbuild/freebsd-arm64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.18.tgz#b83122bb468889399d0d63475d5aea8d6829c2c2" + integrity sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA== -"@esbuild/freebsd-x64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.17.tgz#4dd99acbaaba00949d509e7c144b1b6ef9e1815b" - integrity sha512-lQRS+4sW5S3P1sv0z2Ym807qMDfkmdhUYX30GRBURtLTrJOPDpoU0kI6pVz1hz3U0+YQ0tXGS9YWveQjUewAJw== +"@esbuild/freebsd-x64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.18.tgz#af59e0e03fcf7f221b34d4c5ab14094862c9c864" + integrity sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew== -"@esbuild/linux-arm64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.17.tgz#7f9274140b2bb9f4230dbbfdf5dc2761215e30f6" - integrity sha512-2+pwLx0whKY1/Vqt8lyzStyda1v0qjJ5INWIe+d8+1onqQxHLLi3yr5bAa4gvbzhZqBztifYEu8hh1La5+7sUw== +"@esbuild/linux-arm64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.18.tgz#8551d72ba540c5bce4bab274a81c14ed01eafdcf" + integrity sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ== -"@esbuild/linux-arm@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.17.tgz#5c8e44c2af056bb2147cf9ad13840220bcb8948b" - integrity sha512-biDs7bjGdOdcmIk6xU426VgdRUpGg39Yz6sT9Xp23aq+IEHDb/u5cbmu/pAANpDB4rZpY/2USPhCA+w9t3roQg== +"@esbuild/linux-arm@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.18.tgz#e09e76e526df4f665d4d2720d28ff87d15cdf639" + integrity sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg== -"@esbuild/linux-ia32@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.17.tgz#18a6b3798658be7f46e9873fa0c8d4bec54c9212" - integrity sha512-IBTTv8X60dYo6P2t23sSUYym8fGfMAiuv7PzJ+0LcdAndZRzvke+wTVxJeCq4WgjppkOpndL04gMZIFvwoU34Q== +"@esbuild/linux-ia32@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.18.tgz#47878860ce4fe73a36fd8627f5647bcbbef38ba4" + integrity sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ== -"@esbuild/linux-loong64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.17.tgz#a8d93514a47f7b4232716c9f02aeb630bae24c40" - integrity sha512-WVMBtcDpATjaGfWfp6u9dANIqmU9r37SY8wgAivuKmgKHE+bWSuv0qXEFt/p3qXQYxJIGXQQv6hHcm7iWhWjiw== +"@esbuild/linux-loong64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.18.tgz#3f8fbf5267556fc387d20b2e708ce115de5c967a" + integrity sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ== -"@esbuild/linux-mips64el@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.17.tgz#4784efb1c3f0eac8133695fa89253d558149ee1b" - integrity sha512-2kYCGh8589ZYnY031FgMLy0kmE4VoGdvfJkxLdxP4HJvWNXpyLhjOvxVsYjYZ6awqY4bgLR9tpdYyStgZZhi2A== +"@esbuild/linux-mips64el@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.18.tgz#9d896d8f3c75f6c226cbeb840127462e37738226" + integrity sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA== -"@esbuild/linux-ppc64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.17.tgz#ef6558ec5e5dd9dc16886343e0ccdb0699d70d3c" - integrity sha512-KIdG5jdAEeAKogfyMTcszRxy3OPbZhq0PPsW4iKKcdlbk3YE4miKznxV2YOSmiK/hfOZ+lqHri3v8eecT2ATwQ== +"@esbuild/linux-ppc64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.18.tgz#3d9deb60b2d32c9985bdc3e3be090d30b7472783" + integrity sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ== -"@esbuild/linux-riscv64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.17.tgz#13a87fdbcb462c46809c9d16bcf79817ecf9ce6f" - integrity sha512-Cj6uWLBR5LWhcD/2Lkfg2NrkVsNb2sFM5aVEfumKB2vYetkA/9Uyc1jVoxLZ0a38sUhFk4JOVKH0aVdPbjZQeA== +"@esbuild/linux-riscv64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.18.tgz#8a943cf13fd24ff7ed58aefb940ef178f93386bc" + integrity sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA== -"@esbuild/linux-s390x@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.17.tgz#83cb16d1d3ac0dca803b3f031ba3dc13f1ec7ade" - integrity sha512-lK+SffWIr0XsFf7E0srBjhpkdFVJf3HEgXCwzkm69kNbRar8MhezFpkIwpk0qo2IOQL4JE4mJPJI8AbRPLbuOQ== +"@esbuild/linux-s390x@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.18.tgz#66cb01f4a06423e5496facabdce4f7cae7cb80e5" + integrity sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw== -"@esbuild/linux-x64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.17.tgz#7bc400568690b688e20a0c94b2faabdd89ae1a79" - integrity sha512-XcSGTQcWFQS2jx3lZtQi7cQmDYLrpLRyz1Ns1DzZCtn898cWfm5Icx/DEWNcTU+T+tyPV89RQtDnI7qL2PObPg== +"@esbuild/linux-x64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.18.tgz#23c26050c6c5d1359c7b774823adc32b3883b6c9" + integrity sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA== -"@esbuild/netbsd-x64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.17.tgz#1b5dcfbc4bfba80e67a11e9148de836af5b58b6c" - integrity sha512-RNLCDmLP5kCWAJR+ItLM3cHxzXRTe4N00TQyQiimq+lyqVqZWGPAvcyfUBM0isE79eEZhIuGN09rAz8EL5KdLA== +"@esbuild/netbsd-x64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.18.tgz#789a203d3115a52633ff6504f8cbf757f15e703b" + integrity sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg== -"@esbuild/openbsd-x64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.17.tgz#e275098902291149a5dcd012c9ea0796d6b7adff" - integrity sha512-PAXswI5+cQq3Pann7FNdcpSUrhrql3wKjj3gVkmuz6OHhqqYxKvi6GgRBoaHjaG22HV/ZZEgF9TlS+9ftHVigA== +"@esbuild/openbsd-x64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.18.tgz#d7b998a30878f8da40617a10af423f56f12a5e90" + integrity sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA== -"@esbuild/sunos-x64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.17.tgz#10603474866f64986c0370a2d4fe5a2bb7fee4f5" - integrity sha512-V63egsWKnx/4V0FMYkr9NXWrKTB5qFftKGKuZKFIrAkO/7EWLFnbBZNM1CvJ6Sis+XBdPws2YQSHF1Gqf1oj/Q== +"@esbuild/sunos-x64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.18.tgz#ecad0736aa7dae07901ba273db9ef3d3e93df31f" + integrity sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg== -"@esbuild/win32-arm64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.17.tgz#521a6d97ee0f96b7c435930353cc4e93078f0b54" - integrity sha512-YtUXLdVnd6YBSYlZODjWzH+KzbaubV0YVd6UxSfoFfa5PtNJNaW+1i+Hcmjpg2nEe0YXUCNF5bkKy1NnBv1y7Q== +"@esbuild/win32-arm64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.18.tgz#58dfc177da30acf956252d7c8ae9e54e424887c4" + integrity sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg== -"@esbuild/win32-ia32@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.17.tgz#56f88462ebe82dad829dc2303175c0e0ccd8e38e" - integrity sha512-yczSLRbDdReCO74Yfc5tKG0izzm+lPMYyO1fFTcn0QNwnKmc3K+HdxZWLGKg4pZVte7XVgcFku7TIZNbWEJdeQ== +"@esbuild/win32-ia32@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.18.tgz#340f6163172b5272b5ae60ec12c312485f69232b" + integrity sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw== -"@esbuild/win32-x64@0.17.17": - version "0.17.17" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.17.tgz#2b577b976e6844106715bbe0cdc57cd1528063f9" - integrity sha512-FNZw7H3aqhF9OyRQbDDnzUApDXfC1N6fgBhkqEO2jvYCJ+DxMTfZVqg3AX0R1khg1wHTBRD5SdcibSJ+XF6bFg== +"@esbuild/win32-x64@0.17.18": + version "0.17.18" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.18.tgz#3a8e57153905308db357fd02f57c180ee3a0a1fa" + integrity sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.3.0": version "4.4.0" @@ -144,10 +144,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.38.0": - version "8.38.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.38.0.tgz#73a8a0d8aa8a8e6fe270431c5e72ae91b5337892" - integrity sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g== +"@eslint/js@8.39.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.39.0.tgz#58b536bcc843f4cd1e02a7e6171da5c040f4d44b" + integrity sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng== "@floating-ui/core@^0.3.0": version "0.3.1" @@ -305,9 +305,9 @@ integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@vitejs/plugin-vue@^4.0.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.1.0.tgz#b6a9d83cd91575f7ee15593f6444397f68751073" - integrity sha512-++9JOAFdcXI3lyer9UKUV4rfoQ3T1RN8yDqoCLar86s0xQct5yblxAE+yWgRnU5/0FOlVCpTZpYSBV/bGWrSrQ== + version "4.2.1" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.2.1.tgz#c3ccce9956e8cdca946f465188777e4e3e488f6a" + integrity sha512-ZTZjzo7bmxTRTkb8GSTwkPOYDIP7pwuyV+RV53c9PYUouwcbkIZIvWvNWlX2b1dYZqtOv7D6iUAnJLVNGcLrSw== "@vue/compiler-core@3.2.47": version "3.2.47" @@ -667,32 +667,32 @@ entities@~3.0.1: integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== esbuild@^0.17.5: - version "0.17.17" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.17.tgz#fa906ab11b11d2ed4700f494f4f764229b25c916" - integrity sha512-/jUywtAymR8jR4qsa2RujlAF7Krpt5VWi72Q2yuLD4e/hvtNcFQ0I1j8m/bxq238pf3/0KO5yuXNpuLx8BE1KA== + version "0.17.18" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.18.tgz#f4f8eb6d77384d68cd71c53eb6601c7efe05e746" + integrity sha512-z1lix43jBs6UKjcZVKOw2xx69ffE2aG0PygLL5qJ9OS/gy0Ewd1gW/PUQIOIQGXBHWNywSc0floSKoMFF8aK2w== optionalDependencies: - "@esbuild/android-arm" "0.17.17" - "@esbuild/android-arm64" "0.17.17" - "@esbuild/android-x64" "0.17.17" - "@esbuild/darwin-arm64" "0.17.17" - "@esbuild/darwin-x64" "0.17.17" - "@esbuild/freebsd-arm64" "0.17.17" - "@esbuild/freebsd-x64" "0.17.17" - "@esbuild/linux-arm" "0.17.17" - "@esbuild/linux-arm64" "0.17.17" - "@esbuild/linux-ia32" "0.17.17" - "@esbuild/linux-loong64" "0.17.17" - "@esbuild/linux-mips64el" "0.17.17" - "@esbuild/linux-ppc64" "0.17.17" - "@esbuild/linux-riscv64" "0.17.17" - "@esbuild/linux-s390x" "0.17.17" - "@esbuild/linux-x64" "0.17.17" - "@esbuild/netbsd-x64" "0.17.17" - "@esbuild/openbsd-x64" "0.17.17" - "@esbuild/sunos-x64" "0.17.17" - "@esbuild/win32-arm64" "0.17.17" - "@esbuild/win32-ia32" "0.17.17" - "@esbuild/win32-x64" "0.17.17" + "@esbuild/android-arm" "0.17.18" + "@esbuild/android-arm64" "0.17.18" + "@esbuild/android-x64" "0.17.18" + "@esbuild/darwin-arm64" "0.17.18" + "@esbuild/darwin-x64" "0.17.18" + "@esbuild/freebsd-arm64" "0.17.18" + "@esbuild/freebsd-x64" "0.17.18" + "@esbuild/linux-arm" "0.17.18" + "@esbuild/linux-arm64" "0.17.18" + "@esbuild/linux-ia32" "0.17.18" + "@esbuild/linux-loong64" "0.17.18" + "@esbuild/linux-mips64el" "0.17.18" + "@esbuild/linux-ppc64" "0.17.18" + "@esbuild/linux-riscv64" "0.17.18" + "@esbuild/linux-s390x" "0.17.18" + "@esbuild/linux-x64" "0.17.18" + "@esbuild/netbsd-x64" "0.17.18" + "@esbuild/openbsd-x64" "0.17.18" + "@esbuild/sunos-x64" "0.17.18" + "@esbuild/win32-arm64" "0.17.18" + "@esbuild/win32-ia32" "0.17.18" + "@esbuild/win32-x64" "0.17.18" escape-string-regexp@^4.0.0: version "4.0.0" @@ -717,7 +717,7 @@ eslint-plugin-vue@^9.9.0: vue-eslint-parser "^9.0.1" xml-name-validator "^4.0.0" -eslint-scope@^7.1.1: +eslint-scope@^7.1.1, eslint-scope@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== @@ -731,14 +731,14 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0: integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== eslint@^8.35.0: - version "8.38.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.38.0.tgz#a62c6f36e548a5574dd35728ac3c6209bd1e2f1a" - integrity sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg== + version "8.39.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.39.0.tgz#7fd20a295ef92d43809e914b70c39fd5a23cf3f1" + integrity sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" "@eslint/eslintrc" "^2.0.2" - "@eslint/js" "8.38.0" + "@eslint/js" "8.39.0" "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -748,7 +748,7 @@ eslint@^8.35.0: debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" + eslint-scope "^7.2.0" eslint-visitor-keys "^3.4.0" espree "^9.5.1" esquery "^1.4.2" @@ -889,11 +889,6 @@ fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" @@ -937,13 +932,6 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - highlight.js@^11.7.0: version "11.7.0" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e" @@ -992,13 +980,6 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-core-module@^2.11.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" - integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== - dependencies: - has "^1.0.3" - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -1168,7 +1149,7 @@ ofetch@^1.0.1: node-fetch-native "^1.0.2" ufo "^1.1.0" -omorphia@^0.4.11: +omorphia@^0.4.10: version "0.4.10" resolved "https://registry.yarnpkg.com/omorphia/-/omorphia-0.4.10.tgz#93c0e6a08a233f27d76587286e42450af44bb55d" integrity sha512-WgSFosOqoM0IRpzGNYyprfZSRyBLgqs6sTmKRuWo96ZpzrHRWAom2upIm/HAxAC+YBwFni5sgUeBemXYI7wmuw== @@ -1237,11 +1218,6 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -1253,9 +1229,9 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2: integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pinia@^2.0.33: - version "2.0.34" - resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.0.34.tgz#6c0c84f06b631c30c030633fa64e525c609105a9" - integrity sha512-cgOoGUiyqX0SSgX8XelK9+Ri4XA2/YyNtgjogwfzIx1g7iZTaZPxm7/bZYMCLU2qHRiHhxG7SuQO0eBacFNc2Q== + version "2.0.35" + resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.0.35.tgz#aa2597038bb55ea14ad689f83065d2814ebb8c10" + integrity sha512-P1IKKQWhxGXiiZ3atOaNI75bYlFUbRxtJdhPLX059Z7+b9Z04rnTZdSY8Aph1LA+/4QEMAYHsTQ638Wfe+6K5g== dependencies: "@vue/devtools-api" "^6.5.0" vue-demi "*" @@ -1268,7 +1244,7 @@ postcss-selector-parser@^6.0.9: cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss@^8.1.10, postcss@^8.4.21: +postcss@^8.1.10, postcss@^8.4.23: version "8.4.23" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab" integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA== @@ -1283,9 +1259,9 @@ prelude-ls@^1.2.1: integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prettier@^2.8.7: - version "2.8.7" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450" - integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw== + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== punycode@^2.1.0: version "2.3.0" @@ -1309,15 +1285,6 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve@^1.22.1: - version "1.22.2" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" - integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== - dependencies: - is-core-module "^2.11.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -1337,10 +1304,10 @@ rollup@^2.77.2: optionalDependencies: fsevents "~2.3.2" -rollup@^3.18.0: - version "3.20.6" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.20.6.tgz#53c0fd73e397269d2ce5f0ec12851457dd53cacd" - integrity sha512-2yEB3nQXp/tBQDN0hJScJQheXdvU2wFhh6ld7K/aiZ1vYcak6N/BKjY1QrU6BvO2JWYS8bEs14FRaxXosxy2zw== +rollup@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.21.0.tgz#0a71517db56e150222670f88e5e7acfa4fede7c8" + integrity sha512-ANPhVcyeHvYdQMUyCbczy33nbLzI7RzrBje4uvNiTDJGIMtlKoOStmympwr9OtS1LZxiDmE2wvxHyVhoLtf1KQ== optionalDependencies: fsevents "~2.3.2" @@ -1352,9 +1319,9 @@ run-parallel@^1.1.9: queue-microtask "^1.2.2" sass@^1.58.3: - version "1.62.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.62.0.tgz#3686b2195b93295d20765135e562366b33ece37d" - integrity sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg== + version "1.62.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.62.1.tgz#caa8d6bf098935bc92fc73fa169fb3790cacd029" + integrity sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -1418,11 +1385,6 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - svgo@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.0.2.tgz#5e99eeea42c68ee0dc46aa16da093838c262fe0a" @@ -1499,14 +1461,13 @@ vite-svg-loader@^4.0.0: svgo "^3.0.2" vite@^4.0.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.2.2.tgz#014c30e5163844f6e96d7fe18fbb702236516dc6" - integrity sha512-PcNtT5HeDxb3QaSqFYkEum8f5sCVe0R3WK20qxgIvNBZPXU/Obxs/+ubBMeE7nLWeCo2LDzv+8hRYSlcaSehig== + version "4.3.3" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.3.tgz#26adb4aa01439fc4546c480ea547674d87289396" + integrity sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA== dependencies: esbuild "^0.17.5" - postcss "^8.4.21" - resolve "^1.22.1" - rollup "^3.18.0" + postcss "^8.4.23" + rollup "^3.21.0" optionalDependencies: fsevents "~2.3.2" diff --git a/theseus_playground/src/main.rs b/theseus_playground/src/main.rs index 2179d0bf7..9953ed01e 100644 --- a/theseus_playground/src/main.rs +++ b/theseus_playground/src/main.rs @@ -32,6 +32,8 @@ async fn main() -> theseus::Result<()> { // Initialize state let st = State::get().await?; + State::update(); + st.settings.write().await.max_concurrent_downloads = 5; st.settings.write().await.hooks.post_exit = Some("echo This is after Minecraft runs- global setting!".to_string()); @@ -61,26 +63,30 @@ async fn main() -> theseus::Result<()> { Some(loader_version), None, None, + None, ) .await?; - println!("Adding sodium"); - let sodium_path = profile::add_project_from_version( - &profile_path, - "rAfhHfow".to_string(), - ) - .await?; + // let mut value = list().await?; + // let profile_path = value.iter().next().map(|x| x.0).unwrap(); - let mod_menu_path = profile::add_project_from_version( - &profile_path, - "gSoPJyVn".to_string(), - ) - .await?; - - println!("Disabling sodium"); - profile::toggle_disable_project(&profile_path, &sodium_path).await?; - - profile::remove_project(&profile_path, &mod_menu_path).await?; + // println!("Adding sodium"); + // let sodium_path = profile::add_project_from_version( + // &profile_path, + // "rAfhHfow".to_string(), + // ) + // .await?; + // + // let mod_menu_path = profile::add_project_from_version( + // &profile_path, + // "gSoPJyVn".to_string(), + // ) + // .await?; + // + // println!("Disabling sodium"); + // profile::toggle_disable_project(&profile_path, &sodium_path).await?; + // + // profile::remove_project(&profile_path, &mod_menu_path).await?; // let profile_path = // pack::install_pack_from_version_id("KxUUUFh5".to_string()) // .await @@ -102,7 +108,7 @@ async fn main() -> theseus::Result<()> { State::sync().await?; // Attempt to run game - if auth::users().await?.len() == 0 { + 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 }