feat(update): mirror agent release downloads
This commit is contained in:
parent
f4ff8b11a5
commit
fec91b9aef
|
|
@ -551,3 +551,67 @@ jobs:
|
|||
aws s3 cp "$f" "$R2_BUCKET/$key" --endpoint-url "$R2_ENDPOINT"
|
||||
echo "Uploaded $(basename $f)"
|
||||
done
|
||||
|
||||
sync-release-to-cnb:
|
||||
name: Sync agents releases to CNB
|
||||
needs: release
|
||||
if: ${{ always() && needs.release.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tag: ["${{ github.ref_name }}", "agents-latest"]
|
||||
steps:
|
||||
- name: Sync release assets to CNB
|
||||
env:
|
||||
TAG_NAME: ${{ matrix.tag }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
CNB_REPOSITORY: dbxio.com/dbx
|
||||
CNB_TOKEN: ${{ secrets.CNB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
wget -q https://cnb.cool/znb/mpgrm/-/releases/download/v0.1.0/mpgrm_linux_amd64.tar.gz
|
||||
tar -xf mpgrm_linux_amd64.tar.gz
|
||||
chmod +x mpgrm
|
||||
./mpgrm releases sync \
|
||||
--repo "https://github.com/${GITHUB_REPOSITORY}.git" \
|
||||
--target-repo "https://cnb.cool/${CNB_REPOSITORY}.git" \
|
||||
--tags "${TAG_NAME}"
|
||||
|
||||
sync-release-to-atomgit:
|
||||
name: Sync agents releases to AtomGit
|
||||
needs: release
|
||||
if: ${{ always() && needs.release.result == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tag: ["${{ github.ref_name }}", "agents-latest"]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Sync release assets to AtomGit
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG_NAME: ${{ matrix.tag }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
ATOMGIT_TOKEN: ${{ secrets.ATOMGIT_TOKEN }}
|
||||
ATOMGIT_REPOSITORY: t8y2/dbx
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch origin "refs/tags/${TAG_NAME}:refs/tags/${TAG_NAME}" --force
|
||||
git remote add atomgit "https://t8y2:${ATOMGIT_TOKEN}@atomgit.com/t8y2/dbx.git"
|
||||
git push atomgit "refs/tags/${TAG_NAME}:refs/tags/${TAG_NAME}" --force
|
||||
mkdir -p "$RUNNER_TEMP/github-release" "$RUNNER_TEMP/release-assets"
|
||||
gh release view "$TAG_NAME" --repo "$GITHUB_REPOSITORY" \
|
||||
--json tagName,name,body,targetCommitish,isPrerelease,isDraft,assets \
|
||||
> "$RUNNER_TEMP/github-release/release.json"
|
||||
gh release download "$TAG_NAME" --repo "$GITHUB_REPOSITORY" \
|
||||
--dir "$RUNNER_TEMP/release-assets" --clobber
|
||||
node .github/scripts/sync-atomgit-release.mjs \
|
||||
--github-release "$RUNNER_TEMP/github-release/release.json" \
|
||||
--assets-dir "$RUNNER_TEMP/release-assets" \
|
||||
${{ matrix.tag == 'agents-latest' && '--replace-assets' || '' }}
|
||||
|
|
|
|||
|
|
@ -2994,7 +2994,7 @@ export default {
|
|||
updateNotificationsEnabled: "Enable update reminders",
|
||||
updateNotificationsEnabledDescription: "When disabled, DBX will not automatically check app or driver updates or show update badges. Manual checks are still available.",
|
||||
updateDownloadSource: "Update download source",
|
||||
updateDownloadSourceDescription: "Choose where in-app update installers are downloaded from. The official source is recommended; CNB or AtomGit can be faster on mainland China networks.",
|
||||
updateDownloadSourceDescription: "Choose where app updates, database agents, drivers, and managed JREs are downloaded from. The official source is recommended; CNB or AtomGit can be faster on mainland China networks.",
|
||||
updateDownloadSourceOfficial: "Official source (recommended)",
|
||||
updateDownloadSourceCnb: "CNB",
|
||||
updateDownloadSourceAtomgit: "AtomGit",
|
||||
|
|
|
|||
|
|
@ -2993,7 +2993,7 @@ export default withEnglishFallback({
|
|||
updateNotificationsEnabled: "启用更新提醒",
|
||||
updateNotificationsEnabledDescription: "关闭后,DBX 不会自动检查应用和驱动更新,也不会显示更新红点;仍可手动检查更新。",
|
||||
updateDownloadSource: "更新下载源",
|
||||
updateDownloadSourceDescription: "选择应用内更新安装包的下载来源。官方源为推荐选项,CNB 或 AtomGit 适合国内网络环境。",
|
||||
updateDownloadSourceDescription: "选择应用更新、数据库 Agent、驱动和托管 JRE 的下载来源。官方源为推荐选项,CNB 或 AtomGit 适合国内网络环境。",
|
||||
updateDownloadSourceOfficial: "官方源(推荐)",
|
||||
updateDownloadSourceCnb: "CNB",
|
||||
updateDownloadSourceAtomgit: "AtomGit",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
|
||||
import type * as TauriModule from "@/lib/backend/tauri";
|
||||
import { appendDebugLog } from "@/lib/backend/debugLog";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lazy backend resolution (avoids top-level await)
|
||||
|
|
@ -76,15 +77,24 @@ export const installJdbcPlugin = forward("installJdbcPlugin");
|
|||
export const installJdbcPluginLocal = forward("installJdbcPluginLocal");
|
||||
export const uninstallJdbcPlugin = forward("uninstallJdbcPlugin");
|
||||
export const listInstalledAgentsLocal = forward("listInstalledAgentsLocal");
|
||||
export const listInstalledAgents = forward("listInstalledAgents");
|
||||
export async function listInstalledAgents() {
|
||||
const backend = await getBackend();
|
||||
return backend.listInstalledAgents(useSettingsStore().editorSettings.updateDownloadSource);
|
||||
}
|
||||
export const isAgentInstalled = forward("isAgentInstalled");
|
||||
export const getDriverStoreUsage = forward("getDriverStoreUsage");
|
||||
export const clearDriverDownloadCache = forward("clearDriverDownloadCache");
|
||||
export const getDriverRuntimeSummary = forward("getDriverRuntimeSummary");
|
||||
export const stopDriverRuntime = forward("stopDriverRuntime");
|
||||
export const restartDriverRuntime = forward("restartDriverRuntime");
|
||||
export const installAgent = forward("installAgent");
|
||||
export const upgradeAllAgents = forward("upgradeAllAgents");
|
||||
export async function installAgent(dbType: string) {
|
||||
const backend = await getBackend();
|
||||
return backend.installAgent(dbType, useSettingsStore().editorSettings.updateDownloadSource);
|
||||
}
|
||||
export async function upgradeAllAgents() {
|
||||
const backend = await getBackend();
|
||||
return backend.upgradeAllAgents(useSettingsStore().editorSettings.updateDownloadSource);
|
||||
}
|
||||
export const checkAgentUpdateBlockers = forward("checkAgentUpdateBlockers");
|
||||
export const uninstallAgent = forward("uninstallAgent");
|
||||
export const getAgentJavaRuntimeConfig = forward("getAgentJavaRuntimeConfig");
|
||||
|
|
@ -92,7 +102,10 @@ export const setAgentJavaRuntimeConfig = forward("setAgentJavaRuntimeConfig");
|
|||
export const invalidateAgentRegistryCache = forward("invalidateAgentRegistryCache");
|
||||
export const importAgentsFromZip = forward("importAgentsFromZip");
|
||||
export const importAgentJar = forward("importAgentJar");
|
||||
export const reinstallJre = forward("reinstallJre");
|
||||
export async function reinstallJre(jreKey?: string) {
|
||||
const backend = await getBackend();
|
||||
return backend.reinstallJre(jreKey, useSettingsStore().editorSettings.updateDownloadSource);
|
||||
}
|
||||
export const uninstallJre = forward("uninstallJre");
|
||||
export const listenAgentInstallProgress = forward("listenAgentInstallProgress");
|
||||
export const loadSavedSqlLibrary = forward("loadSavedSqlLibrary");
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ export async function listInstalledAgentsLocal(): Promise<AgentDriverInfo[]> {
|
|||
return get("/api/agents/installed-local");
|
||||
}
|
||||
|
||||
export async function listInstalledAgents(): Promise<AgentDriverInfo[]> {
|
||||
export async function listInstalledAgents(_source?: UpdateDownloadSource): Promise<AgentDriverInfo[]> {
|
||||
return get("/api/agents/installed");
|
||||
}
|
||||
|
||||
|
|
@ -370,11 +370,11 @@ export async function restartDriverRuntime(runtimeId: string): Promise<void> {
|
|||
await post("/api/agents/runtime/restart", { runtimeId });
|
||||
}
|
||||
|
||||
export async function installAgent(dbType: string): Promise<void> {
|
||||
export async function installAgent(dbType: string, _source?: UpdateDownloadSource): Promise<void> {
|
||||
await post("/api/agents/install", { dbType });
|
||||
}
|
||||
|
||||
export async function upgradeAllAgents(): Promise<UpgradeAllAgentDriversResult> {
|
||||
export async function upgradeAllAgents(_source?: UpdateDownloadSource): Promise<UpgradeAllAgentDriversResult> {
|
||||
return post("/api/agents/upgrade-all", {});
|
||||
}
|
||||
|
||||
|
|
@ -427,7 +427,7 @@ export async function importAgentJar(dbType: string, pathOrFile: string | File):
|
|||
if (!uploadRes.ok) throw new Error(await uploadRes.text());
|
||||
}
|
||||
|
||||
export async function reinstallJre(jreKey?: string): Promise<void> {
|
||||
export async function reinstallJre(jreKey?: string, _source?: UpdateDownloadSource): Promise<void> {
|
||||
await post("/api/agents/reinstall-jre", { jreKey });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1188,8 +1188,8 @@ export async function listInstalledAgentsLocal(): Promise<AgentDriverInfo[]> {
|
|||
return invoke("list_installed_agents_local");
|
||||
}
|
||||
|
||||
export async function listInstalledAgents(): Promise<AgentDriverInfo[]> {
|
||||
return invoke("list_installed_agents");
|
||||
export async function listInstalledAgents(source?: UpdateDownloadSource): Promise<AgentDriverInfo[]> {
|
||||
return invoke("list_installed_agents", { source });
|
||||
}
|
||||
|
||||
export async function isAgentInstalled(dbType: string): Promise<boolean> {
|
||||
|
|
@ -1216,12 +1216,12 @@ export async function restartDriverRuntime(runtimeId: string): Promise<void> {
|
|||
return invoke("restart_driver_runtime", { runtimeId });
|
||||
}
|
||||
|
||||
export async function installAgent(dbType: string): Promise<void> {
|
||||
return invoke("install_agent", { dbType });
|
||||
export async function installAgent(dbType: string, source?: UpdateDownloadSource): Promise<void> {
|
||||
return invoke("install_agent", { dbType, source });
|
||||
}
|
||||
|
||||
export async function upgradeAllAgents(): Promise<UpgradeAllAgentDriversResult> {
|
||||
return invoke("upgrade_all_agents");
|
||||
export async function upgradeAllAgents(source?: UpdateDownloadSource): Promise<UpgradeAllAgentDriversResult> {
|
||||
return invoke("upgrade_all_agents", { source });
|
||||
}
|
||||
|
||||
export async function checkAgentUpdateBlockers(dbTypes: string[]): Promise<AgentUpdateBlocker[]> {
|
||||
|
|
@ -1258,8 +1258,8 @@ export async function importAgentJar(dbType: string, path: string | File): Promi
|
|||
return invoke("import_agent_jar_cmd", { dbType, path });
|
||||
}
|
||||
|
||||
export async function reinstallJre(jreKey?: string): Promise<void> {
|
||||
return invoke("reinstall_jre", { jreKey });
|
||||
export async function reinstallJre(jreKey?: string, source?: UpdateDownloadSource): Promise<void> {
|
||||
return invoke("reinstall_jre", { jreKey, source });
|
||||
}
|
||||
|
||||
export async function uninstallJre(jreKey: string): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use crate::agent_catalog;
|
|||
use crate::agent_manager::{
|
||||
AgentDriverInfo, AgentManager, AgentRegistry, InstalledDriver, JavaRuntimeMode, DEFAULT_JRE_KEY,
|
||||
};
|
||||
use crate::DownloadSource;
|
||||
|
||||
/// Number of attempts to delete a JRE directory before giving up (Windows
|
||||
/// experiences transient `ERROR_ACCESS_DENIED` when java.exe is still mapped
|
||||
|
|
@ -131,8 +132,9 @@ fn replace_old_jre_dir(am: &AgentManager, path: &Path) -> Result<Option<PathBuf>
|
|||
const REGISTRY_PATH: &str = "https://github.com/t8y2/dbx/releases/download/agents-latest/agent-registry.json";
|
||||
const REGISTRY_R2_PATH: &str = "agents/agent-registry.json";
|
||||
|
||||
static REGISTRY_CACHE: std::sync::LazyLock<tokio::sync::Mutex<Option<(std::time::Instant, AgentRegistry)>>> =
|
||||
std::sync::LazyLock::new(|| tokio::sync::Mutex::new(None));
|
||||
static REGISTRY_CACHE: std::sync::LazyLock<
|
||||
tokio::sync::Mutex<std::collections::HashMap<DownloadSource, (std::time::Instant, AgentRegistry)>>,
|
||||
> = std::sync::LazyLock::new(|| tokio::sync::Mutex::new(std::collections::HashMap::new()));
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||
pub struct AgentProgressEvent {
|
||||
|
|
@ -323,9 +325,13 @@ pub fn install_local_agent(am: &AgentManager, db_type: &str, source: PathBuf) ->
|
|||
}
|
||||
|
||||
pub async fn fetch_registry() -> Result<AgentRegistry, String> {
|
||||
fetch_registry_from(DownloadSource::Official).await
|
||||
}
|
||||
|
||||
pub async fn fetch_registry_from(source: DownloadSource) -> Result<AgentRegistry, String> {
|
||||
{
|
||||
let cache = REGISTRY_CACHE.lock().await;
|
||||
if let Some((ts, registry)) = cache.as_ref() {
|
||||
if let Some((ts, registry)) = cache.get(&source) {
|
||||
if ts.elapsed() < std::time::Duration::from_secs(300) {
|
||||
return Ok(registry.clone());
|
||||
}
|
||||
|
|
@ -335,16 +341,40 @@ pub async fn fetch_registry() -> Result<AgentRegistry, String> {
|
|||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()
|
||||
.map_err(|err| format!("Failed to create HTTP client: {err}"))?;
|
||||
let resp = crate::race_download(&client, REGISTRY_PATH, REGISTRY_R2_PATH, "dbx-agent-manager")
|
||||
let resp = open_download_response(&client, source, REGISTRY_PATH, REGISTRY_R2_PATH, "dbx-agent-manager")
|
||||
.await
|
||||
.map_err(|err| format!("Failed to fetch agent registry: {err}"))?;
|
||||
let registry: AgentRegistry = resp.json().await.map_err(|err| format!("Failed to parse registry: {err}"))?;
|
||||
*REGISTRY_CACHE.lock().await = Some((std::time::Instant::now(), registry.clone()));
|
||||
REGISTRY_CACHE.lock().await.insert(source, (std::time::Instant::now(), registry.clone()));
|
||||
Ok(registry)
|
||||
}
|
||||
|
||||
async fn open_download_response(
|
||||
client: &reqwest::Client,
|
||||
source: DownloadSource,
|
||||
github_url: &str,
|
||||
r2_path: &str,
|
||||
user_agent: &str,
|
||||
) -> Result<reqwest::Response, String> {
|
||||
let mut errors = Vec::new();
|
||||
for url in source.download_candidate_urls(github_url, r2_path)? {
|
||||
match client
|
||||
.get(&url)
|
||||
.header(reqwest::header::USER_AGENT, user_agent)
|
||||
.header(reqwest::header::ACCEPT_ENCODING, "identity")
|
||||
.send()
|
||||
.await
|
||||
.and_then(|response| response.error_for_status())
|
||||
{
|
||||
Ok(response) => return Ok(response),
|
||||
Err(error) => errors.push(format!("{url}: {error}")),
|
||||
}
|
||||
}
|
||||
Err(errors.join("; "))
|
||||
}
|
||||
|
||||
pub async fn invalidate_registry_cache() {
|
||||
*REGISTRY_CACHE.lock().await = None;
|
||||
REGISTRY_CACHE.lock().await.clear();
|
||||
}
|
||||
|
||||
pub async fn install_agent_driver(
|
||||
|
|
@ -352,14 +382,31 @@ pub async fn install_agent_driver(
|
|||
db_type: &str,
|
||||
progress: impl Fn(AgentProgressEvent),
|
||||
) -> Result<(), String> {
|
||||
install_agent_driver_with_batch(am, db_type, &progress, None, None).await
|
||||
install_agent_driver_from(am, db_type, DownloadSource::Official, progress).await
|
||||
}
|
||||
|
||||
pub async fn install_agent_driver_from(
|
||||
am: &AgentManager,
|
||||
db_type: &str,
|
||||
source: DownloadSource,
|
||||
progress: impl Fn(AgentProgressEvent),
|
||||
) -> Result<(), String> {
|
||||
install_agent_driver_with_batch(am, db_type, source, &progress, None, None).await
|
||||
}
|
||||
|
||||
pub async fn upgrade_all_agent_drivers(
|
||||
am: &AgentManager,
|
||||
progress: impl Fn(AgentProgressEvent),
|
||||
) -> Result<UpgradeAllAgentDriversResult, String> {
|
||||
let registry = fetch_registry().await?;
|
||||
upgrade_all_agent_drivers_from(am, DownloadSource::Official, progress).await
|
||||
}
|
||||
|
||||
pub async fn upgrade_all_agent_drivers_from(
|
||||
am: &AgentManager,
|
||||
source: DownloadSource,
|
||||
progress: impl Fn(AgentProgressEvent),
|
||||
) -> Result<UpgradeAllAgentDriversResult, String> {
|
||||
let registry = fetch_registry_from(source).await?;
|
||||
let agents = build_agent_list(am, Some(®istry));
|
||||
let updatable: Vec<&AgentDriverInfo> = agents.iter().filter(|agent| agent.update_available).collect();
|
||||
let total = updatable.len() as u32;
|
||||
|
|
@ -369,6 +416,7 @@ pub async fn upgrade_all_agent_drivers(
|
|||
match install_agent_driver_from_registry(
|
||||
am,
|
||||
®istry,
|
||||
source,
|
||||
&agent.db_type,
|
||||
&progress,
|
||||
Some((index + 1) as u32),
|
||||
|
|
@ -439,7 +487,16 @@ pub async fn reinstall_agent_jre(
|
|||
jre_key: &str,
|
||||
progress: impl Fn(AgentProgressEvent),
|
||||
) -> Result<(), String> {
|
||||
let registry = fetch_registry().await?;
|
||||
reinstall_agent_jre_from(am, jre_key, DownloadSource::Official, progress).await
|
||||
}
|
||||
|
||||
pub async fn reinstall_agent_jre_from(
|
||||
am: &AgentManager,
|
||||
jre_key: &str,
|
||||
source: DownloadSource,
|
||||
progress: impl Fn(AgentProgressEvent),
|
||||
) -> Result<(), String> {
|
||||
let registry = fetch_registry_from(source).await?;
|
||||
let jre_info = registry.resolve_jre(jre_key).ok_or_else(|| format!("No JRE definition for version: {jre_key}"))?;
|
||||
let platform = AgentManager::current_platform();
|
||||
let platform_jre = jre_info
|
||||
|
|
@ -451,6 +508,7 @@ pub async fn reinstall_agent_jre(
|
|||
am,
|
||||
&progress,
|
||||
"jre",
|
||||
source,
|
||||
&platform_jre.url,
|
||||
&r2_path_with_cache_buster(&github_url_to_r2_path(&platform_jre.url, "jre"), &jre_info.version),
|
||||
&jre_archive,
|
||||
|
|
@ -497,19 +555,23 @@ pub fn import_agents_from_zip(
|
|||
async fn install_agent_driver_with_batch(
|
||||
am: &AgentManager,
|
||||
db_type: &str,
|
||||
source: DownloadSource,
|
||||
progress: &impl Fn(AgentProgressEvent),
|
||||
current: Option<u32>,
|
||||
total_drivers: Option<u32>,
|
||||
) -> Result<(), String> {
|
||||
match fetch_registry().await {
|
||||
match fetch_registry_from(source).await {
|
||||
Ok(registry) => {
|
||||
match install_agent_driver_from_registry(am, ®istry, db_type, progress, current, total_drivers).await {
|
||||
match install_agent_driver_from_registry(am, ®istry, source, db_type, progress, current, total_drivers)
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(()),
|
||||
Err(registry_err) => {
|
||||
if let Some(local_jar) = find_local_agent_jar(db_type) {
|
||||
install_local_agent_with_registry_jre(
|
||||
am,
|
||||
®istry,
|
||||
source,
|
||||
db_type,
|
||||
local_jar,
|
||||
progress,
|
||||
|
|
@ -538,6 +600,7 @@ async fn install_agent_driver_with_batch(
|
|||
async fn ensure_jre_from_registry(
|
||||
am: &AgentManager,
|
||||
registry: &AgentRegistry,
|
||||
source: DownloadSource,
|
||||
jre_key: &str,
|
||||
db_type: &str,
|
||||
progress: &impl Fn(AgentProgressEvent),
|
||||
|
|
@ -560,6 +623,7 @@ async fn ensure_jre_from_registry(
|
|||
am,
|
||||
progress,
|
||||
"jre",
|
||||
source,
|
||||
&platform_jre.url,
|
||||
&r2_path_with_cache_buster(&github_url_to_r2_path(&platform_jre.url, "jre"), &jre_info.version),
|
||||
&jre_archive,
|
||||
|
|
@ -584,6 +648,7 @@ async fn ensure_jre_from_registry(
|
|||
async fn install_local_agent_with_registry_jre(
|
||||
am: &AgentManager,
|
||||
registry: &AgentRegistry,
|
||||
source: DownloadSource,
|
||||
db_type: &str,
|
||||
local_jar: PathBuf,
|
||||
progress: &impl Fn(AgentProgressEvent),
|
||||
|
|
@ -592,7 +657,7 @@ async fn install_local_agent_with_registry_jre(
|
|||
) -> Result<(), String> {
|
||||
let jre_key = DEFAULT_JRE_KEY;
|
||||
if jre_needs_install(am, registry, jre_key) {
|
||||
ensure_jre_from_registry(am, registry, jre_key, db_type, progress, current, total_drivers).await?;
|
||||
ensure_jre_from_registry(am, registry, source, jre_key, db_type, progress, current, total_drivers).await?;
|
||||
}
|
||||
install_local_agent(am, db_type, local_jar)?;
|
||||
if let Some(jre_info) = registry.resolve_jre(jre_key) {
|
||||
|
|
@ -608,6 +673,7 @@ async fn install_local_agent_with_registry_jre(
|
|||
async fn install_agent_driver_from_registry(
|
||||
am: &AgentManager,
|
||||
registry: &AgentRegistry,
|
||||
source: DownloadSource,
|
||||
db_type: &str,
|
||||
progress: &impl Fn(AgentProgressEvent),
|
||||
current: Option<u32>,
|
||||
|
|
@ -615,8 +681,17 @@ async fn install_agent_driver_from_registry(
|
|||
) -> Result<(), String> {
|
||||
let Some(driver) = agent_registry_driver(registry, db_type) else {
|
||||
if let Some(local_jar) = find_local_agent_jar(db_type) {
|
||||
install_local_agent_with_registry_jre(am, registry, db_type, local_jar, progress, current, total_drivers)
|
||||
.await?;
|
||||
install_local_agent_with_registry_jre(
|
||||
am,
|
||||
registry,
|
||||
source,
|
||||
db_type,
|
||||
local_jar,
|
||||
progress,
|
||||
current,
|
||||
total_drivers,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
return Err(format!("Unknown driver type: {db_type}"));
|
||||
|
|
@ -628,7 +703,7 @@ async fn install_agent_driver_from_registry(
|
|||
let needs_jre = requires_java_runtime && jre_needs_install(am, registry, jre_key);
|
||||
|
||||
if needs_jre {
|
||||
ensure_jre_from_registry(am, registry, jre_key, db_type, progress, current, total_drivers).await?;
|
||||
ensure_jre_from_registry(am, registry, source, jre_key, db_type, progress, current, total_drivers).await?;
|
||||
}
|
||||
|
||||
let (artifact, target_path, is_native_artifact) = if let Some(native) = native_artifact {
|
||||
|
|
@ -650,6 +725,7 @@ async fn install_agent_driver_from_registry(
|
|||
am,
|
||||
progress,
|
||||
"driver",
|
||||
source,
|
||||
&artifact.url,
|
||||
&r2_path_with_cache_buster(&github_url_to_r2_path(&artifact.url, "driver"), &driver.version),
|
||||
&target_path,
|
||||
|
|
@ -706,6 +782,7 @@ async fn download_with_progress(
|
|||
am: &AgentManager,
|
||||
progress: &impl Fn(AgentProgressEvent),
|
||||
step: &str,
|
||||
source: DownloadSource,
|
||||
url: &str,
|
||||
r2_path: &str,
|
||||
dest: &std::path::Path,
|
||||
|
|
@ -764,6 +841,7 @@ async fn download_with_progress(
|
|||
|
||||
let (mut resp, resumed, source_url) = match open_agent_download_response(
|
||||
&client,
|
||||
source,
|
||||
url,
|
||||
r2_path,
|
||||
"dbx-agent-manager",
|
||||
|
|
@ -847,6 +925,7 @@ async fn download_with_progress(
|
|||
|
||||
async fn open_agent_download_response(
|
||||
client: &reqwest::Client,
|
||||
source: DownloadSource,
|
||||
github_url: &str,
|
||||
r2_path: &str,
|
||||
user_agent: &str,
|
||||
|
|
@ -855,7 +934,7 @@ async fn open_agent_download_response(
|
|||
resume_source: Option<&str>,
|
||||
) -> Result<(reqwest::Response, bool, String), String> {
|
||||
let mut errors = Vec::new();
|
||||
for candidate_url in crate::download_candidate_urls(github_url, r2_path) {
|
||||
for candidate_url in source.download_candidate_urls(github_url, r2_path)? {
|
||||
if resume_from > 0 && resume_source.is_some_and(|source| source != candidate_url) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1448,7 +1527,17 @@ mod agent_registry_install_tests {
|
|||
write_cached_driver_download(&manager, db_type, version, native_url, &native_path, native_bytes);
|
||||
let progress = |_| {};
|
||||
|
||||
install_agent_driver_from_registry(&manager, ®istry, db_type, &progress, None, None).await.unwrap();
|
||||
install_agent_driver_from_registry(
|
||||
&manager,
|
||||
®istry,
|
||||
DownloadSource::Official,
|
||||
db_type,
|
||||
&progress,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(std::fs::read(&native_path).unwrap(), native_bytes);
|
||||
assert!(!cache_path.exists());
|
||||
|
|
@ -1474,8 +1563,17 @@ mod agent_registry_install_tests {
|
|||
.unwrap();
|
||||
let progress = |_| {};
|
||||
|
||||
let err =
|
||||
install_agent_driver_from_registry(&manager, ®istry, db_type, &progress, None, None).await.unwrap_err();
|
||||
let err = install_agent_driver_from_registry(
|
||||
&manager,
|
||||
®istry,
|
||||
DownloadSource::Official,
|
||||
db_type,
|
||||
&progress,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(err.contains("invalid or corrupt"));
|
||||
assert!(cache_path.exists());
|
||||
|
|
|
|||
|
|
@ -66,6 +66,43 @@ pub mod update;
|
|||
pub mod xlsx_export;
|
||||
|
||||
pub const R2_CDN_BASE: &str = "https://dl.dbxio.com/";
|
||||
pub const GITHUB_RELEASE_DOWNLOAD_PREFIX: &str = "https://github.com/t8y2/dbx/releases/download/";
|
||||
pub const CNB_RELEASE_DOWNLOAD_PREFIX: &str = "https://cnb.cool/dbxio.com/dbx/-/releases/download/";
|
||||
pub const ATOMGIT_RELEASE_DOWNLOAD_PREFIX: &str = "https://atomgit.com/t8y2/dbx/releases/download/";
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, serde::Deserialize, PartialEq, Eq, Hash)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DownloadSource {
|
||||
#[default]
|
||||
Official,
|
||||
Cnb,
|
||||
Atomgit,
|
||||
}
|
||||
|
||||
impl DownloadSource {
|
||||
pub fn download_candidate_urls(self, github_url: &str, r2_path: &str) -> Result<Vec<String>, String> {
|
||||
match self {
|
||||
Self::Official => Ok(download_candidate_urls(github_url, r2_path)),
|
||||
Self::Cnb => Ok(vec![
|
||||
rewrite_github_release_url(github_url, CNB_RELEASE_DOWNLOAD_PREFIX)?,
|
||||
format!("{R2_CDN_BASE}{r2_path}"),
|
||||
]),
|
||||
Self::Atomgit => Ok(vec![
|
||||
rewrite_github_release_url(github_url, ATOMGIT_RELEASE_DOWNLOAD_PREFIX)?,
|
||||
format!("{R2_CDN_BASE}{r2_path}"),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rewrite_github_release_url(url: &str, target_prefix: &str) -> Result<String, String> {
|
||||
if url.starts_with(target_prefix) {
|
||||
return Ok(url.to_string());
|
||||
}
|
||||
url.strip_prefix(GITHUB_RELEASE_DOWNLOAD_PREFIX)
|
||||
.map(|path| format!("{target_prefix}{path}"))
|
||||
.ok_or_else(|| format!("Unsupported DBX release download URL: {url}"))
|
||||
}
|
||||
|
||||
pub fn download_candidate_urls(github_url: &str, r2_path: &str) -> Vec<String> {
|
||||
vec![format!("{R2_CDN_BASE}{r2_path}"), github_url.to_string()]
|
||||
|
|
@ -109,7 +146,7 @@ pub async fn race_download(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::download_candidate_urls;
|
||||
use super::{download_candidate_urls, DownloadSource};
|
||||
|
||||
#[test]
|
||||
fn download_candidates_exclude_third_party_github_proxy() {
|
||||
|
|
@ -126,4 +163,23 @@ mod tests {
|
|||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mirror_download_candidates_rewrite_release_urls() {
|
||||
let github_url = "https://github.com/t8y2/dbx/releases/download/agents-latest/agent-registry.json";
|
||||
assert_eq!(
|
||||
DownloadSource::Cnb.download_candidate_urls(github_url, "agents/agent-registry.json").unwrap(),
|
||||
vec![
|
||||
"https://cnb.cool/dbxio.com/dbx/-/releases/download/agents-latest/agent-registry.json",
|
||||
"https://dl.dbxio.com/agents/agent-registry.json",
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
DownloadSource::Atomgit.download_candidate_urls(github_url, "agents/agent-registry.json").unwrap(),
|
||||
vec![
|
||||
"https://atomgit.com/t8y2/dbx/releases/download/agents-latest/agent-registry.json",
|
||||
"https://dl.dbxio.com/agents/agent-registry.json",
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue