feat(update): mirror agent release downloads
This commit is contained in:
1 parent
f4ff8b11a5
commit
fec91b9aef
10 files changed
+355
-53
No files matched your search
@@ -4,13 +4,14 @@ use tauri::{Emitter, State};
|
||||
|
||||
use dbx_core::agent_manager::{AgentDriverInfo, DriverStoreUsage, JavaRuntimeConfig, JavaRuntimeMode, DEFAULT_JRE_KEY};
|
||||
use dbx_core::agent_service::{
|
||||
build_agent_list, clear_agent_download_cache, fetch_registry, import_agent_jar,
|
||||
import_agents_from_zip as import_agents_from_zip_core, install_agent_driver, invalidate_registry_cache,
|
||||
reinstall_agent_jre, uninstall_agent_driver, uninstall_agent_jre, upgrade_all_agent_drivers, AgentProgressEvent,
|
||||
UpgradeAllAgentDriversResult,
|
||||
build_agent_list, clear_agent_download_cache, fetch_registry_from, import_agent_jar,
|
||||
import_agents_from_zip as import_agents_from_zip_core, install_agent_driver_from, invalidate_registry_cache,
|
||||
reinstall_agent_jre_from, uninstall_agent_driver, uninstall_agent_jre, upgrade_all_agent_drivers_from,
|
||||
AgentProgressEvent, UpgradeAllAgentDriversResult,
|
||||
};
|
||||
use dbx_core::connection::AppState;
|
||||
use dbx_core::driver_runtime::DriverRuntimeSummary;
|
||||
use dbx_core::DownloadSource;
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct AgentUpdateBlocker {
|
||||
@@ -24,8 +25,11 @@ pub async fn list_installed_agents_local(state: State<'_, Arc<AppState>>) -> Res
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_installed_agents(state: State<'_, Arc<AppState>>) -> Result<Vec<AgentDriverInfo>, String> {
|
||||
let registry = fetch_registry().await.ok();
|
||||
pub async fn list_installed_agents(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
source: Option<DownloadSource>,
|
||||
) -> Result<Vec<AgentDriverInfo>, String> {
|
||||
let registry = fetch_registry_from(source.unwrap_or_default()).await.ok();
|
||||
Ok(build_agent_list(&state.agent_manager, registry.as_ref()))
|
||||
}
|
||||
|
||||
@@ -64,24 +68,31 @@ pub async fn install_agent(
|
||||
app: tauri::AppHandle,
|
||||
state: State<'_, Arc<AppState>>,
|
||||
db_type: String,
|
||||
source: Option<DownloadSource>,
|
||||
) -> Result<(), String> {
|
||||
ensure_no_agent_update_blockers(state.inner().as_ref(), std::slice::from_ref(&db_type)).await?;
|
||||
let app_handle = app.clone();
|
||||
install_agent_driver(&state.agent_manager, &db_type, move |event| emit_agent_progress(&app_handle, event)).await
|
||||
install_agent_driver_from(&state.agent_manager, &db_type, source.unwrap_or_default(), move |event| {
|
||||
emit_agent_progress(&app_handle, event)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn upgrade_all_agents(
|
||||
app: tauri::AppHandle,
|
||||
state: State<'_, Arc<AppState>>,
|
||||
source: Option<DownloadSource>,
|
||||
) -> Result<UpgradeAllAgentDriversResult, String> {
|
||||
let registry = fetch_registry().await?;
|
||||
let source = source.unwrap_or_default();
|
||||
let registry = fetch_registry_from(source).await?;
|
||||
let agents = build_agent_list(&state.agent_manager, Some(®istry));
|
||||
let updatable: Vec<String> =
|
||||
agents.iter().filter(|agent| agent.update_available).map(|agent| agent.db_type.clone()).collect();
|
||||
ensure_no_agent_update_blockers(state.inner().as_ref(), &updatable).await?;
|
||||
let app_handle = app.clone();
|
||||
upgrade_all_agent_drivers(&state.agent_manager, move |event| emit_agent_progress(&app_handle, event)).await
|
||||
upgrade_all_agent_drivers_from(&state.agent_manager, source, move |event| emit_agent_progress(&app_handle, event))
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -172,10 +183,14 @@ pub async fn reinstall_jre(
|
||||
app: tauri::AppHandle,
|
||||
state: State<'_, Arc<AppState>>,
|
||||
jre_key: Option<String>,
|
||||
source: Option<DownloadSource>,
|
||||
) -> Result<(), String> {
|
||||
let key = jre_key.as_deref().unwrap_or(DEFAULT_JRE_KEY);
|
||||
let app_handle = app.clone();
|
||||
reinstall_agent_jre(&state.agent_manager, key, move |event| emit_agent_progress(&app_handle, event)).await
|
||||
reinstall_agent_jre_from(&state.agent_manager, key, source.unwrap_or_default(), move |event| {
|
||||
emit_agent_progress(&app_handle, event)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
fn emit_agent_progress(app: &tauri::AppHandle, event: AgentProgressEvent) {
|
||||
|
||||
@@ -12,6 +12,7 @@ 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 R2_LATEST_RELEASE_DOWNLOAD_PREFIX: &str = "https://dl.dbxio.com/releases/latest/";
|
||||
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 ATOMGIT_RELEASE_DOWNLOAD_PREFIX: &str = "https://atomgit.com/t8y2/dbx/releases/download/";
|
||||
@@ -46,12 +47,18 @@ impl UpdateDownloadSource {
|
||||
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))])
|
||||
Ok(vec![
|
||||
format!("{CNB_RELEASE_DOWNLOAD_PREFIX}{}/latest.json", tag_version(version)),
|
||||
OFFICIAL_UPDATE_ENDPOINTS[0].to_string(),
|
||||
])
|
||||
}
|
||||
Self::Atomgit => {
|
||||
let version =
|
||||
latest_version.ok_or_else(|| "Latest version is required for AtomGit updates.".to_string())?;
|
||||
Ok(vec![format!("{ATOMGIT_RELEASE_DOWNLOAD_PREFIX}{}/latest.json", tag_version(version))])
|
||||
Ok(vec![
|
||||
format!("{ATOMGIT_RELEASE_DOWNLOAD_PREFIX}{}/latest.json", tag_version(version)),
|
||||
OFFICIAL_UPDATE_ENDPOINTS[0].to_string(),
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,6 +85,18 @@ impl UpdateDownloadSource {
|
||||
Self::Official => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn r2_fallback_url(&self, url: &str) -> Result<Option<String>, String> {
|
||||
if matches!(self, Self::Official) || url.starts_with(R2_LATEST_RELEASE_DOWNLOAD_PREFIX) {
|
||||
return Ok(None);
|
||||
}
|
||||
let filename = url
|
||||
.rsplit('/')
|
||||
.next()
|
||||
.filter(|name| !name.is_empty())
|
||||
.ok_or_else(|| format!("Unsupported update download URL for {} source: {url}", self.label()))?;
|
||||
Ok(Some(format!("{R2_LATEST_RELEASE_DOWNLOAD_PREFIX}{filename}")))
|
||||
}
|
||||
}
|
||||
|
||||
fn tag_version(version: &str) -> String {
|
||||
@@ -136,6 +155,12 @@ pub async fn download_and_install_update(
|
||||
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}"))?;
|
||||
}
|
||||
if !update_url_is_available(update.download_url.as_str()).await {
|
||||
if let Some(fallback_url) = source.r2_fallback_url(update.download_url.as_str())? {
|
||||
println!("[DBX updater] {} asset unavailable; falling back to R2: {fallback_url}", source.label());
|
||||
update.download_url = fallback_url.parse().map_err(|e| format!("Invalid R2 update download URL: {e}"))?;
|
||||
}
|
||||
}
|
||||
println!("[DBX updater] downloading from {} URL: {}", source.label(), update.download_url);
|
||||
|
||||
let downloaded = Arc::new(AtomicU64::new(0));
|
||||
@@ -159,11 +184,25 @@ pub async fn download_and_install_update(
|
||||
.map_err(|e| format!("Failed to download and install update: {e}"))
|
||||
}
|
||||
|
||||
async fn update_url_is_available(url: &str) -> bool {
|
||||
let client = match reqwest::Client::builder().timeout(std::time::Duration::from_secs(10)).build() {
|
||||
Ok(client) => client,
|
||||
Err(_) => return false,
|
||||
};
|
||||
// Request only the first byte because some release hosts do not implement HEAD consistently.
|
||||
client
|
||||
.get(url)
|
||||
.header(reqwest::header::RANGE, "bytes=0-0")
|
||||
.send()
|
||||
.await
|
||||
.is_ok_and(|response| response.status().is_success())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
tag_version, UpdateDownloadSource, ATOMGIT_RELEASE_DOWNLOAD_PREFIX, CNB_RELEASE_DOWNLOAD_PREFIX,
|
||||
OFFICIAL_UPDATE_ENDPOINTS,
|
||||
OFFICIAL_UPDATE_ENDPOINTS, R2_LATEST_RELEASE_DOWNLOAD_PREFIX,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -181,13 +220,22 @@ mod tests {
|
||||
#[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")]);
|
||||
assert_eq!(
|
||||
endpoints,
|
||||
vec![format!("{CNB_RELEASE_DOWNLOAD_PREFIX}v0.5.39/latest.json"), OFFICIAL_UPDATE_ENDPOINTS[0].to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_atomgit_update_endpoint_for_tag() {
|
||||
let endpoints = UpdateDownloadSource::Atomgit.endpoints(Some("0.5.44")).unwrap();
|
||||
assert_eq!(endpoints, vec![format!("{ATOMGIT_RELEASE_DOWNLOAD_PREFIX}v0.5.44/latest.json")]);
|
||||
assert_eq!(
|
||||
endpoints,
|
||||
vec![
|
||||
format!("{ATOMGIT_RELEASE_DOWNLOAD_PREFIX}v0.5.44/latest.json"),
|
||||
OFFICIAL_UPDATE_ENDPOINTS[0].to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -223,4 +271,12 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(download_url, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_r2_fallback_for_mirror_asset() {
|
||||
let fallback = UpdateDownloadSource::Atomgit
|
||||
.r2_fallback_url("https://atomgit.com/t8y2/dbx/releases/download/v0.5.44/DBX_0.5.44_x64.dmg")
|
||||
.unwrap();
|
||||
assert_eq!(fallback, Some(format!("{R2_LATEST_RELEASE_DOWNLOAD_PREFIX}DBX_0.5.44_x64.dmg")));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user