Add partial reading of files to fastfs

Summary:
public

Adds the ability to read files partially with `readWhile` to `Fastfs`
This feature will be used by for building the haste map, where we only need to read the doc block (if present) to extract the provided module name.

Reviewed By: martinbigio

Differential Revision: D2878093

fb-gh-sync-id: 219cf6d5962b89eeb42c728e176bf9fcf6a67e9c
This commit is contained in:
David Aurelio 2016-01-28 21:18:10 -08:00 committed by facebook-github-bot-5
parent 54e5033221
commit 2d9876dedf
3 changed files with 172 additions and 0 deletions

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
* An arbitrary module header
* @providesModule
*/
const some: string = 'arbitrary code';
const containing: string = 'ūñïčødę';
/**
* An arbitrary class that extends some thing
* It exposes a random number, which may be reset at the callers discretion
*/
class Arbitrary extends Something {
constructor() {
this.reset();
}
/**
* Returns the random number
* @returns number
*/
get random(): number {
return this._random;
}
/**
* Re-creates the internal random number
* @returns void
*/
reset(): void {
this._random = Math.random();
}
}

View File

@ -0,0 +1,86 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.autoMockOff();
const Fastfs = require('../fastfs');
const {EventEmitter} = require('events');
const fs = require('fs');
const path = require('path');
const fileName = path.resolve(__dirname, 'fastfs-data');
const contents = fs.readFileSync(fileName, 'utf-8');
describe('fastfs:', function() {
let fastfs;
const crawling = Promise.resolve([fileName]);
const roots = [__dirname];
const watcher = new EventEmitter();
beforeEach(function(done) {
fastfs = new Fastfs('arbitrary', roots, watcher, {crawling});
fastfs.build().then(done);
});
describe('partial reading', () => {
// these are integrated tests that read real files from disk
pit('reads a file while a predicate returns true', function() {
return fastfs.readWhile(fileName, () => true).then(readContent =>
expect(readContent).toEqual(contents)
);
});
pit('invokes the predicate with the new chunk, the invocation index, and the result collected so far', () => {
const predicate = jest.genMockFn().mockReturnValue(true);
return fastfs.readWhile(fileName, predicate).then(() => {
let aggregated = '';
const {calls} = predicate.mock;
expect(calls).not.toEqual([]);
calls.forEach((call, i) => {
const [chunk] = call;
aggregated += chunk;
expect(chunk).not.toBe('');
expect(call).toEqual([chunk, i, aggregated]);
});
expect(aggregated).toEqual(contents);
});
});
pit('stops reading when the predicate returns false', () => {
const predicate = jest.genMockFn().mockImpl((_, i) => i !== 0);
return fastfs.readWhile(fileName, predicate).then((readContent) => {
const {calls} = predicate.mock;
expect(calls.length).toBe(1);
expect(readContent).toBe(calls[0][2]);
});
});
pit('after reading the whole file with `readWhile`, `read()` still works', () => {
// this test allows to reuse the results of `readWhile` for `readFile`
return fastfs.readWhile(fileName, () => true).then(() => {
fastfs.readFile(fileName).then(readContent =>
expect(readContent).toEqual(contents)
);
});
});
pit('after reading parts of the file with `readWhile`, `read()` still works', () => {
return fastfs.readWhile(fileName, () => false).then(() => {
fastfs.readFile(fileName).then(readContent =>
expect(readContent).toEqual(contents)
);
});
});
});
});

View File

@ -14,6 +14,7 @@ const {EventEmitter} = require('events');
const fs = require('graceful-fs');
const path = require('path');
const open = Promise.denodeify(fs.open);
const readFile = Promise.denodeify(fs.readFile);
const stat = Promise.denodeify(fs.stat);
@ -114,6 +115,14 @@ class Fastfs extends EventEmitter {
return file.read();
}
readWhile(filePath, predicate) {
const file = this._getFile(filePath);
if (!file) {
throw new Error(`Unable to find file with path: ${filePath}`);
}
return file.readWhile(predicate);
}
closest(filePath, name) {
for (let file = this._getFile(filePath).parent;
file;
@ -241,6 +250,44 @@ class File {
return this._read;
}
readWhile(predicate) {
const CHUNK_SIZE = 512;
let result = '';
return open(this.path, 'r').then(fd => {
/* global Buffer: true */
const buffer = new Buffer(CHUNK_SIZE);
const p = new Promise((resolve, reject) => {
let counter = 0;
const callback = (error, bytesRead) => {
if (error) {
reject();
return;
}
const chunk = buffer.toString('utf8', 0, bytesRead);
result += chunk;
if (bytesRead > 0 && predicate(chunk, counter++, result)) {
readChunk(fd, buffer, callback);
} else {
if (bytesRead === 0 && !this._read) { // reached EOF
this._read = Promise.resolve(result);
}
resolve(result);
}
};
readChunk(fd, buffer, callback);
});
p.catch(() => fs.close(fd));
return p;
});
function readChunk(fd, buffer, callback) {
fs.read(fd, buffer, 0, CHUNK_SIZE, null, callback);
}
}
stat() {
if (!this._stat) {
this._stat = stat(this.path);