UNPKG

nufatfs

Version:

A new async-friendly library for accessing FAT16 and FAT32 filesystems

161 lines (160 loc) 6.76 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Chain = exports.ChainError = void 0; class ChainError extends Error { } exports.ChainError = ChainError; class Chain { set cursor(n) { this._cursor = n; this.currentLink = undefined; this.linkSubCursor = undefined; let currentLength = 0; let idx = 0; for (let link of this._links) { if ((link.length + currentLength) > n) { this.currentLink = link; this.linkSubCursor = n - currentLength; this.linkIndex = idx; break; } currentLength += link.length; ++idx; } } get cursor() { return this._cursor; } tell() { return this.cursor; } getTotalLength() { return this.totalLength; } get links() { return this._links; } constructor(_links, allocateLink, readLimitSize) { this._links = _links; this.allocateLink = allocateLink; this.readLimitSize = readLimitSize; this._cursor = 0; this.linkIndex = 0; this.writingBuffer = null; this.isNewByteArray = null; this.writable = _links.every(e => !!e.write); this.cursor = 0; this.totalLength = readLimitSize !== null && readLimitSize !== void 0 ? readLimitSize : this.length(); } length() { return this._links.reduce((a, b) => a + b.length, 0); } async seek(to, whence) { // Regardless of the writing state, flush the buffer on-seek await this.flushChanges(); if (whence === 'start' || !whence) this.cursor = to; else if (whence === 'cur') this.cursor += to; else if (whence === 'end') this.cursor = this.length() - to; } async read(count) { count = Math.min(this.totalLength, count + this.cursor) - this.cursor; const ret = new Uint8Array(count); let currentLength = 0; while (currentLength < count) { if (!this.currentLink || this.linkSubCursor === undefined) { break; } const thisLinkContents = await this.currentLink.read(); const limited = thisLinkContents.slice(this.linkSubCursor, count - currentLength + this.linkSubCursor); ret.set(limited, currentLength); currentLength += limited.length; this._cursor += limited.length; this.linkSubCursor += limited.length; if (this.linkSubCursor >= this.currentLink.length) { // Advance link. this.linkIndex++; this.linkSubCursor -= this.currentLink.length; this.currentLink = this._links[this.linkIndex]; } } return ret.slice(0, currentLength); } async readAll() { return this.read(this.length() - this.cursor); } async flushChanges() { return this.flushWritingBuffer(); } async flushWritingBuffer() { // Rewrite the current link with writingBuffer if (!this.writingBuffer) return; if (!this.currentLink) throw new ChainError("Invalid chain state (Assertion 1)"); if (this.writingBuffer.length !== this.currentLink.length) throw new ChainError("Invalid chain state (Assertion 2)"); // Rewrite. // Depending on if we have any old bytes remaining, overlay the two buffers on one another. Otherwise just use the new one const isUsingAnyOldBytes = this.isNewByteArray.some(e => e === false); let bufferToWrite; if (isUsingAnyOldBytes) { // Overlay const originalData = await this.currentLink.read(); for (let i = 0; i < originalData.length; i++) { if (this.isNewByteArray[i]) { originalData[i] = this.writingBuffer[i]; } } bufferToWrite = originalData; } else { // The new buffer is the absolute authority bufferToWrite = this.writingBuffer; } await this.currentLink.write(bufferToWrite); this.writingBuffer = null; this.isNewByteArray = null; } async write(data) { var _a; if (!this.writable) throw new ChainError("Cannot write to a read-only chain!"); let wholeChainLength = this.length(); // Allocate all the required space first. while ((this.cursor + data.length) > wholeChainLength) { if (!this.allocateLink) { throw new ChainError("No space!"); } let newLinks = await this.allocateLink((_a = this._links[this._links.length - 1]) !== null && _a !== void 0 ? _a : null, (this.cursor + data.length) - wholeChainLength); if (!newLinks.length) throw new ChainError("Allocator can't allocate more links!"); wholeChainLength += newLinks.reduce((a, b) => a + b.length, 0); this._links.push.apply(this._links, newLinks); // Update cursor this.cursor = this.cursor; } let incomingDataCursor = 0; while (incomingDataCursor < data.length) { await this.cacheCurrentLinkForWriting(); // Find the largest possible chunk of data to merge onto the writing buffer. const remainingSpaceInLinkDerivedFromCursor = this.currentLink.length - this.linkSubCursor; const dataLengthForThisLink = Math.min(remainingSpaceInLinkDerivedFromCursor, data.length - incomingDataCursor); // Get the slice. const slice = data.slice(incomingDataCursor, incomingDataCursor + dataLengthForThisLink); // Rewrite the slice to the new buffer, then advance this.writingBuffer.set(slice, this.linkSubCursor); this.isNewByteArray.fill(true, this.linkSubCursor, this.linkSubCursor + slice.byteLength); // Would setting this new cursor advance to next link? if ((this.linkSubCursor + dataLengthForThisLink) >= this.currentLink.length) { // Flush it. await this.flushWritingBuffer(); } this.cursor += dataLengthForThisLink; if (this.cursor > this.totalLength) this.totalLength = this.cursor; incomingDataCursor += dataLengthForThisLink; } } async cacheCurrentLinkForWriting() { if (!this.writingBuffer) { this.writingBuffer = new Uint8Array(this.currentLink.length).fill(0); this.isNewByteArray = Array(this.writingBuffer.length).fill(false); } } } exports.Chain = Chain;