mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 03:21:53 +00:00
fix(updates): guard portable builds from installer updates (#550)
This commit is contained in:
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import type { UpdateInfo } from "@/lib/api";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
import { canDownloadAndInstallUpdate } from "@/composables/useAppUpdater";
|
||||
|
||||
const open = defineModel<boolean>("open", { required: true });
|
||||
|
||||
@@ -98,6 +99,12 @@ watch(
|
||||
<code class="bg-muted px-1 py-0.5 rounded text-[11px]">docker compose pull && docker compose up -d</code>
|
||||
{{ t("updates.toUpdate") }}
|
||||
</p>
|
||||
<p
|
||||
v-if="isDesktop && updateInfo?.update_available && updateInfo.portable_mode"
|
||||
class="text-xs text-muted-foreground"
|
||||
>
|
||||
{{ t("updates.portableManualUpdate") }}
|
||||
</p>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button v-if="!isDownloadingUpdate && !updateReady" variant="outline" @click="open = false">{{
|
||||
@@ -105,7 +112,7 @@ watch(
|
||||
}}</Button>
|
||||
<template v-if="updateInfo?.update_available">
|
||||
<Button variant="outline" @click="emit('open-latest-release')">{{ t("updates.openRelease") }}</Button>
|
||||
<template v-if="isDesktop">
|
||||
<template v-if="canDownloadAndInstallUpdate(updateInfo, isDesktop)">
|
||||
<div v-if="updateReady" class="flex flex-col items-end gap-1">
|
||||
<Button @click="emit('restart')">{{ t("updates.restart") }}</Button>
|
||||
<span class="text-xs text-muted-foreground">{{ t("updates.reopenHint") }}</span>
|
||||
|
||||
@@ -8,6 +8,10 @@ export function shouldOpenUpdateDialog(options: { silent?: boolean }) {
|
||||
return options.silent !== true;
|
||||
}
|
||||
|
||||
export function canDownloadAndInstallUpdate(info: api.UpdateInfo | null, isDesktop: boolean) {
|
||||
return isDesktop && info?.update_available === true && info.portable_mode !== true;
|
||||
}
|
||||
|
||||
export async function resolveUpdaterProxy(): Promise<string | undefined> {
|
||||
if (!isTauriRuntime()) return undefined;
|
||||
try {
|
||||
@@ -80,6 +84,10 @@ export function useAppUpdater() {
|
||||
|
||||
async function downloadAndInstallUpdate() {
|
||||
if (!isTauriRuntime() || isDownloadingUpdate.value) return;
|
||||
if (!canDownloadAndInstallUpdate(updateInfo.value, true)) {
|
||||
openLatestRelease();
|
||||
return;
|
||||
}
|
||||
isDownloadingUpdate.value = true;
|
||||
downloadProgress.value = 0;
|
||||
try {
|
||||
|
||||
@@ -55,6 +55,8 @@ export default {
|
||||
"GitHub update checks are temporarily rate limited. You can still open the release page to check manually.",
|
||||
openRelease: "Open Release",
|
||||
downloadAndInstall: "Download & Install",
|
||||
portableManualUpdate:
|
||||
"Portable builds cannot use the in-app installer. Download the portable ZIP from the release page, then extract it over the current DBX folder to keep portable.dbx and data.",
|
||||
downloading: "Downloading {progress}%",
|
||||
downloadFailed: "Update download failed: {error}",
|
||||
restart: "Exit & Restart",
|
||||
|
||||
@@ -55,6 +55,8 @@ export default {
|
||||
"Las verificaciones de actualizaciones de GitHub están temporalmente limitadas. Puedes abrir la página de lanzamientos para verificar manualmente.",
|
||||
openRelease: "Abrir lanzamiento",
|
||||
downloadAndInstall: "Descargar e instalar",
|
||||
portableManualUpdate:
|
||||
"Las versiones portables no pueden usar el instalador integrado. Descarga el ZIP portable desde la página de lanzamiento y extráelo sobre la carpeta actual de DBX para conservar portable.dbx y data.",
|
||||
downloading: "Descargando {progress}%",
|
||||
downloadFailed: "Error al descargar la actualización: {error}",
|
||||
restart: "Salir y reiniciar",
|
||||
|
||||
@@ -54,6 +54,8 @@ export default {
|
||||
rateLimited: "GitHub 更新检查暂时触发频率限制。你仍然可以打开下载页手动查看。",
|
||||
openRelease: "打开下载页",
|
||||
downloadAndInstall: "下载并安装",
|
||||
portableManualUpdate:
|
||||
"便携版不能使用应用内安装器更新。请从 release 下载便携版 ZIP,并解压覆盖当前 DBX 目录,以保留 portable.dbx 和 data。",
|
||||
downloading: "下载中 {progress}%",
|
||||
downloadFailed: "更新下载失败:{error}",
|
||||
restart: "退出并重启",
|
||||
|
||||
@@ -908,6 +908,7 @@ export interface UpdateInfo {
|
||||
current_version: string;
|
||||
latest_version: string;
|
||||
update_available: boolean;
|
||||
portable_mode: boolean;
|
||||
release_name: string;
|
||||
release_url: string;
|
||||
release_notes: string;
|
||||
|
||||
@@ -35,6 +35,7 @@ pub struct UpdateInfo {
|
||||
pub current_version: String,
|
||||
pub latest_version: String,
|
||||
pub update_available: bool,
|
||||
pub portable_mode: bool,
|
||||
pub release_name: String,
|
||||
pub release_url: String,
|
||||
pub release_notes: String,
|
||||
@@ -227,6 +228,7 @@ pub fn build_update_info(release: TauriRelease, current_version: &str) -> Update
|
||||
|
||||
UpdateInfo {
|
||||
update_available: is_newer_version(&latest_version, current_version),
|
||||
portable_mode: false,
|
||||
current_version: current_version.to_string(),
|
||||
release_name,
|
||||
release_url,
|
||||
@@ -391,5 +393,6 @@ HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings
|
||||
assert_eq!(info.release_name, "DBX v0.5.3");
|
||||
assert_eq!(info.release_url, "https://github.com/t8y2/dbx/releases/tag/v0.5.3");
|
||||
assert_eq!(info.release_notes, "### 新功能\n\n真实发布说明");
|
||||
assert!(!info.portable_mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { canDownloadAndInstallUpdate } from "../../apps/desktop/src/composables/useAppUpdater.ts";
|
||||
import type { UpdateInfo } from "../../apps/desktop/src/lib/api.ts";
|
||||
|
||||
function updateInfo(overrides: Partial<UpdateInfo> = {}): UpdateInfo {
|
||||
return {
|
||||
current_version: "0.5.25",
|
||||
latest_version: "0.5.26",
|
||||
update_available: true,
|
||||
portable_mode: false,
|
||||
release_name: "DBX v0.5.26",
|
||||
release_url: "https://github.com/t8y2/dbx/releases/tag/v0.5.26",
|
||||
release_notes: "",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test("allows in-app update installation for installed desktop builds", () => {
|
||||
assert.equal(canDownloadAndInstallUpdate(updateInfo(), true), true);
|
||||
});
|
||||
|
||||
test("blocks in-app update installation for portable builds", () => {
|
||||
assert.equal(canDownloadAndInstallUpdate(updateInfo({ portable_mode: true }), true), false);
|
||||
});
|
||||
|
||||
test("blocks in-app update installation outside desktop runtime or without an update", () => {
|
||||
assert.equal(canDownloadAndInstallUpdate(updateInfo(), false), false);
|
||||
assert.equal(canDownloadAndInstallUpdate(updateInfo({ update_available: false }), true), false);
|
||||
assert.equal(canDownloadAndInstallUpdate(null, true), false);
|
||||
});
|
||||
@@ -4,7 +4,9 @@ pub use dbx_core::update::UpdateInfo;
|
||||
pub async fn check_for_updates() -> Result<UpdateInfo, String> {
|
||||
let release = dbx_core::update::fetch_latest_release().await?;
|
||||
let current_version = env!("CARGO_PKG_VERSION");
|
||||
Ok(dbx_core::update::build_update_info(release, current_version))
|
||||
let mut info = dbx_core::update::build_update_info(release, current_version);
|
||||
info.portable_mode = crate::data_dir::is_portable_mode();
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -10,7 +10,7 @@ pub fn resolve_data_dir(default_app_data_dir: PathBuf) -> PathBuf {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Some(exe_dir) = current_exe_dir() {
|
||||
if exe_dir.join(PORTABLE_MARKER).is_file() {
|
||||
if portable_marker_exists(&exe_dir) {
|
||||
return exe_dir.join("data");
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,26 @@ pub fn resolve_data_dir(default_app_data_dir: PathBuf) -> PathBuf {
|
||||
default_app_data_dir
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn is_portable_mode() -> bool {
|
||||
current_exe_dir().is_some_and(|dir| portable_marker_exists(&dir))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn is_portable_mode() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn current_exe_dir() -> Option<PathBuf> {
|
||||
std::env::current_exe().ok().and_then(|path| path.parent().map(std::path::Path::to_path_buf))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn portable_marker_exists(exe_dir: &std::path::Path) -> bool {
|
||||
exe_dir.join(PORTABLE_MARKER).is_file()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn resolve_data_dir_from_inputs(
|
||||
default_app_data_dir: PathBuf,
|
||||
@@ -40,11 +55,16 @@ fn resolve_data_dir_from_inputs(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn is_portable_mode_from_inputs(exe_dir: Option<PathBuf>, portable_marker_exists: bool) -> bool {
|
||||
exe_dir.is_some() && portable_marker_exists
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::resolve_data_dir_from_inputs;
|
||||
use super::{is_portable_mode_from_inputs, resolve_data_dir_from_inputs};
|
||||
|
||||
#[test]
|
||||
fn uses_portable_data_dir_when_marker_exists() {
|
||||
@@ -55,4 +75,13 @@ mod tests {
|
||||
|
||||
assert_eq!(data_dir, exe_dir.join("data"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_portable_mode_only_when_marker_exists_next_to_exe() {
|
||||
let exe_dir = PathBuf::from(r"D:\Apps\DBX");
|
||||
|
||||
assert!(is_portable_mode_from_inputs(Some(exe_dir), true));
|
||||
assert!(!is_portable_mode_from_inputs(Some(PathBuf::from(r"D:\Apps\DBX")), false));
|
||||
assert!(!is_portable_mode_from_inputs(None, true));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user