feat(updater): add performAutoUpdate and eligibility to store/service
Additive changes only: adds isAutoUpdateEligible() getter and eligibility fetch in initUpdatesStore (keeps existing skipIfRemote guards); adds performAutoUpdate() in updatesService for the download/install/relaunch flow with cancellation support. cc
This commit is contained in:
2 files changed
+93
No files matched your search
@@ -1,5 +1,7 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import { check } from '@tauri-apps/plugin-updater';
|
||||
import { relaunch } from '@tauri-apps/plugin-process';
|
||||
import {
|
||||
acknowledgeRelease,
|
||||
checkForUpdates,
|
||||
@@ -316,3 +318,77 @@ export async function disableUpdateChecks(): Promise<void> {
|
||||
console.debug('[Updates] Failed to disable update checks:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Auto-update ====================
|
||||
|
||||
export type AutoUpdateProgress =
|
||||
| { state: 'checking' }
|
||||
| { state: 'downloading'; downloadedBytes: number; totalBytes?: number }
|
||||
| { state: 'installing' }
|
||||
| { state: 'restarting' }
|
||||
| { state: 'error'; error: string };
|
||||
|
||||
let autoUpdateInProgress = false;
|
||||
|
||||
export function isAutoUpdateInProgress(): boolean {
|
||||
return autoUpdateInProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download, install and relaunch the pending update.
|
||||
*
|
||||
* `isCancelled` is polled before kicking off the download and again before the
|
||||
* final relaunch so the caller can abort mid-flight.
|
||||
* Returns true when the relaunch was triggered, false otherwise.
|
||||
*/
|
||||
export async function performAutoUpdate(
|
||||
onProgress: (progress: AutoUpdateProgress) => void,
|
||||
isCancelled?: () => boolean,
|
||||
): Promise<boolean> {
|
||||
if (autoUpdateInProgress) {
|
||||
console.debug('[Updates] Auto-update already in progress');
|
||||
return false;
|
||||
}
|
||||
autoUpdateInProgress = true;
|
||||
try {
|
||||
onProgress({ state: 'checking' });
|
||||
const update = await check();
|
||||
if (!update) return false;
|
||||
|
||||
if (isCancelled?.()) return false;
|
||||
|
||||
let totalBytes: number | undefined;
|
||||
let downloadedBytes = 0;
|
||||
|
||||
onProgress({ state: 'downloading', downloadedBytes: 0 });
|
||||
|
||||
await update.downloadAndInstall((event) => {
|
||||
switch (event.event) {
|
||||
case 'Started':
|
||||
totalBytes = event.data.contentLength ?? undefined;
|
||||
onProgress({ state: 'downloading', downloadedBytes: 0, totalBytes });
|
||||
break;
|
||||
case 'Progress':
|
||||
downloadedBytes += event.data.chunkLength;
|
||||
onProgress({ state: 'downloading', downloadedBytes, totalBytes });
|
||||
break;
|
||||
case 'Finished':
|
||||
onProgress({ state: 'installing' });
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (isCancelled?.()) return false;
|
||||
|
||||
onProgress({ state: 'restarting' });
|
||||
await relaunch();
|
||||
return true;
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.error('[Updates] Auto-update failed:', message);
|
||||
onProgress({ state: 'error', error: message });
|
||||
return false;
|
||||
} finally {
|
||||
autoUpdateInProgress = false;
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,10 @@ let preferences: UpdatePreferences = {
|
||||
};
|
||||
|
||||
let currentVersion = '';
|
||||
let autoUpdateEligible = false;
|
||||
let versionLoaded = false;
|
||||
let prefsLoaded = false;
|
||||
let eligibilityLoaded = false;
|
||||
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
@@ -63,6 +65,10 @@ export function getCurrentVersion(): string {
|
||||
return currentVersion;
|
||||
}
|
||||
|
||||
export function isAutoUpdateEligible(): boolean {
|
||||
return autoUpdateEligible;
|
||||
}
|
||||
|
||||
export async function initUpdatesStore(): Promise<void> {
|
||||
if (skipIfRemote()) return;
|
||||
// get_current_version never needs a session (compile-time value).
|
||||
@@ -76,6 +82,17 @@ export async function initUpdatesStore(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-update eligibility is compile-time + runtime (APPIMAGE env, flatpak/snap
|
||||
// detection). No session required. Only fetch once.
|
||||
if (!eligibilityLoaded) {
|
||||
try {
|
||||
autoUpdateEligible = await invoke<boolean>('v2_is_auto_update_eligible');
|
||||
eligibilityLoaded = true;
|
||||
} catch (error) {
|
||||
console.debug('[Updates] Failed to check auto-update eligibility:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// get_update_preferences needs the SQLite store which is only
|
||||
// available after login. Retry on each call until it succeeds,
|
||||
// so a pre-login failure doesn't lock in stale defaults.
|
||||
|
||||
Reference in new issue
Block a user