chore: Refactor ServerCapabilities into ServerInfo.

It has nothing to do with /capabilities so is confusing. We can use this new struct to combine the well-known response into a single cache too.
This commit is contained in:
Doug
2025-06-02 16:14:42 +01:00
committed by Damir Jelić
parent ec30e7b85c
commit c74295c604
12 changed files with 141 additions and 151 deletions
+5 -5
View File
@@ -725,11 +725,11 @@ impl Client {
/// Empty the server version and unstable features cache.
///
/// Since the SDK caches server capabilities (versions and unstable
/// features), it's possible to have a stale entry in the cache. This
/// functions makes it possible to force reset it.
pub async fn reset_server_capabilities(&self) -> Result<(), ClientError> {
Ok(self.inner.reset_server_capabilities().await?)
/// Since the SDK caches server info (versions, unstable features,
/// well-known etc), it's possible to have a stale entry in the cache.
/// This functions makes it possible to force reset it.
pub async fn reset_server_info(&self) -> Result<(), ClientError> {
Ok(self.inner.reset_server_info().await?)
}
}
@@ -34,7 +34,7 @@ use serde_json::{json, value::Value as JsonValue};
use super::{
send_queue::SentRequestKey, DependentQueuedRequestKind, DisplayName, DynStateStore,
RoomLoadSettings, ServerCapabilities,
RoomLoadSettings, ServerInfo,
};
use crate::{
deserialized_responses::MemberEvent,
@@ -90,8 +90,8 @@ pub trait StateStoreIntegrationTests {
async fn test_send_queue_dependents(&self);
/// Test an update to a send queue dependent request.
async fn test_update_send_queue_dependent(&self);
/// Test saving/restoring server capabilities.
async fn test_server_capabilities_saving(&self);
/// Test saving/restoring server info.
async fn test_server_info_saving(&self);
/// Test fetching room infos based on [`RoomLoadSettings`].
async fn test_get_room_infos(&self);
}
@@ -472,34 +472,36 @@ impl StateStoreIntegrationTests for DynStateStore {
);
}
async fn test_server_capabilities_saving(&self) {
async fn test_server_info_saving(&self) {
let versions = &[MatrixVersion::V1_1, MatrixVersion::V1_2, MatrixVersion::V1_11];
let server_caps = ServerCapabilities::new(
versions,
let server_info = ServerInfo::new(
versions.iter().map(|version| version.to_string()).collect(),
[("org.matrix.experimental".to_owned(), true)].into(),
);
self.set_kv_data(
StateStoreDataKey::ServerCapabilities,
StateStoreDataValue::ServerCapabilities(server_caps.clone()),
StateStoreDataKey::ServerInfo,
StateStoreDataValue::ServerInfo(server_info.clone()),
)
.await
.unwrap();
assert_let!(
Ok(Some(StateStoreDataValue::ServerCapabilities(stored_caps))) =
self.get_kv_data(StateStoreDataKey::ServerCapabilities).await
Ok(Some(StateStoreDataValue::ServerInfo(stored_info))) =
self.get_kv_data(StateStoreDataKey::ServerInfo).await
);
assert_eq!(stored_caps, server_caps);
assert_eq!(stored_info, server_info);
let (stored_versions, stored_features) = stored_caps.maybe_decode().unwrap();
let decoded_server_info = stored_info.maybe_decode().unwrap();
let stored_versions = decoded_server_info.known_versions();
let stored_features = decoded_server_info.unstable_features;
assert_eq!(stored_versions, versions);
assert_eq!(stored_features.len(), 1);
assert_eq!(stored_features.get("org.matrix.experimental"), Some(&true));
self.remove_kv_data(StateStoreDataKey::ServerCapabilities).await.unwrap();
assert_matches!(self.get_kv_data(StateStoreDataKey::ServerCapabilities).await, Ok(None));
self.remove_kv_data(StateStoreDataKey::ServerInfo).await.unwrap();
assert_matches!(self.get_kv_data(StateStoreDataKey::ServerInfo).await, Ok(None));
}
async fn test_sync_token_saving(&self) {
@@ -1807,9 +1809,9 @@ macro_rules! statestore_integration_tests {
}
#[async_test]
async fn test_server_capabilities_saving() {
async fn test_server_info_saving() {
let store = get_store().await.unwrap().into_state_store();
store.test_server_capabilities_saving().await
store.test_server_info_saving().await
}
#[async_test]
@@ -37,7 +37,7 @@ use tracing::{debug, instrument, warn};
use super::{
send_queue::{ChildTransactionId, QueuedRequest, SentRequestKey},
traits::{ComposerDraft, ServerCapabilities},
traits::{ComposerDraft, ServerInfo},
DependentQueuedRequest, DependentQueuedRequestKind, QueuedRequestKind, Result, RoomInfo,
RoomLoadSettings, StateChanges, StateStore, StoreError,
};
@@ -54,7 +54,7 @@ struct MemoryStoreInner {
composer_drafts: HashMap<(OwnedRoomId, Option<OwnedEventId>), ComposerDraft>,
user_avatar_url: HashMap<OwnedUserId, OwnedMxcUri>,
sync_token: Option<String>,
server_capabilities: Option<ServerCapabilities>,
server_info: Option<ServerInfo>,
filters: HashMap<String, String>,
utd_hook_manager_data: Option<GrowableBloom>,
account_data: HashMap<GlobalAccountDataEventType, Raw<AnyGlobalAccountDataEvent>>,
@@ -149,8 +149,8 @@ impl StateStore for MemoryStore {
StateStoreDataKey::SyncToken => {
inner.sync_token.clone().map(StateStoreDataValue::SyncToken)
}
StateStoreDataKey::ServerCapabilities => {
inner.server_capabilities.clone().map(StateStoreDataValue::ServerCapabilities)
StateStoreDataKey::ServerInfo => {
inner.server_info.clone().map(StateStoreDataValue::ServerInfo)
}
StateStoreDataKey::Filter(filter_name) => {
inner.filters.get(filter_name).cloned().map(StateStoreDataValue::Filter)
@@ -222,11 +222,9 @@ impl StateStore for MemoryStore {
value.into_composer_draft().expect("Session data not a composer draft"),
);
}
StateStoreDataKey::ServerCapabilities => {
inner.server_capabilities = Some(
value
.into_server_capabilities()
.expect("Session data not containing server capabilities"),
StateStoreDataKey::ServerInfo => {
inner.server_info = Some(
value.into_server_info().expect("Session data not containing server info"),
);
}
StateStoreDataKey::SeenKnockRequests(room_id) => {
@@ -246,7 +244,7 @@ impl StateStore for MemoryStore {
let mut inner = self.inner.write().unwrap();
match key {
StateStoreDataKey::SyncToken => inner.sync_token = None,
StateStoreDataKey::ServerCapabilities => inner.server_capabilities = None,
StateStoreDataKey::ServerInfo => inner.server_info = None,
StateStoreDataKey::Filter(filter_name) => {
inner.filters.remove(filter_name);
}
+2 -2
View File
@@ -81,8 +81,8 @@ pub use self::{
SentMediaInfo, SentRequestKey, SerializableEventContent,
},
traits::{
ComposerDraft, ComposerDraftType, DynStateStore, IntoStateStore, ServerCapabilities,
StateStore, StateStoreDataKey, StateStoreDataValue, StateStoreExt,
ComposerDraft, ComposerDraftType, DynStateStore, IntoStateStore, ServerInfo, StateStore,
StateStoreDataKey, StateStoreDataValue, StateStoreExt,
},
};
+37 -35
View File
@@ -24,7 +24,7 @@ use async_trait::async_trait;
use growable_bloom_filter::GrowableBloom;
use matrix_sdk_common::AsyncTraitDeps;
use ruma::{
api::MatrixVersion,
api::{client::discovery::get_supported_versions, MatrixVersion},
events::{
presence::PresenceEvent,
receipt::{Receipt, ReceiptThread, ReceiptType},
@@ -950,12 +950,11 @@ where
}
}
/// Server capabilities returned by the /client/versions endpoint.
/// Useful server info such as data returned by the /client/versions and
/// .well-known/client/matrix endpoints.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ServerCapabilities {
pub struct ServerInfo {
/// Versions supported by the remote server.
///
/// This contains [`MatrixVersion`]s converted to strings.
pub versions: Vec<String>,
/// List of unstable features and their enablement status.
@@ -966,34 +965,37 @@ pub struct ServerCapabilities {
last_fetch_ts: f64,
}
impl ServerCapabilities {
impl ServerInfo {
/// The number of milliseconds after which the data is considered stale.
pub const STALE_THRESHOLD: f64 = (1000 * 60 * 60 * 24 * 7) as _; // seven days
/// Encode server capabilities into this serializable struct.
pub fn new(versions: &[MatrixVersion], unstable_features: BTreeMap<String, bool>) -> Self {
Self {
versions: versions.iter().map(|item| item.to_string()).collect(),
unstable_features,
last_fetch_ts: now_timestamp_ms(),
}
/// Encode server info into this serializable struct.
pub fn new(versions: Vec<String>, unstable_features: BTreeMap<String, bool>) -> Self {
Self { versions, unstable_features, last_fetch_ts: now_timestamp_ms() }
}
/// Decode server capabilities from this serializable struct.
/// Decode server info from this serializable struct.
///
/// May return `None` if the data is considered stale, after
/// [`Self::STALE_THRESHOLD`] milliseconds since the last time we stored
/// it.
pub fn maybe_decode(&self) -> Option<(Vec<MatrixVersion>, BTreeMap<String, bool>)> {
pub fn maybe_decode(&self) -> Option<Self> {
if now_timestamp_ms() - self.last_fetch_ts >= Self::STALE_THRESHOLD {
None
} else {
Some((
self.versions.iter().filter_map(|item| item.parse().ok()).collect(),
self.unstable_features.clone(),
))
Some(self.clone())
}
}
/// Extracts known Matrix versions from the un-typed list of strings.
///
/// Note: Matrix versions that Ruma cannot parse, or does not know about,
/// are discarded.
pub fn known_versions(&self) -> Vec<MatrixVersion> {
get_supported_versions::Response::new(self.versions.clone())
.known_versions()
.collect::<Vec<_>>()
}
}
/// Get the current timestamp as the number of milliseconds since Unix Epoch.
@@ -1011,8 +1013,8 @@ pub enum StateStoreDataValue {
/// The sync token.
SyncToken(String),
/// The server capabilities.
ServerCapabilities(ServerCapabilities),
/// The server info (versions, well-known etc).
ServerInfo(ServerInfo),
/// A filter with the given ID.
Filter(String),
@@ -1097,9 +1099,9 @@ impl StateStoreDataValue {
as_variant!(self, Self::ComposerDraft)
}
/// Get this value if it is the server capabilities metadata.
pub fn into_server_capabilities(self) -> Option<ServerCapabilities> {
as_variant!(self, Self::ServerCapabilities)
/// Get this value if it is the server info metadata.
pub fn into_server_info(self) -> Option<ServerInfo> {
as_variant!(self, Self::ServerInfo)
}
/// Get this value if it is the data for the ignored join requests.
@@ -1114,8 +1116,8 @@ pub enum StateStoreDataKey<'a> {
/// The sync token.
SyncToken,
/// The server capabilities,
ServerCapabilities,
/// The server info,
ServerInfo,
/// A filter with the given name.
Filter(&'a str),
@@ -1143,9 +1145,9 @@ pub enum StateStoreDataKey<'a> {
impl StateStoreDataKey<'_> {
/// Key to use for the [`SyncToken`][Self::SyncToken] variant.
pub const SYNC_TOKEN: &'static str = "sync_token";
/// Key to use for the [`ServerCapabilities`][Self::ServerCapabilities]
/// Key to use for the [`ServerInfo`][Self::ServerInfo]
/// variant.
pub const SERVER_CAPABILITIES: &'static str = "server_capabilities";
pub const SERVER_INFO: &'static str = "server_capabilities"; // Note: this is the old name, kept for backwards compatibility.
/// Key prefix to use for the [`Filter`][Self::Filter] variant.
pub const FILTER: &'static str = "filter";
/// Key prefix to use for the [`UserAvatarUrl`][Self::UserAvatarUrl]
@@ -1171,21 +1173,21 @@ impl StateStoreDataKey<'_> {
#[cfg(test)]
mod tests {
use super::{now_timestamp_ms, ServerCapabilities};
use super::{now_timestamp_ms, ServerInfo};
#[test]
fn test_stale_server_capabilities() {
let mut caps = ServerCapabilities {
fn test_stale_server_info() {
let mut server_info = ServerInfo {
versions: Default::default(),
unstable_features: Default::default(),
last_fetch_ts: now_timestamp_ms() - ServerCapabilities::STALE_THRESHOLD - 1.0,
last_fetch_ts: now_timestamp_ms() - ServerInfo::STALE_THRESHOLD - 1.0,
};
// Definitely stale.
assert!(caps.maybe_decode().is_none());
assert!(server_info.maybe_decode().is_none());
// Definitely not stale.
caps.last_fetch_ts = now_timestamp_ms() - 1.0;
assert!(caps.maybe_decode().is_some());
server_info.last_fetch_ts = now_timestamp_ms() - 1.0;
assert!(server_info.maybe_decode().is_some());
}
}
@@ -27,7 +27,7 @@ use matrix_sdk_base::{
store::{
ChildTransactionId, ComposerDraft, DependentQueuedRequest, DependentQueuedRequestKind,
QueuedRequest, QueuedRequestKind, RoomLoadSettings, SentRequestKey,
SerializableEventContent, ServerCapabilities, StateChanges, StateStore, StoreError,
SerializableEventContent, ServerInfo, StateChanges, StateStore, StoreError,
},
MinimalRoomMemberEvent, RoomInfo, RoomMemberships, StateStoreDataKey, StateStoreDataValue,
};
@@ -400,10 +400,9 @@ impl IndexeddbStateStore {
StateStoreDataKey::SyncToken => {
self.encode_key(StateStoreDataKey::SYNC_TOKEN, StateStoreDataKey::SYNC_TOKEN)
}
StateStoreDataKey::ServerCapabilities => self.encode_key(
StateStoreDataKey::SERVER_CAPABILITIES,
StateStoreDataKey::SERVER_CAPABILITIES,
),
StateStoreDataKey::ServerInfo => {
self.encode_key(StateStoreDataKey::SERVER_INFO, StateStoreDataKey::SERVER_INFO)
}
StateStoreDataKey::Filter(filter_name) => {
self.encode_key(StateStoreDataKey::FILTER, (StateStoreDataKey::FILTER, filter_name))
}
@@ -537,10 +536,10 @@ impl_state_store!({
.map(|f| self.deserialize_value::<String>(&f))
.transpose()?
.map(StateStoreDataValue::SyncToken),
StateStoreDataKey::ServerCapabilities => value
.map(|f| self.deserialize_value::<ServerCapabilities>(&f))
StateStoreDataKey::ServerInfo => value
.map(|f| self.deserialize_value::<ServerInfo>(&f))
.transpose()?
.map(StateStoreDataValue::ServerCapabilities),
.map(StateStoreDataValue::ServerInfo),
StateStoreDataKey::Filter(_) => value
.map(|f| self.deserialize_value::<String>(&f))
.transpose()?
@@ -580,10 +579,8 @@ impl_state_store!({
let serialized_value = match key {
StateStoreDataKey::SyncToken => self
.serialize_value(&value.into_sync_token().expect("Session data not a sync token")),
StateStoreDataKey::ServerCapabilities => self.serialize_value(
&value
.into_server_capabilities()
.expect("Session data not containing server capabilities"),
StateStoreDataKey::ServerInfo => self.serialize_value(
&value.into_server_info().expect("Session data not containing server info"),
),
StateStoreDataKey::Filter(_) => {
self.serialize_value(&value.into_filter().expect("Session data not a filter"))
+5 -9
View File
@@ -410,9 +410,7 @@ impl SqliteStateStore {
fn encode_state_store_data_key(&self, key: StateStoreDataKey<'_>) -> Key {
let key_s = match key {
StateStoreDataKey::SyncToken => Cow::Borrowed(StateStoreDataKey::SYNC_TOKEN),
StateStoreDataKey::ServerCapabilities => {
Cow::Borrowed(StateStoreDataKey::SERVER_CAPABILITIES)
}
StateStoreDataKey::ServerInfo => Cow::Borrowed(StateStoreDataKey::SERVER_INFO),
StateStoreDataKey::Filter(f) => {
Cow::Owned(format!("{}:{f}", StateStoreDataKey::FILTER))
}
@@ -1029,8 +1027,8 @@ impl StateStore for SqliteStateStore {
StateStoreDataKey::SyncToken => {
StateStoreDataValue::SyncToken(self.deserialize_value(&data)?)
}
StateStoreDataKey::ServerCapabilities => {
StateStoreDataValue::ServerCapabilities(self.deserialize_value(&data)?)
StateStoreDataKey::ServerInfo => {
StateStoreDataValue::ServerInfo(self.deserialize_value(&data)?)
}
StateStoreDataKey::Filter(_) => {
StateStoreDataValue::Filter(self.deserialize_value(&data)?)
@@ -1064,10 +1062,8 @@ impl StateStore for SqliteStateStore {
StateStoreDataKey::SyncToken => self.serialize_value(
&value.into_sync_token().expect("Session data not a sync token"),
)?,
StateStoreDataKey::ServerCapabilities => self.serialize_value(
&value
.into_server_capabilities()
.expect("Session data not containing server capabilities"),
StateStoreDataKey::ServerInfo => self.serialize_value(
&value.into_server_info().expect("Session data not containing server info"),
)?,
StateStoreDataKey::Filter(_) => {
self.serialize_value(&value.into_filter().expect("Session data not a filter"))?
+1 -1
View File
@@ -189,7 +189,7 @@ impl SyncTaskSupervisor {
//
// Still, as a precaution, we're going to sleep here for a while in the Error
// case.
match client.fetch_server_capabilities(Some(request_config)).await {
match client.fetch_server_versions(Some(request_config)).await {
Ok(_) => break,
Err(_) => sleep(Duration::from_millis(100)).await,
}
+4 -6
View File
@@ -40,7 +40,7 @@ use crate::encryption::EncryptionSettings;
use crate::http_client::HttpSettings;
use crate::{
authentication::{oauth::OAuthCtx, AuthCtx},
client::ClientServerCapabilities,
client::ClientServerInfo,
config::RequestConfig,
error::RumaApiError,
http_client::HttpClient,
@@ -560,10 +560,8 @@ impl ClientBuilder {
// Enable the send queue by default.
let send_queue = Arc::new(SendQueueData::new(true));
let server_capabilities = ClientServerCapabilities {
server_versions: self.server_versions,
unstable_features: None,
};
let server_info =
ClientServerInfo { server_versions: self.server_versions, unstable_features: None };
let event_cache = OnceCell::new();
let inner = ClientInner::new(
@@ -573,7 +571,7 @@ impl ClientBuilder {
sliding_sync_version,
http_client,
base_client,
server_capabilities,
server_info,
self.respect_login_well_known,
event_cache,
send_queue,
+4 -4
View File
@@ -16,13 +16,13 @@ use matrix_sdk_base::ttl_cache::TtlCache;
use ruma::api::client::discovery::get_authorization_server_metadata::msc2965::AuthorizationServerMetadata;
use tokio::sync::RwLock;
use super::ClientServerCapabilities;
use super::ClientServerInfo;
/// A collection of in-memory data that the `Client` might want to cache to
/// avoid hitting the homeserver every time users request the data.
pub(crate) struct ClientCaches {
/// Server capabilities, either prefilled during building or fetched from
/// the server.
pub(super) server_capabilities: RwLock<ClientServerCapabilities>,
/// Server info, either prefilled during building or fetched from the
/// server.
pub(super) server_info: RwLock<ClientServerInfo>,
pub(crate) server_metadata: tokio::sync::Mutex<TtlCache<String, AuthorizationServerMetadata>>,
}
+47 -50
View File
@@ -31,7 +31,7 @@ use futures_util::StreamExt;
use matrix_sdk_base::crypto::store::LockableCryptoStore;
use matrix_sdk_base::{
event_cache::store::EventCacheStoreLock,
store::{DynStateStore, RoomLoadSettings, ServerCapabilities},
store::{DynStateStore, RoomLoadSettings, ServerInfo},
sync::{Notification, RoomUpdates},
BaseClient, RoomInfoNotableUpdate, RoomState, RoomStateFilter, SendOutsideWasm, SessionMeta,
StateStoreDataKey, StateStoreDataValue, SyncOutsideWasm,
@@ -357,7 +357,7 @@ impl ClientInner {
sliding_sync_version: SlidingSyncVersion,
http_client: HttpClient,
base_client: BaseClient,
server_capabilities: ClientServerCapabilities,
server_info: ClientServerInfo,
respect_login_well_known: bool,
event_cache: OnceCell<EventCache>,
send_queue: Arc<SendQueueData>,
@@ -366,7 +366,7 @@ impl ClientInner {
cross_process_store_locks_holder_name: String,
) -> Arc<Self> {
let caches = ClientCaches {
server_capabilities: server_capabilities.into(),
server_info: server_info.into(),
server_metadata: Mutex::new(TtlCache::new()),
};
@@ -1736,11 +1736,11 @@ impl Client {
.send(SessionChange::UnknownToken { soft_logout: *soft_logout });
}
/// Fetches server capabilities from network; no caching.
pub async fn fetch_server_capabilities(
/// Fetches server versions from network; no caching.
pub async fn fetch_server_versions(
&self,
request_config: Option<RequestConfig>,
) -> HttpResult<(Box<[MatrixVersion]>, BTreeMap<String, bool>)> {
) -> HttpResult<(Vec<String>, BTreeMap<String, bool>)> {
let resp = self
.inner
.http_client
@@ -1754,78 +1754,73 @@ impl Client {
)
.await?;
// Fill both unstable features and server versions at once.
let mut versions = resp.known_versions().collect::<Vec<_>>();
if versions.is_empty() {
versions.push(MatrixVersion::V1_0);
}
Ok((versions.into(), resp.unstable_features))
Ok((resp.versions, resp.unstable_features))
}
/// Load server capabilities from storage, or fetch them from network and
/// cache them.
async fn load_or_fetch_server_capabilities(
&self,
) -> HttpResult<(Box<[MatrixVersion]>, BTreeMap<String, bool>)> {
match self.state_store().get_kv_data(StateStoreDataKey::ServerCapabilities).await {
/// Load server info from storage, or fetch them from network and cache
/// them.
async fn load_or_fetch_server_info(&self) -> HttpResult<ServerInfo> {
match self.state_store().get_kv_data(StateStoreDataKey::ServerInfo).await {
Ok(Some(stored)) => {
if let Some((versions, unstable_features)) =
stored.into_server_capabilities().and_then(|cap| cap.maybe_decode())
if let Some(server_info) =
stored.into_server_info().and_then(|info| info.maybe_decode())
{
return Ok((versions.into(), unstable_features));
return Ok(server_info);
}
}
Ok(None) => {
// fallthrough: cache is empty
}
Err(err) => {
warn!("error when loading cached server capabilities: {err}");
warn!("error when loading cached server info: {err}");
// fallthrough to network.
}
}
let (versions, unstable_features) = self.fetch_server_capabilities(None).await?;
let (versions, unstable_features) = self.fetch_server_versions(None).await?;
let server_info = ServerInfo::new(versions.clone(), unstable_features.clone());
// Attempt to cache the result in storage.
{
let encoded = ServerCapabilities::new(&versions, unstable_features.clone());
if let Err(err) = self
.state_store()
.set_kv_data(
StateStoreDataKey::ServerCapabilities,
StateStoreDataValue::ServerCapabilities(encoded),
StateStoreDataKey::ServerInfo,
StateStoreDataValue::ServerInfo(server_info.clone()),
)
.await
{
warn!("error when caching server capabilities: {err}");
warn!("error when caching server info: {err}");
}
}
Ok((versions, unstable_features))
Ok(server_info)
}
async fn get_or_load_and_cache_server_capabilities<
T,
F: Fn(&ClientServerCapabilities) -> Option<T>,
>(
async fn get_or_load_and_cache_server_info<T, F: Fn(&ClientServerInfo) -> Option<T>>(
&self,
f: F,
) -> HttpResult<T> {
let caps = &self.inner.caches.server_capabilities;
if let Some(val) = f(&*caps.read().await) {
let server_info = &self.inner.caches.server_info;
if let Some(val) = f(&*server_info.read().await) {
return Ok(val);
}
let mut guard = caps.write().await;
let mut guard = server_info.write().await;
if let Some(val) = f(&guard) {
return Ok(val);
}
let (versions, unstable_features) = self.load_or_fetch_server_capabilities().await?;
let server_info = self.load_or_fetch_server_info().await?;
guard.server_versions = Some(versions);
guard.unstable_features = Some(unstable_features);
// Fill both unstable features and server versions at once.
let mut versions = server_info.known_versions();
if versions.is_empty() {
versions.push(MatrixVersion::V1_0);
}
guard.server_versions = Some(versions.into());
guard.unstable_features = Some(server_info.unstable_features);
// SAFETY: both fields were set above, so the function will always return some.
Ok(f(&guard).unwrap())
@@ -1850,7 +1845,8 @@ impl Client {
/// # anyhow::Ok(()) };
/// ```
pub async fn server_versions(&self) -> HttpResult<Box<[MatrixVersion]>> {
self.get_or_load_and_cache_server_capabilities(|caps| caps.server_versions.clone()).await
self.get_or_load_and_cache_server_info(|server_info| server_info.server_versions.clone())
.await
}
/// Get the unstable features supported by the homeserver by fetching them
@@ -1871,22 +1867,23 @@ impl Client {
/// # anyhow::Ok(()) };
/// ```
pub async fn unstable_features(&self) -> HttpResult<BTreeMap<String, bool>> {
self.get_or_load_and_cache_server_capabilities(|caps| caps.unstable_features.clone()).await
self.get_or_load_and_cache_server_info(|server_info| server_info.unstable_features.clone())
.await
}
/// Empty the server version and unstable features cache.
///
/// Since the SDK caches server capabilities (versions and unstable
/// features), it's possible to have a stale entry in the cache. This
/// Since the SDK caches server info (versions, unstable features,
/// well-known etc), it's possible to have a stale entry in the cache. This
/// functions makes it possible to force reset it.
pub async fn reset_server_capabilities(&self) -> Result<()> {
pub async fn reset_server_info(&self) -> Result<()> {
// Empty the in-memory caches.
let mut guard = self.inner.caches.server_capabilities.write().await;
let mut guard = self.inner.caches.server_info.write().await;
guard.server_versions = None;
guard.unstable_features = None;
// Empty the store cache.
Ok(self.state_store().remove_kv_data(StateStoreDataKey::ServerCapabilities).await?)
Ok(self.state_store().remove_kv_data(StateStoreDataKey::ServerInfo).await?)
}
/// Check whether MSC 4028 is enabled on the homeserver.
@@ -2503,7 +2500,7 @@ impl Client {
.base_client
.clone_with_in_memory_state_store(&cross_process_store_locks_holder_name, false)
.await?,
self.inner.caches.server_capabilities.read().await.clone(),
self.inner.caches.server_info.read().await.clone(),
self.inner.respect_login_well_known,
self.inner.event_cache.clone(),
self.inner.send_queue_data.clone(),
@@ -2622,7 +2619,7 @@ impl WeakClient {
}
#[derive(Clone)]
struct ClientServerCapabilities {
struct ClientServerInfo {
/// The Matrix versions the server supports (well-known ones only).
server_versions: Option<Box<[MatrixVersion]>>,
@@ -3044,7 +3041,7 @@ pub(crate) mod tests {
}
#[async_test]
async fn test_server_capabilities_caching() {
async fn test_server_info_caching() {
let server = MockServer::start().await;
let server_url = server.uri();
let domain = server_url.strip_prefix("http://").unwrap();
@@ -3107,7 +3104,7 @@ pub(crate) mod tests {
server.verify().await;
// Now, reset the cache, and observe the endpoint being called again once.
client.reset_server_capabilities().await.unwrap();
client.reset_server_info().await.unwrap();
Mock::given(method("GET"))
.and(path("/_matrix/client/versions"))
+1 -1
View File
@@ -410,7 +410,7 @@ async fn test_get_media_file_with_auth_matrix_stable_feature() {
async fn test_async_media_upload() {
let (client, server) = logged_in_client_with_server().await;
client.reset_server_capabilities().await.unwrap();
client.reset_server_info().await.unwrap();
// Declare Matrix version v1.7.
Mock::given(method("GET"))