use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; use tdn::types::{ group::{EventId, GroupId}, primitive::{PeerId, Result}, rpc::{json, RpcParam}, }; use tdn_storage::local::{DStorage, DsValue}; use chat_types::{MessageType, NetworkMessage}; 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) fn handle_nmsg( nmsg: NetworkMessage, is_me: bool, gid: GroupId, base: &PathBuf, db: &DStorage, fid: i64, hash: EventId, ) -> Result<(Message, String)> { // handle event. let (m_type, raw) = match nmsg { 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::Invite(content) => (MessageType::Invite, content), }; let scontent = match m_type { MessageType::String => { format!("{}:{}", m_type.to_int(), raw) } _ => format!("{}:", m_type.to_int()), }; let mut msg = Message::new_with_id(hash, fid, is_me, m_type, raw, true); msg.insert(db)?; Ok((msg, scontent)) } 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 Err(anyhow!("message is invalid")); } let cname = v[0].to_owned(); let cgid = GroupId::from_hex(v[1])?; let caddr = PeerId::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::Invite => Ok(NetworkMessage::Invite(model.content)), MessageType::Emoji => Ok(NetworkMessage::Emoji), MessageType::Phone => Ok(NetworkMessage::Phone), MessageType::Video => Ok(NetworkMessage::Video), } } 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 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, self.is_me, self.m_type.to_int(), self.content, self.is_delivery, 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 = {}", is_delivery, 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) } }