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"
|
||||
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> {
|
||||
@ -11,10 +15,15 @@ fn minify_content(content: &str, language: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
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() {
|
||||
use yaml_rust::YamlLoader;
|
||||
|
||||
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);
|
||||
|
262
src/identity.rs
262
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,107 +127,89 @@ 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) => {
|
||||
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();
|
||||
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();
|
||||
match crypto::decrypt_data(encrypted_verified.as_slice(), &self.master_key) {
|
||||
Ok(verified) => {
|
||||
let encrypted_seen: Vec<u8> = row.get(4).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) => {
|
||||
contacts.push(Contact {
|
||||
uuid: Uuid::from_bytes(uuid_bytes),
|
||||
public_key: public_key.try_into().unwrap(),
|
||||
name: std::str::from_utf8(name.as_slice()).unwrap().to_owned(),
|
||||
verified: byte_to_bool(verified[0]).unwrap(),
|
||||
seen: byte_to_bool(seen[0]).unwrap(),
|
||||
})
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
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 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) => {
|
||||
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(4).unwrap();
|
||||
match crypto::decrypt_data(encrypted_verified.as_slice(), &self.master_key) {
|
||||
Ok(verified) => {
|
||||
let encrypted_seen: Vec<u8> = row.get(5).unwrap();
|
||||
match crypto::decrypt_data(encrypted_seen.as_slice(), &self.master_key) {
|
||||
Ok(seen) => {
|
||||
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(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(),
|
||||
})
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
} else {
|
||||
print_error!("Invalid public key length: database corrupted");
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
Some(contacts)
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
return Some(contacts);
|
||||
}
|
||||
}
|
||||
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 rows = stmt.query([]).unwrap();
|
||||
while let Some(row) = rows.next().unwrap() {
|
||||
let encrypted_uuid: Vec<u8> = row.get(0).unwrap();
|
||||
match crypto::decrypt_data(encrypted_uuid.as_slice(), &self.master_key){
|
||||
Ok(test_uuid) => {
|
||||
if test_uuid == uuid.as_bytes() {
|
||||
let encrypted_data: Vec<u8> = row.get(1).unwrap();
|
||||
match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) {
|
||||
Ok(data) => return Some(data),
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
let mut stmt = db.prepare(&format!("SELECT uuid, data FROM \"{}\"", FILES_TABLE)).unwrap();
|
||||
let mut rows = stmt.query([]).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) => {
|
||||
if test_uuid == uuid.as_bytes() {
|
||||
let encrypted_data: Vec<u8> = row.get(1).unwrap();
|
||||
match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) {
|
||||
Ok(data) => return Some(data),
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
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> {
|
||||
@ -244,73 +238,44 @@ 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) => {
|
||||
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 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 rows = stmt.query([]).unwrap();
|
||||
let mut msgs = Vec::new();
|
||||
while let Some(row) = rows.next().unwrap() {
|
||||
let encrypted_outgoing: Vec<u8> = row.get(0).unwrap();
|
||||
match crypto::decrypt_data(encrypted_outgoing.as_slice(), &self.master_key){
|
||||
Ok(outgoing) => {
|
||||
match byte_to_bool(outgoing[0]) {
|
||||
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
|
||||
)
|
||||
)
|
||||
},
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
}
|
||||
if let Ok(mut stmt) = db.prepare(&format!("SELECT count(*) FROM \"{}\"", contact_uuid)) {
|
||||
let mut rows = stmt.query([]).unwrap();
|
||||
if let Ok(Some(row)) = rows.next() {
|
||||
let total: usize = row.get(0).unwrap();
|
||||
if offset < total {
|
||||
if offset+count >= total {
|
||||
count = total-offset;
|
||||
}
|
||||
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 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) => {
|
||||
match byte_to_bool(outgoing[0]) {
|
||||
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)),
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
Some(msgs)
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Err(_) => None
|
||||
return Some(msgs);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
@ -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())
|
||||
}
|
||||