39 lines
2.4 KiB
JavaScript
39 lines
2.4 KiB
JavaScript
'use strict';
|
|
importScripts('/sha256.js');
|
|
let dir, handle, access, hash, pos = 0, claim, name;
|
|
let chain = Promise.resolve();
|
|
self.onmessage = e => { chain = chain.then(() => run(e.data)); };
|
|
async function close() { if (access) { access.flush(); access.close(); access = null; } }
|
|
async function run(m) {
|
|
try {
|
|
if (m.op === 'open') {
|
|
claim = m.file;
|
|
if (!/^[A-Za-z0-9_-]{1,128}$/.test(claim.id) || !/^[a-f0-9]{64}$/.test(claim.cid)) throw Error('Invalid file identity');
|
|
dir = await (await navigator.storage.getDirectory()).getDirectoryHandle('videos', { create: true });
|
|
name = `${claim.id}.${claim.cid}.direct.part`;
|
|
handle = await dir.getFileHandle(name, { create: true });
|
|
access = await handle.createSyncAccessHandle();
|
|
pos = access.getSize(); if (pos > claim.size) { access.truncate(0); pos = 0; }
|
|
hash = self.Sha256.create();
|
|
for (let at = 0; at < pos; at += 1024 * 1024) { const b = new Uint8Array(Math.min(1024 * 1024, pos - at)); access.read(b, { at }); hash.update(b); }
|
|
self.postMessage({ op: 'opened', offset: pos });
|
|
} else if (m.op === 'chunk') {
|
|
const b = new Uint8Array(m.buf);
|
|
if (!access || b.length > 65536 || pos + b.length > claim.size) throw Error('Invalid file chunk');
|
|
if (access.write(b, { at: pos }) !== b.length) throw Error('Incomplete OPFS write');
|
|
hash.update(b); pos += b.length; access.flush();
|
|
self.postMessage({ op: 'progress', received: pos });
|
|
} else if (m.op === 'abort') { await close(); self.postMessage({ op: 'closed' }); }
|
|
else if (m.op === 'finish') {
|
|
await close();
|
|
if (pos !== claim.size || hash.hex() !== claim.cid) { await dir.removeEntry(name); throw Error('File verification failed; retry starts from zero'); }
|
|
const final = `${claim.id}.mp4`;
|
|
// Copy only after verification; existing saved playback remains intact until then.
|
|
const out = await (await dir.getFileHandle(final, { create: true })).createSyncAccessHandle();
|
|
try { const f = await handle.getFile(); for (let at = 0; at < f.size; at += 1024 * 1024) out.write(new Uint8Array(await f.slice(at, at + 1024 * 1024).arrayBuffer()), { at }); out.truncate(f.size); out.flush(); } finally { out.close(); }
|
|
await dir.removeEntry(name);
|
|
self.postMessage({ op: 'done', file: claim });
|
|
}
|
|
} catch (e) { await close(); self.postMessage({ op: 'error', error: e.message }); }
|
|
}
|