diff --git a/src/vs/platform/update/common/update.ts b/src/vs/platform/update/common/update.ts index b859dfd..75e8987 100644 --- a/src/vs/platform/update/common/update.ts +++ b/src/vs/platform/update/common/update.ts @@ -54,3 +54,4 @@ export const enum UpdateType { Archive, - Snap + Snap, + WindowsInstaller, } @@ -116 +117,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 0e63d40..c8426ea 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -14,3 +14,3 @@ 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'; @@ -20,10 +20,8 @@ export interface IUpdateURLOptions { -export function createUpdateURL(baseUpdateUrl: string, platform: string, quality: string, commit: string, options?: IUpdateURLOptions): string { - const url = new URL(`${baseUpdateUrl}/api/update/${platform}/${quality}/${commit}`); - - if (options?.background) { - url.searchParams.set('bg', 'true'); +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`; } - - return url.toString(); } @@ -265,3 +263,3 @@ export abstract class AbstractUpdateService implements IUpdateService { - if (mode === 'none') { + if (mode === 'none' || mode === 'manual') { return undefined; diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index b20673b..af677f6 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, IUpdateURLOptions, UpdateErrorClassification } from './abstractUpdateService.js'; +import { CancellationToken } from '../../../base/common/cancellation.js'; +import * as semver from 'semver'; @@ -75,13 +77,4 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau - protected buildUpdateFeedUrl(quality: string, commit: string, options?: IUpdateURLOptions): string | undefined { - const assetID = this.productService.darwinUniversalAssetId ?? (process.arch === 'x64' ? 'darwin' : 'darwin-arm64'); - const url = createUpdateURL(this.productService.updateUrl!, assetID, quality, commit, options); - 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; + protected buildUpdateFeedUrl(quality: string, _commit: string, _options?: IUpdateURLOptions): string | undefined { + return createUpdateURL(this.productService, quality, process.platform, process.arch); } @@ -112,3 +105,30 @@ export class DarwinUpdateService extends AbstractUpdateService implements IRelau - electron.autoUpdater.checkForUpdates(); + this.requestService.request({ 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 }); + 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 32040dc..59cf109 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, IUpdateURLOptions } from './abstractUpdateService.js'; +import * as semver from 'semver'; @@ -30,4 +31,4 @@ export class LinuxUpdateService extends AbstractUpdateService { - protected buildUpdateFeedUrl(quality: string, commit: string, options?: IUpdateURLOptions): string { - return createUpdateURL(this.productService.updateUrl!, `linux-${process.arch}`, quality, commit, options); + protected buildUpdateFeedUrl(quality: string, _commit: string, _options?: IUpdateURLOptions): string { + return createUpdateURL(this.productService, quality, process.platform, process.arch); } @@ -48,5 +49,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 3edbd9d..6666876 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -13,3 +13,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'; @@ -27,4 +26,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, IUpdateURLOptions, UpdateErrorClassification } from './abstractUpdateService.js'; +import { AvailableForDownload, DisablementReason, IUpdate, State, StateType, Target, UpdateType } from '../common/update.js'; +import { AbstractUpdateService, createUpdateURL, IUpdateURLOptions } from './abstractUpdateService.js'; +import * as semver from 'semver'; @@ -44,5 +44,9 @@ function getUpdateType(): UpdateType { if (typeof _updateType === 'undefined') { - _updateType = existsSync(path.join(path.dirname(process.execPath), 'unins000.exe')) - ? UpdateType.Setup - : UpdateType.Archive; + if (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; + } } @@ -65,2 +69,3 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun @IConfigurationService configurationService: IConfigurationService, + // @ts-expect-error @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -154,12 +159,22 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun - protected buildUpdateFeedUrl(quality: string, commit: string, options?: IUpdateURLOptions): string | undefined { - let platform = `win32-${process.arch}`; - - if (getUpdateType() === UpdateType.Archive) { - platform += '-archive'; - } else if (this.productService.target === 'user') { - platform += '-user'; - } + protected buildUpdateFeedUrl(quality: string, _commit: string, _options?: IUpdateURLOptions): string | undefined { + 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(this.productService.updateUrl!, platform, quality, commit, options); + return createUpdateURL(this.productService, quality, process.platform, process.arch, target); } @@ -185,2 +200,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) { @@ -211,3 +234,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) { @@ -223,3 +246,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); @@ -309,6 +331,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'] + }); + } }