Initial VS Code 1.19 source merge (#571)

* Initial 1.19 xcopy

* Fix yarn build

* Fix numerous build breaks

* Next batch of build break fixes

* More build break fixes

* Runtime breaks

* Additional post merge fixes

* Fix windows setup file

* Fix test failures.

* Update license header blocks to refer to source eula
This commit is contained in:
Karl Burtram
2018-01-28 23:37:17 -08:00
committed by GitHub
parent 9a1ac20710
commit 251ae01c3e
8009 changed files with 93378 additions and 35634 deletions

View File

@@ -153,7 +153,7 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
try {
const watcher = extfs.watch(path, (type, file) => this.onConfigFileChange(type, file, isParentFolder));
watcher.on('error', (code, signal) => this.options.onError(`Error watching ${path} for configuration changes (${code}, ${signal})`));
watcher.on('error', (code: number, signal: string) => this.options.onError(`Error watching ${path} for configuration changes (${code}, ${signal})`));
this.disposables.push(toDisposable(() => {
watcher.removeAllListeners();
@@ -209,7 +209,7 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
return fallback;
}
const value = this.cache ? this.cache[key] : void 0;
const value = this.cache ? (this.cache as any)[key] : void 0;
return typeof value !== 'undefined' ? value : fallback;
}

View File

@@ -8,6 +8,8 @@
import stream = require('vs/base/node/stream');
import iconv = require('iconv-lite');
import { TPromise } from 'vs/base/common/winjs.base';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { exec } from 'child_process';
export const UTF8 = 'utf8';
export const UTF8_with_bom = 'utf8bom';
@@ -42,10 +44,6 @@ 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
@@ -169,3 +167,88 @@ export function toCanonicalName(enc: string): string {
return enc;
}
}
// https://ss64.com/nt/chcp.html
const windowsTerminalEncodings = {
'437': 'cp437', // United States
'850': 'cp850', // Multilingual(Latin I)
'852': 'cp852', // Slavic(Latin II)
'855': 'cp855', // Cyrillic(Russian)
'857': 'cp857', // Turkish
'860': 'cp860', // Portuguese
'861': 'cp861', // Icelandic
'863': 'cp863', // Canadian - French
'865': 'cp865', // Nordic
'866': 'cp866', // Russian
'869': 'cp869', // Modern Greek
'1252': 'cp1252' // West European Latin
};
export function resolveTerminalEncoding(verbose?: boolean): TPromise<string> {
let rawEncodingPromise: TPromise<string>;
// Support a global environment variable to win over other mechanics
const cliEncodingEnv = process.env['VSCODE_CLI_ENCODING'];
if (cliEncodingEnv) {
if (verbose) {
console.log(`Found VSCODE_CLI_ENCODING variable: ${cliEncodingEnv}`);
}
rawEncodingPromise = TPromise.as(cliEncodingEnv);
}
// Linux/Mac: use "locale charmap" command
else if (isLinux || isMacintosh) {
rawEncodingPromise = new TPromise<string>(c => {
if (verbose) {
console.log('Running "locale charmap" to detect terminal encoding...');
}
exec('locale charmap', (err, stdout, stderr) => c(stdout));
});
}
// Windows: educated guess
else {
rawEncodingPromise = new TPromise<string>(c => {
if (verbose) {
console.log('Running "chcp" to detect terminal encoding...');
}
exec('chcp', (err, stdout, stderr) => {
if (stdout) {
const windowsTerminalEncodingKeys = Object.keys(windowsTerminalEncodings);
for (let i = 0; i < windowsTerminalEncodingKeys.length; i++) {
const key = windowsTerminalEncodingKeys[i];
if (stdout.indexOf(key) >= 0) {
return c(windowsTerminalEncodings[key]);
}
}
}
return c(void 0);
});
});
}
return rawEncodingPromise.then(rawEncoding => {
if (verbose) {
console.log(`Detected raw terminal encoding: ${rawEncoding}`);
}
if (!rawEncoding || rawEncoding.toLowerCase() === 'utf-8' || rawEncoding.toLowerCase() === UTF8) {
return UTF8;
}
const iconvEncoding = toIconvLiteEncoding(rawEncoding);
if (iconv.encodingExists(iconvEncoding)) {
return iconvEncoding;
}
if (verbose) {
console.log('Unsupported terminal encoding, falling back to UTF-8.');
}
return UTF8;
});
}

View File

@@ -1,18 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 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;
};

View File

@@ -12,6 +12,8 @@ import * as flow from 'vs/base/node/flow';
import * as fs from 'fs';
import * as paths from 'path';
import { TPromise } from 'vs/base/common/winjs.base';
import { nfcall } from 'vs/base/common/async';
const loop = flow.loop;
@@ -41,80 +43,72 @@ export function readdir(path: string, callback: (error: Error, files: string[])
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 (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 => {
copiedSources[source] = true; // remember as copied
const proceed = function () {
readdir(source, (err, files) => {
loop(files, (file: string, clb: (error: Error, result: string[]) => void) => {
copy(paths.join(source, file), paths.join(target, file), (error: Error) => clb(error, void 0), copiedSources);
}, callback);
});
};
mkdirp(target, stat.mode & 511).done(proceed, proceed);
});
}
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 === paths.dirname(path)) {
return TPromise.as(true);
}
return mkdir().then(null, (err: NodeJS.ErrnoException) => {
if (err.code === 'ENOENT') {
return mkdirp(paths.dirname(path), mode).then(mkdir);
}
return TPromise.wrapError<boolean>(err);
});
}
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 });
const readStream = fs.createReadStream(source);
const writeStream = fs.createWriteStream(target, { mode: mode });
let onError = (error: Error) => {
const onError = (error: Error) => {
if (!callbackHandled) {
callbackHandled = true;
callback(error);
@@ -163,7 +157,7 @@ export function del(path: string, tmpFolder: string, callback: (error: Error) =>
return rmRecursive(path, callback);
}
let pathInTemp = paths.join(tmpFolder, uuid.generateUuid());
const 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
@@ -200,7 +194,7 @@ function rmRecursive(path: string, callback: (error: Error) => void): void {
if (err || !stat) {
callback(err);
} else if (!stat.isDirectory() || stat.isSymbolicLink() /* !!! never recurse into links when deleting !!! */) {
let mode = stat.mode;
const mode = stat.mode;
if (!(mode & 128)) { // 128 === 0200
fs.chmod(path, mode | 128, (err: Error) => { // 128 === 0200
if (err) {
@@ -369,6 +363,35 @@ export function writeFileAndFlush(path: string, data: string | NodeBuffer, optio
});
}
export function writeFileAndFlushSync(path: string, data: string | NodeBuffer, options?: { mode?: number; flag?: string; }): void {
if (!canFlush) {
return fs.writeFileSync(path, data, options);
}
if (!options) {
options = { mode: 0o666, flag: 'w' };
}
// Open the file with same flags and mode as fs.writeFile()
const fd = fs.openSync(path, options.flag, options.mode);
try {
// It is valid to pass a fd handle to fs.writeFile() and this will keep the handle open!
fs.writeFileSync(fd, data);
// Flush contents (not metadata) of the file to disk
try {
fs.fdatasyncSync(fd);
} catch (syncError) {
console.warn('[node.js fs] fdatasyncSync is now disabled for this session because it failed: ', syncError);
canFlush = false;
}
} finally {
fs.closeSync(fd);
}
}
/**
* Copied from: https://github.com/Microsoft/vscode-node-debug/blob/master/src/node/pathUtilities.ts#L83
*
@@ -384,7 +407,7 @@ export function realcaseSync(path: string): string {
return path;
}
const name = paths.basename(path).toLowerCase();
const name = (paths.basename(path) /* can be '' for windows drive letters */ || path).toLowerCase();
try {
const entries = readdirSync(dir);
const found = entries.filter(e => e.toLowerCase() === name); // use a case insensitive search
@@ -454,11 +477,14 @@ function normalizePath(path: string): string {
export function watch(path: string, onChange: (type: string, path: string) => void): fs.FSWatcher {
const watcher = fs.watch(path);
watcher.on('change', (type, raw) => {
let file = raw.toString();
if (platform.isMacintosh) {
// Mac: uses NFD unicode form on disk, but we want NFC
// See also https://github.com/nodejs/node/issues/2165
file = strings.normalizeNFC(file);
let file: string = null;
if (raw) { // https://github.com/Microsoft/vscode/issues/38191
file = raw.toString();
if (platform.isMacintosh) {
// Mac: uses NFD unicode form on disk, but we want NFC
// See also https://github.com/nodejs/node/issues/2165
file = strings.normalizeNFC(file);
}
}
onChange(type, file);

View File

@@ -3,8 +3,6 @@
* 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';
@@ -86,17 +84,22 @@ export function getMachineId(): TPromise<string> {
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) {
TPromise.join([import('crypto'), import('getmac')]).then(([crypto, getmac]) => {
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);
}
}, err => {
errors.onUnexpectedError(err);
resolve(undefined);
}
});
});
}

View File

@@ -5,8 +5,6 @@
'use strict';
import streams = require('stream');
import mime = require('vs/base/common/mime');
import { TPromise } from 'vs/base/common/winjs.base';
@@ -76,18 +74,6 @@ 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 {
@@ -117,57 +103,4 @@ export function detectMimeAndEncodingFromBuffer({ buffer, bytesRead }: stream.Re
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;
}
}

View File

@@ -7,7 +7,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import * as extfs from 'vs/base/node/extfs';
import { dirname, join } from 'path';
import { join } from 'path';
import { nfcall, Queue } from 'vs/base/common/async';
import * as fs from 'fs';
import * as os from 'os';
@@ -26,32 +26,7 @@ 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 import mkdirp = extfs.mkdirp;
export function rimraf(path: string): TPromise<void> {
return lstat(path).then(stat => {

View File

@@ -11,24 +11,24 @@ 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 {
export function findFreePort(startPort: number, giveUpAfter: number, timeout: number): Thenable<number> {
let done = false;
const timeoutHandle = setTimeout(() => {
if (!done) {
done = true;
return new Promise(resolve => {
const timeoutHandle = setTimeout(() => {
if (!done) {
done = true;
return resolve(0);
}
}, timeout);
return clb(0);
}
}, timeout);
doFindFreePort(startPort, giveUpAfter, (port) => {
if (!done) {
done = true;
clearTimeout(timeoutHandle);
return clb(port);
}
doFindFreePort(startPort, giveUpAfter, (port) => {
if (!done) {
done = true;
clearTimeout(timeoutHandle);
return resolve(port);
}
});
});
}

View File

@@ -9,7 +9,6 @@ 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';
@@ -28,17 +27,6 @@ export interface LineData {
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:
@@ -212,7 +200,7 @@ export abstract class AbstractProcess<TProgressData> {
cc(result);
};
if (this.shell && Platform.isWindows) {
let options: any = Objects.clone(this.options);
let options: any = Objects.deepClone(this.options);
options.windowsVerbatimArguments = true;
options.detached = false;
let quotedCommand: boolean = false;
@@ -287,7 +275,7 @@ export abstract class AbstractProcess<TProgressData> {
// Default is to do nothing.
}
private static regexp = /^[^"].* .*[^"]/;
private static readonly regexp = /^[^"].* .*[^"]/;
private ensureQuotes(value: string) {
if (AbstractProcess.regexp.test(value)) {
return {
@@ -302,10 +290,6 @@ export abstract class AbstractProcess<TProgressData> {
}
}
public isRunning(): boolean {
return this.childProcessPromise !== null;
}
public get pid(): TPromise<number> {
return this.childProcessPromise.then(childProcess => childProcess.pid, err => -1);
}
@@ -391,60 +375,6 @@ export class LineProcess extends AbstractProcess<LineData> {
}
}
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;
}

View File

@@ -1,100 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { 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): void;
stopProfiling(): Profile;
}
export declare interface Profile {
title: string;
export(callback: (err, data) => void): void;
delete(): void;
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;
}
};

183
src/vs/base/node/ps-win.ps1 Normal file
View File

@@ -0,0 +1,183 @@
################################################################################################
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the Source EULA. See License.txt in the project root for license information.
################################################################################################
Param(
[string]$ProcessName = "code.exe",
[int]$MaxSamples = 10
)
$processLength = "process(".Length
function Get-MachineInfo {
$model = (Get-WmiObject -Class Win32_Processor).Name
$memory = (Get-WmiObject -Class Win32_PhysicalMemory | Measure-Object -Property Capacity -Sum).Sum / 1MB
$wmi_cs = Get-WmiObject -Class Win32_ComputerSystem
return @{
"type" = "machineInfo"
"model" = $model
"processors" = $wmi_cs.NumberOfProcessors
"logicalProcessors" = $wmi_cs.NumberOfLogicalProcessors
"totalMemory" = $memory
}
}
$machineInfo = Get-MachineInfo
function Get-MachineState {
$proc = Get-WmiObject Win32_Processor
$os = Get-WmiObject win32_OperatingSystem
return @{
"type" = 'machineState'
"cpuLoad" = $proc.LoadPercentage
"handles" = (Get-Process | Measure-Object Handles -Sum).Sum
"memory" = @{
"total" = $os.TotalVisibleMemorySize
"free" = $os.FreePhysicalMemory
"swapTotal" = $os.TotalVirtualMemorySize
"swapFree" = $os.FreeVirtualMemory
}
}
}
$machineState = Get-MachineState
$processId2CpuLoad = @{}
function Get-PerformanceCounters ($logicalProcessors) {
$counterError
# In a first round we get the performance counters and the process ids.
$counters = (Get-Counter ("\Process(*)\% Processor Time", "\Process(*)\ID Process") -ErrorAction SilentlyContinue).CounterSamples
$processKey2Id = @{}
foreach ($counter in $counters) {
if ($counter.Status -ne 0) {
continue
}
$path = $counter.path;
$segments = $path.Split("\");
$kind = $segments[4];
$processKey = $segments[3].Substring($processLength, $segments[3].Length - $processLength - 1)
if ($kind -eq "id process") {
$processKey2Id[$processKey] = [uint32]$counter.CookedValue
}
}
foreach ($counter in $counters) {
if ($counter.Status -ne 0) {
continue
}
$path = $counter.path;
$segments = $path.Split("\");
$kind = $segments[4];
$processKey = $segments[3].Substring($processLength, $segments[3].Length - $processLength - 1)
if ($kind -eq "% processor time") {
$array = New-Object double[] ($MaxSamples + 1)
$array[0] = ($counter.CookedValue / $logicalProcessors)
$processId = $processKey2Id[$processKey]
if ($processId) {
$processId2CpuLoad[$processId] = $array
}
}
}
# Now lets sample another 10 times but only the processor time
$samples = Get-Counter "\Process(*)\% Processor Time" -SampleInterval 1 -MaxSamples $MaxSamples -ErrorAction SilentlyContinue
for ($s = 0; $s -lt $samples.Count; $s++) {
$counters = $samples[$s].CounterSamples;
foreach ($counter in $counters) {
if ($counter.Status -ne 0) {
continue
}
$path = $counter.path;
$segments = $path.Split("\");
$processKey = $segments[3].Substring($processLength, $segments[3].Length - $processLength - 1)
$processKey = $processKey2Id[$processKey];
if ($processKey) {
$processId2CpuLoad[$processKey][$s + 1] = ($counter.CookedValue / $logicalProcessors)
}
}
}
}
Get-PerformanceCounters -logicalProcessors $machineInfo.logicalProcessors
$topElements = New-Object PSObject[] $processId2CpuLoad.Keys.Count;
$index = 0;
foreach ($key in $processId2CpuLoad.Keys) {
$obj = [PSCustomObject]@{
ProcessId = $key
Load = ($processId2CpuLoad[$key] | Measure-Object -Sum).Sum / ($MaxSamples + 1)
}
$topElements[$index] = $obj
$index++
}
$topElements = $topElements | Sort-Object Load -Descending
# Get all code processes
$codeProcesses = @{}
foreach ($item in Get-WmiObject Win32_Process -Filter "name = '$ProcessName'") {
$codeProcesses[$item.ProcessId] = $item
}
foreach ($item in Get-WmiObject Win32_Process -Filter "name = 'codeHelper.exe'") {
$codeProcesses[$item.ProcessId] = $item
}
$otherProcesses = @{}
foreach ($item in Get-WmiObject Win32_Process -Filter "name Like '%'") {
if (!($codeProcesses.Contains($item.ProcessId))) {
$otherProcesses[$item.ProcessId] = $item
}
}
$modified = $false
do {
$toDelete = @()
$modified = $false
foreach ($item in $otherProcesses.Values) {
if ($codeProcesses.Contains([uint32]$item.ParentProcessId)) {
$codeProcesses[$item.ProcessId] = $item;
$toDelete += $item
}
}
foreach ($item in $toDelete) {
$otherProcesses.Remove([uint32]$item.ProcessId)
$modified = $true
}
} while ($modified)
$result = New-Object PSObject[] (2 + [math]::Min(5, $topElements.Count) + $codeProcesses.Count)
$result[0] = $machineInfo
$result[1] = $machineState
$index = 2;
for($i = 0; $i -lt 5 -and $i -lt $topElements.Count; $i++) {
$element = $topElements[$i]
$item = $codeProcesses[[uint32]$element.ProcessId]
if (!$item) {
$item = $otherProcesses[[uint32]$element.ProcessId]
}
if ($item) {
$cpuLoad = $processId2CpuLoad[[uint32]$item.ProcessId] | % { [pscustomobject] $_ }
$result[$index] = [pscustomobject]@{
"type" = "topProcess"
"name" = $item.Name
"processId" = $item.ProcessId
"parentProcessId" = $item.ParentProcessId
"commandLine" = $item.CommandLine
"handles" = $item.HandleCount
"cpuLoad" = $cpuLoad
"workingSetSize" = $item.WorkingSetSize
}
$index++
}
}
foreach ($item in $codeProcesses.Values) {
# we need to convert this otherwise to JSON with create a value, count object and not an inline array
$cpuLoad = $processId2CpuLoad[[uint32]$item.ProcessId] | % { [pscustomobject] $_ }
$result[$index] = [pscustomobject]@{
"type" = "processInfo"
"name" = $item.Name
"processId" = $item.ProcessId
"parentProcessId" = $item.ParentProcessId
"commandLine" = $item.CommandLine
"handles" = $item.HandleCount
"cpuLoad" = $cpuLoad
"workingSetSize" = $item.WorkingSetSize
}
$index++
}
$result | ConvertTo-Json -Depth 99

235
src/vs/base/node/ps.ts Normal file
View File

@@ -0,0 +1,235 @@
/*---------------------------------------------------------------------------------------------
* 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 { spawn, exec } from 'child_process';
import * as path from 'path';
import URI from 'vs/base/common/uri';
export interface ProcessItem {
name: string;
cmd: string;
pid: number;
ppid: number;
load: number;
mem: number;
children?: ProcessItem[];
}
export function listProcesses(rootPid: number): Promise<ProcessItem> {
return new Promise((resolve, reject) => {
let rootItem: ProcessItem;
const map = new Map<number, ProcessItem>();
function addToTree(pid: number, ppid: number, cmd: string, load: number, mem: number) {
const parent = map.get(ppid);
if (pid === rootPid || parent) {
const item: ProcessItem = {
name: findName(cmd),
cmd,
pid,
ppid,
load,
mem
};
map.set(pid, item);
if (pid === rootPid) {
rootItem = item;
}
if (parent) {
if (!parent.children) {
parent.children = [];
}
parent.children.push(item);
if (parent.children.length > 1) {
parent.children = parent.children.sort((a, b) => a.pid - b.pid);
}
}
}
}
function findName(cmd: string): string {
const RENDERER_PROCESS_HINT = /--disable-blink-features=Auxclick/;
const WINDOWS_WATCHER_HINT = /\\watcher\\win32\\CodeHelper.exe/;
const TYPE = /--type=([a-zA-Z-]+)/;
// find windows file watcher
if (WINDOWS_WATCHER_HINT.exec(cmd)) {
return 'watcherService';
}
// find "--type=xxxx"
let matches = TYPE.exec(cmd);
if (matches && matches.length === 2) {
if (matches[1] === 'renderer') {
if (!RENDERER_PROCESS_HINT.exec(cmd)) {
return 'shared-process';
}
return `window`;
}
return matches[1];
}
// find all xxxx.js
const JS = /[a-zA-Z-]+\.js/g;
let result = '';
do {
matches = JS.exec(cmd);
if (matches) {
result += matches + ' ';
}
} while (matches);
if (result) {
if (cmd.indexOf('node ') !== 0) {
return `electron_node ${result}`;
}
}
return cmd;
}
if (process.platform === 'win32') {
interface ProcessInfo {
type: 'processInfo';
name: string;
processId: number;
parentProcessId: number;
commandLine: string;
handles: number;
cpuLoad: number[];
workingSetSize: number;
}
interface TopProcess {
type: 'topProcess';
name: string;
processId: number;
parentProcessId: number;
commandLine: string;
handles: number;
cpuLoad: number[];
workingSetSize: number;
}
type Item = ProcessInfo | TopProcess;
const cleanUNCPrefix = (value: string): string => {
if (value.indexOf('\\\\?\\') === 0) {
return value.substr(4);
} else if (value.indexOf('\\??\\') === 0) {
return value.substr(4);
} else if (value.indexOf('"\\\\?\\') === 0) {
return '"' + value.substr(5);
} else if (value.indexOf('"\\??\\') === 0) {
return '"' + value.substr(5);
} else {
return value;
}
};
const execMain = path.basename(process.execPath);
const script = URI.parse(require.toUrl('vs/base/node/ps-win.ps1')).fsPath;
const commandLine = `& {& '${script}' -ProcessName '${execMain}' -MaxSamples 3}`;
const cmd = spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', commandLine]);
let stdout = '';
let stderr = '';
cmd.stdout.on('data', data => {
stdout += data.toString();
});
cmd.stderr.on('data', data => {
stderr += data.toString();
});
cmd.on('exit', () => {
if (stderr.length > 0) {
reject(stderr);
}
let processItems: Map<number, ProcessItem> = new Map();
try {
const items: Item[] = JSON.parse(stdout);
for (const item of items) {
if (item.type === 'processInfo') {
let load = 0;
if (item.cpuLoad) {
for (let value of item.cpuLoad) {
load += value;
}
load = load / item.cpuLoad.length;
} else {
load = -1;
}
let commandLine = cleanUNCPrefix(item.commandLine);
processItems.set(item.processId, {
name: findName(commandLine),
cmd: commandLine,
pid: item.processId,
ppid: item.parentProcessId,
load: load,
mem: item.workingSetSize
});
}
}
rootItem = processItems.get(rootPid);
if (rootItem) {
processItems.forEach(item => {
let parent = processItems.get(item.ppid);
if (parent) {
if (!parent.children) {
parent.children = [];
}
parent.children.push(item);
}
});
processItems.forEach(item => {
if (item.children) {
item.children = item.children.sort((a, b) => a.pid - b.pid);
}
});
resolve(rootItem);
} else {
reject(new Error(`Root process ${rootPid} not found`));
}
} catch (error) {
reject(error);
}
});
} else { // OS X & Linux
const CMD = 'ps -ax -o pid=,ppid=,pcpu=,pmem=,command=';
const PID_CMD = /^\s*([0-9]+)\s+([0-9]+)\s+([0-9]+\.[0-9]+)\s+([0-9]+\.[0-9]+)\s+(.+)$/;
exec(CMD, { maxBuffer: 1000 * 1024 }, (err, stdout, stderr) => {
if (err || stderr) {
reject(err || stderr.toString());
} else {
const lines = stdout.toString().split('\n');
for (const line of lines) {
let matches = PID_CMD.exec(line.trim());
if (matches && matches.length === 6) {
addToTree(parseInt(matches[1]), parseInt(matches[2]), matches[5], parseFloat(matches[3]), parseFloat(matches[4]));
}
}
resolve(rootItem);
}
});
}
});
}

View File

@@ -1,39 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { 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): void;
export function ticks(): Tick[];
export function tick(name: string): Tick;
export function setProfileList(names: string[]): void;
export function disable(): void;

View File

@@ -1,128 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
/*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;
});

151
src/vs/base/node/stats.ts Normal file
View File

@@ -0,0 +1,151 @@
/*---------------------------------------------------------------------------------------------
* 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 { readdirSync, statSync, existsSync, readFileSync } from 'fs';
import { join } from 'path';
export interface WorkspaceStatItem {
name: string;
count: number;
}
export interface WorkspaceStats {
fileTypes: WorkspaceStatItem[];
configFiles: WorkspaceStatItem[];
fileCount: number;
maxFilesReached: boolean;
}
function asSortedItems(map: Map<string, number>): WorkspaceStatItem[] {
let a: WorkspaceStatItem[] = [];
map.forEach((value, index) => a.push({ name: index, count: value }));
return a.sort((a, b) => b.count - a.count);
}
export function collectLaunchConfigs(folder: string): WorkspaceStatItem[] {
let launchConfigs = new Map<string, number>();
let launchConfig = join(folder, '.vscode', 'launch.json');
if (existsSync(launchConfig)) {
try {
const contents = readFileSync(launchConfig).toString();
const json = JSON.parse(contents);
if (json['configurations']) {
for (const each of json['configurations']) {
const type = each['type'];
if (type) {
if (launchConfigs.has(type)) {
launchConfigs.set(type, launchConfigs.get(type) + 1);
}
else {
launchConfigs.set(type, 1);
}
}
}
}
} catch {
}
}
return asSortedItems(launchConfigs);
}
export function collectWorkspaceStats(folder: string, filter: string[]): WorkspaceStats {
const configFilePatterns = [
{ 'tag': 'grunt.js', 'pattern': /^gruntfile\.js$/i },
{ 'tag': 'gulp.js', 'pattern': /^gulpfile\.js$/i },
{ 'tag': 'tsconfig.json', 'pattern': /^tsconfig\.json$/i },
{ 'tag': 'package.json', 'pattern': /^package\.json$/i },
{ 'tag': 'jsconfig.json', 'pattern': /^jsconfig\.json$/i },
{ 'tag': 'tslint.json', 'pattern': /^tslint\.json$/i },
{ 'tag': 'eslint.json', 'pattern': /^eslint\.json$/i },
{ 'tag': 'tasks.json', 'pattern': /^tasks\.json$/i },
{ 'tag': 'launch.json', 'pattern': /^launch\.json$/i },
{ 'tag': 'settings.json', 'pattern': /^settings\.json$/i },
{ 'tag': 'webpack.config.js', 'pattern': /^webpack\.config\.js$/i },
{ 'tag': 'project.json', 'pattern': /^project\.json$/i },
{ 'tag': 'makefile', 'pattern': /^makefile$/i },
{ 'tag': 'sln', 'pattern': /^.+\.sln$/i },
{ 'tag': 'csproj', 'pattern': /^.+\.csproj$/i },
{ 'tag': 'cmake', 'pattern': /^.+\.cmake$/i }
];
let fileTypes = new Map<string, number>();
let configFiles = new Map<string, number>();
const MAX_FILES = 20000;
let walkSync = (dir: string, acceptFile: (fileName: string) => void, filter: string[], token) => {
if (token.maxReached) {
return;
}
try {
let files = readdirSync(dir);
for (const file of files) {
try {
if (statSync(join(dir, file)).isDirectory()) {
if (filter.indexOf(file) === -1) {
walkSync(join(dir, file), acceptFile, filter, token);
}
}
else {
if (token.count++ >= MAX_FILES) {
token.maxReached = true;
return;
}
acceptFile(file);
}
} catch {
// skip over files for which stat fails
}
}
} catch {
// skip over folders that cannot be read
}
};
let addFileType = (fileType: string) => {
if (fileTypes.has(fileType)) {
fileTypes.set(fileType, fileTypes.get(fileType) + 1);
}
else {
fileTypes.set(fileType, 1);
}
};
let addConfigFiles = (fileName: string) => {
for (const each of configFilePatterns) {
if (each.pattern.test(fileName)) {
if (configFiles.has(each.tag)) {
configFiles.set(each.tag, configFiles.get(each.tag) + 1);
} else {
configFiles.set(each.tag, 1);
}
}
}
};
let acceptFile = (name: string) => {
if (name.lastIndexOf('.') >= 0) {
let suffix: string | undefined = name.split('.').pop();
if (suffix) {
addFileType(suffix);
}
}
addConfigFiles(name);
};
let token: { count: number, maxReached: boolean } = { count: 0, maxReached: false };
walkSync(folder, acceptFile, filter, token);
return {
configFiles: asSortedItems(configFiles),
fileTypes: asSortedItems(fileTypes),
fileCount: token.count,
maxFilesReached: token.maxReached
};
}

View File

@@ -6,7 +6,6 @@
'use strict';
import fs = require('fs');
import stream = require('stream');
import { TPromise } from 'vs/base/common/winjs.base';
@@ -15,43 +14,6 @@ export interface ReadResult {
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.
*/
@@ -63,7 +25,7 @@ export function readExactlyByFile(file: string, totalBytes: number): TPromise<Re
}
function end(err: Error, resultBuffer: NodeBuffer, bytesRead: number): void {
fs.close(fd, (closeError: Error) => {
fs.close(fd, closeError => {
if (closeError) {
return error(closeError);
}
@@ -76,35 +38,30 @@ export function readExactlyByFile(file: string, totalBytes: number): TPromise<Re
});
}
let buffer = new Buffer(totalBytes);
let bytesRead = 0;
let zeroAttempts = 0;
function loop(): void {
fs.read(fd, buffer, bytesRead, totalBytes - bytesRead, null, (err, moreBytesRead) => {
const buffer = new Buffer(totalBytes);
let offset = 0;
function readChunk(): void {
fs.read(fd, buffer, offset, totalBytes - offset, null, (err, bytesRead) => {
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();
if (bytesRead === 0) {
return end(null, buffer, offset);
}
bytesRead += moreBytesRead;
offset += bytesRead;
if (bytesRead === totalBytes) {
return end(null, buffer, bytesRead);
if (offset === totalBytes) {
return end(null, buffer, offset);
}
return loop();
return readChunk();
});
}
loop();
readChunk();
});
});
}
@@ -126,7 +83,7 @@ export function readToMatchingString(file: string, matchingString: string, chunk
}
function end(err: Error, result: string): void {
fs.close(fd, (closeError: Error) => {
fs.close(fd, closeError => {
if (closeError) {
return error(closeError);
}
@@ -140,39 +97,34 @@ export function readToMatchingString(file: string, matchingString: string, chunk
}
let buffer = new Buffer(maximumBytesToRead);
let bytesRead = 0;
let zeroAttempts = 0;
function loop(): void {
fs.read(fd, buffer, bytesRead, chunkBytes, null, (err, moreBytesRead) => {
let offset = 0;
function readChunk(): void {
fs.read(fd, buffer, offset, chunkBytes, null, (err, bytesRead) => {
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();
if (bytesRead === 0) {
return end(null, null);
}
bytesRead += moreBytesRead;
offset += bytesRead;
const newLineIndex = buffer.indexOf(matchingString);
if (newLineIndex >= 0) {
return end(null, buffer.toString('utf8').substr(0, newLineIndex));
}
if (bytesRead >= maximumBytesToRead) {
if (offset >= maximumBytesToRead) {
return end(new Error(`Could not find ${matchingString} in first ${maximumBytesToRead} bytes of ${file}`), null);
}
return loop();
return readChunk();
});
}
loop();
readChunk();
})
);
}