Skip to content

Commit eacaec7

Browse files
Daniel SalinasDaniel Salinas
authored andcommitted
Add support for indexeddb and sqlite session choice
1 parent 6cd9b5d commit eacaec7

File tree

7 files changed

+328
-138
lines changed

7 files changed

+328
-138
lines changed

bindings/matrix-sdk-ffi/CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@ All notable changes to this project will be documented in this file.
66

77
## [Unreleased] - ReleaseDate
88

9+
### Features
10+
- Configuration of the session store has been changed to allow for use of either indexeddb
11+
or sqlite on relevant platforms. These can be enabled via features, `indexeddb` or `sqlite`.
12+
13+
Previously the code to configure a sqlite session would look something like this in a host language:
14+
```
15+
builder
16+
.sessionPaths("data_path", "cache_path")
17+
.passphrase("foobar")
18+
```
19+
With the new system, a helper object is exposed for either Sqlite or IndexedDB to group those settings.
20+
```
21+
builder
22+
.session_store_sqlite(
23+
SqliteSessionStoreBuilder.new({ dataPath: "data_path", cachePath: "cache_path" })
24+
.passphrase("foobar")
25+
)
26+
```
27+
28+
The following methods from `ClientBuilder` have been moved onto `SqliteSessionStoreBuilder`:
29+
`session_paths`, `session_passphrase`, `session_pool_max_size`, `session_cache_size`, and `session_journal_size_limit`.
30+
31+
932
### Refactor
1033

1134
- Adjust features in the `matrix-sdk-ffi` crate to expose more platform-specific knobs.

bindings/matrix-sdk-ffi/Cargo.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,12 @@ unstable-msc4274 = ["matrix-sdk-ui/unstable-msc4274"]
2424
indexeddb = ["matrix-sdk/indexeddb"]
2525
# Use sqlite for session storage, not supported on Wasm platforms.
2626
sqlite = ["matrix-sdk/sqlite"]
27-
# provide a unified API regardless of target platforms, for use with multi-target platforms like react-native
28-
react-native = []
2927
# Required when targeting a Javascript environment, like Wasm in a browser.
3028
js = ["matrix-sdk-ui/js", "uuid/js"]
3129
# Use the TLS implementation provided by the host system, necessary on iOS and Wasm platforms.
32-
native-tls = ["matrix-sdk/native-tls", "sentry?/native-tls", "graphql_client/reqwest"]
30+
native-tls = ["matrix-sdk/native-tls", "sentry?/native-tls"]
3331
# Use Rustls as the TLS implementation, necessary on Android platforms.
34-
rustls-tls = ["matrix-sdk/rustls-tls", "sentry?/rustls", "graphql_client/reqwest-rustls"]
32+
rustls-tls = ["matrix-sdk/rustls-tls", "sentry?/rustls"]
3533
# Enable sentry error monitoring, not compatible on Wasm platforms.
3634
sentry = ["dep:sentry", "dep:sentry-tracing"]
3735

bindings/matrix-sdk-ffi/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Given the number of platforms targeted, we have broken out a number of features
1212
### Functionality
1313
- `sentry`: Enable error monitoring using Sentry, not supports on Wasm platforms.
1414
- `bundled-sqlite`: Use an embedded version of sqlite instead of the system provided one.
15+
- `sqlite`: Use sqlite for session storage, not available on Wasm platforms.
16+
- `indexeddb`: Use IndexedDb for session storage, only available on Wasm platforms.
1517

1618
### Unstable specs
1719
- `unstable-msc4274`: Adds support for gallery message types, which contain multiple media elements.
@@ -22,7 +24,7 @@ Each supported target should use features to select the relevant TLS system. He
2224

2325
- Android: `"bundled-sqlite,unstable-msc4274,rustls-tls,sentry"`
2426
- iOS: `"bundled-sqlite,unstable-msc4274,native-tls,sentry"`
25-
- Javascript/Wasm: `"unstable-msc4274,native-tls"`
27+
- Javascript/Wasm: `"indexeddb,unstable-msc4274,native-tls"`
2628

2729
### Swift/iOS sync
2830

bindings/matrix-sdk-ffi/build.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{
22
env,
33
error::Error,
44
path::{Path, PathBuf},
5-
process::Command,
5+
process::{self, Command},
66
};
77

88
use vergen::EmitBuilder;
@@ -56,7 +56,31 @@ fn get_clang_major_version(clang_path: &Path) -> String {
5656
clang_version.split('.').next().expect("could not parse clang output").to_owned()
5757
}
5858

59+
fn env_is_set(var_name: &str) -> bool {
60+
env::var_os(var_name).is_some()
61+
}
62+
63+
fn ensure(cond: bool, err: &str) {
64+
if !cond {
65+
eprintln!(
66+
"\n\
67+
┏━━━━━━━━{pad}━┓\n\
68+
┃ error: {err} ┃\n\
69+
┗━━━━━━━━{pad}━┛\n\
70+
",
71+
pad = "━".repeat(err.len()),
72+
);
73+
process::exit(1);
74+
}
75+
}
76+
5977
fn main() -> Result<(), Box<dyn Error>> {
78+
let sqlite_set = env_is_set("CARGO_FEATURE_SQLITE");
79+
let indexeddb_set = env_is_set("CARGO_FEATURE_INDEXEDDB");
80+
ensure(
81+
sqlite_set || indexeddb_set,
82+
"one of the features 'sqlite' or 'indexeddb' must be enabled",
83+
);
6084
setup_x86_64_android_workaround();
6185
uniffi::generate_scaffolding("./src/api.udl").expect("Building the UDL file failed");
6286
EmitBuilder::builder().git_sha(true).emit()?;

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

Lines changed: 52 additions & 132 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
use futures_util::StreamExt;
44
use matrix_sdk::{
@@ -16,17 +16,21 @@ use matrix_sdk::{
1616
VersionBuilderError,
1717
},
1818
Client as MatrixClient, ClientBuildError as MatrixClientBuildError, HttpError, IdParseError,
19-
RumaApiError, SqliteStoreConfig,
19+
RumaApiError,
2020
};
2121
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
2222
use ruma::api::error::{DeserializationError, FromHttpResponseError};
2323
use tracing::{debug, error};
24-
use zeroize::Zeroizing;
2524

2625
use super::client::Client;
2726
use crate::{
28-
authentication::OidcConfiguration, client::ClientSessionDelegate, error::ClientError,
29-
helpers::unwrap_or_clone_arc, runtime::get_runtime_handle, task_handle::TaskHandle,
27+
authentication::OidcConfiguration,
28+
client::ClientSessionDelegate,
29+
error::ClientError,
30+
helpers::unwrap_or_clone_arc,
31+
runtime::get_runtime_handle,
32+
session_store::{SessionStoreConfig, SessionStoreResult},
33+
task_handle::TaskHandle,
3034
};
3135

3236
/// A list of bytes containing a certificate in DER or PEM form.
@@ -266,11 +270,7 @@ impl From<ClientError> for ClientBuildError {
266270

267271
#[derive(Clone, uniffi::Object)]
268272
pub struct ClientBuilder {
269-
session_paths: Option<SessionPaths>,
270-
session_passphrase: Zeroizing<Option<String>>,
271-
session_pool_max_size: Option<usize>,
272-
session_cache_size: Option<u32>,
273-
session_journal_size_limit: Option<u32>,
273+
session_store: Option<SessionStoreConfig>,
274274
system_is_memory_constrained: bool,
275275
username: Option<String>,
276276
homeserver_cfg: Option<HomeserverConfig>,
@@ -296,11 +296,7 @@ impl ClientBuilder {
296296
#[uniffi::constructor]
297297
pub fn new() -> Arc<Self> {
298298
Arc::new(Self {
299-
session_paths: None,
300-
session_passphrase: Zeroizing::new(None),
301-
session_pool_max_size: None,
302-
session_cache_size: None,
303-
session_journal_size_limit: None,
299+
session_store: None,
304300
system_is_memory_constrained: false,
305301
username: None,
306302
homeserver_cfg: None,
@@ -351,73 +347,6 @@ impl ClientBuilder {
351347
Arc::new(builder)
352348
}
353349

354-
/// Sets the paths that the client will use to store its data and caches.
355-
/// Both paths **must** be unique per session as the SDK stores aren't
356-
/// capable of handling multiple users, however it is valid to use the
357-
/// same path for both stores on a single session.
358-
///
359-
/// Leaving this unset tells the client to use an in-memory data store.
360-
pub fn session_paths(self: Arc<Self>, data_path: String, cache_path: String) -> Arc<Self> {
361-
let mut builder = unwrap_or_clone_arc(self);
362-
builder.session_paths = Some(SessionPaths { data_path, cache_path });
363-
Arc::new(builder)
364-
}
365-
366-
/// Set the passphrase for the stores given to
367-
/// [`ClientBuilder::session_paths`].
368-
pub fn session_passphrase(self: Arc<Self>, passphrase: Option<String>) -> Arc<Self> {
369-
let mut builder = unwrap_or_clone_arc(self);
370-
builder.session_passphrase = Zeroizing::new(passphrase);
371-
Arc::new(builder)
372-
}
373-
374-
/// Set the pool max size for the SQLite stores given to
375-
/// [`ClientBuilder::session_paths`].
376-
///
377-
/// Each store exposes an async pool of connections. This method controls
378-
/// the size of the pool. The larger the pool is, the more memory is
379-
/// consumed, but also the more the app is reactive because it doesn't need
380-
/// to wait on a pool to be available to run queries.
381-
///
382-
/// See [`SqliteStoreConfig::pool_max_size`] to learn more.
383-
pub fn session_pool_max_size(self: Arc<Self>, pool_max_size: Option<u32>) -> Arc<Self> {
384-
let mut builder = unwrap_or_clone_arc(self);
385-
builder.session_pool_max_size = pool_max_size
386-
.map(|size| size.try_into().expect("`pool_max_size` is too large to fit in `usize`"));
387-
Arc::new(builder)
388-
}
389-
390-
/// Set the cache size for the SQLite stores given to
391-
/// [`ClientBuilder::session_paths`].
392-
///
393-
/// Each store exposes a SQLite connection. This method controls the cache
394-
/// size, in **bytes (!)**.
395-
///
396-
/// The cache represents data SQLite holds in memory at once per open
397-
/// database file. The default cache implementation does not allocate the
398-
/// full amount of cache memory all at once. Cache memory is allocated
399-
/// in smaller chunks on an as-needed basis.
400-
///
401-
/// See [`SqliteStoreConfig::cache_size`] to learn more.
402-
pub fn session_cache_size(self: Arc<Self>, cache_size: Option<u32>) -> Arc<Self> {
403-
let mut builder = unwrap_or_clone_arc(self);
404-
builder.session_cache_size = cache_size;
405-
Arc::new(builder)
406-
}
407-
408-
/// Set the size limit for the SQLite WAL files of stores given to
409-
/// [`ClientBuilder::session_paths`].
410-
///
411-
/// Each store uses the WAL journal mode. This method controls the size
412-
/// limit of the WAL files, in **bytes (!)**.
413-
///
414-
/// See [`SqliteStoreConfig::journal_size_limit`] to learn more.
415-
pub fn session_journal_size_limit(self: Arc<Self>, limit: Option<u32>) -> Arc<Self> {
416-
let mut builder = unwrap_or_clone_arc(self);
417-
builder.session_journal_size_limit = limit;
418-
Arc::new(builder)
419-
}
420-
421350
/// Tell the client that the system is memory constrained, like in a push
422351
/// notification process for example.
423352
///
@@ -583,50 +512,23 @@ impl ClientBuilder {
583512
inner_builder.cross_process_store_locks_holder_name(holder_name.clone());
584513
}
585514

586-
let store_path = if let Some(session_paths) = &builder.session_paths {
587-
// This is the path where both the state store and the crypto store will live.
588-
let data_path = Path::new(&session_paths.data_path);
589-
// This is the path where the event cache store will live.
590-
let cache_path = Path::new(&session_paths.cache_path);
591-
592-
debug!(
593-
data_path = %data_path.to_string_lossy(),
594-
event_cache_path = %cache_path.to_string_lossy(),
595-
"Creating directories for data (state and crypto) and cache stores.",
596-
);
597-
598-
fs::create_dir_all(data_path)?;
599-
fs::create_dir_all(cache_path)?;
600-
601-
let mut sqlite_store_config = if builder.system_is_memory_constrained {
602-
SqliteStoreConfig::with_low_memory_config(data_path)
603-
} else {
604-
SqliteStoreConfig::new(data_path)
605-
};
606-
607-
sqlite_store_config =
608-
sqlite_store_config.passphrase(builder.session_passphrase.as_deref());
609-
610-
if let Some(size) = builder.session_pool_max_size {
611-
sqlite_store_config = sqlite_store_config.pool_max_size(size);
612-
}
613-
614-
if let Some(size) = builder.session_cache_size {
615-
sqlite_store_config = sqlite_store_config.cache_size(size);
616-
}
617-
618-
if let Some(limit) = builder.session_journal_size_limit {
619-
sqlite_store_config = sqlite_store_config.journal_size_limit(limit);
515+
let mut store_path = None;
516+
if let Some(session_store) = builder.session_store {
517+
match session_store.build()? {
518+
#[cfg(feature = "indexeddb")]
519+
SessionStoreResult::IndexedDb { name, passphrase } => {
520+
inner_builder = inner_builder.indexeddb_store(&name, passphrase.as_deref());
521+
}
522+
#[cfg(feature = "sqlite")]
523+
SessionStoreResult::Sqlite { config, cache_path, store_path: data_path } => {
524+
inner_builder = inner_builder
525+
.sqlite_store_with_config_and_cache_path(config, Some(cache_path));
526+
store_path = Some(data_path);
527+
}
620528
}
621-
622-
inner_builder = inner_builder
623-
.sqlite_store_with_config_and_cache_path(sqlite_store_config, Some(cache_path));
624-
625-
Some(data_path.to_owned())
626529
} else {
627-
debug!("Not using a store path.");
628-
None
629-
};
530+
debug!("Not using a session store.")
531+
}
630532

631533
// Determine server either from URL, server name or user ID.
632534
inner_builder = match builder.homeserver_cfg {
@@ -804,14 +706,32 @@ impl ClientBuilder {
804706
}
805707
}
806708

807-
/// The store paths the client will use when built.
808-
#[derive(Clone)]
809-
struct SessionPaths {
810-
/// The path that the client will use to store its data.
811-
data_path: String,
812-
/// The path that the client will use to store its caches. This path can be
813-
/// the same as the data path if you prefer to keep everything in one place.
814-
cache_path: String,
709+
#[cfg(feature = "sqlite")]
710+
#[matrix_sdk_ffi_macros::export]
711+
impl ClientBuilder {
712+
/// Tell the client to use sqlite to store session data.
713+
pub fn session_store_sqlite(
714+
self: Arc<Self>,
715+
config: Arc<crate::session_store::SqliteSessionStoreBuilder>,
716+
) -> Arc<Self> {
717+
let mut builder = unwrap_or_clone_arc(self);
718+
builder.session_store = Some(SessionStoreConfig::Sqlite(config.as_ref().clone()));
719+
Arc::new(builder)
720+
}
721+
}
722+
723+
#[cfg(feature = "indexeddb")]
724+
#[matrix_sdk_ffi_macros::export]
725+
impl ClientBuilder {
726+
/// Tell the client to use IndexedDb to store session data.
727+
pub fn session_store_indexeddb(
728+
self: Arc<Self>,
729+
config: Arc<crate::session_store::IndexedDbSessionStoreBuilder>,
730+
) -> Arc<Self> {
731+
let mut builder = unwrap_or_clone_arc(self);
732+
builder.session_store = Some(SessionStoreConfig::IndexedDb(config.as_ref().clone()));
733+
Arc::new(builder)
734+
}
815735
}
816736

817737
#[derive(Clone, uniffi::Record)]

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ mod room_member;
2727
mod room_preview;
2828
mod ruma;
2929
mod runtime;
30+
mod session_store;
3031
mod session_verification;
3132
mod sync_service;
3233
mod task_handle;

0 commit comments

Comments
 (0)