mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2025-04-20 15:06:15 -04:00
Add state management to network peers
This commit is contained in:
parent
3dd40e238a
commit
1d62a9ff49
3 changed files with 116 additions and 44 deletions
|
@ -180,6 +180,7 @@
|
||||||
"waiting": "Waiting…",
|
"waiting": "Waiting…",
|
||||||
"processing": "Processing…",
|
"processing": "Processing…",
|
||||||
"transferring": "Transferring…",
|
"transferring": "Transferring…",
|
||||||
"receiving": "Receiving…"
|
"receiving": "Receiving…",
|
||||||
|
"complete": "Transfer complete"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,14 +329,14 @@ class Peer {
|
||||||
this._filesQueue = [];
|
this._filesQueue = [];
|
||||||
this._busy = false;
|
this._busy = false;
|
||||||
|
|
||||||
|
this._state = 'idle'; // 'idle', 'prepare', 'wait', 'receive', 'transfer', 'text-sent'
|
||||||
|
|
||||||
// evaluate auto accept
|
// evaluate auto accept
|
||||||
this._evaluateAutoAccept();
|
this._evaluateAutoAccept();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is overwritten in expanding classes
|
|
||||||
_onServerSignalMessage(message) {}
|
_onServerSignalMessage(message) {}
|
||||||
|
|
||||||
// Is overwritten in expanding classes
|
|
||||||
_refresh() {}
|
_refresh() {}
|
||||||
|
|
||||||
_onDisconnected() {}
|
_onDisconnected() {}
|
||||||
|
@ -345,10 +345,8 @@ class Peer {
|
||||||
this._isCaller = isCaller;
|
this._isCaller = isCaller;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is overwritten in expanding classes
|
|
||||||
_sendMessage(message) {}
|
_sendMessage(message) {}
|
||||||
|
|
||||||
// Is overwritten in expanding classes
|
|
||||||
_sendData(data) {}
|
_sendData(data) {}
|
||||||
|
|
||||||
_sendDisplayName(displayName) {
|
_sendDisplayName(displayName) {
|
||||||
|
@ -444,10 +442,28 @@ class Peer {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPeerConnected() {
|
_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
|
// Reconnection during receiving of file. Send request for restart
|
||||||
const offset = this._digester._bytesReceived;
|
const offset = this._digester._bytesReceived;
|
||||||
this._sendResendRequest(offset);
|
this._sendResendRequest(offset);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._state === "transfer" && peerState !== "receive") {
|
||||||
|
this._abortTransfer();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,7 +471,8 @@ class Peer {
|
||||||
let header = [];
|
let header = [];
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
let imagesOnly = true
|
let imagesOnly = true
|
||||||
for (let i=0; i<files.length; i++) {
|
this._state = 'prepare';
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0.8*i/files.length, status: 'prepare'})
|
Events.fire('set-progress', {peerId: this._peerId, progress: 0.8*i/files.length, status: 'prepare'})
|
||||||
header.push({
|
header.push({
|
||||||
name: files[i].name,
|
name: files[i].name,
|
||||||
|
@ -488,6 +505,7 @@ class Peer {
|
||||||
thumbnailDataUrl: dataUrl
|
thumbnailDataUrl: dataUrl
|
||||||
});
|
});
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'wait'})
|
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'wait'})
|
||||||
|
this._state = 'wait';
|
||||||
}
|
}
|
||||||
|
|
||||||
sendFiles() {
|
sendFiles() {
|
||||||
|
@ -527,7 +545,7 @@ class Peer {
|
||||||
|
|
||||||
|
|
||||||
_onResendRequest(offset) {
|
_onResendRequest(offset) {
|
||||||
if (!this._chunker) {
|
if (this._state !== 'transfer' || !this._chunker) {
|
||||||
this._sendTransferAbortion();
|
this._sendTransferAbortion();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -549,7 +567,7 @@ class Peer {
|
||||||
this._onFilesTransferRequest(message);
|
this._onFilesTransferRequest(message);
|
||||||
break;
|
break;
|
||||||
case 'header':
|
case 'header':
|
||||||
this._onFileHeader(message);
|
this._onHeader(message);
|
||||||
break;
|
break;
|
||||||
case 'progress':
|
case 'progress':
|
||||||
this._onProgress(message.progress);
|
this._onProgress(message.progress);
|
||||||
|
@ -564,7 +582,7 @@ class Peer {
|
||||||
this._onFileTransferRequestResponded(message);
|
this._onFileTransferRequestResponded(message);
|
||||||
break;
|
break;
|
||||||
case 'file-transfer-complete':
|
case 'file-transfer-complete':
|
||||||
this._onFileTransferCompleted(message);
|
this._onFileTransferComplete(message);
|
||||||
break;
|
break;
|
||||||
case 'message-transfer-complete':
|
case 'message-transfer-complete':
|
||||||
this._onMessageTransferCompleted();
|
this._onMessageTransferCompleted();
|
||||||
|
@ -575,6 +593,9 @@ class Peer {
|
||||||
case 'display-name-changed':
|
case 'display-name-changed':
|
||||||
this._onDisplayNameChanged(message);
|
this._onDisplayNameChanged(message);
|
||||||
break;
|
break;
|
||||||
|
case 'state':
|
||||||
|
this._onReceiveState(message.state);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Logger.warn('RTC: Unknown message type:', message.type);
|
Logger.warn('RTC: Unknown message type:', message.type);
|
||||||
}
|
}
|
||||||
|
@ -613,12 +634,13 @@ class Peer {
|
||||||
this._pendingRequest = null;
|
this._pendingRequest = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFileHeader(header) {
|
_onHeader(header) {
|
||||||
if (!this._acceptedRequest || !this._acceptedRequest.header.length) {
|
if (!this._acceptedRequest || !this._acceptedRequest.header.length) {
|
||||||
this._sendTransferAbortion();
|
this._sendTransferAbortion();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._state = 'receive';
|
||||||
this._lastProgress = 0;
|
this._lastProgress = 0;
|
||||||
this._timeStart = Date.now();
|
this._timeStart = Date.now();
|
||||||
this._addFileDigester(header);
|
this._addFileDigester(header);
|
||||||
|
@ -631,16 +653,18 @@ class Peer {
|
||||||
}
|
}
|
||||||
|
|
||||||
_abortTransfer() {
|
_abortTransfer() {
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
|
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: null});
|
||||||
Events.fire('notify-user', Localization.getTranslation("notifications.files-incorrect"));
|
this._state = 'idle';
|
||||||
this._filesReceived = [];
|
this._busy = false;
|
||||||
this._requestAccepted = null;
|
this._chunker = null;
|
||||||
|
this._pendingRequest = null;
|
||||||
|
this._acceptedRequest = null;
|
||||||
this._digester = null;
|
this._digester = null;
|
||||||
throw new Error("Received files differ from requested files. Abort!");
|
this._filesReceived = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
_onChunkReceived(chunk) {
|
_onChunkReceived(chunk) {
|
||||||
if(!this._digester || !(chunk.byteLength || chunk.size)) return;
|
if(this._state !== 'receive' || !this._digester || !(chunk.byteLength || chunk.size)) return;
|
||||||
|
|
||||||
this._digester.unchunk(chunk);
|
this._digester.unchunk(chunk);
|
||||||
|
|
||||||
|
@ -648,6 +672,7 @@ class Peer {
|
||||||
|
|
||||||
if (progress > 1) {
|
if (progress > 1) {
|
||||||
this._abortTransfer();
|
this._abortTransfer();
|
||||||
|
Logger.error("Too many bytes received. Abort!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -665,42 +690,46 @@ class Peer {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onProgress(progress) {
|
_onProgress(progress) {
|
||||||
|
if (this._state !== 'transfer') return;
|
||||||
|
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'transfer'});
|
Events.fire('set-progress', {peerId: this._peerId, progress: progress, status: 'transfer'});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onReceiveConfirmation(bytesReceived) {
|
_onReceiveConfirmation(bytesReceived) {
|
||||||
if (!this._chunker) return;
|
if (!this._chunker || this._state !== 'transfer') return;
|
||||||
this._chunker._onReceiveConfirmation(bytesReceived);
|
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();
|
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 sameSize = file.size === acceptedHeader.size;
|
||||||
const sameName = file.name === acceptedHeader.name
|
const sameName = file.name === acceptedHeader.name
|
||||||
if (!sameSize || !sameName) {
|
return sameSize && sameName;
|
||||||
this._abortTransfer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_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
|
// include for compatibility with 'Snapdrop & PairDrop for Android' app
|
||||||
Events.fire('file-received', file);
|
Events.fire('file-received', file);
|
||||||
|
|
||||||
this._filesReceived.push(file);
|
this._filesReceived.push(file);
|
||||||
|
}
|
||||||
|
|
||||||
if (this._acceptedRequest.header.length) return;
|
_allFilesTransferComplete() {
|
||||||
|
|
||||||
// We are done receiving
|
|
||||||
this._busy = false;
|
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'process'});
|
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'process'});
|
||||||
|
this._state = 'idle';
|
||||||
Events.fire('files-received', {
|
Events.fire('files-received', {
|
||||||
peerId: this._peerId,
|
peerId: this._peerId,
|
||||||
files: this._filesReceived,
|
files: this._filesReceived,
|
||||||
|
@ -708,15 +737,43 @@ class Peer {
|
||||||
totalSize: this._acceptedRequest.totalSize
|
totalSize: this._acceptedRequest.totalSize
|
||||||
});
|
});
|
||||||
this._filesReceived = [];
|
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;
|
this._chunker = null;
|
||||||
|
|
||||||
if (!message.success) {
|
if (!message.success) {
|
||||||
Logger.warn('File could not be sent');
|
Logger.warn('File could not be sent');
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null});
|
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: null});
|
||||||
|
|
||||||
|
this._state = 'idle';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -728,37 +785,49 @@ class Peer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// No more files in queue. Transfer is complete
|
// No more files in queue. Transfer is complete
|
||||||
|
this._state = 'idle';
|
||||||
this._busy = false;
|
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('notify-user', Localization.getTranslation("notifications.file-transfer-completed"));
|
||||||
Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app
|
Events.fire('files-sent'); // used by 'Snapdrop & PairDrop for Android' app
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFileTransferRequestResponded(message) {
|
_onFileTransferRequestResponded(message) {
|
||||||
if (!message.accepted) {
|
if (!message.accepted || this._state !== 'wait') {
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'wait'});
|
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: null});
|
||||||
|
this._state = 'idle';
|
||||||
this._filesRequested = null;
|
this._filesRequested = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Events.fire('file-transfer-accepted');
|
Events.fire('file-transfer-accepted');
|
||||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'transfer'});
|
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'transfer'});
|
||||||
|
this._state = 'transfer';
|
||||||
this.sendFiles();
|
this.sendFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onMessageTransferCompleted() {
|
_onMessageTransferCompleted() {
|
||||||
|
if (this._state !== 'text-sent') return;
|
||||||
|
this._state = 'idle';
|
||||||
Events.fire('notify-user', Localization.getTranslation("notifications.message-transfer-completed"));
|
Events.fire('notify-user', Localization.getTranslation("notifications.message-transfer-completed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendText(text) {
|
sendText(text) {
|
||||||
|
this._state = 'text-sent';
|
||||||
const unescaped = btoa(unescape(encodeURIComponent(text)));
|
const unescaped = btoa(unescape(encodeURIComponent(text)));
|
||||||
this._sendMessage({ type: 'text', text: unescaped });
|
this._sendMessage({ type: 'text', text: unescaped });
|
||||||
}
|
}
|
||||||
|
|
||||||
_onTextReceived(message) {
|
_onTextReceived(message) {
|
||||||
if (!message.text) return;
|
if (!message.text) return;
|
||||||
|
try {
|
||||||
const escaped = decodeURIComponent(escape(atob(message.text)));
|
const escaped = decodeURIComponent(escape(atob(message.text)));
|
||||||
Events.fire('text-received', { text: escaped, peerId: this._peerId });
|
Events.fire('text-received', { text: escaped, peerId: this._peerId });
|
||||||
this._sendMessage({ type: 'message-transfer-complete' });
|
this._sendMessage({ type: 'message-transfer-complete' });
|
||||||
}
|
}
|
||||||
|
catch (e) {
|
||||||
|
Logger.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onDisplayNameChanged(message) {
|
_onDisplayNameChanged(message) {
|
||||||
const displayNameHasChanged = message.displayName !== this._displayName;
|
const displayNameHasChanged = message.displayName !== this._displayName;
|
||||||
|
@ -1133,7 +1202,6 @@ class RTCPeer extends Peer {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _sendFile(file) {
|
async _sendFile(file) {
|
||||||
this._sendHeader(file);
|
|
||||||
this._chunker = new FileChunkerRTC(
|
this._chunker = new FileChunkerRTC(
|
||||||
file,
|
file,
|
||||||
chunk => this._sendData(chunk),
|
chunk => this._sendData(chunk),
|
||||||
|
@ -1141,6 +1209,8 @@ class RTCPeer extends Peer {
|
||||||
this._dataChannel
|
this._dataChannel
|
||||||
);
|
);
|
||||||
this._chunker._readChunk();
|
this._chunker._readChunk();
|
||||||
|
this._sendHeader(file);
|
||||||
|
this._state = 'transfer';
|
||||||
}
|
}
|
||||||
|
|
||||||
_onMessage(message) {
|
_onMessage(message) {
|
||||||
|
|
|
@ -715,7 +715,8 @@ class PeerUI {
|
||||||
"transfer": Localization.getTranslation("peer-ui.transferring"),
|
"transfer": Localization.getTranslation("peer-ui.transferring"),
|
||||||
"receive": Localization.getTranslation("peer-ui.receiving"),
|
"receive": Localization.getTranslation("peer-ui.receiving"),
|
||||||
"process": Localization.getTranslation("peer-ui.processing"),
|
"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];
|
}[status];
|
||||||
|
|
||||||
this.$el.setAttribute('status', status);
|
this.$el.setAttribute('status', status);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue