diff --git a/Cargo.lock b/Cargo.lock index ab41ab62..b072adbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,42 +1133,6 @@ dependencies = [ "wayland-client", ] -[[package]] -name = "cargo-packager-updater" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eec09acab5c2227aba2e592d431708305bdeb6d507703f6cd8983fb57b6c5ef7" -dependencies = [ - "base64 0.22.1", - "cargo-packager-utils", - "ctor 0.2.9", - "dirs", - "dunce", - "flate2", - "http 1.5.0", - "log", - "minisign-verify", - "percent-encoding", - "reqwest 0.12.28", - "semver", - "serde", - "serde_json", - "tar", - "tempfile", - "thiserror 1.0.69", - "time", - "url", -] - -[[package]] -name = "cargo-packager-utils" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b43458dd2ee3cdab3f5b105acd80791383b730380c929018701313d7d299d4e8" -dependencies = [ - "ctor 0.2.9", -] - [[package]] name = "cc" version = "1.4.0" @@ -1537,16 +1501,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.117", -] - [[package]] name = "ctor" version = "0.10.1" @@ -1656,28 +1610,7 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" dependencies = [ - "dirs-sys 0.5.0", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.4.6", - "windows-sys 0.48.0", + "dirs-sys", ] [[package]] @@ -1688,7 +1621,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.2", + "redox_users", "windows-sys 0.61.2", ] @@ -2948,7 +2881,6 @@ dependencies = [ "hyper", "hyper-util", "rustls", - "rustls-native-certs", "tokio", "tokio-rustls", "tower-service", @@ -3734,12 +3666,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "minisign-verify" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -4471,15 +4397,15 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "oneclient_app" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "anyhow", "arboard", "bytes 1.11.1", - "cargo-packager-updater", "chrono", "freya", "freya-skia-safe", + "futures-util", "image", "mimalloc", "notify", @@ -4513,7 +4439,7 @@ dependencies = [ [[package]] name = "oneclient_auth" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "base64 0.22.1", "chrono", @@ -4537,7 +4463,7 @@ dependencies = [ [[package]] name = "oneclient_cluster" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "bytes 1.11.1", "chrono", @@ -4560,7 +4486,7 @@ dependencies = [ [[package]] name = "oneclient_common" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "directories", "interfrost", @@ -4571,7 +4497,7 @@ dependencies = [ [[package]] name = "oneclient_content" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "async-trait", "async_zip", @@ -4597,7 +4523,7 @@ dependencies = [ [[package]] name = "oneclient_core" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "async-compression", "async-stream", @@ -4662,7 +4588,7 @@ dependencies = [ [[package]] name = "oneclient_db" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "async-tempfile", "chrono", @@ -4677,7 +4603,7 @@ dependencies = [ [[package]] name = "oneclient_discord" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "chrono", "discord-rich-presence", @@ -4689,7 +4615,7 @@ dependencies = [ [[package]] name = "oneclient_events" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "thiserror 2.0.18", "tokio", @@ -4699,7 +4625,7 @@ dependencies = [ [[package]] name = "oneclient_java" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "astral-tokio-tar", "async-tempfile", @@ -4721,7 +4647,7 @@ dependencies = [ [[package]] name = "oneclient_macro" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "proc-macro2", "quote", @@ -4730,7 +4656,7 @@ dependencies = [ [[package]] name = "oneclient_mc" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "base64 0.22.1", "chrono", @@ -4752,7 +4678,7 @@ dependencies = [ [[package]] name = "oneclient_net" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "arc-swap", "async-trait", @@ -4774,7 +4700,7 @@ dependencies = [ [[package]] name = "oneclient_polyplus" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "async-tungstenite", "bytes 1.11.1", @@ -5052,7 +4978,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "polyio" -version = "2.2.3" +version = "2.2.3-c1" dependencies = [ "astral-tokio-tar", "async-compression", @@ -5529,17 +5455,6 @@ dependencies = [ "bitflags 2.13.1", ] -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 1.0.69", -] - [[package]] name = "redox_users" version = "0.5.2" @@ -5610,7 +5525,6 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", - "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -5618,14 +5532,12 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-rustls", - "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams 0.4.2", "web-sys", "webpki-roots", ] @@ -5672,7 +5584,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams 0.5.0", + "wasm-streams", "web-sys", ] @@ -6973,7 +6885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a90a0ca3ee6a69f2ad28fd11621a4c3f03b371f366be500b64df260c4ffbafb4" dependencies = [ "as-raw-xcb-connection", - "ctor 0.10.1", + "ctor", "libloading", "pkg-config", "tracing", @@ -7622,19 +7534,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasm-streams" version = "0.5.0" @@ -8041,15 +7940,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -8086,21 +7976,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -8143,12 +8018,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -8161,12 +8030,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -8179,12 +8042,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -8209,12 +8066,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -8227,12 +8078,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -8245,12 +8090,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -8263,12 +8102,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 58863123..32e07384 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ homepage = "https://polyfrost.org/projects/oneclient" license = "GPL-3.0-only" readme = "README.md" repository = "https://github.com/Polyfrost/OneLauncher" -version = "2.2.3" +version = "2.2.3-c1" [workspace.dependencies] # core diff --git a/packages/oneclient_app/Cargo.toml b/packages/oneclient_app/Cargo.toml index 74970a0e..ea10677f 100644 --- a/packages/oneclient_app/Cargo.toml +++ b/packages/oneclient_app/Cargo.toml @@ -45,8 +45,8 @@ regex.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true reqwest.workspace = true +futures-util = { workspace = true, features = ["std"] } -cargo-packager-updater = "=0.2.3" rfd = "=0.17.2" open = "=5.4.0" diff --git a/packages/oneclient_app/src/constants.rs b/packages/oneclient_app/src/constants.rs index ee67b519..a8aa2e0c 100644 --- a/packages/oneclient_app/src/constants.rs +++ b/packages/oneclient_app/src/constants.rs @@ -1,7 +1,7 @@ pub const WINDOW_APP_ID: &str = "oneclient_app"; pub const WINDOW_TITLE: &str = "OneClient"; -pub const UPDATER_PUBKEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFGODk3MkMyMjg0MjFDMDUKUldRRkhFSW93bktKSHpkWjNEMXNzaDVINVpCTU8xSnhuK2RnV0dTZ2FkcFJWbG1zUkhGYTNjaUkK"; -pub const UPDATER_ENDPOINT: &str = - "https://github.com/oiupoyt/OneLauncher-Cracked/releases/latest/download/latest.json"; +pub const GITHUB_REPO: &str = "oiupoyt/OneLauncher-Cracked"; +pub const GITHUB_API_LATEST_RELEASE: &str = + "https://api.github.com/repos/oiupoyt/OneLauncher-Cracked/releases/latest"; pub const RELEASES_URL: &str = "https://github.com/oiupoyt/OneLauncher-Cracked/releases/latest"; diff --git a/packages/oneclient_app/src/updater.rs b/packages/oneclient_app/src/updater.rs index 20691f2f..1e16a71a 100644 --- a/packages/oneclient_app/src/updater.rs +++ b/packages/oneclient_app/src/updater.rs @@ -1,10 +1,9 @@ -use std::cell::Cell; +use std::path::PathBuf; -use cargo_packager_updater::{Config, Update, check_update}; use oneclient_events::{Choice, EventBus, Prompt}; use uuid::Uuid; -use crate::constants::{RELEASES_URL, UPDATER_ENDPOINT, UPDATER_PUBKEY}; +use crate::constants::GITHUB_API_LATEST_RELEASE; pub const UPDATE_CHOICE_INSTALL: &str = "update.install"; @@ -15,7 +14,7 @@ enum UpdateAnswer { fn update_prompt(version: &str) -> Prompt { Prompt::new( "Update available", - format!("OneClient {version} is ready to install. Download and install it now?"), + format!("OneLauncher-Cracked {version} is ready to install. Download and install it now?"), ) .option( Choice::primary(UPDATE_CHOICE_INSTALL, "Install"), @@ -26,6 +25,95 @@ fn update_prompt(version: &str) -> Prompt { const PROGRESS_STEP: u64 = 256 * 1024; +/// SemVer parser supporting cracked revision suffixes like `2.2.3-c1`, `2.2.3.c1`, `v2.2.3-c2`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CrackedVersion { + pub major: u64, + pub minor: u64, + pub patch: u64, + pub rev: u64, +} + +impl CrackedVersion { + pub fn parse(s: &str) -> Option { + let trimmed = s.trim().trim_start_matches(['v', 'V']); + + let (base, rev) = if let Some((base_part, rev_part)) = trimmed.split_once("-c").or_else(|| trimmed.split_once(".c")) { + let rev_num = rev_part.parse::().ok()?; + (base_part, rev_num) + } else if let Some((base_part, rev_part)) = trimmed.split_once("-r").or_else(|| trimmed.split_once(".r")) { + let rev_num = rev_part.parse::().ok()?; + (base_part, rev_num) + } else if let Some((base_part, rev_part)) = trimmed.split_once('-') { + let rev_num = rev_part.trim_start_matches(['c', 'C', 'r', 'R']).parse::().unwrap_or(0); + (base_part, rev_num) + } else { + (trimmed, 0) + }; + + let mut parts = base.split('.'); + let major = parts.next()?.parse::().ok()?; + let minor = parts.next()?.parse::().ok()?; + let patch = parts.next().unwrap_or("0").parse::().ok()?; + + Some(Self { + major, + minor, + patch, + rev, + }) + } +} + +#[derive(Debug, Clone, serde::Deserialize)] +pub struct GithubRelease { + pub tag_name: String, + pub name: Option, + pub body: Option, + pub html_url: String, + #[serde(default)] + pub assets: Vec, +} + +#[derive(Debug, Clone, serde::Deserialize)] +pub struct GithubReleaseAsset { + pub name: String, + pub size: u64, + pub browser_download_url: String, +} + +impl GithubRelease { + pub fn find_installer_asset(&self) -> Option<&GithubReleaseAsset> { + #[cfg(target_os = "windows")] + { + self.assets.iter().find(|a| a.name.ends_with("_x64-setup.exe") || a.name.ends_with("-setup.exe") || a.name.ends_with(".exe")) + } + #[cfg(target_os = "linux")] + { + if std::env::var_os("APPIMAGE").is_some() { + self.assets.iter().find(|a| a.name.ends_with(".AppImage")) + } else { + self.assets.iter().find(|a| a.name.ends_with(".deb") || a.name.ends_with(".AppImage") || a.name.ends_with(".rpm")) + } + } + #[cfg(target_os = "macos")] + { + #[cfg(target_arch = "aarch64")] + { + self.assets.iter().find(|a| a.name.ends_with("aarch64.dmg") || a.name.ends_with("arm64.dmg") || a.name.ends_with(".dmg")) + } + #[cfg(not(target_arch = "aarch64"))] + { + self.assets.iter().find(|a| a.name.ends_with("x64.dmg") || a.name.ends_with("x86_64.dmg") || a.name.ends_with(".dmg")) + } + } + #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))] + { + None + } + } +} + pub fn spawn_update_check(auto_install: bool, events: EventBus) { tokio::spawn(async move { if let Err(err) = run_check(auto_install, events).await { @@ -44,7 +132,7 @@ pub fn spawn_simulated_update() { } async fn run_simulated_update() -> anyhow::Result<()> { - const FAKE_VERSION: &str = "9999.9999.9999"; + const FAKE_VERSION: &str = "2.2.3-c99"; const FAKE_TOTAL: u64 = 48 * 1024 * 1024; let events = crate::launcher::state()?.services.events.clone(); @@ -55,7 +143,7 @@ async fn run_simulated_update() -> anyhow::Result<()> { } let progress_id = Uuid::new_v4(); - let label = format!("Downloading OneClient {FAKE_VERSION}"); + let label = format!("Downloading OneLauncher-Cracked {FAKE_VERSION}"); let mut downloaded = 0u64; events.progress(progress_id, &label, downloaded, FAKE_TOTAL); @@ -68,41 +156,38 @@ async fn run_simulated_update() -> anyhow::Result<()> { events.finish_progress( progress_id, "Finished Downloading", - format!("OneClient {FAKE_VERSION} is ready. Restart to apply."), + format!("OneLauncher-Cracked {FAKE_VERSION} is ready. Restart to apply."), ); Ok(()) } async fn run_check(auto_install: bool, events: EventBus) -> anyhow::Result<()> { - // `check_update` performs a blocking HTTP request so offload it to a thread pool - let Some(update) = tokio::task::spawn_blocking(check_for_update).await?? else { + let Some(release) = check_for_github_update().await? else { tracing::info!("no update available"); return Ok(()); }; - tracing::info!("update available: {}", update.version); + tracing::info!("update available: {}", release.tag_name); - // cargo-packager-updater can only replace an AppImage in place deb/rpm installs live - // under a package-managed path that an install would fail on or clobber if !can_self_update() { - tracing::info!("install is not self-updatable (non-AppImage Linux); notifying only"); + tracing::info!("install is not self-updatable in-place; notifying with release URL"); events .notify("Update available") .body(format!( - "OneClient {} is available. Download the latest package from {} to update.", - update.version, RELEASES_URL + "OneLauncher-Cracked {} is available. Download from {}", + release.tag_name, release.html_url )) .send(); return Ok(()); } - if !auto_install && events.ask(update_prompt(&update.version)).await?.is_none() { - tracing::info!("user declined update {}", update.version); + if !auto_install && events.ask(update_prompt(&release.tag_name)).await?.is_none() { + tracing::info!("user declined update {}", release.tag_name); return Ok(()); } - download_and_install(update, events).await + download_and_install_github(release, events).await } fn can_self_update() -> bool { @@ -116,70 +201,185 @@ fn can_self_update() -> bool { return false; } + #[cfg(target_os = "windows")] + { + true + } #[cfg(target_os = "linux")] { std::env::var_os("APPIMAGE").is_some() } - #[cfg(not(target_os = "linux"))] + #[cfg(not(any(target_os = "windows", target_os = "linux")))] { - true + false } } -fn check_for_update() -> anyhow::Result> { - let current = env!("CARGO_PKG_VERSION").parse()?; - let config = Config { - endpoints: vec![UPDATER_ENDPOINT.parse()?], - pubkey: UPDATER_PUBKEY.into(), - ..Default::default() +async fn check_for_github_update() -> anyhow::Result> { + let client = reqwest::Client::builder() + .user_agent("OneLauncher-Cracked-Updater") + .timeout(std::time::Duration::from_secs(10)) + .build()?; + + let resp = client + .get(GITHUB_API_LATEST_RELEASE) + .send() + .await?; + + if !resp.status().is_success() { + tracing::debug!("GitHub releases API returned status: {}", resp.status()); + return Ok(None); + } + + let release: GithubRelease = resp.json().await?; + + let current_str = env!("CARGO_PKG_VERSION"); + let current_ver = CrackedVersion::parse(current_str).unwrap_or(CrackedVersion { + major: 2, + minor: 2, + patch: 3, + rev: 1, + }); + + let remote_ver = match CrackedVersion::parse(&release.tag_name) { + Some(v) => v, + None => return Ok(None), }; - Ok(check_update(current, config)?) + if remote_ver > current_ver { + Ok(Some(release)) + } else { + Ok(None) + } } -async fn download_and_install(update: Update, events: EventBus) -> anyhow::Result<()> { +async fn download_and_install_github(release: GithubRelease, events: EventBus) -> anyhow::Result<()> { + let Some(asset) = release.find_installer_asset() else { + events + .notify("Update available") + .body(format!( + "OneLauncher-Cracked {} is available at {}", + release.tag_name, release.html_url + )) + .send(); + return Ok(()); + }; + let progress_id = Uuid::new_v4(); - let version = update.version.clone(); - let label = format!("Downloading OneClient {version}"); + let version = release.tag_name.clone(); + let label = format!("Downloading OneLauncher-Cracked {version}"); - events.progress(progress_id, &label, 0, 0); + events.progress(progress_id, &label, 0, asset.size); - tokio::task::spawn_blocking(move || -> anyhow::Result<()> { - let downloaded = Cell::new(0u64); - let last_sent = Cell::new(0u64); + let client = reqwest::Client::builder() + .user_agent("OneLauncher-Cracked-Updater") + .build()?; - let bytes = update.download_extended( - |chunk, total| { - let now = downloaded.get() + chunk as u64; - downloaded.set(now); - let total = total.unwrap_or(0); + let mut resp = client.get(&asset.browser_download_url).send().await?; + if !resp.status().is_success() { + anyhow::bail!("Failed to download update asset: HTTP {}", resp.status()); + } - if now == chunk as u64 - || (total > 0 && now >= total) - || now - last_sent.get() >= PROGRESS_STEP - { - last_sent.set(now); - events.progress(progress_id, &label, now, total); - } - }, - || {}, - )?; + let total_size = asset.size.max(resp.content_length().unwrap_or(0)); - let total = downloaded.get().max(1); - events.progress(progress_id, &label, total, total); + #[cfg(target_os = "windows")] + let target_file = std::env::temp_dir().join(&asset.name); - update.install(bytes)?; + #[cfg(target_os = "linux")] + let (target_file, is_appimage, final_appimage_dest) = if let Some(appimage) = std::env::var_os("APPIMAGE") { + let dest = PathBuf::from(appimage); + let temp_download = dest.with_extension("new_appimage"); + (temp_download, true, Some(dest)) + } else { + (std::env::temp_dir().join(&asset.name), false, None) + }; - // Converts the same download card into its finished state rather than adding a second + #[cfg(target_os = "macos")] + let target_file = std::env::temp_dir().join(&asset.name); + + let mut file = tokio::fs::File::create(&target_file).await?; + let mut downloaded = 0u64; + let mut last_sent = 0u64; + + while let Some(chunk) = resp.chunk().await? { + tokio::io::AsyncWriteExt::write_all(&mut file, &chunk).await?; + downloaded += chunk.len() as u64; + + if downloaded - last_sent >= PROGRESS_STEP || (total_size > 0 && downloaded >= total_size) { + last_sent = downloaded; + events.progress(progress_id, &label, downloaded, total_size); + } + } + tokio::io::AsyncWriteExt::flush(&mut file).await?; + drop(file); + + events.progress(progress_id, &label, total_size, total_size); + + #[cfg(target_os = "windows")] + { events.finish_progress( progress_id, - "Finished Downloading", - format!("OneClient {version} is ready. Restart to apply."), + "Launching Installer", + format!("OneLauncher-Cracked {version} ready. Opening installer..."), ); - Ok(()) - }) - .await??; + let _ = std::process::Command::new(&target_file).spawn(); + } - tracing::info!("update installed; restart to apply"); + #[cfg(target_os = "linux")] + { + if is_appimage && let Some(dest) = final_appimage_dest { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let _ = std::fs::set_permissions(&target_file, std::fs::Permissions::from_mode(0o755)); + } + tokio::fs::rename(&target_file, &dest).await?; + events.finish_progress( + progress_id, + "Finished Updating", + format!("OneLauncher-Cracked {version} is ready. Restart to apply."), + ); + } else { + events.finish_progress( + progress_id, + "Download Complete", + format!("Downloaded {} to {}", asset.name, target_file.display()), + ); + } + } + + #[cfg(target_os = "macos")] + { + events.finish_progress( + progress_id, + "Download Complete", + format!("Downloaded {} to {}. Open to install.", asset.name, target_file.display()), + ); + let _ = open::that(&target_file); + } + + tracing::info!("update download and installation handling completed"); Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cracked_version_ordering() { + let v1 = CrackedVersion::parse("2.2.3-c1").unwrap(); + let v2 = CrackedVersion::parse("2.2.3-c2").unwrap(); + let v3 = CrackedVersion::parse("v2.2.4-c1").unwrap(); + let v_base = CrackedVersion::parse("v2.2.3").unwrap(); + + assert!(v2 > v1); + assert!(v3 > v2); + assert!(v1 > v_base); + assert_eq!(v1, CrackedVersion { major: 2, minor: 2, patch: 3, rev: 1 }); + assert_eq!(v2, CrackedVersion { major: 2, minor: 2, patch: 3, rev: 2 }); + assert_eq!(v3, CrackedVersion { major: 2, minor: 2, patch: 4, rev: 1 }); + assert_eq!(v_base, CrackedVersion { major: 2, minor: 2, patch: 3, rev: 0 }); + } +} +