state_store: move path into JsonStore, store must be "connected" before adding to AsyncClientConfig
This commit is contained in:
parent
5491838228
commit
fd7d3db32b
5 changed files with 78 additions and 105 deletions
|
@ -12,9 +12,6 @@ use url::Url;
|
|||
struct CommandBot {
|
||||
/// This clone of the `AsyncClient` will send requests to the server,
|
||||
/// while the other keeps us in sync with the server using `sync_forever`.
|
||||
///
|
||||
/// The type parameter is for the `StateStore` trait specifying the `Store`
|
||||
/// type for state storage, here we don't care.
|
||||
client: AsyncClient,
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
use std::collections::HashMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::result::Result as StdResult;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
@ -48,7 +47,7 @@ use crate::api;
|
|||
use crate::base_client::Client as BaseClient;
|
||||
use crate::models::Room;
|
||||
use crate::session::Session;
|
||||
use crate::state::{ClientState, JsonStore, StateStore};
|
||||
use crate::state::{ClientState, StateStore};
|
||||
use crate::VERSION;
|
||||
use crate::{Error, EventEmitter, Result};
|
||||
|
||||
|
@ -65,8 +64,6 @@ pub struct AsyncClient {
|
|||
http_client: reqwest::Client,
|
||||
/// User session data.
|
||||
pub(crate) base_client: Arc<RwLock<BaseClient>>,
|
||||
/// The path to the default state store.
|
||||
state_store_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AsyncClient {
|
||||
|
@ -93,7 +90,6 @@ pub struct AsyncClientConfig {
|
|||
proxy: Option<reqwest::Proxy>,
|
||||
user_agent: Option<HeaderValue>,
|
||||
disable_ssl_verification: bool,
|
||||
store_path: Option<PathBuf>,
|
||||
state_store: Option<Box<dyn StateStore>>,
|
||||
}
|
||||
|
||||
|
@ -103,7 +99,6 @@ impl std::fmt::Debug for AsyncClientConfig {
|
|||
.field("proxy", &self.proxy)
|
||||
.field("user_agent", &self.user_agent)
|
||||
.field("disable_ssl_verification", &self.disable_ssl_verification)
|
||||
.field("store_path", &self.store_path)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -148,15 +143,6 @@ impl AsyncClientConfig {
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
/// Set the path for the default `StateStore`.
|
||||
///
|
||||
/// When the path is set `AsyncClient` will set the state store
|
||||
/// to `JsonStore`.
|
||||
pub fn state_store_path<P: AsRef<Path>>(mut self, path: P) -> Self {
|
||||
self.store_path = Some(path.as_ref().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `StateStore`.
|
||||
///
|
||||
/// The state store should be "connected" before being set.
|
||||
|
@ -289,11 +275,8 @@ impl AsyncClient {
|
|||
let http_client = http_client.default_headers(headers).build()?;
|
||||
|
||||
let mut base_client = BaseClient::new(session)?;
|
||||
if let Some(path) = config.store_path.as_ref() {
|
||||
let store = JsonStore;
|
||||
store.open(path)?;
|
||||
base_client.state_store = Some(Box::new(store));
|
||||
} else if let Some(store) = config.state_store {
|
||||
|
||||
if let Some(store) = config.state_store {
|
||||
base_client.state_store = Some(store);
|
||||
};
|
||||
|
||||
|
@ -301,7 +284,6 @@ impl AsyncClient {
|
|||
homeserver,
|
||||
http_client,
|
||||
base_client: Arc::new(RwLock::new(base_client)),
|
||||
state_store_path: config.store_path,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -382,15 +364,8 @@ impl AsyncClient {
|
|||
|
||||
let response = self.send(request).await?;
|
||||
let mut client = self.base_client.write().await;
|
||||
// TODO avoid allocation somehow?
|
||||
let path = self.state_store_path.as_ref().map(|p| {
|
||||
let mut path = PathBuf::from(p);
|
||||
path.push(response.user_id.to_string());
|
||||
path
|
||||
});
|
||||
client
|
||||
.receive_login_response(&response, path.as_ref())
|
||||
.await?;
|
||||
|
||||
client.receive_login_response(&response).await?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
@ -706,11 +681,9 @@ impl AsyncClient {
|
|||
|
||||
if updated {
|
||||
if let Some(store) = self.base_client.read().await.state_store.as_ref() {
|
||||
if let Some(path) = self.state_store_path.as_ref() {
|
||||
store
|
||||
.store_room_state(&path, matrix_room.read().await.deref())
|
||||
.await?;
|
||||
};
|
||||
store
|
||||
.store_room_state(matrix_room.read().await.deref())
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -720,10 +693,8 @@ impl AsyncClient {
|
|||
|
||||
if updated {
|
||||
if let Some(store) = client.state_store.as_ref() {
|
||||
if let Some(path) = self.state_store_path.as_ref() {
|
||||
let state = ClientState::from_base_client(&client);
|
||||
store.store_client_state(&path, state).await?;
|
||||
};
|
||||
let state = ClientState::from_base_client(&client);
|
||||
store.store_client_state(state).await?;
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
|
|
|
@ -19,7 +19,6 @@ use std::collections::HashSet;
|
|||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use std::path::PathBuf;
|
||||
#[cfg(feature = "encryption")]
|
||||
use std::result::Result as StdResult;
|
||||
|
||||
|
@ -145,7 +144,6 @@ impl Client {
|
|||
pub async fn receive_login_response(
|
||||
&mut self,
|
||||
response: &api::session::login::Response,
|
||||
store_path: Option<&PathBuf>,
|
||||
) -> Result<()> {
|
||||
let session = Session {
|
||||
access_token: response.access_token.clone(),
|
||||
|
@ -160,25 +158,23 @@ impl Client {
|
|||
*olm = Some(OlmMachine::new(&response.user_id, &response.device_id)?);
|
||||
}
|
||||
|
||||
if let Some(path) = store_path {
|
||||
if let Some(store) = self.state_store.as_ref() {
|
||||
let ClientState {
|
||||
session,
|
||||
sync_token,
|
||||
ignored_users,
|
||||
push_ruleset,
|
||||
} = store.load_client_state(&path).await?;
|
||||
let mut rooms = store.load_all_rooms(&path).await?;
|
||||
if let Some(store) = self.state_store.as_ref() {
|
||||
let ClientState {
|
||||
session,
|
||||
sync_token,
|
||||
ignored_users,
|
||||
push_ruleset,
|
||||
} = store.load_client_state().await?;
|
||||
let mut rooms = store.load_all_rooms().await?;
|
||||
|
||||
self.joined_rooms = rooms
|
||||
.drain()
|
||||
.map(|(k, room)| (k, Arc::new(RwLock::new(room))))
|
||||
.collect();
|
||||
self.session = session;
|
||||
self.sync_token = sync_token;
|
||||
self.ignored_users = ignored_users;
|
||||
self.push_ruleset = push_ruleset;
|
||||
}
|
||||
self.joined_rooms = rooms
|
||||
.drain()
|
||||
.map(|(k, room)| (k, Arc::new(RwLock::new(room))))
|
||||
.collect();
|
||||
self.session = session;
|
||||
self.sync_token = sync_token;
|
||||
self.ignored_users = ignored_users;
|
||||
self.push_ruleset = push_ruleset;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
// limitations under the License.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
pub mod state_store;
|
||||
pub use state_store::JsonStore;
|
||||
|
@ -61,20 +60,18 @@ impl ClientState {
|
|||
/// Abstraction around the data store to avoid unnecessary request on client initialization.
|
||||
#[async_trait::async_trait]
|
||||
pub trait StateStore: Send + Sync {
|
||||
/// Set up connections or check files exist to load/save state.
|
||||
fn open(&self, path: &Path) -> Result<()>;
|
||||
/// Loads the state of `BaseClient` through `StateStore::Store` type.
|
||||
async fn load_client_state(&self, path: &Path) -> Result<ClientState>;
|
||||
async fn load_client_state(&self) -> Result<ClientState>;
|
||||
/// Load the state of a single `Room` by `RoomId`.
|
||||
async fn load_room_state(&self, path: &Path, room_id: &RoomId) -> Result<Room>;
|
||||
async fn load_room_state(&self, room_id: &RoomId) -> Result<Room>;
|
||||
/// Load the state of all `Room`s.
|
||||
///
|
||||
/// This will be mapped over in the client in order to store `Room`s in an async safe way.
|
||||
async fn load_all_rooms(&self, path: &Path) -> Result<HashMap<RoomId, Room>>;
|
||||
async fn load_all_rooms(&self) -> Result<HashMap<RoomId, Room>>;
|
||||
/// Save the current state of the `BaseClient` using the `StateStore::Store` type.
|
||||
async fn store_client_state(&self, path: &Path, _: ClientState) -> Result<()>;
|
||||
async fn store_client_state(&self, _: ClientState) -> Result<()>;
|
||||
/// Save the state a single `Room`.
|
||||
async fn store_room_state(&self, path: &Path, _: &Room) -> Result<()>;
|
||||
async fn store_room_state(&self, _: &Room) -> Result<()>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,26 +1,36 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::{BufReader, BufWriter, Write};
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::{ClientState, StateStore};
|
||||
use crate::identifiers::RoomId;
|
||||
use crate::{Error, Result, Room};
|
||||
/// A default `StateStore` implementation that serializes state as json
|
||||
/// and saves it to disk.
|
||||
pub struct JsonStore;
|
||||
pub struct JsonStore {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl JsonStore {
|
||||
/// Create a `JsonStore` to store the client and room state.
|
||||
///
|
||||
/// Checks if the provided path exists and creates the directories if not.
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||
let p = path.as_ref();
|
||||
if !p.exists() {
|
||||
std::fs::create_dir_all(p)?;
|
||||
}
|
||||
Ok(Self {
|
||||
path: p.to_path_buf(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl StateStore for JsonStore {
|
||||
fn open(&self, path: &Path) -> Result<()> {
|
||||
if !path.exists() {
|
||||
std::fs::create_dir_all(path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_client_state(&self, path: &Path) -> Result<ClientState> {
|
||||
let mut path = path.to_path_buf();
|
||||
async fn load_client_state(&self) -> Result<ClientState> {
|
||||
let mut path = self.path.clone();
|
||||
path.push("client.json");
|
||||
|
||||
let file = OpenOptions::new().read(true).open(path)?;
|
||||
|
@ -28,8 +38,8 @@ impl StateStore for JsonStore {
|
|||
serde_json::from_reader(reader).map_err(Error::from)
|
||||
}
|
||||
|
||||
async fn load_room_state(&self, path: &Path, room_id: &RoomId) -> Result<Room> {
|
||||
let mut path = path.to_path_buf();
|
||||
async fn load_room_state(&self, room_id: &RoomId) -> Result<Room> {
|
||||
let mut path = self.path.clone();
|
||||
path.push(&format!("rooms/{}.json", room_id));
|
||||
|
||||
let file = OpenOptions::new().read(true).open(path)?;
|
||||
|
@ -37,8 +47,8 @@ impl StateStore for JsonStore {
|
|||
serde_json::from_reader(reader).map_err(Error::from)
|
||||
}
|
||||
|
||||
async fn load_all_rooms(&self, path: &Path) -> Result<HashMap<RoomId, Room>> {
|
||||
let mut path = path.to_path_buf();
|
||||
async fn load_all_rooms(&self) -> Result<HashMap<RoomId, Room>> {
|
||||
let mut path = self.path.clone();
|
||||
path.push("rooms");
|
||||
|
||||
let mut rooms_map = HashMap::new();
|
||||
|
@ -61,8 +71,8 @@ impl StateStore for JsonStore {
|
|||
Ok(rooms_map)
|
||||
}
|
||||
|
||||
async fn store_client_state(&self, path: &Path, state: ClientState) -> Result<()> {
|
||||
let mut path = path.to_path_buf();
|
||||
async fn store_client_state(&self, state: ClientState) -> Result<()> {
|
||||
let mut path = self.path.clone();
|
||||
path.push("client.json");
|
||||
|
||||
if !Path::new(&path).exists() {
|
||||
|
@ -84,8 +94,8 @@ impl StateStore for JsonStore {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn store_room_state(&self, path: &Path, room: &Room) -> Result<()> {
|
||||
let mut path = path.to_path_buf();
|
||||
async fn store_room_state(&self, room: &Room) -> Result<()> {
|
||||
let mut path = self.path.clone();
|
||||
path.push(&format!("rooms/{}.json", room.room_id));
|
||||
|
||||
if !Path::new(&path).exists() {
|
||||
|
@ -153,10 +163,11 @@ mod test {
|
|||
}
|
||||
|
||||
async fn test_store_client_state() {
|
||||
let store = JsonStore;
|
||||
let path: &Path = &PATH;
|
||||
let store = JsonStore::open(path).unwrap();
|
||||
let state = ClientState::default();
|
||||
store.store_client_state(&PATH, state).await.unwrap();
|
||||
let loaded = store.load_client_state(&PATH).await.unwrap();
|
||||
store.store_client_state(state).await.unwrap();
|
||||
let loaded = store.load_client_state().await.unwrap();
|
||||
assert_eq!(loaded, ClientState::default());
|
||||
}
|
||||
|
||||
|
@ -166,14 +177,15 @@ mod test {
|
|||
}
|
||||
|
||||
async fn test_store_room_state() {
|
||||
let store = JsonStore;
|
||||
let path: &Path = &PATH;
|
||||
let store = JsonStore::open(path).unwrap();
|
||||
|
||||
let id = RoomId::try_from("!roomid:example.com").unwrap();
|
||||
let user = UserId::try_from("@example:example.com").unwrap();
|
||||
|
||||
let room = Room::new(&id, &user);
|
||||
store.store_room_state(&PATH, &room).await.unwrap();
|
||||
let loaded = store.load_room_state(&PATH, &id).await.unwrap();
|
||||
store.store_room_state(&room).await.unwrap();
|
||||
let loaded = store.load_room_state(&id).await.unwrap();
|
||||
assert_eq!(loaded, Room::new(&id, &user));
|
||||
}
|
||||
|
||||
|
@ -183,14 +195,15 @@ mod test {
|
|||
}
|
||||
|
||||
async fn test_load_rooms() {
|
||||
let store = JsonStore;
|
||||
let path: &Path = &PATH;
|
||||
let store = JsonStore::open(path).unwrap();
|
||||
|
||||
let id = RoomId::try_from("!roomid:example.com").unwrap();
|
||||
let user = UserId::try_from("@example:example.com").unwrap();
|
||||
|
||||
let room = Room::new(&id, &user);
|
||||
store.store_room_state(&PATH, &room).await.unwrap();
|
||||
let loaded = store.load_all_rooms(&PATH).await.unwrap();
|
||||
store.store_room_state(&room).await.unwrap();
|
||||
let loaded = store.load_all_rooms().await.unwrap();
|
||||
assert_eq!(&room, loaded.get(&id).unwrap());
|
||||
}
|
||||
|
||||
|
@ -221,20 +234,19 @@ mod test {
|
|||
.with_body_from_file("tests/data/login_response.json")
|
||||
.create();
|
||||
|
||||
let mut path = PATH.clone();
|
||||
path.push(session.user_id.to_string());
|
||||
// a sync response to populate our JSON store with user_id added to path
|
||||
let config = AsyncClientConfig::default().state_store_path(&path);
|
||||
let path: &Path = &PATH;
|
||||
// a sync response to populate our JSON store
|
||||
let config =
|
||||
AsyncClientConfig::default().state_store(Box::new(JsonStore::open(path).unwrap()));
|
||||
let client =
|
||||
AsyncClient::new_with_config(homeserver.clone(), Some(session.clone()), config)
|
||||
.unwrap();
|
||||
let sync_settings = SyncSettings::new().timeout(std::time::Duration::from_millis(3000));
|
||||
let _ = client.sync(sync_settings).await.unwrap();
|
||||
|
||||
// remove user_id as login will set this
|
||||
path.pop();
|
||||
// once logged in without syncing the client is updated from the state store
|
||||
let config = AsyncClientConfig::default().state_store_path(&path);
|
||||
let config =
|
||||
AsyncClientConfig::default().state_store(Box::new(JsonStore::open(path).unwrap()));
|
||||
let client = AsyncClient::new_with_config(homeserver, None, config).unwrap();
|
||||
client
|
||||
.login("example", "wordpass", None, None)
|
||||
|
|
Loading…
Reference in a new issue