fs-source
Version:
A file system source for the BOB streaming protocol.
125 lines (103 loc) • 3.41 kB
JavaScript
'use strict'
const fs = require('fs')
const { Status } = require('bob-streams')
class FileSource {
constructor (path, options) {
if (options !== undefined && typeof options !== 'object') {
throw new TypeError(`options MUST be an Object, found ${typeof options}`)
}
options = copyObject(options)
if (path !== undefined && typeof path !== 'string') {
throw new TypeError(`path MUST be a String, found ${typeof path}`)
}
this.path = path
this.fd = options.fd === undefined ? null : options.fd
this.flags = options.flags === undefined ? 'r' : options.flags
this.mode = options.mode === undefined ? 0o666 : options.mode
this.pos = 0
if (options.start !== undefined) {
if (typeof options.start !== 'number') {
throw new TypeError(`options.start MUST be a Number, found ${typeof options.start}`)
}
if (options.end === undefined) {
options.end = Infinity
} else if (typeof options.end !== 'number') {
throw new TypeError(`options.end MUST be a Number or undefined, found ${typeof options.end}`)
}
if (options.start > options.end) {
const errVal = `{start: ${options.start}, end: ${options.end}}`
throw new RangeError(`start must be greater than end, found ${errVal}`)
}
this.pos = options.start !== undefined ? options.start : 0
}
// XXX(Fishrock123):
// streams3 opens the FD here, but I think error handling might be better if
// it is opened during pull flow.
//
// That has some disadvantages, namely around telling when to actually open
// the file, but also the delayment of error until after stream construction.
//
// if (typeof this.fd !== 'number')
// this.open()
}
bindSink (sink) {
this.sink = sink
}
pull (error, buffer) {
if (error) {
if (typeof this.fd === 'number') {
fs.close(this.fd, (closeError) => {
this.fd = null
this.sink.next(Status.error, closeError || error)
})
} else {
return this.sink.next(Status.error, error)
}
}
if (typeof this.fd !== 'number') {
fs.open(this.path, this.flags, this.mode, (error, fd) => {
if (error) {
return this.sink.next(Status.error, error)
}
this.fd = fd
this.readFromFile(buffer)
})
} else {
this.readFromFile(buffer)
}
}
readFromFile (buffer) {
if (typeof this.fd !== 'number') {
return this.pull(null, buffer)
}
fs.read(this.fd, buffer, 0, buffer.length, this.pos, (error, bytesRead) => {
if (error) {
fs.close(this.fd, (closeError) => {
this.fd = null
this.sink.next(Status.error, closeError || error)
})
} else {
if (bytesRead > 0) {
this.pos += bytesRead
this.sink.next(Status.continue, null, buffer, bytesRead)
} else {
fs.close(this.fd, (closeError) => {
this.fd = null
if (closeError) {
this.sink.next(Status.error, closeError)
} else {
this.sink.next(Status.end, null, buffer, 0)
}
})
}
}
})
}
}
module.exports = FileSource
/* ## Helpers ## */
function copyObject (source) {
var target = {}
for (var key in source) { target[key] = source[key] }
return target
}