mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 09:08:48 +01:00
debt - merge extfs and pfs
This commit is contained in:
@@ -4,9 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { startsWithIgnoreCase, equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { startsWithIgnoreCase, equalsIgnoreCase, endsWith, rtrim } from 'vs/base/common/strings';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { sep, posix } from 'vs/base/common/path';
|
||||
import { sep, posix, isAbsolute, join, normalize } from 'vs/base/common/path';
|
||||
|
||||
function isPathSeparator(code: number) {
|
||||
return code === CharCode.Slash || code === CharCode.Backslash;
|
||||
@@ -227,4 +227,56 @@ export function isEqualOrParent(path: string, candidate: string, ignoreCase?: bo
|
||||
|
||||
export function isWindowsDriveLetter(char0: number): boolean {
|
||||
return char0 >= CharCode.A && char0 <= CharCode.Z || char0 >= CharCode.a && char0 <= CharCode.z;
|
||||
}
|
||||
|
||||
export function sanitizeFilePath(candidate: string, cwd: string): string {
|
||||
|
||||
// Special case: allow to open a drive letter without trailing backslash
|
||||
if (isWindows && endsWith(candidate, ':')) {
|
||||
candidate += sep;
|
||||
}
|
||||
|
||||
// Ensure absolute
|
||||
if (!isAbsolute(candidate)) {
|
||||
candidate = join(cwd, candidate);
|
||||
}
|
||||
|
||||
// Ensure normalized
|
||||
candidate = normalize(candidate);
|
||||
|
||||
// Ensure no trailing slash/backslash
|
||||
if (isWindows) {
|
||||
candidate = rtrim(candidate, sep);
|
||||
|
||||
// Special case: allow to open drive root ('C:\')
|
||||
if (endsWith(candidate, ':')) {
|
||||
candidate += sep;
|
||||
}
|
||||
|
||||
} else {
|
||||
candidate = rtrim(candidate, sep);
|
||||
|
||||
// Special case: allow to open root ('/')
|
||||
if (!candidate) {
|
||||
candidate = sep;
|
||||
}
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
export function isRootOrDriveLetter(path: string): boolean {
|
||||
const pathNormalized = normalize(path);
|
||||
|
||||
if (isWindows) {
|
||||
if (path.length > 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isWindowsDriveLetter(pathNormalized.charCodeAt(0))
|
||||
&& pathNormalized.charCodeAt(1) === CharCode.Colon
|
||||
&& (path.length === 2 || pathNormalized.charCodeAt(2) === CharCode.Backslash);
|
||||
}
|
||||
|
||||
return pathNormalized === posix.sep;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import * as objects from 'vs/base/common/objects';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as json from 'vs/base/common/json';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { watch } from 'vs/base/node/pfs';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
export interface IConfigurationChangeEvent<T> {
|
||||
@@ -149,7 +149,7 @@ export class ConfigWatcher<T> implements IConfigWatcher<T>, IDisposable {
|
||||
return; // avoid watchers that will never get disposed by checking for being disposed
|
||||
}
|
||||
|
||||
this.disposables.push(extfs.watch(path,
|
||||
this.disposables.push(watch(path,
|
||||
(type, file) => this.onConfigFileChange(type, file, isParentFolder),
|
||||
(error: string) => this.options.onError(error)
|
||||
));
|
||||
|
||||
@@ -1,530 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 paths from 'vs/base/common/path';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
import { encode, encodeStream } from 'vs/base/node/encoding';
|
||||
import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export function readdirSync(path: string): string[] {
|
||||
// Mac: uses NFD unicode form on disk, but we want NFC
|
||||
// See also https://github.com/nodejs/node/issues/2165
|
||||
if (platform.isMacintosh) {
|
||||
return fs.readdirSync(path).map(c => normalizeNFC(c));
|
||||
}
|
||||
|
||||
return fs.readdirSync(path);
|
||||
}
|
||||
|
||||
export function readdir(path: string, callback: (error: Error | null, files: string[]) => void): void {
|
||||
// Mac: uses NFD unicode form on disk, but we want NFC
|
||||
// See also https://github.com/nodejs/node/issues/2165
|
||||
if (platform.isMacintosh) {
|
||||
return fs.readdir(path, (error, children) => {
|
||||
if (error) {
|
||||
return callback(error, []);
|
||||
}
|
||||
|
||||
return callback(null, children.map(c => normalizeNFC(c)));
|
||||
});
|
||||
}
|
||||
|
||||
return fs.readdir(path, callback);
|
||||
}
|
||||
|
||||
export interface IStatAndLink {
|
||||
stat: fs.Stats;
|
||||
isSymbolicLink: boolean;
|
||||
}
|
||||
|
||||
export function statLink(path: string, callback: (error: Error | null, statAndIsLink: IStatAndLink | null) => void): void {
|
||||
fs.lstat(path, (error, lstat) => {
|
||||
if (error || lstat.isSymbolicLink()) {
|
||||
fs.stat(path, (error, stat) => {
|
||||
if (error) {
|
||||
return callback(error, null);
|
||||
}
|
||||
|
||||
callback(null, { stat, isSymbolicLink: lstat && lstat.isSymbolicLink() });
|
||||
});
|
||||
} else {
|
||||
callback(null, { stat: lstat, isSymbolicLink: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Deletes the given path by first moving it out of the workspace. This has two benefits. For one, the operation can return fast because
|
||||
// after the rename, the contents are out of the workspace although not yet deleted. The greater benefit however is that this operation
|
||||
// will fail in case any file is used by another process. fs.unlink() in node will not bail if a file unlinked is used by another process.
|
||||
// However, the consequences are bad as outlined in all the related bugs from https://github.com/joyent/node/issues/7164
|
||||
export function del(path: string, tmpFolder: string, callback: (error: Error | null) => void, done?: (error: Error | null) => void): void {
|
||||
fs.exists(path, exists => {
|
||||
if (!exists) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
fs.stat(path, (err, stat) => {
|
||||
if (err || !stat) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Special windows workaround: A file or folder that ends with a "." cannot be moved to another place
|
||||
// because it is not a valid file name. In this case, we really have to do the deletion without prior move.
|
||||
if (path[path.length - 1] === '.' || strings.endsWith(path, './') || strings.endsWith(path, '.\\')) {
|
||||
return rmRecursive(path, callback);
|
||||
}
|
||||
|
||||
const pathInTemp = paths.join(tmpFolder, uuid.generateUuid());
|
||||
fs.rename(path, pathInTemp, (error: Error | null) => {
|
||||
if (error) {
|
||||
return rmRecursive(path, callback); // if rename fails, delete without tmp dir
|
||||
}
|
||||
|
||||
// Return early since the move succeeded
|
||||
callback(null);
|
||||
|
||||
// do the heavy deletion outside the callers callback
|
||||
rmRecursive(pathInTemp, error => {
|
||||
if (done) {
|
||||
done(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function rmRecursive(path: string, callback: (error: Error | null) => void): void {
|
||||
if (path === paths.win32.sep || path === paths.posix.sep) {
|
||||
return callback(new Error('Will not delete root!'));
|
||||
}
|
||||
|
||||
fs.exists(path, exists => {
|
||||
if (!exists) {
|
||||
callback(null);
|
||||
} else {
|
||||
fs.lstat(path, (err, stat) => {
|
||||
if (err || !stat) {
|
||||
callback(err);
|
||||
} else if (!stat.isDirectory() || stat.isSymbolicLink() /* !!! never recurse into links when deleting !!! */) {
|
||||
const mode = stat.mode;
|
||||
if (!(mode & 128)) { // 128 === 0200
|
||||
fs.chmod(path, mode | 128, (err: Error) => { // 128 === 0200
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
fs.unlink(path, callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fs.unlink(path, callback);
|
||||
}
|
||||
} else {
|
||||
readdir(path, (err, children) => {
|
||||
if (err || !children) {
|
||||
callback(err);
|
||||
} else if (children.length === 0) {
|
||||
fs.rmdir(path, callback);
|
||||
} else {
|
||||
let firstError: Error | null = null;
|
||||
let childrenLeft = children.length;
|
||||
children.forEach(child => {
|
||||
rmRecursive(paths.join(path, child), (err: Error) => {
|
||||
childrenLeft--;
|
||||
if (err) {
|
||||
firstError = firstError || err;
|
||||
}
|
||||
|
||||
if (childrenLeft === 0) {
|
||||
if (firstError) {
|
||||
callback(firstError);
|
||||
} else {
|
||||
fs.rmdir(path, callback);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function delSync(path: string): void {
|
||||
if (path === paths.win32.sep || path === paths.posix.sep) {
|
||||
throw new Error('Will not delete root!');
|
||||
}
|
||||
|
||||
try {
|
||||
const stat = fs.lstatSync(path);
|
||||
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
||||
readdirSync(path).forEach(child => delSync(paths.join(path, child)));
|
||||
fs.rmdirSync(path);
|
||||
} else {
|
||||
fs.unlinkSync(path);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return; // not found
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IWriteFileOptions {
|
||||
mode?: number;
|
||||
flag?: string;
|
||||
encoding?: {
|
||||
charset: string;
|
||||
addBOM: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface IEnsuredWriteFileOptions extends IWriteFileOptions {
|
||||
mode: number;
|
||||
flag: string;
|
||||
}
|
||||
|
||||
let canFlush = true;
|
||||
export function writeFileAndFlush(path: string, data: string | Buffer | NodeJS.ReadableStream | Uint8Array, options: IWriteFileOptions | undefined, callback: (error?: Error) => void): void {
|
||||
const ensuredOptions = ensureWriteOptions(options);
|
||||
|
||||
if (typeof data === 'string' || Buffer.isBuffer(data) || data instanceof Uint8Array) {
|
||||
doWriteFileAndFlush(path, data, ensuredOptions, callback);
|
||||
} else {
|
||||
doWriteFileStreamAndFlush(path, data, ensuredOptions, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function doWriteFileStreamAndFlush(path: string, reader: NodeJS.ReadableStream, options: IEnsuredWriteFileOptions, callback: (error?: Error) => void): void {
|
||||
|
||||
// finish only once
|
||||
let finished = false;
|
||||
const finish = (error?: Error) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
|
||||
// in error cases we need to manually close streams
|
||||
// if the write stream was successfully opened
|
||||
if (error) {
|
||||
if (isOpen) {
|
||||
writer.once('close', () => callback(error));
|
||||
writer.destroy();
|
||||
} else {
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise just return without error
|
||||
else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// create writer to target. we set autoClose: false because we want to use the streams
|
||||
// file descriptor to call fs.fdatasync to ensure the data is flushed to disk
|
||||
const writer = fs.createWriteStream(path, { mode: options.mode, flags: options.flag, autoClose: false });
|
||||
|
||||
// Event: 'open'
|
||||
// Purpose: save the fd for later use and start piping
|
||||
// Notes: will not be called when there is an error opening the file descriptor!
|
||||
let fd: number;
|
||||
let isOpen: boolean;
|
||||
writer.once('open', descriptor => {
|
||||
fd = descriptor;
|
||||
isOpen = true;
|
||||
|
||||
// if an encoding is provided, we need to pipe the stream through
|
||||
// an encoder stream and forward the encoding related options
|
||||
if (options.encoding) {
|
||||
reader = reader.pipe(encodeStream(options.encoding.charset, { addBOM: options.encoding.addBOM }));
|
||||
}
|
||||
|
||||
// start data piping only when we got a successful open. this ensures that we do
|
||||
// not consume the stream when an error happens and helps to fix this issue:
|
||||
// https://github.com/Microsoft/vscode/issues/42542
|
||||
reader.pipe(writer);
|
||||
});
|
||||
|
||||
// Event: 'error'
|
||||
// Purpose: to return the error to the outside and to close the write stream (does not happen automatically)
|
||||
reader.once('error', error => finish(error));
|
||||
writer.once('error', error => finish(error));
|
||||
|
||||
// Event: 'finish'
|
||||
// Purpose: use fs.fdatasync to flush the contents to disk
|
||||
// Notes: event is called when the writer has finished writing to the underlying resource. we must call writer.close()
|
||||
// because we have created the WriteStream with autoClose: false
|
||||
writer.once('finish', () => {
|
||||
|
||||
// flush to disk
|
||||
if (canFlush && isOpen) {
|
||||
fs.fdatasync(fd, (syncError: Error) => {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
writer.destroy();
|
||||
});
|
||||
} else {
|
||||
writer.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// Event: 'close'
|
||||
// Purpose: signal we are done to the outside
|
||||
// Notes: event is called when the writer's filedescriptor is closed
|
||||
writer.once('close', () => finish());
|
||||
}
|
||||
|
||||
// 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) => void): void {
|
||||
if (options.encoding) {
|
||||
data = encode(data instanceof Uint8Array ? Buffer.from(data) : data, options.encoding.charset, { addBOM: options.encoding.addBOM });
|
||||
}
|
||||
|
||||
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) => {
|
||||
|
||||
// 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 writeFileAndFlushSync(path: string, data: string | Buffer, options?: IWriteFileOptions): void {
|
||||
const ensuredOptions = ensureWriteOptions(options);
|
||||
|
||||
if (ensuredOptions.encoding) {
|
||||
data = encode(data, ensuredOptions.encoding.charset, { addBOM: ensuredOptions.encoding.addBOM });
|
||||
}
|
||||
|
||||
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',
|
||||
encoding: options.encoding
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = paths.dirname(path);
|
||||
if (path === dir) { // end recursion
|
||||
return path;
|
||||
}
|
||||
|
||||
const name = (paths.basename(path) /* can be '' for windows drive letters */ || path).toLowerCase();
|
||||
try {
|
||||
const entries = readdirSync(dir);
|
||||
const found = entries.filter(e => e.toLowerCase() === name); // use a case insensitive search
|
||||
if (found.length === 1) {
|
||||
// on a case sensitive filesystem we cannot determine here, whether the file exists or not, hence we need the 'file exists' precondition
|
||||
const prefix = realcaseSync(dir); // recurse
|
||||
if (prefix) {
|
||||
return paths.join(prefix, found[0]);
|
||||
}
|
||||
} else if (found.length > 1) {
|
||||
// must be a case sensitive $filesystem
|
||||
const ix = found.indexOf(name);
|
||||
if (ix >= 0) { // case sensitive
|
||||
const prefix = realcaseSync(dir); // recurse
|
||||
if (prefix) {
|
||||
return paths.join(prefix, found[ix]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// silently ignore error
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function realpathSync(path: string): string {
|
||||
try {
|
||||
return fs.realpathSync(path);
|
||||
} catch (error) {
|
||||
|
||||
// We hit an error calling fs.realpathSync(). Since fs.realpathSync() is doing some path normalization
|
||||
// we now do a similar normalization and then try again if we can access the path with read
|
||||
// permissions at least. If that succeeds, we return that path.
|
||||
// fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is
|
||||
// to not resolve links but to simply see if the path is read accessible or not.
|
||||
const normalizedPath = normalizePath(path);
|
||||
fs.accessSync(normalizedPath, fs.constants.R_OK); // throws in case of an error
|
||||
|
||||
return normalizedPath;
|
||||
}
|
||||
}
|
||||
|
||||
export function realpath(path: string, callback: (error: Error | null, realpath: string) => void): void {
|
||||
return fs.realpath(path, (error, realpath) => {
|
||||
if (!error) {
|
||||
return callback(null, realpath);
|
||||
}
|
||||
|
||||
// We hit an error calling fs.realpath(). Since fs.realpath() is doing some path normalization
|
||||
// we now do a similar normalization and then try again if we can access the path with read
|
||||
// permissions at least. If that succeeds, we return that path.
|
||||
// fs.realpath() is resolving symlinks and that can fail in certain cases. The workaround is
|
||||
// to not resolve links but to simply see if the path is read accessible or not.
|
||||
const normalizedPath = normalizePath(path);
|
||||
|
||||
return fs.access(normalizedPath, fs.constants.R_OK, error => {
|
||||
return callback(error, normalizedPath);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function normalizePath(path: string): string {
|
||||
return strings.rtrim(paths.normalize(path), paths.sep);
|
||||
}
|
||||
|
||||
export function watch(path: string, onChange: (type: string, path?: string) => void, onError: (error: string) => void): IDisposable {
|
||||
try {
|
||||
const watcher = fs.watch(path);
|
||||
|
||||
watcher.on('change', (type, raw) => {
|
||||
let file: string | undefined;
|
||||
if (raw) { // https://github.com/Microsoft/vscode/issues/38191
|
||||
file = raw.toString();
|
||||
if (platform.isMacintosh) {
|
||||
// Mac: uses NFD unicode form on disk, but we want NFC
|
||||
// See also https://github.com/nodejs/node/issues/2165
|
||||
file = normalizeNFC(file);
|
||||
}
|
||||
}
|
||||
|
||||
onChange(type, file);
|
||||
});
|
||||
|
||||
watcher.on('error', (code: number, signal: string) => onError(`Failed to watch ${path} for changes (${code}, ${signal})`));
|
||||
|
||||
return toDisposable(() => {
|
||||
watcher.removeAllListeners();
|
||||
watcher.close();
|
||||
});
|
||||
} catch (error) {
|
||||
fs.exists(path, exists => {
|
||||
if (exists) {
|
||||
onError(`Failed to watch ${path} for changes (${error.toString()})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
export function sanitizeFilePath(candidate: string, cwd: string): string {
|
||||
|
||||
// Special case: allow to open a drive letter without trailing backslash
|
||||
if (platform.isWindows && strings.endsWith(candidate, ':')) {
|
||||
candidate += paths.sep;
|
||||
}
|
||||
|
||||
// Ensure absolute
|
||||
if (!paths.isAbsolute(candidate)) {
|
||||
candidate = paths.join(cwd, candidate);
|
||||
}
|
||||
|
||||
// Ensure normalized
|
||||
candidate = paths.normalize(candidate);
|
||||
|
||||
// Ensure no trailing slash/backslash
|
||||
if (platform.isWindows) {
|
||||
candidate = strings.rtrim(candidate, paths.sep);
|
||||
|
||||
// Special case: allow to open drive root ('C:\')
|
||||
if (strings.endsWith(candidate, ':')) {
|
||||
candidate += paths.sep;
|
||||
}
|
||||
|
||||
} else {
|
||||
candidate = strings.rtrim(candidate, paths.sep);
|
||||
|
||||
// Special case: allow to open root ('/')
|
||||
if (!candidate) {
|
||||
candidate = paths.sep;
|
||||
}
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
+407
-62
@@ -3,7 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { join, dirname } from 'vs/base/common/path';
|
||||
import { Queue } from 'vs/base/common/async';
|
||||
import * as fs from 'fs';
|
||||
@@ -13,9 +12,145 @@ import { Event } from 'vs/base/common/event';
|
||||
import { endsWith } from 'vs/base/common/strings';
|
||||
import { promisify } from 'util';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { isRootOrDriveLetter } from 'vs/base/common/extpath';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
import { toDisposable, Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { encode, encodeStream } from 'vs/base/node/encoding';
|
||||
|
||||
export function readdir(path: string): Promise<string[]> {
|
||||
return promisify(extfs.readdir)(path);
|
||||
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 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> {
|
||||
@@ -26,42 +161,35 @@ export function chmod(path: string, mode: number): Promise<void> {
|
||||
return promisify(fs.chmod)(path, mode);
|
||||
}
|
||||
|
||||
export async function rimraf(path: string): Promise<void> {
|
||||
try {
|
||||
const stat = await lstat(path);
|
||||
|
||||
// Folder delete (recursive) - NOT for symbolic links though!
|
||||
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
||||
const children = await readdir(path);
|
||||
|
||||
await Promise.all(children.map(child => rimraf(join(path, child))));
|
||||
|
||||
await promisify(fs.rmdir)(path);
|
||||
}
|
||||
|
||||
// Single file delete
|
||||
else {
|
||||
return unlink(path);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT') {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function realpath(path: string): Promise<string> {
|
||||
return promisify(extfs.realpath)(path);
|
||||
}
|
||||
|
||||
export function stat(path: string): Promise<fs.Stats> {
|
||||
return promisify(fs.stat)(path);
|
||||
}
|
||||
|
||||
export function statLink(path: string): Promise<{ stat: fs.Stats, isSymbolicLink: boolean }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
extfs.statLink(path, (error, result) => error ? reject(error) : resolve(result!));
|
||||
});
|
||||
export interface IStatAndLink {
|
||||
stat: fs.Stats;
|
||||
isSymbolicLink: boolean;
|
||||
}
|
||||
|
||||
export async function statLink(path: string): Promise<IStatAndLink> {
|
||||
|
||||
// First stat the link
|
||||
let linkStat: fs.Stats | undefined;
|
||||
let linkStatError: NodeJS.ErrnoException | undefined;
|
||||
try {
|
||||
linkStat = await lstat(path);
|
||||
} catch (error) {
|
||||
linkStatError = error;
|
||||
}
|
||||
|
||||
// Then stat the target and return that
|
||||
const isLink = !!(linkStat && linkStat.isSymbolicLink());
|
||||
if (linkStatError || isLink) {
|
||||
const fileStat = await stat(path);
|
||||
|
||||
return { stat: fileStat, isSymbolicLink: isLink };
|
||||
}
|
||||
|
||||
return { stat: linkStat!, isSymbolicLink: false };
|
||||
}
|
||||
|
||||
export function lstat(path: string): Promise<fs.Stats> {
|
||||
@@ -105,19 +233,15 @@ export function readFile(path: string, encoding?: string): Promise<Buffer | stri
|
||||
// Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly.
|
||||
const writeFilePathQueue: { [path: string]: Queue<void> } = Object.create(null);
|
||||
|
||||
export function writeFile(path: string, data: string, options?: extfs.IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: Buffer, options?: extfs.IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: Uint8Array, options?: extfs.IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: NodeJS.ReadableStream, options?: extfs.IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: string | Buffer | NodeJS.ReadableStream | Uint8Array, options?: extfs.IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: string | Buffer | NodeJS.ReadableStream | Uint8Array, options?: extfs.IWriteFileOptions): Promise<void> {
|
||||
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: NodeJS.ReadableStream, options?: IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: string | Buffer | NodeJS.ReadableStream | Uint8Array, options?: IWriteFileOptions): Promise<void>;
|
||||
export function writeFile(path: string, data: string | Buffer | NodeJS.ReadableStream | Uint8Array, options?: IWriteFileOptions): Promise<void> {
|
||||
const queueKey = toQueueKey(path);
|
||||
|
||||
return ensureWriteFileQueue(queueKey).queue(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
extfs.writeFileAndFlush(path, data, options, error => error ? reject(error) : resolve());
|
||||
});
|
||||
});
|
||||
return ensureWriteFileQueue(queueKey).queue(() => writeFileAndFlush(path, data, options));
|
||||
}
|
||||
|
||||
function toQueueKey(path: string): string {
|
||||
@@ -145,6 +269,204 @@ function ensureWriteFileQueue(queueKey: string): Queue<void> {
|
||||
return writeFileQueue;
|
||||
}
|
||||
|
||||
export interface IWriteFileOptions {
|
||||
mode?: number;
|
||||
flag?: string;
|
||||
encoding?: {
|
||||
charset: string;
|
||||
addBOM: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface IEnsuredWriteFileOptions extends IWriteFileOptions {
|
||||
mode: number;
|
||||
flag: string;
|
||||
}
|
||||
|
||||
let canFlush = true;
|
||||
function writeFileAndFlush(path: string, data: string | Buffer | NodeJS.ReadableStream | Uint8Array, options: IWriteFileOptions | undefined): Promise<void> {
|
||||
const ensuredOptions = ensureWriteOptions(options);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof data === 'string' || Buffer.isBuffer(data) || data instanceof Uint8Array) {
|
||||
doWriteFileAndFlush(path, data, ensuredOptions, error => error ? reject(error) : resolve());
|
||||
} else {
|
||||
doWriteFileStreamAndFlush(path, data, ensuredOptions, error => error ? reject(error) : resolve());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function doWriteFileStreamAndFlush(path: string, reader: NodeJS.ReadableStream, options: IEnsuredWriteFileOptions, callback: (error?: Error) => void): void {
|
||||
|
||||
// finish only once
|
||||
let finished = false;
|
||||
const finish = (error?: Error) => {
|
||||
if (!finished) {
|
||||
finished = true;
|
||||
|
||||
// in error cases we need to manually close streams
|
||||
// if the write stream was successfully opened
|
||||
if (error) {
|
||||
if (isOpen) {
|
||||
writer.once('close', () => callback(error));
|
||||
writer.destroy();
|
||||
} else {
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise just return without error
|
||||
else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// create writer to target. we set autoClose: false because we want to use the streams
|
||||
// file descriptor to call fs.fdatasync to ensure the data is flushed to disk
|
||||
const writer = fs.createWriteStream(path, { mode: options.mode, flags: options.flag, autoClose: false });
|
||||
|
||||
// Event: 'open'
|
||||
// Purpose: save the fd for later use and start piping
|
||||
// Notes: will not be called when there is an error opening the file descriptor!
|
||||
let fd: number;
|
||||
let isOpen: boolean;
|
||||
writer.once('open', descriptor => {
|
||||
fd = descriptor;
|
||||
isOpen = true;
|
||||
|
||||
// if an encoding is provided, we need to pipe the stream through
|
||||
// an encoder stream and forward the encoding related options
|
||||
if (options.encoding) {
|
||||
reader = reader.pipe(encodeStream(options.encoding.charset, { addBOM: options.encoding.addBOM }));
|
||||
}
|
||||
|
||||
// start data piping only when we got a successful open. this ensures that we do
|
||||
// not consume the stream when an error happens and helps to fix this issue:
|
||||
// https://github.com/Microsoft/vscode/issues/42542
|
||||
reader.pipe(writer);
|
||||
});
|
||||
|
||||
// Event: 'error'
|
||||
// Purpose: to return the error to the outside and to close the write stream (does not happen automatically)
|
||||
reader.once('error', error => finish(error));
|
||||
writer.once('error', error => finish(error));
|
||||
|
||||
// Event: 'finish'
|
||||
// Purpose: use fs.fdatasync to flush the contents to disk
|
||||
// Notes: event is called when the writer has finished writing to the underlying resource. we must call writer.close()
|
||||
// because we have created the WriteStream with autoClose: false
|
||||
writer.once('finish', () => {
|
||||
|
||||
// flush to disk
|
||||
if (canFlush && isOpen) {
|
||||
fs.fdatasync(fd, (syncError: Error) => {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
writer.destroy();
|
||||
});
|
||||
} else {
|
||||
writer.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// Event: 'close'
|
||||
// Purpose: signal we are done to the outside
|
||||
// Notes: event is called when the writer's filedescriptor is closed
|
||||
writer.once('close', () => finish());
|
||||
}
|
||||
|
||||
// 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) => void): void {
|
||||
if (options.encoding) {
|
||||
data = encode(data instanceof Uint8Array ? Buffer.from(data) : data, options.encoding.charset, { addBOM: options.encoding.addBOM });
|
||||
}
|
||||
|
||||
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) => {
|
||||
|
||||
// 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 (ensuredOptions.encoding) {
|
||||
data = encode(data, ensuredOptions.encoding.charset, { addBOM: ensuredOptions.encoding.addBOM });
|
||||
}
|
||||
|
||||
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',
|
||||
encoding: options.encoding
|
||||
};
|
||||
}
|
||||
|
||||
export async function readDirsInDir(dirPath: string): Promise<string[]> {
|
||||
const children = await readdir(dirPath);
|
||||
const directories: string[] = [];
|
||||
@@ -178,20 +500,6 @@ export async function fileExists(path: string): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
let tmpDir: string | null = null;
|
||||
function getTmpDir(): string {
|
||||
if (!tmpDir) {
|
||||
tmpDir = os.tmpdir();
|
||||
}
|
||||
return tmpDir;
|
||||
}
|
||||
|
||||
export function del(path: string, tmp = getTmpDir()): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
extfs.del(path, tmp, error => error ? reject(error) : resolve());
|
||||
});
|
||||
}
|
||||
|
||||
export function whenDeleted(path: string): Promise<void> {
|
||||
|
||||
// Complete when wait marker file is deleted
|
||||
@@ -249,7 +557,7 @@ export async function move(source: string, target: string): Promise<void> {
|
||||
// really possible to move then, at least on UNC devices.
|
||||
if (source.toLowerCase() !== target.toLowerCase() && error.code === 'EXDEV' || endsWith(source, '.')) {
|
||||
await copy(source, target);
|
||||
await del(source);
|
||||
await rimraf(source, RimRafMode.MOVE);
|
||||
await updateMtime(target);
|
||||
} else {
|
||||
throw error;
|
||||
@@ -355,10 +663,47 @@ export async function mkdirp(path: string, mode?: number, token?: CancellationTo
|
||||
// ENOENT: a parent folder does not exist yet, continue
|
||||
// to create the parent folder and then try again.
|
||||
if (error.code === 'ENOENT') {
|
||||
return mkdirp(dirname(path), mode).then(mkdir);
|
||||
await mkdirp(dirname(path), mode);
|
||||
|
||||
return mkdir();
|
||||
}
|
||||
|
||||
// Any other error
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
export function watch(path: string, onChange: (type: string, path?: string) => void, onError: (error: string) => void): IDisposable {
|
||||
try {
|
||||
const watcher = fs.watch(path);
|
||||
|
||||
watcher.on('change', (type, raw) => {
|
||||
let file: string | undefined;
|
||||
if (raw) { // https://github.com/Microsoft/vscode/issues/38191
|
||||
file = raw.toString();
|
||||
if (platform.isMacintosh) {
|
||||
// Mac: uses NFD unicode form on disk, but we want NFC
|
||||
// See also https://github.com/nodejs/node/issues/2165
|
||||
file = normalizeNFC(file);
|
||||
}
|
||||
}
|
||||
|
||||
onChange(type, file);
|
||||
});
|
||||
|
||||
watcher.on('error', (code: number, signal: string) => onError(`Failed to watch ${path} for changes (${code}, ${signal})`));
|
||||
|
||||
return toDisposable(() => {
|
||||
watcher.removeAllListeners();
|
||||
watcher.close();
|
||||
});
|
||||
} catch (error) {
|
||||
fs.exists(path, exists => {
|
||||
if (exists) {
|
||||
onError(`Failed to watch ${path} for changes (${error.toString()})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Disposable.None;
|
||||
}
|
||||
@@ -16,7 +16,6 @@ suite('Paths', () => {
|
||||
});
|
||||
|
||||
test('getRoot', () => {
|
||||
|
||||
assert.equal(extpath.getRoot('/user/far'), '/');
|
||||
assert.equal(extpath.getRoot('\\\\server\\share\\some\\path'), '//server/share/');
|
||||
assert.equal(extpath.getRoot('//server/share/some/path'), '//server/share/');
|
||||
@@ -65,4 +64,54 @@ suite('Paths', () => {
|
||||
assert.ok(!extpath.isValidBasename('tes"t.txt'));
|
||||
}
|
||||
});
|
||||
|
||||
test('sanitizeFilePath', () => {
|
||||
if (platform.isWindows) {
|
||||
assert.equal(extpath.sanitizeFilePath('.', 'C:\\the\\cwd'), 'C:\\the\\cwd');
|
||||
assert.equal(extpath.sanitizeFilePath('', 'C:\\the\\cwd'), 'C:\\the\\cwd');
|
||||
|
||||
assert.equal(extpath.sanitizeFilePath('C:', 'C:\\the\\cwd'), 'C:\\');
|
||||
assert.equal(extpath.sanitizeFilePath('C:\\', 'C:\\the\\cwd'), 'C:\\');
|
||||
assert.equal(extpath.sanitizeFilePath('C:\\\\', 'C:\\the\\cwd'), 'C:\\');
|
||||
|
||||
assert.equal(extpath.sanitizeFilePath('C:\\folder\\my.txt', 'C:\\the\\cwd'), 'C:\\folder\\my.txt');
|
||||
assert.equal(extpath.sanitizeFilePath('C:\\folder\\my', 'C:\\the\\cwd'), 'C:\\folder\\my');
|
||||
assert.equal(extpath.sanitizeFilePath('C:\\folder\\..\\my', 'C:\\the\\cwd'), 'C:\\my');
|
||||
assert.equal(extpath.sanitizeFilePath('C:\\folder\\my\\', 'C:\\the\\cwd'), 'C:\\folder\\my');
|
||||
assert.equal(extpath.sanitizeFilePath('C:\\folder\\my\\\\\\', 'C:\\the\\cwd'), 'C:\\folder\\my');
|
||||
|
||||
assert.equal(extpath.sanitizeFilePath('my.txt', 'C:\\the\\cwd'), 'C:\\the\\cwd\\my.txt');
|
||||
assert.equal(extpath.sanitizeFilePath('my.txt\\', 'C:\\the\\cwd'), 'C:\\the\\cwd\\my.txt');
|
||||
|
||||
assert.equal(extpath.sanitizeFilePath('\\\\localhost\\folder\\my', 'C:\\the\\cwd'), '\\\\localhost\\folder\\my');
|
||||
assert.equal(extpath.sanitizeFilePath('\\\\localhost\\folder\\my\\', 'C:\\the\\cwd'), '\\\\localhost\\folder\\my');
|
||||
} else {
|
||||
assert.equal(extpath.sanitizeFilePath('.', '/the/cwd'), '/the/cwd');
|
||||
assert.equal(extpath.sanitizeFilePath('', '/the/cwd'), '/the/cwd');
|
||||
assert.equal(extpath.sanitizeFilePath('/', '/the/cwd'), '/');
|
||||
|
||||
assert.equal(extpath.sanitizeFilePath('/folder/my.txt', '/the/cwd'), '/folder/my.txt');
|
||||
assert.equal(extpath.sanitizeFilePath('/folder/my', '/the/cwd'), '/folder/my');
|
||||
assert.equal(extpath.sanitizeFilePath('/folder/../my', '/the/cwd'), '/my');
|
||||
assert.equal(extpath.sanitizeFilePath('/folder/my/', '/the/cwd'), '/folder/my');
|
||||
assert.equal(extpath.sanitizeFilePath('/folder/my///', '/the/cwd'), '/folder/my');
|
||||
|
||||
assert.equal(extpath.sanitizeFilePath('my.txt', '/the/cwd'), '/the/cwd/my.txt');
|
||||
assert.equal(extpath.sanitizeFilePath('my.txt/', '/the/cwd'), '/the/cwd/my.txt');
|
||||
}
|
||||
});
|
||||
|
||||
test('isRoot', () => {
|
||||
if (platform.isWindows) {
|
||||
assert.ok(extpath.isRootOrDriveLetter('c:'));
|
||||
assert.ok(extpath.isRootOrDriveLetter('D:'));
|
||||
assert.ok(extpath.isRootOrDriveLetter('D:/'));
|
||||
assert.ok(extpath.isRootOrDriveLetter('D:\\'));
|
||||
assert.ok(!extpath.isRootOrDriveLetter('D:\\path'));
|
||||
assert.ok(!extpath.isRootOrDriveLetter('D:/path'));
|
||||
} else {
|
||||
assert.ok(extpath.isRootOrDriveLetter('/'));
|
||||
assert.ok(!extpath.isRootOrDriveLetter('/path'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,524 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { Readable } from 'stream';
|
||||
import { canNormalize } from 'vs/base/common/normalization';
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
|
||||
const ignore = () => { };
|
||||
|
||||
const mkdirp = (path: string, mode: number, callback: (error: any) => void) => {
|
||||
pfs.mkdirp(path, mode).then(() => callback(null), error => callback(error));
|
||||
};
|
||||
|
||||
const chunkSize = 64 * 1024;
|
||||
const readError = 'Error while reading';
|
||||
function toReadable(value: string, throwError?: boolean): Readable {
|
||||
const totalChunks = Math.ceil(value.length / chunkSize);
|
||||
const stringChunks: string[] = [];
|
||||
|
||||
for (let i = 0, j = 0; i < totalChunks; ++i, j += chunkSize) {
|
||||
stringChunks[i] = value.substr(j, chunkSize);
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
return new Readable({
|
||||
read: function () {
|
||||
if (throwError) {
|
||||
this.emit('error', new Error(readError));
|
||||
}
|
||||
|
||||
let res!: string;
|
||||
let canPush = true;
|
||||
while (canPush && (res = stringChunks[counter++])) {
|
||||
canPush = this.push(res);
|
||||
}
|
||||
|
||||
// EOS
|
||||
if (!res) {
|
||||
this.push(null);
|
||||
}
|
||||
},
|
||||
encoding: 'utf8'
|
||||
});
|
||||
}
|
||||
|
||||
suite('Extfs', () => {
|
||||
|
||||
test('stat link', function (done) {
|
||||
if (isWindows) {
|
||||
// Symlinks are not the same on win, and we can not create them programitically without admin privileges
|
||||
return done();
|
||||
}
|
||||
|
||||
const id1 = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id1);
|
||||
const directory = path.join(parentDir, 'extfs', id1);
|
||||
|
||||
const id2 = uuid.generateUuid();
|
||||
const symbolicLink = path.join(parentDir, 'extfs', id2);
|
||||
|
||||
mkdirp(directory, 493, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
fs.symlinkSync(directory, symbolicLink);
|
||||
|
||||
extfs.statLink(directory, (error, statAndIsLink) => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.ok(!statAndIsLink!.isSymbolicLink);
|
||||
|
||||
extfs.statLink(symbolicLink, (error, statAndIsLink) => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.ok(statAndIsLink!.isSymbolicLink);
|
||||
extfs.delSync(directory);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('delSync - swallows file not found error', function () {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
|
||||
extfs.delSync(newDir);
|
||||
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
});
|
||||
|
||||
test('delSync - simple', function (done) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents');
|
||||
fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents');
|
||||
|
||||
extfs.delSync(newDir);
|
||||
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
done();
|
||||
}); // 493 = 0755
|
||||
});
|
||||
|
||||
test('delSync - recursive folder structure', function (done) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents');
|
||||
fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents');
|
||||
|
||||
fs.mkdirSync(path.join(newDir, 'somefolder'));
|
||||
fs.writeFileSync(path.join(newDir, 'somefolder', 'somefile.txt'), 'Contents');
|
||||
|
||||
extfs.delSync(newDir);
|
||||
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
done();
|
||||
}); // 493 = 0755
|
||||
});
|
||||
|
||||
test('readdir', function (done) {
|
||||
if (canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id, 'öäü');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
extfs.readdir(path.join(parentDir, 'extfs', id), (error, children) => {
|
||||
assert.equal(children.some(n => n === 'öäü'), true); // Mac always converts to NFD, so
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
}); // 493 = 0755
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (string)', function (done) {
|
||||
const smallData = 'Hello World';
|
||||
const bigData = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
testWriteFileAndFlush(smallData, smallData, bigData, bigData, done);
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (Buffer)', function (done) {
|
||||
const smallData = 'Hello World';
|
||||
const bigData = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
testWriteFileAndFlush(Buffer.from(smallData), smallData, Buffer.from(bigData), bigData, done);
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (UInt8Array)', function (done) {
|
||||
const smallData = 'Hello World';
|
||||
const bigData = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
testWriteFileAndFlush(new TextEncoder().encode(smallData), smallData, new TextEncoder().encode(bigData), bigData, done);
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (stream)', function (done) {
|
||||
const smallData = 'Hello World';
|
||||
const bigData = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
testWriteFileAndFlush(toReadable(smallData), smallData, toReadable(bigData), bigData, done);
|
||||
});
|
||||
|
||||
function testWriteFileAndFlush(
|
||||
smallData: string | Buffer | NodeJS.ReadableStream | Uint8Array,
|
||||
smallDataValue: string,
|
||||
bigData: string | Buffer | NodeJS.ReadableStream | Uint8Array,
|
||||
bigDataValue: string,
|
||||
done: (error: Error | null) => void
|
||||
): void {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
extfs.writeFileAndFlush(testFile, smallData, null!, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.equal(fs.readFileSync(testFile), smallDataValue);
|
||||
|
||||
extfs.writeFileAndFlush(testFile, bigData, null!, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.equal(fs.readFileSync(testFile), bigDataValue);
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('writeFileAndFlush (file stream)', function (done) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const sourceFile = getPathFromAmdModule(require, './fixtures/index.html');
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
extfs.writeFileAndFlush(testFile, fs.createReadStream(sourceFile), null!, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.equal(fs.readFileSync(testFile).toString(), fs.readFileSync(sourceFile).toString());
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (string, error handling)', function (done) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
fs.mkdirSync(testFile); // this will trigger an error because testFile is now a directory!
|
||||
|
||||
extfs.writeFileAndFlush(testFile, 'Hello World', null!, error => {
|
||||
if (!error) {
|
||||
return done(new Error('Expected error for writing to readonly file'));
|
||||
}
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (stream, error handling EISDIR)', function (done) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
fs.mkdirSync(testFile); // this will trigger an error because testFile is now a directory!
|
||||
|
||||
const readable = toReadable('Hello World');
|
||||
extfs.writeFileAndFlush(testFile, readable, null!, error => {
|
||||
if (!error || (<any>error).code !== 'EISDIR') {
|
||||
return done(new Error('Expected EISDIR error for writing to folder but got: ' + (error ? (<any>error).code : 'no error')));
|
||||
}
|
||||
|
||||
// verify that the stream is still consumable (for https://github.com/Microsoft/vscode/issues/42542)
|
||||
assert.equal(readable.read(), 'Hello World');
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (stream, error handling READERROR)', function (done) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
extfs.writeFileAndFlush(testFile, toReadable('Hello World', true /* throw error */), null!, error => {
|
||||
if (!error || error.message !== readError) {
|
||||
return done(new Error('Expected error for writing to folder'));
|
||||
}
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (stream, error handling EACCES)', function (done) {
|
||||
if (isLinux) {
|
||||
return done(); // somehow this test fails on Linux in our TFS builds
|
||||
}
|
||||
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
fs.writeFileSync(testFile, '');
|
||||
fs.chmodSync(testFile, 33060); // make readonly
|
||||
|
||||
extfs.writeFileAndFlush(testFile, toReadable('Hello World'), null!, error => {
|
||||
if (!error || !((<any>error).code !== 'EACCES' || (<any>error).code !== 'EPERM')) {
|
||||
return done(new Error('Expected EACCES/EPERM error for writing to folder but got: ' + (error ? (<any>error).code : 'no error')));
|
||||
}
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlush (file stream, error handling)', function (done) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const sourceFile = getPathFromAmdModule(require, './fixtures/index.html');
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
fs.mkdirSync(testFile); // this will trigger an error because testFile is now a directory!
|
||||
|
||||
extfs.writeFileAndFlush(testFile, fs.createReadStream(sourceFile), null!, error => {
|
||||
if (!error) {
|
||||
return done(new Error('Expected error for writing to folder'));
|
||||
}
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('writeFileAndFlushSync', function (done) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
extfs.writeFileAndFlushSync(testFile, 'Hello World', null!);
|
||||
assert.equal(fs.readFileSync(testFile), 'Hello World');
|
||||
|
||||
const largeString = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
extfs.writeFileAndFlushSync(testFile, largeString, null!);
|
||||
assert.equal(fs.readFileSync(testFile), largeString);
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
|
||||
test('realcase', (done) => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
|
||||
// assume case insensitive file system
|
||||
if (process.platform === 'win32' || process.platform === 'darwin') {
|
||||
const upper = newDir.toUpperCase();
|
||||
const real = extfs.realcaseSync(upper);
|
||||
|
||||
if (real) { // can be null in case of permission errors
|
||||
assert.notEqual(real, upper);
|
||||
assert.equal(real.toUpperCase(), upper);
|
||||
assert.equal(real, newDir);
|
||||
}
|
||||
}
|
||||
|
||||
// linux, unix, etc. -> assume case sensitive file system
|
||||
else {
|
||||
const real = extfs.realcaseSync(newDir);
|
||||
assert.equal(real, newDir);
|
||||
}
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
|
||||
test('realpath', (done) => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
|
||||
extfs.realpath(newDir, (error, realpath) => {
|
||||
assert.ok(realpath);
|
||||
assert.ok(!error);
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('realpathSync', (done) => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
|
||||
mkdirp(newDir, 493, error => {
|
||||
let realpath!: string;
|
||||
try {
|
||||
realpath = extfs.realpathSync(newDir);
|
||||
} catch (error) {
|
||||
assert.ok(!error);
|
||||
}
|
||||
assert.ok(realpath!);
|
||||
|
||||
extfs.del(parentDir, os.tmpdir(), done, ignore);
|
||||
});
|
||||
});
|
||||
|
||||
test('sanitizeFilePath', () => {
|
||||
if (isWindows) {
|
||||
assert.equal(extfs.sanitizeFilePath('.', 'C:\\the\\cwd'), 'C:\\the\\cwd');
|
||||
assert.equal(extfs.sanitizeFilePath('', 'C:\\the\\cwd'), 'C:\\the\\cwd');
|
||||
|
||||
assert.equal(extfs.sanitizeFilePath('C:', 'C:\\the\\cwd'), 'C:\\');
|
||||
assert.equal(extfs.sanitizeFilePath('C:\\', 'C:\\the\\cwd'), 'C:\\');
|
||||
assert.equal(extfs.sanitizeFilePath('C:\\\\', 'C:\\the\\cwd'), 'C:\\');
|
||||
|
||||
assert.equal(extfs.sanitizeFilePath('C:\\folder\\my.txt', 'C:\\the\\cwd'), 'C:\\folder\\my.txt');
|
||||
assert.equal(extfs.sanitizeFilePath('C:\\folder\\my', 'C:\\the\\cwd'), 'C:\\folder\\my');
|
||||
assert.equal(extfs.sanitizeFilePath('C:\\folder\\..\\my', 'C:\\the\\cwd'), 'C:\\my');
|
||||
assert.equal(extfs.sanitizeFilePath('C:\\folder\\my\\', 'C:\\the\\cwd'), 'C:\\folder\\my');
|
||||
assert.equal(extfs.sanitizeFilePath('C:\\folder\\my\\\\\\', 'C:\\the\\cwd'), 'C:\\folder\\my');
|
||||
|
||||
assert.equal(extfs.sanitizeFilePath('my.txt', 'C:\\the\\cwd'), 'C:\\the\\cwd\\my.txt');
|
||||
assert.equal(extfs.sanitizeFilePath('my.txt\\', 'C:\\the\\cwd'), 'C:\\the\\cwd\\my.txt');
|
||||
|
||||
assert.equal(extfs.sanitizeFilePath('\\\\localhost\\folder\\my', 'C:\\the\\cwd'), '\\\\localhost\\folder\\my');
|
||||
assert.equal(extfs.sanitizeFilePath('\\\\localhost\\folder\\my\\', 'C:\\the\\cwd'), '\\\\localhost\\folder\\my');
|
||||
} else {
|
||||
assert.equal(extfs.sanitizeFilePath('.', '/the/cwd'), '/the/cwd');
|
||||
assert.equal(extfs.sanitizeFilePath('', '/the/cwd'), '/the/cwd');
|
||||
assert.equal(extfs.sanitizeFilePath('/', '/the/cwd'), '/');
|
||||
|
||||
assert.equal(extfs.sanitizeFilePath('/folder/my.txt', '/the/cwd'), '/folder/my.txt');
|
||||
assert.equal(extfs.sanitizeFilePath('/folder/my', '/the/cwd'), '/folder/my');
|
||||
assert.equal(extfs.sanitizeFilePath('/folder/../my', '/the/cwd'), '/my');
|
||||
assert.equal(extfs.sanitizeFilePath('/folder/my/', '/the/cwd'), '/folder/my');
|
||||
assert.equal(extfs.sanitizeFilePath('/folder/my///', '/the/cwd'), '/folder/my');
|
||||
|
||||
assert.equal(extfs.sanitizeFilePath('my.txt', '/the/cwd'), '/the/cwd/my.txt');
|
||||
assert.equal(extfs.sanitizeFilePath('my.txt/', '/the/cwd'), '/the/cwd/my.txt');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
'use strict';
|
||||
/// <reference path="employee.ts" />
|
||||
var Workforce;
|
||||
(function (Workforce_1) {
|
||||
var Company = (function () {
|
||||
function Company() {
|
||||
}
|
||||
return Company;
|
||||
})();
|
||||
(function (property, Workforce, IEmployee) {
|
||||
if (property === undefined) { property = employees; }
|
||||
if (IEmployee === undefined) { IEmployee = []; }
|
||||
property;
|
||||
calculateMonthlyExpenses();
|
||||
{
|
||||
var result = 0;
|
||||
for (var i = 0; i < employees.length; i++) {
|
||||
result += employees[i].calculatePay();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
})(Workforce || (Workforce = {}));
|
||||
@@ -1,117 +0,0 @@
|
||||
'use strict';
|
||||
var Conway;
|
||||
(function (Conway) {
|
||||
var Cell = (function () {
|
||||
function Cell() {
|
||||
}
|
||||
return Cell;
|
||||
})();
|
||||
(function (property, number, property, number, property, boolean) {
|
||||
if (property === undefined) { property = row; }
|
||||
if (property === undefined) { property = col; }
|
||||
if (property === undefined) { property = live; }
|
||||
});
|
||||
var GameOfLife = (function () {
|
||||
function GameOfLife() {
|
||||
}
|
||||
return GameOfLife;
|
||||
})();
|
||||
(function () {
|
||||
property;
|
||||
gridSize = 50;
|
||||
property;
|
||||
canvasSize = 600;
|
||||
property;
|
||||
lineColor = '#cdcdcd';
|
||||
property;
|
||||
liveColor = '#666';
|
||||
property;
|
||||
deadColor = '#eee';
|
||||
property;
|
||||
initialLifeProbability = 0.5;
|
||||
property;
|
||||
animationRate = 60;
|
||||
property;
|
||||
cellSize = 0;
|
||||
property;
|
||||
context: ICanvasRenderingContext2D;
|
||||
property;
|
||||
world = createWorld();
|
||||
circleOfLife();
|
||||
function createWorld() {
|
||||
return travelWorld(function (cell) {
|
||||
cell.live = Math.random() < initialLifeProbability;
|
||||
return cell;
|
||||
});
|
||||
}
|
||||
function circleOfLife() {
|
||||
world = travelWorld(function (cell) {
|
||||
cell = world[cell.row][cell.col];
|
||||
draw(cell);
|
||||
return resolveNextGeneration(cell);
|
||||
});
|
||||
setTimeout(function () { circleOfLife(); }, animationRate);
|
||||
}
|
||||
function resolveNextGeneration(cell) {
|
||||
var count = countNeighbors(cell);
|
||||
var newCell = new Cell(cell.row, cell.col, cell.live);
|
||||
if (count < 2 || count > 3)
|
||||
newCell.live = false;
|
||||
else if (count == 3)
|
||||
newCell.live = true;
|
||||
return newCell;
|
||||
}
|
||||
function countNeighbors(cell) {
|
||||
var neighbors = 0;
|
||||
for (var row = -1; row <= 1; row++) {
|
||||
for (var col = -1; col <= 1; col++) {
|
||||
if (row == 0 && col == 0)
|
||||
continue;
|
||||
if (isAlive(cell.row + row, cell.col + col)) {
|
||||
neighbors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return neighbors;
|
||||
}
|
||||
function isAlive(row, col) {
|
||||
// todo - need to guard with world[row] exists?
|
||||
if (row < 0 || col < 0 || row >= gridSize || col >= gridSize)
|
||||
return false;
|
||||
return world[row][col].live;
|
||||
}
|
||||
function travelWorld(callback) {
|
||||
var result = [];
|
||||
for (var row = 0; row < gridSize; row++) {
|
||||
var rowData = [];
|
||||
for (var col = 0; col < gridSize; col++) {
|
||||
rowData.push(callback(new Cell(row, col, false)));
|
||||
}
|
||||
result.push(rowData);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function draw(cell) {
|
||||
if (context == null)
|
||||
context = createDrawingContext();
|
||||
if (cellSize == 0)
|
||||
cellSize = canvasSize / gridSize;
|
||||
context.strokeStyle = lineColor;
|
||||
context.strokeRect(cell.row * cellSize, cell.col * cellSize, cellSize, cellSize);
|
||||
context.fillStyle = cell.live ? liveColor : deadColor;
|
||||
context.fillRect(cell.row * cellSize, cell.col * cellSize, cellSize, cellSize);
|
||||
}
|
||||
function createDrawingContext() {
|
||||
var canvas = document.getElementById('conway-canvas');
|
||||
if (canvas == null) {
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.id = "conway-canvas";
|
||||
canvas.width = canvasSize;
|
||||
canvas.height = canvasSize;
|
||||
document.body.appendChild(canvas);
|
||||
}
|
||||
return canvas.getContext('2d');
|
||||
}
|
||||
});
|
||||
})(Conway || (Conway = {}));
|
||||
var game = new Conway.GameOfLife();
|
||||
@@ -1,38 +0,0 @@
|
||||
'use strict';
|
||||
var Workforce;
|
||||
(function (Workforce) {
|
||||
var Employee = (function () {
|
||||
function Employee() {
|
||||
}
|
||||
return Employee;
|
||||
})();
|
||||
(property);
|
||||
name: string, property;
|
||||
basepay: number;
|
||||
implements;
|
||||
IEmployee;
|
||||
{
|
||||
name;
|
||||
basepay;
|
||||
}
|
||||
var SalesEmployee = (function () {
|
||||
function SalesEmployee() {
|
||||
}
|
||||
return SalesEmployee;
|
||||
})();
|
||||
();
|
||||
Employee(name, basepay);
|
||||
{
|
||||
function calculatePay() {
|
||||
var multiplier = (document.getElementById("mult")), as = any, value;
|
||||
return _super.calculatePay.call(this) * multiplier + bonus;
|
||||
}
|
||||
}
|
||||
var employee = new Employee('Bob', 1000);
|
||||
var salesEmployee = new SalesEmployee('Jim', 800, 400);
|
||||
salesEmployee.calclatePay(); // error: No member 'calclatePay' on SalesEmployee
|
||||
})(Workforce || (Workforce = {}));
|
||||
extern;
|
||||
var $;
|
||||
var s = Workforce.salesEmployee.calculatePay();
|
||||
$('#results').text(s);
|
||||
@@ -1,24 +0,0 @@
|
||||
'use strict';
|
||||
var M;
|
||||
(function (M) {
|
||||
var C = (function () {
|
||||
function C() {
|
||||
}
|
||||
return C;
|
||||
})();
|
||||
(function (x, property, number) {
|
||||
if (property === undefined) { property = w; }
|
||||
var local = 1;
|
||||
// unresolved symbol because x is local
|
||||
//self.x++;
|
||||
self.w--; // ok because w is a property
|
||||
property;
|
||||
f = function (y) {
|
||||
return y + x + local + w + self.w;
|
||||
};
|
||||
function sum(z) {
|
||||
return z + f(z) + w + self.w;
|
||||
}
|
||||
});
|
||||
})(M || (M = {}));
|
||||
var c = new M.C(12, 5);
|
||||
@@ -1,121 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head id='headID'>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Strada </title>
|
||||
<link href="site.css" rel="stylesheet" type="text/css" />
|
||||
<script src="jquery-1.4.1.js"></script>
|
||||
<script src="../compiler/dtree.js" type="text/javascript"></script>
|
||||
<script src="../compiler/typescript.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// Compile strada source into resulting javascript
|
||||
function compile(prog, libText) {
|
||||
var outfile = {
|
||||
source: "",
|
||||
Write: function (s) { this.source += s; },
|
||||
WriteLine: function (s) { this.source += s + "\r"; },
|
||||
}
|
||||
|
||||
var parseErrors = []
|
||||
|
||||
var compiler=new Tools.TypeScriptCompiler(outfile,true);
|
||||
compiler.setErrorCallback(function(start,len, message) { parseErrors.push({start:start, len:len, message:message}); });
|
||||
compiler.addUnit(libText,"lib.ts");
|
||||
compiler.addUnit(prog,"input.ts");
|
||||
compiler.typeCheck();
|
||||
compiler.emit();
|
||||
|
||||
if(parseErrors.length > 0 ) {
|
||||
//throw new Error(parseErrors);
|
||||
}
|
||||
|
||||
while(outfile.source[0] == '/' && outfile.source[1] == '/' && outfile.source[2] == ' ') {
|
||||
outfile.source = outfile.source.slice(outfile.source.indexOf('\r')+1);
|
||||
}
|
||||
var errorPrefix = "";
|
||||
for(var i = 0;i<parseErrors.length;i++) {
|
||||
errorPrefix += "// Error: (" + parseErrors[i].start + "," + parseErrors[i].len + ") " + parseErrors[i].message + "\r";
|
||||
}
|
||||
|
||||
return errorPrefix + outfile.source;
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var libText = "";
|
||||
$.get("../compiler/lib.ts", function(newLibText) {
|
||||
libText = newLibText;
|
||||
});
|
||||
|
||||
|
||||
// execute the javascript in the compiledOutput pane
|
||||
function execute() {
|
||||
$('#compilation').text("Running...");
|
||||
var txt = $('#compiledOutput').val();
|
||||
var res;
|
||||
try {
|
||||
var ret = eval(txt);
|
||||
res = "Ran successfully!";
|
||||
} catch(e) {
|
||||
res = "Exception thrown: " + e;
|
||||
}
|
||||
$('#compilation').text(String(res));
|
||||
}
|
||||
|
||||
// recompile the stradaSrc and populate the compiledOutput pane
|
||||
function srcUpdated() {
|
||||
var newText = $('#stradaSrc').val();
|
||||
var compiledSource;
|
||||
try {
|
||||
compiledSource = compile(newText, libText);
|
||||
} catch (e) {
|
||||
compiledSource = "//Parse error"
|
||||
for(var i in e)
|
||||
compiledSource += "\r// " + e[i];
|
||||
}
|
||||
$('#compiledOutput').val(compiledSource);
|
||||
}
|
||||
|
||||
// Populate the stradaSrc pane with one of the built in samples
|
||||
function exampleSelectionChanged() {
|
||||
var examples = document.getElementById('examples');
|
||||
var selectedExample = examples.options[examples.selectedIndex].value;
|
||||
if (selectedExample != "") {
|
||||
$.get('examples/' + selectedExample, function (srcText) {
|
||||
$('#stradaSrc').val(srcText);
|
||||
setTimeout(srcUpdated,100);
|
||||
}, function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>TypeScript</h1>
|
||||
<br />
|
||||
<select id="examples" onchange='exampleSelectionChanged()'>
|
||||
<option value="">Select...</option>
|
||||
<option value="small.ts">Small</option>
|
||||
<option value="employee.ts">Employees</option>
|
||||
<option value="conway.ts">Conway Game of Life</option>
|
||||
<option value="typescript.ts">TypeScript Compiler</option>
|
||||
</select>
|
||||
|
||||
<div>
|
||||
<textarea id='stradaSrc' rows='40' cols='80' onchange='srcUpdated()' onkeyup='srcUpdated()' spellcheck="false">
|
||||
//Type your TypeScript here...
|
||||
</textarea>
|
||||
<textarea id='compiledOutput' rows='40' cols='80' spellcheck="false">
|
||||
//Compiled code will show up here...
|
||||
</textarea>
|
||||
<br />
|
||||
<button onclick='execute()'/>Run</button>
|
||||
<div id='compilation'>Press 'run' to execute code...</div>
|
||||
<div id='results'>...write your results into #results...</div>
|
||||
</div>
|
||||
<div id='bod' style='display:none'></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,40 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*----------------------------------------------------------
|
||||
The base color for this template is #5c87b2. If you'd like
|
||||
to use a different color start by replacing all instances of
|
||||
#5c87b2 with your new color.
|
||||
----------------------------------------------------------*/
|
||||
body
|
||||
{
|
||||
background-color: #5c87b2;
|
||||
font-size: .75em;
|
||||
font-family: Segoe UI, Verdana, Helvetica, Sans-Serif;
|
||||
margin: 8px;
|
||||
padding: 0;
|
||||
color: #696969;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
{
|
||||
color: #000;
|
||||
font-size: 40px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
textarea
|
||||
{
|
||||
font-family: Consolas
|
||||
}
|
||||
|
||||
#results
|
||||
{
|
||||
margin-top: 2em;
|
||||
margin-left: 2em;
|
||||
color: black;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as os from 'os';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { realcaseSync, realpath, realpathSync } from 'vs/base/node/extpath';
|
||||
|
||||
suite('Extpath', () => {
|
||||
|
||||
test('realcase', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extpath', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
|
||||
// assume case insensitive file system
|
||||
if (process.platform === 'win32' || process.platform === 'darwin') {
|
||||
const upper = newDir.toUpperCase();
|
||||
const real = realcaseSync(upper);
|
||||
|
||||
if (real) { // can be null in case of permission errors
|
||||
assert.notEqual(real, upper);
|
||||
assert.equal(real.toUpperCase(), upper);
|
||||
assert.equal(real, newDir);
|
||||
}
|
||||
}
|
||||
|
||||
// linux, unix, etc. -> assume case sensitive file system
|
||||
else {
|
||||
const real = realcaseSync(newDir);
|
||||
assert.equal(real, newDir);
|
||||
}
|
||||
|
||||
await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('realpath', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extpath', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
|
||||
const realpathVal = await realpath(newDir);
|
||||
assert.ok(realpathVal);
|
||||
|
||||
await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('realpathSync', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extpath', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
|
||||
let realpath!: string;
|
||||
try {
|
||||
realpath = realpathSync(newDir);
|
||||
} catch (error) {
|
||||
assert.ok(!error);
|
||||
}
|
||||
assert.ok(realpath!);
|
||||
|
||||
await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
});
|
||||
@@ -5,15 +5,48 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as os from 'os';
|
||||
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { Readable } from 'stream';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { isWindows, isLinux } from 'vs/base/common/platform';
|
||||
import { canNormalize } from 'vs/base/common/normalization';
|
||||
|
||||
const chunkSize = 64 * 1024;
|
||||
const readError = 'Error while reading';
|
||||
function toReadable(value: string, throwError?: boolean): Readable {
|
||||
const totalChunks = Math.ceil(value.length / chunkSize);
|
||||
const stringChunks: string[] = [];
|
||||
|
||||
for (let i = 0, j = 0; i < totalChunks; ++i, j += chunkSize) {
|
||||
stringChunks[i] = value.substr(j, chunkSize);
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
return new Readable({
|
||||
read: function () {
|
||||
if (throwError) {
|
||||
this.emit('error', new Error(readError));
|
||||
}
|
||||
|
||||
let res!: string;
|
||||
let canPush = true;
|
||||
while (canPush && (res = stringChunks[counter++])) {
|
||||
canPush = this.push(res);
|
||||
}
|
||||
|
||||
// EOS
|
||||
if (!res) {
|
||||
this.push(null);
|
||||
}
|
||||
},
|
||||
encoding: 'utf8'
|
||||
});
|
||||
}
|
||||
|
||||
suite('PFS', () => {
|
||||
|
||||
@@ -29,7 +62,7 @@ suite('PFS', () => {
|
||||
await pfs.writeFile(testFile, 'Hello World', (null!));
|
||||
assert.equal(fs.readFileSync(testFile), 'Hello World');
|
||||
|
||||
await pfs.del(parentDir, os.tmpdir());
|
||||
await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('writeFile - parallel write on different files works', async () => {
|
||||
@@ -58,7 +91,7 @@ suite('PFS', () => {
|
||||
assert.equal(fs.readFileSync(testFile4), 'Hello World 4');
|
||||
assert.equal(fs.readFileSync(testFile5), 'Hello World 5');
|
||||
|
||||
await pfs.del(parentDir, os.tmpdir());
|
||||
await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('writeFile - parallel write on same files works and is sequentalized', async () => {
|
||||
@@ -79,13 +112,13 @@ suite('PFS', () => {
|
||||
]);
|
||||
assert.equal(fs.readFileSync(testFile), 'Hello World 5');
|
||||
|
||||
await pfs.del(parentDir, os.tmpdir());
|
||||
await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('rimraf - simple', async () => {
|
||||
test('rimraf - simple - unlink', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents');
|
||||
@@ -95,10 +128,23 @@ suite('PFS', () => {
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
});
|
||||
|
||||
test('rimraf - recursive folder structure', async () => {
|
||||
test('rimraf - simple - move', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents');
|
||||
fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents');
|
||||
|
||||
await pfs.rimraf(newDir, pfs.RimRafMode.MOVE);
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
});
|
||||
|
||||
test('rimraf - recursive folder structure - unlink', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents');
|
||||
@@ -110,15 +156,98 @@ suite('PFS', () => {
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
});
|
||||
|
||||
test('rimraf - recursive folder structure - move', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents');
|
||||
fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents');
|
||||
fs.mkdirSync(path.join(newDir, 'somefolder'));
|
||||
fs.writeFileSync(path.join(newDir, 'somefolder', 'somefile.txt'), 'Contents');
|
||||
|
||||
await pfs.rimraf(newDir, pfs.RimRafMode.MOVE);
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
});
|
||||
|
||||
test('rimraf - simple ends with dot - move', async () => {
|
||||
const id = `${uuid.generateUuid()}.`;
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents');
|
||||
fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents');
|
||||
|
||||
await pfs.rimraf(newDir, pfs.RimRafMode.MOVE);
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
});
|
||||
|
||||
test('rimraf - simple ends with dot slash/backslash - move', async () => {
|
||||
const id = `${uuid.generateUuid()}.`;
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents');
|
||||
fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents');
|
||||
|
||||
await pfs.rimraf(`${newDir}${path.sep}`, pfs.RimRafMode.MOVE);
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
});
|
||||
|
||||
test('rimrafSync - swallows file not found error', function () {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
pfs.rimrafSync(newDir);
|
||||
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
});
|
||||
|
||||
test('rimrafSync - simple', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
|
||||
fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents');
|
||||
fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents');
|
||||
|
||||
pfs.rimrafSync(newDir);
|
||||
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
});
|
||||
|
||||
test('rimrafSync - recursive folder structure', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
fs.writeFileSync(path.join(newDir, 'somefile.txt'), 'Contents');
|
||||
fs.writeFileSync(path.join(newDir, 'someOtherFile.txt'), 'Contents');
|
||||
|
||||
fs.mkdirSync(path.join(newDir, 'somefolder'));
|
||||
fs.writeFileSync(path.join(newDir, 'somefolder', 'somefile.txt'), 'Contents');
|
||||
|
||||
pfs.rimrafSync(newDir);
|
||||
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
});
|
||||
|
||||
test('moveIgnoreError', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
try {
|
||||
await pfs.renameIgnoreError(path.join(newDir, 'foo'), path.join(newDir, 'bar'));
|
||||
return pfs.del(parentDir, os.tmpdir());
|
||||
return pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
|
||||
}
|
||||
catch (error) {
|
||||
assert.fail(error);
|
||||
@@ -130,7 +259,7 @@ suite('PFS', () => {
|
||||
const id = uuid.generateUuid();
|
||||
const id2 = uuid.generateUuid();
|
||||
const sourceDir = getPathFromAmdModule(require, './fixtures');
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', 'extfs');
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', 'pfs');
|
||||
const targetDir = path.join(parentDir, id);
|
||||
const targetDir2 = path.join(parentDir, id2);
|
||||
|
||||
@@ -158,7 +287,7 @@ suite('PFS', () => {
|
||||
assert.ok(!fs.existsSync(path.join(targetDir2, 'index.html')));
|
||||
assert.ok(fs.existsSync(path.join(targetDir2, 'index_moved.html')));
|
||||
|
||||
await pfs.del(parentDir, os.tmpdir());
|
||||
await pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
|
||||
|
||||
assert.ok(!fs.existsSync(parentDir));
|
||||
});
|
||||
@@ -166,19 +295,19 @@ suite('PFS', () => {
|
||||
test('mkdirp', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
return pfs.del(parentDir, os.tmpdir());
|
||||
return pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('mkdirp cancellation', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
const source = new CancellationTokenSource();
|
||||
|
||||
@@ -189,13 +318,13 @@ suite('PFS', () => {
|
||||
|
||||
assert.ok(!fs.existsSync(newDir));
|
||||
|
||||
return pfs.del(parentDir, os.tmpdir());
|
||||
return pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('readDirsInDir', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'extfs', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
|
||||
@@ -213,4 +342,270 @@ suite('PFS', () => {
|
||||
|
||||
await pfs.rimraf(newDir);
|
||||
});
|
||||
|
||||
test('stat link', async () => {
|
||||
if (isWindows) {
|
||||
return Promise.resolve(); // Symlinks are not the same on win, and we can not create them programitically without admin privileges
|
||||
}
|
||||
|
||||
const id1 = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id1);
|
||||
const directory = path.join(parentDir, 'pfs', id1);
|
||||
|
||||
const id2 = uuid.generateUuid();
|
||||
const symbolicLink = path.join(parentDir, 'pfs', id2);
|
||||
|
||||
await pfs.mkdirp(directory, 493);
|
||||
|
||||
fs.symlinkSync(directory, symbolicLink);
|
||||
|
||||
let statAndIsLink = await pfs.statLink(directory);
|
||||
assert.ok(!statAndIsLink!.isSymbolicLink);
|
||||
|
||||
statAndIsLink = await pfs.statLink(symbolicLink);
|
||||
assert.ok(statAndIsLink!.isSymbolicLink);
|
||||
|
||||
pfs.rimrafSync(directory);
|
||||
});
|
||||
|
||||
test('readdir', async () => {
|
||||
if (canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id, 'öäü');
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
const children = await pfs.readdir(path.join(parentDir, 'pfs', id));
|
||||
assert.equal(children.some(n => n === 'öäü'), true); // Mac always converts to NFD, so
|
||||
|
||||
await pfs.rimraf(parentDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('writeFile (string)', async () => {
|
||||
const smallData = 'Hello World';
|
||||
const bigData = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
return testWriteFileAndFlush(smallData, smallData, bigData, bigData);
|
||||
});
|
||||
|
||||
test('writeFile (Buffer)', async () => {
|
||||
const smallData = 'Hello World';
|
||||
const bigData = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
return testWriteFileAndFlush(Buffer.from(smallData), smallData, Buffer.from(bigData), bigData);
|
||||
});
|
||||
|
||||
test('writeFile (UInt8Array)', async () => {
|
||||
const smallData = 'Hello World';
|
||||
const bigData = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
return testWriteFileAndFlush(new TextEncoder().encode(smallData), smallData, new TextEncoder().encode(bigData), bigData);
|
||||
});
|
||||
|
||||
test('writeFile (stream)', async () => {
|
||||
const smallData = 'Hello World';
|
||||
const bigData = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
return testWriteFileAndFlush(toReadable(smallData), smallData, toReadable(bigData), bigData);
|
||||
});
|
||||
|
||||
async function testWriteFileAndFlush(
|
||||
smallData: string | Buffer | NodeJS.ReadableStream | Uint8Array,
|
||||
smallDataValue: string,
|
||||
bigData: string | Buffer | NodeJS.ReadableStream | Uint8Array,
|
||||
bigDataValue: string
|
||||
): Promise<void> {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
await pfs.writeFile(testFile, smallData);
|
||||
assert.equal(fs.readFileSync(testFile), smallDataValue);
|
||||
|
||||
await pfs.writeFile(testFile, bigData);
|
||||
assert.equal(fs.readFileSync(testFile), bigDataValue);
|
||||
|
||||
await pfs.rimraf(parentDir);
|
||||
}
|
||||
|
||||
test('writeFile (file stream)', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const sourceFile = getPathFromAmdModule(require, './fixtures/index.html');
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
await pfs.writeFile(testFile, fs.createReadStream(sourceFile));
|
||||
assert.equal(fs.readFileSync(testFile).toString(), fs.readFileSync(sourceFile).toString());
|
||||
|
||||
await pfs.rimraf(parentDir);
|
||||
});
|
||||
|
||||
test('writeFile (string, error handling)', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
fs.mkdirSync(testFile); // this will trigger an error because testFile is now a directory!
|
||||
|
||||
let expectedError: Error | undefined;
|
||||
try {
|
||||
await pfs.writeFile(testFile, 'Hello World');
|
||||
} catch (error) {
|
||||
expectedError = error;
|
||||
}
|
||||
|
||||
assert.ok(expectedError);
|
||||
|
||||
await pfs.rimraf(parentDir);
|
||||
});
|
||||
|
||||
test('writeFile (stream, error handling EISDIR)', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
fs.mkdirSync(testFile); // this will trigger an error because testFile is now a directory!
|
||||
|
||||
const readable = toReadable('Hello World');
|
||||
|
||||
let expectedError: Error | undefined;
|
||||
try {
|
||||
await pfs.writeFile(testFile, readable);
|
||||
} catch (error) {
|
||||
expectedError = error;
|
||||
}
|
||||
|
||||
if (!expectedError || (<any>expectedError).code !== 'EISDIR') {
|
||||
return Promise.reject(new Error('Expected EISDIR error for writing to folder but got: ' + (expectedError ? (<any>expectedError).code : 'no error')));
|
||||
}
|
||||
|
||||
// verify that the stream is still consumable (for https://github.com/Microsoft/vscode/issues/42542)
|
||||
assert.equal(readable.read(), 'Hello World');
|
||||
|
||||
await pfs.rimraf(parentDir);
|
||||
});
|
||||
|
||||
test('writeFile (stream, error handling READERROR)', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
let expectedError: Error | undefined;
|
||||
try {
|
||||
await pfs.writeFile(testFile, toReadable('Hello World', true /* throw error */));
|
||||
} catch (error) {
|
||||
expectedError = error;
|
||||
}
|
||||
|
||||
if (!expectedError || expectedError.message !== readError) {
|
||||
return Promise.reject(new Error('Expected error for writing to folder'));
|
||||
}
|
||||
|
||||
await pfs.rimraf(parentDir);
|
||||
});
|
||||
|
||||
test('writeFile (stream, error handling EACCES)', async () => {
|
||||
if (isLinux) {
|
||||
return Promise.resolve(); // somehow this test fails on Linux in our TFS builds
|
||||
}
|
||||
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
fs.writeFileSync(testFile, '');
|
||||
fs.chmodSync(testFile, 33060); // make readonly
|
||||
|
||||
let expectedError: Error | undefined;
|
||||
try {
|
||||
await pfs.writeFile(testFile, toReadable('Hello World'));
|
||||
} catch (error) {
|
||||
expectedError = error;
|
||||
}
|
||||
|
||||
if (!expectedError || !((<any>expectedError).code !== 'EACCES' || (<any>expectedError).code !== 'EPERM')) {
|
||||
return Promise.reject(new Error('Expected EACCES/EPERM error for writing to folder but got: ' + (expectedError ? (<any>expectedError).code : 'no error')));
|
||||
}
|
||||
|
||||
await pfs.rimraf(parentDir);
|
||||
});
|
||||
|
||||
test('writeFile (file stream, error handling)', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const sourceFile = getPathFromAmdModule(require, './fixtures/index.html');
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
fs.mkdirSync(testFile); // this will trigger an error because testFile is now a directory!
|
||||
|
||||
let expectedError: Error | undefined;
|
||||
try {
|
||||
await pfs.writeFile(testFile, fs.createReadStream(sourceFile));
|
||||
} catch (error) {
|
||||
expectedError = error;
|
||||
}
|
||||
|
||||
if (!expectedError) {
|
||||
return Promise.reject(new Error('Expected error for writing to folder'));
|
||||
}
|
||||
|
||||
await pfs.rimraf(parentDir);
|
||||
});
|
||||
|
||||
test('writeFileSync', async () => {
|
||||
const id = uuid.generateUuid();
|
||||
const parentDir = path.join(os.tmpdir(), 'vsctests', id);
|
||||
const newDir = path.join(parentDir, 'pfs', id);
|
||||
const testFile = path.join(newDir, 'flushed.txt');
|
||||
|
||||
await pfs.mkdirp(newDir, 493);
|
||||
|
||||
assert.ok(fs.existsSync(newDir));
|
||||
|
||||
pfs.writeFileSync(testFile, 'Hello World');
|
||||
assert.equal(fs.readFileSync(testFile), 'Hello World');
|
||||
|
||||
const largeString = (new Array(100 * 1024)).join('Large String\n');
|
||||
|
||||
pfs.writeFileSync(testFile, largeString);
|
||||
assert.equal(fs.readFileSync(testFile), largeString);
|
||||
|
||||
await pfs.rimraf(parentDir);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { tmpdir } from 'os';
|
||||
import { equal, ok } from 'assert';
|
||||
import { mkdirp, del, writeFile, exists, unlink } from 'vs/base/node/pfs';
|
||||
import { mkdirp, writeFile, exists, unlink, rimraf, RimRafMode } from 'vs/base/node/pfs';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
@@ -92,7 +92,7 @@ suite('Storage Library', () => {
|
||||
equal(deletePromiseResolved, true);
|
||||
|
||||
await storage.close();
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('external changes', async () => {
|
||||
@@ -148,7 +148,7 @@ suite('Storage Library', () => {
|
||||
equal(changes.size, 0);
|
||||
|
||||
await storage.close();
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('close flushes data', async () => {
|
||||
@@ -202,7 +202,7 @@ suite('Storage Library', () => {
|
||||
ok(!storage.get('bar'));
|
||||
|
||||
await storage.close();
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('conflicting updates', async () => {
|
||||
@@ -244,7 +244,7 @@ suite('Storage Library', () => {
|
||||
ok(setAndDeletePromiseResolved);
|
||||
|
||||
await storage.close();
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('corrupt DB recovers', async () => {
|
||||
@@ -274,7 +274,7 @@ suite('Storage Library', () => {
|
||||
equal(storage.get('foo'), 'bar');
|
||||
|
||||
await storage.close();
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -372,7 +372,7 @@ suite('SQLite Storage Library', () => {
|
||||
|
||||
await testDBBasics(join(storageDir, 'storage.db'));
|
||||
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('basics (open multiple times)', async () => {
|
||||
@@ -383,7 +383,7 @@ suite('SQLite Storage Library', () => {
|
||||
await testDBBasics(join(storageDir, 'storage.db'));
|
||||
await testDBBasics(join(storageDir, 'storage.db'));
|
||||
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('basics (corrupt DB falls back to empty DB)', async () => {
|
||||
@@ -401,7 +401,7 @@ suite('SQLite Storage Library', () => {
|
||||
|
||||
ok(expectedError);
|
||||
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('basics (corrupt DB restores from previous backup)', async () => {
|
||||
@@ -439,7 +439,7 @@ suite('SQLite Storage Library', () => {
|
||||
|
||||
equal(recoveryCalled, false);
|
||||
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('basics (corrupt DB falls back to empty DB if backup is corrupt)', async () => {
|
||||
@@ -468,7 +468,7 @@ suite('SQLite Storage Library', () => {
|
||||
|
||||
await testDBBasics(storagePath);
|
||||
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('basics (DB that becomes corrupt during runtime stores all state from cache on close)', async () => {
|
||||
@@ -536,7 +536,7 @@ suite('SQLite Storage Library', () => {
|
||||
|
||||
equal(recoveryCalled, false);
|
||||
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('real world example', async function () {
|
||||
@@ -627,7 +627,7 @@ suite('SQLite Storage Library', () => {
|
||||
|
||||
await storage.close();
|
||||
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('very large item value', async function () {
|
||||
@@ -682,7 +682,7 @@ suite('SQLite Storage Library', () => {
|
||||
|
||||
await storage.close();
|
||||
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('multiple concurrent writes execute in sequence', async () => {
|
||||
@@ -739,7 +739,7 @@ suite('SQLite Storage Library', () => {
|
||||
|
||||
await storage.close();
|
||||
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('lots of INSERT & DELETE (below inline max)', async () => {
|
||||
@@ -771,7 +771,7 @@ suite('SQLite Storage Library', () => {
|
||||
|
||||
await storage.close();
|
||||
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('lots of INSERT & DELETE (above inline max)', async () => {
|
||||
@@ -803,6 +803,6 @@ suite('SQLite Storage Library', () => {
|
||||
|
||||
await storage.close();
|
||||
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { tmpdir } from 'os';
|
||||
import { mkdirp, del } from 'vs/base/node/pfs';
|
||||
import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs';
|
||||
|
||||
export interface ITestFileResult {
|
||||
testFile: string;
|
||||
@@ -22,7 +22,7 @@ export function testFile(folder: string, file: string): Promise<ITestFileResult>
|
||||
return mkdirp(newDir, 493).then(() => {
|
||||
return {
|
||||
testFile,
|
||||
cleanUp: () => del(parentDir, tmpdir())
|
||||
cleanUp: () => rimraf(parentDir, RimRafMode.MOVE)
|
||||
} as ITestFileResult;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,11 +13,10 @@ import pkg from 'vs/platform/product/node/package';
|
||||
import * as paths from 'vs/base/common/path';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import { whenDeleted } from 'vs/base/node/pfs';
|
||||
import { whenDeleted, writeFileSync } from 'vs/base/node/pfs';
|
||||
import { findFreePort, randomPort } from 'vs/base/node/ports';
|
||||
import { resolveTerminalEncoding } from 'vs/base/node/encoding';
|
||||
import * as iconv from 'iconv-lite';
|
||||
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { ProfilingSession, Target } from 'v8-inspect-profiler';
|
||||
|
||||
@@ -98,9 +97,9 @@ export async function main(argv: string[]): Promise<any> {
|
||||
// prevent removing alternate data streams
|
||||
// (see https://github.com/Microsoft/vscode/issues/6363)
|
||||
fs.truncateSync(target, 0);
|
||||
writeFileAndFlushSync(target, data, { flag: 'r+' });
|
||||
writeFileSync(target, data, { flag: 'r+' });
|
||||
} else {
|
||||
writeFileAndFlushSync(target, data);
|
||||
writeFileSync(target, data);
|
||||
}
|
||||
|
||||
// Restore previous mode as needed
|
||||
|
||||
@@ -10,7 +10,6 @@ import * as extpath from 'vs/base/common/extpath';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { sanitizeFilePath } from 'vs/base/node/extfs';
|
||||
|
||||
export function validatePaths(args: ParsedArgs): ParsedArgs {
|
||||
|
||||
@@ -45,7 +44,7 @@ function doValidatePaths(args: string[], gotoLineMode?: boolean): string[] {
|
||||
pathCandidate = preparePath(cwd, pathCandidate);
|
||||
}
|
||||
|
||||
const sanitizedFilePath = sanitizeFilePath(pathCandidate, cwd);
|
||||
const sanitizedFilePath = extpath.sanitizeFilePath(pathCandidate, cwd);
|
||||
|
||||
const basename = path.basename(sanitizedFilePath);
|
||||
if (basename /* can be empty if code is opened on root */ && !extpath.isValidBasename(basename)) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as fs from 'fs';
|
||||
import * as crypto from 'crypto';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
|
||||
import { writeFileSync, writeFile, readFile, readdir, exists, rimraf, rename, RimRafMode } from 'vs/base/node/pfs';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IBackupMainService, IBackupWorkspacesFormat, IEmptyWindowBackupInfo, IWorkspaceBackupInfo } from 'vs/platform/backup/common/backup';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -19,7 +19,6 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { isEqual as areResourcesEquals, getComparisonKey, hasToIgnoreCase } from 'vs/base/common/resources';
|
||||
import { isEqual } from 'vs/base/common/extpath';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { writeFile, readFile, readdir, exists, del, rename } from 'vs/base/node/pfs';
|
||||
|
||||
export class BackupMainService implements IBackupMainService {
|
||||
|
||||
@@ -343,7 +342,7 @@ export class BackupMainService implements IBackupMainService {
|
||||
private async deleteStaleBackup(backupPath: string): Promise<void> {
|
||||
try {
|
||||
if (await exists(backupPath)) {
|
||||
await del(backupPath);
|
||||
await rimraf(backupPath, RimRafMode.MOVE);
|
||||
}
|
||||
} catch (ex) {
|
||||
this.logService.error(`Backup: Could not delete stale backup: ${ex.toString()}`);
|
||||
@@ -415,7 +414,7 @@ export class BackupMainService implements IBackupMainService {
|
||||
|
||||
private saveSync(): void {
|
||||
try {
|
||||
writeFileAndFlushSync(this.workspacesJsonPath, JSON.stringify(this.serializeBackups()));
|
||||
writeFileSync(this.workspacesJsonPath, JSON.stringify(this.serializeBackups()));
|
||||
} catch (ex) {
|
||||
this.logService.error(`Backup: Could not save workspaces.json: ${ex.toString()}`);
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ suite('BackupMainService', () => {
|
||||
setup(() => {
|
||||
|
||||
// Delete any existing backups completely and then re-create it.
|
||||
return pfs.del(backupHome, os.tmpdir()).then(() => {
|
||||
return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE).then(() => {
|
||||
return pfs.mkdirp(backupHome);
|
||||
}).then(() => {
|
||||
configService = new TestConfigurationService();
|
||||
@@ -132,7 +132,7 @@ suite('BackupMainService', () => {
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
return pfs.del(backupHome, os.tmpdir());
|
||||
return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('service validates backup workspaces on startup and cleans up (folder workspaces)', async function () {
|
||||
|
||||
@@ -18,8 +18,7 @@ import pkg from 'vs/platform/product/node/package';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { readFile } from 'vs/base/node/pfs';
|
||||
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
|
||||
import { writeFileSync, readFile } from 'vs/base/node/pfs';
|
||||
import { generateUuid, isUUID } from 'vs/base/common/uuid';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
@@ -848,7 +847,7 @@ export function resolveMarketplaceHeaders(environmentService: IEnvironmentServic
|
||||
if (!uuid) {
|
||||
uuid = generateUuid();
|
||||
try {
|
||||
writeFileAndFlushSync(marketplaceMachineIdFile, uuid);
|
||||
writeFileSync(marketplaceMachineIdFile, uuid);
|
||||
} catch (error) {
|
||||
//noop
|
||||
}
|
||||
|
||||
@@ -36,6 +36,6 @@ export class ExtensionsManifestCache extends Disposable {
|
||||
}
|
||||
|
||||
invalidate(): void {
|
||||
pfs.del(this.extensionsManifestCache).then(() => { }, () => { });
|
||||
pfs.rimraf(this.extensionsManifestCache, pfs.RimRafMode.MOVE).then(() => { }, () => { });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as os from 'os';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { parseArgs } from 'vs/platform/environment/node/argv';
|
||||
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { mkdirp } from 'vs/base/node/pfs';
|
||||
import { mkdirp, RimRafMode, rimraf } from 'vs/base/node/pfs';
|
||||
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
|
||||
import { isUUID } from 'vs/base/common/uuid';
|
||||
|
||||
@@ -21,15 +20,15 @@ suite('Extension Gallery Service', () => {
|
||||
setup(done => {
|
||||
|
||||
// Delete any existing backups completely and then re-create it.
|
||||
extfs.del(marketplaceHome, os.tmpdir(), () => {
|
||||
rimraf(marketplaceHome, RimRafMode.MOVE).then(() => {
|
||||
mkdirp(marketplaceHome).then(() => {
|
||||
done();
|
||||
}, error => done(error));
|
||||
});
|
||||
}, error => done(error));
|
||||
});
|
||||
|
||||
teardown(done => {
|
||||
extfs.del(marketplaceHome, os.tmpdir(), done);
|
||||
rimraf(marketplaceHome, RimRafMode.MOVE).then(done, done);
|
||||
});
|
||||
|
||||
test('marketplace machine id', () => {
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as fs from 'fs';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
|
||||
import { writeFileSync, readFile } from 'vs/base/node/pfs';
|
||||
import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { readFile } from 'vs/base/node/pfs';
|
||||
|
||||
export class FileStorage {
|
||||
|
||||
@@ -103,7 +102,7 @@ export class FileStorage {
|
||||
}
|
||||
|
||||
try {
|
||||
writeFileAndFlushSync(this.dbPath, serializedDatabase); // permission issue can happen here
|
||||
writeFileSync(this.dbPath, serializedDatabase); // permission issue can happen here
|
||||
this.lastFlushedSerializedDatabase = serializedDatabase;
|
||||
} catch (error) {
|
||||
this.onError(error);
|
||||
|
||||
@@ -6,22 +6,21 @@
|
||||
import * as assert from 'assert';
|
||||
import * as os from 'os';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { FileStorage } from 'vs/platform/state/node/stateService';
|
||||
import { mkdirp } from 'vs/base/node/pfs';
|
||||
import { mkdirp, rimraf, RimRafMode, writeFileSync } from 'vs/base/node/pfs';
|
||||
|
||||
suite('StateService', () => {
|
||||
const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'stateservice');
|
||||
const storageFile = path.join(parentDir, 'storage.json');
|
||||
|
||||
teardown(done => {
|
||||
extfs.del(parentDir, os.tmpdir(), done);
|
||||
rimraf(parentDir, RimRafMode.MOVE).then(done, done);
|
||||
});
|
||||
|
||||
test('Basics', () => {
|
||||
return mkdirp(parentDir).then(() => {
|
||||
extfs.writeFileAndFlushSync(storageFile, '');
|
||||
writeFileSync(storageFile, '');
|
||||
|
||||
let service = new FileStorage(storageFile, () => null);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { StorageService } from 'vs/platform/storage/node/storageService';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { tmpdir } from 'os';
|
||||
import { mkdirp, del } from 'vs/base/node/pfs';
|
||||
import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { parseArgs } from 'vs/platform/environment/node/argv';
|
||||
@@ -115,6 +115,6 @@ suite('StorageService', () => {
|
||||
equal(storage.getBoolean('barBoolean', StorageScope.GLOBAL), true);
|
||||
|
||||
await storage.close();
|
||||
await del(storageDir, tmpdir());
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
});
|
||||
@@ -9,8 +9,7 @@ import * as fs from 'fs';
|
||||
import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties';
|
||||
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { IStorageService, StorageScope, InMemoryStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { del } from 'vs/base/node/extfs';
|
||||
import { mkdirp } from 'vs/base/node/pfs';
|
||||
import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
suite('Telemetry - common properties', function () {
|
||||
@@ -26,7 +25,7 @@ suite('Telemetry - common properties', function () {
|
||||
});
|
||||
|
||||
teardown(done => {
|
||||
del(parentDir, os.tmpdir(), done);
|
||||
rimraf(parentDir, RimRafMode.MOVE).then(done, done);
|
||||
});
|
||||
|
||||
test('default', async function () {
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
import { IWorkspacesMainService, IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { join, dirname } from 'vs/base/common/path';
|
||||
import { mkdirp, writeFile } from 'vs/base/node/pfs';
|
||||
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
||||
import { mkdirp, writeFile, rimrafSync, readdirSync, writeFileSync } from 'vs/base/node/pfs';
|
||||
import { readFileSync, existsSync, mkdirSync } from 'fs';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { delSync, readdirSync, writeFileAndFlushSync } from 'vs/base/node/extfs';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { createHash } from 'crypto';
|
||||
@@ -126,7 +125,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain
|
||||
mkdirSync(configPathDir);
|
||||
}
|
||||
|
||||
writeFileAndFlushSync(configPath, JSON.stringify(storedWorkspace, null, '\t'));
|
||||
writeFileSync(configPath, JSON.stringify(storedWorkspace, null, '\t'));
|
||||
|
||||
return workspace;
|
||||
}
|
||||
@@ -176,8 +175,9 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain
|
||||
private doDeleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void {
|
||||
const configPath = originalFSPath(workspace.configPath);
|
||||
try {
|
||||
|
||||
// Delete Workspace
|
||||
delSync(dirname(configPath));
|
||||
rimrafSync(dirname(configPath));
|
||||
|
||||
// Mark Workspace Storage to be deleted
|
||||
const workspaceStoragePath = join(this.environmentService.workspaceStorageHome, workspace.id);
|
||||
|
||||
@@ -55,13 +55,13 @@ suite('WorkspacesMainService', () => {
|
||||
service = new TestWorkspacesMainService(environmentService, logService);
|
||||
|
||||
// Delete any existing backups completely and then re-create it.
|
||||
return pfs.del(untitledWorkspacesHomePath, os.tmpdir()).then(() => {
|
||||
return pfs.rimraf(untitledWorkspacesHomePath, pfs.RimRafMode.MOVE).then(() => {
|
||||
return pfs.mkdirp(untitledWorkspacesHomePath);
|
||||
});
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
return pfs.del(untitledWorkspacesHomePath, os.tmpdir());
|
||||
return pfs.rimraf(untitledWorkspacesHomePath, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
function assertPathEquals(p1: string, p2: string): void {
|
||||
|
||||
@@ -30,6 +30,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
|
||||
import { IWorkspace } from 'vs/platform/workspace/common/workspace';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { realpath } from 'vs/base/node/extpath';
|
||||
|
||||
class ExtensionMemento implements IExtensionMemento {
|
||||
|
||||
@@ -316,7 +317,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
if (!ext.main) {
|
||||
return undefined;
|
||||
}
|
||||
return pfs.realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext));
|
||||
return realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext));
|
||||
});
|
||||
this._extensionPathIndex = Promise.all(extensions).then(() => tree);
|
||||
}
|
||||
@@ -733,13 +734,13 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
if (!extensionDescription) {
|
||||
return;
|
||||
}
|
||||
const realpath = await pfs.realpath(extensionDescription.extensionLocation.fsPath);
|
||||
trie.delete(URI.file(realpath).fsPath);
|
||||
const realpathValue = await realpath(extensionDescription.extensionLocation.fsPath);
|
||||
trie.delete(URI.file(realpathValue).fsPath);
|
||||
}));
|
||||
|
||||
await Promise.all(toAdd.map(async (extensionDescription) => {
|
||||
const realpath = await pfs.realpath(extensionDescription.extensionLocation.fsPath);
|
||||
trie.set(URI.file(realpath).fsPath, extensionDescription);
|
||||
const realpathValue = await realpath(extensionDescription.extensionLocation.fsPath);
|
||||
trie.set(URI.file(realpathValue).fsPath, extensionDescription);
|
||||
}));
|
||||
|
||||
this._registry.deltaExtensions(toAdd, toRemove);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IFileQuery, IFolderQuery, IRawFileQuery, IRawQuery, IRawTextQuery, ISearchCompleteStats, ITextQuery, isSerializedFileMatch, ISerializedSearchProgressItem } from 'vs/workbench/services/search/common/search';
|
||||
import { FileSearchManager } from 'vs/workbench/services/search/node/fileSearchManager';
|
||||
@@ -35,7 +35,7 @@ export class ExtHostSearch implements ExtHostSearchShape {
|
||||
|
||||
private _fileSearchManager: FileSearchManager;
|
||||
|
||||
constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer | null, private _logService: ILogService, private _extfs = extfs) {
|
||||
constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer | null, private _logService: ILogService, private _pfs = pfs) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadSearch);
|
||||
this._fileSearchManager = new FileSearchManager();
|
||||
}
|
||||
@@ -146,7 +146,7 @@ export class ExtHostSearch implements ExtHostSearchShape {
|
||||
}
|
||||
|
||||
const query = reviveQuery(rawQuery);
|
||||
const engine = new TextSearchManager(query, provider, this._extfs);
|
||||
const engine = new TextSearchManager(query, provider, this._pfs);
|
||||
return engine.search(progress => this._proxy.$handleTextMatch(handle, session, progress), token);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -9,7 +9,7 @@ import * as path from 'vs/base/common/path';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
import { mkdirp } from 'vs/base/node/pfs';
|
||||
import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs';
|
||||
import {
|
||||
IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService,
|
||||
IExtensionEnablementService, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier
|
||||
@@ -28,7 +28,6 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { FileService } from 'vs/workbench/services/files/node/fileService';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { IPager } from 'vs/base/common/paging';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
@@ -255,7 +254,7 @@ suite('ExtensionsTipsService Test', () => {
|
||||
teardown(done => {
|
||||
(<ExtensionTipsService>testObject).dispose();
|
||||
if (parentResource) {
|
||||
extfs.del(parentResource, os.tmpdir(), () => { }, done);
|
||||
rimraf(parentResource, RimRafMode.MOVE).then(done, done);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { dirname, join, basename } from 'vs/base/common/path';
|
||||
import { del, exists, readdir, readFile } from 'vs/base/node/pfs';
|
||||
import { exists, readdir, readFile, rimraf } from 'vs/base/node/pfs';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
@@ -50,7 +50,7 @@ export class StartupProfiler implements IWorkbenchContribution {
|
||||
|
||||
const removeArgs: string[] = ['--prof-startup'];
|
||||
const markerFile = readFile(profileFilenamePrefix).then(value => removeArgs.push(...value.toString().split('|')))
|
||||
.then(() => del(profileFilenamePrefix)) // (1) delete the file to tell the main process to stop profiling
|
||||
.then(() => rimraf(profileFilenamePrefix)) // (1) delete the file to tell the main process to stop profiling
|
||||
.then(() => new Promise(resolve => { // (2) wait for main that recreates the fail to signal profiling has stopped
|
||||
const check = () => {
|
||||
exists(profileFilenamePrefix).then(exists => {
|
||||
@@ -63,7 +63,7 @@ export class StartupProfiler implements IWorkbenchContribution {
|
||||
};
|
||||
check();
|
||||
}))
|
||||
.then(() => del(profileFilenamePrefix)); // (3) finally delete the file again
|
||||
.then(() => rimraf(profileFilenamePrefix)); // (3) finally delete the file again
|
||||
|
||||
markerFile.then(() => {
|
||||
return readdir(dir).then(files => files.filter(value => value.indexOf(prefix) === 0));
|
||||
|
||||
@@ -30,7 +30,7 @@ import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform
|
||||
import { StorageService } from 'vs/platform/storage/node/storageService';
|
||||
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { sanitizeFilePath } from 'vs/base/node/extfs';
|
||||
import { sanitizeFilePath } from 'vs/base/common/extpath';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
|
||||
@@ -242,7 +242,7 @@ class BackupFileServiceImpl implements IBackupFileService {
|
||||
const backupResource = this.toBackupResource(resource);
|
||||
|
||||
return this.ioOperationQueues.queueFor(backupResource).queue(() => {
|
||||
return pfs.del(backupResource.fsPath).then(() => model.remove(backupResource));
|
||||
return pfs.rimraf(backupResource.fsPath, pfs.RimRafMode.MOVE).then(() => model.remove(backupResource));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -251,7 +251,7 @@ class BackupFileServiceImpl implements IBackupFileService {
|
||||
this.isShuttingDown = true;
|
||||
|
||||
return this.ready.then(model => {
|
||||
return pfs.del(this.backupWorkspacePath).then(() => model.clear());
|
||||
return pfs.rimraf(this.backupWorkspacePath, pfs.RimRafMode.MOVE).then(() => model.clear());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ suite('BackupFileService', () => {
|
||||
service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath);
|
||||
|
||||
// Delete any existing backups completely and then re-create it.
|
||||
return pfs.del(backupHome, os.tmpdir()).then(() => {
|
||||
return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE).then(() => {
|
||||
return pfs.mkdirp(backupHome).then(() => {
|
||||
return pfs.writeFile(workspacesJsonPath, '');
|
||||
});
|
||||
@@ -81,7 +81,7 @@ suite('BackupFileService', () => {
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
return pfs.del(backupHome, os.tmpdir());
|
||||
return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
suite('hashPath', () => {
|
||||
|
||||
@@ -17,7 +17,6 @@ import { ConfigurationModel } from 'vs/platform/configuration/common/configurati
|
||||
import { WorkspaceConfigurationModelParser, FolderSettingsModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels';
|
||||
import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService';
|
||||
import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
@@ -599,22 +598,18 @@ export class NodeBasedFolderConfiguration extends AbstractFolderConfiguration {
|
||||
}
|
||||
|
||||
private resolveStat(resource: URI): Promise<{ resource: URI, isDirectory?: boolean, children?: { resource: URI; }[] }> {
|
||||
return new Promise<{ resource: URI, isDirectory?: boolean, children?: { resource: URI; }[] }>((c, e) => {
|
||||
extfs.readdir(resource.fsPath, (error, children) => {
|
||||
if (error) {
|
||||
if ((<any>error).code === 'ENOTDIR') {
|
||||
c({ resource });
|
||||
} else {
|
||||
e(error);
|
||||
}
|
||||
} else {
|
||||
c({
|
||||
resource,
|
||||
isDirectory: true,
|
||||
children: children.map(child => { return { resource: resources.joinPath(resource, child) }; })
|
||||
});
|
||||
}
|
||||
});
|
||||
return pfs.readdir(resource.fsPath).then(children => {
|
||||
return {
|
||||
resource,
|
||||
isDirectory: true,
|
||||
children: children.map(child => { return { resource: resources.joinPath(resource, child) }; })
|
||||
};
|
||||
}, error => {
|
||||
if ((<any>error).code === 'ENOTDIR') {
|
||||
return Promise.resolve({ resource });
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -14,7 +14,6 @@ import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/
|
||||
import { parseArgs } from 'vs/platform/environment/node/argv';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { TestTextFileService, TestTextResourceConfigurationService, workbenchInstantiationService, TestLifecycleService, TestEnvironmentService, TestStorageService } from 'vs/workbench/test/workbenchTestServices';
|
||||
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
@@ -30,7 +29,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { mkdirp } from 'vs/base/node/pfs';
|
||||
import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { CommandService } from 'vs/workbench/services/commands/common/commandService';
|
||||
@@ -134,7 +133,7 @@ suite('ConfigurationEditingService', () => {
|
||||
function clearWorkspace(): Promise<void> {
|
||||
return new Promise<void>((c, e) => {
|
||||
if (parentDir) {
|
||||
extfs.del(parentDir, os.tmpdir(), () => c(undefined), () => c(undefined));
|
||||
rimraf(parentDir, RimRafMode.MOVE).then(c, c);
|
||||
} else {
|
||||
c(undefined);
|
||||
}
|
||||
|
||||
+6
-6
@@ -109,7 +109,7 @@ suite('WorkspaceContextService - Folder', () => {
|
||||
(<WorkspaceService>workspaceContextService).dispose();
|
||||
}
|
||||
if (parentResource) {
|
||||
return pfs.del(parentResource, os.tmpdir());
|
||||
return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -179,7 +179,7 @@ suite('WorkspaceContextService - Workspace', () => {
|
||||
(<WorkspaceService>testObject).dispose();
|
||||
}
|
||||
if (parentResource) {
|
||||
return pfs.del(parentResource, os.tmpdir());
|
||||
return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -244,7 +244,7 @@ suite('WorkspaceContextService - Workspace Editing', () => {
|
||||
(<WorkspaceService>testObject).dispose();
|
||||
}
|
||||
if (parentResource) {
|
||||
return pfs.del(parentResource, os.tmpdir());
|
||||
return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -505,7 +505,7 @@ suite('WorkspaceService - Initialization', () => {
|
||||
(<WorkspaceService>testObject).dispose();
|
||||
}
|
||||
if (parentResource) {
|
||||
return pfs.del(parentResource, os.tmpdir());
|
||||
return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -762,7 +762,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
|
||||
(<WorkspaceService>testObject).dispose();
|
||||
}
|
||||
if (parentResource) {
|
||||
return pfs.del(parentResource, os.tmpdir());
|
||||
return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -1057,7 +1057,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
|
||||
(<WorkspaceService>testObject).dispose();
|
||||
}
|
||||
if (parentResource) {
|
||||
return pfs.del(parentResource, os.tmpdir());
|
||||
return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
@@ -134,7 +134,7 @@ export class CachedExtensionScanner {
|
||||
}
|
||||
|
||||
try {
|
||||
await pfs.del(cacheFile);
|
||||
await pfs.rimraf(cacheFile, pfs.RimRafMode.MOVE);
|
||||
} catch (err) {
|
||||
errors.onUnexpectedError(err);
|
||||
console.error(err);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Profile, ProfileNode } from 'v8-inspect-profiler';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { realpathSync } from 'vs/base/node/extfs';
|
||||
import { realpathSync } from 'vs/base/node/extpath';
|
||||
import { IExtensionHostProfile, IExtensionService, ProfileSegmentId, ProfileSession } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
|
||||
@@ -14,7 +14,6 @@ import { isEqualOrParent } from 'vs/base/common/extpath';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { ThrottledDelayer, timeout } from 'vs/base/common/async';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import * as nls from 'vs/nls';
|
||||
@@ -42,12 +41,50 @@ import product from 'vs/platform/product/node/product';
|
||||
import { IEncodingOverride, ResourceEncodings } from 'vs/workbench/services/files/node/encoding';
|
||||
import { createReadableOfSnapshot } from 'vs/workbench/services/files/node/streams';
|
||||
import { withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
|
||||
export interface IFileServiceTestOptions {
|
||||
disableWatcher?: boolean;
|
||||
encodingOverride?: IEncodingOverride[];
|
||||
}
|
||||
|
||||
interface IStatAndLink {
|
||||
stat: fs.Stats;
|
||||
isSymbolicLink: boolean;
|
||||
}
|
||||
|
||||
function statLink(path: string, callback: (error: Error | null, statAndIsLink: IStatAndLink | null) => void): void {
|
||||
fs.lstat(path, (error, lstat) => {
|
||||
if (error || lstat.isSymbolicLink()) {
|
||||
fs.stat(path, (error, stat) => {
|
||||
if (error) {
|
||||
return callback(error, null);
|
||||
}
|
||||
|
||||
callback(null, { stat, isSymbolicLink: lstat && lstat.isSymbolicLink() });
|
||||
});
|
||||
} else {
|
||||
callback(null, { stat: lstat, isSymbolicLink: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function readdir(path: string, callback: (error: Error | null, files: string[]) => void): void {
|
||||
// Mac: uses NFD unicode form on disk, but we want NFC
|
||||
// See also https://github.com/nodejs/node/issues/2165
|
||||
if (isMacintosh) {
|
||||
return fs.readdir(path, (error, children) => {
|
||||
if (error) {
|
||||
return callback(error, []);
|
||||
}
|
||||
|
||||
return callback(null, children.map(c => normalizeNFC(c)));
|
||||
});
|
||||
}
|
||||
|
||||
return fs.readdir(path, callback);
|
||||
}
|
||||
|
||||
export class FileService extends Disposable implements ILegacyFileService, IFileService {
|
||||
|
||||
_serviceBrand: any;
|
||||
@@ -633,7 +670,7 @@ export class FileService extends Disposable implements ILegacyFileService, IFile
|
||||
private doSetContentsAndResolve(resource: uri, absolutePath: string, value: string | ITextSnapshot, addBOM: boolean, encodingToWrite: string, options?: { mode?: number; flag?: string; }): Promise<IFileStat> {
|
||||
|
||||
// Configure encoding related options as needed
|
||||
const writeFileOptions: extfs.IWriteFileOptions = options ? options : Object.create(null);
|
||||
const writeFileOptions: pfs.IWriteFileOptions = options ? options : Object.create(null);
|
||||
if (addBOM || encodingToWrite !== UTF8) {
|
||||
writeFileOptions.encoding = {
|
||||
charset: encodingToWrite,
|
||||
@@ -694,7 +731,7 @@ export class FileService extends Disposable implements ILegacyFileService, IFile
|
||||
}).then(() => {
|
||||
|
||||
// 3.) delete temp file
|
||||
return pfs.del(tmpPath, os.tmpdir()).then(() => {
|
||||
return pfs.rimraf(tmpPath, pfs.RimRafMode.MOVE).then(() => {
|
||||
|
||||
// 4.) resolve again
|
||||
return this.resolve(resource);
|
||||
@@ -928,7 +965,7 @@ export class FileService extends Disposable implements ILegacyFileService, IFile
|
||||
}
|
||||
|
||||
return assertNonRecursiveDelete.then(() => {
|
||||
return pfs.del(absolutePath, os.tmpdir()).then(() => {
|
||||
return pfs.rimraf(absolutePath, pfs.RimRafMode.MOVE).then(() => {
|
||||
|
||||
// Events
|
||||
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE));
|
||||
@@ -980,7 +1017,7 @@ export class FileService extends Disposable implements ILegacyFileService, IFile
|
||||
const fsPath = resource.fsPath;
|
||||
const fsName = paths.basename(resource.fsPath);
|
||||
|
||||
const watcherDisposable = extfs.watch(fsPath, (eventType: string, filename: string) => {
|
||||
const watcherDisposable = pfs.watch(fsPath, (eventType: string, filename: string) => {
|
||||
const renamedOrDeleted = ((filename && filename !== fsName) || eventType === 'rename');
|
||||
|
||||
// The file was either deleted or renamed. Many tools apply changes to files in an
|
||||
@@ -1386,7 +1423,7 @@ export class StatResolver {
|
||||
}
|
||||
|
||||
private resolveChildren(absolutePath: string, absoluteTargetPaths: string[] | null, resolveSingleChildDescendants: boolean, callback: (children: IFileStat[] | null) => void): void {
|
||||
extfs.readdir(absolutePath, (error: Error, files: string[]) => {
|
||||
readdir(absolutePath, (error: Error, files: string[]) => {
|
||||
if (error) {
|
||||
if (this.errorLogger) {
|
||||
this.errorLogger(error);
|
||||
@@ -1412,15 +1449,15 @@ export class StatResolver {
|
||||
},
|
||||
|
||||
function stat(this: any): void {
|
||||
extfs.statLink(fileResource.fsPath, this);
|
||||
statLink(fileResource.fsPath, this);
|
||||
},
|
||||
|
||||
function countChildren(this: any, statAndLink: extfs.IStatAndLink): void {
|
||||
function countChildren(this: any, statAndLink: IStatAndLink): void {
|
||||
fileStat = statAndLink.stat;
|
||||
isSymbolicLink = statAndLink.isSymbolicLink;
|
||||
|
||||
if (fileStat.isDirectory()) {
|
||||
extfs.readdir(fileResource.fsPath, (error, result) => {
|
||||
readdir(fileResource.fsPath, (error, result) => {
|
||||
this(null, result ? result.length : 0);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ThrottledDelayer } from 'vs/base/common/async';
|
||||
import { FileChangeType } from 'vs/platform/files/common/files';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { realcaseSync, realpathSync } from 'vs/base/node/extfs';
|
||||
import { realcaseSync, realpathSync } from 'vs/base/node/extpath';
|
||||
|
||||
const nsfwActionToRawChangeType: { [key: number]: number } = [];
|
||||
nsfwActionToRawChangeType[nsfw.actions.CREATED] = FileChangeType.ADDED;
|
||||
|
||||
@@ -12,7 +12,7 @@ import * as glob from 'vs/base/common/glob';
|
||||
import { FileChangeType } from 'vs/platform/files/common/files';
|
||||
import { ThrottledDelayer } from 'vs/base/common/async';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
import { realcaseSync } from 'vs/base/node/extfs';
|
||||
import { realcaseSync } from 'vs/base/node/extpath';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import * as watcherCommon from 'vs/workbench/services/files/node/watcher/common';
|
||||
import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from 'vs/workbench/services/files/node/watcher/unix/watcher';
|
||||
|
||||
+17
-17
@@ -147,7 +147,7 @@ suite.skip('Chockidar watching', () => {
|
||||
});
|
||||
|
||||
suiteTeardown(async () => {
|
||||
await pfs.del(testDir);
|
||||
await pfs.rimraf(testDir, pfs.RimRafMode.MOVE);
|
||||
await service.stop();
|
||||
});
|
||||
|
||||
@@ -185,14 +185,14 @@ suite.skip('Chockidar watching', () => {
|
||||
await assertFileEvents(result, [{ path: copiedFilePath, type: FileChangeType.ADDED }, { path: testFolderPath, type: FileChangeType.ADDED }]);
|
||||
|
||||
// delete a file
|
||||
await pfs.del(copiedFilePath);
|
||||
await pfs.rimraf(copiedFilePath, pfs.RimRafMode.MOVE);
|
||||
let renamedFilePath = path.join(testFolderPath, 'file3.txt');
|
||||
// move a file
|
||||
await pfs.rename(testFilePath, renamedFilePath);
|
||||
await assertFileEvents(result, [{ path: copiedFilePath, type: FileChangeType.DELETED }, { path: testFilePath, type: FileChangeType.DELETED }, { path: renamedFilePath, type: FileChangeType.ADDED }]);
|
||||
|
||||
// delete a folder
|
||||
await pfs.del(testFolderPath);
|
||||
await pfs.rimraf(testFolderPath, pfs.RimRafMode.MOVE);
|
||||
await assertFileEvents(result, [{ path: testFolderPath, type: FileChangeType.DELETED }, { path: renamedFilePath, type: FileChangeType.DELETED }]);
|
||||
});
|
||||
|
||||
@@ -234,15 +234,15 @@ suite.skip('Chockidar watching', () => {
|
||||
await assertFileEvents(result, [{ path: movedFile2, type: FileChangeType.ADDED }, { path: movedFile3, type: FileChangeType.ADDED }]);
|
||||
|
||||
// delete all files
|
||||
await pfs.del(movedFile1); // hidden
|
||||
await pfs.del(movedFile2);
|
||||
await pfs.del(movedFile3);
|
||||
await pfs.del(folder1); // hidden
|
||||
await pfs.del(folder2); // hidden
|
||||
await pfs.del(folder3); // hidden
|
||||
await pfs.del(folder4);
|
||||
await pfs.del(folder5);
|
||||
await pfs.del(file4);
|
||||
await pfs.rimraf(movedFile1); // hidden
|
||||
await pfs.rimraf(movedFile2, pfs.RimRafMode.MOVE);
|
||||
await pfs.rimraf(movedFile3, pfs.RimRafMode.MOVE);
|
||||
await pfs.rimraf(folder1); // hidden
|
||||
await pfs.rimraf(folder2); // hidden
|
||||
await pfs.rimraf(folder3); // hidden
|
||||
await pfs.rimraf(folder4, pfs.RimRafMode.MOVE);
|
||||
await pfs.rimraf(folder5, pfs.RimRafMode.MOVE);
|
||||
await pfs.rimraf(file4, pfs.RimRafMode.MOVE);
|
||||
await assertFileEvents(result, [{ path: movedFile2, type: FileChangeType.DELETED }, { path: movedFile3, type: FileChangeType.DELETED }, { path: file4, type: FileChangeType.DELETED }, { path: folder4, type: FileChangeType.DELETED }, { path: folder5, type: FileChangeType.DELETED }]);
|
||||
});
|
||||
|
||||
@@ -278,9 +278,9 @@ suite.skip('Chockidar watching', () => {
|
||||
assert.equal(service.wacherCount, 1);
|
||||
|
||||
// delete all
|
||||
await pfs.del(folderPath1);
|
||||
await pfs.del(folderPath2);
|
||||
await pfs.del(filePath4);
|
||||
await pfs.rimraf(folderPath1, pfs.RimRafMode.MOVE);
|
||||
await pfs.rimraf(folderPath2, pfs.RimRafMode.MOVE);
|
||||
await pfs.rimraf(filePath4, pfs.RimRafMode.MOVE);
|
||||
|
||||
await assertFileEvents(result, [{ path: folderPath1, type: FileChangeType.DELETED }, { path: filePath2, type: FileChangeType.DELETED }]);
|
||||
});
|
||||
@@ -311,8 +311,8 @@ suite.skip('Chockidar watching', () => {
|
||||
await assertFileEvents(result, [{ path: filePath2, type: FileChangeType.DELETED }]);
|
||||
|
||||
// delete all
|
||||
await pfs.del(folderPath1);
|
||||
await pfs.del(filePath1);
|
||||
await pfs.rimraf(folderPath1, pfs.RimRafMode.MOVE);
|
||||
await pfs.rimraf(filePath1, pfs.RimRafMode.MOVE);
|
||||
|
||||
await assertFileEvents(result, [{ path: filePath1, type: FileChangeType.DELETED }]);
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ suite('FileService', () => {
|
||||
|
||||
teardown(() => {
|
||||
service.dispose();
|
||||
return pfs.del(parentDir, os.tmpdir());
|
||||
return pfs.rimraf(parentDir, pfs.RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('createFile', () => {
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mkdir, open, close, read, write } from 'fs';
|
||||
import { tmpdir } from 'os';
|
||||
import { promisify } from 'util';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
import { statLink, readdir, unlink, del, move, copy, readFile, writeFile, fileExists, truncate } from 'vs/base/node/pfs';
|
||||
import { statLink, readdir, unlink, move, copy, readFile, writeFile, fileExists, truncate, rimraf, RimRafMode } from 'vs/base/node/pfs';
|
||||
import { normalize, basename, dirname } from 'vs/base/common/path';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { isEqual } from 'vs/base/common/extpath';
|
||||
@@ -231,7 +230,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
|
||||
|
||||
protected async doDelete(filePath: string, opts: FileDeleteOptions): Promise<void> {
|
||||
if (opts.recursive) {
|
||||
await del(filePath, tmpdir());
|
||||
await rimraf(filePath, RimRafMode.MOVE);
|
||||
} else {
|
||||
await unlink(filePath);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { join, basename, dirname, posix } from 'vs/base/common/path';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { copy, del, symlink } from 'vs/base/node/pfs';
|
||||
import { copy, rimraf, symlink, RimRafMode } from 'vs/base/node/pfs';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { existsSync, statSync, readdirSync, readFileSync } from 'fs';
|
||||
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||
@@ -91,7 +91,7 @@ suite('Disk File Service', () => {
|
||||
teardown(async () => {
|
||||
disposables = dispose(disposables);
|
||||
|
||||
await del(parentDir, tmpdir());
|
||||
await rimraf(parentDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('createFolder', async () => {
|
||||
|
||||
+2
-3
@@ -11,8 +11,7 @@ import * as json from 'vs/base/common/json';
|
||||
import { ChordKeybinding, KeyCode, SimpleKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { OS } from 'vs/base/common/platform';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { mkdirp } from 'vs/base/node/pfs';
|
||||
import { mkdirp, rimraf, RimRafMode } from 'vs/base/node/pfs';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
@@ -109,7 +108,7 @@ suite('KeybindingsEditing', () => {
|
||||
teardown(() => {
|
||||
return new Promise<void>((c, e) => {
|
||||
if (testDir) {
|
||||
extfs.del(testDir, os.tmpdir(), () => c(undefined), () => c(undefined));
|
||||
rimraf(testDir, RimRafMode.MOVE).then(c, c);
|
||||
} else {
|
||||
c(undefined);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { watch } from 'vs/base/node/pfs';
|
||||
import { dirname, join } from 'vs/base/common/path';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
@@ -29,7 +29,7 @@ let callbacks: ((eventType: string, fileName?: string) => void)[] = [];
|
||||
function watchOutputDirectory(outputDir: string, logService: ILogService, onChange: (eventType: string, fileName: string) => void): IDisposable {
|
||||
callbacks.push(onChange);
|
||||
if (!watchingOutputDir) {
|
||||
const watcherDisposable = extfs.watch(outputDir, (eventType, fileName) => {
|
||||
const watcherDisposable = watch(outputDir, (eventType, fileName) => {
|
||||
for (const callback of callbacks) {
|
||||
callback(eventType, fileName);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { readdir } from 'vs/base/node/pfs';
|
||||
import { IFileQuery, IFolderQuery, IProgressMessage, ISearchEngineStats, IRawFileMatch, ISearchEngine, ISearchEngineSuccess } from 'vs/workbench/services/search/common/search';
|
||||
import { spawnRipgrepCmd } from './ripgrepFileSearch';
|
||||
|
||||
@@ -516,12 +516,14 @@ export class FileWalker {
|
||||
this.walkedPaths[realpath] = true; // remember as walked
|
||||
|
||||
// Continue walking
|
||||
return extfs.readdir(currentAbsolutePath, (error: Error, children: string[]): void => {
|
||||
if (error || this.isCanceled || this.isLimitHit) {
|
||||
return readdir(currentAbsolutePath).then(children => {
|
||||
if (this.isCanceled || this.isLimitHit) {
|
||||
return clb(null);
|
||||
}
|
||||
|
||||
this.doWalk(folderQuery, currentRelativePath, children, onResult, err => clb(err || null));
|
||||
}, error => {
|
||||
clb(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { IFileMatch, IProgressMessage, ITextQuery, ITextSearchStats, ITextSearchMatch, ISerializedFileMatch, ISerializedSearchSuccess } from 'vs/workbench/services/search/common/search';
|
||||
import { RipgrepTextSearchEngine } from 'vs/workbench/services/search/node/ripgrepTextSearchEngine';
|
||||
import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager';
|
||||
@@ -30,7 +30,7 @@ export class TextSearchEngineAdapter {
|
||||
onMessage({ message: msg });
|
||||
}
|
||||
};
|
||||
const textSearchManager = new TextSearchManager(this.query, new RipgrepTextSearchEngine(pretendOutputChannel), extfs);
|
||||
const textSearchManager = new TextSearchManager(this.query, new RipgrepTextSearchEngine(pretendOutputChannel), pfs);
|
||||
return new Promise((resolve, reject) => {
|
||||
return textSearchManager
|
||||
.search(
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as glob from 'vs/base/common/glob';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { toCanonicalName } from 'vs/base/node/encoding';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
@@ -22,7 +22,7 @@ export class TextSearchManager {
|
||||
private isLimitHit: boolean;
|
||||
private resultCount = 0;
|
||||
|
||||
constructor(private query: ITextQuery, private provider: vscode.TextSearchProvider, private _extfs: typeof extfs = extfs) {
|
||||
constructor(private query: ITextQuery, private provider: vscode.TextSearchProvider, private _pfs: typeof pfs = pfs) {
|
||||
}
|
||||
|
||||
search(onProgress: (matches: IFileMatch[]) => void, token: CancellationToken): Promise<ISearchCompleteStats> {
|
||||
@@ -159,15 +159,7 @@ export class TextSearchManager {
|
||||
}
|
||||
|
||||
private readdir(dirname: string): Promise<string[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._extfs.readdir(dirname, (err, files) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(files);
|
||||
});
|
||||
});
|
||||
return this._pfs.readdir(dirname);
|
||||
}
|
||||
|
||||
private getSearchOptionsForFolder(fq: IFolderQuery<URI>): vscode.TextSearchOptions {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { IFileMatch, IFileQuery, IPatternInfo, IRawFileMatch2, ISearchCompleteStats, ISearchQuery, ITextQuery, QueryType, resultIsMatch } from 'vs/workbench/services/search/common/search';
|
||||
import { MainContext, MainThreadSearchShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch';
|
||||
@@ -55,7 +55,7 @@ class MockMainThreadSearch implements MainThreadSearchShape {
|
||||
}
|
||||
}
|
||||
|
||||
let mockExtfs: Partial<typeof extfs>;
|
||||
let mockPFS: Partial<typeof pfs>;
|
||||
|
||||
suite('ExtHostSearch', () => {
|
||||
async function registerTestTextSearchProvider(provider: vscode.TextSearchProvider, scheme = 'file'): Promise<void> {
|
||||
@@ -130,8 +130,8 @@ suite('ExtHostSearch', () => {
|
||||
|
||||
rpcProtocol.set(MainContext.MainThreadSearch, mockMainThreadSearch);
|
||||
|
||||
mockExtfs = {};
|
||||
extHostSearch = new ExtHostSearch(rpcProtocol, null!, logService, mockExtfs as typeof extfs);
|
||||
mockPFS = {};
|
||||
extHostSearch = new ExtHostSearch(rpcProtocol, null!, logService, mockPFS as any);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
@@ -856,14 +856,14 @@ suite('ExtHostSearch', () => {
|
||||
});
|
||||
|
||||
test('basic sibling clause', async () => {
|
||||
mockExtfs.readdir = (_path: string, callback: (error: Error, files: string[]) => void) => {
|
||||
mockPFS.readdir = (_path: string) => {
|
||||
if (_path === rootFolderA.fsPath) {
|
||||
callback(null!, [
|
||||
return Promise.resolve([
|
||||
'file1.js',
|
||||
'file1.ts'
|
||||
]);
|
||||
} else {
|
||||
callback(new Error('Wrong path'), null!);
|
||||
return Promise.reject(new Error('Wrong path'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -899,21 +899,21 @@ suite('ExtHostSearch', () => {
|
||||
});
|
||||
|
||||
test('multiroot sibling clause', async () => {
|
||||
mockExtfs.readdir = (_path: string, callback: (error: Error, files: string[]) => void) => {
|
||||
mockPFS.readdir = (_path: string) => {
|
||||
if (_path === joinPath(rootFolderA, 'folder').fsPath) {
|
||||
callback(null!, [
|
||||
return Promise.resolve([
|
||||
'fileA.scss',
|
||||
'fileA.css',
|
||||
'file2.css'
|
||||
]);
|
||||
} else if (_path === rootFolderB.fsPath) {
|
||||
callback(null!, [
|
||||
return Promise.resolve([
|
||||
'fileB.ts',
|
||||
'fileB.js',
|
||||
'file3.js'
|
||||
]);
|
||||
} else {
|
||||
callback(new Error('Wrong path'), null!);
|
||||
return Promise.reject(new Error('Wrong path'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user