Skip to content

Commit 6c5cfeb

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

File tree

7 files changed

+322
-136
lines changed

7 files changed

+322
-136
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
@@ -31,14 +31,12 @@ unstable-msc4274 = ["matrix-sdk-ui/unstable-msc4274"]
3131
indexeddb = ["matrix-sdk/indexeddb"]
3232
# Use sqlite for session storage, not supported on Wasm platforms.
3333
sqlite = ["matrix-sdk/sqlite"]
34-
# provide a unified API regardless of target platforms, for use with multi-target platforms like react-native
35-
react-native = []
3634
# Required when targeting a Javascript environment, like Wasm in a browser.
3735
js = ["matrix-sdk-ui/js", "uuid/js"]
3836
# Use the TLS implementation provided by the host system, necessary on iOS and Wasm platforms.
39-
native-tls = ["matrix-sdk/native-tls", "sentry?/native-tls", "graphql_client/reqwest"]
37+
native-tls = ["matrix-sdk/native-tls", "sentry?/native-tls"]
4038
# Use Rustls as the TLS implementation, necessary on Android platforms.
41-
rustls-tls = ["matrix-sdk/rustls-tls", "sentry?/rustls", "graphql_client/reqwest-rustls"]
39+
rustls-tls = ["matrix-sdk/rustls-tls", "sentry?/rustls"]
4240
# Enable sentry error monitoring, not compatible on Wasm platforms.
4341
sentry = ["dep:sentry", "dep:sentry-tracing"]
4442

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: 46 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
use futures_util::StreamExt;
44
use matrix_sdk::{
@@ -12,11 +12,10 @@ use matrix_sdk::{
1212
VersionBuilderError,
1313
},
1414
Client as MatrixClient, ClientBuildError as MatrixClientBuildError, HttpError, IdParseError,
15-
RumaApiError, SqliteStoreConfig,
15+
RumaApiError,
1616
};
1717
use ruma::api::error::{DeserializationError, FromHttpResponseError};
1818
use tracing::{debug, error};
19-
use zeroize::Zeroizing;
2019

2120
use super::client::Client;
2221
use crate::{
@@ -26,6 +25,7 @@ use crate::{
2625
helpers::unwrap_or_clone_arc,
2726
qr_code::{HumanQrLoginError, QrCodeData, QrLoginProgressListener},
2827
runtime::get_runtime_handle,
28+
session_store::{SessionStoreConfig, SessionStoreResult},
2929
task_handle::TaskHandle,
3030
};
3131

@@ -108,11 +108,7 @@ impl From<ClientError> for ClientBuildError {
108108

109109
#[derive(Clone, uniffi::Object)]
110110
pub struct ClientBuilder {
111-
session_paths: Option<SessionPaths>,
112-
session_passphrase: Zeroizing<Option<String>>,
113-
session_pool_max_size: Option<usize>,
114-
session_cache_size: Option<u32>,
115-
session_journal_size_limit: Option<u32>,
111+
session_store: Option<SessionStoreConfig>,
116112
system_is_memory_constrained: bool,
117113
username: Option<String>,
118114
homeserver_cfg: Option<HomeserverConfig>,
@@ -138,11 +134,7 @@ impl ClientBuilder {
138134
#[uniffi::constructor]
139135
pub fn new() -> Arc<Self> {
140136
Arc::new(Self {
141-
session_paths: None,
142-
session_passphrase: Zeroizing::new(None),
143-
session_pool_max_size: None,
144-
session_cache_size: None,
145-
session_journal_size_limit: None,
137+
session_store: None,
146138
system_is_memory_constrained: false,
147139
username: None,
148140
homeserver_cfg: None,
@@ -193,73 +185,6 @@ impl ClientBuilder {
193185
Arc::new(builder)
194186
}
195187

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

428-
let store_path = if let Some(session_paths) = &builder.session_paths {
429-
// This is the path where both the state store and the crypto store will live.
430-
let data_path = Path::new(&session_paths.data_path);
431-
// This is the path where the event cache store will live.
432-
let cache_path = Path::new(&session_paths.cache_path);
433-
434-
debug!(
435-
data_path = %data_path.to_string_lossy(),
436-
event_cache_path = %cache_path.to_string_lossy(),
437-
"Creating directories for data (state and crypto) and cache stores.",
438-
);
439-
440-
fs::create_dir_all(data_path)?;
441-
fs::create_dir_all(cache_path)?;
442-
443-
let mut sqlite_store_config = if builder.system_is_memory_constrained {
444-
SqliteStoreConfig::with_low_memory_config(data_path)
445-
} else {
446-
SqliteStoreConfig::new(data_path)
447-
};
448-
449-
sqlite_store_config =
450-
sqlite_store_config.passphrase(builder.session_passphrase.as_deref());
451-
452-
if let Some(size) = builder.session_pool_max_size {
453-
sqlite_store_config = sqlite_store_config.pool_max_size(size);
454-
}
455-
456-
if let Some(size) = builder.session_cache_size {
457-
sqlite_store_config = sqlite_store_config.cache_size(size);
458-
}
459-
460-
if let Some(limit) = builder.session_journal_size_limit {
461-
sqlite_store_config = sqlite_store_config.journal_size_limit(limit);
353+
let mut store_path = None;
354+
if let Some(session_store) = builder.session_store {
355+
match session_store.build()? {
356+
#[cfg(feature = "indexeddb")]
357+
SessionStoreResult::IndexedDb { name, passphrase } => {
358+
inner_builder = inner_builder.indexeddb_store(&name, passphrase.as_deref());
359+
}
360+
#[cfg(feature = "sqlite")]
361+
SessionStoreResult::Sqlite { config, cache_path, store_path: data_path } => {
362+
inner_builder = inner_builder
363+
.sqlite_store_with_config_and_cache_path(config, Some(cache_path));
364+
store_path = Some(data_path);
365+
}
462366
}
463-
464-
inner_builder = inner_builder
465-
.sqlite_store_with_config_and_cache_path(sqlite_store_config, Some(cache_path));
466-
467-
Some(data_path.to_owned())
468367
} else {
469-
debug!("Not using a store path.");
470-
None
471-
};
368+
debug!("Not using a session store.")
369+
}
472370

473371
// Determine server either from URL, server name or user ID.
474372
inner_builder = match builder.homeserver_cfg {
@@ -646,14 +544,32 @@ impl ClientBuilder {
646544
}
647545
}
648546

649-
/// The store paths the client will use when built.
650-
#[derive(Clone)]
651-
struct SessionPaths {
652-
/// The path that the client will use to store its data.
653-
data_path: String,
654-
/// The path that the client will use to store its caches. This path can be
655-
/// the same as the data path if you prefer to keep everything in one place.
656-
cache_path: String,
547+
#[cfg(feature = "sqlite")]
548+
#[matrix_sdk_ffi_macros::export]
549+
impl ClientBuilder {
550+
/// Tell the client to use sqlite to store session data.
551+
pub fn session_store_sqlite(
552+
self: Arc<Self>,
553+
config: Arc<crate::session_store::SqliteSessionStoreBuilder>,
554+
) -> Arc<Self> {
555+
let mut builder = unwrap_or_clone_arc(self);
556+
builder.session_store = Some(SessionStoreConfig::Sqlite(config.as_ref().clone()));
557+
Arc::new(builder)
558+
}
559+
}
560+
561+
#[cfg(feature = "indexeddb")]
562+
#[matrix_sdk_ffi_macros::export]
563+
impl ClientBuilder {
564+
/// Tell the client to use IndexedDb to store session data.
565+
pub fn session_store_indexeddb(
566+
self: Arc<Self>,
567+
config: Arc<crate::session_store::IndexedDbSessionStoreBuilder>,
568+
) -> Arc<Self> {
569+
let mut builder = unwrap_or_clone_arc(self);
570+
builder.session_store = Some(SessionStoreConfig::IndexedDb(config.as_ref().clone()));
571+
Arc::new(builder)
572+
}
657573
}
658574

659575
#[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
@@ -28,6 +28,7 @@ mod room_member;
2828
mod room_preview;
2929
mod ruma;
3030
mod runtime;
31+
mod session_store;
3132
mod session_verification;
3233
mod sync_service;
3334
mod task_handle;

0 commit comments

Comments
 (0)