Squashed 'lib/vscode/' content from commit e5a624b788
git-subtree-dir: lib/vscode git-subtree-split: e5a624b788d92b8d34d1392e4c4d9789406efe8f
This commit is contained in:
64
src/vs/base/node/cpuUsage.sh
Executable file
64
src/vs/base/node/cpuUsage.sh
Executable file
@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
|
||||
function get_total_cpu_time() {
|
||||
# Read the first line of /proc/stat and remove the cpu prefix
|
||||
CPU=(`sed -n 's/^cpu\s//p' /proc/stat`)
|
||||
|
||||
# Sum all of the values in CPU to get total time
|
||||
for VALUE in "${CPU[@]}"; do
|
||||
let $1=$1+$VALUE
|
||||
done
|
||||
}
|
||||
|
||||
TOTAL_TIME_BEFORE=0
|
||||
get_total_cpu_time TOTAL_TIME_BEFORE
|
||||
|
||||
# Loop over the arguments, which are a list of PIDs
|
||||
# The 13th and 14th words in /proc/<PID>/stat are the user and system time
|
||||
# the process has used, so sum these to get total process run time
|
||||
declare -a PROCESS_BEFORE_TIMES
|
||||
ITER=0
|
||||
for PID in "$@"; do
|
||||
if [ -f /proc/$PID/stat ]
|
||||
then
|
||||
PROCESS_STATS=`cat /proc/$PID/stat`
|
||||
PROCESS_STAT_ARRAY=($PROCESS_STATS)
|
||||
|
||||
let PROCESS_TIME_BEFORE="${PROCESS_STAT_ARRAY[13]}+${PROCESS_STAT_ARRAY[14]}"
|
||||
else
|
||||
let PROCESS_TIME_BEFORE=0
|
||||
fi
|
||||
|
||||
PROCESS_BEFORE_TIMES[$ITER]=$PROCESS_TIME_BEFORE
|
||||
((++ITER))
|
||||
done
|
||||
|
||||
# Wait for a second
|
||||
sleep 1
|
||||
|
||||
TOTAL_TIME_AFTER=0
|
||||
get_total_cpu_time TOTAL_TIME_AFTER
|
||||
|
||||
# Check the user and system time sum of each process again and compute the change
|
||||
# in process time used over total system time
|
||||
ITER=0
|
||||
for PID in "$@"; do
|
||||
if [ -f /proc/$PID/stat ]
|
||||
then
|
||||
PROCESS_STATS=`cat /proc/$PID/stat`
|
||||
PROCESS_STAT_ARRAY=($PROCESS_STATS)
|
||||
|
||||
let PROCESS_TIME_AFTER="${PROCESS_STAT_ARRAY[13]}+${PROCESS_STAT_ARRAY[14]}"
|
||||
else
|
||||
let PROCESS_TIME_AFTER=${PROCESS_BEFORE_TIMES[$ITER]}
|
||||
fi
|
||||
|
||||
PROCESS_TIME_BEFORE=${PROCESS_BEFORE_TIMES[$ITER]}
|
||||
let PROCESS_DELTA=$PROCESS_TIME_AFTER-$PROCESS_TIME_BEFORE
|
||||
let TOTAL_DELTA=$TOTAL_TIME_AFTER-$TOTAL_TIME_BEFORE
|
||||
CPU_USAGE=`echo "$((100*$PROCESS_DELTA/$TOTAL_DELTA))"`
|
||||
|
||||
# Parent script reads from stdout, so echo result to be read
|
||||
echo $CPU_USAGE
|
||||
((++ITER))
|
||||
done
|
38
src/vs/base/node/crypto.ts
Normal file
38
src/vs/base/node/crypto.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as crypto from 'crypto';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
|
||||
export async function checksum(path: string, sha1hash: string | undefined): Promise<void> {
|
||||
const checksumPromise = new Promise<string | undefined>((resolve, reject) => {
|
||||
const input = fs.createReadStream(path);
|
||||
const hash = crypto.createHash('sha1');
|
||||
input.pipe(hash);
|
||||
|
||||
const done = once((err?: Error, result?: string) => {
|
||||
input.removeAllListeners();
|
||||
hash.removeAllListeners();
|
||||
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
|
||||
input.once('error', done);
|
||||
input.once('end', done);
|
||||
hash.once('error', done);
|
||||
hash.once('data', (data: Buffer) => done(undefined, data.toString('hex')));
|
||||
});
|
||||
|
||||
const hash = await checksumPromise;
|
||||
|
||||
if (hash !== sha1hash) {
|
||||
throw new Error('Hash mismatch');
|
||||
}
|
||||
}
|
62
src/vs/base/node/decoder.ts
Normal file
62
src/vs/base/node/decoder.ts
Normal file
@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as sd from '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.StringDecoder;
|
||||
private remaining: string | null;
|
||||
|
||||
constructor(encoding: string = 'utf8') {
|
||||
this.stringDecoder = new sd.StringDecoder(encoding);
|
||||
this.remaining = null;
|
||||
}
|
||||
|
||||
write(buffer: Buffer): string[] {
|
||||
const result: string[] = [];
|
||||
const 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) {
|
||||
const 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;
|
||||
}
|
||||
|
||||
end(): string | null {
|
||||
return this.remaining;
|
||||
}
|
||||
}
|
91
src/vs/base/node/extpath.ts
Normal file
91
src/vs/base/node/extpath.ts
Normal file
@ -0,0 +1,91 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { rtrim } from 'vs/base/common/strings';
|
||||
import { sep, join, normalize, dirname, basename } from 'vs/base/common/path';
|
||||
import { readdirSync } from 'vs/base/node/pfs';
|
||||
import { promisify } from 'util';
|
||||
|
||||
/**
|
||||
* 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 | null {
|
||||
const dir = dirname(path);
|
||||
if (path === dir) { // end recursion
|
||||
return path;
|
||||
}
|
||||
|
||||
const name = (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
|
||||
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 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 join(prefix, found[ix]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// silently ignore error
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function realpath(path: string): Promise<string> {
|
||||
try {
|
||||
return await promisify(fs.realpath)(path);
|
||||
} catch (error) {
|
||||
|
||||
// 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);
|
||||
|
||||
await promisify(fs.access)(normalizedPath, fs.constants.R_OK);
|
||||
|
||||
return normalizedPath;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePath(path: string): string {
|
||||
return rtrim(normalize(path), sep);
|
||||
}
|
101
src/vs/base/node/id.ts
Normal file
101
src/vs/base/node/id.ts
Normal file
@ -0,0 +1,101 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
import { networkInterfaces } from 'os';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { getMac } from 'vs/base/node/macAddress';
|
||||
|
||||
// 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
|
||||
// Parallels 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?: TernarySearchTree<string, boolean>;
|
||||
private _value?: number;
|
||||
|
||||
private _isVirtualMachineMacAdress(mac: string): boolean {
|
||||
if (!this._virtualMachineOUIs) {
|
||||
this._virtualMachineOUIs = TernarySearchTree.forStrings<boolean>();
|
||||
|
||||
// dash-separated
|
||||
this._virtualMachineOUIs.set('00-50-56', true);
|
||||
this._virtualMachineOUIs.set('00-0C-29', true);
|
||||
this._virtualMachineOUIs.set('00-05-69', true);
|
||||
this._virtualMachineOUIs.set('00-03-FF', true);
|
||||
this._virtualMachineOUIs.set('00-1C-42', true);
|
||||
this._virtualMachineOUIs.set('00-16-3E', true);
|
||||
this._virtualMachineOUIs.set('08-00-27', true);
|
||||
|
||||
// colon-separated
|
||||
this._virtualMachineOUIs.set('00:50:56', true);
|
||||
this._virtualMachineOUIs.set('00:0C:29', true);
|
||||
this._virtualMachineOUIs.set('00:05:69', true);
|
||||
this._virtualMachineOUIs.set('00:03:FF', true);
|
||||
this._virtualMachineOUIs.set('00:1C:42', true);
|
||||
this._virtualMachineOUIs.set('00:16:3E', true);
|
||||
this._virtualMachineOUIs.set('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: Promise<string>;
|
||||
export async function getMachineId(): Promise<string> {
|
||||
if (!machineId) {
|
||||
machineId = (async () => {
|
||||
const id = await getMacMachineId();
|
||||
|
||||
return id || uuid.generateUuid(); // fallback, generate a UUID
|
||||
})();
|
||||
}
|
||||
|
||||
return machineId;
|
||||
}
|
||||
|
||||
async function getMacMachineId(): Promise<string | undefined> {
|
||||
try {
|
||||
const crypto = await import('crypto');
|
||||
const macAddress = await getMac();
|
||||
return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex');
|
||||
} catch (err) {
|
||||
errors.onUnexpectedError(err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
24
src/vs/base/node/languagePacks.d.ts
vendored
Normal file
24
src/vs/base/node/languagePacks.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface NLSConfiguration {
|
||||
locale: string;
|
||||
availableLanguages: {
|
||||
[key: string]: string;
|
||||
};
|
||||
pseudo?: boolean;
|
||||
_languagePackSupport?: boolean;
|
||||
}
|
||||
|
||||
export interface InternalNLSConfiguration extends NLSConfiguration {
|
||||
_languagePackId: string;
|
||||
_translationsConfigFile: string;
|
||||
_cacheRoot: string;
|
||||
_resolvedLanguagePackCoreLocation: string;
|
||||
_corruptedFile: string;
|
||||
_languagePackSupport?: boolean;
|
||||
}
|
||||
|
||||
export function getNLSConfiguration(commit: string, userDataPath: string, metaDataFile: string, locale: string): Promise<NLSConfiguration>;
|
314
src/vs/base/node/languagePacks.js
Normal file
314
src/vs/base/node/languagePacks.js
Normal file
@ -0,0 +1,314 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* @param {NodeRequire} nodeRequire
|
||||
* @param {typeof import('path')} path
|
||||
* @param {typeof import('fs')} fs
|
||||
* @param {typeof import('../common/performance')} perf
|
||||
*/
|
||||
function factory(nodeRequire, path, fs, perf) {
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
function exists(file) {
|
||||
return new Promise(c => fs.exists(file, c));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function touch(file) {
|
||||
return new Promise((c, e) => { const d = new Date(); fs.utimes(file, d, d, err => err ? e(err) : c()); });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
function lstat(file) {
|
||||
return new Promise((c, e) => fs.lstat(file, (err, stats) => err ? e(err) : c(stats)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dir
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
function readdir(dir) {
|
||||
return new Promise((c, e) => fs.readdir(dir, (err, files) => err ? e(err) : c(files)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dir
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
function mkdirp(dir) {
|
||||
return new Promise((c, e) => fs.mkdir(dir, { recursive: true }, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} dir
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function rmdir(dir) {
|
||||
return new Promise((c, e) => fs.rmdir(dir, err => err ? e(err) : c(undefined)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function unlink(file) {
|
||||
return new Promise((c, e) => fs.unlink(file, err => err ? e(err) : c(undefined)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} location
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function rimraf(location) {
|
||||
return lstat(location).then(stat => {
|
||||
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
||||
return readdir(location)
|
||||
.then(children => Promise.all(children.map(child => rimraf(path.join(location, child)))))
|
||||
.then(() => rmdir(location));
|
||||
} else {
|
||||
return unlink(location);
|
||||
}
|
||||
}, err => {
|
||||
if (err.code === 'ENOENT') {
|
||||
return undefined;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function readFile(file) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
fs.readFile(file, 'utf8', function (err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file
|
||||
* @param {string} content
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function writeFile(file, content) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
fs.writeFile(file, content, 'utf8', function (err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} userDataPath
|
||||
* @returns {object}
|
||||
*/
|
||||
function getLanguagePackConfigurations(userDataPath) {
|
||||
const configFile = path.join(userDataPath, 'languagepacks.json');
|
||||
try {
|
||||
return nodeRequire(configFile);
|
||||
} catch (err) {
|
||||
// Do nothing. If we can't read the file we have no
|
||||
// language pack config.
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} config
|
||||
* @param {string} locale
|
||||
*/
|
||||
function resolveLanguagePackLocale(config, locale) {
|
||||
try {
|
||||
while (locale) {
|
||||
if (config[locale]) {
|
||||
return locale;
|
||||
} else {
|
||||
const index = locale.lastIndexOf('-');
|
||||
if (index > 0) {
|
||||
locale = locale.substring(0, index);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Resolving language pack configuration failed.', err);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} commit
|
||||
* @param {string} userDataPath
|
||||
* @param {string} metaDataFile
|
||||
* @param {string} locale
|
||||
*/
|
||||
function getNLSConfiguration(commit, userDataPath, metaDataFile, locale) {
|
||||
if (locale === 'pseudo') {
|
||||
return Promise.resolve({ locale: locale, availableLanguages: {}, pseudo: true });
|
||||
}
|
||||
|
||||
if (process.env['VSCODE_DEV']) {
|
||||
return Promise.resolve({ locale: locale, availableLanguages: {} });
|
||||
}
|
||||
|
||||
// We have a built version so we have extracted nls file. Try to find
|
||||
// the right file to use.
|
||||
|
||||
// Check if we have an English or English US locale. If so fall to default since that is our
|
||||
// English translation (we don't ship *.nls.en.json files)
|
||||
if (locale && (locale === 'en' || locale === 'en-us')) {
|
||||
return Promise.resolve({ locale: locale, availableLanguages: {} });
|
||||
}
|
||||
|
||||
const initialLocale = locale;
|
||||
|
||||
perf.mark('nlsGeneration:start');
|
||||
|
||||
const defaultResult = function (locale) {
|
||||
perf.mark('nlsGeneration:end');
|
||||
return Promise.resolve({ locale: locale, availableLanguages: {} });
|
||||
};
|
||||
try {
|
||||
if (!commit) {
|
||||
return defaultResult(initialLocale);
|
||||
}
|
||||
const configs = getLanguagePackConfigurations(userDataPath);
|
||||
if (!configs) {
|
||||
return defaultResult(initialLocale);
|
||||
}
|
||||
locale = resolveLanguagePackLocale(configs, locale);
|
||||
if (!locale) {
|
||||
return defaultResult(initialLocale);
|
||||
}
|
||||
const packConfig = configs[locale];
|
||||
let mainPack;
|
||||
if (!packConfig || typeof packConfig.hash !== 'string' || !packConfig.translations || typeof (mainPack = packConfig.translations['vscode']) !== 'string') {
|
||||
return defaultResult(initialLocale);
|
||||
}
|
||||
return exists(mainPack).then(fileExists => {
|
||||
if (!fileExists) {
|
||||
return defaultResult(initialLocale);
|
||||
}
|
||||
const packId = packConfig.hash + '.' + locale;
|
||||
const cacheRoot = path.join(userDataPath, 'clp', packId);
|
||||
const coreLocation = path.join(cacheRoot, commit);
|
||||
const translationsConfigFile = path.join(cacheRoot, 'tcf.json');
|
||||
const corruptedFile = path.join(cacheRoot, 'corrupted.info');
|
||||
const result = {
|
||||
locale: initialLocale,
|
||||
availableLanguages: { '*': locale },
|
||||
_languagePackId: packId,
|
||||
_translationsConfigFile: translationsConfigFile,
|
||||
_cacheRoot: cacheRoot,
|
||||
_resolvedLanguagePackCoreLocation: coreLocation,
|
||||
_corruptedFile: corruptedFile
|
||||
};
|
||||
return exists(corruptedFile).then(corrupted => {
|
||||
// The nls cache directory is corrupted.
|
||||
let toDelete;
|
||||
if (corrupted) {
|
||||
toDelete = rimraf(cacheRoot);
|
||||
} else {
|
||||
toDelete = Promise.resolve(undefined);
|
||||
}
|
||||
return toDelete.then(() => {
|
||||
return exists(coreLocation).then(fileExists => {
|
||||
if (fileExists) {
|
||||
// We don't wait for this. No big harm if we can't touch
|
||||
touch(coreLocation).catch(() => { });
|
||||
perf.mark('nlsGeneration:end');
|
||||
return result;
|
||||
}
|
||||
return mkdirp(coreLocation).then(() => {
|
||||
return Promise.all([readFile(metaDataFile), readFile(mainPack)]);
|
||||
}).then(values => {
|
||||
const metadata = JSON.parse(values[0]);
|
||||
const packData = JSON.parse(values[1]).contents;
|
||||
const bundles = Object.keys(metadata.bundles);
|
||||
const writes = [];
|
||||
for (const bundle of bundles) {
|
||||
const modules = metadata.bundles[bundle];
|
||||
const target = Object.create(null);
|
||||
for (const module of modules) {
|
||||
const keys = metadata.keys[module];
|
||||
const defaultMessages = metadata.messages[module];
|
||||
const translations = packData[module];
|
||||
let targetStrings;
|
||||
if (translations) {
|
||||
targetStrings = [];
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const elem = keys[i];
|
||||
const key = typeof elem === 'string' ? elem : elem.key;
|
||||
let translatedMessage = translations[key];
|
||||
if (translatedMessage === undefined) {
|
||||
translatedMessage = defaultMessages[i];
|
||||
}
|
||||
targetStrings.push(translatedMessage);
|
||||
}
|
||||
} else {
|
||||
targetStrings = defaultMessages;
|
||||
}
|
||||
target[module] = targetStrings;
|
||||
}
|
||||
writes.push(writeFile(path.join(coreLocation, bundle.replace(/\//g, '!') + '.nls.json'), JSON.stringify(target)));
|
||||
}
|
||||
writes.push(writeFile(translationsConfigFile, JSON.stringify(packConfig.translations)));
|
||||
return Promise.all(writes);
|
||||
}).then(() => {
|
||||
perf.mark('nlsGeneration:end');
|
||||
return result;
|
||||
}).catch(err => {
|
||||
console.error('Generating translation files failed.', err);
|
||||
return defaultResult(locale);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Generating translation files failed.', err);
|
||||
return defaultResult(locale);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getNLSConfiguration
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (typeof define === 'function') {
|
||||
// amd
|
||||
define(['path', 'fs', 'vs/base/common/performance'], function (path, fs, perf) { return factory(require.__$__nodeRequire, path, fs, perf); });
|
||||
} else if (typeof module === 'object' && typeof module.exports === 'object') {
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const perf = require('../common/performance');
|
||||
module.exports = factory(require, path, fs, perf);
|
||||
} else {
|
||||
throw new Error('Unknown context');
|
||||
}
|
50
src/vs/base/node/macAddress.ts
Normal file
50
src/vs/base/node/macAddress.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { networkInterfaces } from 'os';
|
||||
|
||||
const invalidMacAddresses = new Set([
|
||||
'00:00:00:00:00:00',
|
||||
'ff:ff:ff:ff:ff:ff',
|
||||
'ac:de:48:00:11:22'
|
||||
]);
|
||||
|
||||
function validateMacAddress(candidate: string): boolean {
|
||||
const tempCandidate = candidate.replace(/\-/g, ':').toLowerCase();
|
||||
return !invalidMacAddresses.has(tempCandidate);
|
||||
}
|
||||
|
||||
export function getMac(): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject('Unable to retrieve mac address (timeout after 10s)'), 10000);
|
||||
|
||||
try {
|
||||
resolve(await doGetMac());
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doGetMac(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const ifaces = networkInterfaces();
|
||||
for (const [, infos] of Object.entries(ifaces)) {
|
||||
for (const info of infos) {
|
||||
if (validateMacAddress(info.mac)) {
|
||||
return resolve(info.mac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reject('Unable to retrieve mac address (unexpected format)');
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
16
src/vs/base/node/paths.ts
Normal file
16
src/vs/base/node/paths.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
|
||||
interface IPaths {
|
||||
getAppDataPath(platform: string): string;
|
||||
getDefaultUserDataPath(platform: string): string;
|
||||
}
|
||||
|
||||
const pathsPath = FileAccess.asFileUri('paths', require).fsPath;
|
||||
const paths = require.__$__nodeRequire<IPaths>(pathsPath);
|
||||
export const getAppDataPath = paths.getAppDataPath;
|
||||
export const getDefaultUserDataPath = paths.getDefaultUserDataPath;
|
559
src/vs/base/node/pfs.ts
Normal file
559
src/vs/base/node/pfs.ts
Normal file
@ -0,0 +1,559 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { 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 { Event } from 'vs/base/common/event';
|
||||
import { promisify } from 'util';
|
||||
import { isRootOrDriveLetter } from 'vs/base/common/extpath';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
|
||||
// See https://github.com/microsoft/vscode/issues/30180
|
||||
const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB
|
||||
const GENERAL_MAX_FILE_SIZE = 16 * 1024 * 1024 * 1024; // 16 GB
|
||||
|
||||
// See https://github.com/v8/v8/blob/5918a23a3d571b9625e5cce246bdd5b46ff7cd8b/src/heap/heap.cc#L149
|
||||
const WIN32_MAX_HEAP_SIZE = 700 * 1024 * 1024; // 700 MB
|
||||
const GENERAL_MAX_HEAP_SIZE = 700 * 2 * 1024 * 1024; // 1400 MB
|
||||
|
||||
export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE;
|
||||
export const MAX_HEAP_SIZE = process.arch === 'ia32' ? WIN32_MAX_HEAP_SIZE : GENERAL_MAX_HEAP_SIZE;
|
||||
|
||||
export enum RimRafMode {
|
||||
|
||||
/**
|
||||
* Slow version that unlinks each file and folder.
|
||||
*/
|
||||
UNLINK,
|
||||
|
||||
/**
|
||||
* Fast version that first moves the file/folder
|
||||
* into a temp directory and then deletes that
|
||||
* without waiting for it.
|
||||
*/
|
||||
MOVE
|
||||
}
|
||||
|
||||
export async function rimraf(path: string, mode = RimRafMode.UNLINK): Promise<void> {
|
||||
if (isRootOrDriveLetter(path)) {
|
||||
throw new Error('rimraf - will refuse to recursively delete root');
|
||||
}
|
||||
|
||||
// delete: via unlink
|
||||
if (mode === RimRafMode.UNLINK) {
|
||||
return rimrafUnlink(path);
|
||||
}
|
||||
|
||||
// delete: via move
|
||||
return rimrafMove(path);
|
||||
}
|
||||
|
||||
async function rimrafUnlink(path: string): Promise<void> {
|
||||
try {
|
||||
const stat = await lstat(path);
|
||||
|
||||
// Folder delete (recursive) - NOT for symbolic links though!
|
||||
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
||||
|
||||
// Children
|
||||
const children = await readdir(path);
|
||||
await Promise.all(children.map(child => rimrafUnlink(join(path, child))));
|
||||
|
||||
// Folder
|
||||
await promisify(fs.rmdir)(path);
|
||||
}
|
||||
|
||||
// Single file delete
|
||||
else {
|
||||
|
||||
// chmod as needed to allow for unlink
|
||||
const mode = stat.mode;
|
||||
if (!(mode & 128)) { // 128 === 0200
|
||||
await chmod(path, mode | 128);
|
||||
}
|
||||
|
||||
return unlink(path);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function rimrafMove(path: string): Promise<void> {
|
||||
try {
|
||||
const pathInTemp = join(os.tmpdir(), generateUuid());
|
||||
try {
|
||||
await rename(path, pathInTemp);
|
||||
} catch (error) {
|
||||
return rimrafUnlink(path); // if rename fails, delete without tmp dir
|
||||
}
|
||||
|
||||
// Delete but do not return as promise
|
||||
rimrafUnlink(pathInTemp);
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function rimrafSync(path: string): void {
|
||||
if (isRootOrDriveLetter(path)) {
|
||||
throw new Error('rimraf - will refuse to recursively delete root');
|
||||
}
|
||||
|
||||
try {
|
||||
const stat = fs.lstatSync(path);
|
||||
|
||||
// Folder delete (recursive) - NOT for symbolic links though!
|
||||
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
||||
|
||||
// Children
|
||||
const children = readdirSync(path);
|
||||
children.map(child => rimrafSync(join(path, child)));
|
||||
|
||||
// Folder
|
||||
fs.rmdirSync(path);
|
||||
}
|
||||
|
||||
// Single file delete
|
||||
else {
|
||||
|
||||
// chmod as needed to allow for unlink
|
||||
const mode = stat.mode;
|
||||
if (!(mode & 128)) { // 128 === 0200
|
||||
fs.chmodSync(path, mode | 128);
|
||||
}
|
||||
|
||||
return fs.unlinkSync(path);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function readdir(path: string): Promise<string[]> {
|
||||
return handleDirectoryChildren(await promisify(fs.readdir)(path));
|
||||
}
|
||||
|
||||
export async function readdirWithFileTypes(path: string): Promise<fs.Dirent[]> {
|
||||
const children = await promisify(fs.readdir)(path, { withFileTypes: true });
|
||||
|
||||
// Mac: uses NFD unicode form on disk, but we want NFC
|
||||
// See also https://github.com/nodejs/node/issues/2165
|
||||
if (platform.isMacintosh) {
|
||||
for (const child of children) {
|
||||
child.name = normalizeNFC(child.name);
|
||||
}
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
export function readdirSync(path: string): string[] {
|
||||
return handleDirectoryChildren(fs.readdirSync(path));
|
||||
}
|
||||
|
||||
function handleDirectoryChildren(children: 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 children.map(child => normalizeNFC(child));
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
export function exists(path: string): Promise<boolean> {
|
||||
return promisify(fs.exists)(path);
|
||||
}
|
||||
|
||||
export function chmod(path: string, mode: number): Promise<void> {
|
||||
return promisify(fs.chmod)(path, mode);
|
||||
}
|
||||
|
||||
export function stat(path: string): Promise<fs.Stats> {
|
||||
return promisify(fs.stat)(path);
|
||||
}
|
||||
|
||||
export interface IStatAndLink {
|
||||
|
||||
// The stats of the file. If the file is a symbolic
|
||||
// link, the stats will be of that target file and
|
||||
// not the link itself.
|
||||
// If the file is a symbolic link pointing to a non
|
||||
// existing file, the stat will be of the link and
|
||||
// the `dangling` flag will indicate this.
|
||||
stat: fs.Stats;
|
||||
|
||||
// Will be provided if the resource is a symbolic link
|
||||
// on disk. Use the `dangling` flag to find out if it
|
||||
// points to a resource that does not exist on disk.
|
||||
symbolicLink?: { dangling: boolean };
|
||||
}
|
||||
|
||||
export async function statLink(path: string): Promise<IStatAndLink> {
|
||||
|
||||
// First stat the link
|
||||
let lstats: fs.Stats | undefined;
|
||||
try {
|
||||
lstats = await lstat(path);
|
||||
|
||||
// Return early if the stat is not a symbolic link at all
|
||||
if (!lstats.isSymbolicLink()) {
|
||||
return { stat: lstats };
|
||||
}
|
||||
} catch (error) {
|
||||
/* ignore - use stat() instead */
|
||||
}
|
||||
|
||||
// If the stat is a symbolic link or failed to stat, use fs.stat()
|
||||
// which for symbolic links will stat the target they point to
|
||||
try {
|
||||
const stats = await stat(path);
|
||||
|
||||
return { stat: stats, symbolicLink: lstats?.isSymbolicLink() ? { dangling: false } : undefined };
|
||||
} catch (error) {
|
||||
|
||||
// If the link points to a non-existing file we still want
|
||||
// to return it as result while setting dangling: true flag
|
||||
if (error.code === 'ENOENT' && lstats) {
|
||||
return { stat: lstats, symbolicLink: { dangling: true } };
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function lstat(path: string): Promise<fs.Stats> {
|
||||
return promisify(fs.lstat)(path);
|
||||
}
|
||||
|
||||
export function rename(oldPath: string, newPath: string): Promise<void> {
|
||||
return promisify(fs.rename)(oldPath, newPath);
|
||||
}
|
||||
|
||||
export function renameIgnoreError(oldPath: string, newPath: string): Promise<void> {
|
||||
return new Promise(resolve => fs.rename(oldPath, newPath, () => resolve()));
|
||||
}
|
||||
|
||||
export function unlink(path: string): Promise<void> {
|
||||
return promisify(fs.unlink)(path);
|
||||
}
|
||||
|
||||
export function symlink(target: string, path: string, type?: string): Promise<void> {
|
||||
return promisify(fs.symlink)(target, path, type);
|
||||
}
|
||||
|
||||
export function truncate(path: string, len: number): Promise<void> {
|
||||
return promisify(fs.truncate)(path, len);
|
||||
}
|
||||
|
||||
export function readFile(path: string): Promise<Buffer>;
|
||||
export function readFile(path: string, encoding: string): Promise<string>;
|
||||
export function readFile(path: string, encoding?: string): Promise<Buffer | string> {
|
||||
return promisify(fs.readFile)(path, encoding);
|
||||
}
|
||||
|
||||
export async function mkdirp(path: string, mode?: number): Promise<void> {
|
||||
return promisify(fs.mkdir)(path, { mode, recursive: true });
|
||||
}
|
||||
|
||||
// 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 writeFilePathQueues: Map<string, Queue<void>> = new Map();
|
||||
|
||||
export function writeFile(path: string, data: string, options?: IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: Buffer, options?: IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: Uint8Array, options?: IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise<void> {
|
||||
const queueKey = toQueueKey(path);
|
||||
|
||||
return ensureWriteFileQueue(queueKey).queue(() => {
|
||||
const ensuredOptions = ensureWriteOptions(options);
|
||||
|
||||
return new Promise((resolve, reject) => doWriteFileAndFlush(path, data, ensuredOptions, error => error ? reject(error) : resolve()));
|
||||
});
|
||||
}
|
||||
|
||||
function toQueueKey(path: string): string {
|
||||
let queueKey = path;
|
||||
if (platform.isWindows || platform.isMacintosh) {
|
||||
queueKey = queueKey.toLowerCase(); // accommodate for case insensitive file systems
|
||||
}
|
||||
|
||||
return queueKey;
|
||||
}
|
||||
|
||||
function ensureWriteFileQueue(queueKey: string): Queue<void> {
|
||||
const existingWriteFileQueue = writeFilePathQueues.get(queueKey);
|
||||
if (existingWriteFileQueue) {
|
||||
return existingWriteFileQueue;
|
||||
}
|
||||
|
||||
const writeFileQueue = new Queue<void>();
|
||||
writeFilePathQueues.set(queueKey, writeFileQueue);
|
||||
|
||||
const onFinish = Event.once(writeFileQueue.onFinished);
|
||||
onFinish(() => {
|
||||
writeFilePathQueues.delete(queueKey);
|
||||
writeFileQueue.dispose();
|
||||
});
|
||||
|
||||
return writeFileQueue;
|
||||
}
|
||||
|
||||
export interface IWriteFileOptions {
|
||||
mode?: number;
|
||||
flag?: string;
|
||||
}
|
||||
|
||||
interface IEnsuredWriteFileOptions extends IWriteFileOptions {
|
||||
mode: number;
|
||||
flag: string;
|
||||
}
|
||||
|
||||
let canFlush = true;
|
||||
|
||||
// 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
|
||||
function doWriteFileAndFlush(path: string, data: string | Buffer | Uint8Array, options: IEnsuredWriteFileOptions, callback: (error: Error | null) => void): void {
|
||||
if (!canFlush) {
|
||||
return fs.writeFile(path, data, { mode: options.mode, flag: options.flag }, callback);
|
||||
}
|
||||
|
||||
// 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: Error | null) => {
|
||||
|
||||
// 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));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function writeFileSync(path: string, data: string | Buffer, options?: IWriteFileOptions): void {
|
||||
const ensuredOptions = ensureWriteOptions(options);
|
||||
|
||||
if (!canFlush) {
|
||||
return fs.writeFileSync(path, data, { mode: ensuredOptions.mode, flag: ensuredOptions.flag });
|
||||
}
|
||||
|
||||
// Open the file with same flags and mode as fs.writeFile()
|
||||
const fd = fs.openSync(path, ensuredOptions.flag, ensuredOptions.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);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureWriteOptions(options?: IWriteFileOptions): IEnsuredWriteFileOptions {
|
||||
if (!options) {
|
||||
return { mode: 0o666, flag: 'w' };
|
||||
}
|
||||
|
||||
return {
|
||||
mode: typeof options.mode === 'number' ? options.mode : 0o666,
|
||||
flag: typeof options.flag === 'string' ? options.flag : 'w'
|
||||
};
|
||||
}
|
||||
|
||||
export async function readDirsInDir(dirPath: string): Promise<string[]> {
|
||||
const children = await readdir(dirPath);
|
||||
const directories: string[] = [];
|
||||
|
||||
for (const child of children) {
|
||||
if (await dirExists(join(dirPath, child))) {
|
||||
directories.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
return directories;
|
||||
}
|
||||
|
||||
export async function dirExists(path: string): Promise<boolean> {
|
||||
try {
|
||||
const fileStat = await stat(path);
|
||||
|
||||
return fileStat.isDirectory();
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fileExists(path: string): Promise<boolean> {
|
||||
try {
|
||||
const fileStat = await stat(path);
|
||||
|
||||
return fileStat.isFile();
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function whenDeleted(path: string): Promise<void> {
|
||||
|
||||
// Complete when wait marker file is deleted
|
||||
return new Promise<void>(resolve => {
|
||||
let running = false;
|
||||
const interval = setInterval(() => {
|
||||
if (!running) {
|
||||
running = true;
|
||||
fs.exists(path, exists => {
|
||||
running = false;
|
||||
|
||||
if (!exists) {
|
||||
clearInterval(interval);
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
export async function move(source: string, target: string): Promise<void> {
|
||||
if (source === target) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async function updateMtime(path: string): Promise<void> {
|
||||
const stat = await lstat(path);
|
||||
if (stat.isDirectory() || stat.isSymbolicLink()) {
|
||||
return Promise.resolve(); // only for files
|
||||
}
|
||||
|
||||
const fd = await promisify(fs.open)(path, 'a');
|
||||
try {
|
||||
await promisify(fs.futimes)(fd, stat.atime, new Date());
|
||||
} catch (error) {
|
||||
//ignore
|
||||
}
|
||||
|
||||
return promisify(fs.close)(fd);
|
||||
}
|
||||
|
||||
try {
|
||||
await rename(source, target);
|
||||
await updateMtime(target);
|
||||
} catch (error) {
|
||||
|
||||
// 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 (source.toLowerCase() !== target.toLowerCase() && error.code === 'EXDEV' || source.endsWith('.')) {
|
||||
await copy(source, target);
|
||||
await rimraf(source, RimRafMode.MOVE);
|
||||
await updateMtime(target);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function copy(source: string, target: string, copiedSourcesIn?: { [path: string]: boolean }): Promise<void> {
|
||||
const copiedSources = copiedSourcesIn ? copiedSourcesIn : Object.create(null);
|
||||
|
||||
const fileStat = await stat(source);
|
||||
if (!fileStat.isDirectory()) {
|
||||
return doCopyFile(source, target, fileStat.mode & 511);
|
||||
}
|
||||
|
||||
if (copiedSources[source]) {
|
||||
return Promise.resolve(); // escape when there are cycles (can happen with symlinks)
|
||||
}
|
||||
|
||||
copiedSources[source] = true; // remember as copied
|
||||
|
||||
// Create folder
|
||||
await mkdirp(target, fileStat.mode & 511);
|
||||
|
||||
// Copy each file recursively
|
||||
const files = await readdir(source);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
await copy(join(source, file), join(target, file), copiedSources);
|
||||
}
|
||||
}
|
||||
|
||||
async function doCopyFile(source: string, target: string, mode: number): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = fs.createReadStream(source);
|
||||
const writer = fs.createWriteStream(target, { mode });
|
||||
|
||||
let finished = false;
|
||||
const finish = (error?: Error) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
|
||||
// in error cases, pass to callback
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
// we need to explicitly chmod because of https://github.com/nodejs/node/issues/1104
|
||||
fs.chmod(target, mode, error => error ? reject(error) : resolve());
|
||||
}
|
||||
};
|
||||
|
||||
// handle errors properly
|
||||
reader.once('error', error => finish(error));
|
||||
writer.once('error', error => finish(error));
|
||||
|
||||
// we are done (underlying fd has been closed)
|
||||
writer.once('close', () => finish());
|
||||
|
||||
// start piping
|
||||
reader.pipe(writer);
|
||||
});
|
||||
}
|
128
src/vs/base/node/ports.ts
Normal file
128
src/vs/base/node/ports.ts
Normal file
@ -0,0 +1,128 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as net from 'net';
|
||||
|
||||
/**
|
||||
* @returns Returns a random port between 1025 and 65535.
|
||||
*/
|
||||
export function randomPort(): number {
|
||||
const min = 1025;
|
||||
const max = 65535;
|
||||
return min + Math.floor((max - min) * Math.random());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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): Promise<number> {
|
||||
let done = false;
|
||||
|
||||
return new Promise(resolve => {
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
return resolve(0);
|
||||
}
|
||||
}, timeout);
|
||||
|
||||
doFindFreePort(startPort, giveUpAfter, (port) => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
clearTimeout(timeoutHandle);
|
||||
return resolve(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('data', () => {
|
||||
// this listener is required since node.js 8.x
|
||||
});
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses listen instead of connect. Is faster, but if there is another listener on 0.0.0.0 then this will take 127.0.0.1 from that listener.
|
||||
*/
|
||||
export function findFreePortFaster(startPort: number, giveUpAfter: number, timeout: number): Promise<number> {
|
||||
let resolved: boolean = false;
|
||||
let timeoutHandle: NodeJS.Timeout | undefined = undefined;
|
||||
let countTried: number = 1;
|
||||
const server = net.createServer({ pauseOnConnect: true });
|
||||
function doResolve(port: number, resolve: (port: number) => void) {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
server.removeAllListeners();
|
||||
server.close();
|
||||
if (timeoutHandle) {
|
||||
clearTimeout(timeoutHandle);
|
||||
}
|
||||
resolve(port);
|
||||
}
|
||||
}
|
||||
return new Promise<number>(resolve => {
|
||||
timeoutHandle = setTimeout(() => {
|
||||
doResolve(0, resolve);
|
||||
}, timeout);
|
||||
|
||||
server.on('listening', () => {
|
||||
doResolve(startPort, resolve);
|
||||
});
|
||||
server.on('error', err => {
|
||||
if (err && ((<any>err).code === 'EADDRINUSE' || (<any>err).code === 'EACCES') && (countTried < giveUpAfter)) {
|
||||
startPort++;
|
||||
countTried++;
|
||||
server.listen(startPort, '127.0.0.1');
|
||||
} else {
|
||||
doResolve(0, resolve);
|
||||
}
|
||||
});
|
||||
server.on('close', () => {
|
||||
doResolve(0, resolve);
|
||||
});
|
||||
server.listen(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
|
||||
}
|
||||
}
|
489
src/vs/base/node/processes.ts
Normal file
489
src/vs/base/node/processes.ts
Normal file
@ -0,0 +1,489 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import * as cp from 'child_process';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as Types from 'vs/base/common/types';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import * as Objects from 'vs/base/common/objects';
|
||||
import * as extpath from 'vs/base/common/extpath';
|
||||
import * as Platform from 'vs/base/common/platform';
|
||||
import { LineDecoder } from 'vs/base/node/decoder';
|
||||
import { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode, Executable } from 'vs/base/common/processes';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
export { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode };
|
||||
|
||||
export type ValueCallback<T> = (value: T | Promise<T>) => void;
|
||||
export type ErrorCallback = (error?: any) => void;
|
||||
export type ProgressCallback<T> = (progress: T) => void;
|
||||
|
||||
export interface LineData {
|
||||
line: string;
|
||||
source: Source;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function terminateProcess(process: cp.ChildProcess, cwd?: string): Promise<TerminateResponse> {
|
||||
if (Platform.isWindows) {
|
||||
try {
|
||||
const options: any = {
|
||||
stdio: ['pipe', 'pipe', 'ignore']
|
||||
};
|
||||
if (cwd) {
|
||||
options.cwd = cwd;
|
||||
}
|
||||
const killProcess = cp.execFile('taskkill', ['/T', '/F', '/PID', process.pid.toString()], options);
|
||||
return new Promise((resolve, reject) => {
|
||||
killProcess.once('error', (err) => {
|
||||
resolve({ success: false, error: err });
|
||||
});
|
||||
killProcess.once('exit', (code, signal) => {
|
||||
if (code === 0) {
|
||||
resolve({ success: true });
|
||||
} else {
|
||||
resolve({ success: false, code: code !== null ? code : TerminateResponseCode.Unknown });
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
return Promise.resolve({ success: false, error: err, code: err.status ? getWindowsCode(err.status) : TerminateResponseCode.Unknown });
|
||||
}
|
||||
} else if (Platform.isLinux || Platform.isMacintosh) {
|
||||
try {
|
||||
const cmd = FileAccess.asFileUri('vs/base/node/terminateProcess.sh', require).fsPath;
|
||||
return new Promise((resolve, reject) => {
|
||||
cp.execFile(cmd, [process.pid.toString()], { encoding: 'utf8', shell: true } as cp.ExecFileOptions, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
resolve({ success: false, error: err });
|
||||
} else {
|
||||
resolve({ success: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
return Promise.resolve({ success: false, error: err });
|
||||
}
|
||||
} else {
|
||||
process.kill('SIGKILL');
|
||||
}
|
||||
return Promise.resolve({ success: true });
|
||||
}
|
||||
|
||||
export function getWindowsShell(environment: Platform.IProcessEnvironment = process.env as Platform.IProcessEnvironment): string {
|
||||
return environment['comspec'] || 'cmd.exe';
|
||||
}
|
||||
|
||||
export abstract class AbstractProcess<TProgressData> {
|
||||
private cmd: string;
|
||||
private args: string[];
|
||||
private options: CommandOptions | ForkOptions;
|
||||
protected shell: boolean;
|
||||
|
||||
private childProcess: cp.ChildProcess | null;
|
||||
protected childProcessPromise: Promise<cp.ChildProcess> | null;
|
||||
private pidResolve: ValueCallback<number> | undefined;
|
||||
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[] | undefined, shell: boolean, options: CommandOptions | undefined);
|
||||
public constructor(arg1: string | Executable, arg2?: string[], arg3?: boolean, arg4?: CommandOptions) {
|
||||
if (arg2 !== undefined && arg3 !== undefined && arg4 !== undefined) {
|
||||
this.cmd = <string>arg1;
|
||||
this.args = arg2;
|
||||
this.shell = arg3;
|
||||
this.options = arg4;
|
||||
} else {
|
||||
const 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.childProcessPromise = null;
|
||||
this.terminateRequested = false;
|
||||
|
||||
if (this.options.env) {
|
||||
const 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();
|
||||
const index = result.lastIndexOf(path.sep);
|
||||
if (index !== -1) {
|
||||
result = result.substring(index + 1);
|
||||
}
|
||||
if (AbstractProcess.WellKnowCommands[result]) {
|
||||
return result;
|
||||
}
|
||||
return 'other';
|
||||
}
|
||||
|
||||
public start(pp: ProgressCallback<TProgressData>): Promise<SuccessData> {
|
||||
if (Platform.isWindows && ((this.options && this.options.cwd && extpath.isUNC(this.options.cwd)) || !this.options && extpath.isUNC(process.cwd()))) {
|
||||
return Promise.reject(new Error(nls.localize('TaskRunner.UNC', 'Can\'t execute a shell command on a UNC drive.')));
|
||||
}
|
||||
return this.useExec().then((useExec) => {
|
||||
let cc: ValueCallback<SuccessData>;
|
||||
let ee: ErrorCallback;
|
||||
const result = new Promise<any>((c, e) => {
|
||||
cc = c;
|
||||
ee = e;
|
||||
});
|
||||
|
||||
if (useExec) {
|
||||
let cmd: string = this.cmd;
|
||||
if (this.args) {
|
||||
cmd = cmd + ' ' + this.args.join(' ');
|
||||
}
|
||||
this.childProcess = cp.exec(cmd, this.options, (error, stdout, stderr) => {
|
||||
this.childProcess = null;
|
||||
const 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: cp.ChildProcess | null = null;
|
||||
const closeHandler = (data: any) => {
|
||||
this.childProcess = null;
|
||||
this.childProcessPromise = null;
|
||||
this.handleClose(data, cc, pp, ee);
|
||||
const result: SuccessData = {
|
||||
terminated: this.terminateRequested
|
||||
};
|
||||
if (Types.isNumber(data)) {
|
||||
result.cmdCode = <number>data;
|
||||
}
|
||||
cc(result);
|
||||
};
|
||||
if (this.shell && Platform.isWindows) {
|
||||
const options: any = Objects.deepClone(this.options);
|
||||
options.windowsVerbatimArguments = true;
|
||||
options.detached = false;
|
||||
let quotedCommand: boolean = false;
|
||||
let quotedArg: boolean = false;
|
||||
const 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;
|
||||
});
|
||||
}
|
||||
const 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 = cp.spawn(getWindowsShell(), args, options);
|
||||
} else {
|
||||
if (this.cmd) {
|
||||
childProcess = cp.spawn(this.cmd, this.args, this.options);
|
||||
}
|
||||
}
|
||||
if (childProcess) {
|
||||
this.childProcess = childProcess;
|
||||
this.childProcessPromise = Promise.resolve(childProcess);
|
||||
if (this.pidResolve) {
|
||||
this.pidResolve(Types.isNumber(childProcess.pid) ? childProcess.pid : -1);
|
||||
this.pidResolve = undefined;
|
||||
}
|
||||
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: ValueCallback<SuccessData>, pp: ProgressCallback<TProgressData>, error: Error | null, stdout: Buffer, stderr: Buffer): void;
|
||||
protected abstract handleSpawn(childProcess: cp.ChildProcess, cc: ValueCallback<SuccessData>, pp: ProgressCallback<TProgressData>, ee: ErrorCallback, sync: boolean): void;
|
||||
|
||||
protected handleClose(data: any, cc: ValueCallback<SuccessData>, pp: ProgressCallback<TProgressData>, ee: ErrorCallback): void {
|
||||
// Default is to do nothing.
|
||||
}
|
||||
|
||||
private static readonly 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 get pid(): Promise<number> {
|
||||
if (this.childProcessPromise) {
|
||||
return this.childProcessPromise.then(childProcess => childProcess.pid, err => -1);
|
||||
} else {
|
||||
return new Promise<number>((resolve) => {
|
||||
this.pidResolve = resolve;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public terminate(): Promise<TerminateResponse> {
|
||||
if (!this.childProcessPromise) {
|
||||
return Promise.resolve<TerminateResponse>({ success: true });
|
||||
}
|
||||
return this.childProcessPromise.then((childProcess) => {
|
||||
this.terminateRequested = true;
|
||||
return terminateProcess(childProcess, this.options.cwd).then(response => {
|
||||
if (response.success) {
|
||||
this.childProcess = null;
|
||||
}
|
||||
return response;
|
||||
});
|
||||
}, (err) => {
|
||||
return { success: true };
|
||||
});
|
||||
}
|
||||
|
||||
private useExec(): Promise<boolean> {
|
||||
return new Promise<boolean>(resolve => {
|
||||
if (!this.shell || !Platform.isWindows) {
|
||||
return resolve(false);
|
||||
}
|
||||
const cmdShell = cp.spawn(getWindowsShell(), ['/s', '/c']);
|
||||
cmdShell.on('error', (error: Error) => {
|
||||
return resolve(true);
|
||||
});
|
||||
cmdShell.on('exit', (data: any) => {
|
||||
return resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class LineProcess extends AbstractProcess<LineData> {
|
||||
|
||||
private stdoutLineDecoder: LineDecoder | null;
|
||||
private stderrLineDecoder: LineDecoder | null;
|
||||
|
||||
public constructor(executable: Executable);
|
||||
public constructor(cmd: string, args: string[], shell: boolean, options: CommandOptions);
|
||||
public constructor(arg1: string | Executable, arg2?: string[], arg3?: boolean | ForkOptions, arg4?: CommandOptions) {
|
||||
super(<any>arg1, arg2, <any>arg3, arg4);
|
||||
|
||||
this.stdoutLineDecoder = null;
|
||||
this.stderrLineDecoder = null;
|
||||
}
|
||||
|
||||
protected handleExec(cc: ValueCallback<SuccessData>, pp: ProgressCallback<LineData>, error: Error, stdout: Buffer, stderr: Buffer) {
|
||||
[stdout, stderr].forEach((buffer: Buffer, index: number) => {
|
||||
const lineDecoder = new LineDecoder();
|
||||
const lines = lineDecoder.write(buffer);
|
||||
lines.forEach((line) => {
|
||||
pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr });
|
||||
});
|
||||
const line = lineDecoder.end();
|
||||
if (line) {
|
||||
pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr });
|
||||
}
|
||||
});
|
||||
cc({ terminated: this.terminateRequested, error: error });
|
||||
}
|
||||
|
||||
protected handleSpawn(childProcess: cp.ChildProcess, cc: ValueCallback<SuccessData>, pp: ProgressCallback<LineData>, ee: ErrorCallback, sync: boolean): void {
|
||||
const stdoutLineDecoder = new LineDecoder();
|
||||
const stderrLineDecoder = new LineDecoder();
|
||||
childProcess.stdout!.on('data', (data: Buffer) => {
|
||||
const lines = stdoutLineDecoder.write(data);
|
||||
lines.forEach(line => pp({ line: line, source: Source.stdout }));
|
||||
});
|
||||
childProcess.stderr!.on('data', (data: Buffer) => {
|
||||
const lines = stderrLineDecoder.write(data);
|
||||
lines.forEach(line => pp({ line: line, source: Source.stderr }));
|
||||
});
|
||||
|
||||
this.stdoutLineDecoder = stdoutLineDecoder;
|
||||
this.stderrLineDecoder = stderrLineDecoder;
|
||||
}
|
||||
|
||||
protected handleClose(data: any, cc: ValueCallback<SuccessData>, pp: ProgressCallback<LineData>, ee: ErrorCallback): void {
|
||||
const stdoutLine = this.stdoutLineDecoder ? this.stdoutLineDecoder.end() : null;
|
||||
if (stdoutLine) {
|
||||
pp({ line: stdoutLine, source: Source.stdout });
|
||||
}
|
||||
const stderrLine = this.stderrLineDecoder ? this.stderrLineDecoder.end() : null;
|
||||
if (stderrLine) {
|
||||
pp({ line: stderrLine, source: Source.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: cp.ChildProcess): IQueuedSender {
|
||||
let msgQueue: string[] = [];
|
||||
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;
|
||||
}
|
||||
|
||||
const result = childProcess.send(msg, (error: Error | null) => {
|
||||
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 };
|
||||
}
|
||||
|
||||
export namespace win32 {
|
||||
export async function findExecutable(command: string, cwd?: string, paths?: string[]): Promise<string> {
|
||||
// If we have an absolute path then we take it.
|
||||
if (path.isAbsolute(command)) {
|
||||
return command;
|
||||
}
|
||||
if (cwd === undefined) {
|
||||
cwd = process.cwd();
|
||||
}
|
||||
const dir = path.dirname(command);
|
||||
if (dir !== '.') {
|
||||
// We have a directory and the directory is relative (see above). Make the path absolute
|
||||
// to the current working directory.
|
||||
return path.join(cwd, command);
|
||||
}
|
||||
if (paths === undefined && Types.isString(process.env.PATH)) {
|
||||
paths = process.env.PATH.split(path.delimiter);
|
||||
}
|
||||
// No PATH environment. Make path absolute to the cwd.
|
||||
if (paths === undefined || paths.length === 0) {
|
||||
return path.join(cwd, command);
|
||||
}
|
||||
|
||||
async function fileExists(path: string): Promise<boolean> {
|
||||
if (await promisify(fs.exists)(path)) {
|
||||
return !((await promisify(fs.stat)(path)).isDirectory());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// We have a simple file name. We get the path variable from the env
|
||||
// and try to find the executable on the path.
|
||||
for (let pathEntry of paths) {
|
||||
// The path entry is absolute.
|
||||
let fullPath: string;
|
||||
if (path.isAbsolute(pathEntry)) {
|
||||
fullPath = path.join(pathEntry, command);
|
||||
} else {
|
||||
fullPath = path.join(cwd, pathEntry, command);
|
||||
}
|
||||
if (await fileExists(fullPath)) {
|
||||
return fullPath;
|
||||
}
|
||||
let withExtension = fullPath + '.com';
|
||||
if (await fileExists(withExtension)) {
|
||||
return withExtension;
|
||||
}
|
||||
withExtension = fullPath + '.exe';
|
||||
if (await fileExists(withExtension)) {
|
||||
return withExtension;
|
||||
}
|
||||
}
|
||||
return path.join(cwd, command);
|
||||
}
|
||||
}
|
39
src/vs/base/node/ps.sh
Executable file
39
src/vs/base/node/ps.sh
Executable file
@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
PAGESIZE=`getconf PAGESIZE`;
|
||||
TOTAL_MEMORY=`cat /proc/meminfo | head -n 1 | awk '{print $2}'`;
|
||||
|
||||
# Mimic the output of ps -ax -o pid=,ppid=,pcpu=,pmem=,command=
|
||||
# Read all numeric subdirectories in /proc
|
||||
for pid in `cd /proc && ls -d [0-9]*`
|
||||
do {
|
||||
if [ -e /proc/$pid/stat ]
|
||||
then
|
||||
echo $pid;
|
||||
|
||||
# ppid is the word at index 4 in the stat file for the process
|
||||
awk '{print $4}' /proc/$pid/stat;
|
||||
|
||||
# pcpu - calculation will be done later, this is a placeholder value
|
||||
echo "0.0"
|
||||
|
||||
# pmem - ratio of the process's working set size to total memory.
|
||||
# use the page size to convert to bytes, total memory is in KB
|
||||
# multiplied by 100 to get percentage, extra 10 to be able to move
|
||||
# the decimal over by one place
|
||||
RESIDENT_SET_SIZE=`awk '{print $24}' /proc/$pid/stat`;
|
||||
PERCENT_MEMORY=$(((1000 * $PAGESIZE * $RESIDENT_SET_SIZE) / ($TOTAL_MEMORY * 1024)));
|
||||
if [ $PERCENT_MEMORY -lt 10 ]
|
||||
then
|
||||
# replace the last character with 0. the last character
|
||||
echo $PERCENT_MEMORY | sed 's/.$/0.&/'; #pmem
|
||||
else
|
||||
# insert . before the last character
|
||||
echo $PERCENT_MEMORY | sed 's/.$/.&/';
|
||||
fi
|
||||
|
||||
# cmdline
|
||||
xargs -0 < /proc/$pid/cmdline;
|
||||
fi
|
||||
} | tr "\n" "\t"; # Replace newlines with tab so that all info for a process is shown on one line
|
||||
echo; # But add new lines between processes
|
||||
done
|
259
src/vs/base/node/ps.ts
Normal file
259
src/vs/base/node/ps.ts
Normal file
@ -0,0 +1,259 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { exec } from 'child_process';
|
||||
import { ProcessItem } from 'vs/base/common/processes';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
|
||||
export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let rootItem: ProcessItem | undefined;
|
||||
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 SHARED_PROCESS_HINT = /--disable-blink-features=Auxclick/;
|
||||
const WINDOWS_WATCHER_HINT = /\\watcher\\win32\\CodeHelper\.exe/;
|
||||
const WINDOWS_CRASH_REPORTER = /--crashes-directory/;
|
||||
const WINDOWS_PTY = /\\pipe\\winpty-control/;
|
||||
const WINDOWS_CONSOLE_HOST = /conhost\.exe/;
|
||||
const TYPE = /--type=([a-zA-Z-]+)/;
|
||||
|
||||
// find windows file watcher
|
||||
if (WINDOWS_WATCHER_HINT.exec(cmd)) {
|
||||
return 'watcherService ';
|
||||
}
|
||||
|
||||
// find windows crash reporter
|
||||
if (WINDOWS_CRASH_REPORTER.exec(cmd)) {
|
||||
return 'electron-crash-reporter';
|
||||
}
|
||||
|
||||
// find windows pty process
|
||||
if (WINDOWS_PTY.exec(cmd)) {
|
||||
return 'winpty-process';
|
||||
}
|
||||
|
||||
//find windows console host process
|
||||
if (WINDOWS_CONSOLE_HOST.exec(cmd)) {
|
||||
return 'console-window-host (Windows internal process)';
|
||||
}
|
||||
|
||||
// find "--type=xxxx"
|
||||
let matches = TYPE.exec(cmd);
|
||||
if (matches && matches.length === 2) {
|
||||
if (matches[1] === 'renderer') {
|
||||
if (SHARED_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 && cmd.indexOf('node.exe') < 0) {
|
||||
return `electron_node ${result}`;
|
||||
}
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
(import('windows-process-tree')).then(windowsProcessTree => {
|
||||
windowsProcessTree.getProcessList(rootPid, (processList) => {
|
||||
windowsProcessTree.getProcessCpuUsage(processList, (completeProcessList) => {
|
||||
const processItems: Map<number, ProcessItem> = new Map();
|
||||
completeProcessList.forEach(process => {
|
||||
const commandLine = cleanUNCPrefix(process.commandLine || '');
|
||||
processItems.set(process.pid, {
|
||||
name: findName(commandLine),
|
||||
cmd: commandLine,
|
||||
pid: process.pid,
|
||||
ppid: process.ppid,
|
||||
load: process.cpu || 0,
|
||||
mem: process.memory || 0
|
||||
});
|
||||
});
|
||||
|
||||
rootItem = processItems.get(rootPid);
|
||||
if (rootItem) {
|
||||
processItems.forEach(item => {
|
||||
const 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`));
|
||||
}
|
||||
});
|
||||
}, windowsProcessTree.ProcessDataFlag.CommandLine | windowsProcessTree.ProcessDataFlag.Memory);
|
||||
});
|
||||
} else { // OS X & Linux
|
||||
function calculateLinuxCpuUsage() {
|
||||
// Flatten rootItem to get a list of all VSCode processes
|
||||
let processes = [rootItem];
|
||||
const pids: number[] = [];
|
||||
while (processes.length) {
|
||||
const process = processes.shift();
|
||||
if (process) {
|
||||
pids.push(process.pid);
|
||||
if (process.children) {
|
||||
processes = processes.concat(process.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The cpu usage value reported on Linux is the average over the process lifetime,
|
||||
// recalculate the usage over a one second interval
|
||||
// JSON.stringify is needed to escape spaces, https://github.com/nodejs/node/issues/6803
|
||||
let cmd = JSON.stringify(FileAccess.asFileUri('vs/base/node/cpuUsage.sh', require).fsPath);
|
||||
cmd += ' ' + pids.join(' ');
|
||||
|
||||
exec(cmd, {}, (err, stdout, stderr) => {
|
||||
if (err || stderr) {
|
||||
reject(err || new Error(stderr.toString()));
|
||||
} else {
|
||||
const cpuUsage = stdout.toString().split('\n');
|
||||
for (let i = 0; i < pids.length; i++) {
|
||||
const processInfo = map.get(pids[i])!;
|
||||
processInfo.load = parseFloat(cpuUsage[i]);
|
||||
}
|
||||
|
||||
if (!rootItem) {
|
||||
reject(new Error(`Root process ${rootPid} not found`));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(rootItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exec('which ps', {}, (err, stdout, stderr) => {
|
||||
if (err || stderr) {
|
||||
if (process.platform !== 'linux') {
|
||||
reject(err || new Error(stderr.toString()));
|
||||
} else {
|
||||
const cmd = JSON.stringify(FileAccess.asFileUri('vs/base/node/ps.sh', require).fsPath);
|
||||
exec(cmd, {}, (err, stdout, stderr) => {
|
||||
if (err || stderr) {
|
||||
reject(err || new Error(stderr.toString()));
|
||||
} else {
|
||||
parsePsOutput(stdout, addToTree);
|
||||
calculateLinuxCpuUsage();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const ps = stdout.toString().trim();
|
||||
const args = '-ax -o pid=,ppid=,pcpu=,pmem=,command=';
|
||||
|
||||
// Set numeric locale to ensure '.' is used as the decimal separator
|
||||
exec(`${ps} ${args}`, { maxBuffer: 1000 * 1024, env: { LC_NUMERIC: 'en_US.UTF-8' } }, (err, stdout, stderr) => {
|
||||
// Silently ignoring the screen size is bogus error. See https://github.com/microsoft/vscode/issues/98590
|
||||
if (err || (stderr && !stderr.includes('screen size is bogus'))) {
|
||||
reject(err || new Error(stderr.toString()));
|
||||
} else {
|
||||
parsePsOutput(stdout, addToTree);
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
calculateLinuxCpuUsage();
|
||||
} else {
|
||||
if (!rootItem) {
|
||||
reject(new Error(`Root process ${rootPid} not found`));
|
||||
} else {
|
||||
resolve(rootItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function parsePsOutput(stdout: string, addToTree: (pid: number, ppid: number, cmd: string, load: number, mem: number) => void): void {
|
||||
const PID_CMD = /^\s*([0-9]+)\s+([0-9]+)\s+([0-9]+\.[0-9]+)\s+([0-9]+\.[0-9]+)\s+(.+)$/;
|
||||
const lines = stdout.toString().split('\n');
|
||||
for (const line of lines) {
|
||||
const 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]));
|
||||
}
|
||||
}
|
||||
}
|
101
src/vs/base/node/terminalEncoding.ts
Normal file
101
src/vs/base/node/terminalEncoding.ts
Normal file
@ -0,0 +1,101 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* This code is also used by standalone cli's. Avoid adding dependencies to keep the size of the cli small.
|
||||
*/
|
||||
import { exec } from 'child_process';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
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
|
||||
'936': 'cp936', // Simplified Chinese
|
||||
'1252': 'cp1252' // West European Latin
|
||||
};
|
||||
|
||||
function toIconvLiteEncoding(encodingName: string): string {
|
||||
const normalizedEncodingName = encodingName.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
|
||||
const mapped = JSCHARDET_TO_ICONV_ENCODINGS[normalizedEncodingName];
|
||||
|
||||
return mapped || normalizedEncodingName;
|
||||
}
|
||||
|
||||
const JSCHARDET_TO_ICONV_ENCODINGS: { [name: string]: string } = {
|
||||
'ibm866': 'cp866',
|
||||
'big5': 'cp950'
|
||||
};
|
||||
|
||||
const UTF8 = 'utf8';
|
||||
|
||||
export async function resolveTerminalEncoding(verbose?: boolean): Promise<string> {
|
||||
let rawEncodingPromise: Promise<string | undefined>;
|
||||
|
||||
// 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 = Promise.resolve(cliEncodingEnv);
|
||||
}
|
||||
|
||||
// Windows: educated guess
|
||||
else if (isWindows) {
|
||||
rawEncodingPromise = new Promise<string | undefined>(resolve => {
|
||||
if (verbose) {
|
||||
console.log('Running "chcp" to detect terminal encoding...');
|
||||
}
|
||||
|
||||
exec('chcp', (err, stdout, stderr) => {
|
||||
if (stdout) {
|
||||
if (verbose) {
|
||||
console.log(`Output from "chcp" command is: ${stdout}`);
|
||||
}
|
||||
|
||||
const windowsTerminalEncodingKeys = Object.keys(windowsTerminalEncodings) as Array<keyof typeof windowsTerminalEncodings>;
|
||||
for (const key of windowsTerminalEncodingKeys) {
|
||||
if (stdout.indexOf(key) >= 0) {
|
||||
return resolve(windowsTerminalEncodings[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resolve(undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
// Linux/Mac: use "locale charmap" command
|
||||
else {
|
||||
rawEncodingPromise = new Promise<string>(resolve => {
|
||||
if (verbose) {
|
||||
console.log('Running "locale charmap" to detect terminal encoding...');
|
||||
}
|
||||
|
||||
exec('locale charmap', (err, stdout, stderr) => resolve(stdout));
|
||||
});
|
||||
}
|
||||
|
||||
const rawEncoding = await rawEncodingPromise;
|
||||
if (verbose) {
|
||||
console.log(`Detected raw terminal encoding: ${rawEncoding}`);
|
||||
}
|
||||
|
||||
if (!rawEncoding || rawEncoding.toLowerCase() === 'utf-8' || rawEncoding.toLowerCase() === UTF8) {
|
||||
return UTF8;
|
||||
}
|
||||
|
||||
return toIconvLiteEncoding(rawEncoding);
|
||||
}
|
12
src/vs/base/node/terminateProcess.sh
Executable file
12
src/vs/base/node/terminateProcess.sh
Executable 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
|
192
src/vs/base/node/watcher.ts
Normal file
192
src/vs/base/node/watcher.ts
Normal file
@ -0,0 +1,192 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { join, basename } from 'vs/base/common/path';
|
||||
import { watch } from 'fs';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
import { toDisposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { exists, readdir } from 'vs/base/node/pfs';
|
||||
|
||||
export function watchFile(path: string, onChange: (type: 'added' | 'changed' | 'deleted', path: string) => void, onError: (error: string) => void): IDisposable {
|
||||
return doWatchNonRecursive({ path, isDirectory: false }, onChange, onError);
|
||||
}
|
||||
|
||||
export function watchFolder(path: string, onChange: (type: 'added' | 'changed' | 'deleted', path: string) => void, onError: (error: string) => void): IDisposable {
|
||||
return doWatchNonRecursive({ path, isDirectory: true }, onChange, onError);
|
||||
}
|
||||
|
||||
export const CHANGE_BUFFER_DELAY = 100;
|
||||
|
||||
function doWatchNonRecursive(file: { path: string, isDirectory: boolean }, onChange: (type: 'added' | 'changed' | 'deleted', path: string) => void, onError: (error: string) => void): IDisposable {
|
||||
const originalFileName = basename(file.path);
|
||||
const mapPathToStatDisposable = new Map<string, IDisposable>();
|
||||
|
||||
let disposed = false;
|
||||
let watcherDisposables: IDisposable[] = [toDisposable(() => {
|
||||
mapPathToStatDisposable.forEach(disposable => dispose(disposable));
|
||||
mapPathToStatDisposable.clear();
|
||||
})];
|
||||
|
||||
try {
|
||||
|
||||
// Creating watcher can fail with an exception
|
||||
const watcher = watch(file.path);
|
||||
watcherDisposables.push(toDisposable(() => {
|
||||
watcher.removeAllListeners();
|
||||
watcher.close();
|
||||
}));
|
||||
|
||||
// Folder: resolve children to emit proper events
|
||||
const folderChildren: Set<string> = new Set<string>();
|
||||
if (file.isDirectory) {
|
||||
readdir(file.path).then(children => children.forEach(child => folderChildren.add(child)));
|
||||
}
|
||||
|
||||
watcher.on('error', (code: number, signal: string) => {
|
||||
if (!disposed) {
|
||||
onError(`Failed to watch ${file.path} for changes using fs.watch() (${code}, ${signal})`);
|
||||
}
|
||||
});
|
||||
|
||||
watcher.on('change', (type, raw) => {
|
||||
if (disposed) {
|
||||
return; // ignore if already disposed
|
||||
}
|
||||
|
||||
// Normalize file name
|
||||
let changedFileName: string = '';
|
||||
if (raw) { // https://github.com/microsoft/vscode/issues/38191
|
||||
changedFileName = raw.toString();
|
||||
if (isMacintosh) {
|
||||
// Mac: uses NFD unicode form on disk, but we want NFC
|
||||
// See also https://github.com/nodejs/node/issues/2165
|
||||
changedFileName = normalizeNFC(changedFileName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!changedFileName || (type !== 'change' && type !== 'rename')) {
|
||||
return; // ignore unexpected events
|
||||
}
|
||||
|
||||
// File path: use path directly for files and join with changed file name otherwise
|
||||
const changedFilePath = file.isDirectory ? join(file.path, changedFileName) : file.path;
|
||||
|
||||
// File
|
||||
if (!file.isDirectory) {
|
||||
if (type === 'rename' || changedFileName !== originalFileName) {
|
||||
// The file was either deleted or renamed. Many tools apply changes to files in an
|
||||
// atomic way ("Atomic Save") by first renaming the file to a temporary name and then
|
||||
// renaming it back to the original name. Our watcher will detect this as a rename
|
||||
// and then stops to work on Mac and Linux because the watcher is applied to the
|
||||
// inode and not the name. The fix is to detect this case and trying to watch the file
|
||||
// again after a certain delay.
|
||||
// In addition, we send out a delete event if after a timeout we detect that the file
|
||||
// does indeed not exist anymore.
|
||||
|
||||
const timeoutHandle = setTimeout(async () => {
|
||||
const fileExists = await exists(changedFilePath);
|
||||
|
||||
if (disposed) {
|
||||
return; // ignore if disposed by now
|
||||
}
|
||||
|
||||
// File still exists, so emit as change event and reapply the watcher
|
||||
if (fileExists) {
|
||||
onChange('changed', changedFilePath);
|
||||
|
||||
watcherDisposables = [doWatchNonRecursive(file, onChange, onError)];
|
||||
}
|
||||
|
||||
// File seems to be really gone, so emit a deleted event
|
||||
else {
|
||||
onChange('deleted', changedFilePath);
|
||||
}
|
||||
}, CHANGE_BUFFER_DELAY);
|
||||
|
||||
// Very important to dispose the watcher which now points to a stale inode
|
||||
// and wire in a new disposable that tracks our timeout that is installed
|
||||
dispose(watcherDisposables);
|
||||
watcherDisposables = [toDisposable(() => clearTimeout(timeoutHandle))];
|
||||
} else {
|
||||
onChange('changed', changedFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Folder
|
||||
else {
|
||||
|
||||
// Children add/delete
|
||||
if (type === 'rename') {
|
||||
|
||||
// Cancel any previous stats for this file path if existing
|
||||
const statDisposable = mapPathToStatDisposable.get(changedFilePath);
|
||||
if (statDisposable) {
|
||||
dispose(statDisposable);
|
||||
}
|
||||
|
||||
// Wait a bit and try see if the file still exists on disk to decide on the resulting event
|
||||
const timeoutHandle = setTimeout(async () => {
|
||||
mapPathToStatDisposable.delete(changedFilePath);
|
||||
|
||||
const fileExists = await exists(changedFilePath);
|
||||
|
||||
if (disposed) {
|
||||
return; // ignore if disposed by now
|
||||
}
|
||||
|
||||
// Figure out the correct event type:
|
||||
// File Exists: either 'added' or 'changed' if known before
|
||||
// File Does not Exist: always 'deleted'
|
||||
let type: 'added' | 'deleted' | 'changed';
|
||||
if (fileExists) {
|
||||
if (folderChildren.has(changedFileName)) {
|
||||
type = 'changed';
|
||||
} else {
|
||||
type = 'added';
|
||||
folderChildren.add(changedFileName);
|
||||
}
|
||||
} else {
|
||||
folderChildren.delete(changedFileName);
|
||||
type = 'deleted';
|
||||
}
|
||||
|
||||
onChange(type, changedFilePath);
|
||||
}, CHANGE_BUFFER_DELAY);
|
||||
|
||||
mapPathToStatDisposable.set(changedFilePath, toDisposable(() => clearTimeout(timeoutHandle)));
|
||||
}
|
||||
|
||||
// Other events
|
||||
else {
|
||||
|
||||
// Figure out the correct event type: if this is the
|
||||
// first time we see this child, it can only be added
|
||||
let type: 'added' | 'changed';
|
||||
if (folderChildren.has(changedFileName)) {
|
||||
type = 'changed';
|
||||
} else {
|
||||
type = 'added';
|
||||
folderChildren.add(changedFileName);
|
||||
}
|
||||
|
||||
onChange(type, changedFilePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
exists(file.path).then(exists => {
|
||||
if (exists && !disposed) {
|
||||
onError(`Failed to watch ${file.path} for changes using fs.watch() (${error.toString()})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return toDisposable(() => {
|
||||
disposed = true;
|
||||
|
||||
watcherDisposables = dispose(watcherDisposables);
|
||||
});
|
||||
}
|
250
src/vs/base/node/zip.ts
Normal file
250
src/vs/base/node/zip.ts
Normal file
@ -0,0 +1,250 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { createWriteStream, WriteStream } from 'fs';
|
||||
import { Readable } from 'stream';
|
||||
import { Sequencer, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { mkdirp, rimraf } from 'vs/base/node/pfs';
|
||||
import { open as _openZip, Entry, ZipFile } from 'yauzl';
|
||||
import * as yazl from 'yazl';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
|
||||
export interface IExtractOptions {
|
||||
overwrite?: boolean;
|
||||
|
||||
/**
|
||||
* Source path within the ZIP archive. Only the files contained in this
|
||||
* path will be extracted.
|
||||
*/
|
||||
sourcePath?: string;
|
||||
}
|
||||
|
||||
interface IOptions {
|
||||
sourcePathRegex: RegExp;
|
||||
}
|
||||
|
||||
export type ExtractErrorType = 'CorruptZip' | 'Incomplete';
|
||||
|
||||
export class ExtractError extends Error {
|
||||
|
||||
readonly type?: ExtractErrorType;
|
||||
readonly cause: Error;
|
||||
|
||||
constructor(type: ExtractErrorType | undefined, cause: Error) {
|
||||
let message = cause.message;
|
||||
|
||||
switch (type) {
|
||||
case 'CorruptZip': message = `Corrupt ZIP: ${message}`; break;
|
||||
}
|
||||
|
||||
super(message);
|
||||
this.type = type;
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
|
||||
function modeFromEntry(entry: Entry) {
|
||||
const attr = entry.externalFileAttributes >> 16 || 33188;
|
||||
|
||||
return [448 /* S_IRWXU */, 56 /* S_IRWXG */, 7 /* S_IRWXO */]
|
||||
.map(mask => attr & mask)
|
||||
.reduce((a, b) => a + b, attr & 61440 /* S_IFMT */);
|
||||
}
|
||||
|
||||
function toExtractError(err: Error): ExtractError {
|
||||
if (err instanceof ExtractError) {
|
||||
return err;
|
||||
}
|
||||
|
||||
let type: ExtractErrorType | undefined = undefined;
|
||||
|
||||
if (/end of central directory record signature not found/.test(err.message)) {
|
||||
type = 'CorruptZip';
|
||||
}
|
||||
|
||||
return new ExtractError(type, err);
|
||||
}
|
||||
|
||||
function extractEntry(stream: Readable, fileName: string, mode: number, targetPath: string, options: IOptions, token: CancellationToken): Promise<void> {
|
||||
const dirName = path.dirname(fileName);
|
||||
const targetDirName = path.join(targetPath, dirName);
|
||||
if (!targetDirName.startsWith(targetPath)) {
|
||||
return Promise.reject(new Error(nls.localize('invalid file', "Error extracting {0}. Invalid file.", fileName)));
|
||||
}
|
||||
const targetFileName = path.join(targetPath, fileName);
|
||||
|
||||
let istream: WriteStream;
|
||||
|
||||
token.onCancellationRequested(() => {
|
||||
if (istream) {
|
||||
istream.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.resolve(mkdirp(targetDirName)).then(() => new Promise<void>((c, e) => {
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
istream = createWriteStream(targetFileName, { mode });
|
||||
istream.once('close', () => c());
|
||||
istream.once('error', e);
|
||||
stream.once('error', e);
|
||||
stream.pipe(istream);
|
||||
} catch (error) {
|
||||
e(error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, token: CancellationToken): Promise<void> {
|
||||
let last = createCancelablePromise<void>(() => Promise.resolve());
|
||||
let extractedEntriesCount = 0;
|
||||
|
||||
token.onCancellationRequested(() => {
|
||||
last.cancel();
|
||||
zipfile.close();
|
||||
});
|
||||
|
||||
return new Promise((c, e) => {
|
||||
const throttler = new Sequencer();
|
||||
|
||||
const readNextEntry = (token: CancellationToken) => {
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
extractedEntriesCount++;
|
||||
zipfile.readEntry();
|
||||
};
|
||||
|
||||
zipfile.once('error', e);
|
||||
zipfile.once('close', () => last.then(() => {
|
||||
if (token.isCancellationRequested || zipfile.entryCount === extractedEntriesCount) {
|
||||
c();
|
||||
} else {
|
||||
e(new ExtractError('Incomplete', new Error(nls.localize('incompleteExtract', "Incomplete. Found {0} of {1} entries", extractedEntriesCount, zipfile.entryCount))));
|
||||
}
|
||||
}, e));
|
||||
zipfile.readEntry();
|
||||
zipfile.on('entry', (entry: Entry) => {
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.sourcePathRegex.test(entry.fileName)) {
|
||||
readNextEntry(token);
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = entry.fileName.replace(options.sourcePathRegex, '');
|
||||
|
||||
// directory file names end with '/'
|
||||
if (/\/$/.test(fileName)) {
|
||||
const targetFileName = path.join(targetPath, fileName);
|
||||
last = createCancelablePromise(token => mkdirp(targetFileName).then(() => readNextEntry(token)).then(undefined, e));
|
||||
return;
|
||||
}
|
||||
|
||||
const stream = openZipStream(zipfile, entry);
|
||||
const mode = modeFromEntry(entry);
|
||||
|
||||
last = createCancelablePromise(token => throttler.queue(() => stream.then(stream => extractEntry(stream, fileName, mode, targetPath, options, token).then(() => readNextEntry(token)))).then(null, e));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function openZip(zipFile: string, lazy: boolean = false): Promise<ZipFile> {
|
||||
return new Promise<ZipFile>((resolve, reject) => {
|
||||
_openZip(zipFile, lazy ? { lazyEntries: true } : undefined!, (error?: Error, zipfile?: ZipFile) => {
|
||||
if (error) {
|
||||
reject(toExtractError(error));
|
||||
} else {
|
||||
resolve(assertIsDefined(zipfile));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function openZipStream(zipFile: ZipFile, entry: Entry): Promise<Readable> {
|
||||
return new Promise<Readable>((resolve, reject) => {
|
||||
zipFile.openReadStream(entry, (error?: Error, stream?: Readable) => {
|
||||
if (error) {
|
||||
reject(toExtractError(error));
|
||||
} else {
|
||||
resolve(assertIsDefined(stream));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export interface IFile {
|
||||
path: string;
|
||||
contents?: Buffer | string;
|
||||
localPath?: string;
|
||||
}
|
||||
|
||||
export function zip(zipPath: string, files: IFile[]): Promise<string> {
|
||||
return new Promise<string>((c, e) => {
|
||||
const zip = new yazl.ZipFile();
|
||||
files.forEach(f => {
|
||||
if (f.contents) {
|
||||
zip.addBuffer(typeof f.contents === 'string' ? Buffer.from(f.contents, 'utf8') : f.contents, f.path);
|
||||
} else if (f.localPath) {
|
||||
zip.addFile(f.localPath, f.path);
|
||||
}
|
||||
});
|
||||
zip.end();
|
||||
|
||||
const zipStream = createWriteStream(zipPath);
|
||||
zip.outputStream.pipe(zipStream);
|
||||
|
||||
zip.outputStream.once('error', e);
|
||||
zipStream.once('error', e);
|
||||
zipStream.once('finish', () => c(zipPath));
|
||||
});
|
||||
}
|
||||
|
||||
export function extract(zipPath: string, targetPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise<void> {
|
||||
const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : '');
|
||||
|
||||
let promise = openZip(zipPath, true);
|
||||
|
||||
if (options.overwrite) {
|
||||
promise = promise.then(zipfile => rimraf(targetPath).then(() => zipfile));
|
||||
}
|
||||
|
||||
return promise.then(zipfile => extractZip(zipfile, targetPath, { sourcePathRegex }, token));
|
||||
}
|
||||
|
||||
function read(zipPath: string, filePath: string): Promise<Readable> {
|
||||
return openZip(zipPath).then(zipfile => {
|
||||
return new Promise<Readable>((c, e) => {
|
||||
zipfile.on('entry', (entry: Entry) => {
|
||||
if (entry.fileName === filePath) {
|
||||
openZipStream(zipfile, entry).then(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): Promise<Buffer> {
|
||||
return read(zipPath, filePath).then(stream => {
|
||||
return new Promise<Buffer>((c, e) => {
|
||||
const buffers: Buffer[] = [];
|
||||
stream.once('error', e);
|
||||
stream.on('data', (b: Buffer) => buffers.push(b));
|
||||
stream.on('end', () => c(Buffer.concat(buffers)));
|
||||
});
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user