From 8df1ac8a86164b283848046a83d337ac93912c87 Mon Sep 17 00:00:00 2001 From: Ap_Tx <3045929398@qq.com> Date: Sat, 3 Oct 2026 10:08:43 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20beta9=20=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E8=87=AA=E5=8A=A8=E6=9B=B4=E6=96=B0=E5=88=B0=20beta10?= =?UTF-8?q?=E2=80=94=E2=80=94=E8=87=AA=E5=AE=9A=E4=B9=89=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E6=AF=94=E8=BE=83=E5=99=A8=E6=8C=89=E6=95=B0=E5=AD=97=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=20prerelease?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/app/src/updater_impl.rs | 82 +++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/apps/app/src/updater_impl.rs b/apps/app/src/updater_impl.rs index b7a7e29..57d7691 100644 --- a/apps/app/src/updater_impl.rs +++ b/apps/app/src/updater_impl.rs @@ -8,7 +8,7 @@ use tauri::http::header::ACCEPT; use tauri::{Manager, ResourceId, Runtime, Webview}; use tauri_plugin_http::reqwest; use tauri_plugin_http::reqwest::ClientBuilder; -use tauri_plugin_updater::{Error, Update, UpdaterExt}; +use tauri_plugin_updater::{Error, Update, UpdaterExt, Version}; use theseus::{ LoadingBarType, emit_loading, init_loading, launcher_user_agent, }; @@ -26,6 +26,81 @@ const STARLIGHT_UPDATE_BASE_URL: &str = "https://skin.starlight.cool/"; const UPDATE_DOWNLOAD_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15 * 60); +// semver compares prerelease identifiers lexically, so "beta10" < "beta9" +// (because the character '1' sorts before '9'). Our release names use a +// "betaN" scheme where N is a decimal counter, so a strict lexical compare +// would wrongly report an older beta as the latest. Use a comparator that +// treats the trailing digits of a prerelease identifier numerically. +fn is_remote_newer(current: &Version, remote: &Version) -> bool { + use std::cmp::Ordering; + + let core = (remote.major, remote.minor, remote.patch) + .cmp(&(current.major, current.minor, current.patch)); + if core != Ordering::Equal { + return core == Ordering::Greater; + } + + // Same x.y.z. A final release outranks any prerelease of that version. + match (current.pre.is_empty(), remote.pre.is_empty()) { + (true, true) => false, + (true, false) => false, + (false, true) => true, + (false, false) => { + compare_prerelease(remote.pre.as_str(), current.pre.as_str()) + == Ordering::Greater + } + } +} + +/// Split a prerelease identifier into its non-digit prefix and an optional +/// trailing decimal number (so "beta10" -> ("beta", Some(10))). +fn split_prerelease_identifier(segment: &str) -> (&str, Option) { + let digit_start = segment + .char_indices() + .find(|(_, c)| c.is_ascii_digit()) + .map(|(i, _)| i); + match digit_start { + Some(i) => (&segment[..i], segment[i..].parse::().ok()), + None => (segment, None), + } +} + +/// Compare two prerelease strings, treating a trailing run of ASCII digits +/// as a decimal number (so "beta10" > "beta9"). All other segments fall back +/// to a plain lexical comparison. +fn compare_prerelease(left: &str, right: &str) -> std::cmp::Ordering { + use std::cmp::Ordering; + + let left_segments: Vec<&str> = left.split('.').collect(); + let right_segments: Vec<&str> = right.split('.').collect(); + + for (l, r) in left_segments.iter().zip(right_segments.iter()) { + let (l_prefix, l_num) = split_prerelease_identifier(l); + let (r_prefix, r_num) = split_prerelease_identifier(r); + + match (l_num, r_num) { + (Some(ln), Some(rn)) if l_prefix == r_prefix => { + match ln.cmp(&rn) { + Ordering::Equal => continue, + other => return other, + } + } + _ => { + let prefix_cmp = l_prefix.cmp(r_prefix); + if prefix_cmp != Ordering::Equal { + return prefix_cmp; + } + let num_cmp = l_num.cmp(&r_num); + if num_cmp != Ordering::Equal { + return num_cmp; + } + } + } + } + + left_segments.len().cmp(&right_segments.len()) +} + // ── Shared types ───────────────────────────────────────────────── #[derive(Serialize)] @@ -228,7 +303,10 @@ async fn check_with_endpoints( .header("Accept", "application/json")? .header("X-Axolotl-Channel", channel)? .header("X-Axolotl-Platform", platform)? - .header("X-Axolotl-Version", current_version)?; + .header("X-Axolotl-Version", current_version)? + .version_comparator(|current, release| { + is_remote_newer(¤t, &release.version) + }); #[cfg(target_os = "windows")] {