diff --git a/src/components/pages/NodeFilesPage.vue b/src/components/pages/NodeFilesPage.vue
index 46d3069..89382f2 100644
--- a/src/components/pages/NodeFilesPage.vue
+++ b/src/components/pages/NodeFilesPage.vue
@@ -75,12 +75,12 @@
-
+
-
+
@@ -117,12 +117,12 @@ import NodeIcon from "../nodes/NodeIcon.vue";
import Page from "./Page.vue";
import NodeUtils from "../../js/NodeUtils.js";
import NodeDropDownMenu from "../nodes/NodeDropDownMenu.vue";
-import NodeAPI from "../../js/NodeAPI.js";
import TextButton from "../TextButton.vue";
import DialogUtils from "../../js/DialogUtils.js";
import SaveButton from "../SaveButton.vue";
import FileTransferAPI from "../../js/FileTransferAPI.js";
import IconButton from "../IconButton.vue";
+import FileTransferrer from "../../js/FileTransferrer.js";
export default {
name: 'NodeFilesPage',
@@ -197,61 +197,24 @@ export default {
return;
}
- // generate random file transfer id
- const fileTransferId = NodeAPI.generatePacketId();
-
- // get file details
- const to = parseInt(this.nodeId);
- const fileName = file.name;
- const fileData = new Uint8Array(await file.arrayBuffer());
- const fileSize = fileData.length;
-
+ // offer file
try {
-
- // add to file transfers list
- GlobalState.fileTransfers.push({
- id: fileTransferId,
- to: to,
- from: GlobalState.myNodeId,
- direction: "outgoing",
- status: "offering",
- filename: fileName,
- filesize: fileSize,
- progress: 0,
- data: fileData,
- });
-
- // send data
- await FileTransferAPI.sendFileTransferRequest(to, fileTransferId, fileName, fileSize);
-
+ await FileTransferrer.offerFileTransfer(this.nodeId, file);
} catch(e) {
DialogUtils.showErrorAlert(e);
}
+
},
async acceptFileTransfer(fileTransfer) {
try {
-
- // mark as accepted
- fileTransfer.status = "accepted";
-
- // tell remote node we rejected file transfer
- await FileTransferAPI.acceptFileTransfer(fileTransfer.from, fileTransfer.id);
-
+ await FileTransferrer.acceptFileTransfer(fileTransfer);
} catch(e) {
- console.log(e);
+ DialogUtils.showErrorAlert(e);
}
},
async rejectFileTransfer(fileTransfer) {
try {
-
- // remove from ui
- GlobalState.fileTransfers = GlobalState.fileTransfers.filter((existingFileTransfer) => {
- return existingFileTransfer.id !== fileTransfer.id;
- });
-
- // tell remote node we rejected file transfer
- await FileTransferAPI.rejectFileTransfer(fileTransfer.from, fileTransfer.id);
-
+ await FileTransferrer.rejectFileTransfer(fileTransfer);
} catch(e) {
console.log(e);
}
@@ -263,37 +226,21 @@ export default {
return;
}
- // remove from ui
- GlobalState.fileTransfers = GlobalState.fileTransfers.filter((existingFileTransfer) => {
- return existingFileTransfer.id !== fileTransfer.id;
- });
-
- // do nothing if already completed
- if(fileTransfer.status === "completed"){
- return;
- }
-
try {
-
- // tell remote node we cancelled the file transfer
await FileTransferAPI.cancelFileTransfer(fileTransfer.to, fileTransfer.id);
-
} catch(e) {
console.log(e);
}
},
- async removeFileTransfer(fileTransfer) {
+ removeFileTransfer(fileTransfer) {
// ask user to confirm
if(!confirm("Are you sure you want to remove this file transfer?")){
return;
}
- // remove from ui
- GlobalState.fileTransfers = GlobalState.fileTransfers.filter((existingFileTransfer) => {
- return existingFileTransfer.id !== fileTransfer.id;
- });
+ FileTransferrer.removeFileTransfer(fileTransfer);
},
},
diff --git a/src/js/Connection.js b/src/js/Connection.js
index b80e496..1819fc0 100644
--- a/src/js/Connection.js
+++ b/src/js/Connection.js
@@ -4,6 +4,7 @@ import Database from "./Database.js";
import NodeAPI from "./NodeAPI.js";
import PacketUtils from "./PacketUtils.js";
import FileTransferAPI from "./FileTransferAPI.js";
+import FileTransferrer from "./FileTransferrer.js";
class Connection {
@@ -511,8 +512,8 @@ class Connection {
id: fileTransferOffer.id,
to: meshPacket.to,
from: meshPacket.from,
- direction: "incoming",
- status: "offering",
+ direction: FileTransferrer.DIRECTION_INCOMING,
+ status: FileTransferrer.STATUS_OFFERING,
filename: fileTransferOffer.fileName,
filesize: fileTransferOffer.fileSize,
progress: 0,
@@ -546,12 +547,12 @@ class Connection {
const totalParts = Math.ceil(fileTransfer.data.length / maxAcceptablePartSize);
// update file transfer status
- fileTransfer.status = "accepted";
+ fileTransfer.status = FileTransferrer.STATUS_ACCEPTED;
fileTransfer.total_parts = totalParts;
fileTransfer.max_acceptable_part_size = maxAcceptablePartSize;
// send first file part
- await this.sendFilePart(fileTransfer, 0);
+ await FileTransferrer.sendFilePart(fileTransfer, 0);
}
@@ -570,7 +571,7 @@ class Connection {
console.log(`[FileTransfer] ${fileTransfer.id} rejected`);
// update file transfer status
- fileTransfer.status = "rejected";
+ fileTransfer.status = FileTransferrer.STATUS_REJECTED;
}
@@ -589,7 +590,7 @@ class Connection {
console.log(`[FileTransfer] ${fileTransfer.id} cancelled`);
// remove cancelled file transfer if it was in offering state
- if(fileTransfer.status === "offering"){
+ if(fileTransfer.status === FileTransferrer.STATUS_OFFERING){
GlobalState.fileTransfers = GlobalState.fileTransfers.filter((existingFileTransfer) => {
return existingFileTransfer.id !== fileTransfer.id;
});
@@ -597,7 +598,7 @@ class Connection {
}
// update file transfer status
- fileTransfer.status = "cancelled";
+ fileTransfer.status = FileTransferrer.STATUS_CANCELLED;
}
@@ -616,7 +617,7 @@ class Connection {
console.log(`[FileTransfer] ${fileTransfer.id} completed`);
// update file transfer status
- fileTransfer.status = "complete";
+ fileTransfer.status = FileTransferrer.STATUS_COMPLETED;
}
@@ -638,23 +639,23 @@ class Connection {
fileTransfer.chunks[filePart.partIndex] = filePart.data;
// update file transfer status
- fileTransfer.status = "receiving";
+ fileTransfer.status = FileTransferrer.STATUS_RECEIVING;
fileTransfer.progress = Math.ceil((filePart.partIndex + 1) / filePart.totalParts * 100);
// check if complete
// todo, check if all chunks received, and request others if not?
if(filePart.partIndex === filePart.totalParts - 1){
- fileTransfer.status = "complete";
+ fileTransfer.status = FileTransferrer.STATUS_COMPLETED;
fileTransfer.blob = new Blob(Object.values(fileTransfer.chunks), {
type: "application/octet-stream",
});
- await this.completeFileTransfer(fileTransfer);
+ await FileTransferrer.completeFileTransfer(fileTransfer);
return;
}
// request next part
const nextFilePartIndex = filePart.partIndex + 1;
- await this.requestFileParts(fileTransfer, [
+ await FileTransferrer.requestFileParts(fileTransfer, [
nextFilePartIndex,
]);
@@ -680,80 +681,16 @@ class Connection {
console.log(`[FileTransfer] ${fileTransfer.id} sending part ${partIndex}`);
// send file part
- await this.sendFilePart(fileTransfer, partIndex);
+ await FileTransferrer.sendFilePart(fileTransfer, partIndex);
// update file transfer progress
- fileTransfer.status = "sending";
+ fileTransfer.status = FileTransferrer.STATUS_SENDING;
fileTransfer.progress = Math.ceil((partIndex + 1) / fileTransfer.total_parts * 100);
}
}
- static async completeFileTransfer(fileTransfer) {
- try {
-
- // tell remote node we completed the file transfer
- for(var attempt = 0; attempt < 3; attempt++){
- try {
- await FileTransferAPI.completeFileTransfer(fileTransfer.from, fileTransfer.id);
- console.log(`completeFileTransfer attempt ${attempt + 1} success`);
- break;
- } catch(e) {
- console.log(`completeFileTransfer attempt ${attempt + 1} failed`);
- }
- }
-
- } catch(e) {
- console.log(e);
- }
- }
-
- static async requestFileParts(fileTransfer, partIndexes) {
- try {
-
- // ask remote node for parts
- for(var attempt = 0; attempt < 3; attempt++){
- try {
- await FileTransferAPI.requestFileParts(fileTransfer.from, fileTransfer.id, partIndexes);
- console.log(`requestFileParts attempt ${attempt + 1} success`);
- break;
- } catch(e) {
- console.log(`requestFileParts attempt ${attempt + 1} failed`);
- }
- }
-
- } catch(e) {
- console.log(e);
- }
- }
-
- static async sendFilePart(fileTransfer, partIndex) {
- try {
-
- // get data for this part
- const partSize = fileTransfer.max_acceptable_part_size;
- const start = partIndex * partSize;
- const end = start + partSize;
- const partData = fileTransfer.data.slice(start, end);
-
- // send part to remote node
- for(var attempt = 0; attempt < 3; attempt++){
- try {
- await FileTransferAPI.sendFilePart(fileTransfer.to, fileTransfer.id, partIndex, fileTransfer.total_parts, partData);
- console.log(`sendFilePart attempt ${attempt + 1} success`);
- break;
- } catch(e) {
- console.log(`sendFilePart attempt ${attempt + 1} failed`);
- }
- }
-
-
- } catch(e) {
- console.log(e);
- }
- }
-
}
export default Connection;
diff --git a/src/js/FileTransferrer.js b/src/js/FileTransferrer.js
new file mode 100644
index 0000000..c2d14d8
--- /dev/null
+++ b/src/js/FileTransferrer.js
@@ -0,0 +1,211 @@
+import NodeAPI from "./NodeAPI.js";
+import GlobalState from "./GlobalState.js";
+import FileTransferAPI from "./FileTransferAPI.js";
+
+class FileTransferrer {
+
+ static DIRECTION_INCOMING = "incoming";
+ static DIRECTION_OUTGOING = "outgoing";
+
+ static STATUS_OFFERING = "offering";
+ static STATUS_OFFERED = "offered";
+ static STATUS_ACCEPTED = "accepted";
+ static STATUS_REJECTED = "rejected";
+ static STATUS_CANCELLED = "cancelled";
+ static STATUS_COMPLETED = "completed";
+ static STATUS_SENDING = "sending";
+ static STATUS_RECEIVING = "receiving";
+
+ static MAX_PACKET_ATTEMPTS = 3;
+
+ static log(message) {
+ console.log(`[FileTransferrer] ${message}`);
+ }
+
+ static async offerFileTransfer(to, file) {
+
+ // generate random file transfer id
+ const fileTransferId = NodeAPI.generatePacketId();
+
+ // get file details
+ to = parseInt(to);
+ const fileName = file.name;
+ const fileData = new Uint8Array(await file.arrayBuffer());
+ const fileSize = fileData.length;
+
+ const fileTransfer = {
+ id: fileTransferId,
+ to: to,
+ from: GlobalState.myNodeId,
+ direction: this.DIRECTION_OUTGOING,
+ status: this.STATUS_OFFERING,
+ filename: fileName,
+ filesize: fileSize,
+ progress: 0,
+ data: fileData,
+ };
+
+ // add to file transfers list
+ GlobalState.fileTransfers.push(fileTransfer);
+
+ // send file transfer request
+ for(var attempt = 1; attempt <= this.MAX_PACKET_ATTEMPTS; attempt++){
+ try {
+ this.log(`offerFileTransfer attempt ${attempt}`);
+ await FileTransferAPI.sendFileTransferRequest(to, fileTransferId, fileName, fileSize);
+ this.log(`offerFileTransfer attempt ${attempt} success`);
+ fileTransfer.status = this.STATUS_OFFERED;
+ return;
+ } catch(e) {
+ console.log(e);
+ if(attempt === this.MAX_PACKET_ATTEMPTS){
+
+ this.log("offerFileTransfer failed", e);
+
+ // remove file transfer
+ GlobalState.fileTransfers = GlobalState.fileTransfers.filter((fileTransfer) => {
+ return fileTransfer.id !== fileTransferId;
+ });
+
+ // rethrow exception
+ throw e;
+
+ }
+ }
+ }
+
+ }
+
+ static async acceptFileTransfer(fileTransfer) {
+
+ for(var attempt = 1; attempt <= this.MAX_PACKET_ATTEMPTS; attempt++){
+ try {
+ this.log(`acceptFileTransfer attempt ${attempt}`);
+ await FileTransferAPI.acceptFileTransfer(fileTransfer.from, fileTransfer.id);
+ fileTransfer.status = this.STATUS_ACCEPTED;
+ return;
+ } catch(e) {
+ console.log(e);
+ if(attempt === this.MAX_PACKET_ATTEMPTS){
+ this.log("acceptFileTransfer failed", e);
+ throw e;
+ }
+ }
+ }
+
+ }
+
+ static async rejectFileTransfer(fileTransfer) {
+
+ // remove from ui
+ GlobalState.fileTransfers = GlobalState.fileTransfers.filter((existingFileTransfer) => {
+ return existingFileTransfer.id !== fileTransfer.id;
+ });
+
+ for(var attempt = 1; attempt <= this.MAX_PACKET_ATTEMPTS; attempt++){
+ try {
+ this.log(`rejectFileTransfer attempt ${attempt}`);
+ await FileTransferAPI.acceptFileTransfer(fileTransfer.from, fileTransfer.id);
+ fileTransfer.status = this.STATUS_ACCEPTED;
+ return;
+ } catch(e) {
+ console.log(e);
+ if(attempt === this.MAX_PACKET_ATTEMPTS){
+ this.log("rejectFileTransfer failed", e);
+ throw e;
+ }
+ }
+ }
+
+ }
+
+ static async cancelFileTransfer(fileTransfer) {
+
+ fileTransfer.status = this.STATUS_CANCELLED;
+
+ for(var attempt = 1; attempt <= this.MAX_PACKET_ATTEMPTS; attempt++){
+ try {
+ this.log(`cancelFileTransfer attempt ${attempt}`);
+ await FileTransferAPI.cancelFileTransfer(fileTransfer.to, fileTransfer.id);
+ return;
+ } catch(e) {
+ console.log(e);
+ if(attempt === this.MAX_PACKET_ATTEMPTS){
+ this.log("cancelFileTransfer failed", e);
+ throw e;
+ }
+ }
+ }
+
+ }
+
+ static removeFileTransfer(fileTransfer) {
+ GlobalState.fileTransfers = GlobalState.fileTransfers.filter((existingFileTransfer) => {
+ return existingFileTransfer.id !== fileTransfer.id;
+ });
+ }
+
+ static async sendFilePart(fileTransfer, partIndex) {
+ try {
+
+ // get data for this part
+ const partSize = fileTransfer.max_acceptable_part_size;
+ const start = partIndex * partSize;
+ const end = start + partSize;
+ const partData = fileTransfer.data.slice(start, end);
+
+ // send part to remote node
+ for(var attempt = 1; attempt <= this.MAX_PACKET_ATTEMPTS; attempt++){
+ try {
+ this.log(`sendFilePart attempt ${attempt}`);
+ await FileTransferAPI.sendFilePart(fileTransfer.to, fileTransfer.id, partIndex, fileTransfer.total_parts, partData);
+ return;
+ } catch(e) {
+ console.log(e);
+ if(attempt === this.MAX_PACKET_ATTEMPTS){
+ this.log("sendFilePart failed", e);
+ throw e;
+ }
+ }
+ }
+
+ } catch(e) {
+ console.log(e);
+ }
+ }
+
+ static async requestFileParts(fileTransfer, partIndexes) {
+ for(var attempt = 1; attempt <= this.MAX_PACKET_ATTEMPTS; attempt++){
+ try {
+ this.log(`requestFileParts attempt ${attempt}`);
+ await FileTransferAPI.requestFileParts(fileTransfer.from, fileTransfer.id, partIndexes);
+ return;
+ } catch(e) {
+ console.log(e);
+ if(attempt === this.MAX_PACKET_ATTEMPTS){
+ this.log("requestFileParts failed", e);
+ throw e;
+ }
+ }
+ }
+ }
+
+ static async completeFileTransfer(fileTransfer) {
+ for(var attempt = 1; attempt <= this.MAX_PACKET_ATTEMPTS; attempt++){
+ try {
+ this.log(`completeFileTransfer attempt ${attempt}`);
+ await FileTransferAPI.completeFileTransfer(fileTransfer.from, fileTransfer.id);
+ return;
+ } catch(e) {
+ console.log(e);
+ if(attempt === this.MAX_PACKET_ATTEMPTS){
+ this.log("completeFileTransfer failed", e);
+ throw e;
+ }
+ }
+ }
+ }
+
+}
+
+export default FileTransferrer;