UNPKG

@kayahr/ed-journal

Version:

Typescript library to read/watch the player journal of Frontier's game Elite Dangerous

156 lines 5.67 kB
/* * Copyright (C) 2022 Klaus Reimer <k@ailis.de> * See LICENSE.md for licensing information. */ import { open } from "node:fs/promises"; /** * Concatenates given byte arrays and returns new byte array. * * @param a - First byte array. * @param b - Second byte array. * @returns The concatenated byte array. */ function concat(a, b) { const aSize = a.length; const result = new Uint8Array(aSize + b.length); result.set(a); result.set(b, aSize); return result; } /** * Reads lines from a UTF-8 encoded file. */ export class LineReader { /** The read buffer. */ buffer; /** The file to read from. Null if file is not open. */ file; /** The current read position in the file. */ readPosition; /** The current offset in the text stream. */ currentOffset; /** The current line in the text stream. */ currentLine; /** The current start index in the buffer. */ bufferStart = 0; /** The current end index in the buffer. */ bufferEnd = 0; /** The buffered line. */ bufferedLine = null; /** Text decoder used to decode UTF-8 line into JavaScript string. */ decoder = new TextDecoder(); /** * Constructs a new line reader reading from the given file and position. * * @param file - The file to read from. * @param offset - The file offset to start reading from. * @param line - The line number to start counting with. * @param bufferSize - The size of the read buffer in bytes. */ constructor(file, offset, line, bufferSize) { this.file = file; this.readPosition = offset; this.currentOffset = offset; this.currentLine = line; this.buffer = new Uint8Array(bufferSize); } /** @inheritdoc */ async [Symbol.asyncDispose]() { await this.close(); } /** * Creates a new line reader reading from the given file and position. * * @param filename - The file to read from. * @param offset - The file offset to start reading from. Defaults to 0 (Beginning of file). * @param line - The line number to start counting with. Defaults to 1 (First line). * @param bufferSize - The size of the read buffer in bytes. Defaults to 8 KB. * @returns The created line reader. */ static async create(filename, offset = 0, line = 1, bufferSize = 8192) { const file = await open(filename, "r"); return new LineReader(file, offset, line, bufferSize); } /** * Returns the byte offset from which the next line will be read. * * @returns The byte offset from which the next line will be read. */ getOffset() { return this.currentOffset; } /** * Returns the line from which the next line will be read. * * @returns The line from which the next line will be read. */ getLine() { return this.currentLine; } /** * Closes the file reader and releases the file handle it uses. */ async close() { await this.file.close(); } /** * Reads the next line from the file and returns it. A line must be terminated by a line break (LF or CRLF). Method * returns null if there is currently no complete line to return. You can call the method again to try again (In * case the file is still growing). The returned string includes the line terminated (LF or CRLF) so you have to * strip it yourself if necessary. * * @returns The read line or null if no more complete lines are currently present. */ async next() { // Fill buffer if empty if (this.bufferStart >= this.bufferEnd) { if (this.file.fd === -1) { // Check if file handle has already been closed return null; } const { bytesRead } = await this.file.read({ buffer: this.buffer, position: this.readPosition }); if (bytesRead > 0) { this.bufferStart = 0; this.bufferEnd = bytesRead; this.readPosition += bytesRead; return this.next(); } else { return null; } } // Get text data in buffer const buffer = this.buffer.subarray(this.bufferStart, this.bufferEnd); // Search for line break in text const newLineIndex = buffer.indexOf(0x0a); if (newLineIndex !== -1) { // When line break was found then construct line and return it and prepare state for next line const subBuffer = buffer.subarray(0, newLineIndex + 1); const bytes = this.bufferedLine != null ? concat(this.bufferedLine, subBuffer) : subBuffer; const text = this.decoder.decode(bytes); this.currentOffset += bytes.length; this.currentLine += 1; this.bufferStart += newLineIndex + 1; this.bufferedLine = null; return text; } else { // When no line break was found then buffer the current incomplete line, read the next block and try again this.bufferedLine = this.bufferedLine != null ? concat(this.bufferedLine, buffer) : buffer.slice(); this.bufferStart = this.bufferEnd; return this.next(); } } /** * Reads all lines until the end of the file. * * @yields The read lines as a generator. */ async *[Symbol.asyncIterator]() { let line; while ((line = await this.next()) != null) { yield line; } } } //# sourceMappingURL=LineReader.js.map