Skip to content

Commit

Permalink
Implement new writable stream
Browse files Browse the repository at this point in the history
PR-URL: #9
  • Loading branch information
ivan-tymoshenko committed Jan 11, 2019
1 parent c17ef66 commit 88a3df9
Show file tree
Hide file tree
Showing 9 changed files with 800 additions and 540 deletions.
68 changes: 68 additions & 0 deletions lib/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use strict';

class ERR_INVALID_ARG_TYPE extends TypeError {
constructor(name, expected, actual) {
super(`The "${name}" argument must be one of type ${expected}. ` +
`Received type ${typeof(actual)}`);
this.code = 'ERR_INVALID_ARG_TYPE';
this.name += ` [${this.code}]`;
}
}

class ERR_STREAM_NULL_VALUES extends TypeError {
constructor() {
super('May not write null values to stream');
this.code = 'ERR_STREAM_NULL_VALUES';
this.name += ` [${this.code}]`;
}
}

class ERR_METHODS_NOT_IMPLEMENTED extends Error {
constructor() {
super('_write() and _vritev() methods are not implemented');
this.code = 'ERR_METHOD_NOT_IMPLEMENTED';
this.name += ` [${this.code}]`;
}
}

class ERR_STREAM_DESTROYED extends Error {
constructor() {
super('Cannot call write after a stream was destroyed');
this.code = 'ERR_STREAM_DESTROYED';
this.name += ` [${this.code}]`;
}
}

class ERR_STREAM_WRITE_AFTER_END extends Error {
constructor() {
super('write after end');
this.code = 'ERR_STREAM_WRITE_AFTER_END';
this.name += ` [${this.code}]`;
}
}

class ERR_UNKNOWN_ENCODING extends TypeError {
constructor(encoding) {
super(`Unknown encoding ${encoding}`);
this.code = 'ERR_UNKNOWN_ENCODING';
this.name += ` [${this.code}]`;
}
}

class ERR_OUT_OF_RANGE extends RangeError {
constructor(name, range, actual) {
super(`The value of ${name} is out of range. It must be ${range}. ` +
`Received ${actual}`);
this.code = 'ERR_OUT_OF_RANGE';
this.name += ` [${this.code}]`;
}
}
module.exports = {
ERR_OUT_OF_RANGE,
ERR_INVALID_ARG_TYPE,
ERR_STREAM_DESTROYED,
ERR_UNKNOWN_ENCODING,
ERR_STREAM_NULL_VALUES,
ERR_METHODS_NOT_IMPLEMENTED,
ERR_STREAM_WRITE_AFTER_END,
};
146 changes: 146 additions & 0 deletions lib/fs-writable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright Node.js contributors. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

'use strict';

const fs = require('fs');

const { Writable } = require('./writable');

const {
ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE
} = require('./errors');

const openSymbol = Symbol('open');
const errorSymbol = Symbol('error');
const closeStreamSymbol = Symbol('closeStream');

class WritableFileStream extends Writable {
constructor(path, {
fd = null,
start,
flags = 'w',
bufferSize,
mode = 0o666,
encoding = null,
autoClose = true,
} = {}) {
super({ bufferSize });

this.fd = fd;
this.path = path;

this.closed = false;
this.bytesWritten = 0;

this.autoClose = autoClose;

if (typeof path !== 'string') {
throw new ERR_INVALID_ARG_TYPE('path', 'string', path);
}

if (start !== undefined) {
if (typeof start !== 'number') {
throw new ERR_INVALID_ARG_TYPE('start', 'number', start);
}
if (start < 0) {
throw new ERR_OUT_OF_RANGE('start', '>= 0', `{start: ${start}}`);
}

this.pos = start;
}

if (encoding) this.setDefaultEncoding(encoding);

if (this.fd === null) this[openSymbol](flags, mode);
}

[errorSymbol](error, cb) {
if (cb) cb(error);
if (this.autoClose) {
this.destroy(error);
} else {
this.emit('error', error);
this.emit('close');
}
}

[openSymbol](flags, mode) {
fs.open(this.path, flags, mode, (error, fd) => {
if (error) {
this[errorSymbol](error);
return;
}

this.fd = fd;
this.emit('open', fd);
});
}

[closeStreamSymbol](cb, err) {
fs.close(this.fd, (er) => {
er = er || err;
cb(er);
this.closed = true;
this.emit('close');
});
}

_write(data, cb) {
if (!Buffer.isBuffer(data)) {
const err = new ERR_INVALID_ARG_TYPE('data', 'Buffer', data);
this.emit('error', err);
return;
}

if (this.fd === null) {
this.once('open', () => this._write(data, cb));
return;
}

fs.write(this.fd, data, 0, data.length, this.pos, (error, bytes) => {
if (error) {
this[errorSymbol](error, cb);
return;
}
this.bytesWritten += bytes;
cb();
});

if (this.pos !== undefined) this.pos += data.length;
}

_destroy(err, cb) {
if (this.fd === null) {
this.once('open', () => this[closeStreamSymbol](cb, err));
return;
}
this[closeStreamSymbol](cb, err);
this.fd = null;
}

_final(cb) {
if (this.autoClose) this.destroy();
cb();
}
}

module.exports = { WritableFileStream };
Loading

0 comments on commit 88a3df9

Please sign in to comment.