Feat/automatic update installation (#5345)
Signed-off-by: AdemolaAri <ademola.ari@gmail.com>
This commit is contained in:
committed by
GitHub
parent
8e9b6c9bc6
commit
c3c71a2ff4
@@ -9,6 +9,7 @@ import {
|
||||
dialog,
|
||||
Menu,
|
||||
MenuItemConstructorOptions,
|
||||
Notification,
|
||||
} from 'electron';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs/promises';
|
||||
@@ -30,6 +31,9 @@ let githubUpdateInfo: {
|
||||
// Store update state
|
||||
let lastUpdateState: { updateAvailable: boolean; latestVersion?: string } | null = null;
|
||||
|
||||
// Track last reported progress to prevent backward jumps
|
||||
let lastReportedProgress = 0;
|
||||
|
||||
// Track if IPC handlers have been registered
|
||||
let ipcUpdateHandlersRegistered = false;
|
||||
|
||||
@@ -47,9 +51,10 @@ export function registerUpdateIpcHandlers() {
|
||||
try {
|
||||
log.info('Manual check for updates requested');
|
||||
|
||||
// Reset fallback flag
|
||||
// Reset state for new update check
|
||||
isUsingGitHubFallback = false;
|
||||
githubUpdateInfo = {};
|
||||
lastReportedProgress = 0; // Reset progress tracking
|
||||
|
||||
// Ensure auto-updater is properly initialized
|
||||
if (!autoUpdater.currentVersion) {
|
||||
@@ -116,6 +121,10 @@ export function registerUpdateIpcHandlers() {
|
||||
lastUpdateState = { updateAvailable: true, latestVersion: result.latestVersion };
|
||||
updateTrayIcon(true);
|
||||
sendStatusToWindow('update-available', { version: result.latestVersion });
|
||||
|
||||
// Auto-download for GitHub fallback (matching autoDownload behavior)
|
||||
log.info('Auto-downloading update via GitHub fallback...');
|
||||
await githubAutoDownload(result.downloadUrl!, result.latestVersion!, 'manual check');
|
||||
} else {
|
||||
updateAvailable = false;
|
||||
lastUpdateState = { updateAvailable: false };
|
||||
@@ -149,12 +158,17 @@ export function registerUpdateIpcHandlers() {
|
||||
try {
|
||||
if (isUsingGitHubFallback && githubUpdateInfo.downloadUrl && githubUpdateInfo.latestVersion) {
|
||||
log.info('Using GitHub fallback for download...');
|
||||
lastReportedProgress = 0; // Reset progress tracking
|
||||
|
||||
const result = await githubUpdater.downloadUpdate(
|
||||
githubUpdateInfo.downloadUrl,
|
||||
githubUpdateInfo.latestVersion,
|
||||
(percent) => {
|
||||
sendStatusToWindow('download-progress', { percent });
|
||||
// Only send if progress increased (monotonic)
|
||||
if (percent > lastReportedProgress) {
|
||||
lastReportedProgress = percent;
|
||||
sendStatusToWindow('download-progress', { percent });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -200,25 +214,26 @@ export function registerUpdateIpcHandlers() {
|
||||
throw new Error('Update file not found. Please download the update first.');
|
||||
}
|
||||
|
||||
// Show dialog to inform user about manual installation
|
||||
// Improved dialog with clearer instructions
|
||||
const dialogResult = (await dialog.showMessageBox({
|
||||
type: 'info',
|
||||
title: 'Update Downloaded',
|
||||
message: 'The update has been downloaded to your Downloads folder.',
|
||||
detail: `Please extract the zip file and move the Goose app to your Applications folder to complete the update.`,
|
||||
buttons: ['Open Downloads', 'Cancel'],
|
||||
title: 'Update Ready to Install',
|
||||
message: `Version ${githubUpdateInfo.latestVersion} is ready to install.`,
|
||||
detail: `The update has been downloaded and extracted. To complete the installation:\n\n1. Click "Open Folder" to view the new Goose.app\n2. Quit Goose (this app will close)\n3. Drag the new Goose.app to your Applications folder\n4. Replace the existing app when prompted\n\nThe update will be available the next time you launch Goose.`,
|
||||
buttons: ['Open Folder & Quit', 'Open Folder Only', 'Cancel'],
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
cancelId: 2,
|
||||
})) as unknown as { response: number };
|
||||
|
||||
if (dialogResult.response === 0) {
|
||||
// Open the extracted folder or show the zip file
|
||||
// Open folder and quit app for easy replacement
|
||||
shell.showItemInFolder(updatePath);
|
||||
|
||||
// Optionally quit the app so user can replace it
|
||||
setTimeout(() => {
|
||||
app.quit();
|
||||
}, 1000);
|
||||
}, 1500); // Give user time to see the folder open
|
||||
} else if (dialogResult.response === 1) {
|
||||
// Just open folder, don't quit
|
||||
shell.showItemInFolder(updatePath);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error installing GitHub update:', error);
|
||||
@@ -237,6 +252,10 @@ export function registerUpdateIpcHandlers() {
|
||||
ipcMain.handle('get-update-state', () => {
|
||||
return lastUpdateState;
|
||||
});
|
||||
|
||||
ipcMain.handle('is-using-github-fallback', () => {
|
||||
return isUsingGitHubFallback;
|
||||
});
|
||||
}
|
||||
|
||||
// Configure auto-updater
|
||||
@@ -273,7 +292,7 @@ export function setupAutoUpdater(tray?: Tray) {
|
||||
}
|
||||
|
||||
// Configure auto-updater settings
|
||||
autoUpdater.autoDownload = false; // We'll trigger downloads manually
|
||||
autoUpdater.autoDownload = true; // Automatically download updates when available
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
|
||||
// Enable updates in development mode for testing
|
||||
@@ -330,7 +349,7 @@ export function setupAutoUpdater(tray?: Tray) {
|
||||
|
||||
githubUpdater
|
||||
.checkForUpdates()
|
||||
.then((result) => {
|
||||
.then(async (result) => {
|
||||
if (result.error) {
|
||||
sendStatusToWindow('error', result.error);
|
||||
} else if (result.updateAvailable) {
|
||||
@@ -345,6 +364,10 @@ export function setupAutoUpdater(tray?: Tray) {
|
||||
lastUpdateState = { updateAvailable: true, latestVersion: result.latestVersion };
|
||||
updateTrayIcon(true);
|
||||
sendStatusToWindow('update-available', { version: result.latestVersion });
|
||||
|
||||
// Auto-download for GitHub fallback (matching autoDownload behavior)
|
||||
log.info('Auto-downloading update via GitHub fallback on startup...');
|
||||
await githubAutoDownload(result.downloadUrl!, result.latestVersion!, 'on startup');
|
||||
} else {
|
||||
updateAvailable = false;
|
||||
lastUpdateState = { updateAvailable: false };
|
||||
@@ -365,6 +388,7 @@ export function setupAutoUpdater(tray?: Tray) {
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
log.info('Auto-updater: Checking for update...');
|
||||
log.info(`Auto-updater: Feed URL during check: ${autoUpdater.getFeedURL()}`);
|
||||
lastReportedProgress = 0; // Reset progress tracking for new check
|
||||
sendStatusToWindow('checking-for-update');
|
||||
});
|
||||
|
||||
@@ -421,6 +445,10 @@ export function setupAutoUpdater(tray?: Tray) {
|
||||
updateAvailable = true;
|
||||
updateTrayIcon(true);
|
||||
sendStatusToWindow('update-available', { version: result.latestVersion });
|
||||
|
||||
// Auto-download for GitHub fallback (matching autoDownload behavior)
|
||||
log.info('Auto-downloading update via GitHub fallback after error...');
|
||||
await githubAutoDownload(result.downloadUrl!, result.latestVersion!, 'after error');
|
||||
} else {
|
||||
updateAvailable = false;
|
||||
updateTrayIcon(false);
|
||||
@@ -441,16 +469,37 @@ export function setupAutoUpdater(tray?: Tray) {
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (progressObj) => {
|
||||
let log_message = 'Download speed: ' + progressObj.bytesPerSecond;
|
||||
log_message = log_message + ' - Downloaded ' + progressObj.percent + '%';
|
||||
log_message = log_message + ' (' + progressObj.transferred + '/' + progressObj.total + ')';
|
||||
log.info(log_message);
|
||||
sendStatusToWindow('download-progress', progressObj);
|
||||
const roundedPercent = Math.round(progressObj.percent);
|
||||
|
||||
// Only send progress if it increased (prevents backward jumps)
|
||||
if (roundedPercent > lastReportedProgress) {
|
||||
lastReportedProgress = roundedPercent;
|
||||
|
||||
const log_message = `Download: ${roundedPercent}% (${progressObj.transferred}/${progressObj.total}) @ ${Math.round(progressObj.bytesPerSecond / 1024)} KB/s`;
|
||||
log.info(log_message);
|
||||
|
||||
sendStatusToWindow('download-progress', {
|
||||
...progressObj,
|
||||
percent: roundedPercent,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
|
||||
log.info('Update downloaded:', info);
|
||||
sendStatusToWindow('update-downloaded', info);
|
||||
|
||||
// Show native notification
|
||||
const notification = new Notification({
|
||||
title: 'Update Ready',
|
||||
body: `Version ${info.version} will be installed when you quit Goose. Click to install now.`,
|
||||
});
|
||||
notification.show();
|
||||
|
||||
// Optional: Add click handler to install immediately
|
||||
notification.on('click', () => {
|
||||
autoUpdater.quitAndInstall(false, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -466,6 +515,46 @@ function sendStatusToWindow(event: string, data?: unknown) {
|
||||
});
|
||||
}
|
||||
|
||||
// centralize GitHub fallback auto-download logic.
|
||||
async function githubAutoDownload(
|
||||
downloadUrl: string,
|
||||
latestVersion: string,
|
||||
contextLabel = ''
|
||||
): Promise<void> {
|
||||
// Reset progress tracking for new download
|
||||
lastReportedProgress = 0;
|
||||
|
||||
try {
|
||||
const downloadResult = await githubUpdater.downloadUpdate(
|
||||
downloadUrl,
|
||||
latestVersion,
|
||||
(percent) => {
|
||||
// Only send if progress increased (monotonic)
|
||||
if (percent > lastReportedProgress) {
|
||||
lastReportedProgress = percent;
|
||||
sendStatusToWindow('download-progress', { percent });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (downloadResult.success && downloadResult.downloadPath) {
|
||||
githubUpdateInfo.downloadPath = downloadResult.downloadPath;
|
||||
githubUpdateInfo.extractedPath = downloadResult.extractedPath;
|
||||
sendStatusToWindow('update-downloaded', { version: latestVersion });
|
||||
} else {
|
||||
log.error(
|
||||
`GitHub auto-download failed${contextLabel ? ` (${contextLabel})` : ''}:`,
|
||||
downloadResult.error
|
||||
);
|
||||
}
|
||||
} catch (downloadError) {
|
||||
log.error(
|
||||
`Error during GitHub auto-download${contextLabel ? ` (${contextLabel})` : ''}:`,
|
||||
downloadError
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function updateTrayIcon(hasUpdate: boolean) {
|
||||
if (!trayRef) return;
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ export class GitHubUpdater {
|
||||
log.info(`GitHubUpdater: Looking for asset named: ${assetName}`);
|
||||
log.info(`GitHubUpdater: Available assets: ${release.assets.map((a) => a.name).join(', ')}`);
|
||||
|
||||
const asset = release.assets.find((a) => a.name === assetName);
|
||||
const asset = release.assets.find((a) => a.name.toLowerCase() === assetName.toLowerCase()); // keeping comparison to lower case becasue Goose vs goose
|
||||
if (asset) {
|
||||
downloadUrl = asset.browser_download_url;
|
||||
log.info(`GitHubUpdater: Found matching asset: ${asset.name} (${asset.size} bytes)`);
|
||||
@@ -115,6 +115,12 @@ export class GitHubUpdater {
|
||||
log.warn(`GitHubUpdater: No matching asset found for ${assetName}`);
|
||||
}
|
||||
|
||||
if (!downloadUrl) {
|
||||
throw new Error(
|
||||
`Update Available but no download URL found for platform: ${platform}, arch: ${arch}`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
updateAvailable: true,
|
||||
latestVersion,
|
||||
@@ -159,6 +165,7 @@ export class GitHubUpdater {
|
||||
if (!response.body) {
|
||||
throw new Error('Response body is null');
|
||||
}
|
||||
let lastReportedPercent = -1; // Track last reported percentage to throttle updates
|
||||
|
||||
// Read the response stream
|
||||
const reader = response.body.getReader();
|
||||
@@ -172,10 +179,15 @@ export class GitHubUpdater {
|
||||
chunks.push(value);
|
||||
downloadedSize += value.length;
|
||||
|
||||
// Report progress
|
||||
// Report progress - only when percentage changes by at least 1%
|
||||
if (totalSize > 0 && onProgress) {
|
||||
const percent = Math.round((downloadedSize / totalSize) * 100);
|
||||
onProgress(percent);
|
||||
|
||||
// Only report if percent changed (throttles from hundreds/sec to ~100 total)
|
||||
if (percent !== lastReportedPercent) {
|
||||
onProgress(percent);
|
||||
lastReportedPercent = percent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user