diff options
Diffstat (limited to 'std/io')
-rw-r--r-- | std/io/bufio.ts | 212 | ||||
-rw-r--r-- | std/io/bufio_test.ts | 35 | ||||
-rw-r--r-- | std/io/util.ts | 18 |
3 files changed, 207 insertions, 58 deletions
diff --git a/std/io/bufio.ts b/std/io/bufio.ts index 87613e341..a4e729cfd 100644 --- a/std/io/bufio.ts +++ b/std/io/bufio.ts @@ -5,6 +5,7 @@ type Reader = Deno.Reader; type Writer = Deno.Writer; +type SyncWriter = Deno.SyncWriter; import { charCode, copyBytes } from "./util.ts"; import { assert } from "../testing/asserts.ts"; @@ -400,6 +401,40 @@ export class BufReader implements Reader { } } +abstract class AbstractBufBase { + buf!: Uint8Array; + usedBufferBytes = 0; + err: Error | null = null; + + /** Size returns the size of the underlying buffer in bytes. */ + size(): number { + return this.buf.byteLength; + } + + /** Returns how many bytes are unused in the buffer. */ + available(): number { + return this.buf.byteLength - this.usedBufferBytes; + } + + /** buffered returns the number of bytes that have been written into the + * current buffer. + */ + buffered(): number { + return this.usedBufferBytes; + } + + checkBytesWritten(numBytesWritten: number): void { + if (numBytesWritten < this.usedBufferBytes) { + if (numBytesWritten > 0) { + this.buf.copyWithin(0, numBytesWritten, this.usedBufferBytes); + this.usedBufferBytes -= numBytesWritten; + } + this.err = new Error("Short write"); + throw this.err; + } + } +} + /** BufWriter implements buffering for an deno.Writer object. * If an error occurs writing to a Writer, no more data will be * accepted and all subsequent writes, and flush(), will return the error. @@ -407,106 +442,179 @@ export class BufReader implements Reader { * flush() method to guarantee all data has been forwarded to * the underlying deno.Writer. */ -export class BufWriter implements Writer { - buf: Uint8Array; - n = 0; - err: Error | null = null; - - /** return new BufWriter unless w is BufWriter */ - static create(w: Writer, size: number = DEFAULT_BUF_SIZE): BufWriter { - return w instanceof BufWriter ? w : new BufWriter(w, size); +export class BufWriter extends AbstractBufBase implements Writer { + /** return new BufWriter unless writer is BufWriter */ + static create(writer: Writer, size: number = DEFAULT_BUF_SIZE): BufWriter { + return writer instanceof BufWriter ? writer : new BufWriter(writer, size); } - constructor(private wr: Writer, size: number = DEFAULT_BUF_SIZE) { + constructor(private writer: Writer, size: number = DEFAULT_BUF_SIZE) { + super(); if (size <= 0) { size = DEFAULT_BUF_SIZE; } this.buf = new Uint8Array(size); } - /** Size returns the size of the underlying buffer in bytes. */ - size(): number { - return this.buf.byteLength; - } - /** Discards any unflushed buffered data, clears any error, and - * resets b to write its output to w. + * resets buffer to write its output to w. */ reset(w: Writer): void { this.err = null; - this.n = 0; - this.wr = w; + this.usedBufferBytes = 0; + this.writer = w; } /** Flush writes any buffered data to the underlying io.Writer. */ async flush(): Promise<void> { if (this.err !== null) throw this.err; - if (this.n === 0) return; + if (this.usedBufferBytes === 0) return; - let n = 0; + let numBytesWritten = 0; try { - n = await this.wr.write(this.buf.subarray(0, this.n)); + numBytesWritten = await this.writer.write( + this.buf.subarray(0, this.usedBufferBytes) + ); } catch (e) { this.err = e; throw e; } - if (n < this.n) { - if (n > 0) { - this.buf.copyWithin(0, n, this.n); - this.n -= n; + this.checkBytesWritten(numBytesWritten); + + this.usedBufferBytes = 0; + } + + /** Writes the contents of `data` into the buffer. If the contents won't fully + * fit into the buffer, those bytes that can are copied into the buffer, the + * buffer is the flushed to the writer and the remaining bytes are copied into + * the now empty buffer. + * + * @return the number of bytes written to the buffer. + */ + async write(data: Uint8Array): Promise<number> { + if (this.err !== null) throw this.err; + if (data.length === 0) return 0; + + let totalBytesWritten = 0; + let numBytesWritten = 0; + while (data.byteLength > this.available()) { + if (this.buffered() === 0) { + // Large write, empty buffer. + // Write directly from data to avoid copy. + try { + numBytesWritten = await this.writer.write(data); + } catch (e) { + this.err = e; + throw e; + } + } else { + numBytesWritten = copyBytes(this.buf, data, this.usedBufferBytes); + this.usedBufferBytes += numBytesWritten; + await this.flush(); } - this.err = new Error("Short write"); - throw this.err; + totalBytesWritten += numBytesWritten; + data = data.subarray(numBytesWritten); } - this.n = 0; + numBytesWritten = copyBytes(this.buf, data, this.usedBufferBytes); + this.usedBufferBytes += numBytesWritten; + totalBytesWritten += numBytesWritten; + return totalBytesWritten; } +} - /** Returns how many bytes are unused in the buffer. */ - available(): number { - return this.buf.byteLength - this.n; +/** BufWriterSync implements buffering for a deno.SyncWriter object. + * If an error occurs writing to a SyncWriter, no more data will be + * accepted and all subsequent writes, and flush(), will return the error. + * After all data has been written, the client should call the + * flush() method to guarantee all data has been forwarded to + * the underlying deno.SyncWriter. + */ +export class BufWriterSync extends AbstractBufBase implements SyncWriter { + /** return new BufWriterSync unless writer is BufWriterSync */ + static create( + writer: SyncWriter, + size: number = DEFAULT_BUF_SIZE + ): BufWriterSync { + return writer instanceof BufWriterSync + ? writer + : new BufWriterSync(writer, size); } - /** buffered returns the number of bytes that have been written into the - * current buffer. + constructor(private writer: SyncWriter, size: number = DEFAULT_BUF_SIZE) { + super(); + if (size <= 0) { + size = DEFAULT_BUF_SIZE; + } + this.buf = new Uint8Array(size); + } + + /** Discards any unflushed buffered data, clears any error, and + * resets buffer to write its output to w. */ - buffered(): number { - return this.n; + reset(w: SyncWriter): void { + this.err = null; + this.usedBufferBytes = 0; + this.writer = w; + } + + /** Flush writes any buffered data to the underlying io.SyncWriter. */ + flush(): void { + if (this.err !== null) throw this.err; + if (this.usedBufferBytes === 0) return; + + let numBytesWritten = 0; + try { + numBytesWritten = this.writer.writeSync( + this.buf.subarray(0, this.usedBufferBytes) + ); + } catch (e) { + this.err = e; + throw e; + } + + this.checkBytesWritten(numBytesWritten); + + this.usedBufferBytes = 0; } - /** Writes the contents of p into the buffer. - * Returns the number of bytes written. + /** Writes the contents of `data` into the buffer. If the contents won't fully + * fit into the buffer, those bytes that can are copied into the buffer, the + * buffer is the flushed to the writer and the remaining bytes are copied into + * the now empty buffer. + * + * @return the number of bytes written to the buffer. */ - async write(p: Uint8Array): Promise<number> { + writeSync(data: Uint8Array): number { if (this.err !== null) throw this.err; - if (p.length === 0) return 0; + if (data.length === 0) return 0; - let nn = 0; - let n = 0; - while (p.byteLength > this.available()) { + let totalBytesWritten = 0; + let numBytesWritten = 0; + while (data.byteLength > this.available()) { if (this.buffered() === 0) { // Large write, empty buffer. - // Write directly from p to avoid copy. + // Write directly from data to avoid copy. try { - n = await this.wr.write(p); + numBytesWritten = this.writer.writeSync(data); } catch (e) { this.err = e; throw e; } } else { - n = copyBytes(this.buf, p, this.n); - this.n += n; - await this.flush(); + numBytesWritten = copyBytes(this.buf, data, this.usedBufferBytes); + this.usedBufferBytes += numBytesWritten; + this.flush(); } - nn += n; - p = p.subarray(n); + totalBytesWritten += numBytesWritten; + data = data.subarray(numBytesWritten); } - n = copyBytes(this.buf, p, this.n); - this.n += n; - nn += n; - return nn; + numBytesWritten = copyBytes(this.buf, data, this.usedBufferBytes); + this.usedBufferBytes += numBytesWritten; + totalBytesWritten += numBytesWritten; + return totalBytesWritten; } } diff --git a/std/io/bufio_test.ts b/std/io/bufio_test.ts index c1b1b856b..ef1fcc11e 100644 --- a/std/io/bufio_test.ts +++ b/std/io/bufio_test.ts @@ -14,6 +14,7 @@ import { import { BufReader, BufWriter, + BufWriterSync, BufferFullError, PartialReadError, readStringDelim, @@ -353,6 +354,40 @@ Deno.test(async function bufioWriter(): Promise<void> { } }); +Deno.test(function bufioWriterSync(): void { + const data = new Uint8Array(8192); + + for (let i = 0; i < data.byteLength; i++) { + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + data[i] = charCode(" ") + (i % (charCode("~") - charCode(" "))); + } + + const w = new Buffer(); + for (const nwrite of bufsizes) { + for (const bs of bufsizes) { + // Write nwrite bytes using buffer size bs. + // Check that the right amount makes it out + // and that the data is correct. + + w.reset(); + const buf = new BufWriterSync(w, bs); + + const context = `nwrite=${nwrite} bufsize=${bs}`; + const n = buf.writeSync(data.subarray(0, nwrite)); + assertEquals(n, nwrite, context); + + buf.flush(); + + const written = w.bytes(); + assertEquals(written.byteLength, nwrite); + + for (let l = 0; l < written.byteLength; l++) { + assertEquals(written[l], data[l]); + } + } + } +}); + Deno.test(async function bufReaderReadFull(): Promise<void> { const enc = new TextEncoder(); const dec = new TextDecoder(); diff --git a/std/io/util.ts b/std/io/util.ts index 4479e2c38..28688ae91 100644 --- a/std/io/util.ts +++ b/std/io/util.ts @@ -5,14 +5,20 @@ type Reader = Deno.Reader; import * as path from "../path/mod.ts"; import { encode } from "../encoding/utf8.ts"; -// `off` is the offset into `dst` where it will at which to begin writing values -// from `src`. -// Returns the number of bytes copied. +/** + * Copy bytes from one Uint8Array to another. Bytes from `src` which don't fit + * into `dst` will not be copied. + * + * @param dst Destination byte array + * @param src Source byte array + * @param off Offset into `dst` at which to begin writing values from `src`. + * @return number of bytes copied + */ export function copyBytes(dst: Uint8Array, src: Uint8Array, off = 0): number { off = Math.max(0, Math.min(off, dst.byteLength)); - const r = dst.byteLength - off; - if (src.byteLength > r) { - src = src.subarray(0, r); + const dstBytesAvailable = dst.byteLength - off; + if (src.byteLength > dstBytesAvailable) { + src = src.subarray(0, dstBytesAvailable); } dst.set(src, off); return src.byteLength; |