diff --git a/src/frontend/index.css b/src/frontend/index.css index 9ef469a..463bb74 100644 --- a/src/frontend/index.css +++ b/src/frontend/index.css @@ -165,6 +165,8 @@ label { transform: translateX(26px); } #avatarContainer { + position: relative; + padding-bottom: 1.5em; display: flex; justify-content: center; } @@ -191,6 +193,14 @@ label { #avatarContainer label:hover p { display: block; } +#removeAvatar { + position: absolute; + bottom: 0; + cursor: pointer; +} +#removeAvatar:hover { + color: var(--accent); +} #profile_info section { display: block; margin-bottom: 20px; @@ -199,12 +209,12 @@ label { #profile_info section:first-of-type { border-top: unset; } +#profile_info section:first-of-type h3 { + margin: 0; +} #profile_info input { margin: 10px; } -#profile_info span { - font-weight: bold; -} #profile_info>div>div p { font-weight: normal; font-size: 0.9em; @@ -275,10 +285,6 @@ label { #me>div:hover p { color: var(--accent); } -#identity_fingerprint { - text-align: center; - margin-bottom: 0; -} #left_panel ul:last-of-type, #msg_log { flex-grow: 1; } diff --git a/src/frontend/index.js b/src/frontend/index.js index 8de35d3..3257024 100644 --- a/src/frontend/index.js +++ b/src/frontend/index.js @@ -244,6 +244,7 @@ profile_div.onclick = function() { labelAvatar.setAttribute("for", "avatar_input"); let inputAvatar = document.createElement("input"); inputAvatar.type = "file"; + inputAvatar.accept = "image/*"; inputAvatar.id = "avatar_input"; inputAvatar.onchange = function(event) { let file = event.target.files[0]; @@ -253,11 +254,7 @@ profile_div.onclick = function() { 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(); - } + refreshSelfAvatar(); } else { console.log(response); } @@ -277,11 +274,14 @@ profile_div.onclick = function() { uploadP.textContent = "Upload"; labelAvatar.appendChild(uploadP); avatarContainer.appendChild(labelAvatar); + let removeAvatar = document.createElement("span"); + removeAvatar.id = "removeAvatar"; + removeAvatar.textContent = "Remove"; + removeAvatar.onclick = function() { + socket.send("remove_avatar"); + }; + avatarContainer.appendChild(removeAvatar); mainDiv.appendChild(avatarContainer); - let fingerprint = document.createElement("pre"); - fingerprint.id = "identity_fingerprint"; - fingerprint.textContent = beautifyFingerprint(identityFingerprint); - mainDiv.appendChild(fingerprint); let sectionName = document.createElement("section"); let titleName = document.createElement("h3"); titleName.textContent = "Name:"; @@ -299,6 +299,14 @@ profile_div.onclick = function() { }; sectionName.appendChild(saveNameButton); mainDiv.appendChild(sectionName); + let sectionFingerprint = document.createElement("section"); + let titleFingerprint = document.createElement("h3"); + titleFingerprint.textContent = "Your fingerprint:"; + sectionFingerprint.appendChild(titleFingerprint); + let fingerprint = document.createElement("pre"); + fingerprint.textContent = beautifyFingerprint(identityFingerprint); + sectionFingerprint.appendChild(fingerprint); + mainDiv.appendChild(sectionFingerprint); let sectionPadding = document.createElement("section"); sectionPadding.appendChild(generateSwitchPreference("Use PSEC padding", "PSEC padding obfuscates the length of your messages but uses more network bandwidth.", usePadding, function(checked) { socket.send("set_use_padding "+checked); @@ -499,8 +507,8 @@ socket.onmessage = function(msg) { case "name_told": onNameTold(args[1], msg.data.slice(args[0].length+args[1].length+2)); break; - case "avatar_set": - onAvatarSet(args[1]); + case "avatar_changed": + onAvatarChanged(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)); @@ -554,12 +562,15 @@ function onNameTold(sessionId, name) { } displaySessions(); } -function onAvatarSet(sessionId) { - avatarTimestamps.set(sessionId, Date.now()); +function onAvatarChanged(sessionIdOrSelf) { + avatarTimestamps.set(sessionIdOrSelf, Date.now()); displaySessions(); - if (sessionId === currentSessionId) { + if (sessionIdOrSelf === currentSessionId) { displayHeader(); displayHistory(false); + refreshAvatar("#session_info .avatar", sessionIdOrSelf); + } else if (sessionIdOrSelf === "self") { + refreshSelfAvatar(); } } function setNotSeen(strSessionIds) { @@ -818,6 +829,23 @@ function sendNextLargeFile(sessionId) { } }); } +function refreshAvatar(selector, sessionId) { + let avatar = document.querySelector(selector); + if (typeof avatar !== "undefined") { + if (typeof sessionId === "undefined") { + avatar.src = "/avatar/self?"+avatarTimestamps.get("self"); + } else { + avatar.src = "/avatar/"+sessionId+"/"+sessionsData.get(sessionId).name+"?"+avatarTimestamps.get(sessionId); + } + } +} +function refreshSelfAvatar() { + refreshAvatar("#avatarContainer .avatar"); + displayProfile(); + if (currentSessionId != -1) { + displayHistory(false); + } +} function beautifyFingerprint(f) { for (let i=4; i Result { + pub fn set_contact_avatar(&self, contact_uuid: &Uuid, avatar_uuid: Option<&Uuid>) -> Result { 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()[..]]) + match avatar_uuid { + Some(avatar_uuid) => db.execute(&format!("UPDATE {} SET avatar=?1 WHERE uuid=?2", CONTACTS_TABLE), params![&avatar_uuid.as_bytes()[..], &contact_uuid.as_bytes()[..]]), + None => { + db.execute(&format!("DELETE FROM {} WHERE uuid=(SELECT avatar FROM {} WHERE uuid=?)", AVATARS_TABLE, CONTACTS_TABLE), params![&contact_uuid.as_bytes()[..]])?; + db.execute(&format!("UPDATE {} SET avatar=NULL WHERE uuid=?", CONTACTS_TABLE), params![&contact_uuid.as_bytes()[..]]) + } + } } pub fn set_contact_seen(&self, uuid: &Uuid, seen: bool) -> Result { @@ -473,6 +479,11 @@ impl Identity { db.upsert(DBKeys::AVATAR, avatar) } + pub fn remove_identity_avatar() -> Result { + let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?; + db.del(DBKeys::AVATAR) + } + pub fn get_identity_avatar() -> Result, rusqlite::Error> { let db = KeyValueTable::new(&get_database_path(), MAIN_TABLE)?; db.get(DBKeys::AVATAR) diff --git a/src/key_value_table.rs b/src/key_value_table.rs index 1a07122..e478233 100644 --- a/src/key_value_table.rs +++ b/src/key_value_table.rs @@ -22,9 +22,9 @@ impl<'a> KeyValueTable<'a> { None => Err(rusqlite::Error::QueryReturnedNoRows) } } - /*pub fn del(&self, key: &str) -> Result { - self.db.execute(&format!("DELETE FROM {} WHERE key=\"{}\"", self.table_name, key), NO_PARAMS) - }*/ + pub fn del(&self, key: &str) -> Result { + self.db.execute(&format!("DELETE FROM {} WHERE key=\"{}\"", self.table_name, key), []) + } pub fn update(&self, key: &str, value: &[u8]) -> Result { self.db.execute(&format!("UPDATE {} SET value=? WHERE key=\"{}\"", self.table_name, key), params![value]) } diff --git a/src/main.rs b/src/main.rs index fa87056..4aade81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -231,6 +231,12 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc { + match session_manager.remove_avatar().await { + Ok(_) => ui_connection.on_avatar_changed(None), + Err(e) => print_error!(e) + } + } "set_use_padding" => { let use_padding: bool = args[1].parse().unwrap(); if let Err(e) = session_manager.identity.write().unwrap().as_mut().unwrap().set_use_padding(use_padding) { @@ -240,9 +246,7 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc { let new_name = &msg[args[0].len()+1..]; match session_manager.change_name(new_name.to_string()).await { - Ok(_) => { - ui_connection.set_name(new_name) - } + Ok(_) => ui_connection.set_name(new_name), Err(e) => print_error!(e) }; } diff --git a/src/protocol.rs b/src/protocol.rs index e182aba..86b73c5 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -9,11 +9,12 @@ impl Headers { 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 const REMOVE_AVATAR: u8 = 0x05; + pub const ASK_LARGE_FILES: u8 = 0x06; + pub const ACCEPT_LARGE_FILES: u8 = 0x07; + pub const LARGE_FILE_CHUNK: u8 = 0x08; + pub const ACK_CHUNK: u8 = 0x09; + pub const ABORT_FILES_TRANSFER: u8 = 0x0a; } pub fn new_message(message: String) -> Vec { @@ -84,4 +85,8 @@ pub fn parse_ask_files(buffer: &[u8]) -> Option> { pub fn avatar(avatar: &[u8]) -> Vec { [&[Headers::AVATAR], avatar].concat() +} + +pub fn remove_avatar() -> Vec { + vec![Headers::REMOVE_AVATAR] } \ No newline at end of file diff --git a/src/session_manager.rs b/src/session_manager.rs index a9cc164..1da4e0b 100644 --- a/src/session_manager.rs +++ b/src/session_manager.rs @@ -136,6 +136,21 @@ impl SessionManager { self.not_seen.write().unwrap().retain(|x| x != session_id); } + fn set_avatar_uuid(&self, session_id: &usize, avatar_uuid: Option) { + let mut loaded_contacts = self.loaded_contacts.write().unwrap(); + if let Some(contact) = loaded_contacts.get_mut(session_id) { + contact.avatar = avatar_uuid; + if let Err(e) = self.identity.read().unwrap().as_ref().unwrap().set_contact_avatar(&contact.uuid, avatar_uuid.as_ref()) { + print_error!(e); + } + } else { + self.sessions.write().unwrap().get_mut(session_id).unwrap().avatar = avatar_uuid; + } + self.with_ui_connection(|ui_connection| { + ui_connection.on_avatar_changed(Some(session_id)); + }); + } + async fn send_msg(&self, session_id: usize, session_write: &mut SessionWriteHalf, buff: &[u8], is_sending: &mut bool, file_ack_sender: &mut Option>) -> Result<(), PsecError> { self.encrypt_and_send(session_write, &buff).await?; if buff[0] == protocol::Headers::ACCEPT_LARGE_FILES { @@ -342,23 +357,8 @@ impl SessionManager { 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); - }); - } + match self.identity.read().unwrap().as_ref().unwrap().store_avatar(&buffer[1..]) { + Ok(avatar_uuid) => self.set_avatar_uuid(&session_id, Some(avatar_uuid)), Err(e) => print_error!(e) } } @@ -366,6 +366,7 @@ impl SessionManager { } } } + protocol::Headers::REMOVE_AVATAR => self.set_avatar_uuid(&session_id, None), _ => { let header = buffer[0]; let buffer = match header { @@ -691,6 +692,18 @@ impl SessionManager { Ok(()) } + #[allow(unused_must_use)] + pub async fn remove_avatar(&self) -> Result<(), rusqlite::Error> { + Identity::remove_identity_avatar()?; + let avatar_msg = protocol::remove_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 { let telling_name = protocol::name(&new_name); diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 07bb8e0..3393bb3 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -112,8 +112,11 @@ 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 on_avatar_changed(session_id: Option<&usize>) -> Message { + match session_id { + Some(session_id) => simple_event("avatar_changed", session_id), + None => Message::from("avatar_changed self") + } } 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)) @@ -182,8 +185,8 @@ 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 on_avatar_changed(&mut self, session_id: Option<&usize>) { + self.write_message(ui_messages::on_avatar_changed(session_id)); } pub fn inc_files_transfer(&mut self, session_id: &usize, chunk_size: u64) {