mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-25 01:25:36 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
221
src/vs/base/node/config.ts
Normal file
221
src/vs/base/node/config.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import * as json from 'vs/base/common/json';
|
||||
|
||||
export interface IConfigurationChangeEvent<T> {
|
||||
config: T;
|
||||
}
|
||||
|
||||
export interface IConfigWatcher<T> {
|
||||
path: string;
|
||||
hasParseErrors: boolean;
|
||||
|
||||
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;
|
||||
changeBufferDelay?: number;
|
||||
parse?: (content: string, errors: any[]) => T;
|
||||
initCallback?: (config: T) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple helper to watch a configured file for changes and process its contents as JSON object.
|
||||
* Supports:
|
||||
* - comments in JSON files and errors
|
||||
* - symlinks for the config file itself
|
||||
* - delayed processing of changes to accomodate for lots of changes
|
||||
* - configurable defaults
|
||||
*/
|
||||
export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
|
||||
private cache: T;
|
||||
private parseErrors: json.ParseError[];
|
||||
private disposed: boolean;
|
||||
private loaded: boolean;
|
||||
private timeoutHandle: NodeJS.Timer;
|
||||
private disposables: IDisposable[];
|
||||
private _onDidUpdateConfiguration: Emitter<IConfigurationChangeEvent<T>>;
|
||||
|
||||
constructor(private _path: string, private options: IConfigOptions<T> = { changeBufferDelay: 0, defaultConfig: Object.create(null), onError: error => console.error(error) }) {
|
||||
this.disposables = [];
|
||||
|
||||
this._onDidUpdateConfiguration = new Emitter<IConfigurationChangeEvent<T>>();
|
||||
this.disposables.push(this._onDidUpdateConfiguration);
|
||||
|
||||
this.registerWatcher();
|
||||
this.initAsync();
|
||||
}
|
||||
|
||||
public get path(): string {
|
||||
return this._path;
|
||||
}
|
||||
|
||||
public get hasParseErrors(): boolean {
|
||||
return this.parseErrors && this.parseErrors.length > 0;
|
||||
}
|
||||
|
||||
public get onDidUpdateConfiguration(): Event<IConfigurationChangeEvent<T>> {
|
||||
return this._onDidUpdateConfiguration.event;
|
||||
}
|
||||
|
||||
private initAsync(): void {
|
||||
this.loadAsync(config => {
|
||||
if (!this.loaded) {
|
||||
this.updateCache(config); // prevent race condition if config was loaded sync already
|
||||
}
|
||||
if (this.options.initCallback) {
|
||||
this.options.initCallback(this.getConfig());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateCache(value: T): void {
|
||||
this.cache = value;
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
private loadSync(): T {
|
||||
try {
|
||||
return this.parse(fs.readFileSync(this._path).toString());
|
||||
} catch (error) {
|
||||
return this.options.defaultConfig;
|
||||
}
|
||||
}
|
||||
|
||||
private loadAsync(callback: (config: T) => void): void {
|
||||
fs.readFile(this._path, (error, raw) => {
|
||||
if (error) {
|
||||
return callback(this.options.defaultConfig);
|
||||
}
|
||||
|
||||
return callback(this.parse(raw.toString()));
|
||||
});
|
||||
}
|
||||
|
||||
private parse(raw: string): T {
|
||||
let res: T;
|
||||
try {
|
||||
this.parseErrors = [];
|
||||
res = this.options.parse ? this.options.parse(raw, this.parseErrors) : json.parse(raw, this.parseErrors);
|
||||
} catch (error) {
|
||||
// Ignore parsing errors
|
||||
}
|
||||
|
||||
return res || this.options.defaultConfig;
|
||||
}
|
||||
|
||||
private registerWatcher(): void {
|
||||
|
||||
// Watch the parent of the path so that we detect ADD and DELETES
|
||||
const parentFolder = path.dirname(this._path);
|
||||
this.watch(parentFolder);
|
||||
|
||||
// Check if the path is a symlink and watch its target if so
|
||||
fs.lstat(this._path, (err, stat) => {
|
||||
if (err || stat.isDirectory()) {
|
||||
return; // path is not a valid file
|
||||
}
|
||||
|
||||
// We found a symlink
|
||||
if (stat.isSymbolicLink()) {
|
||||
fs.readlink(this._path, (err, realPath) => {
|
||||
if (err) {
|
||||
return; // path is not a valid symlink
|
||||
}
|
||||
|
||||
this.watch(realPath);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private watch(path: string): void {
|
||||
if (this.disposed) {
|
||||
return; // avoid watchers that will never get disposed by checking for being disposed
|
||||
}
|
||||
|
||||
try {
|
||||
const watcher = fs.watch(path);
|
||||
watcher.on('change', () => this.onConfigFileChange());
|
||||
watcher.on('error', (code, signal) => this.options.onError(`Error watching ${path} for configuration changes (${code}, ${signal})`));
|
||||
|
||||
this.disposables.push(toDisposable(() => {
|
||||
watcher.removeAllListeners();
|
||||
watcher.close();
|
||||
}));
|
||||
} catch (error) {
|
||||
fs.exists(path, exists => {
|
||||
if (exists) {
|
||||
this.options.onError(`Failed to watch ${path} for configuration changes (${error.toString()})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onConfigFileChange(): void {
|
||||
if (this.timeoutHandle) {
|
||||
global.clearTimeout(this.timeoutHandle);
|
||||
this.timeoutHandle = null;
|
||||
}
|
||||
|
||||
// we can get multiple change events for one change, so we buffer through a timeout
|
||||
this.timeoutHandle = global.setTimeout(() => this.reload(), this.options.changeBufferDelay);
|
||||
}
|
||||
|
||||
public reload(callback?: (config: T) => void): void {
|
||||
this.loadAsync(currentConfig => {
|
||||
if (!objects.equals(currentConfig, this.cache)) {
|
||||
this.updateCache(currentConfig);
|
||||
|
||||
this._onDidUpdateConfiguration.fire({ config: this.cache });
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
return callback(currentConfig);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getConfig(): T {
|
||||
this.ensureLoaded();
|
||||
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
public getValue<V>(key: string, fallback?: V): V {
|
||||
this.ensureLoaded();
|
||||
|
||||
if (!key) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const value = this.cache ? this.cache[key] : void 0;
|
||||
|
||||
return typeof value !== 'undefined' ? value : fallback;
|
||||
}
|
||||
|
||||
private ensureLoaded(): void {
|
||||
if (!this.loaded) {
|
||||
this.updateCache(this.loadSync());
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.disposed = true;
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
45
src/vs/base/node/crypto.ts
Normal file
45
src/vs/base/node/crypto.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 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) => {
|
||||
const input = fs.createReadStream(path);
|
||||
const hash = crypto.createHash('sha1');
|
||||
const hashStream = hash as any as stream.PassThrough;
|
||||
input.pipe(hashStream);
|
||||
|
||||
const done = once((err?: Error, result?: string) => {
|
||||
input.removeAllListeners();
|
||||
hashStream.removeAllListeners();
|
||||
|
||||
if (err) {
|
||||
e(err);
|
||||
} else {
|
||||
c(result);
|
||||
}
|
||||
});
|
||||
|
||||
input.once('error', done);
|
||||
input.once('end', done);
|
||||
hashStream.once('error', done);
|
||||
hashStream.once('data', data => done(null, data.toString('hex')));
|
||||
});
|
||||
|
||||
return promise.then(hash => {
|
||||
if (hash !== sha1hash) {
|
||||
return TPromise.wrapError<void>(new Error('Hash mismatch'));
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
});
|
||||
}
|
||||
64
src/vs/base/node/decoder.ts
Normal file
64
src/vs/base/node/decoder.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sd = require('string_decoder');
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
/**
|
||||
* Convenient way to iterate over output line by line. This helper accommodates for the fact that
|
||||
* a buffer might not end with new lines all the way.
|
||||
*
|
||||
* To use:
|
||||
* - call the write method
|
||||
* - forEach() over the result to get the lines
|
||||
*/
|
||||
export class LineDecoder {
|
||||
private stringDecoder: sd.NodeStringDecoder;
|
||||
private remaining: string;
|
||||
|
||||
constructor(encoding: string = 'utf8') {
|
||||
this.stringDecoder = new sd.StringDecoder(encoding);
|
||||
this.remaining = null;
|
||||
}
|
||||
|
||||
public write(buffer: NodeBuffer): string[] {
|
||||
let result: string[] = [];
|
||||
let value = this.remaining
|
||||
? this.remaining + this.stringDecoder.write(buffer)
|
||||
: this.stringDecoder.write(buffer);
|
||||
|
||||
if (value.length < 1) {
|
||||
return result;
|
||||
}
|
||||
let start = 0;
|
||||
let ch: number;
|
||||
let idx = start;
|
||||
while (idx < value.length) {
|
||||
ch = value.charCodeAt(idx);
|
||||
if (ch === CharCode.CarriageReturn || ch === CharCode.LineFeed) {
|
||||
result.push(value.substring(start, idx));
|
||||
idx++;
|
||||
if (idx < value.length) {
|
||||
let lastChar = ch;
|
||||
ch = value.charCodeAt(idx);
|
||||
if ((lastChar === CharCode.CarriageReturn && ch === CharCode.LineFeed) || (lastChar === CharCode.LineFeed && ch === CharCode.CarriageReturn)) {
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
start = idx;
|
||||
} else {
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
this.remaining = start < value.length ? value.substr(start) : null;
|
||||
return result;
|
||||
}
|
||||
|
||||
public end(): string {
|
||||
return this.remaining;
|
||||
}
|
||||
}
|
||||
171
src/vs/base/node/encoding.ts
Normal file
171
src/vs/base/node/encoding.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 stream = require('vs/base/node/stream');
|
||||
import iconv = require('iconv-lite');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
export const UTF8 = 'utf8';
|
||||
export const UTF8_with_bom = 'utf8bom';
|
||||
export const UTF16be = 'utf16be';
|
||||
export const UTF16le = 'utf16le';
|
||||
|
||||
export function bomLength(encoding: string): number {
|
||||
switch (encoding) {
|
||||
case UTF8:
|
||||
return 3;
|
||||
case UTF16be:
|
||||
case UTF16le:
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function decode(buffer: NodeBuffer, encoding: string, options?: any): string {
|
||||
return iconv.decode(buffer, toNodeEncoding(encoding), options);
|
||||
}
|
||||
|
||||
export function encode(content: string, encoding: string, options?: any): NodeBuffer {
|
||||
return iconv.encode(content, toNodeEncoding(encoding), options);
|
||||
}
|
||||
|
||||
export function encodingExists(encoding: string): boolean {
|
||||
return iconv.encodingExists(toNodeEncoding(encoding));
|
||||
}
|
||||
|
||||
export function decodeStream(encoding: string): NodeJS.ReadWriteStream {
|
||||
return iconv.decodeStream(toNodeEncoding(encoding));
|
||||
}
|
||||
|
||||
export function encodeStream(encoding: string): NodeJS.ReadWriteStream {
|
||||
return iconv.encodeStream(toNodeEncoding(encoding));
|
||||
}
|
||||
|
||||
function toNodeEncoding(enc: string): string {
|
||||
if (enc === UTF8_with_bom) {
|
||||
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 {
|
||||
if (!buffer || bytesRead < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const b0 = buffer.readUInt8(0);
|
||||
const b1 = buffer.readUInt8(1);
|
||||
|
||||
// UTF-16 BE
|
||||
if (b0 === 0xFE && b1 === 0xFF) {
|
||||
return UTF16be;
|
||||
}
|
||||
|
||||
// UTF-16 LE
|
||||
if (b0 === 0xFF && b1 === 0xFE) {
|
||||
return UTF16le;
|
||||
}
|
||||
|
||||
if (bytesRead < 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const b2 = buffer.readUInt8(2);
|
||||
|
||||
// UTF-8
|
||||
if (b0 === 0xEF && b1 === 0xBB && b2 === 0xBF) {
|
||||
return UTF8;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
return stream.readExactlyByFile(file, 3).then(({ buffer, bytesRead }) => detectEncodingByBOMFromBuffer(buffer, bytesRead));
|
||||
}
|
||||
|
||||
const MINIMUM_THRESHOLD = 0.2;
|
||||
|
||||
const IGNORE_ENCODINGS = ['ascii', 'utf-8', 'utf-16', 'utf-32'];
|
||||
const MAPPED_ENCODINGS = {
|
||||
'ibm866': 'cp866'
|
||||
};
|
||||
|
||||
/**
|
||||
* Guesses the encoding from buffer.
|
||||
*/
|
||||
export async function guessEncodingByBuffer(buffer: NodeBuffer): TPromise<string> {
|
||||
|
||||
const jschardet = await import('jschardet');
|
||||
|
||||
jschardet.Constants.MINIMUM_THRESHOLD = MINIMUM_THRESHOLD;
|
||||
|
||||
const guessed = jschardet.detect(buffer);
|
||||
if (!guessed || !guessed.encoding) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const enc = guessed.encoding.toLowerCase();
|
||||
|
||||
// Ignore encodings that cannot guess correctly
|
||||
// (http://chardet.readthedocs.io/en/latest/supported-encodings.html)
|
||||
if (0 <= IGNORE_ENCODINGS.indexOf(enc)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return toIconvLiteEncoding(guessed.encoding);
|
||||
}
|
||||
|
||||
function toIconvLiteEncoding(encodingName: string): string {
|
||||
const normalizedEncodingName = encodingName.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
|
||||
const mapped = MAPPED_ENCODINGS[normalizedEncodingName];
|
||||
|
||||
return mapped || normalizedEncodingName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The encodings that are allowed in a settings file don't match the canonical encoding labels specified by WHATWG.
|
||||
* See https://encoding.spec.whatwg.org/#names-and-labels
|
||||
* Iconv-lite strips all non-alphanumeric characters, but ripgrep doesn't. For backcompat, allow these labels.
|
||||
*/
|
||||
export function toCanonicalName(enc: string): string {
|
||||
switch (enc) {
|
||||
case 'shiftjis':
|
||||
return 'shift-jis';
|
||||
case 'utf16le':
|
||||
return 'utf-16le';
|
||||
case 'utf16be':
|
||||
return 'utf-16be';
|
||||
case 'big5hkscs':
|
||||
return 'big5-hkscs';
|
||||
case 'eucjp':
|
||||
return 'euc-jp';
|
||||
case 'euckr':
|
||||
return 'euc-kr';
|
||||
case 'koi8r':
|
||||
return 'koi8-r';
|
||||
case 'koi8u':
|
||||
return 'koi8-u';
|
||||
case 'macroman':
|
||||
return 'x-mac-roman';
|
||||
case 'utf8bom':
|
||||
return 'utf8';
|
||||
default:
|
||||
const m = enc.match(/windows(\d+)/);
|
||||
if (m) {
|
||||
return 'windows-' + m[1];
|
||||
}
|
||||
|
||||
return enc;
|
||||
}
|
||||
}
|
||||
18
src/vs/base/node/event.ts
Normal file
18
src/vs/base/node/event.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 Event, { Emitter } from 'vs/base/common/event';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export function fromEventEmitter<T>(emitter: EventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {
|
||||
const fn = (...args) => result.fire(map(...args));
|
||||
const onFirstListenerAdd = () => emitter.on(eventName, fn);
|
||||
const onLastListenerRemove = () => emitter.removeListener(eventName, fn);
|
||||
const result = new Emitter<T>({ onFirstListenerAdd, onLastListenerRemove });
|
||||
|
||||
return result.event;
|
||||
};
|
||||
452
src/vs/base/node/extfs.ts
Normal file
452
src/vs/base/node/extfs.ts
Normal file
@@ -0,0 +1,452 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 uuid from 'vs/base/common/uuid';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as flow from 'vs/base/node/flow';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as paths from 'path';
|
||||
|
||||
const loop = flow.loop;
|
||||
|
||||
export function readdirSync(path: string): string[] {
|
||||
// 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.readdirSync(path).map(c => strings.normalizeNFC(c));
|
||||
}
|
||||
|
||||
return fs.readdirSync(path);
|
||||
}
|
||||
|
||||
export function readdir(path: string, callback: (error: Error, 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(null, children.map(c => strings.normalizeNFC(c)));
|
||||
});
|
||||
}
|
||||
|
||||
return fs.readdir(path, callback);
|
||||
}
|
||||
|
||||
export function mkdirp(path: string, mode: number, callback: (error: Error) => void): void {
|
||||
fs.exists(path, (exists) => {
|
||||
if (exists) {
|
||||
return isDirectory(path, (err: Error, itIs?: boolean) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!itIs) {
|
||||
return callback(new Error('"' + path + '" is not a directory.'));
|
||||
}
|
||||
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
mkdirp(paths.dirname(path), mode, (err: Error) => {
|
||||
if (err) { callback(err); return; }
|
||||
|
||||
if (mode) {
|
||||
fs.mkdir(path, mode, (error) => {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
fs.chmod(path, mode, callback); // we need to explicitly chmod because of https://github.com/nodejs/node/issues/1104
|
||||
});
|
||||
} else {
|
||||
fs.mkdir(path, null, callback);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isDirectory(path: string, callback: (error: Error, isDirectory?: boolean) => void): void {
|
||||
fs.stat(path, (error, stat) => {
|
||||
if (error) { return callback(error); }
|
||||
|
||||
callback(null, stat.isDirectory());
|
||||
});
|
||||
}
|
||||
|
||||
export function copy(source: string, target: string, callback: (error: Error) => void, copiedSources?: { [path: string]: boolean }): void {
|
||||
if (!copiedSources) {
|
||||
copiedSources = Object.create(null);
|
||||
}
|
||||
|
||||
fs.stat(source, (error, stat) => {
|
||||
if (error) { return callback(error); }
|
||||
if (!stat.isDirectory()) { return pipeFs(source, target, stat.mode & 511, callback); }
|
||||
|
||||
if (copiedSources[source]) {
|
||||
return callback(null); // escape when there are cycles (can happen with symlinks)
|
||||
} else {
|
||||
copiedSources[source] = true; // remember as copied
|
||||
}
|
||||
|
||||
mkdirp(target, stat.mode & 511, (err) => {
|
||||
readdir(source, (err, files) => {
|
||||
loop(files, (file: string, clb: (error: Error, _result) => void) => {
|
||||
copy(paths.join(source, file), paths.join(target, file), (error: Error) => clb(error, undefined), copiedSources);
|
||||
}, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function pipeFs(source: string, target: string, mode: number, callback: (error: Error) => void): void {
|
||||
let callbackHandled = false;
|
||||
|
||||
let readStream = fs.createReadStream(source);
|
||||
let writeStream = fs.createWriteStream(target, { mode: mode });
|
||||
|
||||
let onError = (error: Error) => {
|
||||
if (!callbackHandled) {
|
||||
callbackHandled = true;
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
|
||||
readStream.on('error', onError);
|
||||
writeStream.on('error', onError);
|
||||
|
||||
readStream.on('end', () => {
|
||||
(<any>writeStream).end(() => { // In this case the write stream is known to have an end signature with callback
|
||||
if (!callbackHandled) {
|
||||
callbackHandled = true;
|
||||
|
||||
fs.chmod(target, mode, callback); // we need to explicitly chmod because of https://github.com/nodejs/node/issues/1104
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// In node 0.8 there is no easy way to find out when the pipe operation has finished. As such, we use the end property = false
|
||||
// so that we are in charge of calling end() on the write stream and we will be notified when the write stream is really done.
|
||||
// We can do this because file streams have an end() method that allows to pass in a callback.
|
||||
// In node 0.10 there is an event 'finish' emitted from the write stream that can be used. See
|
||||
// https://groups.google.com/forum/?fromgroups=#!topic/nodejs/YWQ1sRoXOdI
|
||||
readStream.pipe(writeStream, { end: false });
|
||||
}
|
||||
|
||||
// Deletes the given path by first moving it out of the workspace. This has two benefits. For one, the operation can return fast because
|
||||
// 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 {
|
||||
fs.exists(path, (exists) => {
|
||||
if (!exists) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
fs.stat(path, (err, stat) => {
|
||||
if (err || !stat) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Special windows workaround: A file or folder that ends with a "." cannot be moved to another place
|
||||
// because it is not a valid file name. In this case, we really have to do the deletion without prior move.
|
||||
if (path[path.length - 1] === '.' || strings.endsWith(path, './') || strings.endsWith(path, '.\\')) {
|
||||
return rmRecursive(path, callback);
|
||||
}
|
||||
|
||||
let pathInTemp = paths.join(tmpFolder, uuid.generateUuid());
|
||||
fs.rename(path, pathInTemp, (error: Error) => {
|
||||
if (error) {
|
||||
return rmRecursive(path, callback); // if rename fails, delete without tmp dir
|
||||
}
|
||||
|
||||
// Return early since the move succeeded
|
||||
callback(null);
|
||||
|
||||
// do the heavy deletion outside the callers callback
|
||||
rmRecursive(pathInTemp, (error) => {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (done) {
|
||||
done(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function rmRecursive(path: string, callback: (error: Error) => void): void {
|
||||
if (path === '\\' || path === '/') {
|
||||
return callback(new Error('Will not delete root!'));
|
||||
}
|
||||
|
||||
fs.exists(path, (exists) => {
|
||||
if (!exists) {
|
||||
callback(null);
|
||||
} else {
|
||||
fs.lstat(path, (err, stat) => {
|
||||
if (err || !stat) {
|
||||
callback(err);
|
||||
} else if (!stat.isDirectory() || stat.isSymbolicLink() /* !!! never recurse into links when deleting !!! */) {
|
||||
let mode = stat.mode;
|
||||
if (!(mode & 128)) { // 128 === 0200
|
||||
fs.chmod(path, mode | 128, (err: Error) => { // 128 === 0200
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
fs.unlink(path, callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fs.unlink(path, callback);
|
||||
}
|
||||
} else {
|
||||
readdir(path, (err, children) => {
|
||||
if (err || !children) {
|
||||
callback(err);
|
||||
} else if (children.length === 0) {
|
||||
fs.rmdir(path, callback);
|
||||
} else {
|
||||
let firstError: Error = null;
|
||||
let childrenLeft = children.length;
|
||||
children.forEach((child) => {
|
||||
rmRecursive(paths.join(path, child), (err: Error) => {
|
||||
childrenLeft--;
|
||||
if (err) {
|
||||
firstError = firstError || err;
|
||||
}
|
||||
|
||||
if (childrenLeft === 0) {
|
||||
if (firstError) {
|
||||
callback(firstError);
|
||||
} else {
|
||||
fs.rmdir(path, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function delSync(path: string): void {
|
||||
try {
|
||||
const stat = fs.lstatSync(path);
|
||||
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
||||
readdirSync(path).forEach(child => delSync(paths.join(path, child)));
|
||||
fs.rmdirSync(path);
|
||||
} else {
|
||||
fs.unlinkSync(path);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return; // not found
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export function mv(source: string, target: string, callback: (error: Error) => void): void {
|
||||
if (source === target) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
function updateMtime(err: Error): void {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
fs.stat(target, (error, stat) => {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
fs.open(target, 'a', null, (err: Error, fd: number) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
fs.futimes(fd, stat.atime, new Date(), (err: Error) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
fs.close(fd, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Try native rename()
|
||||
fs.rename(source, target, (err: Error) => {
|
||||
if (!err) {
|
||||
return updateMtime(null);
|
||||
}
|
||||
|
||||
// In two cases we fallback to classic copy and delete:
|
||||
//
|
||||
// 1.) The EXDEV error indicates that source and target are on different devices
|
||||
// In this case, fallback to using a copy() operation as there is no way to
|
||||
// rename() between different devices.
|
||||
//
|
||||
// 2.) The user tries to rename a file/folder that ends with a dot. This is not
|
||||
// really possible to move then, at least on UNC devices.
|
||||
if (err && source.toLowerCase() !== target.toLowerCase() && ((<any>err).code === 'EXDEV') || strings.endsWith(source, '.')) {
|
||||
return copy(source, target, (err: Error) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
rmRecursive(source, updateMtime);
|
||||
});
|
||||
}
|
||||
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
// Calls fs.writeFile() followed by a fs.sync() call to flush the changes to disk
|
||||
// We do this in cases where we want to make sure the data is really on disk and
|
||||
// not in some cache.
|
||||
//
|
||||
// See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194
|
||||
let canFlush = true;
|
||||
export function writeFileAndFlush(path: string, data: string | NodeBuffer, options: { mode?: number; flag?: string; }, callback: (error: Error) => void): void {
|
||||
if (!canFlush) {
|
||||
return fs.writeFile(path, data, options, callback);
|
||||
}
|
||||
|
||||
if (!options) {
|
||||
options = { mode: 0o666, flag: 'w' };
|
||||
}
|
||||
|
||||
// Open the file with same flags and mode as fs.writeFile()
|
||||
fs.open(path, options.flag, options.mode, (openError, fd) => {
|
||||
if (openError) {
|
||||
return callback(openError);
|
||||
}
|
||||
|
||||
// It is valid to pass a fd handle to fs.writeFile() and this will keep the handle open!
|
||||
fs.writeFile(fd, data, (writeError) => {
|
||||
if (writeError) {
|
||||
return fs.close(fd, () => callback(writeError)); // still need to close the handle on error!
|
||||
}
|
||||
|
||||
// Flush contents (not metadata) of the file to disk
|
||||
fs.fdatasync(fd, (syncError) => {
|
||||
|
||||
// In some exotic setups it is well possible that node fails to sync
|
||||
// In that case we disable flushing and warn to the console
|
||||
if (syncError) {
|
||||
console.warn('[node.js fs] fdatasync is now disabled for this session because it failed: ', syncError);
|
||||
canFlush = false;
|
||||
}
|
||||
|
||||
return fs.close(fd, (closeError) => callback(closeError));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from: https://github.com/Microsoft/vscode-node-debug/blob/master/src/node/pathUtilities.ts#L83
|
||||
*
|
||||
* Given an absolute, normalized, and existing file path 'realcase' returns the exact path that the file has on disk.
|
||||
* On a case insensitive file system, the returned path might differ from the original path by character casing.
|
||||
* On a case sensitive file system, the returned path will always be identical to the original path.
|
||||
* 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 {
|
||||
const dir = paths.dirname(path);
|
||||
if (path === dir) { // end recursion
|
||||
return path;
|
||||
}
|
||||
|
||||
const name = paths.basename(path).toLowerCase();
|
||||
try {
|
||||
const entries = readdirSync(dir);
|
||||
const found = entries.filter(e => e.toLowerCase() === name); // use a case insensitive search
|
||||
if (found.length === 1) {
|
||||
// on a case sensitive filesystem we cannot determine here, whether the file exists or not, hence we need the 'file exists' precondition
|
||||
const prefix = realcaseSync(dir); // recurse
|
||||
if (prefix) {
|
||||
return paths.join(prefix, found[0]);
|
||||
}
|
||||
} else if (found.length > 1) {
|
||||
// must be a case sensitive $filesystem
|
||||
const ix = found.indexOf(name);
|
||||
if (ix >= 0) { // case sensitive
|
||||
const prefix = realcaseSync(dir); // recurse
|
||||
if (prefix) {
|
||||
return paths.join(prefix, found[ix]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// silently ignore error
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function realpathSync(path: string): string {
|
||||
try {
|
||||
return fs.realpathSync(path);
|
||||
} catch (error) {
|
||||
|
||||
// We hit an error calling fs.realpathSync(). Since fs.realpathSync() is doing some path normalization
|
||||
// we now do a similar normalization and then try again if we can access the path with read
|
||||
// permissions at least. If that succeeds, we return that path.
|
||||
// fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is
|
||||
// to not resolve links but to simply see if the path is read accessible or not.
|
||||
const normalizedPath = normalizePath(path);
|
||||
fs.accessSync(normalizedPath, fs.constants.R_OK); // throws in case of an error
|
||||
|
||||
return normalizedPath;
|
||||
}
|
||||
}
|
||||
|
||||
export function realpath(path: string, callback: (error: Error, realpath: string) => void): void {
|
||||
return fs.realpath(path, (error, realpath) => {
|
||||
if (!error) {
|
||||
return callback(null, realpath);
|
||||
}
|
||||
|
||||
// We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization
|
||||
// we now do a similar normalization and then try again if we can access the path with read
|
||||
// permissions at least. If that succeeds, we return that path.
|
||||
// fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is
|
||||
// to not resolve links but to simply see if the path is read accessible or not.
|
||||
const normalizedPath = normalizePath(path);
|
||||
|
||||
return fs.access(normalizedPath, fs.constants.R_OK, error => {
|
||||
return callback(error, normalizedPath);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function normalizePath(path: string): string {
|
||||
return strings.rtrim(paths.normalize(path), paths.sep);
|
||||
}
|
||||
189
src/vs/base/node/flow.ts
Normal file
189
src/vs/base/node/flow.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 assert = require('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 {
|
||||
let results = new Array(list.length);
|
||||
let errors = new Array<Error>(list.length);
|
||||
let didErrorOccur = false;
|
||||
let doneCount = 0;
|
||||
|
||||
if (list.length === 0) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
list.forEach((item, index) => {
|
||||
fn(item, (error, result) => {
|
||||
if (error) {
|
||||
didErrorOccur = true;
|
||||
results[index] = null;
|
||||
errors[index] = error;
|
||||
} else {
|
||||
results[index] = result;
|
||||
errors[index] = null;
|
||||
}
|
||||
|
||||
if (++doneCount === list.length) {
|
||||
return callback(didErrorOccur ? errors : null, results);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given function (fn) over the given array of items (param) in sequential order and returns the first occurred error or the result as
|
||||
* 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 {
|
||||
|
||||
// Assert
|
||||
assert.ok(param, 'Missing first parameter');
|
||||
assert.ok(typeof (fn) === 'function', 'Second parameter must be a function that is called for each element');
|
||||
assert.ok(typeof (callback) === 'function', 'Third parameter must be a function that is called on error and success');
|
||||
|
||||
// Param is function, execute to retrieve array
|
||||
if (typeof (param) === 'function') {
|
||||
try {
|
||||
param((error: Error, result: E[]) => {
|
||||
if (error) {
|
||||
callback(error, null);
|
||||
} else {
|
||||
loop(result, fn, callback);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
callback(error, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Expect the param to be an array and loop over it
|
||||
else {
|
||||
let results: E[] = [];
|
||||
|
||||
let looper: (i: number) => void = function (i: number): void {
|
||||
|
||||
// Still work to do
|
||||
if (i < param.length) {
|
||||
|
||||
// Execute function on array element
|
||||
try {
|
||||
fn(param[i], (error: any, result: E) => {
|
||||
|
||||
// A method might only send a boolean value as return value (e.g. fs.exists), support this case gracefully
|
||||
if (error === true || error === false) {
|
||||
result = error;
|
||||
error = null;
|
||||
}
|
||||
|
||||
// Quit looping on error
|
||||
if (error) {
|
||||
callback(error, null);
|
||||
}
|
||||
|
||||
// Otherwise push result on stack and continue looping
|
||||
else {
|
||||
if (result) { //Could be that provided function is not returning a result
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
process.nextTick(() => {
|
||||
looper(i + 1);
|
||||
});
|
||||
}
|
||||
}, i, param.length);
|
||||
} catch (error) {
|
||||
callback(error, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Done looping, pass back results too callback function
|
||||
else {
|
||||
callback(null, results);
|
||||
}
|
||||
};
|
||||
|
||||
// Start looping with first element in array
|
||||
looper(0);
|
||||
}
|
||||
}
|
||||
|
||||
function Sequence(sequences: { (...param: any[]): void; }[]): void {
|
||||
|
||||
// Assert
|
||||
assert.ok(sequences.length > 1, 'Need at least one error handler and one function to process sequence');
|
||||
sequences.forEach((sequence) => {
|
||||
assert.ok(typeof (sequence) === 'function');
|
||||
});
|
||||
|
||||
// Execute in Loop
|
||||
let errorHandler = sequences.splice(0, 1)[0]; //Remove error handler
|
||||
let sequenceResult: any = null;
|
||||
|
||||
loop(sequences, (sequence, clb) => {
|
||||
let sequenceFunction = function (error: any, result: any): void {
|
||||
|
||||
// A method might only send a boolean value as return value (e.g. fs.exists), support this case gracefully
|
||||
if (error === true || error === false) {
|
||||
result = error;
|
||||
error = null;
|
||||
}
|
||||
|
||||
// Handle Error and Result
|
||||
if (error) {
|
||||
clb(error, null);
|
||||
} else {
|
||||
sequenceResult = result; //Remember result of sequence
|
||||
clb(null, null); //Don't pass on result to Looper as we are not aggregating it
|
||||
}
|
||||
};
|
||||
|
||||
// We call the sequence function setting "this" to be the callback we define here
|
||||
// and we pass in the "sequenceResult" as first argument. Doing all this avoids having
|
||||
// to pass in a callback to the sequence because the callback is already "this".
|
||||
try {
|
||||
sequence.call(sequenceFunction, sequenceResult);
|
||||
} catch (error) {
|
||||
clb(error, null);
|
||||
}
|
||||
}, (error, result) => {
|
||||
if (error) {
|
||||
errorHandler(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a variable list of functions to execute in sequence. The first function must be the error handler and the
|
||||
* following functions can do arbitrary work. "this" must be used as callback value for async functions to continue
|
||||
* through the sequence:
|
||||
* sequence(
|
||||
* function errorHandler(error) {
|
||||
* clb(error, null);
|
||||
* },
|
||||
*
|
||||
* function doSomethingAsync() {
|
||||
* fs.doAsync(path, this);
|
||||
* },
|
||||
*
|
||||
* function done(result) {
|
||||
* clb(null, result);
|
||||
* }
|
||||
* );
|
||||
*/
|
||||
export function sequence(errorHandler: (error: Error) => void, ...sequences: Function[]): void;
|
||||
export function sequence(sequences: Function[]): void;
|
||||
export function sequence(sequences: any): void {
|
||||
Sequence((Array.isArray(sequences)) ? sequences : Array.prototype.slice.call(arguments));
|
||||
}
|
||||
93
src/vs/base/node/id.ts
Normal file
93
src/vs/base/node/id.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as getmac from 'getmac';
|
||||
import * as crypto from 'crypto';
|
||||
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';
|
||||
import { TrieMap } from 'vs/base/common/map';
|
||||
|
||||
// http://www.techrepublic.com/blog/data-center/mac-address-scorecard-for-common-virtual-machine-platforms/
|
||||
// VMware ESX 3, Server, Workstation, Player 00-50-56, 00-0C-29, 00-05-69
|
||||
// Microsoft Hyper-V, Virtual Server, Virtual PC 00-03-FF
|
||||
// Parallells Desktop, Workstation, Server, Virtuozzo 00-1C-42
|
||||
// Virtual Iron 4 00-0F-4B
|
||||
// Red Hat Xen 00-16-3E
|
||||
// Oracle VM 00-16-3E
|
||||
// XenSource 00-16-3E
|
||||
// Novell Xen 00-16-3E
|
||||
// Sun xVM VirtualBox 08-00-27
|
||||
export const virtualMachineHint: { value(): number } = new class {
|
||||
|
||||
private _virtualMachineOUIs: TrieMap<boolean>;
|
||||
private _value: number;
|
||||
|
||||
private _isVirtualMachineMacAdress(mac: string): boolean {
|
||||
if (!this._virtualMachineOUIs) {
|
||||
this._virtualMachineOUIs = new TrieMap<boolean>(s => s.split(/[-:]/));
|
||||
// this._virtualMachineOUIs.insert('00-00-00', true);
|
||||
this._virtualMachineOUIs.insert('00-50-56', true);
|
||||
this._virtualMachineOUIs.insert('00-0C-29', true);
|
||||
this._virtualMachineOUIs.insert('00-05-69', true);
|
||||
this._virtualMachineOUIs.insert('00-03-FF', true);
|
||||
this._virtualMachineOUIs.insert('00-1C-42', true);
|
||||
this._virtualMachineOUIs.insert('00-16-3E', true);
|
||||
this._virtualMachineOUIs.insert('08-00-27', true);
|
||||
|
||||
}
|
||||
return this._virtualMachineOUIs.findSubstr(mac);
|
||||
}
|
||||
|
||||
value(): number {
|
||||
if (this._value === undefined) {
|
||||
let vmOui = 0;
|
||||
let interfaceCount = 0;
|
||||
|
||||
const interfaces = networkInterfaces();
|
||||
for (let name in interfaces) {
|
||||
if (Object.prototype.hasOwnProperty.call(interfaces, name)) {
|
||||
for (const { mac, internal } of interfaces[name]) {
|
||||
if (!internal) {
|
||||
interfaceCount += 1;
|
||||
if (this._isVirtualMachineMacAdress(mac.toUpperCase())) {
|
||||
vmOui += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this._value = interfaceCount > 0
|
||||
? vmOui / interfaceCount
|
||||
: 0;
|
||||
}
|
||||
|
||||
return this._value;
|
||||
}
|
||||
};
|
||||
|
||||
let machineId: TPromise<string>;
|
||||
export function getMachineId(): TPromise<string> {
|
||||
return machineId || (machineId = getMacMachineId()
|
||||
.then(id => id || uuid.generateUuid())); // fallback, generate a UUID
|
||||
}
|
||||
|
||||
function getMacMachineId(): TPromise<string> {
|
||||
return new TPromise<string>(resolve => {
|
||||
try {
|
||||
getmac.getMac((error, macAddress) => {
|
||||
if (!error) {
|
||||
resolve(crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex'));
|
||||
} else {
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
errors.onUnexpectedError(err);
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
173
src/vs/base/node/mime.ts
Normal file
173
src/vs/base/node/mime.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 streams = require('stream');
|
||||
|
||||
import mime = require('vs/base/common/mime');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
import stream = require('vs/base/node/stream');
|
||||
import encoding = require('vs/base/node/encoding');
|
||||
|
||||
/**
|
||||
* Lots of binary file types exists where the type can be determined by matching the first few bytes against some "magic patterns".
|
||||
* E.g. PDF files always start with %PDF- and the rest of the file contains mostly text, but sometimes binary data (for fonts and images).
|
||||
* In order to detect these types correctly (and independently from the file's extension), the content base mime type detection must be performed
|
||||
* on any file, not only on text files.
|
||||
*
|
||||
* Here is the original mime type detection in pseudocode:
|
||||
*
|
||||
* let mimes = [];
|
||||
*
|
||||
* read file extension
|
||||
*
|
||||
* if (file extension matches) {
|
||||
* if (file extension is bogus) {
|
||||
* // ignore.
|
||||
* // this covers *.manifest files which can contain arbitrary content, so the extension is of no value.
|
||||
* // a consequence of this is that the content based mime type becomes the most specific type in the array
|
||||
* } else {
|
||||
* mimes.push(associated mime type) // first element: most specific
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* read file contents
|
||||
*
|
||||
* if (content based match found) { // this is independent from text or binary
|
||||
* mimes.push(associated mime type)
|
||||
* if (a second mime exists for the match) { // should be rare; text/plain should never be included here
|
||||
* // e.g. for svg: ['image/svg+xml', 'application/xml']
|
||||
* mimes.push(second mime)
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* if (content == text)
|
||||
* mimes.push('text/plain') // last element: least specific
|
||||
* else
|
||||
* mimes.push('application/octet-stream') // last element: least specific
|
||||
*/
|
||||
|
||||
const ZERO_BYTE_DETECTION_BUFFER_MAX_LEN = 512; // number of bytes to look at to decide about a file being binary or not
|
||||
|
||||
const NO_GUESS_BUFFER_MAX_LEN = 512; // when not auto guessing the encoding, small number of bytes are enough
|
||||
const AUTO_GUESS_BUFFER_MAX_LEN = 512 * 8; // with auto guessing we want a lot more content to be read for guessing
|
||||
|
||||
function maxBufferLen(arg1?: DetectMimesOption | boolean): number {
|
||||
let autoGuessEncoding: boolean;
|
||||
if (typeof arg1 === 'boolean') {
|
||||
autoGuessEncoding = arg1;
|
||||
} else {
|
||||
autoGuessEncoding = arg1 && arg1.autoGuessEncoding;
|
||||
}
|
||||
|
||||
return autoGuessEncoding ? AUTO_GUESS_BUFFER_MAX_LEN : NO_GUESS_BUFFER_MAX_LEN;
|
||||
}
|
||||
|
||||
export interface IMimeAndEncoding {
|
||||
encoding: string;
|
||||
mimes: string[];
|
||||
}
|
||||
|
||||
export interface DetectMimesOption {
|
||||
autoGuessEncoding?: boolean;
|
||||
}
|
||||
|
||||
function doDetectMimesFromStream(instream: streams.Readable, option?: DetectMimesOption): TPromise<IMimeAndEncoding> {
|
||||
return stream.readExactlyByStream(instream, maxBufferLen(option)).then((readResult: stream.ReadResult) => {
|
||||
return detectMimeAndEncodingFromBuffer(readResult, option && option.autoGuessEncoding);
|
||||
});
|
||||
}
|
||||
|
||||
function doDetectMimesFromFile(absolutePath: string, option?: DetectMimesOption): TPromise<IMimeAndEncoding> {
|
||||
return stream.readExactlyByFile(absolutePath, maxBufferLen(option)).then((readResult: stream.ReadResult) => {
|
||||
return detectMimeAndEncodingFromBuffer(readResult, option && option.autoGuessEncoding);
|
||||
});
|
||||
}
|
||||
|
||||
export function detectMimeAndEncodingFromBuffer(readResult: stream.ReadResult, autoGuessEncoding?: false): IMimeAndEncoding;
|
||||
export function detectMimeAndEncodingFromBuffer(readResult: stream.ReadResult, autoGuessEncoding?: boolean): TPromise<IMimeAndEncoding>;
|
||||
export function detectMimeAndEncodingFromBuffer({ buffer, bytesRead }: stream.ReadResult, autoGuessEncoding?: boolean): TPromise<IMimeAndEncoding> | IMimeAndEncoding {
|
||||
let enc = encoding.detectEncodingByBOMFromBuffer(buffer, bytesRead);
|
||||
|
||||
// Detect 0 bytes to see if file is binary (ignore for UTF 16 though)
|
||||
let isText = true;
|
||||
if (enc !== encoding.UTF16be && enc !== encoding.UTF16le) {
|
||||
for (let i = 0; i < bytesRead && i < ZERO_BYTE_DETECTION_BUFFER_MAX_LEN; i++) {
|
||||
if (buffer.readInt8(i) === 0) {
|
||||
isText = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (autoGuessEncoding && isText && !enc) {
|
||||
return encoding.guessEncodingByBuffer(buffer.slice(0, bytesRead)).then(enc => {
|
||||
return {
|
||||
mimes: isText ? [mime.MIME_TEXT] : [mime.MIME_BINARY],
|
||||
encoding: enc
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
mimes: isText ? [mime.MIME_TEXT] : [mime.MIME_BINARY],
|
||||
encoding: enc
|
||||
};
|
||||
}
|
||||
|
||||
function filterAndSortMimes(detectedMimes: string[], guessedMimes: string[]): string[] {
|
||||
const mimes = detectedMimes;
|
||||
|
||||
// Add extension based mime as first element as this is the desire of whoever created the file.
|
||||
// Never care about application/octet-stream or application/unknown as guessed mime, as this is the fallback of the guess which is never accurate
|
||||
const guessedMime = guessedMimes[0];
|
||||
if (guessedMime !== mime.MIME_BINARY && guessedMime !== mime.MIME_UNKNOWN) {
|
||||
mimes.unshift(guessedMime);
|
||||
}
|
||||
|
||||
// Remove duplicate elements from array and sort unspecific mime to the end
|
||||
const uniqueSortedMimes = mimes.filter((element, position) => {
|
||||
return element && mimes.indexOf(element) === position;
|
||||
}).sort((mimeA, mimeB) => {
|
||||
if (mimeA === mime.MIME_BINARY) { return 1; }
|
||||
if (mimeB === mime.MIME_BINARY) { return -1; }
|
||||
if (mimeA === mime.MIME_TEXT) { return 1; }
|
||||
if (mimeB === mime.MIME_TEXT) { return -1; }
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return uniqueSortedMimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the given stream to detect its mime type. Returns an array of mime types sorted from most specific to unspecific.
|
||||
* @param instream the readable stream to detect the mime types from.
|
||||
* @param nameHint an additional hint that can be used to detect a mime from a file extension.
|
||||
*/
|
||||
export function detectMimesFromStream(instream: streams.Readable, nameHint: string, option?: DetectMimesOption): TPromise<IMimeAndEncoding> {
|
||||
return doDetectMimesFromStream(instream, option).then(encoding =>
|
||||
handleMimeResult(nameHint, encoding)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the given file to detect its mime type. Returns an array of mime types sorted from most specific to unspecific.
|
||||
* @param absolutePath the absolute path of the file.
|
||||
*/
|
||||
export function detectMimesFromFile(absolutePath: string, option?: DetectMimesOption): TPromise<IMimeAndEncoding> {
|
||||
return doDetectMimesFromFile(absolutePath, option).then(encoding =>
|
||||
handleMimeResult(absolutePath, encoding)
|
||||
);
|
||||
}
|
||||
|
||||
function handleMimeResult(nameHint: string, result: IMimeAndEncoding): IMimeAndEncoding {
|
||||
const filterAndSortedMimes = filterAndSortMimes(result.mimes, mime.guessMimeTypes(nameHint));
|
||||
result.mimes = filterAndSortedMimes;
|
||||
|
||||
return result;
|
||||
}
|
||||
16
src/vs/base/node/paths.ts
Normal file
16
src/vs/base/node/paths.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import uri from 'vs/base/common/uri';
|
||||
|
||||
interface IPaths {
|
||||
getAppDataPath(platform: string): string;
|
||||
getDefaultUserDataPath(platform: string): string;
|
||||
}
|
||||
|
||||
const pathsPath = uri.parse(require.toUrl('paths')).fsPath;
|
||||
const paths = require.__$__nodeRequire<IPaths>(pathsPath);
|
||||
export const getAppDataPath = paths.getAppDataPath;
|
||||
export const getDefaultUserDataPath = paths.getDefaultUserDataPath;
|
||||
191
src/vs/base/node/pfs.ts
Normal file
191
src/vs/base/node/pfs.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { dirname, join } from 'path';
|
||||
import { nfcall, Queue } from 'vs/base/common/async';
|
||||
import * as fs from 'fs';
|
||||
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[]> {
|
||||
return nfcall(extfs.readdir, path);
|
||||
}
|
||||
|
||||
export function exists(path: string): TPromise<boolean> {
|
||||
return new TPromise(c => fs.exists(path, c));
|
||||
}
|
||||
|
||||
export function chmod(path: string, mode: number): TPromise<boolean> {
|
||||
return nfcall(fs.chmod, path, mode);
|
||||
}
|
||||
|
||||
export function mkdirp(path: string, mode?: number): TPromise<boolean> {
|
||||
const mkdir = () => nfcall(fs.mkdir, path, mode)
|
||||
.then(null, (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'EEXIST') {
|
||||
return nfcall(fs.stat, path)
|
||||
.then((stat: fs.Stats) => stat.isDirectory
|
||||
? null
|
||||
: TPromise.wrapError(new Error(`'${path}' exists and is not a directory.`)));
|
||||
}
|
||||
|
||||
return TPromise.wrapError<boolean>(err);
|
||||
});
|
||||
|
||||
// is root?
|
||||
if (path === dirname(path)) {
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
return mkdir().then(null, (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'ENOENT') {
|
||||
return mkdirp(dirname(path), mode).then(mkdir);
|
||||
}
|
||||
|
||||
return TPromise.wrapError<boolean>(err);
|
||||
});
|
||||
}
|
||||
|
||||
export function rimraf(path: string): TPromise<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(() => rmdir(path));
|
||||
} else {
|
||||
return unlink(path);
|
||||
}
|
||||
}, (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'ENOENT') {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
return TPromise.wrapError<void>(err);
|
||||
});
|
||||
}
|
||||
|
||||
export function realpath(path: string): TPromise<string> {
|
||||
return nfcall(extfs.realpath, path);
|
||||
}
|
||||
|
||||
export function stat(path: string): TPromise<fs.Stats> {
|
||||
return nfcall(fs.stat, path);
|
||||
}
|
||||
|
||||
export function lstat(path: string): TPromise<fs.Stats> {
|
||||
return nfcall(fs.lstat, path);
|
||||
}
|
||||
|
||||
export function rename(oldPath: string, newPath: string): TPromise<void> {
|
||||
return nfcall(fs.rename, oldPath, newPath);
|
||||
}
|
||||
|
||||
export function rmdir(path: string): TPromise<void> {
|
||||
return nfcall(fs.rmdir, path);
|
||||
}
|
||||
|
||||
export function unlink(path: string): TPromise<void> {
|
||||
return nfcall(fs.unlink, path);
|
||||
}
|
||||
|
||||
export function symlink(target: string, path: string, type?: string): TPromise<void> {
|
||||
return nfcall<void>(fs.symlink, target, path, type);
|
||||
}
|
||||
|
||||
export function readlink(path: string): TPromise<string> {
|
||||
return nfcall<string>(fs.readlink, path);
|
||||
}
|
||||
|
||||
export function touch(path: string): TPromise<void> {
|
||||
const now = Date.now() / 1000; // the value should be a Unix timestamp in seconds
|
||||
|
||||
return nfcall(fs.utimes, path, now, now);
|
||||
}
|
||||
|
||||
export function truncate(path: string, len: number): TPromise<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> {
|
||||
return nfcall(fs.readFile, path, encoding);
|
||||
}
|
||||
|
||||
// According to node.js docs (https://nodejs.org/docs/v6.5.0/api/fs.html#fs_fs_writefile_file_data_options_callback)
|
||||
// it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return.
|
||||
// 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?: { mode?: number; flag?: string; }): TPromise<void>;
|
||||
export function writeFile(path: string, data: NodeBuffer, options?: { mode?: number; flag?: string; }): TPromise<void>;
|
||||
export function writeFile(path: string, data: any, options?: { mode?: number; flag?: string; }): TPromise<void> {
|
||||
let queueKey = toQueueKey(path);
|
||||
|
||||
return ensureWriteFileQueue(queueKey).queue(() => nfcall(extfs.writeFileAndFlush, path, data, options));
|
||||
}
|
||||
|
||||
function toQueueKey(path: string): string {
|
||||
let queueKey = path;
|
||||
if (platform.isWindows || platform.isMacintosh) {
|
||||
queueKey = queueKey.toLowerCase(); // accomodate for case insensitive file systems
|
||||
}
|
||||
|
||||
return queueKey;
|
||||
}
|
||||
|
||||
function ensureWriteFileQueue(queueKey: string): Queue<void> {
|
||||
let writeFileQueue = writeFilePathQueue[queueKey];
|
||||
if (!writeFileQueue) {
|
||||
writeFileQueue = new Queue<void>();
|
||||
writeFilePathQueue[queueKey] = writeFileQueue;
|
||||
|
||||
const onFinish = once(writeFileQueue.onFinished);
|
||||
onFinish(() => {
|
||||
delete writeFilePathQueue[queueKey];
|
||||
writeFileQueue.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
return writeFileQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a dir and return only subfolders
|
||||
*/
|
||||
export function readDirsInDir(dirPath: string): TPromise<string[]> {
|
||||
return readdir(dirPath).then(children => {
|
||||
return TPromise.join(children.map(c => dirExists(join(dirPath, c)))).then(exists => {
|
||||
return children.filter((_, i) => exists[i]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* `path` exists and is a directory
|
||||
*/
|
||||
export function dirExists(path: string): TPromise<boolean> {
|
||||
return stat(path).then(stat => stat.isDirectory(), () => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* `path` exists and is a file.
|
||||
*/
|
||||
export function fileExists(path: string): TPromise<boolean> {
|
||||
return stat(path).then(stat => stat.isFile(), () => false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a path from disk.
|
||||
*/
|
||||
const tmpDir = os.tmpdir();
|
||||
export function del(path: string, tmp = tmpDir): TPromise<void> {
|
||||
return nfcall(extfs.del, path, tmp);
|
||||
}
|
||||
74
src/vs/base/node/ports.ts
Normal file
74
src/vs/base/node/ports.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 net = require('net');
|
||||
|
||||
/**
|
||||
* Given a start point and a max number of retries, will find a port that
|
||||
* is openable. Will return 0 in case no free port can be found.
|
||||
*/
|
||||
export function findFreePort(startPort: number, giveUpAfter: number, timeout: number, clb: (port: number) => void): void {
|
||||
let done = false;
|
||||
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
|
||||
return clb(0);
|
||||
}
|
||||
}, timeout);
|
||||
|
||||
doFindFreePort(startPort, giveUpAfter, (port) => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
clearTimeout(timeoutHandle);
|
||||
|
||||
return clb(port);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doFindFreePort(startPort: number, giveUpAfter: number, clb: (port: number) => void): void {
|
||||
if (giveUpAfter === 0) {
|
||||
return clb(0);
|
||||
}
|
||||
|
||||
const client = new net.Socket();
|
||||
|
||||
// If we can connect to the port it means the port is already taken so we continue searching
|
||||
client.once('connect', () => {
|
||||
dispose(client);
|
||||
|
||||
return doFindFreePort(startPort + 1, giveUpAfter - 1, clb);
|
||||
});
|
||||
|
||||
client.once('error', (err: Error & { code?: string }) => {
|
||||
dispose(client);
|
||||
|
||||
// If we receive any non ECONNREFUSED error, it means the port is used but we cannot connect
|
||||
if (err.code !== 'ECONNREFUSED') {
|
||||
return doFindFreePort(startPort + 1, giveUpAfter - 1, clb);
|
||||
}
|
||||
|
||||
// Otherwise it means the port is free to use!
|
||||
return clb(startPort);
|
||||
});
|
||||
|
||||
client.connect(startPort, '127.0.0.1');
|
||||
}
|
||||
|
||||
function dispose(socket: net.Socket): void {
|
||||
try {
|
||||
socket.removeAllListeners('connect');
|
||||
socket.removeAllListeners('error');
|
||||
socket.end();
|
||||
socket.destroy();
|
||||
socket.unref();
|
||||
} catch (error) {
|
||||
console.error(error); // otherwise this error would get lost in the callback chain
|
||||
}
|
||||
}
|
||||
488
src/vs/base/node/processes.ts
Normal file
488
src/vs/base/node/processes.ts
Normal file
@@ -0,0 +1,488 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 path = require('path');
|
||||
import * as cp from 'child_process';
|
||||
import ChildProcess = cp.ChildProcess;
|
||||
import exec = cp.exec;
|
||||
import spawn = cp.spawn;
|
||||
import { PassThrough } from 'stream';
|
||||
import { fork } from 'vs/base/node/stdFork';
|
||||
import nls = require('vs/nls');
|
||||
import { PPromise, TPromise, TValueCallback, TProgressCallback, 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';
|
||||
export { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode };
|
||||
|
||||
export interface LineData {
|
||||
line: string;
|
||||
source: Source;
|
||||
}
|
||||
|
||||
export interface BufferData {
|
||||
data: Buffer;
|
||||
source: Source;
|
||||
}
|
||||
|
||||
export interface StreamData {
|
||||
stdin: NodeJS.WritableStream;
|
||||
stdout: NodeJS.ReadableStream;
|
||||
stderr: NodeJS.ReadableStream;
|
||||
}
|
||||
|
||||
function getWindowsCode(status: number): TerminateResponseCode {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return TerminateResponseCode.Success;
|
||||
case 1:
|
||||
return TerminateResponseCode.AccessDenied;
|
||||
case 128:
|
||||
return TerminateResponseCode.ProcessNotFound;
|
||||
default:
|
||||
return TerminateResponseCode.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
export function terminateProcess(process: ChildProcess, cwd?: string): TerminateResponse {
|
||||
if (Platform.isWindows) {
|
||||
try {
|
||||
let options: any = {
|
||||
stdio: ['pipe', 'pipe', 'ignore']
|
||||
};
|
||||
if (cwd) {
|
||||
options.cwd = cwd;
|
||||
}
|
||||
cp.execFileSync('taskkill', ['/T', '/F', '/PID', process.pid.toString()], options);
|
||||
} catch (err) {
|
||||
return { success: false, error: err, code: err.status ? getWindowsCode(err.status) : TerminateResponseCode.Unknown };
|
||||
}
|
||||
} else if (Platform.isLinux || Platform.isMacintosh) {
|
||||
try {
|
||||
let cmd = URI.parse(require.toUrl('vs/base/node/terminateProcess.sh')).fsPath;
|
||||
let result = cp.spawnSync(cmd, [process.pid.toString()]);
|
||||
if (result.error) {
|
||||
return { success: false, error: result.error };
|
||||
}
|
||||
} catch (err) {
|
||||
return { success: false, error: err };
|
||||
}
|
||||
} else {
|
||||
process.kill('SIGKILL');
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
export function getWindowsShell(): string {
|
||||
return process.env['comspec'] || 'cmd.exe';
|
||||
}
|
||||
|
||||
export abstract class AbstractProcess<TProgressData> {
|
||||
private cmd: string;
|
||||
private module: string;
|
||||
private args: string[];
|
||||
private options: CommandOptions | ForkOptions;
|
||||
protected shell: boolean;
|
||||
|
||||
private childProcess: ChildProcess;
|
||||
protected childProcessPromise: TPromise<ChildProcess>;
|
||||
protected terminateRequested: boolean;
|
||||
|
||||
private static WellKnowCommands: IStringDictionary<boolean> = {
|
||||
'ant': true,
|
||||
'cmake': true,
|
||||
'eslint': true,
|
||||
'gradle': true,
|
||||
'grunt': true,
|
||||
'gulp': true,
|
||||
'jake': true,
|
||||
'jenkins': true,
|
||||
'jshint': true,
|
||||
'make': true,
|
||||
'maven': true,
|
||||
'msbuild': true,
|
||||
'msc': true,
|
||||
'nmake': true,
|
||||
'npm': true,
|
||||
'rake': true,
|
||||
'tsc': true,
|
||||
'xbuild': true
|
||||
};
|
||||
|
||||
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) {
|
||||
this.cmd = <string>arg1;
|
||||
this.args = arg2;
|
||||
this.shell = <boolean>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;
|
||||
this.shell = executable.isShellCommand;
|
||||
this.args = executable.args.slice(0);
|
||||
this.options = executable.options || {};
|
||||
}
|
||||
|
||||
this.childProcess = null;
|
||||
this.terminateRequested = false;
|
||||
|
||||
if (this.options.env) {
|
||||
let newEnv: IStringDictionary<string> = Object.create(null);
|
||||
Object.keys(process.env).forEach((key) => {
|
||||
newEnv[key] = process.env[key];
|
||||
});
|
||||
Object.keys(this.options.env).forEach((key) => {
|
||||
newEnv[key] = this.options.env[key];
|
||||
});
|
||||
this.options.env = newEnv;
|
||||
}
|
||||
}
|
||||
|
||||
public getSanitizedCommand(): string {
|
||||
let result = this.cmd.toLowerCase();
|
||||
let index = result.lastIndexOf(path.sep);
|
||||
if (index !== -1) {
|
||||
result = result.substring(index + 1);
|
||||
}
|
||||
if (AbstractProcess.WellKnowCommands[result]) {
|
||||
return result;
|
||||
}
|
||||
return 'other';
|
||||
}
|
||||
|
||||
public start(): PPromise<SuccessData, TProgressData> {
|
||||
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 an UNC drive.')));
|
||||
}
|
||||
return this.useExec().then((useExec) => {
|
||||
let cc: TValueCallback<SuccessData>;
|
||||
let ee: ErrorCallback;
|
||||
let pp: TProgressCallback<TProgressData>;
|
||||
let result = new PPromise<any, TProgressData>((c, e, p) => {
|
||||
cc = c;
|
||||
ee = e;
|
||||
pp = p;
|
||||
});
|
||||
|
||||
if (useExec) {
|
||||
let cmd: string = this.cmd;
|
||||
if (this.args) {
|
||||
cmd = cmd + ' ' + this.args.join(' ');
|
||||
}
|
||||
this.childProcess = exec(cmd, this.options, (error, stdout, stderr) => {
|
||||
this.childProcess = null;
|
||||
let err: any = error;
|
||||
// This is tricky since executing a command shell reports error back in case the executed command return an
|
||||
// error or the command didn't exist at all. So we can't blindly treat an error as a failed command. So we
|
||||
// always parse the output and report success unless the job got killed.
|
||||
if (err && err.killed) {
|
||||
ee({ killed: this.terminateRequested, stdout: stdout.toString(), stderr: stderr.toString() });
|
||||
} else {
|
||||
this.handleExec(cc, pp, error, stdout as any, stderr as any);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let childProcess: ChildProcess = null;
|
||||
let closeHandler = (data: any) => {
|
||||
this.childProcess = null;
|
||||
this.childProcessPromise = null;
|
||||
this.handleClose(data, cc, pp, ee);
|
||||
let result: SuccessData = {
|
||||
terminated: this.terminateRequested
|
||||
};
|
||||
if (Types.isNumber(data)) {
|
||||
result.cmdCode = <number>data;
|
||||
}
|
||||
cc(result);
|
||||
};
|
||||
if (this.shell && Platform.isWindows) {
|
||||
let options: any = Objects.clone(this.options);
|
||||
options.windowsVerbatimArguments = true;
|
||||
options.detached = false;
|
||||
let quotedCommand: boolean = false;
|
||||
let quotedArg: boolean = false;
|
||||
let commandLine: string[] = [];
|
||||
let quoted = this.ensureQuotes(this.cmd);
|
||||
commandLine.push(quoted.value);
|
||||
quotedCommand = quoted.quoted;
|
||||
if (this.args) {
|
||||
this.args.forEach((elem) => {
|
||||
quoted = this.ensureQuotes(elem);
|
||||
commandLine.push(quoted.value);
|
||||
quotedArg = quotedArg && quoted.quoted;
|
||||
});
|
||||
}
|
||||
let args: string[] = [
|
||||
'/s',
|
||||
'/c',
|
||||
];
|
||||
if (quotedCommand) {
|
||||
if (quotedArg) {
|
||||
args.push('"' + commandLine.join(' ') + '"');
|
||||
} else if (commandLine.length > 1) {
|
||||
args.push('"' + commandLine[0] + '"' + ' ' + commandLine.slice(1).join(' '));
|
||||
} else {
|
||||
args.push('"' + commandLine[0] + '"');
|
||||
}
|
||||
} else {
|
||||
args.push(commandLine.join(' '));
|
||||
}
|
||||
childProcess = spawn(getWindowsShell(), args, options);
|
||||
} else {
|
||||
if (this.cmd) {
|
||||
childProcess = spawn(this.cmd, this.args, this.options);
|
||||
} else if (this.module) {
|
||||
this.childProcessPromise = new TPromise<ChildProcess>((c, e, p) => {
|
||||
fork(this.module, this.args, <ForkOptions>this.options, (error: any, childProcess: ChildProcess) => {
|
||||
if (error) {
|
||||
e(error);
|
||||
ee({ terminated: this.terminateRequested, error: error });
|
||||
return;
|
||||
}
|
||||
this.childProcess = childProcess;
|
||||
this.childProcess.on('close', closeHandler);
|
||||
this.handleSpawn(childProcess, cc, pp, ee, false);
|
||||
c(childProcess);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
if (childProcess) {
|
||||
this.childProcess = childProcess;
|
||||
this.childProcessPromise = TPromise.as(childProcess);
|
||||
childProcess.on('error', (error: Error) => {
|
||||
this.childProcess = null;
|
||||
ee({ terminated: this.terminateRequested, error: error });
|
||||
});
|
||||
if (childProcess.pid) {
|
||||
this.childProcess.on('close', closeHandler);
|
||||
this.handleSpawn(childProcess, cc, pp, ee, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract handleExec(cc: TValueCallback<SuccessData>, pp: TProgressCallback<TProgressData>, error: Error, stdout: Buffer, stderr: Buffer): void;
|
||||
protected abstract handleSpawn(childProcess: ChildProcess, cc: TValueCallback<SuccessData>, pp: TProgressCallback<TProgressData>, ee: ErrorCallback, sync: boolean): void;
|
||||
|
||||
protected handleClose(data: any, cc: TValueCallback<SuccessData>, pp: TProgressCallback<TProgressData>, ee: ErrorCallback): void {
|
||||
// Default is to do nothing.
|
||||
}
|
||||
|
||||
private static regexp = /^[^"].* .*[^"]/;
|
||||
private ensureQuotes(value: string) {
|
||||
if (AbstractProcess.regexp.test(value)) {
|
||||
return {
|
||||
value: '"' + value + '"', //`"${value}"`,
|
||||
quoted: true
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
value: value,
|
||||
quoted: value.length > 0 && value[0] === '"' && value[value.length - 1] === '"'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public isRunning(): boolean {
|
||||
return this.childProcessPromise !== null;
|
||||
}
|
||||
|
||||
public get pid(): TPromise<number> {
|
||||
return this.childProcessPromise.then(childProcess => childProcess.pid, err => -1);
|
||||
}
|
||||
|
||||
public terminate(): TPromise<TerminateResponse> {
|
||||
if (!this.childProcessPromise) {
|
||||
return TPromise.as<TerminateResponse>({ success: true });
|
||||
}
|
||||
return this.childProcessPromise.then((childProcess) => {
|
||||
this.terminateRequested = true;
|
||||
let result = terminateProcess(childProcess, this.options.cwd);
|
||||
if (result.success) {
|
||||
this.childProcess = null;
|
||||
}
|
||||
return result;
|
||||
}, (err) => {
|
||||
return { success: true };
|
||||
});
|
||||
}
|
||||
|
||||
private useExec(): TPromise<boolean> {
|
||||
return new TPromise<boolean>((c, e, p) => {
|
||||
if (!this.shell || !Platform.isWindows) {
|
||||
c(false);
|
||||
}
|
||||
let cmdShell = spawn(getWindowsShell(), ['/s', '/c']);
|
||||
cmdShell.on('error', (error: Error) => {
|
||||
c(true);
|
||||
});
|
||||
cmdShell.on('exit', (data: any) => {
|
||||
c(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class LineProcess extends AbstractProcess<LineData> {
|
||||
|
||||
private stdoutLineDecoder: LineDecoder;
|
||||
private stderrLineDecoder: LineDecoder;
|
||||
|
||||
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) {
|
||||
[stdout, stderr].forEach((buffer: Buffer, index: number) => {
|
||||
let lineDecoder = new LineDecoder();
|
||||
let lines = lineDecoder.write(buffer);
|
||||
lines.forEach((line) => {
|
||||
pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr });
|
||||
});
|
||||
let line = lineDecoder.end();
|
||||
if (line) {
|
||||
pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr });
|
||||
}
|
||||
});
|
||||
cc({ terminated: this.terminateRequested, error: error });
|
||||
}
|
||||
|
||||
protected handleSpawn(childProcess: ChildProcess, cc: TValueCallback<SuccessData>, pp: TProgressCallback<LineData>, ee: ErrorCallback, sync: boolean): void {
|
||||
this.stdoutLineDecoder = new LineDecoder();
|
||||
this.stderrLineDecoder = new LineDecoder();
|
||||
childProcess.stdout.on('data', (data: Buffer) => {
|
||||
let lines = this.stdoutLineDecoder.write(data);
|
||||
lines.forEach(line => pp({ line: line, source: Source.stdout }));
|
||||
});
|
||||
childProcess.stderr.on('data', (data: Buffer) => {
|
||||
let lines = this.stderrLineDecoder.write(data);
|
||||
lines.forEach(line => pp({ line: line, source: Source.stderr }));
|
||||
});
|
||||
}
|
||||
|
||||
protected handleClose(data: any, cc: TValueCallback<SuccessData>, pp: TProgressCallback<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 });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class BufferProcess extends AbstractProcess<BufferData> {
|
||||
|
||||
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<BufferData>, error: Error, stdout: Buffer, stderr: Buffer): void {
|
||||
pp({ data: stdout, source: Source.stdout });
|
||||
pp({ data: stderr, source: Source.stderr });
|
||||
cc({ terminated: this.terminateRequested, error: error });
|
||||
}
|
||||
|
||||
protected handleSpawn(childProcess: ChildProcess, cc: TValueCallback<SuccessData>, pp: TProgressCallback<BufferData>, ee: ErrorCallback, sync: boolean): void {
|
||||
childProcess.stdout.on('data', (data: Buffer) => {
|
||||
pp({ data: data, source: Source.stdout });
|
||||
});
|
||||
childProcess.stderr.on('data', (data: Buffer) => {
|
||||
pp({ data: data, source: Source.stderr });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class StreamProcess extends AbstractProcess<StreamData> {
|
||||
|
||||
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<StreamData>, error: Error, stdout: Buffer, stderr: Buffer): void {
|
||||
let stdoutStream = new PassThrough();
|
||||
stdoutStream.end(stdout);
|
||||
let stderrStream = new PassThrough();
|
||||
stderrStream.end(stderr);
|
||||
pp({ stdin: null, stdout: stdoutStream, stderr: stderrStream });
|
||||
cc({ terminated: this.terminateRequested, error: error });
|
||||
}
|
||||
|
||||
protected handleSpawn(childProcess: ChildProcess, cc: TValueCallback<SuccessData>, pp: TProgressCallback<StreamData>, ee: ErrorCallback, sync: boolean): void {
|
||||
if (sync) {
|
||||
process.nextTick(() => {
|
||||
pp({ stdin: childProcess.stdin, stdout: childProcess.stdout, stderr: childProcess.stderr });
|
||||
});
|
||||
} else {
|
||||
pp({ stdin: childProcess.stdin, stdout: childProcess.stdout, stderr: childProcess.stderr });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IQueuedSender {
|
||||
send: (msg: any) => void;
|
||||
}
|
||||
|
||||
// Wrapper around process.send() that will queue any messages if the internal node.js
|
||||
// queue is filled with messages and only continue sending messages when the internal
|
||||
// queue is free again to consume messages.
|
||||
// On Windows we always wait for the send() method to return before sending the next message
|
||||
// to workaround https://github.com/nodejs/node/issues/7657 (IPC can freeze process)
|
||||
export function createQueuedSender(childProcess: ChildProcess | NodeJS.Process): IQueuedSender {
|
||||
let msgQueue = [];
|
||||
let useQueue = false;
|
||||
|
||||
const send = function (msg: any): void {
|
||||
if (useQueue) {
|
||||
msgQueue.push(msg); // add to the queue if the process cannot handle more messages
|
||||
return;
|
||||
}
|
||||
|
||||
let result = childProcess.send(msg, error => {
|
||||
if (error) {
|
||||
console.error(error); // unlikely to happen, best we can do is log this error
|
||||
}
|
||||
|
||||
useQueue = false; // we are good again to send directly without queue
|
||||
|
||||
// now send all the messages that we have in our queue and did not send yet
|
||||
if (msgQueue.length > 0) {
|
||||
const msgQueueCopy = msgQueue.slice(0);
|
||||
msgQueue = [];
|
||||
msgQueueCopy.forEach(entry => send(entry));
|
||||
}
|
||||
});
|
||||
|
||||
if (!result || Platform.isWindows /* workaround https://github.com/nodejs/node/issues/7657 */) {
|
||||
useQueue = true;
|
||||
}
|
||||
};
|
||||
|
||||
return { send };
|
||||
}
|
||||
100
src/vs/base/node/profiler.ts
Normal file
100
src/vs/base/node/profiler.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { join, basename } from 'path';
|
||||
import { writeFile } from 'vs/base/node/pfs';
|
||||
|
||||
export function startProfiling(name: string): TPromise<boolean> {
|
||||
return lazyV8Profiler.value.then(profiler => {
|
||||
profiler.startProfiling(name);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
const _isRunningOutOfDev = process.env['VSCODE_DEV'];
|
||||
|
||||
export function stopProfiling(dir: string, prefix: string): TPromise<string> {
|
||||
return lazyV8Profiler.value.then(profiler => {
|
||||
return profiler.stopProfiling();
|
||||
}).then(profile => {
|
||||
return new TPromise<any>((resolve, reject) => {
|
||||
|
||||
// remove pii paths
|
||||
if (!_isRunningOutOfDev) {
|
||||
removePiiPaths(profile); // remove pii from our users
|
||||
}
|
||||
|
||||
profile.export(function (error, result) {
|
||||
profile.delete();
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
let filepath = join(dir, `${prefix}_${profile.title}.cpuprofile`);
|
||||
if (!_isRunningOutOfDev) {
|
||||
filepath += '.txt'; // github issues must be: txt, zip, png, gif
|
||||
}
|
||||
writeFile(filepath, result).then(() => resolve(filepath), reject);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function removePiiPaths(profile: Profile) {
|
||||
const stack = [profile.head];
|
||||
while (stack.length > 0) {
|
||||
const element = stack.pop();
|
||||
if (element.url) {
|
||||
const shortUrl = basename(element.url);
|
||||
if (element.url !== shortUrl) {
|
||||
element.url = `pii_removed/${shortUrl}`;
|
||||
}
|
||||
}
|
||||
if (element.children) {
|
||||
stack.push(...element.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare interface Profiler {
|
||||
startProfiling(name: string);
|
||||
stopProfiling(): Profile;
|
||||
}
|
||||
|
||||
export declare interface Profile {
|
||||
title: string;
|
||||
export(callback: (err, data) => void);
|
||||
delete();
|
||||
head: ProfileSample;
|
||||
}
|
||||
|
||||
export declare interface ProfileSample {
|
||||
// bailoutReason:""
|
||||
// callUID:2333
|
||||
// children:Array[39]
|
||||
// functionName:"(root)"
|
||||
// hitCount:0
|
||||
// id:1
|
||||
// lineNumber:0
|
||||
// scriptId:0
|
||||
// url:""
|
||||
url: string;
|
||||
children: ProfileSample[];
|
||||
}
|
||||
|
||||
const lazyV8Profiler = new class {
|
||||
private _value: TPromise<Profiler>;
|
||||
get value() {
|
||||
if (!this._value) {
|
||||
this._value = new TPromise<Profiler>((resolve, reject) => {
|
||||
require(['v8-profiler'], resolve, reject);
|
||||
});
|
||||
}
|
||||
return this._value;
|
||||
}
|
||||
};
|
||||
55
src/vs/base/node/proxy.ts
Normal file
55
src/vs/base/node/proxy.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Url, parse as parseUrl } from 'url';
|
||||
import { isBoolean } from 'vs/base/common/types';
|
||||
import { Agent } from './request';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
|
||||
function getSystemProxyURI(requestURL: Url): string {
|
||||
if (requestURL.protocol === 'http:') {
|
||||
return process.env.HTTP_PROXY || process.env.http_proxy || null;
|
||||
} else if (requestURL.protocol === 'https:') {
|
||||
return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface IOptions {
|
||||
proxyUrl?: string;
|
||||
strictSSL?: boolean;
|
||||
}
|
||||
|
||||
export async function getProxyAgent(rawRequestURL: string, options: IOptions = {}): TPromise<Agent> {
|
||||
const requestURL = parseUrl(rawRequestURL);
|
||||
const proxyURL = options.proxyUrl || getSystemProxyURI(requestURL);
|
||||
|
||||
if (!proxyURL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const proxyEndpoint = parseUrl(proxyURL);
|
||||
|
||||
if (!/^https?:$/.test(proxyEndpoint.protocol)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const opts = {
|
||||
host: proxyEndpoint.hostname,
|
||||
port: Number(proxyEndpoint.port),
|
||||
auth: proxyEndpoint.auth,
|
||||
rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true
|
||||
};
|
||||
|
||||
const Ctor = requestURL.protocol === 'http:'
|
||||
? await import('http-proxy-agent')
|
||||
: await import('https-proxy-agent');
|
||||
|
||||
return new Ctor(opts);
|
||||
}
|
||||
172
src/vs/base/node/request.ts
Normal file
172
src/vs/base/node/request.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { isBoolean, isNumber } from 'vs/base/common/types';
|
||||
import https = require('https');
|
||||
import http = require('http');
|
||||
import { Stream } from 'stream';
|
||||
import { parse as parseUrl } from 'url';
|
||||
import { createWriteStream } from 'fs';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { createGunzip } from 'zlib';
|
||||
|
||||
export type Agent = any;
|
||||
|
||||
export interface IRawRequestFunction {
|
||||
(options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest;
|
||||
}
|
||||
|
||||
export interface IRequestOptions {
|
||||
type?: string;
|
||||
url?: string;
|
||||
user?: string;
|
||||
password?: string;
|
||||
headers?: any;
|
||||
timeout?: number;
|
||||
data?: any;
|
||||
agent?: Agent;
|
||||
followRedirects?: number;
|
||||
strictSSL?: boolean;
|
||||
getRawRequest?(options: IRequestOptions): IRawRequestFunction;
|
||||
}
|
||||
|
||||
export interface IRequestContext {
|
||||
// req: http.ClientRequest;
|
||||
// res: http.ClientResponse;
|
||||
res: {
|
||||
headers: { [n: string]: string };
|
||||
statusCode?: number;
|
||||
};
|
||||
stream: Stream;
|
||||
}
|
||||
|
||||
export interface IRequestFunction {
|
||||
(options: IRequestOptions): TPromise<IRequestContext>;
|
||||
}
|
||||
|
||||
async function getNodeRequest(options: IRequestOptions): TPromise<IRawRequestFunction> {
|
||||
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> {
|
||||
let req: http.ClientRequest;
|
||||
|
||||
const rawRequestPromise = options.getRawRequest
|
||||
? TPromise.as(options.getRawRequest(options))
|
||||
: getNodeRequest(options);
|
||||
|
||||
return rawRequestPromise.then(rawRequest => {
|
||||
return new TPromise<IRequestContext>((c, e) => {
|
||||
const endpoint = parseUrl(options.url);
|
||||
|
||||
const opts: https.RequestOptions = {
|
||||
hostname: endpoint.hostname,
|
||||
port: endpoint.port ? parseInt(endpoint.port) : (endpoint.protocol === 'https:' ? 443 : 80),
|
||||
protocol: endpoint.protocol,
|
||||
path: endpoint.path,
|
||||
method: options.type || 'GET',
|
||||
headers: options.headers,
|
||||
agent: options.agent,
|
||||
rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true
|
||||
};
|
||||
|
||||
if (options.user && options.password) {
|
||||
opts.auth = options.user + ':' + options.password;
|
||||
}
|
||||
|
||||
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']) {
|
||||
request(assign({}, options, {
|
||||
url: res.headers['location'],
|
||||
followRedirects: followRedirects - 1
|
||||
})).done(c, e);
|
||||
} else {
|
||||
let stream: Stream = res;
|
||||
|
||||
if (res.headers['content-encoding'] === 'gzip') {
|
||||
stream = stream.pipe(createGunzip());
|
||||
}
|
||||
|
||||
c({ res, stream });
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', e);
|
||||
|
||||
if (options.timeout) {
|
||||
req.setTimeout(options.timeout);
|
||||
}
|
||||
|
||||
if (options.data) {
|
||||
req.write(options.data);
|
||||
}
|
||||
|
||||
req.end();
|
||||
}, () => req && req.abort());
|
||||
});
|
||||
}
|
||||
|
||||
function isSuccess(context: IRequestContext): boolean {
|
||||
return (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) => {
|
||||
const out = createWriteStream(filePath);
|
||||
|
||||
out.once('finish', () => c(null));
|
||||
context.stream.once('error', e);
|
||||
context.stream.pipe(out);
|
||||
});
|
||||
}
|
||||
|
||||
export function asText(context: IRequestContext): TPromise<string> {
|
||||
return new TPromise((c, e) => {
|
||||
if (!isSuccess(context)) {
|
||||
return e('Server returned ' + context.res.statusCode);
|
||||
}
|
||||
|
||||
if (hasNoContent(context)) {
|
||||
return c(null);
|
||||
}
|
||||
|
||||
let buffer: string[] = [];
|
||||
context.stream.on('data', d => buffer.push(d));
|
||||
context.stream.on('end', () => c(buffer.join('')));
|
||||
context.stream.on('error', e);
|
||||
});
|
||||
}
|
||||
|
||||
export function asJson<T>(context: IRequestContext): TPromise<T> {
|
||||
return new TPromise((c, e) => {
|
||||
if (!isSuccess(context)) {
|
||||
return e('Server returned ' + context.res.statusCode);
|
||||
}
|
||||
|
||||
if (hasNoContent(context)) {
|
||||
return c(null);
|
||||
}
|
||||
|
||||
if (!/application\/json/.test(context.res.headers['content-type'])) {
|
||||
return e('Response doesn\'t appear to be JSON');
|
||||
}
|
||||
|
||||
const buffer: string[] = [];
|
||||
context.stream.on('data', d => buffer.push(d));
|
||||
context.stream.on('end', () => c(JSON.parse(buffer.join(''))));
|
||||
context.stream.on('error', e);
|
||||
});
|
||||
}
|
||||
39
src/vs/base/node/startupTimers.d.ts
vendored
Normal file
39
src/vs/base/node/startupTimers.d.ts
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Profile } from './profiler';
|
||||
|
||||
declare interface TickStart {
|
||||
name: string;
|
||||
started: number;
|
||||
}
|
||||
|
||||
export declare class Tick {
|
||||
|
||||
readonly duration: number;
|
||||
readonly name: string;
|
||||
readonly started: number;
|
||||
readonly stopped: number;
|
||||
readonly profile: Profile;
|
||||
|
||||
static compareByStart(a: Tick, b: Tick): number;
|
||||
}
|
||||
|
||||
declare interface TickController {
|
||||
while<T extends Thenable<any>>(t: T): T;
|
||||
stop(stopped?: number): void;
|
||||
}
|
||||
|
||||
export function startTimer(name: string): TickController;
|
||||
|
||||
export function stopTimer(name: string);
|
||||
|
||||
export function ticks(): Tick[];
|
||||
|
||||
export function tick(name: string): Tick;
|
||||
|
||||
export function setProfileList(names: string[]): void;
|
||||
|
||||
export function disable(): void;
|
||||
128
src/vs/base/node/startupTimers.js
Normal file
128
src/vs/base/node/startupTimers.js
Normal file
@@ -0,0 +1,128 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/*global define*/
|
||||
|
||||
var requireProfiler;
|
||||
|
||||
if (typeof define !== "function" && typeof module === "object" && typeof module.exports === "object") {
|
||||
// this is commonjs, fake amd
|
||||
global.define = function (dep, callback) {
|
||||
module.exports = callback();
|
||||
global.define = undefined;
|
||||
};
|
||||
requireProfiler = function () {
|
||||
return require('v8-profiler');
|
||||
};
|
||||
} else {
|
||||
// this is amd
|
||||
requireProfiler = function () {
|
||||
return require.__$__nodeRequire('v8-profiler');
|
||||
};
|
||||
}
|
||||
|
||||
define([], function () {
|
||||
|
||||
function Tick(name, started, stopped, profile) {
|
||||
this.name = name;
|
||||
this.started = started;
|
||||
this.stopped = stopped;
|
||||
this.duration = Math.round(((stopped[0] * 1.e9 + stopped[1]) - (started[0] * 1e9 + started[1])) / 1.e6);
|
||||
this.profile = profile;
|
||||
}
|
||||
Tick.compareByStart = function (a, b) {
|
||||
if (a.started < b.started) {
|
||||
return -1;
|
||||
} else if (a.started > b.started) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// This module can be loaded in an amd and commonjs-context.
|
||||
// Because we want both instances to use the same tick-data
|
||||
// we store them globally
|
||||
global._perfStarts = global._perfStarts || new Map();
|
||||
global._perfTicks = global._perfTicks || new Map();
|
||||
global._perfToBeProfiled = global._perfToBeProfiled || new Set();
|
||||
|
||||
var _starts = global._perfStarts;
|
||||
var _ticks = global._perfTicks;
|
||||
var _toBeProfiled = global._perfToBeProfiled;
|
||||
|
||||
function startTimer(name) {
|
||||
if (_starts.has(name)) {
|
||||
throw new Error("${name}" + " already exists");
|
||||
}
|
||||
if (_toBeProfiled.has(name)) {
|
||||
requireProfiler().startProfiling(name, true);
|
||||
}
|
||||
_starts.set(name, { name: name, started: process.hrtime() });
|
||||
var stop = stopTimer.bind(undefined, name);
|
||||
return {
|
||||
stop: stop,
|
||||
while: function (thenable) {
|
||||
thenable.then(function () { stop(); }, function () { stop(); });
|
||||
return thenable;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function stopTimer(name) {
|
||||
var profile = _toBeProfiled.has(name) ? requireProfiler().stopProfiling(name) : undefined;
|
||||
var start = _starts.get(name);
|
||||
if (start !== undefined) {
|
||||
var tick = new Tick(start.name, start.started, process.hrtime(), profile);
|
||||
_ticks.set(name, tick);
|
||||
_starts.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
function ticks() {
|
||||
var ret = [];
|
||||
_ticks.forEach(function (value) { ret.push(value); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
function tick(name) {
|
||||
var ret = _ticks.get(name);
|
||||
if (!ret) {
|
||||
var now = Date.now();
|
||||
ret = new Tick(name, now, now);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function setProfileList(names) {
|
||||
_toBeProfiled.clear();
|
||||
names.forEach(function (name) { _toBeProfiled.add(name); });
|
||||
}
|
||||
|
||||
var exports = {
|
||||
Tick: Tick,
|
||||
startTimer: startTimer,
|
||||
stopTimer: stopTimer,
|
||||
ticks: ticks,
|
||||
tick: tick,
|
||||
setProfileList: setProfileList,
|
||||
disable: disable,
|
||||
};
|
||||
|
||||
function disable() {
|
||||
var emptyController = Object.freeze({ while: function (t) { return t; }, stop: function () { } });
|
||||
var emptyTicks = Object.create([]);
|
||||
exports.startTimer = function () { return emptyController; };
|
||||
exports.stopTimer = function () { };
|
||||
exports.ticks = function () { return emptyTicks; };
|
||||
|
||||
delete global._perfStarts;
|
||||
delete global._perfTicks;
|
||||
}
|
||||
|
||||
return exports;
|
||||
});
|
||||
141
src/vs/base/node/stdFork.ts
Normal file
141
src/vs/base/node/stdFork.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 path = require('path');
|
||||
import os = require('os');
|
||||
import net = require('net');
|
||||
import cp = require('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', '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-' + 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';
|
||||
newEnv['ELECTRON_NO_ASAR'] = '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);
|
||||
}
|
||||
197
src/vs/base/node/stdForkStart.js
Normal file
197
src/vs/base/node/stdForkStart.js
Normal file
@@ -0,0 +1,197 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 = new Buffer(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');
|
||||
});
|
||||
|
||||
})();
|
||||
178
src/vs/base/node/stream.ts
Normal file
178
src/vs/base/node/stream.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 fs = require('fs');
|
||||
import stream = require('stream');
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
export interface ReadResult {
|
||||
buffer: NodeBuffer;
|
||||
bytesRead: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to total bytes from the provided stream.
|
||||
*/
|
||||
export function readExactlyByStream(stream: stream.Readable, totalBytes: number): TPromise<ReadResult> {
|
||||
return new TPromise<ReadResult>((complete, error) => {
|
||||
let done = false;
|
||||
let buffer = new Buffer(totalBytes);
|
||||
let bytesRead = 0;
|
||||
|
||||
stream.on('data', (data: NodeBuffer) => {
|
||||
let bytesToRead = Math.min(totalBytes - bytesRead, data.length);
|
||||
data.copy(buffer, bytesRead, 0, bytesToRead);
|
||||
bytesRead += bytesToRead;
|
||||
|
||||
if (bytesRead === totalBytes) {
|
||||
(stream as any).destroy(); // Will trigger the close event eventually
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('error', (e: Error) => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
error(e);
|
||||
}
|
||||
});
|
||||
|
||||
let onSuccess = () => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
complete({ buffer, bytesRead });
|
||||
}
|
||||
};
|
||||
|
||||
stream.on('close', onSuccess);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads totalBytes from the provided file.
|
||||
*/
|
||||
export function readExactlyByFile(file: string, totalBytes: number): TPromise<ReadResult> {
|
||||
return new TPromise<ReadResult>((complete, error) => {
|
||||
fs.open(file, 'r', null, (err, fd) => {
|
||||
if (err) {
|
||||
return error(err);
|
||||
}
|
||||
|
||||
function end(err: Error, resultBuffer: NodeBuffer, bytesRead: number): void {
|
||||
fs.close(fd, (closeError: Error) => {
|
||||
if (closeError) {
|
||||
return error(closeError);
|
||||
}
|
||||
|
||||
if (err && (<any>err).code === 'EISDIR') {
|
||||
return error(err); // we want to bubble this error up (file is actually a folder)
|
||||
}
|
||||
|
||||
return complete({ buffer: resultBuffer, bytesRead });
|
||||
});
|
||||
}
|
||||
|
||||
let buffer = new Buffer(totalBytes);
|
||||
let bytesRead = 0;
|
||||
let zeroAttempts = 0;
|
||||
function loop(): void {
|
||||
fs.read(fd, buffer, bytesRead, totalBytes - bytesRead, null, (err, moreBytesRead) => {
|
||||
if (err) {
|
||||
return end(err, null, 0);
|
||||
}
|
||||
|
||||
// Retry up to N times in case 0 bytes where read
|
||||
if (moreBytesRead === 0) {
|
||||
if (++zeroAttempts === 10) {
|
||||
return end(null, buffer, bytesRead);
|
||||
}
|
||||
|
||||
return loop();
|
||||
}
|
||||
|
||||
bytesRead += moreBytesRead;
|
||||
|
||||
if (bytesRead === totalBytes) {
|
||||
return end(null, buffer, bytesRead);
|
||||
}
|
||||
|
||||
return loop();
|
||||
});
|
||||
}
|
||||
|
||||
loop();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a file until a matching string is found.
|
||||
*
|
||||
* @param file The file to read.
|
||||
* @param matchingString The string to search for.
|
||||
* @param chunkBytes The number of bytes to read each iteration.
|
||||
* @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) =>
|
||||
fs.open(file, 'r', null, (err, fd) => {
|
||||
if (err) {
|
||||
return error(err);
|
||||
}
|
||||
|
||||
function end(err: Error, result: string): void {
|
||||
fs.close(fd, (closeError: Error) => {
|
||||
if (closeError) {
|
||||
return error(closeError);
|
||||
}
|
||||
|
||||
if (err && (<any>err).code === 'EISDIR') {
|
||||
return error(err); // we want to bubble this error up (file is actually a folder)
|
||||
}
|
||||
|
||||
return complete(result);
|
||||
});
|
||||
}
|
||||
|
||||
let buffer = new Buffer(maximumBytesToRead);
|
||||
let bytesRead = 0;
|
||||
let zeroAttempts = 0;
|
||||
function loop(): void {
|
||||
fs.read(fd, buffer, bytesRead, chunkBytes, null, (err, moreBytesRead) => {
|
||||
if (err) {
|
||||
return end(err, null);
|
||||
}
|
||||
|
||||
// Retry up to N times in case 0 bytes where read
|
||||
if (moreBytesRead === 0) {
|
||||
if (++zeroAttempts === 10) {
|
||||
return end(null, null);
|
||||
}
|
||||
|
||||
return loop();
|
||||
}
|
||||
|
||||
bytesRead += moreBytesRead;
|
||||
|
||||
const newLineIndex = buffer.indexOf(matchingString);
|
||||
if (newLineIndex >= 0) {
|
||||
return end(null, buffer.toString('utf8').substr(0, newLineIndex));
|
||||
}
|
||||
|
||||
if (bytesRead >= maximumBytesToRead) {
|
||||
return end(new Error(`Could not find ${matchingString} in first ${maximumBytesToRead} bytes of ${file}`), null);
|
||||
}
|
||||
|
||||
return loop();
|
||||
});
|
||||
}
|
||||
|
||||
loop();
|
||||
})
|
||||
);
|
||||
}
|
||||
12
src/vs/base/node/terminateProcess.sh
Normal file
12
src/vs/base/node/terminateProcess.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
terminateTree() {
|
||||
for cpid in $(/usr/bin/pgrep -P $1); do
|
||||
terminateTree $cpid
|
||||
done
|
||||
kill -9 $1 > /dev/null 2>&1
|
||||
}
|
||||
|
||||
for pid in $*; do
|
||||
terminateTree $pid
|
||||
done
|
||||
115
src/vs/base/node/zip.ts
Normal file
115
src/vs/base/node/zip.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import * as path from 'path';
|
||||
import { createWriteStream } 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';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 extractEntry(stream: Readable, fileName: string, mode: number, targetPath: string, options: IOptions): TPromise<void> {
|
||||
const dirName = path.dirname(fileName);
|
||||
const targetDirName = path.join(targetPath, dirName);
|
||||
const targetFileName = path.join(targetPath, fileName);
|
||||
|
||||
return mkdirp(targetDirName).then(() => new TPromise((c, e) => {
|
||||
let istream = createWriteStream(targetFileName, { mode });
|
||||
istream.once('finish', () => c(null));
|
||||
istream.once('error', e);
|
||||
stream.once('error', e);
|
||||
stream.pipe(istream);
|
||||
}));
|
||||
}
|
||||
|
||||
function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions): TPromise<void> {
|
||||
return new TPromise((c, e) => {
|
||||
const throttler = new SimpleThrottler();
|
||||
let last = TPromise.as<any>(null);
|
||||
|
||||
zipfile.once('error', e);
|
||||
zipfile.once('close', () => last.then(c, e));
|
||||
zipfile.on('entry', (entry: Entry) => {
|
||||
if (!options.sourcePathRegex.test(entry.fileName)) {
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
const stream = ninvoke(zipfile, zipfile.openReadStream, entry);
|
||||
const mode = modeFromEntry(entry);
|
||||
|
||||
last = throttler.queue(() => stream.then(stream => extractEntry(stream, fileName, mode, targetPath, options)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function extract(zipPath: string, targetPath: string, options: IExtractOptions = {}): TPromise<void> {
|
||||
const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : '');
|
||||
|
||||
let promise = nfcall<ZipFile>(openZip, zipPath);
|
||||
|
||||
if (options.overwrite) {
|
||||
promise = promise.then(zipfile => rimraf(targetPath).then(() => zipfile));
|
||||
}
|
||||
|
||||
return promise.then(zipfile => extractZip(zipfile, targetPath, { sourcePathRegex }));
|
||||
}
|
||||
|
||||
function read(zipPath: string, filePath: string): TPromise<Readable> {
|
||||
return nfcall(openZip, zipPath).then((zipfile: 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