diff --git a/src/typings/node.d.ts b/src/typings/node.d.ts index ed30c111cda..6f048fdd03e 100644 --- a/src/typings/node.d.ts +++ b/src/typings/node.d.ts @@ -1442,6 +1442,8 @@ declare module "fs" { export function futimesSync(fd: number, atime: Date, mtime: Date): void; export function fsync(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; export function fsyncSync(fd: number): void; + export function fdatasync(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fdatasyncSync(fd: number): void; export function write(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void; export function write(fd: number, buffer: Buffer, offset: number, length: number, callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void; export function write(fd: number, data: any, callback?: (err: NodeJS.ErrnoException, written: number, str: string) => void): void; @@ -1503,9 +1505,9 @@ declare module "fs" { * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFileSync returns a string; otherwise it returns a Buffer. */ export function readFileSync(filename: string, options?: { flag?: string; }): Buffer; - export function writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; - export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; - export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFile(filename: string|number, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFile(filename: string|number, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFile(filename: string|number, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void; export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: string; flag?: string; }): void; export function appendFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; diff --git a/src/vs/base/node/extfs.ts b/src/vs/base/node/extfs.ts index 4335c7198d7..623afce3aa9 100644 --- a/src/vs/base/node/extfs.ts +++ b/src/vs/base/node/extfs.ts @@ -312,4 +312,36 @@ export function mv(source: string, target: string, callback: (error: Error) => v return callback(err); }); +} + +// Calls fs.writeFile() followed by a fs.sync() call to flush the changes to disk +// We do this in cases where we want to make sure the data is really on disk and +// not in some cache. +// +// See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194 +export function writeFileAndFlush(path: string, data: string | NodeBuffer, options: { encoding?: string; mode?: number; flag?: string; }, callback: (error: Error) => void): void { + if (!options) { + options = { encoding: 'utf8', mode: 0o666, flag: 'w' }; + } else if (typeof options === 'string') { + options = { encoding: options, mode: 0o666, flag: 'w' }; + } + + // Open the file with same flags and mode as fs.writeFile() + fs.open(path, options.flag, options.mode, (openError, fd) => { + if (openError) { + return callback(openError); + } + + // It is valid to pass a fd handle to fs.writeFile() and this will keep the handle open! + fs.writeFile(fd, data, options.encoding, (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) => { + return fs.close(fd, (closeError) => callback(syncError || closeError)); // make sure to carry over the fdatasync error if any! + }); + }); + }); } \ No newline at end of file diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 703b0d9dfcd..387b5fbb185 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -135,6 +135,12 @@ export function writeFile(path: string, data: any, encoding: string = 'utf8'): P return nfcall(fs.writeFile, path, data, encoding); } +export function writeFileAndFlush(path: string, data: string, encoding?: string): Promise; +export function writeFileAndFlush(path: string, data: NodeBuffer, encoding?: string): Promise; +export function writeFileAndFlush(path: string, data: any, encoding: string = 'utf8'): Promise { + return nfcall(extfs.writeFileAndFlush, path, data, encoding); +} + /** * Read a dir and return only subfolders */ diff --git a/src/vs/base/test/node/extfs/extfs.test.ts b/src/vs/base/test/node/extfs/extfs.test.ts index 076c54ad3e1..531c9cd0916 100644 --- a/src/vs/base/test/node/extfs/extfs.test.ts +++ b/src/vs/base/test/node/extfs/extfs.test.ts @@ -18,9 +18,9 @@ import extfs = require('vs/base/node/extfs'); suite('Extfs', () => { test('mkdirp', function (done: () => void) { - var id = uuid.generateUuid(); - var parentDir = path.join(os.tmpdir(), 'vsctests', id); - var newDir = path.join(parentDir, 'extfs', id); + const id = uuid.generateUuid(); + const parentDir = path.join(os.tmpdir(), 'vsctests', id); + const newDir = path.join(parentDir, 'extfs', id); extfs.mkdirp(newDir, 493, (error) => { assert.ok(!error); @@ -31,12 +31,12 @@ suite('Extfs', () => { }); test('copy, move and delete', function (done: () => void) { - var id = uuid.generateUuid(); - var id2 = uuid.generateUuid(); - var sourceDir = require.toUrl('./fixtures'); - var parentDir = path.join(os.tmpdir(), 'vsctests', 'extfs'); - var targetDir = path.join(parentDir, id); - var targetDir2 = path.join(parentDir, id2); + const id = uuid.generateUuid(); + const id2 = uuid.generateUuid(); + const sourceDir = require.toUrl('./fixtures'); + const parentDir = path.join(os.tmpdir(), 'vsctests', 'extfs'); + const targetDir = path.join(parentDir, id); + const targetDir2 = path.join(parentDir, id2); extfs.copy(sourceDir, targetDir, (error) => { assert.ok(!error); @@ -76,9 +76,9 @@ suite('Extfs', () => { test('readdir', function (done: () => void) { if (strings.canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) { - var id = uuid.generateUuid(); - var parentDir = path.join(os.tmpdir(), 'vsctests', id); - var newDir = path.join(parentDir, 'extfs', id, 'öäü'); + const id = uuid.generateUuid(); + const parentDir = path.join(os.tmpdir(), 'vsctests', id); + const newDir = path.join(parentDir, 'extfs', id, 'öäü'); extfs.mkdirp(newDir, 493, (error) => { assert.ok(!error); @@ -94,4 +94,30 @@ suite('Extfs', () => { done(); } }); + + test('writeFileAndFlush', function (done: () => 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'); + + extfs.mkdirp(newDir, 493, (error) => { + assert.ok(!error); + assert.ok(fs.existsSync(newDir)); + + extfs.writeFileAndFlush(testFile, 'Hello World', null, (error) => { + assert.ok(!error); + assert.equal(fs.readFileSync(testFile), 'Hello World'); + + const largeString = (new Array(100 * 1024)).join('Large String\n'); + + extfs.writeFileAndFlush(testFile, largeString, null, (error) => { + assert.ok(!error); + assert.equal(fs.readFileSync(testFile), largeString); + + done(); + }); + }); + }); + }); }); \ No newline at end of file diff --git a/src/vs/workbench/services/files/node/fileService.ts b/src/vs/workbench/services/files/node/fileService.ts index 581f57fae81..a3187eb0247 100644 --- a/src/vs/workbench/services/files/node/fileService.ts +++ b/src/vs/workbench/services/files/node/fileService.ts @@ -272,13 +272,13 @@ export class FileService implements IFileService { // Write fast if we do UTF 8 without BOM if (!addBom && encodingToWrite === encoding.UTF8) { - writeFilePromise = pfs.writeFile(absolutePath, value, encoding.UTF8); + writeFilePromise = pfs.writeFileAndFlush(absolutePath, value, encoding.UTF8); } // Otherwise use encoding lib else { let encoded = encoding.encode(value, encodingToWrite, { addBOM: addBom }); - writeFilePromise = pfs.writeFile(absolutePath, encoded); + writeFilePromise = pfs.writeFileAndFlush(absolutePath, encoded); } // 4.) set contents