123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- 'use strict';
- const Readable = require('stream').Readable;
- const EventEmitter = require('events').EventEmitter;
- const path = require('path');
- const normalizeOptions = require('./normalize-options');
- const stat = require('./stat');
- const call = require('./call');
- class DirectoryReader {
-
- constructor (dir, options, internalOptions) {
- this.options = options = normalizeOptions(options, internalOptions);
-
-
- this.shouldRead = true;
-
-
- this.queue = [{
- path: dir,
- basePath: options.basePath,
- posixBasePath: options.posixBasePath,
- depth: 0
- }];
-
- this.pending = 0;
-
- this.buffer = [];
- this.stream = new Readable({ objectMode: true });
- this.stream._read = () => {
-
- this.shouldRead = true;
-
- if (this.buffer.length > 0) {
- this.pushFromBuffer();
- }
-
- if (this.queue.length > 0) {
- if (this.options.facade.sync) {
- while (this.queue.length > 0) {
- this.readNextDirectory();
- }
- }
- else {
- this.readNextDirectory();
- }
- }
- this.checkForEOF();
- };
- }
-
- readNextDirectory () {
- let facade = this.options.facade;
- let dir = this.queue.shift();
- this.pending++;
-
- call.safe(facade.fs.readdir, dir.path, (err, items) => {
- if (err) {
-
- this.emit('error', err);
- return this.finishedReadingDirectory();
- }
- try {
-
- facade.forEach(
- items,
- this.processItem.bind(this, dir),
- this.finishedReadingDirectory.bind(this, dir)
- );
- }
- catch (err2) {
-
-
- this.emit('error', err2);
- this.finishedReadingDirectory();
- }
- });
- }
-
- finishedReadingDirectory () {
- this.pending--;
- if (this.shouldRead) {
-
- if (this.queue.length > 0 && this.options.facade.async) {
- this.readNextDirectory();
- }
- this.checkForEOF();
- }
- }
-
- checkForEOF () {
- if (this.buffer.length === 0 &&
- this.pending === 0 &&
- this.queue.length === 0) {
-
- this.stream.push(null);
- }
- }
-
- processItem (dir, item, done) {
- let stream = this.stream;
- let options = this.options;
- let itemPath = dir.basePath + item;
- let posixPath = dir.posixBasePath + item;
- let fullPath = path.join(dir.path, item);
-
-
-
- let maxDepthReached = dir.depth >= options.recurseDepth;
-
- let needStats =
- !maxDepthReached ||
- options.stats ||
- options.recurseFn ||
- options.filterFn ||
- EventEmitter.listenerCount(stream, 'file') ||
- EventEmitter.listenerCount(stream, 'directory') ||
- EventEmitter.listenerCount(stream, 'symlink');
-
- if (!needStats) {
- if (this.filter(itemPath, posixPath)) {
- this.pushOrBuffer({ data: itemPath });
- }
- return done();
- }
-
- stat(options.facade.fs, fullPath, (err, stats) => {
- if (err) {
-
- this.emit('error', err);
- return done();
- }
- try {
-
-
-
- stats.path = itemPath;
-
- stats.depth = dir.depth;
- if (this.shouldRecurse(stats, posixPath, maxDepthReached)) {
-
- this.queue.push({
- path: fullPath,
- basePath: itemPath + options.sep,
- posixBasePath: posixPath + '/',
- depth: dir.depth + 1,
- });
- }
-
- if (this.filter(stats, posixPath)) {
- this.pushOrBuffer({
- data: options.stats ? stats : itemPath,
- file: stats.isFile(),
- directory: stats.isDirectory(),
- symlink: stats.isSymbolicLink(),
- });
- }
- done();
- }
- catch (err2) {
-
-
- this.emit('error', err2);
- done();
- }
- });
- }
-
- pushOrBuffer (chunk) {
-
- this.buffer.push(chunk);
-
-
- if (this.shouldRead) {
- this.pushFromBuffer();
- }
- }
-
- pushFromBuffer () {
- let stream = this.stream;
- let chunk = this.buffer.shift();
-
- try {
- this.shouldRead = stream.push(chunk.data);
- }
- catch (err) {
- this.emit('error', err);
- }
-
- chunk.file && this.emit('file', chunk.data);
- chunk.symlink && this.emit('symlink', chunk.data);
- chunk.directory && this.emit('directory', chunk.data);
- }
-
- shouldRecurse (stats, posixPath, maxDepthReached) {
- let options = this.options;
- if (maxDepthReached) {
-
- return false;
- }
- else if (!stats.isDirectory()) {
-
- return false;
- }
- else if (options.recurseGlob) {
-
-
- return options.recurseGlob.test(posixPath);
- }
- else if (options.recurseRegExp) {
-
-
- return options.recurseRegExp.test(stats.path);
- }
- else if (options.recurseFn) {
- try {
-
- return options.recurseFn.call(null, stats);
- }
- catch (err) {
-
-
-
- this.emit('error', err);
- }
- }
- else {
-
-
- return true;
- }
- }
-
- filter (value, posixPath) {
- let options = this.options;
- if (options.filterGlob) {
-
-
- return options.filterGlob.test(posixPath);
- }
- else if (options.filterRegExp) {
-
-
- return options.filterRegExp.test(value.path || value);
- }
- else if (options.filterFn) {
- try {
-
- return options.filterFn.call(null, value);
- }
- catch (err) {
-
-
-
- this.emit('error', err);
- }
- }
- else {
-
- return true;
- }
- }
-
- emit (eventName, data) {
- let stream = this.stream;
- try {
- stream.emit(eventName, data);
- }
- catch (err) {
- if (eventName === 'error') {
-
-
- throw err;
- }
- else {
- stream.emit('error', err);
- }
- }
- }
- }
- module.exports = DirectoryReader;
|