From 1d62a9ff49d27619969b2c6ccbe4f09944af8440 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Tue, 6 Feb 2024 04:36:52 +0100 Subject: [PATCH] Add state management to network peers --- public/lang/en.json | 3 +- public/scripts/network.js | 154 +++++++++++++++++++++++++++----------- public/scripts/ui.js | 3 +- 3 files changed, 116 insertions(+), 44 deletions(-) diff --git a/public/lang/en.json b/public/lang/en.json index 0f1c542..0aa810a 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -180,6 +180,7 @@ "waiting": "Waiting…", "processing": "Processing…", "transferring": "Transferring…", - "receiving": "Receiving…" + "receiving": "Receiving…", + "complete": "Transfer complete" } } diff --git a/public/scripts/network.js b/public/scripts/network.js index 37c4355..5ae3f8d 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -329,14 +329,14 @@ class Peer { this._filesQueue = []; this._busy = false; + this._state = 'idle'; // 'idle', 'prepare', 'wait', 'receive', 'transfer', 'text-sent' + // evaluate auto accept this._evaluateAutoAccept(); } - // Is overwritten in expanding classes _onServerSignalMessage(message) {} - // Is overwritten in expanding classes _refresh() {} _onDisconnected() {} @@ -345,10 +345,8 @@ class Peer { this._isCaller = isCaller; } - // Is overwritten in expanding classes _sendMessage(message) {} - // Is overwritten in expanding classes _sendData(data) {} _sendDisplayName(displayName) { @@ -444,10 +442,28 @@ class Peer { } _onPeerConnected() { - if (this._digester) { + this._sendCurrentState(); + } + + _sendCurrentState() { + this._sendMessage({type: 'state', state: this._state}) + } + + _onReceiveState(peerState) { + if (this._state === "receive") { + if (peerState !== "transfer" || !this._digester) { + this._abortTransfer(); + return; + } // Reconnection during receiving of file. Send request for restart const offset = this._digester._bytesReceived; this._sendResendRequest(offset); + return + } + + if (this._state === "transfer" && peerState !== "receive") { + this._abortTransfer(); + return; } } @@ -455,7 +471,8 @@ class Peer { let header = []; let totalSize = 0; let imagesOnly = true - for (let i=0; i 1) { this._abortTransfer(); + Logger.error("Too many bytes received. Abort!"); return; } @@ -665,42 +690,46 @@ class Peer { } _onProgress(progress) { + if (this._state !== 'transfer') return; + Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'transfer'}); } _onReceiveConfirmation(bytesReceived) { - if (!this._chunker) return; + if (!this._chunker || this._state !== 'transfer') return; this._chunker._onReceiveConfirmation(bytesReceived); } - async _onFileReceived(file) { + _fitsHeader(file) { + if (!this._acceptedRequest) { + return false; + } + + // Check if file fits to header const acceptedHeader = this._acceptedRequest.header.shift(); - this._totalBytesReceived += file.size; - - let duration = (Date.now() - this._timeStart) / 1000; - let size = Math.round(10 * file.size / 1000000) / 10; - let speed = Math.round(100 * file.size / 1000000 / duration) / 100; - - Logger.log(`File received.\n\nSize: ${size} MB\tDuration: ${duration} s\tSpeed: ${speed} MB/s`); - - this._sendMessage({type: 'file-transfer-complete', success: true, size: size, duration: duration, speed: speed}); const sameSize = file.size === acceptedHeader.size; const sameName = file.name === acceptedHeader.name - if (!sameSize || !sameName) { - this._abortTransfer(); - } + return sameSize && sameName; + } + + _logTransferSpeed(size, duration, speed) { + Logger.log(`File received.\n\nSize: ${size} MB\tDuration: ${duration} s\tSpeed: ${speed} MB/s`); + } + + _singleFileTransferComplete(file, duration, size, speed) { + this._totalBytesReceived += file.size; + this._sendMessage({type: 'file-transfer-complete', success: true, duration: duration, size: size, speed: speed}); // include for compatibility with 'Snapdrop & PairDrop for Android' app Events.fire('file-received', file); this._filesReceived.push(file); + } - if (this._acceptedRequest.header.length) return; - - // We are done receiving - this._busy = false; + _allFilesTransferComplete() { Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'process'}); + this._state = 'idle'; Events.fire('files-received', { peerId: this._peerId, files: this._filesReceived, @@ -708,15 +737,43 @@ class Peer { totalSize: this._acceptedRequest.totalSize }); this._filesReceived = []; - this._requestAccepted = null; + this._acceptedRequest = null; + this._busy = false; } - _onFileTransferCompleted(message) { + async _onFileReceived(file) { + if (!this._fitsHeader(file)) { + this._abortTransfer(); + Events.fire('notify-user', Localization.getTranslation("notifications.files-incorrect")); + Logger.error("Received files differ from requested files. Abort!"); + return; + } + + const duration = (Date.now() - this._timeStart) / 1000; + const size = Math.round(10 * file.size / 1000000) / 10; + const speed = Math.round(100 * file.size / 1000000 / duration) / 100; + + // Log speed + this._logTransferSpeed(duration, size, speed); + + // File transfer complete + this._singleFileTransferComplete(file, duration, size, speed); + + if (this._acceptedRequest.header.length) return; + + // We are done receiving + Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'transfer'}); + this._allFilesTransferComplete(); + } + + _onFileTransferComplete(message) { this._chunker = null; if (!message.success) { Logger.warn('File could not be sent'); Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null}); + + this._state = 'idle'; return; } @@ -728,36 +785,48 @@ class Peer { } // No more files in queue. Transfer is complete + this._state = 'idle'; this._busy = false; + Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'complete'}); Events.fire('notify-user', Localization.getTranslation("notifications.file-transfer-completed")); Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app } _onFileTransferRequestResponded(message) { - if (!message.accepted) { - Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'}); + if (!message.accepted || this._state !== 'wait') { + Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: null}); + this._state = 'idle'; this._filesRequested = null; return; } Events.fire('file-transfer-accepted'); Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'transfer'}); + this._state = 'transfer'; this.sendFiles(); } _onMessageTransferCompleted() { + if (this._state !== 'text-sent') return; + this._state = 'idle'; Events.fire('notify-user', Localization.getTranslation("notifications.message-transfer-completed")); } sendText(text) { + this._state = 'text-sent'; const unescaped = btoa(unescape(encodeURIComponent(text))); this._sendMessage({ type: 'text', text: unescaped }); } _onTextReceived(message) { if (!message.text) return; - const escaped = decodeURIComponent(escape(atob(message.text))); - Events.fire('text-received', { text: escaped, peerId: this._peerId }); - this._sendMessage({ type: 'message-transfer-complete' }); + try { + const escaped = decodeURIComponent(escape(atob(message.text))); + Events.fire('text-received', { text: escaped, peerId: this._peerId }); + this._sendMessage({ type: 'message-transfer-complete' }); + } + catch (e) { + Logger.error(e); + } } _onDisplayNameChanged(message) { @@ -1133,7 +1202,6 @@ class RTCPeer extends Peer { } async _sendFile(file) { - this._sendHeader(file); this._chunker = new FileChunkerRTC( file, chunk => this._sendData(chunk), @@ -1141,6 +1209,8 @@ class RTCPeer extends Peer { this._dataChannel ); this._chunker._readChunk(); + this._sendHeader(file); + this._state = 'transfer'; } _onMessage(message) { diff --git a/public/scripts/ui.js b/public/scripts/ui.js index cfe8532..f20d0d9 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -715,7 +715,8 @@ class PeerUI { "transfer": Localization.getTranslation("peer-ui.transferring"), "receive": Localization.getTranslation("peer-ui.receiving"), "process": Localization.getTranslation("peer-ui.processing"), - "wait": Localization.getTranslation("peer-ui.waiting") + "wait": Localization.getTranslation("peer-ui.waiting"), + "complete": Localization.getTranslation("peer-ui.complete") }[status]; this.$el.setAttribute('status', status);