'use strict' const MiniPass = require('minipass') const EE = require('events').EventEmitter const fs = require('fs') let writev = fs.writev /* istanbul ignore next */ if (!writev) { // This entire block can be removed if support for earlier than Node.js // 12.9.0 is not needed. const binding = process.binding('fs') const FSReqWrap = binding.FSReqWrap || binding.FSReqCallback writev = (fd, iovec, pos, cb) => { const done = (er, bw) => cb(er, bw, iovec) const req = new FSReqWrap() req.oncomplete = done binding.writeBuffers(fd, iovec, pos, req) } } const _autoClose = Symbol('_autoClose') const _close = Symbol('_close') const _ended = Symbol('_ended') const _fd = Symbol('_fd') const _finished = Symbol('_finished') const _flags = Symbol('_flags') const _flush = Symbol('_flush') const _handleChunk = Symbol('_handleChunk') const _makeBuf = Symbol('_makeBuf') const _mode = Symbol('_mode') const _needDrain = Symbol('_needDrain') const _onerror = Symbol('_onerror') const _onopen = Symbol('_onopen') const _onread = Symbol('_onread') const _onwrite = Symbol('_onwrite') const _open = Symbol('_open') const _path = Symbol('_path') const _pos = Symbol('_pos') const _queue = Symbol('_queue') const _read = Symbol('_read') const _readSize = Symbol('_readSize') const _reading = Symbol('_reading') const _remain = Symbol('_remain') const _size = Symbol('_size') const _write = Symbol('_write') const _writing = Symbol('_writing') const _defaultFlag = Symbol('_defaultFlag') class ReadStream extends MiniPass { constructor (path, opt) { opt = opt || {} super(opt) this.writable = false if (typeof path !== 'string') throw new TypeError('path must be a string') this[_fd] = typeof opt.fd === 'number' ? opt.fd : null this[_path] = path this[_readSize] = opt.readSize || 16*1024*1024 this[_reading] = false this[_size] = typeof opt.size === 'number' ? opt.size : Infinity this[_remain] = this[_size] this[_autoClose] = typeof opt.autoClose === 'boolean' ? opt.autoClose : true if (typeof this[_fd] === 'number') this[_read]() else this[_open]() } get fd () { return this[_fd] } get path () { return this[_path] } write () { throw new TypeError('this is a readable stream') } end () { throw new TypeError('this is a readable stream') } [_open] () { fs.open(this[_path], 'r', (er, fd) => this[_onopen](er, fd)) } [_onopen] (er, fd) { if (er) this[_onerror](er) else { this[_fd] = fd this.emit('open', fd) this[_read]() } } [_makeBuf] () { return Buffer.allocUnsafe(Math.min(this[_readSize], this[_remain])) } [_read] () { if (!this[_reading]) { this[_reading] = true const buf = this[_makeBuf]() /* istanbul ignore if */ if (buf.length === 0) return process.nextTick(() => this[_onread](null, 0, buf)) fs.read(this[_fd], buf, 0, buf.length, null, (er, br, buf) => this[_onread](er, br, buf)) } } [_onread] (er, br, buf) { this[_reading] = false if (er) this[_onerror](er) else if (this[_handleChunk](br, buf)) this[_read]() } [_close] () { if (this[_autoClose] && typeof this[_fd] === 'number') { fs.close(this[_fd], _ => this.emit('close')) this[_fd] = null } } [_onerror] (er) { this[_reading] = true this[_close]() this.emit('error', er) } [_handleChunk] (br, buf) { let ret = false // no effect if infinite this[_remain] -= br if (br > 0) ret = super.write(br < buf.length ? buf.slice(0, br) : buf) if (br === 0 || this[_remain] <= 0) { ret = false this[_close]() super.end() } return ret } emit (ev, data) { switch (ev) { case 'prefinish': case 'finish': break case 'drain': if (typeof this[_fd] === 'number') this[_read]() break default: return super.emit(ev, data) } } } class ReadStreamSync extends ReadStream { [_open] () { let threw = true try { this[_onopen](null, fs.openSync(this[_path], 'r')) threw = false } finally { if (threw) this[_close]() } } [_read] () { let threw = true try { if (!this[_reading]) { this[_reading] = true do { const buf = this[_makeBuf]() /* istanbul ignore next */ const br = buf.length === 0 ? 0 : fs.readSync(this[_fd], buf, 0, buf.length, null) if (!this[_handleChunk](br, buf)) break } while (true) this[_reading] = false } threw = false } finally { if (threw) this[_close]() } } [_close] () { if (this[_autoClose] && typeof this[_fd] === 'number') { try { fs.closeSync(this[_fd]) } catch (er) {} this[_fd] = null this.emit('close') } } } class WriteStream extends EE { constructor (path, opt) { opt = opt || {} super(opt) this.readable = false this[_writing] = false this[_ended] = false this[_needDrain] = false this[_queue] = [] this[_path] = path this[_fd] = typeof opt.fd === 'number' ? opt.fd : null this[_mode] = opt.mode === undefined ? 0o666 : opt.mode this[_pos] = typeof opt.start === 'number' ? opt.start : null this[_autoClose] = typeof opt.autoClose === 'boolean' ? opt.autoClose : true // truncating makes no sense when writing into the middle const defaultFlag = this[_pos] !== null ? 'r+' : 'w' this[_defaultFlag] = opt.flags === undefined this[_flags] = this[_defaultFlag] ? defaultFlag : opt.flags if (this[_fd] === null) this[_open]() } get fd () { return this[_fd] } get path () { return this[_path] } [_onerror] (er) { this[_close]() this[_writing] = true this.emit('error', er) } [_open] () { fs.open(this[_path], this[_flags], this[_mode], (er, fd) => this[_onopen](er, fd)) } [_onopen] (er, fd) { if (this[_defaultFlag] && this[_flags] === 'r+' && er && er.code === 'ENOENT') { this[_flags] = 'w' this[_open]() } else if (er) this[_onerror](er) else { this[_fd] = fd this.emit('open', fd) this[_flush]() } } end (buf, enc) { if (buf) this.write(buf, enc) this[_ended] = true // synthetic after-write logic, where drain/finish live if (!this[_writing] && !this[_queue].length && typeof this[_fd] === 'number') this[_onwrite](null, 0) } write (buf, enc) { if (typeof buf === 'string') buf = Buffer.from(buf, enc) if (this[_ended]) { this.emit('error', new Error('write() after end()')) return false } if (this[_fd] === null || this[_writing] || this[_queue].length) { this[_queue].push(buf) this[_needDrain] = true return false } this[_writing] = true this[_write](buf) return true } [_write] (buf) { fs.write(this[_fd], buf, 0, buf.length, this[_pos], (er, bw) => this[_onwrite](er, bw)) } [_onwrite] (er, bw) { if (er) this[_onerror](er) else { if (this[_pos] !== null) this[_pos] += bw if (this[_queue].length) this[_flush]() else { this[_writing] = false if (this[_ended] && !this[_finished]) { this[_finished] = true this[_close]() this.emit('finish') } else if (this[_needDrain]) { this[_needDrain] = false this.emit('drain') } } } } [_flush] () { if (this[_queue].length === 0) { if (this[_ended]) this[_onwrite](null, 0) } else if (this[_queue].length === 1) this[_write](this[_queue].pop()) else { const iovec = this[_queue] this[_queue] = [] writev(this[_fd], iovec, this[_pos], (er, bw) => this[_onwrite](er, bw)) } } [_close] () { if (this[_autoClose] && typeof this[_fd] === 'number') { fs.close(this[_fd], _ => this.emit('close')) this[_fd] = null } } } class WriteStreamSync extends WriteStream { [_open] () { let fd try { fd = fs.openSync(this[_path], this[_flags], this[_mode]) } catch (er) { if (this[_defaultFlag] && this[_flags] === 'r+' && er && er.code === 'ENOENT') { this[_flags] = 'w' return this[_open]() } else throw er } this[_onopen](null, fd) } [_close] () { if (this[_autoClose] && typeof this[_fd] === 'number') { try { fs.closeSync(this[_fd]) } catch (er) {} this[_fd] = null this.emit('close') } } [_write] (buf) { try { this[_onwrite](null, fs.writeSync(this[_fd], buf, 0, buf.length, this[_pos])) } catch (er) { this[_onwrite](er, 0) } } } exports.ReadStream = ReadStream exports.ReadStreamSync = ReadStreamSync exports.WriteStream = WriteStream exports.WriteStreamSync = WriteStreamSync