Merge branch 'main' into mauroromito/ss_once

# Conflicts:
#	crates/matrix-sdk/src/sliding_sync/mod.rs
This commit is contained in:
Mauro Romito 2023-05-08 18:11:45 +02:00
commit 9c0e604247
37 changed files with 648 additions and 362 deletions

View File

@ -15,8 +15,9 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@nightly
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-05-06
components: rustfmt
- name: Run Benchmarks

View File

@ -34,9 +34,9 @@ jobs:
matrix:
name:
- no-encryption
- no-sled
- no-encryption-and-sled
- sled-cryptostore
- no-sqlite
- no-encryption-and-sqlite
- sqlite-cryptostore
- rustls-tls
- markdown
- socks
@ -327,8 +327,9 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@nightly
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-05-06
components: rustfmt
- name: Cargo fmt
@ -363,8 +364,9 @@ jobs:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install Rust
uses: dtolnay/rust-toolchain@nightly
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-05-06
components: clippy
- name: Load cache

View File

@ -20,7 +20,9 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@nightly
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-05-06
- name: Install Node.js
uses: actions/setup-node@v3

View File

@ -78,8 +78,9 @@ jobs:
- uses: actions/checkout@v3
if: "${{ !inputs.tag }}"
- name: Install Rust
uses: dtolnay/rust-toolchain@nightly
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-05-06
targets: ${{ matrix.target }}
- name: Install Node.js
uses: actions/setup-node@v3
@ -114,7 +115,9 @@ jobs:
- uses: actions/checkout@v3
if: "${{ !inputs.tag }}"
- name: Install Rust
uses: dtolnay/rust-toolchain@nightly
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-05-06
- name: Install Node.js
uses: actions/setup-node@v3
- name: Build lib

6
Cargo.lock generated
View File

@ -538,7 +538,7 @@ version = "1.0.0"
dependencies = [
"criterion",
"matrix-sdk-crypto",
"matrix-sdk-sled",
"matrix-sdk-sqlite",
"matrix-sdk-test",
"pprof",
"ruma",
@ -2633,7 +2633,7 @@ dependencies = [
"matrix-sdk-base",
"matrix-sdk-common",
"matrix-sdk-indexeddb",
"matrix-sdk-sled",
"matrix-sdk-sqlite",
"matrix-sdk-test",
"mime",
"mime2ext",
@ -2859,8 +2859,6 @@ dependencies = [
"futures-util",
"log-panics",
"matrix-sdk",
"matrix-sdk-sled",
"matrix-sdk-sqlite",
"mime",
"once_cell",
"opentelemetry",

View File

@ -10,7 +10,7 @@ publish = false
[dependencies]
criterion = { version = "0.4.0", features = ["async", "async_tokio", "html_reports"] }
matrix-sdk-crypto = { path = "../crates/matrix-sdk-crypto", version = "0.6.0"}
matrix-sdk-sled = { path = "../crates/matrix-sdk-sled", version = "0.2.0", default-features = false, features = ["crypto-store"] }
matrix-sdk-sqlite = { path = "../crates/matrix-sdk-sqlite", version = "0.1.0", default-features = false, features = ["crypto-store"] }
matrix-sdk-test = { path = "../testing/matrix-sdk-test", version = "0.6.0"}
ruma = { workspace = true }
serde_json = { workspace = true }

View File

@ -2,7 +2,7 @@ use std::{ops::Deref, sync::Arc};
use criterion::*;
use matrix_sdk_crypto::{EncryptionSettings, OlmMachine};
use matrix_sdk_sled::SledCryptoStore;
use matrix_sdk_sqlite::SqliteCryptoStore;
use matrix_sdk_test::response_from_file;
use ruma::{
api::{
@ -71,11 +71,11 @@ pub fn keys_query(c: &mut Criterion) {
});
let dir = tempfile::tempdir().unwrap();
let store = Arc::new(runtime.block_on(SledCryptoStore::open(dir.path(), None)).unwrap());
let store = Arc::new(runtime.block_on(SqliteCryptoStore::open(dir.path(), None)).unwrap());
let machine =
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
group.bench_with_input(BenchmarkId::new("sled store", &name), &response, |b, response| {
group.bench_with_input(BenchmarkId::new("sqlite store", &name), &response, |b, response| {
b.to_async(&runtime)
.iter(|| async { machine.mark_request_as_sent(&txn_id, response).await.unwrap() })
});
@ -114,12 +114,12 @@ pub fn keys_claiming(c: &mut Criterion) {
)
});
group.bench_with_input(BenchmarkId::new("sled store", &name), &response, |b, response| {
group.bench_with_input(BenchmarkId::new("sqlite store", &name), &response, |b, response| {
b.iter_batched(
|| {
let dir = tempfile::tempdir().unwrap();
let store =
Arc::new(runtime.block_on(SledCryptoStore::open(dir.path(), None)).unwrap());
Arc::new(runtime.block_on(SqliteCryptoStore::open(dir.path(), None)).unwrap());
let machine = runtime
.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store))
@ -181,14 +181,14 @@ pub fn room_key_sharing(c: &mut Criterion) {
})
});
let dir = tempfile::tempdir().unwrap();
let store = Arc::new(runtime.block_on(SledCryptoStore::open(dir.path(), None)).unwrap());
let store = Arc::new(runtime.block_on(SqliteCryptoStore::open(dir.path(), None)).unwrap());
let machine =
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
runtime.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response)).unwrap();
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
group.bench_function(BenchmarkId::new("sled store", &name), |b| {
group.bench_function(BenchmarkId::new("sqlite store", &name), |b| {
b.to_async(&runtime).iter(|| async {
let requests = machine
.share_room_key(
@ -236,14 +236,14 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
});
let dir = tempfile::tempdir().unwrap();
let store = Arc::new(runtime.block_on(SledCryptoStore::open(dir.path(), None)).unwrap());
let store = Arc::new(runtime.block_on(SqliteCryptoStore::open(dir.path(), None)).unwrap());
let machine =
runtime.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store)).unwrap();
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
group.bench_function(BenchmarkId::new("sled store", &name), |b| {
group.bench_function(BenchmarkId::new("sqlite store", &name), |b| {
b.to_async(&runtime).iter(|| async {
machine.get_missing_sessions(users.iter().map(Deref::deref)).await.unwrap()
})

View File

@ -14,7 +14,7 @@ crate-type = ["cdylib", "staticlib"]
[features]
default = ["bundled-sqlite"]
bundled-sqlite = ["matrix-sdk-sqlite/bundled"]
bundled-sqlite = ["matrix-sdk/bundled-sqlite"]
[build-dependencies]
uniffi = { workspace = true, features = ["build"] }
@ -27,8 +27,6 @@ eyeball-im = { workspace = true }
extension-trait = "1.0.1"
futures-core = "0.3.17"
futures-util = { version = "0.3.17", default-features = false }
matrix-sdk-sqlite = { path = "../../crates/matrix-sdk-sqlite", features = ["crypto-store"] }
matrix-sdk-sled = { path = "../../crates/matrix-sdk-sled" }
mime = "0.3.16"
# FIXME: we currently can't feature flag anything in the api.udl, therefore we must enforce experimental-sliding-sync being exposed here..
# see https://github.com/matrix-org/matrix-rust-sdk/issues/1014
@ -68,6 +66,7 @@ features = [
"markdown",
"socks",
"rustls-tls",
"sqlite",
]
[target.'cfg(not(target_os = "android"))'.dependencies.matrix-sdk]
@ -81,4 +80,5 @@ features = [
"markdown",
"native-tls",
"socks",
"sqlite",
]

View File

@ -91,6 +91,12 @@ callback interface SlidingSyncListRoomItemsObserver {
void did_receive_update();
};
interface SlidingSyncList {};
callback interface SlidingSyncListOnceBuilt {
SlidingSyncList update_list(SlidingSyncList list);
};
dictionary CreateRoomParameters {
string? name;
string? topic = null;

View File

@ -2,7 +2,6 @@ use std::{fs, path::PathBuf, sync::Arc};
use anyhow::anyhow;
use matrix_sdk::{
config::StoreConfig,
ruma::{
api::{error::UnknownVersionError, MatrixVersion},
ServerName, UserId,
@ -98,24 +97,7 @@ impl ClientBuilder {
let data_path = PathBuf::from(base_path).join(sanitize(username));
fs::create_dir_all(&data_path)?;
let mut state_store =
matrix_sdk_sled::SledStateStore::builder().path(data_path.to_owned());
if let Some(passphrase) = builder.passphrase.as_deref() {
state_store = state_store.passphrase(passphrase.to_owned());
}
let state_store = state_store.build()?;
let crypto_store = RUNTIME.block_on(matrix_sdk_sqlite::SqliteCryptoStore::open(
&data_path,
builder.passphrase.as_deref(),
))?;
let store_config =
StoreConfig::new().state_store(state_store).crypto_store(crypto_store);
inner_builder = inner_builder.store_config(store_config)
inner_builder = inner_builder.sqlite_store(&data_path, builder.passphrase.as_deref());
}
// Determine server either from URL, server name or user ID.

View File

@ -107,20 +107,17 @@ pub enum SlidingSyncError {
/// The response we've received from the server can't be parsed or doesn't
/// match up with the current expectations on the client side. A
/// `sync`-restart might be required.
BadResponse {
msg: String,
},
BadResponse { msg: String },
/// Called `.build()` on a builder type, but the given required field was
/// missing.
BuildMissingField { msg: String },
/// A `SlidingSyncListRequestGenerator` has been used without having been
/// initialized. It happens when a response is handled before a request has
/// been sent. It usually happens when testing.
RequestGeneratorHasNotBeenInitialized {
msg: String,
},
RequestGeneratorHasNotBeenInitialized { msg: String },
/// Someone has tried to modify a sliding sync list's ranges, but the
/// selected sync mode doesn't allow that.
CannotModifyRanges {
msg: String,
},
CannotModifyRanges { msg: String },
/// Ranges have a `start` bound greater than `end`.
InvalidRange {
/// Start bound.
@ -128,9 +125,10 @@ pub enum SlidingSyncError {
/// End bound.
end: u32,
},
Unknown {
error: String,
},
/// The SlidingSync internal channel is broken.
InternalChannelIsBroken,
/// Unknown or other error.
Unknown { error: String },
}
impl From<matrix_sdk::sliding_sync::Error> for SlidingSyncError {
@ -144,6 +142,7 @@ impl From<matrix_sdk::sliding_sync::Error> for SlidingSyncError {
}
E::CannotModifyRanges(msg) => Self::CannotModifyRanges { msg },
E::InvalidRange { start, end } => Self::InvalidRange { start, end },
E::InternalChannelIsBroken => Self::InternalChannelIsBroken,
error => Self::Unknown { error: error.to_string() },
}
}
@ -220,10 +219,10 @@ impl SlidingSyncRoom {
let (items, mut stoppable_spawn) = self.add_timeline_listener_inner(listener)?;
let room_id = self.inner.room_id().clone();
self.runner.subscribe(room_id.clone(), settings.map(Into::into));
self.runner.subscribe(room_id.clone(), settings.map(Into::into))?;
let runner = self.runner.clone();
stoppable_spawn.set_finalizer(Box::new(move || runner.unsubscribe(room_id)));
stoppable_spawn.set_finalizer(Box::new(move || runner.unsubscribe(room_id).unwrap()));
Ok(SlidingSyncSubscribeResult { items, task_handle: Arc::new(stoppable_spawn) })
}
@ -478,11 +477,6 @@ impl SlidingSyncListBuilder {
Arc::new(builder)
}
pub fn build(self: Arc<Self>) -> Arc<SlidingSyncList> {
let builder = unwrap_or_clone_arc(self);
Arc::new(builder.inner.build().into())
}
pub fn sort(self: Arc<Self>, sort: Vec<String>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.sort(sort);
@ -550,9 +544,26 @@ impl SlidingSyncListBuilder {
builder.inner = builder.inner.reset_ranges();
Arc::new(builder)
}
pub fn once_built(self: Arc<Self>, callback: Box<dyn SlidingSyncListOnceBuilt>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.once_built(
move |list: matrix_sdk::SlidingSyncList| -> matrix_sdk::SlidingSyncList {
let list = callback.update_list(Arc::new(list.into()));
unwrap_or_clone_arc(list).inner
},
);
Arc::new(builder)
}
}
#[derive(Clone, uniffi::Object)]
pub trait SlidingSyncListOnceBuilt: Sync + Send {
fn update_list(&self, list: Arc<SlidingSyncList>) -> Arc<SlidingSyncList>;
}
#[derive(Clone)]
pub struct SlidingSyncList {
inner: matrix_sdk::SlidingSyncList,
}
@ -685,12 +696,14 @@ impl SlidingSync {
room_id: String,
settings: Option<RoomSubscription>,
) -> Result<(), ClientError> {
self.inner.subscribe(room_id.try_into()?, settings.map(Into::into));
self.inner.subscribe(room_id.try_into()?, settings.map(Into::into))?;
Ok(())
}
pub fn unsubscribe(&self, room_id: String) -> Result<(), ClientError> {
self.inner.unsubscribe(room_id.try_into()?);
self.inner.unsubscribe(room_id.try_into()?)?;
Ok(())
}
@ -732,27 +745,18 @@ impl SlidingSync {
.collect())
}
#[allow(clippy::significant_drop_in_scrutinee)]
pub fn get_list(&self, name: String) -> Option<Arc<SlidingSyncList>> {
self.inner.list(&name).map(|inner| Arc::new(SlidingSyncList { inner }))
pub fn add_list(&self, list_builder: Arc<SlidingSyncListBuilder>) {
self.inner.add_list(unwrap_or_clone_arc(list_builder).inner).unwrap();
}
pub fn add_list(&self, list: Arc<SlidingSyncList>) -> Option<Arc<SlidingSyncList>> {
self.inner.add_list(list.inner.clone()).map(|inner| Arc::new(SlidingSyncList { inner }))
}
pub fn pop_list(&self, name: String) -> Option<Arc<SlidingSyncList>> {
self.inner.pop_list(&name).map(|inner| Arc::new(SlidingSyncList { inner }))
pub fn reset_lists(&self) -> Result<(), SlidingSyncError> {
self.inner.reset_lists().map_err(Into::into)
}
pub fn add_common_extensions(&self) {
self.inner.add_common_extensions();
}
pub fn reset_lists(&self) {
self.inner.reset_lists()
}
pub fn sync(&self) -> Arc<TaskHandle> {
let inner = self.inner.clone();
let client = self.client.clone();
@ -809,10 +813,9 @@ impl SlidingSyncBuilder {
Arc::new(builder)
}
pub fn add_list(self: Arc<Self>, v: Arc<SlidingSyncList>) -> Arc<Self> {
pub fn add_list(self: Arc<Self>, list_builder: Arc<SlidingSyncListBuilder>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
let list = unwrap_or_clone_arc(v);
builder.inner = builder.inner.add_list(list.inner);
builder.inner = builder.inner.add_list(unwrap_or_clone_arc(list_builder).inner);
Arc::new(builder)
}

View File

@ -14,6 +14,7 @@ coverage:
- "crates/matrix-sdk-crypto/"
- "crates/matrix-sdk-qrcode/"
- "crates/matrix-sdk-sled/"
- "crates/matrix-sdk-sqlite/"
- "crates/matrix-sdk-store-encryption/"
# Coverage of wasm tests isn't supported at the moment,
# see rustwasm/wasm-bindgen#2276

View File

@ -19,7 +19,7 @@ e2e-encryption = [
"matrix-sdk/e2e-encryption"
]
eyre = ["matrix-sdk/eyre"]
sled = ["matrix-sdk/sled"]
sqlite = ["matrix-sdk/sqlite"]
markdown = ["matrix-sdk/markdown"]
native-tls = ["matrix-sdk/native-tls"]

View File

@ -17,7 +17,7 @@
//! The storage layer for the [`OlmMachine`] can be customized using a trait.
//! Implementing your own [`CryptoStore`]
//!
//! An in-memory only store is provided as well as a Sled based one, depending
//! An in-memory only store is provided as well as a SQLite-based one, depending
//! on your needs and targets a custom store may be implemented, e.g. for
//! `wasm-unknown-unknown` an indexeddb store would be needed
//!

View File

@ -23,6 +23,10 @@ crypto-store = [
"matrix-sdk-base?/e2e-encryption",
]
docsrs = [
"crypto-store",
]
[dependencies]
async-trait = { workspace = true }
fs_extra = "1.2.0"

View File

@ -20,7 +20,7 @@ use matrix_sdk_crypto::CryptoStoreError;
use thiserror::Error;
use tokio::io;
/// All the errors that can occur when opening a sled store.
/// All the errors that can occur when opening a SQLite store.
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum OpenStoreError {

View File

@ -16,7 +16,10 @@
allow(dead_code, unused_imports)
)]
use std::path::Path;
use deadpool_sqlite::Object as SqliteConn;
use matrix_sdk_base::store::StoreConfig;
use matrix_sdk_store_encryption::StoreCipher;
#[cfg(feature = "crypto-store")]
@ -63,3 +66,26 @@ fn init_logging() {
.with(tracing_subscriber::fmt::layer().with_test_writer())
.init();
}
/// Create a [`StoreConfig`] with an opened [`SqliteStateStore`] in the given
/// directory and using the given passphrase. If the `crypto-store` feature is
/// enabled, a [`SqliteCryptoStore`] with the same parameters is also opened.
#[cfg(feature = "state-store")]
pub async fn make_store_config(
path: &Path,
passphrase: Option<&str>,
) -> Result<StoreConfig, OpenStoreError> {
let state_store = SqliteStateStore::open(path, passphrase).await?;
let config = StoreConfig::new().state_store(state_store);
#[cfg(feature = "crypto-store")]
{
let crypto_store = SqliteCryptoStore::open(path, passphrase).await?;
Ok(config.crypto_store(crypto_store))
}
#[cfg(not(feature = "crypto-store"))]
{
Ok(config)
}
}

View File

@ -3,6 +3,8 @@
- `Common::members` and `Common::members_no_sync` take a `RoomMemberships` to be able to filter the
results by any membership state.
- `Common::active_members(_no_sync)` and `Common::joined_members(_no_sync)` are deprecated.
- `matrix-sdk-sqlite` is the new default store implementation outside of WASM, behind the `sqlite` feature.
- The `sled` feature was removed. It is still possible to use `matrix-sdk-sled` as a custom store.
# 0.6.2

View File

@ -19,7 +19,7 @@ rustdoc-args = ["--cfg", "docsrs"]
default = [
"e2e-encryption",
"automatic-room-key-forwarding",
"sled",
"sqlite",
"native-tls",
]
testing = []
@ -27,12 +27,13 @@ testing = []
e2e-encryption = [
"matrix-sdk-base/e2e-encryption",
"matrix-sdk-base/automatic-room-key-forwarding",
"matrix-sdk-sled?/crypto-store", # activate crypto-store on sled if given
"matrix-sdk-sqlite?/crypto-store", # activate crypto-store on sqlite if given
"matrix-sdk-indexeddb?/e2e-encryption", # activate on indexeddb if given
]
js = ["matrix-sdk-common/js", "matrix-sdk-base/js"]
sled = ["dep:matrix-sdk-sled", "matrix-sdk-sled?/state-store"]
sqlite = ["dep:matrix-sdk-sqlite", "matrix-sdk-sqlite?/state-store"]
bundled-sqlite = ["sqlite", "matrix-sdk-sqlite?/bundled"]
indexeddb = ["dep:matrix-sdk-indexeddb"]
qrcode = ["e2e-encryption", "matrix-sdk-base/qrcode"]
@ -57,7 +58,7 @@ experimental-sliding-sync = [
docsrs = [
"e2e-encryption",
"sled",
"sqlite",
"sso-login",
"qrcode",
"image-proc",
@ -85,7 +86,7 @@ hyper = { version = "0.14.20", features = ["http1", "http2", "server"], optional
matrix-sdk-base = { version = "0.6.0", path = "../matrix-sdk-base", default_features = false }
matrix-sdk-common = { version = "0.6.0", path = "../matrix-sdk-common" }
matrix-sdk-indexeddb = { version = "0.2.0", path = "../matrix-sdk-indexeddb", default-features = false, optional = true }
matrix-sdk-sled = { version = "0.2.0", path = "../matrix-sdk-sled", default-features = false, optional = true }
matrix-sdk-sqlite = { version = "0.1.0", path = "../matrix-sdk-sqlite", default-features = false, optional = true }
mime = "0.3.16"
mime2ext = "0.1.52"
once_cell = { workspace = true }
@ -130,7 +131,7 @@ tokio = { workspace = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
backoff = { version = "0.4.0", features = ["tokio"] }
tokio = { workspace = true, features = ["fs", "rt"] }
tokio = { workspace = true, features = ["fs", "rt", "macros"] }
[dev-dependencies]
anyhow = { workspace = true }

View File

@ -65,7 +65,8 @@ The following crate feature flags are available:
| `js` | No | Enables JavaScript API usage for things like the current system time on WASM (does nothing on other targets) |
| `markdown` | No | Support for sending Markdown-formatted messages |
| `qrcode` | Yes | QR code verification support |
| `sled` | Yes | Persistent storage of state and E2EE data (optionally, if feature `e2e-encryption` is enabled), via Sled |
| `sqlite` | Yes | Persistent storage of state and E2EE data (optionally, if feature `e2e-encryption` is enabled), via SQLite available on system |
| `bundled-sqlite` | No | Persistent storage of state and E2EE data (optionally, if feature `e2e-encryption` is enabled), via SQLite compiled and bundled with the binary |
| `indexeddb` | No | Persistent storage of state and E2EE data (optionally, if feature `e2e-encryption` is enabled) for browsers, via IndexedDB |
| `socks` | No | SOCKS support in the default HTTP client, [`reqwest`] |
| `sso-login` | No | Support for SSO login with a local HTTP server |

View File

@ -118,19 +118,19 @@ impl ClientBuilder {
self
}
/// Set up the store configuration for a sled store.
/// Set up the store configuration for a SQLite store.
///
/// This is the same as
/// <code>.[store_config](Self::store_config)([matrix_sdk_sled]::[make_store_config](matrix_sdk_sled::make_store_config)(path, passphrase)?)</code>.
/// <code>.[store_config](Self::store_config)([matrix_sdk_sqlite]::[make_store_config](matrix_sdk_sqlite::make_store_config)(path, passphrase)?)</code>.
/// except it delegates the actual store config creation to when
/// `.build().await` is called.
#[cfg(feature = "sled")]
pub fn sled_store(
#[cfg(feature = "sqlite")]
pub fn sqlite_store(
mut self,
path: impl AsRef<std::path::Path>,
passphrase: Option<&str>,
) -> Self {
self.store_config = BuilderStoreConfig::Sled {
self.store_config = BuilderStoreConfig::Sqlite {
path: path.as_ref().to_owned(),
passphrase: passphrase.map(ToOwned::to_owned),
};
@ -342,9 +342,9 @@ impl ClientBuilder {
#[allow(clippy::infallible_destructuring_match)]
let store_config = match self.store_config {
#[cfg(feature = "sled")]
BuilderStoreConfig::Sled { path, passphrase } => {
matrix_sdk_sled::make_store_config(&path, passphrase.as_deref()).await?
#[cfg(feature = "sqlite")]
BuilderStoreConfig::Sqlite { path, passphrase } => {
matrix_sdk_sqlite::make_store_config(&path, passphrase.as_deref()).await?
}
#[cfg(feature = "indexeddb")]
BuilderStoreConfig::IndexedDb { name, passphrase } => {
@ -480,8 +480,8 @@ impl Default for HttpConfig {
#[derive(Clone)]
enum BuilderStoreConfig {
#[cfg(feature = "sled")]
Sled {
#[cfg(feature = "sqlite")]
Sqlite {
path: std::path::PathBuf,
passphrase: Option<String>,
},
@ -498,9 +498,9 @@ impl fmt::Debug for BuilderStoreConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[allow(clippy::infallible_destructuring_match)]
match self {
#[cfg(feature = "sled")]
Self::Sled { path, .. } => {
f.debug_struct("Sled").field("path", path).finish_non_exhaustive()
#[cfg(feature = "sqlite")]
Self::Sqlite { path, .. } => {
f.debug_struct("Sqlite").field("path", path).finish_non_exhaustive()
}
#[cfg(feature = "indexeddb")]
Self::IndexedDb { name, .. } => {
@ -535,10 +535,10 @@ pub enum ClientBuildError {
#[error(transparent)]
IndexeddbStore(#[from] matrix_sdk_indexeddb::OpenStoreError),
/// Error opening the sled store.
#[cfg(feature = "sled")]
/// Error opening the sqlite store.
#[cfg(feature = "sqlite")]
#[error(transparent)]
SledStore(#[from] matrix_sdk_sled::OpenStoreError),
SqliteStore(#[from] matrix_sdk_sqlite::OpenStoreError),
}
impl ClientBuildError {

View File

@ -1672,6 +1672,10 @@ impl Client {
/// use [`create_dm`][Self::create_dm], which is more convenient than
/// assembling the [`create_room::v3::Request`] yourself.
///
/// If the `is_direct` field of the request is set to `true` and at least
/// one user is invited, the room will be automatically added to the direct
/// rooms in the account data.
///
/// # Examples
///
/// ```no_run

View File

@ -216,7 +216,7 @@ is **not** supported using the default store.
| Failure | Cause | Fix |
| ------------------- | ----- | ----------- |
| No messages get encrypted nor decrypted | The `e2e-encryption` feature is disabled | [Enable the feature in your `Cargo.toml` file] |
| Messages that were decryptable aren't after a restart | Storage isn't setup to be persistent | Ensure you've activated the persistent storage backend feature, e.g. `sled` |
| Messages that were decryptable aren't after a restart | Storage isn't setup to be persistent | Ensure you've activated the persistent storage backend feature, e.g. `sqlite` |
| Messages are encrypted but can't be decrypted | The access token that the client is using is tied to another device | Clear storage to create a new device, read the [Restoring a Client] section |
| Messages don't get encrypted but get decrypted | The `m.room.encryption` event is missing | Make sure encryption is [enabled] for the room and the event isn't [filtered] out, otherwise it might be a deserialization bug |

View File

@ -386,6 +386,122 @@ impl Device {
self.inner.is_verified()
}
/// Is the device considered to be verified with cross-signing.
///
/// A device is considered to be verified if it's signed by the appropriate
/// cross-signing key.
///
/// ## Cross-signing verification
///
/// Cross-signing verification uses signatures over devices and user
/// identities to check if a device is considered to be verified. The
/// signatures can be uploaded to the homeserver, this allows us to
/// share the verification state with other devices. Devices only need to
/// verify a user identity, if the user identity has verified and signed
/// the device we can consider the device to be verified as well.
///
/// Devices are usually cross-signing verified using interactive
/// verification, which can be started using the
/// [`Device::request_verification()`] method.
///
/// A [`Device`] can also be manually signed using the [`Device::verify()`]
/// method, this works only for devices belonging to our own user.
///
/// Do note that the device that is being manually signed will not trust our
/// own user identity like it would if we interactively verify the device.
/// Such a device can mark our own user as verified using the
/// [`UserIdentity::verify()`] method.
///
/// ### Verification of devices belonging to our own user.
///
/// If the device belongs to our own user, the device will be considered to
/// be verified if:
///
/// * The device has been signed by our self-signing key
/// * Our own user identity is considered to be [verified]
///
/// In other words we need to find a valid signature chain from our user
/// identity to the device:
///
///```text
/// ┌─────────────────────────────────────┐ ┌─────────────┐
/// │ Own User Identity │ │ Device │
/// ├──────────────────┬──────────────────┤───►├─────────────┤
/// │ Master Key │ Self-signing Key │ │ Device Keys │
/// └──────────────────┴──────────────────┘ └─────────────┘
/// ```
///
/// ### Verification of devices belonging to other users.
///
/// If the device belongs to some other user it will be considered to be
/// verified if:
///
/// * The device has been signed by the user's self-signing key
/// * The user's master-signing key has been signed by our own user-signing
/// key, i.e. our own identity trusts the other users identity.
/// * Our own user identity is considered to be [verified]
///
/// ```text
/// ┌─────────────────────────────────────┐
/// │ Own User Identity │
/// ├──────────────────┬──────────────────┤─────┐
/// │ Master Key │ User-signing Key │ │
/// └──────────────────┴──────────────────┘ │
/// ┌───────────────────────────────────────────────────┘
/// │
/// │ ┌─────────────────────────────────────┐ ┌─────────────┐
/// │ │ User Identity │ │ Device │
/// └──────►├──────────────────┬──────────────────┤───►│─────────────│
/// │ Master Key │ Self-signing Key │ │ Device Keys │
/// └──────────────────┴──────────────────┘ └─────────────┘
/// ```
///
/// # Examples
///
/// Let's check if a device is verified:
///
/// ```no_run
/// # use matrix_sdk::{
/// # Client,
/// # ruma::{
/// # device_id, user_id,
/// # events::key::verification::VerificationMethod,
/// # }
/// # };
/// # use url::Url;
/// # use futures::executor::block_on;
/// # block_on(async {
/// # let alice = user_id!("@alice:example.org");
/// # let homeserver = Url::parse("http://example.com")?;
/// # let client = Client::new(homeserver).await?;
/// let device =
/// client.encryption().get_device(alice, device_id!("DEVICEID")).await?;
///
/// if let Some(device) = device {
/// if device.is_verified_with_cross_signing() {
/// println!(
/// "Device {} of user {} is verified with cross-signing",
/// device.device_id(),
/// device.user_id()
/// );
/// } else {
/// println!(
/// "Device {} of user {} is not verified with cross-signing",
/// device.device_id(),
/// device.user_id()
/// );
/// }
/// }
/// # anyhow::Ok(()) });
/// ```
///
/// [`UserIdentity::verify()`]:
/// crate::encryption::identities::UserIdentity::verify
/// [verified]: crate::encryption::identities::UserIdentity::is_verified
pub fn is_verified_with_cross_signing(&self) -> bool {
self.inner.is_cross_signing_trusted()
}
/// Set the local trust state of the device to the given state.
///
/// This won't affect any cross signing verification state, this only sets

View File

@ -434,8 +434,7 @@ let full_sync_list = SlidingSyncList::builder(&full_sync_list_name)
(StateEventType::RoomEncryption, "".to_owned())
]) // only want to know if the room is encrypted
.full_sync_batch_size(50) // grow the window by 50 items at a time
.full_sync_maximum_number_of_rooms_to_fetch(500) // only sync up the top 500 rooms
.build();
.full_sync_maximum_number_of_rooms_to_fetch(500); // only sync up the top 500 rooms
let active_list = SlidingSyncList::builder(&active_list_name) // the active window
.sync_mode(SlidingSyncMode::Selective) // sync up the specific range only
@ -446,8 +445,7 @@ let active_list = SlidingSyncList::builder(&active_list_name) // the active wind
(StateEventType::RoomEncryption, "".to_owned()), // is it encrypted
(StateEventType::RoomTopic, "".to_owned()), // any topic if known
(StateEventType::RoomAvatar, "".to_owned()), // avatar if set
])
.build();
]);
let sliding_sync = sliding_sync_builder
.add_list(active_list)
@ -457,10 +455,9 @@ let sliding_sync = sliding_sync_builder
// subscribe to the list APIs for updates
let active_list = sliding_sync.list(&active_list_name).unwrap();
let list_state_stream = active_list.state_stream();
let list_count_stream = active_list.maximum_number_of_rooms_stream();
let list_stream = active_list.room_list_stream();
let (list_state_stream, list_count_stream, list_stream) = sliding_sync.on_list(&active_list_name, |list| {
(list.state_stream(), list.maximum_number_of_rooms_stream(), list.room_list_stream())
}).unwrap();
tokio::spawn(async move {
pin_mut!(list_state_stream);

View File

@ -13,10 +13,11 @@ use ruma::{
events::TimelineEventType,
OwnedRoomId,
};
use tokio::sync::{mpsc::channel, RwLock as AsyncRwLock};
use url::Url;
use super::{
cache::restore_sliding_sync_state, SlidingSync, SlidingSyncInner, SlidingSyncList,
cache::restore_sliding_sync_state, SlidingSync, SlidingSyncInner, SlidingSyncListBuilder,
SlidingSyncPositionMarkers, SlidingSyncRoom,
};
use crate::{Client, Result};
@ -25,12 +26,12 @@ use crate::{Client, Result};
///
/// Get a new builder with methods like [`crate::Client::sliding_sync`], or
/// [`crate::SlidingSync::builder`].
#[derive(Clone, Debug)]
#[derive(Debug, Clone)]
pub struct SlidingSyncBuilder {
storage_key: Option<String>,
homeserver: Option<Url>,
client: Client,
lists: BTreeMap<String, SlidingSyncList>,
lists: Vec<SlidingSyncListBuilder>,
bump_event_types: Vec<TimelineEventType>,
extensions: Option<ExtensionsConfig>,
subscriptions: BTreeMap<OwnedRoomId, v4::RoomSubscription>,
@ -42,7 +43,7 @@ impl SlidingSyncBuilder {
storage_key: None,
homeserver: None,
client,
lists: BTreeMap::new(),
lists: Vec::new(),
bump_event_types: Vec::new(),
extensions: None,
subscriptions: BTreeMap::new(),
@ -64,9 +65,8 @@ impl SlidingSyncBuilder {
/// Add the given list to the lists.
///
/// Replace any list with the name.
pub fn add_list(mut self, list: SlidingSyncList) -> Self {
self.lists.insert(list.name().to_owned(), list);
pub fn add_list(mut self, list_builder: SlidingSyncListBuilder) -> Self {
self.lists.push(list_builder);
self
}
@ -206,12 +206,22 @@ impl SlidingSyncBuilder {
let mut delta_token = None;
let mut rooms_found: BTreeMap<OwnedRoomId, SlidingSyncRoom> = BTreeMap::new();
let (internal_channel_sender, internal_channel_receiver) = channel(8);
let mut lists = BTreeMap::new();
for list_builder in self.lists {
let list = list_builder.build(internal_channel_sender.clone());
lists.insert(list.name().to_owned(), list);
}
// Load an existing state from the cache.
if let Some(storage_key) = &self.storage_key {
restore_sliding_sync_state(
&client,
storage_key,
&mut self.lists,
&mut lists,
&mut delta_token,
&mut rooms_found,
&mut self.extensions,
@ -220,7 +230,7 @@ impl SlidingSyncBuilder {
}
let rooms = StdRwLock::new(rooms_found);
let lists = StdRwLock::new(self.lists);
let lists = StdRwLock::new(lists);
Ok(SlidingSync::new(SlidingSyncInner {
homeserver: self.homeserver,
@ -241,6 +251,11 @@ impl SlidingSyncBuilder {
subscriptions: StdRwLock::new(self.subscriptions),
unsubscribe: Default::default(),
internal_channel: (
internal_channel_sender,
AsyncRwLock::new(internal_channel_receiver),
),
}))
}
}

View File

@ -244,7 +244,7 @@ mod tests {
.sliding_sync()
.await
.storage_key(Some("hello".to_owned()))
.add_list(SlidingSyncList::builder("list_foo").build())
.add_list(SlidingSyncList::builder("list_foo"))
.build()
.await?;
@ -278,7 +278,7 @@ mod tests {
.sliding_sync()
.await
.storage_key(Some("hello".to_owned()))
.add_list(SlidingSyncList::builder("list_foo").build())
.add_list(SlidingSyncList::builder("list_foo"))
.build()
.await?;

View File

@ -29,4 +29,7 @@ pub enum Error {
/// End bound.
end: u32,
},
/// The internal channel of `SlidingSync` seems to be broken.
#[error("SlidingSync's internal channel is broken")]
InternalChannelIsBroken,
}

View File

@ -1,24 +1,26 @@
//! Builder for [`SlidingSyncList`].
use std::{
fmt::Debug,
convert::identity,
fmt,
sync::{Arc, RwLock as StdRwLock},
};
use eyeball::unique::Observable;
use eyeball_im::ObservableVector;
use ruma::{api::client::sync::sync_events::v4, events::StateEventType, UInt};
use tokio::sync::mpsc::Sender;
use super::{
SlidingSyncList, SlidingSyncListInner, SlidingSyncListRequestGenerator, SlidingSyncMode,
SlidingSyncState,
super::SlidingSyncInternalMessage, SlidingSyncList, SlidingSyncListInner,
SlidingSyncListRequestGenerator, SlidingSyncMode, SlidingSyncState,
};
/// The default name for the full sync list.
pub const FULL_SYNC_LIST_NAME: &str = "full-sync";
/// Builder for [`SlidingSyncList`].
#[derive(Clone, Debug)]
#[derive(Clone)]
pub struct SlidingSyncListBuilder {
sync_mode: SlidingSyncMode,
sort: Vec<String>,
@ -29,6 +31,28 @@ pub struct SlidingSyncListBuilder {
timeline_limit: Option<UInt>,
name: String,
ranges: Vec<(UInt, UInt)>,
once_built: Arc<Box<dyn Fn(SlidingSyncList) -> SlidingSyncList + Send + Sync>>,
}
// Print debug values for the builder, except `once_built` which is ignored.
impl fmt::Debug for SlidingSyncListBuilder {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SlidingSyncListBuilder")
.field("sync_mode", &self.sync_mode)
.field("sort", &self.sort)
.field("required_state", &self.required_state)
.field("full_sync_batch_size", &self.full_sync_batch_size)
.field(
"full_sync_maximum_number_of_rooms_to_fetch",
&self.full_sync_maximum_number_of_rooms_to_fetch,
)
.field("filters", &self.filters)
.field("timeline_limit", &self.timeline_limit)
.field("name", &self.name)
.field("ranges", &self.ranges)
.finish_non_exhaustive()
}
}
impl SlidingSyncListBuilder {
@ -46,9 +70,19 @@ impl SlidingSyncListBuilder {
timeline_limit: None,
name: name.into(),
ranges: Vec::new(),
once_built: Arc::new(Box::new(identity)),
}
}
/// foo
pub fn once_built<C>(mut self, callback: C) -> Self
where
C: Fn(SlidingSyncList) -> SlidingSyncList + Send + Sync + 'static,
{
self.once_built = Arc::new(Box::new(callback));
self
}
/// Create a Builder set up for full sync.
pub fn default_with_fullsync() -> Self {
Self::new(FULL_SYNC_LIST_NAME).sync_mode(SlidingSyncMode::Paging)
@ -133,7 +167,10 @@ impl SlidingSyncListBuilder {
}
/// Build the list.
pub fn build(self) -> SlidingSyncList {
pub(in super::super) fn build(
self,
sliding_sync_internal_channel_sender: Sender<SlidingSyncInternalMessage>,
) -> SlidingSyncList {
let request_generator = match &self.sync_mode {
SlidingSyncMode::Paging => SlidingSyncListRequestGenerator::new_paging(
self.full_sync_batch_size,
@ -148,7 +185,7 @@ impl SlidingSyncListBuilder {
SlidingSyncMode::Selective => SlidingSyncListRequestGenerator::new_selective(),
};
SlidingSyncList {
let list = SlidingSyncList {
inner: Arc::new(SlidingSyncListInner {
// From the builder
sync_mode: self.sync_mode,
@ -166,7 +203,13 @@ impl SlidingSyncListBuilder {
state: StdRwLock::new(Observable::new(SlidingSyncState::default())),
maximum_number_of_rooms: StdRwLock::new(Observable::new(None)),
room_list: StdRwLock::new(ObservableVector::new()),
sliding_sync_internal_channel_sender,
}),
}
};
let once_built = self.once_built;
once_built(list)
}
}

View File

@ -22,9 +22,10 @@ pub(super) use request_generator::*;
pub use room_list_entry::RoomListEntry;
use ruma::{api::client::sync::sync_events::v4, assign, events::StateEventType, OwnedRoomId, UInt};
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc::Sender;
use tracing::{instrument, warn};
use super::{Error, FrozenSlidingSyncRoom, SlidingSyncRoom};
use super::{Error, FrozenSlidingSyncRoom, SlidingSyncInternalMessage, SlidingSyncRoom};
use crate::Result;
/// Holding a specific filtered list within the concept of sliding sync.
@ -72,7 +73,7 @@ impl SlidingSyncList {
}
self.inner.set_ranges(ranges);
self.reset();
self.reset()?;
Ok(())
}
@ -87,7 +88,7 @@ impl SlidingSyncList {
}
self.inner.set_ranges(&[(start, end)]);
self.reset();
self.reset()?;
Ok(())
}
@ -102,7 +103,7 @@ impl SlidingSyncList {
}
self.inner.add_range((start, end));
self.reset();
self.reset()?;
Ok(())
}
@ -119,7 +120,7 @@ impl SlidingSyncList {
}
self.inner.set_ranges::<UInt>(&[]);
self.reset();
self.reset()?;
Ok(())
}
@ -219,8 +220,16 @@ impl SlidingSyncList {
}
// Reset `Self`.
pub(super) fn reset(&self) {
pub(super) fn reset(&self) -> Result<(), Error> {
self.inner.reset();
// When a list is reset, the sync loop must be “restarted”.
self.inner
.sliding_sync_internal_channel_sender
.blocking_send(SlidingSyncInternalMessage::ContinueSyncLoop)
.map_err(|_| Error::InternalChannelIsBroken)?;
Ok(())
}
}
@ -275,6 +284,8 @@ pub(super) struct SlidingSyncListInner {
/// The request generator, i.e. a type that yields the appropriate list
/// request. See [`SlidingSyncListRequestGenerator`] to learn more.
request_generator: StdRwLock<SlidingSyncListRequestGenerator>,
sliding_sync_internal_channel_sender: Sender<SlidingSyncInternalMessage>,
}
impl SlidingSyncListInner {
@ -871,11 +882,19 @@ impl SlidingSyncMode {
#[cfg(test)]
mod tests {
use std::{
cell::Cell,
sync::{Arc, Mutex},
};
use futures::StreamExt;
use imbl::vector;
use ruma::{api::client::sync::sync_events::v4::SlidingOp, room_id, uint};
use serde_json::json;
use tokio::{spawn, sync::mpsc::unbounded_channel};
use tokio::{
spawn,
sync::mpsc::{channel, unbounded_channel},
};
use super::*;
@ -902,10 +921,12 @@ mod tests {
#[test]
fn test_sliding_sync_list_set_ranges() {
let (sender, _receiver) = channel(1);
let list = SlidingSyncList::builder("foo")
.sync_mode(SlidingSyncMode::Selective)
.ranges(ranges![(0, 1), (2, 3)].to_vec())
.build();
.build(sender);
{
let lock = list.inner.ranges.read().unwrap();
@ -926,12 +947,14 @@ mod tests {
#[test]
fn test_sliding_sync_list_set_range() {
let (sender, _receiver) = channel(1);
// Set range on `Selective`.
{
let list = SlidingSyncList::builder("foo")
.sync_mode(SlidingSyncMode::Selective)
.ranges(ranges![(0, 1), (2, 3)].to_vec())
.build();
.build(sender.clone());
{
let lock = list.inner.ranges.read().unwrap();
@ -952,14 +975,17 @@ mod tests {
// Set range on `Growing`.
{
let list = SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::Growing).build();
let list = SlidingSyncList::builder("foo")
.sync_mode(SlidingSyncMode::Growing)
.build(sender.clone());
assert!(list.set_range(4u32, 5).is_err());
}
// Set range on `Paging`.
{
let list = SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::Paging).build();
let list =
SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::Paging).build(sender);
assert!(list.set_range(4u32, 5).is_err());
}
@ -967,12 +993,14 @@ mod tests {
#[test]
fn test_sliding_sync_list_add_range() {
let (sender, _receiver) = channel(1);
// Add range on `Selective`.
{
let list = SlidingSyncList::builder("foo")
.sync_mode(SlidingSyncMode::Selective)
.ranges(ranges![(0, 1)].to_vec())
.build();
.build(sender.clone());
{
let lock = list.inner.ranges.read().unwrap();
@ -993,14 +1021,17 @@ mod tests {
// Add range on `Growing`.
{
let list = SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::Growing).build();
let list = SlidingSyncList::builder("foo")
.sync_mode(SlidingSyncMode::Growing)
.build(sender.clone());
assert!(list.add_range((2u32, 3)).is_err());
}
// Add range on `Paging`.
{
let list = SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::Paging).build();
let list =
SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::Paging).build(sender);
assert!(list.add_range((2u32, 3)).is_err());
}
@ -1008,12 +1039,14 @@ mod tests {
#[test]
fn test_sliding_sync_list_reset_ranges() {
let (sender, _receiver) = channel(1);
// Reset ranges on `Selective`.
{
let list = SlidingSyncList::builder("foo")
.sync_mode(SlidingSyncMode::Selective)
.ranges(ranges![(0, 1)].to_vec())
.build();
.build(sender.clone());
{
let lock = list.inner.ranges.read().unwrap();
@ -1034,14 +1067,17 @@ mod tests {
// Reset range on `Growing`.
{
let list = SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::Growing).build();
let list = SlidingSyncList::builder("foo")
.sync_mode(SlidingSyncMode::Growing)
.build(sender.clone());
assert!(list.reset_ranges().is_err());
}
// Reset range on `Paging`.
{
let list = SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::Paging).build();
let list =
SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::Paging).build(sender);
assert!(list.reset_ranges().is_err());
}
@ -1049,11 +1085,13 @@ mod tests {
#[test]
fn test_sliding_sync_list_timeline_limit() {
let (sender, _receiver) = channel(1);
let list = SlidingSyncList::builder("foo")
.sync_mode(SlidingSyncMode::Selective)
.ranges(ranges![(0, 1)].to_vec())
.timeline_limit(7u32)
.build();
.build(sender);
{
let lock = list.inner.timeline_limit.read().unwrap();
@ -1083,10 +1121,12 @@ mod tests {
#[test]
fn test_sliding_sync_get_room_id() {
let (sender, _receiver) = channel(1);
let mut list = SlidingSyncList::builder("foo")
.sync_mode(SlidingSyncMode::Selective)
.add_range(0u32, 1)
.build();
.build(sender);
let room0 = room_id!("!room0:bar.org");
let room1 = room_id!("!room1:bar.org");
@ -1158,10 +1198,12 @@ mod tests {
#[test]
fn test_generator_paging_full_sync() {
let (sender, _receiver) = channel(1);
let mut list = SlidingSyncList::builder("testing")
.sync_mode(crate::SlidingSyncMode::Paging)
.full_sync_batch_size(10)
.build();
.build(sender);
assert_ranges! {
list = list,
@ -1199,11 +1241,13 @@ mod tests {
#[test]
fn test_generator_paging_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
let (sender, _receiver) = channel(1);
let mut list = SlidingSyncList::builder("testing")
.sync_mode(crate::SlidingSyncMode::Paging)
.full_sync_batch_size(10)
.full_sync_maximum_number_of_rooms_to_fetch(22)
.build();
.build(sender);
assert_ranges! {
list = list,
@ -1241,10 +1285,12 @@ mod tests {
#[test]
fn test_generator_growing_full_sync() {
let (sender, _receiver) = channel(1);
let mut list = SlidingSyncList::builder("testing")
.sync_mode(crate::SlidingSyncMode::Growing)
.full_sync_batch_size(10)
.build();
.build(sender);
assert_ranges! {
list = list,
@ -1282,11 +1328,13 @@ mod tests {
#[test]
fn test_generator_growing_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
let (sender, _receiver) = channel(1);
let mut list = SlidingSyncList::builder("testing")
.sync_mode(crate::SlidingSyncMode::Growing)
.full_sync_batch_size(10)
.full_sync_maximum_number_of_rooms_to_fetch(22)
.build();
.build(sender);
assert_ranges! {
list = list,
@ -1324,10 +1372,12 @@ mod tests {
#[test]
fn test_generator_selective() {
let (sender, _receiver) = channel(1);
let mut list = SlidingSyncList::builder("testing")
.sync_mode(crate::SlidingSyncMode::Selective)
.ranges(ranges![(0, 10), (42, 153)].to_vec())
.build();
.build(sender);
assert_ranges! {
list = list,
@ -1355,10 +1405,12 @@ mod tests {
#[test]
fn test_generator_selective_with_modifying_ranges_on_the_fly() {
let (sender, _receiver) = channel(4);
let mut list = SlidingSyncList::builder("testing")
.sync_mode(crate::SlidingSyncMode::Selective)
.ranges(ranges![(0, 10), (42, 153)].to_vec())
.build();
.build(sender);
assert_ranges! {
list = list,
@ -1439,10 +1491,12 @@ mod tests {
#[tokio::test]
#[allow(clippy::await_holding_lock)]
async fn test_sliding_sync_inner_update_state_room_list_and_maximum_number_of_rooms() {
let (sender, _receiver) = channel(1);
let mut list = SlidingSyncList::builder("foo")
.sync_mode(SlidingSyncMode::Selective)
.add_range(0u32, 3)
.build();
.build(sender);
assert_eq!(**list.inner.maximum_number_of_rooms.read().unwrap(), None);
assert_eq!(list.inner.room_list.read().unwrap().len(), 0);
@ -2093,4 +2147,24 @@ mod tests {
room_list = [F("!r0:x.y"), F("!r1:x.y"), F("!r2:x.y")],
};
}
#[test]
fn test_once_built() {
let (sender, _receiver) = channel(1);
let probe = Arc::new(Mutex::new(Cell::new(false)));
let probe_clone = probe.clone();
let _list = SlidingSyncList::builder("testing")
.once_built(move |list| {
let mut probe_lock = probe.lock().unwrap();
*probe_lock.get_mut() = true;
list
})
.build(sender);
let probe_lock = probe_clone.lock().unwrap();
assert!(probe_lock.get());
}
}

View File

@ -23,7 +23,6 @@ mod list;
mod room;
use std::{
borrow::BorrowMut,
collections::BTreeMap,
fmt::Debug,
mem,
@ -34,6 +33,7 @@ use std::{
time::Duration,
};
use async_stream::stream;
pub use builder::*;
pub use client::*;
pub use error::*;
@ -52,7 +52,13 @@ use ruma::{
OwnedRoomId, RoomId,
};
use serde::{Deserialize, Serialize};
use tokio::{spawn, sync::Mutex as AsyncMutex};
use tokio::{
select, spawn,
sync::{
mpsc::{Receiver, Sender},
Mutex as AsyncMutex, RwLock as AsyncRwLock,
},
};
use tracing::{debug, error, info_span, instrument, warn, Instrument, Span};
use url::Url;
use uuid::Uuid;
@ -113,6 +119,9 @@ pub(super) struct SlidingSyncInner {
/// The intended state of the extensions being supplied to sliding /sync
/// calls. May contain the latest next_batch for to_devices, etc.
extensions: Mutex<Option<ExtensionsConfig>>,
internal_channel:
(Sender<SlidingSyncInternalMessage>, AsyncRwLock<Receiver<SlidingSyncInternalMessage>>),
}
impl SlidingSync {
@ -130,23 +139,35 @@ impl SlidingSync {
}
/// Subscribe to a given room.
///
/// Note: this does not cancel any pending request, so make sure to only
/// poll the stream after you've altered this. If you do that during, it
/// might take one round trip to take effect.
pub fn subscribe(&self, room_id: OwnedRoomId, settings: Option<v4::RoomSubscription>) {
pub fn subscribe(
&self,
room_id: OwnedRoomId,
settings: Option<v4::RoomSubscription>,
) -> Result<()> {
self.inner.subscriptions.write().unwrap().insert(room_id, settings.unwrap_or_default());
self.inner
.internal_channel
.0
.blocking_send(SlidingSyncInternalMessage::ContinueSyncLoop)
.map_err(|_| Error::InternalChannelIsBroken)?;
Ok(())
}
/// Unsubscribe from a given room.
///
/// Note: this does not cancel any pending request, so make sure to only
/// poll the stream after you've altered this. If you do that during, it
/// might take one round trip to take effect.
pub fn unsubscribe(&self, room_id: OwnedRoomId) {
pub fn unsubscribe(&self, room_id: OwnedRoomId) -> Result<()> {
if self.inner.subscriptions.write().unwrap().remove(&room_id).is_some() {
self.inner.unsubscribe.write().unwrap().push(room_id);
self.inner
.internal_channel
.0
.blocking_send(SlidingSyncInternalMessage::ContinueSyncLoop)
.map_err(|_| Error::InternalChannelIsBroken)?;
}
Ok(())
}
/// Add the common extensions if not already configured.
@ -190,23 +211,14 @@ impl SlidingSync {
.since = Some(since);
}
/// Get access to the SlidingSyncList named `list_name`.
///
/// Note: Remember that this list might have been changed since you started
/// listening to the stream and is therefor not necessarily up to date
/// with the lists used for the stream.
pub fn list(&self, list_name: &str) -> Option<SlidingSyncList> {
self.inner.lists.read().unwrap().get(list_name).cloned()
}
/// Find a list by its name, and do something on it if it exists.
pub fn on_list<F, R>(&self, list_name: &str, f: F) -> Option<R>
where
F: FnOnce(&SlidingSyncList) -> R,
{
let lists = self.inner.lists.read().unwrap();
/// Remove the SlidingSyncList named `list_name` from the lists list if
/// found.
///
/// Note: Remember that this change will only be applicable for any new
/// stream created after this. The old stream will still continue to use the
/// previous set of lists.
pub fn pop_list(&self, list_name: &String) -> Option<SlidingSyncList> {
self.inner.lists.write().unwrap().remove(list_name)
lists.get(list_name).map(f)
}
/// Add the list to the list of lists.
@ -214,12 +226,13 @@ impl SlidingSync {
/// As lists need to have a unique `.name`, if a list with the same name
/// is found the new list will replace the old one and the return it or
/// `None`.
///
/// Note: Remember that this change will only be applicable for any new
/// stream created after this. The old stream will still continue to use the
/// previous set of lists.
pub fn add_list(&self, list: SlidingSyncList) -> Option<SlidingSyncList> {
self.inner.lists.write().unwrap().insert(list.name().to_owned(), list)
pub fn add_list(
&self,
list_builder: SlidingSyncListBuilder,
) -> Result<Option<SlidingSyncList>> {
let list = list_builder.build(self.inner.internal_channel.0.clone());
Ok(self.inner.lists.write().unwrap().insert(list.name().to_owned(), list))
}
/// Lookup a set of rooms
@ -277,12 +290,11 @@ impl SlidingSync {
}
/// Handle the HTTP response.
#[instrument(skip_all, fields(lists = lists.len()))]
#[instrument(skip_all, fields(lists = self.inner.lists.read().unwrap().len()))]
fn handle_response(
&self,
sliding_sync_response: v4::Response,
mut sync_response: SyncResponse,
lists: &mut BTreeMap<String, SlidingSyncList>,
) -> Result<UpdateSummary, crate::Error> {
{
debug!(
@ -338,6 +350,8 @@ impl SlidingSync {
// Update the lists.
let mut updated_lists = Vec::with_capacity(sliding_sync_response.lists.len());
let mut lists = self.inner.lists.write().unwrap();
for (name, updates) in sliding_sync_response.lists {
let Some(list) = lists.get_mut(&name) else {
error!("Response for list `{name}` - unknown to us; skipping");
@ -367,65 +381,68 @@ impl SlidingSync {
/// Sync once just to update the E2E encryption keys.
pub async fn sync_once_for_keys(&self) -> Result<()> {
let stream_id = Uuid::new_v4().to_string();
let keys_list = SlidingSyncList::builder("sync_once_for_keys").build();
let keys_list = SlidingSyncListBuilder::default_with_fullsync();
self.add_list(keys_list);
let lists = Arc::new(Mutex::new(self.inner.lists.read().unwrap().clone()));
self.sync_once(&stream_id, lists).await?;
self.sync_once(&stream_id).await?;
Ok(())
}
async fn sync_once(
&self,
stream_id: &str,
lists: Arc<Mutex<BTreeMap<String, SlidingSyncList>>>,
) -> Result<Option<UpdateSummary>> {
let mut requests_lists = BTreeMap::new();
async fn sync_once(&self, stream_id: &str) -> Result<Option<UpdateSummary>> {
let (request, request_config) = {
// Collect requests for lists.
let mut requests_lists = BTreeMap::new();
{
let mut lists_lock = lists.lock().unwrap();
let lists = lists_lock.borrow_mut();
{
let mut lists = self.inner.lists.write().unwrap();
if lists.is_empty() {
return Ok(None);
if lists.is_empty() {
return Ok(None);
}
for (name, list) in lists.iter_mut() {
requests_lists.insert(name.clone(), list.next_request()?);
}
}
for (name, list) in lists.iter_mut() {
requests_lists.insert(name.clone(), list.next_request()?);
}
}
// Collect the `pos` and `delta_token`.
let (pos, delta_token) = {
let position_lock = self.inner.position.read().unwrap();
let (pos, delta_token) = {
let position_lock = self.inner.position.read().unwrap();
(position_lock.pos.clone(), position_lock.delta_token.clone())
};
(position_lock.pos.clone(), position_lock.delta_token.clone())
// Collect other data.
let room_subscriptions = self.inner.subscriptions.read().unwrap().clone();
let unsubscribe_rooms = mem::take(&mut *self.inner.unsubscribe.write().unwrap());
let timeout = Duration::from_secs(30);
let extensions = self.prepare_extension_config(pos.as_deref());
(
// Build the request itself.
assign!(v4::Request::new(), {
pos,
delta_token,
// We want to track whether the incoming response maps to this
// request. We use the (optional) `txn_id` field for that.
txn_id: Some(stream_id.to_owned()),
timeout: Some(timeout),
lists: requests_lists,
bump_event_types: self.inner.bump_event_types.clone(),
room_subscriptions,
unsubscribe_rooms,
extensions,
}),
// Configure long-polling. We need 30 seconds for the long-poll itself, in
// addition to 30 more extra seconds for the network delays.
RequestConfig::default().timeout(timeout + Duration::from_secs(30)),
)
};
let room_subscriptions = self.inner.subscriptions.read().unwrap().clone();
let unsubscribe_rooms = mem::take(&mut *self.inner.unsubscribe.write().unwrap());
let timeout = Duration::from_secs(30);
let extensions = self.prepare_extension_config(pos.as_deref());
debug!("Sending the sliding sync request");
// Configure long-polling. We need 30 seconds for the long-poll itself, in
// addition to 30 more extra seconds for the network delays.
let request_config = RequestConfig::default().timeout(timeout + Duration::from_secs(30));
// Prepare the request.
let request = self.inner.client.send_with_homeserver(
assign!(v4::Request::new(), {
pos,
delta_token,
// We want to track whether the incoming response maps to this
// request. We use the (optional) `txn_id` field for that.
txn_id: Some(stream_id.to_owned()),
timeout: Some(timeout),
lists: requests_lists,
bump_event_types: self.inner.bump_event_types.clone(),
room_subscriptions,
unsubscribe_rooms,
extensions,
}),
request,
Some(request_config),
self.inner.homeserver.as_ref().map(ToString::to_string),
);
@ -508,7 +525,7 @@ impl SlidingSync {
debug!(?sync_response, "Sliding Sync response has been handled by the client");
let updates = this.handle_response(response, sync_response, lists.lock().unwrap().borrow_mut())?;
let updates = this.handle_response(response, sync_response)?;
this.cache_to_storage().await?;
@ -528,9 +545,6 @@ impl SlidingSync {
#[allow(unknown_lints, clippy::let_with_type_underscore)] // triggered by instrument macro
#[instrument(name = "sync_stream", skip_all)]
pub fn stream(&self) -> impl Stream<Item = Result<UpdateSummary, crate::Error>> + '_ {
// Copy all the lists.
let lists = Arc::new(Mutex::new(self.inner.lists.read().unwrap().clone()));
// Define a stream ID.
let stream_id = Uuid::new_v4().to_string();
@ -538,7 +552,7 @@ impl SlidingSync {
let instrument_span = Span::current();
async_stream::stream! {
stream! {
loop {
let sync_span = info_span!(parent: &instrument_span, "sync_once");
@ -546,49 +560,72 @@ impl SlidingSync {
debug!(?self.inner.extensions, "Sync stream loop is running");
});
match self.sync_once(&stream_id, lists.clone()).instrument(sync_span.clone()).await {
Ok(Some(updates)) => {
self.inner.reset_counter.store(0, Ordering::SeqCst);
let mut internal_channel_receiver_lock = self.inner.internal_channel.1.write().await;
yield Ok(updates);
}
select! {
biased;
Ok(None) => {
break;
}
Err(error) => {
if error.client_api_error_kind() == Some(&ErrorKind::UnknownPos) {
// The session has expired.
// Has it expired too many times?
if self.inner.reset_counter.fetch_add(1, Ordering::SeqCst) >= MAXIMUM_SLIDING_SYNC_SESSION_EXPIRATION {
sync_span.in_scope(|| error!("Session expired {MAXIMUM_SLIDING_SYNC_SESSION_EXPIRATION} times in a row"));
// The session has expired too many times, let's raise an error!
yield Err(error);
internal_message = internal_channel_receiver_lock.recv() => {
use SlidingSyncInternalMessage::*;
match internal_message {
None | Some(BreakSyncLoop) => {
break;
}
// Let's reset the Sliding Sync session.
sync_span.in_scope(|| {
warn!("Session expired. Restarting Sliding Sync.");
Some(ContinueSyncLoop) => {
continue;
}
}
}
// To “restart” a Sliding Sync session, we set `pos` to its initial value.
{
let mut position_lock = self.inner.position.write().unwrap();
update_summary = self.sync_once(&stream_id).instrument(sync_span.clone()) => {
match update_summary {
Ok(Some(updates)) => {
self.inner.reset_counter.store(0, Ordering::SeqCst);
Observable::set(&mut position_lock.pos, None);
yield Ok(updates);
}
Ok(None) => {
break;
}
Err(error) => {
if error.client_api_error_kind() == Some(&ErrorKind::UnknownPos) {
// The session has expired.
// Has it expired too many times?
if self.inner.reset_counter.fetch_add(1, Ordering::SeqCst) >= MAXIMUM_SLIDING_SYNC_SESSION_EXPIRATION {
sync_span.in_scope(|| error!("Session expired {MAXIMUM_SLIDING_SYNC_SESSION_EXPIRATION} times in a row"));
// The session has expired too many times, let's raise an error!
yield Err(error);
break;
}
// Let's reset the Sliding Sync session.
sync_span.in_scope(|| {
warn!("Session expired. Restarting Sliding Sync.");
// To “restart” a Sliding Sync session, we set `pos` to its initial value.
{
let mut position_lock = self.inner.position.write().unwrap();
Observable::set(&mut position_lock.pos, None);
}
debug!(?self.inner.extensions, "Sliding Sync has been reset");
});
}
debug!(?self.inner.extensions, "Sliding Sync has been reset");
});
yield Err(error);
continue;
}
}
yield Err(error);
continue;
}
}
}
@ -596,15 +633,24 @@ impl SlidingSync {
}
/// Resets the lists.
pub fn reset_lists(&self) {
pub fn reset_lists(&self) -> Result<(), Error> {
let lists = self.inner.lists.read().unwrap();
for (_, list) in lists.iter() {
list.reset();
list.reset()?;
}
Ok(())
}
}
#[derive(Debug)]
enum SlidingSyncInternalMessage {
#[allow(unused)] // temporary
BreakSyncLoop,
ContinueSyncLoop,
}
#[cfg(any(test, feature = "testing"))]
impl SlidingSync {
/// Get a copy of the `pos` value.

View File

@ -92,7 +92,7 @@ async fn restore_session(session_file: &Path) -> anyhow::Result<(Client, Option<
// Build the client with the previous settings from the session.
let client = Client::builder()
.homeserver_url(client_session.homeserver)
.sled_store(client_session.db_path, Some(&client_session.passphrase))
.sqlite_store(client_session.db_path, Some(&client_session.passphrase))
.build()
.await?;
@ -165,7 +165,7 @@ async fn build_client(data_dir: &Path) -> anyhow::Result<(Client, ClientSession)
// Generating a subfolder for the database is not mandatory, but it is useful if
// you allow several clients to run at the same time. Each one must have a
// separate database, which is a different folder with the sled store.
// separate database, which is a different folder with the SQLite store.
let db_subfolder: String =
(&mut rng).sample_iter(Alphanumeric).take(7).map(char::from).collect();
let db_path = data_dir.join(db_subfolder);
@ -186,10 +186,10 @@ async fn build_client(data_dir: &Path) -> anyhow::Result<(Client, ClientSession)
match Client::builder()
.homeserver_url(&homeserver)
// We use the sled store, which is enabled by default. This is the crucial part to
// We use the SQLite store, which is enabled by default. This is the crucial part to
// persist the encryption setup.
// Note that other store backends are available and you an even implement your own.
.sled_store(&db_path, Some(&passphrase))
// Note that other store backends are available and you can even implement your own.
.sqlite_store(&db_path, Some(&passphrase))
.build()
.await
{

View File

@ -18,5 +18,5 @@ url = "2.2.2"
[dependencies.matrix-sdk]
path = "../../crates/matrix-sdk"
features = ["experimental-timeline", "sled"]
features = ["experimental-timeline"]
version = "0.6.0"

View File

@ -36,7 +36,7 @@ pub fn test_server_conf() -> (String, String) {
)
}
pub async fn get_client_for_user(username: String, use_sled_store: bool) -> Result<Client> {
pub async fn get_client_for_user(username: String, use_sqlite_store: bool) -> Result<Client> {
let mut users = USERS.lock().await;
if let Some((client, _)) = users.get(&username) {
return Ok(client.clone());
@ -50,8 +50,8 @@ pub async fn get_client_for_user(username: String, use_sled_store: bool) -> Resu
.user_agent("matrix-sdk-integation-tests")
.homeserver_url(homeserver_url)
.request_config(RequestConfig::short_retry());
let client = if use_sled_store {
client_builder.sled_store(tmp_dir.path(), None).build().await?
let client = if use_sqlite_store {
client_builder.sqlite_store(tmp_dir.path(), None).build().await?
} else {
client_builder.build().await?
};

View File

@ -18,7 +18,7 @@ use crate::helpers::get_client_for_user;
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_repeated_join_leave() -> Result<()> {
let peter = get_client_for_user("peter".to_owned(), true).await?;
// FIXME: Run once with memory, once with sled
// FIXME: Run once with memory, once with SQLite
let karl = get_client_for_user("karl".to_owned(), false).await?;
let karl_id = karl.user_id().expect("karl has a userid!").to_owned();

View File

@ -1,36 +1,17 @@
#![cfg(test)]
use std::{
iter::{once, repeat},
time::{Duration, Instant},
};
use anyhow::{bail, Context};
use assert_matches::assert_matches;
use eyeball_im::VectorDiff;
use anyhow::Context;
use futures::{pin_mut, stream::StreamExt};
use matrix_sdk::{
ruma::{
api::client::{
error::ErrorKind as RumaError,
receipt::create_receipt::v3::ReceiptType as CreateReceiptType,
room::create_room::v3::Request as CreateRoomRequest,
sync::sync_events::v4::ReceiptsConfig,
},
events::{
receipt::{ReceiptThread, ReceiptType},
room::message::RoomMessageEventContent,
},
uint,
},
Client, RoomListEntry, SlidingSyncBuilder, SlidingSyncList, SlidingSyncMode, SlidingSyncState,
};
use matrix_sdk::{Client, RoomListEntry, SlidingSyncBuilder, SlidingSyncList, SlidingSyncMode};
use matrix_sdk_integration_testing::helpers::get_client_for_user;
async fn setup(name: String, use_sled_store: bool) -> anyhow::Result<(Client, SlidingSyncBuilder)> {
async fn setup(
name: String,
use_sqlite_store: bool,
) -> anyhow::Result<(Client, SlidingSyncBuilder)> {
let sliding_sync_proxy_url =
option_env!("SLIDING_SYNC_PROXY_URL").unwrap_or("http://localhost:8338").to_owned();
let client = get_client_for_user(name, use_sled_store).await?;
let client = get_client_for_user(name, use_sqlite_store).await?;
let sliding_sync_builder = client
.sliding_sync()
.await
@ -39,33 +20,6 @@ async fn setup(name: String, use_sled_store: bool) -> anyhow::Result<(Client, Sl
Ok((client, sliding_sync_builder))
}
async fn random_setup_with_rooms(
number_of_rooms: usize,
) -> anyhow::Result<(Client, SlidingSyncBuilder)> {
random_setup_with_rooms_opt_store(number_of_rooms, false).await
}
async fn random_setup_with_rooms_opt_store(
number_of_rooms: usize,
use_sled_store: bool,
) -> anyhow::Result<(Client, SlidingSyncBuilder)> {
let namespace = uuid::Uuid::new_v4().to_string();
let (client, sliding_sync_builder) = setup(namespace.clone(), use_sled_store).await?;
for room_num in 0..number_of_rooms {
make_room(&client, format!("{namespace}-{room_num}")).await?
}
Ok((client, sliding_sync_builder))
}
async fn make_room(client: &Client, room_name: String) -> anyhow::Result<()> {
let mut request = CreateRoomRequest::new();
request.name = Some(room_name);
let _event_id = client.create_room(request).await?;
Ok(())
}
#[derive(PartialEq, Eq, Clone, Debug)]
enum RoomListEntryEasy {
Empty,
@ -91,8 +45,7 @@ async fn it_works_smoke_test() -> anyhow::Result<()> {
SlidingSyncList::builder("foo")
.sync_mode(SlidingSyncMode::Selective)
.add_range(0u32, 10)
.timeline_limit(0u32)
.build(),
.timeline_limit(0u32),
)
.build()
.await?;
@ -105,6 +58,7 @@ async fn it_works_smoke_test() -> anyhow::Result<()> {
Ok(())
}
/*
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn modifying_timeline_limit() -> anyhow::Result<()> {
let (client, sync_builder) = random_setup_with_rooms(1).await?;
@ -443,9 +397,9 @@ async fn live_lists() -> anyhow::Result<()> {
// we only heard about the ones we had asked for
assert_eq!(summary.lists, [list_name_1, list_name_2, list_name_3]);
let Some(list_2) = sync_proxy.pop_list(&list_name_2.to_owned()) else {
bail!("Room exists");
};
let Some(list_2) = sync_proxy.get_list(&list_name_2.to_owned()) else {
bail!("Room exists");
};
// we need to restart the stream after every list listing update
let stream = sync_proxy.stream();
@ -1305,3 +1259,4 @@ async fn receipts_extension_works() -> anyhow::Result<()> {
assert!(found_receipt);
Ok(())
}
*/

View File

@ -5,15 +5,16 @@ use xshell::{cmd, pushd};
use crate::{build_docs, workspace, DenyWarnings, Result};
const NIGHTLY: &str = "nightly-2023-05-06";
const WASM_TIMEOUT_ENV_KEY: &str = "WASM_BINDGEN_TEST_TIMEOUT";
const WASM_TIMEOUT_VALUE: &str = "120";
#[derive(Args)]
pub struct CiArgs {
#[clap(subcommand)]
cmd: Option<CiCommand>,
}
const WASM_TIMEOUT_ENV_KEY: &str = "WASM_BINDGEN_TEST_TIMEOUT";
const WASM_TIMEOUT_VALUE: &str = "120";
#[derive(Subcommand)]
enum CiCommand {
/// Check style
@ -62,9 +63,9 @@ enum CiCommand {
#[derive(Subcommand, PartialEq, Eq, PartialOrd, Ord)]
enum FeatureSet {
NoEncryption,
NoSled,
NoEncryptionAndSled,
SledCryptostore,
NoSqlite,
NoEncryptionAndSqlite,
SqliteCryptostore,
RustlsTls,
Markdown,
Socks,
@ -156,7 +157,7 @@ fn check_examples() -> Result<()> {
}
fn check_style() -> Result<()> {
cmd!("rustup run nightly cargo fmt -- --check").run()?;
cmd!("rustup run {NIGHTLY} cargo fmt -- --check").run()?;
Ok(())
}
@ -168,9 +169,9 @@ fn check_typos() -> Result<()> {
}
fn check_clippy() -> Result<()> {
cmd!("rustup run nightly cargo clippy --all-targets -- -D warnings").run()?;
cmd!("rustup run {NIGHTLY} cargo clippy --all-targets -- -D warnings").run()?;
cmd!(
"rustup run nightly cargo clippy --workspace --all-targets
"rustup run {NIGHTLY} cargo clippy --workspace --all-targets
--exclude matrix-sdk-crypto --exclude xtask
--no-default-features
--features native-tls,experimental-sliding-sync,sso-login,experimental-timeline
@ -178,7 +179,7 @@ fn check_clippy() -> Result<()> {
)
.run()?;
cmd!(
"rustup run nightly cargo clippy --all-targets -p matrix-sdk-crypto
"rustup run {NIGHTLY} cargo clippy --all-targets -p matrix-sdk-crypto
--no-default-features -- -D warnings"
)
.run()?;
@ -193,13 +194,13 @@ fn run_feature_tests(cmd: Option<FeatureSet>) -> Result<()> {
let args = BTreeMap::from([
(
FeatureSet::NoEncryption,
"--no-default-features --features sled,native-tls,experimental-sliding-sync",
"--no-default-features --features sqlite,native-tls,experimental-sliding-sync",
),
(FeatureSet::NoSled, "--no-default-features --features e2e-encryption,native-tls"),
(FeatureSet::NoEncryptionAndSled, "--no-default-features --features native-tls"),
(FeatureSet::NoSqlite, "--no-default-features --features e2e-encryption,native-tls"),
(FeatureSet::NoEncryptionAndSqlite, "--no-default-features --features native-tls"),
(
FeatureSet::SledCryptostore,
"--no-default-features --features e2e-encryption,sled,native-tls",
FeatureSet::SqliteCryptostore,
"--no-default-features --features e2e-encryption,sqlite,native-tls",
),
(FeatureSet::RustlsTls, "--no-default-features --features rustls-tls"),
(FeatureSet::Markdown, "--features markdown"),