Upgrade to Matrix SDK latest (Store Rewrite) and Tokio 1.0
continuous-integration/drone/push Build is passing Details

This upgrade introduces a handful of breaking changes in the Rust
Matrix SDK.
 - Some types have disappeared and changed name.
 - Some functions are no longer async.
 - Room display name now has a Result type instead of just returning
   the value.
 - Client state store has breaking changes (not really a big deal).

This required introduction of a new type to store room information
that we are interested in on the context struct. This new RoomContext
is required mostly due to unit tests, because it is no longer possible
to instantiate the Room type in the Matrix SDK.
This commit is contained in:
projectmoon 2021-01-29 22:35:07 +00:00
parent a4cdad4936
commit 1b0003ff1b
15 changed files with 551 additions and 1053 deletions

1309
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -37,5 +37,5 @@ version = "1"
features = ['derive']
[dependencies.tokio]
version = "0.2"
version = "1.0"
features = [ "full" ]

View File

@ -1,18 +1,8 @@
use chronicle_dicebot::commands;
use chronicle_dicebot::context::Context;
use chronicle_dicebot::context::{Context, RoomContext};
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"),
)
}
use matrix_sdk::identifiers::room_id;
#[tokio::main]
async fn main() -> Result<(), BotError> {
@ -26,7 +16,10 @@ async fn main() -> Result<(), BotError> {
db: Database::new_temp()?,
matrix_client: &matrix_sdk::Client::new("http://example.com")
.expect("Could not create matrix client"),
room: &dummy_room(),
room: RoomContext {
id: &room_id!("!fakeroomid:example.com"),
display_name: "fake room",
},
username: "@localuser:example.com",
message_body: &input,
};

View File

@ -1,6 +1,6 @@
use crate::commands::execute_command;
use crate::config::*;
use crate::context::Context;
use crate::context::{Context, RoomContext};
use crate::db::Database;
use crate::error::BotError;
use crate::state::DiceBotState;
@ -13,7 +13,7 @@ use matrix_sdk::{
room::message::{MessageEventContent, NoticeMessageEventContent},
AnyMessageEventContent,
},
Client, ClientConfig, JsonStore, Room, SyncSettings,
Client, ClientConfig, JoinedRoom, SyncSettings,
};
//use matrix_sdk_common_macros::async_trait;
use std::clone::Clone;
@ -48,8 +48,8 @@ fn cache_dir() -> Result<PathBuf, BotError> {
/// Creates the matrix client.
fn create_client(config: &Config) -> Result<Client, BotError> {
let cache_dir = cache_dir()?;
let store = JsonStore::open(&cache_dir)?;
let client_config = ClientConfig::new().state_store(Box::new(store));
//let store = JsonStore::open(&cache_dir)?;
let client_config = ClientConfig::new().store_path(cache_dir);
let homeserver_url = Url::parse(&config.matrix_homeserver())?;
Ok(Client::new_with_config(homeserver_url, client_config)?)
@ -90,7 +90,7 @@ impl DiceBot {
let password = &self.config.matrix_password();
//TODO provide a device id from config.
let mut client = self.client.clone();
let client = self.client.clone();
client
.login(username, password, None, Some("matrix dice bot"))
.await?;
@ -118,9 +118,9 @@ impl DiceBot {
Ok(())
}
async fn execute_commands(&self, room: &Room, sender_username: &str, msg_body: &str) {
let room_name = room.display_name().clone();
let room_id = room.room_id.clone();
async fn execute_commands(&self, room: &JoinedRoom, sender_username: &str, msg_body: &str) {
let room_name = room.display_name().await.ok().unwrap_or_default();
let room_id = room.room_id().clone();
let mut results = Vec::with_capacity(msg_body.lines().count());
@ -130,7 +130,7 @@ impl DiceBot {
let ctx = Context {
db: self.db.clone(),
matrix_client: &self.client,
room: room,
room: RoomContext::new_with_name(&room, &room_name),
username: &sender_username,
message_body: &command,
};

View File

@ -11,7 +11,8 @@ use matrix_sdk::{
room::message::{MessageEventContent, TextMessageEventContent},
StrippedStateEvent, SyncMessageEvent, SyncStateEvent,
},
EventEmitter, SyncRoom,
identifiers::RoomId,
EventEmitter, RoomState,
};
use std::clone::Clone;
use std::ops::Sub;
@ -92,86 +93,112 @@ fn should_process_event(db: &Database, room_id: &str, event_id: &str) -> bool {
})
}
/// Convert room state object to the room ID and display name, if
/// possible. We only care about the room if it is a joined or left
/// room.
async fn convert_room_state(state: &RoomState) -> Option<(&RoomId, String)> {
match state {
RoomState::Joined(room) => Some((
room.room_id(),
room.display_name().await.ok().unwrap_or_default(),
)),
RoomState::Left(room) => Some((
room.room_id(),
room.display_name().await.ok().unwrap_or_default(),
)),
_ => None,
}
}
/// This event emitter listens for messages with dice rolling commands.
/// Originally adapted from the matrix-rust-sdk examples.
#[async_trait]
impl EventEmitter for DiceBot {
async fn on_room_member(&self, room: SyncRoom, event: &SyncStateEvent<MemberEventContent>) {
if let SyncRoom::Joined(room) | SyncRoom::Left(room) = room {
//Clone to avoid holding lock.
let room = room.read().await.clone();
let (room_id, username) = (room.room_id.as_str(), &event.state_key);
async fn on_room_member(&self, state: RoomState, event: &SyncStateEvent<MemberEventContent>) {
let (room_id, room_display_name) = match convert_room_state(&state).await {
Some((room_id, room_display_name)) => (room_id, room_display_name),
_ => return,
};
if !should_process_event(&self.db, room_id, event.event_id.as_str()) {
return;
}
let room_id_str = room_id.as_str();
let username = &event.state_key;
let event_affects_us = if let Some(our_user_id) = self.client.user_id().await {
event.state_key == our_user_id
} else {
false
};
if !should_process_event(&self.db, room_id_str, event.event_id.as_str()) {
return;
}
use MembershipChange::*;
let adding_user = match event.membership_change() {
Joined => true,
Banned | Left | Kicked | KickedAndBanned => false,
_ => return,
};
let event_affects_us = if let Some(our_user_id) = self.client.user_id().await {
event.state_key == our_user_id
} else {
false
};
let result = if event_affects_us && !adding_user {
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 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)
} else if !event_affects_us && !adding_user {
info!("Removing user {} from room ID {}", username, room_id);
self.db.rooms.remove_user_from_room(username, room_id)
} else {
debug!("Ignoring a room member event: {:#?}", event);
Ok(())
};
use MembershipChange::*;
let adding_user = match event.membership_change() {
Joined => true,
Banned | Left | Kicked | KickedAndBanned => false,
_ => return,
};
if let Err(e) = result {
error!("Could not update room information: {}", e.to_string());
} else {
debug!("Successfully processed room member update.");
}
let result = if event_affects_us && !adding_user {
info!("Clearing all information for room ID {}", room_id);
self.db.rooms.clear_info(room_id_str)
} else if event_affects_us && adding_user {
info!("Joined room {}; recording room information", room_id);
record_room_information(
&self.client,
&self.db,
&room_id,
&room_display_name,
&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_str)
} else if !event_affects_us && !adding_user {
info!("Removing user {} from room ID {}", username, room_id);
self.db.rooms.remove_user_from_room(username, room_id_str)
} else {
debug!("Ignoring a room member event: {:#?}", event);
Ok(())
};
if let Err(e) = result {
error!("Could not update room information: {}", e.to_string());
} else {
debug!("Successfully processed room member update.");
}
}
async fn on_stripped_state_member(
&self,
room: SyncRoom,
state: RoomState,
event: &StrippedStateEvent<MemberEventContent>,
_: Option<MemberEventContent>,
) {
if let SyncRoom::Invited(room) = room {
if let RoomState::Invited(room) = state {
if let Some(user_id) = self.client.user_id().await {
if event.state_key != user_id {
return;
}
}
//Clone to avoid holding lock.
let room = room.read().await.clone();
info!("Autojoining room {}", room.display_name());
info!("Autojoining room {}", room.display_name().await);
if let Err(e) = self.client.join_room_by_id(&room.room_id).await {
if let Err(e) = self.client.join_room_by_id(&room.room_id()).await {
warn!("Could not join room: {}", e.to_string())
}
}
}
async fn on_room_message(&self, room: SyncRoom, event: &SyncMessageEvent<MessageEventContent>) {
if let SyncRoom::Joined(room) = room {
//Clone to avoid holding lock.
let room = room.read().await.clone();
let room_id = room.room_id.as_str();
async fn on_room_message(
&self,
state: RoomState,
event: &SyncMessageEvent<MessageEventContent>,
) {
if let RoomState::Joined(room) = state {
let room_id = room.room_id().as_str();
if !should_process_event(&self.db, room_id, event.event_id.as_str()) {
return;
}

View File

@ -326,12 +326,13 @@ 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"),
)
macro_rules! dummy_room {
() => {
crate::context::RoomContext {
id: &matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"),
display_name: "displayname",
}
};
}
///Instead of being random, generate a series of numbers we have complete
@ -475,7 +476,7 @@ mod tests {
let ctx = Context {
db: db,
matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(),
room: &dummy_room(),
room: dummy_room!(),
username: "username",
message_body: "message",
};
@ -506,7 +507,7 @@ mod tests {
let ctx = Context {
db: db,
matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(),
room: &dummy_room(),
room: dummy_room!(),
username: "username",
message_body: "message",
};
@ -536,12 +537,12 @@ mod tests {
let ctx = Context {
db: db.clone(),
matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(),
room: &dummy_room(),
room: dummy_room!(),
username: "username",
message_body: "message",
};
let user_and_room = UserAndRoom(&ctx.username, &ctx.room.room_id.as_str());
let user_and_room = UserAndRoom(&ctx.username, &ctx.room.id.as_str());
db.variables
.set_user_variable(&user_and_room, "myvariable", 10)

View File

@ -78,12 +78,13 @@ 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"),
)
macro_rules! dummy_room {
() => {
crate::context::RoomContext {
id: &matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"),
display_name: "displayname",
}
};
}
#[tokio::test]
@ -92,7 +93,7 @@ mod tests {
let ctx = Context {
db: db,
matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(),
room: &dummy_room(),
room: dummy_room!(),
username: "myusername",
message_body: "!notacommand",
};

View File

@ -19,8 +19,14 @@ impl Command for ResyncCommand {
let our_username: Option<UserId> = ctx.matrix_client.user_id().await;
let our_username: &str = our_username.as_ref().map_or("", UserId::as_str);
let result: ResyncResult =
record_room_information(ctx.matrix_client, &ctx.db, &ctx.room, our_username).await;
let result: ResyncResult = record_room_information(
ctx.matrix_client,
&ctx.db,
ctx.room.id,
&ctx.room.display_name,
our_username,
)
.await;
let (plain, html) = match result {
Ok(()) => {

View File

@ -13,7 +13,7 @@ impl Command for GetAllVariablesCommand {
}
async fn execute(&self, ctx: &Context<'_>) -> Execution {
let key = UserAndRoom(&ctx.username, &ctx.room.room_id.as_str());
let key = UserAndRoom(&ctx.username, &ctx.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.room_id.as_str());
let key = UserAndRoom(&ctx.username, &ctx.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.room_id.as_str());
let key = UserAndRoom(&ctx.username, ctx.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.room_id.as_str());
let key = UserAndRoom(&ctx.username, ctx.room.id.as_str());
let result = ctx.db.variables.delete_user_variable(&key, name);
let value = match result {

View File

@ -1,6 +1,7 @@
use crate::db::Database;
use matrix_sdk::identifiers::RoomId;
use matrix_sdk::Client;
use matrix_sdk::Room;
use matrix_sdk::JoinedRoom;
/// A context carried through the system providing access to things
/// like the database.
@ -8,7 +9,22 @@ use matrix_sdk::Room;
pub struct Context<'a> {
pub db: Database,
pub matrix_client: &'a Client,
pub room: &'a Room,
pub room: RoomContext<'a>,
pub username: &'a str,
pub message_body: &'a str,
}
#[derive(Clone)]
pub struct RoomContext<'a> {
pub id: &'a RoomId,
pub display_name: &'a str,
}
impl RoomContext<'_> {
pub fn new_with_name<'a>(room: &'a JoinedRoom, display_name: &'a str) -> RoomContext<'a> {
RoomContext {
id: room.room_id(),
display_name,
}
}
}

View File

@ -343,12 +343,13 @@ 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"),
)
macro_rules! dummy_room {
() => {
crate::context::RoomContext {
id: &matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"),
display_name: "displayname",
}
};
}
/// Generate a series of numbers manually for testing. For this
@ -391,7 +392,7 @@ mod tests {
let ctx = Context {
db: db,
matrix_client: &matrix_sdk::Client::new("https://example.com").unwrap(),
room: &dummy_room(),
room: dummy_room!(),
username: "username",
message_body: "message",
};

View File

@ -9,7 +9,7 @@ use futures::stream::{self, StreamExt, TryStreamExt};
//New hotness
pub async fn calculate_dice_amount(amounts: &[Amount], ctx: &Context<'_>) -> Result<i32, BotError> {
let stream = stream::iter(amounts);
let key = UserAndRoom(&ctx.username, ctx.room.room_id.as_str());
let key = UserAndRoom(&ctx.username, ctx.room.id.as_str());
let variables = &ctx.db.variables.get_user_variables(&key)?;
use DiceRollingError::VariableNotFound;

View File

@ -27,6 +27,9 @@ pub enum BotError {
#[error("could not parse URL")]
UrlParseError(#[from] url::ParseError),
#[error("error in matrix state store: {0}")]
MatrixStateStoreError(#[from] matrix_sdk::StoreError),
#[error("uncategorized matrix SDK error")]
MatrixError(#[from] matrix_sdk::Error),

View File

@ -1,21 +1,22 @@
use crate::db::errors::DataError;
use crate::matrix;
use crate::models::RoomInfo;
use matrix_sdk::{self, Client, Room};
use matrix_sdk::{self, identifiers::RoomId, Client};
/// Record the information about a room, including users in it.
pub async fn record_room_information(
client: &Client,
db: &crate::db::Database,
room: &Room,
room_id: &RoomId,
room_display_name: &str,
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 room_id_str = room_id.as_str();
let usernames = matrix::get_users_in_room(&client, &room_id).await;
let info = RoomInfo {
room_id: room_id_str.to_owned(),
room_name: room.display_name(),
room_name: room_display_name.to_owned(),
};
// TODO this and the username adding should be one whole

View File

@ -1,13 +1,21 @@
use matrix_sdk::{identifiers::RoomId, Client, Room};
use matrix_sdk::{identifiers::RoomId, Client};
/// Retrieve a list of users in a given room.
pub async fn get_users_in_room(client: &Client, room_id: &RoomId) -> Vec<String> {
if let Some(joined_room) = client.get_joined_room(room_id).await {
let joined_room: Room = joined_room.read().await.clone();
if let Some(joined_room) = client.get_joined_room(room_id) {
joined_room
.joined_members
.keys()
.map(|user_id| format!("@{}:{}", user_id.localpart(), user_id.server_name()))
.joined_members()
.await
.ok()
.unwrap_or_default()
.into_iter()
.map(|member| {
format!(
"@{}:{}",
member.user_id().localpart(),
member.user_id().server_name()
)
})
.collect()
} else {
vec![]