Skip to content

Commit b5a0fed

Browse files
committed
feat(ffi): Add IndexedDB and in-memory session stores support.
This patch introduces the `sqlite` and `indexeddb` feature flag, enabling the use of SQLite or IndexedDB for the session store. This patch also introduces the ability to use non-persistent, in-memory session store. The new `ClientBuilder::session_store_in_memory`, `ClientBuilder::session_store_with_sqlite` and `ClientBuilder::session_store_with_indexeddb` methods are introduced to configure the session store. This patch adds new `SqliteSessionStoreBuilder` and `IndexedDbSessionStoreBuilder` structure.
1 parent 9af8fad commit b5a0fed

File tree

6 files changed

+347
-136
lines changed

6 files changed

+347
-136
lines changed

bindings/matrix-sdk-ffi/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ crate-type = [
2525

2626
[features]
2727
default = ["bundled-sqlite", "unstable-msc4274", "experimental-element-recent-emojis"]
28-
bundled-sqlite = ["matrix-sdk/bundled-sqlite"]
28+
# Use SQLite for the session storage.
29+
sqlite = ["matrix-sdk/sqlite"]
30+
# Use an embedded version of SQLite.
31+
bundled-sqlite = ["sqlite", "matrix-sdk/bundled-sqlite"]
32+
# Use IndexedDB for the session storage.
33+
indexeddb = ["matrix-sdk/indexeddb"]
2934
unstable-msc4274 = ["matrix-sdk-ui/unstable-msc4274"]
3035
# Required when targeting a Javascript environment, like Wasm in a browser.
3136
js = ["matrix-sdk-ui/js"]
@@ -51,7 +56,6 @@ matrix-sdk = { workspace = true, features = [
5156
"experimental-widgets",
5257
"markdown",
5358
"socks",
54-
"sqlite",
5559
"uniffi",
5660
] }
5761
matrix-sdk-base.workspace = true

bindings/matrix-sdk-ffi/src/client.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use anyhow::{anyhow, Context as _};
1010
use futures_util::pin_mut;
1111
#[cfg(not(target_family = "wasm"))]
1212
use matrix_sdk::media::MediaFileHandle as SdkMediaFileHandle;
13+
#[cfg(feature = "sqlite")]
14+
use matrix_sdk::STATE_STORE_DATABASE_NAME;
1315
use matrix_sdk::{
1416
authentication::oauth::{
1517
AccountManagementActionFull, ClientId, OAuthAuthorizationData, OAuthSession,
@@ -39,7 +41,6 @@ use matrix_sdk::{
3941
sliding_sync::Version as SdkSlidingSyncVersion,
4042
store::RoomLoadSettings as SdkRoomLoadSettings,
4143
Account, AuthApi, AuthSession, Client as MatrixClient, Error, SessionChange, SessionTokens,
42-
STATE_STORE_DATABASE_NAME,
4344
};
4445
use matrix_sdk_common::{stream::StreamExt, SendOutsideWasm, SyncOutsideWasm};
4546
use matrix_sdk_ui::{
@@ -242,13 +243,18 @@ impl From<matrix_sdk::TransmissionProgress> for TransmissionProgress {
242243
#[derive(uniffi::Object)]
243244
pub struct Client {
244245
pub(crate) inner: AsyncRuntimeDropped<MatrixClient>,
246+
245247
delegate: OnceLock<Arc<dyn ClientDelegate>>,
248+
246249
pub(crate) utd_hook_manager: OnceLock<Arc<UtdHookManager>>,
250+
247251
session_verification_controller:
248252
Arc<tokio::sync::RwLock<Option<SessionVerificationController>>>,
253+
249254
/// The path to the directory where the state store and the crypto store are
250-
/// located, if the `Client` instance has been built with a SQLite store
251-
/// backend.
255+
/// located, if the `Client` instance has been built with a store (either
256+
/// SQLite or IndexedDB).
257+
#[cfg_attr(not(feature = "sqlite"), allow(unused))]
252258
store_path: Option<PathBuf>,
253259
}
254260

@@ -1597,6 +1603,7 @@ impl Client {
15971603
self.inner.event_cache().clear_all_rooms().await?;
15981604

15991605
// Delete the state store file, if it exists.
1606+
#[cfg(feature = "sqlite")]
16001607
if let Some(store_path) = &self.store_path {
16011608
debug!("Removing the state store: {}", store_path.display());
16021609

bindings/matrix-sdk-ffi/src/client_builder.rs

Lines changed: 64 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{fs, num::NonZeroUsize, path::Path, sync::Arc, time::Duration};
1+
use std::{num::NonZeroUsize, sync::Arc, time::Duration};
22

33
#[cfg(not(target_family = "wasm"))]
44
use matrix_sdk::reqwest::Certificate;
@@ -11,15 +11,21 @@ use matrix_sdk::{
1111
VersionBuilderError,
1212
},
1313
Client as MatrixClient, ClientBuildError as MatrixClientBuildError, HttpError, IdParseError,
14-
RumaApiError, SqliteStoreConfig, ThreadingSupport,
14+
RumaApiError, ThreadingSupport,
1515
};
1616
use matrix_sdk_base::crypto::{CollectStrategy, DecryptionSettings, TrustRequirement};
1717
use ruma::api::error::{DeserializationError, FromHttpResponseError};
1818
use tracing::debug;
19-
use zeroize::Zeroizing;
2019

2120
use super::client::Client;
22-
use crate::{client::ClientSessionDelegate, error::ClientError, helpers::unwrap_or_clone_arc};
21+
#[cfg(any(feature = "sqlite", feature = "indexeddb"))]
22+
use crate::session_store;
23+
use crate::{
24+
client::ClientSessionDelegate,
25+
error::ClientError,
26+
helpers::unwrap_or_clone_arc,
27+
session_store::{SessionStoreBuilder, SessionStoreResult},
28+
};
2329

2430
/// A list of bytes containing a certificate in DER or PEM form.
2531
pub type CertificateBytes = Vec<u8>;
@@ -100,11 +106,7 @@ impl From<ClientError> for ClientBuildError {
100106

101107
#[derive(Clone, uniffi::Object)]
102108
pub struct ClientBuilder {
103-
session_paths: Option<SessionPaths>,
104-
session_passphrase: Zeroizing<Option<String>>,
105-
session_pool_max_size: Option<usize>,
106-
session_cache_size: Option<u32>,
107-
session_journal_size_limit: Option<u32>,
109+
session_store: Option<SessionStoreBuilder>,
108110
system_is_memory_constrained: bool,
109111
username: Option<String>,
110112
homeserver_cfg: Option<HomeserverConfig>,
@@ -143,11 +145,7 @@ impl ClientBuilder {
143145
#[uniffi::constructor]
144146
pub fn new() -> Arc<Self> {
145147
Arc::new(Self {
146-
session_paths: None,
147-
session_passphrase: Zeroizing::new(None),
148-
session_pool_max_size: None,
149-
session_cache_size: None,
150-
session_journal_size_limit: None,
148+
session_store: None,
151149
system_is_memory_constrained: false,
152150
username: None,
153151
homeserver_cfg: None,
@@ -201,73 +199,6 @@ impl ClientBuilder {
201199
Arc::new(builder)
202200
}
203201

204-
/// Sets the paths that the client will use to store its data and caches.
205-
/// Both paths **must** be unique per session as the SDK stores aren't
206-
/// capable of handling multiple users, however it is valid to use the
207-
/// same path for both stores on a single session.
208-
///
209-
/// Leaving this unset tells the client to use an in-memory data store.
210-
pub fn session_paths(self: Arc<Self>, data_path: String, cache_path: String) -> Arc<Self> {
211-
let mut builder = unwrap_or_clone_arc(self);
212-
builder.session_paths = Some(SessionPaths { data_path, cache_path });
213-
Arc::new(builder)
214-
}
215-
216-
/// Set the passphrase for the stores given to
217-
/// [`ClientBuilder::session_paths`].
218-
pub fn session_passphrase(self: Arc<Self>, passphrase: Option<String>) -> Arc<Self> {
219-
let mut builder = unwrap_or_clone_arc(self);
220-
builder.session_passphrase = Zeroizing::new(passphrase);
221-
Arc::new(builder)
222-
}
223-
224-
/// Set the pool max size for the SQLite stores given to
225-
/// [`ClientBuilder::session_paths`].
226-
///
227-
/// Each store exposes an async pool of connections. This method controls
228-
/// the size of the pool. The larger the pool is, the more memory is
229-
/// consumed, but also the more the app is reactive because it doesn't need
230-
/// to wait on a pool to be available to run queries.
231-
///
232-
/// See [`SqliteStoreConfig::pool_max_size`] to learn more.
233-
pub fn session_pool_max_size(self: Arc<Self>, pool_max_size: Option<u32>) -> Arc<Self> {
234-
let mut builder = unwrap_or_clone_arc(self);
235-
builder.session_pool_max_size = pool_max_size
236-
.map(|size| size.try_into().expect("`pool_max_size` is too large to fit in `usize`"));
237-
Arc::new(builder)
238-
}
239-
240-
/// Set the cache size for the SQLite stores given to
241-
/// [`ClientBuilder::session_paths`].
242-
///
243-
/// Each store exposes a SQLite connection. This method controls the cache
244-
/// size, in **bytes (!)**.
245-
///
246-
/// The cache represents data SQLite holds in memory at once per open
247-
/// database file. The default cache implementation does not allocate the
248-
/// full amount of cache memory all at once. Cache memory is allocated
249-
/// in smaller chunks on an as-needed basis.
250-
///
251-
/// See [`SqliteStoreConfig::cache_size`] to learn more.
252-
pub fn session_cache_size(self: Arc<Self>, cache_size: Option<u32>) -> Arc<Self> {
253-
let mut builder = unwrap_or_clone_arc(self);
254-
builder.session_cache_size = cache_size;
255-
Arc::new(builder)
256-
}
257-
258-
/// Set the size limit for the SQLite WAL files of stores given to
259-
/// [`ClientBuilder::session_paths`].
260-
///
261-
/// Each store uses the WAL journal mode. This method controls the size
262-
/// limit of the WAL files, in **bytes (!)**.
263-
///
264-
/// See [`SqliteStoreConfig::journal_size_limit`] to learn more.
265-
pub fn session_journal_size_limit(self: Arc<Self>, limit: Option<u32>) -> Arc<Self> {
266-
let mut builder = unwrap_or_clone_arc(self);
267-
builder.session_journal_size_limit = limit;
268-
Arc::new(builder)
269-
}
270-
271202
/// Tell the client that the system is memory constrained, like in a push
272203
/// notification process for example.
273204
///
@@ -404,6 +335,13 @@ impl ClientBuilder {
404335
Arc::new(builder)
405336
}
406337

338+
/// Use in-memory session storage.
339+
pub fn session_store_in_memory(self: Arc<Self>) -> Arc<Self> {
340+
let mut builder = unwrap_or_clone_arc(self);
341+
builder.session_store = Some(SessionStoreBuilder::InMemory);
342+
Arc::new(builder)
343+
}
344+
407345
pub async fn build(self: Arc<Self>) -> Result<Arc<Client>, ClientBuildError> {
408346
let builder = unwrap_or_clone_arc(self);
409347
let mut inner_builder = MatrixClient::builder();
@@ -413,48 +351,26 @@ impl ClientBuilder {
413351
inner_builder.cross_process_store_locks_holder_name(holder_name.clone());
414352
}
415353

416-
let store_path = if let Some(session_paths) = &builder.session_paths {
417-
// This is the path where both the state store and the crypto store will live.
418-
let data_path = Path::new(&session_paths.data_path);
419-
// This is the path where the event cache store will live.
420-
let cache_path = Path::new(&session_paths.cache_path);
354+
let session_store_path = if let Some(session_store) = &builder.session_store {
355+
match session_store.build()? {
356+
#[cfg(feature = "sqlite")]
357+
SessionStoreResult::Sqlite { config, cache_path, store_path: data_path } => {
358+
inner_builder = inner_builder
359+
.sqlite_store_with_config_and_cache_path(config, Some(cache_path));
421360

422-
debug!(
423-
data_path = %data_path.to_string_lossy(),
424-
event_cache_path = %cache_path.to_string_lossy(),
425-
"Creating directories for data (state and crypto) and cache stores.",
426-
);
427-
428-
fs::create_dir_all(data_path)?;
429-
fs::create_dir_all(cache_path)?;
430-
431-
let mut sqlite_store_config = if builder.system_is_memory_constrained {
432-
SqliteStoreConfig::with_low_memory_config(data_path)
433-
} else {
434-
SqliteStoreConfig::new(data_path)
435-
};
436-
437-
sqlite_store_config =
438-
sqlite_store_config.passphrase(builder.session_passphrase.as_deref());
439-
440-
if let Some(size) = builder.session_pool_max_size {
441-
sqlite_store_config = sqlite_store_config.pool_max_size(size);
442-
}
361+
Some(data_path)
362+
}
363+
#[cfg(feature = "indexeddb")]
364+
SessionStoreResult::IndexedDb { name, passphrase } => {
365+
inner_builder = inner_builder.indexeddb_store(&name, passphrase.as_deref());
443366

444-
if let Some(size) = builder.session_cache_size {
445-
sqlite_store_config = sqlite_store_config.cache_size(size);
446-
}
367+
None
368+
}
447369

448-
if let Some(limit) = builder.session_journal_size_limit {
449-
sqlite_store_config = sqlite_store_config.journal_size_limit(limit);
370+
SessionStoreResult::InMemory => None,
450371
}
451-
452-
inner_builder = inner_builder
453-
.sqlite_store_with_config_and_cache_path(sqlite_store_config, Some(cache_path));
454-
455-
Some(data_path.to_owned())
456372
} else {
457-
debug!("Not using a store path.");
373+
debug!("Not using a session store");
458374
None
459375
};
460376

@@ -594,13 +510,41 @@ impl ClientBuilder {
594510
sdk_client,
595511
builder.enable_oidc_refresh_lock,
596512
builder.session_delegate,
597-
store_path,
513+
session_store_path,
598514
)
599515
.await?,
600516
))
601517
}
602518
}
603519

520+
#[cfg(feature = "sqlite")]
521+
#[matrix_sdk_ffi_macros::export]
522+
impl ClientBuilder {
523+
/// Use SQLite as the session storage.
524+
pub fn session_store_with_sqlite(
525+
self: Arc<Self>,
526+
config: Arc<session_store::SqliteSessionStoreBuilder>,
527+
) -> Arc<Self> {
528+
let mut builder = unwrap_or_clone_arc(self);
529+
builder.session_store = Some(SessionStoreBuilder::Sqlite(unwrap_or_clone_arc(config)));
530+
Arc::new(builder)
531+
}
532+
}
533+
534+
#[cfg(feature = "indexeddb")]
535+
#[matrix_sdk_ffi_macros::export]
536+
impl ClientBuilder {
537+
/// Use IndexedDB as the session storage.
538+
pub fn session_store_with_indexeddb(
539+
self: Arc<Self>,
540+
config: Arc<session_store::IndexedDbSessionStoreBuilder>,
541+
) -> Arc<Self> {
542+
let mut builder = unwrap_or_clone_arc(self);
543+
builder.session_store = Some(SessionStoreBuilder::IndexedDb(unwrap_or_clone_arc(config)));
544+
Arc::new(builder)
545+
}
546+
}
547+
604548
#[cfg(not(target_family = "wasm"))]
605549
#[matrix_sdk_ffi_macros::export]
606550
impl ClientBuilder {
@@ -642,18 +586,8 @@ impl ClientBuilder {
642586
}
643587
}
644588

645-
/// The store paths the client will use when built.
646-
#[derive(Clone)]
647-
struct SessionPaths {
648-
/// The path that the client will use to store its data.
649-
data_path: String,
650-
/// The path that the client will use to store its caches. This path can be
651-
/// the same as the data path if you prefer to keep everything in one place.
652-
cache_path: String,
653-
}
654-
655-
#[derive(Clone, uniffi::Record)]
656589
/// The config to use for HTTP requests by default in this client.
590+
#[derive(Clone, uniffi::Record)]
657591
pub struct RequestConfig {
658592
/// Max number of retries.
659593
retry_limit: Option<u64>,

bindings/matrix-sdk-ffi/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ mod room_member;
2424
mod room_preview;
2525
mod ruma;
2626
mod runtime;
27+
mod session_store;
2728
mod session_verification;
2829
mod spaces;
2930
mod sync_service;

0 commit comments

Comments
 (0)