refactor: split database into multiple files, more error handling, cleaner code
This commit is contained in:
parent
4b191a9311
commit
8f67c01efd
17 changed files with 1573 additions and 1630 deletions
120
src/database/account_data.rs
Normal file
120
src/database/account_data.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
use crate::Result;
|
||||
use ruma_events::{collections::only::Event as EduEvent, EventJson};
|
||||
use ruma_identifiers::{RoomId, UserId};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct AccountData {
|
||||
pub(super) roomuserdataid_accountdata: sled::Tree, // RoomUserDataId = Room + User + Count + Type
|
||||
}
|
||||
|
||||
impl AccountData {
|
||||
/// Places one event in the account data of the user and removes the previous entry.
|
||||
pub fn update(
|
||||
&self,
|
||||
room_id: Option<&RoomId>,
|
||||
user_id: &UserId,
|
||||
event: EduEvent,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
let mut prefix = room_id
|
||||
.map(|r| r.to_string())
|
||||
.unwrap_or_default()
|
||||
.as_bytes()
|
||||
.to_vec();
|
||||
prefix.push(0xff);
|
||||
prefix.extend_from_slice(&user_id.to_string().as_bytes());
|
||||
prefix.push(0xff);
|
||||
|
||||
// Remove old entry
|
||||
if let Some(old) = self
|
||||
.roomuserdataid_accountdata
|
||||
.scan_prefix(&prefix)
|
||||
.keys()
|
||||
.rev()
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(|key| key.starts_with(&prefix))
|
||||
.filter(|key| {
|
||||
key.split(|&b| b == 0xff)
|
||||
.nth(1)
|
||||
.filter(|&user| user == user_id.to_string().as_bytes())
|
||||
.is_some()
|
||||
})
|
||||
.next()
|
||||
{
|
||||
// This is the old room_latest
|
||||
self.roomuserdataid_accountdata.remove(old)?;
|
||||
println!("removed old account data");
|
||||
}
|
||||
|
||||
let mut key = prefix;
|
||||
key.extend_from_slice(&globals.next_count()?.to_be_bytes());
|
||||
key.push(0xff);
|
||||
let json = serde_json::to_value(&event)?;
|
||||
key.extend_from_slice(json["type"].as_str().unwrap().as_bytes());
|
||||
|
||||
self.roomuserdataid_accountdata
|
||||
.insert(key, &*json.to_string())
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: Optimize
|
||||
/// Searches the account data for a specific kind.
|
||||
pub fn get(
|
||||
&self,
|
||||
room_id: Option<&RoomId>,
|
||||
user_id: &UserId,
|
||||
kind: &str,
|
||||
) -> Result<Option<EventJson<EduEvent>>> {
|
||||
Ok(self.all(room_id, user_id)?.remove(kind))
|
||||
}
|
||||
|
||||
/// Returns all changes to the account data that happened after `since`.
|
||||
pub fn changes_since(
|
||||
&self,
|
||||
room_id: Option<&RoomId>,
|
||||
user_id: &UserId,
|
||||
since: u64,
|
||||
) -> Result<HashMap<String, EventJson<EduEvent>>> {
|
||||
let mut userdata = HashMap::new();
|
||||
|
||||
let mut prefix = room_id
|
||||
.map(|r| r.to_string())
|
||||
.unwrap_or_default()
|
||||
.as_bytes()
|
||||
.to_vec();
|
||||
prefix.push(0xff);
|
||||
prefix.extend_from_slice(&user_id.to_string().as_bytes());
|
||||
prefix.push(0xff);
|
||||
|
||||
// Skip the data that's exactly at since, because we sent that last time
|
||||
let mut first_possible = prefix.clone();
|
||||
first_possible.extend_from_slice(&(since + 1).to_be_bytes());
|
||||
|
||||
for json in self
|
||||
.roomuserdataid_accountdata
|
||||
.range(&*first_possible..)
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(_, v)| serde_json::from_slice::<serde_json::Value>(&v).unwrap())
|
||||
{
|
||||
userdata.insert(
|
||||
json["type"].as_str().unwrap().to_owned(),
|
||||
serde_json::from_value::<EventJson<EduEvent>>(json)
|
||||
.expect("userdata in db is valid"),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(userdata)
|
||||
}
|
||||
|
||||
/// Returns all account data.
|
||||
pub fn all(
|
||||
&self,
|
||||
room_id: Option<&RoomId>,
|
||||
user_id: &UserId,
|
||||
) -> Result<HashMap<String, EventJson<EduEvent>>> {
|
||||
self.changes_since(room_id, user_id, 0)
|
||||
}
|
||||
}
|
61
src/database/globals.rs
Normal file
61
src/database/globals.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use crate::{utils, Result};
|
||||
|
||||
pub const COUNTER: &str = "c";
|
||||
|
||||
pub struct Globals {
|
||||
pub(super) globals: sled::Tree,
|
||||
hostname: String,
|
||||
keypair: ruma_signatures::Ed25519KeyPair,
|
||||
reqwest_client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl Globals {
|
||||
pub fn load(globals: sled::Tree, hostname: String) -> Self {
|
||||
let keypair = ruma_signatures::Ed25519KeyPair::new(
|
||||
&*globals
|
||||
.update_and_fetch("keypair", utils::generate_keypair)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
"key1".to_owned(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
globals,
|
||||
hostname,
|
||||
keypair,
|
||||
reqwest_client: reqwest::Client::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the hostname of the server.
|
||||
pub fn hostname(&self) -> &str {
|
||||
&self.hostname
|
||||
}
|
||||
|
||||
/// Returns this server's keypair.
|
||||
pub fn keypair(&self) -> &ruma_signatures::Ed25519KeyPair {
|
||||
&self.keypair
|
||||
}
|
||||
|
||||
/// Returns a reqwest client which can be used to send requests.
|
||||
pub fn reqwest_client(&self) -> &reqwest::Client {
|
||||
&self.reqwest_client
|
||||
}
|
||||
|
||||
pub fn next_count(&self) -> Result<u64> {
|
||||
Ok(utils::u64_from_bytes(
|
||||
&self
|
||||
.globals
|
||||
.update_and_fetch(COUNTER, utils::increment)?
|
||||
.expect("utils::increment will always put in a value"),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn current_count(&self) -> Result<u64> {
|
||||
Ok(self
|
||||
.globals
|
||||
.get(COUNTER)?
|
||||
.map_or(0_u64, |bytes| utils::u64_from_bytes(&bytes)))
|
||||
}
|
||||
}
|
547
src/database/rooms.rs
Normal file
547
src/database/rooms.rs
Normal file
|
@ -0,0 +1,547 @@
|
|||
mod edus;
|
||||
|
||||
pub use edus::RoomEdus;
|
||||
|
||||
use crate::{utils, Error, PduEvent, Result};
|
||||
use ruma_events::{room::power_levels::PowerLevelsEventContent, EventJson, EventType};
|
||||
use ruma_identifiers::{EventId, RoomId, UserId};
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
mem,
|
||||
};
|
||||
|
||||
pub struct Rooms {
|
||||
pub edus: edus::RoomEdus,
|
||||
pub(super) pduid_pdu: sled::Tree, // PduId = RoomId + Count
|
||||
pub(super) eventid_pduid: sled::Tree,
|
||||
pub(super) roomid_pduleaves: sled::Tree,
|
||||
pub(super) roomstateid_pdu: sled::Tree, // Room + StateType + StateKey
|
||||
|
||||
pub(super) userroomid_joined: sled::Tree,
|
||||
pub(super) roomuserid_joined: sled::Tree,
|
||||
pub(super) userroomid_invited: sled::Tree,
|
||||
pub(super) roomuserid_invited: sled::Tree,
|
||||
pub(super) userroomid_left: sled::Tree,
|
||||
}
|
||||
|
||||
impl Rooms {
|
||||
/// Checks if a room exists.
|
||||
pub fn exists(&self, room_id: &RoomId) -> Result<bool> {
|
||||
// Look for PDUs in that room.
|
||||
|
||||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
Ok(self
|
||||
.pduid_pdu
|
||||
.get_gt(&prefix)?
|
||||
.filter(|(k, _)| k.starts_with(&prefix))
|
||||
.is_some())
|
||||
}
|
||||
|
||||
// TODO: Remove and replace with public room dir
|
||||
/// Returns a vector over all rooms.
|
||||
pub fn all_rooms(&self) -> Vec<RoomId> {
|
||||
let mut room_ids = self
|
||||
.roomid_pduleaves
|
||||
.iter()
|
||||
.keys()
|
||||
.map(|key| {
|
||||
RoomId::try_from(
|
||||
&*utils::string_from_bytes(
|
||||
&key.unwrap()
|
||||
.iter()
|
||||
.copied()
|
||||
.take_while(|&x| x != 0xff) // until delimiter
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
room_ids.dedup();
|
||||
room_ids
|
||||
}
|
||||
|
||||
/// Returns the full room state.
|
||||
pub fn room_state(&self, room_id: &RoomId) -> Result<HashMap<(EventType, String), PduEvent>> {
|
||||
let mut hashmap = HashMap::new();
|
||||
for pdu in self
|
||||
.roomstateid_pdu
|
||||
.scan_prefix(&room_id.to_string().as_bytes())
|
||||
.values()
|
||||
.map(|value| Ok::<_, Error>(serde_json::from_slice::<PduEvent>(&value?)?))
|
||||
{
|
||||
let pdu = pdu?;
|
||||
hashmap.insert(
|
||||
(
|
||||
pdu.kind.clone(),
|
||||
pdu.state_key
|
||||
.clone()
|
||||
.expect("state events have a state key"),
|
||||
),
|
||||
pdu,
|
||||
);
|
||||
}
|
||||
Ok(hashmap)
|
||||
}
|
||||
|
||||
/// Returns the `count` of this pdu's id.
|
||||
pub fn get_pdu_count(&self, event_id: &EventId) -> Result<Option<u64>> {
|
||||
Ok(self
|
||||
.eventid_pduid
|
||||
.get(event_id.to_string().as_bytes())?
|
||||
.map(|pdu_id| {
|
||||
utils::u64_from_bytes(&pdu_id[pdu_id.len() - mem::size_of::<u64>()..pdu_id.len()])
|
||||
}))
|
||||
}
|
||||
|
||||
/// Returns the json of a pdu.
|
||||
pub fn get_pdu_json(&self, event_id: &EventId) -> Result<Option<serde_json::Value>> {
|
||||
self.eventid_pduid
|
||||
.get(event_id.to_string().as_bytes())?
|
||||
.map_or(Ok(None), |pdu_id| {
|
||||
Ok(serde_json::from_slice(
|
||||
&self.pduid_pdu.get(pdu_id)?.ok_or(Error::BadDatabase(
|
||||
"eventid_pduid points to nonexistent pdu",
|
||||
))?,
|
||||
)?)
|
||||
.map(Some)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the leaf pdus of a room.
|
||||
pub fn get_pdu_leaves(&self, room_id: &RoomId) -> Result<Vec<EventId>> {
|
||||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let mut events = Vec::new();
|
||||
|
||||
for event in self
|
||||
.roomid_pduleaves
|
||||
.scan_prefix(prefix)
|
||||
.values()
|
||||
.map(|bytes| Ok::<_, Error>(EventId::try_from(&*utils::string_from_bytes(&bytes?)?)?))
|
||||
{
|
||||
events.push(event?);
|
||||
}
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
/// Replace the leaves of a room with a new event.
|
||||
pub fn replace_pdu_leaves(&self, room_id: &RoomId, event_id: &EventId) -> Result<()> {
|
||||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
for key in self.roomid_pduleaves.scan_prefix(&prefix).keys() {
|
||||
self.roomid_pduleaves.remove(key?)?;
|
||||
}
|
||||
|
||||
prefix.extend_from_slice(event_id.to_string().as_bytes());
|
||||
self.roomid_pduleaves
|
||||
.insert(&prefix, &*event_id.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a new persisted data unit and adds it to a room.
|
||||
pub fn append_pdu(
|
||||
&self,
|
||||
room_id: RoomId,
|
||||
sender: UserId,
|
||||
event_type: EventType,
|
||||
content: serde_json::Value,
|
||||
unsigned: Option<serde_json::Map<String, serde_json::Value>>,
|
||||
state_key: Option<String>,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<EventId> {
|
||||
// Is the event authorized?
|
||||
if state_key.is_some() {
|
||||
if let Some(pdu) = self
|
||||
.room_state(&room_id)?
|
||||
.get(&(EventType::RoomPowerLevels, "".to_owned()))
|
||||
{
|
||||
let power_levels = serde_json::from_value::<EventJson<PowerLevelsEventContent>>(
|
||||
pdu.content.clone(),
|
||||
)?
|
||||
.deserialize()?;
|
||||
|
||||
match event_type {
|
||||
EventType::RoomMember => {
|
||||
// Member events are okay for now (TODO)
|
||||
}
|
||||
_ if power_levels
|
||||
.users
|
||||
.get(&sender)
|
||||
.unwrap_or(&power_levels.users_default)
|
||||
<= &0.into() =>
|
||||
{
|
||||
// Not authorized
|
||||
return Err(Error::BadRequest("event not authorized"));
|
||||
}
|
||||
// User has sufficient power
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prev_events are the leaves of the current graph. This method removes all leaves from the
|
||||
// room and replaces them with our event
|
||||
// TODO: Make sure this isn't called twice in parallel
|
||||
let prev_events = self.get_pdu_leaves(&room_id)?;
|
||||
|
||||
// Our depth is the maximum depth of prev_events + 1
|
||||
let depth = prev_events
|
||||
.iter()
|
||||
.filter_map(|event_id| Some(self.get_pdu_json(event_id).ok()??.get("depth")?.as_u64()?))
|
||||
.max()
|
||||
.unwrap_or(0_u64)
|
||||
+ 1;
|
||||
|
||||
let mut unsigned = unsigned.unwrap_or_default();
|
||||
// TODO: Optimize this to not load the whole room state?
|
||||
if let Some(state_key) = &state_key {
|
||||
if let Some(prev_pdu) = self
|
||||
.room_state(&room_id)?
|
||||
.get(&(event_type.clone(), state_key.clone()))
|
||||
{
|
||||
unsigned.insert("prev_content".to_owned(), prev_pdu.content.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut pdu = PduEvent {
|
||||
event_id: EventId::try_from("$thiswillbefilledinlater").expect("we know this is valid"),
|
||||
room_id: room_id.clone(),
|
||||
sender: sender.clone(),
|
||||
origin: globals.hostname().to_owned(),
|
||||
origin_server_ts: utils::millis_since_unix_epoch()
|
||||
.try_into()
|
||||
.expect("this only fails many years in the future"),
|
||||
kind: event_type,
|
||||
content,
|
||||
state_key,
|
||||
prev_events,
|
||||
depth: depth
|
||||
.try_into()
|
||||
.expect("depth can overflow and should be deprecated..."),
|
||||
auth_events: Vec::new(),
|
||||
redacts: None,
|
||||
unsigned,
|
||||
hashes: ruma_federation_api::EventHash {
|
||||
sha256: "aaa".to_owned(),
|
||||
},
|
||||
signatures: HashMap::new(),
|
||||
};
|
||||
|
||||
// Generate event id
|
||||
pdu.event_id = EventId::try_from(&*format!(
|
||||
"${}",
|
||||
ruma_signatures::reference_hash(&serde_json::to_value(&pdu)?)
|
||||
.expect("ruma can calculate reference hashes")
|
||||
))
|
||||
.expect("ruma's reference hashes are correct");
|
||||
|
||||
let mut pdu_json = serde_json::to_value(&pdu)?;
|
||||
ruma_signatures::hash_and_sign_event(globals.hostname(), globals.keypair(), &mut pdu_json)
|
||||
.expect("our new event can be hashed and signed");
|
||||
|
||||
self.replace_pdu_leaves(&room_id, &pdu.event_id)?;
|
||||
|
||||
// Increment the last index and use that
|
||||
// This is also the next_batch/since value
|
||||
let index = globals.next_count()?;
|
||||
|
||||
let mut pdu_id = room_id.to_string().as_bytes().to_vec();
|
||||
pdu_id.push(0xff);
|
||||
pdu_id.extend_from_slice(&index.to_be_bytes());
|
||||
|
||||
self.pduid_pdu.insert(&pdu_id, &*pdu_json.to_string())?;
|
||||
|
||||
self.eventid_pduid
|
||||
.insert(pdu.event_id.to_string(), pdu_id.clone())?;
|
||||
|
||||
if let Some(state_key) = pdu.state_key {
|
||||
let mut key = room_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(pdu.kind.to_string().as_bytes());
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(state_key.to_string().as_bytes());
|
||||
self.roomstateid_pdu.insert(key, &*pdu_json.to_string())?;
|
||||
}
|
||||
|
||||
self.edus.room_read_set(&room_id, &sender, index)?;
|
||||
|
||||
Ok(pdu.event_id)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all PDUs in a room.
|
||||
pub fn all_pdus(&self, room_id: &RoomId) -> Result<impl Iterator<Item = Result<PduEvent>>> {
|
||||
self.pdus_since(room_id, 0)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all events in a room that happened after the event with id `since`.
|
||||
pub fn pdus_since(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
since: u64,
|
||||
) -> Result<impl Iterator<Item = Result<PduEvent>>> {
|
||||
// Create the first part of the full pdu id
|
||||
let mut pdu_id = room_id.to_string().as_bytes().to_vec();
|
||||
pdu_id.push(0xff);
|
||||
pdu_id.extend_from_slice(&(since).to_be_bytes());
|
||||
|
||||
self.pdus_since_pduid(room_id, &pdu_id)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all events in a room that happened after the event with id `since`.
|
||||
pub fn pdus_since_pduid(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
pdu_id: &[u8],
|
||||
) -> Result<impl Iterator<Item = Result<PduEvent>>> {
|
||||
// Create the first part of the full pdu id
|
||||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
Ok(self
|
||||
.pduid_pdu
|
||||
.range(pdu_id..)
|
||||
// Skip the first pdu if it's exactly at since, because we sent that last time
|
||||
.skip(if self.pduid_pdu.get(pdu_id)?.is_some() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
})
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(_, v)| Ok(serde_json::from_slice(&v)?)))
|
||||
}
|
||||
|
||||
/// Returns an iterator over all events in a room that happened before the event with id
|
||||
/// `until` in reverse-chronological order.
|
||||
pub fn pdus_until(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
until: u64,
|
||||
) -> impl Iterator<Item = Result<PduEvent>> {
|
||||
// Create the first part of the full pdu id
|
||||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let mut current = prefix.clone();
|
||||
current.extend_from_slice(&until.to_be_bytes());
|
||||
|
||||
let current: &[u8] = ¤t;
|
||||
|
||||
self.pduid_pdu
|
||||
.range(..current)
|
||||
.rev()
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(_, v)| Ok(serde_json::from_slice(&v)?))
|
||||
}
|
||||
|
||||
/// Makes a user join a room.
|
||||
pub fn join(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
displayname: Option<String>,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
if !self.exists(room_id)? {
|
||||
return Err(Error::BadRequest("room does not exist"));
|
||||
}
|
||||
|
||||
let mut userroom_id = user_id.to_string().as_bytes().to_vec();
|
||||
userroom_id.push(0xff);
|
||||
userroom_id.extend_from_slice(room_id.to_string().as_bytes());
|
||||
|
||||
let mut roomuser_id = room_id.to_string().as_bytes().to_vec();
|
||||
roomuser_id.push(0xff);
|
||||
roomuser_id.extend_from_slice(user_id.to_string().as_bytes());
|
||||
|
||||
self.userroomid_joined.insert(&userroom_id, &[])?;
|
||||
self.roomuserid_joined.insert(&roomuser_id, &[])?;
|
||||
self.userroomid_invited.remove(&userroom_id)?;
|
||||
self.roomuserid_invited.remove(&roomuser_id)?;
|
||||
self.userroomid_left.remove(&userroom_id)?;
|
||||
|
||||
let mut content = json!({"membership": "join"});
|
||||
if let Some(displayname) = displayname {
|
||||
content
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("displayname".to_owned(), displayname.into());
|
||||
}
|
||||
|
||||
self.append_pdu(
|
||||
room_id.clone(),
|
||||
user_id.clone(),
|
||||
EventType::RoomMember,
|
||||
content,
|
||||
None,
|
||||
Some(user_id.to_string()),
|
||||
globals,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Makes a user leave a room.
|
||||
pub fn leave(
|
||||
&self,
|
||||
sender: &UserId,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
let mut userroom_id = user_id.to_string().as_bytes().to_vec();
|
||||
userroom_id.push(0xff);
|
||||
userroom_id.extend_from_slice(room_id.to_string().as_bytes());
|
||||
|
||||
let mut roomuser_id = room_id.to_string().as_bytes().to_vec();
|
||||
roomuser_id.push(0xff);
|
||||
roomuser_id.extend_from_slice(user_id.to_string().as_bytes());
|
||||
|
||||
self.userroomid_joined.remove(&userroom_id)?;
|
||||
self.roomuserid_joined.remove(&roomuser_id)?;
|
||||
self.userroomid_invited.remove(&userroom_id)?;
|
||||
self.roomuserid_invited.remove(&userroom_id)?;
|
||||
self.userroomid_left.insert(&userroom_id, &[])?;
|
||||
|
||||
self.append_pdu(
|
||||
room_id.clone(),
|
||||
sender.clone(),
|
||||
EventType::RoomMember,
|
||||
json!({"membership": "leave"}),
|
||||
None,
|
||||
Some(user_id.to_string()),
|
||||
globals,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Makes a user forget a room.
|
||||
pub fn forget(&self, room_id: &RoomId, user_id: &UserId) -> Result<()> {
|
||||
let mut userroom_id = user_id.to_string().as_bytes().to_vec();
|
||||
userroom_id.push(0xff);
|
||||
userroom_id.extend_from_slice(room_id.to_string().as_bytes());
|
||||
|
||||
self.userroomid_left.remove(userroom_id)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Makes a user invite another user into room.
|
||||
pub fn invite(
|
||||
&self,
|
||||
sender: &UserId,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
globals: &super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
let mut userroom_id = user_id.to_string().as_bytes().to_vec();
|
||||
userroom_id.push(0xff);
|
||||
userroom_id.extend_from_slice(room_id.to_string().as_bytes());
|
||||
|
||||
let mut roomuser_id = room_id.to_string().as_bytes().to_vec();
|
||||
roomuser_id.push(0xff);
|
||||
roomuser_id.extend_from_slice(user_id.to_string().as_bytes());
|
||||
|
||||
self.userroomid_invited.insert(userroom_id, &[])?;
|
||||
self.roomuserid_invited.insert(roomuser_id, &[])?;
|
||||
|
||||
self.append_pdu(
|
||||
room_id.clone(),
|
||||
sender.clone(),
|
||||
EventType::RoomMember,
|
||||
json!({"membership": "invite"}),
|
||||
None,
|
||||
Some(user_id.to_string()),
|
||||
globals,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns an iterator over all rooms a user joined.
|
||||
pub fn room_members(&self, room_id: &RoomId) -> impl Iterator<Item = Result<UserId>> {
|
||||
self.roomuserid_joined
|
||||
.scan_prefix(room_id.to_string())
|
||||
.values()
|
||||
.map(|key| {
|
||||
Ok(UserId::try_from(&*utils::string_from_bytes(
|
||||
&key?
|
||||
.rsplit(|&b| b == 0xff)
|
||||
.next()
|
||||
.ok_or(Error::BadDatabase("userroomid is invalid"))?,
|
||||
)?)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator over all rooms a user joined.
|
||||
pub fn room_members_invited(&self, room_id: &RoomId) -> impl Iterator<Item = Result<UserId>> {
|
||||
self.roomuserid_invited
|
||||
.scan_prefix(room_id.to_string())
|
||||
.keys()
|
||||
.map(|key| {
|
||||
Ok(UserId::try_from(&*utils::string_from_bytes(
|
||||
&key?
|
||||
.rsplit(|&b| b == 0xff)
|
||||
.next()
|
||||
.ok_or(Error::BadDatabase("userroomid is invalid"))?,
|
||||
)?)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator over all rooms a user joined.
|
||||
pub fn rooms_joined(&self, user_id: &UserId) -> impl Iterator<Item = Result<RoomId>> {
|
||||
self.userroomid_joined
|
||||
.scan_prefix(user_id.to_string())
|
||||
.keys()
|
||||
.map(|key| {
|
||||
Ok(RoomId::try_from(&*utils::string_from_bytes(
|
||||
&key?
|
||||
.rsplit(|&b| b == 0xff)
|
||||
.next()
|
||||
.ok_or(Error::BadDatabase("userroomid is invalid"))?,
|
||||
)?)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator over all rooms a user was invited to.
|
||||
pub fn rooms_invited(&self, user_id: &UserId) -> impl Iterator<Item = Result<RoomId>> {
|
||||
self.userroomid_invited
|
||||
.scan_prefix(&user_id.to_string())
|
||||
.keys()
|
||||
.map(|key| {
|
||||
Ok(RoomId::try_from(&*utils::string_from_bytes(
|
||||
&key?
|
||||
.rsplit(|&b| b == 0xff)
|
||||
.next()
|
||||
.ok_or(Error::BadDatabase("userroomid is invalid"))?,
|
||||
)?)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator over all rooms a user left.
|
||||
pub fn rooms_left(&self, user_id: &UserId) -> impl Iterator<Item = Result<RoomId>> {
|
||||
self.userroomid_left
|
||||
.scan_prefix(&user_id.to_string())
|
||||
.keys()
|
||||
.map(|key| {
|
||||
Ok(RoomId::try_from(&*utils::string_from_bytes(
|
||||
&key?
|
||||
.rsplit(|&b| b == 0xff)
|
||||
.next()
|
||||
.ok_or(Error::BadDatabase("userroomid is invalid"))?,
|
||||
)?)?)
|
||||
})
|
||||
}
|
||||
}
|
190
src/database/rooms/edus.rs
Normal file
190
src/database/rooms/edus.rs
Normal file
|
@ -0,0 +1,190 @@
|
|||
use crate::{utils, Result};
|
||||
use ruma_events::{collections::only::Event as EduEvent, EventJson};
|
||||
use ruma_identifiers::{RoomId, UserId};
|
||||
|
||||
pub struct RoomEdus {
|
||||
pub(in super::super) roomuserid_lastread: sled::Tree, // RoomUserId = Room + User
|
||||
pub(in super::super) roomlatestid_roomlatest: sled::Tree, // Read Receipts, RoomLatestId = RoomId + Count + UserId
|
||||
pub(in super::super) roomactiveid_roomactive: sled::Tree, // Typing, RoomActiveId = RoomId + TimeoutTime + Count
|
||||
}
|
||||
|
||||
impl RoomEdus {
|
||||
/// Adds an event which will be saved until a new event replaces it (e.g. read receipt).
|
||||
pub fn roomlatest_update(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
event: EduEvent,
|
||||
globals: &super::super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
// Remove old entry
|
||||
if let Some(old) = self
|
||||
.roomlatestid_roomlatest
|
||||
.scan_prefix(&prefix)
|
||||
.keys()
|
||||
.rev()
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(|key| key.starts_with(&prefix))
|
||||
.find(|key| {
|
||||
key.rsplit(|&b| b == 0xff).next().unwrap() == user_id.to_string().as_bytes()
|
||||
})
|
||||
{
|
||||
// This is the old room_latest
|
||||
self.roomlatestid_roomlatest.remove(old)?;
|
||||
}
|
||||
|
||||
let mut room_latest_id = prefix;
|
||||
room_latest_id.extend_from_slice(&globals.next_count()?.to_be_bytes());
|
||||
room_latest_id.push(0xff);
|
||||
room_latest_id.extend_from_slice(&user_id.to_string().as_bytes());
|
||||
|
||||
self.roomlatestid_roomlatest
|
||||
.insert(room_latest_id, &*serde_json::to_string(&event)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns an iterator over the most recent read_receipts in a room that happened after the event with id `since`.
|
||||
pub fn roomlatests_since(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
since: u64,
|
||||
) -> Result<impl Iterator<Item = Result<EventJson<EduEvent>>>> {
|
||||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let mut first_possible_edu = prefix.clone();
|
||||
first_possible_edu.extend_from_slice(&since.to_be_bytes());
|
||||
|
||||
Ok(self
|
||||
.roomlatestid_roomlatest
|
||||
.range(&*first_possible_edu..)
|
||||
// Skip the first pdu if it's exactly at since, because we sent that last time
|
||||
.skip(
|
||||
if self
|
||||
.roomlatestid_roomlatest
|
||||
.get(first_possible_edu)?
|
||||
.is_some()
|
||||
{
|
||||
1
|
||||
} else {
|
||||
0
|
||||
},
|
||||
)
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(_, v)| Ok(serde_json::from_slice(&v)?)))
|
||||
}
|
||||
|
||||
/// Returns a vector of the most recent read_receipts in a room that happened after the event with id `since`.
|
||||
pub fn roomlatests_all(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<impl Iterator<Item = Result<EventJson<EduEvent>>>> {
|
||||
self.roomlatests_since(room_id, 0)
|
||||
}
|
||||
|
||||
/// Adds an event that will be saved until the `timeout` timestamp (e.g. typing notifications).
|
||||
pub fn roomactive_add(
|
||||
&self,
|
||||
event: EduEvent,
|
||||
room_id: &RoomId,
|
||||
timeout: u64,
|
||||
globals: &super::super::globals::Globals,
|
||||
) -> Result<()> {
|
||||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
// Cleanup all outdated edus before inserting a new one
|
||||
for outdated_edu in self
|
||||
.roomactiveid_roomactive
|
||||
.scan_prefix(&prefix)
|
||||
.keys()
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(|k| {
|
||||
utils::u64_from_bytes(
|
||||
k.split(|&c| c == 0xff)
|
||||
.nth(1)
|
||||
.expect("roomactive has valid timestamp and delimiters"),
|
||||
) < utils::millis_since_unix_epoch()
|
||||
})
|
||||
{
|
||||
// This is an outdated edu (time > timestamp)
|
||||
self.roomlatestid_roomlatest.remove(outdated_edu)?;
|
||||
}
|
||||
|
||||
let mut room_active_id = prefix;
|
||||
room_active_id.extend_from_slice(&timeout.to_be_bytes());
|
||||
room_active_id.push(0xff);
|
||||
room_active_id.extend_from_slice(&globals.next_count()?.to_be_bytes());
|
||||
|
||||
self.roomactiveid_roomactive
|
||||
.insert(room_active_id, &*serde_json::to_string(&event)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes an active event manually (before the timeout is reached).
|
||||
pub fn roomactive_remove(&self, event: EduEvent, room_id: &RoomId) -> Result<()> {
|
||||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let json = serde_json::to_string(&event)?;
|
||||
|
||||
// Remove outdated entries
|
||||
for outdated_edu in self
|
||||
.roomactiveid_roomactive
|
||||
.scan_prefix(&prefix)
|
||||
.filter_map(|r| r.ok())
|
||||
.filter(|(_, v)| v == json.as_bytes())
|
||||
{
|
||||
self.roomactiveid_roomactive.remove(outdated_edu.0)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns an iterator over all active events (e.g. typing notifications).
|
||||
pub fn roomactives_all(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> impl Iterator<Item = Result<EventJson<EduEvent>>> {
|
||||
let mut prefix = room_id.to_string().as_bytes().to_vec();
|
||||
prefix.push(0xff);
|
||||
|
||||
let mut first_active_edu = prefix.clone();
|
||||
first_active_edu.extend_from_slice(&utils::millis_since_unix_epoch().to_be_bytes());
|
||||
|
||||
self.roomactiveid_roomactive
|
||||
.range(first_active_edu..)
|
||||
.filter_map(|r| r.ok())
|
||||
.take_while(move |(k, _)| k.starts_with(&prefix))
|
||||
.map(|(_, v)| Ok(serde_json::from_slice(&v)?))
|
||||
}
|
||||
|
||||
/// Sets a private read marker at `count`.
|
||||
pub fn room_read_set(&self, room_id: &RoomId, user_id: &UserId, count: u64) -> Result<()> {
|
||||
let mut key = room_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&user_id.to_string().as_bytes());
|
||||
|
||||
self.roomuserid_lastread.insert(key, &count.to_be_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the private read marker.
|
||||
pub fn room_read_get(&self, room_id: &RoomId, user_id: &UserId) -> Result<Option<u64>> {
|
||||
let mut key = room_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(&user_id.to_string().as_bytes());
|
||||
|
||||
Ok(self
|
||||
.roomuserid_lastread
|
||||
.get(key)?
|
||||
.map(|v| utils::u64_from_bytes(&v)))
|
||||
}
|
||||
}
|
144
src/database/users.rs
Normal file
144
src/database/users.rs
Normal file
|
@ -0,0 +1,144 @@
|
|||
use crate::{utils, Error, Result};
|
||||
use ruma_identifiers::UserId;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub struct Users {
|
||||
pub(super) userid_password: sled::Tree,
|
||||
pub(super) userid_displayname: sled::Tree,
|
||||
pub(super) userid_avatarurl: sled::Tree,
|
||||
pub(super) userdeviceid: sled::Tree,
|
||||
pub(super) userdeviceid_token: sled::Tree,
|
||||
pub(super) token_userid: sled::Tree,
|
||||
}
|
||||
|
||||
impl Users {
|
||||
/// Check if a user has an account on this homeserver.
|
||||
pub fn exists(&self, user_id: &UserId) -> Result<bool> {
|
||||
Ok(self.userid_password.contains_key(user_id.to_string())?)
|
||||
}
|
||||
|
||||
/// Create a new user account on this homeserver.
|
||||
pub fn create(&self, user_id: &UserId, hash: &str) -> Result<()> {
|
||||
self.userid_password.insert(user_id.to_string(), hash)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Find out which user an access token belongs to.
|
||||
pub fn find_from_token(&self, token: &str) -> Result<Option<UserId>> {
|
||||
self.token_userid.get(token)?.map_or(Ok(None), |bytes| {
|
||||
utils::string_from_bytes(&bytes)
|
||||
.and_then(|string| Ok(UserId::try_from(string)?))
|
||||
.map(Some)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator over all users on this homeserver.
|
||||
pub fn iter(&self) -> impl Iterator<Item = Result<UserId>> {
|
||||
self.userid_password.iter().keys().map(|r| {
|
||||
utils::string_from_bytes(&r?).and_then(|string| Ok(UserId::try_from(&*string)?))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the password hash for the given user.
|
||||
pub fn password_hash(&self, user_id: &UserId) -> Result<Option<String>> {
|
||||
self.userid_password
|
||||
.get(user_id.to_string())?
|
||||
.map_or(Ok(None), |bytes| utils::string_from_bytes(&bytes).map(Some))
|
||||
}
|
||||
|
||||
/// Returns the displayname of a user on this homeserver.
|
||||
pub fn displayname(&self, user_id: &UserId) -> Result<Option<String>> {
|
||||
self.userid_displayname
|
||||
.get(user_id.to_string())?
|
||||
.map_or(Ok(None), |bytes| utils::string_from_bytes(&bytes).map(Some))
|
||||
}
|
||||
|
||||
/// Sets a new displayname or removes it if displayname is None. You still need to nofify all rooms of this change.
|
||||
pub fn set_displayname(&self, user_id: &UserId, displayname: Option<String>) -> Result<()> {
|
||||
if let Some(displayname) = displayname {
|
||||
self.userid_displayname
|
||||
.insert(user_id.to_string(), &*displayname)?;
|
||||
} else {
|
||||
self.userid_displayname.remove(user_id.to_string())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
/* TODO:
|
||||
for room_id in self.rooms_joined(user_id) {
|
||||
self.pdu_append(
|
||||
room_id.clone(),
|
||||
user_id.clone(),
|
||||
EventType::RoomMember,
|
||||
json!({"membership": "join", "displayname": displayname}),
|
||||
None,
|
||||
Some(user_id.to_string()),
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// Get a the avatar_url of a user.
|
||||
pub fn avatar_url(&self, user_id: &UserId) -> Result<Option<String>> {
|
||||
self.userid_avatarurl
|
||||
.get(user_id.to_string())?
|
||||
.map_or(Ok(None), |bytes| utils::string_from_bytes(&bytes).map(Some))
|
||||
}
|
||||
|
||||
/// Sets a new avatar_url or removes it if avatar_url is None.
|
||||
pub fn set_avatar_url(&self, user_id: &UserId, avatar_url: Option<String>) -> Result<()> {
|
||||
if let Some(avatar_url) = avatar_url {
|
||||
self.userid_avatarurl
|
||||
.insert(user_id.to_string(), &*avatar_url)?;
|
||||
} else {
|
||||
self.userid_avatarurl.remove(user_id.to_string())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds a new device to a user.
|
||||
pub fn create_device(&self, user_id: &UserId, device_id: &str, token: &str) -> Result<()> {
|
||||
if !self.exists(user_id)? {
|
||||
return Err(Error::BadRequest(
|
||||
"tried to create device for nonexistent user",
|
||||
));
|
||||
}
|
||||
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(device_id.as_bytes());
|
||||
|
||||
self.userdeviceid.insert(key, &[])?;
|
||||
|
||||
self.set_token(user_id, device_id, token)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Replaces the access token of one device.
|
||||
pub fn set_token(&self, user_id: &UserId, device_id: &str, token: &str) -> Result<()> {
|
||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||
key.push(0xff);
|
||||
key.extend_from_slice(device_id.as_bytes());
|
||||
|
||||
if self.userdeviceid.get(&key)?.is_none() {
|
||||
return Err(Error::BadRequest(
|
||||
"Tried to set token for nonexistent device",
|
||||
));
|
||||
}
|
||||
|
||||
// Remove old token
|
||||
if let Some(old_token) = self.userdeviceid_token.get(&key)? {
|
||||
self.token_userid.remove(old_token)?;
|
||||
// It will be removed from userdeviceid_token by the insert later
|
||||
}
|
||||
|
||||
// Assign token to device_id
|
||||
self.userdeviceid_token.insert(key, &*token)?;
|
||||
|
||||
// Assign token to user
|
||||
self.token_userid.insert(token, &*user_id.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue