Compare commits

..

40 Commits

Author SHA1 Message Date
Ivan Enderlin c5f2460e02 fix(sdk): Disable OrderTracker for the moment.
This patch removes the use of `OrderTracker` because the
implementation of `EventCacheStore::load_all_chunks_metadata` for
`SqliteEventCacheStore` is the cause of severe slownesses (up to 100s
for some account).

We are going to undo this patch once the problem has been solved.
2025-07-15 12:17:28 +02:00
fl0lli 8ad785a117 docs(ffi): move entry for device_id change of url_for_oidc to top
Signed-off-by: fl0lli <github@fl0lli.de>
2025-07-15 10:14:17 +02:00
fl0lli a5b936d0b6 docs(ffi): add breaking change entry for Client::url_for_oidc
Signed-off-by: fl0lli <github@fl0lli.de>
2025-07-15 10:14:17 +02:00
fl0lli 1a0544c8eb chore(ffi): formatting
Signed-off-by: fl0lli <github@fl0lli.de>
2025-07-15 10:14:17 +02:00
fl0lli 232c23e8df feat(ffi): allow setting existing device id for Client.url_for_oidc
Signed-off-by: fl0lli <github@fl0lli.de>
2025-07-15 10:14:17 +02:00
Jonas Platte 7d9d5bf3b4 refactor: Use if-let chain 2025-07-15 08:41:44 +02:00
Jonas Platte ea076b3d76 chore: Upgrade testing crates to Rust Edition 2024 2025-07-15 08:41:44 +02:00
Jonas Platte 8aa6f97f7c chore: Upgrade benchmarks to Rust Edition 2024 2025-07-15 08:39:27 +02:00
Jonas Platte 679aa07115 chore(ui): Upgrade to Rust edition 2024 2025-07-14 19:50:36 +02:00
Jonas Platte 0c66d8a53f refactor(ui): Cherry-pick some edition-fix changes
Automated with `cargo fix --edition -p matrix-sdk-ui`, reverting
unnecessary changes.
2025-07-14 19:50:36 +02:00
Michael Goldenberg 25ed7eef2b doc(indexeddb): add explanation of error types in EventCacheStore::load_last_chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-14 18:46:20 +02:00
Michael Goldenberg a399840dff refactor(indexeddb): separate transaction and event cache error conversions
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-14 18:46:20 +02:00
Michael Goldenberg 8d4e7f0478 test(indexeddb): add IndexedDB-specific integration tests for loading last chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-14 18:46:20 +02:00
Michael Goldenberg 3bd93130c5 feat(indexeddb): add IndexedDB-backed impl for EventCacheStore::load_last_chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-14 18:46:20 +02:00
Michael Goldenberg 153618b77c refactor(indexeddb): add helper fns for EventCacheStore::load_last_chunk
Signed-off-by: Michael Goldenberg <m@mgoldenberg.net>
2025-07-14 18:46:20 +02:00
Kévin Commaille dd871ef9ac refactor(base): Store RoomPowerLevels in RoomMember
Avoids to carry around the event content only to convert it when we
want to use it. Avoids also to carry around the room creator when we
might not need it.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-14 18:04:33 +02:00
Kévin Commaille 8a29f17d1d refactor(base): Call Room::power_levels instead of loading event from the store
To reduce code duplication.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-14 18:04:33 +02:00
Kévin Commaille fab520ab33 refactor(base): Add methods on StateChanges to get a member or power level event
To reduce duplication.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-14 18:04:33 +02:00
Ivan Enderlin 435553c3d1 feat(sdk): Add more logs in generate_sync_request. 2025-07-14 17:48:07 +02:00
Ivan Enderlin 610ecd218c feat(sdk): Log the time spent waiting on the position lock. 2025-07-14 17:48:07 +02:00
Kévin Commaille fa300d1f33 refactor(tests): Prefer EventBuilder::into_raw to into_raw_(sync/timeline) then Raw::cast
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2025-07-14 17:46:02 +02:00
Ivan Enderlin af2a483158 feat(sdk): Add debug! log when updating the sliding sync pos. 2025-07-14 16:52:36 +02:00
Ivan Enderlin 753b0d8584 doc(sdk): Update doc of SlidingSyncPositionMarkers::pos.
With `SlidingSync::share_pos`, the `pos` can be persisted. This comment
is outdated.
2025-07-14 16:40:06 +02:00
Ivan Enderlin 36713adbdb feat(sdk): Add more logs around the sync_lock and response_processor in SlidingSync. 2025-07-14 14:58:09 +02:00
Ivan Enderlin d73a02c608 feat(sqlite): Add more timer! logs in each EventCacheStore methods.
This patch adds `timer!` logs in each method from `EventCacheStore` for
`SqliteEventCacheStore`. It will help to know the execution duration of
each of these methods.
2025-07-14 10:34:17 +02:00
Ivan Enderlin f73199b472 feat(sqlite): Instrument SqliteEventCacheStore::open_with_config. 2025-07-14 10:34:17 +02:00
Ivan Enderlin 420d373144 feat(sqlite): Add #[instrument] around all SqliteEventCacheStore methods. 2025-07-14 10:34:17 +02:00
Ivan Enderlin a79e9130e6 feat(sqlite): Add timer! tracings in read and write's SqliteEventCacheStore. 2025-07-14 10:34:17 +02:00
Ivan Enderlin 355b5327f8 refator(common): Rename TracingTimer::new_debug.
This patch renames `TracingTiming::new_debug` to `new`. The
documentation claims it sets the log level to `debug` while the `level`
is actually an argument of the constructor. It's then wrong, and the
constructor must be renamed.
2025-07-14 10:34:17 +02:00
Ivan Enderlin fa77852001 feat(common): TracingTimer uses the Debug impl of Duration.
This changes the `TracingTimer` message to use the `Debug` impl of
`Duration` instead of displaying it as milliseconds. It can help spotting
seconds without counting all the digits.
2025-07-14 10:34:17 +02:00
Ivan Enderlin 7b73311de5 feat(sqlite): Add logs around read and write. 2025-07-14 10:34:17 +02:00
Ivan Enderlin f03934bc4f feat(sqlite): SqliteEventCacheStore has 1 write connection.
Until now, `SqliteEventCacheStore` manages a pool of connections. A
connection is fetched from this pool and operations are executed on it,
regardless whether these are read operations or write operations.

We are seeing more and more _database is busy_ errors. We believe this
is because too many write operations are executed concurrently.

The solution to solve this is to use multiple connections for read
operations, and a single connection for write operations. That way,
concurrent writings are no longer a thing, and we hope it will reduce
the number of _database is busy_ errors to zero. That's our guess.

This patch does that. When the pool of connections is created, a
connection is elected as the `write_connection`. To get a connection for
read operations, one has to use the new `SqliteEventCacheStore::read`
method (it replaces the `acquire` method). To get a connection for
write operations, one has to use the new `SQliteEventCacheStore::write`
method. It returns a `OwnedMutexGuard` from an async `Mutex`. All
callers that want to do write operations on this store have to wait
their turn, this `Mutex` is fair, and the first to wait on the lock is
the first that will take the lock (FIFO). It guarantees the execution
ordering the code expects.

The rest of the patch updates all spots where `acquire` was used and
replaces them by `read()` or `write()`. A particular care was made to
see if other places are using `SqliteEventCacheStore::pool` directly. No
place remains except in `read()` and `write()`.
2025-07-14 10:34:17 +02:00
Ivan Enderlin 014ee98fb7 feat(sqlite): SqliteStoreConfig::pool_size sets a minimum to 2.
This patch updates `SqliteStoreConfig::pool_size` to be at least 2. We
need 2 connections: one for write operations, one for read operations.
This behaviour is coming in the next patches.
2025-07-14 10:34:17 +02:00
Ivan Enderlin fbcf9fce7c feat(sdk): Add more logs in EventCache.
This patch adds more logs inside `EventCache` around the
`multiple_room_updates_lock` and around the `listen_task`, just to be
sure if everything is listened and works as expected.
2025-07-14 09:24:38 +02:00
Damir Jelić 6de403276a feat(base): Remember the inviter if we accept an invite 2025-07-12 10:57:48 +02:00
Richard van der Hoff 6209bc942c indexeddb: Remove incorrect line from changelog 2025-07-11 16:35:50 +02:00
Doug edd371b570 ffi: Refactor ClientBuilder::build_with_qr_code into Client::login_with_qr_code
The FFI's API now matches the SDK and allows for checks to be made on the Client before logging in.
2025-07-11 15:56:46 +02:00
dragonfly1033 817f32e15b test(sdk): added configurable login response builders for mock login endpoint. 2025-07-11 14:14:24 +02:00
dragonfly1033 30eb12ed2d test(sdk): change test_login_username_refresh_token to use MatrixMockServer 2025-07-11 14:14:24 +02:00
Damir Jelić 900697bc3b chore: Add a missing changelog entry for PR #5250 2025-07-10 17:23:21 +02:00
154 changed files with 1569 additions and 1583 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "benchmarks"
description = "Matrix SDK benchmarks"
edition = "2021"
edition = "2024"
license = "Apache-2.0"
rust-version.workspace = true
version = "1.0.0"
+3 -2
View File
@@ -1,15 +1,16 @@
use std::{ops::Deref, sync::Arc};
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput};
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk_crypto::{EncryptionSettings, OlmMachine};
use matrix_sdk_sqlite::SqliteCryptoStore;
use matrix_sdk_test::ruma_response_from_json;
use ruma::{
DeviceId, OwnedUserId, TransactionId, UserId,
api::client::{
keys::{claim_keys, get_keys},
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
},
device_id, room_id, user_id, DeviceId, OwnedUserId, TransactionId, UserId,
device_id, room_id, user_id,
};
use serde_json::Value;
use tokio::runtime::Builder;
+5 -5
View File
@@ -1,16 +1,16 @@
use std::{sync::Arc, time::Duration};
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput};
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::{
linked_chunk::{lazy_loader, LinkedChunk, LinkedChunkId, Update},
SqliteEventCacheStore,
linked_chunk::{LinkedChunk, LinkedChunkId, Update, lazy_loader},
};
use matrix_sdk_base::event_cache::{
store::{DynEventCacheStore, IntoEventCacheStore, MemoryStore, DEFAULT_CHUNK_CAPACITY},
Event, Gap,
store::{DEFAULT_CHUNK_CAPACITY, DynEventCacheStore, IntoEventCacheStore, MemoryStore},
};
use matrix_sdk_test::{event_factory::EventFactory, ALICE};
use ruma::{room_id, EventId};
use matrix_sdk_test::{ALICE, event_factory::EventFactory};
use ruma::{EventId, room_id};
use tempfile::tempdir;
use tokio::runtime::Builder;
+6 -5
View File
@@ -1,21 +1,22 @@
use std::time::Duration;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::{store::RoomLoadSettings, test_utils::mocks::MatrixMockServer};
use matrix_sdk_base::{
store::StoreConfig, BaseClient, RoomInfo, RoomState, SessionMeta, StateChanges, StateStore,
ThreadingSupport,
BaseClient, RoomInfo, RoomState, SessionMeta, StateChanges, StateStore, ThreadingSupport,
store::StoreConfig,
};
use matrix_sdk_sqlite::SqliteStateStore;
use matrix_sdk_test::{event_factory::EventFactory, JoinedRoomBuilder, StateTestEvent};
use matrix_sdk_test::{JoinedRoomBuilder, StateTestEvent, event_factory::EventFactory};
use matrix_sdk_ui::timeline::{TimelineBuilder, TimelineFocus};
use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId,
api::client::membership::get_member_events,
device_id,
events::room::member::{MembershipState, RoomMemberEvent},
mxc_uri, owned_room_id, owned_user_id,
serde::Raw,
user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId,
user_id,
};
use serde_json::json;
use tokio::runtime::Builder;
+5 -5
View File
@@ -1,13 +1,13 @@
use std::sync::Arc;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::{
authentication::matrix::MatrixSession, config::StoreConfig, Client, RoomInfo, RoomState,
SessionTokens, StateChanges,
Client, RoomInfo, RoomState, SessionTokens, StateChanges,
authentication::matrix::MatrixSession, config::StoreConfig,
};
use matrix_sdk_base::{store::MemoryStore, SessionMeta, StateStore as _};
use matrix_sdk_base::{SessionMeta, StateStore as _, store::MemoryStore};
use matrix_sdk_sqlite::SqliteStateStore;
use ruma::{device_id, user_id, RoomId};
use ruma::{RoomId, device_id, user_id};
use tokio::runtime::Builder;
fn criterion() -> Criterion {
+4 -4
View File
@@ -1,10 +1,10 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use matrix_sdk::test_utils::mocks::MatrixMockServer;
use matrix_sdk_test::{event_factory::EventFactory, JoinedRoomBuilder, StateTestEvent};
use matrix_sdk_test::{JoinedRoomBuilder, StateTestEvent, event_factory::EventFactory};
use matrix_sdk_ui::timeline::TimelineBuilder;
use ruma::{
events::room::message::RoomMessageEventContentWithoutRelation, owned_room_id, owned_user_id,
EventId,
EventId, events::room::message::RoomMessageEventContentWithoutRelation, owned_room_id,
owned_user_id,
};
use tokio::runtime::Builder;
+8
View File
@@ -6,6 +6,14 @@ All notable changes to this project will be documented in this file.
## [Unreleased] - ReleaseDate
### Breaking changes:
- `Client::url_for_oidc` now allows passing an optional existing device id from a previous login call.
([#5394](https://github.com/matrix-org/matrix-rust-sdk/pull/5394))
- `ClientBuilder::build_with_qr_code` has been removed. Instead, the Client should be built by passing
`QrCodeData::server_name` to `ClientBuilder::server_name_or_homeserver_url`, after which QR login can be performed by
calling `Client::login_with_qr_code`. ([#5388](https://github.com/matrix-org/matrix-rust-sdk/pull/5388))
## [0.13.0] - 2025-07-10
### Features
+51 -2
View File
@@ -78,7 +78,7 @@ use ruma::{
RoomAccountDataEvent as RumaRoomAccountDataEvent,
},
push::{HttpPusherData as RumaHttpPusherData, PushFormat as RumaPushFormat},
OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName,
OwnedDeviceId, OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName,
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
@@ -96,6 +96,7 @@ use crate::{
encryption::Encryption,
notification::NotificationClient,
notification_settings::NotificationSettings,
qr_code::{HumanQrLoginError, QrCodeData, QrLoginProgressListener},
room::{RoomHistoryVisibility, RoomInfoListener},
room_directory_search::RoomDirectorySearch,
room_preview::RoomPreview,
@@ -456,16 +457,25 @@ impl Client {
/// However, it should be noted that when providing a user ID as a hint
/// for MAS (with no upstream provider), then the format to use is defined
/// by [MSC4198]: https://github.com/matrix-org/matrix-spec-proposals/pull/4198
///
/// * `device_id` - The unique ID that will be associated with the session.
/// If not set, a random one will be generated. It can be an existing
/// device ID from a previous login call. Note that this should be done
/// only if the client also holds the corresponding encryption keys.
pub async fn url_for_oidc(
&self,
oidc_configuration: &OidcConfiguration,
prompt: Option<OidcPrompt>,
login_hint: Option<String>,
device_id: Option<String>,
) -> Result<Arc<OAuthAuthorizationData>, OidcError> {
let registration_data = oidc_configuration.registration_data()?;
let redirect_uri = oidc_configuration.redirect_uri()?;
let mut url_builder = self.inner.oauth().login(redirect_uri, None, Some(registration_data));
let device_id = device_id.map(OwnedDeviceId::from);
let mut url_builder =
self.inner.oauth().login(redirect_uri, device_id, Some(registration_data));
if let Some(prompt) = prompt {
url_builder = url_builder.prompt(vec![prompt.into()]);
@@ -494,6 +504,45 @@ impl Client {
Ok(())
}
/// Log in using the provided [`QrCodeData`]. The `Client` must be built
/// by providing [`QrCodeData::server_name`] as the server name for this
/// login to succeed.
///
/// This method uses the login mechanism described in [MSC4108]. As such
/// this method requires OAuth 2.0 support as well as sliding sync support.
///
/// The usage of the progress_listener is required to transfer the
/// [`CheckCode`] to the existing client.
///
/// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
pub async fn login_with_qr_code(
self: Arc<Self>,
qr_code_data: &QrCodeData,
oidc_configuration: &OidcConfiguration,
progress_listener: Box<dyn QrLoginProgressListener>,
) -> Result<(), HumanQrLoginError> {
let registration_data = oidc_configuration
.registration_data()
.map_err(|_| HumanQrLoginError::OidcMetadataInvalid)?;
let oauth = self.inner.oauth();
let login = oauth.login_with_qr_code(&qr_code_data.inner, Some(&registration_data));
let mut progress = login.subscribe_to_progress();
// We create this task, which will get cancelled once it's dropped, just in case
// the progress stream doesn't end.
let _progress_task = TaskHandle::new(get_runtime_handle().spawn(async move {
while let Some(state) = progress.next().await {
progress_listener.on_update(state.into());
}
}));
login.await?;
Ok(())
}
/// Restores the client from a `Session`.
///
/// It reloads the entire set of rooms from the previous session.
+2 -67
View File
@@ -1,12 +1,9 @@
use std::{fs, num::NonZeroUsize, path::Path, sync::Arc, time::Duration};
use futures_util::StreamExt;
#[cfg(not(target_family = "wasm"))]
use matrix_sdk::reqwest::Certificate;
use matrix_sdk::{
crypto::{
types::qr_login::QrCodeModeData, CollectStrategy, DecryptionSettings, TrustRequirement,
},
crypto::{CollectStrategy, DecryptionSettings, TrustRequirement},
encryption::{BackupDownloadStrategy, EncryptionSettings},
event_cache::EventCacheError,
ruma::{ServerName, UserId},
@@ -22,15 +19,7 @@ use tracing::{debug, error};
use zeroize::Zeroizing;
use super::client::Client;
use crate::{
authentication::OidcConfiguration,
client::ClientSessionDelegate,
error::ClientError,
helpers::unwrap_or_clone_arc,
qr_code::{HumanQrLoginError, QrCodeData, QrLoginProgressListener},
runtime::get_runtime_handle,
task_handle::TaskHandle,
};
use crate::{client::ClientSessionDelegate, error::ClientError, helpers::unwrap_or_clone_arc};
/// A list of bytes containing a certificate in DER or PEM form.
pub type CertificateBytes = Vec<u8>;
@@ -582,60 +571,6 @@ impl ClientBuilder {
.await?,
))
}
/// Finish the building of the client and attempt to log in using the
/// provided [`QrCodeData`].
///
/// This method will build the client and immediately attempt to log the
/// client in using the provided [`QrCodeData`] using the login
/// mechanism described in [MSC4108]. As such this methods requires OAuth
/// 2.0 support as well as sliding sync support.
///
/// The usage of the progress_listener is required to transfer the
/// [`CheckCode`] to the existing client.
///
/// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
pub async fn build_with_qr_code(
self: Arc<Self>,
qr_code_data: &QrCodeData,
oidc_configuration: &OidcConfiguration,
progress_listener: Box<dyn QrLoginProgressListener>,
) -> Result<Arc<Client>, HumanQrLoginError> {
let QrCodeModeData::Reciprocate { server_name } = &qr_code_data.inner.mode_data else {
return Err(HumanQrLoginError::OtherDeviceNotSignedIn);
};
let builder = self.server_name_or_homeserver_url(server_name.to_owned());
let client = builder.build().await.map_err(|e| match e {
ClientBuildError::SlidingSync(_) => HumanQrLoginError::SlidingSyncNotAvailable,
_ => {
error!("Couldn't build the client {e:?}");
HumanQrLoginError::Unknown
}
})?;
let registration_data = oidc_configuration
.registration_data()
.map_err(|_| HumanQrLoginError::OidcMetadataInvalid)?;
let oauth = client.inner.oauth();
let login = oauth.login_with_qr_code(&qr_code_data.inner, Some(&registration_data));
let mut progress = login.subscribe_to_progress();
// We create this task, which will get cancelled once it's dropped, just in case
// the progress stream doesn't end.
let _progress_task = TaskHandle::new(get_runtime_handle().spawn(async move {
while let Some(state) = progress.next().await {
progress_listener.on_update(state.into());
}
}));
login.await?;
Ok(client)
}
}
#[cfg(not(target_family = "wasm"))]
+9
View File
@@ -6,6 +6,15 @@ All notable changes to this project will be documented in this file.
## [Unreleased] - ReleaseDate
### Features
- [**breaking**] The `RoomInfo` method now remembers the inviter at the time
when the `BaseClient::room_joined()` method was called. The caller is
responsible to remember the inviter before a server request to join the room
is made. The `RoomInfo::invite_accepted_at` method was removed, the
`RoomInfo::invite_details` method returns both the timestamp and the
inviter.
([#5390](https://github.com/matrix-org/matrix-rust-sdk/pull/5390))
## [0.13.0] - 2025-07-10
### Features
+59 -22
View File
@@ -43,7 +43,7 @@ use ruma::{
},
push::Ruleset,
time::Instant,
OwnedRoomId, OwnedUserId, RoomId, UserId,
MilliSecondsSinceUnixEpoch, OwnedRoomId, OwnedUserId, RoomId, UserId,
};
use tokio::sync::{broadcast, Mutex};
#[cfg(feature = "e2e-encryption")]
@@ -66,7 +66,7 @@ use crate::{
StateStoreDataValue, StateStoreExt, StoreConfig,
},
sync::{RoomUpdates, SyncResponse},
RoomStateFilter, SessionMeta,
InviteAcceptanceDetails, RoomStateFilter, SessionMeta,
};
/// A no (network) IO client implementation.
@@ -432,21 +432,36 @@ impl BaseClient {
///
/// Update the internal and cached state accordingly. Return the final Room.
///
/// # Arguments
///
/// * `room_id` - The unique ID identifying the joined room.
/// * `inviter` - When joining this room in response to an invitation, the
/// inviter should be recorded before sending the join request to the
/// server. Providing the inviter here ensures that the
/// [`InviteAcceptanceDetails`] are stored for this room.
///
/// # Examples
///
/// ```rust
/// # use matrix_sdk_base::{BaseClient, store::StoreConfig, RoomState, ThreadingSupport};
/// # use ruma::OwnedRoomId;
/// # use ruma::{OwnedRoomId, OwnedUserId, RoomId};
/// # async {
/// # let client = BaseClient::new(StoreConfig::new("example".to_owned()), ThreadingSupport::Disabled);
/// # async fn send_join_request() -> anyhow::Result<OwnedRoomId> { todo!() }
/// # async fn maybe_get_inviter(room_id: &RoomId) -> anyhow::Result<Option<OwnedUserId>> { todo!() }
/// # let room_id: &RoomId = todo!();
/// let maybe_inviter = maybe_get_inviter(room_id).await?;
/// let room_id = send_join_request().await?;
/// let room = client.room_joined(&room_id).await?;
/// let room = client.room_joined(&room_id, maybe_inviter).await?;
///
/// assert_eq!(room.state(), RoomState::Joined);
/// # anyhow::Ok(()) };
/// ```
pub async fn room_joined(&self, room_id: &RoomId) -> Result<Room> {
pub async fn room_joined(
&self,
room_id: &RoomId,
inviter: Option<OwnedUserId>,
) -> Result<Room> {
let room = self.state_store.get_or_create_room(
room_id,
RoomState::Joined,
@@ -459,10 +474,15 @@ impl BaseClient {
let _sync_lock = self.sync_lock().lock().await;
let mut room_info = room.clone_info();
let previous_state = room.state();
room_info.mark_as_joined();
room_info.mark_state_partially_synced();
room_info.mark_members_missing(); // the own member event changed
// If our previous state was an invite and we're now in the joined state, this
// means that the user has explicitly accepted the invite. Let's
// remember when this has happened.
// means that the user has explicitly accepted an invite. Let's
// remember some details about the invite.
//
// This is somewhat of a workaround for our lack of cryptographic membership.
// Later on we will decide if historic room keys should be accepted
@@ -470,14 +490,16 @@ impl BaseClient {
// key bundle shortly after, we might accept it. If we don't do
// this, the homeserver could trick us into accepting any historic room key
// bundle.
if room.state() == RoomState::Invited {
room_info.set_invite_accepted_now();
if previous_state == RoomState::Invited {
if let Some(inviter) = inviter {
let details = InviteAcceptanceDetails {
invite_accepted_at: MilliSecondsSinceUnixEpoch::now(),
inviter,
};
room_info.set_invite_acceptance_details(details);
}
}
room_info.mark_as_joined();
room_info.mark_state_partially_synced();
room_info.mark_members_missing(); // the own member event changed
let mut changes = StateChanges::default();
changes.add_room(room_info.clone());
@@ -1131,7 +1153,7 @@ impl From<&v5::Request> for RequestedRequiredStates {
mod tests {
use std::collections::HashMap;
use assert_matches2::assert_let;
use assert_matches2::{assert_let, assert_matches};
use futures_util::FutureExt as _;
use matrix_sdk_test::{
async_test, event_factory::EventFactory, ruma_response_from_json, InvitedRoomBuilder,
@@ -1739,8 +1761,9 @@ mod tests {
}
#[async_test]
async fn test_joined_at_timestamp_is_set() {
let client = logged_in_base_client(None).await;
async fn test_invite_details_are_set() {
let user_id = user_id!("@alice:localhost");
let client = logged_in_base_client(Some(user_id)).await;
let invited_room_id = room_id!("!invited:localhost");
let unknown_room_id = room_id!("!unknown:localhost");
@@ -1757,27 +1780,41 @@ mod tests {
.expect("The sync should have created a room in the invited state");
assert_eq!(invited_room.state(), RoomState::Invited);
assert!(invited_room.inner.get().invite_accepted_at().is_none());
assert!(invited_room.invite_acceptance_details().is_none());
// Now we join the room.
let joined_room = client
.room_joined(invited_room_id)
.room_joined(invited_room_id, Some(user_id.to_owned()))
.await
.expect("We should be able to mark a room as joined");
// Yup, there's a timestamp now.
// Yup, we now have some invite details.
assert_eq!(joined_room.state(), RoomState::Joined);
assert!(joined_room.inner.get().invite_accepted_at().is_some());
assert_matches!(joined_room.invite_acceptance_details(), Some(details));
assert_eq!(details.inviter, user_id);
// If we didn't know about the room before the join, we assume that there wasn't
// an invite and we don't record the timestamp.
assert!(client.get_room(unknown_room_id).is_none());
let unknown_room = client
.room_joined(unknown_room_id)
.room_joined(unknown_room_id, Some(user_id.to_owned()))
.await
.expect("We should be able to mark a room as joined");
assert_eq!(unknown_room.state(), RoomState::Joined);
assert!(unknown_room.inner.get().invite_accepted_at().is_none());
assert!(unknown_room.invite_acceptance_details().is_none());
sync_builder.clear();
let response =
sync_builder.add_left_room(LeftRoomBuilder::new(invited_room_id)).build_sync_response();
client.receive_sync_response(response).await.unwrap();
// Now that we left the room, we shouldn't have any details anymore.
let left_room = client
.get_room(invited_room_id)
.expect("The sync should have created a room in the invited state");
assert_eq!(left_room.state(), RoomState::Left);
assert!(left_room.invite_acceptance_details().is_none());
}
}
@@ -74,7 +74,7 @@ pub fn make_test_event_with_event_id(
if let Some(event_id) = event_id {
builder = builder.event_id(event_id);
}
let event = builder.into_raw_timeline().cast();
let event = builder.into_raw();
TimelineEvent::from_decrypted(
DecryptedRoomEvent { event, encryption_info, unsigned_encryption_info: None },
+4 -3
View File
@@ -55,9 +55,10 @@ pub use http;
pub use matrix_sdk_crypto as crypto;
pub use once_cell;
pub use room::{
apply_redaction, EncryptionState, PredecessorRoom, Room, RoomCreateWithCreatorEventContent,
RoomDisplayName, RoomHero, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons,
RoomMember, RoomMembersUpdate, RoomMemberships, RoomState, RoomStateFilter, SuccessorRoom,
apply_redaction, EncryptionState, InviteAcceptanceDetails, PredecessorRoom, Room,
RoomCreateWithCreatorEventContent, RoomDisplayName, RoomHero, RoomInfo, RoomInfoNotableUpdate,
RoomInfoNotableUpdateReasons, RoomMember, RoomMembersUpdate, RoomMemberships, RoomState,
RoomStateFilter, SuccessorRoom,
};
pub use store::{
ComposerDraft, ComposerDraftType, QueueWedgeError, StateChanges, StateStore, StateStoreDataKey,
@@ -20,8 +20,6 @@ use std::collections::BTreeSet;
#[cfg(feature = "e2e-encryption")]
use matrix_sdk_common::deserialized_responses::TimelineEvent;
#[cfg(feature = "e2e-encryption")]
use ruma::events::StateEventType;
use ruma::{
api::client::sync::sync_events::{
v3::{InviteState, InvitedRoom, KnockState, KnockedRoom},
@@ -441,19 +439,9 @@ pub(crate) async fn cache_latest_events(
let mut encrypted_events =
Vec::with_capacity(room.latest_encrypted_events.read().unwrap().capacity());
// Try to get room power levels from the current changes
let power_levels_from_changes = || {
let state_changes = changes?.state.get(room_info.room_id())?;
let room_power_levels_state =
state_changes.get(&StateEventType::RoomPowerLevels)?.values().next()?;
match room_power_levels_state.deserialize().ok()? {
AnySyncStateEvent::RoomPowerLevels(ev) => Some(ev.power_levels()),
_ => None,
}
};
// If we didn't get any info, try getting it from local data
let power_levels = match power_levels_from_changes() {
// Try to get room power levels from the current changes. If we didn't get any
// info, try getting it from local data.
let power_levels = match changes.and_then(|changes| changes.power_levels(room_info.room_id())) {
Some(power_levels) => Some(power_levels),
None => room.power_levels().await.ok(),
};
@@ -270,8 +270,7 @@ pub mod stripped {
// We need to check for notifications after we have handled all state
// events, to make sure we have the full push context.
if let Some(push_condition_room_ctx) =
timeline::get_push_room_context(context, room, room_info, notification.state_store)
.await?
timeline::get_push_room_context(context, room, room_info).await?
{
let room_id = room.room_id();
@@ -16,13 +16,7 @@ use matrix_sdk_common::deserialized_responses::TimelineEvent;
#[cfg(feature = "e2e-encryption")]
use ruma::events::SyncMessageLikeEvent;
use ruma::{
events::{
room::power_levels::{
RoomPowerLevelsEvent, RoomPowerLevelsEventContent, StrippedRoomPowerLevelsEvent,
},
AnyStrippedStateEvent, AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent,
StateEventType,
},
events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent},
push::{Action, PushConditionRoomCtx},
UInt, UserId,
};
@@ -31,11 +25,7 @@ use tracing::{instrument, trace, warn};
#[cfg(feature = "e2e-encryption")]
use super::{e2ee, verification};
use super::{notification, Context};
use crate::{
store::{BaseStateStore, StateStoreExt as _},
sync::Timeline,
Result, Room, RoomInfo,
};
use crate::{sync::Timeline, Result, Room, RoomInfo};
/// Process a set of sync timeline event, and create a [`Timeline`].
///
@@ -54,8 +44,7 @@ pub async fn build<'notification, 'e2ee>(
#[cfg(feature = "e2e-encryption")] e2ee: e2ee::E2EE<'e2ee>,
) -> Result<Timeline> {
let mut timeline = Timeline::new(timeline_inputs.limited, timeline_inputs.prev_batch);
let mut push_condition_room_ctx =
get_push_room_context(context, room, room_info, notification.state_store).await?;
let mut push_condition_room_ctx = get_push_room_context(context, room, room_info).await?;
let room_id = room.room_id();
for raw_event in timeline_inputs.raw_events {
@@ -134,8 +123,7 @@ pub async fn build<'notification, 'e2ee>(
)
} else {
push_condition_room_ctx =
get_push_room_context(context, room, room_info, notification.state_store)
.await?;
get_push_room_context(context, room, room_info).await?;
}
if let Some(push_condition_room_ctx) = &push_condition_room_ctx {
@@ -207,23 +195,13 @@ fn update_push_room_context(
push_rules.member_count = UInt::new(room_info.active_members_count()).unwrap_or(UInt::MAX);
// TODO: Use if let chain once stable
if let Some(AnySyncStateEvent::RoomMember(member)) =
context.state_changes.state.get(room_id).and_then(|events| {
events.get(&StateEventType::RoomMember)?.get(user_id.as_str())?.deserialize().ok()
})
{
push_rules.user_display_name = member
.as_original()
.and_then(|ev| ev.content.displayname.clone())
.unwrap_or_else(|| user_id.localpart().to_owned())
if let Some(member) = context.state_changes.member(room_id, user_id) {
push_rules.user_display_name =
member.content.displayname.unwrap_or_else(|| user_id.localpart().to_owned())
}
if let Some(AnySyncStateEvent::RoomPowerLevels(event)) =
context.state_changes.state.get(room_id).and_then(|types| {
types.get(&StateEventType::RoomPowerLevels)?.get("")?.deserialize().ok()
})
{
push_rules.power_levels = Some(event.power_levels().into());
if let Some(power_levels) = context.state_changes.power_levels(room_id) {
push_rules.power_levels = Some(power_levels.into());
}
}
@@ -238,7 +216,6 @@ pub async fn get_push_room_context(
context: &Context,
room: &Room,
room_info: &RoomInfo,
state_store: &BaseStateStore,
) -> Result<Option<PushConditionRoomCtx>> {
let room_id = room.room_id();
let user_id = room.own_user_id();
@@ -246,19 +223,7 @@ pub async fn get_push_room_context(
let member_count = room_info.active_members_count();
// TODO: Use if let chain once stable
let user_display_name = if let Some(AnySyncStateEvent::RoomMember(member)) =
context.state_changes.state.get(room_id).and_then(|events| {
events.get(&StateEventType::RoomMember)?.get(user_id.as_str())?.deserialize().ok()
}) {
member
.as_original()
.and_then(|ev| ev.content.displayname.clone())
.unwrap_or_else(|| user_id.localpart().to_owned())
} else if let Some(AnyStrippedStateEvent::RoomMember(member)) =
context.state_changes.stripped_state.get(room_id).and_then(|events| {
events.get(&StateEventType::RoomMember)?.get(user_id.as_str())?.deserialize().ok()
})
{
let user_display_name = if let Some(member) = context.state_changes.member(room_id, user_id) {
member.content.displayname.unwrap_or_else(|| user_id.localpart().to_owned())
} else if let Some(member) = Box::pin(room.get_member(user_id)).await? {
member.name().to_owned()
@@ -267,31 +232,10 @@ pub async fn get_push_room_context(
return Ok(None);
};
let power_levels = if let Some(event) =
context.state_changes.state.get(room_id).and_then(|types| {
types
.get(&StateEventType::RoomPowerLevels)?
.get("")?
.deserialize_as::<RoomPowerLevelsEvent>()
.ok()
}) {
Some(event.power_levels().into())
} else if let Some(event) =
context.state_changes.stripped_state.get(room_id).and_then(|types| {
types
.get(&StateEventType::RoomPowerLevels)?
.get("")?
.deserialize_as::<StrippedRoomPowerLevelsEvent>()
.ok()
})
{
Some(event.power_levels().into())
let power_levels = if let Some(power_levels) = context.state_changes.power_levels(room_id) {
Some(power_levels)
} else {
state_store
.get_state_event_static::<RoomPowerLevelsEventContent>(room_id)
.await?
.and_then(|e| e.deserialize().ok())
.map(|event| event.power_levels().into())
room.power_levels().await.ok()
};
Ok(Some(PushConditionRoomCtx {
@@ -299,6 +243,6 @@ pub async fn get_push_room_context(
room_id: room_id.to_owned(),
member_count: UInt::new(member_count).unwrap_or(UInt::MAX),
user_display_name,
power_levels,
power_levels: power_levels.map(Into::into),
}))
}
+10 -32
View File
@@ -25,7 +25,7 @@ use ruma::{
presence::PresenceEvent,
room::{
member::{MembershipState, RoomMemberEventContent},
power_levels::{PowerLevelAction, RoomPowerLevels, RoomPowerLevelsEventContent},
power_levels::{PowerLevelAction, RoomPowerLevels},
},
MessageLikeEventType, StateEventType,
},
@@ -35,7 +35,7 @@ use tracing::debug;
use super::Room;
use crate::{
deserialized_responses::{DisplayName, MemberEvent, SyncOrStrippedState},
deserialized_responses::{DisplayName, MemberEvent},
store::{ambiguity_map::is_display_name_ambiguous, Result as StoreResult, StateStoreExt},
MinimalRoomMemberEvent,
};
@@ -166,13 +166,7 @@ impl Room {
display_names: &'a [DisplayName],
) -> StoreResult<MemberRoomInfo<'a>> {
let max_power_level = self.max_power_level();
let room_creator = self.inner.read().creator().map(ToOwned::to_owned);
let power_levels = self
.store
.get_state_event_static(self.room_id())
.await?
.and_then(|e| e.deserialize().ok());
let power_levels = self.power_levels_or_default().await;
let users_display_names =
self.store.get_users_with_display_names(self.room_id(), display_names).await?;
@@ -188,7 +182,6 @@ impl Room {
Ok(MemberRoomInfo {
power_levels: power_levels.into(),
max_power_level,
room_creator,
users_display_names,
ignored_users,
})
@@ -205,9 +198,8 @@ pub struct RoomMember {
pub(crate) profile: Arc<Option<MinimalRoomMemberEvent>>,
#[allow(dead_code)]
pub(crate) presence: Arc<Option<PresenceEvent>>,
pub(crate) power_levels: Arc<Option<SyncOrStrippedState<RoomPowerLevelsEventContent>>>,
pub(crate) power_levels: Arc<RoomPowerLevels>,
pub(crate) max_power_level: i64,
pub(crate) is_room_creator: bool,
pub(crate) display_name_ambiguous: bool,
pub(crate) is_ignored: bool,
}
@@ -219,15 +211,9 @@ impl RoomMember {
presence: Option<PresenceEvent>,
room_info: &MemberRoomInfo<'_>,
) -> Self {
let MemberRoomInfo {
power_levels,
max_power_level,
room_creator,
users_display_names,
ignored_users,
} = room_info;
let MemberRoomInfo { power_levels, max_power_level, users_display_names, ignored_users } =
room_info;
let is_room_creator = room_creator.as_deref() == Some(event.user_id());
let display_name = event.display_name();
let display_name_ambiguous = users_display_names
.get(&display_name)
@@ -240,7 +226,6 @@ impl RoomMember {
presence: presence.into(),
power_levels: power_levels.clone(),
max_power_level: *max_power_level,
is_room_creator,
display_name_ambiguous,
is_ignored,
}
@@ -301,10 +286,7 @@ impl RoomMember {
/// Get the power level of this member.
pub fn power_level(&self) -> i64 {
(*self.power_levels)
.as_ref()
.map(|e| e.power_levels().for_user(self.user_id()).into())
.unwrap_or_else(|| if self.is_room_creator { 100 } else { 0 })
self.power_levels.for_user(self.user_id()).into()
}
/// Whether this user can ban other users based on the power levels.
@@ -377,11 +359,8 @@ impl RoomMember {
self.can_do_impl(|pls| pls.user_can_do(self.user_id(), action))
}
fn can_do_impl(&self, f: impl FnOnce(RoomPowerLevels) -> bool) -> bool {
match &*self.power_levels {
Some(event) => f(event.power_levels()),
None => self.is_room_creator,
}
fn can_do_impl(&self, f: impl FnOnce(&RoomPowerLevels) -> bool) -> bool {
f(&self.power_levels)
}
/// Is the name that the member uses ambiguous in the room.
@@ -405,9 +384,8 @@ impl RoomMember {
// Information about the room a member is in.
pub(crate) struct MemberRoomInfo<'a> {
pub(crate) power_levels: Arc<Option<SyncOrStrippedState<RoomPowerLevelsEventContent>>>,
pub(crate) power_levels: Arc<RoomPowerLevels>,
pub(crate) max_power_level: i64,
pub(crate) room_creator: Option<OwnedUserId>,
pub(crate) users_display_names: HashMap<&'a DisplayName, BTreeSet<OwnedUserId>>,
pub(crate) ignored_users: Option<BTreeSet<OwnedUserId>>,
}
+34 -3
View File
@@ -44,11 +44,11 @@ use matrix_sdk_common::ring_buffer::RingBuffer;
pub use members::{RoomMember, RoomMembersUpdate, RoomMemberships};
pub(crate) use room_info::SyncInfo;
pub use room_info::{
apply_redaction, BaseRoomInfo, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons,
apply_redaction, BaseRoomInfo, InviteAcceptanceDetails, RoomInfo, RoomInfoNotableUpdate,
RoomInfoNotableUpdateReasons,
};
#[cfg(feature = "e2e-encryption")]
use ruma::{events::AnySyncTimelineEvent, serde::Raw};
use ruma::{
assign,
events::{
direct::OwnedDirectUserIdentifier,
receipt::{Receipt, ReceiptThread, ReceiptType},
@@ -60,9 +60,12 @@ use ruma::{
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
},
int,
room::RoomType,
EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, UserId,
};
#[cfg(feature = "e2e-encryption")]
use ruma::{events::AnySyncTimelineEvent, serde::Raw};
use serde::{Deserialize, Serialize};
pub use state::{RoomState, RoomStateFilter};
pub(crate) use tags::RoomNotableTags;
@@ -370,6 +373,23 @@ impl Room {
.power_levels())
}
/// Get the current power levels of this room, or a sensible default if they
/// are not known.
pub async fn power_levels_or_default(&self) -> RoomPowerLevels {
if let Ok(power_levels) = self.power_levels().await {
return power_levels;
}
// As a fallback, create the default power levels of a room, with the creator at
// level 100.
let creator = self.creator();
assign!(
RoomPowerLevelsEventContent::new(),
{ users: creator.into_iter().map(|user_id| (user_id, int!(100))).collect() }
)
.into()
}
/// Get the `m.room.name` of this room.
///
/// The returned string may be empty if the event has been redacted, or it's
@@ -457,6 +477,17 @@ impl Room {
self.inner.read().recency_stamp
}
/// Returns the details about an invite to this room if the invite has been
/// accepted by this specific client.
///
/// # Returns
/// - `Some` if an invite has been accepted by this specific client.
/// - `None` if we didn't join this room using an invite or the invite
/// wasn't accepted by this client.
pub fn invite_acceptance_details(&self) -> Option<InviteAcceptanceDetails> {
self.inner.read().invite_acceptance_details.clone()
}
/// Get a `Stream` of loaded pinned events for this room.
/// If no pinned events are found a single empty `Vec` will be returned.
pub fn pinned_event_ids_stream(&self) -> impl Stream<Item = Vec<OwnedEventId>> {
+26 -10
View File
@@ -50,7 +50,7 @@ use ruma::{
OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId,
};
use serde::{Deserialize, Serialize};
use tracing::{debug, field::debug, info, instrument, warn};
use tracing::{debug, error, field::debug, info, instrument, warn};
use super::{
AccountDataSource, EncryptionState, Room, RoomCreateWithCreatorEventContent, RoomDisplayName,
@@ -66,6 +66,18 @@ use crate::{
MinimalStateEvent, OriginalMinimalStateEvent,
};
/// A struct remembering details of an invite and if the invite has been
/// accepted on this particular client.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InviteAcceptanceDetails {
/// A timestamp remembering when we observed the user accepting an invite
/// using this client.
pub invite_accepted_at: MilliSecondsSinceUnixEpoch,
/// The user ID of the person that invited us.
pub inviter: OwnedUserId,
}
impl Room {
/// Subscribe to the inner `RoomInfo`.
pub fn subscribe_info(&self) -> Subscriber<RoomInfo> {
@@ -471,7 +483,7 @@ pub struct RoomInfo {
/// This is useful to remember if the user accepted this a join on this
/// specific client.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) invite_accepted_at: Option<MilliSecondsSinceUnixEpoch>,
pub(crate) invite_acceptance_details: Option<InviteAcceptanceDetails>,
}
impl RoomInfo {
@@ -494,7 +506,7 @@ impl RoomInfo {
cached_display_name: None,
cached_user_defined_notification_mode: None,
recency_stamp: None,
invite_accepted_at: None,
invite_acceptance_details: None,
}
}
@@ -525,6 +537,12 @@ impl RoomInfo {
/// Set the membership RoomState of this Room
pub fn set_state(&mut self, room_state: RoomState) {
if self.state() != RoomState::Joined && self.invite_acceptance_details.is_some() {
error!(room_id = %self.room_id, "The RoomInfo contains invite acceptance details but the room is not in the joined state");
}
// Changing our state removes the invite details since we can't know that they
// are relevant anymore.
self.invite_acceptance_details = None;
self.room_state = room_state;
}
@@ -758,10 +776,8 @@ impl RoomInfo {
self.summary.invited_member_count = count;
}
/// Mark that the user has accepted an invite and remember when this has
/// happened using a timestamp set to [`MilliSecondsSinceUnixEpoch::now()`].
pub(crate) fn set_invite_accepted_now(&mut self) {
self.invite_accepted_at = Some(MilliSecondsSinceUnixEpoch::now());
pub(crate) fn set_invite_acceptance_details(&mut self, details: InviteAcceptanceDetails) {
self.invite_acceptance_details = Some(details);
}
/// Returns the timestamp when an invite to this room has been accepted by
@@ -770,8 +786,8 @@ impl RoomInfo {
/// # Returns
/// - `Some` if the invite has been accepted by this specific client.
/// - `None` if the invite has not been accepted
pub fn invite_accepted_at(&self) -> Option<MilliSecondsSinceUnixEpoch> {
self.invite_accepted_at
pub fn invite_acceptance_details(&self) -> Option<InviteAcceptanceDetails> {
self.invite_acceptance_details.clone()
}
/// Updates the room heroes.
@@ -1247,7 +1263,7 @@ mod tests {
cached_display_name: None,
cached_user_defined_notification_mode: None,
recency_stamp: Some(42),
invite_accepted_at: None,
invite_acceptance_details: None,
};
let info_json = json!({
@@ -18,17 +18,14 @@ use std::{
};
use ruma::{
events::{
room::member::{MembershipState, SyncRoomMemberEvent},
StateEventType,
},
events::room::member::{MembershipState, SyncRoomMemberEvent},
OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
};
use tracing::{instrument, trace};
use super::{DynStateStore, Result, StateChanges};
use crate::{
deserialized_responses::{AmbiguityChange, DisplayName, RawMemberEvent},
deserialized_responses::{AmbiguityChange, DisplayName, SyncOrStrippedState},
store::StateStoreExt,
};
@@ -188,17 +185,13 @@ impl AmbiguityCache {
) -> Result<Option<String>> {
let user_id = new_event.state_key();
let old_event = if let Some(m) = changes
.state
.get(room_id)
.and_then(|events| events.get(&StateEventType::RoomMember)?.get(user_id.as_str()))
{
Some(RawMemberEvent::Sync(m.clone().cast()))
let old_event = if let Some(member) = changes.member(room_id, user_id) {
Some(SyncOrStrippedState::Stripped(member))
} else {
self.store.get_member_event(room_id, user_id).await?
self.store.get_member_event(room_id, user_id).await?.and_then(|r| r.deserialize().ok())
};
let Some(Ok(old_event)) = old_event.map(|r| r.deserialize()) else { return Ok(None) };
let Some(old_event) = old_event else { return Ok(None) };
if is_member_active(old_event.membership()) {
let display_name = if let Some(d) = changes
@@ -121,7 +121,7 @@ impl RoomInfoV1 {
cached_display_name: None,
cached_user_defined_notification_mode: None,
recency_stamp: None,
invite_accepted_at: None,
invite_acceptance_details: None,
}
}
}
+92 -2
View File
@@ -21,6 +21,7 @@
//! store.
use std::{
borrow::Borrow,
collections::{BTreeMap, BTreeSet, HashMap},
fmt,
ops::Deref,
@@ -47,13 +48,20 @@ use ruma::{
events::{
presence::PresenceEvent,
receipt::ReceiptEventContent,
room::{member::StrippedRoomMemberEvent, redaction::SyncRoomRedactionEvent},
room::{
member::{RoomMemberEventContent, StrippedRoomMemberEvent},
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
redaction::SyncRoomRedactionEvent,
},
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
AnySyncStateEvent, EmptyStateKey, GlobalAccountDataEventType, RedactContent,
RedactedStateEventContent, RoomAccountDataEventType, StateEventType, StaticEventContent,
StaticStateEventContent, StrippedStateEvent, SyncStateEvent,
},
serde::Raw,
EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
};
use serde::de::DeserializeOwned;
use tokio::sync::{broadcast, Mutex, RwLock};
use tracing::warn;
@@ -556,6 +564,88 @@ impl StateChanges {
pub fn add_receipts(&mut self, room_id: &RoomId, event: ReceiptEventContent) {
self.receipts.insert(room_id.to_owned(), event);
}
/// Get a specific state event of statically-known type with the given state
/// key in the given room, if it is present in the `state` map of these
/// `StateChanges`.
pub(crate) fn state_static_for_key<C, K>(
&self,
room_id: &RoomId,
state_key: &K,
) -> Option<&Raw<SyncStateEvent<C>>>
where
C: StaticEventContent + StaticStateEventContent + RedactContent,
C::Redacted: RedactedStateEventContent,
C::StateKey: Borrow<K>,
K: AsRef<str> + ?Sized,
{
self.state.get(room_id)?.get(&C::TYPE.into())?.get(state_key.as_ref()).map(Raw::cast_ref)
}
/// Get a specific stripped state event of statically-known type with the
/// given state key in the given room, if it is present in the
/// `stripped_state` map of these `StateChanges`.
pub(crate) fn stripped_state_static_for_key<C, K>(
&self,
room_id: &RoomId,
state_key: &K,
) -> Option<&Raw<StrippedStateEvent<C::PossiblyRedacted>>>
where
C: StaticEventContent + StaticStateEventContent,
C::StateKey: Borrow<K>,
K: AsRef<str> + ?Sized,
{
self.stripped_state
.get(room_id)?
.get(&C::TYPE.into())?
.get(state_key.as_ref())
.map(Raw::cast_ref)
}
/// Get a specific state event of statically-known type with the given state
/// key in the given room, if it is present in the `state` or
/// `stripped_state` map of these `StateChanges` and it deserializes
/// successfully.
pub(crate) fn any_state_static_for_key<C, K>(
&self,
room_id: &RoomId,
state_key: &K,
) -> Option<StrippedStateEvent<C::PossiblyRedacted>>
where
C: StaticEventContent + StaticStateEventContent + RedactContent,
C::Redacted: RedactedStateEventContent,
C::PossiblyRedacted: DeserializeOwned,
C::StateKey: Borrow<K>,
K: AsRef<str> + ?Sized,
{
self.state_static_for_key::<C, K>(room_id, state_key)
.map(Raw::cast_ref)
.or_else(|| self.stripped_state_static_for_key::<C, K>(room_id, state_key))?
.deserialize()
.ok()
}
/// Get the member for the given user in the given room from an event
/// contained in these `StateChanges`, if any.
pub(crate) fn member(
&self,
room_id: &RoomId,
user_id: &UserId,
) -> Option<StrippedRoomMemberEvent> {
self.any_state_static_for_key::<RoomMemberEventContent, _>(room_id, user_id)
}
/// Get the power levels for the given room from an event contained in these
/// `StateChanges`, if any.
pub(crate) fn power_levels(&self, room_id: &RoomId) -> Option<RoomPowerLevels> {
Some(
self.any_state_static_for_key::<RoomPowerLevelsEventContent, _>(
room_id,
&EmptyStateKey,
)?
.power_levels(),
)
}
}
/// Configuration for the various stores.
@@ -33,7 +33,7 @@ impl std::fmt::Debug for TracingTimer {
impl Drop for TracingTimer {
fn drop(&mut self) {
let message = format!("{} finished in {}ms", self.id, self.start.elapsed().as_millis());
let message = format!("_{}_ finished in {:?}", self.id, self.start.elapsed());
let enabled = tracing::level_enabled!(self.level) && {
let interest = self.callsite.interest();
@@ -62,12 +62,8 @@ impl Drop for TracingTimer {
}
impl TracingTimer {
/// Create a new `TracingTimer` at the `debug` log level.
pub fn new_debug(
callsite: &'static DefaultCallsite,
id: String,
level: tracing::Level,
) -> Self {
/// Create a new `TracingTimer`.
pub fn new(callsite: &'static DefaultCallsite, id: String, level: tracing::Level) -> Self {
Self { id, callsite, start: Instant::now(), level }
}
}
@@ -111,7 +107,7 @@ macro_rules! timer {
fields: []
};
$crate::tracing_timer::TracingTimer::new_debug(&__CALLSITE, $string.into(), $level)
$crate::tracing_timer::TracingTimer::new(&__CALLSITE, $string.into(), $level)
}};
($string:expr) => {
@@ -133,7 +129,7 @@ mod tests {
let _timer_guard = timer!(tracing::Level::DEBUG, "test");
tokio::time::sleep(ruma::time::Duration::from_millis(123)).await;
// Displays: 2023-08-25T15:18:31.169498Z DEBUG
// matrix_sdk_common::tracing_timer::tests: test finished in
// matrix_sdk_common::tracing_timer::tests: _test_ finished in
// 124ms
}
}
-2
View File
@@ -8,8 +8,6 @@ All notable changes to this project will be documented in this file.
## [0.13.0] - 2025-07-10
No notable changes in this release.
### Features
- Add support for received room key bundle data, as required by encrypted history sharing ((MSC4268)[https://github.com/matrix-org/matrix-spec-proposals/pull/4268)). ([#5276](https://github.com/matrix-org/matrix-rust-sdk/pull/5276))
@@ -33,6 +33,14 @@ impl<T> AsyncErrorDeps for T where T: std::error::Error + SendOutsideWasm + Sync
pub enum IndexeddbEventCacheStoreError {
#[error("DomException {name} ({code}): {message}")]
DomException { name: String, message: String, code: u16 },
#[error("chunks contain disjoint lists")]
ChunksContainDisjointLists,
#[error("chunks contain cycle")]
ChunksContainCycle,
#[error("unable to load chunk")]
UnableToLoadChunk,
#[error("no max chunk id")]
NoMaxChunkId,
#[error("transaction: {0}")]
Transaction(#[from] IndexeddbEventCacheStoreTransactionError),
#[error("media store: {0}")]
@@ -51,23 +59,16 @@ impl From<web_sys::DomException> for IndexeddbEventCacheStoreError {
impl From<IndexeddbEventCacheStoreError> for EventCacheStoreError {
fn from(value: IndexeddbEventCacheStoreError) -> Self {
use IndexeddbEventCacheStoreError::*;
match value {
IndexeddbEventCacheStoreError::DomException { .. } => {
Self::InvalidData { details: value.to_string() }
}
IndexeddbEventCacheStoreError::Transaction(ref inner) => match inner {
IndexeddbEventCacheStoreTransactionError::DomException { .. } => {
Self::InvalidData { details: value.to_string() }
}
IndexeddbEventCacheStoreTransactionError::Serialization(e) => {
Self::Serialization(serde_json::Error::custom(e.to_string()))
}
IndexeddbEventCacheStoreTransactionError::ItemIsNotUnique
| IndexeddbEventCacheStoreTransactionError::ItemNotFound => {
Self::InvalidData { details: value.to_string() }
}
},
IndexeddbEventCacheStoreError::MemoryStore(inner) => inner,
DomException { .. }
| ChunksContainCycle
| ChunksContainDisjointLists
| NoMaxChunkId
| UnableToLoadChunk => Self::InvalidData { details: value.to_string() },
Transaction(inner) => inner.into(),
MemoryStore(inner) => inner,
}
}
}
@@ -435,6 +435,73 @@ pub async fn test_linked_chunk_update_is_a_transaction(store: IndexeddbEventCach
assert!(chunks.is_empty());
}
pub async fn test_load_last_chunk(store: IndexeddbEventCacheStore) {
let room_id = &DEFAULT_TEST_ROOM_ID;
let linked_chunk_id = LinkedChunkId::Room(room_id);
let event = |msg: &str| make_test_event(room_id, msg);
// Case #1: no last chunk.
let (last_chunk, chunk_identifier_generator) =
store.load_last_chunk(linked_chunk_id).await.unwrap();
assert!(last_chunk.is_none());
assert_eq!(chunk_identifier_generator.current(), 0);
// Case #2: only one chunk is present.
let updates = vec![
Update::NewItemsChunk { previous: None, new: ChunkIdentifier::new(42), next: None },
Update::PushItems {
at: Position::new(ChunkIdentifier::new(42), 0),
items: vec![event("saucisse de morteau"), event("comté")],
},
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let (last_chunk, chunk_identifier_generator) =
store.load_last_chunk(linked_chunk_id).await.unwrap();
assert_matches!(last_chunk, Some(last_chunk) => {
assert_eq!(last_chunk.identifier, 42);
assert!(last_chunk.previous.is_none());
assert!(last_chunk.next.is_none());
assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
assert_eq!(items.len(), 2);
check_test_event(&items[0], "saucisse de morteau");
check_test_event(&items[1], "comté");
});
});
assert_eq!(chunk_identifier_generator.current(), 42);
// Case #3: more chunks are present.
let updates = vec![
Update::NewItemsChunk {
previous: Some(ChunkIdentifier::new(42)),
new: ChunkIdentifier::new(7),
next: None,
},
Update::PushItems {
at: Position::new(ChunkIdentifier::new(7), 0),
items: vec![event("fondue"), event("gruyère"), event("mont d'or")],
},
];
store.handle_linked_chunk_updates(linked_chunk_id, updates).await.unwrap();
let (last_chunk, chunk_identifier_generator) =
store.load_last_chunk(linked_chunk_id).await.unwrap();
assert_matches!(last_chunk, Some(last_chunk) => {
assert_eq!(last_chunk.identifier, 7);
assert_matches!(last_chunk.previous, Some(previous) => {
assert_eq!(previous, 42);
});
assert!(last_chunk.next.is_none());
assert_matches!(last_chunk.content, ChunkContent::Items(items) => {
assert_eq!(items.len(), 3);
check_test_event(&items[0], "fondue");
check_test_event(&items[1], "gruyère");
check_test_event(&items[2], "mont d'or");
});
});
assert_eq!(chunk_identifier_generator.current(), 42);
}
/// Macro for generating tests for IndexedDB implementation of
/// [`EventCacheStore`]
///
@@ -547,6 +614,13 @@ macro_rules! indexeddb_event_cache_store_integration_tests {
$crate::event_cache_store::integration_tests::test_linked_chunk_update_is_a_transaction(store)
.await
}
#[async_test]
async fn test_load_last_chunk() {
let store = get_event_cache_store().await.expect("Failed to get event cache store");
$crate::event_cache_store::integration_tests::test_load_last_chunk(store)
.await
}
}
};
}
@@ -36,7 +36,7 @@ use web_sys::IdbTransactionMode;
use crate::event_cache_store::{
migrations::current::keys,
serializer::IndexeddbEventCacheStoreSerializer,
transaction::IndexeddbEventCacheStoreTransaction,
transaction::{IndexeddbEventCacheStoreTransaction, IndexeddbEventCacheStoreTransactionError},
types::{ChunkType, InBandEvent},
};
@@ -300,10 +300,52 @@ impl_event_cache_store! {
(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator),
IndexeddbEventCacheStoreError,
> {
self.memory_store
.load_last_chunk(linked_chunk_id)
.await
.map_err(IndexeddbEventCacheStoreError::MemoryStore)
let linked_chunk_id = linked_chunk_id.to_owned();
let room_id = linked_chunk_id.room_id();
let transaction = self.transaction(
&[keys::LINKED_CHUNKS, keys::EVENTS, keys::GAPS],
IdbTransactionMode::Readonly,
)?;
if transaction.get_chunks_count_in_room(room_id).await? == 0 {
return Ok((None, ChunkIdentifierGenerator::new_from_scratch()));
}
// Now that we know we have some chunks in the room, we query IndexedDB
// for the last chunk in the room by getting the chunk which does not
// have a next chunk.
match transaction.get_chunk_by_next_chunk_id(room_id, &None).await {
Err(IndexeddbEventCacheStoreTransactionError::ItemIsNotUnique) => {
// If there are multiple chunks that do not have a next chunk, that
// means we have more than one last chunk, which means that we have
// more than one list in the room.
Err(IndexeddbEventCacheStoreError::ChunksContainDisjointLists)
}
Err(e) => {
// There was some error querying IndexedDB, but it is not necessarily
// a violation of our data constraints.
Err(e.into())
},
Ok(None) => {
// If there is no chunk without a next chunk, that means every chunk
// points to another chunk, which means that we have a cycle in our list.
Err(IndexeddbEventCacheStoreError::ChunksContainCycle)
},
Ok(Some(last_chunk)) => {
let last_chunk_identifier = ChunkIdentifier::new(last_chunk.identifier);
let last_raw_chunk = transaction
.load_chunk_by_id(room_id, &last_chunk_identifier)
.await?
.ok_or(IndexeddbEventCacheStoreError::UnableToLoadChunk)?;
let max_chunk_id = transaction
.get_max_chunk_by_id(room_id)
.await?
.map(|chunk| ChunkIdentifier::new(chunk.identifier))
.ok_or(IndexeddbEventCacheStoreError::NoMaxChunkId)?;
let generator =
ChunkIdentifierGenerator::new_from_previous_chunk_identifier(max_chunk_id);
Ok((Some(last_raw_chunk), generator))
}
}
}
async fn load_previous_chunk(
@@ -14,11 +14,14 @@
use indexed_db_futures::{prelude::IdbTransaction, IdbQuerySource};
use matrix_sdk_base::{
event_cache::{Event as RawEvent, Gap as RawGap},
event_cache::{store::EventCacheStoreError, Event as RawEvent, Gap as RawGap},
linked_chunk::{ChunkContent, ChunkIdentifier, RawChunk},
};
use ruma::{events::relation::RelationType, OwnedEventId, RoomId};
use serde::{de::DeserializeOwned, Serialize};
use serde::{
de::{DeserializeOwned, Error},
Serialize,
};
use thiserror::Error;
use web_sys::IdbCursorDirection;
@@ -55,9 +58,19 @@ impl From<web_sys::DomException> for IndexeddbEventCacheStoreTransactionError {
impl From<serde_wasm_bindgen::Error> for IndexeddbEventCacheStoreTransactionError {
fn from(e: serde_wasm_bindgen::Error) -> Self {
Self::Serialization(Box::new(<serde_json::Error as serde::de::Error>::custom(
e.to_string(),
)))
Self::Serialization(Box::new(serde_json::Error::custom(e.to_string())))
}
}
impl From<IndexeddbEventCacheStoreTransactionError> for EventCacheStoreError {
fn from(value: IndexeddbEventCacheStoreTransactionError) -> Self {
use IndexeddbEventCacheStoreTransactionError::*;
match value {
DomException { .. } => Self::InvalidData { details: value.to_string() },
Serialization(e) => Self::Serialization(serde_json::Error::custom(e.to_string())),
ItemIsNotUnique | ItemNotFound => Self::InvalidData { details: value.to_string() },
}
}
}
@@ -406,6 +419,18 @@ impl<'a> IndexeddbEventCacheStoreTransaction<'a> {
self.get_item_by_key_components::<Chunk, IndexedChunkIdKey>(room_id, chunk_id).await
}
/// Query IndexedDB for chunks such that the next chunk matches the given
/// chunk identifier in the given room. If more than one item is found,
/// an error is returned.
pub async fn get_chunk_by_next_chunk_id(
&self,
room_id: &RoomId,
next_chunk_id: &Option<ChunkIdentifier>,
) -> Result<Option<Chunk>, IndexeddbEventCacheStoreTransactionError> {
self.get_item_by_key_components::<Chunk, IndexedNextChunkIdKey>(room_id, next_chunk_id)
.await
}
/// Query IndexedDB for all chunks in the given room
pub async fn get_chunks_in_room(
&self,
@@ -414,6 +439,22 @@ impl<'a> IndexeddbEventCacheStoreTransaction<'a> {
self.get_items_in_room::<Chunk, IndexedChunkIdKey>(room_id).await
}
/// Query IndexedDB for the number of chunks in the given room.
pub async fn get_chunks_count_in_room(
&self,
room_id: &RoomId,
) -> Result<usize, IndexeddbEventCacheStoreTransactionError> {
self.get_items_count_in_room::<Chunk, IndexedChunkIdKey>(room_id).await
}
/// Query IndexedDB for the chunk with the maximum key in the given room.
pub async fn get_max_chunk_by_id(
&self,
room_id: &RoomId,
) -> Result<Option<Chunk>, IndexeddbEventCacheStoreTransactionError> {
self.get_max_item_by_key::<Chunk, IndexedChunkIdKey>(room_id).await
}
/// Query IndexedDB for given chunk in given room and additionally query
/// for events or gap, depending on chunk type, in order to construct the
/// full chunk.
+164 -52
View File
@@ -36,6 +36,7 @@ use matrix_sdk_base::{
Position, RawChunk, Update,
},
media::{MediaRequestParameters, UniqueKey},
timer,
};
use matrix_sdk_store_encryption::StoreCipher;
use ruma::{
@@ -43,8 +44,11 @@ use ruma::{
OwnedEventId, RoomId,
};
use rusqlite::{params_from_iter, OptionalExtension, ToSql, Transaction, TransactionBehavior};
use tokio::fs;
use tracing::{debug, error, trace};
use tokio::{
fs,
sync::{Mutex, OwnedMutexGuard},
};
use tracing::{debug, error, instrument, trace};
use crate::{
error::{Error, Result},
@@ -86,7 +90,16 @@ const CHUNK_TYPE_GAP_TYPE_STRING: &str = "G";
#[derive(Clone)]
pub struct SqliteEventCacheStore {
store_cipher: Option<Arc<StoreCipher>>,
/// The pool of connections.
pool: SqlitePool,
/// We make the difference between connections for read operations, and for
/// write operations. We keep a single connection apart from write
/// operations. All other connections are used for read operations. The
/// lock is used to ensure there is one owner at a time.
write_connection: Arc<Mutex<SqliteAsyncConn>>,
media_service: MediaService,
}
@@ -114,7 +127,12 @@ impl SqliteEventCacheStore {
}
/// Open the SQLite-based event cache store with the config open config.
#[instrument(skip(config), fields(path = ?config.path))]
pub async fn open_with_config(config: SqliteStoreConfig) -> Result<Self, OpenStoreError> {
debug!(?config);
let _timer = timer!("open_with_config");
let SqliteStoreConfig { path, passphrase, pool_config, runtime_config } = config;
fs::create_dir_all(&path).await.map_err(OpenStoreError::CreateDir)?;
@@ -125,7 +143,7 @@ impl SqliteEventCacheStore {
let pool = config.create_pool(Runtime::Tokio1)?;
let this = Self::open_with_pool(pool, passphrase.as_deref()).await?;
this.pool.get().await?.apply_runtime_config(runtime_config).await?;
this.write().await?.apply_runtime_config(runtime_config).await?;
Ok(this)
}
@@ -151,10 +169,21 @@ impl SqliteEventCacheStore {
let last_media_cleanup_time = conn.get_serialized_kv(keys::LAST_MEDIA_CLEANUP_TIME).await?;
media_service.restore(media_retention_policy, last_media_cleanup_time);
Ok(Self { store_cipher, pool, media_service })
Ok(Self {
store_cipher,
pool,
// Use `conn` as our selected write connections.
write_connection: Arc::new(Mutex::new(conn)),
media_service,
})
}
async fn acquire(&self) -> Result<SqliteAsyncConn> {
// Acquire a connection for executing read operations.
#[instrument(skip_all)]
async fn read(&self) -> Result<SqliteAsyncConn> {
trace!("Taking a `read` connection");
let _timer = timer!("connection");
let connection = self.pool.get().await?;
// Per https://www.sqlite.org/foreignkeys.html#fk_enable, foreign key
@@ -166,6 +195,23 @@ impl SqliteEventCacheStore {
Ok(connection)
}
// Acquire a connection for executing write operations.
#[instrument(skip_all)]
async fn write(&self) -> Result<OwnedMutexGuard<SqliteAsyncConn>> {
trace!("Taking a `write` connection");
let _timer = timer!("connection");
let connection = self.write_connection.clone().lock_owned().await;
// Per https://www.sqlite.org/foreignkeys.html#fk_enable, foreign key
// support must be enabled on a per-connection basis. Execute it every
// time we try to get a connection, since we can't guarantee a previous
// connection did enable it before.
connection.execute_batch("PRAGMA foreign_keys = ON;").await?;
Ok(connection)
}
fn map_row_to_chunk(
row: &rusqlite::Row<'_>,
) -> Result<(u64, Option<u64>, Option<u64>, String), rusqlite::Error> {
@@ -412,12 +458,15 @@ async fn run_migrations(conn: &SqliteAsyncConn, version: u8) -> Result<()> {
impl EventCacheStore for SqliteEventCacheStore {
type Error = Error;
#[instrument(skip(self))]
async fn try_take_leased_lock(
&self,
lease_duration_ms: u32,
key: &str,
holder: &str,
) -> Result<bool> {
let _timer = timer!("method");
let key = key.to_owned();
let holder = holder.to_owned();
@@ -425,7 +474,7 @@ impl EventCacheStore for SqliteEventCacheStore {
let expiration = now + lease_duration_ms as u64;
let num_touched = self
.acquire()
.write()
.await?
.with_transaction(move |txn| {
txn.execute(
@@ -445,11 +494,14 @@ impl EventCacheStore for SqliteEventCacheStore {
Ok(num_touched == 1)
}
#[instrument(skip(self, updates))]
async fn handle_linked_chunk_updates(
&self,
linked_chunk_id: LinkedChunkId<'_>,
updates: Vec<Update<Event, Gap>>,
) -> Result<(), Self::Error> {
let _timer = timer!("method");
// Use a single transaction throughout this function, so that either all updates
// work, or none is taken into account.
let hashed_linked_chunk_id =
@@ -457,7 +509,7 @@ impl EventCacheStore for SqliteEventCacheStore {
let linked_chunk_id = linked_chunk_id.to_owned();
let this = self.clone();
with_immediate_transaction(self.acquire().await?, move |txn| {
with_immediate_transaction(self, move |txn| {
for up in updates {
match up {
Update::NewItemsChunk { previous, new, next } => {
@@ -773,17 +825,20 @@ impl EventCacheStore for SqliteEventCacheStore {
Ok(())
}
#[instrument(skip(self))]
async fn load_all_chunks(
&self,
linked_chunk_id: LinkedChunkId<'_>,
) -> Result<Vec<RawChunk<Event, Gap>>, Self::Error> {
let _timer = timer!("method");
let hashed_linked_chunk_id =
self.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.storage_key());
let this = self.clone();
let result = self
.acquire()
.read()
.await?
.with_transaction(move |txn| -> Result<_> {
let mut items = Vec::new();
@@ -814,14 +869,17 @@ impl EventCacheStore for SqliteEventCacheStore {
Ok(result)
}
#[instrument(skip(self))]
async fn load_all_chunks_metadata(
&self,
linked_chunk_id: LinkedChunkId<'_>,
) -> Result<Vec<ChunkMetadata>, Self::Error> {
let _timer = timer!("method");
let hashed_linked_chunk_id =
self.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.storage_key());
self.acquire()
self.read()
.await?
.with_transaction(move |txn| -> Result<_> {
// I'm not a DB analyst, so for my own future sanity: this query joins the
@@ -874,17 +932,20 @@ impl EventCacheStore for SqliteEventCacheStore {
.await
}
#[instrument(skip(self))]
async fn load_last_chunk(
&self,
linked_chunk_id: LinkedChunkId<'_>,
) -> Result<(Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator), Self::Error> {
let _timer = timer!("method");
let hashed_linked_chunk_id =
self.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.storage_key());
let this = self.clone();
self
.acquire()
.read()
.await?
.with_transaction(move |txn| -> Result<_> {
// Find the latest chunk identifier to generate a `ChunkIdentifierGenerator`, and count the number of chunks.
@@ -966,18 +1027,21 @@ impl EventCacheStore for SqliteEventCacheStore {
.await
}
#[instrument(skip(self))]
async fn load_previous_chunk(
&self,
linked_chunk_id: LinkedChunkId<'_>,
before_chunk_identifier: ChunkIdentifier,
) -> Result<Option<RawChunk<Event, Gap>>, Self::Error> {
let _timer = timer!("method");
let hashed_linked_chunk_id =
self.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.storage_key());
let this = self.clone();
self
.acquire()
.read()
.await?
.with_transaction(move |txn| -> Result<_> {
// Find the chunk before the chunk identified by `before_chunk_identifier`.
@@ -1017,8 +1081,11 @@ impl EventCacheStore for SqliteEventCacheStore {
.await
}
#[instrument(skip(self))]
async fn clear_all_linked_chunks(&self) -> Result<(), Self::Error> {
self.acquire()
let _timer = timer!("method");
self.write()
.await?
.with_transaction(move |txn| {
// Remove all the chunks, and let cascading do its job.
@@ -1027,14 +1094,18 @@ impl EventCacheStore for SqliteEventCacheStore {
txn.execute("DELETE FROM events", ())
})
.await?;
Ok(())
}
#[instrument(skip(self, events))]
async fn filter_duplicated_events(
&self,
linked_chunk_id: LinkedChunkId<'_>,
events: Vec<OwnedEventId>,
) -> Result<Vec<(OwnedEventId, Position)>, Self::Error> {
let _timer = timer!("method");
// If there's no events for which we want to check duplicates, we can return
// early. It's not only an optimization to do so: it's required, otherwise the
// `repeat_vars` call below will panic.
@@ -1047,7 +1118,7 @@ impl EventCacheStore for SqliteEventCacheStore {
self.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.storage_key());
let linked_chunk_id = linked_chunk_id.to_owned();
self.acquire()
self.read()
.await?
.with_transaction(move |txn| -> Result<_> {
txn.chunk_large_query_over(events, None, move |txn, events| {
@@ -1109,17 +1180,20 @@ impl EventCacheStore for SqliteEventCacheStore {
.await
}
#[instrument(skip(self, event_id))]
async fn find_event(
&self,
room_id: &RoomId,
event_id: &EventId,
) -> Result<Option<Event>, Self::Error> {
let _timer = timer!("method");
let event_id = event_id.to_owned();
let this = self.clone();
let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, room_id);
self.acquire()
self.read()
.await?
.with_transaction(move |txn| -> Result<_> {
let Some(event) = txn
@@ -1138,12 +1212,15 @@ impl EventCacheStore for SqliteEventCacheStore {
.await
}
#[instrument(skip(self, event_id, filters))]
async fn find_event_relations(
&self,
room_id: &RoomId,
event_id: &EventId,
filters: Option<&[RelationType]>,
) -> Result<Vec<(Event, Option<Position>)>, Self::Error> {
let _timer = timer!("method");
let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, room_id);
let hashed_linked_chunk_id =
@@ -1153,7 +1230,7 @@ impl EventCacheStore for SqliteEventCacheStore {
let filters = filters.map(ToOwned::to_owned);
let store = self.clone();
self.acquire()
self.read()
.await?
.with_transaction(move |txn| -> Result<_> {
find_event_relations_transaction(
@@ -1168,7 +1245,10 @@ impl EventCacheStore for SqliteEventCacheStore {
.await
}
#[instrument(skip(self, event))]
async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error> {
let _timer = timer!("method");
let Some(event_id) = event.event_id() else {
error!(%room_id, "Trying to save an event with no ID");
return Ok(());
@@ -1178,7 +1258,7 @@ impl EventCacheStore for SqliteEventCacheStore {
let event_id = event_id.to_string();
let encoded_event = self.encode_event(&event)?;
self.acquire()
self.write()
.await?
.with_transaction(move |txn| -> Result<_> {
txn.execute(
@@ -1190,27 +1270,33 @@ impl EventCacheStore for SqliteEventCacheStore {
.await
}
#[instrument(skip_all)]
async fn add_media_content(
&self,
request: &MediaRequestParameters,
content: Vec<u8>,
ignore_policy: IgnoreMediaRetentionPolicy,
) -> Result<()> {
let _timer = timer!("method");
self.media_service.add_media_content(self, request, content, ignore_policy).await
}
#[instrument(skip_all)]
async fn replace_media_key(
&self,
from: &MediaRequestParameters,
to: &MediaRequestParameters,
) -> Result<(), Self::Error> {
let _timer = timer!("method");
let prev_uri = self.encode_key(keys::MEDIA, from.source.unique_key());
let prev_format = self.encode_key(keys::MEDIA, from.format.unique_key());
let new_uri = self.encode_key(keys::MEDIA, to.source.unique_key());
let new_format = self.encode_key(keys::MEDIA, to.format.unique_key());
let conn = self.acquire().await?;
let conn = self.write().await?;
conn.execute(
r#"UPDATE media SET uri = ?, format = ? WHERE uri = ? AND format = ?"#,
(new_uri, new_format, prev_uri, prev_format),
@@ -1220,56 +1306,80 @@ impl EventCacheStore for SqliteEventCacheStore {
Ok(())
}
#[instrument(skip_all)]
async fn get_media_content(&self, request: &MediaRequestParameters) -> Result<Option<Vec<u8>>> {
let _timer = timer!("method");
self.media_service.get_media_content(self, request).await
}
#[instrument(skip_all)]
async fn remove_media_content(&self, request: &MediaRequestParameters) -> Result<()> {
let _timer = timer!("method");
let uri = self.encode_key(keys::MEDIA, request.source.unique_key());
let format = self.encode_key(keys::MEDIA, request.format.unique_key());
let conn = self.acquire().await?;
let conn = self.write().await?;
conn.execute("DELETE FROM media WHERE uri = ? AND format = ?", (uri, format)).await?;
Ok(())
}
#[instrument(skip(self))]
async fn get_media_content_for_uri(
&self,
uri: &MxcUri,
) -> Result<Option<Vec<u8>>, Self::Error> {
let _timer = timer!("method");
self.media_service.get_media_content_for_uri(self, uri).await
}
#[instrument(skip(self))]
async fn remove_media_content_for_uri(&self, uri: &MxcUri) -> Result<()> {
let _timer = timer!("method");
let uri = self.encode_key(keys::MEDIA, uri);
let conn = self.acquire().await?;
let conn = self.write().await?;
conn.execute("DELETE FROM media WHERE uri = ?", (uri,)).await?;
Ok(())
}
#[instrument(skip_all)]
async fn set_media_retention_policy(
&self,
policy: MediaRetentionPolicy,
) -> Result<(), Self::Error> {
let _timer = timer!("method");
self.media_service.set_media_retention_policy(self, policy).await
}
#[instrument(skip_all)]
fn media_retention_policy(&self) -> MediaRetentionPolicy {
let _timer = timer!("method");
self.media_service.media_retention_policy()
}
#[instrument(skip_all)]
async fn set_ignore_media_retention_policy(
&self,
request: &MediaRequestParameters,
ignore_policy: IgnoreMediaRetentionPolicy,
) -> Result<(), Self::Error> {
let _timer = timer!("method");
self.media_service.set_ignore_media_retention_policy(self, request, ignore_policy).await
}
#[instrument(skip_all)]
async fn clean_up_media_cache(&self) -> Result<(), Self::Error> {
let _timer = timer!("method");
self.media_service.clean_up_media_cache(self).await
}
}
@@ -1282,7 +1392,7 @@ impl EventCacheStoreMedia for SqliteEventCacheStore {
async fn media_retention_policy_inner(
&self,
) -> Result<Option<MediaRetentionPolicy>, Self::Error> {
let conn = self.acquire().await?;
let conn = self.read().await?;
conn.get_serialized_kv(keys::MEDIA_RETENTION_POLICY).await
}
@@ -1290,7 +1400,7 @@ impl EventCacheStoreMedia for SqliteEventCacheStore {
&self,
policy: MediaRetentionPolicy,
) -> Result<(), Self::Error> {
let conn = self.acquire().await?;
let conn = self.write().await?;
conn.set_serialized_kv(keys::MEDIA_RETENTION_POLICY, policy).await?;
Ok(())
}
@@ -1314,7 +1424,7 @@ impl EventCacheStoreMedia for SqliteEventCacheStore {
let format = self.encode_key(keys::MEDIA, request.format.unique_key());
let timestamp = time_to_timestamp(last_access);
let conn = self.acquire().await?;
let conn = self.write().await?;
conn.execute(
"INSERT OR REPLACE INTO media (uri, format, data, last_access, ignore_policy) VALUES (?, ?, ?, ?, ?)",
(uri, format, data, timestamp, ignore_policy),
@@ -1333,7 +1443,7 @@ impl EventCacheStoreMedia for SqliteEventCacheStore {
let format = self.encode_key(keys::MEDIA, request.format.unique_key());
let ignore_policy = ignore_policy.is_yes();
let conn = self.acquire().await?;
let conn = self.write().await?;
conn.execute(
r#"UPDATE media SET ignore_policy = ? WHERE uri = ? AND format = ?"#,
(ignore_policy, uri, format),
@@ -1352,7 +1462,7 @@ impl EventCacheStoreMedia for SqliteEventCacheStore {
let format = self.encode_key(keys::MEDIA, request.format.unique_key());
let timestamp = time_to_timestamp(current_time);
let conn = self.acquire().await?;
let conn = self.write().await?;
let data = conn
.with_transaction::<_, rusqlite::Error, _>(move |txn| {
// Update the last access.
@@ -1383,7 +1493,7 @@ impl EventCacheStoreMedia for SqliteEventCacheStore {
let uri = self.encode_key(keys::MEDIA, uri);
let timestamp = time_to_timestamp(current_time);
let conn = self.acquire().await?;
let conn = self.write().await?;
let data = conn
.with_transaction::<_, rusqlite::Error, _>(move |txn| {
// Update the last access.
@@ -1413,7 +1523,7 @@ impl EventCacheStoreMedia for SqliteEventCacheStore {
return Ok(());
}
let conn = self.acquire().await?;
let conn = self.write().await?;
let removed = conn
.with_transaction::<_, Error, _>(move |txn| {
let mut removed = false;
@@ -1532,7 +1642,7 @@ impl EventCacheStoreMedia for SqliteEventCacheStore {
}
async fn last_media_cleanup_time_inner(&self) -> Result<Option<SystemTime>, Self::Error> {
let conn = self.acquire().await?;
let conn = self.read().await?;
conn.get_serialized_kv(keys::LAST_MEDIA_CLEANUP_TIME).await
}
}
@@ -1630,33 +1740,35 @@ async fn with_immediate_transaction<
T: Send + 'static,
F: FnOnce(&Transaction<'_>) -> Result<T, Error> + Send + 'static,
>(
conn: SqliteAsyncConn,
this: &SqliteEventCacheStore,
f: F,
) -> Result<T, Error> {
conn.interact(move |conn| -> Result<T, Error> {
// Start the transaction in IMMEDIATE mode since all updates may cause writes,
// to avoid read transactions upgrading to write mode and causing
// SQLITE_BUSY errors. See also: https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
conn.set_transaction_behavior(TransactionBehavior::Immediate);
this.write()
.await?
.interact(move |conn| -> Result<T, Error> {
// Start the transaction in IMMEDIATE mode since all updates may cause writes,
// to avoid read transactions upgrading to write mode and causing
// SQLITE_BUSY errors. See also: https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions
conn.set_transaction_behavior(TransactionBehavior::Immediate);
let code = || -> Result<T, Error> {
let txn = conn.transaction()?;
let res = f(&txn)?;
txn.commit()?;
Ok(res)
};
let code = || -> Result<T, Error> {
let txn = conn.transaction()?;
let res = f(&txn)?;
txn.commit()?;
Ok(res)
};
let res = code();
let res = code();
// Reset the transaction behavior to use Deferred, after this transaction has
// been run, whether it was successful or not.
conn.set_transaction_behavior(TransactionBehavior::Deferred);
// Reset the transaction behavior to use Deferred, after this transaction has
// been run, whether it was successful or not.
conn.set_transaction_behavior(TransactionBehavior::Deferred);
res
})
.await
// SAFETY: same logic as in [`deadpool::managed::Object::with_transaction`].`
.unwrap()
res
})
.await
// SAFETY: same logic as in [`deadpool::managed::Object::with_transaction`].`
.unwrap()
}
fn insert_chunk(
@@ -1763,7 +1875,7 @@ mod tests {
async fn get_event_cache_store_content_sorted_by_last_access(
event_cache_store: &SqliteEventCacheStore,
) -> Vec<Vec<u8>> {
let sqlite_db = event_cache_store.acquire().await.expect("accessing sqlite db failed");
let sqlite_db = event_cache_store.read().await.expect("accessing sqlite db failed");
sqlite_db
.prepare("SELECT data FROM media ORDER BY last_access DESC", |mut stmt| {
stmt.query(())?.mapped(|row| row.get(0)).collect()
@@ -2053,7 +2165,7 @@ mod tests {
// Check that cascading worked. Yes, SQLite, I doubt you.
let gaps = store
.acquire()
.read()
.await
.unwrap()
.with_transaction(|txn| -> rusqlite::Result<_> {
@@ -2175,7 +2287,7 @@ mod tests {
// Make sure the position have been updated for the remaining events.
let num_rows: u64 = store
.acquire()
.read()
.await
.unwrap()
.with_transaction(move |txn| {
@@ -2324,7 +2436,7 @@ mod tests {
// Check that cascading worked. Yes, SQLite, I doubt you.
store
.acquire()
.read()
.await
.unwrap()
.with_transaction(|txn| -> rusqlite::Result<_> {
+17 -3
View File
@@ -25,6 +25,7 @@ mod event_cache_store;
mod state_store;
mod utils;
use std::{
cmp::max,
fmt,
path::{Path, PathBuf},
};
@@ -66,6 +67,12 @@ impl fmt::Debug for SqliteStoreConfig {
}
}
/// The minimum size of the connections pool.
///
/// We need at least 2 connections: one connection for write operations, and one
/// connection for read operations.
const POOL_MINIMUM_SIZE: usize = 2;
impl SqliteStoreConfig {
/// Create a new [`SqliteStoreConfig`] with a path representing the
/// directory containing the store database.
@@ -76,7 +83,7 @@ impl SqliteStoreConfig {
Self {
path: path.as_ref().to_path_buf(),
passphrase: None,
pool_config: PoolConfig::new(num_cpus::get_physical() * 4),
pool_config: PoolConfig::new(max(POOL_MINIMUM_SIZE, num_cpus::get_physical() * 4)),
runtime_config: RuntimeConfig::default(),
}
}
@@ -122,7 +129,7 @@ impl SqliteStoreConfig {
///
/// See [`deadpool_sqlite::PoolConfig::max_size`] to learn more.
pub fn pool_max_size(mut self, max_size: usize) -> Self {
self.pool_config.max_size = max_size;
self.pool_config.max_size = max(POOL_MINIMUM_SIZE, max_size);
self
}
@@ -218,7 +225,7 @@ mod tests {
path::{Path, PathBuf},
};
use super::SqliteStoreConfig;
use super::{SqliteStoreConfig, POOL_MINIMUM_SIZE};
#[test]
fn test_new() {
@@ -263,4 +270,11 @@ mod tests {
assert_eq!(store_config.path, PathBuf::from("bar"));
}
#[test]
fn test_pool_size_has_a_minimum() {
let store_config = SqliteStoreConfig::new(Path::new("foo")).pool_max_size(1);
assert_eq!(store_config.pool_config.max_size, POOL_MINIMUM_SIZE);
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
name = "matrix-sdk-ui"
description = "GUI-centric utilities on top of matrix-rust-sdk (experimental)."
version = "0.13.0"
edition = "2021"
edition = "2024"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
license = "Apache-2.0"
rust-version.workspace = true
@@ -30,11 +30,11 @@ use std::{pin::Pin, time::Duration};
use async_stream::stream;
use futures_core::stream::Stream;
use futures_util::{pin_mut, StreamExt};
use matrix_sdk::{sleep::sleep, Client, SlidingSync, LEASE_DURATION_MS};
use futures_util::{StreamExt, pin_mut};
use matrix_sdk::{Client, LEASE_DURATION_MS, SlidingSync, sleep::sleep};
use ruma::{api::client::sync::sync_events::v5 as http, assign};
use tokio::sync::OwnedMutexGuard;
use tracing::{debug, instrument, trace, Span};
use tracing::{Span, debug, instrument, trace};
/// Unit type representing a permit to *use* an [`EncryptionSyncService`].
///
@@ -66,11 +66,7 @@ pub enum WithLocking {
impl From<bool> for WithLocking {
fn from(value: bool) -> Self {
if value {
Self::Yes
} else {
Self::No
}
if value { Self::Yes } else { Self::No }
}
}
@@ -130,7 +126,7 @@ impl EncryptionSyncService {
// Any other error is fatal
return Err(Error::ClientError(err));
}
};
}
}
Ok(Self { client, sliding_sync, with_locking })
@@ -18,38 +18,39 @@ use std::{
time::Duration,
};
use futures_util::{pin_mut, StreamExt as _};
use futures_util::{StreamExt as _, pin_mut};
use matrix_sdk::{
room::Room, sleep::sleep, Client, ClientBuildError, SlidingSyncList, SlidingSyncMode,
Client, ClientBuildError, SlidingSyncList, SlidingSyncMode, room::Room, sleep::sleep,
};
use matrix_sdk_base::{deserialized_responses::TimelineEvent, RoomState, StoreError};
use matrix_sdk_base::{RoomState, StoreError, deserialized_responses::TimelineEvent};
use ruma::{
EventId, OwnedEventId, OwnedRoomId, RoomId, UserId,
api::client::sync::sync_events::v5 as http,
assign,
directory::RoomTypeFilter,
events::{
AnyFullStateEventContent, AnyMessageLikeEventContent, AnyStateEvent,
AnySyncMessageLikeEvent, AnySyncTimelineEvent, FullStateEventContent, StateEventType,
TimelineEventType,
room::{
join_rules::JoinRule,
member::{MembershipState, StrippedRoomMemberEvent},
message::{Relation, SyncRoomMessageEvent},
},
AnyFullStateEventContent, AnyMessageLikeEventContent, AnyStateEvent,
AnySyncMessageLikeEvent, AnySyncTimelineEvent, FullStateEventContent, StateEventType,
TimelineEventType,
},
html::RemoveReplyFallback,
push::Action,
serde::Raw,
uint, EventId, OwnedEventId, OwnedRoomId, RoomId, UserId,
uint,
};
use thiserror::Error;
use tokio::sync::Mutex as AsyncMutex;
use tracing::{debug, info, instrument, trace, warn};
use crate::{
DEFAULT_SANITIZER_MODE,
encryption_sync_service::{EncryptionSyncPermit, EncryptionSyncService, WithLocking},
sync_service::SyncService,
DEFAULT_SANITIZER_MODE,
};
/// What kind of process setup do we have for this notification client?
@@ -16,7 +16,7 @@ use super::{BoxedFilterFn, Filter};
/// Create a new filter that will run multiple filters. It returns `true` if at
/// least one of the filter returns `true`.
pub fn new_filter(filters: Vec<BoxedFilterFn>) -> impl Filter {
pub fn new_filter(filters: Vec<BoxedFilterFn>) -> impl Filter + use<> {
move |room| -> bool { filters.iter().any(|filter| filter(room)) }
}
@@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
pub use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher as _};
pub use fuzzy_matcher::{FuzzyMatcher as _, skim::SkimMatcherV2};
use super::{normalize_string, Filter};
use super::{Filter, normalize_string};
struct FuzzyMatcher {
matcher: SkimMatcherV2,
@@ -44,7 +44,7 @@ impl FuzzyMatcher {
///
/// Rooms are fetched from the `Client`. The pattern and the room names are
/// normalized with `normalize_string`.
pub fn new_filter(pattern: &str) -> impl Filter {
pub fn new_filter(pattern: &str) -> impl Filter + use<> {
let searcher = FuzzyMatcher::new().with_pattern(pattern);
move |room| -> bool {
@@ -21,7 +21,7 @@
//!
//! ```rust
//! use matrix_sdk_ui::room_list_service::{
//! filters, RoomListDynamicEntriesController,
//! RoomListDynamicEntriesController, filters,
//! };
//!
//! fn configure_room_list(
@@ -67,7 +67,7 @@ mod unread;
pub use all::new_filter as new_filter_all;
pub use any::new_filter as new_filter_any;
pub use category::{new_filter as new_filter_category, RoomCategory};
pub use category::{RoomCategory, new_filter as new_filter_category};
pub use deduplicate_versions::new_filter as new_filter_deduplicate_versions;
pub use favourite::new_filter as new_filter_favourite;
pub use fuzzy_match_room_name::new_filter as new_filter_fuzzy_match_room_name;
@@ -84,12 +84,12 @@ pub use normalized_match_room_name::new_filter as new_filter_normalized_match_ro
pub use not::new_filter as new_filter_not;
#[cfg(test)]
use ruma::RoomId;
use unicode_normalization::{char::is_combining_mark, UnicodeNormalization};
use unicode_normalization::{UnicodeNormalization, char::is_combining_mark};
pub use unread::new_filter as new_filter_unread;
#[cfg(test)]
use wiremock::{
matchers::{header, method, path},
Mock, MockServer, ResponseTemplate,
matchers::{header, method, path},
};
/// A trait “alias” that represents a _filter_.
@@ -14,7 +14,7 @@
use tracing::error;
use super::{normalize_string, Filter};
use super::{Filter, normalize_string};
struct NormalizedMatcher {
pattern: Option<String>,
@@ -45,7 +45,7 @@ impl NormalizedMatcher {
///
/// Rooms are fetched from the `Client`. The pattern and the room names are
/// normalized with `normalize_string`.
pub fn new_filter(pattern: &str) -> impl Filter {
pub fn new_filter(pattern: &str) -> impl Filter + use<> {
let searcher = NormalizedMatcher::new().with_pattern(pattern);
move |room| -> bool {
@@ -60,15 +60,15 @@ use std::{sync::Arc, time::Duration};
use async_stream::stream;
use eyeball::Subscriber;
use futures_util::{pin_mut, Stream, StreamExt};
use futures_util::{Stream, StreamExt, pin_mut};
use matrix_sdk::{
event_cache::EventCacheError, timeout::timeout, Client, Error as SlidingSyncError, Room,
SlidingSync, SlidingSyncList, SlidingSyncMode,
Client, Error as SlidingSyncError, Room, SlidingSync, SlidingSyncList, SlidingSyncMode,
event_cache::EventCacheError, timeout::timeout,
};
pub use room_list::*;
use ruma::{
api::client::sync::sync_events::v5 as http, assign, directory::RoomTypeFilter,
events::StateEventType, OwnedRoomId, RoomId, UInt,
OwnedRoomId, RoomId, UInt, api::client::sync::sync_events::v5 as http, assign,
directory::RoomTypeFilter, events::StateEventType,
};
pub use state::*;
use thiserror::Error;
@@ -326,7 +326,7 @@ impl RoomListService {
&self,
delay_before_showing: Duration,
delay_before_hiding: Duration,
) -> impl Stream<Item = SyncIndicator> {
) -> impl Stream<Item = SyncIndicator> + use<> {
let mut state = self.state();
stream! {
@@ -501,16 +501,16 @@ pub enum SyncIndicator {
mod tests {
use std::future::ready;
use futures_util::{pin_mut, StreamExt};
use futures_util::{StreamExt, pin_mut};
use matrix_sdk::{
config::RequestConfig, test_utils::client::mock_matrix_session, Client, SlidingSyncMode,
Client, SlidingSyncMode, config::RequestConfig, test_utils::client::mock_matrix_session,
};
use matrix_sdk_test::async_test;
use ruma::api::MatrixVersion;
use serde_json::json;
use wiremock::{http::Method, Match, Mock, MockServer, Request, ResponseTemplate};
use wiremock::{Match, Mock, MockServer, Request, ResponseTemplate, http::Method};
use super::{Error, RoomListService, State, ALL_ROOMS_LIST_NAME};
use super::{ALL_ROOMS_LIST_NAME, Error, RoomListService, State};
async fn new_client() -> (Client, MockServer) {
let session = mock_matrix_session();
@@ -20,10 +20,10 @@ use async_stream::stream;
use eyeball::{SharedObservable, Subscriber};
use eyeball_im::{Vector, VectorDiff};
use eyeball_im_util::vector::VectorObserverExt;
use futures_util::{pin_mut, stream, Stream, StreamExt as _};
use futures_util::{Stream, StreamExt as _, pin_mut, stream};
use matrix_sdk::{
executor::{spawn, JoinHandle},
Client, SlidingSync, SlidingSyncList,
executor::{JoinHandle, spawn},
};
use matrix_sdk_base::RoomInfoNotableUpdate;
use tokio::{
@@ -33,9 +33,9 @@ use tokio::{
use tracing::{error, trace};
use super::{
Error, Room, State,
filters::BoxedFilterFn,
sorters::{new_sorter_lexicographic, new_sorter_name, new_sorter_recency},
Error, Room, State,
};
/// A `RoomList` represents a list of rooms, from a
@@ -17,7 +17,7 @@
use std::{future::ready, sync::Mutex};
use eyeball::{SharedObservable, Subscriber};
use matrix_sdk::{sliding_sync::Range, SlidingSync, SlidingSyncMode};
use matrix_sdk::{SlidingSync, SlidingSyncMode, sliding_sync::Range};
use ruma::time::{Duration, Instant};
use super::Error;
+8 -7
View File
@@ -28,21 +28,22 @@ use std::{sync::Arc, time::Duration};
use eyeball::{SharedObservable, Subscriber};
use futures_util::{
future::{select, Either},
pin_mut, StreamExt as _,
StreamExt as _,
future::{Either, select},
pin_mut,
};
use matrix_sdk::{
config::RequestConfig,
executor::{spawn, JoinHandle},
sleep::sleep,
Client,
config::RequestConfig,
executor::{JoinHandle, spawn},
sleep::sleep,
};
use thiserror::Error;
use tokio::sync::{
mpsc::{Receiver, Sender},
Mutex as AsyncMutex, OwnedMutexGuard,
mpsc::{Receiver, Sender},
};
use tracing::{error, info, instrument, trace, warn, Instrument, Level, Span};
use tracing::{Instrument, Level, Span, error, info, instrument, trace, warn};
use crate::{
encryption_sync_service::{self, EncryptionSyncPermit, EncryptionSyncService, WithLocking},
@@ -20,8 +20,8 @@ use ruma::EventId;
#[cfg(doc)]
use super::controller::TimelineMetadata;
use super::{
event_item::EventTimelineItemKind, item::TimelineUniqueId, EventTimelineItem,
ReactionsByKeyBySender, TimelineEventItemId, TimelineItem,
EventTimelineItem, ReactionsByKeyBySender, TimelineEventItemId, TimelineItem,
event_item::EventTimelineItemKind, item::TimelineUniqueId,
};
pub(super) struct EventTimelineItemWithId<'a> {
+4 -4
View File
@@ -14,14 +14,14 @@
use std::sync::Arc;
use matrix_sdk::{executor::spawn, Room};
use matrix_sdk::{Room, executor::spawn};
use matrix_sdk_base::{SendOutsideWasm, SyncOutsideWasm};
use ruma::{events::AnySyncTimelineEvent, RoomVersionId};
use tracing::{info_span, Instrument, Span};
use ruma::{RoomVersionId, events::AnySyncTimelineEvent};
use tracing::{Instrument, Span, info_span};
use super::{
controller::{TimelineController, TimelineSettings},
DateDividerMode, Error, Timeline, TimelineDropHandle, TimelineFocus,
controller::{TimelineController, TimelineSettings},
};
use crate::{
timeline::{
@@ -42,17 +42,17 @@ use std::{borrow::Cow, collections::HashMap, sync::Arc};
use as_variant::as_variant;
use matrix_sdk::deserialized_responses::EncryptionInfo;
use ruma::{
MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, RoomVersionId,
events::{
AnySyncTimelineEvent,
poll::unstable_start::NewUnstablePollStartEventContentWithoutRelation,
relation::Replacement, room::message::RoomMessageEventContentWithoutRelation,
AnySyncTimelineEvent,
},
serde::Raw,
MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, RoomVersionId,
};
use tracing::{info, trace, warn};
use super::{rfind_event_by_item_id, ObservableItemsTransaction};
use super::{ObservableItemsTransaction, rfind_event_by_item_id};
use crate::timeline::{
EventTimelineItem, MsgLikeContent, MsgLikeKind, PollState, ReactionInfo, ReactionStatus,
TimelineEventItemId, TimelineItem, TimelineItemContent,
@@ -642,11 +642,7 @@ fn resolve_edits(
}
}
if let Some(edit) = best_edit {
edit_item(event, edit)
} else {
false
}
if let Some(edit) = best_edit { edit_item(event, edit) } else { false }
}
/// Apply the selected edit to the given EventTimelineItem.
@@ -22,26 +22,26 @@ use futures_util::pin_mut;
use imbl::Vector;
use itertools::{Either, Itertools as _};
use matrix_sdk::{
Client, Room,
crypto::store::types::RoomKeyInfo,
deserialized_responses::TimelineEventKind as SdkTimelineEventKind,
encryption::backups::BackupState,
event_handler::EventHandlerHandle,
executor::{spawn, JoinHandle},
Client, Room,
executor::{JoinHandle, spawn},
};
use tokio::sync::{
mpsc::{self, Receiver, Sender},
RwLock,
mpsc::{self, Receiver, Sender},
};
use tokio_stream::{wrappers::errors::BroadcastStreamRecvError, StreamExt as _};
use tracing::{debug, error, field, info, info_span, warn, Instrument as _};
use tokio_stream::{StreamExt as _, wrappers::errors::BroadcastStreamRecvError};
use tracing::{Instrument as _, debug, error, field, info, info_span, warn};
use crate::timeline::{
EncryptedMessage, EventTimelineItem, TimelineController, TimelineItem, TimelineItemKind,
controller::{TimelineSettings, TimelineState},
event_item::EventTimelineItemKind,
to_device::{handle_forwarded_room_key_event, handle_room_key_event},
traits::{Decryptor, RoomDataProvider},
EncryptedMessage, EventTimelineItem, TimelineController, TimelineItem, TimelineItemKind,
};
/// All the drop handles for the tasks used for crypto, namely message
@@ -382,11 +382,7 @@ fn compute_event_indices_to_retry_decryption(
// Break the result into 2 lists: (utds, decrypted)
.partition_map(
|(idx, event)| {
if event.content().is_unable_to_decrypt() {
Left(idx)
} else {
Right(idx)
}
if event.content().is_unable_to_decrypt() { Left(idx) } else { Right(idx) }
},
)
}
@@ -518,6 +514,7 @@ mod tests {
deserialized_responses::{AlgorithmInfo, EncryptionInfo, VerificationState},
};
use ruma::{
MilliSecondsSinceUnixEpoch, OwnedTransactionId,
events::room::{
encrypted::{
EncryptedEventScheme, MegolmV1AesSha2Content, MegolmV1AesSha2ContentInit,
@@ -525,19 +522,18 @@ mod tests {
},
message::RoomMessageEventContent,
},
owned_device_id, owned_event_id, owned_user_id, MilliSecondsSinceUnixEpoch,
OwnedTransactionId,
owned_device_id, owned_event_id, owned_user_id,
};
use crate::timeline::{
EncryptedMessage, EventSendState, EventTimelineItem, MsgLikeContent,
ReactionsByKeyBySender, TimelineDetails, TimelineItem, TimelineItemContent,
TimelineItemKind, TimelineUniqueId, VirtualTimelineItem,
controller::decryption_retry_task::compute_event_indices_to_retry_decryption,
event_item::{
EventTimelineItemKind, LocalEventTimelineItem, RemoteEventOrigin,
RemoteEventTimelineItem,
},
EncryptedMessage, EventSendState, EventTimelineItem, MsgLikeContent,
ReactionsByKeyBySender, TimelineDetails, TimelineItem, TimelineItemContent,
TimelineItemKind, TimelineUniqueId, VirtualTimelineItem,
};
#[test]
@@ -20,29 +20,29 @@ use std::{
use imbl::Vector;
use matrix_sdk::deserialized_responses::EncryptionInfo;
use ruma::{
EventId, OwnedEventId, OwnedUserId, RoomVersionId,
events::{
poll::unstable_start::UnstablePollStartEventContent, relation::Replacement,
room::message::RelationWithoutReplacement, AnyMessageLikeEventContent,
AnySyncMessageLikeEvent, AnySyncTimelineEvent, BundledMessageLikeRelations,
AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
BundledMessageLikeRelations, poll::unstable_start::UnstablePollStartEventContent,
relation::Replacement, room::message::RelationWithoutReplacement,
},
serde::Raw,
EventId, OwnedEventId, OwnedUserId, RoomVersionId,
};
use tracing::trace;
use super::{
super::{subscriber::skip::SkipCount, TimelineItem, TimelineItemKind, TimelineUniqueId},
read_receipts::ReadReceipts,
super::{TimelineItem, TimelineItemKind, TimelineUniqueId, subscriber::skip::SkipCount},
Aggregation, AggregationKind, Aggregations, AllRemoteEvents, ObservableItemsTransaction,
PendingEdit, PendingEditKind,
read_receipts::ReadReceipts,
};
use crate::{
timeline::{
InReplyToDetails, TimelineEventItemId,
event_item::{
extract_bundled_edit_event_json, extract_poll_edit_content,
extract_room_msg_edit_content,
},
InReplyToDetails, TimelineEventItemId,
},
unable_to_decrypt_hook::UtdHookManager,
};
@@ -23,31 +23,31 @@ use imbl::Vector;
#[cfg(test)]
use matrix_sdk::crypto::OlmMachine;
use matrix_sdk::{
Result, Room,
deserialized_responses::TimelineEvent,
event_cache::{RoomEventCache, RoomPaginationStatus},
paginators::{PaginationResult, Paginator},
send_queue::{
LocalEcho, LocalEchoContent, RoomSendQueueUpdate, SendHandle, SendReactionHandle,
},
Result, Room,
};
use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, RoomVersionId,
TransactionId, UserId,
api::client::receipt::create_receipt::v3::ReceiptType as SendReceiptType,
events::{
AnyMessageLikeEventContent, AnySyncEphemeralRoomEvent, AnySyncMessageLikeEvent,
AnySyncTimelineEvent, MessageLikeEventType,
poll::unstable_start::UnstablePollStartEventContent,
reaction::ReactionEventContent,
receipt::{Receipt, ReceiptThread, ReceiptType},
relation::Annotation,
room::message::{MessageType, Relation},
AnyMessageLikeEventContent, AnySyncEphemeralRoomEvent, AnySyncMessageLikeEvent,
AnySyncTimelineEvent, MessageLikeEventType,
},
serde::Raw,
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, RoomVersionId,
TransactionId, UserId,
};
#[cfg(test)]
use ruma::{events::receipt::ReceiptEventContent, OwnedRoomId, RoomId};
use ruma::{OwnedRoomId, RoomId, events::receipt::ReceiptEventContent};
use tokio::sync::{RwLock, RwLockWriteGuard};
use tracing::{debug, error, field::debug, info, instrument, trace, warn};
@@ -61,22 +61,22 @@ pub(super) use self::{
state_transaction::TimelineStateTransaction,
};
use super::{
DateDividerMode, EmbeddedEvent, Error, EventSendState, EventTimelineItem, InReplyToDetails,
PaginationError, Profile, TimelineDetails, TimelineEventItemId, TimelineFocus, TimelineItem,
TimelineItemContent, TimelineItemKind, VirtualTimelineItem,
algorithms::{rfind_event_by_id, rfind_event_item},
event_item::{ReactionStatus, RemoteEventOrigin},
item::TimelineUniqueId,
subscriber::TimelineSubscriber,
traits::{Decryptor, RoomDataProvider},
DateDividerMode, EmbeddedEvent, Error, EventSendState, EventTimelineItem, InReplyToDetails,
PaginationError, Profile, TimelineDetails, TimelineEventItemId, TimelineFocus, TimelineItem,
TimelineItemContent, TimelineItemKind, VirtualTimelineItem,
};
use crate::{
timeline::{
MsgLikeContent, MsgLikeKind, TimelineEventFilterFn,
algorithms::rfind_event_by_item_id,
date_dividers::DateDividerAdjuster,
event_item::TimelineItemHandle,
pinned_events_loader::{PinnedEventsLoader, PinnedEventsLoaderError},
MsgLikeContent, MsgLikeKind, TimelineEventFilterFn,
},
unable_to_decrypt_hook::UtdHookManager,
};
@@ -90,7 +90,7 @@ mod state;
mod state_transaction;
pub(super) use aggregations::*;
pub(super) use decryption_retry_task::{spawn_crypto_tasks, CryptoDropHandles};
pub(super) use decryption_retry_task::{CryptoDropHandles, spawn_crypto_tasks};
/// Data associated to the current timeline focus.
///
@@ -1240,7 +1240,9 @@ impl<P: RoomDataProvider, D: Decryptor> TimelineController<P, D> {
}
/// Subscribe to changes in the read receipts of our own user.
pub async fn subscribe_own_user_read_receipts_changed(&self) -> impl Stream<Item = ()> {
pub async fn subscribe_own_user_read_receipts_changed(
&self,
) -> impl Stream<Item = ()> + use<P, D> {
self.state.read().await.meta.read_receipts.subscribe_own_user_read_receipts_changed()
}
@@ -14,7 +14,7 @@
use std::{
cmp::Ordering,
collections::{vec_deque::Iter, VecDeque},
collections::{VecDeque, vec_deque::Iter},
iter::{Enumerate, Skip, Take},
ops::{Deref, RangeBounds},
sync::Arc,
@@ -28,7 +28,7 @@ use eyeball_im::{
use imbl::Vector;
use ruma::EventId;
use super::{metadata::EventMeta, TimelineItem};
use super::{TimelineItem, metadata::EventMeta};
/// An `ObservableItems` is a type similar to
/// [`ObservableVector<Arc<TimelineItem>>`] except the API is limited and,
@@ -450,11 +450,7 @@ impl<'observable_items> ObservableItemsTransaction<'observable_items> {
/// Return the index where to insert the first remote timeline
/// item.
pub fn first_remotes_region_index(&self) -> usize {
if self.items.get(0).is_some_and(|item| item.is_timeline_start()) {
1
} else {
0
}
if self.items.get(0).is_some_and(|item| item.is_timeline_start()) { 1 } else { 0 }
}
/// Iterate over all timeline items in the _remotes_ region.
@@ -719,17 +715,18 @@ mod observable_items_tests {
use assert_matches::assert_matches;
use eyeball_im::VectorDiff;
use ruma::{
MilliSecondsSinceUnixEpoch,
events::room::message::{MessageType, TextMessageEventContent},
owned_user_id, uint, MilliSecondsSinceUnixEpoch,
owned_user_id, uint,
};
use stream_assert::{assert_next_matches, assert_pending};
use super::*;
use crate::timeline::{
controller::RemoteEventOrigin,
event_item::{EventTimelineItemKind, LocalEventTimelineItem, RemoteEventTimelineItem},
EventSendState, EventTimelineItem, Message, MsgLikeContent, MsgLikeKind, TimelineDetails,
TimelineItemContent, TimelineItemKind, TimelineUniqueId, VirtualTimelineItem,
controller::RemoteEventOrigin,
event_item::{EventTimelineItemKind, LocalEventTimelineItem, RemoteEventTimelineItem},
};
fn item(event_id: &str) -> Arc<TimelineItem> {
@@ -17,18 +17,18 @@ use std::{cmp::Ordering, collections::HashMap};
use futures_core::Stream;
use indexmap::IndexMap;
use ruma::{
events::receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, UserId,
events::receipt::{Receipt, ReceiptEventContent, ReceiptThread, ReceiptType},
};
use tokio::sync::watch;
use tokio_stream::wrappers::WatchStream;
use tracing::{debug, error, instrument, trace, warn};
use super::{
rfind_event_by_id, AllRemoteEvents, ObservableItemsTransaction, RelativePosition,
RoomDataProvider, TimelineMetadata, TimelineState,
AllRemoteEvents, ObservableItemsTransaction, RelativePosition, RoomDataProvider,
TimelineMetadata, TimelineState, rfind_event_by_id,
};
use crate::timeline::{controller::TimelineStateTransaction, TimelineItem};
use crate::timeline::{TimelineItem, controller::TimelineStateTransaction};
/// In-memory caches for read receipts.
#[derive(Clone, Debug, Default)]
@@ -56,7 +56,9 @@ impl ReadReceipts {
}
/// Subscribe to changes in the read receipts of our own user.
pub(super) fn subscribe_own_user_read_receipts_changed(&self) -> impl Stream<Item = ()> {
pub(super) fn subscribe_own_user_read_receipts_changed(
&self,
) -> impl Stream<Item = ()> + use<> {
let subscriber = self.own_user_read_receipts_changed_sender.subscribe();
WatchStream::from_changes(subscriber)
}
@@ -19,24 +19,24 @@ use matrix_sdk::{deserialized_responses::TimelineEvent, send_queue::SendHandle};
#[cfg(test)]
use ruma::events::receipt::ReceiptEventContent;
use ruma::{
MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, RoomVersionId,
events::{AnyMessageLikeEventContent, AnySyncEphemeralRoomEvent},
serde::Raw,
MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, RoomVersionId,
};
use tracing::{instrument, trace, warn};
use super::{
super::{
Profile, TimelineItem,
date_dividers::DateDividerAdjuster,
event_handler::{
Flow, TimelineAction, TimelineEventContext, TimelineEventHandler, TimelineItemPosition,
},
event_item::RemoteEventOrigin,
traits::RoomDataProvider,
Profile, TimelineItem,
},
observable_items::ObservableItems,
DateDividerMode, TimelineMetadata, TimelineSettings, TimelineStateTransaction,
observable_items::ObservableItems,
};
use crate::{timeline::controller::TimelineFocusKind, unable_to_decrypt_hook::UtdHookManager};
@@ -20,8 +20,8 @@ use matrix_sdk::deserialized_responses::{
ThreadSummaryStatus, TimelineEvent, TimelineEventKind, UnsignedEventLocation,
};
use ruma::{
events::AnySyncTimelineEvent, push::Action, serde::Raw, EventId, MilliSecondsSinceUnixEpoch,
OwnedEventId, OwnedTransactionId, OwnedUserId, UserId,
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, UserId,
events::AnySyncTimelineEvent, push::Action, serde::Raw,
};
use tracing::{debug, instrument, warn};
@@ -33,13 +33,13 @@ use super::{
event_item::RemoteEventOrigin,
traits::RoomDataProvider,
},
metadata::EventMeta,
ObservableItems, ObservableItemsTransaction, TimelineMetadata, TimelineSettings,
metadata::EventMeta,
};
use crate::timeline::{
EmbeddedEvent, ThreadSummary, TimelineDetails, VirtualTimelineItem,
controller::TimelineFocusKind,
event_handler::{FailedToParseEvent, RemovedItem, TimelineAction},
EmbeddedEvent, ThreadSummary, TimelineDetails, VirtualTimelineItem,
};
pub(in crate::timeline) struct TimelineStateTransaction<'a, P: RoomDataProvider> {
@@ -19,11 +19,11 @@ use std::{fmt::Display, sync::Arc};
use chrono::{Datelike, Local, TimeZone};
use ruma::MilliSecondsSinceUnixEpoch;
use tracing::{error, event_enabled, instrument, trace, warn, Level};
use tracing::{Level, error, event_enabled, instrument, trace, warn};
use super::{
controller::{ObservableItemsTransaction, TimelineMetadata},
DateDividerMode, TimelineItem, TimelineItemKind, VirtualTimelineItem,
controller::{ObservableItemsTransaction, TimelineMetadata},
};
#[derive(Debug, PartialEq)]
@@ -511,11 +511,7 @@ impl DateDividerAdjuster {
}
}
if report.errors.is_empty() {
None
} else {
Some(report)
}
if report.errors.is_empty() { None } else { Some(report) }
}
/// Returns whether the two dates for the given timestamps are the same or
@@ -661,15 +657,15 @@ enum DateDividerInsertError {
#[cfg(test)]
mod tests {
use assert_matches2::assert_let;
use ruma::{owned_event_id, owned_user_id, uint, MilliSecondsSinceUnixEpoch};
use ruma::{MilliSecondsSinceUnixEpoch, owned_event_id, owned_user_id, uint};
use super::{super::controller::ObservableItems, DateDividerAdjuster};
use crate::timeline::{
DateDividerMode, EventTimelineItem, MsgLikeContent, TimelineItemContent,
VirtualTimelineItem,
controller::TimelineMetadata,
date_dividers::timestamp_to_date,
event_item::{EventTimelineItemKind, RemoteEventTimelineItem},
DateDividerMode, EventTimelineItem, MsgLikeContent, TimelineItemContent,
VirtualTimelineItem,
};
fn event_with_ts(timestamp: MilliSecondsSinceUnixEpoch) -> EventTimelineItem {
+3 -3
View File
@@ -13,12 +13,12 @@
// limitations under the License.
use matrix_sdk::{
event_cache::EventCacheError, paginators::PaginatorError, room::reply::ReplyError,
send_queue::RoomSendQueueError, HttpError,
HttpError, event_cache::EventCacheError, paginators::PaginatorError, room::reply::ReplyError,
send_queue::RoomSendQueueError,
};
use thiserror::Error;
use crate::timeline::{pinned_events_loader::PinnedEventsLoaderError, TimelineEventItemId};
use crate::timeline::{TimelineEventItemId, pinned_events_loader::PinnedEventsLoaderError};
/// Errors specific to the timeline.
#[derive(Error, Debug)]
@@ -22,7 +22,12 @@ use matrix_sdk::{
send_queue::SendHandle,
};
use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
TransactionId,
events::{
AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncStateEvent,
AnySyncTimelineEvent, EventContent, FullStateEventContent, MessageLikeEventType,
StateEventType, SyncStateEvent,
poll::unstable_start::{
NewUnstablePollStartEventContentWithoutRelation, UnstablePollStartEventContent,
},
@@ -31,20 +36,18 @@ use ruma::{
room::message::{
Relation, RoomMessageEventContent, RoomMessageEventContentWithoutRelation,
},
AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncStateEvent,
AnySyncTimelineEvent, EventContent, FullStateEventContent, MessageLikeEventType,
StateEventType, SyncStateEvent,
},
serde::Raw,
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId,
TransactionId,
};
use tracing::{debug, error, field::debug, instrument, trace, warn};
use super::{
EmbeddedEvent, EncryptedMessage, EventTimelineItem, InReplyToDetails, MsgLikeContent,
MsgLikeKind, OtherState, ReactionStatus, Sticker, ThreadSummary, TimelineDetails, TimelineItem,
TimelineItemContent,
controller::{
find_item_and_apply_aggregation, Aggregation, AggregationKind, ObservableItemsTransaction,
PendingEditKind, TimelineMetadata, TimelineStateTransaction,
Aggregation, AggregationKind, ObservableItemsTransaction, PendingEditKind,
TimelineMetadata, TimelineStateTransaction, find_item_and_apply_aggregation,
},
date_dividers::DateDividerAdjuster,
event_item::{
@@ -53,9 +56,6 @@ use super::{
TimelineEventItemId,
},
traits::RoomDataProvider,
EmbeddedEvent, EncryptedMessage, EventTimelineItem, InReplyToDetails, MsgLikeContent,
MsgLikeKind, OtherState, ReactionStatus, Sticker, ThreadSummary, TimelineDetails, TimelineItem,
TimelineItemContent,
};
use crate::timeline::controller::aggregations::PendingEdit;
@@ -17,7 +17,9 @@
use std::fmt;
use ruma::{
OwnedEventId,
events::{
AnySyncMessageLikeEvent, AnySyncTimelineEvent, BundledMessageLikeRelations, Mentions,
poll::unstable_start::{
NewUnstablePollStartEventContentWithoutRelation, SyncUnstablePollStartEvent,
UnstablePollStartEventContent,
@@ -25,11 +27,9 @@ use ruma::{
room::message::{
MessageType, Relation, RoomMessageEventContentWithoutRelation, SyncRoomMessageEvent,
},
AnySyncMessageLikeEvent, AnySyncTimelineEvent, BundledMessageLikeRelations, Mentions,
},
html::RemoveReplyFallback,
serde::Raw,
OwnedEventId,
};
use tracing::{error, trace};
@@ -16,9 +16,12 @@ use std::sync::Arc;
use as_variant::as_variant;
use matrix_sdk::crypto::types::events::UtdCause;
use matrix_sdk_base::latest_event::{is_suitable_for_latest_event, PossibleLatestEvent};
use matrix_sdk_base::latest_event::{PossibleLatestEvent, is_suitable_for_latest_event};
use ruma::{
OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedUserId, RoomVersionId, UserId,
events::{
AnyFullStateEventContent, AnySyncTimelineEvent, FullStateEventContent, Mentions,
MessageLikeEventType, StateEventType,
call::{invite::SyncCallInviteEvent, notify::SyncCallNotifyEvent},
policy::rule::{
room::PolicyRuleRoomEventContent, server::PolicyRuleServerEventContent,
@@ -50,11 +53,8 @@ use ruma::{
},
space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
sticker::{StickerEventContent, SyncStickerEvent},
AnyFullStateEventContent, AnySyncTimelineEvent, FullStateEventContent, Mentions,
MessageLikeEventType, StateEventType,
},
html::RemoveReplyFallback,
OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedUserId, RoomVersionId, UserId,
};
use tracing::warn;
@@ -1052,12 +1052,11 @@ mod tests {
use assert_matches2::assert_let;
use matrix_sdk_test::ALICE;
use ruma::{
assign,
RoomVersionId, assign,
events::{
room::member::{MembershipState, RoomMemberEventContent},
FullStateEventContent,
room::member::{MembershipState, RoomMemberEventContent},
},
RoomVersionId,
};
use super::{MembershipChange, RoomMembershipChange, TimelineItemContent};
@@ -15,8 +15,8 @@
use std::collections::HashSet;
use ruma::{
events::{room::pinned_events::RoomPinnedEventsEventContent, FullStateEventContent},
OwnedEventId,
events::{FullStateEventContent, room::pinned_events::RoomPinnedEventsEventContent},
};
#[derive(Clone, Debug)]
@@ -78,11 +78,11 @@ mod tests {
use assert_matches::assert_matches;
use ruma::{
events::{
FullStateEventContent,
room::pinned_events::{
PossiblyRedactedRoomPinnedEventsEventContent, RedactedRoomPinnedEventsEventContent,
RoomPinnedEventsEventContent,
},
FullStateEventContent,
},
owned_event_id,
serde::Raw,
@@ -17,16 +17,15 @@
use std::collections::HashMap;
use ruma::{
MilliSecondsSinceUnixEpoch, OwnedUserId, UserId,
events::poll::{
compile_unstable_poll_results,
PollResponseData, compile_unstable_poll_results,
start::PollKind,
unstable_start::{
NewUnstablePollStartEventContent, NewUnstablePollStartEventContentWithoutRelation,
UnstablePollStartContentBlock,
},
PollResponseData,
},
MilliSecondsSinceUnixEpoch, OwnedUserId, UserId,
};
/// Holds the state of a poll.
@@ -21,11 +21,11 @@ use tracing::{debug, instrument, warn};
use super::TimelineItemContent;
use crate::timeline::{
Error as TimelineError, TimelineEventItemId, TimelineItem,
controller::TimelineMetadata,
event_handler::TimelineAction,
event_item::{EventTimelineItem, Profile, TimelineDetails},
traits::RoomDataProvider,
Error as TimelineError, TimelineEventItemId, TimelineItem,
};
/// Details about an event being replied to.
@@ -15,7 +15,7 @@
use std::sync::Arc;
use as_variant::as_variant;
use matrix_sdk::{send_queue::SendHandle, Error};
use matrix_sdk::{Error, send_queue::SendHandle};
use ruma::{EventId, OwnedEventId, OwnedTransactionId};
use super::TimelineEventItemId;
@@ -20,20 +20,20 @@ use std::{
use as_variant::as_variant;
use indexmap::IndexMap;
use matrix_sdk::{
Client, Error,
deserialized_responses::{EncryptionInfo, ShieldState},
send_queue::{SendHandle, SendReactionHandle},
Client, Error,
};
use matrix_sdk_base::{
deserialized_responses::{ShieldStateCode, SENT_IN_CLEAR},
deserialized_responses::{SENT_IN_CLEAR, ShieldStateCode},
latest_event::LatestEvent,
};
use once_cell::sync::Lazy;
use ruma::{
events::{receipt::Receipt, room::message::MessageType, AnySyncTimelineEvent},
serde::Raw,
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedMxcUri, OwnedTransactionId,
OwnedUserId, RoomId, RoomVersionId, TransactionId, UserId,
events::{AnySyncTimelineEvent, receipt::Receipt, room::message::MessageType},
serde::Raw,
};
use tracing::warn;
use unicode_segmentation::UnicodeSegmentation;
@@ -42,13 +42,6 @@ mod content;
mod local;
mod remote;
pub(super) use self::{
content::{
extract_bundled_edit_event_json, extract_poll_edit_content, extract_room_msg_edit_content,
},
local::LocalEventTimelineItem,
remote::{RemoteEventOrigin, RemoteEventTimelineItem},
};
pub use self::{
content::{
AnyOtherFullStateEventContent, EmbeddedEvent, EncryptedMessage, InReplyToDetails,
@@ -58,6 +51,13 @@ pub use self::{
},
local::EventSendState,
};
pub(super) use self::{
content::{
extract_bundled_edit_event_json, extract_poll_edit_content, extract_room_msg_edit_content,
},
local::LocalEventTimelineItem,
remote::{RemoteEventOrigin, RemoteEventTimelineItem},
};
/// An item in the timeline that represents at least one event.
///
@@ -792,23 +792,24 @@ mod tests {
use assert_matches2::assert_let;
use matrix_sdk::test_utils::logged_in_client;
use matrix_sdk_base::{
deserialized_responses::TimelineEvent, latest_event::LatestEvent, MinimalStateEvent,
OriginalMinimalStateEvent, RequestedRequiredStates,
MinimalStateEvent, OriginalMinimalStateEvent, RequestedRequiredStates,
deserialized_responses::TimelineEvent, latest_event::LatestEvent,
};
use matrix_sdk_test::{async_test, event_factory::EventFactory, sync_state_event};
use ruma::{
RoomId, UInt, UserId,
api::client::sync::sync_events::v5 as http,
event_id,
events::{
AnySyncStateEvent,
room::{
member::RoomMemberEventContent,
message::{MessageFormat, MessageType},
},
AnySyncStateEvent,
},
room_id,
serde::Raw,
user_id, RoomId, UInt, UserId,
user_id,
};
use super::{EventTimelineItem, Profile};
@@ -1006,8 +1007,8 @@ mod tests {
}
#[async_test]
async fn test_latest_message_event_can_be_wrapped_as_a_timeline_item_with_sender_from_the_storage(
) {
async fn test_latest_message_event_can_be_wrapped_as_a_timeline_item_with_sender_from_the_storage()
{
// Given a sync event that is suitable to be used as a latest_event, and a room
// with a member event for the sender
@@ -1055,8 +1056,8 @@ mod tests {
}
#[async_test]
async fn test_latest_message_event_can_be_wrapped_as_a_timeline_item_with_sender_from_the_cache(
) {
async fn test_latest_message_event_can_be_wrapped_as_a_timeline_item_with_sender_from_the_cache()
{
// Given a sync event that is suitable to be used as a latest_event, a room, and
// a member event for the sender (which isn't part of the room yet).
@@ -17,9 +17,9 @@ use std::{fmt, sync::Arc};
use indexmap::IndexMap;
use matrix_sdk::deserialized_responses::EncryptionInfo;
use ruma::{
events::{receipt::Receipt, AnySyncTimelineEvent},
serde::Raw,
OwnedEventId, OwnedTransactionId, OwnedUserId,
events::{AnySyncTimelineEvent, receipt::Receipt},
serde::Raw,
};
/// An item for an event that was received from the homeserver.
+1 -1
View File
@@ -1,7 +1,7 @@
use std::future::IntoFuture;
use eyeball::SharedObservable;
use matrix_sdk::{attachment::AttachmentConfig, TransmissionProgress};
use matrix_sdk::{TransmissionProgress, attachment::AttachmentConfig};
use matrix_sdk_base::boxed_into_future;
use mime::Mime;
use tracing::{Instrument as _, Span};
+8 -7
View File
@@ -28,33 +28,33 @@ use imbl::Vector;
#[cfg(feature = "unstable-msc4274")]
use matrix_sdk::attachment::{AttachmentInfo, Thumbnail};
use matrix_sdk::{
Result,
attachment::AttachmentConfig,
deserialized_responses::TimelineEvent,
event_cache::{EventCacheDropHandles, RoomEventCache},
executor::JoinHandle,
room::{edit::EditedContent, reply::Reply, Receipts, Room},
room::{Receipts, Room, edit::EditedContent, reply::Reply},
send_queue::{RoomSendQueueError, SendHandle},
Result,
};
use mime::Mime;
use pinned_events_loader::PinnedEventsRoom;
use ruma::{
EventId, OwnedEventId, RoomVersionId, UserId,
api::client::receipt::create_receipt::v3::ReceiptType,
events::{
AnyMessageLikeEventContent, AnySyncTimelineEvent,
poll::unstable_start::{NewUnstablePollStartEventContent, UnstablePollStartEventContent},
receipt::{Receipt, ReceiptThread},
room::{
message::RoomMessageEventContentWithoutRelation,
pinned_events::RoomPinnedEventsEventContent,
},
AnyMessageLikeEventContent, AnySyncTimelineEvent,
},
EventId, OwnedEventId, RoomVersionId, UserId,
};
#[cfg(feature = "unstable-msc4274")]
use ruma::{
events::{room::message::FormattedBody, Mentions},
OwnedTransactionId,
events::{Mentions, room::message::FormattedBody},
};
use subscriber::TimelineWithDropHandle;
use thiserror::Error;
@@ -251,7 +251,8 @@ impl Timeline {
/// and batches them.
pub async fn subscribe(
&self,
) -> (Vector<Arc<TimelineItem>>, impl Stream<Item = Vec<VectorDiff<Arc<TimelineItem>>>>) {
) -> (Vector<Arc<TimelineItem>>, impl Stream<Item = Vec<VectorDiff<Arc<TimelineItem>>>> + use<>)
{
let (items, stream) = self.controller.subscribe().await;
let stream = TimelineWithDropHandle::new(stream, self.drop_handle.clone());
(items, stream)
@@ -560,7 +561,7 @@ impl Timeline {
}
/// Subscribe to changes in the read receipts of our own user.
pub async fn subscribe_own_user_read_receipts_changed(&self) -> impl Stream<Item = ()> {
pub async fn subscribe_own_user_read_receipts_changed(&self) -> impl Stream<Item = ()> + use<> {
self.controller.subscribe_own_user_read_receipts_changed().await
}
@@ -15,7 +15,7 @@
use async_rx::StreamExt as _;
use async_stream::stream;
use futures_core::Stream;
use futures_util::{pin_mut, StreamExt as _};
use futures_util::{StreamExt as _, pin_mut};
use matrix_sdk::event_cache::{self, EventCacheError, RoomPaginationStatus};
use tracing::{instrument, warn};
@@ -107,7 +107,7 @@ impl super::Timeline {
/// call to [`Self::paginate_backwards()`].
pub async fn live_back_pagination_status(
&self,
) -> Option<(RoomPaginationStatus, impl Stream<Item = RoomPaginationStatus>)> {
) -> Option<(RoomPaginationStatus, impl Stream<Item = RoomPaginationStatus> + use<>)> {
if !self.controller.is_live() {
return None;
}
@@ -14,10 +14,10 @@
use std::{fmt::Formatter, sync::Arc};
use futures_util::{stream, StreamExt};
use matrix_sdk::{config::RequestConfig, BoxFuture, Room, SendOutsideWasm, SyncOutsideWasm};
use futures_util::{StreamExt, stream};
use matrix_sdk::{BoxFuture, Room, SendOutsideWasm, SyncOutsideWasm, config::RequestConfig};
use matrix_sdk_base::deserialized_responses::TimelineEvent;
use ruma::{events::relation::RelationType, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId};
use ruma::{EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, events::relation::RelationType};
use thiserror::Error;
use tokio::sync::Mutex;
use tracing::{debug, warn};
@@ -25,7 +25,7 @@ use futures_core::Stream;
use imbl::Vector;
use pin_project_lite::pin_project;
use super::{controller::ObservableItems, item::TimelineItem, TimelineDropHandle};
use super::{TimelineDropHandle, controller::ObservableItems, item::TimelineItem};
pin_project! {
/// A stream that wraps a [`TimelineDropHandle`] so that the `Timeline`
@@ -225,8 +225,8 @@ pub mod skip {
///
/// [`Skip`]: eyeball_im_util::vector::Skip
#[allow(unused)] // this is not used yet because only a live timeline is using it, but as soon as
// other kind of timelines will use it, we would need it, it's better to have
// this in case of; everything is tested, the logic is made more robust.
// other kind of timelines will use it, we would need it, it's better to have
// this in case of; everything is tested, the logic is made more robust.
pub fn compute_next_when_paginating_forwards(&self, _page_size: usize) -> usize {
// Nothing to do, the count remains unchanged as we skip the first values, not
// the last values; paginating forwards will add items at the end, not at the
+2 -2
View File
@@ -26,11 +26,11 @@ use matrix_sdk::{
send_queue::RoomSendQueueUpdate,
};
use ruma::OwnedEventId;
use tokio::sync::broadcast::{error::RecvError, Receiver};
use tokio::sync::broadcast::{Receiver, error::RecvError};
use tokio_stream::StreamExt as _;
use tracing::{instrument, trace, warn};
use crate::timeline::{event_item::RemoteEventOrigin, TimelineController, TimelineFocus};
use crate::timeline::{TimelineController, TimelineFocus, event_item::RemoteEventOrigin};
/// Long-lived task, in the pinned events focus mode, that updates the timeline
/// after any changes in the pinned events.
@@ -18,33 +18,32 @@ use eyeball_im::VectorDiff;
use futures_util::StreamExt;
use imbl::vector;
use matrix_sdk_test::{
async_test,
ALICE, BOB, CAROL, async_test,
event_factory::{EventFactory, PreviousMembership},
ALICE, BOB, CAROL,
};
use ruma::{
event_id,
MilliSecondsSinceUnixEpoch, event_id,
events::{
FullStateEventContent,
receipt::{Receipt, ReceiptThread, ReceiptType},
room::{
ImageInfo,
member::{MembershipState, RedactedRoomMemberEventContent},
message::MessageType,
topic::RedactedRoomTopicEventContent,
ImageInfo,
},
FullStateEventContent,
},
mxc_uri, owned_event_id, owned_mxc_uri, user_id, MilliSecondsSinceUnixEpoch,
mxc_uri, owned_event_id, owned_mxc_uri, user_id,
};
use stream_assert::assert_next_matches;
use super::TestTimeline;
use crate::timeline::{
MembershipChange, MsgLikeContent, MsgLikeKind, TimelineDetails, TimelineItemContent,
TimelineItemKind, VirtualTimelineItem,
controller::TimelineSettings,
event_item::{AnyOtherFullStateEventContent, RemoteEventOrigin},
tests::{ReadReceiptMap, TestRoomDataProvider, TestTimelineBuilder},
MembershipChange, MsgLikeContent, MsgLikeKind, TimelineDetails, TimelineItemContent,
TimelineItemKind, VirtualTimelineItem,
};
#[async_test]
@@ -18,11 +18,11 @@ use assert_matches::assert_matches;
use eyeball_im::VectorDiff;
use matrix_sdk::{assert_next_matches_with_timeout, send_queue::RoomSendQueueUpdate};
use matrix_sdk_base::store::QueueWedgeError;
use matrix_sdk_test::{async_test, ALICE, BOB};
use matrix_sdk_test::{ALICE, BOB, async_test};
use ruma::{
event_id,
events::{room::message::RoomMessageEventContent, AnyMessageLikeEventContent},
user_id, MilliSecondsSinceUnixEpoch,
MilliSecondsSinceUnixEpoch, event_id,
events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
user_id,
};
use stream_assert::{assert_next_matches, assert_pending};
@@ -20,7 +20,7 @@ use matrix_sdk::deserialized_responses::{
AlgorithmInfo, EncryptionInfo, VerificationLevel, VerificationState,
};
use matrix_sdk_base::deserialized_responses::{DecryptedRoomEvent, TimelineEvent};
use matrix_sdk_test::{async_test, ALICE, BOB};
use matrix_sdk_test::{ALICE, BOB, async_test};
use ruma::{
event_id,
events::room::message::{MessageType, RedactedRoomMessageEventContent},
@@ -27,14 +27,14 @@ use assert_matches2::assert_let;
use eyeball_im::VectorDiff;
use matrix_sdk::{
assert_next_matches_with_timeout,
crypto::{decrypt_room_key_export, types::events::UtdCause, OlmMachine},
crypto::{OlmMachine, decrypt_room_key_export, types::events::UtdCause},
deserialized_responses::{
AlgorithmInfo, DecryptedRoomEvent, EncryptionInfo, VerificationLevel, VerificationState,
},
test_utils::test_client_builder,
};
use matrix_sdk_base::deserialized_responses::{TimelineEvent, UnableToDecryptReason};
use matrix_sdk_test::{async_test, ALICE, BOB};
use matrix_sdk_test::{ALICE, BOB, async_test};
use ruma::{
assign, event_id,
events::room::encrypted::{
@@ -52,8 +52,8 @@ use tokio::time::sleep;
use super::TestTimeline;
use crate::{
timeline::{
tests::{TestRoomDataProvider, TestTimelineBuilder},
EncryptedMessage, MsgLikeContent, MsgLikeKind, TimelineDetails, TimelineItemContent,
tests::{TestRoomDataProvider, TestTimelineBuilder},
},
unable_to_decrypt_hook::{UnableToDecryptHook, UnableToDecryptInfo, UtdHookManager},
};
@@ -17,21 +17,21 @@ use std::sync::Arc;
use assert_matches2::assert_let;
use eyeball_im::VectorDiff;
use matrix_sdk::deserialized_responses::TimelineEvent;
use matrix_sdk_test::{async_test, sync_timeline_event, ALICE, BOB};
use matrix_sdk_test::{ALICE, BOB, async_test, sync_timeline_event};
use ruma::events::{
AnySyncTimelineEvent, TimelineEventType,
room::{
member::MembershipState,
message::{MessageType, RedactedRoomMessageEventContent},
},
AnySyncTimelineEvent, TimelineEventType,
};
use stream_assert::assert_next_matches;
use super::TestTimeline;
use crate::timeline::{
controller::TimelineSettings, tests::TestTimelineBuilder, AnyOtherFullStateEventContent,
MsgLikeContent, MsgLikeKind, TimelineEventTypeFilter, TimelineItem, TimelineItemContent,
TimelineItemKind,
AnyOtherFullStateEventContent, MsgLikeContent, MsgLikeKind, TimelineEventTypeFilter,
TimelineItem, TimelineItemContent, TimelineItemKind, controller::TimelineSettings,
tests::TestTimelineBuilder,
};
#[async_test]
@@ -15,10 +15,11 @@
use assert_matches2::assert_let;
use eyeball_im::VectorDiff;
use matrix_sdk::deserialized_responses::TimelineEvent;
use matrix_sdk_test::{async_test, sync_timeline_event, ALICE, BOB};
use matrix_sdk_test::{ALICE, BOB, async_test, sync_timeline_event};
use ruma::{
events::{room::message::MessageType, MessageLikeEventType, StateEventType},
uint, MilliSecondsSinceUnixEpoch,
MilliSecondsSinceUnixEpoch,
events::{MessageLikeEventType, StateEventType, room::message::MessageType},
uint,
};
use stream_assert::assert_next_matches;
+11 -10
View File
@@ -27,39 +27,40 @@ use futures_core::Stream;
use imbl::vector;
use indexmap::IndexMap;
use matrix_sdk::{
BoxFuture,
config::RequestConfig,
crypto::OlmMachine,
deserialized_responses::{EncryptionInfo, TimelineEvent},
paginators::{thread::PaginableThread, PaginableRoom, PaginatorError},
paginators::{PaginableRoom, PaginatorError, thread::PaginableThread},
room::{EventWithContextResponse, Messages, MessagesOptions, PushContext, Relations},
send_queue::RoomSendQueueUpdate,
BoxFuture,
};
use matrix_sdk_base::{
crypto::types::events::CryptoContextInfo, latest_event::LatestEvent, RoomInfo, RoomState,
RoomInfo, RoomState, crypto::types::events::CryptoContextInfo, latest_event::LatestEvent,
};
use matrix_sdk_test::{event_factory::EventFactory, ALICE, DEFAULT_TEST_ROOM_ID};
use matrix_sdk_test::{ALICE, DEFAULT_TEST_ROOM_ID, event_factory::EventFactory};
use ruma::{
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedTransactionId,
OwnedUserId, RoomVersionId, TransactionId, UInt, UserId,
events::{
AnyMessageLikeEventContent, AnyTimelineEvent,
reaction::ReactionEventContent,
receipt::{Receipt, ReceiptThread, ReceiptType},
relation::{Annotation, RelationType},
AnyMessageLikeEventContent, AnyTimelineEvent,
},
int,
power_levels::NotificationPowerLevels,
push::{PushConditionPowerLevelsCtx, PushConditionRoomCtx, Ruleset},
room_id,
serde::Raw,
uint, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedTransactionId,
OwnedUserId, RoomVersionId, TransactionId, UInt, UserId,
uint,
};
use tokio::sync::RwLock;
use super::{
algorithms::rfind_event_by_item_id, controller::TimelineSettings,
event_item::RemoteEventOrigin, traits::RoomDataProvider, EventTimelineItem, Profile,
TimelineController, TimelineEventItemId, TimelineFocus, TimelineItem,
EventTimelineItem, Profile, TimelineController, TimelineEventItemId, TimelineFocus,
TimelineItem, algorithms::rfind_event_by_item_id, controller::TimelineSettings,
event_item::RemoteEventOrigin, traits::RoomDataProvider,
};
use crate::{
timeline::pinned_events_loader::PinnedEventsRoom, unable_to_decrypt_hook::UtdHookManager,
@@ -1,16 +1,16 @@
use assert_matches2::assert_let;
use fakes::poll_a2;
use matrix_sdk_test::{async_test, ALICE, BOB};
use matrix_sdk_test::{ALICE, BOB, async_test};
use ruma::{
event_id,
EventId, OwnedEventId, UserId, event_id,
events::poll::unstable_start::{
NewUnstablePollStartEventContent, ReplacementUnstablePollStartEventContent,
UnstablePollStartContentBlock, UnstablePollStartEventContent,
},
server_name, EventId, OwnedEventId, UserId,
server_name,
};
use crate::timeline::{event_item::PollState, tests::TestTimeline, EventTimelineItem};
use crate::timeline::{EventTimelineItem, event_item::PollState, tests::TestTimeline};
#[async_test]
async fn test_poll_is_displayed() {
@@ -20,17 +20,17 @@ use futures_core::Stream;
use futures_util::{FutureExt as _, StreamExt as _};
use imbl::vector;
use matrix_sdk::assert_next_matches_with_timeout;
use matrix_sdk_test::{async_test, ALICE, BOB};
use matrix_sdk_test::{ALICE, BOB, async_test};
use ruma::{
event_id, events::AnyMessageLikeEventContent, server_name, uint, EventId,
MilliSecondsSinceUnixEpoch, OwnedEventId,
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, event_id,
events::AnyMessageLikeEventContent, server_name, uint,
};
use stream_assert::{assert_next_matches, assert_pending};
use tokio::time::timeout;
use crate::timeline::{
event_item::RemoteEventOrigin, tests::TestTimeline, ReactionStatus, TimelineEventItemId,
TimelineItem,
ReactionStatus, TimelineEventItemId, TimelineItem, event_item::RemoteEventOrigin,
tests::TestTimeline,
};
const REACTION_KEY: &str = "👍";
@@ -144,13 +144,15 @@ async fn test_redact_reaction_success() {
// Will immediately redact it on the item.
let event = assert_item_update!(stream, &event_id, item_pos);
assert!(event
.content()
.reactions()
.cloned()
.unwrap_or_default()
.get(&REACTION_KEY.to_owned())
.is_none());
assert!(
event
.content()
.reactions()
.cloned()
.unwrap_or_default()
.get(&REACTION_KEY.to_owned())
.is_none()
);
// And send a redaction request for that reaction.
{
@@ -16,22 +16,22 @@ use std::sync::Arc;
use eyeball_im::VectorDiff;
use matrix_sdk::assert_next_matches_with_timeout;
use matrix_sdk_test::{async_test, event_factory::EventFactory, ALICE, BOB, CAROL};
use matrix_sdk_test::{ALICE, BOB, CAROL, async_test, event_factory::EventFactory};
use ruma::{
event_id,
RoomVersionId, event_id,
events::{
AnySyncMessageLikeEvent, AnySyncTimelineEvent,
receipt::{Receipt, ReceiptThread, ReceiptType},
room::message::{MessageType, RoomMessageEventContent, SyncRoomMessageEvent},
AnySyncMessageLikeEvent, AnySyncTimelineEvent,
},
owned_event_id, room_id, uint, RoomVersionId,
owned_event_id, room_id, uint,
};
use stream_assert::{assert_next_matches, assert_pending};
use super::{ReadReceiptMap, TestRoomDataProvider};
use crate::timeline::{
controller::TimelineSettings, tests::TestTimelineBuilder, MsgLikeContent, MsgLikeKind,
TimelineFocus,
MsgLikeContent, MsgLikeKind, TimelineFocus, controller::TimelineSettings,
tests::TestTimelineBuilder,
};
fn filter_notice(ev: &AnySyncTimelineEvent, _room_version: &RoomVersionId) -> bool {
@@ -376,12 +376,13 @@ async fn test_read_receipts_updates_on_message_decryption() {
use std::{io::Cursor, iter};
use assert_matches2::assert_let;
use matrix_sdk_base::crypto::{decrypt_room_key_export, OlmMachine};
use matrix_sdk_base::crypto::{OlmMachine, decrypt_room_key_export};
use ruma::{
RoomVersionId,
events::room::encrypted::{
EncryptedEventScheme, MegolmV1AesSha2ContentInit, RoomEncryptedEventContent,
},
user_id, RoomVersionId,
user_id,
};
use crate::timeline::{EncryptedMessage, TimelineItemContent};
@@ -16,20 +16,20 @@ use assert_matches::assert_matches;
use assert_matches2::assert_let;
use eyeball_im::VectorDiff;
use imbl::vector;
use matrix_sdk_test::{async_test, ALICE, BOB};
use matrix_sdk_test::{ALICE, BOB, async_test};
use ruma::{
event_id,
events::{
reaction::RedactedReactionEventContent, room::message::OriginalSyncRoomMessageEvent,
FullStateEventContent,
FullStateEventContent, reaction::RedactedReactionEventContent,
room::message::OriginalSyncRoomMessageEvent,
},
};
use stream_assert::{assert_next_matches, assert_pending};
use super::TestTimeline;
use crate::timeline::{
event_item::RemoteEventOrigin, AnyOtherFullStateEventContent, TimelineDetails,
TimelineItemContent,
AnyOtherFullStateEventContent, TimelineDetails, TimelineItemContent,
event_item::RemoteEventOrigin,
};
#[async_test]
@@ -171,9 +171,9 @@ async fn test_reaction_redaction_timeline_filter() {
.controller
.handle_remote_events_with_diffs(
vec![VectorDiff::Append {
values: vector![f
.redacted(*ALICE, RedactedReactionEventContent::new())
.into_event()],
values: vector![
f.redacted(*ALICE, RedactedReactionEventContent::new()).into_event()
],
}],
RemoteEventOrigin::Sync,
)
@@ -1,24 +1,24 @@
use assert_matches::assert_matches;
use eyeball_im::VectorDiff;
use matrix_sdk_base::deserialized_responses::{ShieldState, ShieldStateCode};
use matrix_sdk_test::{async_test, event_factory::EventFactory, ALICE};
use matrix_sdk_test::{ALICE, async_test, event_factory::EventFactory};
use ruma::{
event_id,
events::{
AnyMessageLikeEventContent,
room::{
encrypted::{
EncryptedEventScheme, MegolmV1AesSha2ContentInit, RoomEncryptedEventContent,
},
message::RoomMessageEventContent,
},
AnyMessageLikeEventContent,
},
};
use stream_assert::{assert_next_matches, assert_pending};
use crate::timeline::{
tests::{TestTimeline, TestTimelineBuilder},
EventSendState,
tests::{TestTimeline, TestTimelineBuilder},
};
#[async_test]
@@ -17,15 +17,15 @@ use assert_matches2::assert_let;
use chrono::{Datelike, TimeZone, Utc};
use eyeball_im::VectorDiff;
use futures_util::{FutureExt, StreamExt as _};
use matrix_sdk_test::{async_test, ALICE, BOB};
use matrix_sdk_test::{ALICE, BOB, async_test};
use ruma::{
event_id,
events::{room::message::RoomMessageEventContent, AnyMessageLikeEventContent},
events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
};
use stream_assert::assert_next_matches;
use super::TestTimeline;
use crate::timeline::{traits::RoomDataProvider as _, VirtualTimelineItem};
use crate::timeline::{VirtualTimelineItem, traits::RoomDataProvider as _};
#[async_test]
async fn test_date_divider() {
@@ -16,10 +16,10 @@ use std::iter;
use matrix_sdk::event_handler::EventHandler;
use ruma::{
events::{forwarded_room_key::ToDeviceForwardedRoomKeyEvent, room_key::ToDeviceRoomKeyEvent},
OwnedRoomId,
events::{forwarded_room_key::ToDeviceForwardedRoomKeyEvent, room_key::ToDeviceRoomKeyEvent},
};
use tracing::{debug_span, trace, Instrument};
use tracing::{Instrument, debug_span, trace};
use super::controller::TimelineController;
+8 -8
View File
@@ -19,26 +19,26 @@ use indexmap::IndexMap;
#[cfg(test)]
use matrix_sdk::crypto::{DecryptionSettings, RoomEventDecryptionResult, TrustRequirement};
use matrix_sdk::{
AsyncTraitDeps, Result, Room, SendOutsideWasm,
crypto::types::events::CryptoContextInfo,
deserialized_responses::{EncryptionInfo, TimelineEvent},
paginators::{thread::PaginableThread, PaginableRoom},
paginators::{PaginableRoom, thread::PaginableThread},
room::PushContext,
AsyncTraitDeps, Result, Room, SendOutsideWasm,
};
use matrix_sdk_base::{latest_event::LatestEvent, RoomInfo};
use matrix_sdk_base::{RoomInfo, latest_event::LatestEvent};
use ruma::{
EventId, OwnedEventId, OwnedTransactionId, OwnedUserId, RoomVersionId, UserId,
events::{
AnyMessageLikeEventContent, AnySyncTimelineEvent,
fully_read::FullyReadEventContent,
receipt::{Receipt, ReceiptThread, ReceiptType},
AnyMessageLikeEventContent, AnySyncTimelineEvent,
},
serde::Raw,
EventId, OwnedEventId, OwnedTransactionId, OwnedUserId, RoomVersionId, UserId,
};
use tracing::error;
use super::{EventTimelineItem, Profile, RedactError, TimelineBuilder};
use crate::timeline::{self, pinned_events_loader::PinnedEventsRoom, Timeline};
use crate::timeline::{self, Timeline, pinned_events_loader::PinnedEventsRoom};
pub trait RoomExt {
/// Get a [`Timeline`] for this room.
@@ -49,7 +49,7 @@ pub trait RoomExt {
///
/// This is the same as using `room.timeline_builder().build()`.
fn timeline(&self)
-> impl Future<Output = Result<Timeline, timeline::Error>> + SendOutsideWasm;
-> impl Future<Output = Result<Timeline, timeline::Error>> + SendOutsideWasm;
/// Get a [`TimelineBuilder`] for this room.
///
@@ -93,7 +93,7 @@ pub(super) trait RoomDataProvider:
fn room_version(&self) -> RoomVersionId;
fn crypto_context_info(&self)
-> impl Future<Output = CryptoContextInfo> + SendOutsideWasm + '_;
-> impl Future<Output = CryptoContextInfo> + SendOutsideWasm + '_;
fn profile_from_user_id<'a>(
&'a self,
@@ -25,17 +25,17 @@ use std::{
use growable_bloom_filter::{GrowableBloom, GrowableBloomBuilder};
use matrix_sdk::{
crypto::types::events::UtdCause,
executor::{spawn, JoinHandle},
sleep::sleep,
Client,
crypto::types::events::UtdCause,
executor::{JoinHandle, spawn},
sleep::sleep,
};
use matrix_sdk_base::{
SendOutsideWasm, StateStoreDataKey, StateStoreDataValue, StoreError, SyncOutsideWasm,
};
use ruma::{
time::{Duration, Instant},
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedServerName, UserId,
time::{Duration, Instant},
};
use tokio::sync::{Mutex as AsyncMutex, MutexGuard};
use tracing::{error, trace};
@@ -1,12 +1,12 @@
use std::{
collections::{BTreeMap, HashSet},
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
atomic::{AtomicBool, Ordering},
},
};
use futures_util::{pin_mut, StreamExt as _};
use futures_util::{StreamExt as _, pin_mut};
use matrix_sdk::{
config::RequestConfig,
test_utils::{
@@ -23,13 +23,13 @@ use serde_json::json;
use tokio::sync::Mutex as AsyncMutex;
use tracing::{error, info, trace, warn};
use wiremock::{
matchers::{method, path},
Mock, MockGuard, MockServer, Request, ResponseTemplate,
matchers::{method, path},
};
use crate::{
mock_sync,
sliding_sync::{check_requests, PartialSlidingSyncRequest, SlidingSyncMatcher},
sliding_sync::{PartialSlidingSyncRequest, SlidingSyncMatcher, check_requests},
sliding_sync_then_assert_request_and_fake_response,
};
@@ -14,12 +14,12 @@
use itertools::Itertools as _;
use matrix_sdk::deserialized_responses::TimelineEvent;
use ruma::{events::AnyStateEvent, serde::Raw, EventId, RoomId};
use ruma::{EventId, RoomId, events::AnyStateEvent, serde::Raw};
use serde::Serialize;
use serde_json::json;
use wiremock::{
matchers::{header, method, path, path_regex, query_param, query_param_is_missing},
Mock, MockServer, ResponseTemplate,
matchers::{header, method, path, path_regex, query_param, query_param_is_missing},
};
mod encryption_sync_service;
@@ -11,8 +11,8 @@ use matrix_sdk::{
test_utils::{logged_in_client_with_server, mocks::MatrixMockServer},
};
use matrix_sdk_test::{
async_test, event_factory::EventFactory, mocks::mock_encryption_state, JoinedRoomBuilder,
SyncResponseBuilder,
JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
mocks::mock_encryption_state,
};
use matrix_sdk_ui::{
notification_client::{
@@ -23,18 +23,18 @@ use matrix_sdk_ui::{
};
use ruma::{
event_id,
events::{room::member::MembershipState, AnyStateEvent, TimelineEventType},
events::{AnyStateEvent, TimelineEventType, room::member::MembershipState},
mxc_uri, room_id, user_id,
};
use serde_json::json;
use wiremock::{
matchers::{header, method, path},
Mock, Request, ResponseTemplate,
matchers::{header, method, path},
};
use crate::{
mock_sync,
sliding_sync::{check_requests, PartialSlidingSyncRequest, SlidingSyncMatcher},
sliding_sync::{PartialSlidingSyncRequest, SlidingSyncMatcher, check_requests},
};
#[async_test]
@@ -2,27 +2,27 @@ use std::{ops::Not, sync::Arc};
use assert_matches::assert_matches;
use eyeball_im::VectorDiff;
use futures_util::{pin_mut, FutureExt, StreamExt};
use futures_util::{FutureExt, StreamExt, pin_mut};
use matrix_sdk::{
Client, RoomDisplayName,
config::RequestConfig,
test_utils::{
logged_in_client_with_server,
mocks::{MatrixMockServer, RoomMessagesResponseTemplate},
set_client_session, test_client_builder,
},
Client, RoomDisplayName,
};
use matrix_sdk_base::sync::UnreadNotificationsCount;
use matrix_sdk_test::{
async_test, event_factory::EventFactory, mocks::mock_encryption_state, ALICE,
ALICE, async_test, event_factory::EventFactory, mocks::mock_encryption_state,
};
use matrix_sdk_ui::{
RoomListService,
room_list_service::{
ALL_ROOMS_LIST_NAME as ALL_ROOMS, Error, RoomListLoadingState, State, SyncIndicator,
filters::{new_filter_fuzzy_match_room_name, new_filter_non_left, new_filter_none},
Error, RoomListLoadingState, State, SyncIndicator, ALL_ROOMS_LIST_NAME as ALL_ROOMS,
},
timeline::{RoomExt as _, TimelineItemKind, VirtualTimelineItem},
RoomListService,
};
use ruma::{
api::client::room::create_room::v3::Request as CreateRoomRequest,
@@ -36,8 +36,8 @@ use stream_assert::{assert_next_matches, assert_pending};
use tempfile::TempDir;
use tokio::{spawn, sync::Barrier, task::yield_now, time::sleep};
use wiremock::{
matchers::{header, method, path},
Mock, MockServer, ResponseTemplate,
matchers::{header, method, path},
};
use crate::timeline::sliding_sync::{assert_timeline_stream, timeline_event};
@@ -1,6 +1,6 @@
//! Helpers for integration tests involving sliding sync.
use wiremock::{http::Method, Match, MockServer, Request};
use wiremock::{Match, MockServer, Request, http::Method};
pub(crate) async fn check_requests(server: MockServer, expected_requests: &[serde_json::Value]) {
let mut num_requests = 0;
@@ -21,7 +21,7 @@ use matrix_sdk::{
linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update},
test_utils::mocks::MatrixMockServer,
};
use matrix_sdk_test::{async_test, event_factory::EventFactory, BOB};
use matrix_sdk_test::{BOB, async_test, event_factory::EventFactory};
use matrix_sdk_ui::timeline::RoomExt;
use ruma::{
event_id,
@@ -21,7 +21,7 @@ use futures_util::StreamExt;
use matrix_sdk::{
executor::spawn, ruma::MilliSecondsSinceUnixEpoch, test_utils::mocks::MatrixMockServer,
};
use matrix_sdk_test::{async_test, event_factory::EventFactory, JoinedRoomBuilder};
use matrix_sdk_test::{JoinedRoomBuilder, async_test, event_factory::EventFactory};
use matrix_sdk_ui::timeline::{EventSendState, RoomExt};
use ruma::{
event_id,
@@ -20,21 +20,22 @@ use assert_matches2::assert_let;
use eyeball_im::VectorDiff;
use futures_util::{FutureExt, StreamExt};
use matrix_sdk::{
Client,
room::edit::EditedContent,
test_utils::mocks::{MatrixMockServer, RoomMessagesResponseTemplate},
Client,
};
use matrix_sdk_test::{async_test, event_factory::EventFactory, JoinedRoomBuilder, ALICE, BOB};
use matrix_sdk_test::{ALICE, BOB, JoinedRoomBuilder, async_test, event_factory::EventFactory};
use matrix_sdk_ui::{
Timeline,
timeline::{
EditError, Error, EventSendState, MsgLikeContent, MsgLikeKind, RoomExt, TimelineDetails,
TimelineEventItemId, TimelineItemContent,
},
Timeline,
};
use ruma::{
event_id,
OwnedRoomId, event_id,
events::{
AnyMessageLikeEventContent, AnyTimelineEvent,
poll::unstable_start::{
NewUnstablePollStartEventContent, ReplacementUnstablePollStartEventContent,
UnstablePollAnswer, UnstablePollAnswers, UnstablePollStartContentBlock,
@@ -44,11 +45,9 @@ use ruma::{
MessageType, RoomMessageEventContent, RoomMessageEventContentWithoutRelation,
TextMessageEventContent,
},
AnyMessageLikeEventContent, AnyTimelineEvent,
},
owned_event_id, room_id,
serde::Raw,
OwnedRoomId,
};
use stream_assert::{assert_next_matches, assert_pending};
use tokio::{task::yield_now, time::sleep};
@@ -877,13 +876,14 @@ async fn test_pending_edit_from_backpagination() {
let original_event_id = event_id!("$original");
let edit_event_id = event_id!("$edit");
h.handle_backpagination(
vec![f
.text_msg("* hello")
.sender(&ALICE)
.event_id(edit_event_id)
.room(&h.room_id)
.edit(original_event_id, RoomMessageEventContent::text_plain("hello").into())
.into()],
vec![
f.text_msg("* hello")
.sender(&ALICE)
.event_id(edit_event_id)
.room(&h.room_id)
.edit(original_event_id, RoomMessageEventContent::text_plain("hello").into())
.into(),
],
10,
)
.await;
@@ -941,13 +941,14 @@ async fn test_pending_edit_from_backpagination_doesnt_override_pending_edit_from
// And then I receive an edit from a back-pagination for the same event…
let edit_event_id2 = event_id!("$edit2");
h.handle_backpagination(
vec![f
.text_msg("* aloha")
.sender(&ALICE)
.event_id(edit_event_id2)
.room(&h.room_id)
.edit(original_event_id, RoomMessageEventContent::text_plain("aloha").into())
.into()],
vec![
f.text_msg("* aloha")
.sender(&ALICE)
.event_id(edit_event_id2)
.room(&h.room_id)
.edit(original_event_id, RoomMessageEventContent::text_plain("aloha").into())
.into(),
],
10,
)
.await;
@@ -21,8 +21,8 @@ use eyeball_im::VectorDiff;
use futures_util::StreamExt;
use matrix_sdk::{config::SyncSettings, test_utils::logged_in_client_with_server};
use matrix_sdk_test::{
async_test, event_factory::EventFactory, mocks::mock_encryption_state, JoinedRoomBuilder,
SyncResponseBuilder, ALICE, BOB,
ALICE, BOB, JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
mocks::mock_encryption_state,
};
use matrix_sdk_ui::timeline::{TimelineBuilder, TimelineFocus};
use ruma::{event_id, events::room::message::RoomMessageEventContent, room_id};
@@ -26,7 +26,7 @@ use matrix_sdk::{
room::reply::{EnforceThread, Reply},
test_utils::mocks::MatrixMockServer,
};
use matrix_sdk_test::{async_test, event_factory::EventFactory, JoinedRoomBuilder, ALICE};
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, async_test, event_factory::EventFactory};
use matrix_sdk_ui::timeline::{AttachmentSource, EventSendState, RoomExt};
#[cfg(feature = "unstable-msc4274")]
use matrix_sdk_ui::timeline::{GalleryConfig, GalleryItemInfo};
@@ -37,8 +37,8 @@ use ruma::owned_mxc_uri;
use ruma::{
event_id,
events::room::{
message::{MessageType, ReplyWithinThread},
MediaSource,
message::{MessageType, ReplyWithinThread},
},
room_id,
};
@@ -23,30 +23,30 @@ use matrix_sdk::{
test_utils::mocks::MatrixMockServer,
};
use matrix_sdk_test::{
async_test, event_factory::EventFactory, JoinedRoomBuilder, RoomAccountDataTestEvent,
StateTestEvent, ALICE, BOB,
ALICE, BOB, JoinedRoomBuilder, RoomAccountDataTestEvent, StateTestEvent, async_test,
event_factory::EventFactory,
};
use matrix_sdk_ui::{
Timeline,
timeline::{
AnyOtherFullStateEventContent, Error, EventSendState, RedactError, RoomExt,
TimelineBuilder, TimelineEventItemId, TimelineItemContent, VirtualTimelineItem,
},
Timeline,
};
use ruma::{
event_id,
EventId, MilliSecondsSinceUnixEpoch, event_id,
events::room::{
encryption::RoomEncryptionEventContent,
message::{RedactedRoomMessageEventContent, RoomMessageEventContent},
},
owned_event_id, room_id, user_id, EventId, MilliSecondsSinceUnixEpoch,
owned_event_id, room_id, user_id,
};
use serde_json::json;
use sliding_sync::assert_timeline_stream;
use stream_assert::assert_pending;
use wiremock::{
matchers::{header, method, path_regex},
Mock, ResponseTemplate,
matchers::{header, method, path_regex},
};
mod decryption;
@@ -18,8 +18,8 @@ use assert_matches::assert_matches;
use assert_matches2::assert_let;
use eyeball_im::VectorDiff;
use futures_util::{
future::{join, join3},
FutureExt, StreamExt as _,
future::{join, join3},
};
use matrix_sdk::{
assert_let_timeout,
@@ -31,24 +31,25 @@ use matrix_sdk::{
},
};
use matrix_sdk_test::{
async_test, event_factory::EventFactory, mocks::mock_encryption_state, JoinedRoomBuilder,
StateTestEvent, SyncResponseBuilder, ALICE, BOB,
ALICE, BOB, JoinedRoomBuilder, StateTestEvent, SyncResponseBuilder, async_test,
event_factory::EventFactory, mocks::mock_encryption_state,
};
use matrix_sdk_ui::timeline::{AnyOtherFullStateEventContent, RoomExt, TimelineItemContent};
use once_cell::sync::Lazy;
use ruma::{
events::{room::message::MessageType, FullStateEventContent},
room_id, EventId,
EventId,
events::{FullStateEventContent, room::message::MessageType},
room_id,
};
use serde_json::{json, Value as JsonValue};
use serde_json::{Value as JsonValue, json};
use stream_assert::{assert_next_eq, assert_pending};
use tokio::{
spawn,
time::{sleep, timeout},
};
use wiremock::{
matchers::{header, method, path_regex, query_param, query_param_is_missing},
Mock, ResponseTemplate,
matchers::{header, method, path_regex, query_param, query_param_is_missing},
};
use crate::{mock_sync, timeline::sliding_sync::assert_timeline_stream};
@@ -4,23 +4,25 @@ use assert_matches2::assert_let;
use eyeball_im::VectorDiff;
use futures_util::StreamExt as _;
use matrix_sdk::{
Client, Room,
config::SyncSettings,
test_utils::{
logged_in_client_with_server,
mocks::{MatrixMockServer, RoomMessagesResponseTemplate},
},
Client, Room,
};
use matrix_sdk_base::deserialized_responses::TimelineEvent;
use matrix_sdk_common::executor::spawn;
use matrix_sdk_test::{
async_test, event_factory::EventFactory, JoinedRoomBuilder, StateTestEvent,
SyncResponseBuilder, BOB,
BOB, JoinedRoomBuilder, StateTestEvent, SyncResponseBuilder, async_test,
event_factory::EventFactory,
};
use matrix_sdk_ui::timeline::{RoomExt, TimelineBuilder, TimelineFocus};
use ruma::{
assign, event_id,
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, RoomId, UserId, assign,
event_id,
events::{
AnySyncTimelineEvent,
room::{
encrypted::{
EncryptedEventScheme, MegolmV1AesSha2ContentInit, RoomEncryptedEventContent,
@@ -28,18 +30,17 @@ use ruma::{
message::RoomMessageEventContentWithoutRelation,
pinned_events::RoomPinnedEventsEventContent,
},
AnySyncTimelineEvent,
},
owned_device_id, owned_room_id, owned_user_id, room_id,
serde::Raw,
user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, RoomId, UserId,
user_id,
};
use serde_json::json;
use stream_assert::assert_pending;
use tokio::time::sleep;
use wiremock::{
matchers::{header, method, path_regex},
Mock, ResponseTemplate,
matchers::{header, method, path_regex},
};
use crate::mock_sync;
@@ -18,15 +18,15 @@ use assert_matches::assert_matches;
use matrix_sdk::{config::SyncSettings, test_utils::logged_in_client_with_server};
use matrix_sdk_common::executor::spawn;
use matrix_sdk_test::{
async_test, event_factory::EventFactory, mocks::mock_encryption_state, JoinedRoomBuilder,
SyncResponseBuilder, ALICE, BOB, CAROL, DEFAULT_TEST_ROOM_ID,
ALICE, BOB, CAROL, DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
event_factory::EventFactory, mocks::mock_encryption_state,
};
use matrix_sdk_ui::timeline::{RoomExt, TimelineDetails};
use ruma::events::room::member::MembershipState;
use serde_json::json;
use wiremock::{
matchers::{method, path_regex},
Mock, ResponseTemplate,
matchers::{method, path_regex},
};
use crate::mock_sync;
@@ -18,22 +18,22 @@ use assert_matches::assert_matches;
use assert_matches2::assert_let;
use eyeball_im::VectorDiff;
use futures_util::StreamExt;
use matrix_sdk::{config::SyncSettings, test_utils::logged_in_client_with_server, Error};
use matrix_sdk::{Error, config::SyncSettings, test_utils::logged_in_client_with_server};
use matrix_sdk_base::store::QueueWedgeError;
use matrix_sdk_test::{
async_test, event_factory::EventFactory, mocks::mock_encryption_state, JoinedRoomBuilder,
SyncResponseBuilder, ALICE,
ALICE, JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
mocks::mock_encryption_state,
};
use matrix_sdk_ui::timeline::{EventItemOrigin, EventSendState, RoomExt};
use ruma::{
event_id, events::room::message::RoomMessageEventContent, room_id, MilliSecondsSinceUnixEpoch,
MilliSecondsSinceUnixEpoch, event_id, events::room::message::RoomMessageEventContent, room_id,
};
use serde_json::json;
use stream_assert::{assert_next_matches, assert_pending};
use tokio::{task::yield_now, time::sleep};
use wiremock::{
matchers::{body_string_contains, header, method, path_regex},
Mock, ResponseTemplate,
matchers::{body_string_contains, header, method, path_regex},
};
use crate::mock_sync;
@@ -18,7 +18,7 @@ use assert_matches2::{assert_let, assert_matches};
use eyeball_im::VectorDiff;
use futures_util::StreamExt as _;
use matrix_sdk::{assert_let_timeout, test_utils::mocks::MatrixMockServer};
use matrix_sdk_test::{async_test, event_factory::EventFactory, JoinedRoomBuilder, ALICE};
use matrix_sdk_test::{ALICE, JoinedRoomBuilder, async_test, event_factory::EventFactory};
use matrix_sdk_ui::timeline::{EventSendState, ReactionStatus, RoomExt as _};
use ruma::{event_id, events::room::message::RoomMessageEventContent, room_id};
use serde_json::json;

Some files were not shown because too many files have changed in this diff Show More