提交 1aaf1a80 编写于 作者: J Joao Moreno

Merge branch 'update'

......@@ -20,7 +20,9 @@ import { WindowsService } from 'vs/platform/windows/electron-main/windowsService
import { WindowEventChannel } from 'vs/code/common/windowsIpc';
import { ILifecycleService, LifecycleService } from 'vs/code/electron-main/lifecycle';
import { VSCodeMenu } from 'vs/code/electron-main/menus';
import { IUpdateService, UpdateManager } from 'vs/code/electron-main/update-manager';
import { IUpdateService } from 'vs/platform/update/common/update';
import { UpdateChannel } from 'vs/platform/update/common/updateIpc';
import { UpdateService } from 'vs/platform/update/electron-main/updateService';
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main';
import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net';
import { TPromise } from 'vs/base/common/winjs.base';
......@@ -46,8 +48,13 @@ import { getPathLabel } from 'vs/base/common/labels';
import { IURLService } from 'vs/platform/url/common/url';
import { URLChannel } from 'vs/platform/url/common/urlIpc';
import { URLService } from 'vs/platform/url/electron-main/urlService';
import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
import product from 'vs/platform/product';
import pkg from 'vs/platform/package';
import * as fs from 'original-fs';
import * as cp from 'child_process';
import * as path from 'path';
......@@ -72,13 +79,13 @@ function quit(accessor: ServicesAccessor, arg?: any) {
process.exit(exitCode); // in main, process.exit === app.exit
}
// TODO@Joao wow this is huge, clean up!
function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platform.IProcessEnvironment): void {
const instantiationService = accessor.get(IInstantiationService);
const logService = accessor.get(ILogService);
const environmentService = accessor.get(IEnvironmentService);
const windowsMainService = accessor.get(IWindowsMainService);
const lifecycleService = accessor.get(ILifecycleService);
const updateService = accessor.get(IUpdateService);
const configurationService = accessor.get(IConfigurationService) as ConfigurationService<any>;
const windowEventChannel = new WindowEventChannel(windowsMainService);
......@@ -147,82 +154,107 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo
let sharedProcessDisposable;
spawnSharedProcess(initData, options).done(disposable => {
const sharedProcess = spawnSharedProcess(initData, options).then(disposable => {
sharedProcessDisposable = disposable;
connect(environmentService.sharedIPCHandle, 'main')
.done(client => client.registerChannel('windowEvent', windowEventChannel));
return connect(environmentService.sharedIPCHandle, 'main');
});
// Make sure we associate the program with the app user model id
// This will help Windows to associate the running program with
// any shortcut that is pinned to the taskbar and prevent showing
// two icons in the taskbar for the same app.
if (platform.isWindows && product.win32AppUserModelId) {
app.setAppUserModelId(product.win32AppUserModelId);
// Create a new service collection, because the telemetry service
// requires a connection to shared process, which was only established
// now.
const services = new ServiceCollection();
services.set(IUpdateService, new SyncDescriptor(UpdateService));
if (environmentService.isBuilt && !environmentService.extensionDevelopmentPath && !!product.enableTelemetry) {
const channel = getDelayedChannel<ITelemetryAppenderChannel>(sharedProcess.then(c => c.getChannel('telemetryAppender')));
const appender = new TelemetryAppenderClient(channel);
const commonProperties = resolveCommonProperties(product.commit, pkg.version);
const piiPaths = [environmentService.appRoot, environmentService.extensionsPath];
const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths };
services.set(ITelemetryService, new SyncDescriptor(TelemetryService, config));
} else {
services.set(ITelemetryService, NullTelemetryService);
}
function dispose() {
if (mainIpcServer) {
mainIpcServer.dispose();
mainIpcServer = null;
}
const instantiationService2 = instantiationService.createChild(services);
if (sharedProcessDisposable) {
sharedProcessDisposable.dispose();
}
instantiationService2.invokeFunction(accessor => {
// Register more Electron IPC services
const updateService = accessor.get(IUpdateService);
const updateChannel = new UpdateChannel(updateService);
electronIpcServer.registerChannel('update', updateChannel);
// Register windowEvent
sharedProcess.done(client => client.registerChannel('windowEvent', windowEventChannel));
if (windowsMutex) {
windowsMutex.release();
// Make sure we associate the program with the app user model id
// This will help Windows to associate the running program with
// any shortcut that is pinned to the taskbar and prevent showing
// two icons in the taskbar for the same app.
if (platform.isWindows && product.win32AppUserModelId) {
app.setAppUserModelId(product.win32AppUserModelId);
}
configurationService.dispose();
}
function dispose() {
if (mainIpcServer) {
mainIpcServer.dispose();
mainIpcServer = null;
}
// Dispose on app quit
app.on('will-quit', () => {
logService.log('App#will-quit: disposing resources');
if (sharedProcessDisposable) {
sharedProcessDisposable.dispose();
}
dispose();
});
if (windowsMutex) {
windowsMutex.release();
}
// Dispose on vscode:exit
ipc.on('vscode:exit', (event, code: number) => {
logService.log('IPC#vscode:exit', code);
configurationService.dispose();
}
dispose();
process.exit(code); // in main, process.exit === app.exit
});
// Dispose on app quit
app.on('will-quit', () => {
logService.log('App#will-quit: disposing resources');
// Lifecycle
lifecycleService.ready();
dispose();
});
// Propagate to clients
windowsMainService.ready(userEnv);
// Dispose on vscode:exit
ipc.on('vscode:exit', (event, code: number) => {
logService.log('IPC#vscode:exit', code);
// Install Menu
const menu = instantiationService.createInstance(VSCodeMenu);
menu.ready();
dispose();
process.exit(code); // in main, process.exit === app.exit
});
// Install JumpList on Windows (keep updated when windows open)
if (platform.isWindows) {
updateJumpList(windowsMainService, logService);
windowsMainService.onOpen(() => updateJumpList(windowsMainService, logService));
}
// Lifecycle
lifecycleService.ready();
// Setup auto update
updateService.initialize();
// Propagate to clients
windowsMainService.ready(userEnv);
// Open our first window
if (environmentService.args['new-window'] && environmentService.args._.length === 0) {
windowsMainService.open({ cli: environmentService.args, forceNewWindow: true, forceEmpty: true }); // new window if "-n" was used without paths
} else if (global.macOpenFiles && global.macOpenFiles.length && (!environmentService.args._ || !environmentService.args._.length)) {
windowsMainService.open({ cli: environmentService.args, pathsToOpen: global.macOpenFiles }); // mac: open-file event received on startup
} else {
windowsMainService.open({ cli: environmentService.args, forceNewWindow: environmentService.args['new-window'], diffMode: environmentService.args.diff }); // default: read paths from cli
}
// Install Menu
const menu = instantiationService2.createInstance(VSCodeMenu);
menu.ready();
// Install JumpList on Windows (keep updated when windows open)
if (platform.isWindows) {
updateJumpList(windowsMainService, logService);
windowsMainService.onOpen(() => updateJumpList(windowsMainService, logService));
}
// Open our first window
if (environmentService.args['new-window'] && environmentService.args._.length === 0) {
windowsMainService.open({ cli: environmentService.args, forceNewWindow: true, forceEmpty: true }); // new window if "-n" was used without paths
} else if (global.macOpenFiles && global.macOpenFiles.length && (!environmentService.args._ || !environmentService.args._.length)) {
windowsMainService.open({ cli: environmentService.args, pathsToOpen: global.macOpenFiles }); // mac: open-file event received on startup
} else {
windowsMainService.open({ cli: environmentService.args, forceNewWindow: environmentService.args['new-window'], diffMode: environmentService.args.diff }); // default: read paths from cli
}
});
}
// TODO@Joao TODO@Ben shouldn't this be inside windows service instead?
function updateJumpList(windowsMainService: IWindowsMainService, logService: ILogService): void {
const jumpList: Electron.JumpListCategory[] = [];
......@@ -448,19 +480,7 @@ function createPaths(environmentService: IEnvironmentService): TPromise<any> {
return TPromise.join(paths.map(p => mkdirp(p))) as TPromise<any>;
}
function start(): void {
let args: ParsedArgs;
try {
args = parseMainProcessArgv(process.argv);
args = validatePaths(args);
} catch (err) {
console.error(err.message);
process.exit(1);
return;
}
// TODO: isolate
function createServices(args): IInstantiationService {
const services = new ServiceCollection();
services.set(IEnvironmentService, new SyncDescriptor(EnvironmentService, args, process.execPath));
......@@ -471,10 +491,24 @@ function start(): void {
services.set(IStorageService, new SyncDescriptor(StorageService));
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IUpdateService, new SyncDescriptor(UpdateManager));
services.set(IURLService, new SyncDescriptor(URLService, args['open-url']));
const instantiationService = new InstantiationService(services);
return new InstantiationService(services);
}
function start(): void {
let args: ParsedArgs;
try {
args = parseMainProcessArgv(process.argv);
args = validatePaths(args);
} catch (err) {
console.error(err.message);
process.exit(1);
return;
}
const instantiationService = createServices(args);
// On some platforms we need to manually read from the global environment variables
// and assign them to the process environment (e.g. when doubleclick app on Mac)
......
......@@ -15,7 +15,7 @@ import { IPath, VSCodeWindow } from 'vs/code/electron-main/window';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService } from 'vs/code/electron-main/storage';
import { IFilesConfiguration, AutoSaveConfiguration } from 'vs/platform/files/common/files';
import { IUpdateService, State as UpdateState } from 'vs/code/electron-main/update-manager';
import { IUpdateService, State as UpdateState } from 'vs/platform/update/common/update';
import { Keybinding } from 'vs/base/common/keybinding';
import product from 'vs/platform/product';
......@@ -125,7 +125,7 @@ export class VSCodeMenu {
this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(e.config, true /* update menu if changed */));
// Listen to update service
this.updateService.on('change', () => this.updateMenu());
this.updateService.onStateChange(() => this.updateMenu());
}
private onConfigurationUpdated(config: IConfiguration, handleMenu?: boolean): void {
......@@ -751,11 +751,10 @@ export class VSCodeMenu {
return [];
case UpdateState.UpdateDownloaded:
const update = this.updateService.availableUpdate;
return [new MenuItem({
label: nls.localize('miRestartToUpdate', "Restart To Update..."), click: () => {
this.reportMenuActionTelemetry('RestartToUpdate');
update.quitAndUpdate();
this.updateService.quitAndInstall();
}
})];
......@@ -764,10 +763,9 @@ export class VSCodeMenu {
case UpdateState.UpdateAvailable:
if (platform.isLinux) {
const update = this.updateService.availableUpdate;
return [new MenuItem({
label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => {
update.quitAndUpdate();
this.updateService.quitAndInstall();
}
})];
}
......@@ -782,7 +780,7 @@ export class VSCodeMenu {
const result = [new MenuItem({
label: nls.localize('miCheckForUpdates', "Check For Updates..."), click: () => setTimeout(() => {
this.reportMenuActionTelemetry('CheckForUpdate');
this.updateService.checkForUpdates(true);
this.updateService.checkForUpdates(true).done(null, err => console.error(err));
}, 0)
})];
......
......@@ -22,7 +22,6 @@ import { ipcMain as ipc, app, screen, BrowserWindow, dialog } from 'electron';
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/electron-main/paths';
import { ILifecycleService } from 'vs/code/electron-main/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IUpdateService, IUpdate } from 'vs/code/electron-main/update-manager';
import { ILogService } from 'vs/code/electron-main/log';
import { IWindowEventService } from 'vs/code/common/windows';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
......@@ -153,7 +152,6 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService
@IStorageService private storageService: IStorageService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ILifecycleService private lifecycleService: ILifecycleService,
@IUpdateService private updateService: IUpdateService,
@IConfigurationService private configurationService: IConfigurationService
) { }
......@@ -250,38 +248,6 @@ export class WindowsManager implements IWindowsMainService, IWindowEventService
}
});
this.updateService.on('update-downloaded', (update: IUpdate) => {
this.sendToFocused('vscode:telemetry', { eventName: 'update:downloaded', data: { version: update.version } });
this.sendToAll('vscode:update-downloaded', JSON.stringify({
releaseNotes: update.releaseNotes,
version: update.version,
date: update.date
}));
});
ipc.on('vscode:update-apply', () => {
this.logService.log('IPC#vscode:update-apply');
if (this.updateService.availableUpdate) {
this.updateService.availableUpdate.quitAndUpdate();
}
});
this.updateService.on('update-not-available', (explicit: boolean) => {
this.sendToFocused('vscode:telemetry', { eventName: 'update:notAvailable', data: { explicit } });
if (explicit) {
this.sendToFocused('vscode:update-not-available', '');
}
});
this.updateService.on('update-available', (url: string, version: string) => {
if (url) {
this.sendToFocused('vscode:update-available', url, version);
}
});
this.lifecycleService.onBeforeQuit(() => {
// 0-1 window open: Do not keep the list but just rely on the active window to be stored
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import Event from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
export enum State {
Uninitialized,
Idle,
CheckingForUpdate,
UpdateAvailable,
UpdateDownloaded
}
export enum ExplicitState {
Implicit,
Explicit
}
export interface IRawUpdate {
releaseNotes: string;
version: string;
date: Date;
}
export interface IUpdate {
version: string;
date?: Date;
releaseNotes?: string;
url?: string;
}
export interface IAutoUpdater extends NodeJS.EventEmitter {
setFeedURL(url: string): void;
checkForUpdates(): void;
quitAndInstall(): void;
}
export const IUpdateService = createDecorator<IUpdateService>('updateService');
export interface IUpdateService {
_serviceBrand: any;
readonly onError: Event<any>;
readonly onUpdateAvailable: Event<{ url: string; version: string; }>;
readonly onUpdateNotAvailable: Event<boolean>;
readonly onUpdateReady: Event<IRawUpdate>;
readonly onStateChange: Event<State>;
readonly state: State;
checkForUpdates(explicit: boolean): TPromise<IUpdate>;
quitAndInstall(): TPromise<void>;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import Event from 'vs/base/common/event';
import { IUpdateService, IRawUpdate, State, IUpdate } from './update';
export interface IUpdateChannel extends IChannel {
call(command: 'event:onError'): TPromise<void>;
call(command: 'event:onUpdateAvailable'): TPromise<void>;
call(command: 'event:onUpdateNotAvailable'): TPromise<void>;
call(command: 'event:onUpdateReady'): TPromise<void>;
call(command: 'event:onStateChange'): TPromise<void>;
call(command: 'checkForUpdates', arg: boolean): TPromise<IUpdate>;
call(command: 'quitAndInstall'): TPromise<void>;
call(command: string, arg?: any): TPromise<any>;
}
export class UpdateChannel implements IUpdateChannel {
constructor(private service: IUpdateService) { }
call(command: string, arg?: any): TPromise<any> {
switch (command) {
case 'event:onError': return eventToCall(this.service.onError);
case 'event:onUpdateAvailable': return eventToCall(this.service.onUpdateAvailable);
case 'event:onUpdateNotAvailable': return eventToCall(this.service.onUpdateNotAvailable);
case 'event:onUpdateReady': return eventToCall(this.service.onUpdateReady);
case 'event:onStateChange': return eventToCall(this.service.onStateChange);
case 'checkForUpdates': return this.service.checkForUpdates(arg);
case 'quitAndInstall': return this.service.quitAndInstall();
}
}
}
export class UpdateChannelClient implements IUpdateService {
_serviceBrand: any;
private _onError = eventFromCall<any>(this.channel, 'event:onError');
get onError(): Event<any> { return this._onError; }
private _onUpdateAvailable = eventFromCall<{ url: string; version: string; }>(this.channel, 'event:onUpdateAvailable');
get onUpdateAvailable(): Event<{ url: string; version: string; }> { return this._onUpdateAvailable; }
private _onUpdateNotAvailable = eventFromCall<boolean>(this.channel, 'event:onUpdateNotAvailable');
get onUpdateNotAvailable(): Event<boolean> { return this._onUpdateNotAvailable; }
private _onUpdateReady = eventFromCall<IRawUpdate>(this.channel, 'event:onUpdateReady');
get onUpdateReady(): Event<IRawUpdate> { return this._onUpdateReady; }
private _onStateChange = eventFromCall<State>(this.channel, 'event:onStateChange');
get onStateChange(): Event<State> { return this._onStateChange; }
private _state: State = State.Uninitialized;
get state(): State { return this._state; };
constructor(private channel: IChannel) {
this.onStateChange(state => this._state = state);
}
checkForUpdates(explicit: boolean): TPromise<IUpdate> {
return this.channel.call('checkForUpdates', explicit);
}
quitAndInstall(): TPromise<void> {
return this.channel.call('quitAndInstall');
}
}
\ No newline at end of file
......@@ -10,16 +10,19 @@ import { isString } from 'vs/base/common/types';
import { Promise } from 'vs/base/common/winjs.base';
import { asJson } from 'vs/base/node/request';
import { IRequestService } from 'vs/platform/request/common/request';
import { IAutoUpdater } from 'vs/platform/update/common/update';
import product from 'vs/platform/product';
export interface IUpdate {
interface IUpdate {
url: string;
name: string;
releaseNotes?: string;
version?: string;
version: string;
productVersion: string;
hash: string;
}
export class LinuxAutoUpdaterImpl extends EventEmitter {
export class LinuxAutoUpdaterImpl extends EventEmitter implements IAutoUpdater {
private url: string;
private currentRequest: Promise;
......@@ -51,10 +54,10 @@ export class LinuxAutoUpdaterImpl extends EventEmitter {
this.currentRequest = this.requestService.request({ url: this.url })
.then<IUpdate>(asJson)
.then(update => {
if (!update || !update.url || !update.version) {
if (!update || !update.url || !update.version || !update.productVersion) {
this.emit('update-not-available');
} else {
this.emit('update-available', null, product.downloadUrl, update.version);
this.emit('update-available', null, product.downloadUrl, update.productVersion);
}
})
.then(null, e => {
......@@ -67,4 +70,8 @@ export class LinuxAutoUpdaterImpl extends EventEmitter {
})
.then(() => this.currentRequest = null);
}
quitAndInstall(): void {
// noop
}
}
......@@ -17,9 +17,10 @@ import { Promise, TPromise } from 'vs/base/common/winjs.base';
import { download, asJson } from 'vs/base/node/request';
import { ILifecycleService } from 'vs/code/electron-main/lifecycle';
import { IRequestService } from 'vs/platform/request/common/request';
import { IAutoUpdater } from 'vs/platform/update/common/update';
import product from 'vs/platform/product';
export interface IUpdate {
interface IUpdate {
url: string;
name: string;
releaseNotes?: string;
......@@ -28,19 +29,17 @@ export interface IUpdate {
hash: string;
}
export class Win32AutoUpdaterImpl extends EventEmitter {
export class Win32AutoUpdaterImpl extends EventEmitter implements IAutoUpdater {
private url: string;
private currentRequest: Promise;
private url: string = null;
private currentRequest: Promise = null;
private updatePackagePath: string = null;
constructor(
@ILifecycleService private lifecycleService: ILifecycleService,
@IRequestService private requestService: IRequestService
) {
super();
this.url = null;
this.currentRequest = null;
}
get cachePath(): TPromise<string> {
......@@ -91,13 +90,14 @@ export class Win32AutoUpdaterImpl extends EventEmitter {
.then(() => updatePackagePath);
});
}).then(updatePackagePath => {
this.updatePackagePath = updatePackagePath;
this.emit('update-downloaded',
{},
update.releaseNotes,
update.productVersion,
new Date(),
this.url,
() => this.quitAndUpdate(updatePackagePath)
this.url
);
});
});
......@@ -117,19 +117,6 @@ export class Win32AutoUpdaterImpl extends EventEmitter {
return this.cachePath.then(cachePath => path.join(cachePath, `CodeSetup-${product.quality}-${version}.exe`));
}
private quitAndUpdate(updatePackagePath: string): void {
this.lifecycleService.quit().done(vetod => {
if (vetod) {
return;
}
spawn(updatePackagePath, ['/silent', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore']
});
});
}
private cleanup(exceptVersion: string = null): Promise {
const filter = exceptVersion ? one => !(new RegExp(`${product.quality}-${exceptVersion}\\.exe$`).test(one)) : () => true;
......@@ -141,4 +128,15 @@ export class Win32AutoUpdaterImpl extends EventEmitter {
))
);
}
quitAndInstall(): void {
if (!this.updatePackagePath) {
return;
}
spawn(this.updatePackagePath, ['/silent', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore']
});
}
}
......@@ -8,170 +8,99 @@
import * as fs from 'original-fs';
import * as path from 'path';
import * as electron from 'electron';
import { EventEmitter } from 'events';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import Event, { Emitter, once, filterEvent } from 'vs/base/common/event';
import { always, Throttler } from 'vs/base/common/async';
import { memoize } from 'vs/base/common/decorators';
import { fromEventEmitter } from 'vs/base/node/event';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Win32AutoUpdaterImpl } from 'vs/code/electron-main/auto-updater.win32';
import { LinuxAutoUpdaterImpl } from 'vs/code/electron-main/auto-updater.linux';
import { Win32AutoUpdaterImpl } from './auto-updater.win32';
import { LinuxAutoUpdaterImpl } from './auto-updater.linux';
import { ILifecycleService } from 'vs/code/electron-main/lifecycle';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IRequestService } from 'vs/platform/request/common/request';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import product from 'vs/platform/product';
import { TPromise } from 'vs/base/common/winjs.base';
import { IUpdateService, State, IAutoUpdater, IUpdate, IRawUpdate } from 'vs/platform/update/common/update';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
export enum State {
Uninitialized,
Idle,
CheckingForUpdate,
UpdateAvailable,
UpdateDownloaded
}
export enum ExplicitState {
Implicit,
Explicit
}
export interface IUpdate {
releaseNotes: string;
version: string;
date: Date;
quitAndUpdate: () => void;
}
interface IAutoUpdater extends NodeJS.EventEmitter {
setFeedURL(url: string): void;
checkForUpdates(): void;
}
export const IUpdateService = createDecorator<IUpdateService>('updateService');
export interface IUpdateService {
_serviceBrand: any;
feedUrl: string;
channel: string;
initialize(): void;
state: State;
availableUpdate: IUpdate;
lastCheckDate: Date;
checkForUpdates(explicit: boolean): void;
on(event: string, listener: Function): this;
}
export class UpdateManager extends EventEmitter implements IUpdateService {
export class UpdateService implements IUpdateService {
_serviceBrand: any;
private _state: State;
private explicitState: ExplicitState;
private _availableUpdate: IUpdate;
private _lastCheckDate: Date;
private _state: State = State.Uninitialized;
private _availableUpdate: IUpdate = null;
private raw: IAutoUpdater;
private _feedUrl: string;
private _channel: string;
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@ILifecycleService private lifecycleService: ILifecycleService,
@IConfigurationService private configurationService: IConfigurationService,
@IRequestService requestService: IRequestService
) {
super();
this._state = State.Uninitialized;
this.explicitState = ExplicitState.Implicit;
this._availableUpdate = null;
this._lastCheckDate = null;
this._feedUrl = null;
this._channel = null;
if (process.platform === 'win32') {
this.raw = instantiationService.createInstance(Win32AutoUpdaterImpl);
} else if (process.platform === 'linux') {
this.raw = instantiationService.createInstance(LinuxAutoUpdaterImpl);
} else if (process.platform === 'darwin') {
this.raw = electron.autoUpdater;
}
if (this.raw) {
this.initRaw();
}
}
private initRaw(): void {
this.raw.on('error', (event: any, message: string) => {
this.emit('error', event, message);
this.setState(State.Idle);
});
private throttler: Throttler = new Throttler();
this.raw.on('checking-for-update', () => {
this.emit('checking-for-update');
this.setState(State.CheckingForUpdate);
});
private _onError = new Emitter<any>();
get onError(): Event<any> { return this._onError.event; }
this.raw.on('update-available', (event, url: string, version: string) => {
this.emit('update-available', url, version);
private _onCheckForUpdate = new Emitter<void>();
get onCheckForUpdate(): Event<void> { return this._onCheckForUpdate.event; }
let data: IUpdate = null;
private _onUpdateAvailable = new Emitter<{ url: string; version: string; }>();
get onUpdateAvailable(): Event<{ url: string; version: string; }> { return this._onUpdateAvailable.event; }
if (url) {
data = {
releaseNotes: '',
version: '',
date: new Date(),
quitAndUpdate: () => electron.shell.openExternal(url)
};
}
private _onUpdateNotAvailable = new Emitter<boolean>();
get onUpdateNotAvailable(): Event<boolean> { return this._onUpdateNotAvailable.event; }
this.setState(State.UpdateAvailable, data);
});
private _onUpdateReady = new Emitter<IUpdate>();
get onUpdateReady(): Event<IUpdate> { return this._onUpdateReady.event; }
this.raw.on('update-not-available', () => {
this.emit('update-not-available', this.explicitState === ExplicitState.Explicit);
this.setState(State.Idle);
});
private _onStateChange = new Emitter<State>();
get onStateChange(): Event<State> { return this._onStateChange.event; }
this.raw.on('update-downloaded', (event: any, releaseNotes: string, version: string, date: Date, url: string, rawQuitAndUpdate: () => void) => {
const data: IUpdate = {
releaseNotes: releaseNotes,
version: version,
date: date,
quitAndUpdate: () => this.quitAndUpdate(rawQuitAndUpdate)
};
@memoize
private get onRawError(): Event<string> {
return fromEventEmitter(this.raw, 'error', (_, message) => message);
}
this.emit('update-downloaded', data);
this.setState(State.UpdateDownloaded, data);
});
@memoize
private get onRawUpdateNotAvailable(): Event<void> {
return fromEventEmitter<void>(this.raw, 'update-not-available');
}
private quitAndUpdate(rawQuitAndUpdate: () => void): void {
this.lifecycleService.quit(true /* from update */).done(vetod => {
if (vetod) {
return;
}
@memoize
private get onRawUpdateAvailable(): Event<{ url: string; version: string; }> {
return filterEvent(fromEventEmitter(this.raw, 'update-available', (_, url, version) => ({ url, version })), ({ url }) => !!url);
}
// for some reason updating on Mac causes the local storage not to be flushed.
// we workaround this issue by forcing an explicit flush of the storage data.
// see also https://github.com/Microsoft/vscode/issues/172
if (process.platform === 'darwin') {
electron.session.defaultSession.flushStorageData();
}
@memoize
private get onRawUpdateDownloaded(): Event<IRawUpdate> {
return fromEventEmitter(this.raw, 'update-downloaded', (_, releaseNotes, version, date, url) => ({ releaseNotes, version, date }));
}
rawQuitAndUpdate();
});
get state(): State {
return this._state;
}
public get feedUrl(): string {
return this._feedUrl;
set state(state: State) {
this._state = state;
this._onStateChange.fire(state);
}
public get channel(): string {
return this._channel;
get availableUpdate(): IUpdate {
return this._availableUpdate;
}
public initialize(): void {
if (this.feedUrl) {
return; // already initialized
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@ILifecycleService private lifecycleService: ILifecycleService,
@IConfigurationService private configurationService: IConfigurationService,
@ITelemetryService private telemetryService: ITelemetryService
) {
if (process.platform === 'win32') {
this.raw = instantiationService.createInstance(Win32AutoUpdaterImpl);
} else if (process.platform === 'linux') {
this.raw = instantiationService.createInstance(LinuxAutoUpdaterImpl);
} else if (process.platform === 'darwin') {
this.raw = electron.autoUpdater;
} else {
return;
}
telemetryService.publicLog('whattt', { yeah: 123 });
const channel = this.getUpdateChannel();
const feedUrl = this.getUpdateFeedUrl(channel);
......@@ -185,48 +114,82 @@ export class UpdateManager extends EventEmitter implements IUpdateService {
return; // application not signed
}
this._channel = channel;
this._feedUrl = feedUrl;
this.state = State.Idle;
this.setState(State.Idle);
// Start checking for updates after 30 seconds
this.scheduleCheckForUpdates(30 * 1000)
.done(null, err => console.error(err));
}
// Check for updates on startup after 30 seconds
let timer = setTimeout(() => this.checkForUpdates(), 30 * 1000);
private scheduleCheckForUpdates(delay = 60 * 60 * 1000): TPromise<void> {
return TPromise.timeout(delay)
.then(() => this.checkForUpdates())
.then(update => {
if (update) {
// Update found, no need to check more
return TPromise.as(null);
}
// Check again after 1 hour
return this.scheduleCheckForUpdates(60 * 60 * 1000);
});
}
// Clear timer when checking for update
this.on('error', (error: any, message: string) => console.error(error, message));
checkForUpdates(explicit = false): TPromise<IUpdate> {
return this.throttler.queue(() => this._checkForUpdates(explicit));
}
// Clear timer when checking for update
this.on('checking-for-update', () => clearTimeout(timer));
private _checkForUpdates(explicit: boolean): TPromise<IUpdate> {
if (this.state !== State.Idle) {
return TPromise.as(null);
}
// If update not found, try again in 1 hour
this.on('update-not-available', () => {
timer = setTimeout(() => this.checkForUpdates(), 60 * 60 * 1000);
});
}
this._onCheckForUpdate.fire();
this.state = State.CheckingForUpdate;
const listeners: IDisposable[] = [];
const result = new TPromise<IUpdate>((c, e) => {
once(this.onRawError)(e, null, listeners);
once(this.onRawUpdateNotAvailable)(() => c(null), null, listeners);
once(this.onRawUpdateAvailable)(({ url, version }) => url && c({ url, version }), null, listeners);
once(this.onRawUpdateDownloaded)(({ version, date, releaseNotes }) => c({ version, date, releaseNotes }), null, listeners);
this.raw.checkForUpdates();
}).then(update => {
if (!update) {
this._onUpdateNotAvailable.fire(explicit);
this.state = State.Idle;
this.telemetryService.publicLog('update:notAvailable', { explicit });
} else if (update.url) {
const data: IUpdate = {
url: update.url,
releaseNotes: '',
version: update.version,
date: new Date()
};
public get state(): State {
return this._state;
}
this._availableUpdate = data;
this._onUpdateAvailable.fire({ url: update.url, version: update.version });
this.state = State.UpdateAvailable;
public get availableUpdate(): IUpdate {
return this._availableUpdate;
}
} else {
const data: IUpdate = {
releaseNotes: update.releaseNotes,
version: update.version,
date: update.date
};
public get lastCheckDate(): Date {
return this._lastCheckDate;
}
this._availableUpdate = data;
this._onUpdateReady.fire(data);
this.state = State.UpdateDownloaded;
this.telemetryService.publicLog('update:downloaded', { version: update.version });
}
public checkForUpdates(explicit = false): void {
this.explicitState = explicit ? ExplicitState.Explicit : ExplicitState.Implicit;
this._lastCheckDate = new Date();
this.raw.checkForUpdates();
}
return update;
});
private setState(state: State, availableUpdate: IUpdate = null): void {
this._state = state;
this._availableUpdate = availableUpdate;
this.emit('change');
return always(result, () => dispose(listeners));
}
private getUpdateChannel(): string {
......@@ -253,4 +216,32 @@ export class UpdateManager extends EventEmitter implements IUpdateService {
return `${product.updateUrl}/api/update/${platform}/${channel}/${product.commit}`;
}
}
quitAndInstall(): TPromise<void> {
if (!this._availableUpdate) {
return TPromise.as(null);
}
if (this._availableUpdate.url) {
electron.shell.openExternal(this._availableUpdate.url);
return TPromise.as(null);
}
this.lifecycleService.quit(true /* from update */).done(vetod => {
if (vetod) {
return;
}
// for some reason updating on Mac causes the local storage not to be flushed.
// we workaround this issue by forcing an explicit flush of the storage data.
// see also https://github.com/Microsoft/vscode/issues/172
if (process.platform === 'darwin') {
electron.session.defaultSession.flushStorageData();
}
this.raw.quitAndInstall();
});
return TPromise.as(null);
}
}
\ No newline at end of file
......@@ -81,6 +81,8 @@ import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/
import { IExtensionManagementChannel, ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import { IExtensionManagementService, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { UpdateChannelClient } from 'vs/platform/update/common/updateIpc';
import { IUpdateService } from 'vs/platform/update/common/update';
import { URLChannelClient } from 'vs/platform/url/common/urlIpc';
import { IURLService } from 'vs/platform/url/common/url';
import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions';
......@@ -350,6 +352,10 @@ export class WorkbenchShell {
const integrityService = instantiationService.createInstance(IntegrityServiceImpl);
serviceCollection.set(IIntegrityService, integrityService);
const updateChannel = mainProcessClient.getChannel('update');
const updateChannelClient = new UpdateChannelClient(updateChannel);
serviceCollection.set(IUpdateService, updateChannelClient);
const urlChannel = mainProcessClient.getChannel('url');
const urlChannelClient = new URLChannelClient(urlChannel, windowIPCService.getWindowId());
serviceCollection.set(IURLService, urlChannelClient);
......
......@@ -9,7 +9,6 @@ import nls = require('vs/nls');
import severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import { ipcRenderer as ipc } from 'electron';
import { IMessageService, CloseAction, Severity } from 'vs/platform/message/common/message';
import pkg from 'vs/platform/package';
import product from 'vs/platform/product';
......@@ -25,21 +24,18 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
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 } from 'vs/platform/update/common/update';
import * as semver from 'semver';
interface IUpdate {
releaseNotes: string;
version: string;
date: string;
}
class ApplyUpdateAction extends Action {
constructor( @IUpdateService private updateService: IUpdateService) {
super('update.applyUpdate', nls.localize('updateNow', "Update Now"), null, true);
}
const ApplyUpdateAction = new Action(
'update.applyUpdate',
nls.localize('updateNow', "Update Now"),
null,
true,
() => { ipc.send('vscode:update-apply'); return TPromise.as(true); }
);
run(): TPromise<void> {
return this.updateService.quitAndInstall();
}
}
const NotNowAction = new Action(
'update.later',
......@@ -173,13 +169,16 @@ export class ShowCurrentReleaseNotesAction extends AbstractShowReleaseNotesActio
}
}
export const DownloadAction = (url: string) => new Action(
'update.download',
nls.localize('downloadNow', "Download Now"),
null,
true,
() => { window.open(url); return TPromise.as(true); }
);
export class DownloadAction extends Action {
constructor(private url: string, @IUpdateService private updateService: IUpdateService) {
super('update.download', nls.localize('downloadNow', "Download Now"), null, true);
}
run(): TPromise<void> {
return this.updateService.quitAndInstall();
}
}
const LinkAction = (id: string, message: string, licenseUrl: string) => new Action(
id, message, null, true,
......@@ -196,6 +195,7 @@ export class UpdateContribution implements IWorkbenchContribution {
@IStorageService storageService: IStorageService,
@IInstantiationService instantiationService: IInstantiationService,
@IMessageService messageService: IMessageService,
@IUpdateService updateService: IUpdateService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService
) {
const lastVersion = storageService.get(UpdateContribution.KEY, StorageScope.GLOBAL, '');
......@@ -250,26 +250,31 @@ export class UpdateContribution implements IWorkbenchContribution {
storageService.store(UpdateContribution.KEY, pkg.version, StorageScope.GLOBAL);
ipc.on('vscode:update-downloaded', (event, data: string) => {
const update = JSON.parse(data) as IUpdate;
updateService.onUpdateReady(update => {
const applyUpdateAction = instantiationService.createInstance(ApplyUpdateAction);
const releaseNotesAction = instantiationService.createInstance(ShowReleaseNotesAction, false, update.version);
messageService.show(severity.Info, {
message: nls.localize('updateAvailable', "{0} will be updated after it restarts.", product.nameLong),
actions: [ApplyUpdateAction, NotNowAction, releaseNotesAction]
actions: [applyUpdateAction, NotNowAction, releaseNotesAction]
});
});
ipc.on('vscode:update-available', (event, url: string, version: string) => {
const releaseNotesAction = instantiationService.createInstance(ShowReleaseNotesAction, false, version);
updateService.onUpdateAvailable(update => {
const downloadAction = instantiationService.createInstance(DownloadAction, update.version);
const releaseNotesAction = instantiationService.createInstance(ShowReleaseNotesAction, false, update.version);
messageService.show(severity.Info, {
message: nls.localize('thereIsUpdateAvailable', "There is an available update."),
actions: [DownloadAction(url), NotNowAction, releaseNotesAction]
actions: [downloadAction, NotNowAction, releaseNotesAction]
});
});
ipc.on('vscode:update-not-available', () => {
updateService.onUpdateNotAvailable(explicit => {
if (!explicit) {
return;
}
messageService.show(severity.Info, nls.localize('noUpdatesAvailable', "There are no updates currently available."));
});
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册