Encrypted peer-to-peer IM for data security. Own data, own privacy. (Rust+Flutter)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

292 lines
8.9 KiB

use rand::Rng;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use tdn::types::{
group::GroupId,
primitive::Result,
rpc::{json, RpcParam},
};
use tdn_storage::local::{DStorage, DsValue};
use group_chat_types::NetworkMessage;
use crate::apps::chat::{Friend, MessageType};
use crate::storage::{
chat_db, group_chat_db, read_avatar, read_file, read_record, write_avatar_sync,
write_file_sync, write_image_sync, write_record_sync,
};
use super::Member;
/// Group Chat Message Model.
pub(crate) struct Message {
/// db auto-increment id.
id: i64,
/// group message consensus height.
height: i64,
/// group's db id.
fid: i64,
/// member's db id.
pub mid: i64,
/// message is mine.
is_me: bool,
/// message type.
pub m_type: MessageType,
/// message content.
pub content: String,
/// message is delivery.
is_delivery: bool,
/// message created time.
pub datetime: i64,
}
impl Message {
pub(crate) fn new_with_time(
height: i64,
fid: i64,
mid: i64,
is_me: bool,
m_type: MessageType,
content: String,
datetime: i64,
) -> Message {
Self {
fid,
mid,
m_type,
content,
datetime,
height,
is_me,
is_delivery: true,
id: 0,
}
}
pub(crate) fn new(
height: i64,
fid: i64,
mid: i64,
is_me: bool,
m_type: MessageType,
content: String,
) -> 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.
Self::new_with_time(height, fid, mid, is_me, m_type, content, datetime)
}
/// here is zero-copy and unwrap is safe. checked.
fn from_values(mut v: Vec<DsValue>) -> Message {
Message {
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(),
mid: v.pop().unwrap().as_i64(),
fid: v.pop().unwrap().as_i64(),
height: v.pop().unwrap().as_i64(),
id: v.pop().unwrap().as_i64(),
}
}
pub fn to_rpc(&self) -> RpcParam {
json!([
self.id,
self.height,
self.fid,
self.mid,
self.is_me,
self.m_type.to_int(),
self.content,
self.is_delivery,
self.datetime,
])
}
pub fn get(db: &DStorage, id: &i64) -> Result<Message> {
let mut matrix = db.query(&format!("SELECT id, height, fid, mid, is_me, m_type, content, is_delivery, datetime FROM messages WHERE id = {}", id))?;
if matrix.len() > 0 {
Ok(Message::from_values(matrix.pop().unwrap())) // safe unwrap.
} else {
Err(anyhow!("missing member"))
}
}
pub fn all(db: &DStorage, fid: &i64) -> Result<Vec<Message>> {
let matrix = db.query(&format!("SELECT id, height, fid, mid, is_me, m_type, content, is_delivery, datetime FROM messages WHERE is_deleted = false AND fid = {}", fid))?;
let mut groups = vec![];
for values in matrix {
groups.push(Message::from_values(values));
}
Ok(groups)
}
pub fn insert(&mut self, db: &DStorage) -> Result<()> {
let mut unique_check = db.query(&format!(
"SELECT id from messages WHERE fid = {} AND height = {}",
self.fid, self.height
))?;
if unique_check.len() > 0 {
let id = unique_check.pop().unwrap().pop().unwrap().as_i64();
self.id = id;
} else {
let sql = format!("INSERT INTO messages (height, fid, mid, is_me, m_type, content, is_delivery, datetime, is_deleted) VALUES ({}, {}, {}, {}, {}, '{}', {}, {}, false)",
self.height,
self.fid,
self.mid,
self.is_me,
self.m_type.to_int(),
self.content,
self.is_delivery,
self.datetime,
);
let id = db.insert(&sql)?;
self.id = id;
}
Ok(())
}
}
pub(crate) async fn to_network_message(
base: &PathBuf,
gid: &GroupId,
mtype: MessageType,
content: &str,
) -> Result<(NetworkMessage, i64)> {
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 nmsg = match mtype {
MessageType::String => NetworkMessage::String(content.to_owned()),
MessageType::Image => {
let bytes = read_file(&PathBuf::from(content)).await?;
NetworkMessage::Image(bytes)
}
MessageType::File => {
let file_path = PathBuf::from(content);
let bytes = read_file(&file_path).await?;
let old_name = file_path
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_owned();
NetworkMessage::File(old_name, bytes)
}
MessageType::Contact => {
let cid: i64 = content.parse()?;
let db = chat_db(base, gid)?;
let contact = Friend::get_id(&db, cid)?.ok_or(anyhow!("contact missind"))?;
drop(db);
let avatar_bytes = read_avatar(base, &gid, &contact.gid).await?;
NetworkMessage::Contact(contact.name, contact.gid, contact.addr, avatar_bytes)
}
MessageType::Record => {
let (bytes, time) = if let Some(i) = content.find('-') {
let time = content[0..i].parse().unwrap_or(0);
let bytes = read_record(base, &gid, &content[i + 1..]).await?;
(bytes, time)
} else {
(vec![], 0)
};
NetworkMessage::Record(bytes, time)
}
MessageType::Emoji => {
// TODO
NetworkMessage::Emoji
}
MessageType::Phone => {
// TODO
NetworkMessage::Phone
}
MessageType::Video => {
// TODO
NetworkMessage::Video
}
MessageType::Invite => NetworkMessage::Invite(content.to_owned()),
};
Ok((nmsg, datetime))
}
pub(crate) fn from_network_message(
height: i64,
gdid: i64,
mid: GroupId,
mgid: &GroupId,
msg: NetworkMessage,
datetime: i64,
base: &PathBuf,
) -> Result<(Message, String)> {
let db = group_chat_db(base, mgid)?;
let mdid = Member::get_ok(&db, &gdid, &mid)?;
let is_me = &mid == mgid;
// handle event.
let (m_type, raw) = match msg {
NetworkMessage::String(content) => (MessageType::String, content),
NetworkMessage::Image(bytes) => {
let image_name = write_image_sync(base, mgid, bytes)?;
(MessageType::Image, image_name)
}
NetworkMessage::File(old_name, bytes) => {
let filename = write_file_sync(base, mgid, &old_name, bytes)?;
(MessageType::File, filename)
}
NetworkMessage::Contact(name, rgid, addr, avatar_bytes) => {
write_avatar_sync(base, mgid, &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, mgid, gdid, 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),
NetworkMessage::None => {
return Ok((
Message::new(
height,
gdid,
mdid,
is_me,
MessageType::String,
"".to_owned(),
),
"".to_owned(),
));
}
};
let scontent = match m_type {
MessageType::String => {
format!("{}:{}", m_type.to_int(), raw)
}
_ => format!("{}:", m_type.to_int()),
};
let mut msg = Message::new_with_time(height, gdid, mdid, is_me, m_type, raw, datetime);
msg.insert(&db)?;
Ok((msg, scontent))
}