mirror of https://gitlab.com/famedly/conduit
refactor: split database into multiple files, more error handling, cleaner code
parent
4b191a9311
commit
8f67c01efd
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
}
|
||||
}
|
@ -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)))
|
||||
}
|
||||
}
|
@ -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"))?,
|
||||
)?)?)
|
||||
})
|
||||
}
|
||||
}
|
@ -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)))
|
||||
}
|
||||
}
|
@ -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(())
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("problem with the database")]
|
||||
SledError {
|
||||
#[from]
|
||||
source: sled::Error,
|
||||
},
|
||||
#[error("tried to parse invalid string")]
|
||||
StringFromBytesError {
|
||||
#[from]
|
||||
source: std::string::FromUtf8Error,
|
||||
},
|
||||
#[error("tried to parse invalid identifier")]
|
||||
SerdeJsonError {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
},
|
||||
#[error("tried to parse invalid identifier")]
|
||||
RumaIdentifierError {
|
||||
#[from]
|
||||
source: ruma_identifiers::Error,
|
||||
},
|
||||
#[error("tried to parse invalid event")]
|
||||
RumaEventError {
|
||||
#[from]
|
||||
source: ruma_events::InvalidEvent,
|
||||
},
|
||||
#[error("bad request")]
|
||||
BadRequest(&'static str),
|
||||
#[error("problem in that database")]
|
||||
BadDatabase(&'static str),
|
||||
}
|
Loading…
Reference in New Issue