Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { localize } from 'vs/nls';
import { isWindows, isWeb } from 'vs/base/common/platform';
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
id: 'update',
order: 15,
title: localize('updateConfigurationTitle', "Update"),
type: 'object',
properties: {
'update.mode': {
type: 'string',
enum: ['none', 'manual', 'start', 'default'],
default: 'default',
scope: ConfigurationScope.APPLICATION,
description: localize('updateMode', "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service."),
tags: ['usesOnlineServices'],
enumDescriptions: [
localize('none', "Disable updates."),
localize('manual', "Disable automatic background update checks. Updates will be available if you manually check for updates."),
localize('start', "Check for updates only on startup. Disable automatic background update checks."),
localize('default', "Enable automatic update checks. Code will check for updates automatically and periodically.")
]
},
'update.channel': {
type: 'string',
default: 'default',
scope: ConfigurationScope.APPLICATION,
description: localize('updateMode', "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service."),
deprecationMessage: localize('deprecated', "This setting is deprecated, please use '{0}' instead.", 'update.mode')
},
'update.enableWindowsBackgroundUpdates': {
type: 'boolean',
default: true,
scope: ConfigurationScope.APPLICATION,
title: localize('enableWindowsBackgroundUpdatesTitle', "Enable Background Updates on Windows"),
description: localize('enableWindowsBackgroundUpdates', "Enable to download and install new VS Code Versions in the background on Windows"),
included: isWindows && !isWeb
},
'update.showReleaseNotes': {
type: 'boolean',
default: true,
scope: ConfigurationScope.APPLICATION,
description: localize('showReleaseNotes', "Show Release Notes after an update. The Release Notes are fetched from a Microsoft online service."),
tags: ['usesOnlineServices']
}
}
});

View File

@@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export interface IUpdate {
version: string;
productVersion: string;
supportsFastUpdate?: boolean;
url?: string;
hash?: string;
}
/**
* Updates are run as a state machine:
*
* Uninitialized
* ↓
* Idle
* ↓ ↑
* Checking for Updates → Available for Download
* ↓
* Downloading → Ready
* ↓ ↑
* Downloaded → Updating
*
* Available: There is an update available for download (linux).
* Ready: Code will be updated as soon as it restarts (win32, darwin).
* Donwloaded: There is an update ready to be installed in the background (win32).
*/
export const enum StateType {
Uninitialized = 'uninitialized',
Idle = 'idle',
CheckingForUpdates = 'checking for updates',
AvailableForDownload = 'available for download',
Downloading = 'downloading',
Downloaded = 'downloaded',
Updating = 'updating',
Ready = 'ready',
}
export const enum UpdateType {
Setup,
Archive,
Snap
}
export type Uninitialized = { type: StateType.Uninitialized };
export type Idle = { type: StateType.Idle, updateType: UpdateType, error?: string };
export type CheckingForUpdates = { type: StateType.CheckingForUpdates, context: any };
export type AvailableForDownload = { type: StateType.AvailableForDownload, update: IUpdate };
export type Downloading = { type: StateType.Downloading, update: IUpdate };
export type Downloaded = { type: StateType.Downloaded, update: IUpdate };
export type Updating = { type: StateType.Updating, update: IUpdate };
export type Ready = { type: StateType.Ready, update: IUpdate };
export type State = Uninitialized | Idle | CheckingForUpdates | AvailableForDownload | Downloading | Downloaded | Updating | Ready;
export const State = {
Uninitialized: { type: StateType.Uninitialized } as Uninitialized,
Idle: (updateType: UpdateType, error?: string) => ({ type: StateType.Idle, updateType, error }) as Idle,
CheckingForUpdates: (context: any) => ({ type: StateType.CheckingForUpdates, context } as CheckingForUpdates),
AvailableForDownload: (update: IUpdate) => ({ type: StateType.AvailableForDownload, update } as AvailableForDownload),
Downloading: (update: IUpdate) => ({ type: StateType.Downloading, update } as Downloading),
Downloaded: (update: IUpdate) => ({ type: StateType.Downloaded, update } as Downloaded),
Updating: (update: IUpdate) => ({ type: StateType.Updating, update } as Updating),
Ready: (update: IUpdate) => ({ type: StateType.Ready, update } as Ready),
};
export interface IAutoUpdater extends Event.NodeEventEmitter {
setFeedURL(url: string): void;
checkForUpdates(): void;
applyUpdate?(): Promise<void>;
quitAndInstall(): void;
}
export const IUpdateService = createDecorator<IUpdateService>('updateService');
export interface IUpdateService {
readonly _serviceBrand: undefined;
readonly onStateChange: Event<State>;
readonly state: State;
checkForUpdates(context: any): Promise<void>;
downloadUpdate(): Promise<void>;
applyUpdate(): Promise<void>;
quitAndInstall(): Promise<void>;
isLatestVersion(): Promise<boolean | undefined>;
}

View File

@@ -0,0 +1,205 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { timeout } from 'vs/base/common/async';
import { IConfigurationService, getMigratedSettingValue } from 'vs/platform/configuration/common/configuration';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import product from 'vs/platform/product/common/product';
import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { ILogService } from 'vs/platform/log/common/log';
import { IRequestService } from 'vs/platform/request/common/request';
import { CancellationToken } from 'vs/base/common/cancellation';
export function createUpdateURL(platform: string, quality: string): string {
return `${product.updateUrl}/api/update/${platform}/${quality}/${product.commit}`;
}
export type UpdateNotAvailableClassification = {
explicit: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
export abstract class AbstractUpdateService implements IUpdateService {
declare readonly _serviceBrand: undefined;
protected url: string | undefined;
private _state: State = State.Uninitialized;
private readonly _onStateChange = new Emitter<State>();
readonly onStateChange: Event<State> = this._onStateChange.event;
get state(): State {
return this._state;
}
protected setState(state: State): void {
this.logService.info('update#setState', state.type);
this._state = state;
this._onStateChange.fire(state);
}
constructor(
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@IConfigurationService protected configurationService: IConfigurationService,
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
@IRequestService protected requestService: IRequestService,
@ILogService protected logService: ILogService,
) { }
/**
* This must be called before any other call. This is a performance
* optimization, to avoid using extra CPU cycles before first window open.
* https://github.com/microsoft/vscode/issues/89784
*/
initialize(): void {
if (!this.environmentService.isBuilt) {
return; // updates are never enabled when running out of sources
}
if (this.environmentService.disableUpdates) {
this.logService.info('update#ctor - updates are disabled by the environment');
return;
}
if (!product.updateUrl || !product.commit) {
this.logService.info('update#ctor - updates are disabled as there is no update URL');
return;
}
const updateMode = getMigratedSettingValue<string>(this.configurationService, 'update.mode', 'update.channel');
const quality = this.getProductQuality(updateMode);
if (!quality) {
this.logService.info('update#ctor - updates are disabled by user preference');
return;
}
this.url = this.buildUpdateFeedUrl(quality);
if (!this.url) {
this.logService.info('update#ctor - updates are disabled as the update URL is badly formed');
return;
}
this.setState(State.Idle(this.getUpdateType()));
if (updateMode === 'manual') {
this.logService.info('update#ctor - manual checks only; automatic updates are disabled by user preference');
return;
}
if (updateMode === 'start') {
this.logService.info('update#ctor - startup checks only; automatic updates are disabled by user preference');
// Check for updates only once after 30 seconds
setTimeout(() => this.checkForUpdates(null), 30 * 1000);
} else {
// Start checking for updates after 30 seconds
this.scheduleCheckForUpdates(30 * 1000).then(undefined, err => this.logService.error(err));
}
}
private getProductQuality(updateMode: string): string | undefined {
return updateMode === 'none' ? undefined : product.quality;
}
private scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise<void> {
return timeout(delay)
.then(() => this.checkForUpdates(null))
.then(() => {
// Check again after 1 hour
return this.scheduleCheckForUpdates(60 * 60 * 1000);
});
}
async checkForUpdates(context: any): Promise<void> {
this.logService.trace('update#checkForUpdates, state = ', this.state.type);
if (this.state.type !== StateType.Idle) {
return;
}
this.doCheckForUpdates(context);
}
async downloadUpdate(): Promise<void> {
this.logService.trace('update#downloadUpdate, state = ', this.state.type);
if (this.state.type !== StateType.AvailableForDownload) {
return;
}
await this.doDownloadUpdate(this.state);
}
protected async doDownloadUpdate(state: AvailableForDownload): Promise<void> {
// noop
}
async applyUpdate(): Promise<void> {
this.logService.trace('update#applyUpdate, state = ', this.state.type);
if (this.state.type !== StateType.Downloaded) {
return;
}
await this.doApplyUpdate();
}
protected async doApplyUpdate(): Promise<void> {
// noop
}
quitAndInstall(): Promise<void> {
this.logService.trace('update#quitAndInstall, state = ', this.state.type);
if (this.state.type !== StateType.Ready) {
return Promise.resolve(undefined);
}
this.logService.trace('update#quitAndInstall(): before lifecycle quit()');
this.lifecycleMainService.quit(true /* from update */).then(vetod => {
this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`);
if (vetod) {
return;
}
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
this.doQuitAndInstall();
});
return Promise.resolve(undefined);
}
isLatestVersion(): Promise<boolean | undefined> {
if (!this.url) {
return Promise.resolve(undefined);
}
return this.requestService.request({ url: this.url }, CancellationToken.None).then(context => {
// The update server replies with 204 (No Content) when no
// update is available - that's all we want to know.
if (context.res.statusCode === 204) {
return true;
} else {
return false;
}
});
}
protected getUpdateType(): UpdateType {
return UpdateType.Archive;
}
protected doQuitAndInstall(): void {
// noop
}
protected abstract buildUpdateFeedUrl(quality: string): string | undefined;
protected abstract doCheckForUpdates(context: any): void;
}

View File

@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { IUpdateService } from 'vs/platform/update/common/update';
export class UpdateChannel implements IServerChannel {
constructor(private service: IUpdateService) { }
listen(_: unknown, event: string): Event<any> {
switch (event) {
case 'onStateChange': return this.service.onStateChange;
}
throw new Error(`Event not found: ${event}`);
}
call(_: unknown, command: string, arg?: any): Promise<any> {
switch (command) {
case 'checkForUpdates': return this.service.checkForUpdates(arg);
case 'downloadUpdate': return this.service.downloadUpdate();
case 'applyUpdate': return this.service.applyUpdate();
case 'quitAndInstall': return this.service.quitAndInstall();
case '_getInitialState': return Promise.resolve(this.service.state);
case 'isLatestVersion': return this.service.isLatestVersion();
}
throw new Error(`Call not found: ${command}`);
}
}

View File

@@ -0,0 +1,113 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as electron from 'electron';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { memoize } from 'vs/base/common/decorators';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { State, IUpdate, StateType, UpdateType } from 'vs/platform/update/common/update';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { ILogService } from 'vs/platform/log/common/log';
import { AbstractUpdateService, createUpdateURL, UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService';
import { IRequestService } from 'vs/platform/request/common/request';
export class DarwinUpdateService extends AbstractUpdateService {
declare readonly _serviceBrand: undefined;
private readonly disposables = new DisposableStore();
@memoize private get onRawError(): Event<string> { return Event.fromNodeEventEmitter(electron.autoUpdater, 'error', (_, message) => message); }
@memoize private get onRawUpdateNotAvailable(): Event<void> { return Event.fromNodeEventEmitter<void>(electron.autoUpdater, 'update-not-available'); }
@memoize private get onRawUpdateAvailable(): Event<IUpdate> { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-available', (_, url, version) => ({ url, version, productVersion: version })); }
@memoize private get onRawUpdateDownloaded(): Event<IUpdate> { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, releaseNotes, version, date) => ({ releaseNotes, version, productVersion: version, date })); }
constructor(
@ILifecycleMainService lifecycleMainService: ILifecycleMainService,
@IConfigurationService configurationService: IConfigurationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IEnvironmentMainService environmentService: IEnvironmentMainService,
@IRequestService requestService: IRequestService,
@ILogService logService: ILogService
) {
super(lifecycleMainService, configurationService, environmentService, requestService, logService);
}
initialize(): void {
super.initialize();
this.onRawError(this.onError, this, this.disposables);
this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables);
this.onRawUpdateDownloaded(this.onUpdateDownloaded, this, this.disposables);
this.onRawUpdateNotAvailable(this.onUpdateNotAvailable, this, this.disposables);
}
private onError(err: string): void {
this.logService.error('UpdateService error:', err);
// only show message when explicitly checking for updates
const shouldShowMessage = this.state.type === StateType.CheckingForUpdates ? !!this.state.context : true;
const message: string | undefined = shouldShowMessage ? err : undefined;
this.setState(State.Idle(UpdateType.Archive, message));
}
protected buildUpdateFeedUrl(quality: string): string | undefined {
const url = createUpdateURL('darwin', quality);
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 doCheckForUpdates(context: any): void {
this.setState(State.CheckingForUpdates(context));
electron.autoUpdater.checkForUpdates();
}
private onUpdateAvailable(update: IUpdate): void {
if (this.state.type !== StateType.CheckingForUpdates) {
return;
}
this.setState(State.Downloading(update));
}
private onUpdateDownloaded(update: IUpdate): void {
if (this.state.type !== StateType.Downloading) {
return;
}
type UpdateDownloadedClassification = {
version: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
this.telemetryService.publicLog2<{ version: String }, UpdateDownloadedClassification>('update:downloaded', { version: update.version });
this.setState(State.Ready(update));
}
private onUpdateNotAvailable(): void {
if (this.state.type !== StateType.CheckingForUpdates) {
return;
}
this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: !!this.state.context });
this.setState(State.Idle(UpdateType.Archive));
}
protected doQuitAndInstall(): void {
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
electron.autoUpdater.quitAndInstall();
}
dispose(): void {
this.disposables.dispose();
}
}

View File

@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import product from 'vs/platform/product/common/product';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { State, IUpdate, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { ILogService } from 'vs/platform/log/common/log';
import { createUpdateURL, AbstractUpdateService, UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService';
import { IRequestService, asJson } from 'vs/platform/request/common/request';
import { CancellationToken } from 'vs/base/common/cancellation';
import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
export class LinuxUpdateService extends AbstractUpdateService {
declare readonly _serviceBrand: undefined;
constructor(
@ILifecycleMainService lifecycleMainService: ILifecycleMainService,
@IConfigurationService configurationService: IConfigurationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IEnvironmentMainService environmentService: IEnvironmentMainService,
@IRequestService requestService: IRequestService,
@ILogService logService: ILogService,
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService
) {
super(lifecycleMainService, configurationService, environmentService, requestService, logService);
}
protected buildUpdateFeedUrl(quality: string): string {
return createUpdateURL(`linux-${process.arch}`, quality);
}
protected doCheckForUpdates(context: any): void {
if (!this.url) {
return;
}
this.setState(State.CheckingForUpdates(context));
this.requestService.request({ url: this.url }, CancellationToken.None)
.then<IUpdate | null>(asJson)
.then(update => {
if (!update || !update.url || !update.version || !update.productVersion) {
this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: !!context });
this.setState(State.Idle(UpdateType.Archive));
} else {
this.setState(State.AvailableForDownload(update));
}
})
.then(undefined, err => {
this.logService.error(err);
this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: !!context });
// only show message when explicitly checking for updates
const message: string | undefined = !!context ? (err.message || err) : undefined;
this.setState(State.Idle(UpdateType.Archive, message));
});
}
protected async doDownloadUpdate(state: AvailableForDownload): Promise<void> {
// Use the download URL if available as we don't currently detect the package type that was
// installed and the website download page is more useful than the tarball generally.
if (product.downloadUrl && product.downloadUrl.length > 0) {
this.nativeHostMainService.openExternal(undefined, product.downloadUrl);
} else if (state.update.url) {
this.nativeHostMainService.openExternal(undefined, state.update.url);
}
this.setState(State.Idle(UpdateType.Archive));
}
}

View File

@@ -0,0 +1,201 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { timeout } from 'vs/base/common/async';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { ILogService } from 'vs/platform/log/common/log';
import * as path from 'vs/base/common/path';
import { realpath, watch } from 'fs';
import { spawn } from 'child_process';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService';
abstract class AbstractUpdateService2 implements IUpdateService {
declare readonly _serviceBrand: undefined;
private _state: State = State.Uninitialized;
private readonly _onStateChange = new Emitter<State>();
readonly onStateChange: Event<State> = this._onStateChange.event;
get state(): State {
return this._state;
}
protected setState(state: State): void {
this.logService.info('update#setState', state.type);
this._state = state;
this._onStateChange.fire(state);
}
constructor(
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@IEnvironmentMainService environmentService: IEnvironmentMainService,
@ILogService protected logService: ILogService,
) {
if (environmentService.disableUpdates) {
this.logService.info('update#ctor - updates are disabled');
return;
}
this.setState(State.Idle(this.getUpdateType()));
// Start checking for updates after 30 seconds
this.scheduleCheckForUpdates(30 * 1000).then(undefined, err => this.logService.error(err));
}
private scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise<void> {
return timeout(delay)
.then(() => this.checkForUpdates(null))
.then(() => {
// Check again after 1 hour
return this.scheduleCheckForUpdates(60 * 60 * 1000);
});
}
async checkForUpdates(context: any): Promise<void> {
this.logService.trace('update#checkForUpdates, state = ', this.state.type);
if (this.state.type !== StateType.Idle) {
return;
}
this.doCheckForUpdates(context);
}
async downloadUpdate(): Promise<void> {
this.logService.trace('update#downloadUpdate, state = ', this.state.type);
if (this.state.type !== StateType.AvailableForDownload) {
return;
}
await this.doDownloadUpdate(this.state);
}
protected doDownloadUpdate(state: AvailableForDownload): Promise<void> {
return Promise.resolve(undefined);
}
async applyUpdate(): Promise<void> {
this.logService.trace('update#applyUpdate, state = ', this.state.type);
if (this.state.type !== StateType.Downloaded) {
return;
}
await this.doApplyUpdate();
}
protected doApplyUpdate(): Promise<void> {
return Promise.resolve(undefined);
}
quitAndInstall(): Promise<void> {
this.logService.trace('update#quitAndInstall, state = ', this.state.type);
if (this.state.type !== StateType.Ready) {
return Promise.resolve(undefined);
}
this.logService.trace('update#quitAndInstall(): before lifecycle quit()');
this.lifecycleMainService.quit(true /* from update */).then(vetod => {
this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`);
if (vetod) {
return;
}
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
this.doQuitAndInstall();
});
return Promise.resolve(undefined);
}
protected getUpdateType(): UpdateType {
return UpdateType.Snap;
}
protected doQuitAndInstall(): void {
// noop
}
abstract isLatestVersion(): Promise<boolean | undefined>;
protected abstract doCheckForUpdates(context: any): void;
}
export class SnapUpdateService extends AbstractUpdateService2 {
declare readonly _serviceBrand: undefined;
constructor(
private snap: string,
private snapRevision: string,
@ILifecycleMainService lifecycleMainService: ILifecycleMainService,
@IEnvironmentMainService environmentService: IEnvironmentMainService,
@ILogService logService: ILogService,
@ITelemetryService private readonly telemetryService: ITelemetryService
) {
super(lifecycleMainService, environmentService, logService);
const watcher = watch(path.dirname(this.snap));
const onChange = Event.fromNodeEventEmitter(watcher, 'change', (_, fileName: string) => fileName);
const onCurrentChange = Event.filter(onChange, n => n === 'current');
const onDebouncedCurrentChange = Event.debounce(onCurrentChange, (_, e) => e, 2000);
const listener = onDebouncedCurrentChange(this.checkForUpdates, this);
lifecycleMainService.onWillShutdown(() => {
listener.dispose();
watcher.close();
});
}
protected doCheckForUpdates(context: any): void {
this.setState(State.CheckingForUpdates(context));
this.isUpdateAvailable().then(result => {
if (result) {
this.setState(State.Ready({ version: 'something', productVersion: 'something' }));
} else {
this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: !!context });
this.setState(State.Idle(UpdateType.Snap));
}
}, err => {
this.logService.error(err);
this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: !!context });
this.setState(State.Idle(UpdateType.Snap, err.message || err));
});
}
protected doQuitAndInstall(): void {
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
// Allow 3 seconds for VS Code to close
spawn('sleep 3 && ' + path.basename(process.argv[0]), {
shell: true,
detached: true,
stdio: 'ignore',
});
}
private async isUpdateAvailable(): Promise<boolean> {
const resolvedCurrentSnapPath = await new Promise<string>((c, e) => realpath(`${path.dirname(this.snap)}/current`, (err, r) => err ? e(err) : c(r)));
const currentRevision = path.basename(resolvedCurrentSnapPath);
return this.snapRevision !== currentRevision;
}
isLatestVersion(): Promise<boolean | undefined> {
return this.isUpdateAvailable().then(undefined, err => {
this.logService.error('update#checkForSnapUpdate(): Could not get realpath of application.');
return undefined;
});
}
}

View File

@@ -0,0 +1,264 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as path from 'vs/base/common/path';
import * as pfs from 'vs/base/node/pfs';
import { memoize } from 'vs/base/common/decorators';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import product from 'vs/platform/product/common/product';
import { State, IUpdate, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { ILogService } from 'vs/platform/log/common/log';
import { createUpdateURL, AbstractUpdateService, UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService';
import { IRequestService, asJson } from 'vs/platform/request/common/request';
import { checksum } from 'vs/base/node/crypto';
import { tmpdir } from 'os';
import { spawn } from 'child_process';
import { CancellationToken } from 'vs/base/common/cancellation';
import { timeout } from 'vs/base/common/async';
import { IFileService } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService';
async function pollUntil(fn: () => boolean, millis = 1000): Promise<void> {
while (!fn()) {
await timeout(millis);
}
}
interface IAvailableUpdate {
packagePath: string;
updateFilePath?: string;
}
let _updateType: UpdateType | undefined = undefined;
function getUpdateType(): UpdateType {
if (typeof _updateType === 'undefined') {
_updateType = fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))
? UpdateType.Setup
: UpdateType.Archive;
}
return _updateType;
}
export class Win32UpdateService extends AbstractUpdateService {
declare readonly _serviceBrand: undefined;
private availableUpdate: IAvailableUpdate | undefined;
@memoize
get cachePath(): Promise<string> {
const result = path.join(tmpdir(), `vscode-update-${product.target}-${process.arch}`);
return pfs.mkdirp(result, undefined).then(() => result);
}
constructor(
@ILifecycleMainService lifecycleMainService: ILifecycleMainService,
@IConfigurationService configurationService: IConfigurationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IEnvironmentMainService environmentService: IEnvironmentMainService,
@IRequestService requestService: IRequestService,
@ILogService logService: ILogService,
@IFileService private readonly fileService: IFileService,
@INativeHostMainService private readonly nativeHostMainService: INativeHostMainService
) {
super(lifecycleMainService, configurationService, environmentService, requestService, logService);
}
initialize(): void {
super.initialize();
if (getUpdateType() === UpdateType.Setup) {
/* __GDPR__
"update:win32SetupTarget" : {
"target" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
/* __GDPR__
"update:win<NUMBER>SetupTarget" : {
"target" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('update:win32SetupTarget', { target: product.target });
}
}
protected buildUpdateFeedUrl(quality: string): string | undefined {
let platform = 'win32';
if (process.arch !== 'ia32') {
platform += `-${process.arch}`;
}
if (getUpdateType() === UpdateType.Archive) {
platform += '-archive';
} else if (product.target === 'user') {
platform += '-user';
}
return createUpdateURL(platform, quality);
}
protected doCheckForUpdates(context: any): void {
if (!this.url) {
return;
}
this.setState(State.CheckingForUpdates(context));
this.requestService.request({ url: this.url }, CancellationToken.None)
.then<IUpdate | null>(asJson)
.then(update => {
const updateType = getUpdateType();
if (!update || !update.url || !update.version || !update.productVersion) {
this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: !!context });
this.setState(State.Idle(updateType));
return Promise.resolve(null);
}
if (updateType === UpdateType.Archive) {
this.setState(State.AvailableForDownload(update));
return Promise.resolve(null);
}
this.setState(State.Downloading(update));
return this.cleanup(update.version).then(() => {
return this.getUpdatePackagePath(update.version).then(updatePackagePath => {
return pfs.exists(updatePackagePath).then(exists => {
if (exists) {
return Promise.resolve(updatePackagePath);
}
const url = update.url;
const hash = update.hash;
const downloadPath = `${updatePackagePath}.tmp`;
return this.requestService.request({ url }, CancellationToken.None)
.then(context => this.fileService.writeFile(URI.file(downloadPath), context.stream))
.then(hash ? () => checksum(downloadPath, update.hash) : () => undefined)
.then(() => pfs.rename(downloadPath, updatePackagePath))
.then(() => updatePackagePath);
});
}).then(packagePath => {
const fastUpdatesEnabled = this.configurationService.getValue<boolean>('update.enableWindowsBackgroundUpdates');
this.availableUpdate = { packagePath };
if (fastUpdatesEnabled && update.supportsFastUpdate) {
if (product.target === 'user') {
this.doApplyUpdate();
} else {
this.setState(State.Downloaded(update));
}
} else {
this.setState(State.Ready(update));
}
});
});
})
.then(undefined, err => {
this.logService.error(err);
this.telemetryService.publicLog2<{ explicit: boolean }, UpdateNotAvailableClassification>('update:notAvailable', { explicit: !!context });
// only show message when explicitly checking for updates
const message: string | undefined = !!context ? (err.message || err) : undefined;
this.setState(State.Idle(getUpdateType(), message));
});
}
protected async doDownloadUpdate(state: AvailableForDownload): Promise<void> {
if (state.update.url) {
this.nativeHostMainService.openExternal(undefined, state.update.url);
}
this.setState(State.Idle(getUpdateType()));
}
private async getUpdatePackagePath(version: string): Promise<string> {
const cachePath = await this.cachePath;
return path.join(cachePath, `CodeSetup-${product.quality}-${version}.exe`);
}
private async cleanup(exceptVersion: string | null = null): Promise<any> {
const filter = exceptVersion ? (one: string) => !(new RegExp(`${product.quality}-${exceptVersion}\\.exe$`).test(one)) : () => true;
const cachePath = await this.cachePath;
const versions = await pfs.readdir(cachePath);
const promises = versions.filter(filter).map(async one => {
try {
await pfs.unlink(path.join(cachePath, one));
} catch (err) {
// ignore
}
});
await Promise.all(promises);
}
protected async doApplyUpdate(): Promise<void> {
if (this.state.type !== StateType.Downloaded && this.state.type !== StateType.Downloading) {
return Promise.resolve(undefined);
}
if (!this.availableUpdate) {
return Promise.resolve(undefined);
}
const update = this.state.update;
this.setState(State.Updating(update));
const cachePath = await this.cachePath;
this.availableUpdate.updateFilePath = path.join(cachePath, `CodeSetup-${product.quality}-${update.version}.flag`);
await pfs.writeFile(this.availableUpdate.updateFilePath, 'flag');
const child = spawn(this.availableUpdate.packagePath, ['/verysilent', `/update="${this.availableUpdate.updateFilePath}"`, '/nocloseapplications', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore'],
windowsVerbatimArguments: true
});
child.once('exit', () => {
this.availableUpdate = undefined;
this.setState(State.Idle(getUpdateType()));
});
const readyMutexName = `${product.win32MutexName}-ready`;
const mutex = await import('windows-mutex');
// poll for mutex-ready
pollUntil(() => mutex.isActive(readyMutexName))
.then(() => this.setState(State.Ready(update)));
}
protected doQuitAndInstall(): void {
if (this.state.type !== StateType.Ready || !this.availableUpdate) {
return;
}
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
if (this.state.update.supportsFastUpdate && this.availableUpdate.updateFilePath) {
fs.unlinkSync(this.availableUpdate.updateFilePath);
} else {
spawn(this.availableUpdate.packagePath, ['/silent', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore']
});
}
}
protected getUpdateType(): UpdateType {
return getUpdateType();
}
}