From b8fd40e7359a05f8c4b4004d27c2e695f9c2883b Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Fri, 11 Apr 2025 14:25:40 -0700 Subject: [PATCH] enable auto updates! --- .../contrib/void/browser/voidUpdateActions.ts | 156 +++++++++++++----- .../contrib/void/common/voidUpdateService.ts | 3 +- .../void/common/voidUpdateServiceTypes.ts | 14 ++ .../electron-main/voidUpdateMainService.ts | 83 +++++++--- 4 files changed, 193 insertions(+), 63 deletions(-) create mode 100644 src/vs/workbench/contrib/void/common/voidUpdateServiceTypes.ts diff --git a/src/vs/workbench/contrib/void/browser/voidUpdateActions.ts b/src/vs/workbench/contrib/void/browser/voidUpdateActions.ts index 905b9b80..378171d1 100644 --- a/src/vs/workbench/contrib/void/browser/voidUpdateActions.ts +++ b/src/vs/workbench/contrib/void/browser/voidUpdateActions.ts @@ -8,45 +8,94 @@ import Severity from '../../../../base/common/severity.js'; import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; import { localize2 } from '../../../../nls.js'; import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { INotificationActions, INotificationService } from '../../../../platform/notification/common/notification.js'; import { IMetricsService } from '../common/metricsService.js'; import { IVoidUpdateService } from '../common/voidUpdateService.js'; import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; import * as dom from '../../../../base/browser/dom.js'; +import { IUpdateService } from '../../../../platform/update/common/update.js'; +import { VoidCheckUpdateRespose } from '../common/voidUpdateServiceTypes.js'; +import { IAction } from '../../../../base/common/actions.js'; -const notifyYesUpdate = (notifService: INotificationService, res: { message?: string } = {}) => { +const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifService: INotificationService, updateService: IUpdateService) => { const message = res?.message || 'This is a very old version of Void, please download the latest version! [Void Editor](https://voideditor.com/download-beta)!' - const notifController = notifService.notify({ - severity: Severity.Info, - message: message, - sticky: true, - progress: { worked: 0, total: 100 }, - actions: { - primary: [{ - id: 'void.updater.update', - enabled: true, + + let actions: INotificationActions | undefined + + if (res?.action) { + const primary: IAction[] = [] + + if (res.action === 'reinstall') { + primary.push({ label: `Reinstall`, + id: 'void.updater.reinstall', + enabled: true, tooltip: '', class: undefined, run: () => { const { window } = dom.getActiveWindow() window.open('https://voideditor.com/download-beta') } - }, - { - id: 'void.updater.site', + }) + } + + if (res.action === 'download') { + primary.push({ + label: `Download`, + id: 'void.updater.download', enabled: true, - label: `Void Site`, tooltip: '', class: undefined, run: () => { - const { window } = dom.getActiveWindow() - window.open('https://voideditor.com/') + updateService.downloadUpdate() } - }], + }) + } + + + if (res.action === 'apply') { + primary.push({ + label: `Apply`, + id: 'void.updater.apply', + enabled: true, + tooltip: '', + class: undefined, + run: () => { + updateService.applyUpdate() + } + }) + } + + if (res.action === 'restart') { + primary.push({ + label: `Restart`, + id: 'void.updater.restart', + enabled: true, + tooltip: '', + class: undefined, + run: () => { + updateService.quitAndInstall() + } + }) + } + + primary.push({ + id: 'void.updater.site', + enabled: true, + label: `Void Site`, + tooltip: '', + class: undefined, + run: () => { + const { window } = dom.getActiveWindow() + window.open('https://voideditor.com/') + } + }) + + actions = { + primary: primary, secondary: [{ id: 'void.updater.close', enabled: true, @@ -57,19 +106,24 @@ const notifyYesUpdate = (notifService: INotificationService, res: { message?: st notifController.close() } }] - }, + } + } + else { + actions = undefined + } + + const notifController = notifService.notify({ + severity: Severity.Info, + message: message, + sticky: true, + progress: actions ? { worked: 0, total: 100 } : undefined, + actions: actions, }) // const d = notifController.onDidClose(() => { // notifyYesUpdate(notifService, res) // d.dispose() // }) } -const notifyNoUpdate = (notifService: INotificationService) => { - notifService.notify({ - severity: Severity.Info, - message: 'Void is up-to-date!', - }) -} const notifyErrChecking = (notifService: INotificationService) => { const message = `Void Error: There was an error checking for updates. If this persists, please get in touch or reinstall Void [here](https://voideditor.com/download-beta)!` notifService.notify({ @@ -80,6 +134,34 @@ const notifyErrChecking = (notifService: INotificationService) => { } +const performVoidCheck = async ( + explicit: boolean, + notifService: INotificationService, + voidUpdateService: IVoidUpdateService, + metricsService: IMetricsService, + updateService: IUpdateService, +) => { + + const metricsTag = explicit ? 'Manual' : 'Auto' + + metricsService.capture(`Void Update ${metricsTag}: Checking...`, {}) + const res = await voidUpdateService.check(explicit) + if (!res) { + notifyErrChecking(notifService); + metricsService.capture(`Void Update ${metricsTag}: Error`, { res }) + } + else { + if (res.message) { + notifyUpdate(res, notifService, updateService) + metricsService.capture(`Void Update ${metricsTag}: Yes`, { res }) + } + else { + metricsService.capture(`Void Update ${metricsTag}: No`, { res }) + return + } + } +} + // Action registerAction2(class extends Action2 { @@ -94,12 +176,8 @@ registerAction2(class extends Action2 { const voidUpdateService = accessor.get(IVoidUpdateService) const notifService = accessor.get(INotificationService) const metricsService = accessor.get(IMetricsService) - - metricsService.capture('Void Update Manual: Checking...', {}) - const res = await voidUpdateService.check(true) - if (!res) { notifyErrChecking(notifService); metricsService.capture('Void Update Manual: Error', { res }) } - else if (res.hasUpdate) { notifyYesUpdate(notifService, res); metricsService.capture('Void Update Manual: Yes', { res }) } - else if (!res.hasUpdate) { notifyNoUpdate(notifService); metricsService.capture('Void Update Manual: No', { res }) } + const updateService = accessor.get(IUpdateService) + performVoidCheck(true, notifService, voidUpdateService, metricsService, updateService) } }) @@ -107,17 +185,15 @@ registerAction2(class extends Action2 { class VoidUpdateWorkbenchContribution extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.void.voidUpdate' constructor( - @IVoidUpdateService private readonly voidUpdateService: IVoidUpdateService, - @IMetricsService private readonly metricsService: IMetricsService, - @INotificationService private readonly notifService: INotificationService, + @IVoidUpdateService voidUpdateService: IVoidUpdateService, + @IMetricsService metricsService: IMetricsService, + @INotificationService notifService: INotificationService, + @IUpdateService updateService: IUpdateService, ) { super() - const autoCheck = async () => { - this.metricsService.capture('Void Update Startup: Checking...', {}) - const res = await this.voidUpdateService.check(false) - if (!res) { notifyErrChecking(this.notifService); this.metricsService.capture('Void Update Startup: Error', { res }) } - else if (res.hasUpdate) { notifyYesUpdate(this.notifService, res); this.metricsService.capture('Void Update Startup: Yes', { res }) } - else if (!res.hasUpdate) { this.metricsService.capture('Void Update Startup: No', { res }) } // display nothing if up to date + + const autoCheck = () => { + performVoidCheck(false, notifService, voidUpdateService, metricsService, updateService) } // check once 5 seconds after mount diff --git a/src/vs/workbench/contrib/void/common/voidUpdateService.ts b/src/vs/workbench/contrib/void/common/voidUpdateService.ts index d6fe4140..fbf72b54 100644 --- a/src/vs/workbench/contrib/void/common/voidUpdateService.ts +++ b/src/vs/workbench/contrib/void/common/voidUpdateService.ts @@ -7,12 +7,13 @@ import { ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js'; +import { VoidCheckUpdateRespose } from './voidUpdateServiceTypes.js'; export interface IVoidUpdateService { readonly _serviceBrand: undefined; - check: (explicit: boolean) => Promise<{ hasUpdate: true, message: string } | { hasUpdate: false } | null>; + check: (explicit: boolean) => Promise; } diff --git a/src/vs/workbench/contrib/void/common/voidUpdateServiceTypes.ts b/src/vs/workbench/contrib/void/common/voidUpdateServiceTypes.ts new file mode 100644 index 00000000..eaf90dd1 --- /dev/null +++ b/src/vs/workbench/contrib/void/common/voidUpdateServiceTypes.ts @@ -0,0 +1,14 @@ +/*-------------------------------------------------------------------------------------- + * Copyright 2025 Glass Devtools, Inc. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. + *--------------------------------------------------------------------------------------*/ + +export type VoidCheckUpdateRespose = { + message: string, + action?: 'reinstall' | 'restart' | 'download' | 'apply' +} | { + message: null, + actions?: undefined, +} | null + + diff --git a/src/vs/workbench/contrib/void/electron-main/voidUpdateMainService.ts b/src/vs/workbench/contrib/void/electron-main/voidUpdateMainService.ts index 7570d965..1f38a74c 100644 --- a/src/vs/workbench/contrib/void/electron-main/voidUpdateMainService.ts +++ b/src/vs/workbench/contrib/void/electron-main/voidUpdateMainService.ts @@ -8,6 +8,7 @@ import { IEnvironmentMainService } from '../../../../platform/environment/electr import { IProductService } from '../../../../platform/product/common/productService.js'; import { IUpdateService, StateType } from '../../../../platform/update/common/update.js'; import { IVoidUpdateService } from '../common/voidUpdateService.js'; +import { VoidCheckUpdateRespose } from '../common/voidUpdateServiceTypes.js'; @@ -17,48 +18,86 @@ export class VoidMainUpdateService extends Disposable implements IVoidUpdateServ constructor( @IProductService private readonly _productService: IProductService, @IEnvironmentMainService private readonly _envMainService: IEnvironmentMainService, - @IUpdateService private readonly _updateService: IUpdateService + @IUpdateService private readonly _updateService: IUpdateService, ) { super() } - nIgnores = 0 - async check(explicit: boolean) { + async check(explicit: boolean): Promise { const isDevMode = !this._envMainService.isBuilt // found in abstractUpdateService.ts if (isDevMode) { - return { hasUpdate: false } as const + return { message: null } as const } this._updateService.checkForUpdates(false) // implicity check, then handle result ourselves + console.log('updateState', this._updateService.state) + + if (this._updateService.state.type === StateType.Uninitialized) { + // The update service hasn't been initialized yet + return { message: explicit ? 'Not yet checking for updates...' : null, action: explicit ? 'reinstall' : undefined } as const + } + + if (this._updateService.state.type === StateType.Idle) { + // No updates currently available + return { message: explicit ? 'No update found!' : null, action: explicit ? 'reinstall' : undefined } as const + } + + if (this._updateService.state.type === StateType.CheckingForUpdates) { + // Currently checking for updates + return { message: explicit ? 'Currently checking for updates...' : null } as const + } + + if (this._updateService.state.type === StateType.AvailableForDownload) { + // Update available but requires manual download (mainly for Linux) + return { message: 'A new update is available!', action: 'download', } as const + } + + if (this._updateService.state.type === StateType.Downloading) { + // Update is currently being downloaded + return { message: explicit ? 'Currently downloading update...' : null } as const + } + + if (this._updateService.state.type === StateType.Downloaded) { + // Update has been downloaded but not yet ready + return { message: explicit ? 'Got download, need to apply...' : null, action: 'apply' } as const + } + + if (this._updateService.state.type === StateType.Updating) { + // Update is being applied + return { message: explicit ? 'Applying update...' : null } as const + } + if (this._updateService.state.type === StateType.Ready) { - return { hasUpdate: true, message: 'Restart Void to update!' } + // Update is ready + return { message: 'Restart Void to update!', action: 'restart' } as const } - const wasAutomaticCheck = !explicit // ignore the first auto check, just use it to call updateService.check() - if (wasAutomaticCheck && this.nIgnores < 1) { - this.nIgnores += 1 - return { hasUpdate: false } as const - } + if (this._updateService.state.type === StateType.Disabled) { + try { + const res = await fetch(`https://updates.voideditor.dev/api/v0/${this._productService.commit}`) + const resJSON = await res.json() - try { - const res = await fetch(`https://updates.voideditor.dev/api/v0/${this._productService.commit}`) - const resJSON = await res.json() + if (!resJSON) return null // null means error - if (!resJSON) return null // null means error + const { hasUpdate, downloadMessage } = resJSON ?? {} + if (hasUpdate === undefined) + return null - const { hasUpdate, downloadMessage } = resJSON ?? {} - if (hasUpdate === undefined) + const after = (downloadMessage || '') + '' + if (hasUpdate) + return { message: after, action: 'reinstall' } as const + return { message: 'Void is up-to-date!' } as const + } + catch (e) { return null + } + } + + return null - const after = (downloadMessage || '') + '' - return { hasUpdate: !!hasUpdate, message: after } - } - catch (e) { - return null - } } }