diff --git a/apps/desktop/src/components/config/DriverStoreDialog.vue b/apps/desktop/src/components/config/DriverStoreDialog.vue index cc5595c768..ff23745f35 100644 --- a/apps/desktop/src/components/config/DriverStoreDialog.vue +++ b/apps/desktop/src/components/config/DriverStoreDialog.vue @@ -1875,10 +1875,17 @@ watch(driverStoreTab, (tab) => { - + + + + + + {{ t("driverStore.localInstallHint") }} + + diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index e6f996f790..0e38b9aab5 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -6660,6 +6660,7 @@ export default { updating: "Updating", update: "Update", localInstall: "Local install", + localInstallHint: "For air-gapped machines: on another computer with internet access, download the official dbx-jdbc-plugin-*.zip from the DBX GitHub Releases page, transfer it here, and select it — no network access is needed on this machine.", runtimeDrivers: "Runtime", storageTab: "Storage", runtimeTitle: "Driver runtime", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index ec3eddbf41..4fdc97a691 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -6371,6 +6371,7 @@ export default withEnglishFallback({ updating: "Actualizando", update: "Actualizar", localInstall: "Instalación local", + localInstallHint: "Para entornos sin acceso a internet: en otro equipo con conexión, descarga el dbx-jdbc-plugin-*.zip oficial desde la página de Releases de DBX en GitHub, transfiérelo a esta máquina y selecciónalo; no se necesita acceso a la red en este equipo.", runtimeDrivers: "Runtime", storageTab: "Almacenamiento", runtimeTitle: "Runtime de drivers", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index 58c944c835..edcc29ff89 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -6371,6 +6371,8 @@ export default withEnglishFallback({ updating: "Aggiornamento in corso", update: "Aggiorna", localInstall: "Installazione locale", + localInstallHint: + "Per ambienti completamente isolati: su un altro computer con accesso a internet, scarica il pacchetto ufficiale dbx-jdbc-plugin-*.zip dalla pagina Releases di DBX su GitHub, trasferiscilo su questa macchina e selezionalo: non è richiesto alcun accesso alla rete su questa macchina.", runtimeDrivers: "Runtime", storageTab: "Archiviazione", runtimeTitle: "Runtime del driver", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 9c15d8dfea..879eb4811b 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -6424,6 +6424,7 @@ export default withEnglishFallback({ updating: "更新中", update: "更新", localInstall: "ローカルインストール", + localInstallHint: "完全に隔離されたネットワーク環境の場合:インターネットに接続できる別のコンピューターで DBX の GitHub Releases ページから公式の dbx-jdbc-plugin-*.zip をダウンロードし、このマシンに転送して選択してください。このマシンはネットワークに接続する必要はありません。", runtimeDrivers: "ランタイム", storageTab: "ストレージ", runtimeTitle: "ドライバーランタイム", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index 25b718a30f..9b33013f2f 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -6065,6 +6065,7 @@ export default withEnglishFallback({ updating: "업데이트 중", update: "업데이트", localInstall: "로컬 설치", + localInstallHint: "완전한 폐쇄망 환경의 경우: 인터넷에 연결된 다른 컴퓨터에서 DBX GitHub Releases 페이지의 공식 dbx-jdbc-plugin-*.zip을 다운로드한 뒤 이 기기로 옮겨 선택하세요. 이 기기는 네트워크에 연결할 필요가 없습니다.", runtimeDrivers: "런타임", storageTab: "저장 공간", runtimeTitle: "드라이버 런타임", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index 6782390264..1bff78c2bd 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -6373,6 +6373,7 @@ export default withEnglishFallback({ updating: "Atualizando", update: "Atualizar", localInstall: "Instalação local", + localInstallHint: "Para ambientes sem acesso à internet: em outro computador com internet, baixe o dbx-jdbc-plugin-*.zip oficial na página de Releases do DBX no GitHub, transfira-o para esta máquina e selecione-o — nenhum acesso à rede é necessário aqui.", runtimeDrivers: "Runtime", storageTab: "Armazenamento", runtimeTitle: "Runtime do driver", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index bb81f0bd65..d80fb88d4b 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -6645,6 +6645,7 @@ export default withEnglishFallback({ updating: "更新中", update: "更新", localInstall: "本地安装", + localInstallHint: "适用于完全内网环境:在另一台可联网的电脑上,从 DBX 的 GitHub Releases 页面下载官方 dbx-jdbc-plugin-*.zip 安装包,拷贝到本机后在此选择该文件——本机无需联网即可完成安装。", runtimeDrivers: "运行时", storageTab: "存储", runtimeTitle: "驱动运行时", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index b9b5115fa9..724c2b57f5 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -5683,6 +5683,7 @@ export default withEnglishFallback({ updating: "更新中", update: "更新", localInstall: "本機安裝", + localInstallHint: "適用於完全內網環境:在另一台可連網的電腦上,從 DBX 的 GitHub Releases 頁面下載官方 dbx-jdbc-plugin-*.zip 安裝包,傳輸到本機後在此選取該檔案——本機不需要連網即可完成安裝。", runtimeDrivers: "執行環境", storageTab: "儲存", runtimeTitle: "驅動程式執行環境", diff --git a/crates/dbx-core/src/jdbc.rs b/crates/dbx-core/src/jdbc.rs index f4c27c885c..020236b585 100644 --- a/crates/dbx-core/src/jdbc.rs +++ b/crates/dbx-core/src/jdbc.rs @@ -374,7 +374,9 @@ pub async fn install_jdbc_plugin_from_file(plugins_root: &Path, file_path: &str) }) .await .map_err(|err| err.to_string())??; - jdbc_plugin_status_from_dir(&status_dir).await + // Local install must stay fully offline: build status from the local + // manifest only, do not check for a newer release over the network. + jdbc_plugin_status_from_dir_local(&status_dir) } pub fn uninstall_jdbc_plugin(plugins_root: &Path) -> Result { @@ -390,14 +392,20 @@ pub fn uninstall_jdbc_plugin(plugins_root: &Path) -> Result Result { let manifest_path = plugin_dir.join("manifest.json"); let manifest = match std::fs::read_to_string(&manifest_path) { Ok(raw) => Some(serde_json::from_str::(&raw).map_err(|err| err.to_string())?), Err(err) if err.kind() == std::io::ErrorKind::NotFound => None, Err(err) => return Err(err.to_string()), }; - Ok(build_plugin_status(&manifest, None, &plugin_dir)) + Ok(build_plugin_status(&manifest, None, plugin_dir)) } // ---- System Fonts ---- @@ -451,13 +459,25 @@ fn jdbc_maven_resolver_executable(plugin_dir: &Path) -> PathBuf { } async fn jdbc_plugin_status_from_dir(plugin_dir: &Path) -> Result { + jdbc_plugin_status_from_dir_after(plugin_dir, latest_jdbc_plugin()).await +} + +/// Waits on `latest` before reading the manifest, so a slow/offline network +/// check can never race ahead of a concurrent local install and report a +/// stale pre-install snapshot back to the caller. `latest` is a parameter +/// (rather than calling `latest_jdbc_plugin()` directly) so tests can +/// simulate a delayed network response without a real network call. +async fn jdbc_plugin_status_from_dir_after( + plugin_dir: &Path, + latest: impl std::future::Future>, +) -> Result { + let latest = latest.await; let manifest_path = plugin_dir.join("manifest.json"); let manifest = match std::fs::read_to_string(&manifest_path) { Ok(raw) => Some(serde_json::from_str::(&raw).map_err(|err| err.to_string())?), Err(err) if err.kind() == std::io::ErrorKind::NotFound => None, Err(err) => return Err(err.to_string()), }; - let latest = latest_jdbc_plugin().await; Ok(build_plugin_status(&manifest, latest.as_ref(), plugin_dir)) } @@ -553,7 +573,10 @@ fn install_jdbc_plugin_zip(bytes: &[u8], plugin_dir: &Path) -> Result<(), String if !temp_dir.join("manifest.json").exists() { let _ = std::fs::remove_dir_all(&temp_dir); - return Err("Downloaded JDBC plugin package is missing manifest.json".to_string()); + return Err(format!( + "This ZIP is not a valid DBX JDBC plugin package (missing manifest.json). \ + The correct package is available at: {JDBC_PLUGIN_DOWNLOAD_URL}" + )); } let manifest_path = temp_dir.join("manifest.json"); let manifest = std::fs::read_to_string(&manifest_path) @@ -1447,4 +1470,39 @@ mod tests { let _ = std::fs::remove_dir_all(root); } + + #[tokio::test] + async fn delayed_network_status_check_does_not_revert_a_concurrent_local_install() { + let root = std::env::temp_dir().join(format!("dbx-jdbc-status-race-test-{}", uuid::Uuid::new_v4())); + let plugin_dir = root.join("jdbc"); + std::fs::create_dir_all(&plugin_dir).unwrap(); + + // Mirrors DriverStoreDialog's mount-time status check: it starts + // before the local install and only finishes its (simulated) + // network wait after the local install has already written the + // new manifest to disk. + let status_plugin_dir = plugin_dir.clone(); + let status_task = tokio::spawn(async move { + jdbc_plugin_status_from_dir_after(&status_plugin_dir, async { + tokio::time::sleep(std::time::Duration::from_millis(80)).await; + None + }) + .await + }); + + tokio::time::sleep(std::time::Duration::from_millis(20)).await; + // Local install completes fully offline while the status check + // above is still waiting on its simulated network round-trip. + std::fs::write( + plugin_dir.join("manifest.json"), + r#"{"id":"jdbc","name":"DBX JDBC Plugin","version":"0.1.30","protocol_version":1,"executable":"bin/dbx-jdbc-plugin","drivers":[]}"#, + ) + .unwrap(); + + let status = status_task.await.unwrap().unwrap(); + assert!(status.installed, "stale pre-install snapshot must not overwrite the completed local install"); + assert_eq!(status.version.as_deref(), Some("0.1.30")); + + let _ = std::fs::remove_dir_all(root); + } } diff --git a/docs/content/docs/driver-management.mdx b/docs/content/docs/driver-management.mdx index 1699266879..8bc4ba4c2f 100644 --- a/docs/content/docs/driver-management.mdx +++ b/docs/content/docs/driver-management.mdx @@ -93,6 +93,12 @@ Download `dbx-agent-kingbase--windows-x64.zip` for Windows x64 or `dbx- The ZIP contains the version metadata and native agent for that platform. DBX installs it as `%USERPROFILE%\.dbx\agents\drivers\kingbase\agent.exe`, without requiring users to unblock or manually copy the executable. +### Importing the JDBC plugin offline + +Generic JDBC connections (custom JDBC URL + imported driver JAR) run through a separate **DBX JDBC plugin**, released as its own `dbx-jdbc-plugin-*.zip` asset — not bundled into any per-database Agent ZIP. Install it with **Local install** in the **JDBC Drivers** tab (not **Import offline package** in Built-in Drivers, which is for Agent ZIPs). + +Importing the actual driver JAR (e.g. an Oracle `ojdbc*.jar`) is always local and does not require this plugin package or any network access — use **Import JAR** in the same tab, regardless of whether the JDBC plugin itself is installed. + ## Plugin Updates For databases supported by the [JDBC Plugin](/en/docs/plugins), DBX shows update notices when a new plugin version is available. Plugin updates follow the same install flow as built-in agent drivers.