feat(update): add configurable update download source
This commit is contained in:
1 parent
d128604743
commit
76e2d33cd3
12 files changed
+300
-17
No files matched your search
@@ -1,4 +1,78 @@
|
||||
use std::sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
pub use dbx_core::update::UpdateInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
|
||||
const OFFICIAL_UPDATE_ENDPOINTS: [&str; 2] = [
|
||||
"https://dl.dbxio.com/releases/latest/latest.json",
|
||||
"https://github.com/t8y2/dbx/releases/latest/download/latest.json",
|
||||
];
|
||||
const CNB_RELEASE_DOWNLOAD_PREFIX: &str = "https://cnb.cool/dbxio.com/dbx/-/releases/download/";
|
||||
const GITHUB_RELEASE_DOWNLOAD_PREFIX: &str = "https://github.com/t8y2/dbx/releases/download/";
|
||||
const UPDATE_DOWNLOAD_PROGRESS_EVENT: &str = "update-download-progress";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum UpdateDownloadSource {
|
||||
Official,
|
||||
Cnb,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct UpdateDownloadProgress {
|
||||
pub downloaded: u64,
|
||||
pub total: Option<u64>,
|
||||
}
|
||||
|
||||
impl UpdateDownloadSource {
|
||||
fn label(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Official => "official",
|
||||
Self::Cnb => "cnb",
|
||||
}
|
||||
}
|
||||
|
||||
fn endpoints(&self, latest_version: Option<&str>) -> Result<Vec<String>, String> {
|
||||
match self {
|
||||
Self::Official => Ok(OFFICIAL_UPDATE_ENDPOINTS.iter().map(|endpoint| endpoint.to_string()).collect()),
|
||||
Self::Cnb => {
|
||||
let version =
|
||||
latest_version.ok_or_else(|| "Latest version is required for CNB updates.".to_string())?;
|
||||
Ok(vec![format!("{CNB_RELEASE_DOWNLOAD_PREFIX}{}/latest.json", tag_version(version))])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rewrite_download_url(&self, url: &str) -> Result<Option<String>, String> {
|
||||
if !matches!(self, Self::Cnb) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if url.starts_with(CNB_RELEASE_DOWNLOAD_PREFIX) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let rewritten = url
|
||||
.strip_prefix(GITHUB_RELEASE_DOWNLOAD_PREFIX)
|
||||
.map(|path| format!("{CNB_RELEASE_DOWNLOAD_PREFIX}{path}"))
|
||||
.ok_or_else(|| format!("Unsupported update download URL for CNB source: {url}"))?;
|
||||
Ok(Some(rewritten))
|
||||
}
|
||||
}
|
||||
|
||||
fn tag_version(version: &str) -> String {
|
||||
let version = version.trim();
|
||||
if version.starts_with('v') {
|
||||
version.to_string()
|
||||
} else {
|
||||
format!("v{version}")
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_for_updates() -> Result<UpdateInfo, String> {
|
||||
@@ -13,3 +87,98 @@ pub async fn check_for_updates() -> Result<UpdateInfo, String> {
|
||||
pub async fn get_system_proxy_url() -> Option<String> {
|
||||
tauri::async_runtime::spawn_blocking(dbx_core::update::system_proxy_url).await.ok().flatten()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn download_and_install_update(
|
||||
app: AppHandle,
|
||||
source: UpdateDownloadSource,
|
||||
latest_version: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
if crate::data_dir::is_portable_mode() {
|
||||
return Err("Portable builds cannot use the in-app installer.".to_string());
|
||||
}
|
||||
|
||||
let endpoint_urls = source.endpoints(latest_version.as_deref())?;
|
||||
println!("[DBX updater] checking from {} endpoints: {}", source.label(), endpoint_urls.join(", "));
|
||||
let mut endpoints = Vec::with_capacity(endpoint_urls.len());
|
||||
for endpoint_url in endpoint_urls {
|
||||
endpoints.push(endpoint_url.parse().map_err(|e| format!("Invalid update endpoint: {e}"))?);
|
||||
}
|
||||
let mut builder =
|
||||
app.updater_builder().endpoints(endpoints).map_err(|e| format!("Failed to configure updater endpoint: {e}"))?;
|
||||
|
||||
if let Some(proxy_url) = dbx_core::update::system_proxy_url() {
|
||||
let proxy = proxy_url.parse().map_err(|e| format!("Invalid system proxy URL: {e}"))?;
|
||||
builder = builder.proxy(proxy);
|
||||
}
|
||||
|
||||
let updater = builder.build().map_err(|e| format!("Failed to create updater: {e}"))?;
|
||||
let update = updater.check().await.map_err(|e| format!("Failed to check updates: {e}"))?;
|
||||
let Some(mut update) = update else {
|
||||
return Err("No update available.".to_string());
|
||||
};
|
||||
if let Some(download_url) = source.rewrite_download_url(update.download_url.as_str())? {
|
||||
update.download_url = download_url.parse().map_err(|e| format!("Invalid CNB update download URL: {e}"))?;
|
||||
}
|
||||
println!("[DBX updater] downloading from {} URL: {}", source.label(), update.download_url);
|
||||
|
||||
let downloaded = Arc::new(AtomicU64::new(0));
|
||||
let finished_downloaded = Arc::clone(&downloaded);
|
||||
update
|
||||
.download_and_install(
|
||||
|chunk_len, total| {
|
||||
let downloaded =
|
||||
downloaded.fetch_add(chunk_len as u64, Ordering::Relaxed).saturating_add(chunk_len as u64);
|
||||
let _ = app.emit(UPDATE_DOWNLOAD_PROGRESS_EVENT, UpdateDownloadProgress { downloaded, total });
|
||||
},
|
||||
|| {
|
||||
let downloaded = finished_downloaded.load(Ordering::Relaxed);
|
||||
let _ = app.emit(
|
||||
UPDATE_DOWNLOAD_PROGRESS_EVENT,
|
||||
UpdateDownloadProgress { downloaded, total: Some(downloaded) },
|
||||
);
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to download and install update: {e}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{tag_version, UpdateDownloadSource, CNB_RELEASE_DOWNLOAD_PREFIX, OFFICIAL_UPDATE_ENDPOINTS};
|
||||
|
||||
#[test]
|
||||
fn normalizes_update_tag_versions() {
|
||||
assert_eq!(tag_version("0.5.39"), "v0.5.39");
|
||||
assert_eq!(tag_version("v0.5.39"), "v0.5.39");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_official_update_endpoints() {
|
||||
let endpoints = UpdateDownloadSource::Official.endpoints(None).unwrap();
|
||||
assert_eq!(endpoints, OFFICIAL_UPDATE_ENDPOINTS);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_cnb_update_endpoint_for_tag() {
|
||||
let endpoints = UpdateDownloadSource::Cnb.endpoints(Some("0.5.39")).unwrap();
|
||||
assert_eq!(endpoints, vec![format!("{CNB_RELEASE_DOWNLOAD_PREFIX}v0.5.39/latest.json")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrites_github_asset_url_to_cnb() {
|
||||
let download_url = UpdateDownloadSource::Cnb
|
||||
.rewrite_download_url("https://github.com/t8y2/dbx/releases/download/v0.5.39/DBX_0.5.39_aarch64.dmg")
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(download_url, "https://cnb.cool/dbxio.com/dbx/-/releases/download/v0.5.39/DBX_0.5.39_aarch64.dmg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_existing_cnb_asset_url() {
|
||||
let download_url = UpdateDownloadSource::Cnb
|
||||
.rewrite_download_url("https://cnb.cool/dbxio.com/dbx/-/releases/download/v0.5.39/DBX_0.5.39_aarch64.dmg")
|
||||
.unwrap();
|
||||
assert_eq!(download_url, None);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user