mirror of
https://github.com/Microsoft/vscode
synced 2024-10-04 02:14:06 +00:00
update - move to browser (#80806)
* web update - first cut service * web update - move update contribution to browser * update - tweak settings
This commit is contained in:
parent
5e417f3d22
commit
f6df256c60
|
@ -12,7 +12,7 @@ import { WindowsService } from 'vs/platform/windows/electron-main/windowsService
|
|||
import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { getShellEnvironment } from 'vs/code/node/shellEnv';
|
||||
import { IUpdateService } from 'vs/platform/update/common/update';
|
||||
import { UpdateChannel } from 'vs/platform/update/node/updateIpc';
|
||||
import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc';
|
||||
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main';
|
||||
import { Client } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { Server, connect } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/platform/update/node/update.config.contribution';
|
||||
import 'vs/platform/update/common/update.config.contribution';
|
||||
import { app, dialog } from 'electron';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
|
|
|
@ -12,7 +12,7 @@ const product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/ } as IProductConfigura
|
|||
// Running out of sources
|
||||
if (Object.keys(product).length === 0) {
|
||||
assign(product, {
|
||||
version: '1.38.0-dev',
|
||||
version: '1.39.0-dev',
|
||||
nameLong: 'Visual Studio Code Web Dev',
|
||||
nameShort: 'VSCode Web Dev'
|
||||
});
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
export async function addGAParameters(telemetryService: ITelemetryService, environmentService: IEnvironmentService, uri: URI, origin: string, experiment = '1'): Promise<URI> {
|
||||
if (environmentService.isBuilt && !environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
if (uri.scheme === 'https' && uri.authority === 'code.visualstudio.com') {
|
||||
const info = await telemetryService.getTelemetryInfo();
|
||||
|
||||
return uri.with({ query: `${uri.query ? uri.query + '&' : ''}utm_source=VsCode&utm_medium=${encodeURIComponent(origin)}&utm_campaign=${encodeURIComponent(info.instanceId)}&utm_content=${encodeURIComponent(experiment)}` });
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
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 } from 'vs/base/common/platform';
|
||||
import { isWindows, isWeb } from 'vs/base/common/platform';
|
||||
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
|
@ -42,7 +42,7 @@ configurationRegistry.registerConfiguration({
|
|||
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
|
||||
included: isWindows && !isWeb
|
||||
},
|
||||
'update.showReleaseNotes': {
|
||||
type: 'boolean',
|
|
@ -9,8 +9,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
|
|||
export interface IUpdate {
|
||||
version: string;
|
||||
productVersion: string;
|
||||
date?: Date;
|
||||
releaseNotes?: string;
|
||||
supportsFastUpdate?: boolean;
|
||||
url?: string;
|
||||
hash?: string;
|
||||
|
|
|
@ -10,7 +10,6 @@ import { Event } from 'vs/base/common/event';
|
|||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IUpdateService, State } from 'vs/platform/update/common/update';
|
||||
import { IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IURIToOpen, IMessageBoxResult, IWindowsService, IOpenSettings, IWindowSettings } from 'vs/platform/windows/common/windows';
|
||||
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IRecentlyOpened, IRecent, isRecentFile, isRecentFolder } from 'vs/platform/history/common/history';
|
||||
|
@ -34,40 +33,6 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
|
|||
// tslint:disable-next-line: import-patterns
|
||||
import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats';
|
||||
|
||||
//#region Update
|
||||
|
||||
export class SimpleUpdateService implements IUpdateService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
onStateChange = Event.None;
|
||||
state!: State;
|
||||
|
||||
checkForUpdates(context: any): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
downloadUpdate(): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
applyUpdate(): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
quitAndInstall(): Promise<void> {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
isLatestVersion(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IUpdateService, SimpleUpdateService);
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Window
|
||||
|
||||
export class SimpleWindowService extends Disposable implements IWindowService {
|
||||
|
@ -377,6 +342,7 @@ export class SimpleWindowsService implements IWindowsService {
|
|||
@IClipboardService private readonly clipboardService: IClipboardService
|
||||
) {
|
||||
}
|
||||
|
||||
isFocused(_windowId: number): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
@ -595,7 +561,7 @@ export class SimpleWindowsService implements IWindowsService {
|
|||
}
|
||||
|
||||
getActiveWindowId(): Promise<number | undefined> {
|
||||
return Promise.resolve(undefined);
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
|
||||
// This needs to be handled from browser process to prevent
|
||||
|
|
Before Width: | Height: | Size: 559 B After Width: | Height: | Size: 559 B |
|
@ -18,7 +18,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
|||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IRequestService, asText } from 'vs/platform/request/common/request';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { addGAParameters } from 'vs/platform/telemetry/node/telemetryNodeUtils';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
import { IWebviewEditorService } from 'vs/workbench/contrib/webview/browser/webviewEditorService';
|
||||
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { WebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
|
||||
|
@ -45,7 +45,8 @@ export class ReleaseNotesManager {
|
|||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
|
||||
@IWebviewEditorService private readonly _webviewEditorService: IWebviewEditorService,
|
||||
@IExtensionService private readonly _extensionService: IExtensionService
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
@IProductService private readonly _productService: IProductService
|
||||
) {
|
||||
TokenizationRegistry.onDidChange(async () => {
|
||||
if (!this._currentReleaseNotes || !this._lastText) {
|
||||
|
@ -161,11 +162,22 @@ export class ReleaseNotesManager {
|
|||
}
|
||||
|
||||
private onDidClickLink(uri: URI) {
|
||||
addGAParameters(this._telemetryService, this._environmentService, uri, 'ReleaseNotes')
|
||||
this.addGAParameters(uri, 'ReleaseNotes')
|
||||
.then(updated => this._openerService.open(updated))
|
||||
.then(undefined, onUnexpectedError);
|
||||
}
|
||||
|
||||
private async addGAParameters(uri: URI, origin: string, experiment = '1'): Promise<URI> {
|
||||
if (this._environmentService.isBuilt && !this._environmentService.isExtensionDevelopment && !this._environmentService.args['disable-telemetry'] && !!this._productService.enableTelemetry) {
|
||||
if (uri.scheme === 'https' && uri.authority === 'code.visualstudio.com') {
|
||||
const info = await this._telemetryService.getTelemetryInfo();
|
||||
|
||||
return uri.with({ query: `${uri.query ? uri.query + '&' : ''}utm_source=VsCode&utm_medium=${encodeURIComponent(origin)}&utm_campaign=${encodeURIComponent(info.instanceId)}&utm_content=${encodeURIComponent(experiment)}` });
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
private async renderBody(text: string) {
|
||||
const content = await this.renderContent(text);
|
||||
const colorMap = TokenizationRegistry.getColorMap();
|
|
@ -0,0 +1,21 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/platform/update/common/update.config.contribution';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution } from 'vs/workbench/contrib/update/browser/update';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
const workbench = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
|
||||
workbench.registerWorkbenchContribution(ProductContribution, LifecyclePhase.Restored);
|
||||
workbench.registerWorkbenchContribution(UpdateContribution, LifecyclePhase.Restored);
|
||||
|
||||
// Editor
|
||||
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions)
|
||||
.registerWorkbenchAction(new SyncActionDescriptor(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), 'Show Release Notes');
|
474
src/vs/workbench/contrib/update/browser/update.ts
Normal file
474
src/vs/workbench/contrib/update/browser/update.ts
Normal file
|
@ -0,0 +1,474 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import severity from 'vs/base/common/severity';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IActivityService, NumberBadge, IBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IUpdateService, State as UpdateState, StateType, IUpdate } from 'vs/platform/update/common/update';
|
||||
import * as semver from 'semver-umd';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { ReleaseNotesManager } from './releaseNotesEditor';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { FalseContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
|
||||
const CONTEXT_UPDATE_STATE = new RawContextKey<string>('updateState', StateType.Uninitialized);
|
||||
|
||||
let releaseNotesManager: ReleaseNotesManager | undefined = undefined;
|
||||
|
||||
function showReleaseNotes(instantiationService: IInstantiationService, version: string) {
|
||||
if (!releaseNotesManager) {
|
||||
releaseNotesManager = instantiationService.createInstance(ReleaseNotesManager);
|
||||
}
|
||||
|
||||
return instantiationService.invokeFunction(accessor => releaseNotesManager!.show(accessor, version));
|
||||
}
|
||||
|
||||
export class OpenLatestReleaseNotesInBrowserAction extends Action {
|
||||
|
||||
constructor(
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
super('update.openLatestReleaseNotes', nls.localize('releaseNotes', "Release Notes"), undefined, true);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
if (this.productService.releaseNotesUrl) {
|
||||
const uri = URI.parse(this.productService.releaseNotesUrl);
|
||||
return this.openerService.open(uri);
|
||||
}
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class AbstractShowReleaseNotesAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private version: string,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super(id, label, undefined, true);
|
||||
}
|
||||
|
||||
run(): Promise<boolean> {
|
||||
if (!this.enabled) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
this.enabled = false;
|
||||
|
||||
return showReleaseNotes(this.instantiationService, this.version)
|
||||
.then(undefined, () => {
|
||||
const action = this.instantiationService.createInstance(OpenLatestReleaseNotesInBrowserAction);
|
||||
return action.run().then(() => false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowReleaseNotesAction extends AbstractShowReleaseNotesAction {
|
||||
|
||||
constructor(
|
||||
version: string,
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
super('update.showReleaseNotes', nls.localize('releaseNotes', "Release Notes"), version, instantiationService);
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowCurrentReleaseNotesAction extends AbstractShowReleaseNotesAction {
|
||||
|
||||
static readonly ID = ShowCurrentReleaseNotesActionId;
|
||||
static LABEL = nls.localize('showReleaseNotes', "Show Release Notes");
|
||||
|
||||
constructor(
|
||||
id = ShowCurrentReleaseNotesAction.ID,
|
||||
label = ShowCurrentReleaseNotesAction.LABEL,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IProductService productService: IProductService
|
||||
) {
|
||||
super(id, label, productService.version, instantiationService);
|
||||
}
|
||||
}
|
||||
|
||||
export class ProductContribution implements IWorkbenchContribution {
|
||||
|
||||
private static readonly KEY = 'releaseNotes/lastVersion';
|
||||
|
||||
constructor(
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWindowService windowService: IWindowService,
|
||||
@IWindowsService windowsService: IWindowsService,
|
||||
@IProductService productService: IProductService
|
||||
) {
|
||||
windowsService.getActiveWindowId().then(async windowId => {
|
||||
if (windowId !== windowService.windowId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, '');
|
||||
const shouldShowReleaseNotes = configurationService.getValue<boolean>('update.showReleaseNotes');
|
||||
|
||||
// was there an update? if so, open release notes
|
||||
const releaseNotesUrl = productService.releaseNotesUrl;
|
||||
if (shouldShowReleaseNotes && !environmentService.skipReleaseNotes && releaseNotesUrl && lastVersion && productService.version !== lastVersion) {
|
||||
showReleaseNotes(instantiationService, productService.version)
|
||||
.then(undefined, () => {
|
||||
notificationService.prompt(
|
||||
severity.Info,
|
||||
nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", productService.nameLong, productService.version),
|
||||
[{
|
||||
label: nls.localize('releaseNotes', "Release Notes"),
|
||||
run: () => {
|
||||
const uri = URI.parse(releaseNotesUrl);
|
||||
openerService.open(uri);
|
||||
}
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// should we show the new license?
|
||||
if (productService.licenseUrl && lastVersion && semver.satisfies(lastVersion, '<1.0.0') && semver.satisfies(productService.version, '>=1.0.0')) {
|
||||
notificationService.info(nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", productService.licenseUrl));
|
||||
}
|
||||
|
||||
storageService.store(ProductContribution.KEY, productService.version, StorageScope.GLOBAL);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class UpdateContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private state: UpdateState;
|
||||
private readonly badgeDisposable = this._register(new MutableDisposable());
|
||||
private updateStateContextKey: IContextKey<string>;
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@IUpdateService private readonly updateService: IUpdateService,
|
||||
@IActivityService private readonly activityService: IActivityService,
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
super();
|
||||
this.state = updateService.state;
|
||||
this.updateStateContextKey = CONTEXT_UPDATE_STATE.bindTo(this.contextKeyService);
|
||||
|
||||
this._register(updateService.onStateChange(this.onUpdateStateChange, this));
|
||||
this.onUpdateStateChange(this.updateService.state);
|
||||
|
||||
/*
|
||||
The `update/lastKnownVersion` and `update/updateNotificationTime` storage keys are used in
|
||||
combination to figure out when to show a message to the user that he should update.
|
||||
|
||||
This message should appear if the user has received an update notification but hasn't
|
||||
updated since 5 days.
|
||||
*/
|
||||
|
||||
const currentVersion = this.productService.commit;
|
||||
const lastKnownVersion = this.storageService.get('update/lastKnownVersion', StorageScope.GLOBAL);
|
||||
|
||||
// if current version != stored version, clear both fields
|
||||
if (currentVersion !== lastKnownVersion) {
|
||||
this.storageService.remove('update/lastKnownVersion', StorageScope.GLOBAL);
|
||||
this.storageService.remove('update/updateNotificationTime', StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
this.registerGlobalActivityActions();
|
||||
}
|
||||
|
||||
private onUpdateStateChange(state: UpdateState): void {
|
||||
this.updateStateContextKey.set(state.type);
|
||||
|
||||
switch (state.type) {
|
||||
case StateType.Idle:
|
||||
if (state.error) {
|
||||
this.onError(state.error);
|
||||
} else if (this.state.type === StateType.CheckingForUpdates && this.state.context && this.state.context.windowId === this.environmentService.configuration.windowId) {
|
||||
this.onUpdateNotAvailable();
|
||||
}
|
||||
break;
|
||||
|
||||
case StateType.AvailableForDownload:
|
||||
this.onUpdateAvailable(state.update);
|
||||
break;
|
||||
|
||||
case StateType.Downloaded:
|
||||
this.onUpdateDownloaded(state.update);
|
||||
break;
|
||||
|
||||
case StateType.Updating:
|
||||
this.onUpdateUpdating(state.update);
|
||||
break;
|
||||
|
||||
case StateType.Ready:
|
||||
this.onUpdateReady(state.update);
|
||||
break;
|
||||
}
|
||||
|
||||
let badge: IBadge | undefined = undefined;
|
||||
let clazz: string | undefined;
|
||||
|
||||
if (state.type === StateType.AvailableForDownload || state.type === StateType.Downloaded || state.type === StateType.Ready) {
|
||||
badge = new NumberBadge(1, () => nls.localize('updateIsReady', "New {0} update available.", this.productService.nameShort));
|
||||
} else if (state.type === StateType.CheckingForUpdates || state.type === StateType.Downloading || state.type === StateType.Updating) {
|
||||
badge = new ProgressBadge(() => nls.localize('updateIsReady', "New {0} update available.", this.productService.nameShort));
|
||||
clazz = 'progress-badge';
|
||||
}
|
||||
|
||||
this.badgeDisposable.clear();
|
||||
|
||||
if (badge) {
|
||||
this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz);
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
private onError(error: string): void {
|
||||
error = error.replace(/See https:\/\/github\.com\/Squirrel\/Squirrel\.Mac\/issues\/182 for more information/, 'See [this link](https://github.com/Microsoft/vscode/issues/7426#issuecomment-425093469) for more information');
|
||||
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: error,
|
||||
source: nls.localize('update service', "Update Service"),
|
||||
});
|
||||
}
|
||||
|
||||
private onUpdateNotAvailable(): void {
|
||||
this.dialogService.show(
|
||||
severity.Info,
|
||||
nls.localize('noUpdatesAvailable', "There are currently no updates available."),
|
||||
[nls.localize('ok', "OK")]
|
||||
);
|
||||
}
|
||||
|
||||
// linux
|
||||
private onUpdateAvailable(update: IUpdate): void {
|
||||
if (!this.shouldShowNotification()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.notificationService.prompt(
|
||||
severity.Info,
|
||||
nls.localize('thereIsUpdateAvailable', "There is an available update."),
|
||||
[{
|
||||
label: nls.localize('download update', "Download Update"),
|
||||
run: () => this.updateService.downloadUpdate()
|
||||
}, {
|
||||
label: nls.localize('later', "Later"),
|
||||
run: () => { }
|
||||
}, {
|
||||
label: nls.localize('releaseNotes', "Release Notes"),
|
||||
run: () => {
|
||||
const action = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
}
|
||||
|
||||
// windows fast updates (target === system)
|
||||
private onUpdateDownloaded(update: IUpdate): void {
|
||||
if (!this.shouldShowNotification()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.notificationService.prompt(
|
||||
severity.Info,
|
||||
nls.localize('updateAvailable', "There's an update available: {0} {1}", this.productService.nameLong, update.productVersion),
|
||||
[{
|
||||
label: nls.localize('installUpdate', "Install Update"),
|
||||
run: () => this.updateService.applyUpdate()
|
||||
}, {
|
||||
label: nls.localize('later', "Later"),
|
||||
run: () => { }
|
||||
}, {
|
||||
label: nls.localize('releaseNotes', "Release Notes"),
|
||||
run: () => {
|
||||
const action = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
}
|
||||
|
||||
// windows fast updates
|
||||
private onUpdateUpdating(update: IUpdate): void {
|
||||
if (isWindows && this.productService.target === 'user') {
|
||||
return;
|
||||
}
|
||||
|
||||
// windows fast updates (target === system)
|
||||
this.notificationService.prompt(
|
||||
severity.Info,
|
||||
nls.localize('updateInstalling', "{0} {1} is being installed in the background; we'll let you know when it's done.", this.productService.nameLong, update.productVersion),
|
||||
[],
|
||||
{
|
||||
neverShowAgain: { id: 'neverShowAgain:update/win32-fast-updates', isSecondary: true }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// windows and mac
|
||||
private onUpdateReady(update: IUpdate): void {
|
||||
if (!(isWindows && this.productService.target !== 'user') && !this.shouldShowNotification()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actions = [{
|
||||
label: nls.localize('updateNow', "Update Now"),
|
||||
run: () => this.updateService.quitAndInstall()
|
||||
}, {
|
||||
label: nls.localize('later', "Later"),
|
||||
run: () => { }
|
||||
}];
|
||||
|
||||
// TODO@joao check why snap updates send `update` as falsy
|
||||
if (update.productVersion) {
|
||||
actions.push({
|
||||
label: nls.localize('releaseNotes', "Release Notes"),
|
||||
run: () => {
|
||||
const action = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// windows user fast updates and mac
|
||||
this.notificationService.prompt(
|
||||
severity.Info,
|
||||
nls.localize('updateAvailableAfterRestart', "Restart {0} to apply the latest update.", this.productService.nameLong),
|
||||
actions,
|
||||
{ sticky: true }
|
||||
);
|
||||
}
|
||||
|
||||
private shouldShowNotification(): boolean {
|
||||
const currentVersion = this.productService.commit;
|
||||
const currentMillis = new Date().getTime();
|
||||
const lastKnownVersion = this.storageService.get('update/lastKnownVersion', StorageScope.GLOBAL);
|
||||
|
||||
// if version != stored version, save version and date
|
||||
if (currentVersion !== lastKnownVersion) {
|
||||
this.storageService.store('update/lastKnownVersion', currentVersion!, StorageScope.GLOBAL);
|
||||
this.storageService.store('update/updateNotificationTime', currentMillis, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
const updateNotificationMillis = this.storageService.getNumber('update/updateNotificationTime', StorageScope.GLOBAL, currentMillis);
|
||||
const diffDays = (currentMillis - updateNotificationMillis) / (1000 * 60 * 60 * 24);
|
||||
|
||||
return diffDays > 5;
|
||||
}
|
||||
|
||||
private registerGlobalActivityActions(): void {
|
||||
CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates({ windowId: this.environmentService.configuration.windowId }));
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.check',
|
||||
title: nls.localize('checkForUpdates', "Check for Updates...")
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Idle)
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('update.checking', () => { });
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.checking',
|
||||
title: nls.localize('checkingForUpdates', "Checking for Updates..."),
|
||||
precondition: FalseContext
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.CheckingForUpdates)
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('update.downloadNow', () => this.updateService.downloadUpdate());
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.downloadNow',
|
||||
title: nls.localize('download update', "Download Update")
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload)
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('update.downloading', () => { });
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.downloading',
|
||||
title: nls.localize('DownloadingUpdate', "Downloading Update..."),
|
||||
precondition: FalseContext
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloading)
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('update.install', () => this.updateService.applyUpdate());
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.install',
|
||||
title: nls.localize('installUpdate...', "Install Update...")
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded)
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('update.updating', () => { });
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.updating',
|
||||
title: nls.localize('installingUpdate', "Installing Update..."),
|
||||
precondition: FalseContext
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Updating)
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('update.restart', () => this.updateService.quitAndInstall());
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.restart',
|
||||
title: nls.localize('restartToUpdate', "Restart to Update")
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready)
|
||||
});
|
||||
}
|
||||
}
|
|
@ -3,27 +3,16 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/platform/update/node/update.config.contribution';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, Win3264BitContribution } from './update';
|
||||
import { Win3264BitContribution } from 'vs/workbench/contrib/update/electron-browser/update';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
const workbench = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
|
||||
workbench.registerWorkbenchContribution(ProductContribution, LifecyclePhase.Restored);
|
||||
|
||||
if (platform.isWindows) {
|
||||
if (process.arch === 'ia32') {
|
||||
workbench.registerWorkbenchContribution(Win3264BitContribution, LifecyclePhase.Restored);
|
||||
}
|
||||
}
|
||||
|
||||
workbench.registerWorkbenchContribution(UpdateContribution, LifecyclePhase.Restored);
|
||||
|
||||
// Editor
|
||||
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions)
|
||||
.registerWorkbenchAction(new SyncActionDescriptor(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), 'Show Release Notes');
|
||||
|
|
|
@ -5,163 +5,10 @@
|
|||
|
||||
import * as nls from 'vs/nls';
|
||||
import severity from 'vs/base/common/severity';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import pkg from 'vs/platform/product/node/package';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IActivityService, NumberBadge, IBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IUpdateService, State as UpdateState, StateType, IUpdate } from 'vs/platform/update/common/update';
|
||||
import * as semver from 'semver-umd';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { ReleaseNotesManager } from './releaseNotesEditor';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { FalseContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
|
||||
const CONTEXT_UPDATE_STATE = new RawContextKey<string>('updateState', StateType.Uninitialized);
|
||||
|
||||
let releaseNotesManager: ReleaseNotesManager | undefined = undefined;
|
||||
|
||||
function showReleaseNotes(instantiationService: IInstantiationService, version: string) {
|
||||
if (!releaseNotesManager) {
|
||||
releaseNotesManager = instantiationService.createInstance(ReleaseNotesManager);
|
||||
}
|
||||
|
||||
return instantiationService.invokeFunction(accessor => releaseNotesManager!.show(accessor, version));
|
||||
}
|
||||
|
||||
export class OpenLatestReleaseNotesInBrowserAction extends Action {
|
||||
|
||||
constructor(
|
||||
@IOpenerService private readonly openerService: IOpenerService
|
||||
) {
|
||||
super('update.openLatestReleaseNotes', nls.localize('releaseNotes', "Release Notes"), undefined, true);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
if (product.releaseNotesUrl) {
|
||||
const uri = URI.parse(product.releaseNotesUrl);
|
||||
return this.openerService.open(uri);
|
||||
}
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class AbstractShowReleaseNotesAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private version: string,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super(id, label, undefined, true);
|
||||
}
|
||||
|
||||
run(): Promise<boolean> {
|
||||
if (!this.enabled) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
this.enabled = false;
|
||||
|
||||
return showReleaseNotes(this.instantiationService, this.version)
|
||||
.then(undefined, () => {
|
||||
const action = this.instantiationService.createInstance(OpenLatestReleaseNotesInBrowserAction);
|
||||
return action.run().then(() => false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowReleaseNotesAction extends AbstractShowReleaseNotesAction {
|
||||
|
||||
constructor(
|
||||
version: string,
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
super('update.showReleaseNotes', nls.localize('releaseNotes', "Release Notes"), version, instantiationService);
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowCurrentReleaseNotesAction extends AbstractShowReleaseNotesAction {
|
||||
|
||||
static readonly ID = ShowCurrentReleaseNotesActionId;
|
||||
static LABEL = nls.localize('showReleaseNotes', "Show Release Notes");
|
||||
|
||||
constructor(
|
||||
id = ShowCurrentReleaseNotesAction.ID,
|
||||
label = ShowCurrentReleaseNotesAction.LABEL,
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
super(id, label, pkg.version, instantiationService);
|
||||
}
|
||||
}
|
||||
|
||||
export class ProductContribution implements IWorkbenchContribution {
|
||||
|
||||
private static readonly KEY = 'releaseNotes/lastVersion';
|
||||
|
||||
constructor(
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWindowService windowService: IWindowService,
|
||||
@IWindowsService windowsService: IWindowsService
|
||||
) {
|
||||
windowsService.getActiveWindowId().then(async windowId => {
|
||||
if (windowId !== windowService.windowId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, '');
|
||||
const shouldShowReleaseNotes = configurationService.getValue<boolean>('update.showReleaseNotes');
|
||||
|
||||
// was there an update? if so, open release notes
|
||||
const releaseNotesUrl = product.releaseNotesUrl;
|
||||
if (shouldShowReleaseNotes && !environmentService.skipReleaseNotes && releaseNotesUrl && lastVersion && pkg.version !== lastVersion) {
|
||||
showReleaseNotes(instantiationService, pkg.version)
|
||||
.then(undefined, () => {
|
||||
notificationService.prompt(
|
||||
severity.Info,
|
||||
nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", product.nameLong, pkg.version),
|
||||
[{
|
||||
label: nls.localize('releaseNotes', "Release Notes"),
|
||||
run: () => {
|
||||
const uri = URI.parse(releaseNotesUrl);
|
||||
openerService.open(uri);
|
||||
}
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// should we show the new license?
|
||||
if (product.licenseUrl && lastVersion && semver.satisfies(lastVersion, '<1.0.0') && semver.satisfies(pkg.version, '>=1.0.0')) {
|
||||
notificationService.info(nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", product.licenseUrl));
|
||||
}
|
||||
|
||||
storageService.store(ProductContribution.KEY, pkg.version, StorageScope.GLOBAL);
|
||||
});
|
||||
}
|
||||
}
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class Win3264BitContribution implements IWorkbenchContribution {
|
||||
|
||||
|
@ -169,7 +16,6 @@ export class Win3264BitContribution implements IWorkbenchContribution {
|
|||
private static readonly INSIDER_URL = 'https://github.com/Microsoft/vscode-docs/blob/vnext/release-notes/v1_15.md#windows-64-bit';
|
||||
|
||||
constructor(
|
||||
@IStorageService storageService: IStorageService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService
|
||||
) {
|
||||
|
@ -192,311 +38,3 @@ export class Win3264BitContribution implements IWorkbenchContribution {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class UpdateContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private state: UpdateState;
|
||||
private readonly badgeDisposable = this._register(new MutableDisposable());
|
||||
private updateStateContextKey: IContextKey<string>;
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@IUpdateService private readonly updateService: IUpdateService,
|
||||
@IActivityService private readonly activityService: IActivityService,
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService
|
||||
) {
|
||||
super();
|
||||
this.state = updateService.state;
|
||||
this.updateStateContextKey = CONTEXT_UPDATE_STATE.bindTo(this.contextKeyService);
|
||||
|
||||
this._register(updateService.onStateChange(this.onUpdateStateChange, this));
|
||||
this.onUpdateStateChange(this.updateService.state);
|
||||
|
||||
/*
|
||||
The `update/lastKnownVersion` and `update/updateNotificationTime` storage keys are used in
|
||||
combination to figure out when to show a message to the user that he should update.
|
||||
|
||||
This message should appear if the user has received an update notification but hasn't
|
||||
updated since 5 days.
|
||||
*/
|
||||
|
||||
const currentVersion = product.commit;
|
||||
const lastKnownVersion = this.storageService.get('update/lastKnownVersion', StorageScope.GLOBAL);
|
||||
|
||||
// if current version != stored version, clear both fields
|
||||
if (currentVersion !== lastKnownVersion) {
|
||||
this.storageService.remove('update/lastKnownVersion', StorageScope.GLOBAL);
|
||||
this.storageService.remove('update/updateNotificationTime', StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
this.registerGlobalActivityActions();
|
||||
}
|
||||
|
||||
private onUpdateStateChange(state: UpdateState): void {
|
||||
this.updateStateContextKey.set(state.type);
|
||||
|
||||
switch (state.type) {
|
||||
case StateType.Idle:
|
||||
if (state.error) {
|
||||
this.onError(state.error);
|
||||
} else if (this.state.type === StateType.CheckingForUpdates && this.state.context && this.state.context.windowId === this.environmentService.configuration.windowId) {
|
||||
this.onUpdateNotAvailable();
|
||||
}
|
||||
break;
|
||||
|
||||
case StateType.AvailableForDownload:
|
||||
this.onUpdateAvailable(state.update);
|
||||
break;
|
||||
|
||||
case StateType.Downloaded:
|
||||
this.onUpdateDownloaded(state.update);
|
||||
break;
|
||||
|
||||
case StateType.Updating:
|
||||
this.onUpdateUpdating(state.update);
|
||||
break;
|
||||
|
||||
case StateType.Ready:
|
||||
this.onUpdateReady(state.update);
|
||||
break;
|
||||
}
|
||||
|
||||
let badge: IBadge | undefined = undefined;
|
||||
let clazz: string | undefined;
|
||||
|
||||
if (state.type === StateType.AvailableForDownload || state.type === StateType.Downloaded || state.type === StateType.Ready) {
|
||||
badge = new NumberBadge(1, () => nls.localize('updateIsReady', "New {0} update available.", product.nameShort));
|
||||
} else if (state.type === StateType.CheckingForUpdates || state.type === StateType.Downloading || state.type === StateType.Updating) {
|
||||
badge = new ProgressBadge(() => nls.localize('updateIsReady', "New {0} update available.", product.nameShort));
|
||||
clazz = 'progress-badge';
|
||||
}
|
||||
|
||||
this.badgeDisposable.clear();
|
||||
|
||||
if (badge) {
|
||||
this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz);
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
private onError(error: string): void {
|
||||
error = error.replace(/See https:\/\/github\.com\/Squirrel\/Squirrel\.Mac\/issues\/182 for more information/, 'See [this link](https://github.com/Microsoft/vscode/issues/7426#issuecomment-425093469) for more information');
|
||||
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: error,
|
||||
source: nls.localize('update service', "Update Service"),
|
||||
});
|
||||
}
|
||||
|
||||
private onUpdateNotAvailable(): void {
|
||||
this.dialogService.show(
|
||||
severity.Info,
|
||||
nls.localize('noUpdatesAvailable', "There are currently no updates available."),
|
||||
[nls.localize('ok', "OK")]
|
||||
);
|
||||
}
|
||||
|
||||
// linux
|
||||
private onUpdateAvailable(update: IUpdate): void {
|
||||
if (!this.shouldShowNotification()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.notificationService.prompt(
|
||||
severity.Info,
|
||||
nls.localize('thereIsUpdateAvailable', "There is an available update."),
|
||||
[{
|
||||
label: nls.localize('download update', "Download Update"),
|
||||
run: () => this.updateService.downloadUpdate()
|
||||
}, {
|
||||
label: nls.localize('later', "Later"),
|
||||
run: () => { }
|
||||
}, {
|
||||
label: nls.localize('releaseNotes', "Release Notes"),
|
||||
run: () => {
|
||||
const action = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
}
|
||||
|
||||
// windows fast updates (target === system)
|
||||
private onUpdateDownloaded(update: IUpdate): void {
|
||||
if (!this.shouldShowNotification()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.notificationService.prompt(
|
||||
severity.Info,
|
||||
nls.localize('updateAvailable', "There's an update available: {0} {1}", product.nameLong, update.productVersion),
|
||||
[{
|
||||
label: nls.localize('installUpdate', "Install Update"),
|
||||
run: () => this.updateService.applyUpdate()
|
||||
}, {
|
||||
label: nls.localize('later', "Later"),
|
||||
run: () => { }
|
||||
}, {
|
||||
label: nls.localize('releaseNotes', "Release Notes"),
|
||||
run: () => {
|
||||
const action = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
}
|
||||
|
||||
// windows fast updates
|
||||
private onUpdateUpdating(update: IUpdate): void {
|
||||
if (isWindows && product.target === 'user') {
|
||||
return;
|
||||
}
|
||||
|
||||
// windows fast updates (target === system)
|
||||
this.notificationService.prompt(
|
||||
severity.Info,
|
||||
nls.localize('updateInstalling', "{0} {1} is being installed in the background; we'll let you know when it's done.", product.nameLong, update.productVersion),
|
||||
[],
|
||||
{
|
||||
neverShowAgain: { id: 'neverShowAgain:update/win32-fast-updates', isSecondary: true }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// windows and mac
|
||||
private onUpdateReady(update: IUpdate): void {
|
||||
if (!(isWindows && product.target !== 'user') && !this.shouldShowNotification()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actions = [{
|
||||
label: nls.localize('updateNow', "Update Now"),
|
||||
run: () => this.updateService.quitAndInstall()
|
||||
}, {
|
||||
label: nls.localize('later', "Later"),
|
||||
run: () => { }
|
||||
}];
|
||||
|
||||
// TODO@joao check why snap updates send `update` as falsy
|
||||
if (update.productVersion) {
|
||||
actions.push({
|
||||
label: nls.localize('releaseNotes', "Release Notes"),
|
||||
run: () => {
|
||||
const action = this.instantiationService.createInstance(ShowReleaseNotesAction, update.productVersion);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// windows user fast updates and mac
|
||||
this.notificationService.prompt(
|
||||
severity.Info,
|
||||
nls.localize('updateAvailableAfterRestart', "Restart {0} to apply the latest update.", product.nameLong),
|
||||
actions,
|
||||
{ sticky: true }
|
||||
);
|
||||
}
|
||||
|
||||
private shouldShowNotification(): boolean {
|
||||
const currentVersion = product.commit;
|
||||
const currentMillis = new Date().getTime();
|
||||
const lastKnownVersion = this.storageService.get('update/lastKnownVersion', StorageScope.GLOBAL);
|
||||
|
||||
// if version != stored version, save version and date
|
||||
if (currentVersion !== lastKnownVersion) {
|
||||
this.storageService.store('update/lastKnownVersion', currentVersion!, StorageScope.GLOBAL);
|
||||
this.storageService.store('update/updateNotificationTime', currentMillis, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
const updateNotificationMillis = this.storageService.getNumber('update/updateNotificationTime', StorageScope.GLOBAL, currentMillis);
|
||||
const diffDays = (currentMillis - updateNotificationMillis) / (1000 * 60 * 60 * 24);
|
||||
|
||||
return diffDays > 5;
|
||||
}
|
||||
|
||||
private registerGlobalActivityActions(): void {
|
||||
CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates({ windowId: this.environmentService.configuration.windowId }));
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.check',
|
||||
title: nls.localize('checkForUpdates', "Check for Updates...")
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Idle)
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('update.checking', () => { });
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.checking',
|
||||
title: nls.localize('checkingForUpdates', "Checking for Updates..."),
|
||||
precondition: FalseContext
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.CheckingForUpdates)
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('update.downloadNow', () => this.updateService.downloadUpdate());
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.downloadNow',
|
||||
title: nls.localize('download update', "Download Update")
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload)
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('update.downloading', () => { });
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.downloading',
|
||||
title: nls.localize('DownloadingUpdate', "Downloading Update..."),
|
||||
precondition: FalseContext
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloading)
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('update.install', () => this.updateService.applyUpdate());
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.install',
|
||||
title: nls.localize('installUpdate...', "Install Update...")
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded)
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('update.updating', () => { });
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.updating',
|
||||
title: nls.localize('installingUpdate', "Installing Update..."),
|
||||
precondition: FalseContext
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Updating)
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('update.restart', () => this.updateService.quitAndInstall());
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_update',
|
||||
command: {
|
||||
id: 'update.restart',
|
||||
title: nls.localize('restartToUpdate', "Restart to Update")
|
||||
},
|
||||
when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
95
src/vs/workbench/services/update/browser/updateService.ts
Normal file
95
src/vs/workbench/services/update/browser/updateService.ts
Normal 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, Emitter } from 'vs/base/common/event';
|
||||
import { IUpdateService, State, UpdateType } from 'vs/platform/update/common/update';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IUpdate {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface IUpdateProvider {
|
||||
|
||||
/**
|
||||
* Should return with the `IUpdate` object if an update is
|
||||
* available or `null` otherwise to signal that there are
|
||||
* no updates.
|
||||
*/
|
||||
checkForUpdate(): Promise<IUpdate | null>;
|
||||
}
|
||||
|
||||
export class UpdateService extends Disposable implements IUpdateService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private _onStateChange = this._register(new Emitter<State>());
|
||||
readonly onStateChange: Event<State> = this._onStateChange.event;
|
||||
|
||||
private _state: State = State.Uninitialized;
|
||||
get state(): State { return this._state; }
|
||||
set state(state: State) {
|
||||
this._state = state;
|
||||
this._onStateChange.fire(state);
|
||||
}
|
||||
|
||||
constructor(
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IWindowService private readonly windowService: IWindowService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.checkForUpdates();
|
||||
}
|
||||
|
||||
async isLatestVersion(): Promise<boolean> {
|
||||
const update = await this.doCheckForUpdates();
|
||||
|
||||
return !!update;
|
||||
}
|
||||
|
||||
async checkForUpdates(): Promise<void> {
|
||||
await this.doCheckForUpdates();
|
||||
}
|
||||
|
||||
private async doCheckForUpdates(): Promise<IUpdate | null> {
|
||||
if (this.environmentService.options && this.environmentService.options.updateProvider) {
|
||||
const updateProvider = this.environmentService.options.updateProvider;
|
||||
|
||||
// State -> Checking for Updates
|
||||
this.state = State.CheckingForUpdates(null);
|
||||
|
||||
const update = await updateProvider.checkForUpdate();
|
||||
if (update) {
|
||||
// State -> Downloaded
|
||||
this.state = State.Ready({ version: update.version, productVersion: update.version });
|
||||
} else {
|
||||
// State -> Idle
|
||||
this.state = State.Idle(UpdateType.Archive);
|
||||
}
|
||||
|
||||
return update;
|
||||
}
|
||||
|
||||
return null; // no update provider to ask
|
||||
}
|
||||
|
||||
async downloadUpdate(): Promise<void> {
|
||||
// no-op
|
||||
}
|
||||
|
||||
async applyUpdate(): Promise<void> {
|
||||
this.windowService.reloadWindow();
|
||||
}
|
||||
|
||||
async quitAndInstall(): Promise<void> {
|
||||
this.windowService.reloadWindow();
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IUpdateService, UpdateService);
|
|
@ -219,6 +219,9 @@ import 'vs/workbench/contrib/format/browser/format.contribution';
|
|||
// Themes
|
||||
import 'vs/workbench/contrib/themes/browser/themes.contribution';
|
||||
|
||||
// Update
|
||||
import 'vs/workbench/contrib/update/browser/update.contribution';
|
||||
|
||||
// Watermark
|
||||
import 'vs/workbench/contrib/watermark/browser/watermark';
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import { ICredentialsProvider } from 'vs/workbench/services/credentials/browser/
|
|||
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService';
|
||||
import { LogLevel } from 'vs/platform/log/common/log';
|
||||
import { IUpdateProvider } from 'vs/workbench/services/update/browser/updateService';
|
||||
|
||||
export interface IWorkbenchConstructionOptions {
|
||||
|
||||
|
@ -82,6 +83,11 @@ export interface IWorkbenchConstructionOptions {
|
|||
* Current logging level. Default is `LogLevel.Info`.
|
||||
*/
|
||||
logLevel?: LogLevel;
|
||||
|
||||
/**
|
||||
* Experimental: Support for update reporting.
|
||||
*/
|
||||
updateProvider?: IUpdateProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,6 +38,7 @@ import 'vs/workbench/services/telemetry/browser/telemetryService';
|
|||
import 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
|
||||
import 'vs/workbench/services/credentials/browser/credentialsService';
|
||||
import 'vs/workbench/services/url/browser/urlService';
|
||||
import 'vs/workbench/services/update/browser/updateService';
|
||||
import 'vs/workbench/browser/web.simpleservices';
|
||||
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
|
|
|
@ -549,6 +549,13 @@
|
|||
"**/vs/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "**/vs/workbench/contrib/update/browser/update.ts",
|
||||
"restrictions": [
|
||||
"semver-umd",
|
||||
"**/vs/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "**/vs/code/node/**",
|
||||
"restrictions": [
|
||||
|
|
Loading…
Reference in a new issue