Skip to content

Commit 0c52338

Browse files
committed
[Filesystem] workaround for nodejs/node#35862
1 parent c9358b5 commit 0c52338

File tree

4 files changed

+134
-17
lines changed

4 files changed

+134
-17
lines changed
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Readable } from 'stream';
2+
3+
/**
4+
* @memberOf Jymfony.Component.Filesystem.StreamWrapper.File
5+
* @internal Use this instead of fs.createReadableStream led to undefined behavior
6+
* (https://github.com/nodejs/node/issues/35862).
7+
*/
8+
export default class ReadableStream extends Readable {
9+
/**
10+
* Constructor
11+
*
12+
* @param {Jymfony.Component.Filesystem.StreamWrapper.File.Resource} resource
13+
*/
14+
constructor(resource) {
15+
super();
16+
17+
/**
18+
* @type {Jymfony.Component.Filesystem.StreamWrapper.File.Resource}
19+
*
20+
* @private
21+
*/
22+
this._resource = resource;
23+
}
24+
25+
/**
26+
* @param {number} size
27+
*
28+
* @returns {Promise<void>}
29+
*/
30+
async _read(size) {
31+
const handle = this._resource.handle;
32+
let result;
33+
34+
try {
35+
do {
36+
const { buffer, bytesRead } = await handle.read(Buffer.alloc(size), 0, size, this._resource.position);
37+
38+
if (0 === bytesRead) {
39+
this.push(null);
40+
return;
41+
}
42+
43+
this._resource.advance(bytesRead);
44+
if (bytesRead !== size) {
45+
result = this.push(buffer.slice(0, bytesRead));
46+
} else {
47+
result = this.push(buffer);
48+
}
49+
} while (result);
50+
} catch (err) {
51+
this.destroy(err);
52+
}
53+
}
54+
}
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Writable } from 'stream';
2+
3+
/**
4+
* @memberOf Jymfony.Component.Filesystem.StreamWrapper.File
5+
*/
6+
export default class WritableStream extends Writable {
7+
/**
8+
* Constructor
9+
*
10+
* @param {Jymfony.Component.Filesystem.StreamWrapper.File.Resource} resource
11+
*/
12+
constructor(resource) {
13+
super();
14+
15+
/**
16+
* @type {Jymfony.Component.Filesystem.StreamWrapper.File.Resource}
17+
*
18+
* @private
19+
*/
20+
this._resource = resource;
21+
}
22+
23+
/**
24+
* @param {Buffer|string} chunk
25+
* @param {string} encoding
26+
* @param {Function} callback
27+
*
28+
* @returns {Promise<void>}
29+
*
30+
* @private
31+
*/
32+
async _write(chunk, encoding, callback) {
33+
const handle = this._resource.handle;
34+
35+
try {
36+
if (! isBuffer(chunk)) {
37+
chunk = Buffer.from(chunk, encoding);
38+
}
39+
40+
const { bytesWritten } = await handle.write(chunk, 0, chunk.length, this._resource.position);
41+
this._resource.advance(bytesWritten);
42+
43+
callback();
44+
} catch (err) {
45+
callback(err);
46+
}
47+
}
48+
}

src/StreamWrapper/FileStreamWrapper.js

+5-10
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ import {
1313
symlink,
1414
unlink
1515
} from 'fs/promises';
16-
import { createReadStream, createWriteStream } from 'fs';
1716
import { dirname, resolve as pathResolve } from 'path';
1817
import { parse as urlParse } from 'url';
1918

20-
const File = Jymfony.Component.Filesystem.File;
2119
const AbstractStreamWrapper = Jymfony.Component.Filesystem.StreamWrapper.AbstractStreamWrapper;
20+
const File = Jymfony.Component.Filesystem.File;
21+
const ReadableStream = Jymfony.Component.Filesystem.StreamWrapper.File.ReadableStream;
2222
const Resource = Jymfony.Component.Filesystem.StreamWrapper.File.Resource;
2323
const StreamWrapperInterface = Jymfony.Component.Filesystem.StreamWrapper.StreamWrapperInterface;
24+
const WritableStream = Jymfony.Component.Filesystem.StreamWrapper.File.WritableStream;
2425

2526
const Storage = function () {};
2627
Storage.prototype = {};
@@ -201,20 +202,14 @@ export default class FileStreamWrapper extends AbstractStreamWrapper {
201202
* @inheritdoc
202203
*/
203204
createReadableStream(resource) {
204-
return createReadStream(null, {
205-
fd: resource.handle.fd,
206-
autoClose: false,
207-
});
205+
return new ReadableStream(resource);
208206
}
209207

210208
/**
211209
* @inheritdoc
212210
*/
213211
createWritableStream(resource) {
214-
return createWriteStream(null, {
215-
fd: resource.handle.fd,
216-
autoClose: false,
217-
});
212+
return new WritableStream(resource);
218213
}
219214

220215
/**

test/OpenFileTest.js

+27-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { Readable, Writable } from 'stream';
2+
import { fsyncSync, readFileSync } from 'fs';
3+
import { expect } from 'chai';
4+
import { promisify } from 'util';
5+
16
const OpenFile = Jymfony.Component.Filesystem.OpenFile;
27
const FileStreamWrapper = Jymfony.Component.Filesystem.StreamWrapper.FileStreamWrapper;
38

4-
const { expect } = require('chai');
5-
const fs = require('fs');
6-
const stream = require('stream');
79

810
describe('[Filesystem] OpenFile', function () {
911
beforeEach(() => {
@@ -33,24 +35,42 @@ describe('[Filesystem] OpenFile', function () {
3335
const file = await new OpenFile(__dirname + '/../fixtures/TESTFILE.txt', 'r');
3436
const readable = await file.createReadableStream();
3537

36-
expect(readable).to.be.instanceOf(stream.Readable);
38+
expect(readable).to.be.instanceOf(Readable);
39+
readable.on('data', buf => {
40+
expect(buf.toString('utf-8')).to.be.equal('THIS IS A TEST\n');
41+
});
42+
43+
await new Promise((resolve, reject) => {
44+
readable.on('end', resolve);
45+
readable.on('error', reject);
46+
47+
readable.read();
48+
});
3749

3850
await file.close();
3951
});
4052

4153
it('createWritableStream should return a stream', async () => {
42-
const file = await new OpenFile(__dirname + '/../fixtures/WRITEFILE', 'w');
54+
const path = __dirname + '/../fixtures/WRITEFILE';
55+
const file = await new OpenFile(path, 'w');
4356
const writable = await file.createWritableStream();
4457

45-
expect(writable).to.be.instanceOf(stream.Writable);
58+
expect(writable).to.be.instanceOf(Writable);
59+
await promisify(Writable.prototype.write).call(writable, 'This is ', 'utf-8');
60+
await promisify(Writable.prototype.write).call(writable, 'a te', 'utf-8');
61+
await promisify(Writable.prototype.write).call(writable, 'st of writing', 'utf-8');
4662

4763
await file.close();
64+
65+
const str = readFileSync(path, { encoding: 'utf-8' });
66+
expect(str).to.be.equal('This is a test of writing');
4867
});
4968

5069
it('fwrite should write to file', async () => {
5170
const file = await new OpenFile(__dirname + '/../fixtures/WRITEFILE', 'w');
5271
expect(await file.fwrite(Buffer.from('TEST FILE'))).to.be.equal(9);
53-
fs.fsyncSync((await file._resource).handle.fd);
72+
73+
fsyncSync((await file._resource).handle.fd);
5474
expect(await file.getSize()).to.be.equal(9);
5575

5676
await file.close();

0 commit comments

Comments
 (0)