Multiple Files Transfers
This commit is contained in:
parent
ce246c2234
commit
809ce34bff
|
@ -22,6 +22,7 @@ hex = "0.4.3"
|
||||||
strum_macros = "0.20.1" #display enums
|
strum_macros = "0.20.1" #display enums
|
||||||
actix-web = "3"
|
actix-web = "3"
|
||||||
actix-multipart = "0.3"
|
actix-multipart = "0.3"
|
||||||
|
time = "0.2.25" #needed for actix cookies
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
tungstenite = "0.13.0" #websocket
|
tungstenite = "0.13.0" #websocket
|
||||||
serde = "1.0.124" #serialization
|
serde = "1.0.124" #serialization
|
||||||
|
@ -34,6 +35,5 @@ libmdns = "0.6" #mDNS advertiser
|
||||||
multicast_dns = "0.5" #mDNS browser
|
multicast_dns = "0.5" #mDNS browser
|
||||||
pnet_datalink = "0.27.2"
|
pnet_datalink = "0.27.2"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
time = "0.2.25"
|
|
||||||
scrypt = "0.7.0"
|
scrypt = "0.7.0"
|
||||||
zeroize = "1.2.0"
|
zeroize = "1.2.0"
|
|
@ -54,12 +54,20 @@ input[type="file"] {
|
||||||
right: 0;
|
right: 0;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
width: 40%;
|
width: 40%;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 20px 70px;
|
padding: 20px 70px 10px;
|
||||||
background-color: #2B2F31;
|
background-color: #2B2F31;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
.popup:last-child::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
height: 20px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
.popup_background {
|
.popup_background {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -198,12 +206,6 @@ input[type="file"] {
|
||||||
}
|
}
|
||||||
#left_panel ul li:hover, #left_panel ul li.current {
|
#left_panel ul li:hover, #left_panel ul li.current {
|
||||||
background-color: #333940;
|
background-color: #333940;
|
||||||
}
|
|
||||||
#left_panel ul li.output {
|
|
||||||
|
|
||||||
}
|
|
||||||
#left_panel ul li.input {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
#left_panel ul li p::after {
|
#left_panel ul li p::after {
|
||||||
content: url("/static/imgs/icons/warning/FF3C00");
|
content: url("/static/imgs/icons/warning/FF3C00");
|
||||||
|
@ -250,7 +252,7 @@ input[type="file"] {
|
||||||
background-color: unset;
|
background-color: unset;
|
||||||
content: url("/static/imgs/icons/info/FF3C00");
|
content: url("/static/imgs/icons/info/FF3C00");
|
||||||
}
|
}
|
||||||
ul.ips {
|
.popup ul {
|
||||||
list-style-type: unset;
|
list-style-type: unset;
|
||||||
}
|
}
|
||||||
#chat_header {
|
#chat_header {
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
<div id="message_box">
|
<div id="message_box">
|
||||||
<input type="text" id="message_input" placeholder="Send a message...">
|
<input type="text" id="message_input" placeholder="Send a message...">
|
||||||
<label title="Send file" class="file_picker">
|
<label title="Send file" class="file_picker">
|
||||||
<input type="file" id="attach_file">
|
<input type="file" id="attach_file" multiple>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@ let localIps = [];
|
||||||
let currentSessionId = -1;
|
let currentSessionId = -1;
|
||||||
let sessionsData = new Map();
|
let sessionsData = new Map();
|
||||||
let msgHistory = new Map();
|
let msgHistory = new Map();
|
||||||
let pendingFiles = new Map();
|
let pendingFilesTransfers = new Map();
|
||||||
|
|
||||||
function onClickSession(event) {
|
function onClickSession(event) {
|
||||||
let sessionId = event.currentTarget.getAttribute("data-sessionId");
|
let sessionId = event.currentTarget.getAttribute("data-sessionId");
|
||||||
|
@ -163,9 +163,16 @@ document.getElementById("logout").onclick = function() {
|
||||||
showPopup(mainDiv);
|
showPopup(mainDiv);
|
||||||
}
|
}
|
||||||
document.getElementById("attach_file").onchange = function(event) {
|
document.getElementById("attach_file").onchange = function(event) {
|
||||||
let file = event.target.files[0];
|
let files = event.target.files;
|
||||||
if (file.size > 32760000) {
|
let useLargeFileTransfer = false;
|
||||||
if (pendingFiles.has(currentSessionId)) {
|
for (let i=0; i<files.length; ++i) {
|
||||||
|
if (files[i].size > 32760000) {
|
||||||
|
useLargeFileTransfer = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (useLargeFileTransfer) {
|
||||||
|
if (pendingFilesTransfers.has(currentSessionId)) {
|
||||||
let mainDiv = document.createElement("div");
|
let mainDiv = document.createElement("div");
|
||||||
mainDiv.appendChild(generatePopupWarningTitle());
|
mainDiv.appendChild(generatePopupWarningTitle());
|
||||||
let p = document.createElement("p");
|
let p = document.createElement("p");
|
||||||
|
@ -173,28 +180,39 @@ document.getElementById("attach_file").onchange = function(event) {
|
||||||
mainDiv.appendChild(p);
|
mainDiv.appendChild(p);
|
||||||
showPopup(mainDiv);
|
showPopup(mainDiv);
|
||||||
} else {
|
} else {
|
||||||
pendingFiles.set(currentSessionId, {
|
let fileTransfers = [];
|
||||||
"file": file,
|
let fileInfo = "";
|
||||||
"name": file.name,
|
for (let i=0; i<files.length; ++i) {
|
||||||
"size": file.size,
|
fileTransfers.push({
|
||||||
|
"file": files[i],
|
||||||
|
"name": files[i].name,
|
||||||
|
"size": files[i].size,
|
||||||
|
"transferred": 0,
|
||||||
|
"lastChunk": Date.now()
|
||||||
|
});
|
||||||
|
fileInfo += ' '+files[i].size+' '+b64EncodeUnicode(files[i].name);
|
||||||
|
};
|
||||||
|
pendingFilesTransfers.set(currentSessionId, {
|
||||||
|
"files": fileTransfers,
|
||||||
|
"index": 0,
|
||||||
"state": "waiting",
|
"state": "waiting",
|
||||||
"transferred": 0,
|
|
||||||
"lastChunk": Date.now()
|
|
||||||
});
|
});
|
||||||
socket.send("large_file "+currentSessionId+" "+file.size+" "+file.name);
|
socket.send("large_files "+currentSessionId+fileInfo);
|
||||||
displayChatBottom();
|
displayChatBottom();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let formData = new FormData();
|
for (let i=0; i<files.length; ++i) {
|
||||||
formData.append("session_id", currentSessionId);
|
let formData = new FormData();
|
||||||
formData.append("", file);
|
formData.append("session_id", currentSessionId);
|
||||||
fetch("/send_file", {method: "POST", body: formData}).then(response => {
|
formData.append("", files[i]);
|
||||||
if (response.ok) {
|
fetch("/send_file", {method: "POST", body: formData}).then(response => {
|
||||||
response.text().then(uuid => onFileSent(currentSessionId, uuid, file.name));
|
if (response.ok) {
|
||||||
} else {
|
response.text().then(uuid => onFileSent(currentSessionId, uuid, files[i].name));
|
||||||
console.log(response);
|
} else {
|
||||||
}
|
console.log(response);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.getElementById("file_cancel").onclick = function() {
|
document.getElementById("file_cancel").onclick = function() {
|
||||||
|
@ -270,10 +288,10 @@ profile_div.onclick = function() {
|
||||||
if (isIdentityProtected || newPassword_set) { //don't change password if identity is not protected and new password is blank
|
if (isIdentityProtected || newPassword_set) { //don't change password if identity is not protected and new password is blank
|
||||||
let msg = "change_password";
|
let msg = "change_password";
|
||||||
if (isIdentityProtected) {
|
if (isIdentityProtected) {
|
||||||
msg += " "+btoa(inputs[0].value);
|
msg += " "+b64EncodeUnicode(inputs[0].value);
|
||||||
}
|
}
|
||||||
if (newPassword_set) {
|
if (newPassword_set) {
|
||||||
msg += " "+btoa(newPassword.value);
|
msg += " "+b64EncodeUnicode(newPassword.value);
|
||||||
}
|
}
|
||||||
socket.send(msg);
|
socket.send(msg);
|
||||||
} else {
|
} else {
|
||||||
|
@ -357,9 +375,9 @@ document.querySelector("#refresher button").onclick = function() {
|
||||||
function humanFileSize(bytes, dp=1) {
|
function humanFileSize(bytes, dp=1) {
|
||||||
const thresh = 1000;
|
const thresh = 1000;
|
||||||
if (Math.abs(bytes) < thresh) {
|
if (Math.abs(bytes) < thresh) {
|
||||||
return bytes + ' B';
|
return bytes + " B";
|
||||||
}
|
}
|
||||||
const units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
const units = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||||
let u = -1;
|
let u = -1;
|
||||||
const r = 10**dp;
|
const r = 10**dp;
|
||||||
do {
|
do {
|
||||||
|
@ -368,6 +386,18 @@ function humanFileSize(bytes, dp=1) {
|
||||||
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
||||||
return bytes.toFixed(dp) + ' ' + units[u];
|
return bytes.toFixed(dp) + ' ' + units[u];
|
||||||
}
|
}
|
||||||
|
//source: https://stackoverflow.com/a/30106551
|
||||||
|
function b64EncodeUnicode(str) {
|
||||||
|
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
|
||||||
|
function toSolidBytes(match, p1) {
|
||||||
|
return String.fromCharCode('0x' + p1);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
function b64DecodeUnicode(str) {
|
||||||
|
return decodeURIComponent(atob(str).split('').map(function(c) {
|
||||||
|
return '%' + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
|
||||||
|
}).join(''));
|
||||||
|
}
|
||||||
//source: https://www.w3schools.com/js/js_cookies.asp
|
//source: https://www.w3schools.com/js/js_cookies.asp
|
||||||
function getCookie(cname) {
|
function getCookie(cname) {
|
||||||
var name = cname + "=";
|
var name = cname + "=";
|
||||||
|
@ -421,20 +451,20 @@ socket.onmessage = function(msg) {
|
||||||
case "file":
|
case "file":
|
||||||
onFileReceived(args[1], args[2], msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
onFileReceived(args[1], args[2], msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
||||||
break;
|
break;
|
||||||
case "file_transfer":
|
case "files_transfer":
|
||||||
onNewFileTransfer(args[1], args[2], args[3], args[4], args[5], args[6]);
|
onNewFilesTransfer(args[1], args[2], msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
||||||
break;
|
break;
|
||||||
case "ask_large_file":
|
case "ask_large_files":
|
||||||
onAskLargeFile(args[1], args[2], args[3], args[4]);
|
onAskLargeFiles(args[1], args[2], msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
||||||
break;
|
break;
|
||||||
case "file_accepted":
|
case "files_accepted":
|
||||||
onFileAccepted(args[1]);
|
onFilesAccepted(args[1]);
|
||||||
break;
|
break;
|
||||||
case "aborted":
|
case "aborted":
|
||||||
onFileAborted(args[1]);
|
onFilesTransferAborted(args[1]);
|
||||||
break;
|
break;
|
||||||
case "inc_file_transfer":
|
case "inc_file_transfer":
|
||||||
onIncFileTransfer(args[1], parseInt(args[2]));
|
onIncFilesTransfer(args[1], parseInt(args[2]));
|
||||||
break;
|
break;
|
||||||
case "load_sent_msg":
|
case "load_sent_msg":
|
||||||
onMsgLoad(args[1], args[2] === "true", msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
onMsgLoad(args[1], args[2] === "true", msg.data.slice(args[0].length+args[1].length+args[2].length+3));
|
||||||
|
@ -532,33 +562,47 @@ function onNewMessage(sessionId, outgoing, msg) {
|
||||||
msgHistory.get(sessionId).push([outgoing, false, msg]);
|
msgHistory.get(sessionId).push([outgoing, false, msg]);
|
||||||
onMsgOrFileReceived(sessionId, outgoing, msg);
|
onMsgOrFileReceived(sessionId, outgoing, msg);
|
||||||
}
|
}
|
||||||
function onNewFileTransfer(sessionId, encodedFileName, fileSize, state, transferred, lastChunk) {
|
function onNewFilesTransfer(sessionId, index, filesInfo) {
|
||||||
pendingFiles.set(sessionId, {
|
let split = filesInfo.split(' ');
|
||||||
"file": undefined,
|
let files = [];
|
||||||
"name": atob(encodedFileName),
|
for (let i=0; i<split.length; i += 4) {
|
||||||
"size": parseInt(fileSize),
|
files.push({
|
||||||
"state": state,
|
"file": undefined,
|
||||||
"transferred": parseInt(transferred),
|
"name": b64DecodeUnicode(split[i]),
|
||||||
"lastChunk": parseInt(lastChunk)
|
"size": parseInt(split[i+1]),
|
||||||
|
"transferred": parseInt(split[i+2]),
|
||||||
|
"lastChunk": parseInt(split[i+3])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pendingFilesTransfers.set(sessionId, {
|
||||||
|
"files": files,
|
||||||
|
"index": parseInt(index),
|
||||||
|
"state": "transferring"
|
||||||
});
|
});
|
||||||
if (currentSessionId == sessionId) {
|
if (currentSessionId == sessionId) {
|
||||||
displayChatBottom();
|
displayChatBottom();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onAskLargeFile(sessionId, fileSize, encodedFileName, encodedDownloadLocation) {
|
function onAskLargeFiles(sessionId, encodedDownloadLocation, filesInfo) {
|
||||||
let sessionName = sessionsData.get(sessionId).name;
|
let sessionName = sessionsData.get(sessionId).name;
|
||||||
let mainDiv = document.createElement("div");
|
let mainDiv = document.createElement("div");
|
||||||
let h2 = document.createElement("h2");
|
let h2 = document.createElement("h2");
|
||||||
h2.textContent = sessionName+" wants to send you a file:";
|
h2.textContent = sessionName+" wants to send you some files:";
|
||||||
mainDiv.appendChild(h2);
|
mainDiv.appendChild(h2);
|
||||||
let fileName = atob(encodedFileName);
|
let ul = document.createElement("ul");
|
||||||
let fileInfo = document.createElement("p");
|
let split = filesInfo.split(' ');
|
||||||
generateFileInfo(fileName, fileSize, fileInfo);
|
for (let i=0; i<split.length; i += 2) {
|
||||||
mainDiv.appendChild(fileInfo);
|
let p = document.createElement("p");
|
||||||
|
generateFileInfo(b64DecodeUnicode(split[i]), parseInt(split[i+1]), p);
|
||||||
|
let li = document.createElement("li");
|
||||||
|
li.appendChild(p);
|
||||||
|
ul.appendChild(li);
|
||||||
|
}
|
||||||
|
mainDiv.appendChild(ul);
|
||||||
let spanDownloadLocation = document.createElement("span");
|
let spanDownloadLocation = document.createElement("span");
|
||||||
spanDownloadLocation.textContent = atob(encodedDownloadLocation);
|
spanDownloadLocation.textContent = b64DecodeUnicode(encodedDownloadLocation);
|
||||||
let pQuestion = document.createElement("p");
|
let pQuestion = document.createElement("p");
|
||||||
pQuestion.appendChild(document.createTextNode("Download it in "));
|
pQuestion.appendChild(document.createTextNode("Download them in "));
|
||||||
pQuestion.appendChild(spanDownloadLocation);
|
pQuestion.appendChild(spanDownloadLocation);
|
||||||
pQuestion.appendChild(document.createTextNode(" ?"));
|
pQuestion.appendChild(document.createTextNode(" ?"));
|
||||||
mainDiv.appendChild(pQuestion);
|
mainDiv.appendChild(pQuestion);
|
||||||
|
@ -568,13 +612,20 @@ function onAskLargeFile(sessionId, fileSize, encodedFileName, encodedDownloadLoc
|
||||||
buttonDownload.textContent = "Download";
|
buttonDownload.textContent = "Download";
|
||||||
buttonDownload.onclick = function() {
|
buttonDownload.onclick = function() {
|
||||||
removePopup();
|
removePopup();
|
||||||
pendingFiles.set(sessionId, {
|
let files = [];
|
||||||
"file": undefined,
|
for (let i=0; i<split.length; i += 2) {
|
||||||
"name": fileName,
|
files.push({
|
||||||
"size": fileSize,
|
"file": undefined,
|
||||||
"state": "accepted",
|
"name": b64DecodeUnicode(split[i]),
|
||||||
"transferred": 0,
|
"size": parseInt(split[i+1]),
|
||||||
"lastChunk": Date.now()
|
"transferred": 0,
|
||||||
|
"lastChunk": Date.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pendingFilesTransfers.set(sessionId, {
|
||||||
|
"files": files,
|
||||||
|
"index": 0,
|
||||||
|
"state": "transferring"
|
||||||
});
|
});
|
||||||
socket.send("download "+sessionId);
|
socket.send("download "+sessionId);
|
||||||
if (currentSessionId == sessionId) {
|
if (currentSessionId == sessionId) {
|
||||||
|
@ -593,47 +644,41 @@ function onAskLargeFile(sessionId, fileSize, encodedFileName, encodedDownloadLoc
|
||||||
showPopup(mainDiv, false);
|
showPopup(mainDiv, false);
|
||||||
if (document.hidden && notificationAllowed) {
|
if (document.hidden && notificationAllowed) {
|
||||||
new Notification(sessionName, {
|
new Notification(sessionName, {
|
||||||
"body": fileName
|
"body": "Files download request"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onFileAccepted(sessionId) {
|
function onFilesAccepted(sessionId) {
|
||||||
if (pendingFiles.has(sessionId)) {
|
if (pendingFilesTransfers.has(sessionId)) {
|
||||||
let file = pendingFiles.get(sessionId);
|
sendNextLargeFile(sessionId);
|
||||||
file.state = "sending";
|
|
||||||
file.lastChunk = Date.now();
|
|
||||||
if (currentSessionId == sessionId) {
|
|
||||||
displayChatBottom();
|
|
||||||
}
|
|
||||||
let formData = new FormData();
|
|
||||||
formData.append("session_id", currentSessionId);
|
|
||||||
formData.append("", file.file);
|
|
||||||
fetch("/send_large_file", {method: "POST", body: formData}).then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
console.log(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onFileAborted(sessionId) {
|
function onFilesTransferAborted(sessionId) {
|
||||||
if (pendingFiles.has(sessionId)) {
|
if (pendingFilesTransfers.has(sessionId)) {
|
||||||
pendingFiles.get(sessionId).state = "aborted";
|
pendingFilesTransfers.get(sessionId).state = "aborted";
|
||||||
if (sessionId == currentSessionId) {
|
if (sessionId == currentSessionId) {
|
||||||
displayChatBottom();
|
displayChatBottom();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onIncFileTransfer(sessionId, chunkSize) {
|
function onIncFilesTransfer(sessionId, chunkSize) {
|
||||||
if (pendingFiles.has(sessionId)) {
|
if (pendingFilesTransfers.has(sessionId)) {
|
||||||
let file = pendingFiles.get(sessionId);
|
let filesTransfer = pendingFilesTransfers.get(sessionId);
|
||||||
file.transferred += chunkSize;
|
let fileTransfer = filesTransfer.files[filesTransfer.index];
|
||||||
|
fileTransfer.transferred += chunkSize;
|
||||||
let now = Date.now();
|
let now = Date.now();
|
||||||
let speed = chunkSize/(now-file.lastChunk)*1000;
|
let speed = chunkSize/(now-fileTransfer.lastChunk)*1000;
|
||||||
file.lastChunk = now;
|
fileTransfer.lastChunk = now;
|
||||||
if (file.transferred >= file.size) {
|
if (fileTransfer.transferred >= fileTransfer.size) {
|
||||||
file.state = "completed";
|
if (filesTransfer.index == filesTransfer.files.length-1) {
|
||||||
} else {
|
filesTransfer.state = "completed";
|
||||||
file.state = "transferring";
|
socket.send("sending_ended "+sessionId);
|
||||||
|
} else {
|
||||||
|
filesTransfer.index += 1;
|
||||||
|
if (typeof fileTransfer.file !== "undefined") {
|
||||||
|
sendNextLargeFile(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (currentSessionId == sessionId) {
|
if (currentSessionId == sessionId) {
|
||||||
displayChatBottom(speed);
|
displayChatBottom(speed);
|
||||||
|
@ -653,7 +698,7 @@ function onFileLoad(sessionId, outgoing, uuid, fileName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onDisconnected(sessionId) {
|
function onDisconnected(sessionId) {
|
||||||
pendingFiles.delete(sessionId);
|
pendingFilesTransfers.delete(sessionId);
|
||||||
let session = sessionsData.get(sessionId);
|
let session = sessionsData.get(sessionId);
|
||||||
if (session.isContact) {
|
if (session.isContact) {
|
||||||
session.isOnline = false;
|
session.isOnline = false;
|
||||||
|
@ -696,6 +741,23 @@ function onPasswordChanged(success, isProtected) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendNextLargeFile(sessionId) {
|
||||||
|
let filesTransfer = pendingFilesTransfers.get(sessionId);
|
||||||
|
filesTransfer.state = "transferring";
|
||||||
|
let fileTransfer = filesTransfer.files[filesTransfer.index];
|
||||||
|
fileTransfer.lastChunk = Date.now();
|
||||||
|
if (currentSessionId == sessionId) {
|
||||||
|
displayChatBottom();
|
||||||
|
}
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append("session_id", currentSessionId);
|
||||||
|
formData.append("", fileTransfer.file);
|
||||||
|
fetch("/send_large_file", {method: "POST", body: formData}).then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
console.log(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
function beautifyFingerprint(f) {
|
function beautifyFingerprint(f) {
|
||||||
for (let i=4; i<f.length; i+=5) {
|
for (let i=4; i<f.length; i+=5) {
|
||||||
f = f.slice(0, i)+" "+f.slice(i);
|
f = f.slice(0, i)+" "+f.slice(i);
|
||||||
|
@ -758,17 +820,17 @@ function displayHeader() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function showPopup(content, closeButton = true) {
|
function showPopup(content, cancelable = true) {
|
||||||
let popup_background = document.createElement("div");
|
let popup_background = document.createElement("div");
|
||||||
popup_background.classList.add("popup_background");
|
popup_background.classList.add("popup_background");
|
||||||
popup_background.onclick = function(e) {
|
|
||||||
if (e.target == popup_background) {
|
|
||||||
removePopup();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let popup = document.createElement("div");
|
let popup = document.createElement("div");
|
||||||
popup.classList.add("popup");
|
popup.classList.add("popup");
|
||||||
if (closeButton) {
|
if (cancelable) {
|
||||||
|
popup_background.onclick = function(e) {
|
||||||
|
if (e.target == popup_background) {
|
||||||
|
removePopup();
|
||||||
|
}
|
||||||
|
};
|
||||||
let close = document.createElement("button");
|
let close = document.createElement("button");
|
||||||
close.classList.add("close");
|
close.classList.add("close");
|
||||||
close.onclick = removePopup;
|
close.onclick = removePopup;
|
||||||
|
@ -805,11 +867,6 @@ function generateSession(sessionId, session) {
|
||||||
li.setAttribute("data-sessionId", sessionId);
|
li.setAttribute("data-sessionId", sessionId);
|
||||||
li.appendChild(generateAvatar(session.name));
|
li.appendChild(generateAvatar(session.name));
|
||||||
li.appendChild(generateName(session.name));
|
li.appendChild(generateName(session.name));
|
||||||
if (session.outgoing) {
|
|
||||||
li.classList.add("outgoing");
|
|
||||||
} else {
|
|
||||||
li.classList.add("incomming");
|
|
||||||
}
|
|
||||||
if (session.isContact) {
|
if (session.isContact) {
|
||||||
li.classList.add("is_contact");
|
li.classList.add("is_contact");
|
||||||
}
|
}
|
||||||
|
@ -884,20 +941,22 @@ function generateFileInfo(fileName, fileSize, p) {
|
||||||
}
|
}
|
||||||
function displayChatBottom(speed = undefined) {
|
function displayChatBottom(speed = undefined) {
|
||||||
let msgBox = document.getElementById("message_box");
|
let msgBox = document.getElementById("message_box");
|
||||||
|
let fileTransfer = document.getElementById("file_transfer");
|
||||||
let session = sessionsData.get(currentSessionId);
|
let session = sessionsData.get(currentSessionId);
|
||||||
if (typeof session === "undefined") {
|
if (typeof session === "undefined") {
|
||||||
msgBox.removeAttribute("style");
|
msgBox.removeAttribute("style");
|
||||||
|
fileTransfer.classList.remove("active");
|
||||||
} else {
|
} else {
|
||||||
if (session.isOnline) {
|
if (session.isOnline) {
|
||||||
msgBox.style.display = "flex";
|
msgBox.style.display = "flex";
|
||||||
} else {
|
} else {
|
||||||
msgBox.removeAttribute("style");
|
msgBox.removeAttribute("style");
|
||||||
}
|
}
|
||||||
let fileTransfer = document.getElementById("file_transfer");
|
if (pendingFilesTransfers.has(currentSessionId)) {
|
||||||
if (pendingFiles.has(currentSessionId)) {
|
|
||||||
let file = pendingFiles.get(currentSessionId);
|
|
||||||
let fileInfo = document.getElementById("file_info");
|
let fileInfo = document.getElementById("file_info");
|
||||||
fileInfo.innerHTML = "";
|
fileInfo.innerHTML = "";
|
||||||
|
let filesTransfer = pendingFilesTransfers.get(currentSessionId);
|
||||||
|
let file = filesTransfer.files[filesTransfer.index];
|
||||||
generateFileInfo(file.name, file.size, fileInfo);
|
generateFileInfo(file.name, file.size, fileInfo);
|
||||||
let fileProgress = document.getElementById("file_progress");
|
let fileProgress = document.getElementById("file_progress");
|
||||||
fileProgress.style.display = "none"; //hide by default
|
fileProgress.style.display = "none"; //hide by default
|
||||||
|
@ -906,7 +965,7 @@ function displayChatBottom(speed = undefined) {
|
||||||
let fileCancel = document.getElementById("file_cancel");
|
let fileCancel = document.getElementById("file_cancel");
|
||||||
fileCancel.style.display = "none"; //hide by default
|
fileCancel.style.display = "none"; //hide by default
|
||||||
document.querySelector("#file_progress_bar>div").style.width = 0;
|
document.querySelector("#file_progress_bar>div").style.width = 0;
|
||||||
switch (file.state) {
|
switch (filesTransfer.state) {
|
||||||
case "transferring":
|
case "transferring":
|
||||||
fileCancel.removeAttribute("style"); //show
|
fileCancel.removeAttribute("style"); //show
|
||||||
fileStatus.style.display = "none";
|
fileStatus.style.display = "none";
|
||||||
|
@ -921,19 +980,13 @@ function displayChatBottom(speed = undefined) {
|
||||||
case "waiting":
|
case "waiting":
|
||||||
fileStatus.textContent = "Waiting for peer confirmation...";
|
fileStatus.textContent = "Waiting for peer confirmation...";
|
||||||
break;
|
break;
|
||||||
case "accepted":
|
|
||||||
fileStatus.textContent = "Downloading file...";
|
|
||||||
break;
|
|
||||||
case "aborted":
|
case "aborted":
|
||||||
fileStatus.textContent = "Transfer aborted.";
|
fileStatus.textContent = "Transfer aborted.";
|
||||||
pendingFiles.delete(currentSessionId);
|
pendingFilesTransfers.delete(currentSessionId);
|
||||||
break;
|
|
||||||
case "sending":
|
|
||||||
fileStatus.textContent = "Sending file...";
|
|
||||||
break;
|
break;
|
||||||
case "completed":
|
case "completed":
|
||||||
fileStatus.textContent = "Transfer completed.";
|
fileStatus.textContent = "Transfer completed.";
|
||||||
pendingFiles.delete(currentSessionId);
|
pendingFilesTransfers.delete(currentSessionId);
|
||||||
}
|
}
|
||||||
fileTransfer.classList.add("active");
|
fileTransfer.classList.add("active");
|
||||||
} else {
|
} else {
|
||||||
|
|
64
src/main.rs
64
src/main.rs
|
@ -20,7 +20,7 @@ use platform_dirs::AppDirs;
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
use utils::escape_double_quote;
|
use utils::escape_double_quote;
|
||||||
use identity::Identity;
|
use identity::Identity;
|
||||||
use session_manager::{SessionManager, protocol};
|
use session_manager::{SessionManager, SessionCommand, protocol};
|
||||||
use ui_interface::UiConnection;
|
use ui_interface::UiConnection;
|
||||||
|
|
||||||
async fn start_websocket_server(global_vars: Arc<RwLock<GlobalVars>>) -> u16 {
|
async fn start_websocket_server(global_vars: Arc<RwLock<GlobalVars>>) -> u16 {
|
||||||
|
@ -104,7 +104,7 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||||
session.1.outgoing,
|
session.1.outgoing,
|
||||||
&crypto::generate_fingerprint(&session.1.peer_public_key),
|
&crypto::generate_fingerprint(&session.1.peer_public_key),
|
||||||
session.1.ip,
|
session.1.ip,
|
||||||
session.1.file_download.as_ref()
|
session.1.files_download.as_ref()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
{
|
{
|
||||||
|
@ -156,28 +156,44 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||||
"send" => {
|
"send" => {
|
||||||
let session_id: usize = args[1].parse().unwrap();
|
let session_id: usize = args[1].parse().unwrap();
|
||||||
let buffer = protocol::new_message(msg[args[0].len()+args[1].len()+2..].to_string());
|
let buffer = protocol::new_message(msg[args[0].len()+args[1].len()+2..].to_string());
|
||||||
match session_manager.send_to(&session_id, buffer.clone()).await {
|
match session_manager.send_command(&session_id, SessionCommand::Send {
|
||||||
|
buff: buffer.clone()
|
||||||
|
}).await {
|
||||||
Ok(_) => session_manager.store_msg(&session_id, true, buffer),
|
Ok(_) => session_manager.store_msg(&session_id, true, buffer),
|
||||||
Err(e) => print_error!(e)
|
Err(e) => print_error!(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"large_file" => {
|
"large_files" => {
|
||||||
let session_id: usize = args[1].parse().unwrap();
|
let session_id: usize = args[1].parse().unwrap();
|
||||||
let file_size: u64 = args[2].parse().unwrap();
|
let mut file_info = Vec::new();
|
||||||
let file_name = &msg[args[0].len()+args[1].len()+args[2].len()+3..];
|
for n in (2..args.len()).step_by(2) {
|
||||||
if let Err(e) = session_manager.send_to(&session_id, protocol::ask_large_file(file_size, file_name)).await {
|
file_info.push((args[n].parse::<u64>().unwrap(), base64::decode(args[n+1]).unwrap()));
|
||||||
|
}
|
||||||
|
if let Err(e) = session_manager.send_command(&session_id, SessionCommand::Send {
|
||||||
|
buff: protocol::ask_large_files(file_info)
|
||||||
|
}).await {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"download" => {
|
"download" => {
|
||||||
let session_id: usize = args[1].parse().unwrap();
|
let session_id: usize = args[1].parse().unwrap();
|
||||||
if let Err(e) = session_manager.send_to(&session_id, vec![protocol::Headers::ACCEPT_LARGE_FILE]).await {
|
if let Err(e) = session_manager.send_command(&session_id, SessionCommand::Send {
|
||||||
|
buff: vec![protocol::Headers::ACCEPT_LARGE_FILES]
|
||||||
|
}).await {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"abort" => {
|
"abort" => {
|
||||||
let session_id: usize = args[1].parse().unwrap();
|
let session_id: usize = args[1].parse().unwrap();
|
||||||
if let Err(e) = session_manager.send_to(&session_id, vec![protocol::Headers::ABORT_FILE_TRANSFER]).await {
|
if let Err(e) = session_manager.send_command(&session_id, SessionCommand::Send {
|
||||||
|
buff: vec![protocol::Headers::ABORT_FILES_TRANSFER]
|
||||||
|
}).await {
|
||||||
|
print_error!(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"sending_ended" => {
|
||||||
|
let session_id: usize = args[1].parse().unwrap();
|
||||||
|
if let Err(e) = session_manager.send_command(&session_id, SessionCommand::SendingEnded).await {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,7 +340,9 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
||||||
while let Some(Ok(chunk)) = field.next().await {
|
while let Some(Ok(chunk)) = field.next().await {
|
||||||
buffer.extend(chunk);
|
buffer.extend(chunk);
|
||||||
}
|
}
|
||||||
match global_vars_read.session_manager.send_to(&session_id, protocol::file(filename, &buffer)).await {
|
match global_vars_read.session_manager.send_command(&session_id, SessionCommand::Send {
|
||||||
|
buff: protocol::file(filename, &buffer)
|
||||||
|
}).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
match global_vars_read.session_manager.store_file(&session_id, &buffer) {
|
match global_vars_read.session_manager.store_file(&session_id, &buffer) {
|
||||||
Ok(file_uuid) => {
|
Ok(file_uuid) => {
|
||||||
|
@ -359,15 +377,18 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Err(e) = global_vars_read.session_manager.encrypt_file_chunk(&session_id, chunk_buffer.clone()).await {
|
if let Err(e) = global_vars_read.session_manager.send_command(&session_id, SessionCommand::EncryptFileChunk{
|
||||||
|
plain_text: chunk_buffer.clone()
|
||||||
|
}).await {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
return HttpResponse::InternalServerError().finish();
|
return HttpResponse::InternalServerError().finish();
|
||||||
}
|
}
|
||||||
let last_chunk = chunk_buffer.len() < constants::FILE_CHUNK_SIZE;
|
|
||||||
if !match ack_receiver.recv().await {
|
if !match ack_receiver.recv().await {
|
||||||
Some(should_continue) => {
|
Some(should_continue) => {
|
||||||
//send previous encrypted chunk even if transfert is aborted to keep PSEC nonces syncrhonized
|
//send previous encrypted chunk even if transfert is aborted to keep PSEC nonces syncrhonized
|
||||||
if let Err(e) = global_vars_read.session_manager.send_encrypted_file_chunk(&session_id, ack_sender.clone(), last_chunk).await {
|
if let Err(e) = global_vars_read.session_manager.send_command(&session_id, SessionCommand::SendEncryptedFileChunk {
|
||||||
|
ack_sender: ack_sender.clone()
|
||||||
|
}).await {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
|
@ -378,7 +399,7 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
||||||
} {
|
} {
|
||||||
return HttpResponse::InternalServerError().finish()
|
return HttpResponse::InternalServerError().finish()
|
||||||
}
|
}
|
||||||
if last_chunk {
|
if chunk_buffer.len() < constants::FILE_CHUNK_SIZE {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
chunk_buffer.truncate(1);
|
chunk_buffer.truncate(1);
|
||||||
|
@ -540,8 +561,19 @@ fn handle_static(req: HttpRequest) -> HttpResponse {
|
||||||
if splits[0] == "static" {
|
if splits[0] == "static" {
|
||||||
let mut response_builder = HttpResponse::Ok();
|
let mut response_builder = HttpResponse::Ok();
|
||||||
match splits[1] {
|
match splits[1] {
|
||||||
"index.js" => return response_builder.content_type(JS_CONTENT_TYPE).body(include_str!("frontend/index.js")),
|
"index.js" => {
|
||||||
"index.css" => return response_builder.body(include_str!("frontend/index.css")),
|
response_builder.content_type(JS_CONTENT_TYPE);
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
return response_builder.body(fs::read_to_string("src/frontend/index.js").unwrap());
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
return response_builder.body(include_str!("frontend/index.js"));
|
||||||
|
}
|
||||||
|
"index.css" => {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
return response_builder.body(fs::read_to_string("src/frontend/index.css").unwrap());
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
return response_builder.body(include_str!("frontend/index.css"));
|
||||||
|
}
|
||||||
"imgs" => {
|
"imgs" => {
|
||||||
if splits[2] == "icons" && splits.len() <= 5 {
|
if splits[2] == "icons" && splits.len() <= 5 {
|
||||||
let color = if splits.len() == 5 {
|
let color = if splits.len() == 5 {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
mod session;
|
mod session;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
|
|
||||||
use std::{collections::HashMap, net::{IpAddr, SocketAddr}, io::{self, Write}, str::from_utf8, fs::OpenOptions, sync::{Mutex, RwLock, Arc}};
|
use std::{collections::HashMap, fs::OpenOptions, io::{self, Write}, net::{IpAddr, SocketAddr}, path::PathBuf, str::from_utf8, sync::{Mutex, RwLock, Arc}};
|
||||||
use tokio::{net::{TcpListener, TcpStream}, sync::mpsc::{self, Sender, Receiver}};
|
use tokio::{net::{TcpListener, TcpStream}, sync::mpsc::{self, Sender, Receiver}};
|
||||||
use libmdns::Service;
|
use libmdns::Service;
|
||||||
use strum_macros::Display;
|
use strum_macros::Display;
|
||||||
|
@ -24,36 +24,36 @@ pub enum SessionError {
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SessionCommand {
|
pub enum SessionCommand {
|
||||||
Send {
|
Send {
|
||||||
buff: Vec<u8>,
|
buff: Vec<u8>,
|
||||||
},
|
},
|
||||||
SendEncryptedFileChunk {
|
SendEncryptedFileChunk {
|
||||||
sender: Sender<bool>,
|
ack_sender: Sender<bool>,
|
||||||
last_chunk: bool,
|
|
||||||
},
|
},
|
||||||
EncryptFileChunk {
|
EncryptFileChunk {
|
||||||
plain_text: Vec<u8>,
|
plain_text: Vec<u8>,
|
||||||
},
|
},
|
||||||
|
SendingEnded,
|
||||||
Close,
|
Close,
|
||||||
}
|
}
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub enum FileState {
|
|
||||||
ASKING,
|
|
||||||
ACCEPTED,
|
|
||||||
TRANSFERRING,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LargeFileDownload {
|
pub struct LargeFileDownload {
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
pub download_location: String,
|
|
||||||
pub file_size: u64,
|
pub file_size: u64,
|
||||||
pub state: FileState,
|
|
||||||
pub transferred: u64,
|
pub transferred: u64,
|
||||||
pub last_chunk: u128,
|
pub last_chunk: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LargeFilesDownload {
|
||||||
|
pub download_location: PathBuf,
|
||||||
|
pub accepted: bool,
|
||||||
|
pub index: usize,
|
||||||
|
pub files: Vec<LargeFileDownload>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SessionData {
|
pub struct SessionData {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -61,7 +61,7 @@ pub struct SessionData {
|
||||||
pub peer_public_key: [u8; PUBLIC_KEY_LENGTH],
|
pub peer_public_key: [u8; PUBLIC_KEY_LENGTH],
|
||||||
pub ip: IpAddr,
|
pub ip: IpAddr,
|
||||||
sender: Sender<SessionCommand>,
|
sender: Sender<SessionCommand>,
|
||||||
pub file_download: Option<LargeFileDownload>,
|
pub files_download: Option<LargeFilesDownload>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SessionManager {
|
pub struct SessionManager {
|
||||||
|
@ -79,7 +79,7 @@ pub struct SessionManager {
|
||||||
|
|
||||||
impl SessionManager {
|
impl SessionManager {
|
||||||
|
|
||||||
fn with_ui_connection<F>(&self, f: F) where F: Fn(&mut UiConnection) {
|
fn with_ui_connection<F>(&self, f: F) where F: FnOnce(&mut UiConnection) {
|
||||||
let mut ui_connection_opt = self.ui_connection.lock().unwrap();
|
let mut ui_connection_opt = self.ui_connection.lock().unwrap();
|
||||||
match ui_connection_opt.as_mut() {
|
match ui_connection_opt.as_mut() {
|
||||||
Some(ui_connection) => if ui_connection.is_valid {
|
Some(ui_connection) => if ui_connection.is_valid {
|
||||||
|
@ -119,44 +119,9 @@ impl SessionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn encrypt_file_chunk(&self, session_id: &usize, plain_text: Vec<u8>) -> Result<(), SessionError> {
|
pub async fn send_command(&self, session_id: &usize, session_command: SessionCommand) -> Result<(), SessionError> {
|
||||||
if let Some(sender) = self.get_session_sender(session_id) {
|
if let Some(sender) = self.get_session_sender(session_id) {
|
||||||
match sender.send(SessionCommand::EncryptFileChunk {
|
match sender.send(session_command).await {
|
||||||
plain_text,
|
|
||||||
}).await {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) => {
|
|
||||||
print_error!(e);
|
|
||||||
Err(SessionError::BrokenPipe)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(SessionError::InvalidSessionId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_encrypted_file_chunk(&self, session_id: &usize, ack_sender: Sender<bool>, last_chunk: bool) -> Result<(), SessionError> {
|
|
||||||
if let Some(sender) = self.get_session_sender(session_id) {
|
|
||||||
match sender.send(SessionCommand::SendEncryptedFileChunk {
|
|
||||||
sender: ack_sender,
|
|
||||||
last_chunk
|
|
||||||
}).await {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) => {
|
|
||||||
print_error!(e);
|
|
||||||
Err(SessionError::BrokenPipe)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(SessionError::InvalidSessionId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_to(&self, session_id: &usize, message: Vec<u8>) -> Result<(), SessionError> {
|
|
||||||
if let Some(sender) = self.get_session_sender(session_id) {
|
|
||||||
match sender.send(SessionCommand::Send {
|
|
||||||
buff: message
|
|
||||||
}).await {
|
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
|
@ -179,10 +144,10 @@ impl SessionManager {
|
||||||
|
|
||||||
async fn send_msg(&self, session_id: usize, session_write: &mut SessionWrite, buff: &[u8], is_sending: &mut bool, file_ack_sender: Option<&Sender<bool>>) -> Result<(), SessionError> {
|
async fn send_msg(&self, session_id: usize, session_write: &mut SessionWrite, buff: &[u8], is_sending: &mut bool, file_ack_sender: Option<&Sender<bool>>) -> Result<(), SessionError> {
|
||||||
session_write.encrypt_and_send(&buff).await?;
|
session_write.encrypt_and_send(&buff).await?;
|
||||||
if buff[0] == protocol::Headers::ACCEPT_LARGE_FILE {
|
if buff[0] == protocol::Headers::ACCEPT_LARGE_FILES {
|
||||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().file_download.as_mut().unwrap().state = FileState::ACCEPTED;
|
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download.as_mut().unwrap().accepted = true;
|
||||||
} else if buff[0] == protocol::Headers::ABORT_FILE_TRANSFER {
|
} else if buff[0] == protocol::Headers::ABORT_FILES_TRANSFER {
|
||||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().file_download = None;
|
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = None;
|
||||||
*is_sending = false;
|
*is_sending = false;
|
||||||
if let Some(sender) = file_ack_sender {
|
if let Some(sender) = file_ack_sender {
|
||||||
if let Err(e) = sender.send(false).await {
|
if let Err(e) = sender.send(false).await {
|
||||||
|
@ -198,10 +163,10 @@ impl SessionManager {
|
||||||
|
|
||||||
async fn session_worker(&self, session_id: usize, mut receiver: Receiver<SessionCommand>, session: Session) {
|
async fn session_worker(&self, session_id: usize, mut receiver: Receiver<SessionCommand>, session: Session) {
|
||||||
//used when we receive large file
|
//used when we receive large file
|
||||||
let mut local_file_path = None;
|
|
||||||
let mut local_file_handle = None;
|
let mut local_file_handle = None;
|
||||||
//used when we send large file
|
//used when we send large file
|
||||||
let mut next_chunk: Option<Vec<u8>> = None;
|
let mut next_chunk: Option<Vec<u8>> = None;
|
||||||
|
let mut last_chunks_sizes: Option<Vec<u32>> = None;
|
||||||
let mut file_ack_sender: Option<Sender<bool>> = None;
|
let mut file_ack_sender: Option<Sender<bool>> = None;
|
||||||
let mut msg_queue = Vec::new();
|
let mut msg_queue = Vec::new();
|
||||||
let mut is_sending = false;
|
let mut is_sending = false;
|
||||||
|
@ -230,13 +195,14 @@ impl SessionManager {
|
||||||
protocol::Headers::TELL_NAME => {
|
protocol::Headers::TELL_NAME => {
|
||||||
match from_utf8(&buffer[1..]) {
|
match from_utf8(&buffer[1..]) {
|
||||||
Ok(new_name) => {
|
Ok(new_name) => {
|
||||||
|
let new_name = new_name.replace('\n', " ");
|
||||||
self.with_ui_connection(|ui_connection| {
|
self.with_ui_connection(|ui_connection| {
|
||||||
ui_connection.on_name_told(&session_id, new_name);
|
ui_connection.on_name_told(&session_id, &new_name);
|
||||||
});
|
});
|
||||||
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
||||||
if let Some(contact) = loaded_contacts.get_mut(&session_id) {
|
if let Some(contact) = loaded_contacts.get_mut(&session_id) {
|
||||||
contact.name = new_name.to_string();
|
contact.name = new_name.to_string();
|
||||||
if let Err(e) = self.identity.read().unwrap().as_ref().unwrap().change_contact_name(&contact.uuid, new_name) {
|
if let Err(e) = self.identity.read().unwrap().as_ref().unwrap().change_contact_name(&contact.uuid, &new_name) {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -246,53 +212,52 @@ impl SessionManager {
|
||||||
Err(e) => print_error!(e)
|
Err(e) => print_error!(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protocol::Headers::ASK_LARGE_FILE => {
|
protocol::Headers::ASK_LARGE_FILES => {
|
||||||
if self.sessions.read().unwrap().get(&session_id).unwrap().file_download.is_none() && !is_sending { //don't accept 2 file transfers at the same time
|
if self.sessions.read().unwrap().get(&session_id).unwrap().files_download.is_none() && !is_sending { //don't accept 2 file transfers at the same time
|
||||||
if let Some((file_size, file_name)) = protocol::parse_ask_file(&buffer) {
|
if let Some(files_info) = protocol::parse_ask_files(&buffer) {
|
||||||
let download_dir = UserDirs::new().unwrap().download_dir;
|
let download_location = UserDirs::new().unwrap().download_dir;
|
||||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().file_download = Some(LargeFileDownload{
|
let files: Vec<LargeFileDownload> = files_info.into_iter().map(|info| {
|
||||||
file_name: file_name.clone(),
|
LargeFileDownload {
|
||||||
download_location: download_dir.to_str().unwrap().to_string(),
|
file_name: info.1,
|
||||||
file_size,
|
file_size: info.0,
|
||||||
state: FileState::ASKING,
|
transferred: 0,
|
||||||
transferred: 0,
|
last_chunk: get_unix_timestamp(),
|
||||||
last_chunk: get_unix_timestamp(),
|
}
|
||||||
|
}).collect();
|
||||||
|
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = Some(LargeFilesDownload {
|
||||||
|
download_location: download_location.clone(),
|
||||||
|
accepted: false,
|
||||||
|
index: 0,
|
||||||
|
files: files.clone(),
|
||||||
});
|
});
|
||||||
local_file_path = Some(get_not_used_path(&file_name, &download_dir));
|
|
||||||
self.with_ui_connection(|ui_connection| {
|
self.with_ui_connection(|ui_connection| {
|
||||||
ui_connection.on_ask_large_file(&session_id, file_size, &file_name, download_dir.to_str().unwrap());
|
ui_connection.on_ask_large_files(&session_id, &files, download_location.to_str().unwrap());
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if let Err(e) = session_write.encrypt_and_send(&[protocol::Headers::ABORT_FILE_TRANSFER]).await {
|
} else if let Err(e) = session_write.encrypt_and_send(&[protocol::Headers::ABORT_FILES_TRANSFER]).await {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protocol::Headers::LARGE_FILE_CHUNK => {
|
protocol::Headers::LARGE_FILE_CHUNK => {
|
||||||
let state = {
|
|
||||||
let sessions = self.sessions.read().unwrap();
|
|
||||||
match sessions.get(&session_id).unwrap().file_download.as_ref() {
|
|
||||||
Some(file_transfer) => Some(file_transfer.state),
|
|
||||||
None => None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut should_accept_chunk = false;
|
let mut should_accept_chunk = false;
|
||||||
if let Some(state) = state {
|
{
|
||||||
if state == FileState::ACCEPTED {
|
let sessions = self.sessions.read().unwrap();
|
||||||
if let Some(file_path) = local_file_path.as_ref() {
|
if let Some(files_transfer) = sessions.get(&session_id).unwrap().files_download.as_ref() {
|
||||||
match OpenOptions::new().append(true).create(true).open(file_path) {
|
if files_transfer.accepted {
|
||||||
Ok(file) => {
|
if local_file_handle.is_some() {
|
||||||
local_file_handle = Some(file);
|
should_accept_chunk = true;
|
||||||
let mut sessions = self.sessions.write().unwrap();
|
} else {
|
||||||
let file_transfer = sessions.get_mut(&session_id).unwrap().file_download.as_mut().unwrap();
|
let local_file_path = get_not_used_path(&files_transfer.files[files_transfer.index].file_name, &files_transfer.download_location);
|
||||||
file_transfer.state = FileState::TRANSFERRING;
|
match OpenOptions::new().append(true).create(true).open(local_file_path) {
|
||||||
should_accept_chunk = true;
|
Ok(file) => {
|
||||||
|
local_file_handle = Some(file);
|
||||||
|
should_accept_chunk = true;
|
||||||
|
}
|
||||||
|
Err(e) => print_error!(e)
|
||||||
}
|
}
|
||||||
Err(e) => print_error!(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if state == FileState::TRANSFERRING {
|
|
||||||
should_accept_chunk = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if should_accept_chunk {
|
if should_accept_chunk {
|
||||||
|
@ -303,12 +268,16 @@ impl SessionManager {
|
||||||
let chunk_size = (buffer.len()-1) as u64;
|
let chunk_size = (buffer.len()-1) as u64;
|
||||||
{
|
{
|
||||||
let mut sessions = self.sessions.write().unwrap();
|
let mut sessions = self.sessions.write().unwrap();
|
||||||
let file_transfer = sessions.get_mut(&session_id).unwrap().file_download.as_mut().unwrap();
|
let files_transfer = sessions.get_mut(&session_id).unwrap().files_download.as_mut().unwrap();
|
||||||
|
let file_transfer = &mut files_transfer.files[files_transfer.index];
|
||||||
file_transfer.last_chunk = get_unix_timestamp();
|
file_transfer.last_chunk = get_unix_timestamp();
|
||||||
file_transfer.transferred += chunk_size;
|
file_transfer.transferred += chunk_size;
|
||||||
if file_transfer.transferred >= file_transfer.file_size { //we downloaded all the file
|
if file_transfer.transferred >= file_transfer.file_size { //we downloaded all the file
|
||||||
sessions.get_mut(&session_id).unwrap().file_download = None;
|
if files_transfer.index+1 == files_transfer.files.len() {
|
||||||
local_file_path = None;
|
sessions.get_mut(&session_id).unwrap().files_download = None;
|
||||||
|
} else {
|
||||||
|
files_transfer.index += 1;
|
||||||
|
}
|
||||||
local_file_handle = None;
|
local_file_handle = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -317,7 +286,7 @@ impl SessionManager {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
self.with_ui_connection(|ui_connection| {
|
self.with_ui_connection(|ui_connection| {
|
||||||
ui_connection.inc_file_transfer(&session_id, chunk_size);
|
ui_connection.inc_files_transfer(&session_id, chunk_size);
|
||||||
});
|
});
|
||||||
is_success = true;
|
is_success = true;
|
||||||
}
|
}
|
||||||
|
@ -325,10 +294,9 @@ impl SessionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !is_success {
|
if !is_success {
|
||||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().file_download = None;
|
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = None;
|
||||||
local_file_path = None;
|
|
||||||
local_file_handle = None;
|
local_file_handle = None;
|
||||||
if let Err(e) = session_write.encrypt_and_send(&[protocol::Headers::ABORT_FILE_TRANSFER]).await {
|
if let Err(e) = session_write.encrypt_and_send(&[protocol::Headers::ABORT_FILES_TRANSFER]).await {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -337,9 +305,10 @@ impl SessionManager {
|
||||||
}
|
}
|
||||||
protocol::Headers::ACK_CHUNK => {
|
protocol::Headers::ACK_CHUNK => {
|
||||||
if let Some(sender) = file_ack_sender.clone() {
|
if let Some(sender) = file_ack_sender.clone() {
|
||||||
if let Some(next_chunk) = next_chunk.as_ref() {
|
if let Some(last_chunks_sizes) = last_chunks_sizes.as_mut() {
|
||||||
|
let chunk_size = last_chunks_sizes.remove(0);
|
||||||
self.with_ui_connection(|ui_connection| {
|
self.with_ui_connection(|ui_connection| {
|
||||||
ui_connection.inc_file_transfer(&session_id, next_chunk.len() as u64);
|
ui_connection.inc_files_transfer(&session_id, chunk_size.into());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if sender.send(true).await.is_err() {
|
if sender.send(true).await.is_err() {
|
||||||
|
@ -347,15 +316,14 @@ impl SessionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protocol::Headers::ABORT_FILE_TRANSFER => {
|
protocol::Headers::ABORT_FILES_TRANSFER => {
|
||||||
if let Some(sender) = file_ack_sender.clone() {
|
if let Some(sender) = file_ack_sender.clone() {
|
||||||
if let Err(e) = sender.send(false).await {
|
if let Err(e) = sender.send(false).await {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
}
|
}
|
||||||
is_sending = false;
|
is_sending = false;
|
||||||
}
|
}
|
||||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().file_download = None;
|
self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = None;
|
||||||
local_file_path = None;
|
|
||||||
local_file_handle = None;
|
local_file_handle = None;
|
||||||
self.with_ui_connection(|ui_connection| {
|
self.with_ui_connection(|ui_connection| {
|
||||||
ui_connection.on_received(&session_id, &buffer);
|
ui_connection.on_received(&session_id, &buffer);
|
||||||
|
@ -387,8 +355,9 @@ impl SessionManager {
|
||||||
let is_classical_message = header == protocol::Headers::MESSAGE || header == protocol::Headers::FILE;
|
let is_classical_message = header == protocol::Headers::MESSAGE || header == protocol::Headers::FILE;
|
||||||
if is_classical_message {
|
if is_classical_message {
|
||||||
self.set_seen(session_id, false);
|
self.set_seen(session_id, false);
|
||||||
} else if header == protocol::Headers::ACCEPT_LARGE_FILE {
|
} else if header == protocol::Headers::ACCEPT_LARGE_FILES {
|
||||||
is_sending = true;
|
is_sending = true;
|
||||||
|
last_chunks_sizes = Some(Vec::new());
|
||||||
}
|
}
|
||||||
self.with_ui_connection(|ui_connection| {
|
self.with_ui_connection(|ui_connection| {
|
||||||
ui_connection.on_received(&session_id, buffer.as_ref().unwrap());
|
ui_connection.on_received(&session_id, buffer.as_ref().unwrap());
|
||||||
|
@ -421,12 +390,15 @@ impl SessionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SessionCommand::EncryptFileChunk { plain_text } => next_chunk = Some(session_write.encrypt(&plain_text)),
|
SessionCommand::EncryptFileChunk { plain_text } => {
|
||||||
SessionCommand::SendEncryptedFileChunk { sender, last_chunk } => {
|
last_chunks_sizes.as_mut().unwrap().push(plain_text.len() as u32);
|
||||||
|
next_chunk = Some(session_write.encrypt(&plain_text));
|
||||||
|
}
|
||||||
|
SessionCommand::SendEncryptedFileChunk { ack_sender } => {
|
||||||
if let Some(chunk) = next_chunk.as_ref() {
|
if let Some(chunk) = next_chunk.as_ref() {
|
||||||
match session_write.socket_write(chunk).await {
|
match session_write.socket_write(chunk).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
file_ack_sender = Some(sender);
|
file_ack_sender = Some(ack_sender);
|
||||||
//once the pre-encrypted chunk is sent, we can send the pending messages
|
//once the pre-encrypted chunk is sent, we can send the pending messages
|
||||||
while msg_queue.len() > 0 {
|
while msg_queue.len() > 0 {
|
||||||
let msg = msg_queue.remove(0);
|
let msg = msg_queue.remove(0);
|
||||||
|
@ -435,9 +407,6 @@ impl SessionManager {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if last_chunk {
|
|
||||||
is_sending = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
print_error!(e);
|
print_error!(e);
|
||||||
|
@ -446,13 +415,10 @@ impl SessionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SessionCommand::SendingEnded => is_sending = false,
|
||||||
SessionCommand::Close => break
|
SessionCommand::Close => break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else => {
|
|
||||||
println!("{} dead", session_id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -508,7 +474,7 @@ impl SessionManager {
|
||||||
peer_public_key,
|
peer_public_key,
|
||||||
ip,
|
ip,
|
||||||
sender: sender,
|
sender: sender,
|
||||||
file_download: None,
|
files_download: None,
|
||||||
};
|
};
|
||||||
let mut session_id = None;
|
let mut session_id = None;
|
||||||
for (i, contact) in session_manager.loaded_contacts.read().unwrap().iter() {
|
for (i, contact) in session_manager.loaded_contacts.read().unwrap().iter() {
|
||||||
|
|
|
@ -8,11 +8,11 @@ impl Headers {
|
||||||
pub const ASK_NAME: u8 = 0x01;
|
pub const ASK_NAME: u8 = 0x01;
|
||||||
pub const TELL_NAME: u8 = 0x02;
|
pub const TELL_NAME: u8 = 0x02;
|
||||||
pub const FILE: u8 = 0x03;
|
pub const FILE: u8 = 0x03;
|
||||||
pub const ASK_LARGE_FILE: u8 = 0x04;
|
pub const ASK_LARGE_FILES: u8 = 0x04;
|
||||||
pub const ACCEPT_LARGE_FILE: u8 = 0x05;
|
pub const ACCEPT_LARGE_FILES: u8 = 0x05;
|
||||||
pub const LARGE_FILE_CHUNK: u8 = 0x06;
|
pub const LARGE_FILE_CHUNK: u8 = 0x06;
|
||||||
pub const ACK_CHUNK: u8 = 0x07;
|
pub const ACK_CHUNK: u8 = 0x07;
|
||||||
pub const ABORT_FILE_TRANSFER: u8 = 0x08;
|
pub const ABORT_FILES_TRANSFER: u8 = 0x08;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_message(message: String) -> Vec<u8> {
|
pub fn new_message(message: String) -> Vec<u8> {
|
||||||
|
@ -42,20 +42,41 @@ pub fn parse_file<'a>(buffer: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ask_large_file(file_size: u64, file_name: &str) -> Vec<u8> {
|
pub fn ask_large_files(file_info: Vec<(u64, Vec<u8>)>) -> Vec<u8> {
|
||||||
[&[Headers::ASK_LARGE_FILE], &file_size.to_be_bytes()[..], file_name.as_bytes()].concat()
|
let mut buff = vec![Headers::ASK_LARGE_FILES];
|
||||||
|
file_info.into_iter().for_each(|info| {
|
||||||
|
buff.extend(&info.0.to_be_bytes());
|
||||||
|
buff.extend(&(info.1.len() as u16).to_be_bytes());
|
||||||
|
buff.extend(info.1);
|
||||||
|
});
|
||||||
|
buff
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_ask_file(buffer: &[u8]) -> Option<(u64, String)> {
|
pub fn parse_ask_files(buffer: &[u8]) -> Option<Vec<(u64, String)>> {
|
||||||
if buffer.len() > 9 {
|
let mut files_info = Vec::new();
|
||||||
let file_size = u64::from_be_bytes(buffer[1..9].try_into().unwrap());
|
let mut n = 1;
|
||||||
match from_utf8(&buffer[9..]) {
|
while n < buffer.len() {
|
||||||
Ok(file_name) => {
|
if buffer[n..].len() > 10 { //8 + 2
|
||||||
let file_name = sanitize_filename::sanitize(file_name);
|
let file_size = u64::from_be_bytes(buffer[n..n+8].try_into().unwrap());
|
||||||
return Some((file_size, file_name));
|
let file_name_len = u16::from_be_bytes(buffer[n+8..n+10].try_into().unwrap()) as usize;
|
||||||
|
if buffer.len() >= n+10+file_name_len {
|
||||||
|
match from_utf8(&buffer[n+10..n+10+file_name_len]) {
|
||||||
|
Ok(file_name) => {
|
||||||
|
let file_name = sanitize_filename::sanitize(file_name);
|
||||||
|
files_info.push((file_size, file_name));
|
||||||
|
n += 10+file_name_len;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
print_error!(e);
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None
|
||||||
}
|
}
|
||||||
Err(e) => print_error!(e),
|
} else {
|
||||||
|
return None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
Some(files_info)
|
||||||
}
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
use std::net::{IpAddr, TcpStream};
|
use std::net::{IpAddr, TcpStream};
|
||||||
use tungstenite::{WebSocket, protocol::Role, Message};
|
use tungstenite::{WebSocket, protocol::Role, Message};
|
||||||
use crate::{protocol, session_manager::LargeFileDownload};
|
use crate::{protocol, session_manager::{LargeFileDownload, LargeFilesDownload}};
|
||||||
|
|
||||||
mod ui_messages {
|
mod ui_messages {
|
||||||
use std::{fmt::Display, iter::FromIterator, net::IpAddr, str::from_utf8};
|
use std::{fmt::Display, iter::FromIterator, net::IpAddr, str::from_utf8};
|
||||||
use tungstenite::Message;
|
use tungstenite::Message;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use crate::{print_error, session_manager::{protocol, LargeFileDownload, FileState}, utils::to_uuid_bytes};
|
use crate::{print_error, session_manager::{LargeFileDownload, LargeFilesDownload, protocol}, utils::to_uuid_bytes};
|
||||||
|
|
||||||
const ON_NEW_MESSAGE: &str = "new_message";
|
const ON_NEW_MESSAGE: &str = "new_message";
|
||||||
const LOAD_SENT_MESSAGE: &str = "load_sent_msg";
|
const LOAD_SENT_MESSAGE: &str = "load_sent_msg";
|
||||||
|
@ -45,30 +45,40 @@ mod ui_messages {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new_file_transfer(session_id: &usize, file_transfer: &LargeFileDownload) -> Message {
|
pub fn new_files_transfer(session_id: &usize, files_transfer: &LargeFilesDownload) -> Message {
|
||||||
if file_transfer.state == FileState::ASKING {
|
if files_transfer.accepted {
|
||||||
on_ask_large_file(session_id, file_transfer.file_size, &file_transfer.file_name, &file_transfer.download_location)
|
let mut s = format!(
|
||||||
} else {
|
"files_transfer {} {}",
|
||||||
Message::from(format!(
|
|
||||||
"file_transfer {} {} {} {} {} {}",
|
|
||||||
session_id,
|
session_id,
|
||||||
base64::encode(&file_transfer.file_name),
|
files_transfer.index
|
||||||
file_transfer.file_size,
|
);
|
||||||
if file_transfer.state == FileState::ACCEPTED {
|
files_transfer.files.iter().for_each(|file| {
|
||||||
"accepted"
|
s.push_str(&format!(
|
||||||
} else {
|
" {} {} {} {}",
|
||||||
"transferring"
|
base64::encode(&file.file_name),
|
||||||
},
|
file.file_size,
|
||||||
file_transfer.transferred,
|
file.transferred,
|
||||||
file_transfer.last_chunk,
|
file.last_chunk,
|
||||||
))
|
));
|
||||||
|
});
|
||||||
|
Message::from(s)
|
||||||
|
} else {
|
||||||
|
on_ask_large_files(session_id, &files_transfer.files, files_transfer.download_location.to_str().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn on_ask_large_file(session_id: &usize, file_size: u64, file_name: &str, download_location: &str) -> Message {
|
pub fn on_ask_large_files(session_id: &usize, files: &Vec<LargeFileDownload>, download_location: &str) -> Message {
|
||||||
Message::from(format!("ask_large_file {} {} {} {}", session_id, file_size, base64::encode(file_name), base64::encode(download_location)))
|
let mut s = format!("ask_large_files {} {}", session_id, base64::encode(download_location));
|
||||||
|
files.into_iter().for_each(|file| {
|
||||||
|
s.push_str(&format!(
|
||||||
|
" {} {}",
|
||||||
|
base64::encode(&file.file_name),
|
||||||
|
file.file_size,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
Message::from(s)
|
||||||
}
|
}
|
||||||
pub fn on_large_file_accepted(session_id: &usize) -> Message {
|
pub fn on_large_files_accepted(session_id: &usize) -> Message {
|
||||||
simple_event("file_accepted", session_id)
|
simple_event("files_accepted", session_id)
|
||||||
}
|
}
|
||||||
pub fn on_file_transfer_aborted(session_id: &usize) -> Message {
|
pub fn on_file_transfer_aborted(session_id: &usize) -> Message {
|
||||||
simple_event("aborted", session_id)
|
simple_event("aborted", session_id)
|
||||||
|
@ -76,7 +86,7 @@ mod ui_messages {
|
||||||
pub fn on_new_message(session_id: &usize, outgoing: bool, buffer: &[u8]) -> Option<Message> {
|
pub fn on_new_message(session_id: &usize, outgoing: bool, buffer: &[u8]) -> Option<Message> {
|
||||||
new_message(ON_NEW_MESSAGE, session_id, outgoing, &buffer[1..])
|
new_message(ON_NEW_MESSAGE, session_id, outgoing, &buffer[1..])
|
||||||
}
|
}
|
||||||
pub fn inc_file_transfer(session_id: &usize, chunk_size: u64) -> Message {
|
pub fn inc_files_transfer(session_id: &usize, chunk_size: u64) -> Message {
|
||||||
Message::from(format!("inc_file_transfer {} {}", session_id, chunk_size))
|
Message::from(format!("inc_file_transfer {} {}", session_id, chunk_size))
|
||||||
}
|
}
|
||||||
pub fn load_msg(session_id: &usize, outgoing: bool, buffer: &[u8]) -> Option<Message> {
|
pub fn load_msg(session_id: &usize, outgoing: bool, buffer: &[u8]) -> Option<Message> {
|
||||||
|
@ -138,16 +148,16 @@ impl UiConnection {
|
||||||
let ui_message = match buffer[0] {
|
let ui_message = match buffer[0] {
|
||||||
protocol::Headers::MESSAGE => ui_messages::on_new_message(session_id, false, buffer),
|
protocol::Headers::MESSAGE => ui_messages::on_new_message(session_id, false, buffer),
|
||||||
protocol::Headers::FILE => ui_messages::on_file_received(session_id, buffer),
|
protocol::Headers::FILE => ui_messages::on_file_received(session_id, buffer),
|
||||||
protocol::Headers::ACCEPT_LARGE_FILE => Some(ui_messages::on_large_file_accepted(session_id)),
|
protocol::Headers::ACCEPT_LARGE_FILES => Some(ui_messages::on_large_files_accepted(session_id)),
|
||||||
protocol::Headers::ABORT_FILE_TRANSFER => Some(ui_messages::on_file_transfer_aborted(session_id)),
|
protocol::Headers::ABORT_FILES_TRANSFER => Some(ui_messages::on_file_transfer_aborted(session_id)),
|
||||||
_ => None
|
_ => None
|
||||||
};
|
};
|
||||||
if ui_message.is_some() {
|
if ui_message.is_some() {
|
||||||
self.write_message(ui_message.unwrap())
|
self.write_message(ui_message.unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn on_ask_large_file(&mut self, session_id: &usize, file_size: u64, file_name: &str, download_location: &str) {
|
pub fn on_ask_large_files(&mut self, session_id: &usize, files: &Vec<LargeFileDownload>, download_location: &str) {
|
||||||
self.write_message(ui_messages::on_ask_large_file(session_id, file_size, file_name, download_location))
|
self.write_message(ui_messages::on_ask_large_files(session_id, files, download_location))
|
||||||
}
|
}
|
||||||
pub fn on_msg_sent(&mut self, session_id: usize, buffer: &[u8]) {
|
pub fn on_msg_sent(&mut self, session_id: usize, buffer: &[u8]) {
|
||||||
match buffer[0] {
|
match buffer[0] {
|
||||||
|
@ -155,14 +165,14 @@ impl UiConnection {
|
||||||
Some(msg) => self.write_message(msg),
|
Some(msg) => self.write_message(msg),
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
protocol::Headers::ABORT_FILE_TRANSFER => self.write_message(ui_messages::on_file_transfer_aborted(&session_id)),
|
protocol::Headers::ABORT_FILES_TRANSFER => self.write_message(ui_messages::on_file_transfer_aborted(&session_id)),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn on_new_session(&mut self, session_id: &usize, name: &str, outgoing: bool, fingerprint: &str, ip: IpAddr, file_transfer: Option<&LargeFileDownload>) {
|
pub fn on_new_session(&mut self, session_id: &usize, name: &str, outgoing: bool, fingerprint: &str, ip: IpAddr, files_transfer: Option<&LargeFilesDownload>) {
|
||||||
self.write_message(ui_messages::on_new_session(session_id, name, outgoing, fingerprint, ip));
|
self.write_message(ui_messages::on_new_session(session_id, name, outgoing, fingerprint, ip));
|
||||||
if let Some(file_transfer) = file_transfer {
|
if let Some(files_transfer) = files_transfer {
|
||||||
self.write_message(ui_messages::new_file_transfer(session_id, file_transfer));
|
self.write_message(ui_messages::new_files_transfer(session_id, files_transfer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn on_disconnected(&mut self, session_id: &usize) {
|
pub fn on_disconnected(&mut self, session_id: &usize) {
|
||||||
|
@ -172,8 +182,8 @@ impl UiConnection {
|
||||||
self.write_message(ui_messages::on_name_told(session_id, name));
|
self.write_message(ui_messages::on_name_told(session_id, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inc_file_transfer(&mut self, session_id: &usize, chunk_size: u64) {
|
pub fn inc_files_transfer(&mut self, session_id: &usize, chunk_size: u64) {
|
||||||
self.write_message(ui_messages::inc_file_transfer(session_id, chunk_size));
|
self.write_message(ui_messages::inc_files_transfer(session_id, chunk_size));
|
||||||
}
|
}
|
||||||
pub fn set_as_contact(&mut self, session_id: usize, name: &str, verified: bool, fingerprint: &str) {
|
pub fn set_as_contact(&mut self, session_id: usize, name: &str, verified: bool, fingerprint: &str) {
|
||||||
self.write_message(ui_messages::set_as_contact(session_id, name, verified, fingerprint));
|
self.write_message(ui_messages::set_as_contact(session_id, name, verified, fingerprint));
|
||||||
|
|
Loading…
Reference in New Issue