mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-14 11:05:32 -04:00
Merge branch 'main' into feat-sdk-sliding-sync-cancellation-token
This commit is contained in:
8
.github/workflows/bindings_ci.yml
vendored
8
.github/workflows/bindings_ci.yml
vendored
@@ -93,16 +93,16 @@ jobs:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node-version: [14.0, 16.0, 18.0, 19.0]
|
||||
node-version: [16.0, 18.0, 19.0, 20.0]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
os-name: 🐧
|
||||
|
||||
- os: macos-latest
|
||||
os-name: 🍏
|
||||
node-version: 18.0
|
||||
node-version: 20.0
|
||||
|
||||
- node-version: 18.0
|
||||
- node-version: 20.0
|
||||
build-doc: true
|
||||
|
||||
steps:
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.0
|
||||
node-version: 20.0
|
||||
|
||||
- name: Install NPM dependencies
|
||||
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
|
||||
|
||||
2
.github/workflows/documentation.yml
vendored
2
.github/workflows/documentation.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
2
.github/workflows/release_crypto_js.yml
vendored
2
.github/workflows/release_crypto_js.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.0
|
||||
node-version: 20.0
|
||||
|
||||
- name: Install NPM dependencies
|
||||
working-directory: ${{ env.PKG_PATH }}
|
||||
|
||||
1170
Cargo.lock
generated
1170
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -26,14 +26,14 @@ async-stream = "0.3.3"
|
||||
async-trait = "0.1.60"
|
||||
base64 = "0.21.0"
|
||||
byteorder = "1.4.3"
|
||||
ctor = "0.1.26"
|
||||
ctor = "0.2.0"
|
||||
dashmap = "5.2.0"
|
||||
eyeball = "0.4.0"
|
||||
eyeball = "0.6.0"
|
||||
eyeball-im = "0.2.0"
|
||||
futures-util = { version = "0.3.26", default-features = false, features = ["alloc"] }
|
||||
http = "0.2.6"
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "89e398fd062b4e763a3341fc7067428285d51d09", features = ["client-api-c"] }
|
||||
ruma-common = { git = "https://github.com/ruma/ruma", rev = "89e398fd062b4e763a3341fc7067428285d51d09" }
|
||||
ruma = { git = "https://github.com/ruma/ruma", rev = "0143bd9b9f5dcfcaa835afb76f342c12f014f945", features = ["client-api-c", "compat-user-id"] }
|
||||
ruma-common = { git = "https://github.com/ruma/ruma", rev = "0143bd9b9f5dcfcaa835afb76f342c12f014f945" }
|
||||
once_cell = "1.16.0"
|
||||
serde = "1.0.151"
|
||||
serde_html_form = "0.2.0"
|
||||
|
||||
@@ -27,7 +27,8 @@ pub enum PkDecryptionError {
|
||||
}
|
||||
|
||||
/// Error type for the decoding and storing of the backup key.
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Error, uniffi::Error)]
|
||||
#[uniffi(flat_error)]
|
||||
pub enum DecodeError {
|
||||
/// An error happened while decoding the recovery key.
|
||||
#[error(transparent)]
|
||||
@@ -40,7 +41,7 @@ pub enum DecodeError {
|
||||
|
||||
/// Struct containing info about the way the backup key got derived from a
|
||||
/// passphrase.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct PassphraseInfo {
|
||||
/// The salt that was used during key derivation.
|
||||
pub private_key_salt: String,
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::collections::HashMap;
|
||||
use matrix_sdk_crypto::Device as InnerDevice;
|
||||
|
||||
/// An E2EE capable Matrix device.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct Device {
|
||||
/// The device owner.
|
||||
pub user_id: String,
|
||||
|
||||
@@ -7,7 +7,8 @@ use matrix_sdk_crypto::{
|
||||
use matrix_sdk_sqlite::OpenStoreError;
|
||||
use ruma::{IdParseError, OwnedUserId};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||
#[uniffi(flat_error)]
|
||||
pub enum KeyImportError {
|
||||
#[error(transparent)]
|
||||
Export(#[from] KeyExportError),
|
||||
@@ -17,7 +18,8 @@ pub enum KeyImportError {
|
||||
Json(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||
#[uniffi(flat_error)]
|
||||
pub enum SecretImportError {
|
||||
#[error(transparent)]
|
||||
CryptoStore(#[from] InnerStoreError),
|
||||
@@ -25,7 +27,8 @@ pub enum SecretImportError {
|
||||
Import(#[from] RustSecretImportError),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||
#[uniffi(flat_error)]
|
||||
pub enum SignatureError {
|
||||
#[error(transparent)]
|
||||
Signature(#[from] InnerSignatureError),
|
||||
@@ -55,7 +58,7 @@ pub enum CryptoStoreError {
|
||||
Identifier(#[from] IdParseError),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, uniffi::Error)]
|
||||
pub enum DecryptionError {
|
||||
Serialization { error: String },
|
||||
Identifier { error: String },
|
||||
|
||||
@@ -56,7 +56,7 @@ pub use verification::{
|
||||
use vodozemac::{Curve25519PublicKey, Ed25519PublicKey};
|
||||
|
||||
/// Struct collecting data that is important to migrate to the rust-sdk
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, uniffi::Record)]
|
||||
pub struct MigrationData {
|
||||
/// The pickled version of the Olm Account
|
||||
account: PickledAccount,
|
||||
@@ -79,6 +79,7 @@ pub struct MigrationData {
|
||||
}
|
||||
|
||||
/// Struct collecting data that is important to migrate sessions to the rust-sdk
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct SessionMigrationData {
|
||||
/// The user id that the data belongs to.
|
||||
user_id: String,
|
||||
@@ -100,7 +101,7 @@ pub struct SessionMigrationData {
|
||||
///
|
||||
/// Holds all the information that needs to be stored in a database to restore
|
||||
/// an account.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, uniffi::Record)]
|
||||
pub struct PickledAccount {
|
||||
/// The user id of the account owner.
|
||||
pub user_id: String,
|
||||
@@ -118,7 +119,7 @@ pub struct PickledAccount {
|
||||
///
|
||||
/// Holds all the information that needs to be stored in a database to restore
|
||||
/// a Session.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, uniffi::Record)]
|
||||
pub struct PickledSession {
|
||||
/// The pickle string holding the Olm Session.
|
||||
pub pickle: String,
|
||||
@@ -136,7 +137,7 @@ pub struct PickledSession {
|
||||
///
|
||||
/// Holds all the information that needs to be stored in a database to restore
|
||||
/// an InboundGroupSession.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, uniffi::Record)]
|
||||
pub struct PickledInboundGroupSession {
|
||||
/// The pickle string holding the InboundGroupSession.
|
||||
pub pickle: String,
|
||||
@@ -157,7 +158,7 @@ pub struct PickledInboundGroupSession {
|
||||
}
|
||||
|
||||
/// Error type for the migration process.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||
pub enum MigrationError {
|
||||
/// Generic catch all error variant.
|
||||
#[error("error migrating database: {error_message}")]
|
||||
@@ -188,15 +189,16 @@ impl From<anyhow::Error> for MigrationError {
|
||||
///
|
||||
/// * `progress_listener` - A callback that can be used to introspect the
|
||||
/// progress of the migration.
|
||||
#[uniffi::export]
|
||||
pub fn migrate(
|
||||
data: MigrationData,
|
||||
path: &str,
|
||||
path: String,
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> Result<(), MigrationError> {
|
||||
let runtime = Runtime::new().context("initializing tokio runtime")?;
|
||||
runtime.block_on(async move {
|
||||
migrate_data(data, path, passphrase, progress_listener).await?;
|
||||
migrate_data(data, &path, passphrase, progress_listener).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -350,14 +352,15 @@ async fn save_changes(
|
||||
///
|
||||
/// * `progress_listener` - A callback that can be used to introspect the
|
||||
/// progress of the migration.
|
||||
#[uniffi::export]
|
||||
pub fn migrate_sessions(
|
||||
data: SessionMigrationData,
|
||||
path: &str,
|
||||
path: String,
|
||||
passphrase: Option<String>,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> Result<(), MigrationError> {
|
||||
let runtime = Runtime::new().context("initializing tokio runtime")?;
|
||||
runtime.block_on(migrate_session_data(data, path, passphrase, progress_listener))?;
|
||||
runtime.block_on(migrate_session_data(data, &path, passphrase, progress_listener))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -497,9 +500,10 @@ fn collect_sessions(
|
||||
/// * `passphrase` - The passphrase that should be used to encrypt the data at
|
||||
/// rest in the Sqlite store. **Warning**, if no passphrase is given, the store
|
||||
/// and all its data will remain unencrypted.
|
||||
#[uniffi::export]
|
||||
pub fn migrate_room_settings(
|
||||
room_settings: HashMap<String, RoomSettings>,
|
||||
path: &str,
|
||||
path: String,
|
||||
passphrase: Option<String>,
|
||||
) -> Result<(), MigrationError> {
|
||||
let runtime = Runtime::new().context("initializing tokio runtime")?;
|
||||
@@ -540,7 +544,7 @@ impl<T: Fn(i32, i32)> ProgressListener for T {
|
||||
}
|
||||
|
||||
/// An encryption algorithm to be used to encrypt messages sent to a room.
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, uniffi::Enum)]
|
||||
pub enum EventEncryptionAlgorithm {
|
||||
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
|
||||
OlmV1Curve25519AesSha2,
|
||||
@@ -572,6 +576,7 @@ impl TryFrom<RustEventEncryptionAlgorithm> for EventEncryptionAlgorithm {
|
||||
}
|
||||
|
||||
/// Who can see a room's history.
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum HistoryVisibility {
|
||||
/// Previous events are accessible to newly joined members from the point
|
||||
/// they were invited onwards.
|
||||
@@ -717,7 +722,7 @@ pub struct CrossSigningStatus {
|
||||
|
||||
/// A struct containing private cross signing keys that can be backed up or
|
||||
/// uploaded to the secret store.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, uniffi::Record)]
|
||||
pub struct CrossSigningKeyExport {
|
||||
/// The seed of the master key encoded as unpadded base64.
|
||||
pub master_key: Option<String>,
|
||||
@@ -810,7 +815,7 @@ impl From<matrix_sdk_crypto::CrossSigningStatus> for CrossSigningStatus {
|
||||
}
|
||||
|
||||
/// Room encryption settings which are modified by state events or user options
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, uniffi::Record)]
|
||||
pub struct RoomSettings {
|
||||
/// The encryption algorithm that should be used in the room.
|
||||
pub algorithm: EventEncryptionAlgorithm,
|
||||
@@ -959,12 +964,15 @@ mod test {
|
||||
let migration_data: MigrationData = serde_json::from_value(data)?;
|
||||
|
||||
let dir = tempdir()?;
|
||||
let path =
|
||||
dir.path().to_str().expect("Creating a string from the tempdir path should not fail");
|
||||
let path = dir
|
||||
.path()
|
||||
.to_str()
|
||||
.expect("Creating a string from the tempdir path should not fail")
|
||||
.to_owned();
|
||||
|
||||
migrate(migration_data, path, None, Box::new(|_, _| {}))?;
|
||||
migrate(migration_data, path.clone(), None, Box::new(|_, _| {}))?;
|
||||
|
||||
let machine = OlmMachine::new("@ganfra146:matrix.org", "DEWRCMENGS", path, None)?;
|
||||
let machine = OlmMachine::new("@ganfra146:matrix.org", "DEWRCMENGS", &path, None)?;
|
||||
|
||||
assert_eq!(
|
||||
machine.identity_keys()["ed25519"],
|
||||
|
||||
@@ -41,6 +41,7 @@ pub struct LoggerWrapper {
|
||||
}
|
||||
|
||||
/// Set the logger that should be used to forward Rust logs over FFI.
|
||||
#[uniffi::export]
|
||||
pub fn set_logger(logger: Box<dyn Logger>) {
|
||||
let logger = LoggerWrapper { inner: Arc::new(Mutex::new(logger)) };
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ impl Drop for OlmMachine {
|
||||
|
||||
/// A pair of outgoing room key requests, both of those are sendToDevice
|
||||
/// requests.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct KeyRequestPair {
|
||||
/// The optional cancellation, this is None if no previous key request was
|
||||
/// sent out for this key, thus it doesn't need to be cancelled.
|
||||
@@ -88,7 +89,7 @@ pub struct KeyRequestPair {
|
||||
}
|
||||
|
||||
/// The result of a signature verification of a signed JSON object.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, uniffi::Record)]
|
||||
pub struct SignatureVerification {
|
||||
/// The result of the signature verification using the public key of our own
|
||||
/// device.
|
||||
@@ -127,36 +128,6 @@ impl From<RustSignatureCheckResult> for SignatureVerification {
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl OlmMachine {
|
||||
/// Get the user ID of the owner of this `OlmMachine`.
|
||||
pub fn user_id(&self) -> String {
|
||||
self.inner.user_id().to_string()
|
||||
}
|
||||
|
||||
/// Get the device ID of the device of this `OlmMachine`.
|
||||
pub fn device_id(&self) -> String {
|
||||
self.inner.device_id().to_string()
|
||||
}
|
||||
|
||||
/// Get our own identity keys.
|
||||
pub fn identity_keys(&self) -> HashMap<String, String> {
|
||||
let identity_keys = self.inner.identity_keys();
|
||||
let curve_key = identity_keys.curve25519.to_base64();
|
||||
let ed25519_key = identity_keys.ed25519.to_base64();
|
||||
|
||||
HashMap::from([("ed25519".to_owned(), ed25519_key), ("curve25519".to_owned(), curve_key)])
|
||||
}
|
||||
|
||||
/// Get the status of the private cross signing keys.
|
||||
///
|
||||
/// This can be used to check which private cross signing keys we have
|
||||
/// stored locally.
|
||||
pub fn cross_signing_status(&self) -> CrossSigningStatus {
|
||||
self.runtime.block_on(self.inner.cross_signing_status()).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl OlmMachine {
|
||||
/// Create a new `OlmMachine`
|
||||
///
|
||||
@@ -192,9 +163,63 @@ impl OlmMachine {
|
||||
Ok(OlmMachine { inner: ManuallyDrop::new(inner), runtime })
|
||||
}
|
||||
|
||||
/// Get the display name of our own device.
|
||||
pub fn display_name(&self) -> Result<Option<String>, CryptoStoreError> {
|
||||
Ok(self.runtime.block_on(self.inner.display_name())?)
|
||||
fn import_room_keys_helper(
|
||||
&self,
|
||||
keys: Vec<ExportedRoomKey>,
|
||||
from_backup: bool,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> Result<KeysImportResult, KeyImportError> {
|
||||
let listener = |progress: usize, total: usize| {
|
||||
progress_listener.on_progress(progress as i32, total as i32)
|
||||
};
|
||||
|
||||
let result =
|
||||
self.runtime.block_on(self.inner.import_room_keys(keys, from_backup, listener))?;
|
||||
|
||||
Ok(KeysImportResult {
|
||||
imported: result.imported_count as i64,
|
||||
total: result.total_count as i64,
|
||||
keys: result
|
||||
.keys
|
||||
.into_iter()
|
||||
.map(|(r, m)| {
|
||||
(
|
||||
r.to_string(),
|
||||
m.into_iter().map(|(s, k)| (s, k.into_iter().collect())).collect(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl OlmMachine {
|
||||
/// Get the user ID of the owner of this `OlmMachine`.
|
||||
pub fn user_id(&self) -> String {
|
||||
self.inner.user_id().to_string()
|
||||
}
|
||||
|
||||
/// Get the device ID of the device of this `OlmMachine`.
|
||||
pub fn device_id(&self) -> String {
|
||||
self.inner.device_id().to_string()
|
||||
}
|
||||
|
||||
/// Get our own identity keys.
|
||||
pub fn identity_keys(&self) -> HashMap<String, String> {
|
||||
let identity_keys = self.inner.identity_keys();
|
||||
let curve_key = identity_keys.curve25519.to_base64();
|
||||
let ed25519_key = identity_keys.ed25519.to_base64();
|
||||
|
||||
HashMap::from([("ed25519".to_owned(), ed25519_key), ("curve25519".to_owned(), curve_key)])
|
||||
}
|
||||
|
||||
/// Get the status of the private cross signing keys.
|
||||
///
|
||||
/// This can be used to check which private cross signing keys we have
|
||||
/// stored locally.
|
||||
pub fn cross_signing_status(&self) -> CrossSigningStatus {
|
||||
self.runtime.block_on(self.inner.cross_signing_status()).into()
|
||||
}
|
||||
|
||||
/// Get a cross signing user identity for the given user ID.
|
||||
@@ -211,10 +236,10 @@ impl OlmMachine {
|
||||
/// received.
|
||||
pub fn get_identity(
|
||||
&self,
|
||||
user_id: &str,
|
||||
user_id: String,
|
||||
timeout: u32,
|
||||
) -> Result<Option<UserIdentity>, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
let user_id = parse_user_id(&user_id)?;
|
||||
|
||||
let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
|
||||
|
||||
@@ -230,8 +255,8 @@ impl OlmMachine {
|
||||
}
|
||||
|
||||
/// Check if a user identity is considered to be verified by us.
|
||||
pub fn is_identity_verified(&self, user_id: &str) -> Result<bool, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
pub fn is_identity_verified(&self, user_id: String) -> Result<bool, CryptoStoreError> {
|
||||
let user_id = parse_user_id(&user_id)?;
|
||||
|
||||
Ok(
|
||||
if let Some(identity) =
|
||||
@@ -258,7 +283,10 @@ impl OlmMachine {
|
||||
///
|
||||
/// Returns a request that needs to be sent out for the user identity to be
|
||||
/// marked as verified.
|
||||
pub fn verify_identity(&self, user_id: &str) -> Result<SignatureUploadRequest, SignatureError> {
|
||||
pub fn verify_identity(
|
||||
&self,
|
||||
user_id: String,
|
||||
) -> Result<SignatureUploadRequest, SignatureError> {
|
||||
let user_id = UserId::parse(user_id)?;
|
||||
|
||||
let user_identity = self.runtime.block_on(self.inner.get_identity(&user_id, None))?;
|
||||
@@ -290,17 +318,17 @@ impl OlmMachine {
|
||||
/// received.
|
||||
pub fn get_device(
|
||||
&self,
|
||||
user_id: &str,
|
||||
device_id: &str,
|
||||
user_id: String,
|
||||
device_id: String,
|
||||
timeout: u32,
|
||||
) -> Result<Option<Device>, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
let user_id = parse_user_id(&user_id)?;
|
||||
|
||||
let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
|
||||
|
||||
Ok(self
|
||||
.runtime
|
||||
.block_on(self.inner.get_device(&user_id, device_id.into(), timeout))?
|
||||
.block_on(self.inner.get_device(&user_id, device_id.as_str().into(), timeout))?
|
||||
.map(|d| d.into()))
|
||||
}
|
||||
|
||||
@@ -319,17 +347,20 @@ impl OlmMachine {
|
||||
/// as verified.
|
||||
pub fn verify_device(
|
||||
&self,
|
||||
user_id: &str,
|
||||
device_id: &str,
|
||||
user_id: String,
|
||||
device_id: String,
|
||||
) -> Result<SignatureUploadRequest, SignatureError> {
|
||||
let user_id = UserId::parse(user_id)?;
|
||||
let device =
|
||||
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?;
|
||||
let device = self.runtime.block_on(self.inner.get_device(
|
||||
&user_id,
|
||||
device_id.as_str().into(),
|
||||
None,
|
||||
))?;
|
||||
|
||||
if let Some(device) = device {
|
||||
Ok(self.runtime.block_on(device.verify())?.into())
|
||||
} else {
|
||||
Err(SignatureError::UnknownDevice(user_id, device_id.to_owned()))
|
||||
Err(SignatureError::UnknownDevice(user_id, device_id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,14 +368,17 @@ impl OlmMachine {
|
||||
/// or uploading any signatures if verified
|
||||
pub fn set_local_trust(
|
||||
&self,
|
||||
user_id: &str,
|
||||
device_id: &str,
|
||||
user_id: String,
|
||||
device_id: String,
|
||||
trust_state: LocalTrust,
|
||||
) -> Result<(), CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
let user_id = parse_user_id(&user_id)?;
|
||||
|
||||
let device =
|
||||
self.runtime.block_on(self.inner.get_device(&user_id, device_id.into(), None))?;
|
||||
let device = self.runtime.block_on(self.inner.get_device(
|
||||
&user_id,
|
||||
device_id.as_str().into(),
|
||||
None,
|
||||
))?;
|
||||
|
||||
if let Some(device) = device {
|
||||
self.runtime.block_on(device.set_local_trust(trust_state))?;
|
||||
@@ -367,10 +401,10 @@ impl OlmMachine {
|
||||
/// received.
|
||||
pub fn get_user_devices(
|
||||
&self,
|
||||
user_id: &str,
|
||||
user_id: String,
|
||||
timeout: u32,
|
||||
) -> Result<Vec<Device>, CryptoStoreError> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
let user_id = parse_user_id(&user_id)?;
|
||||
|
||||
let timeout = if timeout == 0 { None } else { Some(Duration::from_secs(timeout.into())) };
|
||||
Ok(self
|
||||
@@ -410,13 +444,13 @@ impl OlmMachine {
|
||||
/// * `response_body` - The body of the response that was received.
|
||||
pub fn mark_request_as_sent(
|
||||
&self,
|
||||
request_id: &str,
|
||||
request_id: String,
|
||||
request_type: RequestType,
|
||||
response_body: &str,
|
||||
response_body: String,
|
||||
) -> Result<(), CryptoStoreError> {
|
||||
let id: OwnedTransactionId = request_id.into();
|
||||
|
||||
let response = response_from_string(response_body);
|
||||
let response = response_from_string(&response_body);
|
||||
|
||||
let response: OwnedResponse = match request_type {
|
||||
RequestType::KeysUpload => {
|
||||
@@ -447,10 +481,7 @@ impl OlmMachine {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl OlmMachine {
|
||||
/// Let the state machine know about E2EE related sync changes that we
|
||||
/// received from the server.
|
||||
///
|
||||
@@ -867,37 +898,6 @@ impl OlmMachine {
|
||||
|
||||
Ok(encrypted)
|
||||
}
|
||||
}
|
||||
|
||||
impl OlmMachine {
|
||||
fn import_room_keys_helper(
|
||||
&self,
|
||||
keys: Vec<ExportedRoomKey>,
|
||||
from_backup: bool,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> Result<KeysImportResult, KeyImportError> {
|
||||
let listener = |progress: usize, total: usize| {
|
||||
progress_listener.on_progress(progress as i32, total as i32)
|
||||
};
|
||||
|
||||
let result =
|
||||
self.runtime.block_on(self.inner.import_room_keys(keys, from_backup, listener))?;
|
||||
|
||||
Ok(KeysImportResult {
|
||||
imported: result.imported_count as i64,
|
||||
total: result.total_count as i64,
|
||||
keys: result
|
||||
.keys
|
||||
.into_iter()
|
||||
.map(|(r, m)| {
|
||||
(
|
||||
r.to_string(),
|
||||
m.into_iter().map(|(s, k)| (s, k.into_iter().collect())).collect(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Import room keys from the given serialized key export.
|
||||
///
|
||||
@@ -911,12 +911,12 @@ impl OlmMachine {
|
||||
/// progress of the key import.
|
||||
pub fn import_room_keys(
|
||||
&self,
|
||||
keys: &str,
|
||||
passphrase: &str,
|
||||
keys: String,
|
||||
passphrase: String,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> Result<KeysImportResult, KeyImportError> {
|
||||
let keys = Cursor::new(keys);
|
||||
let keys = decrypt_room_key_export(keys, passphrase)?;
|
||||
let keys = decrypt_room_key_export(keys, &passphrase)?;
|
||||
self.import_room_keys_helper(keys, false, progress_listener)
|
||||
}
|
||||
|
||||
@@ -935,19 +935,16 @@ impl OlmMachine {
|
||||
/// progress of the key import.
|
||||
pub fn import_decrypted_room_keys(
|
||||
&self,
|
||||
keys: &str,
|
||||
keys: String,
|
||||
progress_listener: Box<dyn ProgressListener>,
|
||||
) -> Result<KeysImportResult, KeyImportError> {
|
||||
let keys: Vec<Value> = serde_json::from_str(keys)?;
|
||||
let keys: Vec<Value> = serde_json::from_str(&keys)?;
|
||||
|
||||
let keys = keys.into_iter().map(serde_json::from_value).filter_map(|k| k.ok()).collect();
|
||||
|
||||
self.import_room_keys_helper(keys, true, progress_listener)
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl OlmMachine {
|
||||
/// Discard the currently active room key for the given room if there is
|
||||
/// one.
|
||||
pub fn discard_room_key(&self, room_id: String) -> Result<(), CryptoStoreError> {
|
||||
@@ -1352,14 +1349,12 @@ impl OlmMachine {
|
||||
.ok()
|
||||
.map(Arc::new))
|
||||
}
|
||||
}
|
||||
|
||||
impl OlmMachine {
|
||||
/// Sign the given message using our device key and if available cross
|
||||
/// signing master key.
|
||||
pub fn sign(&self, message: &str) -> HashMap<String, HashMap<String, String>> {
|
||||
pub fn sign(&self, message: String) -> HashMap<String, HashMap<String, String>> {
|
||||
self.runtime
|
||||
.block_on(self.inner.sign(message))
|
||||
.block_on(self.inner.sign(&message))
|
||||
.into_iter()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
@@ -1397,9 +1392,9 @@ impl OlmMachine {
|
||||
/// ```
|
||||
pub fn verify_backup(
|
||||
&self,
|
||||
backup_info: &str,
|
||||
backup_info: String,
|
||||
) -> Result<SignatureVerification, CryptoStoreError> {
|
||||
let backup_info = serde_json::from_str(backup_info)?;
|
||||
let backup_info = serde_json::from_str(&backup_info)?;
|
||||
|
||||
Ok(self
|
||||
.runtime
|
||||
|
||||
@@ -1,31 +1,4 @@
|
||||
namespace matrix_sdk_crypto_ffi {
|
||||
void set_logger(Logger logger);
|
||||
[Throws=MigrationError]
|
||||
void migrate(
|
||||
MigrationData data,
|
||||
[ByRef] string path,
|
||||
string? passphrase,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
[Throws=MigrationError]
|
||||
void migrate_sessions(
|
||||
SessionMigrationData data,
|
||||
[ByRef] string path,
|
||||
string? passphrase,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
[Throws=MigrationError]
|
||||
void migrate_room_settings(
|
||||
record<string, RoomSettings> room_settings,
|
||||
[ByRef] string path,
|
||||
string? passphrase
|
||||
);
|
||||
};
|
||||
|
||||
[Error]
|
||||
interface MigrationError {
|
||||
Generic(string error_message);
|
||||
};
|
||||
namespace matrix_sdk_crypto_ffi {};
|
||||
|
||||
callback interface Logger {
|
||||
void log(string log_line);
|
||||
@@ -35,29 +8,6 @@ callback interface ProgressListener {
|
||||
void on_progress(i32 progress, i32 total);
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum KeyImportError {
|
||||
"Export",
|
||||
"CryptoStore",
|
||||
"Json",
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum SignatureError {
|
||||
"Signature",
|
||||
"Identifier",
|
||||
"CryptoStore",
|
||||
"UnknownDevice",
|
||||
"UnknownUserIdentity",
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum SecretImportError {
|
||||
"Import",
|
||||
"CryptoStore",
|
||||
};
|
||||
|
||||
|
||||
[Error]
|
||||
enum CryptoStoreError {
|
||||
"OpenStore",
|
||||
@@ -68,96 +18,12 @@ enum CryptoStoreError {
|
||||
"Identifier",
|
||||
};
|
||||
|
||||
[Error]
|
||||
interface DecryptionError {
|
||||
Identifier(string error);
|
||||
Serialization(string error);
|
||||
Megolm(string error);
|
||||
MissingRoomKey(string error, string? withheld_code);
|
||||
Store(string error);
|
||||
};
|
||||
|
||||
dictionary KeysImportResult {
|
||||
i64 imported;
|
||||
i64 total;
|
||||
record<DOMString, record<DOMString, sequence<string>>> keys;
|
||||
};
|
||||
|
||||
dictionary Device {
|
||||
string user_id;
|
||||
string device_id;
|
||||
record<DOMString, string> keys;
|
||||
sequence<string> algorithms;
|
||||
string? display_name;
|
||||
boolean is_blocked;
|
||||
boolean locally_trusted;
|
||||
boolean cross_signing_trusted;
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface UserIdentity {
|
||||
Own(
|
||||
string user_id,
|
||||
boolean trusts_our_own_device,
|
||||
string master_key,
|
||||
string self_signing_key,
|
||||
string user_signing_key
|
||||
);
|
||||
Other(
|
||||
string user_id,
|
||||
string master_key,
|
||||
string self_signing_key
|
||||
);
|
||||
};
|
||||
|
||||
dictionary CrossSigningKeyExport {
|
||||
string? master_key;
|
||||
string? self_signing_key;
|
||||
string? user_signing_key;
|
||||
};
|
||||
|
||||
dictionary UploadSigningKeysRequest {
|
||||
string master_key;
|
||||
string self_signing_key;
|
||||
string user_signing_key;
|
||||
};
|
||||
|
||||
dictionary BootstrapCrossSigningResult {
|
||||
UploadSigningKeysRequest upload_signing_keys_request;
|
||||
SignatureUploadRequest signature_request;
|
||||
};
|
||||
|
||||
dictionary CancelInfo {
|
||||
string cancel_code;
|
||||
string reason;
|
||||
boolean cancelled_by_us;
|
||||
};
|
||||
|
||||
dictionary StartSasResult {
|
||||
Sas sas;
|
||||
OutgoingVerificationRequest request;
|
||||
};
|
||||
|
||||
interface Sas {
|
||||
string other_user_id();
|
||||
string other_device_id();
|
||||
string flow_id();
|
||||
string? room_id();
|
||||
boolean we_started();
|
||||
boolean is_done();
|
||||
|
||||
OutgoingVerificationRequest? accept();
|
||||
[Throws=CryptoStoreError]
|
||||
ConfirmVerificationResult? confirm();
|
||||
OutgoingVerificationRequest? cancel([ByRef] string cancel_code);
|
||||
|
||||
sequence<i32>? get_emoji_indices();
|
||||
sequence<i32>? get_decimals();
|
||||
|
||||
void set_changes_listener(SasListener listener);
|
||||
SasState state();
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface SasState {
|
||||
Started();
|
||||
@@ -172,32 +38,6 @@ callback interface SasListener {
|
||||
void on_change(SasState state);
|
||||
};
|
||||
|
||||
dictionary ScanResult {
|
||||
QrCode qr;
|
||||
OutgoingVerificationRequest request;
|
||||
};
|
||||
|
||||
interface QrCode {
|
||||
string other_user_id();
|
||||
string other_device_id();
|
||||
string flow_id();
|
||||
string? room_id();
|
||||
boolean we_started();
|
||||
boolean is_done();
|
||||
boolean is_cancelled();
|
||||
CancelInfo? cancel_info();
|
||||
|
||||
boolean reciprocated();
|
||||
boolean has_been_scanned();
|
||||
|
||||
ConfirmVerificationResult? confirm();
|
||||
OutgoingVerificationRequest? cancel([ByRef] string cancel_code);
|
||||
string? generate_qr_code();
|
||||
|
||||
void set_changes_listener(QrCodeListener listener);
|
||||
QrCodeState state();
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface QrCodeState {
|
||||
Started();
|
||||
@@ -212,36 +52,6 @@ callback interface QrCodeListener {
|
||||
void on_change(QrCodeState state);
|
||||
};
|
||||
|
||||
interface VerificationRequest {
|
||||
string other_user_id();
|
||||
string? other_device_id();
|
||||
string flow_id();
|
||||
string? room_id();
|
||||
boolean we_started();
|
||||
boolean is_ready();
|
||||
boolean is_done();
|
||||
boolean is_passive();
|
||||
boolean is_cancelled();
|
||||
CancelInfo? cancel_info();
|
||||
|
||||
sequence<string>? their_supported_methods();
|
||||
sequence<string>? our_supported_methods();
|
||||
|
||||
OutgoingVerificationRequest? accept(sequence<string> methods);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
StartSasResult? start_sas_verification();
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
QrCode? start_qr_verification();
|
||||
ScanResult? scan_qr_code([ByRef] string data);
|
||||
|
||||
OutgoingVerificationRequest? cancel();
|
||||
|
||||
void set_changes_listener(VerificationRequestListener listener);
|
||||
VerificationRequestState state();
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface VerificationRequestState {
|
||||
Requested();
|
||||
@@ -254,57 +64,6 @@ callback interface VerificationRequestListener {
|
||||
void on_change(VerificationRequestState state);
|
||||
};
|
||||
|
||||
dictionary RequestVerificationResult {
|
||||
VerificationRequest verification;
|
||||
OutgoingVerificationRequest request;
|
||||
};
|
||||
|
||||
dictionary ConfirmVerificationResult {
|
||||
sequence<OutgoingVerificationRequest> requests;
|
||||
SignatureUploadRequest? signature_request;
|
||||
};
|
||||
|
||||
interface Verification {
|
||||
QrCode? as_qr();
|
||||
Sas? as_sas();
|
||||
};
|
||||
|
||||
dictionary KeyRequestPair {
|
||||
Request? cancellation;
|
||||
Request key_request;
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface OutgoingVerificationRequest {
|
||||
ToDevice(string request_id, string event_type, string body);
|
||||
InRoom(string request_id, string room_id, string event_type, string content);
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface Request {
|
||||
ToDevice(string request_id, string event_type, string body);
|
||||
KeysUpload(string request_id, string body);
|
||||
KeysQuery(string request_id, sequence<string> users);
|
||||
KeysClaim(string request_id, record<DOMString, record<DOMString, string>> one_time_keys);
|
||||
KeysBackup(string request_id, string version, string rooms);
|
||||
RoomMessage(string request_id, string room_id, string event_type, string content);
|
||||
SignatureUpload(string request_id, string body);
|
||||
};
|
||||
|
||||
dictionary SignatureUploadRequest {
|
||||
string body;
|
||||
};
|
||||
|
||||
enum RequestType {
|
||||
"KeysQuery",
|
||||
"KeysClaim",
|
||||
"KeysUpload",
|
||||
"ToDevice",
|
||||
"SignatureUpload",
|
||||
"KeysBackup",
|
||||
"RoomMessage",
|
||||
};
|
||||
|
||||
enum LocalTrust {
|
||||
"Verified",
|
||||
"BlackListed",
|
||||
@@ -312,19 +71,6 @@ enum LocalTrust {
|
||||
"Unset",
|
||||
};
|
||||
|
||||
|
||||
enum EventEncryptionAlgorithm {
|
||||
"OlmV1Curve25519AesSha2",
|
||||
"MegolmV1AesSha2",
|
||||
};
|
||||
|
||||
enum HistoryVisibility {
|
||||
"Invited",
|
||||
"Joined",
|
||||
"Shared",
|
||||
"WorldReadable",
|
||||
};
|
||||
|
||||
interface OlmMachine {
|
||||
[Throws=CryptoStoreError]
|
||||
constructor(
|
||||
@@ -333,59 +79,6 @@ interface OlmMachine {
|
||||
[ByRef] string path,
|
||||
string? passphrase
|
||||
);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
sequence<Request> outgoing_requests();
|
||||
[Throws=CryptoStoreError]
|
||||
void mark_request_as_sent(
|
||||
[ByRef] string request_id,
|
||||
RequestType request_type,
|
||||
[ByRef] string response
|
||||
);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
UserIdentity? get_identity([ByRef] string user_id, u32 timeout);
|
||||
[Throws=SignatureError]
|
||||
SignatureUploadRequest verify_identity([ByRef] string user_id);
|
||||
[Throws=CryptoStoreError]
|
||||
Device? get_device([ByRef] string user_id, [ByRef] string device_id, u32 timeout);
|
||||
[Throws=CryptoStoreError]
|
||||
void set_local_trust([ByRef] string user_id, [ByRef] string device_id, LocalTrust trust_state);
|
||||
[Throws=SignatureError]
|
||||
SignatureUploadRequest verify_device([ByRef] string user_id, [ByRef] string device_id);
|
||||
[Throws=CryptoStoreError]
|
||||
sequence<Device> get_user_devices([ByRef] string user_id, u32 timeout);
|
||||
|
||||
[Throws=KeyImportError]
|
||||
KeysImportResult import_room_keys(
|
||||
[ByRef] string keys,
|
||||
[ByRef] string passphrase,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
[Throws=KeyImportError]
|
||||
KeysImportResult import_decrypted_room_keys(
|
||||
[ByRef] string keys,
|
||||
ProgressListener progress_listener
|
||||
);
|
||||
|
||||
[Throws=CryptoStoreError]
|
||||
boolean is_identity_verified([ByRef] string user_id);
|
||||
|
||||
record<DOMString, record<DOMString, string>> sign([ByRef] string message);
|
||||
[Throws=CryptoStoreError]
|
||||
SignatureVerification verify_backup([ByRef] string auth_data);
|
||||
};
|
||||
|
||||
dictionary PassphraseInfo {
|
||||
string private_key_salt;
|
||||
i32 private_key_iterations;
|
||||
};
|
||||
|
||||
dictionary SignatureVerification {
|
||||
SignatureState device_signature;
|
||||
SignatureState user_identity_signature;
|
||||
record<DOMString, SignatureState> other_devices_signatures;
|
||||
boolean trusted;
|
||||
};
|
||||
|
||||
enum SignatureState {
|
||||
@@ -395,12 +88,6 @@ enum SignatureState {
|
||||
"ValidAndTrusted",
|
||||
};
|
||||
|
||||
[Error]
|
||||
enum DecodeError {
|
||||
"Decode",
|
||||
"CryptoStore",
|
||||
};
|
||||
|
||||
interface BackupRecoveryKey {
|
||||
constructor();
|
||||
[Name=from_passphrase]
|
||||
@@ -412,56 +99,3 @@ interface BackupRecoveryKey {
|
||||
[Name=from_base58, Throws=DecodeError]
|
||||
constructor(string key);
|
||||
};
|
||||
|
||||
dictionary MigrationData {
|
||||
PickledAccount account;
|
||||
sequence<PickledSession> sessions;
|
||||
sequence<PickledInboundGroupSession> inbound_group_sessions;
|
||||
string? backup_version;
|
||||
string? backup_recovery_key;
|
||||
sequence<u8> pickle_key;
|
||||
CrossSigningKeyExport cross_signing;
|
||||
sequence<string> tracked_users;
|
||||
record<string, RoomSettings> room_settings;
|
||||
};
|
||||
|
||||
dictionary SessionMigrationData {
|
||||
string user_id;
|
||||
string device_id;
|
||||
string curve25519_key;
|
||||
string ed25519_key;
|
||||
sequence<PickledSession> sessions;
|
||||
sequence<PickledInboundGroupSession> inbound_group_sessions;
|
||||
sequence<u8> pickle_key;
|
||||
};
|
||||
|
||||
dictionary PickledAccount {
|
||||
string user_id;
|
||||
string device_id;
|
||||
string pickle;
|
||||
boolean shared;
|
||||
i64 uploaded_signed_key_count;
|
||||
};
|
||||
|
||||
dictionary PickledSession {
|
||||
string pickle;
|
||||
string sender_key;
|
||||
boolean created_using_fallback_key;
|
||||
string creation_time;
|
||||
string last_use_time;
|
||||
};
|
||||
|
||||
dictionary PickledInboundGroupSession {
|
||||
string pickle;
|
||||
string sender_key;
|
||||
record<DOMString, string> signing_key;
|
||||
string room_id;
|
||||
sequence<string> forwarding_chains;
|
||||
boolean imported;
|
||||
boolean backed_up;
|
||||
};
|
||||
|
||||
dictionary RoomSettings {
|
||||
EventEncryptionAlgorithm algorithm;
|
||||
boolean only_allow_trusted_devices;
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ use ruma::{
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct SignatureUploadRequest {
|
||||
pub body: String,
|
||||
}
|
||||
@@ -41,6 +42,7 @@ impl From<RustSignatureUploadRequest> for SignatureUploadRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct UploadSigningKeysRequest {
|
||||
pub master_key: String,
|
||||
pub self_signing_key: String,
|
||||
@@ -66,6 +68,7 @@ impl From<RustUploadSigningKeysRequest> for UploadSigningKeysRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct BootstrapCrossSigningResult {
|
||||
pub upload_signing_keys_request: UploadSigningKeysRequest,
|
||||
pub signature_request: SignatureUploadRequest,
|
||||
@@ -82,6 +85,7 @@ impl From<(RustUploadSigningKeysRequest, RustSignatureUploadRequest)>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum OutgoingVerificationRequest {
|
||||
ToDevice { request_id: String, event_type: String, body: String },
|
||||
InRoom { request_id: String, room_id: String, event_type: String, content: String },
|
||||
@@ -112,7 +116,7 @@ impl From<ToDeviceRequest> for OutgoingVerificationRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, uniffi::Enum)]
|
||||
pub enum Request {
|
||||
ToDevice { request_id: String, event_type: String, body: String },
|
||||
KeysUpload { request_id: String, body: String },
|
||||
@@ -221,6 +225,7 @@ pub(crate) fn response_from_string(body: &str) -> Response<Vec<u8>> {
|
||||
.expect("Can't create HTTP response")
|
||||
}
|
||||
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum RequestType {
|
||||
KeysQuery,
|
||||
KeysClaim,
|
||||
@@ -254,6 +259,7 @@ impl From<DeviceLists> for RumaDeviceLists {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct KeysImportResult {
|
||||
/// The number of room keys that were imported.
|
||||
pub imported: i64,
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::CryptoStoreError;
|
||||
|
||||
/// Enum representing cross signing identities of our own user or some other
|
||||
/// user.
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum UserIdentity {
|
||||
/// Our own user identity.
|
||||
Own {
|
||||
|
||||
@@ -77,11 +77,13 @@ impl From<RustSasState> for SasState {
|
||||
}
|
||||
|
||||
/// Enum representing the different verification flows we support.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct Verification {
|
||||
pub(crate) inner: InnerVerification,
|
||||
pub(crate) runtime: Handle,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl Verification {
|
||||
/// Try to represent the `Verification` as an `Sas` verification object,
|
||||
/// returns `None` if the verification is not a `Sas` verification.
|
||||
@@ -105,11 +107,13 @@ impl Verification {
|
||||
}
|
||||
|
||||
/// The `m.sas.v1` verification flow.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct Sas {
|
||||
pub(crate) inner: InnerSas,
|
||||
pub(crate) runtime: Handle,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl Sas {
|
||||
/// Get the user id of the other side.
|
||||
pub fn other_user_id(&self) -> String {
|
||||
@@ -170,7 +174,7 @@ impl Sas {
|
||||
/// list of cancel codes can be found in the [spec]
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
|
||||
pub fn cancel(&self, cancel_code: &str) -> Option<OutgoingVerificationRequest> {
|
||||
pub fn cancel(&self, cancel_code: String) -> Option<OutgoingVerificationRequest> {
|
||||
self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
|
||||
}
|
||||
|
||||
@@ -248,7 +252,9 @@ impl Sas {
|
||||
pub fn state(&self) -> SasState {
|
||||
self.inner.state().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Sas {
|
||||
async fn changes_listener(
|
||||
mut stream: impl Stream<Item = RustSasState> + std::marker::Unpin,
|
||||
listener: Box<dyn SasListener>,
|
||||
@@ -315,11 +321,13 @@ impl From<QrVerificationState> for QrCodeState {
|
||||
|
||||
/// The `m.qr_code.scan.v1`, `m.qr_code.show.v1`, and `m.reciprocate.v1`
|
||||
/// verification flow.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct QrCode {
|
||||
pub(crate) inner: InnerQr,
|
||||
pub(crate) runtime: Handle,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl QrCode {
|
||||
/// Get the user id of the other side.
|
||||
pub fn other_user_id(&self) -> String {
|
||||
@@ -386,7 +394,7 @@ impl QrCode {
|
||||
/// list of cancel codes can be found in the [spec]
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#mkeyverificationcancel
|
||||
pub fn cancel(&self, cancel_code: &str) -> Option<OutgoingVerificationRequest> {
|
||||
pub fn cancel(&self, cancel_code: String) -> Option<OutgoingVerificationRequest> {
|
||||
self.inner.cancel_with_code(cancel_code.into()).map(|r| r.into())
|
||||
}
|
||||
|
||||
@@ -424,7 +432,9 @@ impl QrCode {
|
||||
pub fn state(&self) -> QrCodeState {
|
||||
self.inner.state().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl QrCode {
|
||||
async fn changes_listener(
|
||||
mut stream: impl Stream<Item = QrVerificationState> + std::marker::Unpin,
|
||||
listener: Box<dyn QrCodeListener>,
|
||||
@@ -468,6 +478,7 @@ impl From<RustCancelInfo> for CancelInfo {
|
||||
}
|
||||
|
||||
/// A result type for starting SAS verifications.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct StartSasResult {
|
||||
/// The SAS verification object that got created.
|
||||
pub sas: Arc<Sas>,
|
||||
@@ -477,6 +488,7 @@ pub struct StartSasResult {
|
||||
}
|
||||
|
||||
/// A result type for scanning QR codes.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct ScanResult {
|
||||
/// The QR code verification object that got created.
|
||||
pub qr: Arc<QrCode>,
|
||||
@@ -486,6 +498,7 @@ pub struct ScanResult {
|
||||
}
|
||||
|
||||
/// A result type for requesting verifications.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct RequestVerificationResult {
|
||||
/// The verification request object that got created.
|
||||
pub verification: Arc<VerificationRequest>,
|
||||
@@ -495,6 +508,7 @@ pub struct RequestVerificationResult {
|
||||
}
|
||||
|
||||
/// A result type for confirming verifications.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct ConfirmVerificationResult {
|
||||
/// The requests that needs to be sent out to notify the other side that we
|
||||
/// confirmed the verification.
|
||||
@@ -558,11 +572,13 @@ impl From<RustVerificationRequestState> for VerificationRequestState {
|
||||
|
||||
/// The verificatoin request object which then can transition into some concrete
|
||||
/// verification method
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct VerificationRequest {
|
||||
pub(crate) inner: InnerVerificationRequest,
|
||||
pub(crate) runtime: Handle,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl VerificationRequest {
|
||||
/// The id of the other user that is participating in this verification
|
||||
/// request.
|
||||
@@ -709,7 +725,7 @@ impl VerificationRequest {
|
||||
///
|
||||
/// * `data` - The data that was extracted from the scanned QR code as an
|
||||
/// base64 encoded string, without padding.
|
||||
pub fn scan_qr_code(&self, data: &str) -> Option<ScanResult> {
|
||||
pub fn scan_qr_code(&self, data: String) -> Option<ScanResult> {
|
||||
let data = STANDARD_NO_PAD.decode(data).ok()?;
|
||||
let data = QrVerificationData::from_bytes(data).ok()?;
|
||||
|
||||
@@ -738,7 +754,9 @@ impl VerificationRequest {
|
||||
pub fn state(&self) -> VerificationRequestState {
|
||||
self.inner.state().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl VerificationRequest {
|
||||
async fn changes_listener(
|
||||
mut stream: impl Stream<Item = RustVerificationRequestState> + std::marker::Unpin,
|
||||
listener: Box<dyn VerificationRequestListener>,
|
||||
|
||||
@@ -96,8 +96,8 @@ according to [the Node.js Releases
|
||||
Page](https://nodejs.org/en/about/releases/), _and_ which are
|
||||
compatible with [NAPI v6 (Node.js
|
||||
API)](https://nodejs.org/api/n-api.html#node-api-version-matrix). It
|
||||
means that this binding will work with the following versions: 14.0.0,
|
||||
16.0.0 and 18.0.0.
|
||||
means that this binding will work with the following versions: 16.0.0,
|
||||
18.0.0, 19.0.0 and 20.0.0.
|
||||
|
||||
Once the Rust compiler, Node.js and npm are installed, you can run the
|
||||
following commands:
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"yargs-parser": "~21.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
"node": ">= 16"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "prettier --check .",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
|
||||
use napi::bindgen_prelude::{BigInt, ToNapiValue};
|
||||
use napi::bindgen_prelude::{BigInt, FromNapiValue, ToNapiValue};
|
||||
use napi_derive::*;
|
||||
|
||||
use crate::events;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Types related to events.
|
||||
|
||||
use napi::bindgen_prelude::ToNapiValue;
|
||||
use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
|
||||
use napi_derive::*;
|
||||
use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Types for [Matrix](https://matrix.org/) identifiers for devices,
|
||||
//! events, keys, rooms, servers, users and URIs.
|
||||
|
||||
use napi::bindgen_prelude::ToNapiValue;
|
||||
use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
|
||||
use napi_derive::*;
|
||||
|
||||
use crate::into_err;
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use napi::bindgen_prelude::{within_runtime_if_available, Either7, ToNapiValue};
|
||||
use napi::bindgen_prelude::{within_runtime_if_available, Either7, FromNapiValue, ToNapiValue};
|
||||
use napi_derive::*;
|
||||
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
|
||||
use serde_json::{value::RawValue, Value as JsonValue};
|
||||
|
||||
@@ -6,7 +6,7 @@ use matrix_sdk_crypto::requests::{
|
||||
KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest,
|
||||
RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest,
|
||||
};
|
||||
use napi::bindgen_prelude::{Either7, ToNapiValue};
|
||||
use napi::bindgen_prelude::{Either7, FromNapiValue, ToNapiValue};
|
||||
use napi_derive::*;
|
||||
use ruma::{
|
||||
api::client::keys::{
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
namespace matrix_sdk_ffi {};
|
||||
|
||||
|
||||
interface TaskHandle {};
|
||||
|
||||
[Error]
|
||||
interface ClientError {
|
||||
Generic(string msg);
|
||||
@@ -12,16 +9,6 @@ callback interface ClientDelegate {
|
||||
void did_receive_auth_error(boolean is_soft_logout);
|
||||
};
|
||||
|
||||
dictionary RequiredState {
|
||||
string key;
|
||||
string value;
|
||||
};
|
||||
|
||||
dictionary RoomSubscription {
|
||||
sequence<RequiredState>? required_state;
|
||||
u32? timeline_limit;
|
||||
};
|
||||
|
||||
dictionary UpdateSummary {
|
||||
sequence<string> lists;
|
||||
sequence<string> rooms;
|
||||
@@ -93,59 +80,10 @@ callback interface SlidingSyncListRoomItemsObserver {
|
||||
|
||||
interface SlidingSyncListBuilder {
|
||||
constructor();
|
||||
|
||||
[Self=ByArc]
|
||||
SlidingSyncListBuilder sync_mode(SlidingSyncMode mode);
|
||||
|
||||
[Throws=ClientError, Self=ByArc]
|
||||
SlidingSyncList build();
|
||||
};
|
||||
|
||||
interface SlidingSyncList {
|
||||
TaskHandle observe_room_list(SlidingSyncListRoomListObserver observer);
|
||||
TaskHandle observe_rooms_count(SlidingSyncListRoomsCountObserver observer);
|
||||
TaskHandle observe_state(SlidingSyncListStateObserver observer);
|
||||
};
|
||||
|
||||
interface SlidingSyncRoom {
|
||||
[Throws=ClientError]
|
||||
SlidingSyncSubscribeResult subscribe_and_add_timeline_listener(TimelineListener listener, RoomSubscription? settings);
|
||||
[Throws=ClientError]
|
||||
SlidingSyncSubscribeResult add_timeline_listener(TimelineListener listener);
|
||||
};
|
||||
|
||||
dictionary SlidingSyncSubscribeResult {
|
||||
sequence<TimelineItem> items;
|
||||
TaskHandle task_handle;
|
||||
};
|
||||
|
||||
interface SlidingSync {
|
||||
void set_observer(SlidingSyncObserver? observer);
|
||||
|
||||
[Throws=ClientError]
|
||||
void subscribe(string room_id, RoomSubscription? settings);
|
||||
[Throws=ClientError]
|
||||
void unsubscribe(string room_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
SlidingSyncRoom? get_room(string room_id);
|
||||
[Throws=ClientError]
|
||||
sequence<SlidingSyncRoom?> get_rooms(sequence<string> room_ids);
|
||||
};
|
||||
|
||||
interface ClientBuilder {
|
||||
constructor();
|
||||
|
||||
[Throws=ClientError, Self=ByArc]
|
||||
Client build();
|
||||
};
|
||||
|
||||
interface SlidingSyncBuilder {
|
||||
[Throws=ClientError, Self=ByArc]
|
||||
SlidingSyncBuilder homeserver(string url);
|
||||
|
||||
[Throws=ClientError, Self=ByArc]
|
||||
SlidingSync build();
|
||||
};
|
||||
|
||||
dictionary CreateRoomParameters {
|
||||
@@ -181,107 +119,8 @@ enum RoomPreset {
|
||||
"TrustedPrivateChat",
|
||||
};
|
||||
|
||||
interface Client {
|
||||
void set_delegate(ClientDelegate? delegate);
|
||||
|
||||
[Throws=ClientError]
|
||||
void login(string username, string password, string? initial_device_name, string? device_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
MediaFileHandle get_media_file(MediaSource source, string mime_type);
|
||||
};
|
||||
|
||||
interface MediaFileHandle {
|
||||
string path();
|
||||
};
|
||||
|
||||
enum MembershipState {
|
||||
/// The user is banned.
|
||||
"Ban",
|
||||
/// The user has been invited.
|
||||
"Invite",
|
||||
/// The user has joined.
|
||||
"Join",
|
||||
/// The user has requested to join.
|
||||
"Knock",
|
||||
/// The user has left.
|
||||
"Leave",
|
||||
};
|
||||
|
||||
interface RoomMember { };
|
||||
|
||||
interface Room {
|
||||
[Throws=ClientError]
|
||||
string display_name();
|
||||
|
||||
[Throws=ClientError]
|
||||
boolean is_encrypted();
|
||||
|
||||
[Throws=ClientError]
|
||||
sequence<RoomMember> members();
|
||||
|
||||
[Throws=ClientError]
|
||||
string? member_avatar_url(string user_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
string? member_display_name(string user_id);
|
||||
|
||||
sequence<TimelineItem> add_timeline_listener(TimelineListener listener);
|
||||
|
||||
// Loads older messages into the timeline.
|
||||
//
|
||||
// Raises an exception if there are no timeline listeners.
|
||||
[Throws=ClientError]
|
||||
void paginate_backwards(PaginationOptions opts);
|
||||
|
||||
[Throws=ClientError]
|
||||
void send_read_receipt(string event_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
void send_read_marker(string fully_read_event_id, string? read_receipt_event_id);
|
||||
|
||||
void send(RoomMessageEventContent msg, string? txn_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
void send_reply(string msg, string in_reply_to_event_id, string? txn_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
void edit(string new_msg, string original_event_id, string? txn_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
void redact(string event_id, string? reason, string? txn_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
void report_content(string event_id, i32? score, string? reason);
|
||||
|
||||
[Throws=ClientError]
|
||||
void ignore_user(string user_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
void send_reaction(string event_id, string key);
|
||||
|
||||
[Throws=ClientError]
|
||||
void leave();
|
||||
|
||||
[Throws=ClientError]
|
||||
void reject_invitation();
|
||||
|
||||
[Throws=ClientError]
|
||||
void accept_invitation();
|
||||
|
||||
[Throws=ClientError]
|
||||
void set_topic(string topic);
|
||||
|
||||
[Throws=ClientError]
|
||||
void upload_avatar(string mime_type, sequence<u8> data);
|
||||
|
||||
[Throws=ClientError]
|
||||
void remove_avatar();
|
||||
|
||||
[Throws=ClientError]
|
||||
void invite_user_by_id(string user_id);
|
||||
};
|
||||
|
||||
callback interface TimelineListener {
|
||||
void on_update(TimelineDiff update);
|
||||
};
|
||||
@@ -311,19 +150,8 @@ interface AuthenticationService {
|
||||
constructor(string base_path, string? passphrase, string? custom_sliding_sync_proxy);
|
||||
};
|
||||
|
||||
dictionary NotificationItem {
|
||||
TimelineItem item;
|
||||
string title;
|
||||
string? subtitle;
|
||||
boolean is_noisy;
|
||||
string? avatar_url;
|
||||
};
|
||||
|
||||
interface NotificationService {
|
||||
constructor(string base_path, string user_id);
|
||||
|
||||
[Throws=ClientError]
|
||||
NotificationItem? get_notification_item(string room_id, string event_id);
|
||||
};
|
||||
|
||||
interface SessionVerificationEmoji {};
|
||||
@@ -337,25 +165,6 @@ callback interface SessionVerificationControllerDelegate {
|
||||
void did_finish();
|
||||
};
|
||||
|
||||
interface SessionVerificationController {
|
||||
void set_delegate(SessionVerificationControllerDelegate? delegate);
|
||||
|
||||
[Throws=ClientError]
|
||||
void request_verification();
|
||||
|
||||
[Throws=ClientError]
|
||||
void start_sas_verification();
|
||||
|
||||
[Throws=ClientError]
|
||||
void approve_verification();
|
||||
|
||||
[Throws=ClientError]
|
||||
void decline_verification();
|
||||
|
||||
[Throws=ClientError]
|
||||
void cancel_verification();
|
||||
};
|
||||
|
||||
interface Span {
|
||||
constructor(string file, u32 line, u32 column, LogLevel level, string target, string name);
|
||||
[Name=current]
|
||||
|
||||
@@ -9,6 +9,7 @@ use url::Url;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use super::{client::Client, client_builder::ClientBuilder, RUNTIME};
|
||||
use crate::error::ClientError;
|
||||
|
||||
pub struct AuthenticationService {
|
||||
base_path: String,
|
||||
@@ -144,7 +145,7 @@ impl AuthenticationService {
|
||||
}
|
||||
}
|
||||
|
||||
let client = builder.build().or_else(|e| {
|
||||
let client = builder.build_inner().or_else(|e| {
|
||||
if !server_name_or_homeserver_url.starts_with("http://")
|
||||
&& !server_name_or_homeserver_url.starts_with("https://")
|
||||
{
|
||||
@@ -153,7 +154,7 @@ impl AuthenticationService {
|
||||
// When discovery fails, fallback to the homeserver URL if supplied.
|
||||
let mut builder = Arc::new(ClientBuilder::new()).base_path(self.base_path.clone());
|
||||
builder = builder.homeserver_url(server_name_or_homeserver_url);
|
||||
builder.build()
|
||||
builder.build_inner()
|
||||
})?;
|
||||
|
||||
let details = RUNTIME.block_on(self.details_from_client(&client))?;
|
||||
@@ -186,7 +187,9 @@ impl AuthenticationService {
|
||||
|
||||
// Login and ask the server for the full user ID as this could be different from
|
||||
// the username that was entered.
|
||||
client.login(username, password, initial_device_name, device_id)?;
|
||||
client.login(username, password, initial_device_name, device_id).map_err(|e| match e {
|
||||
ClientError::Generic { msg } => AuthenticationError::Generic { message: msg },
|
||||
})?;
|
||||
let whoami = client.whoami()?;
|
||||
|
||||
// Create a new client to setup the store path now the user ID is known.
|
||||
@@ -208,7 +211,7 @@ impl AuthenticationService {
|
||||
.homeserver_url(homeserver_url)
|
||||
.sliding_sync_proxy(sliding_sync_proxy)
|
||||
.username(whoami.user_id.to_string())
|
||||
.build()?;
|
||||
.build_inner()?;
|
||||
|
||||
// Restore the client using the session from the login request.
|
||||
client.restore_session_inner(session)?;
|
||||
@@ -260,7 +263,7 @@ impl AuthenticationService {
|
||||
.passphrase(self.passphrase.clone())
|
||||
.homeserver_url(homeserver_url)
|
||||
.username(whoami.user_id.to_string())
|
||||
.build()?;
|
||||
.build_inner()?;
|
||||
|
||||
// Restore the client using the session.
|
||||
client.restore_session_inner(session)?;
|
||||
|
||||
@@ -105,7 +105,7 @@ pub trait ClientDelegate: Sync + Send {
|
||||
fn did_receive_auth_error(&self, is_soft_logout: bool);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, uniffi::Object)]
|
||||
pub struct Client {
|
||||
pub(crate) client: MatrixClient,
|
||||
delegate: Arc<RwLock<Option<Box<dyn ClientDelegate>>>>,
|
||||
@@ -161,7 +161,10 @@ impl Client {
|
||||
|
||||
client
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl Client {
|
||||
/// Login using a username and password.
|
||||
pub fn login(
|
||||
&self,
|
||||
@@ -169,7 +172,7 @@ impl Client {
|
||||
password: String,
|
||||
initial_device_name: Option<String>,
|
||||
device_id: Option<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> Result<(), ClientError> {
|
||||
RUNTIME.block_on(async move {
|
||||
let mut builder = self.client.login_username(&username, &password);
|
||||
if let Some(initial_device_name) = initial_device_name.as_ref() {
|
||||
@@ -187,7 +190,7 @@ impl Client {
|
||||
&self,
|
||||
media_source: Arc<MediaSource>,
|
||||
mime_type: String,
|
||||
) -> anyhow::Result<Arc<MediaFileHandle>> {
|
||||
) -> Result<Arc<MediaFileHandle>, ClientError> {
|
||||
let client = self.client.clone();
|
||||
let source = (*media_source).clone();
|
||||
let mime_type: mime::Mime = mime_type.parse()?;
|
||||
@@ -205,10 +208,7 @@ impl Client {
|
||||
Ok(Arc::new(MediaFileHandle { inner: handle }))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl Client {
|
||||
/// Restores the client from a `Session`.
|
||||
pub fn restore_session(&self, session: Session) -> Result<(), ClientError> {
|
||||
let Session {
|
||||
@@ -241,17 +241,13 @@ impl Client {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_delegate(&self, delegate: Option<Box<dyn ClientDelegate>>) {
|
||||
*self.delegate.write().unwrap() = delegate;
|
||||
}
|
||||
|
||||
pub async fn async_homeserver(&self) -> String {
|
||||
pub(crate) async fn async_homeserver(&self) -> String {
|
||||
self.client.homeserver().await.to_string()
|
||||
}
|
||||
|
||||
/// The OIDC Provider that is trusted by the homeserver. `None` when
|
||||
/// not configured.
|
||||
pub async fn authentication_issuer(&self) -> Option<String> {
|
||||
pub(crate) async fn authentication_issuer(&self) -> Option<String> {
|
||||
self.client.authentication_issuer().await
|
||||
}
|
||||
|
||||
@@ -268,7 +264,7 @@ impl Client {
|
||||
}
|
||||
|
||||
/// Whether or not the client's homeserver supports the password login flow.
|
||||
pub async fn supports_password_login(&self) -> anyhow::Result<bool> {
|
||||
pub(crate) async fn supports_password_login(&self) -> anyhow::Result<bool> {
|
||||
let login_types = self.client.get_login_types().await?;
|
||||
let supports_password = login_types
|
||||
.flows
|
||||
@@ -278,7 +274,7 @@ impl Client {
|
||||
}
|
||||
|
||||
/// Gets information about the owner of a given access token.
|
||||
pub fn whoami(&self) -> anyhow::Result<whoami::v3::Response> {
|
||||
pub(crate) fn whoami(&self) -> anyhow::Result<whoami::v3::Response> {
|
||||
RUNTIME
|
||||
.block_on(async move { self.client.whoami().await.map_err(|e| anyhow!(e.to_string())) })
|
||||
}
|
||||
@@ -286,6 +282,10 @@ impl Client {
|
||||
|
||||
#[uniffi::export]
|
||||
impl Client {
|
||||
pub fn set_delegate(&self, delegate: Option<Box<dyn ClientDelegate>>) {
|
||||
*self.delegate.write().unwrap() = delegate;
|
||||
}
|
||||
|
||||
pub fn session(&self) -> Result<Session, ClientError> {
|
||||
RUNTIME.block_on(async move {
|
||||
let matrix_sdk::Session { access_token, refresh_token, user_id, device_id } =
|
||||
@@ -722,10 +722,12 @@ fn gen_transaction_id() -> String {
|
||||
|
||||
/// A file handle that takes ownership of a media file on disk. When the handle
|
||||
/// is dropped, the file will be removed from the disk.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct MediaFileHandle {
|
||||
inner: SdkMediaFileHandle,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl MediaFileHandle {
|
||||
/// Get the media file's path.
|
||||
pub fn path(&self) -> String {
|
||||
|
||||
@@ -13,7 +13,7 @@ use sanitize_filename_reader_friendly::sanitize;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use super::{client::Client, RUNTIME};
|
||||
use crate::helpers::unwrap_or_clone_arc;
|
||||
use crate::{error::ClientError, helpers::unwrap_or_clone_arc};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientBuilder {
|
||||
@@ -28,6 +28,22 @@ pub struct ClientBuilder {
|
||||
inner: MatrixClientBuilder,
|
||||
}
|
||||
|
||||
impl ClientBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base_path: None,
|
||||
username: None,
|
||||
server_name: None,
|
||||
homeserver_url: None,
|
||||
server_versions: None,
|
||||
passphrase: Zeroizing::new(None),
|
||||
user_agent: None,
|
||||
sliding_sync_proxy: None,
|
||||
inner: MatrixClient::builder(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl ClientBuilder {
|
||||
pub fn base_path(self: Arc<Self>, path: String) -> Arc<Self> {
|
||||
@@ -77,24 +93,14 @@ impl ClientBuilder {
|
||||
builder.sliding_sync_proxy = sliding_sync_proxy;
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn build(self: Arc<Self>) -> Result<Arc<Client>, ClientError> {
|
||||
Ok(self.build_inner()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base_path: None,
|
||||
username: None,
|
||||
server_name: None,
|
||||
homeserver_url: None,
|
||||
server_versions: None,
|
||||
passphrase: Zeroizing::new(None),
|
||||
user_agent: None,
|
||||
sliding_sync_proxy: None,
|
||||
inner: MatrixClient::builder(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<Client>> {
|
||||
pub(crate) fn build_inner(self: Arc<Self>) -> anyhow::Result<Arc<Client>> {
|
||||
let builder = unwrap_or_clone_arc(self);
|
||||
let mut inner_builder = builder.inner;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use matrix_sdk::{self, encryption::CryptoStoreError, HttpError, IdParseError};
|
||||
use matrix_sdk::{self, encryption::CryptoStoreError, HttpError, IdParseError, StoreError};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientError {
|
||||
@@ -18,6 +18,12 @@ impl From<matrix_sdk::Error> for ClientError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StoreError> for ClientError {
|
||||
fn from(e: StoreError) -> Self {
|
||||
anyhow::Error::from(e).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CryptoStoreError> for ClientError {
|
||||
fn from(e: CryptoStoreError) -> Self {
|
||||
anyhow::Error::from(e).into()
|
||||
@@ -41,3 +47,15 @@ impl From<serde_json::Error> for ClientError {
|
||||
anyhow::Error::from(e).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::ParseError> for ClientError {
|
||||
fn from(e: url::ParseError) -> Self {
|
||||
anyhow::Error::from(e).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mime::FromStrError> for ClientError {
|
||||
fn from(e: mime::FromStrError) -> Self {
|
||||
anyhow::Error::from(e).into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::TimelineItem;
|
||||
use crate::{error::ClientError, TimelineItem};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct NotificationService {
|
||||
@@ -9,6 +9,7 @@ pub struct NotificationService {
|
||||
}
|
||||
|
||||
/// Notification item struct.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct NotificationItem {
|
||||
/// Actual timeline item for the event sent.
|
||||
pub item: Arc<TimelineItem>,
|
||||
@@ -32,7 +33,10 @@ impl NotificationService {
|
||||
pub fn new(base_path: String, user_id: String) -> Self {
|
||||
Self { base_path, user_id }
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl NotificationService {
|
||||
/// Get notification item for a given `room_id `and `event_id`.
|
||||
///
|
||||
/// Returns `None` if this notification should not be displayed to the user.
|
||||
@@ -40,7 +44,7 @@ impl NotificationService {
|
||||
&self,
|
||||
_room_id: String,
|
||||
_event_id: String,
|
||||
) -> anyhow::Result<Option<NotificationItem>> {
|
||||
) -> Result<Option<NotificationItem>, ClientError> {
|
||||
// TODO: Implement
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -98,7 +98,13 @@ pub fn create_otlp_tracer(
|
||||
fn setup_tracing_helper(configuration: String, colors: bool) {
|
||||
tracing_subscriber::registry()
|
||||
.with(EnvFilter::new(configuration))
|
||||
.with(fmt::layer().with_ansi(colors).with_writer(io::stderr))
|
||||
.with(
|
||||
fmt::layer()
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_ansi(colors)
|
||||
.with_writer(io::stderr),
|
||||
)
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -116,7 +122,13 @@ fn setup_otlp_tracing_helper(
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(EnvFilter::new(configuration))
|
||||
.with(fmt::layer().with_ansi(colors).with_writer(io::stderr))
|
||||
.with(
|
||||
fmt::layer()
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_ansi(colors)
|
||||
.with_writer(io::stderr),
|
||||
)
|
||||
.with(otlp_layer)
|
||||
.init();
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use futures_util::StreamExt;
|
||||
use matrix_sdk::{
|
||||
room::{timeline::Timeline, Receipts, Room as SdkRoom},
|
||||
@@ -19,12 +19,13 @@ use matrix_sdk::{
|
||||
},
|
||||
EventId, UserId,
|
||||
},
|
||||
RoomMemberships,
|
||||
};
|
||||
use mime::Mime;
|
||||
use tracing::error;
|
||||
|
||||
use super::RUNTIME;
|
||||
use crate::{RoomMember, TimelineDiff, TimelineItem, TimelineListener};
|
||||
use crate::{error::ClientError, RoomMember, TimelineDiff, TimelineItem, TimelineListener};
|
||||
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum Membership {
|
||||
@@ -35,11 +36,22 @@ pub enum Membership {
|
||||
|
||||
pub(crate) type TimelineLock = Arc<RwLock<Option<Arc<Timeline>>>>;
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct Room {
|
||||
room: SdkRoom,
|
||||
timeline: TimelineLock,
|
||||
}
|
||||
|
||||
impl Room {
|
||||
pub(crate) fn new(room: SdkRoom) -> Self {
|
||||
Room { room, timeline: Default::default() }
|
||||
}
|
||||
|
||||
pub(crate) fn with_timeline(room: SdkRoom, timeline: TimelineLock) -> Self {
|
||||
Room { room, timeline }
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl Room {
|
||||
pub fn id(&self) -> String {
|
||||
@@ -139,22 +151,13 @@ impl Room {
|
||||
timeline.fetch_members().await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Room {
|
||||
pub fn new(room: SdkRoom) -> Self {
|
||||
Room { room, timeline: Default::default() }
|
||||
}
|
||||
pub fn with_timeline(room: SdkRoom, timeline: TimelineLock) -> Self {
|
||||
Room { room, timeline }
|
||||
}
|
||||
|
||||
pub fn display_name(&self) -> Result<String> {
|
||||
pub fn display_name(&self) -> Result<String, ClientError> {
|
||||
let r = self.room.clone();
|
||||
RUNTIME.block_on(async move { Ok(r.display_name().await?.to_string()) })
|
||||
}
|
||||
|
||||
pub fn is_encrypted(&self) -> Result<bool> {
|
||||
pub fn is_encrypted(&self) -> Result<bool, ClientError> {
|
||||
let room = self.room.clone();
|
||||
RUNTIME.block_on(async move {
|
||||
let is_encrypted = room.is_encrypted().await?;
|
||||
@@ -162,11 +165,11 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn members(&self) -> Result<Vec<Arc<RoomMember>>> {
|
||||
pub fn members(&self) -> Result<Vec<Arc<RoomMember>>, ClientError> {
|
||||
let room = self.room.clone();
|
||||
RUNTIME.block_on(async move {
|
||||
let members = room
|
||||
.members()
|
||||
.members(RoomMemberships::empty())
|
||||
.await?
|
||||
.iter()
|
||||
.map(|m| Arc::new(RoomMember::new(m.clone())))
|
||||
@@ -175,7 +178,7 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn member_avatar_url(&self, user_id: String) -> Result<Option<String>> {
|
||||
pub fn member_avatar_url(&self, user_id: String) -> Result<Option<String>, ClientError> {
|
||||
let room = self.room.clone();
|
||||
let user_id = user_id;
|
||||
RUNTIME.block_on(async move {
|
||||
@@ -186,7 +189,7 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn member_display_name(&self, user_id: String) -> Result<Option<String>> {
|
||||
pub fn member_display_name(&self, user_id: String) -> Result<Option<String>, ClientError> {
|
||||
let room = self.room.clone();
|
||||
let user_id = user_id;
|
||||
RUNTIME.block_on(async move {
|
||||
@@ -233,18 +236,25 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn paginate_backwards(&self, opts: PaginationOptions) -> Result<()> {
|
||||
/// Loads older messages into the timeline.
|
||||
///
|
||||
/// Raises an exception if there are no timeline listeners.
|
||||
pub fn paginate_backwards(&self, opts: PaginationOptions) -> Result<(), ClientError> {
|
||||
if let Some(timeline) = &*self.timeline.read().unwrap() {
|
||||
RUNTIME.block_on(async move { Ok(timeline.paginate_backwards(opts.into()).await?) })
|
||||
} else {
|
||||
bail!("No timeline listeners registered, can't paginate");
|
||||
Err(anyhow!("No timeline listeners registered, can't paginate").into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_read_receipt(&self, event_id: String) -> Result<()> {
|
||||
pub fn send_read_receipt(&self, event_id: String) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Joined(j) => j.clone(),
|
||||
_ => bail!("Can't send read receipts to room that isn't in joined state"),
|
||||
_ => {
|
||||
return Err(
|
||||
anyhow!("Can't send read receipts to room that isn't in joined state").into()
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let event_id = EventId::parse(event_id)?;
|
||||
@@ -260,10 +270,14 @@ impl Room {
|
||||
&self,
|
||||
fully_read_event_id: String,
|
||||
read_receipt_event_id: Option<String>,
|
||||
) -> Result<()> {
|
||||
) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Joined(j) => j.clone(),
|
||||
_ => bail!("Can't send read markers to room that isn't in joined state"),
|
||||
_ => {
|
||||
return Err(
|
||||
anyhow!("Can't send read markers to room that isn't in joined state").into()
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let fully_read =
|
||||
@@ -300,15 +314,15 @@ impl Room {
|
||||
msg: String,
|
||||
in_reply_to_event_id: String,
|
||||
txn_id: Option<String>,
|
||||
) -> Result<()> {
|
||||
) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Joined(j) => j.clone(),
|
||||
_ => bail!("Can't send to a room that isn't in joined state"),
|
||||
_ => return Err(anyhow!("Can't send to a room that isn't in joined state").into()),
|
||||
};
|
||||
|
||||
let timeline = match &*self.timeline.read().unwrap() {
|
||||
Some(t) => Arc::clone(t),
|
||||
None => bail!("Timeline not set up, can't send message"),
|
||||
None => return Err(anyhow!("Timeline not set up, can't send message").into()),
|
||||
};
|
||||
|
||||
let event_id: &EventId =
|
||||
@@ -342,15 +356,15 @@ impl Room {
|
||||
new_msg: String,
|
||||
original_event_id: String,
|
||||
txn_id: Option<String>,
|
||||
) -> Result<()> {
|
||||
) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Joined(j) => j.clone(),
|
||||
_ => bail!("Can't send to a room that isn't in joined state"),
|
||||
_ => return Err(anyhow!("Can't send to a room that isn't in joined state").into()),
|
||||
};
|
||||
|
||||
let timeline = match &*self.timeline.read().unwrap() {
|
||||
Some(t) => Arc::clone(t),
|
||||
None => bail!("Timeline not set up, can't send message"),
|
||||
None => return Err(anyhow!("Timeline not set up, can't send message").into()),
|
||||
};
|
||||
|
||||
let event_id: &EventId =
|
||||
@@ -365,7 +379,7 @@ impl Room {
|
||||
.context("Couldn't deserialise event")?;
|
||||
|
||||
if self.own_user_id() != event_content.sender() {
|
||||
bail!("Can't edit an event not sent by own user")
|
||||
bail!("Can't edit an event not sent by own user");
|
||||
}
|
||||
|
||||
let replacement = Replacement::new(
|
||||
@@ -399,10 +413,10 @@ impl Room {
|
||||
event_id: String,
|
||||
reason: Option<String>,
|
||||
txn_id: Option<String>,
|
||||
) -> Result<()> {
|
||||
) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Joined(j) => j.clone(),
|
||||
_ => bail!("Can't redact in a room that isn't in joined state"),
|
||||
_ => return Err(anyhow!("Can't redact in a room that isn't in joined state").into()),
|
||||
};
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
@@ -412,10 +426,14 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send_reaction(&self, event_id: String, key: String) -> Result<()> {
|
||||
pub fn send_reaction(&self, event_id: String, key: String) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Joined(j) => j.clone(),
|
||||
_ => bail!("Can't send reaction in a room that isn't in joined state"),
|
||||
_ => {
|
||||
return Err(
|
||||
anyhow!("Can't send reaction in a room that isn't in joined state").into()
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
@@ -440,7 +458,7 @@ impl Room {
|
||||
event_id: String,
|
||||
score: Option<i32>,
|
||||
reason: Option<String>,
|
||||
) -> Result<()> {
|
||||
) -> Result<(), ClientError> {
|
||||
let int_score = score.map(|value| value.into());
|
||||
RUNTIME.block_on(async move {
|
||||
let event_id = EventId::parse(event_id)?;
|
||||
@@ -465,7 +483,7 @@ impl Room {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event_id` - The ID of the user to ignore.
|
||||
pub fn ignore_user(&self, user_id: String) -> Result<()> {
|
||||
pub fn ignore_user(&self, user_id: String) -> Result<(), ClientError> {
|
||||
RUNTIME.block_on(async move {
|
||||
let user_id = UserId::parse(user_id)?;
|
||||
self.client().account().ignore_user(&user_id).await?;
|
||||
@@ -476,10 +494,10 @@ impl Room {
|
||||
/// Leaves the joined room.
|
||||
///
|
||||
/// Will throw an error if used on an room that isn't in a joined state
|
||||
pub fn leave(&self) -> Result<()> {
|
||||
pub fn leave(&self) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Joined(j) => j.clone(),
|
||||
_ => bail!("Can't leave a room that isn't in joined state"),
|
||||
_ => return Err(anyhow!("Can't leave a room that isn't in joined state").into()),
|
||||
};
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
@@ -491,10 +509,15 @@ impl Room {
|
||||
/// Rejects the invitation for the invited room.
|
||||
///
|
||||
/// Will throw an error if used on an room that isn't in an invited state
|
||||
pub fn reject_invitation(&self) -> Result<()> {
|
||||
pub fn reject_invitation(&self) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Invited(i) => i.clone(),
|
||||
_ => bail!("Can't reject an invite for a room that isn't in invited state"),
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"Can't reject an invite for a room that isn't in invited state"
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
@@ -506,10 +529,15 @@ impl Room {
|
||||
/// Accepts the invitation for the invited room.
|
||||
///
|
||||
/// Will throw an error if used on an room that isn't in an invited state
|
||||
pub fn accept_invitation(&self) -> Result<()> {
|
||||
pub fn accept_invitation(&self) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Invited(i) => i.clone(),
|
||||
_ => bail!("Can't accept an invite for a room that isn't in invited state"),
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"Can't accept an invite for a room that isn't in invited state"
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
@@ -519,10 +547,12 @@ impl Room {
|
||||
}
|
||||
|
||||
/// Sets a new topic in the room.
|
||||
pub fn set_topic(&self, topic: String) -> Result<()> {
|
||||
pub fn set_topic(&self, topic: String) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Joined(j) => j.clone(),
|
||||
_ => bail!("Can't set a topic in a room that isn't in joined state"),
|
||||
_ => {
|
||||
return Err(anyhow!("Can't set a topic in a room that isn't in joined state").into())
|
||||
}
|
||||
};
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
@@ -543,10 +573,14 @@ impl Room {
|
||||
/// image/jpeg
|
||||
/// * `data` - The raw data that will be uploaded to the homeserver's
|
||||
/// content repository
|
||||
pub fn upload_avatar(&self, mime_type: String, data: Vec<u8>) -> Result<()> {
|
||||
pub fn upload_avatar(&self, mime_type: String, data: Vec<u8>) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Joined(j) => j.clone(),
|
||||
_ => bail!("Can't set a avatar in a room that isn't in joined state"),
|
||||
_ => {
|
||||
return Err(
|
||||
anyhow!("Can't set a avatar in a room that isn't in joined state").into()
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
@@ -558,10 +592,14 @@ impl Room {
|
||||
}
|
||||
|
||||
/// Removes the current room avatar
|
||||
pub fn remove_avatar(&self) -> Result<()> {
|
||||
pub fn remove_avatar(&self) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Joined(j) => j.clone(),
|
||||
_ => bail!("Can't remove a avatar in a room that isn't in joined state"),
|
||||
_ => {
|
||||
return Err(
|
||||
anyhow!("Can't remove a avatar in a room that isn't in joined state").into()
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
@@ -570,10 +608,10 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn invite_user_by_id(&self, user_id: String) -> Result<()> {
|
||||
pub fn invite_user_by_id(&self, user_id: String) -> Result<(), ClientError> {
|
||||
let room = match &self.room {
|
||||
SdkRoom::Joined(joined_room) => joined_room.clone(),
|
||||
_ => bail!("Can't invite user to room that isn't in joined state"),
|
||||
_ => return Err(anyhow!("Can't invite user to room that isn't in joined state").into()),
|
||||
};
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
@@ -583,6 +621,22 @@ impl Room {
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fetch_event_details(&self, event_id: String) -> Result<(), ClientError> {
|
||||
let timeline = self
|
||||
.timeline
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.context("Timeline not set up, can't fetch event details")?
|
||||
.clone();
|
||||
|
||||
RUNTIME.block_on(async move {
|
||||
let event_id = <&EventId>::try_from(event_id.as_str())?;
|
||||
timeline.fetch_event_details(event_id).await.context("Fetching event details")?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Room {
|
||||
|
||||
@@ -3,7 +3,7 @@ use matrix_sdk::room::RoomMember as SdkRoomMember;
|
||||
use super::RUNTIME;
|
||||
use crate::ClientError;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, uniffi::Enum)]
|
||||
pub enum MembershipState {
|
||||
/// The user is banned.
|
||||
Ban,
|
||||
|
||||
@@ -7,13 +7,11 @@ use matrix_sdk::{
|
||||
verification::{SasState, SasVerification, VerificationRequest},
|
||||
Encryption,
|
||||
},
|
||||
ruma::{
|
||||
events::{key::verification::VerificationMethod, AnyToDeviceEvent},
|
||||
serde::Raw,
|
||||
},
|
||||
ruma::events::{key::verification::VerificationMethod, AnyToDeviceEvent},
|
||||
};
|
||||
|
||||
use super::RUNTIME;
|
||||
use crate::error::ClientError;
|
||||
|
||||
pub struct SessionVerificationEmoji {
|
||||
symbol: String,
|
||||
@@ -42,7 +40,7 @@ pub trait SessionVerificationControllerDelegate: Sync + Send {
|
||||
|
||||
pub type Delegate = Arc<RwLock<Option<Box<dyn SessionVerificationControllerDelegate>>>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, uniffi::Object)]
|
||||
pub struct SessionVerificationController {
|
||||
encryption: Encryption,
|
||||
user_identity: UserIdentity,
|
||||
@@ -56,35 +54,26 @@ impl SessionVerificationController {
|
||||
pub fn is_verified(&self) -> bool {
|
||||
self.user_identity.is_verified()
|
||||
}
|
||||
}
|
||||
|
||||
impl SessionVerificationController {
|
||||
pub fn new(encryption: Encryption, user_identity: UserIdentity) -> Self {
|
||||
SessionVerificationController {
|
||||
encryption,
|
||||
user_identity,
|
||||
delegate: Arc::new(RwLock::new(None)),
|
||||
verification_request: Arc::new(RwLock::new(None)),
|
||||
sas_verification: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_delegate(&self, delegate: Option<Box<dyn SessionVerificationControllerDelegate>>) {
|
||||
*self.delegate.write().unwrap() = delegate;
|
||||
}
|
||||
|
||||
pub fn request_verification(&self) -> anyhow::Result<()> {
|
||||
pub fn request_verification(&self) -> Result<(), ClientError> {
|
||||
RUNTIME.block_on(async move {
|
||||
let methods = vec![VerificationMethod::SasV1];
|
||||
let verification_request =
|
||||
self.user_identity.request_verification_with_methods(methods).await?;
|
||||
let verification_request = self
|
||||
.user_identity
|
||||
.request_verification_with_methods(methods)
|
||||
.await
|
||||
.map_err(anyhow::Error::from)?;
|
||||
*self.verification_request.write().unwrap() = Some(verification_request);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_sas_verification(&self) -> anyhow::Result<()> {
|
||||
pub fn start_sas_verification(&self) -> Result<(), ClientError> {
|
||||
RUNTIME.block_on(async move {
|
||||
let verification_request = self.verification_request.read().unwrap().clone();
|
||||
|
||||
@@ -112,7 +101,7 @@ impl SessionVerificationController {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn approve_verification(&self) -> anyhow::Result<()> {
|
||||
pub fn approve_verification(&self) -> Result<(), ClientError> {
|
||||
RUNTIME.block_on(async move {
|
||||
let sas_verification = self.sas_verification.read().unwrap().clone();
|
||||
if let Some(sas_verification) = sas_verification {
|
||||
@@ -123,7 +112,7 @@ impl SessionVerificationController {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decline_verification(&self) -> anyhow::Result<()> {
|
||||
pub fn decline_verification(&self) -> Result<(), ClientError> {
|
||||
RUNTIME.block_on(async move {
|
||||
let sas_verification = self.sas_verification.read().unwrap().clone();
|
||||
if let Some(sas_verification) = sas_verification {
|
||||
@@ -134,7 +123,7 @@ impl SessionVerificationController {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cancel_verification(&self) -> anyhow::Result<()> {
|
||||
pub fn cancel_verification(&self) -> Result<(), ClientError> {
|
||||
RUNTIME.block_on(async move {
|
||||
let verification_request = self.verification_request.read().unwrap().clone();
|
||||
if let Some(verification) = verification_request {
|
||||
@@ -144,8 +133,20 @@ impl SessionVerificationController {
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn process_to_device_message(&self, event: AnyToDeviceEvent) {
|
||||
impl SessionVerificationController {
|
||||
pub(crate) fn new(encryption: Encryption, user_identity: UserIdentity) -> Self {
|
||||
SessionVerificationController {
|
||||
encryption,
|
||||
user_identity,
|
||||
delegate: Arc::new(RwLock::new(None)),
|
||||
verification_request: Arc::new(RwLock::new(None)),
|
||||
sas_verification: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn process_to_device_message(&self, event: AnyToDeviceEvent) {
|
||||
match event {
|
||||
// TODO: Use the changes stream for this as well once we expose
|
||||
// VerificationRequest::changes() in the main crate.
|
||||
@@ -190,12 +191,6 @@ impl SessionVerificationController {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn process_to_device_messages(&self, to_device_events: Vec<Raw<AnyToDeviceEvent>>) {
|
||||
for event in to_device_events.into_iter().filter_map(|e| e.deserialize().ok()) {
|
||||
self.process_to_device_message(event).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn is_transaction_id_valid(&self, transaction_id: String) -> bool {
|
||||
match &*self.verification_request.read().unwrap() {
|
||||
Some(verification) => verification.flow_id() == transaction_id,
|
||||
|
||||
@@ -20,12 +20,13 @@ use tracing::{debug, error, warn};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
helpers::unwrap_or_clone_arc, room::TimelineLock, Client, EventTimelineItem, Room,
|
||||
TimelineDiff, TimelineItem, TimelineListener, RUNTIME,
|
||||
error::ClientError, helpers::unwrap_or_clone_arc, room::TimelineLock, Client,
|
||||
EventTimelineItem, Room, TimelineDiff, TimelineItem, TimelineListener, RUNTIME,
|
||||
};
|
||||
|
||||
type TaskHandleFinalizer = Box<dyn FnOnce() + Send + Sync>;
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct TaskHandle {
|
||||
handle: JoinHandle<()>,
|
||||
finalizer: RwLock<Option<TaskHandleFinalizer>>,
|
||||
@@ -154,6 +155,7 @@ impl From<matrix_sdk::sliding_sync::Error> for SlidingSyncError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct SlidingSyncRoom {
|
||||
inner: matrix_sdk::SlidingSyncRoom,
|
||||
timeline: TimelineLock,
|
||||
@@ -206,13 +208,11 @@ impl SlidingSyncRoom {
|
||||
let item = RUNTIME.block_on(self.inner.latest_event())?;
|
||||
Some(Arc::new(EventTimelineItem(item)))
|
||||
}
|
||||
}
|
||||
|
||||
impl SlidingSyncRoom {
|
||||
pub fn add_timeline_listener(
|
||||
&self,
|
||||
listener: Box<dyn TimelineListener>,
|
||||
) -> anyhow::Result<SlidingSyncSubscribeResult> {
|
||||
) -> Result<SlidingSyncSubscribeResult, ClientError> {
|
||||
let (items, stoppable_spawn) = self.add_timeline_listener_inner(listener)?;
|
||||
|
||||
Ok(SlidingSyncSubscribeResult { items, task_handle: Arc::new(stoppable_spawn) })
|
||||
@@ -222,7 +222,7 @@ impl SlidingSyncRoom {
|
||||
&self,
|
||||
listener: Box<dyn TimelineListener>,
|
||||
settings: Option<RoomSubscription>,
|
||||
) -> anyhow::Result<SlidingSyncSubscribeResult> {
|
||||
) -> Result<SlidingSyncSubscribeResult, ClientError> {
|
||||
let (items, mut stoppable_spawn) = self.add_timeline_listener_inner(listener)?;
|
||||
let room_id = self.inner.room_id().clone();
|
||||
|
||||
@@ -233,7 +233,9 @@ impl SlidingSyncRoom {
|
||||
|
||||
Ok(SlidingSyncSubscribeResult { items, task_handle: Arc::new(stoppable_spawn) })
|
||||
}
|
||||
}
|
||||
|
||||
impl SlidingSyncRoom {
|
||||
fn add_timeline_listener_inner(
|
||||
&self,
|
||||
listener: Box<dyn TimelineListener>,
|
||||
@@ -312,6 +314,7 @@ impl SlidingSyncRoom {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct SlidingSyncSubscribeResult {
|
||||
pub items: Vec<Arc<TimelineItem>>,
|
||||
pub task_handle: Arc<TaskHandle>,
|
||||
@@ -323,11 +326,13 @@ pub struct UpdateSummary {
|
||||
pub rooms: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct RequiredState {
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct RoomSubscription {
|
||||
pub required_state: Option<Vec<RequiredState>>,
|
||||
pub timeline_limit: Option<u32>,
|
||||
@@ -470,27 +475,21 @@ impl SlidingSyncListBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self { inner: matrix_sdk::SlidingSyncList::builder() }
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl SlidingSyncListBuilder {
|
||||
pub fn sync_mode(self: Arc<Self>, mode: SlidingSyncMode) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.sync_mode(mode);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn ranges(self: Arc<Self>, ranges: Vec<(u32, u32)>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.ranges(ranges);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<SlidingSyncList>> {
|
||||
pub fn build(self: Arc<Self>) -> Result<Arc<SlidingSyncList>, ClientError> {
|
||||
let builder = unwrap_or_clone_arc(self);
|
||||
Ok(Arc::new(builder.inner.build()?.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl SlidingSyncListBuilder {
|
||||
pub fn sort(self: Arc<Self>, sort: Vec<String>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.sort(sort);
|
||||
@@ -566,7 +565,7 @@ impl SlidingSyncListBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, uniffi::Object)]
|
||||
pub struct SlidingSyncList {
|
||||
inner: matrix_sdk::SlidingSyncList,
|
||||
}
|
||||
@@ -577,6 +576,7 @@ impl From<matrix_sdk::SlidingSyncList> for SlidingSyncList {
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl SlidingSyncList {
|
||||
pub fn observe_state(
|
||||
&self,
|
||||
@@ -622,10 +622,7 @@ impl SlidingSyncList {
|
||||
}
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl SlidingSyncList {
|
||||
/// Get the current list of rooms
|
||||
pub fn current_room_list(&self) -> Vec<RoomListEntry> {
|
||||
self.inner.room_list()
|
||||
@@ -677,6 +674,7 @@ pub trait SlidingSyncObserver: Sync + Send {
|
||||
fn did_receive_sync_update(&self, summary: UpdateSummary);
|
||||
}
|
||||
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct SlidingSync {
|
||||
inner: matrix_sdk::SlidingSync,
|
||||
client: Client,
|
||||
@@ -687,7 +685,10 @@ impl SlidingSync {
|
||||
fn new(inner: matrix_sdk::SlidingSync, client: Client) -> Self {
|
||||
Self { inner, client, observer: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl SlidingSync {
|
||||
pub fn set_observer(&self, observer: Option<Box<dyn SlidingSyncObserver>>) {
|
||||
*self.observer.write().unwrap() = observer;
|
||||
}
|
||||
@@ -696,17 +697,17 @@ impl SlidingSync {
|
||||
&self,
|
||||
room_id: String,
|
||||
settings: Option<RoomSubscription>,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> Result<(), ClientError> {
|
||||
self.inner.subscribe(room_id.try_into()?, settings.map(Into::into));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unsubscribe(&self, room_id: String) -> anyhow::Result<()> {
|
||||
pub fn unsubscribe(&self, room_id: String) -> Result<(), ClientError> {
|
||||
self.inner.unsubscribe(room_id.try_into()?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_room(&self, room_id: String) -> anyhow::Result<Option<Arc<SlidingSyncRoom>>> {
|
||||
pub fn get_room(&self, room_id: String) -> Result<Option<Arc<SlidingSyncRoom>>, ClientError> {
|
||||
let runner = self.inner.clone();
|
||||
|
||||
Ok(self.inner.get_room(<&RoomId>::try_from(room_id.as_str())?).map(|inner| {
|
||||
@@ -722,7 +723,7 @@ impl SlidingSync {
|
||||
pub fn get_rooms(
|
||||
&self,
|
||||
room_ids: Vec<String>,
|
||||
) -> anyhow::Result<Vec<Option<Arc<SlidingSyncRoom>>>> {
|
||||
) -> Result<Vec<Option<Arc<SlidingSyncRoom>>>, ClientError> {
|
||||
let actual_ids = room_ids
|
||||
.into_iter()
|
||||
.map(OwnedRoomId::try_from)
|
||||
@@ -743,10 +744,7 @@ impl SlidingSync {
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl SlidingSync {
|
||||
#[allow(clippy::significant_drop_in_scrutinee)]
|
||||
pub fn get_list(&self, name: String) -> Option<Arc<SlidingSyncList>> {
|
||||
self.inner.list(&name).map(|inner| Arc::new(SlidingSyncList { inner }))
|
||||
@@ -800,29 +798,20 @@ impl SlidingSync {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, uniffi::Object)]
|
||||
pub struct SlidingSyncBuilder {
|
||||
inner: MatrixSlidingSyncBuilder,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl SlidingSyncBuilder {
|
||||
pub fn homeserver(self: Arc<Self>, url: String) -> anyhow::Result<Arc<Self>> {
|
||||
pub fn homeserver(self: Arc<Self>, url: String) -> Result<Arc<Self>, ClientError> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.homeserver(url.parse()?);
|
||||
Ok(Arc::new(builder))
|
||||
}
|
||||
|
||||
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<SlidingSync>> {
|
||||
let builder = unwrap_or_clone_arc(self);
|
||||
RUNTIME.block_on(async move {
|
||||
Ok(Arc::new(SlidingSync::new(builder.inner.build().await?, builder.client)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl SlidingSyncBuilder {
|
||||
pub fn storage_key(self: Arc<Self>, name: Option<String>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.storage_key(name);
|
||||
@@ -877,6 +866,21 @@ impl SlidingSyncBuilder {
|
||||
builder.inner = builder.inner.with_all_extensions();
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn bump_event_types(self: Arc<Self>, bump_event_types: Vec<String>) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.inner = builder.inner.bump_event_types(
|
||||
bump_event_types.into_iter().map(Into::into).collect::<Vec<_>>().as_slice(),
|
||||
);
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn build(self: Arc<Self>) -> Result<Arc<SlidingSync>, ClientError> {
|
||||
let builder = unwrap_or_clone_arc(self);
|
||||
RUNTIME.block_on(async move {
|
||||
Ok(Arc::new(SlidingSync::new(builder.inner.build().await?, builder.client)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
|
||||
@@ -550,7 +550,7 @@ mod tests {
|
||||
use matrix_sdk::{
|
||||
config::RequestConfig,
|
||||
ruma::{api::appservice::Registration, events::room::member::OriginalSyncRoomMemberEvent},
|
||||
Client,
|
||||
Client, RoomMemberships,
|
||||
};
|
||||
use matrix_sdk_test::{appservice::TransactionBuilder, async_test, TimelineTestEvent};
|
||||
use ruma::{
|
||||
@@ -888,7 +888,7 @@ mod tests {
|
||||
.await?
|
||||
.get_room(room_id)
|
||||
.expect("Expected room to be available")
|
||||
.members_no_sync()
|
||||
.members_no_sync(RoomMemberships::empty())
|
||||
.await?;
|
||||
|
||||
assert_eq!(members[0].display_name().unwrap(), "changed");
|
||||
|
||||
@@ -30,6 +30,7 @@ testing = ["dep:http", "dep:matrix-sdk-test", "dep:assert_matches"]
|
||||
assert_matches = { workspace = true, optional = true }
|
||||
async-stream = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
bitflags = "2.1.0"
|
||||
dashmap = { workspace = true }
|
||||
eyeball = { workspace = true }
|
||||
futures-core = "0.3.21"
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
- Add `RoomInfo::state` accessor
|
||||
- Remove `members` and `stripped_members` fields in `StateChanges`. Room member events are now with
|
||||
other state events in `state` and `stripped_state`.
|
||||
- `StateStore::get_user_ids` takes a `RoomMemberships` to be able to filter the results by any
|
||||
membership state.
|
||||
- `StateStore::get_joined_user_ids` and `StateStore::get_invited_user_ids` are deprecated.
|
||||
- `Room::members` takes a `RoomMemberships` to be able to filter the results by any membership
|
||||
state.
|
||||
- `Room::active_members` and `Room::joined_members` are deprecated.
|
||||
|
||||
## 0.5.1
|
||||
|
||||
|
||||
@@ -56,8 +56,6 @@ use ruma::{
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{debug, info, trace, warn};
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use crate::error::Error;
|
||||
use crate::{
|
||||
deserialized_responses::{AmbiguityChanges, MembersResponse, SyncTimelineEvent},
|
||||
error::Result,
|
||||
@@ -69,6 +67,8 @@ use crate::{
|
||||
sync::{JoinedRoom, LeftRoom, Rooms, SyncResponse, Timeline},
|
||||
Session, SessionMeta, SessionTokens,
|
||||
};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use crate::{error::Error, RoomMemberships};
|
||||
|
||||
/// A no IO Client implementation.
|
||||
///
|
||||
@@ -757,12 +757,9 @@ impl BaseClient {
|
||||
// The room turned on encryption in this sync, we need
|
||||
// to also get all the existing users and mark them for
|
||||
// tracking.
|
||||
let joined = self.store.get_joined_user_ids(&room_id).await?;
|
||||
let invited = self.store.get_invited_user_ids(&room_id).await?;
|
||||
|
||||
let user_ids: Vec<&UserId> =
|
||||
joined.iter().chain(&invited).map(Deref::deref).collect();
|
||||
o.update_tracked_users(user_ids).await?
|
||||
let user_ids =
|
||||
self.store.get_user_ids(&room_id, RoomMemberships::ACTIVE).await?;
|
||||
o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?
|
||||
}
|
||||
|
||||
o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?;
|
||||
@@ -1058,21 +1055,20 @@ impl BaseClient {
|
||||
.map(|r| (r.history_visibility(), r.encryption_settings()))
|
||||
.unwrap_or((HistoryVisibility::Joined, None));
|
||||
|
||||
let joined = self.store.get_joined_user_ids(room_id).await?;
|
||||
let invited = self.store.get_invited_user_ids(room_id).await?;
|
||||
|
||||
// Don't share the group session with members that are invited
|
||||
// if the history visibility is set to `Joined`
|
||||
let members = if history_visibility == HistoryVisibility::Joined {
|
||||
joined.iter().chain(&[])
|
||||
let filter = if history_visibility == HistoryVisibility::Joined {
|
||||
RoomMemberships::JOIN
|
||||
} else {
|
||||
joined.iter().chain(&invited)
|
||||
RoomMemberships::ACTIVE
|
||||
};
|
||||
|
||||
let members = self.store.get_user_ids(room_id, filter).await?;
|
||||
|
||||
let settings = settings.ok_or(Error::EncryptionNotEnabled)?;
|
||||
let settings = EncryptionSettings::new(settings, history_visibility, false);
|
||||
|
||||
Ok(o.share_room_key(room_id, members.map(Deref::deref), settings).await?)
|
||||
Ok(o.share_room_key(room_id, members.iter().map(Deref::deref), settings).await?)
|
||||
}
|
||||
None => panic!("Olm machine wasn't started"),
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ pub use http;
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
pub use matrix_sdk_crypto as crypto;
|
||||
pub use once_cell;
|
||||
pub use rooms::{DisplayName, Room, RoomInfo, RoomMember, RoomState};
|
||||
pub use rooms::{DisplayName, Room, RoomInfo, RoomMember, RoomMemberships, RoomState};
|
||||
pub use store::{StateChanges, StateStore, StateStoreDataKey, StateStoreDataValue, StoreError};
|
||||
pub use utils::{
|
||||
MinimalRoomMemberEvent, MinimalStateEvent, OriginalMinimalStateEvent, RedactedMinimalStateEvent,
|
||||
|
||||
@@ -3,6 +3,7 @@ mod normal;
|
||||
|
||||
use std::{collections::HashSet, fmt};
|
||||
|
||||
use bitflags::bitflags;
|
||||
pub use members::RoomMember;
|
||||
pub use normal::{Room, RoomInfo, RoomState};
|
||||
use ruma::{
|
||||
@@ -13,9 +14,9 @@ use ruma::{
|
||||
create::RoomCreateEventContent, encryption::RoomEncryptionEventContent,
|
||||
guest_access::RoomGuestAccessEventContent,
|
||||
history_visibility::RoomHistoryVisibilityEventContent,
|
||||
join_rules::RoomJoinRulesEventContent, name::RoomNameEventContent,
|
||||
redaction::OriginalSyncRoomRedactionEvent, tombstone::RoomTombstoneEventContent,
|
||||
topic::RoomTopicEventContent,
|
||||
join_rules::RoomJoinRulesEventContent, member::MembershipState,
|
||||
name::RoomNameEventContent, redaction::OriginalSyncRoomRedactionEvent,
|
||||
tombstone::RoomTombstoneEventContent, topic::RoomTopicEventContent,
|
||||
},
|
||||
AnyStrippedStateEvent, AnySyncStateEvent, RedactContent, RedactedStateEventContent,
|
||||
StaticStateEventContent, SyncStateEvent,
|
||||
@@ -307,6 +308,49 @@ fn calculate_room_name(
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Room membership filter as a bitset.
|
||||
///
|
||||
/// Note that [`RoomMemberships::empty()`] doesn't filter the results and
|
||||
/// [`RoomMemberships::all()`] filters out unknown memberships.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct RoomMemberships: u16 {
|
||||
/// The member joined the room.
|
||||
const JOIN = 0b00000001;
|
||||
/// The member was invited to the room.
|
||||
const INVITE = 0b00000010;
|
||||
/// The member requested to join the room.
|
||||
const KNOCK = 0b00000100;
|
||||
/// The member left the room.
|
||||
const LEAVE = 0b00001000;
|
||||
/// The member was banned.
|
||||
const BAN = 0b00010000;
|
||||
|
||||
/// The member is active in the room (i.e. joined or invited).
|
||||
const ACTIVE = Self::JOIN.bits() | Self::INVITE.bits();
|
||||
}
|
||||
}
|
||||
|
||||
impl RoomMemberships {
|
||||
/// Whether the given membership matches this `RoomMemberships`.
|
||||
pub fn matches(&self, membership: &MembershipState) -> bool {
|
||||
if self.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let membership = match membership {
|
||||
MembershipState::Ban => Self::BAN,
|
||||
MembershipState::Invite => Self::INVITE,
|
||||
MembershipState::Join => Self::JOIN,
|
||||
MembershipState::Knock => Self::KNOCK,
|
||||
MembershipState::Leave => Self::LEAVE,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
self.contains(membership)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -44,7 +44,7 @@ use crate::{
|
||||
deserialized_responses::MemberEvent,
|
||||
store::{DynStateStore, Result as StoreResult, StateStoreExt},
|
||||
sync::UnreadNotificationsCount,
|
||||
MinimalStateEvent,
|
||||
MinimalStateEvent, RoomMemberships,
|
||||
};
|
||||
|
||||
/// The underlying room data structure collecting state for joined, left and
|
||||
@@ -318,12 +318,13 @@ impl Room {
|
||||
/// Get the list of users ids that are considered to be joined members of
|
||||
/// this room.
|
||||
pub async fn joined_user_ids(&self) -> StoreResult<Vec<OwnedUserId>> {
|
||||
self.store.get_joined_user_ids(self.room_id()).await
|
||||
self.store.get_user_ids(self.room_id(), RoomMemberships::JOIN).await
|
||||
}
|
||||
|
||||
/// Get the all `RoomMember`s of this room that are known to the store.
|
||||
pub async fn members(&self) -> StoreResult<Vec<RoomMember>> {
|
||||
let user_ids = self.store.get_user_ids(self.room_id()).await?;
|
||||
/// Get the `RoomMember`s of this room that are known to the store, with the
|
||||
/// given memberships.
|
||||
pub async fn members(&self, memberships: RoomMemberships) -> StoreResult<Vec<RoomMember>> {
|
||||
let user_ids = self.store.get_user_ids(self.room_id(), memberships).await?;
|
||||
let mut members = Vec::new();
|
||||
|
||||
for u in user_ids {
|
||||
@@ -339,38 +340,16 @@ impl Room {
|
||||
|
||||
/// Get the list of `RoomMember`s that are considered to be joined members
|
||||
/// of this room.
|
||||
#[deprecated = "Use members with RoomMemberships::JOIN instead"]
|
||||
pub async fn joined_members(&self) -> StoreResult<Vec<RoomMember>> {
|
||||
let joined = self.store.get_joined_user_ids(self.room_id()).await?;
|
||||
let mut members = Vec::new();
|
||||
|
||||
for u in joined {
|
||||
let m = self.get_member(&u).await?;
|
||||
|
||||
if let Some(member) = m {
|
||||
members.push(member);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(members)
|
||||
self.members(RoomMemberships::JOIN).await
|
||||
}
|
||||
|
||||
/// Get the list of `RoomMember`s that are considered to be joined or
|
||||
/// invited members of this room.
|
||||
#[deprecated = "Use members with RoomMemberships::ACTIVE instead"]
|
||||
pub async fn active_members(&self) -> StoreResult<Vec<RoomMember>> {
|
||||
let joined = self.store.get_joined_user_ids(self.room_id()).await?;
|
||||
let invited = self.store.get_invited_user_ids(self.room_id()).await?;
|
||||
|
||||
let mut members = Vec::new();
|
||||
|
||||
for u in joined.iter().chain(&invited) {
|
||||
let m = self.get_member(u).await?;
|
||||
|
||||
if let Some(member) = m {
|
||||
members.push(member);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(members)
|
||||
self.members(RoomMemberships::ACTIVE).await
|
||||
}
|
||||
|
||||
async fn calculate_name(&self) -> StoreResult<DisplayName> {
|
||||
@@ -391,7 +370,12 @@ impl Room {
|
||||
let is_own_user_id = |u: &str| u == self.own_user_id().as_str();
|
||||
|
||||
let members: Vec<RoomMember> = if summary.heroes.is_empty() {
|
||||
self.active_members().await?.into_iter().filter(|u| !is_own_member(u)).take(5).collect()
|
||||
self.members(RoomMemberships::ACTIVE)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|u| !is_own_member(u))
|
||||
.take(5)
|
||||
.collect()
|
||||
} else {
|
||||
let members: Vec<_> =
|
||||
stream::iter(summary.heroes.iter().filter(|u| !is_own_user_id(u)))
|
||||
|
||||
@@ -2,8 +2,6 @@ use std::collections::BTreeMap;
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use std::ops::Deref;
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use ruma::UserId;
|
||||
use ruma::{
|
||||
api::client::sync::sync_events::{
|
||||
v3::{self, Ephemeral},
|
||||
@@ -14,6 +12,8 @@ use ruma::{
|
||||
use tracing::{debug, info, instrument};
|
||||
|
||||
use super::BaseClient;
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use crate::RoomMemberships;
|
||||
use crate::{
|
||||
deserialized_responses::AmbiguityChanges,
|
||||
error::Result,
|
||||
@@ -196,12 +196,9 @@ impl BaseClient {
|
||||
// The room turned on encryption in this sync, we need
|
||||
// to also get all the existing users and mark them for
|
||||
// tracking.
|
||||
let joined = store.get_joined_user_ids(room_id).await?;
|
||||
let invited = store.get_invited_user_ids(room_id).await?;
|
||||
|
||||
let user_ids: Vec<&UserId> =
|
||||
joined.iter().chain(&invited).map(Deref::deref).collect();
|
||||
o.update_tracked_users(user_ids).await?
|
||||
let user_ids =
|
||||
store.get_user_ids(room_id, RoomMemberships::ACTIVE).await?;
|
||||
o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?
|
||||
}
|
||||
|
||||
if !user_ids.is_empty() {
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::{
|
||||
deserialized_responses::MemberEvent,
|
||||
media::{MediaFormat, MediaRequest, MediaThumbnailSize},
|
||||
store::{Result, StateStoreExt},
|
||||
RoomInfo, RoomState, StateChanges, StateStoreDataKey, StateStoreDataValue,
|
||||
RoomInfo, RoomMemberships, RoomState, StateChanges, StateStoreDataKey, StateStoreDataValue,
|
||||
};
|
||||
|
||||
/// `StateStore` integration tests.
|
||||
@@ -330,17 +330,17 @@ impl StateStoreIntegrationTests for DynStateStore {
|
||||
assert!(self.get_profile(room_id, user_id).await?.is_some());
|
||||
assert!(self.get_member_event(room_id, user_id).await?.is_some());
|
||||
assert_eq!(
|
||||
self.get_user_ids(room_id).await?.len(),
|
||||
self.get_user_ids(room_id, RoomMemberships::empty()).await?.len(),
|
||||
2,
|
||||
"Expected to find 2 members for room"
|
||||
);
|
||||
assert_eq!(
|
||||
self.get_invited_user_ids(room_id).await?.len(),
|
||||
self.get_user_ids(room_id, RoomMemberships::INVITE).await?.len(),
|
||||
1,
|
||||
"Expected to find 1 invited user ids"
|
||||
);
|
||||
assert_eq!(
|
||||
self.get_joined_user_ids(room_id).await?.len(),
|
||||
self.get_user_ids(room_id, RoomMemberships::JOIN).await?.len(),
|
||||
1,
|
||||
"Expected to find 1 joined user ids"
|
||||
);
|
||||
@@ -394,7 +394,7 @@ impl StateStoreIntegrationTests for DynStateStore {
|
||||
self.save_changes(&changes).await.unwrap();
|
||||
assert!(self.get_member_event(room_id, user_id).await.unwrap().is_some());
|
||||
|
||||
let members = self.get_user_ids(room_id).await.unwrap();
|
||||
let members = self.get_user_ids(room_id, RoomMemberships::empty()).await.unwrap();
|
||||
assert!(!members.is_empty(), "We expected to find members for the room")
|
||||
}
|
||||
|
||||
@@ -466,7 +466,7 @@ impl StateStoreIntegrationTests for DynStateStore {
|
||||
self.save_changes(&changes).await.unwrap();
|
||||
assert!(self.get_member_event(room_id, user_id).await.unwrap().is_some());
|
||||
|
||||
let members = self.get_user_ids(room_id).await.unwrap();
|
||||
let members = self.get_user_ids(room_id, RoomMemberships::empty()).await.unwrap();
|
||||
assert!(!members.is_empty(), "We expected to find members for the room")
|
||||
}
|
||||
|
||||
@@ -774,7 +774,7 @@ impl StateStoreIntegrationTests for DynStateStore {
|
||||
assert_eq!(self.get_room_infos().await.unwrap().len(), 1);
|
||||
assert_eq!(self.get_stripped_room_infos().await.unwrap().len(), 0);
|
||||
|
||||
let members = self.get_user_ids(room_id).await.unwrap();
|
||||
let members = self.get_user_ids(room_id, RoomMemberships::empty()).await.unwrap();
|
||||
assert_eq!(members, vec![user_id.to_owned()]);
|
||||
|
||||
let mut changes = StateChanges::default();
|
||||
@@ -788,7 +788,7 @@ impl StateStoreIntegrationTests for DynStateStore {
|
||||
assert_eq!(self.get_room_infos().await.unwrap().len(), 0);
|
||||
assert_eq!(self.get_stripped_room_infos().await.unwrap().len(), 1);
|
||||
|
||||
let members = self.get_user_ids(room_id).await.unwrap();
|
||||
let members = self.get_user_ids(room_id, RoomMemberships::empty()).await.unwrap();
|
||||
assert_eq!(members, vec![user_id.to_owned()]);
|
||||
|
||||
Ok(())
|
||||
@@ -813,12 +813,18 @@ impl StateStoreIntegrationTests for DynStateStore {
|
||||
);
|
||||
assert!(self.get_profile(room_id, user_id).await?.is_none());
|
||||
assert!(self.get_member_event(room_id, user_id).await?.is_none());
|
||||
assert!(self.get_user_ids(room_id).await?.is_empty(), "still user ids found");
|
||||
assert!(
|
||||
self.get_invited_user_ids(room_id).await?.is_empty(),
|
||||
self.get_user_ids(room_id, RoomMemberships::empty()).await?.is_empty(),
|
||||
"still user ids found"
|
||||
);
|
||||
assert!(
|
||||
self.get_user_ids(room_id, RoomMemberships::INVITE).await?.is_empty(),
|
||||
"still invited user ids found"
|
||||
);
|
||||
assert!(self.get_joined_user_ids(room_id).await?.is_empty(), "still joined users found");
|
||||
assert!(
|
||||
self.get_user_ids(room_id, RoomMemberships::JOIN).await?.is_empty(),
|
||||
"still joined users found"
|
||||
);
|
||||
assert!(
|
||||
self.get_users_with_display_name(room_id, "example").await?.is_empty(),
|
||||
"still display names found"
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::{
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use dashmap::DashMap;
|
||||
use matrix_sdk_common::instant::Instant;
|
||||
use ruma::{
|
||||
canonical_json::redact,
|
||||
@@ -38,7 +38,7 @@ use tracing::{debug, warn};
|
||||
use super::{Result, RoomInfo, StateChanges, StateStore, StoreError};
|
||||
use crate::{
|
||||
deserialized_responses::RawMemberEvent, media::MediaRequest, MinimalRoomMemberEvent,
|
||||
StateStoreDataKey, StateStoreDataValue,
|
||||
RoomMemberships, StateStoreDataKey, StateStoreDataValue,
|
||||
};
|
||||
|
||||
/// In-Memory, non-persistent implementation of the `StateStore`
|
||||
@@ -51,11 +51,9 @@ pub struct MemoryStore {
|
||||
sync_token: Arc<RwLock<Option<String>>>,
|
||||
filters: Arc<DashMap<String, String>>,
|
||||
account_data: Arc<DashMap<GlobalAccountDataEventType, Raw<AnyGlobalAccountDataEvent>>>,
|
||||
members: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
|
||||
profiles: Arc<DashMap<OwnedRoomId, DashMap<OwnedUserId, MinimalRoomMemberEvent>>>,
|
||||
display_names: Arc<DashMap<OwnedRoomId, DashMap<String, BTreeSet<OwnedUserId>>>>,
|
||||
joined_user_ids: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
|
||||
invited_user_ids: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
|
||||
members: Arc<DashMap<OwnedRoomId, DashMap<OwnedUserId, MembershipState>>>,
|
||||
room_info: Arc<DashMap<OwnedRoomId, RoomInfo>>,
|
||||
room_state:
|
||||
Arc<DashMap<OwnedRoomId, DashMap<StateEventType, DashMap<String, Raw<AnySyncStateEvent>>>>>,
|
||||
@@ -65,9 +63,7 @@ pub struct MemoryStore {
|
||||
stripped_room_state: Arc<
|
||||
DashMap<OwnedRoomId, DashMap<StateEventType, DashMap<String, Raw<AnyStrippedStateEvent>>>>,
|
||||
>,
|
||||
stripped_members: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
|
||||
stripped_joined_user_ids: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
|
||||
stripped_invited_user_ids: Arc<DashMap<OwnedRoomId, DashSet<OwnedUserId>>>,
|
||||
stripped_members: Arc<DashMap<OwnedRoomId, DashMap<OwnedUserId, MembershipState>>>,
|
||||
presence: Arc<DashMap<OwnedUserId, Raw<PresenceEvent>>>,
|
||||
room_user_receipts: Arc<
|
||||
DashMap<
|
||||
@@ -99,19 +95,15 @@ impl MemoryStore {
|
||||
sync_token: Default::default(),
|
||||
filters: Default::default(),
|
||||
account_data: Default::default(),
|
||||
members: Default::default(),
|
||||
profiles: Default::default(),
|
||||
display_names: Default::default(),
|
||||
joined_user_ids: Default::default(),
|
||||
invited_user_ids: Default::default(),
|
||||
members: Default::default(),
|
||||
room_info: Default::default(),
|
||||
room_state: Default::default(),
|
||||
room_account_data: Default::default(),
|
||||
stripped_room_infos: Default::default(),
|
||||
stripped_room_state: Default::default(),
|
||||
stripped_members: Default::default(),
|
||||
stripped_joined_user_ids: Default::default(),
|
||||
stripped_invited_user_ids: Default::default(),
|
||||
presence: Default::default(),
|
||||
room_user_receipts: Default::default(),
|
||||
room_event_receipts: Default::default(),
|
||||
@@ -240,47 +232,12 @@ impl MemoryStore {
|
||||
}
|
||||
};
|
||||
|
||||
self.stripped_joined_user_ids.remove(room);
|
||||
self.stripped_invited_user_ids.remove(room);
|
||||
|
||||
match event.membership() {
|
||||
MembershipState::Join => {
|
||||
self.joined_user_ids
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.insert(event.state_key().to_owned());
|
||||
self.invited_user_ids
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.remove(event.state_key());
|
||||
}
|
||||
MembershipState::Invite => {
|
||||
self.invited_user_ids
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.insert(event.state_key().to_owned());
|
||||
self.joined_user_ids
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.remove(event.state_key());
|
||||
}
|
||||
_ => {
|
||||
self.joined_user_ids
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.remove(event.state_key());
|
||||
self.invited_user_ids
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.remove(event.state_key());
|
||||
}
|
||||
}
|
||||
self.stripped_members.remove(room);
|
||||
|
||||
self.members
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.insert(event.state_key().to_owned());
|
||||
self.stripped_members.remove(room);
|
||||
.insert(event.state_key().to_owned(), event.membership().clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,43 +281,10 @@ impl MemoryStore {
|
||||
}
|
||||
};
|
||||
|
||||
match event.content.membership {
|
||||
MembershipState::Join => {
|
||||
self.stripped_joined_user_ids
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.insert(event.state_key.clone());
|
||||
self.stripped_invited_user_ids
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.remove(&event.state_key);
|
||||
}
|
||||
MembershipState::Invite => {
|
||||
self.stripped_invited_user_ids
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.insert(event.state_key.clone());
|
||||
self.stripped_joined_user_ids
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.remove(&event.state_key);
|
||||
}
|
||||
_ => {
|
||||
self.stripped_joined_user_ids
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.remove(&event.state_key);
|
||||
self.stripped_invited_user_ids
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.remove(&event.state_key);
|
||||
}
|
||||
}
|
||||
|
||||
self.stripped_members
|
||||
.entry(room.clone())
|
||||
.or_default()
|
||||
.insert(event.state_key.clone());
|
||||
.insert(event.state_key, event.content.membership.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -507,42 +431,25 @@ impl MemoryStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
|
||||
if let Some(u) = self.stripped_members.get(room_id) {
|
||||
u.iter().map(|u| u.key().clone()).collect()
|
||||
} else {
|
||||
self.members
|
||||
.get(room_id)
|
||||
.map(|u| u.iter().map(|u| u.key().clone()).collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
/// Get the user IDs for the given room with the given memberships and
|
||||
/// stripped state.
|
||||
///
|
||||
/// If `memberships` is empty, returns all user IDs in the room with the
|
||||
/// given stripped state.
|
||||
fn get_user_ids_inner(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
memberships: RoomMemberships,
|
||||
stripped: bool,
|
||||
) -> Vec<OwnedUserId> {
|
||||
let map = if stripped { &self.stripped_members } else { &self.members };
|
||||
|
||||
fn get_invited_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
|
||||
self.invited_user_ids
|
||||
.get(room_id)
|
||||
.map(|u| u.iter().map(|u| u.clone()).collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get_joined_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
|
||||
self.joined_user_ids
|
||||
.get(room_id)
|
||||
.map(|u| u.iter().map(|u| u.clone()).collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get_stripped_invited_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
|
||||
self.stripped_invited_user_ids
|
||||
.get(room_id)
|
||||
.map(|u| u.iter().map(|u| u.clone()).collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get_stripped_joined_user_ids(&self, room_id: &RoomId) -> Vec<OwnedUserId> {
|
||||
self.stripped_joined_user_ids
|
||||
.get(room_id)
|
||||
.map(|u| u.iter().map(|u| u.clone()).collect())
|
||||
map.get(room_id)
|
||||
.map(|u| {
|
||||
u.iter()
|
||||
.filter_map(|u| memberships.matches(u.value()).then(|| u.key().clone()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -631,11 +538,9 @@ impl MemoryStore {
|
||||
}
|
||||
|
||||
async fn remove_room(&self, room_id: &RoomId) -> Result<()> {
|
||||
self.members.remove(room_id);
|
||||
self.profiles.remove(room_id);
|
||||
self.display_names.remove(room_id);
|
||||
self.joined_user_ids.remove(room_id);
|
||||
self.invited_user_ids.remove(room_id);
|
||||
self.members.remove(room_id);
|
||||
self.room_info.remove(room_id);
|
||||
self.room_state.remove(room_id);
|
||||
self.room_account_data.remove(room_id);
|
||||
@@ -711,24 +616,24 @@ impl StateStore for MemoryStore {
|
||||
self.get_member_event(room_id, state_key).await
|
||||
}
|
||||
|
||||
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
|
||||
Ok(self.get_user_ids(room_id))
|
||||
async fn get_user_ids(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
memberships: RoomMemberships,
|
||||
) -> Result<Vec<OwnedUserId>> {
|
||||
let v = self.get_user_ids_inner(room_id, memberships, true);
|
||||
if !v.is_empty() {
|
||||
return Ok(v);
|
||||
}
|
||||
Ok(self.get_user_ids_inner(room_id, memberships, false))
|
||||
}
|
||||
|
||||
async fn get_invited_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
|
||||
let v = self.get_stripped_invited_user_ids(room_id);
|
||||
if !v.is_empty() {
|
||||
return Ok(v);
|
||||
}
|
||||
Ok(self.get_invited_user_ids(room_id))
|
||||
StateStore::get_user_ids(self, room_id, RoomMemberships::INVITE).await
|
||||
}
|
||||
|
||||
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
|
||||
let v = self.get_stripped_joined_user_ids(room_id);
|
||||
if !v.is_empty() {
|
||||
return Ok(v);
|
||||
}
|
||||
Ok(self.get_joined_user_ids(room_id))
|
||||
StateStore::get_user_ids(self, room_id, RoomMemberships::JOIN).await
|
||||
}
|
||||
|
||||
async fn get_room_infos(&self) -> Result<Vec<RoomInfo>> {
|
||||
|
||||
@@ -33,6 +33,7 @@ use ruma::{
|
||||
use super::{StateChanges, StoreError};
|
||||
use crate::{
|
||||
deserialized_responses::RawMemberEvent, media::MediaRequest, MinimalRoomMemberEvent, RoomInfo,
|
||||
RoomMemberships,
|
||||
};
|
||||
|
||||
/// An abstract state store trait that can be used to implement different stores
|
||||
@@ -142,17 +143,23 @@ pub trait StateStore: AsyncTraitDeps {
|
||||
state_key: &UserId,
|
||||
) -> Result<Option<RawMemberEvent>, Self::Error>;
|
||||
|
||||
/// Get all the user ids of members for a given room, for stripped and
|
||||
/// regular rooms alike.
|
||||
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>, Self::Error>;
|
||||
/// Get the user ids of members for a given room with the given memberships,
|
||||
/// for stripped and regular rooms alike.
|
||||
async fn get_user_ids(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
memberships: RoomMemberships,
|
||||
) -> Result<Vec<OwnedUserId>, Self::Error>;
|
||||
|
||||
/// Get all the user ids of members that are in the invited state for a
|
||||
/// given room, for stripped and regular rooms alike.
|
||||
#[deprecated = "Use get_user_ids with RoomMemberships::INVITE instead."]
|
||||
async fn get_invited_user_ids(&self, room_id: &RoomId)
|
||||
-> Result<Vec<OwnedUserId>, Self::Error>;
|
||||
|
||||
/// Get all the user ids of members that are in the joined state for a
|
||||
/// given room, for stripped and regular rooms alike.
|
||||
#[deprecated = "Use get_user_ids with RoomMemberships::JOIN instead."]
|
||||
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>, Self::Error>;
|
||||
|
||||
/// Get all the pure `RoomInfo`s the store knows about.
|
||||
@@ -391,19 +398,23 @@ impl<T: StateStore> StateStore for EraseStateStoreError<T> {
|
||||
self.0.get_member_event(room_id, state_key).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>, Self::Error> {
|
||||
self.0.get_user_ids(room_id).await.map_err(Into::into)
|
||||
async fn get_user_ids(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
memberships: RoomMemberships,
|
||||
) -> Result<Vec<OwnedUserId>, Self::Error> {
|
||||
self.0.get_user_ids(room_id, memberships).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_invited_user_ids(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> Result<Vec<OwnedUserId>, Self::Error> {
|
||||
self.0.get_invited_user_ids(room_id).await.map_err(Into::into)
|
||||
self.0.get_user_ids(room_id, RoomMemberships::INVITE).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>, Self::Error> {
|
||||
self.0.get_joined_user_ids(room_id).await.map_err(Into::into)
|
||||
self.0.get_user_ids(room_id, RoomMemberships::JOIN).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_room_infos(&self) -> Result<Vec<RoomInfo>, Self::Error> {
|
||||
|
||||
@@ -35,11 +35,12 @@ use wasm_bindgen::JsValue;
|
||||
use web_sys::IdbTransactionMode;
|
||||
|
||||
use super::{
|
||||
deserialize_event, encode_key, encode_to_range, keys, serialize_event, Result, ALL_STORES,
|
||||
deserialize_event, encode_key, encode_to_range, keys, serialize_event, Result, RoomMember,
|
||||
ALL_STORES,
|
||||
};
|
||||
use crate::IndexeddbStateStoreError;
|
||||
|
||||
const CURRENT_DB_VERSION: u32 = 5;
|
||||
const CURRENT_DB_VERSION: u32 = 6;
|
||||
const CURRENT_META_DB_VERSION: u32 = 2;
|
||||
|
||||
/// Sometimes Migrations can't proceed without having to drop existing
|
||||
@@ -65,6 +66,10 @@ mod old_keys {
|
||||
pub const SYNC_TOKEN: &str = "sync_token";
|
||||
pub const MEMBERS: &str = "members";
|
||||
pub const STRIPPED_MEMBERS: &str = "stripped_members";
|
||||
pub const JOINED_USER_IDS: &str = "joined_user_ids";
|
||||
pub const INVITED_USER_IDS: &str = "invited_user_ids";
|
||||
pub const STRIPPED_JOINED_USER_IDS: &str = "stripped_joined_user_ids";
|
||||
pub const STRIPPED_INVITED_USER_IDS: &str = "stripped_invited_user_ids";
|
||||
}
|
||||
|
||||
pub async fn upgrade_meta_db(
|
||||
@@ -211,6 +216,9 @@ pub async fn upgrade_inner_db(
|
||||
if old_version < 5 {
|
||||
migration.merge(migrate_to_v5(&pre_db, store_cipher).await?);
|
||||
}
|
||||
if old_version < 6 {
|
||||
migration.merge(migrate_to_v6(&pre_db, store_cipher).await?);
|
||||
}
|
||||
}
|
||||
|
||||
pre_db.close();
|
||||
@@ -255,8 +263,8 @@ pub const V1_STORES: &[&str] = &[
|
||||
old_keys::MEMBERS,
|
||||
keys::PROFILES,
|
||||
keys::DISPLAY_NAMES,
|
||||
keys::JOINED_USER_IDS,
|
||||
keys::INVITED_USER_IDS,
|
||||
old_keys::JOINED_USER_IDS,
|
||||
old_keys::INVITED_USER_IDS,
|
||||
keys::ROOM_STATE,
|
||||
keys::ROOM_INFOS,
|
||||
keys::PRESENCE,
|
||||
@@ -264,8 +272,8 @@ pub const V1_STORES: &[&str] = &[
|
||||
keys::STRIPPED_ROOM_INFOS,
|
||||
old_keys::STRIPPED_MEMBERS,
|
||||
keys::STRIPPED_ROOM_STATE,
|
||||
keys::STRIPPED_JOINED_USER_IDS,
|
||||
keys::STRIPPED_INVITED_USER_IDS,
|
||||
old_keys::STRIPPED_JOINED_USER_IDS,
|
||||
old_keys::STRIPPED_INVITED_USER_IDS,
|
||||
keys::ROOM_USER_RECEIPTS,
|
||||
keys::ROOM_EVENT_RECEIPTS,
|
||||
keys::MEDIA,
|
||||
@@ -499,6 +507,97 @@ async fn migrate_to_v5(
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove the old user IDs stores and populate the new ones.
|
||||
async fn migrate_to_v6(
|
||||
db: &IdbDatabase,
|
||||
store_cipher: Option<&StoreCipher>,
|
||||
) -> Result<OngoingMigration> {
|
||||
// We only have joined and invited user IDs in the old store, so instead we will
|
||||
// use the room member events to populate the new store.
|
||||
let tx = db.transaction_on_multi_with_mode(
|
||||
&[keys::ROOM_STATE, keys::ROOM_INFOS, keys::STRIPPED_ROOM_STATE, keys::STRIPPED_ROOM_INFOS],
|
||||
IdbTransactionMode::Readonly,
|
||||
)?;
|
||||
|
||||
let state_store = tx.object_store(keys::ROOM_STATE)?;
|
||||
let room_infos = tx
|
||||
.object_store(keys::ROOM_INFOS)?
|
||||
.get_all()?
|
||||
.await?
|
||||
.iter()
|
||||
.filter_map(|f| deserialize_event::<RoomInfo>(store_cipher, f).ok())
|
||||
.collect::<Vec<_>>();
|
||||
let mut values = Vec::new();
|
||||
|
||||
for room_info in room_infos {
|
||||
let room_id = room_info.room_id();
|
||||
let range =
|
||||
encode_to_range(store_cipher, keys::ROOM_STATE, (room_id, StateEventType::RoomMember))?;
|
||||
for value in state_store.get_all_with_key(&range)?.await?.iter() {
|
||||
let member_event =
|
||||
deserialize_event::<Raw<SyncRoomMemberEvent>>(store_cipher, value.clone())?
|
||||
.deserialize()?;
|
||||
let key = encode_key(store_cipher, keys::USER_IDS, (room_id, member_event.state_key()));
|
||||
let value = serialize_event(store_cipher, &RoomMember::from(&member_event))?;
|
||||
|
||||
values.push((key, value));
|
||||
}
|
||||
}
|
||||
|
||||
let stripped_state_store = tx.object_store(keys::STRIPPED_ROOM_STATE)?;
|
||||
let stripped_room_infos = tx
|
||||
.object_store(keys::STRIPPED_ROOM_INFOS)?
|
||||
.get_all()?
|
||||
.await?
|
||||
.iter()
|
||||
.filter_map(|f| deserialize_event::<RoomInfo>(store_cipher, f).ok())
|
||||
.collect::<Vec<_>>();
|
||||
let mut stripped_values = Vec::new();
|
||||
|
||||
for room_info in stripped_room_infos {
|
||||
let room_id = room_info.room_id();
|
||||
let range = encode_to_range(
|
||||
store_cipher,
|
||||
keys::STRIPPED_ROOM_STATE,
|
||||
(room_id, StateEventType::RoomMember),
|
||||
)?;
|
||||
for value in stripped_state_store.get_all_with_key(&range)?.await?.iter() {
|
||||
let stripped_member_event =
|
||||
deserialize_event::<Raw<StrippedRoomMemberEvent>>(store_cipher, value.clone())?
|
||||
.deserialize()?;
|
||||
let key = encode_key(
|
||||
store_cipher,
|
||||
keys::STRIPPED_USER_IDS,
|
||||
(room_id, &stripped_member_event.state_key),
|
||||
);
|
||||
let value = serialize_event(store_cipher, &RoomMember::from(&stripped_member_event))?;
|
||||
|
||||
stripped_values.push((key, value));
|
||||
}
|
||||
}
|
||||
|
||||
tx.await.into_result()?;
|
||||
|
||||
let mut data = HashMap::new();
|
||||
if !values.is_empty() {
|
||||
data.insert(keys::USER_IDS, values);
|
||||
}
|
||||
if !stripped_values.is_empty() {
|
||||
data.insert(keys::STRIPPED_USER_IDS, stripped_values);
|
||||
}
|
||||
|
||||
Ok(OngoingMigration {
|
||||
drop_stores: HashSet::from_iter([
|
||||
old_keys::JOINED_USER_IDS,
|
||||
old_keys::INVITED_USER_IDS,
|
||||
old_keys::STRIPPED_JOINED_USER_IDS,
|
||||
old_keys::STRIPPED_INVITED_USER_IDS,
|
||||
]),
|
||||
create_stores: HashSet::from_iter([keys::USER_IDS, keys::STRIPPED_USER_IDS]),
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(test, target_arch = "wasm32"))]
|
||||
mod tests {
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
@@ -506,8 +605,8 @@ mod tests {
|
||||
use assert_matches::assert_matches;
|
||||
use indexed_db_futures::prelude::*;
|
||||
use matrix_sdk_base::{
|
||||
deserialized_responses::RawMemberEvent, RoomInfo, RoomState, StateStore, StateStoreDataKey,
|
||||
StoreError,
|
||||
deserialized_responses::RawMemberEvent, RoomInfo, RoomMemberships, RoomState, StateStore,
|
||||
StateStoreDataKey, StoreError,
|
||||
};
|
||||
use matrix_sdk_test::{async_test, test_json};
|
||||
use ruma::{
|
||||
@@ -523,12 +622,10 @@ mod tests {
|
||||
use uuid::Uuid;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
use super::{
|
||||
old_keys, MigrationConflictStrategy, CURRENT_DB_VERSION, CURRENT_META_DB_VERSION, V1_STORES,
|
||||
};
|
||||
use super::{old_keys, MigrationConflictStrategy, CURRENT_DB_VERSION, CURRENT_META_DB_VERSION};
|
||||
use crate::{
|
||||
safe_encode::SafeEncode,
|
||||
state_store::{encode_key, keys, serialize_event, Result, ALL_STORES},
|
||||
state_store::{encode_key, keys, serialize_event, Result},
|
||||
IndexeddbStateStore, IndexeddbStateStoreError,
|
||||
};
|
||||
|
||||
@@ -541,20 +638,53 @@ mod tests {
|
||||
move |evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
let db = evt.db();
|
||||
|
||||
// Initialize stores.
|
||||
if version < 4 {
|
||||
for name in V1_STORES {
|
||||
db.create_object_store(name)?;
|
||||
}
|
||||
} else {
|
||||
for name in ALL_STORES {
|
||||
db.create_object_store(name)?;
|
||||
}
|
||||
// Stores common to all versions.
|
||||
let common_stores = &[
|
||||
keys::ACCOUNT_DATA,
|
||||
keys::PROFILES,
|
||||
keys::DISPLAY_NAMES,
|
||||
keys::ROOM_STATE,
|
||||
keys::ROOM_INFOS,
|
||||
keys::PRESENCE,
|
||||
keys::ROOM_ACCOUNT_DATA,
|
||||
keys::STRIPPED_ROOM_INFOS,
|
||||
keys::STRIPPED_ROOM_STATE,
|
||||
keys::ROOM_USER_RECEIPTS,
|
||||
keys::ROOM_EVENT_RECEIPTS,
|
||||
keys::MEDIA,
|
||||
keys::CUSTOM,
|
||||
];
|
||||
|
||||
if version < 5 {
|
||||
for name in [old_keys::MEMBERS, old_keys::STRIPPED_MEMBERS] {
|
||||
db.create_object_store(name)?;
|
||||
}
|
||||
for name in common_stores {
|
||||
db.create_object_store(name)?;
|
||||
}
|
||||
|
||||
if version < 4 {
|
||||
for name in [old_keys::SYNC_TOKEN, old_keys::SESSION] {
|
||||
db.create_object_store(name)?;
|
||||
}
|
||||
}
|
||||
if version >= 4 {
|
||||
db.create_object_store(keys::KV)?;
|
||||
}
|
||||
if version < 5 {
|
||||
for name in [old_keys::MEMBERS, old_keys::STRIPPED_MEMBERS] {
|
||||
db.create_object_store(name)?;
|
||||
}
|
||||
}
|
||||
if version < 6 {
|
||||
for name in [
|
||||
old_keys::INVITED_USER_IDS,
|
||||
old_keys::JOINED_USER_IDS,
|
||||
old_keys::STRIPPED_INVITED_USER_IDS,
|
||||
old_keys::STRIPPED_JOINED_USER_IDS,
|
||||
] {
|
||||
db.create_object_store(name)?;
|
||||
}
|
||||
}
|
||||
if version >= 6 {
|
||||
for name in [keys::USER_IDS, keys::STRIPPED_USER_IDS] {
|
||||
db.create_object_store(name)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -924,4 +1054,145 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn test_migrating_to_v6() -> Result<()> {
|
||||
let name = format!("migrating-v6-{}", Uuid::new_v4().as_hyphenated().to_string());
|
||||
|
||||
let room_id = room_id!("!room:localhost");
|
||||
let invite_member_event =
|
||||
Raw::new(&*test_json::MEMBER_INVITE).unwrap().cast::<SyncRoomMemberEvent>();
|
||||
let invite_user_id = user_id!("@invited:localhost");
|
||||
let ban_member_event =
|
||||
Raw::new(&*test_json::MEMBER_BAN).unwrap().cast::<SyncRoomMemberEvent>();
|
||||
let ban_user_id = user_id!("@banned:localhost");
|
||||
|
||||
let stripped_room_id = room_id!("!stripped_room:localhost");
|
||||
let stripped_member_event =
|
||||
Raw::new(&*test_json::MEMBER_STRIPPED).unwrap().cast::<StrippedRoomMemberEvent>();
|
||||
let stripped_user_id = user_id!("@example:localhost");
|
||||
|
||||
// Populate DB with old table.
|
||||
{
|
||||
let db = create_fake_db(&name, 5).await?;
|
||||
let tx = db.transaction_on_multi_with_mode(
|
||||
&[
|
||||
keys::ROOM_STATE,
|
||||
keys::ROOM_INFOS,
|
||||
keys::STRIPPED_ROOM_STATE,
|
||||
keys::STRIPPED_ROOM_INFOS,
|
||||
old_keys::INVITED_USER_IDS,
|
||||
old_keys::JOINED_USER_IDS,
|
||||
old_keys::STRIPPED_INVITED_USER_IDS,
|
||||
old_keys::STRIPPED_JOINED_USER_IDS,
|
||||
],
|
||||
IdbTransactionMode::Readwrite,
|
||||
)?;
|
||||
|
||||
let state_store = tx.object_store(keys::ROOM_STATE)?;
|
||||
state_store.put_key_val(
|
||||
&encode_key(
|
||||
None,
|
||||
keys::ROOM_STATE,
|
||||
(room_id, StateEventType::RoomMember, invite_user_id),
|
||||
),
|
||||
&serialize_event(None, &invite_member_event)?,
|
||||
)?;
|
||||
state_store.put_key_val(
|
||||
&encode_key(
|
||||
None,
|
||||
keys::ROOM_STATE,
|
||||
(room_id, StateEventType::RoomMember, ban_user_id),
|
||||
),
|
||||
&serialize_event(None, &ban_member_event)?,
|
||||
)?;
|
||||
let room_infos_store = tx.object_store(keys::ROOM_INFOS)?;
|
||||
let room_info = RoomInfo::new(room_id, RoomState::Joined);
|
||||
room_infos_store.put_key_val(
|
||||
&encode_key(None, keys::ROOM_INFOS, room_id),
|
||||
&serialize_event(None, &room_info)?,
|
||||
)?;
|
||||
|
||||
let stripped_state_store = tx.object_store(keys::STRIPPED_ROOM_STATE)?;
|
||||
stripped_state_store.put_key_val(
|
||||
&encode_key(
|
||||
None,
|
||||
keys::STRIPPED_ROOM_STATE,
|
||||
(stripped_room_id, StateEventType::RoomMember, stripped_user_id),
|
||||
),
|
||||
&serialize_event(None, &stripped_member_event)?,
|
||||
)?;
|
||||
let stripped_room_infos_store = tx.object_store(keys::STRIPPED_ROOM_INFOS)?;
|
||||
let stripped_room_info = RoomInfo::new(stripped_room_id, RoomState::Invited);
|
||||
stripped_room_infos_store.put_key_val(
|
||||
&encode_key(None, keys::STRIPPED_ROOM_INFOS, stripped_room_id),
|
||||
&serialize_event(None, &stripped_room_info)?,
|
||||
)?;
|
||||
|
||||
// Populate the old user IDs stores to check the data is not reused.
|
||||
let joined_user_id = user_id!("@joined_user:localhost");
|
||||
tx.object_store(old_keys::JOINED_USER_IDS)?.put_key_val(
|
||||
&encode_key(None, old_keys::JOINED_USER_IDS, (room_id, joined_user_id)),
|
||||
&serialize_event(None, &joined_user_id)?,
|
||||
)?;
|
||||
let invited_user_id = user_id!("@invited_user:localhost");
|
||||
tx.object_store(old_keys::INVITED_USER_IDS)?.put_key_val(
|
||||
&encode_key(None, old_keys::INVITED_USER_IDS, (room_id, invited_user_id)),
|
||||
&serialize_event(None, &invited_user_id)?,
|
||||
)?;
|
||||
let stripped_joined_user_id = user_id!("@stripped_joined_user:localhost");
|
||||
tx.object_store(old_keys::STRIPPED_JOINED_USER_IDS)?.put_key_val(
|
||||
&encode_key(
|
||||
None,
|
||||
old_keys::STRIPPED_JOINED_USER_IDS,
|
||||
(room_id, stripped_joined_user_id),
|
||||
),
|
||||
&serialize_event(None, &stripped_joined_user_id)?,
|
||||
)?;
|
||||
let stripped_invited_user_id = user_id!("@stripped_invited_user:localhost");
|
||||
tx.object_store(old_keys::STRIPPED_INVITED_USER_IDS)?.put_key_val(
|
||||
&encode_key(
|
||||
None,
|
||||
old_keys::STRIPPED_INVITED_USER_IDS,
|
||||
(room_id, stripped_invited_user_id),
|
||||
),
|
||||
&serialize_event(None, &stripped_invited_user_id)?,
|
||||
)?;
|
||||
|
||||
tx.await.into_result()?;
|
||||
db.close();
|
||||
}
|
||||
|
||||
// this transparently migrates to the latest version
|
||||
let store = IndexeddbStateStore::builder().name(name).build().await?;
|
||||
|
||||
assert_eq!(store.get_user_ids(room_id, RoomMemberships::JOIN).await.unwrap().len(), 0);
|
||||
assert_eq!(
|
||||
store.get_user_ids(room_id, RoomMemberships::INVITE).await.unwrap().as_slice(),
|
||||
[invite_user_id.to_owned()]
|
||||
);
|
||||
let user_ids = store.get_user_ids(room_id, RoomMemberships::empty()).await.unwrap();
|
||||
assert_eq!(user_ids.len(), 2);
|
||||
assert!(user_ids.contains(&invite_user_id.to_owned()));
|
||||
assert!(user_ids.contains(&ban_user_id.to_owned()));
|
||||
|
||||
assert_eq!(
|
||||
store.get_user_ids(stripped_room_id, RoomMemberships::JOIN).await.unwrap().as_slice(),
|
||||
[stripped_user_id.to_owned()]
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_user_ids(stripped_room_id, RoomMemberships::INVITE).await.unwrap().len(),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.get_user_ids(stripped_room_id, RoomMemberships::empty())
|
||||
.await
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
[stripped_user_id.to_owned()]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ use matrix_sdk_base::{
|
||||
deserialized_responses::RawMemberEvent,
|
||||
media::{MediaRequest, UniqueKey},
|
||||
store::{StateChanges, StateStore, StoreError},
|
||||
MinimalStateEvent, RoomInfo, StateStoreDataKey, StateStoreDataValue,
|
||||
MinimalStateEvent, RoomInfo, RoomMemberships, StateStoreDataKey, StateStoreDataValue,
|
||||
};
|
||||
use matrix_sdk_store_encryption::{Error as EncryptionError, StoreCipher};
|
||||
use ruma::{
|
||||
@@ -37,12 +37,12 @@ use ruma::{
|
||||
MembershipState, RoomMemberEventContent, StrippedRoomMemberEvent, SyncRoomMemberEvent,
|
||||
},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent,
|
||||
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, SyncStateEvent,
|
||||
},
|
||||
serde::Raw,
|
||||
CanonicalJsonObject, EventId, MxcUri, OwnedEventId, OwnedUserId, RoomId, RoomVersionId, UserId,
|
||||
};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use tracing::{debug, warn};
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::IdbKeyRange;
|
||||
@@ -96,8 +96,7 @@ mod keys {
|
||||
|
||||
pub const PROFILES: &str = "profiles";
|
||||
pub const DISPLAY_NAMES: &str = "display_names";
|
||||
pub const JOINED_USER_IDS: &str = "joined_user_ids";
|
||||
pub const INVITED_USER_IDS: &str = "invited_user_ids";
|
||||
pub const USER_IDS: &str = "user_ids";
|
||||
|
||||
pub const ROOM_STATE: &str = "room_state";
|
||||
pub const ROOM_INFOS: &str = "room_infos";
|
||||
@@ -106,8 +105,7 @@ mod keys {
|
||||
|
||||
pub const STRIPPED_ROOM_INFOS: &str = "stripped_room_infos";
|
||||
pub const STRIPPED_ROOM_STATE: &str = "stripped_room_state";
|
||||
pub const STRIPPED_JOINED_USER_IDS: &str = "stripped_joined_user_ids";
|
||||
pub const STRIPPED_INVITED_USER_IDS: &str = "stripped_invited_user_ids";
|
||||
pub const STRIPPED_USER_IDS: &str = "stripped_user_ids";
|
||||
|
||||
pub const ROOM_USER_RECEIPTS: &str = "room_user_receipts";
|
||||
pub const ROOM_EVENT_RECEIPTS: &str = "room_event_receipts";
|
||||
@@ -122,16 +120,14 @@ mod keys {
|
||||
ACCOUNT_DATA,
|
||||
PROFILES,
|
||||
DISPLAY_NAMES,
|
||||
JOINED_USER_IDS,
|
||||
INVITED_USER_IDS,
|
||||
USER_IDS,
|
||||
ROOM_STATE,
|
||||
ROOM_INFOS,
|
||||
PRESENCE,
|
||||
ROOM_ACCOUNT_DATA,
|
||||
STRIPPED_ROOM_INFOS,
|
||||
STRIPPED_ROOM_STATE,
|
||||
STRIPPED_JOINED_USER_IDS,
|
||||
STRIPPED_INVITED_USER_IDS,
|
||||
STRIPPED_USER_IDS,
|
||||
ROOM_USER_RECEIPTS,
|
||||
ROOM_EVENT_RECEIPTS,
|
||||
MEDIA,
|
||||
@@ -316,85 +312,52 @@ impl IndexeddbStateStore {
|
||||
encode_to_range(self.store_cipher.as_deref(), table_name, key)
|
||||
}
|
||||
|
||||
pub async fn get_user_ids_stream(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
|
||||
Ok([
|
||||
self.get_invited_user_ids_inner(room_id).await?,
|
||||
self.get_joined_user_ids_inner(room_id).await?,
|
||||
]
|
||||
.concat())
|
||||
}
|
||||
|
||||
pub async fn get_invited_user_ids_inner(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
|
||||
let range = self.encode_to_range(keys::INVITED_USER_IDS, room_id)?;
|
||||
let entries = self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(keys::INVITED_USER_IDS, IdbTransactionMode::Readonly)?
|
||||
.object_store(keys::INVITED_USER_IDS)?
|
||||
.get_all_with_key(&range)?
|
||||
.await?
|
||||
.iter()
|
||||
.filter_map(|f| self.deserialize_event::<OwnedUserId>(f).ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
pub async fn get_joined_user_ids_inner(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
|
||||
let range = self.encode_to_range(keys::JOINED_USER_IDS, room_id)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(keys::JOINED_USER_IDS, IdbTransactionMode::Readonly)?
|
||||
.object_store(keys::JOINED_USER_IDS)?
|
||||
.get_all_with_key(&range)?
|
||||
.await?
|
||||
.iter()
|
||||
.filter_map(|f| self.deserialize_event::<OwnedUserId>(f).ok())
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
pub async fn get_stripped_user_ids_stream(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
|
||||
Ok([
|
||||
self.get_stripped_invited_user_ids(room_id).await?,
|
||||
self.get_stripped_joined_user_ids(room_id).await?,
|
||||
]
|
||||
.concat())
|
||||
}
|
||||
|
||||
pub async fn get_stripped_invited_user_ids(
|
||||
/// Get user IDs for the given room with the given memberships and stripped
|
||||
/// state.
|
||||
pub async fn get_user_ids_inner(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
memberships: RoomMemberships,
|
||||
stripped: bool,
|
||||
) -> Result<Vec<OwnedUserId>> {
|
||||
let range = self.encode_to_range(keys::STRIPPED_INVITED_USER_IDS, room_id)?;
|
||||
let entries = self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(
|
||||
keys::STRIPPED_INVITED_USER_IDS,
|
||||
IdbTransactionMode::Readonly,
|
||||
)?
|
||||
.object_store(keys::STRIPPED_INVITED_USER_IDS)?
|
||||
.get_all_with_key(&range)?
|
||||
.await?
|
||||
.iter()
|
||||
.filter_map(|f| self.deserialize_event::<OwnedUserId>(f).ok())
|
||||
.collect::<Vec<_>>();
|
||||
let store_name = if stripped { keys::STRIPPED_USER_IDS } else { keys::USER_IDS };
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
let tx =
|
||||
self.inner.transaction_on_one_with_mode(store_name, IdbTransactionMode::Readonly)?;
|
||||
let store = tx.object_store(store_name)?;
|
||||
let range = self.encode_to_range(store_name, room_id)?;
|
||||
|
||||
pub async fn get_stripped_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
|
||||
let range = self.encode_to_range(keys::STRIPPED_JOINED_USER_IDS, room_id)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.transaction_on_one_with_mode(
|
||||
keys::STRIPPED_JOINED_USER_IDS,
|
||||
IdbTransactionMode::Readonly,
|
||||
)?
|
||||
.object_store(keys::STRIPPED_JOINED_USER_IDS)?
|
||||
.get_all_with_key(&range)?
|
||||
.await?
|
||||
.iter()
|
||||
.filter_map(|f| self.deserialize_event::<OwnedUserId>(f).ok())
|
||||
.collect::<Vec<_>>())
|
||||
let user_ids = if memberships.is_empty() {
|
||||
// It should be faster to just get all user IDs in this case.
|
||||
store
|
||||
.get_all_with_key(&range)?
|
||||
.await?
|
||||
.iter()
|
||||
.filter_map(|f| self.deserialize_event::<RoomMember>(f).ok().map(|m| m.user_id))
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
let mut user_ids = Vec::new();
|
||||
let cursor = store.open_cursor_with_range(&range)?.await?;
|
||||
|
||||
if let Some(cursor) = cursor {
|
||||
loop {
|
||||
let value = cursor.value();
|
||||
let member = self.deserialize_event::<RoomMember>(value)?;
|
||||
|
||||
if memberships.matches(&member.membership) {
|
||||
user_ids.push(member.user_id);
|
||||
}
|
||||
|
||||
if !cursor.continue_cursor()?.await? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user_ids
|
||||
};
|
||||
|
||||
Ok(user_ids)
|
||||
}
|
||||
|
||||
async fn get_custom_value_for_js(&self, jskey: &JsValue) -> Result<Option<Vec<u8>>> {
|
||||
@@ -543,10 +506,8 @@ impl_state_store! {
|
||||
if !changes.state.is_empty() {
|
||||
stores.extend([
|
||||
keys::ROOM_STATE,
|
||||
keys::INVITED_USER_IDS,
|
||||
keys::JOINED_USER_IDS,
|
||||
keys::STRIPPED_INVITED_USER_IDS,
|
||||
keys::STRIPPED_JOINED_USER_IDS,
|
||||
keys::USER_IDS,
|
||||
keys::STRIPPED_USER_IDS,
|
||||
keys::STRIPPED_ROOM_STATE,
|
||||
keys::PROFILES,
|
||||
]);
|
||||
@@ -563,8 +524,7 @@ impl_state_store! {
|
||||
if !changes.stripped_state.is_empty() {
|
||||
stores.extend([
|
||||
keys::STRIPPED_ROOM_STATE,
|
||||
keys::STRIPPED_INVITED_USER_IDS,
|
||||
keys::STRIPPED_JOINED_USER_IDS,
|
||||
keys::STRIPPED_USER_IDS,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -622,11 +582,9 @@ impl_state_store! {
|
||||
if !changes.state.is_empty() {
|
||||
let state = tx.object_store(keys::ROOM_STATE)?;
|
||||
let profiles = tx.object_store(keys::PROFILES)?;
|
||||
let joined = tx.object_store(keys::JOINED_USER_IDS)?;
|
||||
let invited = tx.object_store(keys::INVITED_USER_IDS)?;
|
||||
let user_ids = tx.object_store(keys::USER_IDS)?;
|
||||
let stripped_state = tx.object_store(keys::STRIPPED_ROOM_STATE)?;
|
||||
let stripped_joined = tx.object_store(keys::STRIPPED_JOINED_USER_IDS)?;
|
||||
let stripped_invited = tx.object_store(keys::STRIPPED_INVITED_USER_IDS)?;
|
||||
let stripped_user_ids = tx.object_store(keys::STRIPPED_USER_IDS)?;
|
||||
|
||||
for (room, event_types) in &changes.state {
|
||||
let profile_changes = changes.profiles.get(room);
|
||||
@@ -650,31 +608,13 @@ impl_state_store! {
|
||||
|
||||
let key = (room, state_key);
|
||||
|
||||
stripped_joined
|
||||
.delete(&self.encode_key(keys::STRIPPED_JOINED_USER_IDS, key))?;
|
||||
stripped_invited
|
||||
.delete(&self.encode_key(keys::STRIPPED_INVITED_USER_IDS, key))?;
|
||||
stripped_user_ids
|
||||
.delete(&self.encode_key(keys::STRIPPED_USER_IDS, key))?;
|
||||
|
||||
match event.membership() {
|
||||
MembershipState::Join => {
|
||||
joined.put_key_val_owned(
|
||||
&self.encode_key(keys::JOINED_USER_IDS, key),
|
||||
&self.serialize_event(state_key)?,
|
||||
user_ids.put_key_val_owned(
|
||||
&self.encode_key(keys::USER_IDS, key),
|
||||
&self.serialize_event(&RoomMember::from(&event))?,
|
||||
)?;
|
||||
invited.delete(&self.encode_key(keys::INVITED_USER_IDS, key))?;
|
||||
}
|
||||
MembershipState::Invite => {
|
||||
invited.put_key_val_owned(
|
||||
&self.encode_key(keys::INVITED_USER_IDS, key),
|
||||
&self.serialize_event(state_key)?,
|
||||
)?;
|
||||
joined.delete(&self.encode_key(keys::JOINED_USER_IDS, key))?;
|
||||
}
|
||||
_ => {
|
||||
joined.delete(&self.encode_key(keys::JOINED_USER_IDS, key))?;
|
||||
invited.delete(&self.encode_key(keys::INVITED_USER_IDS, key))?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(profile) = profile_changes.and_then(|p| p.get(event.state_key())) {
|
||||
profiles.put_key_val_owned(
|
||||
@@ -724,8 +664,7 @@ impl_state_store! {
|
||||
|
||||
if !changes.stripped_state.is_empty() {
|
||||
let store = tx.object_store(keys::STRIPPED_ROOM_STATE)?;
|
||||
let joined = tx.object_store(keys::STRIPPED_JOINED_USER_IDS)?;
|
||||
let invited = tx.object_store(keys::STRIPPED_INVITED_USER_IDS)?;
|
||||
let user_ids = tx.object_store(keys::STRIPPED_USER_IDS)?;
|
||||
|
||||
for (room, event_types) in &changes.stripped_state {
|
||||
for (event_type, events) in event_types {
|
||||
@@ -735,42 +674,24 @@ impl_state_store! {
|
||||
store.put_key_val(&key, &self.serialize_event(&raw_event)?)?;
|
||||
|
||||
if *event_type == StateEventType::RoomMember {
|
||||
let event = match raw_event.deserialize_as::<StrippedRoomMemberEvent>() {
|
||||
Ok(ev) => ev,
|
||||
Err(e) => {
|
||||
let event_id: Option<String> =
|
||||
raw_event.get_field("event_id").ok().flatten();
|
||||
debug!(event_id, "Failed to deserialize stripped member event: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let event = match raw_event.deserialize_as::<StrippedRoomMemberEvent>() {
|
||||
Ok(ev) => ev,
|
||||
Err(e) => {
|
||||
let event_id: Option<String> =
|
||||
raw_event.get_field("event_id").ok().flatten();
|
||||
debug!(event_id, "Failed to deserialize stripped member event: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let key = (room, state_key);
|
||||
let key = (room, state_key);
|
||||
|
||||
match event.content.membership {
|
||||
MembershipState::Join => {
|
||||
joined.put_key_val_owned(
|
||||
&self.encode_key(keys::STRIPPED_JOINED_USER_IDS, key),
|
||||
&self.serialize_event(state_key)?,
|
||||
)?;
|
||||
invited
|
||||
.delete(&self.encode_key(keys::STRIPPED_INVITED_USER_IDS, key))?;
|
||||
}
|
||||
MembershipState::Invite => {
|
||||
invited.put_key_val_owned(
|
||||
&self.encode_key(keys::STRIPPED_INVITED_USER_IDS, key),
|
||||
&self.serialize_event(state_key)?,
|
||||
)?;
|
||||
joined.delete(&self.encode_key(keys::STRIPPED_JOINED_USER_IDS, key))?;
|
||||
}
|
||||
_ => {
|
||||
joined.delete(&self.encode_key(keys::STRIPPED_JOINED_USER_IDS, key))?;
|
||||
invited
|
||||
.delete(&self.encode_key(keys::STRIPPED_INVITED_USER_IDS, key))?;
|
||||
}
|
||||
user_ids.put_key_val_owned(
|
||||
&self.encode_key(keys::STRIPPED_USER_IDS, key),
|
||||
&self.serialize_event(&RoomMember::from(&event))?,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1176,13 +1097,13 @@ impl_state_store! {
|
||||
let prefixed_stores = [
|
||||
keys::PROFILES,
|
||||
keys::DISPLAY_NAMES,
|
||||
keys::INVITED_USER_IDS,
|
||||
keys::JOINED_USER_IDS,
|
||||
keys::USER_IDS,
|
||||
keys::ROOM_STATE,
|
||||
keys::ROOM_ACCOUNT_DATA,
|
||||
keys::ROOM_EVENT_RECEIPTS,
|
||||
keys::ROOM_USER_RECEIPTS,
|
||||
keys::STRIPPED_ROOM_STATE,
|
||||
keys::STRIPPED_USER_IDS,
|
||||
];
|
||||
|
||||
let all_stores = {
|
||||
@@ -1210,28 +1131,39 @@ impl_state_store! {
|
||||
tx.await.into_result().map_err(|e| e.into())
|
||||
}
|
||||
|
||||
async fn get_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
|
||||
let ids: Vec<OwnedUserId> = self.get_stripped_user_ids_stream(room_id).await?;
|
||||
async fn get_user_ids(&self, room_id: &RoomId, memberships: RoomMemberships) -> Result<Vec<OwnedUserId>> {
|
||||
let ids = self.get_user_ids_inner(room_id, memberships, true).await?;
|
||||
if !ids.is_empty() {
|
||||
return Ok(ids);
|
||||
}
|
||||
self.get_user_ids_stream(room_id).await
|
||||
self.get_user_ids_inner(room_id, memberships, false).await
|
||||
}
|
||||
|
||||
async fn get_invited_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
|
||||
let ids: Vec<OwnedUserId> = self.get_stripped_invited_user_ids(room_id).await?;
|
||||
if !ids.is_empty() {
|
||||
return Ok(ids);
|
||||
}
|
||||
self.get_invited_user_ids_inner(room_id).await
|
||||
self.get_user_ids(room_id, RoomMemberships::INVITE).await
|
||||
}
|
||||
|
||||
async fn get_joined_user_ids(&self, room_id: &RoomId) -> Result<Vec<OwnedUserId>> {
|
||||
let ids: Vec<OwnedUserId> = self.get_stripped_joined_user_ids(room_id).await?;
|
||||
if !ids.is_empty() {
|
||||
return Ok(ids);
|
||||
}
|
||||
self.get_joined_user_ids_inner(room_id).await
|
||||
self.get_user_ids(room_id, RoomMemberships::JOIN).await
|
||||
}
|
||||
}
|
||||
|
||||
/// A room member.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct RoomMember {
|
||||
user_id: OwnedUserId,
|
||||
membership: MembershipState,
|
||||
}
|
||||
|
||||
impl From<&SyncStateEvent<RoomMemberEventContent>> for RoomMember {
|
||||
fn from(event: &SyncStateEvent<RoomMemberEventContent>) -> Self {
|
||||
Self { user_id: event.state_key().clone(), membership: event.membership().clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&StrippedRoomMemberEvent> for RoomMember {
|
||||
fn from(event: &StrippedRoomMemberEvent) -> Self {
|
||||
Self { user_id: event.state_key.clone(), membership: event.content.membership.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@ use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue};
|
||||
use sled::{transaction::TransactionError, Batch, Transactional, Tree};
|
||||
use tracing::debug;
|
||||
|
||||
use super::{keys, Result, SledStateStore, SledStoreError};
|
||||
use super::{keys, Result, RoomMember, SledStateStore, SledStoreError};
|
||||
use crate::encode_key::EncodeKey;
|
||||
|
||||
const DATABASE_VERSION: u8 = 5;
|
||||
const DATABASE_VERSION: u8 = 7;
|
||||
|
||||
const VERSION_KEY: &str = "state-store-version";
|
||||
|
||||
@@ -87,6 +87,13 @@ impl SledStateStore {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Version 6 was dropped and migration is similar to v7.
|
||||
|
||||
if old_version < 7 {
|
||||
self.migrate_to_v7()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// FUTURE UPGRADE CODE GOES HERE
|
||||
|
||||
// can't upgrade from that version to the new one
|
||||
@@ -270,6 +277,82 @@ impl SledStateStore {
|
||||
|
||||
self.set_db_version(5)
|
||||
}
|
||||
|
||||
/// Remove the old user IDs stores and populate the new ones.
|
||||
fn migrate_to_v7(&self) -> Result<()> {
|
||||
{
|
||||
// Reset v6 stores.
|
||||
self.user_ids.clear()?;
|
||||
self.stripped_user_ids.clear()?;
|
||||
|
||||
// We only have joined and invited user IDs in the old stores, so instead we
|
||||
// use the room member events to populate the new stores.
|
||||
let state = &self.inner.open_tree(keys::ROOM_STATE)?;
|
||||
let mut user_ids_batch = sled::Batch::default();
|
||||
|
||||
for room_info in
|
||||
self.room_info.iter().map(|r| self.deserialize_value::<RoomInfo>(&r?.1))
|
||||
{
|
||||
let room_info = room_info?;
|
||||
let room_id = room_info.room_id();
|
||||
let prefix =
|
||||
self.encode_key(keys::ROOM_STATE, (room_id, StateEventType::RoomMember));
|
||||
|
||||
for entry in state.scan_prefix(prefix) {
|
||||
let (_, value) = entry?;
|
||||
let member_event = self
|
||||
.deserialize_value::<Raw<SyncRoomMemberEvent>>(&value)?
|
||||
.deserialize()?;
|
||||
let key = self.encode_key(keys::USER_ID, (room_id, member_event.state_key()));
|
||||
let value = self.serialize_value(&RoomMember::from(&member_event))?;
|
||||
user_ids_batch.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
let stripped_state = &self.inner.open_tree(keys::STRIPPED_ROOM_STATE)?;
|
||||
let mut stripped_user_ids_batch = sled::Batch::default();
|
||||
|
||||
for room_info in
|
||||
self.stripped_room_infos.iter().map(|r| self.deserialize_value::<RoomInfo>(&r?.1))
|
||||
{
|
||||
let room_info = room_info?;
|
||||
let room_id = room_info.room_id();
|
||||
let prefix = self
|
||||
.encode_key(keys::STRIPPED_ROOM_STATE, (room_id, StateEventType::RoomMember));
|
||||
|
||||
for entry in stripped_state.scan_prefix(prefix) {
|
||||
let (_, value) = entry?;
|
||||
let stripped_member_event = self
|
||||
.deserialize_value::<Raw<StrippedRoomMemberEvent>>(&value)?
|
||||
.deserialize()?;
|
||||
let key = self.encode_key(
|
||||
keys::STRIPPED_USER_ID,
|
||||
(room_id, &stripped_member_event.state_key),
|
||||
);
|
||||
let value = self.serialize_value(&RoomMember::from(&stripped_member_event))?;
|
||||
stripped_user_ids_batch.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
let ret: Result<(), TransactionError<SledStoreError>> =
|
||||
(&self.user_ids, &self.stripped_user_ids).transaction(
|
||||
|(user_ids, stripped_user_ids)| {
|
||||
user_ids.apply_batch(&user_ids_batch)?;
|
||||
stripped_user_ids.apply_batch(&stripped_user_ids_batch)?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
ret?;
|
||||
}
|
||||
|
||||
self.inner.drop_tree(old_keys::JOINED_USER_ID)?;
|
||||
self.inner.drop_tree(old_keys::INVITED_USER_ID)?;
|
||||
self.inner.drop_tree(old_keys::STRIPPED_JOINED_USER_ID)?;
|
||||
self.inner.drop_tree(old_keys::STRIPPED_INVITED_USER_ID)?;
|
||||
|
||||
self.set_db_version(7)
|
||||
}
|
||||
}
|
||||
|
||||
mod old_keys {
|
||||
@@ -278,14 +361,18 @@ mod old_keys {
|
||||
pub const SESSION: &str = "session";
|
||||
pub const MEMBER: &str = "member";
|
||||
pub const STRIPPED_ROOM_MEMBER: &str = "stripped-room-member";
|
||||
pub const INVITED_USER_ID: &str = "invited-user-id";
|
||||
pub const JOINED_USER_ID: &str = "joined-user-id";
|
||||
pub const STRIPPED_INVITED_USER_ID: &str = "stripped-invited-user-id";
|
||||
pub const STRIPPED_JOINED_USER_ID: &str = "stripped-joined-user-id";
|
||||
}
|
||||
|
||||
pub const V1_DB_STORES: &[&str] = &[
|
||||
keys::ACCOUNT_DATA,
|
||||
old_keys::SYNC_TOKEN,
|
||||
keys::DISPLAY_NAME,
|
||||
keys::INVITED_USER_ID,
|
||||
keys::JOINED_USER_ID,
|
||||
old_keys::INVITED_USER_ID,
|
||||
old_keys::JOINED_USER_ID,
|
||||
keys::MEDIA,
|
||||
old_keys::MEMBER,
|
||||
keys::PRESENCE,
|
||||
@@ -297,8 +384,8 @@ pub const V1_DB_STORES: &[&str] = &[
|
||||
keys::ROOM_USER_RECEIPT,
|
||||
keys::ROOM,
|
||||
old_keys::SESSION,
|
||||
keys::STRIPPED_INVITED_USER_ID,
|
||||
keys::STRIPPED_JOINED_USER_ID,
|
||||
old_keys::STRIPPED_INVITED_USER_ID,
|
||||
old_keys::STRIPPED_JOINED_USER_ID,
|
||||
keys::STRIPPED_ROOM_INFO,
|
||||
old_keys::STRIPPED_ROOM_MEMBER,
|
||||
keys::STRIPPED_ROOM_STATE,
|
||||
@@ -309,7 +396,8 @@ pub const V1_DB_STORES: &[&str] = &[
|
||||
mod test {
|
||||
use assert_matches::assert_matches;
|
||||
use matrix_sdk_base::{
|
||||
deserialized_responses::RawMemberEvent, RoomInfo, RoomState, StateStoreDataKey,
|
||||
deserialized_responses::RawMemberEvent, RoomInfo, RoomMemberships, RoomState,
|
||||
StateStoreDataKey,
|
||||
};
|
||||
use matrix_sdk_test::{async_test, test_json};
|
||||
use ruma::{
|
||||
@@ -613,4 +701,122 @@ mod test {
|
||||
);
|
||||
assert_eq!(stored_stripped_member_event.json().get(), stripped_member_event.json().get());
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
pub async fn migrating_v5_to_v7() {
|
||||
let room_id = room_id!("!room:localhost");
|
||||
let invite_member_event =
|
||||
Raw::new(&*test_json::MEMBER_INVITE).unwrap().cast::<SyncRoomMemberEvent>();
|
||||
let invite_user_id = user_id!("@invited:localhost");
|
||||
let ban_member_event =
|
||||
Raw::new(&*test_json::MEMBER_BAN).unwrap().cast::<SyncRoomMemberEvent>();
|
||||
let ban_user_id = user_id!("@banned:localhost");
|
||||
|
||||
let stripped_room_id = room_id!("!stripped_room:localhost");
|
||||
let stripped_member_event =
|
||||
Raw::new(&*test_json::MEMBER_STRIPPED).unwrap().cast::<StrippedRoomMemberEvent>();
|
||||
let stripped_user_id = user_id!("@example:localhost");
|
||||
|
||||
let folder = TempDir::new().unwrap();
|
||||
{
|
||||
let store = SledStateStore::builder()
|
||||
.path(folder.path().to_path_buf())
|
||||
.passphrase("secret".to_owned())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let state = store.inner.open_tree(keys::ROOM_STATE).unwrap();
|
||||
state
|
||||
.insert(
|
||||
store.encode_key(
|
||||
keys::ROOM_STATE,
|
||||
(room_id, StateEventType::RoomMember, invite_user_id),
|
||||
),
|
||||
store.serialize_value(&invite_member_event).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
state
|
||||
.insert(
|
||||
store.encode_key(
|
||||
keys::ROOM_STATE,
|
||||
(room_id, StateEventType::RoomMember, ban_user_id),
|
||||
),
|
||||
store.serialize_value(&ban_member_event).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let room_infos = store.inner.open_tree(keys::ROOM_INFO).unwrap();
|
||||
let room_info = RoomInfo::new(room_id, RoomState::Joined);
|
||||
room_infos
|
||||
.insert(
|
||||
store.encode_key(keys::ROOM_INFO, room_id),
|
||||
store.serialize_value(&room_info).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let stripped_state = store.inner.open_tree(keys::STRIPPED_ROOM_STATE).unwrap();
|
||||
stripped_state
|
||||
.insert(
|
||||
store.encode_key(
|
||||
keys::STRIPPED_ROOM_STATE,
|
||||
(stripped_room_id, StateEventType::RoomMember, stripped_user_id),
|
||||
),
|
||||
store.serialize_value(&stripped_member_event).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let stripped_room_infos = store.inner.open_tree(keys::STRIPPED_ROOM_INFO).unwrap();
|
||||
let stripped_room_info = RoomInfo::new(stripped_room_id, RoomState::Invited);
|
||||
stripped_room_infos
|
||||
.insert(
|
||||
store.encode_key(keys::STRIPPED_ROOM_INFO, stripped_room_id),
|
||||
store.serialize_value(&stripped_room_info).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
store.set_db_version(5).unwrap();
|
||||
}
|
||||
|
||||
let store = SledStateStore::builder()
|
||||
.path(folder.path().to_path_buf())
|
||||
.passphrase("secret".to_owned())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
store.get_user_ids(room_id, RoomMemberships::JOIN, false).await.unwrap().len(),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_user_ids(room_id, RoomMemberships::INVITE, false).await.unwrap().as_slice(),
|
||||
[invite_user_id.to_owned()]
|
||||
);
|
||||
let user_ids = store.get_user_ids(room_id, RoomMemberships::empty(), false).await.unwrap();
|
||||
assert_eq!(user_ids.len(), 2);
|
||||
assert!(user_ids.contains(&invite_user_id.to_owned()));
|
||||
assert!(user_ids.contains(&ban_user_id.to_owned()));
|
||||
|
||||
assert_eq!(
|
||||
store
|
||||
.get_user_ids(stripped_room_id, RoomMemberships::JOIN, true)
|
||||
.await
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
[stripped_user_id.to_owned()]
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.get_user_ids(stripped_room_id, RoomMemberships::INVITE, true)
|
||||
.await
|
||||
.unwrap()
|
||||
.len(),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.get_user_ids(stripped_room_id, RoomMemberships::empty(), true)
|
||||
.await
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
[stripped_user_id.to_owned()]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ use std::{
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures_core::stream::Stream;
|
||||
use futures_util::stream::{self, StreamExt, TryStreamExt};
|
||||
use futures_util::stream::{self, TryStreamExt};
|
||||
use matrix_sdk_base::{
|
||||
deserialized_responses::RawMemberEvent,
|
||||
media::{MediaRequest, UniqueKey},
|
||||
store::{Result as StoreResult, StateChanges, StateStore, StoreError},
|
||||
MinimalStateEvent, RoomInfo, StateStoreDataKey, StateStoreDataValue,
|
||||
MinimalStateEvent, RoomInfo, RoomMemberships, StateStoreDataKey, StateStoreDataValue,
|
||||
};
|
||||
use matrix_sdk_store_encryption::{Error as KeyEncryptionError, StoreCipher};
|
||||
use ruma::{
|
||||
@@ -38,13 +38,13 @@ use ruma::{
|
||||
MembershipState, RoomMemberEventContent, StrippedRoomMemberEvent, SyncRoomMemberEvent,
|
||||
},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent,
|
||||
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, SyncStateEvent,
|
||||
},
|
||||
serde::Raw,
|
||||
CanonicalJsonObject, EventId, IdParseError, MxcUri, OwnedEventId, OwnedUserId, RoomId,
|
||||
RoomVersionId, UserId,
|
||||
};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use sled::{
|
||||
transaction::{ConflictableTransactionError, TransactionError},
|
||||
Config, Db, Transactional, Tree,
|
||||
@@ -112,8 +112,7 @@ mod keys {
|
||||
pub const ACCOUNT_DATA: &str = "account-data";
|
||||
pub const CUSTOM: &str = "custom";
|
||||
pub const DISPLAY_NAME: &str = "display-name";
|
||||
pub const INVITED_USER_ID: &str = "invited-user-id";
|
||||
pub const JOINED_USER_ID: &str = "joined-user-id";
|
||||
pub const USER_ID: &str = "user-ids";
|
||||
pub const MEDIA: &str = "media";
|
||||
pub const PRESENCE: &str = "presence";
|
||||
pub const PROFILE: &str = "profile";
|
||||
@@ -123,8 +122,7 @@ mod keys {
|
||||
pub const ROOM_STATE: &str = "room-state";
|
||||
pub const ROOM_USER_RECEIPT: &str = "room-user-receipt";
|
||||
pub const ROOM: &str = "room";
|
||||
pub const STRIPPED_INVITED_USER_ID: &str = "stripped-invited-user-id";
|
||||
pub const STRIPPED_JOINED_USER_ID: &str = "stripped-joined-user-id";
|
||||
pub const STRIPPED_USER_ID: &str = "stripped-user-ids";
|
||||
pub const STRIPPED_ROOM_INFO: &str = "stripped-room-info";
|
||||
pub const STRIPPED_ROOM_STATE: &str = "stripped-room-state";
|
||||
pub const KV: &str = "kv";
|
||||
@@ -282,13 +280,11 @@ pub struct SledStateStore {
|
||||
account_data: Tree,
|
||||
profiles: Tree,
|
||||
display_names: Tree,
|
||||
joined_user_ids: Tree,
|
||||
invited_user_ids: Tree,
|
||||
user_ids: Tree,
|
||||
room_info: Tree,
|
||||
room_state: Tree,
|
||||
room_account_data: Tree,
|
||||
stripped_joined_user_ids: Tree,
|
||||
stripped_invited_user_ids: Tree,
|
||||
stripped_user_ids: Tree,
|
||||
stripped_room_infos: Tree,
|
||||
stripped_room_state: Tree,
|
||||
presence: Tree,
|
||||
@@ -319,16 +315,14 @@ impl SledStateStore {
|
||||
|
||||
let profiles = db.open_tree(keys::PROFILE)?;
|
||||
let display_names = db.open_tree(keys::DISPLAY_NAME)?;
|
||||
let joined_user_ids = db.open_tree(keys::JOINED_USER_ID)?;
|
||||
let invited_user_ids = db.open_tree(keys::INVITED_USER_ID)?;
|
||||
let user_ids = db.open_tree(keys::USER_ID)?;
|
||||
|
||||
let room_state = db.open_tree(keys::ROOM_STATE)?;
|
||||
let room_info = db.open_tree(keys::ROOM_INFO)?;
|
||||
let presence = db.open_tree(keys::PRESENCE)?;
|
||||
let room_account_data = db.open_tree(keys::ROOM_ACCOUNT_DATA)?;
|
||||
|
||||
let stripped_joined_user_ids = db.open_tree(keys::STRIPPED_JOINED_USER_ID)?;
|
||||
let stripped_invited_user_ids = db.open_tree(keys::STRIPPED_INVITED_USER_ID)?;
|
||||
let stripped_user_ids = db.open_tree(keys::STRIPPED_USER_ID)?;
|
||||
let stripped_room_infos = db.open_tree(keys::STRIPPED_ROOM_INFO)?;
|
||||
let stripped_room_state = db.open_tree(keys::STRIPPED_ROOM_STATE)?;
|
||||
|
||||
@@ -347,14 +341,12 @@ impl SledStateStore {
|
||||
account_data,
|
||||
profiles,
|
||||
display_names,
|
||||
joined_user_ids,
|
||||
invited_user_ids,
|
||||
user_ids,
|
||||
room_account_data,
|
||||
presence,
|
||||
room_state,
|
||||
room_info,
|
||||
stripped_joined_user_ids,
|
||||
stripped_invited_user_ids,
|
||||
stripped_user_ids,
|
||||
stripped_room_infos,
|
||||
stripped_room_state,
|
||||
room_user_receipts,
|
||||
@@ -467,13 +459,11 @@ impl SledStateStore {
|
||||
let ret: Result<(), TransactionError<SledStoreError>> = (
|
||||
&self.profiles,
|
||||
&self.display_names,
|
||||
&self.joined_user_ids,
|
||||
&self.invited_user_ids,
|
||||
&self.user_ids,
|
||||
&self.room_info,
|
||||
&self.room_state,
|
||||
&self.room_account_data,
|
||||
&self.stripped_joined_user_ids,
|
||||
&self.stripped_invited_user_ids,
|
||||
&self.stripped_user_ids,
|
||||
&self.stripped_room_infos,
|
||||
&self.stripped_room_state,
|
||||
)
|
||||
@@ -481,13 +471,11 @@ impl SledStateStore {
|
||||
|(
|
||||
profiles,
|
||||
display_names,
|
||||
joined,
|
||||
invited,
|
||||
user_ids,
|
||||
rooms,
|
||||
state,
|
||||
room_account_data,
|
||||
stripped_joined,
|
||||
stripped_invited,
|
||||
stripped_user_ids,
|
||||
stripped_rooms,
|
||||
stripped_state,
|
||||
)| {
|
||||
@@ -546,43 +534,14 @@ impl SledStateStore {
|
||||
|
||||
let key = (room, state_key);
|
||||
|
||||
stripped_joined.remove(
|
||||
self.encode_key(keys::STRIPPED_JOINED_USER_ID, key),
|
||||
)?;
|
||||
stripped_invited.remove(
|
||||
self.encode_key(keys::STRIPPED_INVITED_USER_ID, key),
|
||||
)?;
|
||||
stripped_user_ids
|
||||
.remove(self.encode_key(keys::STRIPPED_USER_ID, key))?;
|
||||
|
||||
match event.membership() {
|
||||
MembershipState::Join => {
|
||||
joined.insert(
|
||||
self.encode_key(keys::JOINED_USER_ID, key),
|
||||
self.serialize_value(state_key)
|
||||
.map_err(ConflictableTransactionError::Abort)?,
|
||||
)?;
|
||||
invited.remove(
|
||||
self.encode_key(keys::INVITED_USER_ID, key),
|
||||
)?;
|
||||
}
|
||||
MembershipState::Invite => {
|
||||
invited.insert(
|
||||
self.encode_key(keys::INVITED_USER_ID, key),
|
||||
self.serialize_value(state_key)
|
||||
.map_err(ConflictableTransactionError::Abort)?,
|
||||
)?;
|
||||
joined.remove(
|
||||
self.encode_key(keys::JOINED_USER_ID, key),
|
||||
)?;
|
||||
}
|
||||
_ => {
|
||||
joined.remove(
|
||||
self.encode_key(keys::JOINED_USER_ID, key),
|
||||
)?;
|
||||
invited.remove(
|
||||
self.encode_key(keys::INVITED_USER_ID, key),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
user_ids.insert(
|
||||
self.encode_key(keys::USER_ID, key),
|
||||
self.serialize_value(&RoomMember::from(&event))
|
||||
.map_err(ConflictableTransactionError::Abort)?,
|
||||
)?;
|
||||
|
||||
if let Some(profile) =
|
||||
profile_changes.and_then(|p| p.get(event.state_key()))
|
||||
@@ -646,46 +605,11 @@ impl SledStateStore {
|
||||
};
|
||||
|
||||
let key = (room, state_key);
|
||||
|
||||
match event.content.membership {
|
||||
MembershipState::Join => {
|
||||
stripped_joined.insert(
|
||||
self.encode_key(keys::STRIPPED_JOINED_USER_ID, key),
|
||||
self.serialize_value(state_key)
|
||||
.map_err(ConflictableTransactionError::Abort)?,
|
||||
)?;
|
||||
stripped_invited.remove(
|
||||
self.encode_key(
|
||||
keys::STRIPPED_INVITED_USER_ID,
|
||||
key,
|
||||
),
|
||||
)?;
|
||||
}
|
||||
MembershipState::Invite => {
|
||||
stripped_invited.insert(
|
||||
self.encode_key(
|
||||
keys::STRIPPED_INVITED_USER_ID,
|
||||
key,
|
||||
),
|
||||
self.serialize_value(state_key)
|
||||
.map_err(ConflictableTransactionError::Abort)?,
|
||||
)?;
|
||||
stripped_joined.remove(
|
||||
self.encode_key(keys::STRIPPED_JOINED_USER_ID, key),
|
||||
)?;
|
||||
}
|
||||
_ => {
|
||||
stripped_joined.remove(
|
||||
self.encode_key(keys::STRIPPED_JOINED_USER_ID, key),
|
||||
)?;
|
||||
stripped_invited.remove(
|
||||
self.encode_key(
|
||||
keys::STRIPPED_INVITED_USER_ID,
|
||||
key,
|
||||
),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
stripped_user_ids.insert(
|
||||
self.encode_key(keys::STRIPPED_USER_ID, key),
|
||||
self.serialize_value(&RoomMember::from(&event))
|
||||
.map_err(ConflictableTransactionError::Abort)?,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -930,87 +854,34 @@ impl SledStateStore {
|
||||
.await?
|
||||
}
|
||||
|
||||
pub async fn get_user_ids_stream(
|
||||
/// Get the user IDs for the given room with the given memberships and
|
||||
/// stripped state.
|
||||
pub async fn get_user_ids(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
|
||||
Ok(self
|
||||
.get_joined_user_ids(room_id)
|
||||
.await?
|
||||
.chain(self.get_invited_user_ids(room_id).await?))
|
||||
}
|
||||
pub async fn get_stripped_user_ids_stream(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
|
||||
Ok(self
|
||||
.get_stripped_joined_user_ids(room_id)
|
||||
.await?
|
||||
.chain(self.get_stripped_invited_user_ids(room_id).await?))
|
||||
}
|
||||
|
||||
pub async fn get_invited_user_ids(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
|
||||
memberships: RoomMemberships,
|
||||
stripped: bool,
|
||||
) -> StoreResult<Vec<OwnedUserId>> {
|
||||
let db = self.clone();
|
||||
let key = self.encode_key(keys::INVITED_USER_ID, room_id);
|
||||
let store_name = if stripped { keys::STRIPPED_USER_ID } else { keys::USER_ID };
|
||||
let key = self.encode_key(store_name, room_id);
|
||||
|
||||
spawn_blocking(move || {
|
||||
stream::iter(db.invited_user_ids.scan_prefix(key).map(move |u| {
|
||||
db.deserialize_value(&u.map_err(StoreError::backend)?.1)
|
||||
.map_err(StoreError::backend)
|
||||
}))
|
||||
let tree = if stripped { &db.stripped_user_ids } else { &db.user_ids };
|
||||
|
||||
tree.scan_prefix(key)
|
||||
.map(move |u| {
|
||||
let member = db
|
||||
.deserialize_value::<RoomMember>(&u.map_err(StoreError::backend)?.1)
|
||||
.map_err(StoreError::backend)?;
|
||||
|
||||
Ok(memberships.matches(&member.membership).then_some(member.user_id))
|
||||
})
|
||||
.filter_map(|u| u.transpose())
|
||||
.collect::<StoreResult<Vec<_>>>()
|
||||
})
|
||||
.await
|
||||
.map_err(StoreError::backend)
|
||||
}
|
||||
|
||||
pub async fn get_joined_user_ids(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
|
||||
let db = self.clone();
|
||||
let key = self.encode_key(keys::JOINED_USER_ID, room_id);
|
||||
spawn_blocking(move || {
|
||||
stream::iter(db.joined_user_ids.scan_prefix(key).map(move |u| {
|
||||
db.deserialize_value(&u.map_err(StoreError::backend)?.1)
|
||||
.map_err(StoreError::backend)
|
||||
}))
|
||||
})
|
||||
.await
|
||||
.map_err(StoreError::backend)
|
||||
}
|
||||
|
||||
pub async fn get_stripped_invited_user_ids(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
|
||||
let db = self.clone();
|
||||
let key = self.encode_key(keys::STRIPPED_INVITED_USER_ID, room_id);
|
||||
spawn_blocking(move || {
|
||||
stream::iter(db.stripped_invited_user_ids.scan_prefix(key).map(move |u| {
|
||||
db.deserialize_value(&u.map_err(StoreError::backend)?.1)
|
||||
.map_err(StoreError::backend)
|
||||
}))
|
||||
})
|
||||
.await
|
||||
.map_err(StoreError::backend)
|
||||
}
|
||||
|
||||
pub async fn get_stripped_joined_user_ids(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
) -> StoreResult<impl Stream<Item = StoreResult<OwnedUserId>>> {
|
||||
let db = self.clone();
|
||||
let key = self.encode_key(keys::STRIPPED_JOINED_USER_ID, room_id);
|
||||
spawn_blocking(move || {
|
||||
stream::iter(db.stripped_joined_user_ids.scan_prefix(key).map(move |u| {
|
||||
db.deserialize_value(&u.map_err(StoreError::backend)?.1)
|
||||
.map_err(StoreError::backend)
|
||||
}))
|
||||
})
|
||||
.await
|
||||
.map_err(StoreError::backend)
|
||||
.map_err(StoreError::backend)?
|
||||
}
|
||||
|
||||
pub async fn get_room_infos(&self) -> Result<impl Stream<Item = Result<RoomInfo>>> {
|
||||
@@ -1210,38 +1081,18 @@ impl SledStateStore {
|
||||
display_names_batch.remove(key?);
|
||||
}
|
||||
|
||||
let mut joined_user_ids_batch = sled::Batch::default();
|
||||
for key in
|
||||
self.joined_user_ids.scan_prefix(self.encode_key(keys::JOINED_USER_ID, room_id)).keys()
|
||||
{
|
||||
joined_user_ids_batch.remove(key?);
|
||||
let mut user_ids_batch = sled::Batch::default();
|
||||
for key in self.user_ids.scan_prefix(self.encode_key(keys::USER_ID, room_id)).keys() {
|
||||
user_ids_batch.remove(key?);
|
||||
}
|
||||
|
||||
let mut stripped_joined_user_ids_batch = sled::Batch::default();
|
||||
let mut stripped_user_ids_batch = sled::Batch::default();
|
||||
for key in self
|
||||
.stripped_joined_user_ids
|
||||
.scan_prefix(self.encode_key(keys::STRIPPED_JOINED_USER_ID, room_id))
|
||||
.stripped_user_ids
|
||||
.scan_prefix(self.encode_key(keys::STRIPPED_USER_ID, room_id))
|
||||
.keys()
|
||||
{
|
||||
stripped_joined_user_ids_batch.remove(key?);
|
||||
}
|
||||
|
||||
let mut invited_user_ids_batch = sled::Batch::default();
|
||||
for key in self
|
||||
.invited_user_ids
|
||||
.scan_prefix(self.encode_key(keys::INVITED_USER_ID, room_id))
|
||||
.keys()
|
||||
{
|
||||
invited_user_ids_batch.remove(key?);
|
||||
}
|
||||
|
||||
let mut stripped_invited_user_ids_batch = sled::Batch::default();
|
||||
for key in self
|
||||
.stripped_invited_user_ids
|
||||
.scan_prefix(self.encode_key(keys::STRIPPED_INVITED_USER_ID, room_id))
|
||||
.keys()
|
||||
{
|
||||
stripped_invited_user_ids_batch.remove(key?);
|
||||
stripped_user_ids_batch.remove(key?);
|
||||
}
|
||||
|
||||
let mut room_state_batch = sled::Batch::default();
|
||||
@@ -1270,10 +1121,8 @@ impl SledStateStore {
|
||||
let ret: Result<(), TransactionError<SledStoreError>> = (
|
||||
&self.profiles,
|
||||
&self.display_names,
|
||||
&self.joined_user_ids,
|
||||
&self.stripped_joined_user_ids,
|
||||
&self.invited_user_ids,
|
||||
&self.stripped_invited_user_ids,
|
||||
&self.user_ids,
|
||||
&self.stripped_user_ids,
|
||||
&self.room_info,
|
||||
&self.stripped_room_infos,
|
||||
&self.room_state,
|
||||
@@ -1284,10 +1133,8 @@ impl SledStateStore {
|
||||
|(
|
||||
profiles,
|
||||
display_names,
|
||||
joined,
|
||||
stripped_joined,
|
||||
invited,
|
||||
stripped_invited,
|
||||
user_ids,
|
||||
stripped_user_ids,
|
||||
rooms,
|
||||
stripped_rooms,
|
||||
state,
|
||||
@@ -1299,10 +1146,8 @@ impl SledStateStore {
|
||||
|
||||
profiles.apply_batch(&profiles_batch)?;
|
||||
display_names.apply_batch(&display_names_batch)?;
|
||||
joined.apply_batch(&joined_user_ids_batch)?;
|
||||
stripped_joined.apply_batch(&stripped_joined_user_ids_batch)?;
|
||||
invited.apply_batch(&invited_user_ids_batch)?;
|
||||
stripped_invited.apply_batch(&stripped_invited_user_ids_batch)?;
|
||||
user_ids.apply_batch(&user_ids_batch)?;
|
||||
stripped_user_ids.apply_batch(&stripped_user_ids_batch)?;
|
||||
state.apply_batch(&room_state_batch)?;
|
||||
stripped_state.apply_batch(&stripped_room_state_batch)?;
|
||||
room_account_data.apply_batch(&room_account_data_batch)?;
|
||||
@@ -1413,31 +1258,24 @@ impl StateStore for SledStateStore {
|
||||
self.get_member_event(room_id, state_key).await.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn get_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<OwnedUserId>> {
|
||||
let v: Vec<OwnedUserId> =
|
||||
self.get_stripped_user_ids_stream(room_id).await?.try_collect().await?;
|
||||
async fn get_user_ids(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
memberships: RoomMemberships,
|
||||
) -> StoreResult<Vec<OwnedUserId>> {
|
||||
let v = self.get_user_ids(room_id, memberships, true).await?;
|
||||
if !v.is_empty() {
|
||||
return Ok(v);
|
||||
}
|
||||
self.get_user_ids_stream(room_id).await?.try_collect().await
|
||||
self.get_user_ids(room_id, memberships, false).await
|
||||
}
|
||||
|
||||
async fn get_invited_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<OwnedUserId>> {
|
||||
let v: Vec<OwnedUserId> =
|
||||
self.get_stripped_invited_user_ids(room_id).await?.try_collect().await?;
|
||||
if !v.is_empty() {
|
||||
return Ok(v);
|
||||
}
|
||||
self.get_invited_user_ids(room_id).await?.try_collect().await
|
||||
StateStore::get_user_ids(self, room_id, RoomMemberships::INVITE).await
|
||||
}
|
||||
|
||||
async fn get_joined_user_ids(&self, room_id: &RoomId) -> StoreResult<Vec<OwnedUserId>> {
|
||||
let v: Vec<OwnedUserId> =
|
||||
self.get_stripped_joined_user_ids(room_id).await?.try_collect().await?;
|
||||
if !v.is_empty() {
|
||||
return Ok(v);
|
||||
}
|
||||
self.get_joined_user_ids(room_id).await?.try_collect().await
|
||||
StateStore::get_user_ids(self, room_id, RoomMemberships::JOIN).await
|
||||
}
|
||||
|
||||
async fn get_room_infos(&self) -> StoreResult<Vec<RoomInfo>> {
|
||||
@@ -1538,6 +1376,25 @@ impl StateStore for SledStateStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// A room member.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct RoomMember {
|
||||
user_id: OwnedUserId,
|
||||
membership: MembershipState,
|
||||
}
|
||||
|
||||
impl From<&SyncStateEvent<RoomMemberEventContent>> for RoomMember {
|
||||
fn from(event: &SyncStateEvent<RoomMemberEventContent>) -> Self {
|
||||
Self { user_id: event.state_key().clone(), membership: event.membership().clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&StrippedRoomMemberEvent> for RoomMember {
|
||||
fn from(event: &StrippedRoomMemberEvent) -> Self {
|
||||
Self { user_id: event.state_key.clone(), membership: event.content.membership.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use matrix_sdk_base::statestore_integration_tests;
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# unreleased
|
||||
|
||||
- `Common::members` and `Common::members_no_sync` take a `RoomMemberships` to be able to filter the
|
||||
results by any membership state.
|
||||
- `Common::active_members(_no_sync)` and `Common::joined_members(_no_sync)` are deprecated.
|
||||
|
||||
# 0.6.2
|
||||
|
||||
- Fix the access token being printed in tracing span fields.
|
||||
|
||||
@@ -14,8 +14,12 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use matrix_sdk_base::crypto::{
|
||||
types::MasterPubkey, OwnUserIdentity as InnerOwnUserIdentity, UserIdentity as InnerUserIdentity,
|
||||
use matrix_sdk_base::{
|
||||
crypto::{
|
||||
types::MasterPubkey, OwnUserIdentity as InnerOwnUserIdentity,
|
||||
UserIdentity as InnerUserIdentity,
|
||||
},
|
||||
RoomMemberships,
|
||||
};
|
||||
use ruma::{
|
||||
events::{
|
||||
@@ -465,7 +469,7 @@ impl OtherUserIdentity {
|
||||
let room = if let Some(room) = self.direct_message_room.read().await.as_ref() {
|
||||
// Make sure that the user, to be verified, is still in the room
|
||||
if !room
|
||||
.active_members()
|
||||
.members(RoomMemberships::ACTIVE)
|
||||
.await?
|
||||
.iter()
|
||||
.any(|member| member.user_id() == self.inner.user_id())
|
||||
|
||||
@@ -20,7 +20,7 @@ pub use async_trait::async_trait;
|
||||
pub use bytes;
|
||||
pub use matrix_sdk_base::{
|
||||
deserialized_responses, DisplayName, Room as BaseRoom, RoomInfo, RoomMember as BaseRoomMember,
|
||||
RoomState, Session, StateChanges, StoreError,
|
||||
RoomMemberships, RoomState, Session, StateChanges, StoreError,
|
||||
};
|
||||
pub use matrix_sdk_common::*;
|
||||
pub use reqwest;
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{borrow::Borrow, collections::BTreeMap, ops::Deref, sync::Arc};
|
||||
use matrix_sdk_base::{
|
||||
deserialized_responses::{MembersResponse, TimelineEvent},
|
||||
store::StateStoreExt,
|
||||
StateChanges,
|
||||
RoomMemberships, StateChanges,
|
||||
};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use ruma::events::{
|
||||
@@ -431,9 +431,10 @@ impl Common {
|
||||
///
|
||||
/// Use [active_members_no_sync()](#method.active_members_no_sync) if you
|
||||
/// want a method that doesn't do any requests.
|
||||
#[deprecated = "Use members with RoomMemberships::ACTIVE instead"]
|
||||
pub async fn active_members(&self) -> Result<Vec<RoomMember>> {
|
||||
self.ensure_members().await?;
|
||||
self.active_members_no_sync().await
|
||||
self.members_no_sync(RoomMemberships::ACTIVE).await
|
||||
}
|
||||
|
||||
/// Get active members for this room, includes invited, joined members.
|
||||
@@ -444,14 +445,9 @@ impl Common {
|
||||
///
|
||||
/// Use [active_members()](#method.active_members) if you want to ensure to
|
||||
/// always get the full member list.
|
||||
#[deprecated = "Use members_no_sync with RoomMemberships::ACTIVE instead"]
|
||||
pub async fn active_members_no_sync(&self) -> Result<Vec<RoomMember>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.active_members()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|member| RoomMember::new(self.client.clone(), member))
|
||||
.collect())
|
||||
self.members_no_sync(RoomMemberships::ACTIVE).await
|
||||
}
|
||||
|
||||
/// Get all the joined members of this room.
|
||||
@@ -462,9 +458,10 @@ impl Common {
|
||||
///
|
||||
/// Use [joined_members_no_sync()](#method.joined_members_no_sync) if you
|
||||
/// want a method that doesn't do any requests.
|
||||
#[deprecated = "Use members with RoomMemberships::JOIN instead"]
|
||||
pub async fn joined_members(&self) -> Result<Vec<RoomMember>> {
|
||||
self.ensure_members().await?;
|
||||
self.joined_members_no_sync().await
|
||||
self.members_no_sync(RoomMemberships::JOIN).await
|
||||
}
|
||||
|
||||
/// Get all the joined members of this room.
|
||||
@@ -475,14 +472,9 @@ impl Common {
|
||||
///
|
||||
/// Use [joined_members()](#method.joined_members) if you want to ensure to
|
||||
/// always get the full member list.
|
||||
#[deprecated = "Use members_no_sync with RoomMemberships::JOIN instead"]
|
||||
pub async fn joined_members_no_sync(&self) -> Result<Vec<RoomMember>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.joined_members()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|member| RoomMember::new(self.client.clone(), member))
|
||||
.collect())
|
||||
self.members_no_sync(RoomMemberships::JOIN).await
|
||||
}
|
||||
|
||||
/// Get a specific member of this room.
|
||||
@@ -524,8 +516,7 @@ impl Common {
|
||||
.map(|member| RoomMember::new(self.client.clone(), member)))
|
||||
}
|
||||
|
||||
/// Get all members for this room, includes invited, joined and left
|
||||
/// members.
|
||||
/// Get members for this room, with the given memberships.
|
||||
///
|
||||
/// *Note*: This method will fetch the members from the homeserver if the
|
||||
/// member list isn't synchronized due to member lazy loading. Because of
|
||||
@@ -533,13 +524,12 @@ impl Common {
|
||||
///
|
||||
/// Use [members_no_sync()](#method.members_no_sync) if you want a
|
||||
/// method that doesn't do any requests.
|
||||
pub async fn members(&self) -> Result<Vec<RoomMember>> {
|
||||
pub async fn members(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
|
||||
self.ensure_members().await?;
|
||||
self.members_no_sync().await
|
||||
self.members_no_sync(memberships).await
|
||||
}
|
||||
|
||||
/// Get all members for this room, includes invited, joined and left
|
||||
/// members.
|
||||
/// Get members for this room, with the given memberships.
|
||||
///
|
||||
/// *Note*: This method will not fetch the members from the homeserver if
|
||||
/// the member list isn't synchronized due to member lazy loading. Thus,
|
||||
@@ -547,10 +537,10 @@ impl Common {
|
||||
///
|
||||
/// Use [members()](#method.members) if you want to ensure to always get
|
||||
/// the full member list.
|
||||
pub async fn members_no_sync(&self) -> Result<Vec<RoomMember>> {
|
||||
pub async fn members_no_sync(&self, memberships: RoomMemberships) -> Result<Vec<RoomMember>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.members()
|
||||
.members(memberships)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|member| RoomMember::new(self.client.clone(), member))
|
||||
@@ -701,7 +691,8 @@ impl Common {
|
||||
/// Returns true if all devices in the room are verified, otherwise false.
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
pub async fn contains_only_verified_devices(&self) -> Result<bool> {
|
||||
let user_ids = self.client.store().get_user_ids(self.room_id()).await?;
|
||||
let user_ids =
|
||||
self.client.store().get_user_ids(self.room_id(), RoomMemberships::empty()).await?;
|
||||
|
||||
for user_id in user_ids {
|
||||
let devices = self.client.encryption().get_user_devices(&user_id).await?;
|
||||
@@ -800,7 +791,7 @@ impl Common {
|
||||
let this_room_id = self.inner.room_id();
|
||||
|
||||
if is_direct {
|
||||
let mut room_members = self.active_members().await?;
|
||||
let mut room_members = self.members(RoomMemberships::ACTIVE).await?;
|
||||
room_members.retain(|member| member.user_id() != self.own_user_id());
|
||||
|
||||
for member in room_members {
|
||||
@@ -866,7 +857,7 @@ impl Common {
|
||||
// - Are blocked due to server ACLs
|
||||
// - Are IP addresses
|
||||
let members: Vec<_> = self
|
||||
.joined_members_no_sync()
|
||||
.members_no_sync(RoomMemberships::JOIN)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|member| {
|
||||
|
||||
@@ -4,6 +4,8 @@ use std::io::Cursor;
|
||||
use std::sync::Arc;
|
||||
use std::{borrow::Borrow, ops::Deref};
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use matrix_sdk_base::RoomMemberships;
|
||||
use matrix_sdk_common::instant::{Duration, Instant};
|
||||
use mime::{self, Mime};
|
||||
use ruma::{
|
||||
@@ -385,11 +387,12 @@ impl Joined {
|
||||
let _guard = mutex.lock().await;
|
||||
|
||||
{
|
||||
let joined = self.client.store().get_joined_user_ids(self.inner.room_id()).await?;
|
||||
let invited =
|
||||
self.client.store().get_invited_user_ids(self.inner.room_id()).await?;
|
||||
let members = joined.iter().chain(&invited).map(Deref::deref);
|
||||
self.client.claim_one_time_keys(members).await?;
|
||||
let members = self
|
||||
.client
|
||||
.store()
|
||||
.get_user_ids(self.inner.room_id(), RoomMemberships::ACTIVE)
|
||||
.await?;
|
||||
self.client.claim_one_time_keys(members.iter().map(Deref::deref)).await?;
|
||||
};
|
||||
|
||||
let response = self.share_room_key().await;
|
||||
|
||||
@@ -41,9 +41,10 @@ impl RoomMember {
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use futures::executor::block_on;
|
||||
/// # use matrix_sdk::{
|
||||
/// # media::MediaFormat, room::RoomMember, ruma::room_id, Client,
|
||||
/// # };
|
||||
/// use matrix_sdk::{
|
||||
/// media::MediaFormat, room::RoomMember, ruma::room_id, Client,
|
||||
/// RoomMemberships,
|
||||
/// };
|
||||
/// # use url::Url;
|
||||
/// # let homeserver = Url::parse("http://example.com").unwrap();
|
||||
/// # block_on(async {
|
||||
@@ -52,7 +53,7 @@ impl RoomMember {
|
||||
/// client.login_username(user, "password").send().await.unwrap();
|
||||
/// let room_id = room_id!("!roomid:example.com");
|
||||
/// let room = client.get_joined_room(&room_id).unwrap();
|
||||
/// let members = room.members().await.unwrap();
|
||||
/// let members = room.members(RoomMemberships::empty()).await.unwrap();
|
||||
/// let member = members.first().unwrap();
|
||||
/// if let Some(avatar) = member.avatar(MediaFormat::File).await.unwrap() {
|
||||
/// std::fs::write("avatar.png", avatar);
|
||||
|
||||
@@ -177,7 +177,10 @@ impl From<AnySyncTimelineEvent> for TimelineEventKind {
|
||||
#[derive(Debug)]
|
||||
pub(super) enum TimelineItemPosition {
|
||||
Start,
|
||||
End,
|
||||
End {
|
||||
/// Whether this event is coming from a local cache.
|
||||
from_cache: bool,
|
||||
},
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
Update(usize),
|
||||
}
|
||||
@@ -569,7 +572,8 @@ impl<'a> TimelineEventHandler<'a> {
|
||||
let origin = match position {
|
||||
TimelineItemPosition::Start => RemoteEventOrigin::Pagination,
|
||||
// We only paginate backwards for now, so End only happens for syncs
|
||||
TimelineItemPosition::End => RemoteEventOrigin::Sync,
|
||||
TimelineItemPosition::End { from_cache: true } => RemoteEventOrigin::Cache,
|
||||
TimelineItemPosition::End { from_cache: false } => RemoteEventOrigin::Sync,
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
TimelineItemPosition::Update(idx) => self.items[*idx]
|
||||
.as_event()
|
||||
@@ -670,7 +674,9 @@ impl<'a> TimelineEventHandler<'a> {
|
||||
self.items.insert(offset + 1, Arc::new(item.into()));
|
||||
}
|
||||
|
||||
Flow::Remote { position: TimelineItemPosition::End, txn_id, event_id, .. } => {
|
||||
Flow::Remote {
|
||||
position: TimelineItemPosition::End { .. }, txn_id, event_id, ..
|
||||
} => {
|
||||
let result = rfind_event_item(self.items, |it| {
|
||||
txn_id.is_some() && it.transaction_id() == txn_id.as_deref()
|
||||
|| it.event_id() == Some(event_id)
|
||||
|
||||
@@ -68,6 +68,8 @@ impl RemoteEventTimelineItem {
|
||||
/// Where we got an event from.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(in crate::room::timeline) enum RemoteEventOrigin {
|
||||
/// The event came from a cache.
|
||||
Cache,
|
||||
/// The event came from a sync response.
|
||||
Sync,
|
||||
/// The event came from pagination.
|
||||
|
||||
@@ -150,7 +150,7 @@ impl<P: RoomDataProvider> TimelineInner<P> {
|
||||
event.event,
|
||||
event.encryption_info,
|
||||
event.push_actions,
|
||||
TimelineItemPosition::End,
|
||||
TimelineItemPosition::End { from_cache: true },
|
||||
state,
|
||||
&self.room_data_provider,
|
||||
self.track_read_receipts,
|
||||
@@ -182,7 +182,7 @@ impl<P: RoomDataProvider> TimelineInner<P> {
|
||||
raw,
|
||||
encryption_info,
|
||||
push_actions,
|
||||
TimelineItemPosition::End,
|
||||
TimelineItemPosition::End { from_cache: false },
|
||||
&mut state,
|
||||
&self.room_data_provider,
|
||||
self.track_read_receipts,
|
||||
|
||||
@@ -10,7 +10,9 @@ use ruma::{
|
||||
self, AccountDataConfig, E2EEConfig, ExtensionsConfig, ReceiptsConfig, ToDeviceConfig,
|
||||
TypingConfig,
|
||||
},
|
||||
assign, OwnedRoomId,
|
||||
assign,
|
||||
events::TimelineEventType,
|
||||
OwnedRoomId,
|
||||
};
|
||||
use tokio::sync::{
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
@@ -35,6 +37,7 @@ pub struct SlidingSyncBuilder {
|
||||
homeserver: Option<Url>,
|
||||
client: Option<Client>,
|
||||
lists: BTreeMap<String, SlidingSyncList>,
|
||||
bump_event_types: Vec<TimelineEventType>,
|
||||
extensions: Option<ExtensionsConfig>,
|
||||
subscriptions: BTreeMap<OwnedRoomId, v4::RoomSubscription>,
|
||||
internal_channel: (Sender<SlidingSyncInternalMessage>, Receiver<SlidingSyncInternalMessage>),
|
||||
@@ -47,6 +50,7 @@ impl SlidingSyncBuilder {
|
||||
homeserver: None,
|
||||
client: None,
|
||||
lists: BTreeMap::new(),
|
||||
bump_event_types: Vec::new(),
|
||||
extensions: None,
|
||||
subscriptions: BTreeMap::new(),
|
||||
internal_channel: channel(8),
|
||||
@@ -198,6 +202,17 @@ impl SlidingSyncBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Allowlist of event types which should be considered recent activity
|
||||
/// when sorting `by_recency`. By omitting event types, clients can ensure
|
||||
/// that uninteresting events (e.g. a profile rename) do not cause a
|
||||
/// room to jump to the top of its list(s). Empty or
|
||||
/// omitted `bump_event_types` have no effect: all events in a room will
|
||||
/// be considered recent activity.
|
||||
pub fn bump_event_types(mut self, bump_event_types: &[TimelineEventType]) -> Self {
|
||||
self.bump_event_types = bump_event_types.to_vec();
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the Sliding Sync.
|
||||
///
|
||||
/// If `self.storage_key` is `Some(_)`, load the cached data from cold
|
||||
@@ -231,6 +246,7 @@ impl SlidingSyncBuilder {
|
||||
|
||||
lists,
|
||||
rooms,
|
||||
bump_event_types: self.bump_event_types,
|
||||
|
||||
extensions: Mutex::new(self.extensions),
|
||||
reset_counter: Default::default(),
|
||||
|
||||
@@ -562,9 +562,10 @@ impl SlidingSyncListInner {
|
||||
self.set_ranges(&[(0, range_end)]);
|
||||
|
||||
// Finally, let's update the list' state.
|
||||
Observable::update_eq(&mut self.state.write().unwrap(), |state| {
|
||||
*state = SlidingSyncState::PartiallyLoaded;
|
||||
});
|
||||
Observable::set_if_not_eq(
|
||||
&mut self.state.write().unwrap(),
|
||||
SlidingSyncState::PartiallyLoaded,
|
||||
);
|
||||
}
|
||||
// Otherwise the current range has reached its maximum, we switched to `FullyLoaded`
|
||||
// mode.
|
||||
@@ -579,17 +580,19 @@ impl SlidingSyncListInner {
|
||||
self.set_ranges(&[(0, range_maximum)]);
|
||||
|
||||
// Finally, let's update the list' state.
|
||||
Observable::update_eq(&mut self.state.write().unwrap(), |state| {
|
||||
*state = SlidingSyncState::FullyLoaded;
|
||||
});
|
||||
Observable::set_if_not_eq(
|
||||
&mut self.state.write().unwrap(),
|
||||
SlidingSyncState::FullyLoaded,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SlidingSyncListRequestGeneratorKind::Selective => {
|
||||
// Selective mode always loads everything.
|
||||
Observable::update_eq(&mut self.state.write().unwrap(), |state| {
|
||||
*state = SlidingSyncState::FullyLoaded;
|
||||
});
|
||||
Observable::set_if_not_eq(
|
||||
&mut self.state.write().unwrap(),
|
||||
SlidingSyncState::FullyLoaded,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,9 @@ use ruma::{
|
||||
self, AccountDataConfig, E2EEConfig, ExtensionsConfig, ToDeviceConfig,
|
||||
},
|
||||
},
|
||||
assign, OwnedRoomId, RoomId,
|
||||
assign,
|
||||
events::TimelineEventType,
|
||||
OwnedRoomId, RoomId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{
|
||||
@@ -106,13 +108,17 @@ pub(super) struct SlidingSyncInner {
|
||||
/// The rooms details
|
||||
rooms: StdRwLock<BTreeMap<OwnedRoomId, SlidingSyncRoom>>,
|
||||
|
||||
/// The `bump_event_types` field. See
|
||||
/// [`SlidingSyncBuilder::bump_event_types`] to learn more.
|
||||
bump_event_types: Vec<TimelineEventType>,
|
||||
|
||||
subscriptions: StdRwLock<BTreeMap<OwnedRoomId, v4::RoomSubscription>>,
|
||||
unsubscribe: StdRwLock<Vec<OwnedRoomId>>,
|
||||
|
||||
/// Number of times a Sliding Session session has been reset.
|
||||
reset_counter: AtomicU8,
|
||||
|
||||
/// the intended state of the extensions being supplied to sliding /sync
|
||||
/// The intended state of the extensions being supplied to sliding /sync
|
||||
/// calls. May contain the latest next_batch for to_devices, etc.
|
||||
extensions: Mutex<Option<ExtensionsConfig>>,
|
||||
|
||||
@@ -406,6 +412,7 @@ impl SlidingSync {
|
||||
txn_id: Some(stream_id.to_owned()),
|
||||
timeout: Some(timeout),
|
||||
lists: requests_lists,
|
||||
bump_event_types: self.inner.bump_event_types.clone(),
|
||||
room_subscriptions,
|
||||
unsubscribe_rooms,
|
||||
extensions,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use matrix_sdk::{config::SyncSettings, room::RoomMember, DisplayName};
|
||||
use matrix_sdk::{config::SyncSettings, room::RoomMember, DisplayName, RoomMemberships};
|
||||
use matrix_sdk_test::{
|
||||
async_test, bulk_room_members, test_json, EventBuilder, JoinedRoomBuilder, StateTestEvent,
|
||||
TimelineTestEvent,
|
||||
@@ -40,7 +40,7 @@ async fn user_presence() {
|
||||
let _response = client.sync_once(sync_settings).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap();
|
||||
let members: Vec<RoomMember> = room.active_members().await.unwrap();
|
||||
let members: Vec<RoomMember> = room.members(RoomMemberships::ACTIVE).await.unwrap();
|
||||
|
||||
assert_eq!(2, members.len());
|
||||
// assert!(room.power_levels.is_some())
|
||||
|
||||
@@ -9,7 +9,7 @@ use matrix_sdk::{
|
||||
api::client::room::create_room::v3::Request as CreateRoomRequest,
|
||||
events::room::member::{MembershipState, StrippedRoomMemberEvent},
|
||||
},
|
||||
Client, RoomState,
|
||||
Client, RoomMemberships, RoomState,
|
||||
};
|
||||
use tokio::sync::Notify;
|
||||
|
||||
@@ -105,11 +105,11 @@ async fn test_repeated_join_leave() -> Result<()> {
|
||||
|
||||
// Now check the underlying state store that it also has the correct information
|
||||
// (for when the client restarts).
|
||||
let invited = karl.store().get_invited_user_ids(room_id).await?;
|
||||
let invited = karl.store().get_user_ids(room_id, RoomMemberships::INVITE).await?;
|
||||
assert_eq!(invited.len(), 1);
|
||||
assert_eq!(invited[0], karl_id);
|
||||
|
||||
let joined = karl.store().get_joined_user_ids(room_id).await?;
|
||||
let joined = karl.store().get_user_ids(room_id, RoomMemberships::JOIN).await?;
|
||||
assert!(!joined.contains(&karl_id));
|
||||
|
||||
let event = karl
|
||||
|
||||
@@ -18,6 +18,7 @@ pub enum TimelineTestEvent {
|
||||
HistoryVisibility,
|
||||
JoinRules,
|
||||
Member,
|
||||
MemberBan,
|
||||
MemberInvite,
|
||||
MemberNameChange,
|
||||
MessageEdit,
|
||||
@@ -47,6 +48,7 @@ impl TimelineTestEvent {
|
||||
Self::HistoryVisibility => test_json::sync_events::HISTORY_VISIBILITY.to_owned(),
|
||||
Self::JoinRules => test_json::sync_events::JOIN_RULES.to_owned(),
|
||||
Self::Member => test_json::sync_events::MEMBER.to_owned(),
|
||||
Self::MemberBan => test_json::sync_events::MEMBER_BAN.to_owned(),
|
||||
Self::MemberInvite => test_json::sync_events::MEMBER_INVITE.to_owned(),
|
||||
Self::MemberNameChange => test_json::sync_events::MEMBER_NAME_CHANGE.to_owned(),
|
||||
Self::MessageEdit => test_json::sync_events::MESSAGE_EDIT.to_owned(),
|
||||
@@ -81,6 +83,7 @@ pub enum StateTestEvent {
|
||||
HistoryVisibility,
|
||||
JoinRules,
|
||||
Member,
|
||||
MemberBan,
|
||||
MemberInvite,
|
||||
MemberNameChange,
|
||||
PowerLevels,
|
||||
@@ -103,6 +106,7 @@ impl StateTestEvent {
|
||||
Self::HistoryVisibility => test_json::sync_events::HISTORY_VISIBILITY.to_owned(),
|
||||
Self::JoinRules => test_json::sync_events::JOIN_RULES.to_owned(),
|
||||
Self::Member => test_json::sync_events::MEMBER.to_owned(),
|
||||
Self::MemberBan => test_json::sync_events::MEMBER_BAN.to_owned(),
|
||||
Self::MemberInvite => test_json::sync_events::MEMBER_INVITE.to_owned(),
|
||||
Self::MemberNameChange => test_json::sync_events::MEMBER_NAME_CHANGE.to_owned(),
|
||||
Self::PowerLevels => test_json::sync_events::POWER_LEVELS.to_owned(),
|
||||
|
||||
@@ -27,10 +27,10 @@ pub use sync::{
|
||||
MORE_SYNC, MORE_SYNC_2, SYNC, VOIP_SYNC,
|
||||
};
|
||||
pub use sync_events::{
|
||||
ALIAS, ALIASES, ENCRYPTION, MEMBER, MEMBER_INVITE, MEMBER_NAME_CHANGE, MEMBER_STRIPPED,
|
||||
MESSAGE_EDIT, MESSAGE_TEXT, NAME, NAME_STRIPPED, POWER_LEVELS, PRESENCE, PUSH_RULES, REACTION,
|
||||
READ_RECEIPT, READ_RECEIPT_OTHER, REDACTED, REDACTED_INVALID, REDACTED_STATE, REDACTION, TAG,
|
||||
TOPIC, TOPIC_REDACTION, TYPING,
|
||||
ALIAS, ALIASES, ENCRYPTION, MEMBER, MEMBER_BAN, MEMBER_INVITE, MEMBER_NAME_CHANGE,
|
||||
MEMBER_STRIPPED, MESSAGE_EDIT, MESSAGE_TEXT, NAME, NAME_STRIPPED, POWER_LEVELS, PRESENCE,
|
||||
PUSH_RULES, REACTION, READ_RECEIPT, READ_RECEIPT_OTHER, REDACTED, REDACTED_INVALID,
|
||||
REDACTED_STATE, REDACTION, TAG, TOPIC, TOPIC_REDACTION, TYPING,
|
||||
};
|
||||
|
||||
/// An empty response.
|
||||
|
||||
@@ -146,6 +146,21 @@ pub static MEMBER: Lazy<JsonValue> = Lazy::new(|| {
|
||||
})
|
||||
});
|
||||
|
||||
pub static MEMBER_BAN: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"content": {
|
||||
"avatar_url": null,
|
||||
"displayname": "example",
|
||||
"membership": "ban"
|
||||
},
|
||||
"event_id": "$151800140517rfvjc:localhost",
|
||||
"origin_server_ts": 151800140,
|
||||
"sender": "@example:localhost",
|
||||
"state_key": "@banned:localhost",
|
||||
"type": "m.room.member",
|
||||
})
|
||||
});
|
||||
|
||||
pub static MEMBER_INVITE: Lazy<JsonValue> = Lazy::new(|| {
|
||||
json!({
|
||||
"content": {
|
||||
|
||||
Reference in New Issue
Block a user