Skip to content

Commit

Permalink
refactor file transfers
Browse files Browse the repository at this point in the history
  • Loading branch information
liamcottle committed Dec 14, 2024
1 parent f9aca21 commit f5efab0
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 142 deletions.
75 changes: 11 additions & 64 deletions src/components/pages/NodeFilesPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@

<!-- action buttons -->
<div v-else class="ml-2 flex items-center space-x-1">
<TextButton v-if="fileTransfer.direction === 'incoming' && fileTransfer.status === 'complete'" @click="downloadBlob(fileTransfer.filename, fileTransfer.blob)" class="bg-gray-500 hover:bg-gray-400">
<TextButton v-if="fileTransfer.direction === 'incoming' && fileTransfer.status === 'completed'" @click="downloadBlob(fileTransfer.filename, fileTransfer.blob)" class="bg-gray-500 hover:bg-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
</svg>
</TextButton>
<TextButton v-if="fileTransfer.status === 'complete' || fileTransfer.status === 'cancelled' || fileTransfer.status === 'rejected'" @click="removeFileTransfer(fileTransfer)" class="bg-gray-500 hover:bg-gray-400">
<TextButton v-if="fileTransfer.status === 'completed' || fileTransfer.status === 'cancelled' || fileTransfer.status === 'rejected'" @click="removeFileTransfer(fileTransfer)" class="bg-gray-500 hover:bg-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
},
},
Expand Down
93 changes: 15 additions & 78 deletions src/js/Connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);

}

Expand All @@ -570,7 +571,7 @@ class Connection {
console.log(`[FileTransfer] ${fileTransfer.id} rejected`);

// update file transfer status
fileTransfer.status = "rejected";
fileTransfer.status = FileTransferrer.STATUS_REJECTED;

}

Expand All @@ -589,15 +590,15 @@ 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;
});
return;
}

// update file transfer status
fileTransfer.status = "cancelled";
fileTransfer.status = FileTransferrer.STATUS_CANCELLED;

}

Expand All @@ -616,7 +617,7 @@ class Connection {
console.log(`[FileTransfer] ${fileTransfer.id} completed`);

// update file transfer status
fileTransfer.status = "complete";
fileTransfer.status = FileTransferrer.STATUS_COMPLETED;

}

Expand All @@ -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,
]);

Expand All @@ -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;
Loading

0 comments on commit f5efab0

Please sign in to comment.