perf: race GitHub proxies concurrently for driver downloads and registry fetch

Replace sequential proxy fallback (up to 30s worst-case) with concurrent
racing via futures::select_ok, returning the first successful response.
Add list_installed_agents_local command for instant UI rendering with
async registry update. Fix missing useToast import in RedisValueViewer.
This commit is contained in:
t8y2
2026-05-13 14:18:28 +08:00
parent 321ebc2636
commit fa0dfc9383
7 changed files with 121 additions and 128 deletions
+58 -81
View File
@@ -11,38 +11,34 @@ const REGISTRY_PATH: &str = "https://github.com/t8y2/dbx-agents/releases/latest/
static REGISTRY_CACHE: std::sync::LazyLock<Mutex<Option<(std::time::Instant, AgentRegistry)>>> =
std::sync::LazyLock::new(|| Mutex::new(None));
#[tauri::command]
pub async fn list_installed_agents(state: State<'_, Arc<AppState>>) -> Result<Vec<AgentDriverInfo>, String> {
let am = &state.agent_manager;
const AGENT_TYPES: &[(&str, &str)] = &[
("dameng", "达梦 DM8"),
("kingbase", "人大金仓 KingbaseES"),
("vastbase", "Vastbase"),
("goldendb", "GoldenDB"),
("oracle", "Oracle"),
("h2", "H2"),
("snowflake", "Snowflake"),
("trino", "Trino (Presto)"),
("hive", "Apache Hive"),
("db2", "IBM DB2"),
("informix", "IBM Informix"),
("neo4j", "Neo4j"),
("cassandra", "Apache Cassandra"),
("bigquery", "Google BigQuery"),
("kylin", "Apache Kylin"),
("sundb", "SunDB"),
("gaussdb", "GaussDB"),
];
fn build_agent_list(am: &AgentManager, registry: Option<&AgentRegistry>) -> Vec<AgentDriverInfo> {
let local_state = am.load_state();
let registry = fetch_registry().await.ok();
let agent_types = [
("dameng", "达梦 DM8"),
("kingbase", "人大金仓 KingbaseES"),
("vastbase", "Vastbase"),
("goldendb", "GoldenDB"),
("oracle", "Oracle"),
("h2", "H2"),
("snowflake", "Snowflake"),
("trino", "Trino (Presto)"),
("hive", "Apache Hive"),
("db2", "IBM DB2"),
("informix", "IBM Informix"),
("neo4j", "Neo4j"),
("cassandra", "Apache Cassandra"),
("bigquery", "Google BigQuery"),
("kylin", "Apache Kylin"),
("sundb", "SunDB"),
("gaussdb", "GaussDB"),
];
Ok(agent_types
AGENT_TYPES
.iter()
.map(|(key, label)| {
let installed = am.is_driver_installed(key);
let local = local_state.installed_drivers.get(*key);
let remote = registry.as_ref().and_then(|r| r.drivers.get(*key));
let remote = registry.and_then(|r| r.drivers.get(*key));
AgentDriverInfo {
db_type: key.to_string(),
label: label.to_string(),
@@ -56,7 +52,18 @@ pub async fn list_installed_agents(state: State<'_, Arc<AppState>>) -> Result<Ve
},
}
})
.collect())
.collect()
}
#[tauri::command]
pub async fn list_installed_agents_local(state: State<'_, Arc<AppState>>) -> Result<Vec<AgentDriverInfo>, String> {
Ok(build_agent_list(&state.agent_manager, None))
}
#[tauri::command]
pub async fn list_installed_agents(state: State<'_, Arc<AppState>>) -> Result<Vec<AgentDriverInfo>, String> {
let registry = fetch_registry().await.ok();
Ok(build_agent_list(&state.agent_manager, registry.as_ref()))
}
#[tauri::command]
@@ -175,27 +182,12 @@ async fn fetch_registry() -> Result<AgentRegistry, String> {
.timeout(std::time::Duration::from_secs(10))
.build()
.map_err(|e| format!("Failed to create HTTP client: {e}"))?;
let mut last_err = String::new();
for proxy in dbx_core::GITHUB_PROXIES {
let url = format!("{proxy}{REGISTRY_PATH}");
match client
.get(&url)
.header(reqwest::header::USER_AGENT, "dbx-agent-manager")
.send()
.await
.and_then(|r| r.error_for_status())
{
Ok(resp) => {
let reg: AgentRegistry = resp.json().await.map_err(|e| format!("Failed to parse registry: {e}"))?;
*REGISTRY_CACHE.lock().await = Some((std::time::Instant::now(), reg.clone()));
return Ok(reg);
}
Err(e) => {
last_err = format!("{e}");
}
}
}
Err(format!("Failed to fetch agent registry: {last_err}"))
let resp = dbx_core::race_github_proxies(&client, REGISTRY_PATH, "dbx-agent-manager")
.await
.map_err(|e| format!("Failed to fetch agent registry: {e}"))?;
let reg: AgentRegistry = resp.json().await.map_err(|e| format!("Failed to parse registry: {e}"))?;
*REGISTRY_CACHE.lock().await = Some((std::time::Instant::now(), reg.clone()));
Ok(reg)
}
async fn download_with_progress(
@@ -212,39 +204,24 @@ async fn download_with_progress(
.timeout(std::time::Duration::from_secs(300))
.build()
.map_err(|e| format!("Failed to create HTTP client: {e}"))?;
let mut last_err = String::new();
for proxy in dbx_core::GITHUB_PROXIES {
let full_url = format!("{proxy}{url}");
log::info!("[agent] downloading from {full_url}");
match client
.get(&full_url)
.header(reqwest::header::USER_AGENT, "dbx-agent-manager")
.send()
.await
.and_then(|r| r.error_for_status())
{
Ok(resp) => {
let content_length = resp.content_length().unwrap_or(total_size);
let mut file = std::fs::File::create(dest).map_err(|e| format!("Failed to create file: {e}"))?;
let mut downloaded: u64 = 0;
let mut bytes = resp;
while let Some(chunk) = bytes.chunk().await.map_err(|e| format!("Download stream error: {e}"))? {
std::io::Write::write_all(&mut file, &chunk).map_err(|e| format!("Failed to write chunk: {e}"))?;
downloaded += chunk.len() as u64;
let _ = app.emit(
"agent-install-progress",
serde_json::json!({ "step": step, "downloaded": downloaded, "total": content_length }),
);
}
return Ok(());
}
Err(e) => {
last_err = format!("{e}");
log::warn!("[agent] download failed from {full_url}: {last_err}");
}
}
let resp = dbx_core::race_github_proxies(&client, url, "dbx-agent-manager")
.await
.map_err(|e| format!("Failed to download {url}: {e}"))?;
let content_length = resp.content_length().unwrap_or(total_size);
let mut file = std::fs::File::create(dest).map_err(|e| format!("Failed to create file: {e}"))?;
let mut downloaded: u64 = 0;
let mut bytes = resp;
while let Some(chunk) = bytes.chunk().await.map_err(|e| format!("Download stream error: {e}"))? {
std::io::Write::write_all(&mut file, &chunk).map_err(|e| format!("Failed to write chunk: {e}"))?;
downloaded += chunk.len() as u64;
let _ = app.emit(
"agent-install-progress",
serde_json::json!({ "step": step, "downloaded": downloaded, "total": content_length }),
);
}
Err(format!("Failed to download {url}: {last_err}"))
Ok(())
}
fn extract_archive(archive: &std::path::Path, dest: &std::path::Path) -> Result<(), String> {
+6 -23
View File
@@ -6,13 +6,7 @@ use serde::Serialize;
use super::connection::AppState;
const JDBC_PLUGIN_DOWNLOAD_VERSION: &str = "0.1.0";
fn jdbc_plugin_download_urls() -> Vec<String> {
let asset = format!("dbx-jdbc-plugin-{JDBC_PLUGIN_DOWNLOAD_VERSION}.zip");
let latest_url = format!("https://github.com/t8y2/dbx/releases/latest/download/{asset}");
vec![format!("https://update.hwdns.net/{latest_url}"), format!("https://gh-proxy.org/{latest_url}"), latest_url]
}
const JDBC_PLUGIN_DOWNLOAD_URL: &str = "https://github.com/t8y2/dbx/releases/latest/download/dbx-jdbc-plugin-0.1.0.zip";
#[tauri::command]
pub async fn list_plugins(state: State<'_, Arc<AppState>>) -> Result<Vec<InstalledPlugin>, String> {
@@ -146,24 +140,13 @@ async fn download_jdbc_plugin_zip() -> Result<Vec<u8>, String> {
.timeout(std::time::Duration::from_secs(60))
.build()
.map_err(|err| err.to_string())?;
let mut last_error = String::new();
for url in jdbc_plugin_download_urls() {
match client.get(&url).header(reqwest::header::USER_AGENT, "dbx-jdbc-plugin-installer").send().await {
Ok(response) if response.status().is_success() => {
let bytes = response.bytes().await.map_err(|err| err.to_string())?;
return Ok(bytes.to_vec());
}
Ok(response) => {
last_error = format!("{url} returned {}", response.status());
}
Err(err) => {
last_error = format!("{url}: {err}");
}
}
}
let resp = dbx_core::race_github_proxies(&client, JDBC_PLUGIN_DOWNLOAD_URL, "dbx-jdbc-plugin-installer")
.await
.map_err(|err| format!("Failed to download JDBC plugin: {err}"))?;
Err(format!("Failed to download JDBC plugin: {last_error}"))
let bytes = resp.bytes().await.map_err(|err| err.to_string())?;
Ok(bytes.to_vec())
}
fn install_jdbc_plugin_zip(bytes: &[u8], plugin_dir: &std::path::Path) -> Result<(), String> {
+1
View File
@@ -145,6 +145,7 @@ pub fn run() {
commands::database_export::export_database_sql,
commands::database_export::cancel_database_export,
commands::agents::list_installed_agents,
commands::agents::list_installed_agents_local,
commands::agents::install_agent,
commands::agents::uninstall_agent,
commands::agents::check_jre_installed,