diff --git a/src/lang/en/home.json b/src/lang/en/home.json index c98dfaeb..dffc08fc 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 4800ccf5..5b83af53 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/hash-worker.ts b/src/pages/home/uploads/hash-worker.ts new file mode 100644 index 00000000..0172cd68 --- /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/stream.ts b/src/pages/home/uploads/stream.ts index 2f9e260c..62c3793b 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 ade472f7..bd366d5d 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 aacd6a8d..143caa84 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[] = [] @@ -68,24 +67,32 @@ export const File2Upload = (file: File): UploadFileProps => { } } -export const calculateHash = async (file: File) => { - const md5Digest = await createMD5() - const sha1Digest = await createSHA1() - const sha256Digest = await createSHA256() - const reader = file.stream().getReader() - const read = async () => { - const { done, value } = await reader.read() - if (done) { - return - } - md5Digest.update(value) - sha1Digest.update(value) - sha256Digest.update(value) - await read() - } - await read() - const md5 = md5Digest.digest("hex") - const sha1 = sha1Digest.digest("hex") - const sha256 = sha256Digest.digest("hex") - return { md5, sha1, sha256 } +export const calculateHash = async ( + file: File, + onProgress?: (progress: number) => void, +) => { + 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) + } + }, + ) }