state_store: load_client takes a sessions obj, docs, copyright, move state store into base client
parent
83f3fc6796
commit
ef560fd545
|
@ -675,8 +675,8 @@ impl AsyncClient {
|
||||||
let decrypted_event = {
|
let decrypted_event = {
|
||||||
let mut client = self.base_client.write().await;
|
let mut client = self.base_client.write().await;
|
||||||
let mut timeline_update = false;
|
let mut timeline_update = false;
|
||||||
let decrypt_ev = client
|
let (decrypt_ev, timeline_update) = client
|
||||||
.receive_joined_timeline_event(room_id, &mut event, &mut timeline_update)
|
.receive_joined_timeline_event(room_id, &mut event)
|
||||||
.await;
|
.await;
|
||||||
if timeline_update {
|
if timeline_update {
|
||||||
updated = true;
|
updated = true;
|
||||||
|
@ -746,7 +746,7 @@ impl AsyncClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut client = self.base_client.write().await;
|
let mut client = self.base_client.write().await;
|
||||||
client.receive_sync_response(&mut response).await;
|
client.receive_sync_response(&mut response, updated).await?;
|
||||||
|
|
||||||
if updated {
|
if updated {
|
||||||
if let Some(store) = client.state_store.as_ref() {
|
if let Some(store) = client.state_store.as_ref() {
|
||||||
|
|
|
@ -76,7 +76,10 @@ pub struct Client {
|
||||||
/// Any implementor of EventEmitter will act as the callbacks for various
|
/// Any implementor of EventEmitter will act as the callbacks for various
|
||||||
/// events.
|
/// events.
|
||||||
pub event_emitter: Option<Box<dyn EventEmitter>>,
|
pub event_emitter: Option<Box<dyn EventEmitter>>,
|
||||||
|
/// Any implementor of `StateStore` will be called to save `Room` and
|
||||||
|
/// some `BaseClient` state during `AsyncClient::sync` calls.
|
||||||
///
|
///
|
||||||
|
/// There is a default implementation `JsonStore` that saves JSON to disk.
|
||||||
pub state_store: Option<Box<dyn StateStore>>,
|
pub state_store: Option<Box<dyn StateStore>>,
|
||||||
/// Does the `Client` need to sync with the state store.
|
/// Does the `Client` need to sync with the state store.
|
||||||
needs_state_store_sync: bool,
|
needs_state_store_sync: bool,
|
||||||
|
@ -148,41 +151,42 @@ impl Client {
|
||||||
/// Returns `true` when a sync has successfully completed.
|
/// Returns `true` when a sync has successfully completed.
|
||||||
pub(crate) async fn sync_with_state_store(&mut self) -> Result<bool> {
|
pub(crate) async fn sync_with_state_store(&mut self) -> Result<bool> {
|
||||||
if let Some(store) = self.state_store.as_ref() {
|
if let Some(store) = self.state_store.as_ref() {
|
||||||
if let Some(client_state) = store.load_client_state().await? {
|
if let Some(sess) = self.session.as_ref() {
|
||||||
let ClientState {
|
if let Some(client_state) = store.load_client_state(sess).await? {
|
||||||
user_id,
|
let ClientState {
|
||||||
device_id,
|
user_id,
|
||||||
sync_token,
|
device_id,
|
||||||
ignored_users,
|
sync_token,
|
||||||
push_ruleset,
|
ignored_users,
|
||||||
} = client_state;
|
push_ruleset,
|
||||||
|
} = client_state;
|
||||||
|
|
||||||
if let Some(sess) = self.session.as_mut() {
|
if let Some(sess) = self.session.as_mut() {
|
||||||
if let Some(device) = device_id {
|
if let Some(device) = device_id {
|
||||||
sess.device_id = device;
|
sess.device_id = device;
|
||||||
}
|
}
|
||||||
if let Some(user) = user_id {
|
if let Some(user) = user_id {
|
||||||
sess.user_id = user;
|
sess.user_id = user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
self.sync_token = sync_token;
|
||||||
|
self.ignored_users = ignored_users;
|
||||||
|
self.push_ruleset = push_ruleset;
|
||||||
|
} else {
|
||||||
|
// return false and continues with a sync request then save the state and create
|
||||||
|
// and populate the files during the sync
|
||||||
|
return Ok(false);
|
||||||
}
|
}
|
||||||
self.sync_token = sync_token;
|
|
||||||
self.ignored_users = ignored_users;
|
let mut rooms = store.load_all_rooms().await?;
|
||||||
self.push_ruleset = push_ruleset;
|
self.joined_rooms = rooms
|
||||||
} else {
|
.drain()
|
||||||
// return false and continues with a sync request then save the state and create
|
.map(|(k, room)| (k, Arc::new(RwLock::new(room))))
|
||||||
// and populate the files during the sync
|
.collect();
|
||||||
return Ok(false);
|
|
||||||
|
self.needs_state_store_sync = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut rooms = store.load_all_rooms().await?;
|
|
||||||
self.joined_rooms = rooms
|
|
||||||
.drain()
|
|
||||||
.map(|(k, room)| (k, Arc::new(RwLock::new(room))))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
self.needs_state_store_sync = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(!self.needs_state_store_sync)
|
Ok(!self.needs_state_store_sync)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,23 +282,19 @@ impl Client {
|
||||||
|
|
||||||
/// Receive a timeline event for a joined room and update the client state.
|
/// Receive a timeline event for a joined room and update the client state.
|
||||||
///
|
///
|
||||||
/// If the event was a encrypted room event and decryption was successful
|
/// Returns a tuple of the successfully decrypted event, or None on failure and
|
||||||
/// the decrypted event will be returned, otherwise None.
|
/// a bool, true when the `Room` state has been updated.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `room_id` - The unique id of the room the event belongs to.
|
/// * `room_id` - The unique id of the room the event belongs to.
|
||||||
///
|
///
|
||||||
/// * `event` - The event that should be handled by the client.
|
/// * `event` - The event that should be handled by the client.
|
||||||
///
|
|
||||||
/// * `did_update` - This is used internally to confirm when the state has
|
|
||||||
/// been updated.
|
|
||||||
pub async fn receive_joined_timeline_event(
|
pub async fn receive_joined_timeline_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
event: &mut EventJson<RoomEvent>,
|
event: &mut EventJson<RoomEvent>,
|
||||||
did_update: &mut bool,
|
) -> (Option<EventJson<RoomEvent>>, bool) {
|
||||||
) -> Option<EventJson<RoomEvent>> {
|
|
||||||
match event.deserialize() {
|
match event.deserialize() {
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
Ok(mut e) => {
|
Ok(mut e) => {
|
||||||
|
@ -319,11 +319,9 @@ impl Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut room = self.get_or_create_room(&room_id).write().await;
|
let mut room = self.get_or_create_room(&room_id).write().await;
|
||||||
// TODO is passing in the bool to use in `AsyncClient::sync` ok here
|
(decrypted_event, room.receive_timeline_event(&e))
|
||||||
*did_update = room.receive_timeline_event(&e);
|
|
||||||
decrypted_event
|
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => (None, false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,7 +417,13 @@ impl Client {
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `response` - The response that we received after a successful sync.
|
/// * `response` - The response that we received after a successful sync.
|
||||||
pub async fn receive_sync_response(&mut self, response: &mut api::sync::sync_events::Response) {
|
///
|
||||||
|
/// * `did_update` - Signals to the `StateStore` if the client state needs updating.
|
||||||
|
pub async fn receive_sync_response(
|
||||||
|
&mut self,
|
||||||
|
response: &mut api::sync::sync_events::Response,
|
||||||
|
did_update: bool,
|
||||||
|
) -> Result<()> {
|
||||||
self.sync_token = Some(response.next_batch.clone());
|
self.sync_token = Some(response.next_batch.clone());
|
||||||
|
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
|
@ -442,6 +446,14 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if did_update {
|
||||||
|
if let Some(store) = self.state_store.as_ref() {
|
||||||
|
let state = ClientState::from_base_client(&self);
|
||||||
|
store.store_client_state(state).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should account or one-time keys be uploaded to the server.
|
/// Should account or one-time keys be uploaded to the server.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Damir Jelić
|
// Copyright 2020 Devin Ragotzy
|
||||||
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -23,8 +23,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::base_client::{Client as BaseClient, Token};
|
use crate::base_client::{Client as BaseClient, Token};
|
||||||
use crate::events::push_rules::Ruleset;
|
use crate::events::push_rules::Ruleset;
|
||||||
use crate::identifiers::{DeviceId, RoomId, UserId};
|
use crate::identifiers::{DeviceId, RoomId, UserId};
|
||||||
use crate::models::Room;
|
use crate::{Result, Room, Session};
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
/// `ClientState` holds all the information to restore a `BaseClient`
|
/// `ClientState` holds all the information to restore a `BaseClient`
|
||||||
/// except the `access_token` as the default store is not secure.
|
/// except the `access_token` as the default store is not secure.
|
||||||
|
@ -72,7 +71,7 @@ pub trait StateStore: Send + Sync {
|
||||||
///
|
///
|
||||||
/// An `Option::None` should be returned only if the `StateStore` tries to
|
/// An `Option::None` should be returned only if the `StateStore` tries to
|
||||||
/// load but no state has been stored.
|
/// load but no state has been stored.
|
||||||
async fn load_client_state(&self) -> Result<Option<ClientState>>;
|
async fn load_client_state(&self, _: &Session) -> Result<Option<ClientState>>;
|
||||||
/// Load the state of all `Room`s.
|
/// 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.
|
/// This will be mapped over in the client in order to store `Room`s in an async safe way.
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::{self, OpenOptions};
|
use std::fs;
|
||||||
use std::io::{BufReader, BufWriter, Write};
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
|
@ -8,16 +7,17 @@ use std::sync::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use tokio::fs as async_fs;
|
use tokio::fs as async_fs;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use super::{ClientState, StateStore};
|
use super::{ClientState, StateStore};
|
||||||
use crate::identifiers::RoomId;
|
use crate::identifiers::RoomId;
|
||||||
use crate::{Error, Result, Room};
|
use crate::{Error, Result, Room, Session};
|
||||||
/// A default `StateStore` implementation that serializes state as json
|
/// A default `StateStore` implementation that serializes state as json
|
||||||
/// and saves it to disk.
|
/// and saves it to disk.
|
||||||
///
|
///
|
||||||
/// When logged in the `JsonStore` appends the user_id to it's folder path,
|
/// When logged in the `JsonStore` appends the user_id to it's folder path,
|
||||||
/// so all files are saved in `my_client/user_id/*`.
|
/// so all files are saved in `my_client/user_id_localpart/*`.
|
||||||
pub struct JsonStore {
|
pub struct JsonStore {
|
||||||
path: Arc<RwLock<PathBuf>>,
|
path: Arc<RwLock<PathBuf>>,
|
||||||
user_path_set: AtomicBool,
|
user_path_set: AtomicBool,
|
||||||
|
@ -41,7 +41,12 @@ impl JsonStore {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl StateStore for JsonStore {
|
impl StateStore for JsonStore {
|
||||||
async fn load_client_state(&self) -> Result<Option<ClientState>> {
|
async fn load_client_state(&self, sess: &Session) -> Result<Option<ClientState>> {
|
||||||
|
if !self.user_path_set.load(Ordering::SeqCst) {
|
||||||
|
self.user_path_set.swap(true, Ordering::SeqCst);
|
||||||
|
self.path.write().await.push(sess.user_id.localpart())
|
||||||
|
}
|
||||||
|
|
||||||
let mut path = self.path.read().await.clone();
|
let mut path = self.path.read().await.clone();
|
||||||
path.push("client.json");
|
path.push("client.json");
|
||||||
|
|
||||||
|
@ -67,10 +72,9 @@ impl StateStore for JsonStore {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let f_hdl = OpenOptions::new().read(true).open(&file)?;
|
let json = async_fs::read_to_string(&file).await?;
|
||||||
let reader = BufReader::new(f_hdl);
|
|
||||||
|
|
||||||
let room = serde_json::from_reader::<_, Room>(reader).map_err(Error::from)?;
|
let room = serde_json::from_str::<Room>(&json).map_err(Error::from)?;
|
||||||
let room_id = room.room_id.clone();
|
let room_id = room.room_id.clone();
|
||||||
|
|
||||||
rooms_map.insert(room_id, room);
|
rooms_map.insert(room_id, room);
|
||||||
|
@ -97,15 +101,13 @@ impl StateStore for JsonStore {
|
||||||
|
|
||||||
let json = serde_json::to_string(&state).map_err(Error::from)?;
|
let json = serde_json::to_string(&state).map_err(Error::from)?;
|
||||||
|
|
||||||
let file = OpenOptions::new()
|
let mut file = async_fs::OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.open(path)?;
|
.open(path)
|
||||||
let mut writer = BufWriter::new(file);
|
.await?;
|
||||||
writer.write_all(json.as_bytes())?;
|
file.write_all(json.as_bytes()).await.map_err(Error::from)
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn store_room_state(&self, room: &Room) -> Result<()> {
|
async fn store_room_state(&self, room: &Room) -> Result<()> {
|
||||||
|
@ -125,15 +127,13 @@ impl StateStore for JsonStore {
|
||||||
|
|
||||||
let json = serde_json::to_string(&room).map_err(Error::from)?;
|
let json = serde_json::to_string(&room).map_err(Error::from)?;
|
||||||
|
|
||||||
let file = OpenOptions::new()
|
let mut file = async_fs::OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.open(path)?;
|
.open(path)
|
||||||
let mut writer = BufWriter::new(file);
|
.await?;
|
||||||
writer.write_all(json.as_bytes())?;
|
file.write_all(json.as_bytes()).await.map_err(Error::from)
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,6 +187,12 @@ mod test {
|
||||||
|
|
||||||
let user = UserId::try_from("@example:example.com").unwrap();
|
let user = UserId::try_from("@example:example.com").unwrap();
|
||||||
|
|
||||||
|
let sess = Session {
|
||||||
|
access_token: "32nj9zu034btz90".to_string(),
|
||||||
|
user_id: user.clone(),
|
||||||
|
device_id: "Tester".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
let store = JsonStore::open(path).unwrap();
|
let store = JsonStore::open(path).unwrap();
|
||||||
|
|
||||||
let state = ClientState {
|
let state = ClientState {
|
||||||
|
@ -198,7 +204,9 @@ mod test {
|
||||||
};
|
};
|
||||||
|
|
||||||
store.store_client_state(state.clone()).await.unwrap();
|
store.store_client_state(state.clone()).await.unwrap();
|
||||||
let loaded = store.load_client_state().await.unwrap();
|
|
||||||
|
let store = JsonStore::open(path).unwrap();
|
||||||
|
let loaded = store.load_client_state(&sess).await.unwrap();
|
||||||
assert_eq!(loaded, Some(state));
|
assert_eq!(loaded, Some(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -367,7 +367,7 @@ impl ClientTestRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
for event in &self.room_events {
|
for event in &self.room_events {
|
||||||
cli.receive_joined_timeline_event(room_id, &mut EventJson::from(event), &mut false)
|
cli.receive_joined_timeline_event(room_id, &mut EventJson::from(event))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
for event in &self.presence_events {
|
for event in &self.presence_events {
|
||||||
|
|
Loading…
Reference in New Issue