From 90b3287a253412aac76ad90def528b5d0657fdc4 Mon Sep 17 00:00:00 2001 From: aecsocket Date: Tue, 3 Mar 2026 02:10:03 +0000 Subject: [PATCH 01/15] fixes --- Cargo.lock | 185 +++++++++++------- Cargo.toml | 2 +- .../settings/ResourceManagementSettings.vue | 3 + 3 files changed, 113 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8930c71b41..07e0b9d3b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2147,7 +2147,7 @@ dependencies = [ "futures", "indexmap 2.11.4", "itertools 0.14.0", - "reqwest", + "reqwest 0.12.24", "rust-s3", "serde", "serde-xml-rs", @@ -4189,9 +4189,9 @@ dependencies = [ [[package]] name = "ico" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" dependencies = [ "byteorder", "png 0.17.16", @@ -4682,9 +4682,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -4850,7 +4850,7 @@ dependencies = [ "rand_chacha 0.3.1", "redis", "regex", - "reqwest", + "reqwest 0.12.24", "rust-s3", "rust_decimal", "rust_iso3166", @@ -5305,7 +5305,7 @@ dependencies = [ "log", "meilisearch-index-setting-macro", "pin-project-lite", - "reqwest", + "reqwest 0.12.24", "serde", "serde_json", "thiserror 2.0.17", @@ -5414,7 +5414,7 @@ dependencies = [ "flate2", "maxminddb", "modrinth-util", - "reqwest", + "reqwest 0.12.24", "tar", "tokio", "tracing", @@ -5493,7 +5493,7 @@ dependencies = [ "bytes", "chrono", "derive_more 2.0.1", - "reqwest", + "reqwest 0.12.24", "rust_decimal", "rust_iso3166", "secrecy", @@ -6247,7 +6247,7 @@ dependencies = [ "bytes", "http 1.3.1", "opentelemetry", - "reqwest", + "reqwest 0.12.24", ] [[package]] @@ -6262,7 +6262,7 @@ dependencies = [ "opentelemetry-proto", "opentelemetry_sdk", "prost 0.13.5", - "reqwest", + "reqwest 0.12.24", "thiserror 2.0.17", "tokio", "tonic 0.13.1", @@ -7703,11 +7703,45 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-streams 0.4.2", "web-sys", "webpki-roots 1.0.3", ] +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.7.0", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams 0.5.0", + "web-sys", +] + [[package]] name = "resolv-conf" version = "0.7.5" @@ -7893,7 +7927,7 @@ dependencies = [ "minidom", "percent-encoding", "quick-xml 0.38.3", - "reqwest", + "reqwest 0.12.24", "serde", "serde_derive", "serde_json", @@ -8361,7 +8395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48b85e25e8a1fc13928885e8bf13abe8a09e15c46993aed05d6405f7755d6e20" dependencies = [ "httpdate", - "reqwest", + "reqwest 0.12.24", "rustls 0.23.32", "sentry-backtrace", "sentry-contexts", @@ -9464,9 +9498,9 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tao" -version = "0.34.3" +version = "0.34.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7" +checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" dependencies = [ "bitflags 2.9.4", "block2 0.6.2", @@ -9538,9 +9572,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.8.5" +version = "2.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d1d3b3dc4c101ac989fd7db77e045cc6d91a25349cd410455cb5c57d510c1c" +checksum = "463ae8677aa6d0f063a900b9c41ecd4ac2b7ca82f0b058cc4491540e55b20129" dependencies = [ "anyhow", "bytes", @@ -9567,7 +9601,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest", + "reqwest 0.13.2", "serde", "serde_json", "serde_repr", @@ -9582,7 +9616,6 @@ dependencies = [ "tokio", "tray-icon", "url", - "urlpattern", "webkit2gtk", "webview2-com", "window-vibrancy", @@ -9591,9 +9624,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.4.1" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f" +checksum = "ca7bd893329425df750813e95bd2b643d5369d929438da96d5bbb7cc2c918f74" dependencies = [ "anyhow", "cargo_toml", @@ -9615,9 +9648,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.4.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a" +checksum = "aac423e5859d9f9ccdd32e3cf6a5866a15bedbf25aa6630bcb2acde9468f6ae3" dependencies = [ "base64 0.22.1", "brotli", @@ -9642,9 +9675,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.4.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e" +checksum = "1b6a1bd2861ff0c8766b1d38b32a6a410f6dc6532d4ef534c47cfb2236092f59" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -9656,9 +9689,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.4.0" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f" +checksum = "692a77abd8b8773e107a42ec0e05b767b8d2b7ece76ab36c6c3947e34df9f53f" dependencies = [ "anyhow", "glob", @@ -9712,9 +9745,9 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.4.2" +version = "2.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "315784ec4be45e90a987687bae7235e6be3d6e9e350d2b75c16b8a4bf22c1db7" +checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804" dependencies = [ "anyhow", "dunce", @@ -9734,16 +9767,16 @@ dependencies = [ [[package]] name = "tauri-plugin-http" -version = "2.5.2" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938a3d7051c9a82b431e3a0f3468f85715b3442b3c3a3913095e9fa509e2652c" +checksum = "d8f069451c4e87e7e2636b7f065a4c52866c4ce5e60e2d53fa1038edb6d184dc" dependencies = [ "bytes", "cookie_store", "data-url", "http 1.3.1", "regex", - "reqwest", + "reqwest 0.12.24", "schemars 0.8.22", "serde", "serde_json", @@ -9827,7 +9860,7 @@ dependencies = [ "minisign-verify", "osakit", "percent-encoding", - "reqwest", + "reqwest 0.12.24", "semver", "serde", "serde_json", @@ -9860,9 +9893,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.8.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846" +checksum = "b885ffeac82b00f1f6fd292b6e5aabfa7435d537cef57d11e38a489956535651" dependencies = [ "cookie 0.18.1", "dpi", @@ -9885,9 +9918,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.8.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807" +checksum = "5204682391625e867d16584fedc83fc292fb998814c9f7918605c789cd876314" dependencies = [ "gtk", "http 1.3.1", @@ -9912,9 +9945,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.7.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212" +checksum = "fcd169fccdff05eff2c1033210b9b94acd07a47e6fa9a3431cf09cfd4f01c87e" dependencies = [ "anyhow", "brotli", @@ -10068,7 +10101,7 @@ dependencies = [ "quick-xml 0.38.3", "rand 0.8.5", "regex", - "reqwest", + "reqwest 0.12.24", "rgb", "serde", "serde_ini", @@ -10589,9 +10622,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags 2.9.4", "bytes", @@ -11319,9 +11352,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -11330,27 +11363,14 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-futures" -version = "0.4.54" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -11359,9 +11379,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11369,22 +11389,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn 2.0.106", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -11402,6 +11422,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wayland-backend" version = "0.3.11" @@ -11464,9 +11497,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -11484,9 +11517,9 @@ dependencies = [ [[package]] name = "webkit2gtk" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -11508,9 +11541,9 @@ dependencies = [ [[package]] name = "webkit2gtk-sys" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" dependencies = [ "bitflags 1.3.2", "cairo-sys-rs", @@ -12213,9 +12246,9 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wry" -version = "0.53.4" +version = "0.54.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d78ec082b80fa088569a970d043bb3050abaabf4454101d44514ee8d9a8c9f6" +checksum = "bb26159b420aa77684589a744ae9a9461a95395b848764ad12290a14d960a11a" dependencies = [ "base64 0.22.1", "block2 0.6.2", diff --git a/Cargo.toml b/Cargo.toml index 0ed38e6777..dd4dd8fd3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,7 +179,7 @@ tauri = "2.8.5" tauri-build = "2.4.1" tauri-plugin-deep-link = "2.4.3" tauri-plugin-dialog = "2.4.0" -tauri-plugin-http = "2.5.2" +tauri-plugin-http = "2.5.7" tauri-plugin-opener = "2.5.0" tauri-plugin-os = "2.3.1" tauri-plugin-single-instance = "2.3.4" diff --git a/apps/app-frontend/src/components/ui/settings/ResourceManagementSettings.vue b/apps/app-frontend/src/components/ui/settings/ResourceManagementSettings.vue index 2c21bdd4ea..99f56f22bd 100644 --- a/apps/app-frontend/src/components/ui/settings/ResourceManagementSettings.vue +++ b/apps/app-frontend/src/components/ui/settings/ResourceManagementSettings.vue @@ -33,6 +33,7 @@ async function purgeCache() { 'user', 'team', 'organization', + 'file', 'loader_manifest', 'minecraft_manifest', 'categories', @@ -40,8 +41,10 @@ async function purgeCache() { 'loaders', 'game_versions', 'donation_platforms', + 'file_hash', 'file_update', 'search_results', + 'search_results_v3', ]).catch(handleError) } From ab83ab3376cc8ef5299ba7aaa64cd17f7a6f55ec Mon Sep 17 00:00:00 2001 From: aecsocket Date: Tue, 3 Mar 2026 02:40:21 +0000 Subject: [PATCH 02/15] #[serde(untagged)] my BEHATED (still kinda broken) --- packages/app-lib/src/state/cache.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/app-lib/src/state/cache.rs b/packages/app-lib/src/state/cache.rs index fa374824b1..040c34417e 100644 --- a/packages/app-lib/src/state/cache.rs +++ b/packages/app-lib/src/state/cache.rs @@ -15,7 +15,7 @@ use std::path::{Path, PathBuf}; // 1 day const DEFAULT_ID: &str = "0"; -#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum CacheValueType { Project, @@ -136,32 +136,23 @@ impl CacheValueType { #[allow(clippy::large_enum_variant)] pub enum CacheValue { Project(Project), - - ProjectV3(ProjectV3), - Version(Version), - User(User), - Team(Vec), - Organization(Organization), - File(CachedFile), - LoaderManifest(CachedLoaderManifest), MinecraftManifest(daedalus::minecraft::VersionManifest), - Categories(Vec), ReportTypes(Vec), Loaders(Vec), GameVersions(Vec), DonationPlatforms(Vec), - FileHash(CachedFileHash), FileUpdate(CachedFileUpdate), SearchResults(SearchResults), SearchResultsV3(SearchResultsV3), + ProjectV3(ProjectV3), } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -789,6 +780,16 @@ impl CachedEntry { }); if let Some(data) = parsed_data { + if data.get_type() != type_ { + return Err(crate::ErrorKind::OtherError(format!( + "Cache type mismatch for id {}: expected {:?}, got {:?}", + row.id, + type_, + data.get_type() + )) + .as_error()); + } + return_vals.push(Self { id: row.id, alias: row.alias, From 7944c9aa9626f480c0cf43b6df9658125cc68ad4 Mon Sep 17 00:00:00 2001 From: tdgao Date: Mon, 2 Mar 2026 19:27:39 -0800 Subject: [PATCH 03/15] remove unused hasContent ref --- apps/app-frontend/src/pages/instance/Index.vue | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/app-frontend/src/pages/instance/Index.vue b/apps/app-frontend/src/pages/instance/Index.vue index 0a98b597b1..274f85351a 100644 --- a/apps/app-frontend/src/pages/instance/Index.vue +++ b/apps/app-frontend/src/pages/instance/Index.vue @@ -315,7 +315,7 @@ import { trackEvent } from '@/helpers/analytics' import { get_project_v3, get_version, get_version_many } from '@/helpers/cache.js' import { process_listener, profile_listener } from '@/helpers/events' import { get_by_profile_path } from '@/helpers/process' -import { finish_install, get, get_full_path, get_projects, kill, run } from '@/helpers/profile' +import { finish_install, get, get_full_path, kill, run } from '@/helpers/profile' import type { GameInstance } from '@/helpers/types' import { showProfileInFolder } from '@/helpers/utils.js' import { get_server_status } from '@/helpers/worlds' @@ -348,7 +348,6 @@ const exportModal = ref>() const updateToPlayModal = ref>() const isServerInstance = ref(false) -const hasContent = ref(true) const linkedProjectV3 = ref() const modpackContentProjectV3 = ref(null) const selected = ref([]) @@ -364,7 +363,6 @@ async function fetchInstance() { linkedProjectV3.value = undefined modpackContentProjectV3.value = null modrinthVersions.value = [] - hasContent.value = true ping.value = undefined instance.value = await get(route.params.id as string).catch(handleError) @@ -400,12 +398,10 @@ async function fetchInstance() { } await fetchModpackContent() - const projects = await get_projects(instance.value!.path).catch(() => ({})) - hasContent.value = Object.keys(projects).length > 0 } } - } catch (error: Error) { - handleError(error) + } catch (error) { + handleError(error as Error) } } From ba5eef8e21ceb4c2ce68a4d83c7c6480ca253b6b Mon Sep 17 00:00:00 2001 From: tdgao Date: Mon, 2 Mar 2026 19:33:50 -0800 Subject: [PATCH 04/15] clean up code in fetch instance --- .../app-frontend/src/pages/instance/Index.vue | 50 +++++++------------ 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/apps/app-frontend/src/pages/instance/Index.vue b/apps/app-frontend/src/pages/instance/Index.vue index 274f85351a..265553f0fa 100644 --- a/apps/app-frontend/src/pages/instance/Index.vue +++ b/apps/app-frontend/src/pages/instance/Index.vue @@ -67,7 +67,7 @@
@@ -312,7 +312,7 @@ import InstanceSettingsModal from '@/components/ui/modal/InstanceSettingsModal.v import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue' import NavTabs from '@/components/ui/NavTabs.vue' import { trackEvent } from '@/helpers/analytics' -import { get_project_v3, get_version, get_version_many } from '@/helpers/cache.js' +import { get_project_v3, get_version_many } from '@/helpers/cache.js' import { process_listener, profile_listener } from '@/helpers/events' import { get_by_profile_path } from '@/helpers/process' import { finish_install, get, get_full_path, kill, run } from '@/helpers/profile' @@ -349,7 +349,6 @@ const updateToPlayModal = ref>() const isServerInstance = ref(false) const linkedProjectV3 = ref() -const modpackContentProjectV3 = ref(null) const selected = ref([]) const minecraftServer = computed(() => linkedProjectV3.value?.minecraft_server) @@ -361,7 +360,6 @@ const ping = ref(undefined) async function fetchInstance() { isServerInstance.value = false linkedProjectV3.value = undefined - modpackContentProjectV3.value = null modrinthVersions.value = [] ping.value = undefined @@ -380,24 +378,22 @@ async function fetchInstance() { (a: Labrinth.Versions.v2.Version, b: Labrinth.Versions.v2.Version) => dayjs(b.date_published).valueOf() - dayjs(a.date_published).valueOf(), ) - if (linkedProjectV3.value?.minecraft_server != null) { - isServerInstance.value = true - - const serverAddress = linkedProjectV3.value?.minecraft_java_server?.address - if (serverAddress) { - get_server_status(serverAddress) - .then((status) => { - if (status.ping != null) { - ping.value = status.ping - playersOnline.value = status.players?.online - } - }) - .catch((err) => { - console.error(`Failed to ping server ${serverAddress}:`, err) - }) - } + } - await fetchModpackContent() + if (linkedProjectV3.value?.minecraft_server != null) { + isServerInstance.value = true + + const serverAddress = linkedProjectV3.value?.minecraft_java_server?.address + if (serverAddress) { + try { + const status = await get_server_status(serverAddress) + if (status.ping != null) { + ping.value = status.ping + playersOnline.value = status.players?.online + } + } catch (err) { + console.error(`Failed to ping server ${serverAddress}:`, err) + } } } } catch (error) { @@ -408,18 +404,6 @@ async function fetchInstance() { await updatePlayState() } -async function fetchModpackContent() { - modpackContentProjectV3.value = null - const versionId = instance.value?.linked_data?.version_id - if (!versionId) return - - const contentVersion = await get_version(versionId, 'must_revalidate') - const projectId = contentVersion?.project_id - if (projectId) { - modpackContentProjectV3.value = await get_project_v3(projectId, 'must_revalidate') - } -} - async function updatePlayState() { const runningProcesses = await get_by_profile_path(route.params.id as string).catch(handleError) From 0df1928e55e1ea7a0ee88efc4a4f30e0dfaf8e30 Mon Sep 17 00:00:00 2001 From: tdgao Date: Mon, 2 Mar 2026 19:50:11 -0800 Subject: [PATCH 05/15] ping 3 times for average latency --- apps/app-frontend/src/helpers/worlds.ts | 19 +++++++++++++++ apps/app-frontend/src/pages/Browse.vue | 23 ++++++++++--------- .../app-frontend/src/pages/instance/Index.vue | 9 ++++---- apps/app-frontend/src/pages/project/Index.vue | 16 +++++-------- 4 files changed, 41 insertions(+), 26 deletions(-) diff --git a/apps/app-frontend/src/helpers/worlds.ts b/apps/app-frontend/src/helpers/worlds.ts index ad1f1472e2..8eb2e1f336 100644 --- a/apps/app-frontend/src/helpers/worlds.ts +++ b/apps/app-frontend/src/helpers/worlds.ts @@ -231,6 +231,25 @@ export function isLinkedWorld(world: World): boolean { return world.type === 'server' && !!world.linked_project_id } +export async function getServerLatency( + address: string, + protocolVersion: ProtocolVersion | null = null, +): Promise { + const pings: number[] = [] + for (let i = 0; i < 3; i++) { + try { + const status = await get_server_status(address, protocolVersion) + if (status.ping != null) { + pings.push(status.ping) + } + } catch { + // Ignore individual ping failures + } + } + if (pings.length === 0) return undefined + return Math.round(pings.reduce((sum, p) => sum + p, 0) / pings.length) +} + export async function refreshServerData( serverData: ServerData, protocolVersion: ProtocolVersion | null, diff --git a/apps/app-frontend/src/pages/Browse.vue b/apps/app-frontend/src/pages/Browse.vue index dc48825136..05c7afcd6b 100644 --- a/apps/app-frontend/src/pages/Browse.vue +++ b/apps/app-frontend/src/pages/Browse.vue @@ -49,7 +49,7 @@ import { } from '@/helpers/profile.js' import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags' import type { GameInstance } from '@/helpers/types' -import { get_server_status } from '@/helpers/worlds' +import { getServerLatency } from '@/helpers/worlds' import { useBreadcrumbs } from '@/store/breadcrumbs' import { getServerAddress, playServerProject, useInstall } from '@/store/install.js' @@ -287,17 +287,18 @@ const { } = useServerSearch({ tags, query, maxResults, currentPage }) async function pingServerHits(hits: Labrinth.Search.v3.ResultSearchProject[]) { - for (const hit of hits) { - const address = hit.minecraft_java_server?.address - if (!address) continue - get_server_status(address) - .then((status) => { - serverPings.value = { ...serverPings.value, [hit.project_id]: status.ping } - }) - .catch((err) => { + const pingsToFetch = hits.filter((hit) => hit.minecraft_java_server?.address) + await Promise.all( + pingsToFetch.map(async (hit) => { + const address = hit.minecraft_java_server!.address! + try { + const latency = await getServerLatency(address) + serverPings.value = { ...serverPings.value, [hit.project_id]: latency } + } catch (err) { console.error(`Failed to ping server ${address}:`, err) - }) - } + } + }), + ) } const previousFilterState = ref('') diff --git a/apps/app-frontend/src/pages/instance/Index.vue b/apps/app-frontend/src/pages/instance/Index.vue index 265553f0fa..3167f204fa 100644 --- a/apps/app-frontend/src/pages/instance/Index.vue +++ b/apps/app-frontend/src/pages/instance/Index.vue @@ -318,7 +318,7 @@ import { get_by_profile_path } from '@/helpers/process' import { finish_install, get, get_full_path, kill, run } from '@/helpers/profile' import type { GameInstance } from '@/helpers/types' import { showProfileInFolder } from '@/helpers/utils.js' -import { get_server_status } from '@/helpers/worlds' +import { get_server_status, getServerLatency } from '@/helpers/worlds' import { handleSevereError } from '@/store/error.js' import { playServerProject } from '@/store/install.js' import { useBreadcrumbs, useLoading } from '@/store/state' @@ -387,10 +387,9 @@ async function fetchInstance() { if (serverAddress) { try { const status = await get_server_status(serverAddress) - if (status.ping != null) { - ping.value = status.ping - playersOnline.value = status.players?.online - } + const latency = await getServerLatency(serverAddress) + ping.value = latency + playersOnline.value = status.players?.online } catch (err) { console.error(`Failed to ping server ${serverAddress}:`, err) } diff --git a/apps/app-frontend/src/pages/project/Index.vue b/apps/app-frontend/src/pages/project/Index.vue index 6463c8ee38..c3d40a1409 100644 --- a/apps/app-frontend/src/pages/project/Index.vue +++ b/apps/app-frontend/src/pages/project/Index.vue @@ -265,7 +265,7 @@ import { list as listInstances, } from '@/helpers/profile' import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags' -import { get_server_status } from '@/helpers/worlds' +import { getServerLatency } from '@/helpers/worlds' import { useBreadcrumbs } from '@/store/breadcrumbs' import { getServerAddress, @@ -398,15 +398,11 @@ async function fetchProjectData() { serverStatusOnline.value = !!projectV3.value?.minecraft_java_server?.ping?.data if (serverAddress) { serverPing.value = undefined - get_server_status(serverAddress) - .then((status) => { - if (status.ping != null) { - serverPing.value = status.ping - } - }) - .catch((err) => { - console.error(`Failed to ping server ${serverAddress}:`, err) - }) + try { + serverPing.value = await getServerLatency(serverAddress) + } catch (error) { + console.error(`Failed to ping server ${serverAddress}:`, err) + } } // Fetch server sidebar data (modpack version + project) From d5690e690d5029a7885e6939ca4b05cfd91241eb Mon Sep 17 00:00:00 2001 From: tdgao Date: Mon, 2 Mar 2026 20:06:53 -0800 Subject: [PATCH 06/15] fix: pinging to be more accurate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TCP_NODELAY — Set on the TCP stream right after connect, preventing Nagle's algorithm from buffering the small ping packet (could save up to ~40ms) Instant over Utc::now() — Switched to monotonic std::time::Instant for timing, which is more precise and designed for measuring elapsed time (still using chrono just for the ping magic value) --- packages/app-lib/src/util/server_ping.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/app-lib/src/util/server_ping.rs b/packages/app-lib/src/util/server_ping.rs index 7173fd4b31..76fcdd56a3 100644 --- a/packages/app-lib/src/util/server_ping.rs +++ b/packages/app-lib/src/util/server_ping.rs @@ -69,7 +69,7 @@ pub async fn get_server_status( mod modern { use super::ServerStatus; use crate::ErrorKind; - use chrono::Utc; + use std::time::Instant; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpStream, ToSocketAddrs}; @@ -79,6 +79,7 @@ mod modern { protocol_version: Option, ) -> crate::Result { let mut stream = TcpStream::connect(address).await?; + stream.set_nodelay(true)?; handshake(&mut stream, original_address, protocol_version).await?; let mut result = status_body(&mut stream).await?; result.ping = ping(&mut stream).await.ok(); @@ -155,9 +156,9 @@ mod modern { } async fn ping(stream: &mut TcpStream) -> crate::Result { - let start_time = Utc::now(); - let ping_magic = start_time.timestamp_millis(); + let ping_magic = chrono::Utc::now().timestamp_millis(); + let start_time = Instant::now(); stream.write_all(&[0x09, 0x01]).await?; stream.write_i64(ping_magic).await?; stream.flush().await?; @@ -172,8 +173,7 @@ mod modern { .into()); } - let response_time = Utc::now(); - Ok((response_time - start_time).num_milliseconds()) + Ok(start_time.elapsed().as_millis() as i64) } mod varint { From 433a3abff34ca03902f425e10bb80bf9c7d3106b Mon Sep 17 00:00:00 2001 From: tdgao Date: Mon, 2 Mar 2026 20:43:51 -0800 Subject: [PATCH 07/15] delete useFetch util and just use native fetch --- apps/app-frontend/src/App.vue | 17 +++++------------ apps/app-frontend/src/helpers/fetch.js | 18 ------------------ 2 files changed, 5 insertions(+), 30 deletions(-) delete mode 100644 apps/app-frontend/src/helpers/fetch.js diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index ba05cea8d7..eed52f8356 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -85,7 +85,6 @@ import { debugAnalytics, initAnalytics, trackEvent } from '@/helpers/analytics' import { check_reachable } from '@/helpers/auth.js' import { get_user } from '@/helpers/cache.js' import { command_listener, warning_listener } from '@/helpers/events.js' -import { useFetch } from '@/helpers/fetch.js' import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.ts' import { list } from '@/helpers/profile.js' import { get as getSettings, set as setSettings } from '@/helpers/settings.ts' @@ -303,11 +302,7 @@ async function setupApp() { }), ) - useFetch( - `https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`, - 'criticalAnnouncements', - true, - ) + fetch(`https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`) .then((response) => response.json()) .then((res) => { if (res && res.header && res.body) { @@ -320,23 +315,21 @@ async function setupApp() { ) }) - useFetch(`https://modrinth.com/news/feed/articles.json`, 'news', true) + fetch(`https://modrinth.com/news/feed/articles.json`) .then((response) => response.json()) .then((res) => { if (res && res.articles) { - // Format expected by NewsArticleCard component. news.value = res.articles .map((article) => ({ ...article, path: article.link, - thumbnail: article.thumbnail, - title: article.title, - summary: article.summary, - date: article.date, })) .slice(0, 4) } }) + .catch((error) => { + console.error('Failed to fetch news articles', error) + }) get_opening_command().then(handleCommand) fetchCredentials() diff --git a/apps/app-frontend/src/helpers/fetch.js b/apps/app-frontend/src/helpers/fetch.js deleted file mode 100644 index 3e29387f8e..0000000000 --- a/apps/app-frontend/src/helpers/fetch.js +++ /dev/null @@ -1,18 +0,0 @@ -import { getVersion } from '@tauri-apps/api/app' -import { fetch } from '@tauri-apps/plugin-http' - -export const useFetch = async (url, item, isSilent) => { - try { - const version = await getVersion() - return await fetch(url, { - method: 'GET', - headers: { 'User-Agent': `modrinth/theseus/${version} (support@modrinth.com)` }, - }) - } catch (err) { - if (!isSilent) { - throw err - } else { - console.error(err) - } - } -} From d51bdf513faeb03c7322f4c4eaaae3c46314a5d6 Mon Sep 17 00:00:00 2001 From: tdgao Date: Mon, 2 Mar 2026 21:27:39 -0800 Subject: [PATCH 08/15] rename worlds until functions for more clarity --- apps/app-frontend/src/store/install.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/app-frontend/src/store/install.js b/apps/app-frontend/src/store/install.js index 6c31eb3656..a785cdedfb 100644 --- a/apps/app-frontend/src/store/install.js +++ b/apps/app-frontend/src/store/install.js @@ -4,8 +4,8 @@ import { defineStore } from 'pinia' import { trackEvent } from '@/helpers/analytics' import { get_project, get_project_v3, get_version, get_version_many } from '@/helpers/cache.js' import { - create_profile_and_install as packInstall, install_to_existing_profile, + create_profile_and_install as packInstall, } from '@/helpers/pack.js' import { add_project_from_version, @@ -328,7 +328,7 @@ export const installServerProject = async (serverProjectId) => { }) await edit_icon(profilePath, originalIconPath) - await syncServerAsWorld(profilePath, project.title, serverAddress, serverProjectId) + await syncServerProjectAsWorld(profilePath, project.title, serverAddress, serverProjectId) } export const getServerAddress = (javaServer) => { @@ -337,7 +337,7 @@ export const getServerAddress = (javaServer) => { return port !== 25565 ? `${address}:${port}` : address } -const syncServerAsWorld = async ( +const syncServerProjectAsWorld = async ( profilePath, serverName, serverAddress, @@ -405,7 +405,7 @@ const findInstalledInstance = async (projectId) => { return packs.find((pack) => pack.linked_data?.project_id === projectId) ?? null } -const createVanillaInstance = async (project, gameVersion, serverAddress) => { +const createVanillaServerInstance = async (project, gameVersion, serverAddress) => { const profilePath = await create( project.title, gameVersion, @@ -420,7 +420,8 @@ const createVanillaInstance = async (project, gameVersion, serverAddress) => { }, ) - await syncServerAsWorld(profilePath, project.title, serverAddress, project.id) + // + await syncServerProjectAsWorld(profilePath, project.title, serverAddress, project.id) return profilePath } @@ -514,6 +515,7 @@ export const playServerProject = async (projectId) => { if (projectV3?.minecraft_server == null) { console.warn('playServerProject failed: project is not a server project') + return } const content = projectV3?.minecraft_java_server?.content @@ -529,7 +531,7 @@ export const playServerProject = async (projectId) => { if (installStore.installingServerProjects.includes(projectId)) return installStore.startInstallingServer(projectId) try { - const path = await createVanillaInstance(project, recommendedGameVersion, serverAddress) + const path = await createVanillaServerInstance(project, recommendedGameVersion, serverAddress) if (path) { instance = await get(path) showModpackInstallSuccess(installStore, instance, serverAddress) @@ -543,8 +545,6 @@ export const playServerProject = async (projectId) => { installStore.showInstallToPlayModal(projectV3, modpackVersionId, async () => { const newInstance = await findInstalledInstance(project.id) if (!newInstance) return - // Ensure the server is in the worlds list after modpack install - await syncServerAsWorld(newInstance.path, project.title, serverAddress, project.id) showModpackInstallSuccess(installStore, newInstance, serverAddress) }) return @@ -552,7 +552,7 @@ export const playServerProject = async (projectId) => { if (!instance) return - await syncServerAsWorld(instance.path, project.title, serverAddress, project.id) + await syncServerProjectAsWorld(instance.path, project.title, serverAddress, project.id) // Update existing instance if needed if (isModpack && instance.linked_data?.version_id !== modpackVersionId) { From f91c3e21b84f6a4d161bc1ea16c59b6ea32bc6cf Mon Sep 17 00:00:00 2001 From: tdgao Date: Mon, 2 Mar 2026 21:30:51 -0800 Subject: [PATCH 09/15] fix lint --- apps/app-frontend/src/pages/project/Index.vue | 2 +- apps/app-frontend/src/store/install.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app-frontend/src/pages/project/Index.vue b/apps/app-frontend/src/pages/project/Index.vue index c3d40a1409..b1437ec480 100644 --- a/apps/app-frontend/src/pages/project/Index.vue +++ b/apps/app-frontend/src/pages/project/Index.vue @@ -401,7 +401,7 @@ async function fetchProjectData() { try { serverPing.value = await getServerLatency(serverAddress) } catch (error) { - console.error(`Failed to ping server ${serverAddress}:`, err) + console.error(`Failed to ping server ${serverAddress}:`, error) } } diff --git a/apps/app-frontend/src/store/install.js b/apps/app-frontend/src/store/install.js index a785cdedfb..ce5d3b9cb0 100644 --- a/apps/app-frontend/src/store/install.js +++ b/apps/app-frontend/src/store/install.js @@ -4,8 +4,8 @@ import { defineStore } from 'pinia' import { trackEvent } from '@/helpers/analytics' import { get_project, get_project_v3, get_version, get_version_many } from '@/helpers/cache.js' import { - install_to_existing_profile, create_profile_and_install as packInstall, + install_to_existing_profile, } from '@/helpers/pack.js' import { add_project_from_version, From 318bd307a2c01bc4daa33ce0798eb9bf45a21672 Mon Sep 17 00:00:00 2001 From: aecsocket Date: Tue, 3 Mar 2026 13:56:58 +0000 Subject: [PATCH 10/15] fix cache.rs logic --- packages/app-lib/src/state/cache.rs | 163 ++++++++++++++++++++++++++-- 1 file changed, 152 insertions(+), 11 deletions(-) diff --git a/packages/app-lib/src/state/cache.rs b/packages/app-lib/src/state/cache.rs index 040c34417e..803b0a01e0 100644 --- a/packages/app-lib/src/state/cache.rs +++ b/packages/app-lib/src/state/cache.rs @@ -131,8 +131,21 @@ impl CacheValueType { } } +// De/serialization strategy: +// - on serialize: +// - in the `cache` table, save the `data_type` (variant of this value) alongside +// the data +// - data column contains the serialized form of the INNER value (i.e. for a +// `CacheValue::Project`, we serialize it as a `Project,` NOT as a `CacheValue`) +// - this way, we do not tag the data using serde in any way +// - on deserialize: +// - use the `data_type` to figure out what type of value to deser as +// - then wrap that in a `CacheValue` +// +// do NOT use `#[serde(untagged)]` here, since then a value of one variant can be +// deser'd as a value of another variant, if it comes before it in the enum +// definition list. #[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(untagged)] #[allow(clippy::large_enum_variant)] pub enum CacheValue { Project(Project), @@ -557,6 +570,47 @@ impl CacheValue { | CacheValue::SearchResultsV3(_) => None, } } + + fn to_json_value(&self) -> crate::Result { + let value = match self { + CacheValue::Project(project) => serde_json::to_value(project), + CacheValue::ProjectV3(project) => serde_json::to_value(project), + CacheValue::Version(version) => serde_json::to_value(version), + CacheValue::User(user) => serde_json::to_value(user), + CacheValue::Team(members) => serde_json::to_value(members), + CacheValue::Organization(org) => serde_json::to_value(org), + CacheValue::File(file) => serde_json::to_value(file), + CacheValue::LoaderManifest(loader) => serde_json::to_value(loader), + CacheValue::MinecraftManifest(manifest) => { + serde_json::to_value(manifest) + } + CacheValue::Categories(categories) => { + serde_json::to_value(categories) + } + CacheValue::ReportTypes(report_types) => { + serde_json::to_value(report_types) + } + CacheValue::Loaders(loaders) => serde_json::to_value(loaders), + CacheValue::GameVersions(versions) => { + serde_json::to_value(versions) + } + CacheValue::DonationPlatforms(platforms) => { + serde_json::to_value(platforms) + } + CacheValue::FileHash(hash) => serde_json::to_value(hash), + CacheValue::FileUpdate(update) => serde_json::to_value(update), + CacheValue::SearchResults(search) => serde_json::to_value(search), + CacheValue::SearchResultsV3(search) => serde_json::to_value(search), + } + .map_err(|err| { + crate::ErrorKind::OtherError(format!( + "Failed to serialize cache value: {err}" + )) + .as_error() + })?; + + Ok(value) + } } #[derive( @@ -750,15 +804,11 @@ impl CachedEntry { .await?; for row in query { - let row_exists = row.data.is_some(); - let parsed_data = row - .data - .and_then(|x| serde_json::from_value::(x).ok()); - - // If data is corrupted/failed to parse ignore it - if row_exists && parsed_data.is_none() { - continue; - } + let parsed_data = if let Some(data) = row.data.clone() { + Some(Self::deserialize_cache_value(type_, data, &row.id)?) + } else { + None + }; if row.expires <= Utc::now().timestamp() { if cache_behaviour == CacheBehaviour::MustRevalidate { @@ -1510,11 +1560,102 @@ impl CachedEntry { }) } + fn deserialize_cache_value( + type_: CacheValueType, + data: serde_json::Value, + id: &str, + ) -> crate::Result { + fn parse( + data: serde_json::Value, + id: &str, + label: &str, + ) -> crate::Result { + serde_json::from_value::(data.clone()).map_err(|err| { + crate::ErrorKind::OtherError(format!( + "Failed to deserialize cache {label} for id {id}: {err}\n\ndata:\n{}", + serde_json::to_string_pretty(&data).unwrap(), + )) + .as_error() + }) + } + + let value = match type_ { + CacheValueType::Project => { + CacheValue::Project(parse(data, id, "project")?) + } + CacheValueType::ProjectV3 => { + CacheValue::ProjectV3(parse(data, id, "project_v3")?) + } + CacheValueType::Version => { + CacheValue::Version(parse(data, id, "version")?) + } + CacheValueType::User => CacheValue::User(parse(data, id, "user")?), + CacheValueType::Team => CacheValue::Team(parse(data, id, "team")?), + CacheValueType::Organization => { + CacheValue::Organization(parse(data, id, "organization")?) + } + CacheValueType::File => CacheValue::File(parse(data, id, "file")?), + CacheValueType::LoaderManifest => { + CacheValue::LoaderManifest(parse(data, id, "loader_manifest")?) + } + CacheValueType::MinecraftManifest => CacheValue::MinecraftManifest( + parse(data, id, "minecraft_manifest")?, + ), + CacheValueType::Categories => { + CacheValue::Categories(parse(data, id, "categories")?) + } + CacheValueType::ReportTypes => { + CacheValue::ReportTypes(parse(data, id, "report_types")?) + } + CacheValueType::Loaders => { + CacheValue::Loaders(parse(data, id, "loaders")?) + } + CacheValueType::GameVersions => { + CacheValue::GameVersions(parse(data, id, "game_versions")?) + } + CacheValueType::DonationPlatforms => CacheValue::DonationPlatforms( + parse(data, id, "donation_platforms")?, + ), + CacheValueType::FileHash => { + CacheValue::FileHash(parse(data, id, "file_hash")?) + } + CacheValueType::FileUpdate => { + CacheValue::FileUpdate(parse(data, id, "file_update")?) + } + CacheValueType::SearchResults => { + CacheValue::SearchResults(parse(data, id, "search_results")?) + } + CacheValueType::SearchResultsV3 => CacheValue::SearchResultsV3( + parse(data, id, "search_results_v3")?, + ), + }; + + Ok(value) + } + pub(crate) async fn upsert_many( items: &[Self], exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, ) -> crate::Result<()> { - let items = serde_json::to_string(items)?; + let items = items + .iter() + .map(|item| { + let data = item + .data + .as_ref() + .map(|value| value.to_json_value()) + .transpose()?; + + Ok(serde_json::json!({ + "id": item.id, + "data_type": item.type_.as_str(), + "alias": item.alias, + "data": data, + "expires": item.expires, + })) + }) + .collect::>>()?; + let items = serde_json::to_string(&items)?; sqlx::query!( " From e4a2b2866eb59ab95dd8c1db8149c79d8f390782 Mon Sep 17 00:00:00 2001 From: aecsocket Date: Tue, 3 Mar 2026 14:01:28 +0000 Subject: [PATCH 11/15] make backend ping use both impls --- apps/labrinth/src/queue/server_ping.rs | 84 ++++++++++++++------------ 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/apps/labrinth/src/queue/server_ping.rs b/apps/labrinth/src/queue/server_ping.rs index 96550d955d..ea8993f157 100644 --- a/apps/labrinth/src/queue/server_ping.rs +++ b/apps/labrinth/src/queue/server_ping.rs @@ -256,35 +256,7 @@ pub async fn ping_server( let start = Instant::now(); let timeout = Duration::from_millis(ENV.SERVER_PING_TIMEOUT_MS); - let task1 = async move { - let conn = async_minecraft_ping::ConnectionConfig::build(address) - .with_port(port) - .connect() - .await - .wrap_err("failed to connect to server")?; - - let status = conn - .status() - .await - .wrap_err("failed to get server status")? - .status; - - debug!("Successful ping with `async_minecraft_ping`"); - eyre::Ok(exp::minecraft::JavaServerPingData { - latency: start.elapsed(), - version_name: status.version.name, - version_protocol: status.version.protocol, - description: match status.description { - ServerDescription::Plain(text) - | ServerDescription::Object { text } => text, - }, - players_online: status.players.online, - players_max: status.players.max, - }) - }; - let task1 = tokio::time::timeout(timeout, task1); - - let task2 = async move { + let task_ep = async move { fn map_component(c: elytra_ping::parse::TextComponent) -> String { match c { elytra_ping::parse::TextComponent::Plain(t) => t, @@ -303,7 +275,6 @@ pub async fn ping_server( elytra_ping::ping_or_timeout((address.to_string(), port), timeout) .await?; - debug!("Successful ping with `elytra_ping`"); eyre::Ok(exp::minecraft::JavaServerPingData { latency, version_name: result @@ -326,15 +297,54 @@ pub async fn ping_server( }) }; - async move { - if let Ok(t) = task1 + let task_amp = async move { + let task = async move { + let conn = async_minecraft_ping::ConnectionConfig::build(address) + .with_port(port) + .connect() + .await + .wrap_err("failed to connect to server")?; + + let status = conn + .status() + .await + .wrap_err("failed to get server status")? + .status; + + eyre::Ok(exp::minecraft::JavaServerPingData { + latency: start.elapsed(), + version_name: status.version.name, + version_protocol: status.version.protocol, + description: match status.description { + ServerDescription::Plain(text) + | ServerDescription::Object { text } => text, + }, + players_online: status.players.online, + players_max: status.players.max, + }) + }; + + tokio::time::timeout(timeout, task) .await - .wrap_err("failed to ping with `async_minecraft_ping`")? - { - return Ok(t); - } + .map_err(eyre::Error::new) + .flatten() + }; - task2.await.wrap_err("failed to ping with `elytra_ping`") + async move { + let (result_ep, result_amp) = (task_ep.await, task_amp.await); + + let result_ep = result_ep + .inspect(|_| debug!("Successful ping with `elytra_ping`")) + .inspect_err(|err| { + debug!("Failed to ping with `elytra_ping`: {err:#}") + }); + let result_amp = result_amp + .inspect(|_| debug!("Successful ping with `async_minecraft_ping`")) + .inspect_err(|err| { + debug!("Failed to ping with `async_minecraft_ping`: {err:#}") + }); + + result_ep.or(result_amp) } .await } From dac4929098e12569dbce47d87e4d605221f43298 Mon Sep 17 00:00:00 2001 From: aecsocket Date: Tue, 3 Mar 2026 14:13:52 +0000 Subject: [PATCH 12/15] Add optional timeout to server ping --- apps/labrinth/src/queue/server_ping.rs | 21 +++++++++++++++---- .../src/routes/internal/server_ping.rs | 6 +++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/labrinth/src/queue/server_ping.rs b/apps/labrinth/src/queue/server_ping.rs index ea8993f157..f03a1aea0a 100644 --- a/apps/labrinth/src/queue/server_ping.rs +++ b/apps/labrinth/src/queue/server_ping.rs @@ -54,7 +54,7 @@ impl ServerPingQueue { let mut retries = ENV.SERVER_PING_RETRIES; let result = loop { - match ping_server(&address, port).await { + match ping_server(&address, port, None).await { Ok(ping) => { info!(?ping, "Received successful ping"); break Ok(ping); @@ -252,9 +252,13 @@ impl ServerPingQueue { pub async fn ping_server( address: &str, port: u16, + timeout: Option, ) -> eyre::Result { let start = Instant::now(); - let timeout = Duration::from_millis(ENV.SERVER_PING_TIMEOUT_MS); + let default_duration = Duration::from_millis(ENV.SERVER_PING_TIMEOUT_MS); + let timeout = timeout + .map(|duration| duration.min(default_duration)) + .unwrap_or(default_duration); let task_ep = async move { fn map_component(c: elytra_ping::parse::TextComponent) -> String { @@ -369,11 +373,20 @@ mod tests { #[actix_rt::test] async fn test_ping_server_success() { - let _status = ping_server("mc.hypixel.net", 25565).await.unwrap(); + let _status = ping_server("mc.hypixel.net", 25565, None).await.unwrap(); } #[actix_rt::test] async fn test_ping_server_invalid_address() { - _ = ping_server("invalid.invalid", 25565).await.unwrap_err(); + _ = ping_server("invalid.invalid", 25565, None) + .await + .unwrap_err(); + } + + #[actix_rt::test] + async fn test_ping_zero_timeout() { + _ = ping_server("hypixel.net", 25565, Some(Duration::ZERO)) + .await + .unwrap_err(); } } diff --git a/apps/labrinth/src/routes/internal/server_ping.rs b/apps/labrinth/src/routes/internal/server_ping.rs index e067a11e88..423cfc5342 100644 --- a/apps/labrinth/src/routes/internal/server_ping.rs +++ b/apps/labrinth/src/routes/internal/server_ping.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use actix_web::{HttpRequest, post, web}; use serde::{Deserialize, Serialize}; @@ -18,6 +20,7 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) { pub struct PingRequest { pub address: String, pub port: u16, + pub timeout_ms: Option, } #[utoipa::path] @@ -38,7 +41,8 @@ pub async fn ping_minecraft_java( ) .await?; - server_ping::ping_server(&request.address, request.port) + let timeout = request.timeout_ms.map(Duration::from_millis); + server_ping::ping_server(&request.address, request.port, timeout) .await .wrap_request_err("failed to ping server")?; From e11b5709686af1331a046d85590bd4a1f6682bdc Mon Sep 17 00:00:00 2001 From: tdgao Date: Tue, 3 Mar 2026 09:26:13 -0800 Subject: [PATCH 13/15] fix gallery appearing in nav with no items --- apps/frontend/src/pages/[type]/[id].vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/frontend/src/pages/[type]/[id].vue b/apps/frontend/src/pages/[type]/[id].vue index 5f67faaf82..d4836dc114 100644 --- a/apps/frontend/src/pages/[type]/[id].vue +++ b/apps/frontend/src/pages/[type]/[id].vue @@ -2523,6 +2523,13 @@ const navLinks = computed(() => { const routeType = route.params.type || project.value.project_type const projectUrl = `/${routeType}/${project.value.slug ? project.value.slug : project.value.id}` + const galleryCount = + routeType === 'server' + ? project.value.gallery.filter((item) => item.name === '__mc_server_banner__').length + : project.value.gallery.length + + console.log('galleryCount', galleryCount, !!currentMember.value) + return [ { label: formatMessage(messages.descriptionTab), @@ -2531,7 +2538,7 @@ const navLinks = computed(() => { { label: formatMessage(messages.galleryTab), href: `${projectUrl}/gallery`, - shown: project.value.gallery.length > 0 || !!currentMember.value, + shown: galleryCount > 0 || !!currentMember.value, }, { label: formatMessage(messages.changelogTab), From 0a6fccff4e22c21f14d3f72052986586294d9c40 Mon Sep 17 00:00:00 2001 From: tdgao Date: Tue, 3 Mar 2026 09:47:18 -0800 Subject: [PATCH 14/15] remove EU countries and add EU option for server country --- .../src/pages/[type]/[id]/settings/server.vue | 56 +++++++++++-------- packages/ui/src/components/base/Combobox.vue | 5 +- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/apps/frontend/src/pages/[type]/[id]/settings/server.vue b/apps/frontend/src/pages/[type]/[id]/settings/server.vue index 5728bef7dc..ec2d0125df 100644 --- a/apps/frontend/src/pages/[type]/[id]/settings/server.vue +++ b/apps/frontend/src/pages/[type]/[id]/settings/server.vue @@ -281,35 +281,45 @@ if (projectV3.value) { const countryOptions = [ { value: 'US', label: 'United States' }, { value: 'CA', label: 'Canada' }, + { + value: 'EU', + label: 'European Union', + searchTerms: [ + 'Germany', + 'France', + 'Netherlands', + 'Finland', + 'Sweden', + 'Denmark', + 'Poland', + 'Czech Republic', + 'Romania', + 'Austria', + 'Belgium', + 'Ireland', + 'Spain', + 'Italy', + 'Portugal', + 'Lithuania', + 'Latvia', + 'Estonia', + 'Bulgaria', + 'Croatia', + 'Hungary', + 'Slovakia', + 'Greece', + 'Luxembourg', + 'Malta', + 'Cyprus', + 'Slovenia', + ], + }, { value: 'GB', label: 'United Kingdom' }, - { value: 'DE', label: 'Germany' }, - { value: 'FR', label: 'France' }, - { value: 'NL', label: 'Netherlands' }, - { value: 'FI', label: 'Finland' }, - { value: 'SE', label: 'Sweden' }, { value: 'NO', label: 'Norway' }, - { value: 'DK', label: 'Denmark' }, - { value: 'PL', label: 'Poland' }, - { value: 'CZ', label: 'Czech Republic' }, - { value: 'RO', label: 'Romania' }, { value: 'CH', label: 'Switzerland' }, - { value: 'AT', label: 'Austria' }, - { value: 'BE', label: 'Belgium' }, - { value: 'IE', label: 'Ireland' }, - { value: 'ES', label: 'Spain' }, - { value: 'IT', label: 'Italy' }, - { value: 'PT', label: 'Portugal' }, { value: 'RU', label: 'Russia' }, { value: 'UA', label: 'Ukraine' }, - { value: 'LT', label: 'Lithuania' }, - { value: 'LV', label: 'Latvia' }, - { value: 'EE', label: 'Estonia' }, - { value: 'BG', label: 'Bulgaria' }, - { value: 'HR', label: 'Croatia' }, - { value: 'HU', label: 'Hungary' }, - { value: 'SK', label: 'Slovakia' }, { value: 'RS', label: 'Serbia' }, - { value: 'GR', label: 'Greece' }, { value: 'TR', label: 'Turkey' }, { value: 'IL', label: 'Israel' }, { value: 'AE', label: 'United Arab Emirates' }, diff --git a/packages/ui/src/components/base/Combobox.vue b/packages/ui/src/components/base/Combobox.vue index 2e2649fd3b..cbced13e78 100644 --- a/packages/ui/src/components/base/Combobox.vue +++ b/packages/ui/src/components/base/Combobox.vue @@ -156,6 +156,7 @@ export interface ComboboxOption { href?: string target?: string action?: () => void + searchTerms?: string[] } const DROPDOWN_VIEWPORT_MARGIN = 8 @@ -272,7 +273,9 @@ const filteredOptions = computed(() => { const query = searchQuery.value.toLowerCase() return optionsWithKeys.value.filter((opt) => { if (isDivider(opt)) return false - return opt.label.toLowerCase().includes(query) + if (opt.label.toLowerCase().includes(query)) return true + if (opt.searchTerms?.some((term) => term.toLowerCase().includes(query))) return true + return false }) }) From be0bf7177ba2a20e5c948f9a5d4caa6485cd10d9 Mon Sep 17 00:00:00 2001 From: tdgao Date: Tue, 3 Mar 2026 10:25:31 -0800 Subject: [PATCH 15/15] add uk to europe --- apps/frontend/src/pages/[type]/[id]/settings/server.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/frontend/src/pages/[type]/[id]/settings/server.vue b/apps/frontend/src/pages/[type]/[id]/settings/server.vue index ec2d0125df..c3dddbc1d1 100644 --- a/apps/frontend/src/pages/[type]/[id]/settings/server.vue +++ b/apps/frontend/src/pages/[type]/[id]/settings/server.vue @@ -283,7 +283,7 @@ const countryOptions = [ { value: 'CA', label: 'Canada' }, { value: 'EU', - label: 'European Union', + label: 'Europe', searchTerms: [ 'Germany', 'France', @@ -312,9 +312,10 @@ const countryOptions = [ 'Malta', 'Cyprus', 'Slovenia', + 'Great Britain', + 'United Kingdom', ], }, - { value: 'GB', label: 'United Kingdom' }, { value: 'NO', label: 'Norway' }, { value: 'CH', label: 'Switzerland' }, { value: 'RU', label: 'Russia' },