├── .gitignore ├── example.js ├── .travis.yml ├── binding.gyp ├── package.json ├── index.js ├── README.md ├── LICENSE ├── test.js └── binding.cc /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var createStream = require('./') 2 | 3 | var path = __dirname 4 | 5 | createStream(path) 6 | .on('data', data => console.log(data)) 7 | .on('end', () => console.log('(no more data)')) 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | addons: 6 | apt: 7 | sources: 8 | - ubuntu-toolchain-r-test 9 | packages: 10 | - g++-4.8 11 | 12 | node_js: 13 | - "9" 14 | - "8" 15 | - "6" 16 | 17 | before_install: 18 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export CXX=g++-4.8; fi 19 | 20 | os: 21 | - linux 22 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'fs_directory_stream', 5 | 'include_dirs' : [ 6 | " console.log(data)) 20 | .on('end', () => console.log('(no more data)')) 21 | ``` 22 | 23 | Uses [uv_fs_scandir_next](http://docs.libuv.org/en/v1.x/fs.html?highlight=scandir_next#c.uv_fs_scandir_next) behind the scenes to perform the 24 | streaming readdir. 25 | 26 | ## API 27 | 28 | #### `var readableStream = createDirectoryStream(path)` 29 | 30 | Make a new stream of directory entries. Will emit an error if `path` doesn't exist, etc. 31 | 32 | Every `data` event is the string name of a directory entry. 33 | 34 | ## License 35 | 36 | MIT 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Mathias Buus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var createDirectoryStream = require('./') 3 | var fs = require('fs') 4 | 5 | tape('streams a dir', function (t) { 6 | var expected = fs.readdirSync(__dirname) 7 | 8 | createDirectoryStream(__dirname) 9 | .on('data', function (data) { 10 | t.same(data, expected.shift(), 'same dir entry') 11 | }) 12 | .on('end', function () { 13 | t.same(expected.length, 0, 'no more entries') 14 | t.end() 15 | }) 16 | }) 17 | 18 | tape('ENOENT', function (t) { 19 | createDirectoryStream('.PHONY') 20 | .on('error', function (err) { 21 | t.same(err.code, 'ENOENT') 22 | t.same(err.path, '.PHONY') 23 | t.end() 24 | }) 25 | .resume() 26 | }) 27 | 28 | tape('ENOTDIR', function (t) { 29 | createDirectoryStream(__filename) 30 | .on('error', function (err) { 31 | t.same(err.code, 'ENOTDIR') 32 | t.same(err.path, __filename) 33 | t.end() 34 | }) 35 | .resume() 36 | }) 37 | 38 | tape('running GC after creating a stream works fine', function (t) { 39 | createDirectoryStream(__dirname) 40 | .on('data', function dummy () {}) 41 | .on('end', function () { 42 | setTimeout(function () { 43 | global.gc() 44 | global.gc(true) 45 | t.end() 46 | }, 10) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /binding.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define EXPORT_NUMBER(name, num) \ 5 | Nan::Set(target, Nan::New(name).ToLocalChecked(), Nan::New(num)); 6 | 7 | #define EXPORT_FUNCTION(name) \ 8 | Nan::Set(target, Nan::New(#name).ToLocalChecked(), Nan::GetFunction(Nan::New(name)).ToLocalChecked()); 9 | 10 | #define BUFFER_CAST(type, name, info) \ 11 | type *name = (type *) node::Buffer::Data(info->ToObject()); 12 | 13 | using namespace v8; 14 | 15 | struct fs_directory_stream_t { 16 | uv_fs_t req; 17 | Nan::Callback callback; 18 | uv_dirent_t dirent; 19 | 20 | ~fs_directory_stream_t() { 21 | uv_fs_req_cleanup(&req); 22 | } 23 | }; 24 | 25 | void fs_callback (uv_fs_s* req) { 26 | Nan::HandleScope scope; 27 | 28 | fs_directory_stream_t *self = (fs_directory_stream_t *) req; 29 | 30 | if (req->result < 0) { 31 | Local args[] = { 32 | Nan::ErrnoException(-req->result, "uv_fs_scandir", NULL, req->path) 33 | }; 34 | self->callback.Call(1, args); 35 | } else { 36 | self->callback.Call(0, NULL); 37 | } 38 | self->callback.Reset(); 39 | } 40 | 41 | // int uv_fs_scandir(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags, uv_fs_cb cb) 42 | NAN_METHOD(uv_fs_scandir) { 43 | BUFFER_CAST(fs_directory_stream_t, self, info[0]) 44 | BUFFER_CAST(const char, path, info[1]) 45 | 46 | Local cb = info[2].As(); 47 | self->callback.Reset(cb); 48 | 49 | int err = uv_fs_scandir(uv_default_loop(), &self->req, path, 0, &fs_callback); 50 | if (err < 0) { 51 | Nan::ThrowError("uv_fs_scandir failed"); 52 | return; 53 | } 54 | } 55 | 56 | // int uv_fs_scandir_next(uv_fs_t* req, uv_dirent_t* ent) 57 | NAN_METHOD(uv_fs_scandir_next) { 58 | BUFFER_CAST(fs_directory_stream_t, self, info[0]) 59 | 60 | uv_dirent_t dirent; 61 | Local names = Nan::New(); 62 | 63 | for (int i = 0; i < 16; i++) { 64 | int err = uv_fs_scandir_next(&self->req, &dirent); 65 | if (err == UV_EOF) break; 66 | if (err < 0) { 67 | Nan::ThrowError("uv_fs_scandir_next failed"); 68 | return; 69 | } 70 | Nan::Set(names, i, Nan::New(dirent.name).ToLocalChecked()); 71 | } 72 | 73 | info.GetReturnValue().Set(names); 74 | } 75 | 76 | void free_fs_directory_stream_t(char *data, void *hint) { 77 | delete (fs_directory_stream_t *) data; 78 | } 79 | 80 | NAN_METHOD(make_uv_fs_scandir_buffer) { 81 | Local buf = Nan::NewBuffer((char *)new fs_directory_stream_t, 82 | sizeof(fs_directory_stream_t), 83 | free_fs_directory_stream_t, 84 | NULL).ToLocalChecked(); 85 | info.GetReturnValue().Set(buf); 86 | } 87 | 88 | NAN_MODULE_INIT(InitAll) { 89 | EXPORT_FUNCTION(uv_fs_scandir) 90 | EXPORT_FUNCTION(uv_fs_scandir_next) 91 | EXPORT_FUNCTION(make_uv_fs_scandir_buffer) 92 | } 93 | 94 | NODE_MODULE(fs_directory_stream, InitAll) 95 | --------------------------------------------------------------------------------