Browse Source

split group chat models

pull/18/head
Sun 4 years ago
parent
commit
f4c1df6342
  1. 1035
      src/apps/group_chat/models.rs
  2. 175
      src/apps/group_chat/models/consensus.rs
  3. 366
      src/apps/group_chat/models/group.rs
  4. 196
      src/apps/group_chat/models/member.rs
  5. 292
      src/apps/group_chat/models/message.rs
  6. 210
      src/apps/group_chat/models/request.rs
  7. 8
      src/migrate/group_chat.rs

1035
src/apps/group_chat/models.rs

File diff suppressed because it is too large Load Diff

175
src/apps/group_chat/models/consensus.rs

@ -0,0 +1,175 @@
use std::path::PathBuf;
use tdn::types::{group::GroupId, primitive::Result};
use tdn_storage::local::{DStorage, DsValue};
use group_chat_types::PackedEvent;
use super::{to_network_message, Member, Message};
pub(crate) enum ConsensusType {
GroupInfo,
GroupTransfer,
GroupManagerAdd,
GroupManagerDel,
GroupClose,
MemberInfo,
MemberJoin,
MemberLeave,
MessageCreate,
None,
}
impl ConsensusType {
fn to_i64(&self) -> i64 {
match self {
ConsensusType::None => 0,
ConsensusType::GroupInfo => 1,
ConsensusType::GroupTransfer => 2,
ConsensusType::GroupManagerAdd => 3,
ConsensusType::GroupManagerDel => 4,
ConsensusType::GroupClose => 5,
ConsensusType::MemberInfo => 6,
ConsensusType::MemberJoin => 7,
ConsensusType::MemberLeave => 8,
ConsensusType::MessageCreate => 9,
}
}
fn from_i64(a: i64) -> Self {
match a {
1 => ConsensusType::GroupInfo,
2 => ConsensusType::GroupTransfer,
3 => ConsensusType::GroupManagerAdd,
4 => ConsensusType::GroupManagerDel,
5 => ConsensusType::GroupClose,
6 => ConsensusType::MemberInfo,
7 => ConsensusType::MemberJoin,
8 => ConsensusType::MemberLeave,
9 => ConsensusType::MessageCreate,
_ => ConsensusType::None,
}
}
}
/// Group Chat Consensus.
pub(crate) struct Consensus {
/// db auto-increment id.
id: i64,
/// group's db id.
fid: i64,
/// group's height.
height: i64,
/// consensus type.
ctype: ConsensusType,
/// consensus point value db id.
cid: i64,
}
impl Consensus {
fn from_values(mut v: Vec<DsValue>) -> Consensus {
Consensus {
cid: v.pop().unwrap().as_i64(),
ctype: ConsensusType::from_i64(v.pop().unwrap().as_i64()),
height: v.pop().unwrap().as_i64(),
fid: v.pop().unwrap().as_i64(),
id: v.pop().unwrap().as_i64(),
}
}
pub async fn pack(
db: &DStorage,
base: &PathBuf,
gcd: &GroupId,
fid: &i64,
from: &i64,
to: &i64,
) -> Result<Vec<PackedEvent>> {
let matrix = db.query(&format!(
"SELECT id, fid, height, ctype, cid FROM consensus WHERE fid = {} AND height BETWEEN {} AND {}", fid, from, to))?;
let mut packed = vec![];
let mut consensuses = vec![];
for res in matrix {
consensuses.push(Consensus::from_values(res));
}
for consensus in consensuses {
match consensus.ctype {
ConsensusType::GroupInfo => {
//
}
ConsensusType::GroupTransfer => {
//
}
ConsensusType::GroupManagerAdd => {
//
}
ConsensusType::GroupManagerDel => {
//
}
ConsensusType::GroupClose => {
//
}
ConsensusType::MemberInfo => {
//
}
ConsensusType::MemberJoin => {
let m = Member::get(db, &consensus.cid)?;
// TODO load member avatar.
let mavatar = vec![];
packed.push(PackedEvent::MemberJoin(
m.m_id, m.m_addr, m.m_name, mavatar, m.datetime,
))
}
ConsensusType::MemberLeave => {
//
}
ConsensusType::MessageCreate => {
let m = Message::get(db, &consensus.cid)?;
let datetime = m.datetime;
let mem = Member::get(db, &m.mid)?;
let (nmsg, _) = to_network_message(base, gcd, m.m_type, &m.content).await?;
packed.push(PackedEvent::MessageCreate(mem.m_id, nmsg, datetime))
}
ConsensusType::None => {
// None
}
}
}
Ok(packed)
}
pub async fn insert(
db: &DStorage,
fid: &i64,
height: &i64,
cid: &i64,
ctype: &ConsensusType,
) -> Result<()> {
let unique_check = db.query(&format!(
"SELECT id from consensus WHERE fid = {} AND height = {}",
fid, height
));
if let Ok(mut rec) = unique_check {
let id = rec.pop().unwrap().pop().unwrap().as_i64();
let _ = db.query(&format!(
"UPDATE consensus SET ctype = {}, cid = {} WHERE id = {}",
ctype.to_i64(),
cid,
id
))?;
} else {
let _ = db.query(&format!(
"INSERT INTO consensus ( fid, height, ctype, cid ) VALUES ( {}, {}, {}, {} )",
fid,
height,
ctype.to_i64(),
cid
))?;
}
Ok(())
}
}

366
src/apps/group_chat/models/group.rs

@ -0,0 +1,366 @@
use rand::Rng;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use tdn::types::{
group::GroupId,
primitive::{PeerAddr, Result},
rpc::{json, RpcParam},
};
use tdn_storage::local::{DStorage, DsValue};
use group_chat_types::{GroupInfo, GroupType};
use crate::session::{Session, SessionType};
use crate::storage::write_avatar_sync;
use super::GroupChatKey;
/// Group Chat Model.
pub(crate) struct GroupChat {
/// db auto-increment id.
pub id: i64,
/// consensus height.
pub height: i64,
/// group chat owner.
pub owner: GroupId,
/// group chat id.
pub g_id: GroupId,
/// group chat type.
pub g_type: GroupType,
/// group chat server addresse.
pub g_addr: PeerAddr,
/// group chat name.
pub g_name: String,
/// group chat simple intro.
g_bio: String,
/// group chat is created ok.
is_ok: bool,
/// group chat is closed.
is_closed: bool,
/// group chat need manager agree.
is_need_agree: bool,
/// group chat encrypted-key.
pub key: GroupChatKey,
/// group chat created time.
pub datetime: i64,
/// is remote.
pub is_remote: bool,
}
impl GroupChat {
pub fn new(
owner: GroupId,
g_type: GroupType,
g_addr: PeerAddr,
g_name: String,
g_bio: String,
is_need_agree: bool,
is_ok: bool,
is_remote: bool,
) -> Self {
let g_id = GroupId(rand::thread_rng().gen::<[u8; 32]>());
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 key = GroupChatKey(vec![]);
Self {
owner,
g_id,
g_type,
g_addr,
g_name,
g_bio,
is_need_agree,
key,
datetime,
is_ok,
is_remote,
id: 0,
height: 0,
is_closed: false,
}
}
fn new_from(
g_id: GroupId,
height: i64,
owner: GroupId,
g_type: GroupType,
g_addr: PeerAddr,
g_name: String,
g_bio: String,
is_need_agree: bool,
key: GroupChatKey,
is_remote: bool,
) -> Self {
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 {
owner,
g_id,
g_type,
g_addr,
g_name,
g_bio,
is_need_agree,
key,
datetime,
id: 0,
height,
is_remote,
is_ok: true,
is_closed: false,
}
}
pub fn from_info(
key: GroupChatKey,
info: GroupInfo,
height: i64,
addr: PeerAddr,
base: &PathBuf,
mgid: &GroupId,
is_remote: bool,
) -> Result<Self> {
match info {
GroupInfo::Common(owner, _, _, g_id, g_type, agree, name, g_bio, avatar) => {
write_avatar_sync(base, &mgid, &g_id, avatar)?;
Ok(Self::new_from(
g_id, height, owner, g_type, addr, name, g_bio, agree, key, is_remote,
))
}
GroupInfo::Encrypted(owner, _, _, g_id, agree, _hash, _name, _bio, avatar) => {
// TODO decrypted.
let g_type = GroupType::Encrypted;
let name = "".to_owned();
let bio = "".to_owned();
write_avatar_sync(base, &mgid, &g_id, avatar)?;
Ok(Self::new_from(
g_id, height, owner, g_type, addr, name, bio, agree, key, is_remote,
))
}
}
}
pub fn to_session(&self) -> Session {
Session::new(
self.id,
self.g_id,
self.g_addr,
SessionType::Group,
self.g_name.clone(),
self.datetime,
)
}
pub fn to_group_info(self, name: String, avatar: Vec<u8>, owner_avatar: Vec<u8>) -> GroupInfo {
match self.g_type {
GroupType::Private | GroupType::Open => GroupInfo::Common(
self.owner,
name,
owner_avatar,
self.g_id,
self.g_type,
self.is_need_agree,
self.g_name,
self.g_bio,
avatar,
),
GroupType::Encrypted => GroupInfo::Common(
// TODO encrypted
self.owner,
name,
owner_avatar,
self.g_id,
self.g_type,
self.is_need_agree,
self.g_name,
self.g_bio,
avatar,
),
}
}
pub fn to_rpc(&self) -> RpcParam {
json!([
self.id,
self.owner.to_hex(),
self.g_id.to_hex(),
self.g_type.to_u32(),
self.g_addr.to_hex(),
self.g_name,
self.g_bio,
self.is_ok,
self.is_closed,
self.is_need_agree,
self.is_remote,
])
}
fn from_values(mut v: Vec<DsValue>) -> Self {
Self {
is_remote: v.pop().unwrap().as_bool(),
datetime: v.pop().unwrap().as_i64(),
key: GroupChatKey::from_hex(v.pop().unwrap().as_string())
.unwrap_or(GroupChatKey::new(vec![])),
is_closed: v.pop().unwrap().as_bool(),
is_need_agree: v.pop().unwrap().as_bool(),
is_ok: v.pop().unwrap().as_bool(),
g_bio: v.pop().unwrap().as_string(),
g_name: v.pop().unwrap().as_string(),
g_addr: PeerAddr::from_hex(v.pop().unwrap().as_string()).unwrap_or(Default::default()),
g_type: GroupType::from_u32(v.pop().unwrap().as_i64() as u32),
g_id: GroupId::from_hex(v.pop().unwrap().as_string()).unwrap_or(Default::default()),
owner: GroupId::from_hex(v.pop().unwrap().as_string()).unwrap_or(Default::default()),
height: v.pop().unwrap().as_i64(),
id: v.pop().unwrap().as_i64(),
}
}
/// use in rpc when load account friends.
pub fn all(db: &DStorage) -> Result<Vec<GroupChat>> {
let matrix = db.query("SELECT id, height, owner, gcd, gtype, addr, name, bio, is_ok, is_need_agree, is_closed, key, datetime, is_remote FROM groups WHERE is_deleted = false")?;
let mut groups = vec![];
for values in matrix {
groups.push(GroupChat::from_values(values));
}
Ok(groups)
}
/// use in rpc when load account groups.
pub fn all_ok(db: &DStorage) -> Result<Vec<GroupChat>> {
let matrix = db.query("SELECT id, height, owner, gcd, gtype, addr, name, bio, is_ok, is_need_agree, is_closed, key, datetime, is_remote FROM groups WHERE is_closed = false")?;
let mut groups = vec![];
for values in matrix {
groups.push(GroupChat::from_values(values));
}
Ok(groups)
}
/// list all local group chat as running layer.
pub fn all_local(db: &DStorage, owner: &GroupId) -> Result<Vec<(i64, GroupId, i64)>> {
let matrix = db.query(&format!("SELECT id, gcd, height FROM groups WHERE owner = '{}' and is_remote = false and is_closed = false", owner.to_hex()))?;
let mut groups = vec![];
for mut values in matrix {
let height = values.pop().unwrap().as_i64();
let gcd =
GroupId::from_hex(values.pop().unwrap().as_string()).unwrap_or(Default::default());
let id = values.pop().unwrap().as_i64();
groups.push((id, gcd, height));
}
Ok(groups)
}
pub fn get(db: &DStorage, gid: &GroupId) -> Result<Option<GroupChat>> {
let sql = format!("SELECT id, height, owner, gcd, gtype, addr, name, bio, is_ok, is_need_agree, is_closed, key, datetime, is_remote FROM groups WHERE gcd = '{}' 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(GroupChat::from_values(values)));
}
Ok(None)
}
pub fn get_id(db: &DStorage, id: &i64) -> Result<Option<GroupChat>> {
let sql = format!("SELECT id, height, owner, gcd, gtype, addr, name, bio, is_ok, is_need_agree, is_closed, key, datetime, is_remote FROM groups WHERE id = {} AND is_deleted = false", id);
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Some(GroupChat::from_values(values)));
}
Ok(None)
}
pub fn insert(&mut self, db: &DStorage) -> Result<()> {
let mut unique_check = db.query(&format!(
"SELECT id from groups WHERE gcd = '{}'",
self.g_id.to_hex()
))?;
if unique_check.len() > 0 {
let id = unique_check.pop().unwrap().pop().unwrap().as_i64();
self.id = id;
let sql = format!("UPDATE groups SET height = {}, owner = '{}', gtype = {}, addr='{}', name = '{}', bio = '{}', is_ok = {}, is_need_agree = {}, is_closed = {}, key = '{}', datetime = {}, is_remote = {}, is_deleted = false WHERE id = {}",
self.height,
self.owner.to_hex(),
self.g_type.to_u32(),
self.g_addr.to_hex(),
self.g_name,
self.g_bio,
self.is_ok,
self.is_need_agree,
self.is_closed,
self.key.to_hex(),
self.datetime,
self.is_remote,
self.id
);
db.update(&sql)?;
} else {
let sql = format!("INSERT INTO groups (height, owner, gcd, gtype, addr, name, bio, is_ok, is_need_agree, is_closed, key, datetime, is_remote, is_deleted) VALUES ({}, '{}', '{}', {}, '{}', '{}', '{}', {}, {}, {}, '{}', {}, {}, false)",
self.height,
self.owner.to_hex(),
self.g_id.to_hex(),
self.g_type.to_u32(),
self.g_addr.to_hex(),
self.g_name,
self.g_bio,
self.is_ok,
self.is_need_agree,
self.is_closed,
self.key.to_hex(),
self.datetime,
self.is_remote,
);
let id = db.insert(&sql)?;
self.id = id;
}
Ok(())
}
pub fn ok(&mut self, db: &DStorage) -> Result<usize> {
self.is_ok = true;
let sql = format!("UPDATE groups SET is_ok=1 WHERE id = {}", self.id);
db.update(&sql)
}
pub fn add_height(db: &DStorage, id: i64, height: i64) -> Result<usize> {
let sql = format!("UPDATE groups SET height={} WHERE id = {}", height, id,);
db.update(&sql)
}
pub fn close(db: &DStorage, id: &i64) -> Result<usize> {
let sql = format!("UPDATE groups SET is_closed = 1 WHERE id = {}", id);
db.update(&sql)
}
/// return if is closed
pub fn delete(db: &DStorage, id: &i64) -> Result<bool> {
let sql = format!("SELECT is_closed FROM groups WHERE id = {}", id);
let mut matrix = db.query(&sql)?;
let is_closed = if let Some(mut value) = matrix.pop() {
value.pop().unwrap().as_bool() // safe unwrap
} else {
false
};
let sql = format!(
"UPDATE groups SET is_closed = 1, is_deleted = 1 WHERE id = {}",
id
);
db.update(&sql)?;
Ok(is_closed)
}
}

196
src/apps/group_chat/models/member.rs

@ -0,0 +1,196 @@
use rand::Rng;
use tdn::types::{
group::GroupId,
primitive::{PeerAddr, Result},
rpc::{json, RpcParam},
};
use tdn_storage::local::{DStorage, DsValue};
/// Group Member Model.
pub(crate) struct Member {
/// db auto-increment id.
id: i64,
/// group's db id.
fid: i64,
/// member's Did(GroupId)
pub m_id: GroupId,
/// member's addresse.
pub m_addr: PeerAddr,
/// member's name.
pub m_name: String,
/// is group chat manager.
is_manager: bool,
/// is member is block by me.
is_block: bool,
/// member's joined time.
pub datetime: i64,
/// member is leave or delete.
is_deleted: bool,
}
impl Member {
pub fn new(
fid: i64,
m_id: GroupId,
m_addr: PeerAddr,
m_name: String,
is_manager: bool,
datetime: i64,
) -> Self {
Self {
fid,
m_id,
m_addr,
m_name,
is_manager,
datetime,
id: 0,
is_block: false,
is_deleted: false,
}
}
pub fn to_rpc(&self) -> RpcParam {
json!([
self.id,
self.fid,
self.m_id.to_hex(),
self.m_addr.to_hex(),
self.m_name,
self.is_manager,
self.is_block,
])
}
fn from_values(mut v: Vec<DsValue>, contains_deleted: bool) -> Self {
let is_deleted = if contains_deleted {
v.pop().unwrap().as_bool()
} else {
false
};
Self {
is_deleted,
datetime: v.pop().unwrap().as_i64(),
is_block: v.pop().unwrap().as_bool(),
is_manager: v.pop().unwrap().as_bool(),
m_name: v.pop().unwrap().as_string(),
m_addr: PeerAddr::from_hex(v.pop().unwrap().as_string()).unwrap_or(Default::default()),
m_id: GroupId::from_hex(v.pop().unwrap().as_string()).unwrap_or(Default::default()),
fid: v.pop().unwrap().as_i64(),
id: v.pop().unwrap().as_i64(),
}
}
pub fn all(db: &DStorage, fid: &i64) -> Result<Vec<Member>> {
let matrix = db.query(&format!(
"SELECT id, fid, mid, addr, name, is_manager, is_block, datetime FROM members WHERE is_deleted = false AND fid = {}", fid))?;
let mut groups = vec![];
for values in matrix {
groups.push(Member::from_values(values, false));
}
Ok(groups)
}
pub fn insert(&mut self, db: &DStorage) -> Result<()> {
let mut unique_check = db.query(&format!(
"SELECT id from members WHERE fid = {} AND mid = '{}'",
self.fid,
self.m_id.to_hex()
))?;
if unique_check.len() > 0 {
let id = unique_check.pop().unwrap().pop().unwrap().as_i64();
self.id = id;
let sql = format!("UPDATE members SET addr='{}', name = '{}', is_manager = {}, datetime = {}, is_delete = false WHERE id = {}",
self.m_addr.to_hex(),
self.m_name,
self.is_manager,
self.datetime,
self.id,
);
db.update(&sql)?;
} else {
let sql = format!("INSERT INTO members (fid, mid, addr, name, is_manager, is_block, datetime, is_deleted) VALUES ({}, '{}', '{}', '{}', {}, {}, {}, false)",
self.fid,
self.m_id.to_hex(),
self.m_addr.to_hex(),
self.m_name,
self.is_manager,
self.is_block,
self.datetime,
);
let id = db.insert(&sql)?;
self.id = id;
}
Ok(())
}
pub fn get(db: &DStorage, id: &i64) -> Result<Member> {
let mut matrix = db.query(&format!(
"SELECT id, fid, m_id, m_addr, m_name, is_manager, is_block, datetime FROM members WHERE id = {}",
id,
))?;
if matrix.len() > 0 {
Ok(Member::from_values(matrix.pop().unwrap(), false)) // safe unwrap.
} else {
Err(anyhow!("missing member"))
}
}
pub fn get_id(db: &DStorage, fid: &i64, mid: &GroupId) -> Result<i64> {
let mut matrix = db.query(&format!(
"SELECT id FROM members WHERE fid = {} AND mid = '{}' AND is_delete = false",
fid,
mid.to_hex()
))?;
if matrix.len() > 0 {
Ok(matrix.pop().unwrap().pop().unwrap().as_i64()) // safe unwrap.
} else {
Err(anyhow!("missing member"))
}
}
/// get member not deleted, not blocked.
pub fn get_ok(db: &DStorage, fid: &i64, mid: &GroupId) -> Result<i64> {
let mut matrix = db.query(&format!(
"SELECT id FROM members WHERE is_deleted = false AND is_block = false AND fid = {} AND mid = '{}'",
fid,
mid.to_hex()
))?;
if matrix.len() > 0 {
Ok(matrix.pop().unwrap().pop().unwrap().as_i64()) // safe unwrap.
} else {
Err(anyhow!("missing member"))
}
}
pub fn addr_update(db: &DStorage, fid: &i64, mid: &GroupId, addr: &PeerAddr) -> Result<usize> {
let sql = format!(
"UPDATE members SET addr='{}' WHERE fid = {} AND mid = '{}'",
addr.to_hex(),
fid,
mid.to_hex(),
);
db.update(&sql)
}
pub fn update(db: &DStorage, id: &i64, addr: &PeerAddr, name: &str) -> Result<usize> {
let sql = format!(
"UPDATE members SET addr='{}', name='{}' WHERE id = {}",
addr.to_hex(),
name,
id,
);
db.update(&sql)
}
pub fn leave(db: &DStorage, id: &i64) -> Result<usize> {
let sql = format!("UPDATE members SET is_deleted = 1 WHERE id = {}", id);
db.update(&sql)
}
pub fn block(db: &DStorage, id: &i64, block: bool) -> Result<usize> {
let sql = format!("UPDATE members SET is_block={} WHERE id = {}", block, id,);
db.update(&sql)
}
}

292
src/apps/group_chat/models/message.rs

@ -0,0 +1,292 @@
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))
}

210
src/apps/group_chat/models/request.rs

@ -0,0 +1,210 @@
use rand::Rng;
use std::time::{SystemTime, UNIX_EPOCH};
use tdn::types::{
group::GroupId,
primitive::{PeerAddr, Result},
rpc::{json, RpcParam},
};
use tdn_storage::local::{DStorage, DsValue};
use super::GroupChatKey;
/// Group Join Request model. include my requests and other requests.
/// When fid is 0, it's my requests.
pub(crate) struct Request {
id: i64,
fid: i64,
rid: i64,
pub gid: GroupId,
pub addr: PeerAddr,
pub name: String,
key: GroupChatKey,
remark: String,
is_ok: bool,
is_over: bool,
datetime: i64,
is_deleted: bool,
}
impl Request {
pub fn new_by_remote(
fid: i64,
rid: i64,
gid: GroupId,
addr: PeerAddr,
name: String,
remark: String,
datetime: i64,
) -> Self {
Self {
fid,
rid,
gid,
addr,
name,
remark,
datetime,
key: GroupChatKey(vec![]),
is_ok: false,
is_over: false,
is_deleted: false,
id: 0,
}
}
pub fn new_by_me(
gid: GroupId,
addr: PeerAddr,
name: String,
remark: String,
key: GroupChatKey,
) -> Self {
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 {
gid,
addr,
name,
remark,
datetime,
key,
is_ok: false,
is_over: false,
is_deleted: false,
fid: 0,
rid: 0,
id: 0,
}
}
pub fn to_rpc(&self) -> RpcParam {
json!([
self.id,
self.fid,
self.rid,
self.gid.to_hex(),
self.addr.to_hex(),
self.name,
self.remark,
self.is_ok,
self.is_over,
self.datetime,
])
}
fn from_values(mut v: Vec<DsValue>, contains_deleted: bool) -> Self {
let is_deleted = if contains_deleted {
v.pop().unwrap().as_bool()
} else {
false
};
Self {
is_deleted,
key: GroupChatKey(vec![]),
datetime: v.pop().unwrap().as_i64(),
is_over: v.pop().unwrap().as_bool(),
is_ok: 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_string()).unwrap_or(Default::default()),
gid: GroupId::from_hex(v.pop().unwrap().as_string()).unwrap_or(Default::default()),
rid: v.pop().unwrap().as_i64(),
fid: v.pop().unwrap().as_i64(),
id: v.pop().unwrap().as_i64(),
}
}
pub fn list(db: &DStorage, is_all: bool) -> Result<Vec<Request>> {
let sql = if is_all {
format!("SELECT id, fid, rid, gid, addr, name, remark, is_ok, is_over, datetime FROM requests WHERE is_deleted = false")
} else {
format!("SELECT id, fid, rid, gid, addr, name, remark, is_ok, is_over, datetime FROM requests WHERE is_deleted = false AND is_over = 0")
};
let matrix = db.query(&sql)?;
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 (fid, rid, gid, addr, name, remark, key, is_ok, is_over, datetime, is_deleted) VALUES ({}, {}, '{}', '{}', '{}', '{}', '{}', {}, {}, {}, false)",
self.fid,
self.rid,
self.gid.to_hex(),
self.addr.to_hex(),
self.name,
self.remark,
self.key.to_hex(),
self.is_ok,
self.is_over,
self.datetime,
);
let id = db.insert(&sql)?;
self.id = id;
Ok(())
}
pub fn exist(db: &DStorage, gcd: &GroupId) -> Result<bool> {
let matrix = db.query(&format!(
"SELECT id from requests WHERE gid = '{}' AND is_over = 0",
gcd.to_hex(),
))?;
if matrix.len() == 0 {
Ok(false)
} else {
Ok(true)
}
}
pub fn over_rid(db: &DStorage, gcd: &GroupId, rid: &i64, is_ok: bool) -> Result<i64> {
let mut matrix = db.query(&format!(
"SELECT id from requests WHERE gid = '{}' AND rid = {} AND is_over = 0",
gcd.to_hex(),
rid
))?;
if matrix.len() == 0 {
return Err(anyhow!("request is missing"));
}
let id = matrix.pop().unwrap().pop().unwrap().as_i64(); // safe.
let sql = format!(
"UPDATE requests SET is_ok={}, is_over=1 WHERE id = {}",
is_ok, id,
);
db.update(&sql)?;
Ok(id)
}
pub fn over(db: &DStorage, gcd: &GroupId, is_ok: bool) -> Result<(i64, GroupChatKey)> {
let matrix = db.query(&format!(
"SELECT id, key from requests WHERE gid = '{}' AND is_over = 0 ORDER BY id",
gcd.to_hex()
))?;
let mut requests = vec![];
for mut values in matrix {
let id = values.pop().unwrap().as_i64();
let key = GroupChatKey::from_hex(values.pop().unwrap().as_string())
.unwrap_or(GroupChatKey::new(vec![]));
requests.push((id, key));
}
let sql = format!(
"UPDATE requests SET is_ok={}, is_over=1 WHERE gid = '{}' AND is_over = 0",
is_ok,
gcd.to_hex(),
);
db.update(&sql)?;
if requests.len() > 0 {
Ok(requests.pop().unwrap()) // safe.
} else {
Err(anyhow!("no requests"))
}
}
}

8
src/migrate/group_chat.rs

@ -1,5 +1,5 @@
#[rustfmt::skip] #[rustfmt::skip]
pub(super) const GROUP_CHAT_VERSIONS: [&str; 4] = [ pub(super) const GROUP_CHAT_VERSIONS: [&str; 5] = [
"CREATE TABLE IF NOT EXISTS groups( "CREATE TABLE IF NOT EXISTS groups(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
height INTEGER NOT NULL, height INTEGER NOT NULL,
@ -50,4 +50,10 @@ pub(super) const GROUP_CHAT_VERSIONS: [&str; 4] = [
is_delivery INTEGER NOT NULL, is_delivery INTEGER NOT NULL,
datetime INTEGER NOT NULL, datetime INTEGER NOT NULL,
is_deleted INTEGER NOT NULL);", is_deleted INTEGER NOT NULL);",
"CREATE TABLE IF NOT EXISTS consensus(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
fid INTEGER NOT NULL,
height INTEGER NOT NULL,
ctype INTEGER NOT NULL,
cid INTEGER NOT NULL);",
]; ];

Loading…
Cancel
Save