Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c5f2460e02 | |||
| 8ad785a117 | |||
| a5b936d0b6 | |||
| 1a0544c8eb | |||
| 232c23e8df | |||
| 7d9d5bf3b4 | |||
| ea076b3d76 | |||
| 8aa6f97f7c | |||
| 679aa07115 | |||
| 0c66d8a53f | |||
| 25ed7eef2b | |||
| a399840dff | |||
| 8d4e7f0478 | |||
| 3bd93130c5 | |||
| 153618b77c | |||
| dd871ef9ac | |||
| 8a29f17d1d | |||
| fab520ab33 | |||
| 435553c3d1 | |||
| 610ecd218c | |||
| fa300d1f33 | |||
| af2a483158 | |||
| 753b0d8584 | |||
| 36713adbdb | |||
| d73a02c608 | |||
| f73199b472 | |||
| 420d373144 | |||
| a79e9130e6 | |||
| 355b5327f8 | |||
| fa77852001 | |||
| 7b73311de5 | |||
| f03934bc4f | |||
| 014ee98fb7 | |||
| fbcf9fce7c | |||
| 6de403276a | |||
| 6209bc942c | |||
| edd371b570 | |||
| 817f32e15b | |||
| 30eb12ed2d | |||
| 900697bc3b |
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(®istration_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.
|
||||
|
||||
@@ -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(®istration_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"))]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -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>>,
|
||||
}
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<_> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user