From 43173b6bf65b64dd977937838683282895d0a15a Mon Sep 17 00:00:00 2001 From: Yang MingCheng <1107238486@qq.com> Date: Wed, 4 Feb 2026 16:53:43 +0800 Subject: [PATCH 1/3] fix(uploads): resolve hash calculation memory crash and add hash progress --- src/lang/en/home.json | 1 + src/pages/home/uploads/form.ts | 6 +++++- src/pages/home/uploads/stream.ts | 6 +++++- src/pages/home/uploads/types.ts | 9 ++++++++- src/pages/home/uploads/util.ts | 19 ++++++++++++++----- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/lang/en/home.json b/src/lang/en/home.json index c98dfaeb7..dffc08fc1 100644 --- a/src/lang/en/home.json +++ b/src/lang/en/home.json @@ -112,6 +112,7 @@ "no_files_drag": "No files were dragged in.", "upload_files": "Choose Files", "upload_folder": "Choose Folder", + "hashing": "Hashing", "pending": "Pending", "uploading": "Uploading", "backending": "Uploading in the backend", diff --git a/src/pages/home/uploads/form.ts b/src/pages/home/uploads/form.ts index 4800ccf53..5b83af53c 100644 --- a/src/pages/home/uploads/form.ts +++ b/src/pages/home/uploads/form.ts @@ -24,11 +24,15 @@ export const FormUpload: Upload = async ( Overwrite: overwrite.toString(), } if (rapid) { - const { md5, sha1, sha256 } = await calculateHash(file) + setUpload("status", "hashing") + const { md5, sha1, sha256 } = await calculateHash(file, (p) => { + setUpload("progress", p | 0) + }) headers["X-File-Md5"] = md5 headers["X-File-Sha1"] = sha1 headers["X-File-Sha256"] = sha256 } + setUpload("status", "uploading") const resp: EmptyResp = await r.put("/fs/form", form, { headers: headers, onUploadProgress: (progressEvent) => { diff --git a/src/pages/home/uploads/stream.ts b/src/pages/home/uploads/stream.ts index 2f9e260c5..62c3793b7 100644 --- a/src/pages/home/uploads/stream.ts +++ b/src/pages/home/uploads/stream.ts @@ -22,11 +22,15 @@ export const StreamUpload: Upload = async ( Overwrite: overwrite.toString(), } if (rapid) { - const { md5, sha1, sha256 } = await calculateHash(file) + setUpload("status", "hashing") + const { md5, sha1, sha256 } = await calculateHash(file, (p) => { + setUpload("progress", p | 0) + }) headers["X-File-Md5"] = md5 headers["X-File-Sha1"] = sha1 headers["X-File-Sha256"] = sha256 } + setUpload("status", "uploading") const resp: EmptyResp = await r.put("/fs/put", file, { headers: headers, onUploadProgress: (progressEvent) => { diff --git a/src/pages/home/uploads/types.ts b/src/pages/home/uploads/types.ts index ade472f71..bd366d5db 100644 --- a/src/pages/home/uploads/types.ts +++ b/src/pages/home/uploads/types.ts @@ -1,4 +1,10 @@ -type Status = "pending" | "uploading" | "backending" | "success" | "error" +type Status = + | "pending" + | "hashing" + | "uploading" + | "backending" + | "success" + | "error" export interface UploadFileProps { name: string path: string @@ -10,6 +16,7 @@ export interface UploadFileProps { } export const StatusBadge = { pending: "neutral", + hashing: "warning", uploading: "info", backending: "info", success: "success", diff --git a/src/pages/home/uploads/util.ts b/src/pages/home/uploads/util.ts index aacd6a8dc..966a5f237 100644 --- a/src/pages/home/uploads/util.ts +++ b/src/pages/home/uploads/util.ts @@ -68,22 +68,31 @@ export const File2Upload = (file: File): UploadFileProps => { } } -export const calculateHash = async (file: File) => { +export const calculateHash = async ( + file: File, + onProgress?: (progress: number) => void, +) => { const md5Digest = await createMD5() const sha1Digest = await createSHA1() const sha256Digest = await createSHA256() const reader = file.stream().getReader() - const read = async () => { + let count = 0 + let loaded = 0 + while (true) { const { done, value } = await reader.read() if (done) { - return + break } + loaded += value.length md5Digest.update(value) sha1Digest.update(value) sha256Digest.update(value) - await read() + onProgress?.((loaded / file.size) * 100) + count++ + if (count % 10 === 0) { + await new Promise((resolve) => setTimeout(resolve, 0)) + } } - await read() const md5 = md5Digest.digest("hex") const sha1 = sha1Digest.digest("hex") const sha256 = sha256Digest.digest("hex") From 38b3c5b18ce03d719e2ad13c3464753916914639 Mon Sep 17 00:00:00 2001 From: Yang MingCheng <1107238486@qq.com> Date: Mon, 9 Feb 2026 12:08:07 +0800 Subject: [PATCH 2/3] =?UTF-8?q?refactor(uploads):=20=E4=BD=BF=E7=94=A8=20W?= =?UTF-8?q?eb=20Worker=20=E5=A4=84=E7=90=86=E5=93=88=E5=B8=8C=E8=AE=A1?= =?UTF-8?q?=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/home/uploads/hash-worker.ts | 38 ++++++++++++++++++++ src/pages/home/uploads/util.ts | 50 +++++++++++++-------------- 2 files changed, 62 insertions(+), 26 deletions(-) create mode 100644 src/pages/home/uploads/hash-worker.ts diff --git a/src/pages/home/uploads/hash-worker.ts b/src/pages/home/uploads/hash-worker.ts new file mode 100644 index 000000000..0172cd68d --- /dev/null +++ b/src/pages/home/uploads/hash-worker.ts @@ -0,0 +1,38 @@ +import { createMD5, createSHA1, createSHA256 } from "hash-wasm" + +self.onmessage = async (e: MessageEvent<{ file: File }>) => { + const { file } = e.data + try { + const md5Digest = await createMD5() + const sha1Digest = await createSHA1() + const sha256Digest = await createSHA256() + const reader = file.stream().getReader() + let loaded = 0 + while (true) { + const { done, value } = await reader.read() + if (done) { + break + } + loaded += value.length + md5Digest.update(value) + sha1Digest.update(value) + sha256Digest.update(value) + self.postMessage({ + type: "progress", + progress: (loaded / file.size) * 100, + }) + } + const md5 = md5Digest.digest("hex") + const sha1 = sha1Digest.digest("hex") + const sha256 = sha256Digest.digest("hex") + self.postMessage({ + type: "result", + hash: { md5, sha1, sha256 }, + }) + } catch (error) { + self.postMessage({ + type: "error", + error: error instanceof Error ? error.message : String(error), + }) + } +} diff --git a/src/pages/home/uploads/util.ts b/src/pages/home/uploads/util.ts index 966a5f237..143caa848 100644 --- a/src/pages/home/uploads/util.ts +++ b/src/pages/home/uploads/util.ts @@ -1,5 +1,4 @@ import { UploadFileProps } from "./types" -import { createMD5, createSHA1, createSHA256 } from "hash-wasm" export const traverseFileTree = async (entry: FileSystemEntry) => { let res: File[] = [] @@ -72,29 +71,28 @@ export const calculateHash = async ( file: File, onProgress?: (progress: number) => void, ) => { - const md5Digest = await createMD5() - const sha1Digest = await createSHA1() - const sha256Digest = await createSHA256() - const reader = file.stream().getReader() - let count = 0 - let loaded = 0 - while (true) { - const { done, value } = await reader.read() - if (done) { - break - } - loaded += value.length - md5Digest.update(value) - sha1Digest.update(value) - sha256Digest.update(value) - onProgress?.((loaded / file.size) * 100) - count++ - if (count % 10 === 0) { - await new Promise((resolve) => setTimeout(resolve, 0)) - } - } - const md5 = md5Digest.digest("hex") - const sha1 = sha1Digest.digest("hex") - const sha256 = sha256Digest.digest("hex") - return { md5, sha1, sha256 } + return new Promise<{ md5: string; sha1: string; sha256: string }>( + (resolve, reject) => { + const worker = new Worker(new URL("./hash-worker.ts", import.meta.url), { + type: "module", + }) + worker.postMessage({ file }) + worker.onmessage = (e) => { + const { type, progress, hash, error } = e.data + if (type === "progress") { + onProgress?.(progress) + } else if (type === "result") { + worker.terminate() + resolve(hash) + } else if (type === "error") { + worker.terminate() + reject(new Error(error)) + } + } + worker.onerror = (e) => { + worker.terminate() + reject(e) + } + }, + ) } From 574c3e64436f264501392b530aea1fe88c520c65 Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Sat, 21 Feb 2026 14:04:37 +0800 Subject: [PATCH 3/3] feat(hash-worker): enhance type and promise handling Signed-off-by: MadDogOwner --- src/pages/home/uploads/hash-worker.ts | 61 ++++++++++++++++++++------- src/pages/home/uploads/util.ts | 52 ++++++++++++----------- 2 files changed, 73 insertions(+), 40 deletions(-) diff --git a/src/pages/home/uploads/hash-worker.ts b/src/pages/home/uploads/hash-worker.ts index 0172cd68d..5218bb840 100644 --- a/src/pages/home/uploads/hash-worker.ts +++ b/src/pages/home/uploads/hash-worker.ts @@ -1,38 +1,67 @@ import { createMD5, createSHA1, createSHA256 } from "hash-wasm" +interface WorkerProgressMessage { + type: "progress" + progress: number +} + +interface WorkerResultMessage { + type: "result" + hash: { md5: string; sha1: string; sha256: string } +} + +interface WorkerErrorMessage { + type: "error" + error: string +} + +export type WorkerMessage = + | WorkerProgressMessage + | WorkerResultMessage + | WorkerErrorMessage + self.onmessage = async (e: MessageEvent<{ file: File }>) => { const { file } = e.data try { - const md5Digest = await createMD5() - const sha1Digest = await createSHA1() - const sha256Digest = await createSHA256() + const [md5Digest, sha1Digest, sha256Digest] = await Promise.all([ + createMD5(), + createSHA1(), + createSHA256(), + ]) + const reader = file.stream().getReader() let loaded = 0 + while (true) { const { done, value } = await reader.read() - if (done) { - break - } + if (done) break + loaded += value.length md5Digest.update(value) sha1Digest.update(value) sha256Digest.update(value) - self.postMessage({ + + const progress: WorkerProgressMessage = { type: "progress", progress: (loaded / file.size) * 100, - }) + } + self.postMessage(progress) } - const md5 = md5Digest.digest("hex") - const sha1 = sha1Digest.digest("hex") - const sha256 = sha256Digest.digest("hex") - self.postMessage({ + + const result: WorkerResultMessage = { type: "result", - hash: { md5, sha1, sha256 }, - }) + hash: { + md5: md5Digest.digest("hex"), + sha1: sha1Digest.digest("hex"), + sha256: sha256Digest.digest("hex"), + }, + } + self.postMessage(result) } catch (error) { - self.postMessage({ + const err: WorkerErrorMessage = { type: "error", error: error instanceof Error ? error.message : String(error), - }) + } + self.postMessage(err) } } diff --git a/src/pages/home/uploads/util.ts b/src/pages/home/uploads/util.ts index 143caa848..dc0b395f5 100644 --- a/src/pages/home/uploads/util.ts +++ b/src/pages/home/uploads/util.ts @@ -1,9 +1,11 @@ import { UploadFileProps } from "./types" +import type { WorkerMessage } from "./hash-worker" export const traverseFileTree = async (entry: FileSystemEntry) => { - let res: File[] = [] + const res: File[] = [] + const internalProcess = async (entry: FileSystemEntry, path: string) => { - const promise = new Promise<{}>((resolve, reject) => { + await new Promise((resolve, reject) => { const errorCallback: ErrorCallback = (e) => { console.error(e) reject(e) @@ -15,7 +17,7 @@ export const traverseFileTree = async (entry: FileSystemEntry) => { }) res.push(newFile) console.log(newFile) - resolve({}) + resolve() }, errorCallback) } else if (entry.isDirectory) { const dirReader = (entry as FileSystemDirectoryEntry).createReader() @@ -27,10 +29,9 @@ export const traverseFileTree = async (entry: FileSystemEntry) => { if (entries.length > 0) { readEntries() } else { - resolve({}) + resolve() } - /* resolve({}) /** why? https://stackoverflow.com/questions/3590058/does-html5-allow-drag-drop-upload-of-folders-or-a-folder-tree/53058574#53058574 Unfortunately none of the existing answers are completely correct because @@ -41,16 +42,12 @@ export const traverseFileTree = async (entry: FileSystemEntry) => { until it returns an empty array. If we don't, we will miss some files/sub-directories in a directory e.g. in Chrome, readEntries will only return at most 100 entries at a time. - if (entries.length > 0) { - readEntries() - } */ }, errorCallback) } readEntries() } }) - await promise } await internalProcess(entry, "") return res @@ -59,7 +56,7 @@ export const traverseFileTree = async (entry: FileSystemEntry) => { export const File2Upload = (file: File): UploadFileProps => { return { name: file.name, - path: file.webkitRelativePath ? file.webkitRelativePath : file.name, + path: file.webkitRelativePath || file.name, size: file.size, progress: 0, speed: 0, @@ -76,23 +73,30 @@ export const calculateHash = async ( const worker = new Worker(new URL("./hash-worker.ts", import.meta.url), { type: "module", }) + + const terminate = (fn: () => void) => { + worker.terminate() + fn() + } + worker.postMessage({ file }) - worker.onmessage = (e) => { - const { type, progress, hash, error } = e.data - if (type === "progress") { - onProgress?.(progress) - } else if (type === "result") { - worker.terminate() - resolve(hash) - } else if (type === "error") { - worker.terminate() - reject(new Error(error)) + + worker.onmessage = (e: MessageEvent) => { + const data = e.data + switch (data.type) { + case "progress": + onProgress?.(data.progress) + break + case "result": + terminate(() => resolve(data.hash)) + break + case "error": + terminate(() => reject(new Error(data.error))) + break } } - worker.onerror = (e) => { - worker.terminate() - reject(e) - } + + worker.onerror = (e) => terminate(() => reject(e)) }, ) }