use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; use tdn::types::{ group::{EventId, GroupId}, primitive::{PeerAddr, Result}, rpc::{json, RpcParam}, }; use tdn_storage::local::{DStorage, DsValue}; use crate::storage::{ read_avatar_sync, read_file_sync, read_image_sync, read_record_sync, write_avatar_sync, write_file_sync, write_image_sync, write_record_sync, }; pub(crate) struct Friend { pub id: i64, pub gid: GroupId, pub addr: PeerAddr, pub name: String, pub remark: String, pub is_top: bool, pub is_closed: bool, pub last_message_datetime: i64, pub last_message_content: String, pub last_message_readed: bool, pub online: bool, pub is_deleted: bool, } #[derive(Clone)] pub(crate) struct Request { pub id: i64, pub gid: GroupId, pub addr: PeerAddr, pub name: String, pub remark: String, pub is_me: bool, pub is_ok: bool, pub is_over: bool, pub is_delivery: bool, pub datetime: i64, pub is_deleted: bool, } /// message type use in network. #[derive(Serialize, Deserialize, Clone)] pub(crate) enum NetworkMessage { String(String), // content Image(Vec), // image bytes. File(String, Vec), // filename, file bytes. Contact(String, GroupId, PeerAddr, Vec), // name, gid, addr, avatar bytes. Record(Vec, u32), // record audio bytes. Emoji, Phone, Video, None, } impl NetworkMessage { pub(crate) fn handle( self, is_me: bool, gid: GroupId, base: &PathBuf, db: &DStorage, fid: i64, hash: EventId, ) -> Result { // handle event. let (m_type, raw) = match self { NetworkMessage::String(content) => (MessageType::String, content), NetworkMessage::Image(bytes) => { let image_name = write_image_sync(base, &gid, bytes)?; (MessageType::Image, image_name) } NetworkMessage::File(old_name, bytes) => { let filename = write_file_sync(base, &gid, &old_name, bytes)?; (MessageType::File, filename) } NetworkMessage::Contact(name, rgid, addr, avatar_bytes) => { write_avatar_sync(base, &gid, &rgid, avatar_bytes)?; let tmp_name = name.replace(";", "-;"); let contact_values = format!("{};;{};;{}", tmp_name, rgid.to_hex(), addr.to_hex()); (MessageType::Contact, contact_values) } NetworkMessage::Emoji => { // TODO (MessageType::Emoji, "".to_owned()) } NetworkMessage::Record(bytes, time) => { let record_name = write_record_sync(base, &gid, fid, time, bytes)?; (MessageType::Record, record_name) } NetworkMessage::Phone => { // TODO (MessageType::Phone, "".to_owned()) } NetworkMessage::Video => { // TODO (MessageType::Video, "".to_owned()) } NetworkMessage::None => { return Ok(Message::new_with_id( hash, fid, is_me, MessageType::String, "".to_owned(), true, )); } }; let mut msg = Message::new_with_id(hash, fid, is_me, m_type, raw, true); msg.insert(db)?; Friend::update_last_message(db, fid, &msg, false)?; Ok(msg) } pub fn from_model(base: &PathBuf, gid: &GroupId, model: Message) -> Result { // handle message's type. match model.m_type { MessageType::String => Ok(NetworkMessage::String(model.content)), MessageType::Image => { let bytes = read_image_sync(base, gid, &model.content)?; Ok(NetworkMessage::Image(bytes)) } MessageType::File => { let bytes = read_file_sync(base, gid, &model.content)?; Ok(NetworkMessage::File(model.content, bytes)) } MessageType::Contact => { let v: Vec<&str> = model.content.split(";;").collect(); if v.len() != 3 { return Ok(NetworkMessage::None); } let cname = v[0].to_owned(); let cgid = GroupId::from_hex(v[1])?; let caddr = PeerAddr::from_hex(v[2])?; let avatar_bytes = read_avatar_sync(base, gid, &cgid)?; Ok(NetworkMessage::Contact(cname, cgid, caddr, avatar_bytes)) } MessageType::Record => { let (bytes, time) = if let Some(i) = model.content.find('-') { let time = model.content[0..i].parse().unwrap_or(0); let bytes = read_record_sync(base, gid, &model.content[i + 1..])?; (bytes, time) } else { (vec![], 0) }; Ok(NetworkMessage::Record(bytes, time)) } MessageType::Emoji => Ok(NetworkMessage::Emoji), MessageType::Phone => Ok(NetworkMessage::Phone), MessageType::Video => Ok(NetworkMessage::Video), } } } #[derive(Eq, PartialEq)] pub(crate) enum MessageType { String, Image, File, Contact, Emoji, Record, Phone, Video, } impl MessageType { pub fn to_int(&self) -> i64 { match self { MessageType::String => 0, MessageType::Image => 1, MessageType::File => 2, MessageType::Contact => 3, MessageType::Emoji => 4, MessageType::Record => 5, MessageType::Phone => 6, MessageType::Video => 7, } } pub fn from_int(i: i64) -> MessageType { match i { 0 => MessageType::String, 1 => MessageType::Image, 2 => MessageType::File, 3 => MessageType::Contact, 4 => MessageType::Emoji, 5 => MessageType::Record, 6 => MessageType::Phone, 7 => MessageType::Video, _ => MessageType::String, } } } pub(crate) struct Message { pub id: i64, pub hash: EventId, pub fid: i64, pub is_me: bool, pub m_type: MessageType, pub content: String, pub is_delivery: bool, pub datetime: i64, pub is_deleted: bool, } impl Friend { pub fn new(gid: GroupId, addr: PeerAddr, name: String, remark: String) -> Friend { let start = SystemTime::now(); let datetime = start .duration_since(UNIX_EPOCH) .map(|s| s.as_secs()) .unwrap_or(0) as i64; // safe for all life. Friend { id: 0, last_message_datetime: datetime, last_message_content: "".to_owned(), last_message_readed: true, gid, addr, name, remark, online: false, is_top: false, is_closed: false, is_deleted: false, } } pub fn contains_addr(&self, addr: &PeerAddr) -> bool { &self.addr == addr } /// here is zero-copy and unwrap is safe. checked. fn from_values(mut v: Vec, contains_deleted: bool) -> Friend { let is_deleted = if contains_deleted { v.pop().unwrap().as_bool() } else { false }; Friend { is_deleted, last_message_readed: v.pop().unwrap().as_bool(), last_message_content: v.pop().unwrap().as_string(), last_message_datetime: v.pop().unwrap().as_i64(), is_closed: v.pop().unwrap().as_bool(), is_top: v.pop().unwrap().as_bool(), remark: v.pop().unwrap().as_string(), name: v.pop().unwrap().as_string(), addr: PeerAddr::from_hex(v.pop().unwrap().as_str()).unwrap_or(PeerAddr::default()), gid: GroupId::from_hex(v.pop().unwrap().as_str()).unwrap_or(GroupId::default()), id: v.pop().unwrap().as_i64(), online: false, } } pub fn from_request(db: &DStorage, request: Request) -> Result { if let Some(mut friend) = Friend::get_it(&db, &request.gid)? { friend.name = request.name; friend.addr = request.addr; friend.is_closed = false; friend.remote_update(&db)?; Ok(friend) } else { let mut friend = request.to_friend(); friend.insert(&db)?; Ok(friend) } } pub fn to_rpc(&self) -> RpcParam { json!([ self.id, self.gid.to_hex(), self.addr.to_hex(), self.name, self.remark, if self.is_top { "1" } else { "0" }, if self.is_closed { "1" } else { "0" }, self.last_message_datetime, self.last_message_content, self.last_message_readed, if self.online { "1" } else { "0" }, ]) } pub fn get(db: &DStorage, gid: &GroupId) -> Result> { let sql = format!("SELECT id, gid, addr, name, remark, is_top, is_closed, last_message_datetime, last_message_content, last_message_readed FROM friends WHERE gid = '{}' and is_deleted = false", gid.to_hex()); let mut matrix = db.query(&sql)?; if matrix.len() > 0 { return Ok(Some(Friend::from_values(matrix.pop().unwrap(), false))); // safe unwrap() } Ok(None) } pub fn get_it(db: &DStorage, gid: &GroupId) -> Result> { let sql = format!("SELECT id, gid, addr, name, remark, is_top, is_closed, last_message_datetime, last_message_content, last_message_readed, is_deleted FROM friends WHERE gid = '{}'", gid.to_hex()); let mut matrix = db.query(&sql)?; if matrix.len() > 0 { return Ok(Some(Friend::from_values(matrix.pop().unwrap(), true))); // safe unwrap() } Ok(None) } pub fn get_id(db: &DStorage, id: i64) -> Result> { let sql = format!("SELECT id, gid, addr, name, remark, is_top, is_closed, last_message_datetime, last_message_content, last_message_readed, is_deleted FROM friends WHERE id = {}", id); let mut matrix = db.query(&sql)?; if matrix.len() > 0 { return Ok(Some(Friend::from_values(matrix.pop().unwrap(), true))); // safe unwrap() } Ok(None) } /// use in rpc when load account friends. pub fn all(db: &DStorage) -> Result> { let matrix = db.query("SELECT id, gid, addr, name, remark, is_top, is_closed, last_message_datetime, last_message_content, last_message_readed FROM friends where is_deleted = false ORDER BY last_message_datetime DESC")?; let mut friends = vec![]; for values in matrix { friends.push(Friend::from_values(values, false)); } Ok(friends) } /// use in rpc when load account friends. pub fn all_ok(db: &DStorage) -> Result> { let matrix = db.query("SELECT id, gid, addr, name, remark, is_top, is_closed, last_message_datetime, last_message_content, last_message_readed FROM friends where is_closed = false ORDER BY last_message_datetime DESC")?; let mut friends = vec![]; for values in matrix { friends.push(Friend::from_values(values, false)); } Ok(friends) } /// use in layer load friends ids. pub fn all_id(db: &DStorage) -> Result> { let matrix = db.query("SELECT id, gid FROM friends where is_closed = false ORDER BY id DESC")?; let mut friends = vec![]; for mut values in matrix { friends.push(( GroupId::from_hex(values.pop().unwrap().as_str()).unwrap_or(GroupId::default()), values.pop().unwrap().as_i64(), )); } Ok(friends) } pub fn insert(&mut self, db: &DStorage) -> Result<()> { let sql = format!("INSERT INTO friends (gid, addr, name, remark, is_top, is_closed, last_message_datetime, last_message_content, last_message_readed, is_deleted) VALUES ('{}', '{}', '{}', '{}', {}, {}, {}, '{}', {}, false)", self.gid.to_hex(), self.addr.to_hex(), self.name, self.remark, if self.is_top { 1 } else { 0 }, if self.is_closed { 1 } else { 0 }, self.last_message_datetime, self.last_message_content, if self.last_message_readed { 1 } else { 0 } ); let id = db.insert(&sql)?; self.id = id; Ok(()) } pub fn update(&self, db: &DStorage) -> Result { let sql = format!("UPDATE friends SET addr = '{}', name = '{}', remark = '{}', is_top = {}, is_closed = {}, last_message_datetime = {}, last_message_content = '{}', last_message_readed = {}, is_deleted = {} WHERE id = {}", self.addr.to_hex(), self.name, self.remark, if self.is_top { 1 } else { 0 }, if self.is_closed { 1 } else { 0 }, self.last_message_datetime, self.last_message_content, if self.last_message_readed { 1 } else { 0 }, if self.is_deleted { 1 } else { 0 }, self.id ); db.update(&sql) } pub fn me_update(&mut self, db: &DStorage) -> Result { let sql = format!( "UPDATE friends SET remark='{}', is_top={} WHERE id = {}", self.remark, self.is_top, self.id, ); db.update(&sql) } pub fn addr_update(db: &DStorage, id: i64, addr: &PeerAddr) -> Result { let sql = format!( "UPDATE friends SET addr='{}' WHERE id = {}", addr.to_hex(), id, ); db.update(&sql) } pub fn remote_update(&self, db: &DStorage) -> Result { let sql = format!( "UPDATE friends SET addr='{}', name='{}', is_closed = false, is_deleted = false WHERE id = {}", self.addr.to_hex(), self.name, self.id, ); db.update(&sql) } pub fn update_last_message(db: &DStorage, id: i64, msg: &Message, read: bool) -> Result { let sql = format!("UPDATE friends SET last_message_datetime={}, last_message_content='{}', last_message_readed={} WHERE id = {}", msg.datetime, msg.content, if read { 1 } else { 0 }, id, ); db.update(&sql) } pub fn readed(db: &DStorage, id: i64) -> Result { let sql = format!("UPDATE friends SET last_message_readed=1 WHERE id = {}", id); db.update(&sql) } /// used in rpc, when what to delete a friend. pub fn close(&self, db: &DStorage) -> Result { let sql = format!("UPDATE friends SET is_closed = true WHERE id = {}", self.id); db.update(&sql) } /// used in rpc, when what to delete a friend. pub fn delete(&self, db: &DStorage) -> Result { let sql = format!( "UPDATE friends SET is_deleted = true, is_closed = true WHERE id = {}", self.id ); db.update(&sql) } pub fn is_friend(db: &DStorage, gid: &GroupId) -> Result { let sql = format!( "SELECT id FROM friends WHERE is_closed = false and gid = '{}'", gid.to_hex() ); let matrix = db.query(&sql)?; Ok(matrix.len() > 0) } /// used in layers, when receive remote had closed. pub fn id_close(db: &DStorage, id: i64) -> Result { let sql = format!("UPDATE friends SET is_closed = true WHERE id = {}", id); db.update(&sql) } } impl Request { pub fn new( gid: GroupId, addr: PeerAddr, name: String, remark: String, is_me: bool, is_delivery: bool, ) -> Request { let start = SystemTime::now(); let datetime = start .duration_since(UNIX_EPOCH) .map(|s| s.as_secs()) .unwrap_or(0) as i64; // safe for all life. Request { id: 0, gid, addr, name, remark, is_me, is_ok: false, is_over: false, is_delivery, datetime: datetime, is_deleted: false, } } pub fn to_friend(self) -> Friend { let mut friend = Friend::new(self.gid, self.addr, self.name, self.remark); friend.is_top = true; // default set to top page. friend } /// here is zero-copy and unwrap is safe. checked. fn from_values(mut v: Vec, contains_deleted: bool) -> Request { let is_deleted = if contains_deleted { v.pop().unwrap().as_bool() } else { false }; Request { is_deleted, datetime: v.pop().unwrap().as_i64(), is_delivery: v.pop().unwrap().as_bool(), is_over: v.pop().unwrap().as_bool(), is_ok: v.pop().unwrap().as_bool(), is_me: v.pop().unwrap().as_bool(), remark: v.pop().unwrap().as_string(), name: v.pop().unwrap().as_string(), addr: PeerAddr::from_hex(v.pop().unwrap().as_str()).unwrap_or(PeerAddr::default()), gid: GroupId::from_hex(v.pop().unwrap().as_str()).unwrap_or(GroupId::default()), id: v.pop().unwrap().as_i64(), } } pub fn to_rpc(&self) -> RpcParam { json!([ self.id, self.gid.to_hex(), self.addr.to_hex(), self.name, self.remark, self.is_me, self.is_ok, self.is_over, self.is_delivery, self.datetime, ]) } pub fn get(db: &DStorage, gid: &GroupId) -> Result> { let sql = format!("SELECT id, gid, addr, name, remark, is_me, is_ok, is_over, is_delivery, datetime FROM requests WHERE gid = '{}' AND is_deleted = false", gid.to_hex()); let mut matrix = db.query(&sql)?; if matrix.len() > 0 { let values = matrix.pop().unwrap(); // safe unwrap() return Ok(Some(Request::from_values(values, false))); } Ok(None) } pub fn get_id(db: &DStorage, id: i64) -> Result> { let sql = format!("SELECT id, gid, addr, name, remark, is_me, is_ok, is_over, is_delivery, datetime, is_deleted FROM requests WHERE id = {}", id); let mut matrix = db.query(&sql)?; if matrix.len() > 0 { let values = matrix.pop().unwrap(); // safe unwrap() return Ok(Some(Request::from_values(values, true))); } Ok(None) } pub fn all(db: &DStorage) -> Result> { let matrix = db.query("SELECT id, gid, addr, name, remark, is_me, is_ok, is_over, is_delivery, datetime FROM requests WHERE is_deleted = false ORDER BY id DESC")?; let mut requests = vec![]; for values in matrix { requests.push(Request::from_values(values, false)); } Ok(requests) } pub fn insert(&mut self, db: &DStorage) -> Result<()> { let sql = format!("INSERT INTO requests (gid, addr, name, remark, is_me, is_ok, is_over, is_delivery, datetime, is_deleted) VALUES ('{}', '{}', '{}', '{}', {}, {}, {}, {}, {}, false)", self.gid.to_hex(), self.addr.to_hex(), self.name, self.remark, if self.is_me { 1 } else { 0 }, if self.is_ok { 1 } else { 0 }, if self.is_over { 1 } else { 0 }, if self.is_delivery { 1 } else { 0 }, self.datetime, ); let id = db.insert(&sql)?; self.id = id; Ok(()) } pub fn update(&self, db: &DStorage) -> Result { let sql = format!("UPDATE requests SET gid='{}', addr='{}', name='{}', remark='{}', is_me={}, is_ok={}, is_over={}, is_delivery={}, datetime={}, is_deleted = {} WHERE id = {}", self.gid.to_hex(), self.addr.to_hex(), self.name, self.remark, if self.is_me { 1 } else { 0 }, if self.is_ok { 1 } else { 0 }, if self.is_over { 1 } else { 0 }, if self.is_delivery { 1 } else { 0 }, self.datetime, if self.is_deleted { 1 } else { 0 }, self.id, ); db.update(&sql) } pub fn delivery(db: &DStorage, id: i64, is_delivery: bool) -> Result { let sql = format!( "UPDATE requests SET is_delivery={} WHERE id = {}", if is_delivery { 1 } else { 0 }, id, ); db.update(&sql) } pub fn delete(&self, db: &DStorage) -> Result { let sql = format!( "UPDATE requests SET is_deleted = true WHERE id = {}", self.id ); db.delete(&sql) } } impl Message { pub fn new( gid: &GroupId, fid: i64, is_me: bool, m_type: MessageType, content: String, is_delivery: bool, ) -> Message { let start = SystemTime::now(); let datetime = start .duration_since(UNIX_EPOCH) .map(|s| s.as_secs()) .unwrap_or(0) as i64; // safe for all life. let mut bytes = [0u8; 32]; bytes[0..8].copy_from_slice(&gid.0[0..8]); bytes[8..16].copy_from_slice(&(fid as u64).to_le_bytes()); // 8-bytes. bytes[16..24].copy_from_slice(&(datetime as u64).to_le_bytes()); // 8-bytes. let content_bytes = content.as_bytes(); if content_bytes.len() >= 8 { bytes[24..32].copy_from_slice(&content_bytes[0..8]); } else { bytes[24..(24 + content_bytes.len())].copy_from_slice(&content_bytes); } Message { id: 0, hash: EventId(bytes), is_deleted: false, fid, is_me, m_type, content, is_delivery, datetime, } } pub fn new_with_id( hash: EventId, fid: i64, is_me: bool, m_type: MessageType, content: String, is_delivery: bool, ) -> Message { let start = SystemTime::now(); let datetime = start .duration_since(UNIX_EPOCH) .map(|s| s.as_secs()) .unwrap_or(0) as i64; // safe for all life. Message { id: 0, is_deleted: false, hash, fid, is_me, m_type, content, is_delivery, datetime, } } /// here is zero-copy and unwrap is safe. checked. fn from_values(mut v: Vec, contains_deleted: bool) -> Message { let is_deleted = if contains_deleted { v.pop().unwrap().as_bool() } else { false }; Message { is_deleted, datetime: v.pop().unwrap().as_i64(), is_delivery: v.pop().unwrap().as_bool(), content: v.pop().unwrap().as_string(), m_type: MessageType::from_int(v.pop().unwrap().as_i64()), is_me: v.pop().unwrap().as_bool(), fid: v.pop().unwrap().as_i64(), hash: EventId::from_hex(v.pop().unwrap().as_str()).unwrap_or(EventId::default()), id: v.pop().unwrap().as_i64(), } } pub fn to_rpc(&self) -> RpcParam { json!([ self.id, self.hash.to_hex(), self.fid, self.is_me, self.m_type.to_int(), self.content, self.is_delivery, self.datetime, ]) } pub fn get(db: &DStorage, fid: &i64) -> Result> { let sql = format!("SELECT id, hash, fid, is_me, m_type, content, is_delivery, datetime FROM messages WHERE fid = {} and is_deleted = false ORDER BY id DESC", fid); let matrix = db.query(&sql)?; let mut messages = vec![]; for values in matrix { messages.push(Message::from_values(values, false)); } Ok(messages) } pub fn get_id(db: &DStorage, id: i64) -> Result> { let sql = format!("SELECT id, hash, fid, is_me, m_type, content, is_delivery, datetime, is_deleted FROM messages WHERE id = {}", id); let mut matrix = db.query(&sql)?; if matrix.len() > 0 { let values = matrix.pop().unwrap(); // safe unwrap() return Ok(Some(Message::from_values(values, true))); } Ok(None) } pub fn get_it(db: &DStorage, hash: &EventId) -> Result> { let sql = format!("SELECT id, hash, fid, is_me, m_type, content, is_delivery, datetime, is_deleted FROM messages WHERE hash = {}", hash.to_hex()); let mut matrix = db.query(&sql)?; if matrix.len() > 0 { let values = matrix.pop().unwrap(); // safe unwrap() return Ok(Some(Message::from_values(values, true))); } Ok(None) } pub fn insert(&mut self, db: &DStorage) -> Result<()> { let sql = format!( "INSERT INTO messages (hash, fid, is_me, m_type, content, is_delivery, datetime, is_deleted) VALUES ('{}',{},{},{},'{}',{},{},false)", self.hash.to_hex(), self.fid, if self.is_me { 1 } else { 0 }, self.m_type.to_int(), self.content, if self.is_delivery { 1 } else { 0 }, self.datetime, ); self.id = db.insert(&sql)?; Ok(()) } pub fn delivery(db: &DStorage, id: i64, is_delivery: bool) -> Result { let sql = format!( "UPDATE messages SET is_delivery={} WHERE id = {}", if is_delivery { 1 } else { 0 }, id, ); db.update(&sql) } pub fn delete(&self, db: &DStorage) -> Result { let sql = format!( "UPDATE messages SET is_deleted = true WHERE id = {}", self.id ); db.delete(&sql) } pub fn exist(db: &DStorage, hash: &EventId) -> Result { let sql = format!("SELECT id FROM messages WHERE hash = '{}'", hash.to_hex()); let matrix = db.query(&sql)?; Ok(matrix.len() > 0) } }