Skip to content

Commit

Permalink
fm: store file to OPFS temporarily (#413)
Browse files Browse the repository at this point in the history
  • Loading branch information
uubulb authored Aug 21, 2024
1 parent 0ec1bb2 commit cae443d
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 58 deletions.
70 changes: 70 additions & 0 deletions resource/static/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
let receivedLength = 0;
let expectedLength = 0;
let root;
let draftHandle;
let accessHandle;

const Operation = Object.freeze({
WriteHeader: 1,
WriteChunks: 2,
DeleteFiles: 3
});

onmessage = async function (event) {
try {
const { operation, arrayBuffer, fileName } = event.data;

switch (operation) {
case Operation.WriteHeader: {
const dataView = new DataView(arrayBuffer);
expectedLength = Number(dataView.getBigUint64(4, false));
receivedLength = 0;

// Create a new temporary file
root = await navigator.storage.getDirectory();
draftHandle = await root.getFileHandle(fileName, { create: true });
accessHandle = await draftHandle.createSyncAccessHandle();

// Inform that file handle is created
const dataChunk = arrayBuffer.slice(12);
receivedLength += dataChunk.byteLength;
accessHandle.write(dataChunk, { at: 0 });
const progress = 'got handle';
postMessage({ type: 'progress', progress: progress });
break;
}
case Operation.WriteChunks: {
if (!accessHandle) {
throw new Error('accessHandle is undefined');
}

const dataChunk = arrayBuffer;
accessHandle.write(dataChunk, { at: receivedLength });
receivedLength += dataChunk.byteLength;

if (receivedLength === expectedLength) {
accessHandle.flush();
accessHandle.close();

const fileBlob = await draftHandle.getFile();
const blob = new Blob([fileBlob], { type: 'application/octet-stream' });

postMessage({ type: 'result', blob: blob, fileName: fileName });
}
break;
}
case Operation.DeleteFiles: {
for await (const [name, handle] of root.entries()) {
if (handle.kind === 'file') {
await root.removeEntry(name);
} else if (handle.kind === 'directory') {
await root.removeEntry(name, { recursive: true });
}
}
break;
}
}
} catch (error) {
postMessage({ error: error.message });
}
};
102 changes: 44 additions & 58 deletions resource/template/dashboard-default/file.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@
let receivedLength = 0;
let isFirstChunk = true;
let isUpCompleted = false;
let handleReady = false;
let worker;

function updateDirectoryTitle() {
const directoryTitle = document.getElementById('current-directory');
Expand Down Expand Up @@ -160,7 +162,6 @@
expectedLength = 0;
receivedLength = 0;
isFirstChunk = true;
updateProgress(0);
}

function downloadFile(filePath) {
Expand Down Expand Up @@ -265,20 +266,6 @@
return { items };
}

async function handleDownloadFile(blob) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);

hideUpdModal();
resetUpdState();
}

function readFileAsArrayBuffer(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
Expand Down Expand Up @@ -337,8 +324,6 @@
modal.open = true;
if (operation === 'd') {
modal.setAttribute('headline', 'Downloading...');
modal.setAttribute('value', '0');
modal.setAttribute('max', '100');
} else if (operation === 'u') {
modal.setAttribute('headline', 'Uploading...');
}
Expand All @@ -349,9 +334,17 @@
modal.open = false;
}

function updateProgress(percentage) {
const progressBar = document.getElementById('upd-progress');
progressBar.value = percentage;
function waitForHandleReady() {
return new Promise(resolve => {
const checkReady = () => {
if (handleReady) {
resolve();
} else {
setTimeout(checkReady, 10);
}
};
checkReady();
});
}

const socket = new WebSocket((window.location.protocol === 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/file/' + '{{.SessionID}}');
Expand All @@ -369,31 +362,36 @@
const completeIdentifier = new Uint8Array([0x4E, 0x5A, 0x55, 0x50]); // NZUP

if (arraysEqual(identifier, fileIdentifier)) {
// Download
const dataView = new DataView(arrayBuffer);
expectedLength = Number(dataView.getBigUint64(4, false));

isFirstChunk = false;
receivedLength = 0;

// Initialize writer
const stream = new WritableStream({
write(chunk) {
receivedBuffer.push(chunk);
},
close() {
// Save to blob
const completeBlob = new Blob(receivedBuffer);
handleDownloadFile(completeBlob);
worker = new Worker('/static/file.js');
worker.onmessage = async function (event) {
switch (event.data.type) {
case 'error':
console.error('Error from worker:', event.data.error);
break;
case 'progress':
handleReady = true;
break;
case 'result':
handleReady = false;
const url = URL.createObjectURL(event.data.blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = event.data.fileName;
anchor.click();
URL.revokeObjectURL(url);

// Delete the file in OPFS
window.addEventListener('beforeunload', async () => {
await worker.postMessage({ operation: 3, arrayBuffer: null, fileName: event.data.fileName });
});

hideUpdModal();
resetUpdState();
break;
}
});

writer = stream.getWriter();

// Read data after 12 bytes (if any)
const dataChunk = arrayBuffer.slice(12);
writer.write(dataChunk);
receivedLength += dataChunk.byteLength;
};
await worker.postMessage({ operation: 1, arrayBuffer: arrayBuffer, fileName: fileName });
isFirstChunk = false;
} else if (arraysEqual(identifier, fileNameIdentifier)) {
// List files
const { items } = await parseFileList(arrayBuffer);
Expand All @@ -414,23 +412,11 @@
return;
}
} else {
// Handle data chunks
receivedLength += arrayBuffer.byteLength;
writer.write(arrayBuffer);

// Update progress bar
const percentage = Math.min((receivedLength / expectedLength) * 100, 100);
updateProgress(percentage);

if (receivedLength === expectedLength) {
writer.close(); // Close the writer
}
await waitForHandleReady();
await worker.postMessage({ operation: 2, arrayBuffer: arrayBuffer, fileName: fileName });
}
} catch (error) {
console.error('Error processing received data:', error);
if (writer) {
writer.abort();
}
}
};

Expand Down

0 comments on commit cae443d

Please sign in to comment.