Avatars
This commit is contained in:
parent
da4cc4995f
commit
8cc6f6b50f
219
Cargo.lock
generated
219
Cargo.lock
generated
@ -285,6 +285,12 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.4.1"
|
||||
@ -372,8 +378,10 @@ dependencies = [
|
||||
"html-escape",
|
||||
"html-minifier",
|
||||
"if-addrs",
|
||||
"image",
|
||||
"lazy_static",
|
||||
"libmdns",
|
||||
"linked-hash-map",
|
||||
"multicast_dns",
|
||||
"platform-dirs",
|
||||
"rand 0.7.3",
|
||||
@ -395,9 +403,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-psec"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58ab60657d7b3949c3bdb06570fc4855a4e83dbe6c9da73902c67355dd8f37a8"
|
||||
checksum = "31dcd9dc064b59d84e03e9c42c97ca8bba139ff04a874afc0e93944d7f62e235"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"async-trait",
|
||||
@ -510,6 +518,12 @@ version = "3.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
@ -564,6 +578,12 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "const_fn"
|
||||
version = "0.4.7"
|
||||
@ -633,6 +653,51 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if 1.0.0",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.11.0"
|
||||
@ -665,6 +730,16 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deflate"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.14"
|
||||
@ -809,7 +884,7 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crc32fast",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.4.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1004,6 +1079,16 @@ dependencies = [
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a668f699973d0f573d15749b7002a9ac9e1f9c6b220e7b165601334c173d8de"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.2.7"
|
||||
@ -1173,6 +1258,25 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.23.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"gif",
|
||||
"jpeg-decoder",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"png",
|
||||
"scoped_threadpool",
|
||||
"tiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.6.2"
|
||||
@ -1228,6 +1332,15 @@ version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
|
||||
dependencies = [
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.51"
|
||||
@ -1351,6 +1464,15 @@ version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.16"
|
||||
@ -1366,6 +1488,15 @@ dependencies = [
|
||||
"macro-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.4"
|
||||
@ -1518,6 +1649,28 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
@ -1706,6 +1859,18 @@ dependencies = [
|
||||
"dirs-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.16.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"deflate",
|
||||
"miniz_oxide 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.5.0"
|
||||
@ -1840,6 +2005,31 @@ dependencies = [
|
||||
"rand_core 0.6.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"crossbeam-deque",
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.8"
|
||||
@ -1954,6 +2144,12 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped_threadpool"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
@ -2265,6 +2461,17 @@ dependencies = [
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
|
||||
dependencies = [
|
||||
"jpeg-decoder",
|
||||
"miniz_oxide 0.4.4",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.2.26"
|
||||
@ -2664,6 +2871,12 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e"
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "0.4.3"
|
||||
|
@ -36,8 +36,10 @@ if-addrs = "0.6"
|
||||
base64 = "0.13"
|
||||
scrypt = "0.7"
|
||||
zeroize = "1.2"
|
||||
image = "0.23"
|
||||
yaml-rust = "0.4" #only in debug mode
|
||||
|
||||
[build-dependencies]
|
||||
html-minifier = "3.0"
|
||||
yaml-rust = "0.4"
|
||||
linked-hash-map = "0.5"
|
25
build.rs
25
build.rs
@ -1,5 +1,9 @@
|
||||
#[cfg(not(debug_assertions))]
|
||||
use std::{env, fs::{File, read_to_string, create_dir}, path::Path, io::{Write, ErrorKind}};
|
||||
use {
|
||||
std::{env, fs::{File, read_to_string, create_dir}, path::Path, io::{Write, ErrorKind}},
|
||||
yaml_rust::{YamlLoader, Yaml},
|
||||
linked_hash_map::LinkedHashMap,
|
||||
};
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn minify_content(content: &str, language: &str) -> Option<String> {
|
||||
@ -12,9 +16,14 @@ fn minify_content(content: &str, language: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn generate_web_files() {
|
||||
use yaml_rust::YamlLoader;
|
||||
fn replace_fields(content: &mut String, fields: &LinkedHashMap<Yaml, Yaml>) {
|
||||
fields.into_iter().for_each(|field| {
|
||||
*content = content.replace(field.0.as_str().unwrap(), field.1.as_str().unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn generate_web_files() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let out_dir = Path::new(&out_dir);
|
||||
let src_dir = Path::new("src/frontend");
|
||||
@ -26,7 +35,7 @@ fn generate_web_files() {
|
||||
}
|
||||
|
||||
let config = &YamlLoader::load_from_str(&read_to_string("config.yml").unwrap()).unwrap()[0];
|
||||
let css_values = config["css"].as_hash().unwrap();
|
||||
let fields = config.as_hash().unwrap();
|
||||
|
||||
[
|
||||
"login.html",
|
||||
@ -40,9 +49,7 @@ fn generate_web_files() {
|
||||
let extension = path.extension().unwrap().to_str().unwrap();
|
||||
let mut content = read_to_string(src_dir.join(path)).unwrap();
|
||||
if extension == "css" {
|
||||
css_values.into_iter().for_each(|entry| {
|
||||
content = content.replace(entry.0.as_str().unwrap(), entry.1.as_str().unwrap());
|
||||
});
|
||||
replace_fields(&mut content, fields);
|
||||
}
|
||||
if file_name == &"index.html" {
|
||||
content = content.replace("AIRA_VERSION", env!("CARGO_PKG_VERSION"));
|
||||
@ -51,6 +58,10 @@ fn generate_web_files() {
|
||||
let mut dst = File::create(out_dir.join(path)).unwrap();
|
||||
dst.write(minified_content.as_bytes()).unwrap();
|
||||
});
|
||||
|
||||
let mut text_avatar = read_to_string("src/frontend/imgs/text_avatar.svg").unwrap();
|
||||
replace_fields(&mut text_avatar, fields);
|
||||
File::create(out_dir.join("text_avatar.svg")).unwrap().write(text_avatar.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -1,2 +1 @@
|
||||
css:
|
||||
ACCENT_COLOR: "19a52c"
|
||||
ACCENT_COLOR: "19a52c"
|
@ -1,13 +1,17 @@
|
||||
function generateAvatar(name) {
|
||||
let span = document.createElement("span");
|
||||
if (typeof name == "undefined") {
|
||||
span.appendChild(document.createTextNode("?"));
|
||||
} else if (name.length > 0) {
|
||||
span.appendChild(document.createTextNode(name[0].toUpperCase()));
|
||||
}
|
||||
let div = document.createElement("div");
|
||||
div.classList.add("avatar");
|
||||
div.appendChild(span);
|
||||
div.appendChild(document.createElement("div")); //used for background
|
||||
return div;
|
||||
function generateImgAvatar() {
|
||||
let img = document.createElement("img");
|
||||
img.classList.add("avatar");
|
||||
return img;
|
||||
}
|
||||
|
||||
function generateSelfAvatar(timestamp) {
|
||||
let img = generateImgAvatar();
|
||||
img.src = "/avatar/self?"+timestamp;
|
||||
return img;
|
||||
}
|
||||
|
||||
function generateAvatar(sessionId, name, timestamp) {
|
||||
let img = generateImgAvatar();
|
||||
img.src = "/avatar/"+sessionId+"/"+name+"?"+timestamp;
|
||||
return img;
|
||||
}
|
@ -26,24 +26,8 @@ input[type="text"], input[type="password"] {
|
||||
}
|
||||
|
||||
.avatar {
|
||||
position: relative;
|
||||
margin-right: .5em;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
}
|
||||
.avatar div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--accent);
|
||||
border-radius: 100%;
|
||||
}
|
||||
.avatar span {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border-radius: 50%;
|
||||
}
|
4
src/frontend/imgs/text_avatar.svg
Normal file
4
src/frontend/imgs/text_avatar.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="50" cy="50" r="50" fill="#ACCENT_COLOR"/>
|
||||
<text x="50" y="50" text-anchor="middle" dominant-baseline="middle" fill="white" font-weight="bold" font-size="60" dy=".1em" font-family="Arial,Helvetica,Sans-Serif">LETTER</text>
|
||||
</svg>
|
After Width: | Height: | Size: 305 B |
@ -67,7 +67,7 @@ input[type="file"] {
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: 40%;
|
||||
width: 40vw;
|
||||
max-height: 90vh;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
@ -102,29 +102,6 @@ input[type="file"] {
|
||||
content: url("/static/imgs/icons/cancel");
|
||||
background-color: unset;
|
||||
}
|
||||
.popup .avatar {
|
||||
font-size: 4em;
|
||||
margin: auto;
|
||||
}
|
||||
.popup section {
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
.popup section:first-of-type {
|
||||
border-top: unset;
|
||||
}
|
||||
.popup input {
|
||||
display: block;
|
||||
margin: 10px;
|
||||
}
|
||||
.popup span {
|
||||
font-weight: bold;
|
||||
}
|
||||
.popup>div>div p {
|
||||
font-weight: normal;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.popup h2.warning::before {
|
||||
content: url("/static/imgs/icons/warning/ACCENT_COLOR");
|
||||
width: 9%;
|
||||
@ -187,6 +164,57 @@ label {
|
||||
.switch input:checked + span:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
#avatarContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
#avatarContainer .avatar {
|
||||
font-size: 4em;
|
||||
margin-right: unset;
|
||||
}
|
||||
#avatarContainer label:hover .avatar {
|
||||
opacity: .4;
|
||||
}
|
||||
#avatarContainer>label {
|
||||
position: relative;
|
||||
}
|
||||
#avatarContainer .avatar + p {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
#avatarContainer label:hover p {
|
||||
display: block;
|
||||
}
|
||||
#profile_info section {
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
#profile_info section:first-of-type {
|
||||
border-top: unset;
|
||||
}
|
||||
#profile_info input {
|
||||
margin: 10px;
|
||||
}
|
||||
#profile_info span {
|
||||
font-weight: bold;
|
||||
}
|
||||
#profile_info>div>div p {
|
||||
font-weight: normal;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
#session_info .avatar {
|
||||
width: 6em;
|
||||
height: 6em;
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
#session_info .name {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -8,6 +8,9 @@ let currentSessionId = -1;
|
||||
let sessionsData = new Map();
|
||||
let msgHistory = new Map();
|
||||
let pendingFilesTransfers = new Map();
|
||||
let avatarTimestamps = new Map([
|
||||
["self", Date.now()]
|
||||
]);
|
||||
|
||||
function onClickSession(event) {
|
||||
let sessionId = event.currentTarget.getAttribute("data-sessionId");
|
||||
@ -75,7 +78,7 @@ document.getElementById("delete_conversation").onclick = function() {
|
||||
showPopup(mainDiv);
|
||||
};
|
||||
document.getElementById("add_contact").onclick = function() {
|
||||
socket.send("contact "+currentSessionId+" "+sessionsData.get(currentSessionId).name);
|
||||
socket.send("contact "+currentSessionId);
|
||||
sessionsData.get(currentSessionId).isContact = true;
|
||||
displayHeader();
|
||||
displaySessions();
|
||||
@ -234,8 +237,47 @@ msg_log.onscroll = function() {
|
||||
let profile_div = document.querySelector("#me>div");
|
||||
profile_div.onclick = function() {
|
||||
let mainDiv = document.createElement("div");
|
||||
let avatar = generateAvatar(identityName);
|
||||
mainDiv.appendChild(avatar);
|
||||
mainDiv.id = "profile_info";
|
||||
let avatarContainer = document.createElement("div");
|
||||
avatarContainer.id = "avatarContainer";
|
||||
let labelAvatar = document.createElement("label");
|
||||
labelAvatar.setAttribute("for", "avatar_input");
|
||||
let inputAvatar = document.createElement("input");
|
||||
inputAvatar.type = "file";
|
||||
inputAvatar.id = "avatar_input";
|
||||
inputAvatar.onchange = function(event) {
|
||||
let file = event.target.files[0];
|
||||
if (file.size < 10000000) {
|
||||
let formData = new FormData();
|
||||
formData.append("avatar", file);
|
||||
fetch("/set_avatar", {method: "POST", body: formData}).then(response => {
|
||||
if (response.ok) {
|
||||
avatarTimestamps.set("self", Date.now());
|
||||
document.querySelector("#avatarContainer .avatar").src = "/avatar/self?"+avatarTimestamps.get("self");
|
||||
displayProfile();
|
||||
if (currentSessionId != -1) {
|
||||
displayHistory();
|
||||
}
|
||||
} else {
|
||||
console.log(response);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let mainDiv = document.createElement("div");
|
||||
mainDiv.appendChild(generatePopupWarningTitle());
|
||||
let p = document.createElement("p");
|
||||
p.textContent = "Avatar cannot be larger than 10MB.";
|
||||
mainDiv.appendChild(p);
|
||||
showPopup(mainDiv);
|
||||
}
|
||||
};
|
||||
labelAvatar.appendChild(inputAvatar);
|
||||
labelAvatar.appendChild(generateSelfAvatar(avatarTimestamps.get("self")));
|
||||
let uploadP = document.createElement("p");
|
||||
uploadP.textContent = "Upload";
|
||||
labelAvatar.appendChild(uploadP);
|
||||
avatarContainer.appendChild(labelAvatar);
|
||||
mainDiv.appendChild(avatarContainer);
|
||||
let fingerprint = document.createElement("pre");
|
||||
fingerprint.id = "identity_fingerprint";
|
||||
fingerprint.textContent = beautifyFingerprint(identityFingerprint);
|
||||
@ -427,8 +469,8 @@ socket.onmessage = function(msg) {
|
||||
console.log("Message: "+msg.data);
|
||||
let args = msg.data.split(" ");
|
||||
switch (args[0]) {
|
||||
case "disconnected":
|
||||
onDisconnected(args[1]);
|
||||
case "inc_file_transfer":
|
||||
onIncFilesTransfer(args[1], parseInt(args[2]));
|
||||
break;
|
||||
case "new_session":
|
||||
onNewSession(args[1], args[2] === "true", args[3], args[4], msg.data.slice(args[0].length+args[1].length+args[2].length+args[3].length+args[4].length+5));
|
||||
@ -451,15 +493,15 @@ socket.onmessage = function(msg) {
|
||||
case "aborted":
|
||||
onFilesTransferAborted(args[1]);
|
||||
break;
|
||||
case "inc_file_transfer":
|
||||
onIncFilesTransfer(args[1], parseInt(args[2]));
|
||||
break;
|
||||
case "load_msgs":
|
||||
onMsgsLoad(args[1], msg.data.slice(args[0].length+args[1].length+2));
|
||||
break;
|
||||
case "name_told":
|
||||
onNameTold(args[1], msg.data.slice(args[0].length+args[1].length+2));
|
||||
break;
|
||||
case "avatar_set":
|
||||
onAvatarSet(args[1]);
|
||||
break;
|
||||
case "is_contact":
|
||||
onIsContact(args[1], args[2] === "true", args[3], msg.data.slice(args[0].length+args[1].length+args[2].length+args[3].length+4));
|
||||
break;
|
||||
@ -475,6 +517,9 @@ socket.onmessage = function(msg) {
|
||||
case "password_changed":
|
||||
onPasswordChanged(args[1] === "true", args[2] === "true");
|
||||
break;
|
||||
case "disconnected":
|
||||
onDisconnected(args[1]);
|
||||
break;
|
||||
case "logout":
|
||||
logout();
|
||||
}
|
||||
@ -509,6 +554,14 @@ function onNameTold(sessionId, name) {
|
||||
}
|
||||
displaySessions();
|
||||
}
|
||||
function onAvatarSet(sessionId) {
|
||||
avatarTimestamps.set(sessionId, Date.now());
|
||||
displaySessions();
|
||||
if (sessionId === currentSessionId) {
|
||||
displayHeader();
|
||||
displayHistory(false);
|
||||
}
|
||||
}
|
||||
function setNotSeen(strSessionIds) {
|
||||
let sessionIds = strSessionIds.split(' ');
|
||||
for (let i=0; i<sessionIds.length; ++i) {
|
||||
@ -787,7 +840,7 @@ function showSessionInfoPopup() {
|
||||
if (typeof session !== "undefined") {
|
||||
let mainDiv = document.createElement("div");
|
||||
mainDiv.id = "session_info";
|
||||
mainDiv.appendChild(generateAvatar(session.name));
|
||||
mainDiv.appendChild(generateAvatar(currentSessionId, session.name, avatarTimestamps.get(currentSessionId)));
|
||||
let nameDiv = document.createElement("div");
|
||||
nameDiv.classList.add("name");
|
||||
let h2 = document.createElement("h2");
|
||||
@ -796,7 +849,7 @@ function showSessionInfoPopup() {
|
||||
if (session.isOnline) {
|
||||
let button = document.createElement("button");
|
||||
button.onclick = function() {
|
||||
socket.send("ask_name "+currentSessionId);
|
||||
socket.send("refresh_profile "+currentSessionId);
|
||||
};
|
||||
nameDiv.appendChild(button);
|
||||
}
|
||||
@ -839,6 +892,7 @@ function addSession(sessionId, name, outgoing, fingerprint, ip, isContact, isVer
|
||||
"isOnline": isOnline,
|
||||
});
|
||||
msgHistory.set(sessionId, []);
|
||||
avatarTimestamps.set(sessionId, Date.now());
|
||||
displaySessions();
|
||||
}
|
||||
function displaySessions() {
|
||||
@ -860,7 +914,7 @@ function logout() {
|
||||
}
|
||||
function displayProfile() {
|
||||
profile_div.innerHTML = "";
|
||||
profile_div.appendChild(generateAvatar(identityName));
|
||||
profile_div.appendChild(generateSelfAvatar(avatarTimestamps.get("self")));
|
||||
let p = document.createElement("p");
|
||||
p.textContent = identityName;
|
||||
profile_div.appendChild(p);
|
||||
@ -872,7 +926,7 @@ function displayHeader() {
|
||||
if (typeof session === "undefined") {
|
||||
chatHeader.style.display = "none";
|
||||
} else {
|
||||
chatHeader.children[0].appendChild(generateAvatar(session.name));
|
||||
chatHeader.children[0].appendChild(generateAvatar(currentSessionId, session.name, avatarTimestamps.get(currentSessionId)));
|
||||
chatHeader.children[0].appendChild(generateName(session.name));
|
||||
chatHeader.style.display = "flex";
|
||||
if (session.isContact) {
|
||||
@ -954,7 +1008,7 @@ function generateName(name) {
|
||||
function generateSession(sessionId, session) {
|
||||
let li = document.createElement("li");
|
||||
li.setAttribute("data-sessionId", sessionId);
|
||||
li.appendChild(generateAvatar(session.name));
|
||||
li.appendChild(generateAvatar(sessionId, session.name, avatarTimestamps.get(sessionId)));
|
||||
li.appendChild(generateName(session.name));
|
||||
if (session.isContact) {
|
||||
li.classList.add("is_contact");
|
||||
@ -973,16 +1027,23 @@ function generateSession(sessionId, session) {
|
||||
li.onclick = onClickSession;
|
||||
return li;
|
||||
}
|
||||
function generateMsgHeader(name) {
|
||||
function generateMsgHeader(name, sessionId) {
|
||||
let p = document.createElement("p");
|
||||
p.appendChild(document.createTextNode(name));
|
||||
let div = document.createElement("div");
|
||||
div.classList.add("header");
|
||||
div.appendChild(generateAvatar(name));
|
||||
let timestamp = avatarTimestamps.get(sessionId);
|
||||
let avatar;
|
||||
if (typeof sessionId === "undefined") {
|
||||
avatar = generateSelfAvatar(timestamp);
|
||||
} else {
|
||||
avatar = generateAvatar(sessionId, name, timestamp);
|
||||
}
|
||||
div.appendChild(avatar);
|
||||
div.appendChild(p);
|
||||
return div;
|
||||
}
|
||||
function generateMessage(name, msg) {
|
||||
function generateMessage(name, sessionId, msg) {
|
||||
let p = document.createElement("p");
|
||||
p.appendChild(document.createTextNode(msg));
|
||||
let div = document.createElement("div");
|
||||
@ -990,12 +1051,12 @@ function generateMessage(name, msg) {
|
||||
div.appendChild(linkifyElement(p));
|
||||
let li = document.createElement("li");
|
||||
if (typeof name !== "undefined") {
|
||||
li.appendChild(generateMsgHeader(name));
|
||||
li.appendChild(generateMsgHeader(name, sessionId));
|
||||
}
|
||||
li.appendChild(div);
|
||||
return li;
|
||||
}
|
||||
function generateFile(name, outgoing, file_info) {
|
||||
function generateFile(name, sessionId, outgoing, file_info) {
|
||||
let div1 = document.createElement("div");
|
||||
div1.classList.add("file");
|
||||
div1.classList.add("content");
|
||||
@ -1017,7 +1078,7 @@ function generateFile(name, outgoing, file_info) {
|
||||
div1.appendChild(a);
|
||||
let li = document.createElement("li");
|
||||
if (typeof name !== "undefined") {
|
||||
li.appendChild(generateMsgHeader(name));
|
||||
li.appendChild(generateMsgHeader(name, sessionId));
|
||||
}
|
||||
li.appendChild(div1);
|
||||
return li;
|
||||
@ -1090,18 +1151,20 @@ function displayHistory(scrollToBottom = true) {
|
||||
let previousOutgoing = undefined;
|
||||
msgHistory.get(currentSessionId).forEach(entry => {
|
||||
let name = undefined;
|
||||
let sessionId = undefined;
|
||||
if (previousOutgoing != entry[0]) {
|
||||
previousOutgoing = entry[0];
|
||||
if (entry[0]) { //outgoing msg
|
||||
name = identityName;
|
||||
} else {
|
||||
name = session.name;
|
||||
sessionId = currentSessionId;
|
||||
}
|
||||
}
|
||||
if (entry[1]) { //is file
|
||||
msg_log.appendChild(generateFile(name, entry[0], entry[2]));
|
||||
msg_log.appendChild(generateFile(name, sessionId, entry[0], entry[2]));
|
||||
} else {
|
||||
msg_log.appendChild(generateMessage(name, entry[2]));
|
||||
msg_log.appendChild(generateMessage(name, sessionId, entry[2]));
|
||||
}
|
||||
});
|
||||
if (scrollToBottom) {
|
||||
|
@ -63,9 +63,9 @@
|
||||
padding: 10px 50px;
|
||||
}
|
||||
.avatar {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
font-size: 3em;
|
||||
width: 7em;
|
||||
height: 7em;
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
#identity h2 {
|
||||
@ -157,7 +157,7 @@
|
||||
}
|
||||
} else {
|
||||
let identity = document.getElementById("identity");
|
||||
identity.appendChild(generateAvatar(identity_name));
|
||||
identity.appendChild(generateSelfAvatar(Date.now()));
|
||||
let h2 = document.createElement("h2");
|
||||
h2.textContent = identity_name;
|
||||
identity.appendChild(h2);
|
||||
|
178
src/identity.rs
178
src/identity.rs
@ -11,6 +11,7 @@ use crate::{constants, crypto, key_value_table::KeyValueTable, print_error, util
|
||||
const MAIN_TABLE: &str = "main";
|
||||
const CONTACTS_TABLE: &str = "contacts";
|
||||
const FILES_TABLE: &str = "files";
|
||||
const AVATARS_TABLE: &str = "avatars";
|
||||
|
||||
const DATABASE_CORRUPED_ERROR: &str = "Database corrupted";
|
||||
|
||||
@ -21,6 +22,7 @@ impl<'a> DBKeys {
|
||||
pub const SALT: &'a str = "salt";
|
||||
pub const MASTER_KEY: &'a str = "master_key";
|
||||
pub const USE_PADDING: &'a str = "use_padding";
|
||||
pub const AVATAR: &'a str = "avatar";
|
||||
}
|
||||
|
||||
fn bool_to_byte(b: bool) -> u8 {
|
||||
@ -53,6 +55,7 @@ pub struct Contact {
|
||||
pub uuid: Uuid,
|
||||
pub public_key: [u8; PUBLIC_KEY_LENGTH],
|
||||
pub name: String,
|
||||
pub avatar: Option<Uuid>,
|
||||
pub verified: bool,
|
||||
pub seen: bool,
|
||||
}
|
||||
@ -70,19 +73,23 @@ impl Identity {
|
||||
self.keypair.public.to_bytes()
|
||||
}
|
||||
|
||||
pub fn add_contact(&self, name: String, public_key: [u8; PUBLIC_KEY_LENGTH]) -> Result<Contact, rusqlite::Error> {
|
||||
pub fn add_contact(&self, name: String, avatar_uuid: Option<Uuid>, public_key: [u8; PUBLIC_KEY_LENGTH]) -> Result<Contact, rusqlite::Error> {
|
||||
let db = Connection::open(get_database_path())?;
|
||||
db.execute(&("CREATE TABLE IF NOT EXISTS ".to_owned()+CONTACTS_TABLE+"(uuid BLOB PRIMARY KEY, name BLOB, key BLOB, verified BLOB, seen BLOB)"), [])?;
|
||||
db.execute(&("CREATE TABLE IF NOT EXISTS ".to_owned()+CONTACTS_TABLE+"(uuid BLOB PRIMARY KEY, name BLOB, avatar BLOB, key BLOB, verified BLOB, seen BLOB)"), [])?;
|
||||
let contact_uuid = Uuid::new_v4();
|
||||
let encrypted_name = crypto::encrypt_data(name.as_bytes(), &self.master_key).unwrap();
|
||||
let encrypted_public_key = crypto::encrypt_data(&public_key, &self.master_key).unwrap();
|
||||
let encrypted_verified = crypto::encrypt_data(&[bool_to_byte(false)], &self.master_key).unwrap();
|
||||
let encrypted_seen = crypto::encrypt_data(&[bool_to_byte(true)], &self.master_key).unwrap();
|
||||
db.execute(&("INSERT INTO ".to_owned()+CONTACTS_TABLE+" (uuid, name, key, verified, seen) VALUES (?1, ?2, ?3, ?4, ?5)"), params![&contact_uuid.as_bytes()[..], encrypted_name, encrypted_public_key, encrypted_verified, encrypted_seen])?;
|
||||
match avatar_uuid {
|
||||
Some(avatar_uuid) => db.execute(&format!("INSERT INTO {} (uuid, name, avatar, key, verified, seen) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", CONTACTS_TABLE), params![&contact_uuid.as_bytes()[..], encrypted_name, &avatar_uuid.as_bytes()[..], encrypted_public_key, encrypted_verified, encrypted_seen])?,
|
||||
None => db.execute(&format!("INSERT INTO {} (uuid, name, key, verified, seen) VALUES (?1, ?2, ?3, ?4, ?5)", CONTACTS_TABLE), params![&contact_uuid.as_bytes()[..], encrypted_name, encrypted_public_key, encrypted_verified, encrypted_seen])?
|
||||
};
|
||||
Ok(Contact {
|
||||
uuid: contact_uuid,
|
||||
public_key: public_key,
|
||||
name: name,
|
||||
avatar: avatar_uuid,
|
||||
verified: false,
|
||||
seen: true,
|
||||
})
|
||||
@ -91,7 +98,7 @@ impl Identity {
|
||||
pub fn remove_contact(uuid: &Uuid) -> Result<usize, rusqlite::Error> {
|
||||
let db = Connection::open(get_database_path())?;
|
||||
Identity::delete_conversation(uuid)?;
|
||||
db.execute(&("DELETE FROM ".to_owned()+CONTACTS_TABLE+" WHERE uuid=?"), [&uuid.as_bytes()[..]])
|
||||
db.execute(&format!("DELETE FROM {} WHERE uuid=?", CONTACTS_TABLE), [&uuid.as_bytes()[..]])
|
||||
}
|
||||
|
||||
pub fn set_verified(&self, uuid: &Uuid) -> Result<usize, rusqlite::Error> {
|
||||
@ -106,6 +113,11 @@ impl Identity {
|
||||
db.execute(&format!("UPDATE {} SET name=?1 WHERE uuid=?2", CONTACTS_TABLE), [encrypted_name.as_slice(), uuid.as_bytes()])
|
||||
}
|
||||
|
||||
pub fn set_contact_avatar(&self, contact_uuid: &Uuid, avatar_uuid: &Uuid) -> Result<usize, rusqlite::Error> {
|
||||
let db = Connection::open(get_database_path())?;
|
||||
db.execute(&format!("UPDATE {} SET avatar=?1 WHERE uuid=?2", CONTACTS_TABLE), params![&avatar_uuid.as_bytes()[..], &contact_uuid.as_bytes()[..]])
|
||||
}
|
||||
|
||||
pub fn set_contact_seen(&self, uuid: &Uuid, seen: bool) -> Result<usize, rusqlite::Error> {
|
||||
let db = Connection::open(get_database_path())?;
|
||||
let encrypted_seen = crypto::encrypt_data(&[bool_to_byte(seen)], &self.master_key).unwrap();
|
||||
@ -115,36 +127,38 @@ impl Identity {
|
||||
pub fn load_contacts(&self) -> Option<Vec<Contact>> {
|
||||
match Connection::open(get_database_path()) {
|
||||
Ok(db) => {
|
||||
match db.prepare(&("SELECT uuid, name, key, verified, seen FROM ".to_owned()+CONTACTS_TABLE)) {
|
||||
Ok(mut stmt) => {
|
||||
if let Ok(mut stmt) = db.prepare(&("SELECT uuid, name, avatar, key, verified, seen FROM ".to_owned()+CONTACTS_TABLE)) {
|
||||
let mut rows = stmt.query([]).unwrap();
|
||||
let mut contacts = Vec::new();
|
||||
while let Some(row) = rows.next().unwrap() {
|
||||
let encrypted_public_key: Vec<u8> = row.get(2).unwrap();
|
||||
while let Ok(Some(row)) = rows.next() {
|
||||
let encrypted_public_key: Vec<u8> = row.get(3).unwrap();
|
||||
match crypto::decrypt_data(encrypted_public_key.as_slice(), &self.master_key) {
|
||||
Ok(public_key) => {
|
||||
if public_key.len() == PUBLIC_KEY_LENGTH {
|
||||
let encrypted_name: Vec<u8> = row.get(1).unwrap();
|
||||
match crypto::decrypt_data(encrypted_name.as_slice(), &self.master_key) {
|
||||
Ok(name) => {
|
||||
let encrypted_verified: Vec<u8> = row.get(3).unwrap();
|
||||
let encrypted_verified: Vec<u8> = row.get(4).unwrap();
|
||||
match crypto::decrypt_data(encrypted_verified.as_slice(), &self.master_key) {
|
||||
Ok(verified) => {
|
||||
let encrypted_seen: Vec<u8> = row.get(4).unwrap();
|
||||
let encrypted_seen: Vec<u8> = row.get(5).unwrap();
|
||||
match crypto::decrypt_data(encrypted_seen.as_slice(), &self.master_key) {
|
||||
Ok(seen) => {
|
||||
let uuid: Vec<u8> = row.get(0).unwrap();
|
||||
match to_uuid_bytes(&uuid) {
|
||||
Some(uuid_bytes) => {
|
||||
let contact_uuid: Vec<u8> = row.get(0).unwrap();
|
||||
let avatar_result: Result<Vec<u8>, rusqlite::Error> = row.get(2);
|
||||
let avatar = match avatar_result {
|
||||
Ok(avatar_uuid) => Some(Uuid::from_bytes(to_uuid_bytes(&avatar_uuid).unwrap())),
|
||||
Err(_) => None
|
||||
};
|
||||
contacts.push(Contact {
|
||||
uuid: Uuid::from_bytes(uuid_bytes),
|
||||
uuid: Uuid::from_bytes(to_uuid_bytes(&contact_uuid).unwrap()),
|
||||
public_key: public_key.try_into().unwrap(),
|
||||
name: std::str::from_utf8(name.as_slice()).unwrap().to_owned(),
|
||||
avatar,
|
||||
verified: byte_to_bool(verified[0]).unwrap(),
|
||||
seen: byte_to_bool(seen[0]).unwrap(),
|
||||
})
|
||||
}
|
||||
None => {}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
@ -155,40 +169,29 @@ impl Identity {
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
} else {
|
||||
print_error!("Invalid public key length: database corrupted");
|
||||
}
|
||||
return Some(contacts);
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
Some(contacts)
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_temporary_files() -> Result<usize, rusqlite::Error> {
|
||||
#[allow(unused_must_use)]
|
||||
pub fn clear_cache() -> Result<(), rusqlite::Error> {
|
||||
let db = Connection::open(get_database_path())?;
|
||||
db.execute(&format!("DELETE FROM {} WHERE contact_uuid IS NULL", FILES_TABLE), [])
|
||||
db.execute(&format!("DELETE FROM {} WHERE contact_uuid IS NULL", FILES_TABLE), []);
|
||||
db.execute(&format!("DELETE FROM {} WHERE uuid NOT IN (SELECT avatar FROM {})", AVATARS_TABLE, CONTACTS_TABLE), []);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_file(&self, uuid: Uuid) -> Option<Vec<u8>> {
|
||||
match Connection::open(get_database_path()) {
|
||||
Ok(db) => {
|
||||
match db.prepare(&format!("SELECT uuid, data FROM \"{}\"", FILES_TABLE)) {
|
||||
Ok(mut stmt) => {
|
||||
let mut stmt = db.prepare(&format!("SELECT uuid, data FROM \"{}\"", FILES_TABLE)).unwrap();
|
||||
let mut rows = stmt.query([]).unwrap();
|
||||
while let Some(row) = rows.next().unwrap() {
|
||||
while let Ok(Some(row)) = rows.next() {
|
||||
let encrypted_uuid: Vec<u8> = row.get(0).unwrap();
|
||||
match crypto::decrypt_data(encrypted_uuid.as_slice(), &self.master_key){
|
||||
Ok(test_uuid) => {
|
||||
@ -203,20 +206,11 @@ impl Identity {
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store_file(&self, contact_uuid: Option<Uuid>, data: &[u8]) -> Result<Uuid, rusqlite::Error> {
|
||||
let db = Connection::open(get_database_path())?;
|
||||
@ -244,23 +238,18 @@ impl Identity {
|
||||
pub fn load_msgs(&self, contact_uuid: &Uuid, offset: usize, mut count: usize) -> Option<Vec<(bool, Vec<u8>)>> {
|
||||
match Connection::open(get_database_path()) {
|
||||
Ok(db) => {
|
||||
match db.prepare(&format!("SELECT count(*) FROM \"{}\"", contact_uuid)) {
|
||||
Ok(mut stmt) => {
|
||||
if let Ok(mut stmt) = db.prepare(&format!("SELECT count(*) FROM \"{}\"", contact_uuid)) {
|
||||
let mut rows = stmt.query([]).unwrap();
|
||||
match rows.next() {
|
||||
Ok(row) => if row.is_some() {
|
||||
let total: usize = row.unwrap().get(0).unwrap();
|
||||
if offset >= total {
|
||||
None
|
||||
} else {
|
||||
if let Ok(Some(row)) = rows.next() {
|
||||
let total: usize = row.get(0).unwrap();
|
||||
if offset < total {
|
||||
if offset+count >= total {
|
||||
count = total-offset;
|
||||
}
|
||||
match db.prepare(&format!("SELECT outgoing, data FROM \"{}\" LIMIT {} OFFSET {}", contact_uuid, count, total-offset-count)) {
|
||||
Ok(mut stmt) => {
|
||||
let mut stmt = db.prepare(&format!("SELECT outgoing, data FROM \"{}\" LIMIT {} OFFSET {}", contact_uuid, count, total-offset-count)).unwrap();
|
||||
let mut rows = stmt.query([]).unwrap();
|
||||
let mut msgs = Vec::new();
|
||||
while let Some(row) = rows.next().unwrap() {
|
||||
while let Ok(Some(row)) = rows.next() {
|
||||
let encrypted_outgoing: Vec<u8> = row.get(0).unwrap();
|
||||
match crypto::decrypt_data(encrypted_outgoing.as_slice(), &self.master_key){
|
||||
Ok(outgoing) => {
|
||||
@ -268,14 +257,7 @@ impl Identity {
|
||||
Ok(outgoing) => {
|
||||
let encrypted_data: Vec<u8> = row.get(1).unwrap();
|
||||
match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) {
|
||||
Ok(data) => {
|
||||
msgs.push(
|
||||
(
|
||||
outgoing,
|
||||
data
|
||||
)
|
||||
)
|
||||
},
|
||||
Ok(data) => msgs.push((outgoing, data)),
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
@ -286,32 +268,15 @@ impl Identity {
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
Some(msgs)
|
||||
return Some(msgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Err(_) => None
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
pub fn delete_conversation(contact_uuid: &Uuid) -> Result<usize, rusqlite::Error> {
|
||||
@ -336,6 +301,29 @@ impl Identity {
|
||||
db.update(DBKeys::USE_PADDING, &encrypted_use_padding)
|
||||
}
|
||||
|
||||
pub fn store_avatar(&self, avatar: &[u8]) -> Result<Uuid, rusqlite::Error> {
|
||||
let db = Connection::open(get_database_path())?;
|
||||
db.execute(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (uuid BLOB PRIMARY KEY, data BLOB)", AVATARS_TABLE), [])?;
|
||||
let uuid = Uuid::new_v4();
|
||||
let encrypted_avatar = crypto::encrypt_data(avatar, &self.master_key).unwrap();
|
||||
db.execute(&format!("INSERT INTO {} (uuid, data) VALUES (?1, ?2)", AVATARS_TABLE), params![&uuid.as_bytes()[..], encrypted_avatar])?;
|
||||
Ok(uuid)
|
||||
}
|
||||
|
||||
pub fn get_avatar(&self, avatar_uuid: &Uuid) -> Option<Vec<u8>> {
|
||||
let db = Connection::open(get_database_path()).ok()?;
|
||||
let mut stmt = db.prepare(&format!("SELECT data FROM {} WHERE uuid=?", AVATARS_TABLE)).unwrap();
|
||||
let mut rows = stmt.query(params![&avatar_uuid.as_bytes()[..]]).unwrap();
|
||||
let encrypted_avatar: Vec<u8> = rows.next().ok()??.get(0).unwrap();
|
||||
match crypto::decrypt_data(&encrypted_avatar, &self.master_key) {
|
||||
Ok(avatar) => Some(avatar),
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zeroize(&mut self){
|
||||
self.master_key.zeroize();
|
||||
self.keypair.secret.zeroize();
|
||||
@ -480,6 +468,16 @@ impl Identity {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_identity_avatar(avatar: &[u8]) -> Result<usize, rusqlite::Error> {
|
||||
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
|
||||
db.upsert(DBKeys::AVATAR, avatar)
|
||||
}
|
||||
|
||||
pub fn get_identity_avatar() -> Result<Vec<u8>, rusqlite::Error> {
|
||||
let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?;
|
||||
db.get(DBKeys::AVATAR)
|
||||
}
|
||||
|
||||
pub fn delete_identity() -> Result<(), std::io::Error> {
|
||||
std::fs::remove_file(get_database_path())
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ impl<'a> KeyValueTable<'a> {
|
||||
pub fn update(&self, key: &str, value: &[u8]) -> Result<usize, Error> {
|
||||
self.db.execute(&format!("UPDATE {} SET value=? WHERE key=\"{}\"", self.table_name, key), params![value])
|
||||
}
|
||||
/*pub fn upsert(&self, key: &str, value: &[u8]) -> Result<usize, Error> {
|
||||
pub fn upsert(&self, key: &str, value: &[u8]) -> Result<usize, Error> {
|
||||
self.db.execute(&format!("INSERT INTO {} (key, value) VALUES(?1, ?2) ON CONFLICT(key) DO UPDATE SET value=?3", self.table_name), params![key, value, value])
|
||||
}*/
|
||||
}
|
||||
}
|
102
src/main.rs
102
src/main.rs
@ -9,6 +9,7 @@ mod constants;
|
||||
mod discovery;
|
||||
|
||||
use std::{env, fs, io, net::SocketAddr, str::{FromStr, from_utf8}, sync::{Arc, RwLock}};
|
||||
use image::GenericImageView;
|
||||
use tokio::{net::TcpListener, runtime::Handle, sync::mpsc};
|
||||
use actix_web::{App, HttpMessage, HttpRequest, HttpResponse, HttpServer, http::{header, CookieBuilder}, web, web::Data};
|
||||
use actix_multipart::Multipart;
|
||||
@ -198,7 +199,7 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||
}
|
||||
"contact" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
match session_manager.add_contact(session_id, msg[args[0].len()+args[1].len()+2..].to_string()) {
|
||||
match session_manager.add_contact(session_id) {
|
||||
Ok(_) => {},
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
@ -224,10 +225,10 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc<RwLo
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
"ask_name" => {
|
||||
"refresh_profile" => {
|
||||
let session_id: usize = args[1].parse().unwrap();
|
||||
session_manager.send_command(&session_id, SessionCommand::Send {
|
||||
buff: protocol::ask_name()
|
||||
buff: protocol::ask_profile_info()
|
||||
}).await;
|
||||
}
|
||||
"set_use_padding" => {
|
||||
@ -303,12 +304,86 @@ fn is_authenticated(req: &HttpRequest) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn handle_set_avatar(req: HttpRequest, mut payload: Multipart) -> HttpResponse {
|
||||
if is_authenticated(&req) {
|
||||
while let Ok(Some(mut field)) = payload.try_next().await {
|
||||
let content_disposition = field.content_disposition().unwrap();
|
||||
if let Some(name) = content_disposition.get_name() {
|
||||
if name == "avatar" {
|
||||
let mut buffer = Vec::new();
|
||||
while let Some(Ok(chunk)) = field.next().await {
|
||||
buffer.extend(chunk);
|
||||
}
|
||||
match image::guess_format(&buffer) {
|
||||
Ok(format) => {
|
||||
match image::load_from_memory_with_format(&buffer, format) {
|
||||
Ok(image) => {
|
||||
let (width, height) = image.dimensions();
|
||||
let image = if width < height {
|
||||
image.crop_imm(0, (height-width)/2, width, width)
|
||||
} else if width > height {
|
||||
image.crop_imm((width-height)/2, 0, height, height)
|
||||
} else {
|
||||
image
|
||||
};
|
||||
let mut avatar = Vec::new();
|
||||
image.write_to(&mut avatar, format).unwrap();
|
||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
||||
match global_vars.read().unwrap().session_manager.set_avatar(&avatar).await {
|
||||
Ok(_) => {
|
||||
return HttpResponse::Ok().finish();
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
return HttpResponse::InternalServerError().finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
HttpResponse::BadRequest().finish()
|
||||
}
|
||||
|
||||
fn reply_with_avatar(avatar: Option<Vec<u8>>, name: &str) -> HttpResponse {
|
||||
match avatar {
|
||||
Some(avatar) => HttpResponse::Ok().content_type("image/*").body(avatar),
|
||||
None => {
|
||||
#[cfg(not(debug_assertions))]
|
||||
let svg = include_str!(concat!(env!("OUT_DIR"), "/text_avatar.svg"));
|
||||
#[cfg(debug_assertions)]
|
||||
let svg = replace_fields("src/frontend/imgs/text_avatar.svg");
|
||||
HttpResponse::Ok().content_type("image/svg+xml").body(svg.replace("LETTER", &name.chars().nth(0).unwrap().to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_avatar(req: HttpRequest) -> HttpResponse {
|
||||
let splits: Vec<&str> = req.path()[1..].split("/").collect();
|
||||
if splits.len() == 2 {
|
||||
if splits[1] == "self" {
|
||||
return reply_with_avatar(Identity::get_identity_avatar().ok(), &Identity::get_identity_name().unwrap());
|
||||
}
|
||||
} else if splits.len() == 3 {
|
||||
if let Ok(session_id) = splits[1].parse() {
|
||||
let global_vars = req.app_data::<Data<Arc<RwLock<GlobalVars>>>>().unwrap();
|
||||
return reply_with_avatar(global_vars.read().unwrap().session_manager.get_avatar(&session_id), splits[2]);
|
||||
}
|
||||
}
|
||||
HttpResponse::BadRequest().finish()
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct FileInfo {
|
||||
uuid: String,
|
||||
file_name: String,
|
||||
}
|
||||
|
||||
fn handle_load_file(req: HttpRequest, file_info: web::Query<FileInfo>) -> HttpResponse {
|
||||
if is_authenticated(&req) {
|
||||
match Uuid::from_str(&file_info.uuid) {
|
||||
@ -368,7 +443,9 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
||||
loop {
|
||||
chunk_buffer.extend(&pending_buffer);
|
||||
pending_buffer.clear();
|
||||
println!("Calling next()");
|
||||
while let Some(Ok(chunk)) = field.next().await {
|
||||
println!("Not None");
|
||||
if chunk_buffer.len()+chunk.len() <= constants::FILE_CHUNK_SIZE {
|
||||
chunk_buffer.extend(chunk);
|
||||
} else if chunk_buffer.len() == constants::FILE_CHUNK_SIZE {
|
||||
@ -381,6 +458,7 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo
|
||||
break;
|
||||
}
|
||||
}
|
||||
println!("May be None");
|
||||
if !global_vars_read.session_manager.send_command(&session_id, SessionCommand::EncryptFileChunk{
|
||||
plain_text: chunk_buffer.clone()
|
||||
}).await {
|
||||
@ -457,7 +535,7 @@ fn login(identity: Identity, global_vars: &Arc<RwLock<GlobalVars>>) -> HttpRespo
|
||||
}
|
||||
|
||||
fn on_identity_loaded(identity: Identity, global_vars: &Arc<RwLock<GlobalVars>>) -> HttpResponse {
|
||||
match Identity::clear_temporary_files() {
|
||||
match Identity::clear_cache() {
|
||||
Ok(_) => {},
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
@ -571,13 +649,13 @@ async fn handle_index(req: HttpRequest) -> HttpResponse {
|
||||
const JS_CONTENT_TYPE: &str = "text/javascript";
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn replace_css(file_path: &str) -> String {
|
||||
fn replace_fields(file_path: &str) -> String {
|
||||
use yaml_rust::YamlLoader;
|
||||
let mut content = fs::read_to_string(file_path).unwrap();
|
||||
let config = &YamlLoader::load_from_str(&fs::read_to_string("config.yml").unwrap()).unwrap()[0];
|
||||
let css_values = config["css"].as_hash().unwrap();
|
||||
css_values.into_iter().for_each(|entry| {
|
||||
content = content.replace(entry.0.as_str().unwrap(), entry.1.as_str().unwrap());
|
||||
let fields = config.as_hash().unwrap();
|
||||
fields.into_iter().for_each(|field| {
|
||||
content = content.replace(field.0.as_str().unwrap(), field.1.as_str().unwrap());
|
||||
});
|
||||
content
|
||||
}
|
||||
@ -596,7 +674,7 @@ fn handle_static(req: HttpRequest) -> HttpResponse {
|
||||
}
|
||||
"index.css" => {
|
||||
#[cfg(debug_assertions)]
|
||||
return response_builder.body(replace_css("src/frontend/index.css"));
|
||||
return response_builder.body(replace_fields("src/frontend/index.css"));
|
||||
#[cfg(not(debug_assertions))]
|
||||
return response_builder.body(include_str!(concat!(env!("OUT_DIR"), "/index.css")));
|
||||
}
|
||||
@ -655,7 +733,7 @@ fn handle_static(req: HttpRequest) -> HttpResponse {
|
||||
}
|
||||
"style.css" => {
|
||||
#[cfg(debug_assertions)]
|
||||
return response_builder.body(replace_css("src/frontend/commons/style.css"));
|
||||
return response_builder.body(replace_fields("src/frontend/commons/style.css"));
|
||||
#[cfg(not(debug_assertions))]
|
||||
return response_builder.body(include_str!(concat!(env!("OUT_DIR"), "/commons/style.css")));
|
||||
}
|
||||
@ -700,6 +778,8 @@ async fn start_http_server(global_vars: Arc<RwLock<GlobalVars>>) -> io::Result<(
|
||||
.route("/send_file", web::post().to(handle_send_file))
|
||||
.route("/send_large_file", web::post().to(handle_send_file))
|
||||
.route("/load_file", web::get().to(handle_load_file))
|
||||
.route("/set_avatar", web::post().to(handle_set_avatar))
|
||||
.route("/avatar/*", web::get().to(handle_avatar))
|
||||
.route("/static/.*", web::get().to(handle_static))
|
||||
.route("/logout", web::get().to(handle_logout))
|
||||
}
|
||||
|
@ -5,26 +5,27 @@ pub struct Headers;
|
||||
|
||||
impl Headers {
|
||||
pub const MESSAGE: u8 = 0x00;
|
||||
pub const ASK_NAME: u8 = 0x01;
|
||||
pub const TELL_NAME: u8 = 0x02;
|
||||
pub const FILE: u8 = 0x03;
|
||||
pub const ASK_LARGE_FILES: u8 = 0x04;
|
||||
pub const ACCEPT_LARGE_FILES: u8 = 0x05;
|
||||
pub const LARGE_FILE_CHUNK: u8 = 0x06;
|
||||
pub const ACK_CHUNK: u8 = 0x07;
|
||||
pub const ABORT_FILES_TRANSFER: u8 = 0x08;
|
||||
pub const FILE: u8 = 0x01;
|
||||
pub const ASK_PROFILE_INFO: u8 = 0x02;
|
||||
pub const NAME: u8 = 0x03;
|
||||
pub const AVATAR: u8 = 0x04;
|
||||
pub const ASK_LARGE_FILES: u8 = 0x05;
|
||||
pub const ACCEPT_LARGE_FILES: u8 = 0x06;
|
||||
pub const LARGE_FILE_CHUNK: u8 = 0x07;
|
||||
pub const ACK_CHUNK: u8 = 0x08;
|
||||
pub const ABORT_FILES_TRANSFER: u8 = 0x09;
|
||||
}
|
||||
|
||||
pub fn new_message(message: String) -> Vec<u8> {
|
||||
[&[Headers::MESSAGE], message.as_bytes()].concat()
|
||||
}
|
||||
|
||||
pub fn ask_name() -> Vec<u8> {
|
||||
vec![Headers::ASK_NAME]
|
||||
pub fn ask_profile_info() -> Vec<u8> {
|
||||
vec![Headers::ASK_PROFILE_INFO]
|
||||
}
|
||||
|
||||
pub fn tell_name(name: &str) -> Vec<u8> {
|
||||
[&[Headers::TELL_NAME], name.as_bytes()].concat()
|
||||
pub fn name(name: &str) -> Vec<u8> {
|
||||
[&[Headers::NAME], name.as_bytes()].concat()
|
||||
}
|
||||
|
||||
pub fn file(file_name: &str, buffer: &[u8]) -> Vec<u8> {
|
||||
@ -80,3 +81,7 @@ pub fn parse_ask_files(buffer: &[u8]) -> Option<Vec<(u64, String)>> {
|
||||
}
|
||||
Some(files_info)
|
||||
}
|
||||
|
||||
pub fn avatar(avatar: &[u8]) -> Vec<u8> {
|
||||
[&[Headers::AVATAR], avatar].concat()
|
||||
}
|
@ -40,6 +40,7 @@ pub struct LargeFilesDownload {
|
||||
#[derive(Clone)]
|
||||
pub struct SessionData {
|
||||
pub name: String,
|
||||
avatar: Option<Uuid>,
|
||||
pub outgoing: bool,
|
||||
pub peer_public_key: [u8; PUBLIC_KEY_LENGTH],
|
||||
pub ip: IpAddr,
|
||||
@ -54,7 +55,7 @@ pub struct SessionManager {
|
||||
ui_connection: Mutex<Option<UiConnection>>,
|
||||
loaded_contacts: RwLock<HashMap<usize, Contact>>,
|
||||
pub last_loaded_msg_offsets: RwLock<HashMap<usize, usize>>,
|
||||
pub saved_msgs: Mutex<HashMap<usize, Vec<(bool, Vec<u8>)>>>,
|
||||
pub saved_msgs: RwLock<HashMap<usize, Vec<(bool, Vec<u8>)>>>,
|
||||
pub not_seen: RwLock<Vec<usize>>,
|
||||
mdns_service: Mutex<Option<Service>>,
|
||||
listener_stop_signal: Mutex<Option<Sender<()>>>,
|
||||
@ -72,6 +73,10 @@ impl SessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_all_senders(&self) -> Vec<Sender<SessionCommand>> {
|
||||
self.sessions.read().unwrap().iter().map(|i| i.1.sender.clone()).collect()
|
||||
}
|
||||
|
||||
async fn encrypt_and_send<T: PsecWriter>(&self, writer: &mut T, buff: &[u8]) -> Result<(), PsecError> {
|
||||
let use_padding = self.identity.read().unwrap().as_ref().unwrap().use_padding;
|
||||
writer.encrypt_and_send(buff, use_padding).await
|
||||
@ -96,7 +101,7 @@ impl SessionManager {
|
||||
}
|
||||
}
|
||||
if !msg_saved {
|
||||
self.saved_msgs.lock().unwrap().get_mut(&session_id).unwrap().push((false, buffer));
|
||||
self.saved_msgs.write().unwrap().get_mut(&session_id).unwrap().push((outgoing, buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,7 +132,7 @@ impl SessionManager {
|
||||
ui_connection.on_disconnected(&session_id);
|
||||
});
|
||||
self.sessions.write().unwrap().remove(session_id);
|
||||
self.saved_msgs.lock().unwrap().remove(session_id);
|
||||
self.saved_msgs.write().unwrap().remove(session_id);
|
||||
self.not_seen.write().unwrap().retain(|x| x != session_id);
|
||||
}
|
||||
|
||||
@ -172,64 +177,6 @@ impl SessionManager {
|
||||
let session_read = result.1;
|
||||
receiving.set(session_read.into_receive_and_decrypt());
|
||||
match buffer[0] {
|
||||
protocol::Headers::ASK_NAME => {
|
||||
let name = {
|
||||
self.identity.read().unwrap().as_ref().and_then(|identity| Some(identity.name.clone()))
|
||||
};
|
||||
if name.is_some() { //can be None if we log out just before locking the identity mutex
|
||||
if let Err(e) = self.encrypt_and_send(&mut session_write, &protocol::tell_name(&name.unwrap())).await {
|
||||
print_error!(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
protocol::Headers::TELL_NAME => {
|
||||
match from_utf8(&buffer[1..]) {
|
||||
Ok(new_name) => {
|
||||
let new_name = new_name.replace('\n', " ");
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_name_told(&session_id, &new_name);
|
||||
});
|
||||
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
||||
if let Some(contact) = loaded_contacts.get_mut(&session_id) {
|
||||
contact.name = new_name.to_string();
|
||||
if let Err(e) = self.identity.read().unwrap().as_ref().unwrap().change_contact_name(&contact.uuid, &new_name) {
|
||||
print_error!(e);
|
||||
}
|
||||
} else {
|
||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().name = new_name.to_string();
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
protocol::Headers::ASK_LARGE_FILES => {
|
||||
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(files_info) = protocol::parse_ask_files(&buffer) {
|
||||
let download_location = UserDirs::new().unwrap().download_dir;
|
||||
let files: Vec<LargeFileDownload> = files_info.into_iter().map(|info| {
|
||||
LargeFileDownload {
|
||||
file_name: info.1,
|
||||
file_size: info.0,
|
||||
transferred: 0,
|
||||
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(),
|
||||
});
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_ask_large_files(&session_id, &files, download_location.to_str().unwrap());
|
||||
})
|
||||
}
|
||||
} else if let Err(e) = self.encrypt_and_send(&mut session_write, &[protocol::Headers::ABORT_FILES_TRANSFER]).await {
|
||||
print_error!(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
protocol::Headers::LARGE_FILE_CHUNK => {
|
||||
let mut should_accept_chunk = false;
|
||||
{
|
||||
@ -322,6 +269,103 @@ impl SessionManager {
|
||||
ui_connection.on_received(&session_id, &buffer);
|
||||
});
|
||||
}
|
||||
protocol::Headers::ASK_LARGE_FILES => {
|
||||
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(files_info) = protocol::parse_ask_files(&buffer) {
|
||||
let download_location = UserDirs::new().unwrap().download_dir;
|
||||
let files: Vec<LargeFileDownload> = files_info.into_iter().map(|info| {
|
||||
LargeFileDownload {
|
||||
file_name: info.1,
|
||||
file_size: info.0,
|
||||
transferred: 0,
|
||||
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(),
|
||||
});
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_ask_large_files(&session_id, &files, download_location.to_str().unwrap());
|
||||
})
|
||||
}
|
||||
} else if let Err(e) = self.encrypt_and_send(&mut session_write, &[protocol::Headers::ABORT_FILES_TRANSFER]).await {
|
||||
print_error!(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
protocol::Headers::ASK_PROFILE_INFO => {
|
||||
let identity = {
|
||||
self.identity.read().unwrap().clone()
|
||||
};
|
||||
if let Some(identity) = identity { //can be None if we log out just before locking the identity mutex
|
||||
match self.encrypt_and_send(&mut session_write, &protocol::name(&identity.name)).await {
|
||||
Ok(_) => {
|
||||
if let Ok(avatar) = Identity::get_identity_avatar() {
|
||||
if let Err(e) = self.encrypt_and_send(&mut session_write, &protocol::avatar(&avatar)).await {
|
||||
print_error!(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
protocol::Headers::NAME => {
|
||||
match from_utf8(&buffer[1..]) {
|
||||
Ok(new_name) => {
|
||||
let new_name = new_name.replace('\n', " ");
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_name_told(&session_id, &new_name);
|
||||
});
|
||||
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
||||
if let Some(contact) = loaded_contacts.get_mut(&session_id) {
|
||||
contact.name = new_name.to_string();
|
||||
if let Err(e) = self.identity.read().unwrap().as_ref().unwrap().change_contact_name(&contact.uuid, &new_name) {
|
||||
print_error!(e);
|
||||
}
|
||||
} else {
|
||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().name = new_name.to_string();
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
protocol::Headers::AVATAR => {
|
||||
if buffer.len() < 10000000 {
|
||||
match image::load_from_memory(&buffer[1..]) {
|
||||
Ok(image) => {
|
||||
drop(image);
|
||||
let identity_opt = self.identity.read().unwrap();
|
||||
let identity = identity_opt.as_ref().unwrap();
|
||||
match identity.store_avatar(&buffer[1..]) {
|
||||
Ok(avatar_uuid) => {
|
||||
let mut loaded_contacts = self.loaded_contacts.write().unwrap();
|
||||
if let Some(contact) = loaded_contacts.get_mut(&session_id) {
|
||||
contact.avatar = Some(avatar_uuid);
|
||||
if let Err(e) = identity.set_contact_avatar(&contact.uuid, &avatar_uuid) {
|
||||
print_error!(e);
|
||||
}
|
||||
} else {
|
||||
self.sessions.write().unwrap().get_mut(&session_id).unwrap().avatar = Some(avatar_uuid);
|
||||
}
|
||||
self.with_ui_connection(|ui_connection| {
|
||||
ui_connection.on_avatar_set(&session_id);
|
||||
});
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let header = buffer[0];
|
||||
let buffer = match header {
|
||||
@ -421,11 +465,7 @@ impl SessionManager {
|
||||
let mut peer_public_key = [0; PUBLIC_KEY_LENGTH];
|
||||
let session = {
|
||||
let identity = {
|
||||
let identity_opt = session_manager.identity.read().unwrap();
|
||||
match identity_opt.as_ref() {
|
||||
Some(identity) => Some(identity.clone()),
|
||||
None => None
|
||||
}
|
||||
session_manager.identity.read().unwrap().clone()
|
||||
};
|
||||
match identity {
|
||||
Some(identity) => {
|
||||
@ -461,8 +501,9 @@ impl SessionManager {
|
||||
}
|
||||
if is_new_session && session_manager.is_identity_loaded() { //check if we didn't logged out during the handshake
|
||||
let (sender, receiver) = mpsc::channel(32);
|
||||
let session_data = SessionData{
|
||||
let session_data = SessionData {
|
||||
name: ip.to_string(),
|
||||
avatar: None,
|
||||
outgoing,
|
||||
peer_public_key,
|
||||
ip,
|
||||
@ -485,7 +526,7 @@ impl SessionManager {
|
||||
*session_counter += 1;
|
||||
}
|
||||
let session_id = session_id.unwrap();
|
||||
session_manager.saved_msgs.lock().unwrap().insert(session_id, Vec::new());
|
||||
session_manager.saved_msgs.write().unwrap().insert(session_id, Vec::new());
|
||||
Some((session_id, receiver))
|
||||
} else {
|
||||
None
|
||||
@ -497,7 +538,7 @@ impl SessionManager {
|
||||
ui_connection.on_new_session(&session_id, &ip.to_string(), outgoing, &crypto::generate_fingerprint(&peer_public_key), ip, None);
|
||||
});
|
||||
if !is_contact {
|
||||
match session_manager.encrypt_and_send(&mut session, &protocol::ask_name()).await {
|
||||
match session_manager.encrypt_and_send(&mut session, &protocol::ask_profile_info()).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
@ -542,7 +583,7 @@ impl SessionManager {
|
||||
}
|
||||
|
||||
pub fn get_saved_msgs(&self) -> HashMap<usize, Vec<(bool, Vec<u8>)>> {
|
||||
self.saved_msgs.lock().unwrap().clone()
|
||||
self.saved_msgs.read().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn set_seen(&self, session_id: usize, seen: bool) {
|
||||
@ -567,8 +608,10 @@ impl SessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_contact(&self, session_id: usize, name: String) -> Result<(), rusqlite::Error> {
|
||||
let contact = self.identity.read().unwrap().as_ref().unwrap().add_contact(name, self.sessions.read().unwrap().get(&session_id).unwrap().peer_public_key)?;
|
||||
pub fn add_contact(&self, session_id: usize) -> Result<(), rusqlite::Error> {
|
||||
let sessions = self.sessions.read().unwrap();
|
||||
let session = sessions.get(&session_id).unwrap();
|
||||
let contact = self.identity.read().unwrap().as_ref().unwrap().add_contact(session.name.clone(), session.avatar, session.peer_public_key)?;
|
||||
self.loaded_contacts.write().unwrap().insert(session_id, contact);
|
||||
self.last_loaded_msg_offsets.write().unwrap().insert(session_id, 0);
|
||||
Ok(())
|
||||
@ -581,6 +624,7 @@ impl SessionManager {
|
||||
if let Some(contact) = loaded_contacts.remove(&session_id) {
|
||||
if let Some(session) = self.sessions.write().unwrap().get_mut(&session_id) {
|
||||
session.name = contact.name;
|
||||
session.avatar = contact.avatar;
|
||||
}
|
||||
}
|
||||
self.last_loaded_msg_offsets.write().unwrap().remove(&session_id);
|
||||
@ -602,7 +646,7 @@ impl SessionManager {
|
||||
let result = Identity::delete_conversation(&self.loaded_contacts.read().unwrap().get(&session_id).unwrap().uuid);
|
||||
if result.is_ok() {
|
||||
self.last_loaded_msg_offsets.write().unwrap().insert(session_id, 0);
|
||||
self.saved_msgs.lock().unwrap().insert(session_id, Vec::new());
|
||||
self.saved_msgs.write().unwrap().insert(session_id, Vec::new());
|
||||
}
|
||||
result
|
||||
}
|
||||
@ -627,15 +671,32 @@ impl SessionManager {
|
||||
msgs
|
||||
}
|
||||
|
||||
pub fn get_avatar(&self, session_id: &usize) -> Option<Vec<u8>> {
|
||||
let avatar_uuid = match self.loaded_contacts.read().unwrap().get(session_id) {
|
||||
Some(contact) => contact.avatar?,
|
||||
None => self.sessions.read().unwrap().get(session_id)?.avatar?
|
||||
};
|
||||
self.identity.read().unwrap().as_ref().unwrap().get_avatar(&avatar_uuid)
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
pub async fn set_avatar(&self, avatar: &[u8]) -> Result<(), rusqlite::Error> {
|
||||
Identity::set_identity_avatar(&avatar)?;
|
||||
let avatar_msg = protocol::avatar(&avatar);
|
||||
for sender in self.get_all_senders().into_iter() {
|
||||
sender.send(SessionCommand::Send {
|
||||
buff: avatar_msg.clone()
|
||||
}).await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
pub async fn change_name(&self, new_name: String) -> Result<usize, rusqlite::Error> {
|
||||
let telling_name = protocol::tell_name(&new_name);
|
||||
let telling_name = protocol::name(&new_name);
|
||||
let result = self.identity.write().unwrap().as_mut().unwrap().change_name(new_name);
|
||||
if result.is_ok() {
|
||||
let senders: Vec<Sender<SessionCommand>> = {
|
||||
self.sessions.read().unwrap().iter().map(|i| i.1.sender.clone()).collect()
|
||||
};
|
||||
for sender in senders.into_iter() {
|
||||
for sender in self.get_all_senders().into_iter() {
|
||||
sender.send(SessionCommand::Send {
|
||||
buff: telling_name.clone()
|
||||
}).await;
|
||||
@ -659,7 +720,7 @@ impl SessionManager {
|
||||
*self.ui_connection.lock().unwrap() = None;
|
||||
*self.session_counter.write().unwrap() = 0;
|
||||
self.loaded_contacts.write().unwrap().clear();
|
||||
self.saved_msgs.lock().unwrap().clear();
|
||||
self.saved_msgs.write().unwrap().clear();
|
||||
}
|
||||
|
||||
pub fn is_identity_loaded(&self) -> bool {
|
||||
@ -703,7 +764,7 @@ impl SessionManager {
|
||||
ui_connection: Mutex::new(None),
|
||||
loaded_contacts: RwLock::new(HashMap::new()),
|
||||
last_loaded_msg_offsets: RwLock::new(HashMap::new()),
|
||||
saved_msgs: Mutex::new(HashMap::new()),
|
||||
saved_msgs: RwLock::new(HashMap::new()),
|
||||
not_seen: RwLock::new(Vec::new()),
|
||||
mdns_service: Mutex::new(None),
|
||||
listener_stop_signal: Mutex::new(None),
|
||||
|
@ -112,6 +112,9 @@ mod ui_messages {
|
||||
pub fn on_name_told(session_id: &usize, name: &str) -> Message {
|
||||
Message::from(format!("name_told {} {}", session_id, name))
|
||||
}
|
||||
pub fn on_avatar_set(session_id: &usize) -> Message {
|
||||
simple_event("avatar_set", session_id)
|
||||
}
|
||||
pub fn set_as_contact(session_id: usize, name: &str, verified: bool, fingerprint: &str) -> Message {
|
||||
Message::from(format!("is_contact {} {} {} {}", session_id, verified, fingerprint, name))
|
||||
}
|
||||
@ -179,6 +182,9 @@ impl UiConnection {
|
||||
pub fn on_name_told(&mut self, session_id: &usize, name: &str) {
|
||||
self.write_message(ui_messages::on_name_told(session_id, name));
|
||||
}
|
||||
pub fn on_avatar_set(&mut self, session_id: &usize) {
|
||||
self.write_message(ui_messages::on_avatar_set(session_id));
|
||||
}
|
||||
|
||||
pub fn inc_files_transfer(&mut self, session_id: &usize, chunk_size: u64) {
|
||||
self.write_message(ui_messages::inc_files_transfer(session_id, chunk_size));
|
||||
|
Loading…
Reference in New Issue
Block a user