diff --git a/src/vs/platform/update/common/update.ts b/src/vs/platform/update/common/update.ts index 199f433..a6cbb10 100644 --- a/src/vs/platform/update/common/update.ts +++ b/src/vs/platform/update/common/update.ts @@ -51,3 +51,4 @@ export const enum UpdateType { Archive, - Snap + Snap, + WindowsInstaller, } @@ -110 +111,38 @@ export interface IUpdateService { } + +export type Architecture = + | "arm" + | "arm64" + | "ia32" + | "loong64" + | "mips" + | "mipsel" + | "ppc" + | "ppc64" + | "riscv64" + | "s390" + | "s390x" + | "x64"; + +export type Platform = + | "aix" + | "android" + | "darwin" + | "freebsd" + | "haiku" + | "linux" + | "openbsd" + | "sunos" + | "win32" + | "cygwin" + | "netbsd"; + +export type Quality = + | "insider" + | "stable"; + +export type Target = + | "archive" + | "msi" + | "system" + | "user"; \ No newline at end of file diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 48d0d86..840b9d3 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -14,6 +14,10 @@ import { IProductService } from '../../product/common/productService.js'; import { IRequestService } from '../../request/common/request.js'; -import { AvailableForDownload, DisablementReason, IUpdateService, State, StateType, UpdateType } from '../common/update.js'; +import { Architecture, AvailableForDownload, DisablementReason, IUpdateService, Platform, State, StateType, Target, UpdateType } from '../common/update.js'; -export function createUpdateURL(platform: string, quality: string, productService: IProductService): string { - return `${productService.updateUrl}/api/update/${platform}/${quality}/${productService.commit}`; +export function createUpdateURL(productService: IProductService, quality: string, platform: Platform, architecture: Architecture, target?: Target): string { + if (target) { + return `${productService.updateUrl}/${quality}/${platform}/${architecture}/${target}/latest.json`; + } else { + return `${productService.updateUrl}/${quality}/${platform}/${architecture}/latest.json`; + } } diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index b78ebc5..a4a3b1d 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -15,3 +15,3 @@ import { ILogService } from '../../log/common/log.js'; import { IProductService } from '../../product/common/productService.js'; -import { IRequestService } from '../../request/common/request.js'; +import { IRequestService, asJson } from '../../request/common/request.js'; import { ITelemetryService } from '../../telemetry/common/telemetry.js'; @@ -19,2 +19,4 @@ import { IUpdate, State, StateType, UpdateType } from '../common/update.js'; import { AbstractUpdateService, createUpdateURL, UpdateErrorClassification } from './abstractUpdateService.js'; +import { CancellationToken } from '../../../base/common/cancellation.js'; +import * as semver from 'semver'; @@ -76,17 +78,3 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau protected buildUpdateFeedUrl(quality: string): string | undefined { - let assetID: string; - if (!this.productService.darwinUniversalAssetId) { - assetID = process.arch === 'x64' ? 'darwin' : 'darwin-arm64'; - } else { - assetID = this.productService.darwinUniversalAssetId; - } - const url = createUpdateURL(assetID, quality, this.productService); - try { - electron.autoUpdater.setFeedURL({ url }); - } catch (e) { - // application is very likely not signed - this.logService.error('Failed to set update feed URL', e); - return undefined; - } - return url; + return createUpdateURL(this.productService, quality, process.platform, process.arch); } @@ -100,5 +88,30 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau - const url = explicit ? this.url : `${this.url}?bg=true`; - electron.autoUpdater.setFeedURL({ url }); - electron.autoUpdater.checkForUpdates(); + this.requestService.request({ url: this.url }, CancellationToken.None) + .then(asJson) + .then(update => { + if (!update || !update.url || !update.version || !update.productVersion) { + this.setState(State.Idle(UpdateType.Setup)); + + return Promise.resolve(null); + } + + const fetchedVersion = /\d+\.\d+\.\d+\.\d+/.test(update.productVersion) ? update.productVersion.replace(/(\d+\.\d+\.\d+)\.\d+(\-\w+)?/, '$1$2') : update.productVersion.replace(/(\d+\.\d+\.)0+(\d+)(\-\w+)?/, '$1$2$3') + const currentVersion = this.productService.version.replace(/(\d+\.\d+\.)0+(\d+)(\-\w+)?/, '$1$2$3') + + if(semver.compareBuild(currentVersion, fetchedVersion) >= 0) { + this.setState(State.Idle(UpdateType.Setup)); + } + else { + electron.autoUpdater.setFeedURL({ url: this.url! }); + electron.autoUpdater.checkForUpdates(); + } + + return Promise.resolve(null); + }) + .then(undefined, err => { + this.logService.error(err); + // only show message when explicitly checking for updates + const message: string | undefined = explicit ? (err.message || err) : undefined; + this.setState(State.Idle(UpdateType.Setup, message)); + }); } diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index 8550ace..c2fddcb 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -15,2 +15,3 @@ import { AvailableForDownload, IUpdate, State, UpdateType } from '../common/upda import { AbstractUpdateService, createUpdateURL } from './abstractUpdateService.js'; +import * as semver from 'semver'; @@ -31,3 +32,3 @@ export class LinuxUpdateService extends AbstractUpdateService { protected buildUpdateFeedUrl(quality: string): string { - return createUpdateURL(`linux-${process.arch}`, quality, this.productService); + return createUpdateURL(this.productService, quality, process.platform, process.arch); } @@ -39,6 +40,5 @@ export class LinuxUpdateService extends AbstractUpdateService { - const url = explicit ? this.url : `${this.url}?bg=true`; this.setState(State.CheckingForUpdates(explicit)); - this.requestService.request({ url }, CancellationToken.None) + this.requestService.request({ url: this.url }, CancellationToken.None) .then(asJson) @@ -47,5 +47,17 @@ export class LinuxUpdateService extends AbstractUpdateService { this.setState(State.Idle(UpdateType.Archive)); - } else { + + return Promise.resolve(null); + } + + const fetchedVersion = /\d+\.\d+\.\d+\.\d+/.test(update.productVersion) ? update.productVersion.replace(/(\d+\.\d+\.\d+)\.\d+(\-\w+)?/, '$1$2') : update.productVersion.replace(/(\d+\.\d+\.)0+(\d+)(\-\w+)?/, '$1$2$3') + const currentVersion = this.productService.version.replace(/(\d+\.\d+\.)0+(\d+)(\-\w+)?/, '$1$2$3') + + if(semver.compareBuild(currentVersion, fetchedVersion) >= 0) { + this.setState(State.Idle(UpdateType.Archive)); + } + else { this.setState(State.AvailableForDownload(update)); } + + return Promise.resolve(null); }) diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 8f92a3e..020e690 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -11,3 +11,2 @@ import { CancellationToken } from '../../../base/common/cancellation.js'; import { memoize } from '../../../base/common/decorators.js'; -import { hash } from '../../../base/common/hash.js'; import * as path from '../../../base/common/path.js'; @@ -25,4 +24,5 @@ import { asJson, IRequestService } from '../../request/common/request.js'; import { ITelemetryService } from '../../telemetry/common/telemetry.js'; -import { AvailableForDownload, DisablementReason, IUpdate, State, StateType, UpdateType } from '../common/update.js'; -import { AbstractUpdateService, createUpdateURL, UpdateErrorClassification } from './abstractUpdateService.js'; +import { AvailableForDownload, DisablementReason, IUpdate, State, StateType, Target, UpdateType } from '../common/update.js'; +import { AbstractUpdateService, createUpdateURL} from './abstractUpdateService.js'; +import * as semver from 'semver'; @@ -42,5 +42,9 @@ function getUpdateType(): UpdateType { if (typeof _updateType === 'undefined') { - _updateType = fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe')) - ? UpdateType.Setup - : UpdateType.Archive; + if (fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))) { + _updateType = UpdateType.Setup; + } else if (path.basename(path.normalize(path.join(process.execPath, '..', '..'))) === 'Program Files') { + _updateType = UpdateType.WindowsInstaller; + } else { + _updateType = UpdateType.Archive; + } } @@ -63,2 +67,3 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun @IConfigurationService configurationService: IConfigurationService, + // @ts-expect-error @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -102,11 +107,21 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun protected buildUpdateFeedUrl(quality: string): string | undefined { - let platform = `win32-${process.arch}`; - - if (getUpdateType() === UpdateType.Archive) { - platform += '-archive'; - } else if (this.productService.target === 'user') { - platform += '-user'; + let target: Target; + + switch (getUpdateType()) { + case UpdateType.Archive: + target = "archive" + break; + case UpdateType.WindowsInstaller: + target = "msi" + break; + default: + if (this.productService.target === 'user') { + target = "user" + } + else { + target = "system" + } } - return createUpdateURL(platform, quality, this.productService); + return createUpdateURL(this.productService, quality, process.platform, process.arch, target); } @@ -131,2 +146,10 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun + const fetchedVersion = /\d+\.\d+\.\d+\.\d+/.test(update.productVersion) ? update.productVersion.replace(/(\d+\.\d+\.\d+)\.\d+(\-\w+)?/, '$1$2') : update.productVersion.replace(/(\d+\.\d+\.)0+(\d+)(\-\w+)?/, '$1$2$3') + const currentVersion = this.productService.version.replace(/(\d+\.\d+\.)0+(\d+)(\-\w+)?/, '$1$2$3') + + if(semver.compareBuild(currentVersion, fetchedVersion) >= 0) { + this.setState(State.Idle(updateType)); + return Promise.resolve(null); + } + if (updateType === UpdateType.Archive) { @@ -157,3 +180,3 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun - const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates'); + const fastUpdatesEnabled = getUpdateType() == UpdateType.Setup && this.configurationService.getValue('update.enableWindowsBackgroundUpdates'); if (fastUpdatesEnabled) { @@ -169,3 +192,2 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun .then(undefined, err => { - this.telemetryService.publicLog2<{ messageHash: string }, UpdateErrorClassification>('update:error', { messageHash: String(hash(String(err))) }); this.logService.error(err); @@ -253,6 +275,14 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun } else { - spawn(this.availableUpdate.packagePath, ['/silent', '/log', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { - detached: true, - stdio: ['ignore', 'ignore', 'ignore'] - }); + const type = getUpdateType(); + if (type == UpdateType.WindowsInstaller) { + spawn('msiexec.exe', ['/i', this.availableUpdate.packagePath], { + detached: true, + stdio: ['ignore', 'ignore', 'ignore'] + }); + } else { + spawn(this.availableUpdate.packagePath, ['/silent', '/log', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { + detached: true, + stdio: ['ignore', 'ignore', 'ignore'] + }); + } }