| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 3753f47 commit 7f6a0ed
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1674,6 +1674,10 @@ changes: | |||
| 1674 | 1674 | - version: v2.3.0 | |
| 1675 | 1675 | pr-url: https://github.com/nodejs/node/pull/1845 | |
| 1676 | 1676 | description: The passed `options` object can be a string now. | |
| 1677 | + - version: REPLACEME | ||
| 1678 | + pr-url: https://github.com/nodejs/node/pull/REPLACEME | ||
| 1679 | + description: The `fs` options allow overriding the used `fs` | ||
| 1680 | + implementation. | ||
| 1677 | 1681 | --> | |
| 1678 | 1682 | ||
| 1679 | 1683 | * `path` {string|Buffer|URL} | |
@@ -1688,7 +1692,8 @@ changes: | |||
| 1688 | 1692 | * `start` {integer} | |
| 1689 | 1693 | * `end` {integer} **Default:** `Infinity` | |
| 1690 | 1694 | * `highWaterMark` {integer} **Default:** `64 * 1024` | |
| 1691 | - * Returns: {fs.ReadStream} | ||
| 1695 | + * `fs` {Object|null} **Default:** `null` | ||
| 1696 | + * Returns: {fs.ReadStream} See [Readable Stream][]. | ||
| 1692 | 1697 | ||
| 1693 | 1698 | Unlike the 16 kb default `highWaterMark` for a readable stream, the stream | |
| 1694 | 1699 | returned by this method has a default `highWaterMark` of 64 kb. | |
@@ -1715,6 +1720,10 @@ By default, the stream will not emit a `'close'` event after it has been | |||
| 1715 | 1720 | destroyed. This is the opposite of the default for other `Readable` streams. | |
| 1716 | 1721 | Set the `emitClose` option to `true` to change this behavior. | |
| 1717 | 1722 | ||
| 1723 | + By providing the `fs` option it is possible to override the corresponding `fs` | ||
| 1724 | + implementations for `open`, `read` and `close`. When providing the `fs` option, | ||
| 1725 | + you must override `open`, `close` and `read`. | ||
| 1726 | + | ||
| 1718 | 1727 | ```js | |
| 1719 | 1728 | const fs = require('fs'); | |
| 1720 | 1729 | // Create a stream from some character device. | |
@@ -1768,6 +1777,10 @@ changes: | |||
| 1768 | 1777 | - version: v2.3.0 | |
| 1769 | 1778 | pr-url: https://github.com/nodejs/node/pull/1845 | |
| 1770 | 1779 | description: The passed `options` object can be a string now. | |
| 1780 | + - version: REPLACEME | ||
| 1781 | + pr-url: https://github.com/nodejs/node/pull/REPLACEME | ||
| 1782 | + description: The `fs` options allow overriding the used `fs` | ||
| 1783 | + implementation. | ||
| 1771 | 1784 | --> | |
| 1772 | 1785 | ||
| 1773 | 1786 | * `path` {string|Buffer|URL} | |
@@ -1780,7 +1793,8 @@ changes: | |||
| 1780 | 1793 | * `autoClose` {boolean} **Default:** `true` | |
| 1781 | 1794 | * `emitClose` {boolean} **Default:** `false` | |
| 1782 | 1795 | * `start` {integer} | |
| 1783 | - * Returns: {fs.WriteStream} | ||
| 1796 | + * `fs` {Object|null} **Default:** `null` | ||
| 1797 | + * Returns: {fs.WriteStream} See [Writable Stream][]. | ||
| 1784 | 1798 | ||
| 1785 | 1799 | `options` may also include a `start` option to allow writing data at | |
| 1786 | 1800 | some position past the beginning of the file, allowed values are in the | |
@@ -1799,6 +1813,12 @@ By default, the stream will not emit a `'close'` event after it has been | |||
| 1799 | 1813 | destroyed. This is the opposite of the default for other `Writable` streams. | |
| 1800 | 1814 | Set the `emitClose` option to `true` to change this behavior. | |
| 1801 | 1815 | ||
| 1816 | + By providing the `fs` option it is possible to override the corresponding `fs` | ||
| 1817 | + implementations for `open`, `write`, `writev` and `close`. Overriding `write()` | ||
| 1818 | + without `writev()` can reduce performance as some optimizations (`_writev()`) | ||
| 1819 | + will be disabled. When providing the `fs` option, you must override `open`, | ||
| 1820 | + `close` and at least one of `write` and `writev`. | ||
| 1821 | + | ||
| 1802 | 1822 | Like [`ReadStream`][], if `fd` is specified, [`WriteStream`][] will ignore the | |
| 1803 | 1823 | `path` argument and will use the specified file descriptor. This means that no | |
| 1804 | 1824 | `'open'` event will be emitted. `fd` should be blocking; non-blocking `fd`s | |
@@ -5520,6 +5540,7 @@ the file contents. | |||
| 5520 | 5540 | [`Number.MAX_SAFE_INTEGER`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER | |
| 5521 | 5541 | [`ReadDirectoryChangesW`]: https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-readdirectorychangesw | |
| 5522 | 5542 | [`ReadStream`]: #fs_class_fs_readstream | |
| 5543 | + [Readable Stream]: #stream_class_stream_readable | ||
| 5523 | 5544 | [`URL`]: url.html#url_the_whatwg_url_api | |
| 5524 | 5545 | [`UV_THREADPOOL_SIZE`]: cli.html#cli_uv_threadpool_size_size | |
| 5525 | 5546 | [`WriteStream`]: #fs_class_fs_writestream | |
@@ -5577,3 +5598,4 @@ the file contents. | |||
| 5577 | 5598 | [chcp]: https://ss64.com/nt/chcp.html | |
| 5578 | 5599 | [inode]: https://en.wikipedia.org/wiki/Inode | |
| 5579 | 5600 | [support of file system `flags`]: #fs_file_system_flags | |
| 5601 | + [Writable Stream]: #stream_class_stream_writable | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -11,6 +11,7 @@ const { | |||
| 11 | 11 | } = primordials; | |
| 12 | 12 | ||
| 13 | 13 | const { | |
| 14 | + ERR_INVALID_ARG_TYPE, | ||
| 14 | 15 | ERR_OUT_OF_RANGE, | |
| 15 | 16 | ERR_STREAM_DESTROYED | |
| 16 | 17 | } = require('internal/errors').codes; | |
@@ -28,6 +29,7 @@ const kIoDone = Symbol('kIoDone'); | |||
| 28 | 29 | const kIsPerformingIO = Symbol('kIsPerformingIO'); | |
| 29 | 30 | ||
| 30 | 31 | const kMinPoolSpace = 128; | |
| 32 | + const kFs = Symbol('kFs'); | ||
| 31 | 33 | ||
| 32 | 34 | let pool; | |
| 33 | 35 | // It can happen that we expect to read a large chunk of data, and reserve | |
@@ -76,6 +78,23 @@ function ReadStream(path, options) { | |||
| 76 | 78 | options.emitClose = false; | |
| 77 | 79 | } | |
| 78 | 80 | ||
| 81 | + this[kFs] = options.fs || fs; | ||
| 82 | + | ||
| 83 | + if (typeof this[kFs].open !== 'function') { | ||
| 84 | + throw new ERR_INVALID_ARG_TYPE('options.fs.open', 'function', | ||
| 85 | + this[kFs].open); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + if (typeof this[kFs].read !== 'function') { | ||
| 89 | + throw new ERR_INVALID_ARG_TYPE('options.fs.read', 'function', | ||
| 90 | + this[kFs].read); | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + if (typeof this[kFs].close !== 'function') { | ||
| 94 | + throw new ERR_INVALID_ARG_TYPE('options.fs.close', 'function', | ||
| 95 | + this[kFs].close); | ||
| 96 | + } | ||
| 97 | + | ||
| 79 | 98 | Readable.call(this, options); | |
| 80 | 99 | ||
| 81 | 100 | // Path will be ignored when fd is specified, so it can be falsy | |
@@ -136,7 +155,7 @@ function _openReadFs(stream) { | |||
| 136 | 155 | return; | |
| 137 | 156 | } | |
| 138 | 157 | ||
| 139 | - fs.open(stream.path, stream.flags, stream.mode, (er, fd) => { | ||
| 158 | + stream[kFs].open(stream.path, stream.flags, stream.mode, (er, fd) => { | ||
| 140 | 159 | if (er) { | |
| 141 | 160 | if (stream.autoClose) { | |
| 142 | 161 | stream.destroy(); | |
@@ -186,42 +205,43 @@ ReadStream.prototype._read = function(n) { | |||
| 186 | 205 | ||
| 187 | 206 | // the actual read. | |
| 188 | 207 | this[kIsPerformingIO] = true; | |
| 189 | - fs.read(this.fd, pool, pool.used, toRead, this.pos, (er, bytesRead) => { | ||
| 190 | - this[kIsPerformingIO] = false; | ||
| 191 | - // Tell ._destroy() that it's safe to close the fd now. | ||
| 192 | - if (this.destroyed) return this.emit(kIoDone, er); | ||
| 193 | - | ||
| 194 | - if (er) { | ||
| 195 | - if (this.autoClose) { | ||
| 196 | - this.destroy(); | ||
| 197 | - } | ||
| 198 | - this.emit('error', er); | ||
| 199 | - } else { | ||
| 200 | - let b = null; | ||
| 201 | - // Now that we know how much data we have actually read, re-wind the | ||
| 202 | - // 'used' field if we can, and otherwise allow the remainder of our | ||
| 203 | - // reservation to be used as a new pool later. | ||
| 204 | - if (start + toRead === thisPool.used && thisPool === pool) { | ||
| 205 | - const newUsed = thisPool.used + bytesRead - toRead; | ||
| 206 | - thisPool.used = roundUpToMultipleOf8(newUsed); | ||
| 208 | + this[kFs].read( | ||
| 209 | + this.fd, pool, pool.used, toRead, this.pos, (er, bytesRead) => { | ||
| 210 | + this[kIsPerformingIO] = false; | ||
| 211 | + // Tell ._destroy() that it's safe to close the fd now. | ||
| 212 | + if (this.destroyed) return this.emit(kIoDone, er); | ||
| 213 | + | ||
| 214 | + if (er) { | ||
| 215 | + if (this.autoClose) { | ||
| 216 | + this.destroy(); | ||
| 217 | + } | ||
| 218 | + this.emit('error', er); | ||
| 207 | 219 | } else { | |
| 208 | - // Round down to the next lowest multiple of 8 to ensure the new pool | ||
| 209 | - // fragment start and end positions are aligned to an 8 byte boundary. | ||
| 210 | - const alignedEnd = (start + toRead) & ~7; | ||
| 211 | - const alignedStart = roundUpToMultipleOf8(start + bytesRead); | ||
| 212 | - if (alignedEnd - alignedStart >= kMinPoolSpace) { | ||
| 213 | - poolFragments.push(thisPool.slice(alignedStart, alignedEnd)); | ||
| 220 | + let b = null; | ||
| 221 | + // Now that we know how much data we have actually read, re-wind the | ||
| 222 | + // 'used' field if we can, and otherwise allow the remainder of our | ||
| 223 | + // reservation to be used as a new pool later. | ||
| 224 | + if (start + toRead === thisPool.used && thisPool === pool) { | ||
| 225 | + const newUsed = thisPool.used + bytesRead - toRead; | ||
| 226 | + thisPool.used = roundUpToMultipleOf8(newUsed); | ||
| 227 | + } else { | ||
| 228 | + // Round down to the next lowest multiple of 8 to ensure the new pool | ||
| 229 | + // fragment start and end positions are aligned to an 8 byte boundary. | ||
| 230 | + const alignedEnd = (start + toRead) & ~7; | ||
| 231 | + const alignedStart = roundUpToMultipleOf8(start + bytesRead); | ||
| 232 | + if (alignedEnd - alignedStart >= kMinPoolSpace) { | ||
| 233 | + poolFragments.push(thisPool.slice(alignedStart, alignedEnd)); | ||
| 234 | + } | ||
| 214 | 235 | } | |
| 215 | - } | ||
| 216 | 236 | ||
| 217 | - if (bytesRead > 0) { | ||
| 218 | - this.bytesRead += bytesRead; | ||
| 219 | - b = thisPool.slice(start, start + bytesRead); | ||
| 220 | - } | ||
| 237 | + if (bytesRead > 0) { | ||
| 238 | + this.bytesRead += bytesRead; | ||
| 239 | + b = thisPool.slice(start, start + bytesRead); | ||
| 240 | + } | ||
| 221 | 241 | ||
| 222 | - this.push(b); | ||
| 223 | - } | ||
| 224 | - }); | ||
| 242 | + this.push(b); | ||
| 243 | + } | ||
| 244 | + }); | ||
| 225 | 245 | ||
| 226 | 246 | // Move the pool positions, and internal position for reading. | |
| 227 | 247 | if (this.pos !== undefined) | |
@@ -245,7 +265,7 @@ ReadStream.prototype._destroy = function(err, cb) { | |||
| 245 | 265 | }; | |
| 246 | 266 | ||
| 247 | 267 | function closeFsStream(stream, cb, err) { | |
| 248 | - fs.close(stream.fd, (er) => { | ||
| 268 | + stream[kFs].close(stream.fd, (er) => { | ||
| 249 | 269 | er = er || err; | |
| 250 | 270 | cb(er); | |
| 251 | 271 | stream.closed = true; | |
@@ -279,6 +299,40 @@ function WriteStream(path, options) { | |||
| 279 | 299 | options.emitClose = false; | |
| 280 | 300 | } | |
| 281 | 301 | ||
| 302 | + this[kFs] = options.fs || fs; | ||
| 303 | + if (typeof this[kFs].open !== 'function') { | ||
| 304 | + throw new ERR_INVALID_ARG_TYPE('options.fs.open', 'function', | ||
| 305 | + this[kFs].open); | ||
| 306 | + } | ||
| 307 | + | ||
| 308 | + if (!this[kFs].write && !this[kFs].writev) { | ||
| 309 | + throw new ERR_INVALID_ARG_TYPE('options.fs.write', 'function', | ||
| 310 | + this[kFs].write); | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + if (this[kFs].write && typeof this[kFs].write !== 'function') { | ||
| 314 | + throw new ERR_INVALID_ARG_TYPE('options.fs.write', 'function', | ||
| 315 | + this[kFs].write); | ||
| 316 | + } | ||
| 317 | + | ||
| 318 | + if (this[kFs].writev && typeof this[kFs].writev !== 'function') { | ||
| 319 | + throw new ERR_INVALID_ARG_TYPE('options.fs.writev', 'function', | ||
| 320 | + this[kFs].writev); | ||
| 321 | + } | ||
| 322 | + | ||
| 323 | + if (typeof this[kFs].close !== 'function') { | ||
| 324 | + throw new ERR_INVALID_ARG_TYPE('options.fs.close', 'function', | ||
| 325 | + this[kFs].close); | ||
| 326 | + } | ||
| 327 | + | ||
| 328 | + // It's enough to override either, in which case only one will be used. | ||
| 329 | + if (!this[kFs].write) { | ||
| 330 | + this._write = null; | ||
| 331 | + } | ||
| 332 | + if (!this[kFs].writev) { | ||
| 333 | + this._writev = null; | ||
| 334 | + } | ||
| 335 | + | ||
| 282 | 336 | Writable.call(this, options); | |
| 283 | 337 | ||
| 284 | 338 | // Path will be ignored when fd is specified, so it can be falsy | |
@@ -335,7 +389,7 @@ function _openWriteFs(stream) { | |||
| 335 | 389 | return; | |
| 336 | 390 | } | |
| 337 | 391 | ||
| 338 | - fs.open(stream.path, stream.flags, stream.mode, (er, fd) => { | ||
| 392 | + stream[kFs].open(stream.path, stream.flags, stream.mode, (er, fd) => { | ||
| 339 | 393 | if (er) { | |
| 340 | 394 | if (stream.autoClose) { | |
| 341 | 395 | stream.destroy(); | |
@@ -361,7 +415,7 @@ WriteStream.prototype._write = function(data, encoding, cb) { | |||
| 361 | 415 | if (this.destroyed) return cb(new ERR_STREAM_DESTROYED('write')); | |
| 362 | 416 | ||
| 363 | 417 | this[kIsPerformingIO] = true; | |
| 364 | - fs.write(this.fd, data, 0, data.length, this.pos, (er, bytes) => { | ||
| 418 | + this[kFs].write(this.fd, data, 0, data.length, this.pos, (er, bytes) => { | ||
| 365 | 419 | this[kIsPerformingIO] = false; | |
| 366 | 420 | // Tell ._destroy() that it's safe to close the fd now. | |
| 367 | 421 | if (this.destroyed) { | |
@@ -405,7 +459,7 @@ WriteStream.prototype._writev = function(data, cb) { | |||
| 405 | 459 | } | |
| 406 | 460 | ||
| 407 | 461 | this[kIsPerformingIO] = true; | |
| 408 | - fs.writev(this.fd, chunks, this.pos, (er, bytes) => { | ||
| 462 | + this[kFs].writev(this.fd, chunks, this.pos, (er, bytes) => { | ||
| 409 | 463 | this[kIsPerformingIO] = false; | |
| 410 | 464 | // Tell ._destroy() that it's safe to close the fd now. | |
| 411 | 465 | if (this.destroyed) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -31,11 +31,11 @@ const fixtures = require('../common/fixtures'); | |||
| 31 | 31 | const fn = fixtures.path('elipses.txt'); | |
| 32 | 32 | const rangeFile = fixtures.path('x.txt'); | |
| 33 | 33 | ||
| 34 | - { | ||
| 34 | + function test1(options) { | ||
| 35 | 35 | let paused = false; | |
| 36 | 36 | let bytesRead = 0; | |
| 37 | 37 | ||
| 38 | - const file = fs.createReadStream(fn); | ||
| 38 | + const file = fs.createReadStream(fn, options); | ||
| 39 | 39 | const fileSize = fs.statSync(fn).size; | |
| 40 | 40 | ||
| 41 | 41 | assert.strictEqual(file.bytesRead, 0); | |
@@ -88,6 +88,15 @@ const rangeFile = fixtures.path('x.txt'); | |||
| 88 | 88 | }); | |
| 89 | 89 | } | |
| 90 | 90 | ||
| 91 | + test1({}); | ||
| 92 | + test1({ | ||
| 93 | + fs: { | ||
| 94 | + open: common.mustCall(fs.open), | ||
| 95 | + read: common.mustCallAtLeast(fs.read, 1), | ||
| 96 | + close: common.mustCall(fs.close), | ||
| 97 | + } | ||
| 98 | + }); | ||
| 99 | + | ||
| 91 | 100 | { | |
| 92 | 101 | const file = fs.createReadStream(fn, { encoding: 'utf8' }); | |
| 93 | 102 | file.length = 0; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,38 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const path = require('path'); | ||
| 4 | + const fs = require('fs'); | ||
| 5 | + | ||
| 6 | + const tmpdir = require('../common/tmpdir'); | ||
| 7 | + tmpdir.refresh(); | ||
| 8 | + | ||
| 9 | + { | ||
| 10 | + const file = path.join(tmpdir.path, 'write-end-test0.txt'); | ||
| 11 | + const stream = fs.createWriteStream(file, { | ||
| 12 | + fs: { | ||
| 13 | + open: common.mustCall(fs.open), | ||
| 14 | + write: common.mustCallAtLeast(fs.write, 1), | ||
| 15 | + close: common.mustCall(fs.close), | ||
| 16 | + } | ||
| 17 | + }); | ||
| 18 | + stream.end('asd'); | ||
| 19 | + stream.on('close', common.mustCall()); | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + { | ||
| 24 | + const file = path.join(tmpdir.path, 'write-end-test1.txt'); | ||
| 25 | + const stream = fs.createWriteStream(file, { | ||
| 26 | + fs: { | ||
| 27 | + open: common.mustCall(fs.open), | ||
| 28 | + write: fs.write, | ||
| 29 | + writev: common.mustCallAtLeast(fs.writev, 1), | ||
| 30 | + close: common.mustCall(fs.close), | ||
| 31 | + } | ||
| 32 | + }); | ||
| 33 | + stream.write('asd'); | ||
| 34 | + stream.write('asd'); | ||
| 35 | + stream.write('asd'); | ||
| 36 | + stream.end(); | ||
| 37 | + stream.on('close', common.mustCall()); | ||
| 38 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments