Files
eh-downloader/utils/progress_readable.ts
2024-01-02 15:11:26 +08:00

121 lines
3.8 KiB
TypeScript

type EventMap = {
"finished": number;
"progress": number;
};
export class ProgressReadable extends EventTarget {
readable: ReadableStream<Uint8Array>;
readed: number;
error?: unknown;
timeout: number;
get signal() {
return this.#controller.signal;
}
get is_timeout() {
return this.#is_timeout;
}
#controller: AbortController;
#is_timeout: boolean;
#timeout?: number;
#last_readed: number;
constructor(
readable: ReadableStream<Uint8Array>,
timeout: number,
originalSignal?: AbortSignal,
) {
super();
this.readed = 0;
this.timeout = timeout;
this.#is_timeout = false;
this.#last_readed = Date.now();
const reader = readable.getReader();
this.#controller = new AbortController();
originalSignal?.addEventListener("abort", () => {
this.#controller.abort();
this.#clearInterval();
});
this.readable = new ReadableStream({
pull: (c) => {
if (c.byobRequest) {
throw Error("Unimplemented.");
} else {
reader.read().then((v) => {
if (v.done) {
this.dispatchEvent("finished", this.readed);
c.close();
this.#clearInterval();
return;
} else {
const len = v.value.byteLength;
this.readed += len;
this.dispatchEvent("progress", this.readed);
c.enqueue(v.value);
if (len != 0) {
this.#last_readed = Date.now();
}
}
}).catch((e) => {
try {
c.close();
} catch (_) {
null;
}
this.error = e;
this.#clearInterval();
});
}
},
cancel: (reason) => {
try {
if (!readable.locked) readable.cancel(reason);
this.#clearInterval();
} catch (_) {
null;
}
},
type: "bytes",
});
this.#setInterval();
}
#clearInterval() {
if (this.#timeout) {
clearInterval(this.#timeout);
this.#timeout = undefined;
}
}
#setInterval() {
this.#timeout = setInterval(() => {
const now = Date.now();
if (now - this.#last_readed > this.timeout) {
this.#is_timeout = true;
this.#controller.abort();
this.#clearInterval();
}
}, 1);
}
// @ts-ignore Checked type
addEventListener<T extends keyof EventMap>(
type: T,
callback: (e: CustomEvent<EventMap[T]>) => void | Promise<void>,
options?: boolean | AddEventListenerOptions,
): void {
super.addEventListener(type, <EventListener> callback, options);
}
// @ts-ignore Checked type
dispatchEvent<T extends keyof EventMap>(type: T, detail: EventMap[T]) {
return super.dispatchEvent(new CustomEvent(type, { detail }));
}
// @ts-ignore Checked type
removeEventListener<T extends keyof EventMap>(
type: T,
callback: (e: CustomEvent<EventMap[T]>) => void | Promise<void>,
options?: boolean | EventListenerOptions,
): void {
super.removeEventListener(
type,
<EventListener> callback,
options,
);
}
}