Created
October 1, 2024 03:15
-
-
Save Pho3nixHun/8941b09fe5623f8df0255b9b51df7858 to your computer and use it in GitHub Desktop.
Utils
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// CRC32 lookup table | |
export const crc32LookupTable = Array.from({ length: 256 }, (_, n) => | |
Array.from({ length: 8 }).reduce( | |
(c) => (c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1), | |
n | |
) | |
); | |
// Calculate the CRC32 checksum on the main thread | |
export const crc32ArrayBuffer = (uint8Array) => | |
uint8Array.reduce( | |
(crc, byte) => (crc >>> 8) ^ crc32LookupTable[(crc ^ byte) & 0xff], | |
0xffffffff | |
) ^ | |
(0xffffffff >>> 0); | |
/** | |
* Generate CRC32 checksum of a Blob in the main thread. | |
* | |
* @param {Blob} blob - The Blob object to generate the checksum for. | |
* @returns {Promise<string>} - The CRC32 checksum as a hex string. | |
*/ | |
export const generateBlobChecksum = async (blob) => { | |
const arrayBuffer = await blob.arrayBuffer(); | |
const uint8Array = new Uint8Array(arrayBuffer); | |
return crc32ArrayBuffer(uint8Array).toString(16); | |
}; | |
/** | |
* @type {Worker|null} Web Worker instance, initially null until the worker is created. | |
*/ | |
let worker = null; | |
/** | |
* Check if the worker is running. | |
* @returns {worker is Worker} - Whether the worker is currently running. | |
*/ | |
export const checkWorker = () => worker instanceof Worker; | |
/** | |
* Start the Web Worker. | |
* @param {Object} [options={}] - Options to control worker behavior. | |
* @param {number} [options.chunkSize=1MB] - The chunk size (in bytes) to process the blob. | |
* Smaller chunk sizes reduce memory usage but increase the number of iterations. | |
* Common values (powers of two): | |
* - 512 KB = 2**19 | |
* - 1 MB = 2**20 | |
* - 2 MB = 2**21 | |
* @returns {void} | |
*/ | |
export const startWorker = ({chunkSize = 2**21) => { | |
if (!worker) { | |
// Create the worker inline | |
const workerBlob = new Blob( | |
[ | |
` | |
self.crc32LookupTable = ${crc32LookupTable.toString()}; | |
const crc32ArrayBuffer = (uint8Array, crc = 0xFFFFFFFF) => | |
uint8Array.reduce( | |
(crc, byte) => (crc >>> 8) ^ crc32LookupTable[(crc ^ byte) & 0xFF], | |
crc | |
); | |
const processChunk = async (blob, start, chunkSize, crc) => { | |
const chunk = blob.slice(start, start + chunkSize); | |
const arrayBuffer = await chunk.arrayBuffer(); | |
const uint8Array = new Uint8Array(arrayBuffer); | |
return crc32ArrayBuffer(uint8Array, crc); | |
}; | |
self.onmessage = async event => { | |
const blob = event.data; | |
const chunkSize = ${chunkSize}; // Read in 64MB chunks (adjust as needed) | |
let crc = 0xFFFFFFFF; | |
let offset = 0; | |
while (offset < blob.size) { | |
crc = await processChunk(blob, offset, chunkSize, crc); | |
offset += chunkSize; | |
} | |
// Finalize CRC32 checksum | |
crc = crc ^ 0xFFFFFFFF; | |
self.postMessage(crc.toString(16)); | |
}; | |
`, | |
], | |
{ type: "application/javascript" } | |
); | |
const workerURL = URL.createObjectURL(workerBlob); | |
worker = new Worker(workerURL); | |
return worker; | |
} | |
}; | |
/** | |
* Stop the Web Worker. | |
* @returns {void} | |
* @throws {Error} If there is no worker to stop. | |
*/ | |
export const stopWorker = () => { | |
if (checkWorker()) { | |
throw new Error("No running worker found"); | |
} | |
worker.terminate(); | |
worker = null; | |
return; | |
}; | |
/** | |
* Generate CRC32 checksum of a Blob in a Web Worker. | |
* | |
* @param {Blob} blob - The Blob object to generate the checksum for. | |
* @param {Object} [options={}] - Options to control worker start and stop behavior. | |
* @param {boolean} [options.start=true] - Whether to start the worker if it is not already running. | |
* @param {boolean} [options.stop] - Whether to stop the worker after the operation. If undefined, it depends on whether the worker was started by this function call. | |
* @returns {Promise<string>} - A Promise that resolves with the CRC32 checksum. | |
*/ | |
export const generateBlobChecksumWithWorker = async ( | |
blob, | |
{ start = true, stop } = {} | |
) => { | |
const workerStartedHere = start && !checkWorker(); | |
const stopWorkerIfNeeded = () => | |
stop === true || | |
(stop === undefined && workerStartedHere) && checkWorker() && stopWorker(); | |
// Check if we need to start the worker | |
if (workerStartedHere) { | |
await startWorker(); // Ensure the worker is ready | |
} | |
return new Promise((resolve, reject) => { | |
// Set up event listener for the result | |
worker.onmessage = (event) => { | |
resolve(event.data); // Resolve the promise with the checksum | |
stopWorkerIfNeeded(); | |
}; | |
worker.onerror = (error) => { | |
reject(error); // Reject the promise if an error occurs | |
stopWorkerIfNeeded(); | |
}; | |
// Send the Blob to the worker for processing | |
worker.postMessage(blob); | |
}); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment