mirror of
https://github.com/nezhahq/nezha.git
synced 2025-02-08 12:38:13 -05:00
fm: store file to OPFS temporarily (#413)
This commit is contained in:
parent
0ec1bb2c54
commit
cae443d5c8
70
resource/static/file.js
Normal file
70
resource/static/file.js
Normal file
@ -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 });
|
||||||
|
}
|
||||||
|
};
|
98
resource/template/dashboard-default/file.html
vendored
98
resource/template/dashboard-default/file.html
vendored
@ -90,6 +90,8 @@
|
|||||||
let receivedLength = 0;
|
let receivedLength = 0;
|
||||||
let isFirstChunk = true;
|
let isFirstChunk = true;
|
||||||
let isUpCompleted = false;
|
let isUpCompleted = false;
|
||||||
|
let handleReady = false;
|
||||||
|
let worker;
|
||||||
|
|
||||||
function updateDirectoryTitle() {
|
function updateDirectoryTitle() {
|
||||||
const directoryTitle = document.getElementById('current-directory');
|
const directoryTitle = document.getElementById('current-directory');
|
||||||
@ -160,7 +162,6 @@
|
|||||||
expectedLength = 0;
|
expectedLength = 0;
|
||||||
receivedLength = 0;
|
receivedLength = 0;
|
||||||
isFirstChunk = true;
|
isFirstChunk = true;
|
||||||
updateProgress(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadFile(filePath) {
|
function downloadFile(filePath) {
|
||||||
@ -265,20 +266,6 @@
|
|||||||
return { items };
|
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) {
|
function readFileAsArrayBuffer(blob) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
@ -337,8 +324,6 @@
|
|||||||
modal.open = true;
|
modal.open = true;
|
||||||
if (operation === 'd') {
|
if (operation === 'd') {
|
||||||
modal.setAttribute('headline', 'Downloading...');
|
modal.setAttribute('headline', 'Downloading...');
|
||||||
modal.setAttribute('value', '0');
|
|
||||||
modal.setAttribute('max', '100');
|
|
||||||
} else if (operation === 'u') {
|
} else if (operation === 'u') {
|
||||||
modal.setAttribute('headline', 'Uploading...');
|
modal.setAttribute('headline', 'Uploading...');
|
||||||
}
|
}
|
||||||
@ -349,9 +334,17 @@
|
|||||||
modal.open = false;
|
modal.open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateProgress(percentage) {
|
function waitForHandleReady() {
|
||||||
const progressBar = document.getElementById('upd-progress');
|
return new Promise(resolve => {
|
||||||
progressBar.value = percentage;
|
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}}');
|
const socket = new WebSocket((window.location.protocol === 'https:' ? 'wss' : 'ws') + '://' + window.location.host + '/file/' + '{{.SessionID}}');
|
||||||
@ -369,31 +362,36 @@
|
|||||||
const completeIdentifier = new Uint8Array([0x4E, 0x5A, 0x55, 0x50]); // NZUP
|
const completeIdentifier = new Uint8Array([0x4E, 0x5A, 0x55, 0x50]); // NZUP
|
||||||
|
|
||||||
if (arraysEqual(identifier, fileIdentifier)) {
|
if (arraysEqual(identifier, fileIdentifier)) {
|
||||||
// Download
|
worker = new Worker('/static/file.js');
|
||||||
const dataView = new DataView(arrayBuffer);
|
worker.onmessage = async function (event) {
|
||||||
expectedLength = Number(dataView.getBigUint64(4, false));
|
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);
|
||||||
|
|
||||||
isFirstChunk = false;
|
// Delete the file in OPFS
|
||||||
receivedLength = 0;
|
window.addEventListener('beforeunload', async () => {
|
||||||
|
await worker.postMessage({ operation: 3, arrayBuffer: null, fileName: event.data.fileName });
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize writer
|
hideUpdModal();
|
||||||
const stream = new WritableStream({
|
resetUpdState();
|
||||||
write(chunk) {
|
break;
|
||||||
receivedBuffer.push(chunk);
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
// Save to blob
|
|
||||||
const completeBlob = new Blob(receivedBuffer);
|
|
||||||
handleDownloadFile(completeBlob);
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
await worker.postMessage({ operation: 1, arrayBuffer: arrayBuffer, fileName: fileName });
|
||||||
writer = stream.getWriter();
|
isFirstChunk = false;
|
||||||
|
|
||||||
// Read data after 12 bytes (if any)
|
|
||||||
const dataChunk = arrayBuffer.slice(12);
|
|
||||||
writer.write(dataChunk);
|
|
||||||
receivedLength += dataChunk.byteLength;
|
|
||||||
} else if (arraysEqual(identifier, fileNameIdentifier)) {
|
} else if (arraysEqual(identifier, fileNameIdentifier)) {
|
||||||
// List files
|
// List files
|
||||||
const { items } = await parseFileList(arrayBuffer);
|
const { items } = await parseFileList(arrayBuffer);
|
||||||
@ -414,23 +412,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle data chunks
|
await waitForHandleReady();
|
||||||
receivedLength += arrayBuffer.byteLength;
|
await worker.postMessage({ operation: 2, arrayBuffer: arrayBuffer, fileName: fileName });
|
||||||
writer.write(arrayBuffer);
|
|
||||||
|
|
||||||
// Update progress bar
|
|
||||||
const percentage = Math.min((receivedLength / expectedLength) * 100, 100);
|
|
||||||
updateProgress(percentage);
|
|
||||||
|
|
||||||
if (receivedLength === expectedLength) {
|
|
||||||
writer.close(); // Close the writer
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing received data:', error);
|
console.error('Error processing received data:', error);
|
||||||
if (writer) {
|
|
||||||
writer.abort();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user