mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2025-04-21 15:26:17 -04:00
Move file creation to serviceworker to prevent loading everything into RAM
This commit is contained in:
parent
ef3c338dad
commit
5ee8bb871e
3 changed files with 135 additions and 3 deletions
|
@ -1742,7 +1742,7 @@ class FileDigester {
|
|||
this._mime = meta.mime;
|
||||
this._totalSize = totalSize;
|
||||
this._totalBytesReceived = totalBytesReceived;
|
||||
this._onFileCompleteCallback = fileCompleteCallback;
|
||||
this._fileCompleteCallback = fileCompleteCallback;
|
||||
this._receiveConfimationCallback = receiveConfirmationCallback;
|
||||
}
|
||||
|
||||
|
@ -1763,13 +1763,95 @@ class FileDigester {
|
|||
if (this._bytesReceived < this._size) return;
|
||||
|
||||
// we are done
|
||||
if (!window.Worker && !window.isSecureContext) {
|
||||
this.processFileViaMemory();
|
||||
return;
|
||||
}
|
||||
|
||||
this.processFileViaWorker();
|
||||
}
|
||||
|
||||
processFileViaMemory() {
|
||||
Logger.warn('Big file transfers might exceed the RAM of the receiver. Use a secure context (https) to prevent this.');
|
||||
|
||||
// Loads complete file into RAM which might lead to a page crash (Memory limit iOS Safari: ~380 MB)
|
||||
const file = new File(this._buffer, this._name, {
|
||||
type: this._mime,
|
||||
lastModified: new Date().getTime()
|
||||
})
|
||||
this._fileCompleteCallback(file);
|
||||
}
|
||||
|
||||
this._buffer = [];
|
||||
this._onFileCompleteCallback(file);
|
||||
processFileViaWorker() {
|
||||
// Use service worker to prevent loading the complete file into RAM
|
||||
const fileWorker = new Worker("scripts/sw-file-digester.js");
|
||||
|
||||
let i = 0;
|
||||
let offset = 0;
|
||||
|
||||
const _this = this;
|
||||
|
||||
function sendPart(buffer, offset) {
|
||||
fileWorker.postMessage({
|
||||
type: "part",
|
||||
name: _this._name,
|
||||
buffer: buffer,
|
||||
offset: offset
|
||||
});
|
||||
}
|
||||
|
||||
function getFile() {
|
||||
fileWorker.postMessage({
|
||||
type: "get-file",
|
||||
name: _this._name,
|
||||
});
|
||||
}
|
||||
|
||||
function onPart(part) {
|
||||
// remove old chunk from buffer
|
||||
_this._buffer[i] = null;
|
||||
|
||||
if (i < _this._buffer.length - 1) {
|
||||
// process next chunk
|
||||
offset += part.byteLength;
|
||||
i++;
|
||||
sendPart(_this._buffer[i], offset);
|
||||
return;
|
||||
}
|
||||
|
||||
// File processing complete -> retrieve completed file
|
||||
getFile();
|
||||
}
|
||||
|
||||
function onFile(file) {
|
||||
_this._buffer = [];
|
||||
fileWorker.terminate();
|
||||
_this._fileCompleteCallback(file);
|
||||
}
|
||||
|
||||
function onError(error) {
|
||||
// an error occurred. Use memory method instead.
|
||||
Logger.error(error);
|
||||
Logger.warn('Failed to process file via service-worker. Do not use Firefox private mode to prevent this.')
|
||||
fileWorker.terminate();
|
||||
_this.processFileViaMemory();
|
||||
}
|
||||
|
||||
sendPart(this._buffer[i], offset);
|
||||
|
||||
fileWorker.onmessage = (e) => {
|
||||
switch (e.data.type) {
|
||||
case "part":
|
||||
onPart(e.data.part);
|
||||
break;
|
||||
case "file":
|
||||
onFile(e.data.file);
|
||||
break;
|
||||
case "error":
|
||||
onError(e.data.error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
48
public/scripts/sw-file-digester.js
Normal file
48
public/scripts/sw-file-digester.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
self.addEventListener('message', async e => {
|
||||
try {
|
||||
switch (e.data.type) {
|
||||
case "part":
|
||||
await this.onPart(e.data.name, e.data.buffer, e.data.offset);
|
||||
break;
|
||||
case "get-file":
|
||||
await this.onGetFile(e.data.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
self.postMessage({type: "error", error: e});
|
||||
}
|
||||
})
|
||||
|
||||
async function getFileHandle(fileName) {
|
||||
const root = await navigator.storage.getDirectory();
|
||||
return await root.getFileHandle(fileName, {create: true});
|
||||
}
|
||||
|
||||
async function getAccessHandle(fileName) {
|
||||
const fileHandle = await getFileHandle(fileName);
|
||||
|
||||
// Create FileSystemSyncAccessHandle on the file.
|
||||
return await fileHandle.createSyncAccessHandle();
|
||||
}
|
||||
|
||||
async function onPart(fileName, buffer, offset) {
|
||||
const accessHandle = await getAccessHandle(fileName);
|
||||
|
||||
// Write the message to the end of the file.
|
||||
let encodedMessage = new DataView(buffer);
|
||||
accessHandle.write(encodedMessage, { at: offset });
|
||||
accessHandle.close();
|
||||
|
||||
self.postMessage({type: "part", part: encodedMessage});
|
||||
encodedMessage = null;
|
||||
}
|
||||
|
||||
async function onGetFile(fileName) {
|
||||
const fileHandle = await getFileHandle(fileName);
|
||||
let file = await fileHandle.getFile();
|
||||
|
||||
self.postMessage({type: "file", file: file});
|
||||
file = null;
|
||||
// Todo: delete file from storage
|
||||
}
|
|
@ -1143,6 +1143,8 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
navigator.share({files: files})
|
||||
.catch(err => {
|
||||
Logger.error(err);
|
||||
// Todo: tidy up, setDownloadButton instead and show warning to user
|
||||
// Differentiate: "File too big to be shared. It can be downloaded instead." and "Error while sharing. It can be downloaded instead."
|
||||
});
|
||||
|
||||
// Prevent clicking the button multiple times
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue