Reuse device ID generated by matrix SDK after first login.
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Adds new db tree for simple global state values (which also lays foundation for other stuff), and stores device ID in that tree after first login. The ID is then reused on subsequent runs of the application. This is simpler than storing device ID in config file. Fixes #9.
This commit is contained in:
parent
7db639f16c
commit
14f8bc8b39
40
src/bot.rs
40
src/bot.rs
|
@ -120,20 +120,40 @@ impl DiceBot {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Logs the bot into Matrix and listens for events until program
|
/// Logs in to matrix and potentially records a new device ID. If
|
||||||
|
/// no device ID is found in the database, a new one will be
|
||||||
|
/// generated by the matrix SDK, and we will store it.
|
||||||
|
async fn login(&self, client: &Client) -> Result<(), BotError> {
|
||||||
|
let username = self.config.matrix_username();
|
||||||
|
let password = self.config.matrix_password();
|
||||||
|
|
||||||
|
// Pull device ID from database, if it exists. Then write it
|
||||||
|
// to DB if the library generated one for us.
|
||||||
|
let device_id: Option<String> = self.db.state.get_device_id()?;
|
||||||
|
let device_id: Option<&str> = device_id.as_deref();
|
||||||
|
|
||||||
|
client
|
||||||
|
.login(username, password, device_id, Some("matrix dice bot"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if device_id.is_none() {
|
||||||
|
let device_id = client.device_id().await.ok_or(BotError::NoDeviceIdFound)?;
|
||||||
|
self.db.state.set_device_id(device_id.as_str())?;
|
||||||
|
info!("Recorded new device ID: {}", device_id.as_str());
|
||||||
|
} else {
|
||||||
|
info!("Using existing device ID: {}", device_id.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Logged in as {}", username);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs the bot in to Matrix and listens for events until program
|
||||||
/// terminated, or a panic occurs. Originally adapted from the
|
/// terminated, or a panic occurs. Originally adapted from the
|
||||||
/// matrix-rust-sdk command bot example.
|
/// matrix-rust-sdk command bot example.
|
||||||
pub async fn run(self) -> Result<(), BotError> {
|
pub async fn run(self) -> Result<(), BotError> {
|
||||||
let username = &self.config.matrix_username();
|
|
||||||
let password = &self.config.matrix_password();
|
|
||||||
|
|
||||||
//TODO provide a device id from config.
|
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
client
|
self.login(&client).await?;
|
||||||
.login(username, password, None, Some("matrix dice bot"))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
info!("Logged in as {}", username);
|
|
||||||
|
|
||||||
// Initial sync without event handler prevents responding to
|
// Initial sync without event handler prevents responding to
|
||||||
// messages received while bot was offline. TODO: selectively
|
// messages received while bot was offline. TODO: selectively
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::db::errors::{DataError, MigrationError};
|
use crate::db::errors::{DataError, MigrationError};
|
||||||
use crate::db::migrations::{get_migration_version, Migrations};
|
use crate::db::migrations::{get_migration_version, Migrations};
|
||||||
use crate::db::rooms::Rooms;
|
use crate::db::rooms::Rooms;
|
||||||
|
use crate::db::state::DbState;
|
||||||
use crate::db::variables::Variables;
|
use crate::db::variables::Variables;
|
||||||
use log::info;
|
use log::info;
|
||||||
use sled::{Config, Db};
|
use sled::{Config, Db};
|
||||||
|
@ -11,6 +12,7 @@ pub mod errors;
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
pub mod rooms;
|
pub mod rooms;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
|
pub mod state;
|
||||||
pub mod variables;
|
pub mod variables;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -19,6 +21,7 @@ pub struct Database {
|
||||||
pub(crate) variables: Variables,
|
pub(crate) variables: Variables,
|
||||||
pub(crate) migrations: Migrations,
|
pub(crate) migrations: Migrations,
|
||||||
pub(crate) rooms: Rooms,
|
pub(crate) rooms: Rooms,
|
||||||
|
pub(crate) state: DbState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
|
@ -30,6 +33,7 @@ impl Database {
|
||||||
variables: Variables::new(&db)?,
|
variables: Variables::new(&db)?,
|
||||||
migrations: Migrations(migrations),
|
migrations: Migrations(migrations),
|
||||||
rooms: Rooms::new(&db)?,
|
rooms: Rooms::new(&db)?,
|
||||||
|
state: DbState::new(&db)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
//Start any event handlers.
|
//Start any event handlers.
|
||||||
|
|
|
@ -29,8 +29,11 @@ pub enum DataError {
|
||||||
#[error("unexpected or corruptd data bytes")]
|
#[error("unexpected or corruptd data bytes")]
|
||||||
InvalidValue,
|
InvalidValue,
|
||||||
|
|
||||||
|
#[error("expected string ref, but utf8 schema was violated: {0}")]
|
||||||
|
Utf8RefSchemaViolation(#[from] std::str::Utf8Error),
|
||||||
|
|
||||||
#[error("expected string, but utf8 schema was violated: {0}")]
|
#[error("expected string, but utf8 schema was violated: {0}")]
|
||||||
Utf8chemaViolation(#[from] std::str::Utf8Error),
|
Utf8SchemaViolation(#[from] std::string::FromUtf8Error),
|
||||||
|
|
||||||
#[error("internal database error: {0}")]
|
#[error("internal database error: {0}")]
|
||||||
InternalError(#[from] sled::Error),
|
InternalError(#[from] sled::Error),
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
use crate::db::errors::DataError;
|
||||||
|
use sled::Tree;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DbState {
|
||||||
|
/// Tree of simple key-values for global state values that persist
|
||||||
|
/// between restarts (e.g. device ID).
|
||||||
|
pub(in crate::db) global_metadata: Tree,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEVICE_ID_KEY: &'static [u8] = b"device_id";
|
||||||
|
|
||||||
|
impl DbState {
|
||||||
|
pub(in crate::db) fn new(db: &sled::Db) -> Result<DbState, sled::Error> {
|
||||||
|
Ok(DbState {
|
||||||
|
global_metadata: db.open_tree("global_metadata")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_device_id(&self) -> Result<Option<String>, DataError> {
|
||||||
|
self.global_metadata
|
||||||
|
.get(DEVICE_ID_KEY)?
|
||||||
|
.map(|v| String::from_utf8(v.to_vec()))
|
||||||
|
.transpose()
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_device_id(&self, device_id: &str) -> Result<(), DataError> {
|
||||||
|
self.global_metadata
|
||||||
|
.insert(DEVICE_ID_KEY, device_id.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use sled::Config;
|
||||||
|
|
||||||
|
fn create_test_instance() -> DbState {
|
||||||
|
let config = Config::new().temporary(true);
|
||||||
|
let db = config.open().unwrap();
|
||||||
|
DbState::new(&db).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_device_id_works() {
|
||||||
|
let state = create_test_instance();
|
||||||
|
let result = state.set_device_id("test-device");
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn set_device_id_can_overwrite() {
|
||||||
|
let state = create_test_instance();
|
||||||
|
state.set_device_id("test-device").expect("insert 1 failed");
|
||||||
|
let result = state.set_device_id("test-device2");
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_device_id_returns_some_when_set() {
|
||||||
|
let state = create_test_instance();
|
||||||
|
|
||||||
|
state
|
||||||
|
.set_device_id("test-device")
|
||||||
|
.expect("could not store device id properly");
|
||||||
|
|
||||||
|
let device_id = state.get_device_id();
|
||||||
|
|
||||||
|
assert!(device_id.is_ok());
|
||||||
|
|
||||||
|
let device_id = device_id.unwrap();
|
||||||
|
assert!(device_id.is_some());
|
||||||
|
assert_eq!("test-device", device_id.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_device_id_returns_none_when_unset() {
|
||||||
|
let state = create_test_instance();
|
||||||
|
let device_id = state.get_device_id();
|
||||||
|
assert!(device_id.is_ok());
|
||||||
|
|
||||||
|
let device_id = device_id.unwrap();
|
||||||
|
assert!(device_id.is_none());
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,9 @@ pub enum BotError {
|
||||||
#[error("the sync token could not be retrieved")]
|
#[error("the sync token could not be retrieved")]
|
||||||
SyncTokenRequired,
|
SyncTokenRequired,
|
||||||
|
|
||||||
|
#[error("could not retrieve device id")]
|
||||||
|
NoDeviceIdFound,
|
||||||
|
|
||||||
#[error("command error: {0}")]
|
#[error("command error: {0}")]
|
||||||
CommandError(#[from] CommandError),
|
CommandError(#[from] CommandError),
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue