Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/matrix-sdk-base/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ All notable changes to this project will be documented in this file.

### Features

- [**breaking**] Add functions for storing custom values in `EventCacheStore`.
([#6541](https://github.com/matrix-org/matrix-rust-sdk/pull/6541))
- Add `RoomMember::is_service_member` that automatically checks the room info and retrieves this info.
([#6536](https://github.com/matrix-org/matrix-rust-sdk/pull/6536))
- [**breaking**] Add `DmRoomDefinition` enum, allowing clients to specify what a DM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ pub trait EventCacheStoreIntegrationTests {
/// Test multiple things related to distinguishing a thread linked chunk
/// from a room linked chunk.
async fn test_thread_vs_room_linked_chunk(&self);

/// Test that custom values can be stored, retrieved, and removed from the
/// store.
async fn test_custom_values(&self);
}

impl EventCacheStoreIntegrationTests for DynEventCacheStore {
Expand Down Expand Up @@ -2262,6 +2266,35 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore {
assert_eq!(observed_items.len(), 1);
assert_eq!(observed_items[0].event_id(), thread1_ev.event_id());
}

async fn test_custom_values(&self) {
let key = "key";
let value = Vec::from(b"value");

// Before the value is set, it cannot be retrieved
let retrieved = self.get_custom_value(key).await.expect("get custom value");
assert_eq!(retrieved, None);

// After the value is set, it can be retrieved
self.set_custom_value(key, value.clone()).await.expect("set custom value");
let retrieved = self.get_custom_value(key).await.expect("get custom value");
assert_matches!(retrieved, Some(bytes) => assert_eq!(bytes, value));

// After the value is set again, the new value can be retrieved but the original
// value cannot
let new_value = Vec::from(b"new-value");
self.set_custom_value(key, new_value.clone()).await.expect("set custom value");
let retrieved = self.get_custom_value(key).await.expect("get custom value");
assert_matches!(retrieved, Some(bytes) => {
assert_ne!(bytes, value);
assert_eq!(bytes, new_value);
});

// After the value is removed, it can no longer be retrieved
self.remove_custom_value(key).await.expect("remove custom value");
let retrieved = self.get_custom_value(key).await.expect("get custom value");
assert_eq!(retrieved, None);
}
}

/// Macro building to allow your `EventCacheStore` implementation to run the
Expand Down Expand Up @@ -2519,6 +2552,13 @@ macro_rules! event_cache_store_integration_tests {
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_thread_vs_room_linked_chunk().await;
}

#[async_test]
async fn test_custom_values() {
let event_cache_store =
get_event_cache_store().await.unwrap().into_event_cache_store();
event_cache_store.test_custom_values().await;
}
}
};
}
Expand Down
16 changes: 16 additions & 0 deletions crates/matrix-sdk-base/src/event_cache/store/memory_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub struct MemoryStore {
struct MemoryStoreInner {
leases: HashMap<String, Lease>,
events: RelationalLinkedChunk<OwnedEventId, Event, Gap>,
values: HashMap<String, Vec<u8>>,
}

impl Default for MemoryStore {
Expand All @@ -60,6 +61,7 @@ impl Default for MemoryStore {
inner: Arc::new(StdRwLock::new(MemoryStoreInner {
leases: Default::default(),
events: RelationalLinkedChunk::new(),
values: Default::default(),
})),
}
}
Expand Down Expand Up @@ -292,6 +294,20 @@ impl EventCacheStore for MemoryStore {
Ok(())
}

async fn get_custom_value(&self, key: &str) -> Result<Option<Vec<u8>>, Self::Error> {
Ok(self.inner.read().unwrap().values.get(key).cloned())
}

async fn set_custom_value(&self, key: &str, value: Vec<u8>) -> Result<(), Self::Error> {
self.inner.write().unwrap().values.insert(key.to_owned(), value);
Ok(())
}

async fn remove_custom_value(&self, key: &str) -> Result<(), Self::Error> {
self.inner.write().unwrap().values.remove(key);
Ok(())
}

async fn optimize(&self) -> Result<(), Self::Error> {
Ok(())
}
Expand Down
22 changes: 22 additions & 0 deletions crates/matrix-sdk-base/src/event_cache/store/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,16 @@ pub trait EventCacheStore: AsyncTraitDeps {
/// without causing an error.
async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error>;

/// Get the value (as bytes) that is associated with the provided key from
/// the store.
async fn get_custom_value(&self, key: &str) -> Result<Option<Vec<u8>>, Self::Error>;

/// Store the provided key and associated value in the store.
async fn set_custom_value(&self, key: &str, value: Vec<u8>) -> Result<(), Self::Error>;

/// Remove the provided key and its associated value from the store.
async fn remove_custom_value(&self, key: &str) -> Result<(), Self::Error>;

/// Perform database optimizations if any are available, i.e. vacuuming in
/// SQLite.
///
Expand Down Expand Up @@ -294,6 +304,18 @@ impl<T: EventCacheStore> EventCacheStore for EraseEventCacheStoreError<T> {
self.0.save_event(room_id, event).await.map_err(Into::into)
}

async fn get_custom_value(&self, key: &str) -> Result<Option<Vec<u8>>, Self::Error> {
self.0.get_custom_value(key).await.map_err(Into::into)
}

async fn set_custom_value(&self, key: &str, value: Vec<u8>) -> Result<(), Self::Error> {
self.0.set_custom_value(key, value).await.map_err(Into::into)
}

async fn remove_custom_value(&self, key: &str) -> Result<(), Self::Error> {
self.0.remove_custom_value(key).await.map_err(Into::into)
}

async fn optimize(&self) -> Result<(), Self::Error> {
self.0.optimize().await.map_err(Into::into)?;
Ok(())
Expand Down
3 changes: 3 additions & 0 deletions crates/matrix-sdk-common/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ All notable changes to this project will be documented in this file.

### Features

- Add function `MappedCrossProcessLockState::into_inner` for extracting the inner
value without regard for whether the lock is clean or dirty.
([#6541](https://github.com/matrix-org/matrix-rust-sdk/pull/6541))
- [**breaking**] Change to the stable identifiers for `m.history_not_shared`.
We still support reading the unstable identifier.
([#6467](https://github.com/matrix-org/matrix-rust-sdk/pull/6467))
Expand Down
8 changes: 8 additions & 0 deletions crates/matrix-sdk-common/src/cross_process_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,14 @@ impl<G> MappedCrossProcessLockState<G> {
Self::Dirty(_) => None,
}
}

/// Map this value into the inner type, ignoring whether
/// it is clean or dirty.
pub fn into_inner(self) -> G {
match self {
Self::Clean(inner) | Self::Dirty(inner) => inner,
}
}
}

/// Represent an unsuccessful result of a lock attempt, either by
Expand Down
2 changes: 2 additions & 0 deletions crates/matrix-sdk-indexeddb/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file.

### Features

- Add implementations of `EventCacheStore` functions for storing custom values.
([#6541](https://github.com/matrix-org/matrix-rust-sdk/pull/6541))
- Add support in the implementation of `EventCacheStore` for
having duplicate events in a room, where each duplicate is in a different
`LinkedChunk`. This is useful, e.g., when an event is in a room and a
Expand Down
49 changes: 45 additions & 4 deletions crates/matrix-sdk-indexeddb/src/event_cache_store/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ use thiserror::Error;

/// The current version and keys used in the database.
pub mod current {
use super::{Version, v4};
use super::{Version, v5};

pub const VERSION: Version = Version::V4;
pub use v4::keys;
pub const VERSION: Version = Version::V5;
pub use v5::keys;
}

/// Opens a connection to the IndexedDB database and takes care of upgrading it
Expand Down Expand Up @@ -60,6 +60,8 @@ pub enum Version {
V3 = 3,
/// Version 4 of the database, for details see [`v4`].
V4 = 4,
/// Version 5 of the database, for details see [`v5`].
V5 = 5,
}

impl Version {
Expand All @@ -70,7 +72,8 @@ impl Version {
Self::V1 => v1::upgrade(transaction).map(Some),
Self::V2 => v2::upgrade(transaction).map(Some),
Self::V3 => v3::upgrade(transaction).map(Some),
Self::V4 => Ok(None),
Self::V4 => v4::upgrade(transaction).map(Some),
Self::V5 => Ok(None),
}
}
}
Expand All @@ -89,6 +92,7 @@ impl TryFrom<u32> for Version {
2 => Ok(Version::V2),
3 => Ok(Version::V3),
4 => Ok(Version::V4),
5 => Ok(Version::V5),
v => Err(UnknownVersionError(v)),
}
}
Expand Down Expand Up @@ -332,4 +336,41 @@ mod v4 {

Ok(())
}

/// Upgrade database from `v4` to `v5`
pub fn upgrade(transaction: &Transaction<'_>) -> Result<Version, Error> {
v5::create_custom_values_object_store(transaction)?;
Ok(Version::V5)
}
}

pub mod v5 {
use indexed_db_futures::Build;

use super::*;

pub mod keys {
// Re-use all the same keys from [`v4`], and add another
// for the custom values object store.
pub use super::v4::keys::*;

pub const CUSTOM_VALUES: &str = "custom-values";
pub const CUSTOM_VALUES_KEY_PATH: &str = "id";
}

/// Create an object store for tracking custom values.
///
/// * Primary Key - `id`
///
/// Note that this object store expects binary data values - e.g.,
/// [`Vec<u8>`]. It is intended to hold any individual values that are
/// not appropriate for storing in other parts of the database.
pub fn create_custom_values_object_store(transaction: &Transaction<'_>) -> Result<(), Error> {
let _ = transaction
.db()
.create_object_store(keys::CUSTOM_VALUES)
.with_key_path(keys::CUSTOM_VALUES_KEY_PATH.into())
.build()?;
Ok(())
}
}
21 changes: 21 additions & 0 deletions crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,27 @@ impl EventCacheStore for IndexeddbEventCacheStore {
Ok(())
}

async fn get_custom_value(&self, key: &str) -> Result<Option<Vec<u8>>, Self::Error> {
let transaction = self.transaction(&[keys::CUSTOM_VALUES], IdbTransactionMode::Readonly)?;
Ok(transaction.get_custom_value(key).await?.map(|v| v.value))
}

async fn set_custom_value(&self, key: &str, value: Vec<u8>) -> Result<(), Self::Error> {
let transaction =
self.transaction(&[keys::CUSTOM_VALUES], IdbTransactionMode::Readwrite)?;
transaction.put_custom_value(key, value).await?;
transaction.commit().await?;
Ok(())
}

async fn remove_custom_value(&self, key: &str) -> Result<(), Self::Error> {
let transaction =
self.transaction(&[keys::CUSTOM_VALUES], IdbTransactionMode::Readwrite)?;
transaction.delete_custom_value(key).await?;
transaction.commit().await?;
Ok(())
}

async fn optimize(&self) -> Result<(), Self::Error> {
Ok(())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use crate::{
INDEXED_KEY_LOWER_EVENT_POSITION, INDEXED_KEY_UPPER_CHUNK_IDENTIFIER,
INDEXED_KEY_UPPER_EVENT_INDEX, INDEXED_KEY_UPPER_EVENT_POSITION,
},
types::{Chunk, Event, Gap, Lease, Position},
types::{Chunk, CustomValue, Event, Gap, Lease, Position},
},
serializer::{
indexed_type::{
Expand Down Expand Up @@ -95,6 +95,9 @@ pub type IndexedEventContent = MaybeEncrypted;
/// A (possibly) encrypted representation of a [`Gap`]
pub type IndexedGapContent = MaybeEncrypted;

/// A (possibly) encrypted representation of a [`CustomValue`]
pub type IndexedCustomValueContent = MaybeEncrypted;

/// Represents the [`LEASES`][1] object store.
///
/// [1]: crate::event_cache_store::migrations::v1::create_lease_object_store
Expand Down Expand Up @@ -659,3 +662,65 @@ impl<'a> IndexedPrefixKeyComponentBounds<'a, Gap, LinkedChunkId<'a>> for Indexed
)
}
}

/// Represents the [`CUSTOM_VALUES`][1] object store.
///
/// [1]: crate::event_cache_store::migrations::v5::create_custom_values_object_store
#[derive(Debug, Serialize, Deserialize)]
pub struct IndexedCustomValue {
/// The primary key of the object store.
pub id: IndexedCustomValueIdKey,
/// The (possibly encrypted) content - i.e., a [`CustomValue`].
pub content: IndexedCustomValueContent,
}

impl Indexed for CustomValue {
type IndexedType = IndexedCustomValue;

const OBJECT_STORE: &'static str = keys::CUSTOM_VALUES;

type Error = CryptoStoreError;

fn to_indexed(
&self,
serializer: &SafeEncodeSerializer,
) -> Result<Self::IndexedType, Self::Error> {
Ok(IndexedCustomValue {
id: <IndexedCustomValueIdKey as IndexedKey<CustomValue>>::encode(&self.key, serializer),
content: serializer.maybe_encrypt_value(self)?,
})
}

fn from_indexed(
indexed: Self::IndexedType,
serializer: &SafeEncodeSerializer,
) -> Result<Self, Self::Error> {
serializer.maybe_decrypt_value(indexed.content)
}
}

/// The value associated with the [primary key](IndexedCustomValue::id) of the
/// [`CUSTOM_VALUES`][1] object store, which is constructed from the value in
/// [`CustomValue::key`]. This value may or may not be hashed depending on the
/// provided [`IndexeddbSerializer`].
///
/// [1]: crate::event_cache_store::migrations::v1::create_custom_values_object_store
pub type IndexedCustomValueIdKey = String;

impl IndexedKey<CustomValue> for IndexedCustomValueIdKey {
type KeyComponents<'a> = &'a str;

fn encode(components: Self::KeyComponents<'_>, serializer: &SafeEncodeSerializer) -> Self {
serializer.encode_key_as_string(keys::CUSTOM_VALUES, components)
}
}

impl IndexedKeyComponentBounds<CustomValue> for IndexedCustomValueIdKey {
fn lower_key_components() -> Self::KeyComponents<'static> {
INDEXED_KEY_LOWER_STRING.as_str()
}

fn upper_key_components() -> Self::KeyComponents<'static> {
INDEXED_KEY_UPPER_STRING.as_str()
}
}
Loading
Loading