Merge from master

This commit is contained in:
Raj Musuku
2019-02-21 17:56:04 -08:00
parent 5a146e34fa
commit 666ae11639
11482 changed files with 119352 additions and 255574 deletions

View File

@@ -3,12 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as fs from 'fs';
import { dirname, basename } from 'path';
import * as objects from 'vs/base/common/objects';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import * as json from 'vs/base/common/json';
import * as extfs from 'vs/base/node/extfs';
@@ -24,12 +22,11 @@ export interface IConfigWatcher<T> {
reload(callback: (config: T) => void): void;
getConfig(): T;
getValue<V>(key: string, fallback?: V): V;
}
export interface IConfigOptions<T> {
onError: (error: Error | string) => void;
defaultConfig?: T;
defaultConfig: T;
changeBufferDelay?: number;
parse?: (content: string, errors: any[]) => T;
initCallback?: (config: T) => void;
@@ -48,12 +45,12 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
private parseErrors: json.ParseError[];
private disposed: boolean;
private loaded: boolean;
private timeoutHandle: NodeJS.Timer;
private timeoutHandle: NodeJS.Timer | null;
private disposables: IDisposable[];
private readonly _onDidUpdateConfiguration: Emitter<IConfigurationChangeEvent<T>>;
private configName: string;
constructor(private _path: string, private options: IConfigOptions<T> = { changeBufferDelay: 0, defaultConfig: Object.create(null), onError: error => console.error(error) }) {
constructor(private _path: string, private options: IConfigOptions<T> = { defaultConfig: Object.create(null), onError: error => console.error(error) }) {
this.disposables = [];
this.configName = basename(this._path);
@@ -115,11 +112,11 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
try {
this.parseErrors = [];
res = this.options.parse ? this.options.parse(raw, this.parseErrors) : json.parse(raw, this.parseErrors);
return res || this.options.defaultConfig;
} catch (error) {
// Ignore parsing errors
return this.options.defaultConfig;
}
return res || this.options.defaultConfig;
}
private registerWatcher(): void {
@@ -152,20 +149,13 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
return; // avoid watchers that will never get disposed by checking for being disposed
}
const watcher = extfs.watch(path,
this.disposables.push(extfs.watch(path,
(type, file) => this.onConfigFileChange(type, file, isParentFolder),
(error: string) => this.options.onError(error)
);
if (watcher) {
this.disposables.push(toDisposable(() => {
watcher.removeAllListeners();
watcher.close();
}));
}
));
}
private onConfigFileChange(eventType: string, filename: string, isParentFolder: boolean): void {
private onConfigFileChange(eventType: string, filename: string | undefined, isParentFolder: boolean): void {
if (isParentFolder) {
// Windows: in some cases the filename contains artifacts from the absolute path
@@ -186,7 +176,7 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
}
// we can get multiple change events for one change, so we buffer through a timeout
this.timeoutHandle = global.setTimeout(() => this.reload(), this.options.changeBufferDelay);
this.timeoutHandle = global.setTimeout(() => this.reload(), this.options.changeBufferDelay || 0);
}
public reload(callback?: (config: T) => void): void {
@@ -209,18 +199,6 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
return this.cache;
}
public getValue<V>(key: string, fallback?: V): V {
this.ensureLoaded();
if (!key) {
return fallback;
}
const value = this.cache ? (this.cache as any)[key] : void 0;
return typeof value !== 'undefined' ? value : fallback;
}
private ensureLoaded(): void {
if (!this.loaded) {
this.updateCache(this.loadSync());

View File

@@ -3,9 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import URI from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
export interface IRemoteConsoleLog {
type: string;
@@ -31,7 +29,7 @@ export function isRemoteConsoleLog(obj: any): obj is IRemoteConsoleLog {
export function parse(entry: IRemoteConsoleLog): { args: any[], stack?: string } {
const args: any[] = [];
let stack: string;
let stack: string | undefined;
// Parse Entry
try {
@@ -52,11 +50,11 @@ export function parse(entry: IRemoteConsoleLog): { args: any[], stack?: string }
return { args, stack };
}
export function getFirstFrame(entry: IRemoteConsoleLog): IStackFrame;
export function getFirstFrame(stack: string): IStackFrame;
export function getFirstFrame(arg0: IRemoteConsoleLog | string): IStackFrame {
export function getFirstFrame(entry: IRemoteConsoleLog): IStackFrame | undefined;
export function getFirstFrame(stack: string | undefined): IStackFrame | undefined;
export function getFirstFrame(arg0: IRemoteConsoleLog | string | undefined): IStackFrame | undefined {
if (typeof arg0 !== 'string') {
return getFirstFrame(parse(arg0).stack);
return getFirstFrame(parse(arg0!).stack);
}
// Parse a source information out of the stack if we have one. Format can be:
@@ -75,7 +73,7 @@ export function getFirstFrame(arg0: IRemoteConsoleLog | string): IStackFrame {
// (?:(?:[a-zA-Z]+:)|(?:[\/])|(?:\\\\) => windows drive letter OR unix root OR unc root
// (?:.+) => simple pattern for the path, only works because of the line/col pattern after
// :(?:\d+):(?:\d+) => :line:column data
const matches = /at [^\/]*((?:(?:[a-zA-Z]+:)|(?:[\/])|(?:\\\\))(?:.+)):(\d+):(\d+)/.exec(topFrame);
const matches = /at [^\/]*((?:(?:[a-zA-Z]+:)|(?:[\/])|(?:\\\\))(?:.+)):(\d+):(\d+)/.exec(topFrame || '');
if (matches && matches.length === 4) {
return {
uri: URI.file(matches[1]),
@@ -88,7 +86,7 @@ export function getFirstFrame(arg0: IRemoteConsoleLog | string): IStackFrame {
return void 0;
}
function findFirstFrame(stack: string): string {
function findFirstFrame(stack: string | undefined): string | undefined {
if (!stack) {
return stack;
}
@@ -111,7 +109,7 @@ export function log(entry: IRemoteConsoleLog, label: string): void {
topFrame = `(${topFrame.trim()})`;
}
let consoleArgs = [];
let consoleArgs: string[] = [];
// First arg is a string
if (typeof args[0] === 'string') {

View File

@@ -3,16 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as fs from 'fs';
import * as crypto from 'crypto';
import * as stream from 'stream';
import { TPromise } from 'vs/base/common/winjs.base';
import { once } from 'vs/base/common/functional';
export function checksum(path: string, sha1hash: string): TPromise<void> {
const promise = new TPromise<string>((c, e) => {
export function checksum(path: string, sha1hash: string): Promise<void> {
const promise = new Promise<string | undefined>((c, e) => {
const input = fs.createReadStream(path);
const hash = crypto.createHash('sha1');
const hashStream = hash as any as stream.PassThrough;
@@ -32,14 +29,14 @@ export function checksum(path: string, sha1hash: string): TPromise<void> {
input.once('error', done);
input.once('end', done);
hashStream.once('error', done);
hashStream.once('data', (data: NodeBuffer) => done(null, data.toString('hex')));
hashStream.once('data', (data: Buffer) => done(undefined, data.toString('hex')));
});
return promise.then(hash => {
if (hash !== sha1hash) {
return TPromise.wrapError<void>(new Error('Hash mismatch'));
return Promise.reject(new Error('Hash mismatch'));
}
return TPromise.as(null);
return Promise.resolve();
});
}
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as sd from 'string_decoder';
import { CharCode } from 'vs/base/common/charCode';
@@ -18,14 +16,14 @@ import { CharCode } from 'vs/base/common/charCode';
*/
export class LineDecoder {
private stringDecoder: sd.NodeStringDecoder;
private remaining: string;
private remaining: string | null;
constructor(encoding: string = 'utf8') {
this.stringDecoder = new sd.StringDecoder(encoding);
this.remaining = null;
}
public write(buffer: NodeBuffer): string[] {
public write(buffer: Buffer): string[] {
let result: string[] = [];
let value = this.remaining
? this.remaining + this.stringDecoder.write(buffer)
@@ -58,7 +56,7 @@ export class LineDecoder {
return result;
}
public end(): string {
public end(): string | null {
return this.remaining;
}
}

View File

@@ -3,15 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as stream from 'vs/base/node/stream';
import * as iconv from 'iconv-lite';
import { TPromise } from 'vs/base/common/winjs.base';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { exec } from 'child_process';
import { Readable, Writable, WritableOptions } from 'stream';
import { toWinJsPromise } from 'vs/base/common/async';
export const UTF8 = 'utf8';
export const UTF8_with_bom = 'utf8bom';
@@ -21,11 +17,10 @@ export const UTF16le = 'utf16le';
export interface IDecodeStreamOptions {
guessEncoding?: boolean;
minBytesRequiredForDetection?: number;
overwriteEncoding?(detectedEncoding: string): string;
overwriteEncoding?(detectedEncoding: string | null): string;
}
export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): TPromise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }> {
export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): Promise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }> {
if (!options.minBytesRequiredForDetection) {
options.minBytesRequiredForDetection = options.guessEncoding ? AUTO_GUESS_BUFFER_MAX_LEN : NO_GUESS_BUFFER_MAX_LEN;
}
@@ -34,7 +29,10 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions
options.overwriteEncoding = detected => detected || UTF8;
}
return new TPromise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }>((resolve, reject) => {
return new Promise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }>((resolve, reject) => {
readable.on('error', reject);
readable.pipe(new class extends Writable {
private _decodeStream: NodeJS.ReadWriteStream;
@@ -65,7 +63,7 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions
// waiting for the decoder to be ready
this._decodeStreamConstruction.then(_ => callback(), err => callback(err));
} else if (this._bytesBuffered >= options.minBytesRequiredForDetection) {
} else if (typeof options.minBytesRequiredForDetection === 'number' && this._bytesBuffered >= options.minBytesRequiredForDetection) {
// buffered enough data, create stream and forward data
this._startDecodeStream(callback);
@@ -77,10 +75,12 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions
_startDecodeStream(callback: Function): void {
this._decodeStreamConstruction = TPromise.as(detectEncodingFromBuffer({
this._decodeStreamConstruction = Promise.resolve(detectEncodingFromBuffer({
buffer: Buffer.concat(this._buffer), bytesRead: this._bytesBuffered
}, options.guessEncoding)).then(detected => {
detected.encoding = options.overwriteEncoding(detected.encoding);
if (options.overwriteEncoding) {
detected.encoding = options.overwriteEncoding(detected.encoding);
}
this._decodeStream = decodeStream(detected.encoding);
for (const buffer of this._buffer) {
this._decodeStream.write(buffer);
@@ -119,11 +119,11 @@ export function bomLength(encoding: string): number {
return 0;
}
export function decode(buffer: NodeBuffer, encoding: string): string {
export function decode(buffer: Buffer, encoding: string): string {
return iconv.decode(buffer, toNodeEncoding(encoding));
}
export function encode(content: string | NodeBuffer, encoding: string, options?: { addBOM?: boolean }): NodeBuffer {
export function encode(content: string | Buffer, encoding: string, options?: { addBOM?: boolean }): Buffer {
return iconv.encode(content, toNodeEncoding(encoding), options);
}
@@ -131,7 +131,7 @@ export function encodingExists(encoding: string): boolean {
return iconv.encodingExists(toNodeEncoding(encoding));
}
export function decodeStream(encoding: string): NodeJS.ReadWriteStream {
export function decodeStream(encoding: string | null): NodeJS.ReadWriteStream {
return iconv.decodeStream(toNodeEncoding(encoding));
}
@@ -139,15 +139,15 @@ export function encodeStream(encoding: string, options?: { addBOM?: boolean }):
return iconv.encodeStream(toNodeEncoding(encoding), options);
}
function toNodeEncoding(enc: string): string {
if (enc === UTF8_with_bom) {
function toNodeEncoding(enc: string | null): string {
if (enc === UTF8_with_bom || enc === null) {
return UTF8; // iconv does not distinguish UTF 8 with or without BOM, so we need to help it
}
return enc;
}
export function detectEncodingByBOMFromBuffer(buffer: NodeBuffer, bytesRead: number): string {
export function detectEncodingByBOMFromBuffer(buffer: Buffer | null, bytesRead: number): string | null {
if (!buffer || bytesRead < 2) {
return null;
}
@@ -183,7 +183,7 @@ export function detectEncodingByBOMFromBuffer(buffer: NodeBuffer, bytesRead: num
* Detects the Byte Order Mark in a given file.
* If no BOM is detected, null will be passed to callback.
*/
export function detectEncodingByBOM(file: string): TPromise<string> {
export function detectEncodingByBOM(file: string): Promise<string | null> {
return stream.readExactlyByFile(file, 3).then(({ buffer, bytesRead }) => detectEncodingByBOMFromBuffer(buffer, bytesRead));
}
@@ -193,8 +193,8 @@ const IGNORE_ENCODINGS = ['ascii', 'utf-8', 'utf-16', 'utf-32'];
/**
* Guesses the encoding from buffer.
*/
export function guessEncodingByBuffer(buffer: NodeBuffer): TPromise<string> {
return toWinJsPromise(import('jschardet')).then(jschardet => {
export function guessEncodingByBuffer(buffer: Buffer): Promise<string | null> {
return import('jschardet').then(jschardet => {
jschardet.Constants.MINIMUM_THRESHOLD = MINIMUM_THRESHOLD;
const guessed = jschardet.detect(buffer);
@@ -268,17 +268,13 @@ const NO_GUESS_BUFFER_MAX_LEN = 512; // when not auto guessing the encoding,
const AUTO_GUESS_BUFFER_MAX_LEN = 512 * 8; // with auto guessing we want a lot more content to be read for guessing
export interface IDetectedEncodingResult {
encoding: string;
encoding: string | null;
seemsBinary: boolean;
}
export interface DetectEncodingOption {
autoGuessEncoding?: boolean;
}
export function detectEncodingFromBuffer(readResult: stream.ReadResult, autoGuessEncoding?: false): IDetectedEncodingResult;
export function detectEncodingFromBuffer(readResult: stream.ReadResult, autoGuessEncoding?: boolean): TPromise<IDetectedEncodingResult>;
export function detectEncodingFromBuffer({ buffer, bytesRead }: stream.ReadResult, autoGuessEncoding?: boolean): TPromise<IDetectedEncodingResult> | IDetectedEncodingResult {
export function detectEncodingFromBuffer(readResult: stream.ReadResult, autoGuessEncoding?: boolean): Promise<IDetectedEncodingResult>;
export function detectEncodingFromBuffer({ buffer, bytesRead }: stream.ReadResult, autoGuessEncoding?: boolean): Promise<IDetectedEncodingResult> | IDetectedEncodingResult {
// Always first check for BOM to find out about encoding
let encoding = detectEncodingByBOMFromBuffer(buffer, bytesRead);
@@ -286,7 +282,7 @@ export function detectEncodingFromBuffer({ buffer, bytesRead }: stream.ReadResul
// Detect 0 bytes to see if file is binary or UTF-16 LE/BE
// unless we already know that this file has a UTF-16 encoding
let seemsBinary = false;
if (encoding !== UTF16be && encoding !== UTF16le) {
if (encoding !== UTF16be && encoding !== UTF16le && buffer) {
let couldBeUTF16LE = true; // e.g. 0xAA 0x00
let couldBeUTF16BE = true; // e.g. 0x00 0xAA
let containsZeroByte = false;
@@ -334,11 +330,11 @@ export function detectEncodingFromBuffer({ buffer, bytesRead }: stream.ReadResul
}
// Auto guess encoding if configured
if (autoGuessEncoding && !seemsBinary && !encoding) {
return guessEncodingByBuffer(buffer.slice(0, bytesRead)).then(encoding => {
if (autoGuessEncoding && !seemsBinary && !encoding && buffer) {
return guessEncodingByBuffer(buffer.slice(0, bytesRead)).then(guessedEncoding => {
return {
seemsBinary: false,
encoding
encoding: guessedEncoding
};
});
}
@@ -363,8 +359,8 @@ const windowsTerminalEncodings = {
'1252': 'cp1252' // West European Latin
};
export function resolveTerminalEncoding(verbose?: boolean): TPromise<string> {
let rawEncodingPromise: TPromise<string>;
export function resolveTerminalEncoding(verbose?: boolean): Promise<string> {
let rawEncodingPromise: Promise<string>;
// Support a global environment variable to win over other mechanics
const cliEncodingEnv = process.env['VSCODE_CLI_ENCODING'];
@@ -373,23 +369,23 @@ export function resolveTerminalEncoding(verbose?: boolean): TPromise<string> {
console.log(`Found VSCODE_CLI_ENCODING variable: ${cliEncodingEnv}`);
}
rawEncodingPromise = TPromise.as(cliEncodingEnv);
rawEncodingPromise = Promise.resolve(cliEncodingEnv);
}
// Linux/Mac: use "locale charmap" command
else if (isLinux || isMacintosh) {
rawEncodingPromise = new TPromise<string>(c => {
rawEncodingPromise = new Promise<string>(resolve => {
if (verbose) {
console.log('Running "locale charmap" to detect terminal encoding...');
}
exec('locale charmap', (err, stdout, stderr) => c(stdout));
exec('locale charmap', (err, stdout, stderr) => resolve(stdout));
});
}
// Windows: educated guess
else {
rawEncodingPromise = new TPromise<string>(c => {
rawEncodingPromise = new Promise<string>(resolve => {
if (verbose) {
console.log('Running "chcp" to detect terminal encoding...');
}
@@ -400,12 +396,12 @@ export function resolveTerminalEncoding(verbose?: boolean): TPromise<string> {
for (let i = 0; i < windowsTerminalEncodingKeys.length; i++) {
const key = windowsTerminalEncodingKeys[i];
if (stdout.indexOf(key) >= 0) {
return c(windowsTerminalEncodings[key]);
return resolve(windowsTerminalEncodings[key]);
}
}
}
return c(void 0);
return resolve(void 0);
});
});
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as fs from 'fs';
import * as paths from 'path';
import { nfcall } from 'vs/base/common/async';
@@ -12,9 +10,10 @@ import { normalizeNFC } from 'vs/base/common/normalization';
import * as platform from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings';
import * as uuid from 'vs/base/common/uuid';
import { TPromise } from 'vs/base/common/winjs.base';
import { encode, encodeStream } from 'vs/base/node/encoding';
import * as flow from 'vs/base/node/flow';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle';
const loop = flow.loop;
@@ -28,13 +27,13 @@ export function readdirSync(path: string): string[] {
return fs.readdirSync(path);
}
export function readdir(path: string, callback: (error: Error, files: string[]) => void): void {
export function readdir(path: string, callback: (error: Error | null, files: string[]) => void): void {
// Mac: uses NFD unicode form on disk, but we want NFC
// See also https://github.com/nodejs/node/issues/2165
if (platform.isMacintosh) {
return fs.readdir(path, (error, children) => {
if (error) {
return callback(error, null);
return callback(error, []);
}
return callback(null, children.map(c => normalizeNFC(c)));
@@ -49,7 +48,7 @@ export interface IStatAndLink {
isSymbolicLink: boolean;
}
export function statLink(path: string, callback: (error: Error, statAndIsLink: IStatAndLink) => void): void {
export function statLink(path: string, callback: (error: Error | null, statAndIsLink: IStatAndLink | null) => void): void {
fs.lstat(path, (error, lstat) => {
if (error || lstat.isSymbolicLink()) {
fs.stat(path, (error, stat) => {
@@ -65,10 +64,8 @@ export function statLink(path: string, callback: (error: Error, statAndIsLink: I
});
}
export function copy(source: string, target: string, callback: (error: Error) => void, copiedSources?: { [path: string]: boolean }): void {
if (!copiedSources) {
copiedSources = Object.create(null);
}
export function copy(source: string, target: string, callback: (error: Error | null) => void, copiedSourcesIn?: { [path: string]: boolean }): void {
const copiedSources = copiedSourcesIn ? copiedSourcesIn : Object.create(null);
fs.stat(source, (error, stat) => {
if (error) {
@@ -87,13 +84,13 @@ export function copy(source: string, target: string, callback: (error: Error) =>
const proceed = function () {
readdir(source, (err, files) => {
loop(files, (file: string, clb: (error: Error, result: string[]) => void) => {
copy(paths.join(source, file), paths.join(target, file), (error: Error) => clb(error, void 0), copiedSources);
loop(files, (file: string, clb: (error: Error | null, result: string[]) => void) => {
copy(paths.join(source, file), paths.join(target, file), (error: Error) => clb(error, []), copiedSources);
}, callback);
});
};
mkdirp(target, stat.mode & 511).done(proceed, proceed);
mkdirp(target, stat.mode & 511).then(proceed, proceed);
});
}
@@ -129,37 +126,42 @@ function doCopyFile(source: string, target: string, mode: number, callback: (err
reader.pipe(writer);
}
export function mkdirp(path: string, mode?: number): TPromise<boolean> {
const mkdir = () => {
export function mkdirp(path: string, mode?: number, token?: CancellationToken): Promise<boolean> {
const mkdir = (): Promise<null> => {
return nfcall(fs.mkdir, path, mode).then(null, (mkdirErr: NodeJS.ErrnoException) => {
// ENOENT: a parent folder does not exist yet
if (mkdirErr.code === 'ENOENT') {
return TPromise.wrapError(mkdirErr);
return Promise.reject(mkdirErr);
}
// Any other error: check if folder exists and
// return normally in that case if its a folder
return nfcall(fs.stat, path).then((stat: fs.Stats) => {
if (!stat.isDirectory()) {
return TPromise.wrapError(new Error(`'${path}' exists and is not a directory.`));
return Promise.reject(new Error(`'${path}' exists and is not a directory.`));
}
return null;
}, statErr => {
return TPromise.wrapError(mkdirErr); // bubble up original mkdir error
return Promise.reject(mkdirErr); // bubble up original mkdir error
});
});
};
// stop at root
if (path === paths.dirname(path)) {
return TPromise.as(true);
return Promise.resolve(true);
}
// recursively mkdir
return mkdir().then(null, (err: NodeJS.ErrnoException) => {
// Respect cancellation
if (token && token.isCancellationRequested) {
return Promise.resolve(false);
}
// ENOENT: a parent folder does not exist yet, continue
// to create the parent folder and then try again.
if (err.code === 'ENOENT') {
@@ -167,7 +169,7 @@ export function mkdirp(path: string, mode?: number): TPromise<boolean> {
}
// Any other error
return TPromise.wrapError<boolean>(err);
return Promise.reject(err);
});
}
@@ -175,7 +177,7 @@ export function mkdirp(path: string, mode?: number): TPromise<boolean> {
// after the rename, the contents are out of the workspace although not yet deleted. The greater benefit however is that this operation
// will fail in case any file is used by another process. fs.unlink() in node will not bail if a file unlinked is used by another process.
// However, the consequences are bad as outlined in all the related bugs from https://github.com/joyent/node/issues/7164
export function del(path: string, tmpFolder: string, callback: (error: Error) => void, done?: (error: Error) => void): void {
export function del(path: string, tmpFolder: string, callback: (error: Error | null) => void, done?: (error: Error | null) => void): void {
fs.exists(path, exists => {
if (!exists) {
return callback(null);
@@ -193,7 +195,7 @@ export function del(path: string, tmpFolder: string, callback: (error: Error) =>
}
const pathInTemp = paths.join(tmpFolder, uuid.generateUuid());
fs.rename(path, pathInTemp, (error: Error) => {
fs.rename(path, pathInTemp, (error: Error | null) => {
if (error) {
return rmRecursive(path, callback); // if rename fails, delete without tmp dir
}
@@ -216,7 +218,7 @@ export function del(path: string, tmpFolder: string, callback: (error: Error) =>
});
}
function rmRecursive(path: string, callback: (error: Error) => void): void {
function rmRecursive(path: string, callback: (error: Error | null) => void): void {
if (path === '\\' || path === '/') {
return callback(new Error('Will not delete root!'));
}
@@ -248,7 +250,7 @@ function rmRecursive(path: string, callback: (error: Error) => void): void {
} else if (children.length === 0) {
fs.rmdir(path, callback);
} else {
let firstError: Error = null;
let firstError: Error | null = null;
let childrenLeft = children.length;
children.forEach(child => {
rmRecursive(paths.join(path, child), (err: Error) => {
@@ -292,12 +294,12 @@ export function delSync(path: string): void {
}
}
export function mv(source: string, target: string, callback: (error: Error) => void): void {
export function mv(source: string, target: string, callback: (error: Error | null) => void): void {
if (source === target) {
return callback(null);
}
function updateMtime(err: Error): void {
function updateMtime(err: Error | null): void {
if (err) {
return callback(err);
}
@@ -365,7 +367,7 @@ export interface IWriteFileOptions {
}
let canFlush = true;
export function writeFileAndFlush(path: string, data: string | NodeBuffer | NodeJS.ReadableStream, options: IWriteFileOptions, callback: (error?: Error) => void): void {
export function writeFileAndFlush(path: string, data: string | Buffer | NodeJS.ReadableStream, options: IWriteFileOptions, callback: (error?: Error) => void): void {
options = ensureOptions(options);
if (typeof data === 'string' || Buffer.isBuffer(data)) {
@@ -466,7 +468,7 @@ function doWriteFileStreamAndFlush(path: string, reader: NodeJS.ReadableStream,
// not in some cache.
//
// See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194
function doWriteFileAndFlush(path: string, data: string | NodeBuffer, options: IWriteFileOptions, callback: (error?: Error) => void): void {
function doWriteFileAndFlush(path: string, data: string | Buffer, options: IWriteFileOptions, callback: (error?: Error) => void): void {
if (options.encoding) {
data = encode(data, options.encoding.charset, { addBOM: options.encoding.addBOM });
}
@@ -476,7 +478,7 @@ function doWriteFileAndFlush(path: string, data: string | NodeBuffer, options: I
}
// Open the file with same flags and mode as fs.writeFile()
fs.open(path, options.flag, options.mode, (openError, fd) => {
fs.open(path, typeof options.flag === 'string' ? options.flag : 'r', options.mode, (openError, fd) => {
if (openError) {
return callback(openError);
}
@@ -503,7 +505,7 @@ function doWriteFileAndFlush(path: string, data: string | NodeBuffer, options: I
});
}
export function writeFileAndFlushSync(path: string, data: string | NodeBuffer, options?: IWriteFileOptions): void {
export function writeFileAndFlushSync(path: string, data: string | Buffer, options?: IWriteFileOptions): void {
options = ensureOptions(options);
if (options.encoding) {
@@ -515,7 +517,7 @@ export function writeFileAndFlushSync(path: string, data: string | NodeBuffer, o
}
// Open the file with same flags and mode as fs.writeFile()
const fd = fs.openSync(path, options.flag, options.mode);
const fd = fs.openSync(path, typeof options.flag === 'string' ? options.flag : 'r', options.mode);
try {
@@ -561,7 +563,7 @@ function ensureOptions(options?: IWriteFileOptions): IWriteFileOptions {
* In case of errors, null is returned. But you cannot use this function to verify that a path exists.
* realcaseSync does not handle '..' or '.' path segments and it does not take the locale into account.
*/
export function realcaseSync(path: string): string {
export function realcaseSync(path: string): string | null {
const dir = paths.dirname(path);
if (path === dir) { // end recursion
return path;
@@ -611,7 +613,7 @@ export function realpathSync(path: string): string {
}
}
export function realpath(path: string, callback: (error: Error, realpath: string) => void): void {
export function realpath(path: string, callback: (error: Error | null, realpath: string) => void): void {
return fs.realpath(path, (error, realpath) => {
if (!error) {
return callback(null, realpath);
@@ -634,12 +636,12 @@ function normalizePath(path: string): string {
return strings.rtrim(paths.normalize(path), paths.sep);
}
export function watch(path: string, onChange: (type: string, path?: string) => void, onError: (error: string) => void): fs.FSWatcher {
export function watch(path: string, onChange: (type: string, path?: string) => void, onError: (error: string) => void): IDisposable {
try {
const watcher = fs.watch(path);
watcher.on('change', (type, raw) => {
let file: string = null;
let file: string | undefined;
if (raw) { // https://github.com/Microsoft/vscode/issues/38191
file = raw.toString();
if (platform.isMacintosh) {
@@ -654,7 +656,10 @@ export function watch(path: string, onChange: (type: string, path?: string) => v
watcher.on('error', (code: number, signal: string) => onError(`Failed to watch ${path} for changes (${code}, ${signal})`));
return watcher;
return toDisposable(() => {
watcher.removeAllListeners();
watcher.close();
});
} catch (error) {
fs.exists(path, exists => {
if (exists) {
@@ -663,5 +668,41 @@ export function watch(path: string, onChange: (type: string, path?: string) => v
});
}
return void 0;
return Disposable.None;
}
export function sanitizeFilePath(candidate: string, cwd: string): string {
// Special case: allow to open a drive letter without trailing backslash
if (platform.isWindows && strings.endsWith(candidate, ':')) {
candidate += paths.sep;
}
// Ensure absolute
if (!paths.isAbsolute(candidate)) {
candidate = paths.join(cwd, candidate);
}
// Ensure normalized
candidate = paths.normalize(candidate);
// Ensure no trailing slash/backslash
if (platform.isWindows) {
candidate = strings.rtrim(candidate, paths.sep);
// Special case: allow to open drive root ('C:\')
if (strings.endsWith(candidate, ':')) {
candidate += paths.sep;
}
} else {
candidate = strings.rtrim(candidate, paths.sep);
// Special case: allow to open root ('/')
if (!candidate) {
candidate = paths.sep;
}
}
return candidate;
}

View File

@@ -3,17 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
/**
* Executes the given function (fn) over the given array of items (list) in parallel and returns the resulting errors and results as
* array to the callback (callback). The resulting errors and results are evaluated by calling the provided callback function.
*/
export function parallel<T, E>(list: T[], fn: (item: T, callback: (err: Error, result: E) => void) => void, callback: (err: Error[], result: E[]) => void): void {
export function parallel<T, E>(list: T[], fn: (item: T, callback: (err: Error, result: E) => void) => void, callback: (err: Array<Error | null> | null, result: E[]) => void): void {
let results = new Array(list.length);
let errors = new Array<Error>(list.length);
let errors = new Array<Error | null>(list.length);
let didErrorOccur = false;
let doneCount = 0;
@@ -44,9 +42,9 @@ export function parallel<T, E>(list: T[], fn: (item: T, callback: (err: Error, r
* array to the callback (callback). The resulting errors and results are evaluated by calling the provided callback function. The first param can
* either be a function that returns an array of results to loop in async fashion or be an array of items already.
*/
export function loop<T, E>(param: (callback: (error: Error, result: T[]) => void) => void, fn: (item: T, callback: (error: Error, result: E) => void, index: number, total: number) => void, callback: (error: Error, result: E[]) => void): void;
export function loop<T, E>(param: T[], fn: (item: T, callback: (error: Error, result: E) => void, index: number, total: number) => void, callback: (error: Error, result: E[]) => void): void;
export function loop<E>(param: any, fn: (item: any, callback: (error: Error, result: E) => void, index: number, total: number) => void, callback: (error: Error, result: E[]) => void): void {
export function loop<T, E>(param: (callback: (error: Error, result: T[]) => void) => void, fn: (item: T, callback: (error: Error | null, result: E | null) => void, index: number, total: number) => void, callback: (error: Error | null, result: E[] | null) => void): void;
export function loop<T, E>(param: T[], fn: (item: T, callback: (error: Error | null, result: E | null) => void, index: number, total: number) => void, callback: (error: Error | null, result: E[] | null) => void): void;
export function loop<E>(param: any, fn: (item: any, callback: (error: Error | null, result: E | null) => void, index: number, total: number) => void, callback: (error: Error | null, result: E[] | null) => void): void {
// Assert
assert.ok(param, 'Missing first parameter');

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import * as errors from 'vs/base/common/errors';
import * as uuid from 'vs/base/common/uuid';
import { networkInterfaces } from 'os';
@@ -46,7 +45,7 @@ export const virtualMachineHint: { value(): number } = new class {
this._virtualMachineOUIs.set('00:16:3E', true);
this._virtualMachineOUIs.set('08:00:27', true);
}
return this._virtualMachineOUIs.findSubstr(mac);
return !!this._virtualMachineOUIs.findSubstr(mac);
}
value(): number {
@@ -76,15 +75,15 @@ export const virtualMachineHint: { value(): number } = new class {
}
};
let machineId: TPromise<string>;
export function getMachineId(): TPromise<string> {
let machineId: Promise<string>;
export function getMachineId(): Promise<string> {
return machineId || (machineId = getMacMachineId()
.then(id => id || uuid.generateUuid())); // fallback, generate a UUID
}
function getMacMachineId(): TPromise<string> {
return new TPromise<string>(resolve => {
TPromise.join([import('crypto'), import('getmac')]).then(([crypto, getmac]) => {
function getMacMachineId(): Promise<string> {
return new Promise<string>(resolve => {
Promise.all([import('crypto'), import('getmac')]).then(([crypto, getmac]) => {
try {
getmac.getMac((error, macAddress) => {
if (!error) {
@@ -93,6 +92,12 @@ function getMacMachineId(): TPromise<string> {
resolve(undefined);
}
});
// Timeout due to hang with reduced privileges #58392
// TODO@sbatten: Remove this when getmac is patched
setTimeout(() => {
resolve(undefined);
}, 1000);
} catch (err) {
errors.onUnexpectedError(err);
resolve(undefined);

View File

@@ -3,14 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import uri from 'vs/base/common/uri';
import { getPathFromAmdModule } from 'vs/base/common/amd';
interface IPaths {
getAppDataPath(platform: string): string;
getDefaultUserDataPath(platform: string): string;
}
const pathsPath = uri.parse(require.toUrl('paths')).fsPath;
const pathsPath = getPathFromAmdModule(require, 'paths');
const paths = require.__$__nodeRequire<IPaths>(pathsPath);
export const getAppDataPath = paths.getAppDataPath;
export const getDefaultUserDataPath = paths.getDefaultUserDataPath;
export const getDefaultUserDataPath = paths.getDefaultUserDataPath;

View File

@@ -3,9 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import * as extfs from 'vs/base/node/extfs';
import { join } from 'path';
import { nfcall, Queue } from 'vs/base/common/async';
@@ -14,25 +11,25 @@ import * as os from 'os';
import * as platform from 'vs/base/common/platform';
import { once } from 'vs/base/common/event';
export function readdir(path: string): TPromise<string[]> {
export function readdir(path: string): Promise<string[]> {
return nfcall(extfs.readdir, path);
}
export function exists(path: string): TPromise<boolean> {
return new TPromise(c => fs.exists(path, c), () => { });
export function exists(path: string): Promise<boolean> {
return new Promise(c => fs.exists(path, c));
}
export function chmod(path: string, mode: number): TPromise<boolean> {
export function chmod(path: string, mode: number): Promise<boolean> {
return nfcall(fs.chmod, path, mode);
}
export import mkdirp = extfs.mkdirp;
export function rimraf(path: string): TPromise<void> {
export function rimraf(path: string): Promise<void> {
return lstat(path).then(stat => {
if (stat.isDirectory() && !stat.isSymbolicLink()) {
return readdir(path)
.then(children => TPromise.join(children.map(child => rimraf(join(path, child)))))
.then(children => Promise.all(children.map(child => rimraf(join(path, child)))))
.then(() => rmdir(path));
} else {
return unlink(path);
@@ -42,53 +39,65 @@ export function rimraf(path: string): TPromise<void> {
return void 0;
}
return TPromise.wrapError<void>(err);
return Promise.reject(err);
});
}
export function realpath(path: string): TPromise<string> {
export function realpath(path: string): Promise<string> {
return nfcall(extfs.realpath, path);
}
export function stat(path: string): TPromise<fs.Stats> {
export function stat(path: string): Promise<fs.Stats> {
return nfcall(fs.stat, path);
}
export function statLink(path: string): TPromise<{ stat: fs.Stats, isSymbolicLink: boolean }> {
export function statLink(path: string): Promise<{ stat: fs.Stats, isSymbolicLink: boolean }> {
return nfcall(extfs.statLink, path);
}
export function lstat(path: string): TPromise<fs.Stats> {
export function lstat(path: string): Promise<fs.Stats> {
return nfcall(fs.lstat, path);
}
export function rename(oldPath: string, newPath: string): TPromise<void> {
export function rename(oldPath: string, newPath: string): Promise<void> {
return nfcall(fs.rename, oldPath, newPath);
}
export function rmdir(path: string): TPromise<void> {
export function renameIgnoreError(oldPath: string, newPath: string): Promise<void> {
return new Promise(resolve => {
fs.rename(oldPath, newPath, () => resolve());
});
}
export function rmdir(path: string): Promise<void> {
return nfcall(fs.rmdir, path);
}
export function unlink(path: string): TPromise<void> {
export function unlink(path: string): Promise<void> {
return nfcall(fs.unlink, path);
}
export function symlink(target: string, path: string, type?: string): TPromise<void> {
export function unlinkIgnoreError(path: string): Promise<void> {
return new Promise(resolve => {
fs.unlink(path, () => resolve());
});
}
export function symlink(target: string, path: string, type?: string): Promise<void> {
return nfcall<void>(fs.symlink, target, path, type);
}
export function readlink(path: string): TPromise<string> {
export function readlink(path: string): Promise<string> {
return nfcall<string>(fs.readlink, path);
}
export function truncate(path: string, len: number): TPromise<void> {
export function truncate(path: string, len: number): Promise<void> {
return nfcall(fs.truncate, path, len);
}
export function readFile(path: string): TPromise<Buffer>;
export function readFile(path: string, encoding: string): TPromise<string>;
export function readFile(path: string, encoding?: string): TPromise<Buffer | string> {
export function readFile(path: string): Promise<Buffer>;
export function readFile(path: string, encoding: string): Promise<string>;
export function readFile(path: string, encoding?: string): Promise<Buffer | string> {
return nfcall(fs.readFile, path, encoding);
}
@@ -97,11 +106,12 @@ export function readFile(path: string, encoding?: string): TPromise<Buffer | str
// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.
const writeFilePathQueue: { [path: string]: Queue<void> } = Object.create(null);
export function writeFile(path: string, data: string, options?: extfs.IWriteFileOptions): TPromise<void>;
export function writeFile(path: string, data: NodeBuffer, options?: extfs.IWriteFileOptions): TPromise<void>;
export function writeFile(path: string, data: Uint8Array, options?: extfs.IWriteFileOptions): TPromise<void>;
export function writeFile(path: string, data: NodeJS.ReadableStream, options?: extfs.IWriteFileOptions): TPromise<void>;
export function writeFile(path: string, data: any, options?: extfs.IWriteFileOptions): TPromise<void> {
export function writeFile(path: string, data: string, options?: extfs.IWriteFileOptions): Promise<void>;
export function writeFile(path: string, data: Buffer, options?: extfs.IWriteFileOptions): Promise<void>;
export function writeFile(path: string, data: Uint8Array, options?: extfs.IWriteFileOptions): Promise<void>;
export function writeFile(path: string, data: NodeJS.ReadableStream, options?: extfs.IWriteFileOptions): Promise<void>;
export function writeFile(path: string, data: any, options?: extfs.IWriteFileOptions): Promise<void>;
export function writeFile(path: string, data: any, options?: extfs.IWriteFileOptions): any {
const queueKey = toQueueKey(path);
return ensureWriteFileQueue(queueKey).queue(() => nfcall(extfs.writeFileAndFlush, path, data, options));
@@ -135,9 +145,9 @@ function ensureWriteFileQueue(queueKey: string): Queue<void> {
/**
* Read a dir and return only subfolders
*/
export function readDirsInDir(dirPath: string): TPromise<string[]> {
export function readDirsInDir(dirPath: string): Promise<string[]> {
return readdir(dirPath).then(children => {
return TPromise.join(children.map(c => dirExists(join(dirPath, c)))).then(exists => {
return Promise.all(children.map(c => dirExists(join(dirPath, c)))).then(exists => {
return children.filter((_, i) => exists[i]);
});
});
@@ -146,35 +156,35 @@ export function readDirsInDir(dirPath: string): TPromise<string[]> {
/**
* `path` exists and is a directory
*/
export function dirExists(path: string): TPromise<boolean> {
export function dirExists(path: string): Promise<boolean> {
return stat(path).then(stat => stat.isDirectory(), () => false);
}
/**
* `path` exists and is a file.
*/
export function fileExists(path: string): TPromise<boolean> {
export function fileExists(path: string): Promise<boolean> {
return stat(path).then(stat => stat.isFile(), () => false);
}
/**
* Deletes a path from disk.
*/
let _tmpDir: string = null;
let _tmpDir: string | null = null;
function getTmpDir(): string {
if (!_tmpDir) {
_tmpDir = os.tmpdir();
}
return _tmpDir;
}
export function del(path: string, tmp = getTmpDir()): TPromise<void> {
export function del(path: string, tmp = getTmpDir()): Promise<void> {
return nfcall(extfs.del, path, tmp);
}
export function whenDeleted(path: string): TPromise<void> {
export function whenDeleted(path: string): Promise<void> {
// Complete when wait marker file is deleted
return new TPromise<void>(c => {
return new Promise<void>(resolve => {
let running = false;
const interval = setInterval(() => {
if (!running) {
@@ -184,7 +194,7 @@ export function whenDeleted(path: string): TPromise<void> {
if (!exists) {
clearInterval(interval);
c(null);
resolve(void 0);
}
});
}
@@ -192,6 +202,6 @@ export function whenDeleted(path: string): TPromise<void> {
});
}
export function copy(source: string, target: string): TPromise<void> {
export function copy(source: string, target: string): Promise<void> {
return nfcall(extfs.copy, source, target);
}
}

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as net from 'net';
/**

View File

@@ -2,24 +2,24 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as path from 'path';
import * as fs from 'fs';
import * as cp from 'child_process';
import { fork } from 'vs/base/node/stdFork';
import * as nls from 'vs/nls';
import { TPromise, TValueCallback, ErrorCallback } from 'vs/base/common/winjs.base';
import * as Types from 'vs/base/common/types';
import { IStringDictionary } from 'vs/base/common/collections';
import URI from 'vs/base/common/uri';
import * as Objects from 'vs/base/common/objects';
import * as TPath from 'vs/base/common/paths';
import * as Platform from 'vs/base/common/platform';
import { LineDecoder } from 'vs/base/node/decoder';
import { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode, Executable } from 'vs/base/common/processes';
import { getPathFromAmdModule } from 'vs/base/common/amd';
export { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode };
export type TProgressCallback<T> = (progress: T) => void;
export type ValueCallback<T> = (value?: T | Thenable<T>) => void;
export type ErrorCallback = (error?: any) => void;
export type ProgressCallback<T> = (progress: T) => void;
export interface LineData {
line: string;
@@ -54,7 +54,7 @@ export function terminateProcess(process: cp.ChildProcess, cwd?: string): Termin
}
} else if (Platform.isLinux || Platform.isMacintosh) {
try {
let cmd = URI.parse(require.toUrl('vs/base/node/terminateProcess.sh')).fsPath;
let cmd = getPathFromAmdModule(require, 'vs/base/node/terminateProcess.sh');
let result = cp.spawnSync(cmd, [process.pid.toString()]);
if (result.error) {
return { success: false, error: result.error };
@@ -74,14 +74,13 @@ export function getWindowsShell(): string {
export abstract class AbstractProcess<TProgressData> {
private cmd: string;
private module: string;
private args: string[];
private options: CommandOptions | ForkOptions;
protected shell: boolean;
private childProcess: cp.ChildProcess;
protected childProcessPromise: TPromise<cp.ChildProcess>;
private pidResolve: TValueCallback<number>;
private childProcess: cp.ChildProcess | null;
protected childProcessPromise: Promise<cp.ChildProcess> | null;
private pidResolve?: ValueCallback<number>;
protected terminateRequested: boolean;
private static WellKnowCommands: IStringDictionary<boolean> = {
@@ -106,19 +105,13 @@ export abstract class AbstractProcess<TProgressData> {
};
public constructor(executable: Executable);
public constructor(cmd: string, args: string[], shell: boolean, options: CommandOptions);
public constructor(module: string, args: string[], options: ForkOptions);
public constructor(arg1: string | Executable, arg2?: string[], arg3?: boolean | ForkOptions, arg4?: CommandOptions) {
if (arg4) {
public constructor(cmd: string, args: string[] | undefined, shell: boolean, options: CommandOptions | undefined);
public constructor(arg1: string | Executable, arg2?: string[], arg3?: boolean, arg4?: CommandOptions) {
if (arg2 !== void 0 && arg3 !== void 0 && arg4 !== void 0) {
this.cmd = <string>arg1;
this.args = arg2;
this.shell = <boolean>arg3;
this.shell = arg3;
this.options = arg4;
} else if (arg3 && arg2) {
this.module = <string>arg1;
this.args = arg2;
this.shell = false;
this.options = <ForkOptions>arg3;
} else {
let executable = <Executable>arg1;
this.cmd = executable.command;
@@ -133,10 +126,10 @@ export abstract class AbstractProcess<TProgressData> {
if (this.options.env) {
let newEnv: IStringDictionary<string> = Object.create(null);
Object.keys(process.env).forEach((key) => {
newEnv[key] = process.env[key];
newEnv[key] = process.env[key]!;
});
Object.keys(this.options.env).forEach((key) => {
newEnv[key] = this.options.env[key];
newEnv[key] = this.options.env![key]!;
});
this.options.env = newEnv;
}
@@ -154,14 +147,14 @@ export abstract class AbstractProcess<TProgressData> {
return 'other';
}
public start(pp: TProgressCallback<TProgressData>): TPromise<SuccessData> {
if (Platform.isWindows && ((this.options && this.options.cwd && TPath.isUNC(this.options.cwd)) || !this.options && !this.options.cwd && TPath.isUNC(process.cwd()))) {
return TPromise.wrapError(new Error(nls.localize('TaskRunner.UNC', 'Can\'t execute a shell command on a UNC drive.')));
public start(pp: ProgressCallback<TProgressData>): Promise<SuccessData> {
if (Platform.isWindows && ((this.options && this.options.cwd && TPath.isUNC(this.options.cwd)) || !this.options && TPath.isUNC(process.cwd()))) {
return Promise.reject(new Error(nls.localize('TaskRunner.UNC', 'Can\'t execute a shell command on a UNC drive.')));
}
return this.useExec().then((useExec) => {
let cc: TValueCallback<SuccessData>;
let cc: ValueCallback<SuccessData>;
let ee: ErrorCallback;
let result = new TPromise<any>((c, e) => {
let result = new Promise<any>((c, e) => {
cc = c;
ee = e;
});
@@ -184,7 +177,7 @@ export abstract class AbstractProcess<TProgressData> {
}
});
} else {
let childProcess: cp.ChildProcess = null;
let childProcess: cp.ChildProcess | null = null;
let closeHandler = (data: any) => {
this.childProcess = null;
this.childProcessPromise = null;
@@ -233,29 +226,11 @@ export abstract class AbstractProcess<TProgressData> {
} else {
if (this.cmd) {
childProcess = cp.spawn(this.cmd, this.args, this.options);
} else if (this.module) {
this.childProcessPromise = new TPromise<cp.ChildProcess>((c, e) => {
fork(this.module, this.args, <ForkOptions>this.options, (error: any, childProcess: cp.ChildProcess) => {
if (error) {
e(error);
ee({ terminated: this.terminateRequested, error: error });
return;
}
this.childProcess = childProcess;
if (this.pidResolve) {
this.pidResolve(Types.isNumber(childProcess.pid) ? childProcess.pid : -1);
this.pidResolve = undefined;
}
this.childProcess.on('close', closeHandler);
this.handleSpawn(childProcess, cc, pp, ee, false);
c(childProcess);
});
});
}
}
if (childProcess) {
this.childProcess = childProcess;
this.childProcessPromise = TPromise.as(childProcess);
this.childProcessPromise = Promise.resolve(childProcess);
if (this.pidResolve) {
this.pidResolve(Types.isNumber(childProcess.pid) ? childProcess.pid : -1);
this.pidResolve = undefined;
@@ -266,7 +241,7 @@ export abstract class AbstractProcess<TProgressData> {
});
if (childProcess.pid) {
this.childProcess.on('close', closeHandler);
this.handleSpawn(childProcess, cc, pp, ee, true);
this.handleSpawn(childProcess, cc!, pp, ee!, true);
}
}
}
@@ -274,10 +249,10 @@ export abstract class AbstractProcess<TProgressData> {
});
}
protected abstract handleExec(cc: TValueCallback<SuccessData>, pp: TProgressCallback<TProgressData>, error: Error, stdout: Buffer, stderr: Buffer): void;
protected abstract handleSpawn(childProcess: cp.ChildProcess, cc: TValueCallback<SuccessData>, pp: TProgressCallback<TProgressData>, ee: ErrorCallback, sync: boolean): void;
protected abstract handleExec(cc: ValueCallback<SuccessData>, pp: ProgressCallback<TProgressData>, error: Error | null, stdout: Buffer, stderr: Buffer): void;
protected abstract handleSpawn(childProcess: cp.ChildProcess, cc: ValueCallback<SuccessData>, pp: ProgressCallback<TProgressData>, ee: ErrorCallback, sync: boolean): void;
protected handleClose(data: any, cc: TValueCallback<SuccessData>, pp: TProgressCallback<TProgressData>, ee: ErrorCallback): void {
protected handleClose(data: any, cc: ValueCallback<SuccessData>, pp: ProgressCallback<TProgressData>, ee: ErrorCallback): void {
// Default is to do nothing.
}
@@ -296,19 +271,19 @@ export abstract class AbstractProcess<TProgressData> {
}
}
public get pid(): TPromise<number> {
public get pid(): Promise<number> {
if (this.childProcessPromise) {
return this.childProcessPromise.then(childProcess => childProcess.pid, err => -1);
} else {
return new TPromise<number>((resolve) => {
return new Promise<number>((resolve) => {
this.pidResolve = resolve;
});
}
}
public terminate(): TPromise<TerminateResponse> {
public terminate(): Promise<TerminateResponse> {
if (!this.childProcessPromise) {
return TPromise.as<TerminateResponse>({ success: true });
return Promise.resolve<TerminateResponse>({ success: true });
}
return this.childProcessPromise.then((childProcess) => {
this.terminateRequested = true;
@@ -322,8 +297,8 @@ export abstract class AbstractProcess<TProgressData> {
});
}
private useExec(): TPromise<boolean> {
return new TPromise<boolean>((c, e) => {
private useExec(): Promise<boolean> {
return new Promise<boolean>((c, e) => {
if (!this.shell || !Platform.isWindows) {
c(false);
}
@@ -345,12 +320,11 @@ export class LineProcess extends AbstractProcess<LineData> {
public constructor(executable: Executable);
public constructor(cmd: string, args: string[], shell: boolean, options: CommandOptions);
public constructor(module: string, args: string[], options: ForkOptions);
public constructor(arg1: string | Executable, arg2?: string[], arg3?: boolean | ForkOptions, arg4?: CommandOptions) {
super(<any>arg1, arg2, <any>arg3, arg4);
}
protected handleExec(cc: TValueCallback<SuccessData>, pp: TProgressCallback<LineData>, error: Error, stdout: Buffer, stderr: Buffer) {
protected handleExec(cc: ValueCallback<SuccessData>, pp: ProgressCallback<LineData>, error: Error, stdout: Buffer, stderr: Buffer) {
[stdout, stderr].forEach((buffer: Buffer, index: number) => {
let lineDecoder = new LineDecoder();
let lines = lineDecoder.write(buffer);
@@ -365,7 +339,7 @@ export class LineProcess extends AbstractProcess<LineData> {
cc({ terminated: this.terminateRequested, error: error });
}
protected handleSpawn(childProcess: cp.ChildProcess, cc: TValueCallback<SuccessData>, pp: TProgressCallback<LineData>, ee: ErrorCallback, sync: boolean): void {
protected handleSpawn(childProcess: cp.ChildProcess, cc: ValueCallback<SuccessData>, pp: ProgressCallback<LineData>, ee: ErrorCallback, sync: boolean): void {
this.stdoutLineDecoder = new LineDecoder();
this.stderrLineDecoder = new LineDecoder();
childProcess.stdout.on('data', (data: Buffer) => {
@@ -378,7 +352,7 @@ export class LineProcess extends AbstractProcess<LineData> {
});
}
protected handleClose(data: any, cc: TValueCallback<SuccessData>, pp: TProgressCallback<LineData>, ee: ErrorCallback): void {
protected handleClose(data: any, cc: ValueCallback<SuccessData>, pp: ProgressCallback<LineData>, ee: ErrorCallback): void {
[this.stdoutLineDecoder.end(), this.stderrLineDecoder.end()].forEach((line, index) => {
if (line) {
pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr });
@@ -428,3 +402,51 @@ export function createQueuedSender(childProcess: cp.ChildProcess): IQueuedSender
return { send };
}
export namespace win32 {
export function findExecutable(command: string, cwd?: string, paths?: string[]): string {
// If we have an absolute path then we take it.
if (path.isAbsolute(command)) {
return command;
}
if (cwd === void 0) {
cwd = process.cwd();
}
let dir = path.dirname(command);
if (dir !== '.') {
// We have a directory and the directory is relative (see above). Make the path absolute
// to the current working directory.
return path.join(cwd, command);
}
if (paths === void 0 && Types.isString(process.env.PATH)) {
paths = process.env.PATH.split(path.delimiter);
}
// No PATH environment. Make path absolute to the cwd.
if (paths === void 0 || paths.length === 0) {
return path.join(cwd, command);
}
// We have a simple file name. We get the path variable from the env
// and try to find the executable on the path.
for (let pathEntry of paths) {
// The path entry is absolute.
let fullPath: string;
if (path.isAbsolute(pathEntry)) {
fullPath = path.join(pathEntry, command);
} else {
fullPath = path.join(cwd, pathEntry, command);
}
if (fs.existsSync(fullPath)) {
return fullPath;
}
let withExtension = fullPath + '.com';
if (fs.existsSync(withExtension)) {
return withExtension;
}
withExtension = fullPath + '.exe';
if (fs.existsSync(withExtension)) {
return withExtension;
}
}
return path.join(cwd, command);
}
}

View File

@@ -3,13 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Url, parse as parseUrl } from 'url';
import { isBoolean } from 'vs/base/common/types';
import { Agent } from './request';
function getSystemProxyURI(requestURL: Url): string {
function getSystemProxyURI(requestURL: Url): string | null {
if (requestURL.protocol === 'http:') {
return process.env.HTTP_PROXY || process.env.http_proxy || null;
} else if (requestURL.protocol === 'https:') {
@@ -34,12 +32,12 @@ export async function getProxyAgent(rawRequestURL: string, options: IOptions = {
const proxyEndpoint = parseUrl(proxyURL);
if (!/^https?:$/.test(proxyEndpoint.protocol)) {
if (!/^https?:$/.test(proxyEndpoint.protocol || '')) {
return null;
}
const opts = {
host: proxyEndpoint.hostname,
host: proxyEndpoint.hostname || '',
port: Number(proxyEndpoint.port),
auth: proxyEndpoint.auth,
rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true

View File

@@ -3,10 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { exec } from 'child_process';
import URI from 'vs/base/common/uri';
import { getPathFromAmdModule } from 'vs/base/common/amd';
export interface ProcessItem {
name: string;
@@ -59,7 +58,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
function findName(cmd: string): string {
const RENDERER_PROCESS_HINT = /--disable-blink-features=Auxclick/;
const SHARED_PROCESS_HINT = /--disable-blink-features=Auxclick/;
const WINDOWS_WATCHER_HINT = /\\watcher\\win32\\CodeHelper\.exe/;
const WINDOWS_CRASH_REPORTER = /--crashes-directory/;
const WINDOWS_PTY = /\\pipe\\winpty-control/;
@@ -90,7 +89,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
let matches = TYPE.exec(cmd);
if (matches && matches.length === 2) {
if (matches[1] === 'renderer') {
if (!RENDERER_PROCESS_HINT.exec(cmd)) {
if (SHARED_PROCESS_HINT.exec(cmd)) {
return 'shared-process';
}
@@ -138,14 +137,14 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
windowsProcessTree.getProcessCpuUsage(processList, (completeProcessList) => {
const processItems: Map<number, ProcessItem> = new Map();
completeProcessList.forEach(process => {
const commandLine = cleanUNCPrefix(process.commandLine);
const commandLine = cleanUNCPrefix(process.commandLine || '');
processItems.set(process.pid, {
name: findName(commandLine),
cmd: commandLine,
pid: process.pid,
ppid: process.ppid,
load: process.cpu,
mem: process.memory
load: process.cpu || 0,
mem: process.memory || 0
});
});
@@ -178,7 +177,8 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
const CMD = '/bin/ps -ax -o pid=,ppid=,pcpu=,pmem=,command=';
const PID_CMD = /^\s*([0-9]+)\s+([0-9]+)\s+([0-9]+\.[0-9]+)\s+([0-9]+\.[0-9]+)\s+(.+)$/;
exec(CMD, { maxBuffer: 1000 * 1024 }, (err, stdout, stderr) => {
// Set numeric locale to ensure '.' is used as the decimal separator
exec(CMD, { maxBuffer: 1000 * 1024, env: { LC_NUMERIC: 'en_US.UTF-8' } }, (err, stdout, stderr) => {
if (err || stderr) {
reject(err || stderr.toString());
@@ -195,19 +195,21 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
if (process.platform === 'linux') {
// Flatten rootItem to get a list of all VSCode processes
let processes = [rootItem];
const pids = [];
const pids: number[] = [];
while (processes.length) {
const process = processes.shift();
pids.push(process.pid);
if (process.children) {
processes = processes.concat(process.children);
if (process) {
pids.push(process.pid);
if (process.children) {
processes = processes.concat(process.children);
}
}
}
// The cpu usage value reported on Linux is the average over the process lifetime,
// recalculate the usage over a one second interval
// JSON.stringify is needed to escape spaces, https://github.com/nodejs/node/issues/6803
let cmd = JSON.stringify(URI.parse(require.toUrl('vs/base/node/cpuUsage.sh')).fsPath);
let cmd = JSON.stringify(getPathFromAmdModule(require, 'vs/base/node/cpuUsage.sh'));
cmd += ' ' + pids.join(' ');
exec(cmd, {}, (err, stdout, stderr) => {

View File

@@ -3,9 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { isBoolean, isNumber } from 'vs/base/common/types';
import * as https from 'https';
import * as http from 'http';
@@ -14,6 +11,8 @@ import { parse as parseUrl } from 'url';
import { createWriteStream } from 'fs';
import { assign } from 'vs/base/common/objects';
import { createGunzip } from 'zlib';
import { CancellationToken } from 'vs/base/common/cancellation';
import { canceled } from 'vs/base/common/errors';
export type Agent = any;
@@ -46,26 +45,26 @@ export interface IRequestContext {
}
export interface IRequestFunction {
(options: IRequestOptions): TPromise<IRequestContext>;
(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext>;
}
async function getNodeRequest(options: IRequestOptions): Promise<IRawRequestFunction> {
const endpoint = parseUrl(options.url);
const endpoint = parseUrl(options.url!);
const module = endpoint.protocol === 'https:' ? await import('https') : await import('http');
return module.request;
}
export function request(options: IRequestOptions): TPromise<IRequestContext> {
export function request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
let req: http.ClientRequest;
const rawRequestPromise = options.getRawRequest
? TPromise.as(options.getRawRequest(options))
: TPromise.wrap(getNodeRequest(options));
? Promise.resolve(options.getRawRequest(options))
: Promise.resolve(getNodeRequest(options));
return rawRequestPromise.then(rawRequest => {
return new TPromise<IRequestContext>((c, e) => {
const endpoint = parseUrl(options.url);
return new Promise<IRequestContext>((c, e) => {
const endpoint = parseUrl(options.url!);
const opts: https.RequestOptions = {
hostname: endpoint.hostname,
@@ -83,12 +82,12 @@ export function request(options: IRequestOptions): TPromise<IRequestContext> {
}
req = rawRequest(opts, (res: http.ClientResponse) => {
const followRedirects = isNumber(options.followRedirects) ? options.followRedirects : 3;
if (res.statusCode >= 300 && res.statusCode < 400 && followRedirects > 0 && res.headers['location']) {
const followRedirects: number = isNumber(options.followRedirects) ? options.followRedirects : 3;
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && followRedirects > 0 && res.headers['location']) {
request(assign({}, options, {
url: res.headers['location'],
followRedirects: followRedirects - 1
})).done(c, e);
}), token).then(c, e);
} else {
let stream: Stream = res;
@@ -116,30 +115,35 @@ export function request(options: IRequestOptions): TPromise<IRequestContext> {
}
req.end();
}, () => req && req.abort());
token.onCancellationRequested(() => {
req.abort();
e(canceled());
});
});
});
}
function isSuccess(context: IRequestContext): boolean {
return (context.res.statusCode >= 200 && context.res.statusCode < 300) || context.res.statusCode === 1223;
return (context.res.statusCode && context.res.statusCode >= 200 && context.res.statusCode < 300) || context.res.statusCode === 1223;
}
function hasNoContent(context: IRequestContext): boolean {
return context.res.statusCode === 204;
}
export function download(filePath: string, context: IRequestContext): TPromise<void> {
return new TPromise<void>((c, e) => {
export function download(filePath: string, context: IRequestContext): Promise<void> {
return new Promise<void>((c, e) => {
const out = createWriteStream(filePath);
out.once('finish', () => c(null));
out.once('finish', () => c(void 0));
context.stream.once('error', e);
context.stream.pipe(out);
});
}
export function asText(context: IRequestContext): TPromise<string> {
return new TPromise((c, e) => {
export function asText(context: IRequestContext): Promise<string | null> {
return new Promise((c, e) => {
if (!isSuccess(context)) {
return e('Server returned ' + context.res.statusCode);
}
@@ -155,8 +159,8 @@ export function asText(context: IRequestContext): TPromise<string> {
});
}
export function asJson<T>(context: IRequestContext): TPromise<T> {
return new TPromise((c, e) => {
export function asJson<T>(context: IRequestContext): Promise<T | null> {
return new Promise((c, e) => {
if (!isSuccess(context)) {
return e('Server returned ' + context.res.statusCode);
}

View File

@@ -3,11 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { readdir, stat, exists, readFile } from 'fs';
import { join } from 'path';
import { parse } from 'vs/base/common/json';
import { parse, ParseError } from 'vs/base/common/json';
export interface WorkspaceStatItem {
name: string;
@@ -39,7 +37,7 @@ export function collectLaunchConfigs(folder: string): Promise<WorkspaceStatItem[
return resolve([]);
}
const errors = [];
const errors: ParseError[] = [];
const json = parse(contents.toString(), errors);
if (errors.length) {
console.log(`Unable to parse ${launchConfig}`);
@@ -95,7 +93,7 @@ export function collectWorkspaceStats(folder: string, filter: string[]): Promise
const MAX_FILES = 20000;
function walk(dir: string, filter: string[], token, done: (allFiles: string[]) => void): void {
let results = [];
let results: string[] = [];
readdir(dir, async (err, files) => {
// Ignore folders that can't be read
if (err) {

View File

@@ -1,140 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as path from 'path';
import * as os from 'os';
import * as net from 'net';
import * as cp from 'child_process';
import uri from 'vs/base/common/uri';
export interface IForkOpts {
cwd?: string;
env?: any;
encoding?: string;
execArgv?: string[];
}
function makeRandomHexString(length: number): string {
let chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
let result = '';
for (let i = 0; i < length; i++) {
let idx = Math.floor(chars.length * Math.random());
result += chars[idx];
}
return result;
}
function generatePipeName(): string {
let randomName = 'vscode-std-' + makeRandomHexString(40);
if (process.platform === 'win32') {
return '\\\\.\\pipe\\' + randomName + '-sock';
}
// Mac/Unix: use socket file
return path.join(os.tmpdir(), randomName + '.sock');
}
function generatePatchedEnv(env: any, stdInPipeName: string, stdOutPipeName: string, stdErrPipeName: string): any {
// Set the two unique pipe names and the electron flag as process env
let newEnv: any = {};
for (let key in env) {
newEnv[key] = env[key];
}
newEnv['STDIN_PIPE_NAME'] = stdInPipeName;
newEnv['STDOUT_PIPE_NAME'] = stdOutPipeName;
newEnv['STDERR_PIPE_NAME'] = stdErrPipeName;
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
return newEnv;
}
export function fork(modulePath: string, args: string[], options: IForkOpts, callback: (error: any, cp: cp.ChildProcess) => void): void {
let callbackCalled = false;
let resolve = (result: cp.ChildProcess) => {
if (callbackCalled) {
return;
}
callbackCalled = true;
callback(null, result);
};
let reject = (err: any) => {
if (callbackCalled) {
return;
}
callbackCalled = true;
callback(err, null);
};
// Generate three unique pipe names
let stdInPipeName = generatePipeName();
let stdOutPipeName = generatePipeName();
let stdErrPipeName = generatePipeName();
let newEnv = generatePatchedEnv(options.env || process.env, stdInPipeName, stdOutPipeName, stdErrPipeName);
let childProcess: cp.ChildProcess;
// Begin listening to stderr pipe
let stdErrServer = net.createServer((stdErrStream) => {
// From now on the childProcess.stderr is available for reading
childProcess.stderr = stdErrStream;
});
stdErrServer.listen(stdErrPipeName);
// Begin listening to stdout pipe
let stdOutServer = net.createServer((stdOutStream) => {
// The child process will write exactly one chunk with content `ready` when it has installed a listener to the stdin pipe
stdOutStream.once('data', (chunk: Buffer) => {
// The child process is sending me the `ready` chunk, time to connect to the stdin pipe
childProcess.stdin = <any>net.connect(stdInPipeName);
// From now on the childProcess.stdout is available for reading
childProcess.stdout = stdOutStream;
resolve(childProcess);
});
});
stdOutServer.listen(stdOutPipeName);
let serverClosed = false;
let closeServer = () => {
if (serverClosed) {
return;
}
serverClosed = true;
process.removeListener('exit', closeServer);
stdOutServer.close();
stdErrServer.close();
};
// Create the process
let bootstrapperPath = (uri.parse(require.toUrl('./stdForkStart.js')).fsPath);
childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), {
silent: true,
cwd: options.cwd,
env: newEnv,
execArgv: options.execArgv
});
childProcess.once('error', (err: Error) => {
closeServer();
reject(err);
});
childProcess.once('exit', (err: Error) => {
closeServer();
reject(err);
});
// On vscode exit still close server #7758
process.once('exit', closeServer);
}

View File

@@ -1,197 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const net = require('net');
const fs = require('fs');
// const stream = require('stream');
// const util = require('util');
var ENABLE_LOGGING = false;
var log = (function () {
if (!ENABLE_LOGGING) {
return function () { };
}
var isFirst = true;
var LOG_LOCATION = 'C:\\stdFork.log';
return function log(str) {
if (isFirst) {
isFirst = false;
fs.writeFileSync(LOG_LOCATION, str + '\n');
return;
}
fs.appendFileSync(LOG_LOCATION, str + '\n');
};
})();
var stdInPipeName = process.env['STDIN_PIPE_NAME'];
var stdOutPipeName = process.env['STDOUT_PIPE_NAME'];
var stdErrPipeName = process.env['STDERR_PIPE_NAME'];
log('STDIN_PIPE_NAME: ' + stdInPipeName);
log('STDOUT_PIPE_NAME: ' + stdOutPipeName);
log('STDERR_PIPE_NAME: ' + stdErrPipeName);
log('ELECTRON_RUN_AS_NODE: ' + process.env['ELECTRON_RUN_AS_NODE']);
// stdout redirection to named pipe
(function () {
log('Beginning stdout redirection...');
// Create a writing stream to the stdout pipe
var stdOutStream = net.connect(stdOutPipeName);
// unref stdOutStream to behave like a normal standard out
stdOutStream.unref();
// handle process.stdout
process.__defineGetter__('stdout', function () { return stdOutStream; });
// Create a writing stream to the stderr pipe
var stdErrStream = net.connect(stdErrPipeName);
// unref stdErrStream to behave like a normal standard out
stdErrStream.unref();
// handle process.stderr
process.__defineGetter__('stderr', function () { return stdErrStream; });
var fsWriteSyncString = function (fd, str, position, encoding) {
// fs.writeSync(fd, string[, position[, encoding]]);
var buf = Buffer.from(str, encoding || 'utf8');
return fsWriteSyncBuffer(fd, buf, 0, buf.length);
};
var fsWriteSyncBuffer = function (fd, buffer, off, len/* , position */) {
off = Math.abs(off | 0);
len = Math.abs(len | 0);
// fs.writeSync(fd, buffer, offset, length[, position]);
var buffer_length = buffer.length;
if (off > buffer_length) {
throw new Error('offset out of bounds');
}
if (len > buffer_length) {
throw new Error('length out of bounds');
}
if (((off + len) | 0) < off) {
throw new Error('off + len overflow');
}
if (buffer_length - off < len) {
// Asking for more than is left over in the buffer
throw new Error('off + len > buffer.length');
}
var slicedBuffer = buffer;
if (off !== 0 || len !== buffer_length) {
slicedBuffer = buffer.slice(off, off + len);
}
if (fd === 1) {
stdOutStream.write(slicedBuffer);
} else {
stdErrStream.write(slicedBuffer);
}
return slicedBuffer.length;
};
// handle fs.writeSync(1, ...) and fs.writeSync(2, ...)
var originalWriteSync = fs.writeSync;
fs.writeSync = function (fd, data/* , position, encoding */) {
if (fd !== 1 && fd !== 2) {
return originalWriteSync.apply(fs, arguments);
}
// usage:
// fs.writeSync(fd, buffer, offset, length[, position]);
// OR
// fs.writeSync(fd, string[, position[, encoding]]);
if (data instanceof Buffer) {
return fsWriteSyncBuffer.apply(null, arguments);
}
// For compatibility reasons with fs.writeSync, writing null will write "null", etc
if (typeof data !== 'string') {
data += '';
}
return fsWriteSyncString.apply(null, arguments);
};
log('Finished defining process.stdout, process.stderr and fs.writeSync');
})();
// stdin redirection to named pipe
(function () {
// Begin listening to stdin pipe
var server = net.createServer(function (stream) {
// Stop accepting new connections, keep the existing one alive
server.close();
log('Parent process has connected to my stdin. All should be good now.');
// handle process.stdin
process.__defineGetter__('stdin', function () {
return stream;
});
// Remove myself from process.argv
process.argv.splice(1, 1);
// Load the actual program
var program = process.argv[1];
log('Loading program: ' + program);
// Unset the custom environmental variables that should not get inherited
delete process.env['STDIN_PIPE_NAME'];
delete process.env['STDOUT_PIPE_NAME'];
delete process.env['STDERR_PIPE_NAME'];
delete process.env['ELECTRON_RUN_AS_NODE'];
require(program);
log('Finished loading program.');
var stdinIsReferenced = true;
var timer = setInterval(function () {
var listenerCount = (
stream.listeners('data').length +
stream.listeners('end').length +
stream.listeners('close').length +
stream.listeners('error').length
);
// log('listenerCount: ' + listenerCount);
if (listenerCount <= 1) {
// No more "actual" listeners, only internal node
if (stdinIsReferenced) {
stdinIsReferenced = false;
// log('unreferencing stream!!!');
stream.unref();
}
} else {
// There are "actual" listeners
if (!stdinIsReferenced) {
stdinIsReferenced = true;
stream.ref();
}
}
// log(
// '' + stream.listeners('data').length +
// ' ' + stream.listeners('end').length +
// ' ' + stream.listeners('close').length +
// ' ' + stream.listeners('error').length
// );
}, 1000);
timer.unref();
});
server.listen(stdInPipeName, function () {
// signal via stdout that the parent process can now begin writing to stdin pipe
process.stdout.write('ready');
});
})();

701
src/vs/base/node/storage.ts Normal file
View File

@@ -0,0 +1,701 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Database, Statement } from 'vscode-sqlite3';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { ThrottledDelayer, timeout } from 'vs/base/common/async';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { mapToString, setToString } from 'vs/base/common/map';
import { basename } from 'path';
import { mark } from 'vs/base/common/performance';
import { rename, unlinkIgnoreError, copy, renameIgnoreError } from 'vs/base/node/pfs';
export enum StorageHint {
// A hint to the storage that the storage
// does not exist on disk yet. This allows
// the storage library to improve startup
// time by not checking the storage for data.
STORAGE_DOES_NOT_EXIST
}
export interface IStorageOptions {
hint?: StorageHint;
}
export interface IUpdateRequest {
insert?: Map<string, string>;
delete?: Set<string>;
}
export interface IStorageItemsChangeEvent {
items: Map<string, string>;
}
export interface IStorageDatabase {
readonly onDidChangeItemsExternal: Event<IStorageItemsChangeEvent>;
getItems(): Thenable<Map<string, string>>;
updateItems(request: IUpdateRequest): Thenable<void>;
close(): Thenable<void>;
checkIntegrity(full: boolean): Thenable<string>;
}
export interface IStorage extends IDisposable {
readonly items: Map<string, string>;
readonly size: number;
readonly onDidChangeStorage: Event<string>;
init(): Thenable<void>;
get(key: string, fallbackValue: string): string;
get(key: string, fallbackValue?: string): string | undefined;
getBoolean(key: string, fallbackValue: boolean): boolean;
getBoolean(key: string, fallbackValue?: boolean): boolean | undefined;
getInteger(key: string, fallbackValue: number): number;
getInteger(key: string, fallbackValue?: number): number | undefined;
set(key: string, value: any): Thenable<void>;
delete(key: string): Thenable<void>;
beforeClose(): void;
close(): Thenable<void>;
checkIntegrity(full: boolean): Thenable<string>;
}
enum StorageState {
None,
Initialized,
Closed
}
export class Storage extends Disposable implements IStorage {
_serviceBrand: any;
private static readonly DEFAULT_FLUSH_DELAY = 100;
private _onDidChangeStorage: Emitter<string> = this._register(new Emitter<string>());
get onDidChangeStorage(): Event<string> { return this._onDidChangeStorage.event; }
private state = StorageState.None;
private cache: Map<string, string> = new Map<string, string>();
private flushDelayer: ThrottledDelayer<void>;
private flushDelay = Storage.DEFAULT_FLUSH_DELAY;
private pendingDeletes: Set<string> = new Set<string>();
private pendingInserts: Map<string, string> = new Map();
constructor(
protected database: IStorageDatabase,
private options: IStorageOptions = Object.create(null)
) {
super();
this.flushDelayer = this._register(new ThrottledDelayer(this.flushDelay));
this.registerListeners();
}
private registerListeners(): void {
this._register(this.database.onDidChangeItemsExternal(e => this.onDidChangeItemsExternal(e)));
}
private onDidChangeItemsExternal(e: IStorageItemsChangeEvent): void {
// items that change external require us to update our
// caches with the values. we just accept the value and
// emit an event if there is a change.
e.items.forEach((value, key) => this.accept(key, value));
}
private accept(key: string, value: string): void {
if (this.state === StorageState.Closed) {
return; // Return early if we are already closed
}
let changed = false;
// Item got removed, check for deletion
if (isUndefinedOrNull(value)) {
changed = this.cache.delete(key);
}
// Item got updated, check for change
else {
const currentValue = this.cache.get(key);
if (currentValue !== value) {
this.cache.set(key, value);
changed = true;
}
}
// Signal to outside listeners
if (changed) {
this._onDidChangeStorage.fire(key);
}
}
get items(): Map<string, string> {
return this.cache;
}
get size(): number {
return this.cache.size;
}
init(): Thenable<void> {
if (this.state !== StorageState.None) {
return Promise.resolve(); // either closed or already initialized
}
this.state = StorageState.Initialized;
if (this.options.hint === StorageHint.STORAGE_DOES_NOT_EXIST) {
// return early if we know the storage file does not exist. this is a performance
// optimization to not load all items of the underlying storage if we know that
// there can be no items because the storage does not exist.
return Promise.resolve();
}
return this.database.getItems().then(items => {
this.cache = items;
});
}
get(key: string, fallbackValue: string): string;
get(key: string, fallbackValue?: string): string | undefined;
get(key: string, fallbackValue?: string): string | undefined {
const value = this.cache.get(key);
if (isUndefinedOrNull(value)) {
return fallbackValue;
}
return value;
}
getBoolean(key: string, fallbackValue: boolean): boolean;
getBoolean(key: string, fallbackValue?: boolean): boolean | undefined;
getBoolean(key: string, fallbackValue?: boolean): boolean | undefined {
const value = this.get(key);
if (isUndefinedOrNull(value)) {
return fallbackValue;
}
return value === 'true';
}
getInteger(key: string, fallbackValue: number): number;
getInteger(key: string, fallbackValue?: number): number | undefined;
getInteger(key: string, fallbackValue?: number): number | undefined {
const value = this.get(key);
if (isUndefinedOrNull(value)) {
return fallbackValue;
}
return parseInt(value, 10);
}
set(key: string, value: any): Thenable<void> {
if (this.state === StorageState.Closed) {
return Promise.resolve(); // Return early if we are already closed
}
// We remove the key for undefined/null values
if (isUndefinedOrNull(value)) {
return this.delete(key);
}
// Otherwise, convert to String and store
const valueStr = String(value);
// Return early if value already set
const currentValue = this.cache.get(key);
if (currentValue === valueStr) {
return Promise.resolve();
}
// Update in cache and pending
this.cache.set(key, valueStr);
this.pendingInserts.set(key, valueStr);
this.pendingDeletes.delete(key);
// Event
this._onDidChangeStorage.fire(key);
// Accumulate work by scheduling after timeout
return this.flushDelayer.trigger(() => this.flushPending(), this.flushDelay);
}
delete(key: string): Thenable<void> {
if (this.state === StorageState.Closed) {
return Promise.resolve(); // Return early if we are already closed
}
// Remove from cache and add to pending
const wasDeleted = this.cache.delete(key);
if (!wasDeleted) {
return Promise.resolve(); // Return early if value already deleted
}
if (!this.pendingDeletes.has(key)) {
this.pendingDeletes.add(key);
}
this.pendingInserts.delete(key);
// Event
this._onDidChangeStorage.fire(key);
// Accumulate work by scheduling after timeout
return this.flushDelayer.trigger(() => this.flushPending(), this.flushDelay);
}
beforeClose(): void {
this.flushDelay = 0; // when we are about to close, reduce our flush delay to 0 to consume too much time
}
close(): Thenable<void> {
if (this.state === StorageState.Closed) {
return Promise.resolve(); // return if already closed
}
// Update state
this.state = StorageState.Closed;
// Trigger new flush to ensure data is persisted and then close
// even if there is an error flushing. We must always ensure
// the DB is closed to avoid corruption.
const onDone = () => this.database.close();
return this.flushDelayer.trigger(() => this.flushPending(), 0 /* as soon as possible */).then(onDone, onDone);
}
private flushPending(): Thenable<void> {
if (this.pendingInserts.size === 0 && this.pendingDeletes.size === 0) {
return Promise.resolve(); // return early if nothing to do
}
// Get pending data
const updateRequest: IUpdateRequest = { insert: this.pendingInserts, delete: this.pendingDeletes };
// Reset pending data for next run
this.pendingDeletes = new Set<string>();
this.pendingInserts = new Map<string, string>();
// Update in storage
return this.database.updateItems(updateRequest);
}
checkIntegrity(full: boolean): Thenable<string> {
return this.database.checkIntegrity(full);
}
}
interface IOpenDatabaseResult {
db: Database;
path: string;
}
export interface ISQLiteStorageDatabaseOptions {
logging?: ISQLiteStorageDatabaseLoggingOptions;
}
export interface ISQLiteStorageDatabaseLoggingOptions {
logError?: (error: string | Error) => void;
logTrace?: (msg: string) => void;
}
export class SQLiteStorageDatabase implements IStorageDatabase {
static IN_MEMORY_PATH = ':memory:';
get onDidChangeItemsExternal(): Event<IStorageItemsChangeEvent> { return Event.None; } // since we are the only client, there can be no external changes
private static measuredRequireDuration: boolean; // TODO@Ben remove me after a while
private static BUSY_OPEN_TIMEOUT = 2000; // timeout in ms to retry when opening DB fails with SQLITE_BUSY
private name: string;
private logger: SQLiteStorageDatabaseLogger;
private whenOpened: Promise<IOpenDatabaseResult>;
constructor(path: string, options: ISQLiteStorageDatabaseOptions = Object.create(null)) {
this.name = basename(path);
this.logger = new SQLiteStorageDatabaseLogger(options.logging);
this.whenOpened = this.open(path);
}
getItems(): Promise<Map<string, string>> {
return this.whenOpened.then(({ db }) => {
const items = new Map<string, string>();
return this.all(db, 'SELECT * FROM ItemTable').then(rows => {
rows.forEach(row => items.set(row.key, row.value));
if (this.logger.isTracing) {
this.logger.trace(`[storage ${this.name}] getItems(): ${mapToString(items)}`);
}
return items;
});
});
}
updateItems(request: IUpdateRequest): Promise<void> {
let updateCount = 0;
if (request.insert) {
updateCount += request.insert.size;
}
if (request.delete) {
updateCount += request.delete.size;
}
if (updateCount === 0) {
return Promise.resolve();
}
if (this.logger.isTracing) {
this.logger.trace(`[storage ${this.name}] updateItems(): insert(${request.insert ? mapToString(request.insert) : '0'}), delete(${request.delete ? setToString(request.delete) : '0'})`);
}
return this.whenOpened.then(({ db }) => {
return this.transaction(db, () => {
if (request.insert && request.insert.size > 0) {
this.prepare(db, 'INSERT INTO ItemTable VALUES (?,?)', stmt => {
request.insert!.forEach((value, key) => {
stmt.run([key, value]);
});
});
}
if (request.delete && request.delete.size) {
this.prepare(db, 'DELETE FROM ItemTable WHERE key=?', stmt => {
request.delete!.forEach(key => {
stmt.run(key);
});
});
}
});
});
}
close(): Promise<void> {
this.logger.trace(`[storage ${this.name}] close()`);
return this.whenOpened.then(result => {
return new Promise((resolve, reject) => {
result.db.close(error => {
if (error) {
this.logger.error(`[storage ${this.name}] close(): ${error}`);
return reject(error);
}
// If the DB closed successfully and we are not running in-memory
// make a backup of the DB so that we can use it as fallback in
// case the actual DB becomes corrupt.
if (result.path !== SQLiteStorageDatabase.IN_MEMORY_PATH) {
return this.backup(result).then(resolve, error => {
this.logger.error(`[storage ${this.name}] backup(): ${error}`);
return resolve(); // ignore failing backup
});
}
return resolve();
});
});
});
}
private backup(db: IOpenDatabaseResult): Promise<void> {
if (db.path === SQLiteStorageDatabase.IN_MEMORY_PATH) {
return Promise.resolve(); // no backups when running in-memory
}
const backupPath = this.toBackupPath(db.path);
return unlinkIgnoreError(backupPath).then(() => copy(db.path, backupPath));
}
private toBackupPath(path: string): string {
return `${path}.backup`;
}
checkIntegrity(full: boolean): Promise<string> {
this.logger.trace(`[storage ${this.name}] checkIntegrity(full: ${full})`);
return this.whenOpened.then(({ db }) => {
return this.get(db, full ? 'PRAGMA integrity_check' : 'PRAGMA quick_check').then(row => {
return full ? row['integrity_check'] : row['quick_check'];
});
});
}
private open(path: string): Promise<IOpenDatabaseResult> {
this.logger.trace(`[storage ${this.name}] open()`);
return new Promise((resolve, reject) => {
const fallbackToInMemoryDatabase = (error: Error) => {
this.logger.error(`[storage ${this.name}] open(): Error (open DB): ${error}`);
this.logger.error(`[storage ${this.name}] open(): Falling back to in-memory DB`);
// In case of any error to open the DB, use an in-memory
// DB so that we always have a valid DB to talk to.
this.doOpen(SQLiteStorageDatabase.IN_MEMORY_PATH).then(resolve, reject);
};
this.doOpen(path).then(resolve, error => {
// TODO@Ben check if this is still happening. This error code should only arise if
// another process is locking the same DB we want to open at that time. This typically
// never happens because a DB connection is limited per window. However, in the event
// of a window reload, it may be possible that the previous connection was not properly
// closed while the new connection is already established.
if (error.code === 'SQLITE_BUSY') {
return this.handleSQLiteBusy(path).then(resolve, fallbackToInMemoryDatabase);
}
// This error code indicates that even though the DB file exists,
// SQLite cannot open it and signals it is corrupt or not a DB.
if (error.code === 'SQLITE_CORRUPT' || error.code === 'SQLITE_NOTADB') {
return this.handleSQLiteCorrupt(path, error).then(resolve, fallbackToInMemoryDatabase);
}
// Otherwise give up and fallback to in-memory DB
return fallbackToInMemoryDatabase(error);
});
});
}
private handleSQLiteBusy(path: string): Promise<IOpenDatabaseResult> {
this.logger.error(`[storage ${this.name}] open(): Retrying after ${SQLiteStorageDatabase.BUSY_OPEN_TIMEOUT}ms due to SQLITE_BUSY`);
// Retry after some time if the DB is busy
return timeout(SQLiteStorageDatabase.BUSY_OPEN_TIMEOUT).then(() => this.doOpen(path));
}
private handleSQLiteCorrupt(path: string, error: any): Promise<IOpenDatabaseResult> {
this.logger.error(`[storage ${this.name}] open(): Unable to open DB due to ${error.code}`);
// Move corrupt DB to a different filename and try to load from backup
// If that fails, a new empty DB is being created automatically
return rename(path, this.toCorruptPath(path))
.then(() => renameIgnoreError(this.toBackupPath(path), path))
.then(() => this.doOpen(path));
}
private toCorruptPath(path: string): string {
const randomSuffix = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 4);
return `${path}.${randomSuffix}.corrupt`;
}
private doOpen(path: string): Promise<IOpenDatabaseResult> {
// TODO@Ben clean up performance markers
return new Promise((resolve, reject) => {
let measureRequireDuration = false;
if (!SQLiteStorageDatabase.measuredRequireDuration) {
SQLiteStorageDatabase.measuredRequireDuration = true;
measureRequireDuration = true;
mark('willRequireSQLite');
}
import('vscode-sqlite3').then(sqlite3 => {
if (measureRequireDuration) {
mark('didRequireSQLite');
}
const db: Database = new (this.logger.isTracing ? sqlite3.verbose().Database : sqlite3.Database)(path, error => {
if (error) {
return db ? db.close(() => reject(error)) : reject(error);
}
// The following exec() statement serves two purposes:
// - create the DB if it does not exist yet
// - validate that the DB is not corrupt (the open() call does not throw otherwise)
mark('willSetupSQLiteSchema');
this.exec(db, [
'PRAGMA user_version = 1;',
'CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB)'
].join('')).then(() => {
mark('didSetupSQLiteSchema');
return resolve({ path, db });
}, error => {
mark('didSetupSQLiteSchema');
return db.close(() => reject(error));
});
});
// Errors
db.on('error', error => this.logger.error(`[storage ${this.name}] Error (event): ${error}`));
// Tracing
if (this.logger.isTracing) {
db.on('trace', sql => this.logger.trace(`[storage ${this.name}] Trace (event): ${sql}`));
}
});
});
}
private exec(db: Database, sql: string): Promise<void> {
return new Promise((resolve, reject) => {
db.exec(sql, error => {
if (error) {
this.logger.error(`[storage ${this.name}] exec(): ${error}`);
return reject(error);
}
return resolve();
});
});
}
private get(db: Database, sql: string): Promise<object> {
return new Promise((resolve, reject) => {
db.get(sql, (error, row) => {
if (error) {
this.logger.error(`[storage ${this.name}] get(): ${error}`);
return reject(error);
}
return resolve(row);
});
});
}
private all(db: Database, sql: string): Promise<{ key: string, value: string }[]> {
return new Promise((resolve, reject) => {
db.all(sql, (error, rows) => {
if (error) {
this.logger.error(`[storage ${this.name}] all(): ${error}`);
return reject(error);
}
return resolve(rows);
});
});
}
private transaction(db: Database, transactions: () => void): Promise<void> {
return new Promise((resolve, reject) => {
db.serialize(() => {
db.run('BEGIN TRANSACTION');
transactions();
db.run('END TRANSACTION', error => {
if (error) {
this.logger.error(`[storage ${this.name}] transaction(): ${error}`);
return reject(error);
}
return resolve();
});
});
});
}
private prepare(db: Database, sql: string, runCallback: (stmt: Statement) => void): void {
const stmt = db.prepare(sql);
const statementErrorListener = error => {
this.logger.error(`[storage ${this.name}] prepare(): ${error} (${sql})`);
};
stmt.on('error', statementErrorListener);
runCallback(stmt);
stmt.finalize(error => {
if (error) {
statementErrorListener(error);
}
stmt.removeListener('error', statementErrorListener);
});
}
}
class SQLiteStorageDatabaseLogger {
private readonly logTrace: (msg: string) => void;
private readonly logError: (error: string | Error) => void;
constructor(options?: ISQLiteStorageDatabaseLoggingOptions) {
if (options && typeof options.logTrace === 'function') {
this.logTrace = options.logTrace;
}
if (options && typeof options.logError === 'function') {
this.logError = options.logError;
}
}
get isTracing(): boolean {
return !!this.logTrace;
}
trace(msg: string): void {
if (this.logTrace) {
this.logTrace(msg);
}
}
error(error: string | Error): void {
if (this.logError) {
this.logError(error);
}
}
}
export class InMemoryStorageDatabase implements IStorageDatabase {
readonly onDidChangeItemsExternal = Event.None;
private items = new Map<string, string>();
getItems(): Thenable<Map<string, string>> {
return Promise.resolve(this.items);
}
updateItems(request: IUpdateRequest): Thenable<void> {
if (request.insert) {
request.insert.forEach((value, key) => this.items.set(key, value));
}
if (request.delete) {
request.delete.forEach(key => this.items.delete(key));
}
return Promise.resolve();
}
close(): Thenable<void> {
return Promise.resolve();
}
checkIntegrity(full: boolean): Thenable<string> {
return Promise.resolve('ok');
}
}

View File

@@ -3,38 +3,34 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as fs from 'fs';
import { TPromise } from 'vs/base/common/winjs.base';
export interface ReadResult {
buffer: NodeBuffer;
buffer: Buffer | null;
bytesRead: number;
}
/**
* Reads totalBytes from the provided file.
*/
export function readExactlyByFile(file: string, totalBytes: number): TPromise<ReadResult> {
return new TPromise<ReadResult>((complete, error) => {
export function readExactlyByFile(file: string, totalBytes: number): Promise<ReadResult> {
return new Promise<ReadResult>((resolve, reject) => {
fs.open(file, 'r', null, (err, fd) => {
if (err) {
return error(err);
return reject(err);
}
function end(err: Error, resultBuffer: NodeBuffer, bytesRead: number): void {
function end(err: Error | null, resultBuffer: Buffer | null, bytesRead: number): void {
fs.close(fd, closeError => {
if (closeError) {
return error(closeError);
return reject(closeError);
}
if (err && (<any>err).code === 'EISDIR') {
return error(err); // we want to bubble this error up (file is actually a folder)
return reject(err); // we want to bubble this error up (file is actually a folder)
}
return complete({ buffer: resultBuffer, bytesRead });
return resolve({ buffer: resultBuffer, bytesRead });
});
}
@@ -75,24 +71,24 @@ export function readExactlyByFile(file: string, totalBytes: number): TPromise<Re
* @param maximumBytesToRead The maximum number of bytes to read before giving up.
* @param callback The finished callback.
*/
export function readToMatchingString(file: string, matchingString: string, chunkBytes: number, maximumBytesToRead: number): TPromise<string> {
return new TPromise<string>((complete, error) =>
export function readToMatchingString(file: string, matchingString: string, chunkBytes: number, maximumBytesToRead: number): Promise<string | null> {
return new Promise<string | null>((resolve, reject) =>
fs.open(file, 'r', null, (err, fd) => {
if (err) {
return error(err);
return reject(err);
}
function end(err: Error, result: string): void {
function end(err: Error | null, result: string | null): void {
fs.close(fd, closeError => {
if (closeError) {
return error(closeError);
return reject(closeError);
}
if (err && (<any>err).code === 'EISDIR') {
return error(err); // we want to bubble this error up (file is actually a folder)
return reject(err); // we want to bubble this error up (file is actually a folder)
}
return complete(result);
return resolve(result);
});
}

View File

@@ -1,189 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as path from 'path';
import { createWriteStream, WriteStream } from 'fs';
import { Readable } from 'stream';
import { nfcall, ninvoke, SimpleThrottler } from 'vs/base/common/async';
import { mkdirp, rimraf } from 'vs/base/node/pfs';
import { TPromise } from 'vs/base/common/winjs.base';
import { open as _openZip, Entry, ZipFile } from 'yauzl';
import { ILogService } from 'vs/platform/log/common/log';
export interface IExtractOptions {
overwrite?: boolean;
/**
* Source path within the ZIP archive. Only the files contained in this
* path will be extracted.
*/
sourcePath?: string;
}
interface IOptions {
sourcePathRegex: RegExp;
}
export type ExtractErrorType = 'CorruptZip' | 'Incomplete';
export class ExtractError extends Error {
readonly type: ExtractErrorType;
readonly cause: Error;
constructor(type: ExtractErrorType, cause: Error) {
let message = cause.message;
switch (type) {
case 'CorruptZip': message = `Corrupt ZIP: ${message}`; break;
}
super(message);
this.type = type;
this.cause = cause;
}
}
function modeFromEntry(entry: Entry) {
let attr = entry.externalFileAttributes >> 16 || 33188;
return [448 /* S_IRWXU */, 56 /* S_IRWXG */, 7 /* S_IRWXO */]
.map(mask => attr & mask)
.reduce((a, b) => a + b, attr & 61440 /* S_IFMT */);
}
function toExtractError(err: Error): ExtractError {
if (err instanceof ExtractError) {
return err;
}
let type: ExtractErrorType = void 0;
if (/end of central directory record signature not found/.test(err.message)) {
type = 'CorruptZip';
}
return new ExtractError(type, err);
}
function extractEntry(stream: Readable, fileName: string, mode: number, targetPath: string, options: IOptions): TPromise<void> {
const dirName = path.dirname(fileName);
const targetDirName = path.join(targetPath, dirName);
if (targetDirName.indexOf(targetPath) !== 0) {
return TPromise.wrapError(new Error(nls.localize('invalid file', "Error extracting {0}. Invalid file.", fileName)));
}
const targetFileName = path.join(targetPath, fileName);
let istream: WriteStream;
return mkdirp(targetDirName).then(() => new TPromise((c, e) => {
istream = createWriteStream(targetFileName, { mode });
istream.once('close', () => c(null));
istream.once('error', e);
stream.once('error', e);
stream.pipe(istream);
}, () => {
if (istream) {
istream.close();
}
}));
}
function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, logService: ILogService): TPromise<void> {
let isCanceled = false;
let last = TPromise.wrap<any>(null);
let extractedEntriesCount = 0;
return new TPromise((c, e) => {
const throttler = new SimpleThrottler();
const readNextEntry = () => {
extractedEntriesCount++;
zipfile.readEntry();
};
zipfile.once('error', e);
zipfile.once('close', () => last.then(() => {
if (isCanceled || zipfile.entryCount === extractedEntriesCount) {
c(null);
} else {
e(new ExtractError('Incomplete', new Error(nls.localize('incompleteExtract', "Incomplete. Found {0} of {1} entries", extractedEntriesCount, zipfile.entryCount))));
}
}, e));
zipfile.readEntry();
zipfile.on('entry', (entry: Entry) => {
if (isCanceled) {
return;
}
if (!options.sourcePathRegex.test(entry.fileName)) {
readNextEntry();
return;
}
const fileName = entry.fileName.replace(options.sourcePathRegex, '');
// directory file names end with '/'
if (/\/$/.test(fileName)) {
const targetFileName = path.join(targetPath, fileName);
last = mkdirp(targetFileName).then(() => readNextEntry());
return;
}
const stream = ninvoke(zipfile, zipfile.openReadStream, entry);
const mode = modeFromEntry(entry);
last = throttler.queue(() => stream.then(stream => extractEntry(stream, fileName, mode, targetPath, options).then(() => readNextEntry())));
});
}, () => {
logService.debug(targetPath, 'Cancelled.');
isCanceled = true;
last.cancel();
zipfile.close();
}).then(null, err => TPromise.wrapError(toExtractError(err)));
}
function openZip(zipFile: string, lazy: boolean = false): TPromise<ZipFile> {
return nfcall<ZipFile>(_openZip, zipFile, lazy ? { lazyEntries: true } : void 0)
.then(null, err => TPromise.wrapError(toExtractError(err)));
}
export function extract(zipPath: string, targetPath: string, options: IExtractOptions = {}, logService: ILogService): TPromise<void> {
const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : '');
let promise = openZip(zipPath, true);
if (options.overwrite) {
promise = promise.then(zipfile => rimraf(targetPath).then(() => zipfile));
}
return promise.then(zipfile => extractZip(zipfile, targetPath, { sourcePathRegex }, logService));
}
function read(zipPath: string, filePath: string): TPromise<Readable> {
return openZip(zipPath).then(zipfile => {
return new TPromise<Readable>((c, e) => {
zipfile.on('entry', (entry: Entry) => {
if (entry.fileName === filePath) {
ninvoke<Readable>(zipfile, zipfile.openReadStream, entry).done(stream => c(stream), err => e(err));
}
});
zipfile.once('close', () => e(new Error(nls.localize('notFound', "{0} not found inside zip.", filePath))));
});
});
}
export function buffer(zipPath: string, filePath: string): TPromise<Buffer> {
return read(zipPath, filePath).then(stream => {
return new TPromise<Buffer>((c, e) => {
const buffers: Buffer[] = [];
stream.once('error', e);
stream.on('data', b => buffers.push(b as Buffer));
stream.on('end', () => c(Buffer.concat(buffers)));
});
});
}