feat(updater): implement direct GitHub Releases auto-updater with cracked revision support (2.2.3-c1)

This commit is contained in:
katR 2026-08-30 14:43:16 +05:30
parent 05dac19dd2
commit f84fec2c35
5 changed files with 284 additions and 251 deletions

207
Cargo.lock generated
View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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";

View file

@ -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<UpdateAnswer> {
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<UpdateAnswer> {
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<Self> {
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::<u64>().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::<u64>().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::<u64>().unwrap_or(0);
(base_part, rev_num)
} else {
(trimmed, 0)
};
let mut parts = base.split('.');
let major = parts.next()?.parse::<u64>().ok()?;
let minor = parts.next()?.parse::<u64>().ok()?;
let patch = parts.next().unwrap_or("0").parse::<u64>().ok()?;
Some(Self {
major,
minor,
patch,
rev,
})
}
}
#[derive(Debug, Clone, serde::Deserialize)]
pub struct GithubRelease {
pub tag_name: String,
pub name: Option<String>,
pub body: Option<String>,
pub html_url: String,
#[serde(default)]
pub assets: Vec<GithubReleaseAsset>,
}
#[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<Option<Update>> {
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<Option<GithubRelease>> {
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 });
}
}