SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

221
src/vs/base/node/config.ts Normal file
View 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);
}
}

View 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);
});
}

View 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;
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}
}

View 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 };
}

View 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
View 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
View 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
View 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;

View 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
View 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);
}

View 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
View 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();
})
);
}

View 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
View 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)));
});
});
}