mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-08 17:24:01 -05:00
Merge from master
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
701
src/vs/base/node/storage.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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)));
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user