fix: ensure proper closure of zip reader and file handles
This commit is contained in:
parent
c5d15240bc
commit
26b55be260
2 changed files with 47 additions and 14 deletions
|
@ -29,7 +29,7 @@ export async function renderComicPage({ path, page, reqHeaders, set }: RenderOpt
|
||||||
|
|
||||||
if (page < 0 || page >= entries.length) {
|
if (page < 0 || page >= entries.length) {
|
||||||
set.status = 404;
|
set.status = 404;
|
||||||
zip.reader.close();
|
await zip.reader.close();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ export async function renderComicPage({ path, page, reqHeaders, set }: RenderOpt
|
||||||
const cachedDate = new Date(ifModifiedSince);
|
const cachedDate = new Date(ifModifiedSince);
|
||||||
if (!Number.isNaN(cachedDate.valueOf()) && lastModified <= cachedDate) {
|
if (!Number.isNaN(cachedDate.valueOf()) && lastModified <= cachedDate) {
|
||||||
set.status = 304;
|
set.status = 304;
|
||||||
zip.reader.close();
|
await zip.reader.close();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,14 @@ export async function renderComicPage({ path, page, reqHeaders, set }: RenderOpt
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let zipClosed = false;
|
||||||
|
const closeZip = async () => {
|
||||||
|
if (!zipClosed) {
|
||||||
|
zipClosed = true;
|
||||||
|
await zip.reader.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
readStream.pipeTo(new WritableStream({
|
readStream.pipeTo(new WritableStream({
|
||||||
write(chunk) {
|
write(chunk) {
|
||||||
nodeReadable.push(chunk);
|
nodeReadable.push(chunk);
|
||||||
|
@ -64,15 +72,21 @@ export async function renderComicPage({ path, page, reqHeaders, set }: RenderOpt
|
||||||
close() {
|
close() {
|
||||||
nodeReadable.push(null);
|
nodeReadable.push(null);
|
||||||
},
|
},
|
||||||
|
abort(err) {
|
||||||
|
nodeReadable.destroy(err);
|
||||||
|
},
|
||||||
})).catch((err) => {
|
})).catch((err) => {
|
||||||
nodeReadable.destroy(err);
|
nodeReadable.destroy(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
nodeReadable.on("close", () => {
|
nodeReadable.on("close", () => {
|
||||||
zip.reader.close();
|
closeZip().catch(console.error);
|
||||||
});
|
});
|
||||||
nodeReadable.on("error", () => {
|
nodeReadable.on("error", () => {
|
||||||
zip.reader.close();
|
closeZip().catch(console.error);
|
||||||
|
});
|
||||||
|
nodeReadable.on("end", () => {
|
||||||
|
closeZip().catch(console.error);
|
||||||
});
|
});
|
||||||
|
|
||||||
const ext = entry.filename.split(".").pop()?.toLowerCase() ?? "jpeg";
|
const ext = entry.filename.split(".").pop()?.toLowerCase() ?? "jpeg";
|
||||||
|
@ -84,7 +98,7 @@ export async function renderComicPage({ path, page, reqHeaders, set }: RenderOpt
|
||||||
set.status = 200;
|
set.status = 200;
|
||||||
return nodeReadable;
|
return nodeReadable;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
zip.reader.close();
|
await zip.reader.close();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
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, ZipReaderConstructorOptions } from "@zip.js/zip.js";
|
import { ZipReader, Reader, type Entry, ZipReaderConstructorOptions } from "@zip.js/zip.js";
|
||||||
import EventEmitter from "node:events";
|
|
||||||
|
|
||||||
class FileReader extends Reader<string> {
|
class FileReader extends Reader<string> {
|
||||||
private fd?: FileHandle;
|
private fd?: FileHandle;
|
||||||
private path: string;
|
private path: string;
|
||||||
|
private closed = false;
|
||||||
|
|
||||||
constructor(path: string) {
|
constructor(path: string) {
|
||||||
super(path);
|
super(path);
|
||||||
|
@ -14,21 +14,29 @@ class FileReader extends Reader<string> {
|
||||||
|
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
await super.init?.();
|
await super.init?.();
|
||||||
|
if (this.closed) return;
|
||||||
|
|
||||||
const fd = await open(this.path, "r");
|
const fd = await open(this.path, "r");
|
||||||
const stat = await fd.stat();
|
const stat = await fd.stat();
|
||||||
this.fd = fd;
|
this.fd = fd;
|
||||||
this.size = stat.size;
|
this.size = stat.size;
|
||||||
// not implemented yet
|
|
||||||
(this.fd as unknown as EventEmitter).on("close", () => {
|
|
||||||
this.fd?.close();
|
|
||||||
this.fd = undefined;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
await this.fd?.close();
|
if (this.closed) return;
|
||||||
|
this.closed = true;
|
||||||
|
|
||||||
|
if (this.fd) {
|
||||||
|
await this.fd.close();
|
||||||
|
this.fd = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async readUint8Array(index: number, length: number): Promise<Uint8Array> {
|
async readUint8Array(index: number, length: number): Promise<Uint8Array> {
|
||||||
|
if (this.closed) {
|
||||||
|
throw new Error("FileReader is closed");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const buffer = new Uint8Array(length);
|
const buffer = new Uint8Array(length);
|
||||||
if (this.fd === undefined) {
|
if (this.fd === undefined) {
|
||||||
|
@ -49,19 +57,30 @@ class FileReader extends Reader<string> {
|
||||||
return buffer;
|
return buffer;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("read error", error);
|
console.error("read error", error);
|
||||||
|
// 에러 발생 시 파일 핸들 정리
|
||||||
|
await this.close();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileZipReader extends ZipReader<FileHandle> {
|
class FileZipReader extends ZipReader<FileHandle> {
|
||||||
|
private closed = false;
|
||||||
|
|
||||||
constructor(private reader: FileReader, options?: ZipReaderConstructorOptions) {
|
constructor(private reader: FileReader, options?: ZipReaderConstructorOptions) {
|
||||||
super(reader, options);
|
super(reader, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async close(): Promise<void> {
|
override async close(): Promise<void> {
|
||||||
super.close();
|
if (this.closed) return;
|
||||||
|
this.closed = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await super.close();
|
||||||
|
} finally {
|
||||||
await this.reader.close();
|
await this.reader.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readZip(path: string): Promise<{
|
export async function readZip(path: string): Promise<{
|
||||||
|
|
Loading…
Add table
Reference in a new issue