Fixed large file upload to device on mobile site.

Ylian Saint-Hilaire committed Dec 16, 2021 at 16:28 UTC 7333c0065053f8741971a09ee9ea4eced4edc1e3
1 file changed +58 -17
views/default-mobile.handlebars
+58 -17
@@ -5418,6 +5418,19 @@
5418 p13uploadNextFile();
5419 }
5420
5421 + // Perform SHA-384 hashing
5422 + const byteToHex = [];
5423 + for (var n = 0; n <= 0xff; ++n) { var hexOctet = n.toString(16).padStart(2, '0'); byteToHex.push(hexOctet); }
5424 + function arrayBufferToHex(arrayBuffer) { return Array.prototype.map.call(new Uint8Array(arrayBuffer), n => byteToHex[n]).join(''); }
5425 + function performHash(data, f) { window.crypto.subtle.digest('SHA-384', data).then(function (v) { f(arrayBufferToHex(v)); }, function () { f(null); }); }
5426 + function performHashOnFile(file, f) {
5427 + // TODO: At some point, try to make this work for files of unlimited size using a digest stream
5428 + var reader = new FileReader();
5429 + reader.onerror = function (err) { f(null); }
5430 + reader.onload = function () { window.crypto.subtle.digest('SHA-384', reader.result).then(function (v) { f(arrayBufferToHex(v)); }, function () { f(null); }); };
5431 + reader.readAsArrayBuffer(file);
5432 + }
5433 +
5434 // Push the next file
5435 function p13uploadNextFile() {
5436 uploadFile.xfilePtr++;
@@ -5428,13 +5441,15 @@
5441 Q('d2progressBar').max = file.size;
5442 Q('d2progressBar').value = 0;
5443 if (file.xdata == null) {
5431 - // Load the data
5432 - uploadFile.xreader = new FileReader();
5433 - uploadFile.xreader.onload = function () {
5434 - uploadFile.xdata = uploadFile.xreader.result;
5435 - files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength }));
5436 - };
5437 - uploadFile.xreader.readAsArrayBuffer(file);
5444 + uploadFile.xfile = file;
5445 + // If the remote file already exists and is smaller then our file, see if we can resume the trasfer
5446 + var f = null;
5447 + for (var i in p13filetree.dir) { if (p13filetree.dir[i].n == file.name) { f = p13filetree.dir[i]; } }
5448 + if ((f != null) && (f.s <= uploadFile.xfile.size)) {
5449 + performHashOnFile(uploadFile.xfile, function (hash) { files.sendText(JSON.stringify({ action: 'uploadhash', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, tag: { h: hash.toUpperCase(), s: f.s, skip: f.s == uploadFile.xfile.size } })); });
5450 + } else {
5451 + files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xfile.size }));
5452 + }
5453 } else {
5454 // Data already loaded
5455 uploadFile.xdata = file.xdata;
@@ -5462,24 +5477,44 @@
5477 function p13gotUploadData(cmd) {
5478 if ((uploadFile == null) || (parseInt(uploadFile.xfilePtr) != parseInt(cmd.reqid))) { return; }
5479 switch (cmd.action) {
5465 - case 'uploadstart': { p13uploadNextPart(false); for (var i = 0; i < 8; i++) { p13uploadNextPart(true); } break; } // Send 8 more blocks of 16k to fill the websocket.
5480 + case 'uploadstart': { uploadFile.xdataPriming = 8; p13uploadNextPart(false); break; } // Send 8 more blocks of 16k to fill the websocket.
5481 case 'uploadack': { p13uploadNextPart(false); break; }
5482 case 'uploaddone': { if (uploadFile.xfiles.length > uploadFile.xfilePtr + 1) { p13uploadNextFile(); } else { p13uploadFileTransferDone(); } break; }
5483 case 'uploaderror': { p13uploadFileCancel(); break; }
5484 + case 'uploadhash': {
5485 + var file = uploadFile.xfiles[uploadFile.xfilePtr];
5486 + if (file) {
5487 + if (cmd.tag.h === cmd.hash) {
5488 + if (cmd.tag.skip) {
5489 + p13uploadNextFile();
5490 + } else {
5491 + uploadFile.xptr = cmd.tag.s;
5492 + files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xfile.size, append: true }));
5493 + }
5494 + } else {
5495 + files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xfile.size, append: false }));
5496 + }
5497 + }
5498 + break;
5499 + }
5500 }
5501 }
5502
5503 // Push the next part of the file into the websocket. If dataPriming is true, push more data only if it's not the last block of the file.
5504 function p13uploadNextPart(dataPriming) {
5474 - var data = uploadFile.xdata, start = uploadFile.xptr;
5475 - if (start >= data.byteLength) {
5476 - files.sendText(JSON.stringify({ action: 'uploaddone', reqid: uploadFile.xfilePtr }));
5477 - } else {
5478 - var end = uploadFile.xptr + 16384;
5479 - if (end > data.byteLength) { if (dataPriming == true) { return; } end = data.byteLength; }
5480 - var dataslice = new Uint8Array(data.slice(start, end))
5505 + if (uploadFile.xreader != null) return; // Data reading already in process
5506 + if (uploadFile.xptr >= uploadFile.xfile.size) return;
5507 + var end = uploadFile.xptr + 16384;
5508 + if (end > uploadFile.xfile.size) { if (dataPriming == true) { return; } end = uploadFile.xfile.size; }
5509 + uploadFile.xreader = new FileReader();
5510 + uploadFile.xreader.onerror = function (err) { console.log(err); }
5511 + uploadFile.xreader.onload = function () {
5512 + var data = uploadFile.xreader.result;
5513 + delete uploadFile.xreader;
5514 + if (data == null) return;
5515 + var dataslice = new Uint8Array(data)
5516 if ((dataslice[0] == 123) || (dataslice[0] == 0)) {
5482 - var datapart = new Uint8Array(end - start + 1);
5517 + var datapart = new Uint8Array(data.byteLength + 1);
5518 datapart.set(dataslice, 1); // Add a zero char at the start of the send, this will indicate that it's not a JSON command.
5519 files.send(datapart);
5520 } else {
@@ -5487,7 +5522,13 @@
5522 }
5523 uploadFile.xptr = end;
5524 Q('d2progressBar').value = end;
5490 - }
5525 + if (uploadFile.xptr >= uploadFile.xfile.size) {
5526 + files.sendText(JSON.stringify({ action: 'uploaddone', reqid: uploadFile.xfilePtr }));
5527 + } else {
5528 + if (uploadFile.xdataPriming > 0) { uploadFile.xdataPriming--; p13uploadNextPart(true); }
5529 + }
5530 + };
5531 + uploadFile.xreader.readAsArrayBuffer(uploadFile.xfile.slice(uploadFile.xptr, end));
5532 }
5533
5534 //