enable auto updates!

This commit is contained in:
Andrew Pareles 2025-04-11 14:25:40 -07:00
parent a869ddb6f3
commit b8fd40e735
4 changed files with 193 additions and 63 deletions

View file

@ -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

View file

@ -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<VoidCheckUpdateRespose>;
}

View file

@ -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

View file

@ -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<VoidCheckUpdateRespose> {
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
}
}
}