From 267453de4d9ee1cc31113f7162142b529d2ff2e8 Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 21 Apr 2020 09:36:59 -0400 Subject: [PATCH] state_store: add Path to StateStore methods, remove associated Error type --- examples/command_bot.rs | 8 +- examples/login.rs | 2 +- src/async_client.rs | 14 +-- src/base_client.rs | 10 +-- src/event_emitter/mod.rs | 2 +- src/models/room.rs | 2 +- src/models/room_member.rs | 2 +- src/request_builder.rs | 4 +- src/state/mod.rs | 17 ++-- src/state/state_store.rs | 164 +++++++++++++++++------------------- src/test_builder.rs | 30 +++---- tests/async_client_tests.rs | 6 +- 12 files changed, 123 insertions(+), 138 deletions(-) diff --git a/examples/command_bot.rs b/examples/command_bot.rs index 0cf607ba0..40aed4586 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -13,13 +13,13 @@ 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 two type parameters are for the `StateStore` trait and specify the `Store` - /// type and `IoError` type to use, here we don't care. - client: AsyncClient<(), ()>, + /// The type parameter is for the `StateStore` trait specifying the `Store` + /// type for state storage, here we don't care. + client: AsyncClient<()>, } impl CommandBot { - pub fn new(client: AsyncClient<(), ()>) -> Self { + pub fn new(client: AsyncClient<()>) -> Self { Self { client } } } diff --git a/examples/login.rs b/examples/login.rs index 4521332ce..9fbe4e161 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -46,7 +46,7 @@ async fn login( .disable_ssl_verification(); let homeserver_url = Url::parse(&homeserver_url)?; let mut client = - AsyncClient::<(), ()>::new_with_config(homeserver_url, None, client_config).unwrap(); + AsyncClient::<()>::new_with_config(homeserver_url, None, client_config).unwrap(); client.add_event_emitter(Box::new(EventCallback)).await; diff --git a/src/async_client.rs b/src/async_client.rs index fae92458f..7a0f14557 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -55,16 +55,16 @@ const DEFAULT_SYNC_TIMEOUT: Duration = Duration::from_secs(30); /// An async/await enabled Matrix client. /// /// All of the state is held in an `Arc` so the `AsyncClient` can be cloned freely. -pub struct AsyncClient { +pub struct AsyncClient { /// The URL of the homeserver to connect to. homeserver: Url, /// The underlying HTTP client. http_client: reqwest::Client, /// User session data. - pub(crate) base_client: Arc>>, + pub(crate) base_client: Arc>>, } -impl std::fmt::Debug for AsyncClient { +impl std::fmt::Debug for AsyncClient { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> StdResult<(), std::fmt::Error> { write!(fmt, "AsyncClient {{ homeserver: {} }}", self.homeserver) } @@ -197,7 +197,7 @@ use api::r0::room::create_room; use api::r0::session::login; use api::r0::sync::sync_events; -impl AsyncClient { +impl AsyncClient { /// Creates a new client for making HTTP requests to the given homeserver. /// /// # Arguments @@ -1119,7 +1119,7 @@ mod test { device_id: "DEVICEID".to_owned(), }; let homeserver = url::Url::parse(&mockito::server_url()).unwrap(); - let client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::<()>::new(homeserver, Some(session)).unwrap(); let rid = RoomId::try_from("!roomid:room.com").unwrap(); let uid = UserId::try_from("@example:localhost").unwrap(); @@ -1151,7 +1151,7 @@ mod test { }; let homeserver = url::Url::parse(&mockito::server_url()).unwrap(); - let client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::<()>::new(homeserver, Some(session)).unwrap(); let mut bld = EventBuilder::default() .add_room_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember) @@ -1181,7 +1181,7 @@ mod test { .with_body_from_file("tests/data/login_response_error.json") .create(); - let client = AsyncClient::<(), ()>::new(homeserver, None).unwrap(); + let client = AsyncClient::<()>::new(homeserver, None).unwrap(); if let Err(err) = client.login("example", "wordpass", None, None).await { if let crate::Error::RumaResponse(ruma_api::error::FromHttpResponseError::Http( diff --git a/src/base_client.rs b/src/base_client.rs index 1655fe869..5fce01fde 100644 --- a/src/base_client.rs +++ b/src/base_client.rs @@ -61,7 +61,7 @@ pub type Token = String; /// /// This Client is a state machine that receives responses and events and /// accordingly updates it's state. -pub struct Client { +pub struct Client { /// The current client session containing our user id, device id and access /// token. pub session: Option, @@ -77,13 +77,13 @@ pub struct Client { /// events. pub event_emitter: Option>, /// - pub state_store: Option>>, + pub state_store: Option>>, #[cfg(feature = "encryption")] olm: Arc>>, } -impl fmt::Debug for Client { +impl fmt::Debug for Client { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Client") .field("session", &self.session) @@ -96,7 +96,7 @@ impl fmt::Debug for Client { } } -impl Client { +impl Client { /// Create a new client. /// /// # Arguments @@ -815,7 +815,7 @@ mod test { .with_body_from_file("tests/data/sync.json") .create(); - let client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::<()>::new(homeserver, Some(session)).unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); diff --git a/src/event_emitter/mod.rs b/src/event_emitter/mod.rs index 369f73be3..4ff8b265c 100644 --- a/src/event_emitter/mod.rs +++ b/src/event_emitter/mod.rs @@ -238,7 +238,7 @@ mod test { let vec = Arc::new(Mutex::new(Vec::new())); let test_vec = Arc::clone(&vec); let emitter = Box::new(EvEmitterTest(vec)) as Box<(dyn EventEmitter)>; - let mut client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); + let mut client = AsyncClient::<()>::new(homeserver, Some(session)).unwrap(); client.add_event_emitter(emitter).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); diff --git a/src/models/room.rs b/src/models/room.rs index c8d99ebdb..b5979e5ac 100644 --- a/src/models/room.rs +++ b/src/models/room.rs @@ -439,7 +439,7 @@ mod test { .with_body_from_file("tests/data/sync.json") .create(); - let client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::<()>::new(homeserver, Some(session)).unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); diff --git a/src/models/room_member.rs b/src/models/room_member.rs index b53d660ca..4f9db7245 100644 --- a/src/models/room_member.rs +++ b/src/models/room_member.rs @@ -67,7 +67,7 @@ pub struct RoomMember { impl PartialEq for RoomMember { fn eq(&self, other: &RoomMember) -> bool { - // TODO check everything but events and presence_events they don;t impl PartialEq + // TODO check everything but events and presence_events they don't impl PartialEq self.room_id == other.room_id && self.user_id == other.user_id && self.name == other.name diff --git a/src/request_builder.rs b/src/request_builder.rs index d76a51796..15ed1ee10 100644 --- a/src/request_builder.rs +++ b/src/request_builder.rs @@ -341,7 +341,7 @@ mod test { .room_alias_name("room_alias") .topic("room topic") .visibility(Visibility::Private); - let cli = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); + let cli = AsyncClient::<()>::new(homeserver, Some(session)).unwrap(); assert!(cli.create_room(builder).await.is_ok()); } @@ -373,7 +373,7 @@ mod test { // TODO this makes ruma error `Err(IntoHttp(IntoHttpError(Query(Custom("unsupported value")))))`?? // .filter(RoomEventFilter::default()); - let cli = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); + let cli = AsyncClient::<()>::new(homeserver, Some(session)).unwrap(); assert!(cli.room_messages(builder).await.is_ok()); } } diff --git a/src/state/mod.rs b/src/state/mod.rs index 087fc76bd..18b07e011 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -26,7 +26,7 @@ use crate::events::push_rules::Ruleset; use crate::identifiers::{RoomId, UserId}; use crate::models::Room; use crate::session::Session; - +use crate::Result; #[derive(Debug, Default, PartialEq, Serialize, Deserialize)] pub struct ClientState { /// The current client session containing our user id, device id and access @@ -46,23 +46,20 @@ pub trait StateStore: Send + Sync { /// to serialize and deserialize state to JSON files. type Store; - /// The error type to return. - type IoError; - /// Set up connections or open files to load/save state. - fn open(&self, path: &Path) -> Result<(), Self::IoError>; + fn open(&self, path: &Path) -> Result<()>; /// Loads the state of `BaseClient` through `StateStore::Store` type. - fn load_client_state(&self) -> Result; + fn load_client_state(&self, path: &Path) -> Result; /// Load the state of a single `Room` by `RoomId`. - fn load_room_state(&self, room_id: &RoomId) -> Result; + fn load_room_state(&self, path: &Path, room_id: &RoomId) -> Result; /// 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. - fn load_all_rooms(&self) -> Result, Self::IoError>; + fn load_all_rooms(&self, path: &Path) -> Result>; /// Save the current state of the `BaseClient` using the `StateStore::Store` type. - fn store_client_state(&self, _: Self::Store) -> Result<(), Self::IoError>; + fn store_client_state(&self, path: &Path, _: Self::Store) -> Result<()>; /// Save the state a single `Room`. - fn store_room_state(&self, _: &Room) -> Result<(), Self::IoError>; + fn store_room_state(&self, path: &Path, _: &Room) -> Result<()>; } #[cfg(test)] diff --git a/src/state/state_store.rs b/src/state/state_store.rs index 8e9fda09f..b5ef6617b 100644 --- a/src/state/state_store.rs +++ b/src/state/state_store.rs @@ -1,7 +1,7 @@ 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; @@ -11,7 +11,6 @@ use crate::{Error, Result, Room}; pub struct JsonStore; impl StateStore for JsonStore { - type IoError = Error; type Store = ClientState; fn open(&self, path: &Path) -> Result<()> { @@ -20,98 +19,84 @@ impl StateStore for JsonStore { } Ok(()) } - fn load_client_state(&self) -> Result { - if let Some(mut path) = dirs::home_dir() { - path.push(".matrix_store/client.json"); - let file = OpenOptions::new().read(true).open(path)?; - let reader = BufReader::new(file); - serde_json::from_reader(reader).map_err(Error::from) - } else { - todo!("Error maybe") - } + fn load_client_state(&self, path: &Path) -> Result { + let mut path = path.to_path_buf(); + path.push("client.json"); + + let file = OpenOptions::new().read(true).open(path)?; + let reader = BufReader::new(file); + serde_json::from_reader(reader).map_err(Error::from) } - fn load_room_state(&self, room_id: &RoomId) -> Result { - if let Some(mut path) = dirs::home_dir() { - path.push(&format!(".matrix_store/rooms/{}.json", room_id)); + fn load_room_state(&self, path: &Path, room_id: &RoomId) -> Result { + let mut path = path.to_path_buf(); + path.push(&format!("rooms/{}.json", room_id)); - let file = OpenOptions::new().read(true).open(path)?; - let reader = BufReader::new(file); - serde_json::from_reader(reader).map_err(Error::from) - } else { - todo!("Error maybe") - } + let file = OpenOptions::new().read(true).open(path)?; + let reader = BufReader::new(file); + serde_json::from_reader(reader).map_err(Error::from) } - fn load_all_rooms(&self) -> Result> { - if let Some(mut path) = dirs::home_dir() { - path.push(".matrix_store/rooms/"); + fn load_all_rooms(&self, path: &Path) -> Result> { + let mut path = path.to_path_buf(); + path.push("rooms"); - let mut rooms_map = HashMap::new(); - for file in fs::read_dir(&path)? { - let file = file?.path(); + let mut rooms_map = HashMap::new(); + for file in fs::read_dir(&path)? { + let file = file?.path(); - if file.is_dir() { - continue; - } - - let f_hdl = OpenOptions::new().read(true).open(&file)?; - let reader = BufReader::new(f_hdl); - - let room = serde_json::from_reader::<_, Room>(reader).map_err(Error::from)?; - let room_id = room.room_id.clone(); - - rooms_map.insert(room_id, room); + if file.is_dir() { + continue; } - Ok(rooms_map) - } else { - todo!("Error maybe") + let f_hdl = OpenOptions::new().read(true).open(&file)?; + let reader = BufReader::new(f_hdl); + + let room = serde_json::from_reader::<_, Room>(reader).map_err(Error::from)?; + let room_id = room.room_id.clone(); + + rooms_map.insert(room_id, room); } + + Ok(rooms_map) } - fn store_client_state(&self, state: ClientState) -> Result<()> { - if let Some(mut path) = dirs::home_dir() { - path.push(".matrix_store/client.json"); + fn store_client_state(&self, path: &Path, state: ClientState) -> Result<()> { + let mut path = path.to_path_buf(); + path.push("client.json"); - if !Path::new(&path).exists() { - let mut dir = path.clone(); - dir.pop(); - std::fs::create_dir_all(dir)?; - } - - let json = serde_json::to_string(&state).map_err(Error::from)?; - - let file = OpenOptions::new().write(true).create(true).open(path)?; - let mut writer = BufWriter::new(file); - writer.write_all(json.as_bytes())?; - - Ok(()) - } else { - todo!("Error maybe") + if !Path::new(&path).exists() { + let mut dir = path.clone(); + dir.pop(); + std::fs::create_dir_all(dir)?; } + + let json = serde_json::to_string(&state).map_err(Error::from)?; + + let file = OpenOptions::new().write(true).create(true).open(path)?; + let mut writer = BufWriter::new(file); + writer.write_all(json.as_bytes())?; + + Ok(()) } - fn store_room_state(&self, room: &Room) -> Result<()> { - if let Some(mut path) = dirs::home_dir() { - path.push(&format!(".matrix_store/rooms/{}.json", room.room_id)); + fn store_room_state(&self, path: &Path, room: &Room) -> Result<()> { + let mut path = path.to_path_buf(); + path.push(&format!("rooms/{}.json", room.room_id)); - if !Path::new(&path).exists() { - let mut dir = path.clone(); - dir.pop(); - std::fs::create_dir_all(dir)?; - } - - let json = serde_json::to_string(&room).map_err(Error::from)?; - - let file = OpenOptions::new().write(true).create(true).open(path)?; - let mut writer = BufWriter::new(file); - writer.write_all(json.as_bytes())?; - - Ok(()) - } else { - todo!("Error maybe") + if !Path::new(&path).exists() { + let mut dir = path.clone(); + dir.pop(); + std::fs::create_dir_all(dir)?; } + + let json = serde_json::to_string(&room).map_err(Error::from)?; + + let file = OpenOptions::new().write(true).create(true).open(path)?; + let mut writer = BufWriter::new(file); + writer.write_all(json.as_bytes())?; + + Ok(()) } } @@ -132,15 +117,22 @@ mod test { pub static ref MTX: Mutex<()> = Mutex::new(()); } + lazy_static! { + /// Limit io tests to one thread at a time. + pub static ref PATH: PathBuf = { + let mut path = dirs::home_dir().unwrap(); + path.push(".matrix_store"); + path + }; + } + fn run_and_cleanup(test: fn()) { let _lock = MTX.lock(); test(); - let mut path = dirs::home_dir().unwrap(); - path.push(".matrix_store"); - - if path.exists() { + if PATH.exists() { + let path: &Path = &PATH; fs::remove_dir_all(path).unwrap(); } } @@ -148,8 +140,8 @@ mod test { fn test_store_client_state() { let store = JsonStore; let state = ClientState::default(); - store.store_client_state(state).unwrap(); - let loaded = store.load_client_state().unwrap(); + store.store_client_state(&PATH, state).unwrap(); + let loaded = store.load_client_state(&PATH).unwrap(); assert_eq!(loaded, ClientState::default()); } @@ -165,8 +157,8 @@ mod test { let user = UserId::try_from("@example:example.com").unwrap(); let room = Room::new(&id, &user); - store.store_room_state(&room).unwrap(); - let loaded = store.load_room_state(&id).unwrap(); + store.store_room_state(&PATH, &room).unwrap(); + let loaded = store.load_room_state(&PATH, &id).unwrap(); assert_eq!(loaded, Room::new(&id, &user)); } @@ -182,8 +174,8 @@ mod test { let user = UserId::try_from("@example:example.com").unwrap(); let room = Room::new(&id, &user); - store.store_room_state(&room).unwrap(); - let loaded = store.load_all_rooms().unwrap(); + store.store_room_state(&PATH, &room).unwrap(); + let loaded = store.load_all_rooms(&PATH).unwrap(); println!("{:?}", loaded); } diff --git a/src/test_builder.rs b/src/test_builder.rs index edcff2556..b6ef8cbda 100644 --- a/src/test_builder.rs +++ b/src/test_builder.rs @@ -49,9 +49,9 @@ pub struct RoomTestRunner { state_events: Vec, } -pub struct ClientTestRunner { +pub struct ClientTestRunner { /// Used when testing the whole client - client: Option>, + client: Option>, /// RoomId and UserId to use for the events. /// /// The RoomId must match the RoomId of the events to track. @@ -69,9 +69,9 @@ pub struct ClientTestRunner { } #[allow(dead_code)] -pub struct MockTestRunner { +pub struct MockTestRunner { /// Used when testing the whole client - client: Option>, + client: Option>, /// The ephemeral room events that determine the state of a `Room`. ephemeral: Vec, /// The account data events that determine the state of a `Room`. @@ -169,11 +169,11 @@ impl EventBuilder { /// /// The `TestRunner` streams the events to the client and holds methods to make assertions /// about the state of the client. - pub fn build_mock_runner>( + pub fn build_mock_runner>( mut self, method: &str, path: P, - ) -> MockTestRunner { + ) -> MockTestRunner { let body = serde_json::json! { { "device_one_time_keys_count": {}, @@ -238,11 +238,7 @@ impl EventBuilder { /// /// The `TestRunner` streams the events to the `AsyncClient` and holds methods to make assertions /// about the state of the `AsyncClient`. - pub fn build_client_runner( - self, - room_id: RoomId, - user_id: UserId, - ) -> ClientTestRunner { + pub fn build_client_runner(self, room_id: RoomId, user_id: UserId) -> ClientTestRunner { ClientTestRunner { client: None, room_user_id: (room_id, user_id), @@ -317,8 +313,8 @@ impl RoomTestRunner { } } -impl ClientTestRunner { - pub fn set_client(&mut self, client: AsyncClient) -> &mut Self { +impl ClientTestRunner { + pub fn set_client(&mut self, client: AsyncClient) -> &mut Self { self.client = Some(client); self } @@ -359,14 +355,14 @@ impl ClientTestRunner { } } - pub async fn to_client(&mut self) -> &mut AsyncClient { + pub async fn to_client(&mut self) -> &mut AsyncClient { self.stream_client_events().await; self.client.as_mut().unwrap() } } -impl MockTestRunner { - pub fn set_client(&mut self, client: AsyncClient) -> &mut Self { +impl MockTestRunner { + pub fn set_client(&mut self, client: AsyncClient) -> &mut Self { self.client = Some(client); self } @@ -376,7 +372,7 @@ impl MockTestRunner { self } - pub async fn to_client(&mut self) -> Result<&mut AsyncClient, crate::Error> { + pub async fn to_client(&mut self) -> Result<&mut AsyncClient, crate::Error> { self.client .as_mut() .unwrap() diff --git a/tests/async_client_tests.rs b/tests/async_client_tests.rs index fe68f9ae2..9e27beda9 100644 --- a/tests/async_client_tests.rs +++ b/tests/async_client_tests.rs @@ -17,7 +17,7 @@ async fn login() { .with_body_from_file("tests/data/login_response.json") .create(); - let client = AsyncClient::<(), ()>::new(homeserver, None).unwrap(); + let client = AsyncClient::<()>::new(homeserver, None).unwrap(); client .login("example", "wordpass", None, None) @@ -46,7 +46,7 @@ async fn sync() { .with_body_from_file("tests/data/sync.json") .create(); - let client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::<()>::new(homeserver, Some(session)).unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -75,7 +75,7 @@ async fn room_names() { .with_body_from_file("tests/data/sync.json") .create(); - let client = AsyncClient::<(), ()>::new(homeserver, Some(session)).unwrap(); + let client = AsyncClient::<()>::new(homeserver, Some(session)).unwrap(); let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));