web/storage: add memory storage and init() function

This commit is contained in:
jj 2025-04-30 17:55:57 +00:00
parent be4e7e2d7d
commit ce4ded64a2
No known key found for this signature in database
3 changed files with 107 additions and 1 deletions

View File

@ -0,0 +1,15 @@
import type { AbstractStorage } from "./storage";
import { MemoryStorage } from "./memory";
import { OPFSStorage } from "./opfs";
export function init(expectedSize?: number): Promise<AbstractStorage> {
if (OPFSStorage.isAvailable()) {
return OPFSStorage.init();
}
if (MemoryStorage.isAvailable()) {
return MemoryStorage.init(expectedSize || 0);
}
throw "no storage method is available";
}

View File

@ -0,0 +1,91 @@
import { AbstractStorage } from "./storage";
export class MemoryStorage extends AbstractStorage {
#chunkSize: number;
#actualSize: number = 0;
#chunks: Uint8Array[] = [];
constructor(chunkSize: number) {
super();
this.#chunkSize = chunkSize;
}
static async init(expectedSize: number) {
const MB = 1024 * 1024;
const chunkSize = Math.min(512 * MB, expectedSize);
const storage = new this(chunkSize);
// since we expect the output file to be roughly the same size
// as inputs, preallocate its size for the output
for (
let toAllocate = expectedSize;
toAllocate > 0;
toAllocate -= chunkSize
) {
storage.#chunks.push(new Uint8Array(chunkSize));
}
return storage;
}
async res() {
// if we didn't need as much space as we allocated for some reason,
// shrink the buffers so that we don't inflate the file with zeroes
const outputView: Uint8Array[] = [];
for (let i = 0; i < this.#chunks.length; ++i) {
outputView.push(
this.#chunks[i].subarray(
0,
Math.min(this.#chunkSize, this.#actualSize),
),
);
this.#actualSize -= this.#chunkSize;
if (this.#actualSize <= 0) {
break;
}
}
return new Blob(outputView);
}
#expand(size: number) {
while (size > this.#chunkSize * this.#chunks.length) {
this.#chunks.push(new Uint8Array(this.#chunkSize));
}
}
async write(data: Uint8Array | Int8Array, pos: number) {
const writeEnd = pos + data.length;
this.#expand(writeEnd);
const chunkIndex = pos / this.#chunkSize | 0;
const offset = pos - (this.#chunkSize * chunkIndex);
if (offset + data.length > this.#chunkSize) {
this.#chunks[chunkIndex].set(
data.subarray(0, this.#chunkSize - offset),
offset,
);
this.#chunks[chunkIndex + 1].set(
data.subarray(this.#chunkSize - offset),
0,
);
} else {
this.#chunks[chunkIndex].set(data, offset);
}
this.#actualSize = Math.max(writeEnd, this.#actualSize);
return data.length;
}
async destroy() {
this.#chunks = [];
}
static isAvailable() {
return true;
}
}

View File

@ -1,5 +1,5 @@
export abstract class AbstractStorage {
static init(): Promise<AbstractStorage> {
static init(_expected_size: number): Promise<AbstractStorage> {
throw "init() call on abstract implementation";
}