Added automatic file upload resume & skip.

Ylian Saint-Hilaire committed Oct 15, 2021 at 14:46 UTC b260f740536ecb05b81d1cc16c5c84869347fd83
6 files changed +71 -31
agents/meshcore.js
+11 -3
@@ -1865,8 +1865,7 @@ function onTunnelData(data) {
1865 if (this.httprequest.state == 0) {
1866 // Check if this is a relay connection
1867 if ((data == 'c') || (data == 'cr')) { this.httprequest.state = 1; /*sendConsoleText("Tunnel #" + this.httprequest.index + " now active", this.httprequest.sessionid);*/ }
1868 - }
1869 - else {
1868 + } else {
1869 // Handle tunnel data
1870 if (this.httprequest.protocol == 0) { // 1 = Terminal (admin), 2 = Desktop, 5 = Files, 6 = PowerShell (admin), 7 = Plugin Data Exchange, 8 = Terminal (user), 9 = PowerShell (user), 10 = FileTransfer
1871 // Take a look at the protocol
@@ -2648,7 +2647,7 @@ function onTunnelData(data) {
2647 var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
2648 this.httprequest.uploadFilePath = filepath;
2649 this.httprequest.uploadFileSize = 0;
2651 - try { this.httprequest.uploadFile = fs.openSync(filepath, 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
2650 + try { this.httprequest.uploadFile = fs.openSync(filepath, cmd.append ? 'abN' : 'wbN'); } catch (e) { this.write(Buffer.from(JSON.stringify({ action: 'uploaderror', reqid: cmd.reqid }))); break; }
2651 this.httprequest.uploadFileid = cmd.reqid;
2652 if (this.httprequest.uploadFile) { this.write(Buffer.from(JSON.stringify({ action: 'uploadstart', reqid: this.httprequest.uploadFileid }))); }
2653 break;
@@ -2681,6 +2680,15 @@ function onTunnelData(data) {
2680 }
2681 break;
2682 }
2683 + case 'uploadhash':
2684 + {
2685 + // Hash a file
2686 + var filepath = cmd.name ? obj.path.join(cmd.path, cmd.name) : cmd.path;
2687 + var h = null;
2688 + try { h = getSHA384FileHash(filepath); } catch (ex) { sendConsoleText(ex); }
2689 + this.write(Buffer.from(JSON.stringify({ action: 'uploadhash', reqid: cmd.reqid, path: cmd.path, name: cmd.name, tag: cmd.tag, hash: (h ? h.toString('hex') : null) })));
2690 + break
2691 + }
2692 case 'copy':
2693 {
2694 // Copy a bunch of files from scpath to dspath
emails/translations/device-notify_nl.html
+6 -6
@@ -1,20 +1,20 @@
1 -<html><head></head><body><div>[[[SERVERNAME]]] - Device Notification</div>
1 +<html><head></head><body><div>[[[SERVERNAME]]] - Apparaatmelding</div>
2 <div style="font-family:Arial,Helvetica,sans-serif">
3 <table style="background-color:#003366;color:lightgray;width:100%" cellpadding="8">
4 <tbody><tr>
5 <td>
6 - <b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Device Notification</b>
6 + <b style="font-size:20px;font-family:Arial,Helvetica,sans-serif">[[[SERVERNAME]]] - Apparaatmelding</b>
7 </td>
8 </tr>
9 </tbody></table>
10 <area-header>
11 <p>
12 - The following devices have changed their connection state.
12 + De volgende apparaten hebben hun verbindingsstatus gewijzigd.
13 </p>
14 </area-header>
15 <area-connections>
16 <p>
17 - Connected devices:
17 + Verbonden apparaten:
18 </p>
19 <p><b>
20 [[[CONNECTIONS]]]
@@ -22,7 +22,7 @@
22 </area-connections>
23 <area-disconnections>
24 <p>
25 - Disconnected devices:
25 + Losgekoppelde apparaten:
26 </p>
27 <p><b>
28 [[[DISCONNECTIONS]]]
@@ -30,7 +30,7 @@
30 </area-disconnections>
31 <area-footer>
32 <p>
33 - To unsubscribe, <a href="[[[SERVERURL]]][[[UNSUBSCRIBELINK]]]">Klik hier</a> within 1 hour of getting this message.
33 + Uitschrijven, <a href="[[[SERVERURL]]][[[UNSUBSCRIBELINK]]]">Klik hier</a> binnen 1 uur na ontvangst van dit bericht.
34 </p>
35 </area-footer>
36 </div></body></html>
\ No newline at end of file
emails/translations/device-notify_nl.txt
+5 -5
@@ -1,22 +1,22 @@
1 -[[[SERVERNAME]]] - Device Notification
1 +[[[SERVERNAME]]] - Apparaatmelding
2 ~<area-header>
3 -The following devices have changed their connection state.
3 +De volgende apparaten hebben hun verbindingsstatus gewijzigd.
4 ~</area-header>
5 ~<area-connections>
6 ~
7 -Connected devices:
7 +Verbonden apparaten:
8 ~
9 ~[[[CONNECTIONS]]]
10 ~
11 ~</area-connections>
12 ~<area-disconnections>
13 ~
14 -Disconnected devices:
14 +Losgekoppelde apparaten:
15 ~
16 ~[[[DISCONNECTIONS]]]
17 ~
18 ~</area-disconnections>
19 ~<area-footer>
20
21 -To unsubscribe, load this link within 1 hour of getting this message: [[[SERVERURL]]][[[UNSUBSCRIBELINK]]]
21 +Om je af te melden, open je deze link binnen 1 uur nadat je dit bericht hebt ontvangen: [[[SERVERURL]]][[[UNSUBSCRIBELINK]]]
22 ~</area-footer>
\ No newline at end of file
public/scripts/common-0.0.1.js
+14 -14
@@ -43,19 +43,19 @@ function ArrayElementMove(arr, from, to) { arr.splice(to, 0, arr.splice(from, 1)
43 // Print object for HTML
44 function ObjectToStringEx(x, c) {
45 var r = "";
46 - if (x != 0 && (!x || x == null)) return "(Null)";
47 - if (x instanceof Array) { for (var i in x) { r += '<br />' + gap(c) + "Item #" + i + ": " + ObjectToStringEx(x[i], c + 1); } }
48 - else if (x instanceof Object) { for (var i in x) { r += '<br />' + gap(c) + i + " = " + ObjectToStringEx(x[i], c + 1); } }
46 + if (x != 0 && (!x || x == null)) return '(Null)';
47 + if (x instanceof Array) { for (var i in x) { r += '<br />' + gap(c) + 'Item #' + i + ": " + ObjectToStringEx(x[i], c + 1); } }
48 + else if (x instanceof Object) { for (var i in x) { r += '<br />' + gap(c) + i + ' = ' + ObjectToStringEx(x[i], c + 1); } }
49 else { r += EscapeHtml(x); }
50 return r;
51 }
52
53 // Print object for console
54 function ObjectToStringEx2(x, c) {
55 - var r = "";
56 - if (x != 0 && (!x || x == null)) return "(Null)";
57 - if (x instanceof Array) { for (var i in x) { r += '\r\n' + gap2(c) + "Item #" + i + ": " + ObjectToStringEx2(x[i], c + 1); } }
58 - else if (x instanceof Object) { for (var i in x) { r += '\r\n' + gap2(c) + i + " = " + ObjectToStringEx2(x[i], c + 1); } }
55 + var r = '';
56 + if (x != 0 && (!x || x == null)) return '(Null)';
57 + if (x instanceof Array) { for (var i in x) { r += '\r\n' + gap2(c) + 'Item #' + i + ': ' + ObjectToStringEx2(x[i], c + 1); } }
58 + else if (x instanceof Object) { for (var i in x) { r += '\r\n' + gap2(c) + i + ' = ' + ObjectToStringEx2(x[i], c + 1); } }
59 else { r += EscapeHtml(x); }
60 return r;
61 }
@@ -70,7 +70,7 @@ function ObjectToString2(x) { return ObjectToStringEx2(x, 0); }
70
71 // Convert a hex string to a raw string
72 function hex2rstr(d) {
73 - if (typeof d != "string" || d.length == 0) return '';
73 + if (typeof d != 'string' || d.length == 0) return '';
74 var r = '', m = ('' + d).match(/../g), t;
75 while (t = m.shift()) r += String.fromCharCode('0x' + t);
76 return r
@@ -107,7 +107,7 @@ function random(max) { return Math.floor(Math.random() * max); }
107 function trademarks(x) { return x.replace(/\(R\)/g, '&reg;').replace(/\(TM\)/g, '&trade;'); }
108
109 // Pad a number with zeros on the left
110 -function zeroPad(num, c) { if (c == null) { c = 2; } var s = "00000000" + num; return s.substr(s.length - c); }
110 +function zeroPad(num, c) { if (c == null) { c = 2; } var s = '00000000' + num; return s.substr(s.length - c); }
111
112 // String validation
113 function isAlphaNumeric(str) { if (typeof str == 'number') { return true; } return (str.match(/^[A-Za-z0-9]+$/) != null); };
@@ -135,10 +135,10 @@ function parseUriArgs() {
135 // From: https://stackoverflow.com/questions/5573096/detecting-webp-support
136 function check_webp_feature(feature, callback) {
137 var kTestImages = {
138 - lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA"//,
139 - //lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
140 - //alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
141 - //animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"
138 + lossy: 'UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'//,
139 + //lossless: 'UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==',
140 + //alpha: 'UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==',
141 + //animation: 'UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA'
142 };
143 var img = new Image();
144 img.onload = function () {
@@ -148,5 +148,5 @@ function check_webp_feature(feature, callback) {
148 img.onerror = function () {
149 callback(feature, false);
150 };
151 - img.src = "data:image/webp;base64," + kTestImages[feature];
151 + img.src = 'data:image/webp;base64,' + kTestImages[feature];
152 }
\ No newline at end of file
translate/translate.json
+1 -1
@@ -68697,4 +68697,4 @@
68697 ]
68698 }
68699 ]
68700 -}
68700 +}
\ No newline at end of file
views/default.handlebars
+34 -2
@@ -9689,7 +9689,7 @@
9689 function p13folderup(x) {
9690 if (x == null) { p13filetreelocation.pop(); } else { while (p13filetreelocation.length > x) { p13filetreelocation.pop(); } }
9691 p13targetpath = p13filetreelocation.join('/');
9692 - files.sendText({ action: 'ls', reqid: 1, path: p13targetpath });
9692 + if (files) { files.sendText({ action: 'ls', reqid: 1, path: p13targetpath }); }
9693 return false;
9694 }
9695
@@ -10004,6 +10004,12 @@
10004 p13uploadNextFile();
10005 }
10006
10007 + // Perform SHA-384 hashing
10008 + const byteToHex = [];
10009 + for (var n = 0; n <= 0xff; ++n) { var hexOctet = n.toString(16).padStart(2, '0'); byteToHex.push(hexOctet); }
10010 + function arrayBufferToHex(arrayBuffer) { return Array.prototype.map.call( new Uint8Array(arrayBuffer), n => byteToHex[n] ).join(''); }
10011 + function performHash(data, f) { window.crypto.subtle.digest('SHA-384', data).then(function (v) { f(arrayBufferToHex(v)); }, function() { f(null); }); }
10012 +
10013 // Push the next file
10014 function p13uploadNextFile() {
10015 uploadFile.xfilePtr++;
@@ -10018,7 +10024,17 @@
10024 uploadFile.xreader = new FileReader();
10025 uploadFile.xreader.onload = function () {
10026 uploadFile.xdata = uploadFile.xreader.result;
10021 - files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength }));
10027 +
10028 + // If the remote file already exists and is smaller then our file, see if we can resume the trasfer
10029 + var f = null;
10030 + for (var i in p13filetree.dir) { if (p13filetree.dir[i].n == file.name) { f = p13filetree.dir[i]; } }
10031 + if ((f != null) && (f.s <= uploadFile.xreader.result.byteLength)) {
10032 + performHash(uploadFile.xreader.result.slice(0, f.s), function(hash) {
10033 + 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.xreader.result.byteLength } }));
10034 + });
10035 + } else {
10036 + files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength }));
10037 + }
10038 };
10039 uploadFile.xreader.readAsArrayBuffer(file);
10040 } else {
@@ -10052,6 +10068,22 @@
10068 case 'uploadack': { p13uploadNextPart(false); break; }
10069 case 'uploaddone': { if (uploadFile.xfiles.length > uploadFile.xfilePtr + 1) { p13uploadNextFile(); } else { p13uploadFileTransferDone(); } break; }
10070 case 'uploaderror': { p13uploadFileCancel(); break; }
10071 + case 'uploadhash': {
10072 + var file = uploadFile.xfiles[uploadFile.xfilePtr];
10073 + if (file) {
10074 + if (cmd.tag.h === cmd.hash) {
10075 + if (cmd.tag.skip) {
10076 + p13uploadNextFile();
10077 + } else {
10078 + uploadFile.xptr = cmd.tag.s;
10079 + files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength, append: true }));
10080 + }
10081 + } else {
10082 + files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength, append: false }));
10083 + }
10084 + }
10085 + break;
10086 + }
10087 }
10088 }
10089