feat: file reader rework

This commit is contained in:
monoid 2025-05-01 16:51:24 +09:00
parent f8e2930ec1
commit b3f0f6d980
4 changed files with 77 additions and 44 deletions

View file

@ -12,7 +12,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@std/async": "npm:@jsr/std__async@^1.0.12", "@std/async": "npm:@jsr/std__async@^1.0.12",
"@zip.js/zip.js": "^2.7.52", "@zip.js/zip.js": "^2.7.60",
"better-sqlite3": "^9.6.0", "better-sqlite3": "^9.6.0",
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
"dbtype": "workspace:dbtype", "dbtype": "workspace:dbtype",
@ -33,7 +33,7 @@
"@types/koa-bodyparser": "^4.3.12", "@types/koa-bodyparser": "^4.3.12",
"@types/koa-compose": "^3.2.8", "@types/koa-compose": "^3.2.8",
"@types/koa-router": "^7.4.8", "@types/koa-router": "^7.4.8",
"@types/node": "^22.7.4", "@types/node": "^22.15.3",
"@types/tiny-async-pool": "^1.0.5", "@types/tiny-async-pool": "^1.0.5",
"nodemon": "^3.1.7", "nodemon": "^3.1.7",
"tsx": "^4.19.1", "tsx": "^4.19.1",

View file

@ -12,7 +12,6 @@ import { Readable } from "node:stream";
*/ */
const ZipStreamCache = new Map<string, { const ZipStreamCache = new Map<string, {
reader: ZipReader<FileHandle>, reader: ZipReader<FileHandle>,
handle: FileHandle,
refCount: number, refCount: number,
}>(); }>();
@ -39,7 +38,6 @@ async function acquireZip(path: string, marked = false) {
// if the cache is not updated, set the new one. // if the cache is not updated, set the new one.
ZipStreamCache.set(path, { ZipStreamCache.set(path, {
reader: obj.reader, reader: obj.reader,
handle: obj.handle,
refCount: 1, refCount: 1,
}); });
return obj.reader; return obj.reader;
@ -57,10 +55,8 @@ function releaseZip(path: string) {
return; return;
} }
if (obj.refCount === 1) { if (obj.refCount === 1) {
const { reader, handle } = obj; const { reader } = obj;
reader.close().then(() => { reader.close();
handle.close();
});
ZipStreamCache.delete(path); ZipStreamCache.delete(path);
} else { } else {
obj.refCount--; obj.refCount--;

View file

@ -1,42 +1,67 @@
import { type FileHandle, open } from "node:fs/promises"; import { type FileHandle, open } from "node:fs/promises";
import { orderBy } from "natural-orderby"; import { orderBy } from "natural-orderby";
import { ZipReader, Reader, type Entry } from "@zip.js/zip.js"; import { ZipReader, Reader, type Entry } from "@zip.js/zip.js";
import EventEmitter from "node:events";
class FileReader extends Reader<FileHandle> { class FileReader extends Reader<string> {
private fd: FileHandle; private fd?: FileHandle;
constructor(fd: FileHandle) { private path: string;
super(fd);
this.fd = fd; constructor(path: string) {
super(path);
this.path = path;
} }
async init(): Promise<void> { async init(): Promise<void> {
this.size = (await this.fd.stat()).size; await super.init?.();
const fd = await open(this.path, "r");
const stat = await fd.stat();
this.fd = fd;
this.size = stat.size;
// not implemented yet
(this.fd as unknown as EventEmitter).on("close", () => {
console.warn(`file handle closed: ${this.path}`);
this.fd = undefined;
});
} }
close(): void { async close(): Promise<void> {
this.fd.close(); await this.fd?.close();
} }
async readUint8Array(index: number, length: number): Promise<Uint8Array> { async readUint8Array(index: number, length: number): Promise<Uint8Array> {
try {
const buffer = new Uint8Array(length); const buffer = new Uint8Array(length);
if (this.fd === undefined) {
console.error("file handle is undefined", this.path);
// reopen the file handle
this.fd = await open(this.path, "r");
const stat = await this.fd.stat();
if (stat.size !== this.size) {
console.error("file size changed", this.path, stat.size, this.size);
throw new Error("file size changed");
}
}
const buf = await this.fd.read(buffer, 0, length, index); const buf = await this.fd.read(buffer, 0, length, index);
if (buf.bytesRead !== length) { if (buf.bytesRead !== length) {
console.error(`read error: ${buf.bytesRead} !== ${length}`); console.error(`read error: ${buf.bytesRead} !== ${length}`);
throw new Error("read error"); throw new Error("read error");
} }
return buffer; return buffer;
} catch (error) {
console.error("read error", error);
throw error;
}
} }
} }
export async function readZip(path: string): Promise<{ export async function readZip(path: string): Promise<{
reader: ZipReader<FileHandle> reader: ZipReader<FileHandle>
handle: FileHandle
}> { }> {
const fd = await open(path, "r"); const reader = new ZipReader(new FileReader(path), {
const reader = new ZipReader(new FileReader(fd), {
useCompressionStream: true, useCompressionStream: true,
preventClose: false, preventClose: false,
}); });
return { reader, handle: fd }; return { reader };
} }
export async function entriesByNaturalOrder(zip: ZipReader<FileHandle>) { export async function entriesByNaturalOrder(zip: ZipReader<FileHandle>) {
const entries = await zip.getEntries(); const entries = await zip.getEntries();

46
pnpm-lock.yaml generated
View file

@ -173,8 +173,8 @@ importers:
specifier: npm:@jsr/std__async@^1.0.12 specifier: npm:@jsr/std__async@^1.0.12
version: '@jsr/std__async@1.0.12' version: '@jsr/std__async@1.0.12'
'@zip.js/zip.js': '@zip.js/zip.js':
specifier: ^2.7.52 specifier: ^2.7.60
version: 2.7.52 version: 2.7.60
better-sqlite3: better-sqlite3:
specifier: ^9.6.0 specifier: ^9.6.0
version: 9.6.0 version: 9.6.0
@ -231,8 +231,8 @@ importers:
specifier: ^7.4.8 specifier: ^7.4.8
version: 7.4.8 version: 7.4.8
'@types/node': '@types/node':
specifier: ^22.7.4 specifier: ^22.15.3
version: 22.7.4 version: 22.15.3
'@types/tiny-async-pool': '@types/tiny-async-pool':
specifier: ^1.0.5 specifier: ^1.0.5
version: 1.0.5 version: 1.0.5
@ -1468,6 +1468,9 @@ packages:
'@types/mime@1.3.5': '@types/mime@1.3.5':
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
'@types/node@22.15.3':
resolution: {integrity: sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==}
'@types/node@22.7.4': '@types/node@22.7.4':
resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==} resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==}
@ -1591,8 +1594,8 @@ packages:
'@vitest/utils@2.1.2': '@vitest/utils@2.1.2':
resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==} resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==}
'@zip.js/zip.js@2.7.52': '@zip.js/zip.js@2.7.60':
resolution: {integrity: sha512-+5g7FQswvrCHwYKNMd/KFxZSObctLSsQOgqBSi0LzwHo3li9Eh1w5cF5ndjQw9Zbr3ajVnd2+XyiX85gAetx1Q==} resolution: {integrity: sha512-vA3rLyqdxBrVo1FWSsbyoecaqWTV+vgPRf0QKeM7kVDG0r+lHUqd7zQDv1TO9k4BcAoNzNDSNrrel24Mk6addA==}
engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=16.5.0'} engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=16.5.0'}
accepts@1.3.8: accepts@1.3.8:
@ -3357,6 +3360,9 @@ packages:
undici-types@6.19.8: undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
universalify@2.0.1: universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
@ -4521,11 +4527,11 @@ snapshots:
'@types/accepts@1.3.7': '@types/accepts@1.3.7':
dependencies: dependencies:
'@types/node': 22.7.4 '@types/node': 22.15.3
'@types/better-sqlite3@7.6.11': '@types/better-sqlite3@7.6.11':
dependencies: dependencies:
'@types/node': 22.7.4 '@types/node': 22.15.3
'@types/better-sqlite3@7.6.9': '@types/better-sqlite3@7.6.9':
dependencies: dependencies:
@ -4534,11 +4540,11 @@ snapshots:
'@types/body-parser@1.19.5': '@types/body-parser@1.19.5':
dependencies: dependencies:
'@types/connect': 3.4.38 '@types/connect': 3.4.38
'@types/node': 22.7.4 '@types/node': 22.15.3
'@types/connect@3.4.38': '@types/connect@3.4.38':
dependencies: dependencies:
'@types/node': 22.7.4 '@types/node': 22.15.3
'@types/content-disposition@0.5.8': {} '@types/content-disposition@0.5.8': {}
@ -4547,7 +4553,7 @@ snapshots:
'@types/connect': 3.4.38 '@types/connect': 3.4.38
'@types/express': 5.0.0 '@types/express': 5.0.0
'@types/keygrip': 1.0.6 '@types/keygrip': 1.0.6
'@types/node': 22.7.4 '@types/node': 22.15.3
'@types/d3-array@3.2.1': {} '@types/d3-array@3.2.1': {}
@ -4577,7 +4583,7 @@ snapshots:
'@types/express-serve-static-core@5.0.0': '@types/express-serve-static-core@5.0.0':
dependencies: dependencies:
'@types/node': 22.7.4 '@types/node': 22.15.3
'@types/qs': 6.9.16 '@types/qs': 6.9.16
'@types/range-parser': 1.2.7 '@types/range-parser': 1.2.7
'@types/send': 0.17.4 '@types/send': 0.17.4
@ -4595,7 +4601,7 @@ snapshots:
'@types/jsonwebtoken@8.5.9': '@types/jsonwebtoken@8.5.9':
dependencies: dependencies:
'@types/node': 22.7.4 '@types/node': 22.15.3
'@types/keygrip@1.0.6': {} '@types/keygrip@1.0.6': {}
@ -4620,10 +4626,14 @@ snapshots:
'@types/http-errors': 2.0.4 '@types/http-errors': 2.0.4
'@types/keygrip': 1.0.6 '@types/keygrip': 1.0.6
'@types/koa-compose': 3.2.8 '@types/koa-compose': 3.2.8
'@types/node': 22.7.4 '@types/node': 22.15.3
'@types/mime@1.3.5': {} '@types/mime@1.3.5': {}
'@types/node@22.15.3':
dependencies:
undici-types: 6.21.0
'@types/node@22.7.4': '@types/node@22.7.4':
dependencies: dependencies:
undici-types: 6.19.8 undici-types: 6.19.8
@ -4646,12 +4656,12 @@ snapshots:
'@types/send@0.17.4': '@types/send@0.17.4':
dependencies: dependencies:
'@types/mime': 1.3.5 '@types/mime': 1.3.5
'@types/node': 22.7.4 '@types/node': 22.15.3
'@types/serve-static@1.15.7': '@types/serve-static@1.15.7':
dependencies: dependencies:
'@types/http-errors': 2.0.4 '@types/http-errors': 2.0.4
'@types/node': 22.7.4 '@types/node': 22.15.3
'@types/send': 0.17.4 '@types/send': 0.17.4
'@types/tiny-async-pool@1.0.5': {} '@types/tiny-async-pool@1.0.5': {}
@ -4786,7 +4796,7 @@ snapshots:
loupe: 3.1.2 loupe: 3.1.2
tinyrainbow: 1.2.0 tinyrainbow: 1.2.0
'@zip.js/zip.js@2.7.52': {} '@zip.js/zip.js@2.7.60': {}
accepts@1.3.8: accepts@1.3.8:
dependencies: dependencies:
@ -6564,6 +6574,8 @@ snapshots:
undici-types@6.19.8: {} undici-types@6.19.8: {}
undici-types@6.21.0: {}
universalify@2.0.1: {} universalify@2.0.1: {}
unpipe@1.0.0: {} unpipe@1.0.0: {}