diff --git a/Cargo.lock b/Cargo.lock index d774972d964..1d1be2f699e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1476,6 +1476,7 @@ name = "example-command-bot" version = "0.1.0" dependencies = [ "anyhow", + "dirs", "matrix-sdk", "tokio", "tracing-subscriber", @@ -2889,6 +2890,7 @@ dependencies = [ "ruma", "serde", "serde_json", + "thiserror", "tokio", ] diff --git a/crates/matrix-sdk-redis/Cargo.toml b/crates/matrix-sdk-redis/Cargo.toml index 45f715dfa35..a46a71f523c 100644 --- a/crates/matrix-sdk-redis/Cargo.toml +++ b/crates/matrix-sdk-redis/Cargo.toml @@ -26,6 +26,7 @@ async-trait = "0.1.53" dashmap = "5.2.0" futures-core = "0.3.21" futures-util = { version = "0.3.21", default-features = false } +matrix-sdk-base = { version = "0.6.0", path = "../matrix-sdk-base", optional = true } matrix-sdk-common = { version = "0.6.0", path = "../matrix-sdk-common" } matrix-sdk-crypto = { version = "0.6.0", path = "../matrix-sdk-crypto", optional = true } matrix-sdk-store-encryption = { version = "0.2.0", path = "../matrix-sdk-store-encryption" } @@ -33,6 +34,7 @@ redis = { version = "0.21", features = ["tokio-comp"] } ruma = { workspace = true } serde = "1.0.136" serde_json = "1.0.79" +thiserror = "1.0.30" [dev-dependencies] matrix-sdk-base = { path = "../matrix-sdk-base", features = ["testing"] } diff --git a/crates/matrix-sdk-redis/src/lib.rs b/crates/matrix-sdk-redis/src/lib.rs index 6a702056be4..25016621d93 100644 --- a/crates/matrix-sdk-redis/src/lib.rs +++ b/crates/matrix-sdk-redis/src/lib.rs @@ -19,5 +19,85 @@ mod real_redis; mod redis_crypto_store; mod redis_shim; +#[cfg(any(feature = "state-store", feature = "crypto-store"))] +use matrix_sdk_base::store::StoreConfig; +#[cfg(feature = "state-store")] +use matrix_sdk_base::store::StoreError; +#[cfg(feature = "crypto-store")] +use matrix_sdk_crypto::store::CryptoStoreError; +use redis::RedisError; +use thiserror::Error; + #[cfg(feature = "crypto-store")] pub use redis_crypto_store::RedisStore as CryptoStore; + +use redis_crypto_store::RedisStore; + +/// All the errors that can occur when opening a redis store. +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum OpenStoreError { + /// An error occurred with the state store implementation. + #[cfg(feature = "state-store")] + #[error(transparent)] + State(#[from] StoreError), + + /// An error occurred with the crypto store implementation. + #[cfg(feature = "crypto-store")] + #[error(transparent)] + Crypto(#[from] CryptoStoreError), + + /// An error occurred with redis. + #[error(transparent)] + Redis(#[from] RedisError), +} + +/// Create a [`StoreConfig`] with an opened [`RedisStateStore`] that uses the +/// given path and passphrase. +/// +/// If the `e2e-encryption` Cargo feature is enabled, a [`RedisCryptoStore`] with +/// the same parameters is also opened. +/// +/// [`StoreConfig`]: #StoreConfig +#[cfg(any(feature = "state-store", feature = "crypto-store"))] +pub async fn make_store_config( + redis_url: &str, + passphrase: Option<&str>, + redis_prefix: &str, +) -> Result { + use real_redis::RealRedisClient; + + #[cfg(all(feature = "crypto-store", feature = "state-store"))] + { + panic!("Currently don't have a Redis state store!"); + + let underlying_client = redis::Client::open(redis_url).unwrap(); + let client = RealRedisClient::from(underlying_client); + let crypto_store = RedisStore::open(client, passphrase, String::from(redis_prefix)).await?; + // TODO: state_store + Ok(StoreConfig::new().state_store(state_store).crypto_store(crypto_store)) + } + + #[cfg(all(feature = "crypto-store", not(feature = "state-store")))] + { + let underlying_client = redis::Client::open(redis_url).unwrap(); + let client = RealRedisClient::from(underlying_client); + let crypto_store = RedisStore::open(client, passphrase, String::from(redis_prefix)).await?; + Ok(StoreConfig::new().crypto_store(crypto_store)) + } + + #[cfg(not(feature = "crypto-store"))] + { + panic!("Currently don't have a Redis state store!"); + + let mut store_builder = RedisStateStore::builder(); + store_builder.path(path.as_ref().to_path_buf()); + + if let Some(passphrase) = passphrase { + store_builder.passphrase(passphrase.to_owned()); + }; + let state_store = store_builder.build().map_err(StoreError::backend)?; + + Ok(StoreConfig::new().state_store(state_store)) + } +} diff --git a/crates/matrix-sdk-redis/src/real_redis.rs b/crates/matrix-sdk-redis/src/real_redis.rs index 2bbcfde8778..7e8c7e9c614 100644 --- a/crates/matrix-sdk-redis/src/real_redis.rs +++ b/crates/matrix-sdk-redis/src/real_redis.rs @@ -103,8 +103,6 @@ pub struct RealRedisClient { } impl RealRedisClient { - #[cfg(feature = "real-redis-tests")] - #[cfg(test)] pub fn from(client: redis::Client) -> Self { Self { client } } diff --git a/crates/matrix-sdk-redis/src/redis_crypto_store.rs b/crates/matrix-sdk-redis/src/redis_crypto_store.rs index b31c954f98c..d1f7ef0c486 100644 --- a/crates/matrix-sdk-redis/src/redis_crypto_store.rs +++ b/crates/matrix-sdk-redis/src/redis_crypto_store.rs @@ -155,9 +155,10 @@ where C: RedisClientShim, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("RedisStore") - .field("redis_url", &self.client.get_connection_info().redis) + f.debug_struct("RedisStore") + .field("client.get_connection_info().redis", &self.client.get_connection_info().redis) .field("key_prefix", &self.key_prefix) + .field("account_info", &self.account_info) .finish() } } diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index cc3fe02e2a9..e9efb3960cd 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -143,6 +143,22 @@ impl ClientBuilder { self } + /// Set up the store configuration for a redis store. + /// + /// This is a shorthand for + /// .[store_config](Self::store_config)([matrix_sdk_redis]::[make_store_config](matrix_sdk_redis::make_store_config)(path, passphrase)?). + #[cfg(feature = "redis")] + pub async fn redis_store( + self, + redis_url: &str, + passphrase: Option<&str>, + redis_prefix: &str, + ) -> Result { + let config = + matrix_sdk_redis::make_store_config(redis_url, passphrase, redis_prefix).await?; + Ok(self.store_config(config)) + } + /// Set up the store configuration for a IndexedDB store. /// /// This is the same as diff --git a/examples/command_bot/Cargo.toml b/examples/command_bot/Cargo.toml index 4e02b266c28..9718c29c026 100644 --- a/examples/command_bot/Cargo.toml +++ b/examples/command_bot/Cargo.toml @@ -10,10 +10,16 @@ test = false [dependencies] anyhow = "1" +dirs = { version = "4.0.0", optional = true } tokio = { version = "1.20.1", features = ["macros", "rt-multi-thread"] } tracing-subscriber = "0.3.15" url = "2.2.2" +[features] +default = [] +redis = ["matrix-sdk/redis"] +sled = ["matrix-sdk/sled", "dep:dirs"] + [dependencies.matrix-sdk] path = "../../crates/matrix-sdk" version = "0.6.0" diff --git a/examples/command_bot/src/main.rs b/examples/command_bot/src/main.rs index 676dd729347..f57692f1fb7 100644 --- a/examples/command_bot/src/main.rs +++ b/examples/command_bot/src/main.rs @@ -34,10 +34,12 @@ async fn login_and_sync( homeserver_url: String, username: String, password: String, + device_id: Option, ) -> anyhow::Result<()> { #[allow(unused_mut)] let mut client_builder = Client::builder().homeserver_url(homeserver_url); + // TODO: sled feature is not actually working! #[cfg(feature = "sled")] { // The location to save files to @@ -45,17 +47,26 @@ async fn login_and_sync( client_builder = client_builder.sled_store(home, None)?; } + #[cfg(feature = "redis")] + { + println!("Creating a Redis store on 127.0.0.1"); + let redis_url = "redis://127.0.0.1/"; + let redis_prefix = "party_bot"; + client_builder = client_builder.redis_store(redis_url, None, redis_prefix).await?; + } + #[cfg(feature = "indexeddb")] { client_builder = client_builder.indexeddb_store("party_bot", None).await?; } let client = client_builder.build().await.unwrap(); - client - .login_username(&username, &password) - .initial_device_display_name("command bot") - .send() - .await?; + + let mut login = client.login_username(&username, &password); + if let Some(device_id) = &device_id { + login = login.device_id(device_id); + } + login.initial_device_display_name("command bot").send().await?; println!("logged in as {username}"); @@ -86,13 +97,17 @@ async fn main() -> anyhow::Result<()> { (Some(a), Some(b), Some(c)) => (a, b, c), _ => { eprintln!( - "Usage: {} ", + "Usage: {} \ + \ + \ + \ + []", env::args().next().unwrap() ); exit(1) } }; - login_and_sync(homeserver_url, username, password).await?; + login_and_sync(homeserver_url, username, password, env::args().nth(4)).await?; Ok(()) }