1
0
forked from GRIN/grim
Files
goblin/src/http/release.rs
T
2ro d49cf5c82c Route update check through Tor and drop identifying User-Agent
retrieve_release now uses tor::http_request (honors route_over_tor:
Tor when on, clearnet when off) instead of the clearnet-only
HttpClient::send. Drops the goblin-wallet User-Agent so the check no
longer self-identifies; the Tor helper supplies a browser-like default
UA that also satisfies GitHub's UA requirement.
2026-07-10 15:36:29 -04:00

152 lines
4.6 KiB
Rust

// Copyright 2026 The Grim Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::gui::views::View;
use chrono::NaiveDateTime;
use egui::os::OperatingSystem;
use serde_derive::Deserialize;
#[derive(Deserialize)]
pub struct ReleaseAsset {
pub name: String,
pub browser_download_url: String,
pub size: u64,
}
#[derive(Deserialize)]
pub struct ReleaseInfo {
pub tag_name: String,
pub body: String,
pub published_at: String,
pub assets: Vec<ReleaseAsset>,
}
#[cfg(target_arch = "x86_64")]
/// x86 CPU architecture.
const X86_ARCH: &str = "x86_64";
#[cfg(target_arch = "x86_64")]
const ARCH: &'static str = X86_ARCH;
/// ARM CPU architecture.
const ARM_ARCH: &str = "arm";
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
const ARCH: &'static str = ARM_ARCH;
/// Base endpoint to download the release.
const BASE_DOWNLOAD_URL: &'static str = "https://github.com/2ro/goblin/releases/download/";
impl ReleaseInfo {
/// Release version (the build tag, e.g. "build71").
pub fn version(&self) -> String {
self.tag_name.clone()
}
/// Get artifact release name based on current platform. Matches the assets
/// attached to Goblin's GitHub releases; platforms Goblin doesn't ship
/// (linux-arm, macOS, windows-arm) return None.
fn name(&self) -> Option<String> {
let os = OperatingSystem::from_target_os();
match os {
OperatingSystem::Unknown => None,
OperatingSystem::Android => {
let name = if ARCH == ARM_ARCH {
format!("goblin-{}-android-arm.apk", self.tag_name)
} else {
format!("goblin-{}-android-x86_64.apk", self.tag_name)
};
Some(name)
}
OperatingSystem::IOS => None,
OperatingSystem::Nix => {
if ARCH == ARM_ARCH {
None
} else {
Some(format!("goblin-{}-linux-x86_64.tar.gz", self.tag_name))
}
}
OperatingSystem::Mac => None,
OperatingSystem::Windows => {
if ARCH == ARM_ARCH {
None
} else {
Some(format!("goblin-{}-win-x86_64.zip", self.tag_name))
}
}
}
}
/// Get link to download the release.
pub fn url(&self) -> Option<String> {
let base_url = format!("{}{}/", BASE_DOWNLOAD_URL, self.tag_name);
if let Some(name) = self.name() {
return Some(format!("{}{}", base_url, name));
}
None
}
/// Get formatted release date.
pub fn date(&self) -> String {
let date = self.published_at.clone().replace("T", " ").replace("Z", "");
let date_format = NaiveDateTime::parse_from_str(date.as_str(), "%Y-%m-%d %H:%M:%S");
if let Ok(date) = date_format {
return View::format_time(date.and_utc().timestamp());
}
date
}
/// Get release size in megabytes.
pub fn size(&self) -> Option<String> {
let name = self.name()?;
for a in &self.assets {
if a.name == name {
let size_mb = a.size as f64 / 1000000.0;
return Some(format!("{:.2}", size_mb));
}
}
None
}
/// Whether this release is newer than the running build. Goblin versions by
/// build number ("buildNN" tags) rather than semver, so compare the numbers.
pub fn is_update(&self) -> bool {
let cur: u64 = crate::BUILD.trim().parse().unwrap_or(0);
let rel: u64 = self
.tag_name
.trim()
.trim_start_matches("build")
.parse()
.unwrap_or(0);
rel > cur
}
}
/// API endpoint to check last release (Goblin's own GitHub releases).
const REQUEST_URL: &'static str = "https://api.github.com/repos/2ro/goblin/releases/latest";
pub async fn retrieve_release() -> Result<ReleaseInfo, String> {
// Route the update check through the Tor helper so it honors route_over_tor()
// (Tor when the toggle is on, clearnet when off) instead of the clearnet-only
// HttpClient::send. The helper sets a browser-like default User-Agent, so we
// send no identifying UA of our own; we keep only GitHub's required Accept
// header (GitHub also requires a UA, which the helper's default satisfies).
let headers = vec![(
"Accept".to_string(),
"application/vnd.github+json".to_string(),
)];
let body = crate::tor::http_request("GET", REQUEST_URL.to_string(), None, headers)
.await
.ok_or_else(|| "Error checking update".to_string())?;
serde_json::from_str::<ReleaseInfo>(&body).map_err(|_| "Error checking update".to_string())
}