diff --git a/src/bin/dicebot-cmd.rs b/src/bin/dicebot-cmd.rs index 8af1121..c9b93da 100644 --- a/src/bin/dicebot-cmd.rs +++ b/src/bin/dicebot-cmd.rs @@ -2,10 +2,20 @@ use chronicle_dicebot::commands; use chronicle_dicebot::context::Context; use chronicle_dicebot::db::Database; use chronicle_dicebot::error::BotError; +use matrix_sdk::{ + identifiers::{room_id, user_id}, + Room, +}; + +fn dummy_room() -> Room { + Room::new( + &room_id!("!fakeroomid:example.com"), + &user_id!("@fakeuserid:example.com"), + ) +} #[tokio::main] async fn main() -> Result<(), BotError> { - let db = Database::new_temp()?; let input = std::env::args().skip(1).collect::>().join(" "); let command = match commands::parser::parse_command(&input) { Ok(command) => command, @@ -13,10 +23,10 @@ async fn main() -> Result<(), BotError> { }; let context = Context { - db: db, + db: Database::new_temp()?, matrix_client: &matrix_sdk::Client::new("http://example.com") .expect("Could not create matrix client"), - room_id: "roomid", + room: &dummy_room(), username: "@localuser:example.com", message_body: &input, }; diff --git a/src/bot.rs b/src/bot.rs index 0886381..9d2d5c7 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -130,7 +130,7 @@ impl DiceBot { let ctx = Context { db: self.db.clone(), matrix_client: &self.client, - room_id: room_id.as_str(), + room: room, username: &sender_username, message_body: &command, }; diff --git a/src/bot/event_handlers.rs b/src/bot/event_handlers.rs index 0e22433..afb725f 100644 --- a/src/bot/event_handlers.rs +++ b/src/bot/event_handlers.rs @@ -1,7 +1,7 @@ use super::DiceBot; use crate::db::Database; use crate::error::BotError; -use crate::matrix; +use crate::logic::record_room_information; use async_trait::async_trait; use log::{debug, error, info, warn}; use matrix_sdk::{ @@ -123,13 +123,8 @@ impl EventEmitter for DiceBot { info!("Clearing all information for room ID {}", room_id); self.db.rooms.clear_info(room_id) } else if event_affects_us && adding_user { - info!("Joined room {}; recording user information", room_id); - let usernames = matrix::get_users_in_room(&self.client, &room.room_id).await; - usernames - .into_iter() - .filter(|username| username != &event.state_key) - .map(|username| self.db.rooms.add_user_to_room(&username, room_id)) - .collect() //Make use of collect impl on Result. + info!("Joined room {}; recording room information", room_id); + record_room_information(&self.client, &self.db, &room, &event.state_key).await } else if !event_affects_us && adding_user { info!("Adding user {} to room ID {}", username, room_id); self.db.rooms.add_user_to_room(username, room_id) diff --git a/src/cofd/dice.rs b/src/cofd/dice.rs index df18dfa..64e798c 100644 --- a/src/cofd/dice.rs +++ b/src/cofd/dice.rs @@ -323,6 +323,14 @@ mod tests { use super::*; use crate::db::Database; + /// Create dummy room instance. + fn dummy_room() -> matrix_sdk::Room { + matrix_sdk::Room::new( + &matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"), + &matrix_sdk::identifiers::user_id!("@fakeuserid:example.com"), + ) + } + ///Instead of being random, generate a series of numbers we have complete ///control over. struct SequentialDieRoller { @@ -464,7 +472,7 @@ mod tests { let ctx = Context { db: db, matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(), - room_id: "roomid", + room: &dummy_room(), username: "username", message_body: "message", }; @@ -495,7 +503,7 @@ mod tests { let ctx = Context { db: db, matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(), - room_id: "roomid", + room: &dummy_room(), username: "username", message_body: "message", }; @@ -519,15 +527,18 @@ mod tests { #[tokio::test] async fn can_resolve_variables_test() { + use crate::db::variables::UserAndRoom; + let db = Database::new_temp().unwrap(); let ctx = Context { db: db.clone(), matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(), - room_id: "roomid", + room: &dummy_room(), username: "username", message_body: "message", }; - let user_and_room = crate::db::variables::UserAndRoom(&ctx.username, &ctx.room_id); + + let user_and_room = UserAndRoom(&ctx.username, &ctx.room.room_id.as_str()); db.variables .set_user_variable(&user_and_room, "myvariable", 10) diff --git a/src/commands.rs b/src/commands.rs index dd2c89e..8bb950a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -78,13 +78,21 @@ pub async fn execute_command(ctx: &Context<'_>) -> CommandResult { mod tests { use super::*; + /// Create a dummy room instance. + fn dummy_room() -> matrix_sdk::Room { + matrix_sdk::Room::new( + &matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"), + &matrix_sdk::identifiers::user_id!("@fakeuserid:example.com"), + ) + } + #[tokio::test] async fn unrecognized_command() { let db = crate::db::Database::new_temp().unwrap(); let ctx = Context { db: db, matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(), - room_id: "myroomid", + room: &dummy_room(), username: "myusername", message_body: "!notacommand", }; diff --git a/src/commands/management.rs b/src/commands/management.rs index 564a59f..e71630b 100644 --- a/src/commands/management.rs +++ b/src/commands/management.rs @@ -1,10 +1,9 @@ use super::{Command, Execution}; use crate::context::Context; use crate::db::errors::DataError; -use crate::matrix; +use crate::logic::record_room_information; use async_trait::async_trait; -use matrix_sdk::identifiers::{RoomId, UserId}; -use std::convert::TryFrom; +use matrix_sdk::identifiers::UserId; pub struct ResyncCommand; @@ -17,17 +16,11 @@ impl Command for ResyncCommand { } async fn execute(&self, ctx: &Context<'_>) -> Execution { - let room_id = RoomId::try_from(ctx.room_id).expect("failed to decode room ID"); let our_username: Option = ctx.matrix_client.user_id().await; let our_username: &str = our_username.as_ref().map_or("", UserId::as_str); - let usernames = matrix::get_users_in_room(&ctx.matrix_client, &room_id).await; - - let result: ResyncResult = usernames - .into_iter() - .filter(|username| username != our_username) - .map(|username| ctx.db.rooms.add_user_to_room(&username, room_id.as_str())) - .collect(); //Make use of collect impl on Result. + let result: ResyncResult = + record_room_information(ctx.matrix_client, &ctx.db, &ctx.room, our_username).await; let (plain, html) = match result { Ok(()) => { diff --git a/src/commands/variables.rs b/src/commands/variables.rs index 3f5cb50..8e47dbd 100644 --- a/src/commands/variables.rs +++ b/src/commands/variables.rs @@ -13,7 +13,7 @@ impl Command for GetAllVariablesCommand { } async fn execute(&self, ctx: &Context<'_>) -> Execution { - let key = UserAndRoom(&ctx.username, &ctx.room_id); + let key = UserAndRoom(&ctx.username, &ctx.room.room_id.as_str()); let result = ctx.db.variables.get_user_variables(&key); let value = match result { @@ -48,7 +48,7 @@ impl Command for GetVariableCommand { async fn execute(&self, ctx: &Context<'_>) -> Execution { let name = &self.0; - let key = UserAndRoom(&ctx.username, &ctx.room_id); + let key = UserAndRoom(&ctx.username, &ctx.room.room_id.as_str()); let result = ctx.db.variables.get_user_variable(&key, name); let value = match result { @@ -74,7 +74,7 @@ impl Command for SetVariableCommand { async fn execute(&self, ctx: &Context<'_>) -> Execution { let name = &self.0; let value = self.1; - let key = UserAndRoom(&ctx.username, ctx.room_id); + let key = UserAndRoom(&ctx.username, ctx.room.room_id.as_str()); let result = ctx.db.variables.set_user_variable(&key, name, value); let content = match result { @@ -98,7 +98,7 @@ impl Command for DeleteVariableCommand { async fn execute(&self, ctx: &Context<'_>) -> Execution { let name = &self.0; - let key = UserAndRoom(&ctx.username, ctx.room_id); + let key = UserAndRoom(&ctx.username, ctx.room.room_id.as_str()); let result = ctx.db.variables.delete_user_variable(&key, name); let value = match result { diff --git a/src/context.rs b/src/context.rs index 42a854a..7fead4b 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,5 +1,6 @@ use crate::db::Database; use matrix_sdk::Client; +use matrix_sdk::Room; /// A context carried through the system providing access to things /// like the database. @@ -7,7 +8,7 @@ use matrix_sdk::Client; pub struct Context<'a> { pub db: Database, pub matrix_client: &'a Client, - pub room_id: &'a str, + pub room: &'a Room, pub username: &'a str, pub message_body: &'a str, } diff --git a/src/cthulhu/dice.rs b/src/cthulhu/dice.rs index 4851034..0aeb015 100644 --- a/src/cthulhu/dice.rs +++ b/src/cthulhu/dice.rs @@ -343,6 +343,14 @@ mod tests { use crate::db::Database; use crate::parser::{Amount, Element, Operator}; + /// Create a dummy room instance. + fn dummy_room() -> matrix_sdk::Room { + matrix_sdk::Room::new( + &matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"), + &matrix_sdk::identifiers::user_id!("@fakeuserid:example.com"), + ) + } + /// Generate a series of numbers manually for testing. For this /// die system, the first roll in the Vec should be the unit roll, /// and any subsequent rolls should be the tens place roll. The @@ -383,7 +391,7 @@ mod tests { let ctx = Context { db: db, matrix_client: &matrix_sdk::Client::new("https://example.com").unwrap(), - room_id: "roomid", + room: &dummy_room(), username: "username", message_body: "message", }; diff --git a/src/db/rooms.rs b/src/db/rooms.rs index 02a80ea..be79fd3 100644 --- a/src/db/rooms.rs +++ b/src/db/rooms.rs @@ -1,5 +1,6 @@ use crate::db::errors::DataError; use crate::db::schema::convert_u64; +use crate::models::RoomInfo; use byteorder::BigEndian; use log::{debug, error, log_enabled}; use sled::transaction::TransactionalTree; @@ -14,7 +15,8 @@ use zerocopy::AsBytes; #[derive(Clone)] pub struct Rooms { - /// Room ID -> RoomInfo struct (single entries) + /// Room ID -> RoomInfo struct (single entries). + /// Key is just room ID as bytes. pub(in crate::db) roomid_roominfo: Tree, /// Room ID -> list of usernames in room. @@ -225,6 +227,25 @@ impl Rooms { } } + pub fn insert_room_info(&self, info: &RoomInfo) -> Result<(), DataError> { + let key = info.room_id.as_bytes(); + let serialized = bincode::serialize(&info)?; + self.roomid_roominfo.insert(key, serialized)?; + Ok(()) + } + + pub fn get_room_info(&self, room_id: &str) -> Result, DataError> { + let key = room_id.as_bytes(); + + let room_info: Option = self + .roomid_roominfo + .get(key)? + .map(|bytes| bincode::deserialize(&bytes)) + .transpose()?; + + Ok(room_info) + } + pub fn get_rooms_for_user(&self, username: &str) -> Result, DataError> { hashset_tree::get_set(&self.username_roomids, username.as_bytes()) } @@ -357,7 +378,71 @@ mod tests { } #[test] - fn clear_info() { + fn insert_room_info_works() { + let rooms = create_test_instance(); + + let info = RoomInfo { + room_id: matrix_sdk::identifiers::room_id!("!fakeroom:example.com") + .as_str() + .to_owned(), + room_name: "fake room name".to_owned(), + }; + + rooms + .insert_room_info(&info) + .expect("Could insert room info"); + + let found_info = rooms + .get_room_info("!fakeroom:example.com") + .expect("Error loading room info"); + + assert!(found_info.is_some()); + assert_eq!(info, found_info.unwrap()); + } + + #[test] + fn insert_room_info_updates_data() { + let rooms = create_test_instance(); + + let mut info = RoomInfo { + room_id: matrix_sdk::identifiers::room_id!("!fakeroom:example.com") + .as_str() + .to_owned(), + room_name: "fake room name".to_owned(), + }; + + rooms + .insert_room_info(&info) + .expect("Could insert room info"); + + //Update info to have a new room name before inserting again. + info.room_name = "new fake room name".to_owned(); + + rooms + .insert_room_info(&info) + .expect("Could insert room info"); + + let found_info = rooms + .get_room_info("!fakeroom:example.com") + .expect("Error loading room info"); + + assert!(found_info.is_some()); + assert_eq!(info, found_info.unwrap()); + } + + #[test] + fn get_room_info_none_when_room_does_not_exist() { + let rooms = create_test_instance(); + + let found_info = rooms + .get_room_info("!fakeroom:example.com") + .expect("Error loading room info"); + + assert!(found_info.is_none()); + } + + #[test] + fn clear_info_modifies_removes_requested_room() { let rooms = create_test_instance(); rooms diff --git a/src/dice.rs b/src/dice.rs index 1472559..f2e0fba 100644 --- a/src/dice.rs +++ b/src/dice.rs @@ -9,7 +9,7 @@ use futures::stream::{self, StreamExt, TryStreamExt}; //New hotness pub async fn calculate_dice_amount(amounts: &[Amount], ctx: &Context<'_>) -> Result { let stream = stream::iter(amounts); - let key = UserAndRoom(&ctx.username, &ctx.room_id); + let key = UserAndRoom(&ctx.username, ctx.room.room_id.as_str()); let variables = &ctx.db.variables.get_user_variables(&key)?; use DiceRollingError::VariableNotFound; diff --git a/src/lib.rs b/src/lib.rs index 8340421..d67ade8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub mod db; pub mod dice; pub mod error; mod help; +pub mod logic; pub mod matrix; pub mod models; mod parser; diff --git a/src/logic.rs b/src/logic.rs new file mode 100644 index 0000000..8649956 --- /dev/null +++ b/src/logic.rs @@ -0,0 +1,30 @@ +use crate::db::errors::DataError; +use crate::matrix; +use crate::models::RoomInfo; +use matrix_sdk::{self, Client, Room}; + +/// Record the information about a room, including users in it. +pub async fn record_room_information( + client: &Client, + db: &crate::db::Database, + room: &Room, + our_username: &str, +) -> Result<(), DataError> { + let room_id_str = room.room_id.as_str(); + let usernames = matrix::get_users_in_room(&client, &room.room_id).await; + + let info = RoomInfo { + room_id: room_id_str.to_owned(), + room_name: room.display_name(), + }; + + // TODO this and the username adding should be one whole + // transaction in the db. + db.rooms.insert_room_info(&info)?; + + usernames + .into_iter() + .filter(|username| username != our_username) + .map(|username| db.rooms.add_user_to_room(&username, room_id_str)) + .collect() //Make use of collect impl on Result. +} diff --git a/src/models.rs b/src/models.rs index 5d76f96..83802cb 100644 --- a/src/models.rs +++ b/src/models.rs @@ -1,4 +1,7 @@ +use serde::{Deserialize, Serialize}; + /// RoomInfo has basic metadata about a room: its name, ID, etc. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct RoomInfo { pub room_id: String, pub room_name: String,