mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-10-21 23:01:26 +02:00
feat: update notice for all
This commit is contained in:
1 parent
262deda120
commit
54184bda90
15 files changed
+131
-50
No files matched your search
@@ -124,6 +124,23 @@ pub struct AppUpdateInfo {
|
||||
/// cross-checked in addition to SHA256SUMS.txt when present.
|
||||
#[serde(default)]
|
||||
pub asset_digest: Option<String>,
|
||||
/// True when the app downloads and installs this update without asking.
|
||||
/// False for a manual update and when the person turned automatic updates
|
||||
/// off: the UI then offers the update and waits.
|
||||
#[serde(default)]
|
||||
pub auto_install: bool,
|
||||
}
|
||||
|
||||
impl AppUpdateInfo {
|
||||
/// What this install may do with the update. A portable copy is replaced
|
||||
/// by hand; with automatic updates off, the person starts the install.
|
||||
fn with_install_policy(mut self, portable: bool, auto_updates_disabled: bool) -> Self {
|
||||
if portable {
|
||||
self.manual_update_required = true;
|
||||
}
|
||||
self.auto_install = !self.manual_update_required && !auto_updates_disabled;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppAutoUpdater {
|
||||
@@ -309,6 +326,7 @@ impl AppAutoUpdater {
|
||||
repo_update,
|
||||
checksums_url,
|
||||
asset_digest,
|
||||
auto_install: !manual_update_required,
|
||||
};
|
||||
let result = format!(
|
||||
"available asset={asset} manual_update_required={manual_update_required} repo_update={repo_update}"
|
||||
@@ -333,6 +351,7 @@ impl AppAutoUpdater {
|
||||
repo_update: false,
|
||||
checksums_url,
|
||||
asset_digest,
|
||||
auto_install: true,
|
||||
};
|
||||
(
|
||||
Some(update_info),
|
||||
@@ -2042,27 +2061,25 @@ pub async fn check_for_app_updates() -> Result<Option<AppUpdateInfo>, String> {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if crate::app_dirs::is_portable() {
|
||||
log::debug!("App update check skipped reason=portable");
|
||||
return Ok(None);
|
||||
}
|
||||
// The disable_auto_updates setting controls app self-updates only
|
||||
let disabled = crate::settings_manager::SettingsManager::instance()
|
||||
.load_settings()
|
||||
.map(|s| s.disable_auto_updates)
|
||||
.unwrap_or(false);
|
||||
if disabled {
|
||||
log::debug!("App update check skipped reason=disabled_by_setting");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let updater = AppAutoUpdater::instance();
|
||||
updater
|
||||
.check_for_updates()
|
||||
.await
|
||||
.map(with_install_policy)
|
||||
.map_err(|e| format!("Failed to check for app updates: {e}"))
|
||||
}
|
||||
|
||||
/// Every install hears about a new version, also a portable one and one with
|
||||
/// automatic updates off; only how it installs differs.
|
||||
fn with_install_policy(update: Option<AppUpdateInfo>) -> Option<AppUpdateInfo> {
|
||||
// The disable_auto_updates setting controls app self-updates only.
|
||||
let auto_updates_disabled = crate::settings_manager::SettingsManager::instance()
|
||||
.load_settings()
|
||||
.map(|s| s.disable_auto_updates)
|
||||
.unwrap_or(false);
|
||||
update.map(|info| info.with_install_policy(crate::app_dirs::is_portable(), auto_updates_disabled))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn download_and_prepare_app_update(
|
||||
app_handle: tauri::AppHandle,
|
||||
@@ -2107,6 +2124,7 @@ pub async fn check_for_app_updates_manual() -> Result<Option<AppUpdateInfo>, Str
|
||||
updater
|
||||
.check(true)
|
||||
.await
|
||||
.map(with_install_policy)
|
||||
.map_err(|e| format!("Failed to check for app updates: {e}"))
|
||||
}
|
||||
|
||||
@@ -2597,4 +2615,51 @@ mod tests {
|
||||
let _deb = AppAutoUpdater::is_deb_repo_configured();
|
||||
let _rpm = AppAutoUpdater::is_rpm_repo_configured();
|
||||
}
|
||||
|
||||
fn update_info(manual_update_required: bool) -> AppUpdateInfo {
|
||||
AppUpdateInfo {
|
||||
current_version: "v0.34.0".to_string(),
|
||||
new_version: "v0.35.0".to_string(),
|
||||
release_notes: String::new(),
|
||||
download_url: "https://example.invalid/Donut.msi".to_string(),
|
||||
is_nightly: false,
|
||||
published_at: String::new(),
|
||||
manual_update_required,
|
||||
release_page_url: None,
|
||||
repo_update: false,
|
||||
checksums_url: None,
|
||||
asset_digest: None,
|
||||
auto_install: !manual_update_required,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_installed_app_with_automatic_updates_installs_by_itself() {
|
||||
let info = update_info(false).with_install_policy(false, false);
|
||||
assert!(info.auto_install);
|
||||
assert!(!info.manual_update_required);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn automatic_updates_off_still_reports_the_update_and_waits() {
|
||||
let info = update_info(false).with_install_policy(false, true);
|
||||
assert!(!info.auto_install);
|
||||
assert!(!info.manual_update_required);
|
||||
assert_eq!(info.new_version, "v0.35.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_portable_copy_is_told_to_download_by_hand() {
|
||||
for disabled in [false, true] {
|
||||
let info = update_info(false).with_install_policy(true, disabled);
|
||||
assert!(info.manual_update_required);
|
||||
assert!(!info.auto_install);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_manual_update_never_installs_by_itself() {
|
||||
let info = update_info(true).with_install_policy(false, false);
|
||||
assert!(!info.auto_install);
|
||||
}
|
||||
}
|
||||
@@ -2,25 +2,30 @@
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaExternalLinkAlt, FaTimes } from "react-icons/fa";
|
||||
import { LuCheckCheck } from "react-icons/lu";
|
||||
import { LuCheckCheck, LuDownload } from "react-icons/lu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { AppUpdateInfo } from "@/types";
|
||||
import { RippleButton } from "./ui/ripple";
|
||||
|
||||
interface AppUpdateToastProps {
|
||||
updateInfo: AppUpdateInfo;
|
||||
onInstall: () => void;
|
||||
onRestart: () => Promise<void>;
|
||||
onDismiss: () => void;
|
||||
updateReady?: boolean;
|
||||
isUpdating?: boolean;
|
||||
}
|
||||
|
||||
export function AppUpdateToast({
|
||||
updateInfo,
|
||||
onInstall,
|
||||
onRestart,
|
||||
onDismiss,
|
||||
updateReady = false,
|
||||
isUpdating = false,
|
||||
}: AppUpdateToastProps) {
|
||||
const { t } = useTranslation();
|
||||
const version = updateInfo.new_version;
|
||||
const handleRestartClick = async () => {
|
||||
await onRestart();
|
||||
};
|
||||
@@ -46,9 +51,13 @@ export function AppUpdateToast({
|
||||
<span className="text-sm font-semibold text-foreground">
|
||||
{updateReady
|
||||
? t("appUpdate.toast.updateReady")
|
||||
: updateInfo.repo_update
|
||||
? "Update available via package manager"
|
||||
: t("appUpdate.toast.manualDownloadRequired")}
|
||||
: isUpdating
|
||||
? t("about.update.downloading", { version })
|
||||
: updateInfo.repo_update
|
||||
? t("about.update.packageManager", { version })
|
||||
: updateInfo.manual_update_required
|
||||
? t("appUpdate.toast.manualDownloadRequired")
|
||||
: t("about.update.available", { version })}
|
||||
</span>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{updateInfo.current_version} → {updateInfo.new_version}
|
||||
@@ -75,18 +84,25 @@ export function AppUpdateToast({
|
||||
<LuCheckCheck className="size-3" />
|
||||
{t("appUpdate.toast.restartNow")}
|
||||
</RippleButton>
|
||||
) : updateInfo.repo_update ||
|
||||
isUpdating ? null : updateInfo.manual_update_required ? (
|
||||
<RippleButton
|
||||
onClick={handleViewRelease}
|
||||
size="sm"
|
||||
className="flex items-center gap-2 text-xs"
|
||||
>
|
||||
<FaExternalLinkAlt className="size-3" />
|
||||
{t("appUpdate.toast.viewRelease")}
|
||||
</RippleButton>
|
||||
) : (
|
||||
!updateInfo.repo_update &&
|
||||
updateInfo.manual_update_required && (
|
||||
<RippleButton
|
||||
onClick={handleViewRelease}
|
||||
size="sm"
|
||||
className="flex items-center gap-2 text-xs"
|
||||
>
|
||||
<FaExternalLinkAlt className="size-3" />
|
||||
{t("appUpdate.toast.viewRelease")}
|
||||
</RippleButton>
|
||||
)
|
||||
<RippleButton
|
||||
onClick={onInstall}
|
||||
size="sm"
|
||||
className="flex items-center gap-2 text-xs"
|
||||
>
|
||||
<LuDownload className="size-3" />
|
||||
{t("about.update.update")}
|
||||
</RippleButton>
|
||||
)}
|
||||
<RippleButton
|
||||
variant="outline"
|
||||
|
||||
@@ -168,8 +168,7 @@ export function useAppUpdateNotifications() {
|
||||
useEffect(() => {
|
||||
if (
|
||||
!isClient ||
|
||||
!updateInfo ||
|
||||
updateInfo.manual_update_required ||
|
||||
!updateInfo?.auto_install ||
|
||||
isUpdating ||
|
||||
updateReady ||
|
||||
autoDownloadedVersion.current === updateInfo.new_version
|
||||
@@ -181,23 +180,21 @@ export function useAppUpdateNotifications() {
|
||||
void handleAppUpdate(updateInfo);
|
||||
}, [isClient, updateInfo, isUpdating, updateReady, handleAppUpdate]);
|
||||
|
||||
// Show toast only when update is ready to install or requires manual action
|
||||
// A toast when the update is ready, or when it waits for the person: a
|
||||
// manual update, or automatic updates turned off.
|
||||
useEffect(() => {
|
||||
if (!isClient) return;
|
||||
|
||||
const showManualToast = updateInfo?.manual_update_required && !isUpdating;
|
||||
if (!updateReady && !showManualToast) {
|
||||
return;
|
||||
}
|
||||
if (!updateInfo) return;
|
||||
if (!isClient || !updateInfo) return;
|
||||
if (!updateReady && updateInfo.auto_install) return;
|
||||
|
||||
toast.custom(
|
||||
() => (
|
||||
<AppUpdateToast
|
||||
updateInfo={updateInfo}
|
||||
onInstall={() => void handleAppUpdate(updateInfo)}
|
||||
onRestart={handleRestart}
|
||||
onDismiss={dismissAppUpdate}
|
||||
updateReady={updateReady}
|
||||
isUpdating={isUpdating}
|
||||
/>
|
||||
),
|
||||
{
|
||||
@@ -213,6 +210,7 @@ export function useAppUpdateNotifications() {
|
||||
);
|
||||
}, [
|
||||
updateInfo,
|
||||
handleAppUpdate,
|
||||
handleRestart,
|
||||
dismissAppUpdate,
|
||||
updateReady,
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"logs": "Protokolle"
|
||||
},
|
||||
"disableAutoUpdates": "Automatische App-Updates deaktivieren",
|
||||
"disableAutoUpdatesDescription": "Verhindert, dass die App automatisch nach Updates für Donut Browser sucht und sie installiert. Browser-Updates sind davon nicht betroffen.",
|
||||
"disableAutoUpdatesDescription": "Donut Browser lädt und installiert seine Updates nicht selbst. Es zeigt weiterhin an, wenn eine neue Version verfügbar ist. Browser-Updates sind davon nicht betroffen.",
|
||||
"keepDecryptedProfilesInRam": "Entschlüsselte Profile im RAM behalten",
|
||||
"keepDecryptedProfilesInRamDescription": "Behält die entschlüsselte Kopie passwortgeschützter Profile zwischen den Starts im RAM, damit sie schneller starten. Die Kopie auf dem Datenträger bleibt in jedem Fall verschlüsselt.",
|
||||
"privacy": {
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"logs": "Logs"
|
||||
},
|
||||
"disableAutoUpdates": "Disable app auto updates",
|
||||
"disableAutoUpdatesDescription": "Prevent the app from automatically checking and installing Donut Browser updates. Browser updates are not affected.",
|
||||
"disableAutoUpdatesDescription": "Donut Browser does not download or install its updates by itself. It still shows when a new version is available. Browser updates are not affected.",
|
||||
"keepDecryptedProfilesInRam": "Keep decrypted profiles in RAM",
|
||||
"keepDecryptedProfilesInRamDescription": "Preserve the decrypted in-RAM copy of password-protected profiles between launches for faster startup. The on-disk copy stays encrypted regardless.",
|
||||
"privacy": {
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"logs": "Registros"
|
||||
},
|
||||
"disableAutoUpdates": "Desactivar actualizaciones automáticas de la app",
|
||||
"disableAutoUpdatesDescription": "Evita que la aplicación busque e instale actualizaciones de Donut Browser automáticamente. Las actualizaciones de navegadores no se ven afectadas.",
|
||||
"disableAutoUpdatesDescription": "Donut Browser no descarga ni instala sus actualizaciones por sí solo. Sigue avisando cuando hay una versión nueva. Las actualizaciones de navegadores no se ven afectadas.",
|
||||
"keepDecryptedProfilesInRam": "Mantener perfiles descifrados en RAM",
|
||||
"keepDecryptedProfilesInRamDescription": "Conservar la copia descifrada en RAM de los perfiles protegidos por contraseña entre lanzamientos para un inicio más rápido. La copia en disco permanece cifrada en cualquier caso.",
|
||||
"privacy": {
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"logs": "Journaux"
|
||||
},
|
||||
"disableAutoUpdates": "Désactiver les mises à jour automatiques de l'app",
|
||||
"disableAutoUpdatesDescription": "Empêche l'application de vérifier et d'installer automatiquement les mises à jour de Donut Browser. Les mises à jour des navigateurs ne sont pas affectées.",
|
||||
"disableAutoUpdatesDescription": "Donut Browser ne télécharge ni n'installe ses mises à jour de lui-même. Il indique toujours quand une nouvelle version est disponible. Les mises à jour des navigateurs ne sont pas affectées.",
|
||||
"keepDecryptedProfilesInRam": "Conserver les profils déchiffrés en RAM",
|
||||
"keepDecryptedProfilesInRamDescription": "Conserver en RAM la copie déchiffrée des profils protégés par mot de passe entre les lancements pour un démarrage plus rapide. La copie sur disque reste chiffrée dans tous les cas.",
|
||||
"privacy": {
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"logs": "ログ"
|
||||
},
|
||||
"disableAutoUpdates": "アプリの自動更新を無効にする",
|
||||
"disableAutoUpdatesDescription": "Donut Browserの自動更新確認・インストールを無効にします。ブラウザの更新には影響しません。",
|
||||
"disableAutoUpdatesDescription": "Donut Browser は更新を自動でダウンロード・インストールしません。新しいバージョンがあるときは引き続き通知します。ブラウザの更新には影響しません。",
|
||||
"keepDecryptedProfilesInRam": "復号済みプロファイルをRAMに保持",
|
||||
"keepDecryptedProfilesInRamDescription": "起動を高速化するため、パスワード保護されたプロファイルの復号済みコピーをRAMに保持します。ディスク上のコピーは常に暗号化されたままです。",
|
||||
"privacy": {
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"logs": "로그"
|
||||
},
|
||||
"disableAutoUpdates": "앱 자동 업데이트 사용 안 함",
|
||||
"disableAutoUpdatesDescription": "Donut Browser 업데이트를 앱이 자동으로 확인하고 설치하지 않도록 합니다. 브라우저 업데이트는 영향을 받지 않습니다.",
|
||||
"disableAutoUpdatesDescription": "Donut Browser가 업데이트를 스스로 다운로드하거나 설치하지 않습니다. 새 버전이 나오면 계속 알려 드립니다. 브라우저 업데이트는 영향을 받지 않습니다.",
|
||||
"keepDecryptedProfilesInRam": "복호화된 프로필을 RAM에 유지",
|
||||
"keepDecryptedProfilesInRamDescription": "비밀번호로 보호된 프로필의 복호화된 RAM 사본을 실행 사이에 유지하여 시작 속도를 높입니다. 디스크의 사본은 그대로 암호화된 상태로 유지됩니다.",
|
||||
"privacy": {
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"logs": "Logs"
|
||||
},
|
||||
"disableAutoUpdates": "Desativar atualizações automáticas do app",
|
||||
"disableAutoUpdatesDescription": "Impede que o aplicativo verifique e instale atualizações do Donut Browser automaticamente. As atualizações de navegadores não são afetadas.",
|
||||
"disableAutoUpdatesDescription": "O Donut Browser não baixa nem instala suas atualizações sozinho. Ele continua avisando quando há uma nova versão. As atualizações de navegadores não são afetadas.",
|
||||
"keepDecryptedProfilesInRam": "Manter perfis descriptografados na RAM",
|
||||
"keepDecryptedProfilesInRamDescription": "Preserva a cópia descriptografada na RAM dos perfis protegidos por senha entre execuções para um início mais rápido. A cópia em disco permanece criptografada em qualquer caso.",
|
||||
"privacy": {
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"logs": "Журналы"
|
||||
},
|
||||
"disableAutoUpdates": "Отключить автообновление приложения",
|
||||
"disableAutoUpdatesDescription": "Запретить автоматическую проверку и установку обновлений Donut Browser. Обновления браузеров не затрагиваются.",
|
||||
"disableAutoUpdatesDescription": "Donut Browser не скачивает и не устанавливает свои обновления сам. Он по-прежнему сообщает о новой версии. Обновления браузеров не затрагиваются.",
|
||||
"keepDecryptedProfilesInRam": "Хранить расшифрованные профили в ОЗУ",
|
||||
"keepDecryptedProfilesInRamDescription": "Сохранять расшифрованную копию защищённых паролем профилей в ОЗУ между запусками для ускорения старта. Копия на диске в любом случае остаётся зашифрованной.",
|
||||
"privacy": {
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"logs": "Günlükler"
|
||||
},
|
||||
"disableAutoUpdates": "Uygulama otomatik güncellemelerini devre dışı bırak",
|
||||
"disableAutoUpdatesDescription": "Uygulamanın Donut Browser güncellemelerini otomatik olarak denetlemesini ve yüklemesini engelleyin. Tarayıcı güncellemeleri bundan etkilenmez.",
|
||||
"disableAutoUpdatesDescription": "Donut Browser güncellemelerini kendisi indirmez ve yüklemez. Yeni bir sürüm çıktığında yine de gösterir. Tarayıcı güncellemeleri bundan etkilenmez.",
|
||||
"keepDecryptedProfilesInRam": "Şifresi çözülmüş profilleri RAM'de tut",
|
||||
"keepDecryptedProfilesInRamDescription": "Daha hızlı başlatma için parola korumalı profillerin şifresi çözülmüş RAM kopyasını başlatmalar arasında koruyun. Diskteki kopya her durumda şifreli kalır.",
|
||||
"privacy": {
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"logs": "Nhật ký"
|
||||
},
|
||||
"disableAutoUpdates": "Tắt tự động cập nhật ứng dụng",
|
||||
"disableAutoUpdatesDescription": "Ngăn ứng dụng tự động kiểm tra và cài đặt bản cập nhật Donut Browser. Cập nhật trình duyệt không bị ảnh hưởng.",
|
||||
"disableAutoUpdatesDescription": "Donut Browser không tự tải xuống hay cài đặt bản cập nhật. Ứng dụng vẫn báo khi có phiên bản mới. Cập nhật trình duyệt không bị ảnh hưởng.",
|
||||
"keepDecryptedProfilesInRam": "Giữ hồ sơ đã giải mã trong RAM",
|
||||
"keepDecryptedProfilesInRamDescription": "Giữ bản sao đã giải mã trong RAM của hồ sơ được bảo vệ bằng mật khẩu giữa các lần khởi chạy để khởi động nhanh hơn. Bản sao trên ổ đĩa vẫn được mã hóa.",
|
||||
"privacy": {
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"logs": "日志"
|
||||
},
|
||||
"disableAutoUpdates": "禁用应用自动更新",
|
||||
"disableAutoUpdatesDescription": "阻止应用程序自动检查和安装 Donut Browser 更新。浏览器更新不受影响。",
|
||||
"disableAutoUpdatesDescription": "Donut Browser 不会自行下载或安装更新,但仍会在有新版本时提示。浏览器更新不受影响。",
|
||||
"keepDecryptedProfilesInRam": "在内存中保留已解密的配置文件",
|
||||
"keepDecryptedProfilesInRamDescription": "在启动之间保留密码保护配置文件的已解密内存副本,以便更快地启动。无论如何磁盘上的副本始终保持加密。",
|
||||
"privacy": {
|
||||
|
||||
@@ -555,6 +555,8 @@ export interface AppUpdateInfo {
|
||||
checksums_url?: string | null;
|
||||
/** GitHub-computed digest of the chosen asset ("sha256:<hex>"). */
|
||||
asset_digest?: string | null;
|
||||
/** The app installs it without asking; false when the person starts it. */
|
||||
auto_install: boolean;
|
||||
}
|
||||
|
||||
export interface AppUpdateProgress {
|
||||
|
||||
Reference in new issue
Block a user