univ-conv
Version:
The universal binary and text converter
217 lines • 7.37 kB
JavaScript
import { Duplex, PassThrough, Readable } from "stream";
import { AbstractConverter, _ } from "./AbstractConverter";
import { deleteStartLength, getStartEnd, } from "./core";
import { EMPTY_BUFFER, fileURLToReadable, handleReadable, hasReadable, isReadable, } from "./Environment";
import { getTextHelper } from "./StringUtil";
export class PartialReadable extends Readable {
constructor(src, start, end = Number.MAX_SAFE_INTEGER) {
super();
this.src = src;
this.start = start;
this.end = end;
src.once("readable", () => this.setup());
}
_read() {
}
setup() {
let iStart = 0;
const onData = (value) => {
const u8 = value;
const size = u8.byteLength;
const iEnd = iStart + size;
const u8End = (iEnd < this.end ? iEnd : this.end) - iStart;
let chunk;
if (iStart <= this.start && this.start < iEnd) {
chunk = u8.subarray(this.start - iStart, u8End);
}
else if (this.start < iStart && iStart < this.end) {
chunk = u8.subarray(0, u8End);
}
if (chunk) {
this.push(chunk);
}
iStart += size;
};
const src = this.src;
src.once("error", (e) => {
this.destroy(e);
src.off("data", onData);
});
src.once("end", () => {
this.push(null);
src.off("data", onData);
});
src.on("data", onData);
}
}
class ReadableOfReadableStream extends Readable {
constructor(stream, start, end = Number.MAX_SAFE_INTEGER) {
super();
this.stream = stream;
this.start = start;
this.end = end;
this.setup();
}
async setup() {
const reader = this.stream.getReader();
try {
const converter = _()._of("uint8array");
let iStart = 0;
let done;
do {
const res = await reader.read();
const value = res.value;
done = res.done;
if (value) {
const u8 = await converter.from(value);
const size = u8.byteLength;
const iEnd = iStart + size;
const u8End = (iEnd < this.end ? iEnd : this.end) - iStart;
let chunk;
if (iStart <= this.start && this.start < iEnd) {
chunk = u8.subarray(this.start - iStart, u8End);
}
else if (this.start < iStart && iStart < this.end) {
chunk = u8.subarray(0, u8End);
}
if (chunk) {
this.push(chunk);
}
iStart += size;
}
} while (!done && iStart < this.end);
this.push(null);
}
catch (e) {
this.destroy(e);
}
finally {
reader.releaseLock();
this.stream.cancel().catch((e) => console.debug(e));
}
}
}
export class ReadableConverter extends AbstractConverter {
constructor() {
super(...arguments);
this.type = "readable";
}
empty() {
return new Readable({
read() {
this.push(EMPTY_BUFFER);
this.push(null);
},
});
}
is(input) {
return isReadable(input);
}
async _from(input, options) {
if (typeof input === "string" && options.inputStringType === "url") {
if (input.startsWith("http:") || input.startsWith("https:")) {
const resp = await fetch(input);
if (hasReadable) {
input = resp.body;
}
else {
input = resp.body;
}
}
else if (input.startsWith("file:") && fileURLToReadable) {
input = await fileURLToReadable(input);
}
}
if (this.is(input)) {
const { start, end } = getStartEnd(options);
return new PartialReadable(input, start, end);
}
if (_().is("readablestream", input, options)) {
const { start, end } = getStartEnd(options);
return new ReadableOfReadableStream(input, start, end);
}
const buffer = await _().convert("uint8array", input, options);
const duplex = new Duplex();
duplex.push(buffer);
duplex.push(null);
return duplex;
}
_getStartEnd(_input, options) {
return Promise.resolve(getStartEnd(options));
}
_isEmpty(input) {
return !input.readable;
}
_merge(readables) {
const end = readables.length;
if (!readables || end === 0) {
return Promise.resolve(this.empty());
}
if (end === 1) {
return Promise.resolve(readables[0]);
}
const pt = new PassThrough();
const process = (i) => {
if (i < end) {
const readable = readables[i];
readable.once("error", (e) => {
readable.unpipe();
pt.destroy(e);
for (let j = i; j < end; j++) {
const r = readables[j];
r.destroy();
}
});
readable.once("end", () => process(++i));
readable.pipe(pt, { end: false });
}
else {
pt.end();
}
};
process(0);
return Promise.resolve(pt);
}
_size() {
throw new Error("Cannot get size of Readable");
}
async _toArrayBuffer(input, options) {
const u8 = await this._toUint8Array(input, options);
return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength);
}
async _toBase64(input, options) {
const buffer = await this._toUint8Array(input, options);
return await _()
._of("uint8array")
.toBase64(buffer, deleteStartLength(options));
}
async _toText(input, options) {
const u8 = await this._toUint8Array(input, options);
const textHelper = await getTextHelper();
return await textHelper.bufferToText(u8, options);
}
async _toUint8Array(input, options) {
const { start, end } = await this._getStartEnd(input, options);
const bufferSize = options.bufferSize;
let index = 0;
const converter = _()._of("uint8array");
const chunks = [];
await handleReadable(input, async (chunk) => {
const buffer = await converter.from(chunk, { bufferSize });
const size = buffer.byteLength;
let e = index + size;
if (end != null && end < e)
e = end;
if (index < start && start < e) {
chunks.push(buffer.subarray(start, e));
}
else if (start <= index) {
chunks.push(buffer);
}
index += size;
return end == null || index < end;
});
return Buffer.concat(chunks);
}
}
//# sourceMappingURL=ReadableConverter.js.map