提交 f4321ecf 编写于 作者: J João Moreno

Merge pull request #5658 from joaomoreno/main-services

Introduce services in Electron main process
......@@ -6,12 +6,12 @@
'use strict';
import events = require('events');
import {isString} from 'vs/base/common/types';
import {Promise} from 'vs/base/common/winjs.base';
import {json } from 'vs/base/node/request';
import { isString } from 'vs/base/common/types';
import { Promise } from 'vs/base/common/winjs.base';
import { json } from 'vs/base/node/request';
import { getProxyAgent } from 'vs/base/node/proxy';
import {manager as Settings} from 'vs/workbench/electron-main/settings';
import env = require('vs/workbench/electron-main/env');
import { ISettingsManager } from 'vs/workbench/electron-main/settings';
import { IEnvService } from 'vs/workbench/electron-main/env';
export interface IUpdate {
url: string;
......@@ -25,7 +25,10 @@ export class LinuxAutoUpdaterImpl extends events.EventEmitter {
private url: string;
private currentRequest: Promise;
constructor() {
constructor(
@IEnvService private envService: IEnvService,
@ISettingsManager private settingsManager: ISettingsManager
) {
super();
this.url = null;
......@@ -47,8 +50,8 @@ export class LinuxAutoUpdaterImpl extends events.EventEmitter {
this.emit('checking-for-update');
const proxyUrl = Settings.getValue('http.proxy');
const strictSSL = Settings.getValue('http.proxyStrictSSL', true);
const proxyUrl = this.settingsManager.getValue('http.proxy');
const strictSSL = this.settingsManager.getValue('http.proxyStrictSSL', true);
const agent = getProxyAgent(this.url, { proxyUrl, strictSSL });
this.currentRequest = json<IUpdate>({ url: this.url, agent })
......@@ -56,7 +59,7 @@ export class LinuxAutoUpdaterImpl extends events.EventEmitter {
if (!update || !update.url || !update.version) {
this.emit('update-not-available');
} else {
this.emit('update-available', null, env.product.downloadUrl);
this.emit('update-available', null, this.envService.product.downloadUrl);
}
})
.then(null, e => {
......
......@@ -15,9 +15,9 @@ import { isString } from 'vs/base/common/types';
import { Promise, TPromise } from 'vs/base/common/winjs.base';
import { download, json } from 'vs/base/node/request';
import { getProxyAgent } from 'vs/base/node/proxy';
import { manager as Settings } from 'vs/workbench/electron-main/settings';
import { manager as Lifecycle } from 'vs/workbench/electron-main/lifecycle';
import { quality } from './env';
import { ISettingsManager } from 'vs/workbench/electron-main/settings';
import { ILifecycleService } from 'vs/workbench/electron-main/lifecycle';
import { IEnvService } from './env';
export interface IUpdate {
url: string;
......@@ -31,7 +31,11 @@ export class Win32AutoUpdaterImpl extends events.EventEmitter {
private url: string;
private currentRequest: Promise;
constructor() {
constructor(
@ILifecycleService private lifecycleService: ILifecycleService,
@IEnvService private envService: IEnvService,
@ISettingsManager private settingsManager: ISettingsManager
) {
super();
this.url = null;
......@@ -58,8 +62,8 @@ export class Win32AutoUpdaterImpl extends events.EventEmitter {
this.emit('checking-for-update');
const proxyUrl = Settings.getValue('http.proxy');
const strictSSL = Settings.getValue('http.proxyStrictSSL', true);
const proxyUrl = this.settingsManager.getValue('http.proxy');
const strictSSL = this.settingsManager.getValue('http.proxyStrictSSL', true);
const agent = getProxyAgent(this.url, { proxyUrl, strictSSL });
this.currentRequest = json<IUpdate>({ url: this.url, agent })
......@@ -110,11 +114,11 @@ export class Win32AutoUpdaterImpl extends events.EventEmitter {
}
private getUpdatePackagePath(version: string): TPromise<string> {
return this.cachePath.then(cachePath => path.join(cachePath, `CodeSetup-${ quality }-${ version }.exe`));
return this.cachePath.then(cachePath => path.join(cachePath, `CodeSetup-${ this.envService.quality }-${ version }.exe`));
}
private quitAndUpdate(updatePackagePath: string): void {
Lifecycle.quit().done(vetod => {
this.lifecycleService.quit().done(vetod => {
if (vetod) {
return;
}
......@@ -127,7 +131,7 @@ export class Win32AutoUpdaterImpl extends events.EventEmitter {
}
private cleanup(exceptVersion: string = null): Promise {
const filter = exceptVersion ? one => !(new RegExp(`${ quality }-${ exceptVersion }\\.exe$`).test(one)) : () => true;
const filter = exceptVersion ? one => !(new RegExp(`${ this.envService.quality }-${ exceptVersion }\\.exe$`).test(one)) : () => true;
return this.cachePath
.then(cachePath => pfs.readdir(cachePath)
......
......@@ -10,17 +10,14 @@ import fs = require('fs');
import path = require('path');
import os = require('os');
import {app} from 'electron';
import arrays = require('vs/base/common/arrays');
import strings = require('vs/base/common/strings');
import paths = require('vs/base/common/paths');
import platform = require('vs/base/common/platform');
import uri from 'vs/base/common/uri';
import { assign } from 'vs/base/common/objects';
import types = require('vs/base/common/types');
export interface IUpdateInfo {
baseUrl: string;
}
import {ServiceIdentifier, createDecorator} from 'vs/platform/instantiation/common/instantiation';
export interface IProductConfiguration {
nameShort: string;
......@@ -60,60 +57,6 @@ export interface IProductConfiguration {
privacyStatementUrl: string;
}
export const isBuilt = !process.env.VSCODE_DEV;
export const appRoot = path.dirname(uri.parse(require.toUrl('')).fsPath);
export const currentWorkingDirectory = process.env.VSCODE_CWD || process.cwd();
let productContents: IProductConfiguration;
try {
productContents = JSON.parse(fs.readFileSync(path.join(appRoot, 'product.json'), 'utf8'));
} catch (error) {
productContents = Object.create(null);
}
export const product: IProductConfiguration = productContents;
product.nameShort = product.nameShort + (isBuilt ? '' : ' Dev');
product.nameLong = product.nameLong + (isBuilt ? '' : ' Dev');
product.dataFolderName = product.dataFolderName + (isBuilt ? '' : '-dev');
export const updateUrl = product.updateUrl;
export const quality = product.quality;
export const mainIPCHandle = getMainIPCHandle();
export const sharedIPCHandle = getSharedIPCHandle();
export const version = app.getVersion();
export const cliArgs = parseCli();
export const appHome = app.getPath('userData');
export const appSettingsHome = path.join(appHome, 'User');
if (!fs.existsSync(appSettingsHome)) {
fs.mkdirSync(appSettingsHome);
}
export const appSettingsPath = path.join(appSettingsHome, 'settings.json');
export const appKeybindingsPath = path.join(appSettingsHome, 'keybindings.json');
export const userHome = path.join(app.getPath('home'), product.dataFolderName);
if (!fs.existsSync(userHome)) {
fs.mkdirSync(userHome);
}
export const userExtensionsHome = cliArgs.extensionsHomePath || path.join(userHome, 'extensions');
if (!fs.existsSync(userExtensionsHome)) {
fs.mkdirSync(userExtensionsHome);
}
// Helper to identify if we have extension tests to run from the command line without debugger
export const isTestingFromCli = cliArgs.extensionTestsPath && !cliArgs.debugBrkExtensionHost;
export function log(...a: any[]): void {
if (cliArgs.verboseLogging) {
console.log.call(null, `(${new Date().toLocaleTimeString()})`, ...a);
}
}
export interface IProcessEnvironment {
[key: string]: string;
}
......@@ -124,139 +67,240 @@ export interface ICommandLineArguments {
debugBrkExtensionHost: boolean;
logExtensionHostCommunication: boolean;
disableExtensions: boolean;
extensionsHomePath: string;
extensionDevelopmentPath: string;
extensionTestsPath: string;
programStart: number;
pathArguments?: string[];
enablePerformance?: boolean;
firstrun?: boolean;
openNewWindow?: boolean;
openInSameWindow?: boolean;
gotoLineMode?: boolean;
diffMode?: boolean;
locale?: string;
waitForWindowClose?: boolean;
}
function parseCli(): ICommandLineArguments {
export const IEnvService = createDecorator<IEnvService>('environmentService');
export interface IEnvService {
serviceId: ServiceIdentifier<any>;
cliArgs: ICommandLineArguments;
userExtensionsHome: string;
isTestingFromCli: boolean;
isBuilt: boolean;
product: IProductConfiguration;
updateUrl: string;
quality: string;
userHome: string;
appRoot: string;
currentWorkingDirectory: string;
version: string;
appHome: string;
appSettingsHome: string;
appSettingsPath: string;
appKeybindingsPath: string;
mainIPCHandle: string;
sharedIPCHandle: string;
}
// We need to do some argv massaging. First, remove the Electron executable
let args = Array.prototype.slice.call(process.argv, 1);
export class EnvService implements IEnvService {
// Then, when in dev, remove the first non option argument, it will be the app location
if (!isBuilt) {
let i = (() => {
for (let j = 0; j < args.length; j++) {
if (args[j][0] !== '-') {
return j;
}
}
serviceId = IEnvService;
private _cliArgs: ICommandLineArguments;
get cliArgs(): ICommandLineArguments { return this._cliArgs; }
private _userExtensionsHome: string;
get userExtensionsHome(): string { return this._userExtensionsHome; }
private _isTestingFromCli: boolean;
get isTestingFromCli(): boolean { return this._isTestingFromCli; }
get isBuilt(): boolean { return !process.env['VSCODE_DEV']; }
private _product: IProductConfiguration;
get product(): IProductConfiguration { return this._product; }
get updateUrl(): string { return this.product.updateUrl; }
get quality(): string { return this.product.quality; }
private _userHome: string;
get userHome(): string { return this._userHome; }
private _appRoot: string;
get appRoot(): string { return this._appRoot; }
private _currentWorkingDirectory: string;
get currentWorkingDirectory(): string { return this._currentWorkingDirectory; }
return -1;
})();
private _version: string;
get version(): string { return this._version; }
if (i > -1) {
args.splice(i, 1);
private _appHome: string;
get appHome(): string { return this._appHome; }
private _appSettingsHome: string;
get appSettingsHome(): string { return this._appSettingsHome; }
private _appSettingsPath: string;
get appSettingsPath(): string { return this._appSettingsPath; }
private _appKeybindingsPath: string;
get appKeybindingsPath(): string { return this._appKeybindingsPath; }
private _mainIPCHandle: string;
get mainIPCHandle(): string { return this._mainIPCHandle; }
private _sharedIPCHandle: string;
get sharedIPCHandle(): string { return this._sharedIPCHandle; }
constructor() {
this._appRoot = path.dirname(uri.parse(require.toUrl('')).fsPath);
this._currentWorkingDirectory = process.env['VSCODE_CWD'] || process.cwd();
this._version = app.getVersion();
this._appHome = app.getPath('userData');
this._appSettingsHome = path.join(this._appHome, 'User');
// TODO move out of here!
if (!fs.existsSync(this._appSettingsHome)) {
fs.mkdirSync(this._appSettingsHome);
}
}
// Finally, any extra arguments in the 'argv' file should be prepended
if (fs.existsSync(path.join(appRoot, 'argv'))) {
let extraargs: string[] = JSON.parse(fs.readFileSync(path.join(appRoot, 'argv'), 'utf8'));
args = extraargs.concat(args);
}
this._appSettingsPath = path.join(this._appSettingsHome, 'settings.json');
this._appKeybindingsPath = path.join(this._appSettingsHome, 'keybindings.json');
let opts = parseOpts(args);
// Remove the Electron executable
let [, ...args] = process.argv;
let gotoLineMode = !!opts['g'] || !!opts['goto'];
// Id dev, remove the first argument: it's the app location
if (!this.isBuilt) {
[, ...args] = args;
}
let debugBrkExtensionHostPort = parseNumber(args, '--debugBrkPluginHost', 5870);
let debugExtensionHostPort: number;
let debugBrkExtensionHost: boolean;
if (debugBrkExtensionHostPort) {
debugExtensionHostPort = debugBrkExtensionHostPort;
debugBrkExtensionHost = true;
} else {
debugExtensionHostPort = parseNumber(args, '--debugPluginHost', 5870, isBuilt ? void 0 : 5870);
}
// Finally, prepend any extra arguments from the 'argv' file
if (fs.existsSync(path.join(this._appRoot, 'argv'))) {
const extraargs: string[] = JSON.parse(fs.readFileSync(path.join(this._appRoot, 'argv'), 'utf8'));
args = [...extraargs, ...args];
}
let pathArguments = parsePathArguments(args, gotoLineMode);
const opts = parseOpts(args);
return {
pathArguments: pathArguments,
programStart: parseNumber(args, '--timestamp', 0, 0),
enablePerformance: !!opts['p'],
verboseLogging: !!opts['verbose'],
debugExtensionHostPort: debugExtensionHostPort,
debugBrkExtensionHost: debugBrkExtensionHost,
logExtensionHostCommunication: !!opts['logExtensionHostCommunication'],
firstrun: !!opts['squirrel-firstrun'],
openNewWindow: !!opts['n'] || !!opts['new-window'],
openInSameWindow: !!opts['r'] || !!opts['reuse-window'],
gotoLineMode: gotoLineMode,
diffMode: (!!opts['d'] || !!opts['diff']) && pathArguments.length === 2,
extensionsHomePath: normalizePath(parseString(args, '--extensionHomePath')),
extensionDevelopmentPath: normalizePath(parseString(args, '--extensionDevelopmentPath')),
extensionTestsPath: normalizePath(parseString(args, '--extensionTestsPath')),
disableExtensions: !!opts['disableExtensions'] || !!opts['disable-extensions'],
locale: parseString(args, '--locale'),
waitForWindowClose: !!opts['w'] || !!opts['wait']
};
}
const gotoLineMode = !!opts['g'] || !!opts['goto'];
const debugBrkExtensionHostPort = parseNumber(args, '--debugBrkPluginHost', 5870);
let debugExtensionHostPort: number;
let debugBrkExtensionHost: boolean;
function getIPCHandleName(): string {
let handleName = app.getName();
if (debugBrkExtensionHostPort) {
debugExtensionHostPort = debugBrkExtensionHostPort;
debugBrkExtensionHost = true;
} else {
debugExtensionHostPort = parseNumber(args, '--debugPluginHost', 5870, this.isBuilt ? void 0 : 5870);
}
const pathArguments = parsePathArguments(this._currentWorkingDirectory, args, gotoLineMode);
this._cliArgs = Object.freeze({
pathArguments: pathArguments,
programStart: parseNumber(args, '--timestamp', 0, 0),
enablePerformance: !!opts['p'],
verboseLogging: !!opts['verbose'],
debugExtensionHostPort: debugExtensionHostPort,
debugBrkExtensionHost: debugBrkExtensionHost,
logExtensionHostCommunication: !!opts['logExtensionHostCommunication'],
firstrun: !!opts['squirrel-firstrun'],
openNewWindow: !!opts['n'] || !!opts['new-window'],
openInSameWindow: !!opts['r'] || !!opts['reuse-window'],
gotoLineMode: gotoLineMode,
diffMode: (!!opts['d'] || !!opts['diff']) && pathArguments.length === 2,
extensionsHomePath: normalizePath(parseString(args, '--extensionHomePath')),
extensionDevelopmentPath: normalizePath(parseString(args, '--extensionDevelopmentPath')),
extensionTestsPath: normalizePath(parseString(args, '--extensionTestsPath')),
disableExtensions: !!opts['disableExtensions'] || !!opts['disable-extensions'],
locale: parseString(args, '--locale'),
waitForWindowClose: !!opts['w'] || !!opts['wait']
});
this._isTestingFromCli = this.cliArgs.extensionTestsPath && !this.cliArgs.debugBrkExtensionHost;
try {
this._product = JSON.parse(fs.readFileSync(path.join(this._appRoot, 'product.json'), 'utf8'));
} catch (error) {
this._product = Object.create(null);
}
if (!isBuilt) {
handleName += '-dev';
if (!this.isBuilt) {
const nameShort = `${ this._product.nameShort } Dev`;
const nameLong = `${ this._product.nameLong } Dev`;
const dataFolderName = `${ this._product.dataFolderName }-dev`;
this._product = assign(this._product, { nameShort, nameLong, dataFolderName });
}
this._userHome = path.join(app.getPath('home'), this._product.dataFolderName);
// TODO move out of here!
if (!fs.existsSync(this._userHome)) {
fs.mkdirSync(this._userHome);
}
this._userExtensionsHome = this.cliArgs.extensionsHomePath || path.join(this.userHome, 'extensions');
// TODO move out of here!
if (!fs.existsSync(this._userExtensionsHome)) {
fs.mkdirSync(this._userExtensionsHome);
}
this._mainIPCHandle = this.getMainIPCHandle();
this._sharedIPCHandle = this.getSharedIPCHandle();
}
// Support to run VS Code multiple times as different user
// by making the socket unique over the logged in user
let userId = uniqueUserId();
if (userId) {
handleName += ('-' + userId);
private getMainIPCHandle(): string {
return this.getIPCHandleName() + (process.platform === 'win32' ? '-sock' : '.sock');
}
if (process.platform === 'win32') {
return '\\\\.\\pipe\\' + handleName;
private getSharedIPCHandle(): string {
return this.getIPCHandleName() + '-shared' + (process.platform === 'win32' ? '-sock' : '.sock');
}
return path.join(os.tmpdir(), handleName);
}
private getIPCHandleName(): string {
let handleName = app.getName();
function getMainIPCHandle(): string {
return getIPCHandleName() + (process.platform === 'win32' ? '-sock' : '.sock');
}
if (!this.isBuilt) {
handleName += '-dev';
}
function getSharedIPCHandle(): string {
return getIPCHandleName() + '-shared' + (process.platform === 'win32' ? '-sock' : '.sock');
}
// Support to run VS Code multiple times as different user
// by making the socket unique over the logged in user
let userId = EnvService.getUniqueUserId();
if (userId) {
handleName += ('-' + userId);
}
function uniqueUserId(): string {
let username: string;
if (platform.isWindows) {
username = process.env.USERNAME;
} else {
username = process.env.USER;
}
if (process.platform === 'win32') {
return '\\\\.\\pipe\\' + handleName;
}
if (!username) {
return ''; // fail gracefully if there is no user name
return path.join(os.tmpdir(), handleName);
}
// use sha256 to ensure the userid value can be used in filenames and are unique
return crypto.createHash('sha256').update(username).digest('hex').substr(0, 6);
private static getUniqueUserId(): string {
let username: string;
if (platform.isWindows) {
username = process.env.USERNAME;
} else {
username = process.env.USER;
}
if (!username) {
return ''; // fail gracefully if there is no user name
}
// use sha256 to ensure the userid value can be used in filenames and are unique
return crypto.createHash('sha256').update(username).digest('hex').substr(0, 6);
}
}
type OptionBag = { [opt: string]: boolean; };
......@@ -268,7 +312,7 @@ function parseOpts(argv: string[]): OptionBag {
.reduce((r, a) => { r[a] = true; return r; }, <OptionBag>{});
}
function parsePathArguments(argv: string[], gotoLineMode?: boolean): string[] {
function parsePathArguments(cwd: string, argv: string[], gotoLineMode?: boolean): string[] {
return arrays.coalesce( // no invalid paths
arrays.distinct( // no duplicates
argv.filter(a => !(/^-/.test(a))) // arguments without leading "-"
......@@ -282,7 +326,7 @@ function parsePathArguments(argv: string[], gotoLineMode?: boolean): string[] {
}
if (pathCandidate) {
pathCandidate = preparePath(pathCandidate);
pathCandidate = preparePath(cwd, pathCandidate);
}
let realPath: string;
......@@ -291,7 +335,7 @@ function parsePathArguments(argv: string[], gotoLineMode?: boolean): string[] {
} catch (error) {
// in case of an error, assume the user wants to create this file
// if the path is relative, we join it to the cwd
realPath = path.normalize(path.isAbsolute(pathCandidate) ? pathCandidate : path.join(currentWorkingDirectory, pathCandidate));
realPath = path.normalize(path.isAbsolute(pathCandidate) ? pathCandidate : path.join(cwd, pathCandidate));
}
if (!paths.isValidBasename(path.basename(realPath))) {
......@@ -312,7 +356,7 @@ function parsePathArguments(argv: string[], gotoLineMode?: boolean): string[] {
);
}
function preparePath(p: string): string {
function preparePath(cwd: string, p: string): string {
// Trim trailing quotes
if (platform.isWindows) {
......@@ -325,7 +369,7 @@ function preparePath(p: string): string {
if (platform.isWindows) {
// Resolve the path against cwd if it is relative
p = path.resolve(currentWorkingDirectory, p);
p = path.resolve(cwd, p);
// Trim trailing '.' chars on Windows to prevent invalid file names
p = strings.rtrim(p, '.');
......
/*---------------------------------------------------------------------------------------------
* 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 { ICommandLineArguments, IProcessEnvironment } from 'vs/workbench/electron-main/env';
import { IWindowsManager } from 'vs/workbench/electron-main/windows';
import { VSCodeWindow } from 'vs/workbench/electron-main/window';
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { ILogService } from './log';
export interface ILaunchService {
start(args: ICommandLineArguments, userEnv: IProcessEnvironment): TPromise<void>;
}
export interface ILaunchChannel extends IChannel {
call(command: 'start', args: ICommandLineArguments, userEnv: IProcessEnvironment): TPromise<void>;
call(command: string, ...args: any[]): TPromise<any>;
}
export class LaunchChannel implements ILaunchChannel {
constructor(private service: ILaunchService) { }
call(command: string, ...args: any[]): TPromise<any> {
switch (command) {
case 'start': return this.service.start(args[0], args[1]);
}
}
}
export class LaunchChannelClient implements ILaunchService {
constructor(private channel: ILaunchChannel) { }
start(args: ICommandLineArguments, userEnv: IProcessEnvironment): TPromise<void> {
return this.channel.call('start', args, userEnv);
}
}
export class LaunchService implements ILaunchService {
constructor(
@ILogService private logService: ILogService,
@IWindowsManager private windowsManager: IWindowsManager
) {}
start(args: ICommandLineArguments, userEnv: IProcessEnvironment): TPromise<void> {
this.logService.log('Received data from other instance', args);
// Otherwise handle in windows manager
let usedWindows: VSCodeWindow[];
if (!!args.extensionDevelopmentPath) {
this.windowsManager.openPluginDevelopmentHostWindow({ cli: args, userEnv: userEnv });
} else if (args.pathArguments.length === 0 && args.openNewWindow) {
usedWindows = this.windowsManager.open({ cli: args, userEnv: userEnv, forceNewWindow: true, forceEmpty: true });
} else if (args.pathArguments.length === 0) {
usedWindows = [this.windowsManager.focusLastActive(args)];
} else {
usedWindows = this.windowsManager.open({
cli: args,
userEnv: userEnv,
forceNewWindow: args.waitForWindowClose || args.openNewWindow,
preferNewWindow: !args.openInSameWindow,
diffMode: args.diffMode
});
}
// If the other instance is waiting to be killed, we hook up a window listener if one window
// is being used and only then resolve the startup promise which will kill this second instance
if (args.waitForWindowClose && usedWindows && usedWindows.length === 1 && usedWindows[0]) {
const windowId = usedWindows[0].id;
return new TPromise<void>((c, e) => {
const unbind = this.windowsManager.onClose(id => {
if (id === windowId) {
unbind();
c(null);
}
});
});
}
return TPromise.as(null);
}
}
\ No newline at end of file
......@@ -11,37 +11,55 @@ import {ipcMain as ipc, app} from 'electron';
import {TPromise, TValueCallback} from 'vs/base/common/winjs.base';
import {ReadyState, VSCodeWindow} from 'vs/workbench/electron-main/window';
import env = require('vs/workbench/electron-main/env');
const eventEmitter = new events.EventEmitter();
import {ServiceIdentifier, createDecorator} from 'vs/platform/instantiation/common/instantiation';
import {ILogService} from './log';
const EventTypes = {
BEFORE_QUIT: 'before-quit'
};
/**
* Due to the way we handle lifecycle with eventing, the general app.on('before-quit')
* event cannot be used because it can be called twice on shutdown. Instead the onBeforeQuit
* handler in this module can be used and it is only called once on a shutdown sequence.
*/
export function onBeforeQuit(clb: () => void): () => void {
eventEmitter.addListener(EventTypes.BEFORE_QUIT, clb);
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
return () => eventEmitter.removeListener(EventTypes.BEFORE_QUIT, clb);
export interface ILifecycleService {
serviceId: ServiceIdentifier<any>;
onBeforeQuit(clb: () => void): () => void;
ready(): void;
registerWindow(vscodeWindow: VSCodeWindow): void;
unload(vscodeWindow: VSCodeWindow): TPromise<boolean /* veto */>;
quit(): TPromise<boolean /* veto */>;
}
export class Lifecycle {
export class LifecycleService implements ILifecycleService {
serviceId = ILifecycleService;
private eventEmitter = new events.EventEmitter();
private windowToCloseRequest: { [windowId: string]: boolean };
private quitRequested: boolean;
private pendingQuitPromise: TPromise<boolean>;
private pendingQuitPromiseComplete: TValueCallback<boolean>;
private oneTimeListenerTokenGenerator: number;
constructor() {
constructor(
@env.IEnvService private envService: env.IEnvService,
@ILogService private logService: ILogService
) {
this.windowToCloseRequest = Object.create(null);
this.quitRequested = false;
this.oneTimeListenerTokenGenerator = 0;
}
/**
* Due to the way we handle lifecycle with eventing, the general app.on('before-quit')
* event cannot be used because it can be called twice on shutdown. Instead the onBeforeQuit
* handler in this module can be used and it is only called once on a shutdown sequence.
*/
onBeforeQuit(clb: () => void): () => void {
this.eventEmitter.addListener(EventTypes.BEFORE_QUIT, clb);
return () => this.eventEmitter.removeListener(EventTypes.BEFORE_QUIT, clb);
}
public ready(): void {
this.registerListeners();
}
......@@ -50,10 +68,10 @@ export class Lifecycle {
// before-quit
app.on('before-quit', (e) => {
env.log('Lifecycle#before-quit');
this.logService.log('Lifecycle#before-quit');
if (!this.quitRequested) {
eventEmitter.emit(EventTypes.BEFORE_QUIT); // only send this if this is the first quit request we have
this.eventEmitter.emit(EventTypes.BEFORE_QUIT); // only send this if this is the first quit request we have
}
this.quitRequested = true;
......@@ -61,12 +79,12 @@ export class Lifecycle {
// window-all-closed
app.on('window-all-closed', () => {
env.log('Lifecycle#window-all-closed');
this.logService.log('Lifecycle#window-all-closed');
// Windows/Linux: we quit when all windows have closed
// Mac: we only quit when quit was requested
// --wait: we quit when all windows are closed
if (this.quitRequested || process.platform !== 'darwin' || env.cliArgs.waitForWindowClose) {
if (this.quitRequested || process.platform !== 'darwin' || this.envService.cliArgs.waitForWindowClose) {
app.quit();
}
});
......@@ -77,11 +95,11 @@ export class Lifecycle {
// Window Before Closing: Main -> Renderer
vscodeWindow.win.on('close', (e) => {
let windowId = vscodeWindow.id;
env.log('Lifecycle#window-before-close', windowId);
this.logService.log('Lifecycle#window-before-close', windowId);
// The window already acknowledged to be closed
if (this.windowToCloseRequest[windowId]) {
env.log('Lifecycle#window-close', windowId);
this.logService.log('Lifecycle#window-close', windowId);
delete this.windowToCloseRequest[windowId];
......@@ -108,8 +126,8 @@ export class Lifecycle {
if (vscodeWindow.readyState !== ReadyState.READY) {
return TPromise.as<boolean>(false);
}
env.log('Lifecycle#unload()', vscodeWindow.id);
this.logService.log('Lifecycle#unload()', vscodeWindow.id);
return new TPromise<boolean>((c) => {
let oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
......@@ -141,7 +159,7 @@ export class Lifecycle {
* by the user or not.
*/
public quit(): TPromise<boolean /* veto */> {
env.log('Lifecycle#quit()');
this.logService.log('Lifecycle#quit()');
if (!this.pendingQuitPromise) {
this.pendingQuitPromise = new TPromise<boolean>((c) => {
......@@ -163,6 +181,4 @@ export class Lifecycle {
return this.pendingQuitPromise;
}
}
export const manager = new Lifecycle();
\ No newline at end of file
}
\ 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 { ServiceIdentifier, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IEnvService } from './env';
export const ILogService = createDecorator<ILogService>('logService');
export interface ILogService {
serviceId: ServiceIdentifier<any>;
log(... args: any[]): void;
}
export class MainLogService implements ILogService {
serviceId = ILogService;
constructor(@IEnvService private envService: IEnvService) {
}
log(...args: any[]): void {
const { verboseLogging } = this.envService.cliArgs;
if (verboseLogging) {
console.log(`(${new Date().toLocaleTimeString()})`, ...args);
}
}
}
\ No newline at end of file
......@@ -10,120 +10,35 @@ import fs = require('fs');
import nls = require('vs/nls');
import {assign} from 'vs/base/common/objects';
import platform = require('vs/base/common/platform');
import env = require('vs/workbench/electron-main/env');
import { IProcessEnvironment, IEnvService, EnvService } from 'vs/workbench/electron-main/env';
import windows = require('vs/workbench/electron-main/windows');
import window = require('vs/workbench/electron-main/window');
import lifecycle = require('vs/workbench/electron-main/lifecycle');
import menu = require('vs/workbench/electron-main/menus');
import settings = require('vs/workbench/electron-main/settings');
import {Instance as UpdateManager} from 'vs/workbench/electron-main/update-manager';
import { ILifecycleService, LifecycleService } from 'vs/workbench/electron-main/lifecycle';
import { VSCodeMenu } from 'vs/workbench/electron-main/menus';
import {ISettingsManager, SettingsManager} from 'vs/workbench/electron-main/settings';
import {IUpdateManager, UpdateManager} from 'vs/workbench/electron-main/update-manager';
import {Server, serve, connect} from 'vs/base/parts/ipc/node/ipc.net';
import {getUserEnvironment} from 'vs/base/node/env';
import {TPromise} from 'vs/base/common/winjs.base';
import {AskpassChannel} from 'vs/workbench/parts/git/common/gitIpc';
import {GitAskpassService} from 'vs/workbench/parts/git/electron-main/askpassService';
import {spawnSharedProcess} from 'vs/workbench/electron-main/sharedProcess';
import {IChannel} from 'vs/base/parts/ipc/common/ipc';
import {Mutex} from 'windows-mutex';
import {LaunchService, ILaunchChannel, LaunchChannel, LaunchChannelClient} from './launch';
import {ServicesAccessor, IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService';
import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection';
import {SyncDescriptor} from 'vs/platform/instantiation/common/descriptors';
import {ILogService, MainLogService} from './log';
import {IStorageService, StorageService} from './storage';
function quit(accessor: ServicesAccessor, error?: Error);
function quit(accessor: ServicesAccessor, message?: string);
function quit(accessor: ServicesAccessor, arg?: any) {
const logService = accessor.get(ILogService);
interface ILaunchService {
start(args: env.ICommandLineArguments, userEnv: env.IProcessEnvironment): TPromise<void>;
}
export interface ILaunchChannel extends IChannel {
call(command: 'start', args: env.ICommandLineArguments, userEnv: env.IProcessEnvironment): TPromise<void>;
call(command: string, ...args: any[]): TPromise<any>;
}
class LaunchChannel implements ILaunchChannel {
constructor(private service: ILaunchService) { }
call(command: string, ...args: any[]): TPromise<any> {
switch (command) {
case 'start': return this.service.start(args[0], args[1]);
}
}
}
class LaunchChannelClient implements ILaunchService {
constructor(private channel: ILaunchChannel) { }
start(args: env.ICommandLineArguments, userEnv: env.IProcessEnvironment): TPromise<void> {
return this.channel.call('start', args, userEnv);
}
}
class LaunchService {
start(args: env.ICommandLineArguments, userEnv: env.IProcessEnvironment): TPromise<void> {
env.log('Received data from other instance', args);
// Otherwise handle in windows manager
let usedWindows: window.VSCodeWindow[];
if (!!args.extensionDevelopmentPath) {
windows.manager.openPluginDevelopmentHostWindow({ cli: args, userEnv: userEnv });
} else if (args.pathArguments.length === 0 && args.openNewWindow) {
usedWindows = windows.manager.open({ cli: args, userEnv: userEnv, forceNewWindow: true, forceEmpty: true });
} else if (args.pathArguments.length === 0) {
usedWindows = [windows.manager.focusLastActive(args)];
} else {
usedWindows = windows.manager.open({
cli: args,
userEnv: userEnv,
forceNewWindow: args.waitForWindowClose || args.openNewWindow,
preferNewWindow: !args.openInSameWindow,
diffMode: args.diffMode
});
}
// If the other instance is waiting to be killed, we hook up a window listener if one window
// is being used and only then resolve the startup promise which will kill this second instance
if (args.waitForWindowClose && usedWindows && usedWindows.length === 1 && usedWindows[0]) {
const windowId = usedWindows[0].id;
return new TPromise<void>((c, e) => {
const unbind = windows.onClose(id => {
if (id === windowId) {
unbind();
c(null);
}
});
});
}
return TPromise.as(null);
}
}
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
process.on('uncaughtException', (err: any) => {
if (err) {
// take only the message and stack property
let friendlyError = {
message: err.message,
stack: err.stack
};
// handle on client side
windows.manager.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
}
console.error('[uncaught exception in main]: ' + err);
if (err.stack) {
console.error(err.stack);
}
});
function quit(error?: Error);
function quit(message?: string);
function quit(arg?: any) {
let exitCode = 0;
if (typeof arg === 'string') {
env.log(arg);
logService.log(arg);
} else {
exitCode = 1; // signal error to the outside
if (arg.stack) {
......@@ -136,21 +51,49 @@ function quit(arg?: any) {
process.exit(exitCode); // in main, process.exit === app.exit
}
function main(ipcServer: Server, userEnv: env.IProcessEnvironment): void {
env.log('### VSCode main.js ###');
env.log(env.appRoot, env.cliArgs);
function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEnvironment): void {
const instantiationService = accessor.get(IInstantiationService);
const logService = accessor.get(ILogService);
const envService = accessor.get(IEnvService);
const windowManager = accessor.get(windows.IWindowsManager);
const lifecycleService = accessor.get(ILifecycleService);
const updateManager = accessor.get(IUpdateManager);
const settingsManager = accessor.get(ISettingsManager);
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
process.on('uncaughtException', (err: any) => {
if (err) {
// take only the message and stack property
let friendlyError = {
message: err.message,
stack: err.stack
};
// handle on client side
windowManager.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
}
console.error('[uncaught exception in main]: ' + err);
if (err.stack) {
console.error(err.stack);
}
});
logService.log('### VSCode main.js ###');
logService.log(envService.appRoot, envService.cliArgs);
// Setup Windows mutex
let windowsMutex: Mutex = null;
try {
const Mutex = (<any>require.__$__nodeRequire('windows-mutex')).Mutex;
windowsMutex = new Mutex(env.product.win32MutexName);
windowsMutex = new Mutex(envService.product.win32MutexName);
} catch (e) {
// noop
}
// Register IPC services
const launchService = new LaunchService();
const launchService = instantiationService.createInstance(LaunchService);
const launchChannel = new LaunchChannel(launchService);
ipcServer.registerChannel('launch', launchChannel);
......@@ -160,22 +103,22 @@ function main(ipcServer: Server, userEnv: env.IProcessEnvironment): void {
// Used by sub processes to communicate back to the main instance
process.env['VSCODE_PID'] = '' + process.pid;
process.env['VSCODE_IPC_HOOK'] = env.mainIPCHandle;
process.env['VSCODE_SHARED_IPC_HOOK'] = env.sharedIPCHandle;
process.env['VSCODE_IPC_HOOK'] = envService.mainIPCHandle;
process.env['VSCODE_SHARED_IPC_HOOK'] = envService.sharedIPCHandle;
// Spawn shared process
const sharedProcess = spawnSharedProcess();
const sharedProcess = instantiationService.invokeFunction(spawnSharedProcess);
// 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 && env.product.win32AppUserModelId) {
app.setAppUserModelId(env.product.win32AppUserModelId);
if (platform.isWindows && envService.product.win32AppUserModelId) {
app.setAppUserModelId(envService.product.win32AppUserModelId);
}
// Set programStart in the global scope
global.programStart = env.cliArgs.programStart;
global.programStart = envService.cliArgs.programStart;
function dispose() {
if (ipcServer) {
......@@ -192,33 +135,34 @@ function main(ipcServer: Server, userEnv: env.IProcessEnvironment): void {
// Dispose on app quit
app.on('will-quit', () => {
env.log('App#will-quit: disposing resources');
logService.log('App#will-quit: disposing resources');
dispose();
});
// Dispose on vscode:exit
ipc.on('vscode:exit', (event, code: number) => {
env.log('IPC#vscode:exit', code);
logService.log('IPC#vscode:exit', code);
dispose();
process.exit(code); // in main, process.exit === app.exit
});
// Lifecycle
lifecycle.manager.ready();
lifecycleService.ready();
// Load settings
settings.manager.loadSync();
settingsManager.loadSync();
// Propagate to clients
windows.manager.ready(userEnv);
windowManager.ready(userEnv);
// Install Menu
menu.manager.ready();
const menuManager = instantiationService.createInstance(VSCodeMenu);
menuManager.ready();
// Install Tasks
if (platform.isWindows && env.isBuilt) {
if (platform.isWindows && envService.isBuilt) {
app.setUserTasks([
{
title: nls.localize('newWindow', "New Window"),
......@@ -231,21 +175,24 @@ function main(ipcServer: Server, userEnv: env.IProcessEnvironment): void {
}
// Setup auto update
UpdateManager.initialize();
updateManager.initialize();
// Open our first window
if (env.cliArgs.openNewWindow && env.cliArgs.pathArguments.length === 0) {
windows.manager.open({ cli: env.cliArgs, forceNewWindow: true, forceEmpty: true }); // new window if "-n" was used without paths
} else if (global.macOpenFiles && global.macOpenFiles.length && (!env.cliArgs.pathArguments || !env.cliArgs.pathArguments.length)) {
windows.manager.open({ cli: env.cliArgs, pathsToOpen: global.macOpenFiles }); // mac: open-file event received on startup
if (envService.cliArgs.openNewWindow && envService.cliArgs.pathArguments.length === 0) {
windowManager.open({ cli: envService.cliArgs, forceNewWindow: true, forceEmpty: true }); // new window if "-n" was used without paths
} else if (global.macOpenFiles && global.macOpenFiles.length && (!envService.cliArgs.pathArguments || !envService.cliArgs.pathArguments.length)) {
windowManager.open({ cli: envService.cliArgs, pathsToOpen: global.macOpenFiles }); // mac: open-file event received on startup
} else {
windows.manager.open({ cli: env.cliArgs, forceNewWindow: env.cliArgs.openNewWindow, diffMode: env.cliArgs.diffMode }); // default: read paths from cli
windowManager.open({ cli: envService.cliArgs, forceNewWindow: envService.cliArgs.openNewWindow, diffMode: envService.cliArgs.diffMode }); // default: read paths from cli
}
}
function setupIPC(): TPromise<Server> {
function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
const logService = accessor.get(ILogService);
const envService = accessor.get(IEnvService);
function setup(retry: boolean): TPromise<Server> {
return serve(env.mainIPCHandle).then(server => {
return serve(envService.mainIPCHandle).then(server => {
if (platform.isMacintosh) {
app.dock.show(); // dock might be hidden at this case due to a retry
}
......@@ -262,23 +209,23 @@ function setupIPC(): TPromise<Server> {
}
// there's a running instance, let's connect to it
return connect(env.mainIPCHandle).then(
return connect(envService.mainIPCHandle).then(
client => {
// Tests from CLI require to be the only instance currently (TODO@Ben support multiple instances and output)
if (env.isTestingFromCli) {
if (envService.isTestingFromCli) {
const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
console.error(msg);
client.dispose();
return TPromise.wrapError(msg);
}
env.log('Sending env to running instance...');
logService.log('Sending env to running instance...');
const channel = client.getChannel<ILaunchChannel>('launch');
const service = new LaunchChannelClient(channel);
return service.start(env.cliArgs, process.env)
return service.start(envService.cliArgs, process.env)
.then(() => client.dispose())
.then(() => TPromise.wrapError('Sent env to running instance. Terminating...'));
},
......@@ -291,9 +238,9 @@ function setupIPC(): TPromise<Server> {
// let's delete it, since we can't connect to it
// and the retry the whole thing
try {
fs.unlinkSync(env.mainIPCHandle);
fs.unlinkSync(envService.mainIPCHandle);
} catch (e) {
env.log('Fatal error deleting obsolete instance handle', e);
logService.log('Fatal error deleting obsolete instance handle', e);
return TPromise.wrapError(e);
}
......@@ -306,6 +253,19 @@ function setupIPC(): TPromise<Server> {
return setup(true);
}
// TODO: isolate
const services = new ServiceCollection();
services.set(IEnvService, new SyncDescriptor(EnvService));
services.set(ILogService, new SyncDescriptor(MainLogService));
services.set(windows.IWindowsManager, new SyncDescriptor(windows.WindowsManager));
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
services.set(IStorageService, new SyncDescriptor(StorageService));
services.set(IUpdateManager, new SyncDescriptor(UpdateManager));
services.set(ISettingsManager, new SyncDescriptor(SettingsManager));
const instantiationService = new InstantiationService(services);
// 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)
getUserEnvironment()
......@@ -314,7 +274,8 @@ getUserEnvironment()
// Make sure the NLS Config travels to the rendered process
// See also https://github.com/Microsoft/vscode/issues/4558
userEnv['VSCODE_NLS_CONFIG'] = process.env['VSCODE_NLS_CONFIG'];
return setupIPC()
.then(ipcServer => main(ipcServer, userEnv));
return instantiationService.invokeFunction(setupIPC)
.then(ipcServer => instantiationService.invokeFunction(main, ipcServer, userEnv));
})
.done(null, quit);
\ No newline at end of file
.done(null, err => instantiationService.invokeFunction(quit, err));
\ No newline at end of file
......@@ -6,21 +6,34 @@
'use strict';
import {app} from 'electron';
import { ServiceIdentifier, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { UserSettings, ISettings } from 'vs/workbench/node/userSettings';
import { IEnvService } from 'vs/workbench/electron-main/env';
import Event from 'vs/base/common/event';
export const ISettingsManager = createDecorator<ISettingsManager>('settingsManager');
export interface ISettingsManager {
serviceId: ServiceIdentifier<any>;
globalSettings: ISettings;
loadSync(): boolean;
getValue(key: string, fallback?: any): any;
onChange: Event<ISettings>;
}
import env = require('vs/workbench/electron-main/env');
import {UserSettings} from 'vs/workbench/node/userSettings';
export class SettingsManager extends UserSettings implements ISettingsManager {
export class SettingsManager extends UserSettings {
serviceId = ISettingsManager;
constructor() {
super(env.appSettingsPath, env.appKeybindingsPath);
constructor(@IEnvService envService: IEnvService) {
super(envService.appSettingsPath, envService.appKeybindingsPath);
app.on('will-quit', () => {
this.dispose();
});
}
public loadSync(): boolean {
loadSync(): boolean {
const settingsChanged = super.loadSync();
// Store into global so that any renderer can access the value with remote.getGlobal()
......@@ -30,6 +43,4 @@ export class SettingsManager extends UserSettings {
return settingsChanged;
}
}
export const manager = new SettingsManager();
\ No newline at end of file
}
\ No newline at end of file
......@@ -8,32 +8,33 @@ import URI from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
import { assign } from 'vs/base/common/objects';
import { IEnvironment } from 'vs/platform/workspace/common/workspace';
import env = require('vs/workbench/electron-main/env');
import { manager as SettingsManager } from 'vs/workbench/electron-main/settings';
import { Instance as UpdateManager } from 'vs/workbench/electron-main/update-manager';
import { IEnvService } from 'vs/workbench/electron-main/env';
import { ISettingsManager } from 'vs/workbench/electron-main/settings';
import { IUpdateManager } from 'vs/workbench/electron-main/update-manager';
import {ServicesAccessor} from 'vs/platform/instantiation/common/instantiation';
const boostrapPath = URI.parse(require.toUrl('bootstrap')).fsPath;
function getEnvironment(): IEnvironment {
let configuration: IEnvironment = assign({}, env.cliArgs);
function getEnvironment(envService: IEnvService, updateManager: IUpdateManager): IEnvironment {
let configuration: IEnvironment = assign({}, envService.cliArgs);
configuration.execPath = process.execPath;
configuration.appName = env.product.nameLong;
configuration.appRoot = env.appRoot;
configuration.version = env.version;
configuration.commitHash = env.product.commit;
configuration.appSettingsHome = env.appSettingsHome;
configuration.appSettingsPath = env.appSettingsPath;
configuration.appKeybindingsPath = env.appKeybindingsPath;
configuration.userExtensionsHome = env.userExtensionsHome;
configuration.isBuilt = env.isBuilt;
configuration.updateFeedUrl = UpdateManager.feedUrl;
configuration.updateChannel = UpdateManager.channel;
configuration.extensionsGallery = env.product.extensionsGallery;
configuration.appName = envService.product.nameLong;
configuration.appRoot = envService.appRoot;
configuration.version = envService.version;
configuration.commitHash = envService.product.commit;
configuration.appSettingsHome = envService.appSettingsHome;
configuration.appSettingsPath = envService.appSettingsPath;
configuration.appKeybindingsPath = envService.appKeybindingsPath;
configuration.userExtensionsHome = envService.userExtensionsHome;
configuration.isBuilt = envService.isBuilt;
configuration.updateFeedUrl = updateManager.feedUrl;
configuration.updateChannel = updateManager.channel;
configuration.extensionsGallery = envService.product.extensionsGallery;
return configuration;
}
function _spawnSharedProcess(): cp.ChildProcess {
function _spawnSharedProcess(envService: IEnvService, updateManager: IUpdateManager, settingsManager: ISettingsManager): cp.ChildProcess {
// Make sure the nls configuration travels to the shared process.
const opts = {
env: assign(assign({}, process.env), {
......@@ -47,10 +48,10 @@ function _spawnSharedProcess(): cp.ChildProcess {
result.once('message', () => {
result.send({
configuration: {
env: getEnvironment()
env: getEnvironment(envService, updateManager)
},
contextServiceOptions: {
globalSettings: SettingsManager.globalSettings
globalSettings: settingsManager.globalSettings
}
});
});
......@@ -60,7 +61,11 @@ function _spawnSharedProcess(): cp.ChildProcess {
let spawnCount = 0;
export function spawnSharedProcess(): IDisposable {
export function spawnSharedProcess(accessor: ServicesAccessor): IDisposable {
const envService = accessor.get(IEnvService);
const updateManager = accessor.get(IUpdateManager);
const settingsManager = accessor.get(ISettingsManager);
let child: cp.ChildProcess;
const spawn = () => {
......@@ -68,7 +73,7 @@ export function spawnSharedProcess(): IDisposable {
return;
}
child = _spawnSharedProcess();
child = _spawnSharedProcess(envService, updateManager, settingsManager);
child.on('exit', spawn);
};
......
......@@ -9,82 +9,100 @@
import path = require('path');
import fs = require('fs');
import events = require('events');
import env = require('vs/workbench/electron-main/env');
const dbPath = path.join(env.appHome, 'storage.json');
let database: any = null;
import {ServiceIdentifier, createDecorator} from 'vs/platform/instantiation/common/instantiation';
const EventTypes = {
STORE: 'store'
};
const eventEmitter = new events.EventEmitter();
export function onStore<T>(clb: (key: string, oldValue: T, newValue: T) => void): () => void {
eventEmitter.addListener(EventTypes.STORE, clb);
export const IStorageService = createDecorator<IStorageService>('storageService');
return () => eventEmitter.removeListener(EventTypes.STORE, clb);
export interface IStorageService {
serviceId: ServiceIdentifier<any>;
onStore<T>(clb: (key: string, oldValue: T, newValue: T) => void): () => void;
getItem<T>(key: string, defaultValue?: T): T;
setItem(key: string, data: any): void;
removeItem(key: string): void;
}
export function getItem<T>(key: string, defaultValue?: T): T {
if (!database) {
database = load();
}
export class StorageService implements IStorageService {
serviceId = IStorageService;
private dbPath: string;
private database: any = null;
private eventEmitter = new events.EventEmitter();
const res = database[key];
if (typeof res === 'undefined') {
return defaultValue;
constructor(@env.IEnvService private envService: env.IEnvService) {
this.dbPath = path.join(envService.appHome, 'storage.json');
}
return database[key];
}
onStore<T>(clb: (key: string, oldValue: T, newValue: T) => void): () => void {
this.eventEmitter.addListener(EventTypes.STORE, clb);
export function setItem(key: string, data: any): void {
if (!database) {
database = load();
return () => this.eventEmitter.removeListener(EventTypes.STORE, clb);
}
// Shortcut for primitives that did not change
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
if (database[key] === data) {
return;
getItem<T>(key: string, defaultValue?: T): T {
if (!this.database) {
this.database = this.load();
}
const res = this.database[key];
if (typeof res === 'undefined') {
return defaultValue;
}
return this.database[key];
}
let oldValue = database[key];
database[key] = data;
save();
setItem(key: string, data: any): void {
if (!this.database) {
this.database = this.load();
}
eventEmitter.emit(EventTypes.STORE, key, oldValue, data);
}
// Shortcut for primitives that did not change
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
if (this.database[key] === data) {
return;
}
}
let oldValue = this.database[key];
this.database[key] = data;
this.save();
export function removeItem(key: string): void {
if (!database) {
database = load();
this.eventEmitter.emit(EventTypes.STORE, key, oldValue, data);
}
if (database[key]) {
let oldValue = database[key];
delete database[key];
save();
removeItem(key: string): void {
if (!this.database) {
this.database = this.load();
}
if (this.database[key]) {
let oldValue = this.database[key];
delete this.database[key];
this.save();
eventEmitter.emit(EventTypes.STORE, key, oldValue, null);
this.eventEmitter.emit(EventTypes.STORE, key, oldValue, null);
}
}
}
function load(): any {
try {
return JSON.parse(fs.readFileSync(dbPath).toString());
} catch (error) {
if (env.cliArgs.verboseLogging) {
console.error(error);
private load(): any {
try {
return JSON.parse(fs.readFileSync(this.dbPath).toString());
} catch (error) {
if (this.envService.cliArgs.verboseLogging) {
console.error(error);
}
return {};
}
}
return {};
private save(): void {
fs.writeFileSync(this.dbPath, JSON.stringify(this.database, null, 4));
}
}
function save(): void {
fs.writeFileSync(dbPath, JSON.stringify(database, null, 4));
}
\ No newline at end of file
......@@ -8,14 +8,14 @@
import fs = require('fs');
import path = require('path');
import events = require('events');
import electron = require('electron');
import platform = require('vs/base/common/platform');
import env = require('vs/workbench/electron-main/env');
import settings = require('vs/workbench/electron-main/settings');
import {Win32AutoUpdaterImpl} from 'vs/workbench/electron-main/auto-updater.win32';
import {LinuxAutoUpdaterImpl} from 'vs/workbench/electron-main/auto-updater.linux';
import {manager as Lifecycle} from 'vs/workbench/electron-main/lifecycle';
import { IEnvService, getPlatformIdentifier } from 'vs/workbench/electron-main/env';
import { ISettingsManager } from 'vs/workbench/electron-main/settings';
import { Win32AutoUpdaterImpl } from 'vs/workbench/electron-main/auto-updater.win32';
import { LinuxAutoUpdaterImpl } from 'vs/workbench/electron-main/auto-updater.linux';
import { ILifecycleService } from 'vs/workbench/electron-main/lifecycle';
import { ServiceIdentifier, createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export enum State {
Uninitialized,
......@@ -42,7 +42,23 @@ interface IAutoUpdater extends NodeJS.EventEmitter {
checkForUpdates(): void;
}
export class UpdateManager extends events.EventEmitter {
export const IUpdateManager = createDecorator<IUpdateManager>('updateManager');
export interface IUpdateManager {
serviceId: ServiceIdentifier<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 events.EventEmitter implements IUpdateManager {
serviceId = IUpdateManager;
private _state: State;
private explicitState: ExplicitState;
......@@ -52,7 +68,12 @@ export class UpdateManager extends events.EventEmitter {
private _feedUrl: string;
private _channel: string;
constructor() {
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@ILifecycleService private lifecycleService: ILifecycleService,
@IEnvService private envService: IEnvService,
@ISettingsManager private settingsManager: ISettingsManager
) {
super();
this._state = State.Uninitialized;
......@@ -63,9 +84,9 @@ export class UpdateManager extends events.EventEmitter {
this._channel = null;
if (platform.isWindows) {
this.raw = new Win32AutoUpdaterImpl();
this.raw = instantiationService.createInstance(Win32AutoUpdaterImpl);
} else if (platform.isLinux) {
this.raw = new LinuxAutoUpdaterImpl();
this.raw = instantiationService.createInstance(LinuxAutoUpdaterImpl);
} else if (platform.isMacintosh) {
this.raw = electron.autoUpdater;
}
......@@ -122,7 +143,7 @@ export class UpdateManager extends events.EventEmitter {
}
private quitAndUpdate(rawQuitAndUpdate: () => void): void {
Lifecycle.quit().done(vetod => {
this.lifecycleService.quit().done(vetod => {
if (vetod) {
return;
}
......@@ -151,8 +172,8 @@ export class UpdateManager extends events.EventEmitter {
return; // already initialized
}
const channel = UpdateManager.getUpdateChannel();
const feedUrl = UpdateManager.getUpdateFeedUrl(channel);
const channel = this.getUpdateChannel();
const feedUrl = this.getUpdateFeedUrl(channel);
if (!feedUrl) {
return; // updates not available
......@@ -203,12 +224,12 @@ export class UpdateManager extends events.EventEmitter {
this.emit('change');
}
private static getUpdateChannel(): string {
const channel = settings.manager.getValue('update.channel') || 'default';
return channel === 'none' ? null : env.quality;
private getUpdateChannel(): string {
const channel = this.settingsManager.getValue('update.channel') || 'default';
return channel === 'none' ? null : this.envService.quality;
}
private static getUpdateFeedUrl(channel: string): string {
private getUpdateFeedUrl(channel: string): string {
if (!channel) {
return null;
}
......@@ -217,12 +238,10 @@ export class UpdateManager extends events.EventEmitter {
return null;
}
if (!env.updateUrl || !env.product.commit) {
if (!this.envService.updateUrl || !this.envService.product.commit) {
return null;
}
return `${ env.updateUrl }/api/update/${ env.getPlatformIdentifier() }/${ channel }/${ env.product.commit }`;
return `${ this.envService.updateUrl }/api/update/${ getPlatformIdentifier() }/${ channel }/${ this.envService.product.commit }`;
}
}
export const Instance = new UpdateManager();
......@@ -12,8 +12,9 @@ import {shell, screen, BrowserWindow} from 'electron';
import {TPromise, TValueCallback} from 'vs/base/common/winjs.base';
import platform = require('vs/base/common/platform');
import objects = require('vs/base/common/objects');
import env = require('vs/workbench/electron-main/env');
import { ICommandLineArguments, IEnvService, IProcessEnvironment } from 'vs/workbench/electron-main/env';
import storage = require('vs/workbench/electron-main/storage');
import { ILogService } from './log';
export interface IWindowState {
width?: number;
......@@ -86,7 +87,7 @@ export interface IPath {
installExtensionPath?: boolean;
}
export interface IWindowConfiguration extends env.ICommandLineArguments {
export interface IWindowConfiguration extends ICommandLineArguments {
execPath: string;
version: string;
appName: string;
......@@ -121,7 +122,7 @@ export interface IWindowConfiguration extends env.ICommandLineArguments {
licenseUrl: string;
productDownloadUrl: string;
enableTelemetry: boolean;
userEnv: env.IProcessEnvironment;
userEnv: IProcessEnvironment;
aiConfig: {
key: string;
asimovKey: string;
......@@ -154,7 +155,12 @@ export class VSCodeWindow {
private currentConfig: IWindowConfiguration;
private pendingLoadConfig: IWindowConfiguration;
constructor(config: IWindowCreationOptions) {
constructor(
config: IWindowCreationOptions,
@ILogService private logService: ILogService,
@IEnvService private envService: IEnvService,
@storage.IStorageService private storageService: storage.IStorageService
) {
this._lastFocusTime = -1;
this._readyState = ReadyState.NONE;
this._extensionDevelopmentPath = config.extensionDevelopmentPath;
......@@ -164,7 +170,7 @@ export class VSCodeWindow {
this.restoreWindowState(config.state);
// For VS theme we can show directly because background is white
const usesLightTheme = /vs($| )/.test(storage.getItem<string>(VSCodeWindow.themeStorageKey));
const usesLightTheme = /vs($| )/.test(this.storageService.getItem<string>(VSCodeWindow.themeStorageKey));
let showDirectly = true; // set to false to prevent background color flash (flash should be fixed for Electron >= 0.37.x)
if (showDirectly && !global.windowShow) {
global.windowShow = new Date().getTime();
......@@ -179,14 +185,14 @@ export class VSCodeWindow {
minWidth: VSCodeWindow.MIN_WIDTH,
minHeight: VSCodeWindow.MIN_HEIGHT,
show: showDirectly && this.currentWindowMode !== WindowMode.Maximized, // in case we are maximized, only show later after the call to maximize (see below)
title: env.product.nameLong,
title: this.envService.product.nameLong,
webPreferences: {
'backgroundThrottling': false // by default if Code is in the background, intervals and timeouts get throttled
}
};
if (platform.isLinux) {
options.icon = path.join(env.appRoot, 'resources/linux/code.png'); // Windows and Mac are better off using the embedded icon(s)
options.icon = path.join(this.envService.appRoot, 'resources/linux/code.png'); // Windows and Mac are better off using the embedded icon(s)
}
// Create the browser window.
......@@ -205,7 +211,7 @@ export class VSCodeWindow {
this._lastFocusTime = new Date().getTime(); // since we show directly, we need to set the last focus time too
}
if (storage.getItem<boolean>(VSCodeWindow.menuBarHiddenKey, false)) {
if (this.storageService.getItem<boolean>(VSCodeWindow.menuBarHiddenKey, false)) {
this.setMenuBarVisibility(false); // respect configured menu bar visibility
}
......@@ -342,7 +348,7 @@ export class VSCodeWindow {
// Prevent any kind of navigation triggered by the user!
// But do not touch this in dev version because it will prevent "Reload" from dev tools
if (env.isBuilt) {
if (this.envService.isBuilt) {
this._win.webContents.on('will-navigate', (event: Event) => {
if (event) {
event.preventDefault();
......@@ -383,7 +389,7 @@ export class VSCodeWindow {
}
}
public reload(cli?: env.ICommandLineArguments): void {
public reload(cli?: ICommandLineArguments): void {
// Inherit current properties but overwrite some
let configuration: IWindowConfiguration = objects.mixin({}, this.currentConfig);
......@@ -458,7 +464,7 @@ export class VSCodeWindow {
try {
state = this.validateWindowState(state);
} catch (err) {
env.log(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate
this.logService.log(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate
}
}
......@@ -558,7 +564,7 @@ export class VSCodeWindow {
if (willBeFullScreen) {
this.setMenuBarVisibility(false);
} else {
this.setMenuBarVisibility(!storage.getItem<boolean>(VSCodeWindow.menuBarHiddenKey, false)); // restore as configured
this.setMenuBarVisibility(!this.storageService.getItem<boolean>(VSCodeWindow.menuBarHiddenKey, false)); // restore as configured
}
}
}
......
/*---------------------------------------------------------------------------------------------
* 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 env = require('vs/workbench/electron-main/env');
import events = require('vs/base/common/eventEmitter');
import platform = require('vs/base/common/platform');
import { ipcMain as ipc, BrowserWindow } from 'electron';
interface ICredentialsContext {
id: number;
host: string;
command: string;
}
interface ICredentials {
username: string;
password: string;
}
interface ICredentialsResult {
id: number;
credentials: ICredentials;
}
interface IContext {
credentials: ICredentials;
window: Electron.BrowserWindow;
}
export function configure(bus: events.EventEmitter): void {
var cache: { [id: string]: IContext } = Object.create(null);
ipc.on('git:askpass', (event, result: ICredentialsResult) => {
cache[result.id].credentials = result.credentials;
});
bus.addListener('git:askpass', (context: ICredentialsContext) => {
var cachedResult = cache[context.id];
if (typeof cachedResult !== 'undefined') {
bus.emit('git:askpass:' + context.id, cachedResult.credentials);
return;
}
if (context.command === 'fetch') {
bus.emit('git:askpass:' + context.id, { id: context.id, credentials: { username: '', password: '' }});
return;
}
var win = new BrowserWindow({
alwaysOnTop: true,
skipTaskbar: true,
resizable: false,
width: 450,
height: platform.isWindows ? 280 : 260,
show: true,
title: env.product.nameLong
});
win.setMenuBarVisibility(false);
cache[context.id] = {
window: win,
credentials: null
};
win.loadURL(require.toUrl('vs/workbench/parts/git/electron-main/index.html'));
win.webContents.executeJavaScript('init(' + JSON.stringify(context) + ')');
win.once('closed', () => {
bus.emit('git:askpass:' + context.id, cache[context.id].credentials);
setTimeout(function () {
delete cache[context.id];
}, 1000 * 10);
});
});
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册