Compare commits
500 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ffe5aa6ca | |||
| c604e4acd2 | |||
| f8b343bece | |||
| 94f8f8c44c | |||
| 4c1f80faf7 | |||
| f9bf492fdb | |||
| 824fc0b62e | |||
| 359db7f28b | |||
| 30672e6feb | |||
| f9b419077d | |||
| d46f934d57 | |||
| 0bed6afc29 | |||
| 412d4b80ee | |||
| bcabf1bda4 | |||
| 7767ef6ca3 | |||
| 6765ca0c39 | |||
| 17abab0d53 | |||
| cc0bf91a06 | |||
| 0b3345f592 | |||
| 472b934816 | |||
| 27a28e55d1 | |||
| d6a418f46a | |||
| 268e14e4f5 | |||
| f1190deef9 | |||
| ee62cd749f | |||
| cea5c190d8 | |||
| ad4cb4f6c9 | |||
| 949e7a6cac | |||
| 8e66963a1e | |||
| aa02e31cf6 | |||
| 57c7972c63 | |||
| e89ac3d7df | |||
| a3704c3563 | |||
| 5fb728e8f0 | |||
| eab62ec0b5 | |||
| 2fae949a42 | |||
| 4adbb4aa88 | |||
| 18affe3edd | |||
| ea59bc8955 | |||
| 68f6d927f1 | |||
| c3766789cc | |||
| 7c31525f68 | |||
| b2dd5ce02d | |||
| 1f2b4f87bc | |||
| 893c45af74 | |||
| a161dfa9a0 | |||
| 20cd0bedfa | |||
| d4a1ce06e4 | |||
| e53906a920 | |||
| 1e30916754 | |||
| 15c46b503c | |||
| 5cd3818841 | |||
| 79b7d6d235 | |||
| 05d0f9e077 | |||
| 4c1e2d6d51 | |||
| c9162373a1 | |||
| 9f22f550bf | |||
| 7a762035f1 | |||
| 8c0a918e6e | |||
| 33c317e6d2 | |||
| 371ed49670 | |||
| f0c1c65308 | |||
| ea5063ca84 | |||
| dcc07e1049 | |||
| 76a0eb1599 | |||
| a9c16c96e0 | |||
| 93f8ebba27 | |||
| 95bb153269 | |||
| ff72a09870 | |||
| 06de58dee9 | |||
| fbb49e9b65 | |||
| 04728cc1a6 | |||
| d43d141dc8 | |||
| 3c069f0c5c | |||
| a2200b6324 | |||
| 9caa0817ae | |||
| b3229041fb | |||
| 50446377de | |||
| 4f02a6d3be | |||
| 87fdd3c3bf | |||
| 990fe86fdc | |||
| be2ba26974 | |||
| 1392a0e637 | |||
| 03ffa4c9a4 | |||
| f8b5992101 | |||
| 97a5fbebfb | |||
| 74c2032974 | |||
| 274aaf5ba3 | |||
| 3e72cce7a0 | |||
| aa4b176ab3 | |||
| ad2f4c731a | |||
| f78015fae1 | |||
| 211a1f5a40 | |||
| b8be1fdb26 | |||
| a43e42c170 | |||
| f031eaf96b | |||
| 3ba31d1e97 | |||
| bbf8f9f900 | |||
| 49dc2bb640 | |||
| 99af951d7a | |||
| a17bf18ff2 | |||
| 5d87570a33 | |||
| 80806303b5 | |||
| 6155772bb1 | |||
| 33db267a89 | |||
| 6fc68dac83 | |||
| f3eeb82b0b | |||
| 951d22ac24 | |||
| 527d001010 | |||
| 759eeeb27f | |||
| 97d6f57aee | |||
| 6622a3ac93 | |||
| 39730173d4 | |||
| 763314645b | |||
| b2fee72d79 | |||
| 9803d2bcca | |||
| 296867d2ac | |||
| 710b57e035 | |||
| baa75368d6 | |||
| feb264e899 | |||
| 8e5075569e | |||
| 33c11d08f0 | |||
| 9714ac8e10 | |||
| e1d136aa6e | |||
| 8018753332 | |||
| 61824f866c | |||
| 6919444e98 | |||
| c5097cf07e | |||
| b77c6c65cc | |||
| f4ce4356ab | |||
| d66733052a | |||
| 001dadffe1 | |||
| b2387bf3a9 | |||
| d43858ecb2 | |||
| 8804966094 | |||
| a3ee011b61 | |||
| f586172f3e | |||
| 85d52586b6 | |||
| 40d3dd57db | |||
| 4e2655aa1b | |||
| 41fcebbcb0 | |||
| 13b86a3f5d | |||
| dba23b66fa | |||
| 132d0eb34a | |||
| e2e70448ca | |||
| adaccbab2c | |||
| 7a09ca0bbd | |||
| fffdd34ebd | |||
| 24502d2706 | |||
| b6433dea27 | |||
| 385f1a824f | |||
| dfc0ef8b35 | |||
| d2feeaac30 | |||
| 25a81876a0 | |||
| e388fe6522 | |||
| ef20342ddf | |||
| e6b1ffba99 | |||
| 9a3ceb8be6 | |||
| faee647c3a | |||
| 1866143456 | |||
| be8e322ad6 | |||
| 838f607b32 | |||
| 6965004812 | |||
| 8ec23a95d5 | |||
| 7880ec5b01 | |||
| 36428564fc | |||
| 7adaf7be73 | |||
| 7920723bb4 | |||
| b73163aa45 | |||
| feeeb53f19 | |||
| 1ac876db98 | |||
| afaf2cc036 | |||
| 5a3bb0a86d | |||
| 2640aa1e23 | |||
| d1a8392ce7 | |||
| 4d14dd3692 | |||
| 63defca8af | |||
| fd83904b4d | |||
| 9254c38a8d | |||
| 63d9dd5c6e | |||
| 23dacc329e | |||
| 5896a438f5 | |||
| 31a3d76436 | |||
| dfaaf323ad | |||
| 94439d8913 | |||
| 9cc29d7c65 | |||
| f2fbdfbac2 | |||
| fd34927f61 | |||
| 0190d3556d | |||
| 2d657fe908 | |||
| 5e43177d3a | |||
| e44b01cbe5 | |||
| 4882c98f99 | |||
| 4bf0187310 | |||
| 10ca400d4d | |||
| cc61e123b7 | |||
| 6f23981268 | |||
| 859044285a | |||
| 6c1134006e | |||
| 6d1cdbc613 | |||
| 6160c15103 | |||
| 100cbde526 | |||
| 6ff8a26cca | |||
| a1c484fb6e | |||
| d2ecc77014 | |||
| 2e86fbc234 | |||
| 64698eaf1a | |||
| f180a14c88 | |||
| bb0d480f24 | |||
| 7af1d3ab0e | |||
| 13ee4c8098 | |||
| 7bbd02ca73 | |||
| e22a7a2ed5 | |||
| 4aad2c6b07 | |||
| 960162453c | |||
| 1ea2162012 | |||
| 1f0151705a | |||
| 84ebbd913c | |||
| 756d50737e | |||
| c32877284c | |||
| 6260811ea5 | |||
| 4f0415d4d2 | |||
| b43aac129b | |||
| c019009d00 | |||
| a88d6b37dc | |||
| 705d6f870e | |||
| 4fc28c4701 | |||
| 64eecd0aee | |||
| c25be8b070 | |||
| 1554c9d8fa | |||
| d568c07489 | |||
| 13c30f6691 | |||
| 0e70a2fdfb | |||
| d70d758861 | |||
| eefa9ff556 | |||
| 28a8603f42 | |||
| ae7f0fe022 | |||
| d9f4e7c426 | |||
| 247ec1dcd2 | |||
| 558d7b56f9 | |||
| 1201be484a | |||
| 1ffc014621 | |||
| 9491757cad | |||
| 33df0422e8 | |||
| a3a239f999 | |||
| ca8b64e041 | |||
| 140e751af0 | |||
| a66b2c5123 | |||
| 69bef9a76a | |||
| b3c53dd08f | |||
| c8bffa26a4 | |||
| b4ef6cef55 | |||
| c6854a5c22 | |||
| fb563953c9 | |||
| bc0018aecb | |||
| 12292c5375 | |||
| cf9d058265 | |||
| 4da13e1096 | |||
| 333d4563ce | |||
| 01059ef26c | |||
| 7724271508 | |||
| 8dfe732cce | |||
| 1cf3477ada | |||
| 0a2205f540 | |||
| c586812159 | |||
| c6210cad21 | |||
| a9ce1c6e58 | |||
| 1eb8f6ac16 | |||
| e0feebdb2b | |||
| 0fee716c1e | |||
| c41ed8a78a | |||
| 53f02c9f2d | |||
| e2f0b4f3fd | |||
| 0a796cb468 | |||
| e3390c17ec | |||
| c6dc070c31 | |||
| 486befc7fb | |||
| 9848d1472e | |||
| 6c944a9b39 | |||
| b4b010f9fe | |||
| 536ba518bb | |||
| 917c46b570 | |||
| b29886c0df | |||
| 360c2d7f32 | |||
| 683f0f4027 | |||
| c783ed8a6f | |||
| 139673810f | |||
| 669ebf2408 | |||
| 992774b8b5 | |||
| 9d90a92b4c | |||
| d79975e0e3 | |||
| 2e598c0532 | |||
| 5a3ef30fdc | |||
| 05178ccaf9 | |||
| 65b9bd20a8 | |||
| 35505f9130 | |||
| a6d630216d | |||
| 159c9b4547 | |||
| aead1a4489 | |||
| 7fee1c7fd7 | |||
| ab9bfb2d61 | |||
| de5f00fd33 | |||
| 33c16b2979 | |||
| e9dcdb7176 | |||
| 0a3fe939c5 | |||
| 37e07ea331 | |||
| e4e3ff63f5 | |||
| 8409e52654 | |||
| e8096ee518 | |||
| 6814e70aa4 | |||
| efa4539a91 | |||
| 42d2b93489 | |||
| 872713c4bc | |||
| feb22d4370 | |||
| 6520c9b16e | |||
| cd6fe271ba | |||
| 5f447bbb17 | |||
| 94e7ddd1ab | |||
| 6ac4a8431d | |||
| b585963abb | |||
| 5719fde701 | |||
| 2914d7a727 | |||
| 0cdec9d912 | |||
| d180d49c07 | |||
| bcee5badae | |||
| ebb7059d55 | |||
| 8d3b1d3c7e | |||
| 056e90db25 | |||
| 787861eb35 | |||
| f081416baa | |||
| 8a6cc7bc22 | |||
| 61258e823f | |||
| e86aab68b4 | |||
| 48f1bc0780 | |||
| 1fe71acbcb | |||
| 0e054deb19 | |||
| d2b7dc6116 | |||
| 1089a25588 | |||
| 3276bc87ad | |||
| a4da6ba7c8 | |||
| 033c6bd6a4 | |||
| b02e1da471 | |||
| 5ad477ac96 | |||
| 975432565d | |||
| 46b0113765 | |||
| 09eff8c6bd | |||
| 7ee546a3d9 | |||
| b164cd6a51 | |||
| 6d95abfb36 | |||
| 33f09d6d26 | |||
| 8c01e99144 | |||
| 277cb7ac49 | |||
| fc7124fd1a | |||
| 30c0420f83 | |||
| cb13c345ad | |||
| cd26973082 | |||
| 0edcdd33b2 | |||
| c191eb7cd1 | |||
| fa6f270812 | |||
| d4e96595d9 | |||
| 540a11e7a8 | |||
| 92192c549b | |||
| 88360040fb | |||
| 4184e245a4 | |||
| f37bf2f5d1 | |||
| d57d3c4124 | |||
| 1a5cb2beb8 | |||
| b645c1101f | |||
| 8091094bbc | |||
| feadfde1b5 | |||
| e2ad07881c | |||
| 1be8b42d03 | |||
| 7a5f83f6ec | |||
| 88bb7a366f | |||
| 7d9bf56581 | |||
| 770f65ede0 | |||
| 1f33e0f4d1 | |||
| 117f76102d | |||
| fd04ebfaba | |||
| afe9f7a979 | |||
| 27a002c8e2 | |||
| b8ab0972b3 | |||
| d3419ea4ac | |||
| 019adb9a56 | |||
| ca89700dfe | |||
| a0c87cfe4f | |||
| 9ddc892aa0 | |||
| 0a7ac18d9f | |||
| d8b6966c0a | |||
| d40f04e32c | |||
| d8294a0788 | |||
| 06a4476e7f | |||
| def1fedea3 | |||
| d061e7a5b2 | |||
| f4619c91d3 | |||
| 227f6eab85 | |||
| 16d7c3c094 | |||
| c238a0edb8 | |||
| 06bf487512 | |||
| c636ec63f4 | |||
| ffe239d620 | |||
| 822b709107 | |||
| d75d7973b2 | |||
| cfe3adce48 | |||
| b478ae65f7 | |||
| ada68e1114 | |||
| c9137f0cad | |||
| 4e0dab959a | |||
| e862ded147 | |||
| 5cb033ad91 | |||
| 0e622cc5a1 | |||
| 6d562eff2f | |||
| af2e15e02f | |||
| 79aa5aaf16 | |||
| 0833ffdef2 | |||
| 16f7239215 | |||
| da946e51dd | |||
| cba711dbdf | |||
| e87e9331c1 | |||
| 6e9fc70d13 | |||
| e2148e46bc | |||
| d1163b75bf | |||
| 5ae7d0f60f | |||
| 6f067d5510 | |||
| d6fe654814 | |||
| 2710510786 | |||
| 35a8528712 | |||
| f9735c75d3 | |||
| 15e6b81835 | |||
| bcb4ab4b10 | |||
| 4931c0749e | |||
| 37626b5ad9 | |||
| d19616da03 | |||
| 7c8f870d16 | |||
| b482ccd318 | |||
| 165ec9db1b | |||
| 6f42210d6a | |||
| 6125580275 | |||
| 8b3e295429 | |||
| 1e568efbb5 | |||
| f89ced3ded | |||
| a5fbcf1491 | |||
| 8923e58ee3 | |||
| f14994baa9 | |||
| d42d0f3e17 | |||
| e4849d5cab | |||
| 65aec7ee7f | |||
| a6868386d0 | |||
| b3c1ca1577 | |||
| ce66ee4a16 | |||
| 2bbf6fc711 | |||
| 1a9e5b904b | |||
| 4c43b06445 | |||
| 49a0765880 | |||
| 39cf8b325d | |||
| bb67150d6b | |||
| 471e3c340c | |||
| 74972d8db7 | |||
| 03a76fbaf5 | |||
| 392a1ef47b | |||
| c5f2460e02 | |||
| 8ad785a117 | |||
| a5b936d0b6 | |||
| 1a0544c8eb | |||
| 232c23e8df | |||
| 7d9d5bf3b4 | |||
| ea076b3d76 | |||
| 8aa6f97f7c | |||
| 679aa07115 | |||
| 0c66d8a53f | |||
| 25ed7eef2b | |||
| a399840dff | |||
| 8d4e7f0478 | |||
| 3bd93130c5 | |||
| 153618b77c | |||
| dd871ef9ac | |||
| 8a29f17d1d | |||
| fab520ab33 | |||
| 435553c3d1 | |||
| 610ecd218c | |||
| fa300d1f33 | |||
| af2a483158 | |||
| 753b0d8584 | |||
| 36713adbdb | |||
| d73a02c608 | |||
| f73199b472 | |||
| 420d373144 | |||
| a79e9130e6 | |||
| 355b5327f8 | |||
| fa77852001 | |||
| 7b73311de5 | |||
| f03934bc4f | |||
| 014ee98fb7 | |||
| fbcf9fce7c | |||
| 6de403276a | |||
| 6209bc942c | |||
| edd371b570 | |||
| 817f32e15b | |||
| 30eb12ed2d | |||
| 900697bc3b |
+2
-12
@@ -17,6 +17,7 @@ version = 2
|
||||
allow = [
|
||||
"Apache-2.0",
|
||||
"Apache-2.0 WITH LLVM-exception",
|
||||
"CDLA-Permissive-2.0",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"BSL-1.0",
|
||||
@@ -28,15 +29,6 @@ allow = [
|
||||
]
|
||||
exceptions = [
|
||||
{ allow = ["Unicode-DFS-2016"], crate = "unicode-ident" },
|
||||
{ allow = ["CDDL-1.0"], crate = "inferno" },
|
||||
{ allow = ["LicenseRef-ring"], crate = "ring" },
|
||||
]
|
||||
|
||||
[[licenses.clarify]]
|
||||
name = "ring"
|
||||
expression = "LicenseRef-ring"
|
||||
license-files = [
|
||||
{ path = "LICENSE", hash = 0xbd0eed23 },
|
||||
]
|
||||
|
||||
[bans]
|
||||
@@ -51,9 +43,7 @@ unknown-git = "deny"
|
||||
allow-git = [
|
||||
# A patch override for the bindings fixing a bug for Android before upstream
|
||||
# releases a new version.
|
||||
"https://github.com/element-hq/tracing.git",
|
||||
# Same as for the tracing dependency.
|
||||
"https://github.com/element-hq/paranoid-android.git",
|
||||
"https://github.com/tokio-rs/tracing.git",
|
||||
# Well, it's Ruma.
|
||||
"https://github.com/ruma/ruma",
|
||||
# A patch override for the bindings: https://github.com/rodrimati1992/const_panic/pull/10
|
||||
|
||||
@@ -1,54 +1,40 @@
|
||||
name: Benchmarks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
benchmarks:
|
||||
name: Run Benchmarks
|
||||
runs-on: ubuntu-latest
|
||||
environment: matrix-rust-bot
|
||||
if: github.event_name == 'push'
|
||||
strategy:
|
||||
matrix:
|
||||
benchmark:
|
||||
- crypto_bench
|
||||
- event_cache
|
||||
- linked_chunk
|
||||
- store_bench
|
||||
- timeline
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly-2025-06-27
|
||||
components: rustfmt
|
||||
- name: Setup rust toolchain, cache and cargo-codspeed binary
|
||||
uses: moonrepo/setup-rust@ede6de059f8046a5e236c94046823e2af11ca670
|
||||
with:
|
||||
channel: stable
|
||||
cache-target: release
|
||||
bins: cargo-codspeed
|
||||
|
||||
- name: Run Benchmarks
|
||||
run: cargo bench | tee benchmark-output.txt
|
||||
- name: Build the benchmark target(s)
|
||||
run: cargo codspeed build -p benchmarks ${{ matrix.benchmark }} --features codspeed
|
||||
|
||||
- name: Check benchmark result for PR
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: benchmark-action/github-action-benchmark@v1
|
||||
with:
|
||||
name: Rust Benchmark
|
||||
tool: 'cargo'
|
||||
output-file-path: benchmark-output.txt
|
||||
auto-push: false
|
||||
# comment to alert the user this has gone bad
|
||||
github-token: ${{ secrets.MRB_ACCESS_TOKEN }}
|
||||
alert-threshold: '120%'
|
||||
comment-on-alert: true
|
||||
fail-threshold: '150%'
|
||||
fail-on-alert: true
|
||||
|
||||
- name: Store benchmark result
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: benchmark-action/github-action-benchmark@v1
|
||||
with:
|
||||
name: Rust Benchmark
|
||||
tool: 'cargo'
|
||||
output-file-path: benchmark-output.txt
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto-push: true
|
||||
# Show alert with commit comment on detecting possible performance regression
|
||||
alert-threshold: '150%'
|
||||
comment-on-alert: true
|
||||
fail-on-alert: true
|
||||
alert-comment-cc-users: '@gnunicornBen,@jplatte,@poljar'
|
||||
- name: Run the benchmarks
|
||||
uses: CodSpeedHQ/action@76578c2a7ddd928664caa737f0e962e3085d4e7c
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install protoc
|
||||
uses: taiki-e/install-action@v2
|
||||
@@ -69,17 +69,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Rust SDK
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Checkout Kotlin Rust Components project
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: matrix-org/matrix-rust-components-kotlin
|
||||
path: rust-components-kotlin
|
||||
ref: main
|
||||
|
||||
- name: Use JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '17'
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# install protoc in case we end up rebuilding opentelemetry-proto
|
||||
- name: Install protoc
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# install protoc in case we end up rebuilding opentelemetry-proto
|
||||
- name: Install protoc
|
||||
|
||||
+23
-15
@@ -34,14 +34,16 @@ jobs:
|
||||
- no-sqlite
|
||||
- no-encryption-and-sqlite
|
||||
- sqlite-cryptostore
|
||||
- experimental-encrypted-state-events
|
||||
- rustls-tls
|
||||
- markdown
|
||||
- socks
|
||||
- sso-login
|
||||
- search
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -83,7 +85,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -114,7 +116,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install libsqlite
|
||||
run: |
|
||||
@@ -165,7 +167,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install protoc
|
||||
uses: taiki-e/install-action@v2
|
||||
@@ -237,7 +239,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -249,7 +251,7 @@ jobs:
|
||||
uses: qmaru/wasm-pack-action@v0.5.1
|
||||
if: '!matrix.check_only'
|
||||
with:
|
||||
version: v0.10.3
|
||||
version: v0.13.1
|
||||
|
||||
- name: Load cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@@ -287,10 +289,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Check the spelling of the files in our repo
|
||||
uses: crate-ci/typos@v1.34.0
|
||||
uses: crate-ci/typos@v1.35.7
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
@@ -299,7 +301,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install protoc
|
||||
uses: taiki-e/install-action@v2
|
||||
@@ -309,7 +311,7 @@ jobs:
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly-2025-06-27
|
||||
toolchain: nightly-2025-08-08
|
||||
components: clippy, rustfmt
|
||||
|
||||
- name: Load cache
|
||||
@@ -333,8 +335,7 @@ jobs:
|
||||
target/debug/xtask ci clippy
|
||||
|
||||
integration-tests:
|
||||
name: Integration test
|
||||
|
||||
name: 'Integration test (features: ${{ matrix.feature }})'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# run several docker containers with the same networking stack so the hostname 'synapse'
|
||||
@@ -350,9 +351,16 @@ jobs:
|
||||
ports:
|
||||
- 8008:8008
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
feature:
|
||||
- "default"
|
||||
- "experimental-encrypted-state-events"
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install libsqlite
|
||||
run: |
|
||||
@@ -376,7 +384,7 @@ jobs:
|
||||
HOMESERVER_URL: "http://localhost:8008"
|
||||
HOMESERVER_DOMAIN: "synapse"
|
||||
run: |
|
||||
cargo nextest run -p matrix-sdk-integration-testing
|
||||
cargo nextest run -p matrix-sdk-integration-testing --features "${{ matrix.feature }}"
|
||||
|
||||
compile-bench:
|
||||
name: 🚄 Compile benchmarks
|
||||
@@ -384,7 +392,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
@@ -42,10 +42,62 @@ jobs:
|
||||
# This CI workflow can run into space issue, so we're cleaning up some
|
||||
# space here.
|
||||
- name: Create some more space
|
||||
run: rm -rf /opt/hostedtoolcache
|
||||
run: |
|
||||
echo "Disk space before cleanup"
|
||||
df -h
|
||||
|
||||
cd /opt
|
||||
find . -maxdepth 1 -mindepth 1 '!' -path ./containerd '!' -path ./actionarchivecache '!' -path ./runner '!' -path ./runner-cache -exec rm -rf '{}' ';'
|
||||
rm -rf /opt/hostedtoolcache
|
||||
|
||||
# Get rid of binaries and libs we're not interested in.
|
||||
sudo rm -rf \
|
||||
/usr/local/julia* \
|
||||
/usr/local/aws*
|
||||
|
||||
sudo rm -rf \
|
||||
/usr/local/bin/minikube \
|
||||
/usr/local/bin/node \
|
||||
/usr/local/bin/stack \
|
||||
/usr/local/bin/bicep \
|
||||
/usr/local/bin/pulumi* \
|
||||
/usr/local/bin/helm \
|
||||
/usr/local/bin/azcopy \
|
||||
/usr/local/bin/packer \
|
||||
/usr/local/bin/cmake-gui \
|
||||
/usr/local/bin/cpack
|
||||
|
||||
sudo rm -rf \
|
||||
/usr/local/share/powershell \
|
||||
/usr/local/share/chromium
|
||||
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
|
||||
echo "::group::/usr/local/bin/*"
|
||||
du -hsc /usr/local/bin/* | sort -h
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::/usr/local/share/*"
|
||||
du -hsc /usr/local/share/* | sort -h
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::/usr/local/*"
|
||||
du -hsc /usr/local/* | sort -h
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::/usr/local/lib/*"
|
||||
du -hsc /usr/local/lib/* | sort -h
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::/opt/*"
|
||||
du -hsc /opt/* | sort -h
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "Disk space after cleanup"
|
||||
df -h
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
@@ -53,6 +105,8 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libsqlite3-dev
|
||||
sudo apt-get clean
|
||||
sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
@@ -81,6 +135,10 @@ jobs:
|
||||
key: "${{ needs.xtask.outputs.cachekey-linux }}"
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Check total disk space before running
|
||||
run: |
|
||||
df -h
|
||||
|
||||
- name: Create the coverage report
|
||||
run: |
|
||||
target/debug/xtask ci coverage -o codecov
|
||||
|
||||
@@ -10,5 +10,5 @@ jobs:
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Check for changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v46.0.5
|
||||
|
||||
@@ -7,6 +7,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Machete
|
||||
uses: bnjbvr/cargo-machete@v0.8.0
|
||||
uses: bnjbvr/cargo-machete@v0.9.1
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install protoc
|
||||
uses: taiki-e/install-action@v2
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly-2025-06-27
|
||||
toolchain: nightly-2025-08-08
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Upload artifact
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: './target/doc/'
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Block Fixup Commit Merge
|
||||
uses: 13rac1/block-fixup-merge-action@v2.0.0
|
||||
|
||||
@@ -11,6 +11,6 @@ jobs:
|
||||
msrv:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- run: cargo hack check --rust-version --workspace --all-targets --ignore-private
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
echo "override_commit=$(<commit_sha.txt)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ steps.parse_previous_artifacts.outputs.override_commit || '' }}
|
||||
path: repo_root
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Calculate cache key
|
||||
id: cachekey
|
||||
|
||||
@@ -4,6 +4,7 @@ master.zip
|
||||
emsdk-*
|
||||
.idea/
|
||||
.env
|
||||
.envrc
|
||||
.build
|
||||
.swiftpm
|
||||
/Package.swift
|
||||
|
||||
Generated
+1250
-656
File diff suppressed because it is too large
Load Diff
+59
-47
@@ -16,23 +16,28 @@ default-members = ["benchmarks", "crates/*", "labs/*"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
rust-version = "1.85"
|
||||
rust-version = "1.88"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.95"
|
||||
anyhow = "1.0.99"
|
||||
aquamarine = "0.6.0"
|
||||
as_variant = "1.3.0"
|
||||
assert-json-diff = "2.0.2"
|
||||
assert_matches = "1.5.0"
|
||||
assert_matches2 = "0.1.2"
|
||||
async-compat = "0.2.4"
|
||||
async-compat = "0.2.5"
|
||||
async-rx = "0.1.3"
|
||||
# Bumping this to 0.3.6 produces a test failure because the semantic between the
|
||||
# versions changed subtly.
|
||||
async-stream = "0.3.5"
|
||||
async-trait = "0.1.85"
|
||||
async-trait = "0.1.89"
|
||||
base64 = "0.22.1"
|
||||
bitflags = "2.8.0"
|
||||
bitflags = "2.9.3"
|
||||
byteorder = "1.5.0"
|
||||
chrono = "0.4.39"
|
||||
cfg-if = "1.0.3"
|
||||
clap = "4.5.46"
|
||||
chrono = "0.4.41"
|
||||
dirs = "6.0.0"
|
||||
eyeball = { version = "0.8.8", features = ["tracing"] }
|
||||
eyeball-im = { version = "0.7.0", features = ["tracing"] }
|
||||
eyeball-im-util = "0.9.0"
|
||||
@@ -44,26 +49,24 @@ gloo-timers = "0.3.0"
|
||||
growable-bloom-filter = "2.1.1"
|
||||
hkdf = "0.12.4"
|
||||
hmac = "0.12.1"
|
||||
http = "1.2.0"
|
||||
http = "1.3.1"
|
||||
imbl = "5.0.0"
|
||||
indexmap = "2.7.1"
|
||||
insta = { version = "1.42.1", features = ["json", "redactions"] }
|
||||
indexmap = "2.11.0"
|
||||
insta = { version = "1.43.1", features = ["json", "redactions"] }
|
||||
itertools = "0.14.0"
|
||||
js-sys = "0.3.69"
|
||||
js-sys = "0.3.77"
|
||||
mime = "0.3.17"
|
||||
once_cell = "1.20.2"
|
||||
oauth2 = { version = "5.0.0", default-features = false, features = ["reqwest", "timing-resistant-secret-traits"] }
|
||||
once_cell = "1.21.3"
|
||||
pbkdf2 = { version = "0.12.2" }
|
||||
pin-project-lite = "0.2.16"
|
||||
proptest = { version = "1.6.0", default-features = false, features = ["std"] }
|
||||
rand = "0.8.5"
|
||||
reqwest = { version = "0.12.12", default-features = false }
|
||||
reqwest = { version = "0.12.23", default-features = false }
|
||||
rmp-serde = "1.3.0"
|
||||
# Be careful to use commits from the https://github.com/ruma/ruma/tree/ruma-0.12
|
||||
# branch until a proper release with breaking changes happens.
|
||||
ruma = { version = "0.12.5", features = [
|
||||
ruma = { version = "0.13.0", features = [
|
||||
"client-api-c",
|
||||
"compat-upload-signatures",
|
||||
"compat-user-id",
|
||||
"compat-arbitrary-length-ids",
|
||||
"compat-tag-info",
|
||||
"compat-encrypted-stickers",
|
||||
@@ -76,48 +79,54 @@ ruma = { version = "0.12.5", features = [
|
||||
"unstable-msc4140",
|
||||
"unstable-msc4143",
|
||||
"unstable-msc4171",
|
||||
"unstable-msc4222",
|
||||
"unstable-msc4278",
|
||||
"unstable-msc4286",
|
||||
"unstable-msc4306",
|
||||
"unstable-msc4308"
|
||||
] }
|
||||
ruma-common = "0.15.4"
|
||||
sentry = "0.36.0"
|
||||
sentry-tracing = "0.36.0"
|
||||
serde = { version = "1.0.217", features = ["rc"] }
|
||||
sentry = { version = "0.42.0", default-features = false }
|
||||
sentry-tracing = "0.42.0"
|
||||
serde = { version = "1.0.219", features = ["rc"] }
|
||||
serde_html_form = "0.2.7"
|
||||
serde_json = "1.0.138"
|
||||
sha2 = "0.10.8"
|
||||
similar-asserts = "1.6.1"
|
||||
serde_json = "1.0.143"
|
||||
sha2 = "0.10.9"
|
||||
similar-asserts = "1.7.0"
|
||||
stream_assert = "0.1.1"
|
||||
tempfile = "3.16.0"
|
||||
thiserror = "2.0.11"
|
||||
tokio = { version = "1.43.1", default-features = false, features = ["sync"] }
|
||||
tempfile = "3.21.0"
|
||||
thiserror = "2.0.16"
|
||||
tokio = { version = "1.47.1", default-features = false, features = ["sync"] }
|
||||
tokio-stream = "0.1.17"
|
||||
tracing = { version = "0.1.40", default-features = false, features = ["std"] }
|
||||
tracing-core = "0.1.32"
|
||||
tracing-subscriber = "0.3.18"
|
||||
tracing = { version = "0.1.41", default-features = false, features = ["std"] }
|
||||
tracing-appender = "0.2.3"
|
||||
tracing-core = "0.1.34"
|
||||
tracing-subscriber = "0.3.20"
|
||||
unicode-normalization = "0.1.24"
|
||||
uniffi = { version = "0.28.0" }
|
||||
uniffi_bindgen = { version = "0.28.0" }
|
||||
url = "2.5.4"
|
||||
uuid = "1.12.1"
|
||||
url = "2.5.7"
|
||||
uuid = "1.18.0"
|
||||
vergen-gitcl = "1.0.8"
|
||||
vodozemac = { version = "0.9.0", features = ["insecure-pk-encryption"] }
|
||||
wasm-bindgen = "0.2.84"
|
||||
wasm-bindgen-test = "0.3.50"
|
||||
web-sys = "0.3.69"
|
||||
wiremock = "0.6.2"
|
||||
wiremock = "0.6.5"
|
||||
zeroize = "1.8.1"
|
||||
|
||||
matrix-sdk = { path = "crates/matrix-sdk", version = "0.13.0", default-features = false }
|
||||
matrix-sdk-base = { path = "crates/matrix-sdk-base", version = "0.13.0" }
|
||||
matrix-sdk-common = { path = "crates/matrix-sdk-common", version = "0.13.0" }
|
||||
matrix-sdk-crypto = { path = "crates/matrix-sdk-crypto", version = "0.13.0" }
|
||||
matrix-sdk = { path = "crates/matrix-sdk", version = "0.14.0", default-features = false }
|
||||
matrix-sdk-base = { path = "crates/matrix-sdk-base", version = "0.14.0" }
|
||||
matrix-sdk-common = { path = "crates/matrix-sdk-common", version = "0.14.0" }
|
||||
matrix-sdk-crypto = { path = "crates/matrix-sdk-crypto", version = "0.14.0" }
|
||||
matrix-sdk-ffi-macros = { path = "bindings/matrix-sdk-ffi-macros", version = "0.7.0" }
|
||||
matrix-sdk-indexeddb = { path = "crates/matrix-sdk-indexeddb", version = "0.13.0", default-features = false }
|
||||
matrix-sdk-qrcode = { path = "crates/matrix-sdk-qrcode", version = "0.13.0" }
|
||||
matrix-sdk-sqlite = { path = "crates/matrix-sdk-sqlite", version = "0.13.0", default-features = false }
|
||||
matrix-sdk-store-encryption = { path = "crates/matrix-sdk-store-encryption", version = "0.13.0" }
|
||||
matrix-sdk-test = { path = "testing/matrix-sdk-test", version = "0.13.0" }
|
||||
matrix-sdk-ui = { path = "crates/matrix-sdk-ui", version = "0.13.0", default-features = false }
|
||||
matrix-sdk-indexeddb = { path = "crates/matrix-sdk-indexeddb", version = "0.14.0", default-features = false }
|
||||
matrix-sdk-qrcode = { path = "crates/matrix-sdk-qrcode", version = "0.14.0" }
|
||||
matrix-sdk-sqlite = { path = "crates/matrix-sdk-sqlite", version = "0.14.0", default-features = false }
|
||||
matrix-sdk-store-encryption = { path = "crates/matrix-sdk-store-encryption", version = "0.14.0" }
|
||||
matrix-sdk-test = { path = "testing/matrix-sdk-test", version = "0.14.0" }
|
||||
matrix-sdk-test-utils = { path = "testing/matrix-sdk-test-utils", version = "0.14.0" }
|
||||
matrix-sdk-ui = { path = "crates/matrix-sdk-ui", version = "0.14.0", default-features = false }
|
||||
matrix-sdk-search = { path = "crates/matrix-sdk-search", version = "0.14.0" }
|
||||
|
||||
[workspace.lints.rust]
|
||||
rust_2018_idioms = "warn"
|
||||
@@ -182,12 +191,15 @@ lto = false
|
||||
# Get symbol names for profiling purposes.
|
||||
debug = true
|
||||
|
||||
[profile.bench]
|
||||
inherits = "release"
|
||||
lto = false
|
||||
|
||||
[patch.crates-io]
|
||||
async-compat = { git = "https://github.com/element-hq/async-compat", rev = "5a27c8b290f1f1dcfc0c4ec22c464e38528aa591" }
|
||||
const_panic = { git = "https://github.com/jplatte/const_panic", rev = "9024a4cb3eac45c1d2d980f17aaee287b17be498" }
|
||||
# Needed to fix rotation log issue on Android (https://github.com/tokio-rs/tracing/issues/2937)
|
||||
tracing = { git = "https://github.com/element-hq/tracing.git", rev = "ca9431f74d37c9d3b5e6a9f35b2c706711dab7dd" }
|
||||
tracing-core = { git = "https://github.com/element-hq/tracing.git", rev = "ca9431f74d37c9d3b5e6a9f35b2c706711dab7dd" }
|
||||
tracing-subscriber = { git = "https://github.com/element-hq/tracing.git", rev = "ca9431f74d37c9d3b5e6a9f35b2c706711dab7dd" }
|
||||
tracing-appender = { git = "https://github.com/element-hq/tracing.git", rev = "ca9431f74d37c9d3b5e6a9f35b2c706711dab7dd" }
|
||||
paranoid-android = { git = "https://github.com/element-hq/paranoid-android.git", rev = "69388ac5b4afeed7be4401c70ce17f6d9a2cf19b" }
|
||||
tracing = { git = "https://github.com/tokio-rs/tracing.git", rev = "20f5b3d8ba057ca9c4ae00ad30dda3dce8a71c05" }
|
||||
tracing-core = { git = "https://github.com/tokio-rs/tracing.git", rev = "20f5b3d8ba057ca9c4ae00ad30dda3dce8a71c05" }
|
||||
tracing-subscriber = { git = "https://github.com/tokio-rs/tracing.git", rev = "20f5b3d8ba057ca9c4ae00ad30dda3dce8a71c05" }
|
||||
tracing-appender = { git = "https://github.com/tokio-rs/tracing.git", rev = "20f5b3d8ba057ca9c4ae00ad30dda3dce8a71c05" }
|
||||
|
||||
+10
-6
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "benchmarks"
|
||||
description = "Matrix SDK benchmarks"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "Apache-2.0"
|
||||
rust-version.workspace = true
|
||||
version = "1.0.0"
|
||||
@@ -10,8 +10,11 @@ publish = false
|
||||
[package.metadata.release]
|
||||
release = false
|
||||
|
||||
[features]
|
||||
codspeed = []
|
||||
|
||||
[dependencies]
|
||||
criterion = { version = "0.5.1", features = ["async", "async_tokio", "html_reports"] }
|
||||
criterion = { version = "3.0.5", features = ["async", "async_tokio", "html_reports"], package = "codspeed-criterion-compat" }
|
||||
matrix-sdk = { workspace = true, features = ["native-tls", "e2e-encryption", "sqlite", "testing"] }
|
||||
matrix-sdk-base.workspace = true
|
||||
matrix-sdk-crypto.workspace = true
|
||||
@@ -21,13 +24,10 @@ matrix-sdk-ui.workspace = true
|
||||
ruma.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tempfile = "3.3.0"
|
||||
tempfile.workspace = true
|
||||
tokio = { workspace = true, default-features = false, features = ["rt-multi-thread"] }
|
||||
wiremock.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
pprof = { version = "0.14.0", features = ["flamegraph", "criterion"] }
|
||||
|
||||
[[bench]]
|
||||
name = "crypto_bench"
|
||||
harness = false
|
||||
@@ -47,3 +47,7 @@ harness = false
|
||||
[[bench]]
|
||||
name = "timeline"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "event_cache"
|
||||
harness = false
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput};
|
||||
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
|
||||
use matrix_sdk_crypto::{EncryptionSettings, OlmMachine};
|
||||
use matrix_sdk_sqlite::SqliteCryptoStore;
|
||||
use matrix_sdk_test::ruma_response_from_json;
|
||||
use ruma::{
|
||||
DeviceId, OwnedUserId, TransactionId, UserId,
|
||||
api::client::{
|
||||
keys::{claim_keys, get_keys},
|
||||
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
|
||||
},
|
||||
device_id, room_id, user_id, DeviceId, OwnedUserId, TransactionId, UserId,
|
||||
device_id, room_id, user_id,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use tokio::runtime::Builder;
|
||||
@@ -58,10 +59,14 @@ pub fn keys_query(c: &mut Criterion) {
|
||||
|
||||
// Benchmark memory store.
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("memory store", &name), &response, |b, response| {
|
||||
b.to_async(&runtime)
|
||||
.iter(|| async { machine.mark_request_as_sent(&txn_id, response).await.unwrap() })
|
||||
});
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("Device keys query [memory]", &name),
|
||||
&response,
|
||||
|b, response| {
|
||||
b.to_async(&runtime)
|
||||
.iter(|| async { machine.mark_request_as_sent(&txn_id, response).await.unwrap() })
|
||||
},
|
||||
);
|
||||
|
||||
// Benchmark sqlite store.
|
||||
|
||||
@@ -71,10 +76,14 @@ pub fn keys_query(c: &mut Criterion) {
|
||||
.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store, None))
|
||||
.unwrap();
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("sqlite store", &name), &response, |b, response| {
|
||||
b.to_async(&runtime)
|
||||
.iter(|| async { machine.mark_request_as_sent(&txn_id, response).await.unwrap() })
|
||||
});
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("Device keys query [SQLite]", &name),
|
||||
&response,
|
||||
|b, response| {
|
||||
b.to_async(&runtime)
|
||||
.iter(|| async { machine.mark_request_as_sent(&txn_id, response).await.unwrap() })
|
||||
},
|
||||
);
|
||||
|
||||
{
|
||||
let _guard = runtime.enter();
|
||||
@@ -84,6 +93,8 @@ pub fn keys_query(c: &mut Criterion) {
|
||||
group.finish()
|
||||
}
|
||||
|
||||
/// This test panics on the CI, not sure why so we're disabling it for now.
|
||||
#[cfg(not(feature = "codspeed"))]
|
||||
pub fn keys_claiming(c: &mut Criterion) {
|
||||
let runtime = Builder::new_multi_thread().build().expect("Can't create runtime");
|
||||
|
||||
@@ -99,49 +110,65 @@ pub fn keys_claiming(c: &mut Criterion) {
|
||||
|
||||
let name = format!("{count} one-time keys");
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("memory store", &name), &response, |b, response| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let machine = runtime.block_on(OlmMachine::new(alice_id(), alice_device_id()));
|
||||
runtime
|
||||
.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response))
|
||||
.unwrap();
|
||||
(machine, &runtime, &txn_id)
|
||||
},
|
||||
move |(machine, runtime, txn_id)| {
|
||||
runtime.block_on(async {
|
||||
machine.mark_request_as_sent(txn_id, response).await.unwrap();
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("One-time keys claiming [memory]", &name),
|
||||
&response,
|
||||
|b, response| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let machine = runtime.block_on(OlmMachine::new(alice_id(), alice_device_id()));
|
||||
runtime
|
||||
.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response))
|
||||
.unwrap();
|
||||
(machine, &runtime, &txn_id)
|
||||
},
|
||||
move |(machine, runtime, txn_id)| {
|
||||
runtime.block_on(async {
|
||||
machine.mark_request_as_sent(txn_id, response).await.unwrap();
|
||||
drop(machine);
|
||||
})
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("One-time keys claiming [SQLite]", &name),
|
||||
&response,
|
||||
|b, response| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store = Arc::new(
|
||||
runtime.block_on(SqliteCryptoStore::open(dir.path(), None)).unwrap(),
|
||||
);
|
||||
|
||||
let machine = runtime
|
||||
.block_on(OlmMachine::with_store(
|
||||
alice_id(),
|
||||
alice_device_id(),
|
||||
store,
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
runtime
|
||||
.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response))
|
||||
.unwrap();
|
||||
(machine, &runtime, &txn_id)
|
||||
},
|
||||
move |(machine, runtime, txn_id)| {
|
||||
runtime.block_on(async {
|
||||
machine.mark_request_as_sent(txn_id, response).await.unwrap();
|
||||
});
|
||||
|
||||
let _ = runtime.enter();
|
||||
drop(machine);
|
||||
})
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("sqlite store", &name), &response, |b, response| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let store =
|
||||
Arc::new(runtime.block_on(SqliteCryptoStore::open(dir.path(), None)).unwrap());
|
||||
|
||||
let machine = runtime
|
||||
.block_on(OlmMachine::with_store(alice_id(), alice_device_id(), store, None))
|
||||
.unwrap();
|
||||
runtime
|
||||
.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response))
|
||||
.unwrap();
|
||||
(machine, &runtime, &txn_id)
|
||||
},
|
||||
move |(machine, runtime, txn_id)| {
|
||||
runtime.block_on(async {
|
||||
machine.mark_request_as_sent(txn_id, response).await.unwrap();
|
||||
drop(machine)
|
||||
})
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
group.finish()
|
||||
}
|
||||
@@ -169,7 +196,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
|
||||
|
||||
// Benchmark memory store.
|
||||
|
||||
group.bench_function(BenchmarkId::new("memory store", &name), |b| {
|
||||
group.bench_function(BenchmarkId::new("Room key sharing [memory]", &name), |b| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
let requests = machine
|
||||
.share_room_key(
|
||||
@@ -201,7 +228,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
|
||||
runtime.block_on(machine.mark_request_as_sent(&txn_id, &keys_query_response)).unwrap();
|
||||
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
|
||||
|
||||
group.bench_function(BenchmarkId::new("sqlite store", &name), |b| {
|
||||
group.bench_function(BenchmarkId::new("Room key sharing [SQLite]", &name), |b| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
let requests = machine
|
||||
.share_room_key(
|
||||
@@ -249,7 +276,7 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
|
||||
|
||||
// Benchmark memory store.
|
||||
|
||||
group.bench_function(BenchmarkId::new("memory store", &name), |b| {
|
||||
group.bench_function(BenchmarkId::new("Devices collecting [memory]", &name), |b| {
|
||||
b.to_async(&runtime).iter_with_large_drop(|| async {
|
||||
machine.get_missing_sessions(users.iter().map(Deref::deref)).await.unwrap()
|
||||
})
|
||||
@@ -266,7 +293,7 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
|
||||
|
||||
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
|
||||
|
||||
group.bench_function(BenchmarkId::new("sqlite store", &name), |b| {
|
||||
group.bench_function(BenchmarkId::new("Devices collecting [SQLite]", &name), |b| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
machine.get_missing_sessions(users.iter().map(Deref::deref)).await.unwrap()
|
||||
})
|
||||
@@ -280,21 +307,18 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
|
||||
group.finish()
|
||||
}
|
||||
|
||||
fn criterion() -> Criterion {
|
||||
#[cfg(target_os = "linux")]
|
||||
let criterion = Criterion::default().with_profiler(pprof::criterion::PProfProfiler::new(
|
||||
100,
|
||||
pprof::criterion::Output::Flamegraph(None),
|
||||
));
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let criterion = Criterion::default();
|
||||
|
||||
criterion
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "codspeed"))]
|
||||
criterion_group! {
|
||||
name = benches;
|
||||
config = criterion();
|
||||
config = Criterion::default();
|
||||
targets = keys_query, keys_claiming, room_key_sharing, devices_missing_sessions_collecting,
|
||||
}
|
||||
|
||||
#[cfg(feature = "codspeed")]
|
||||
criterion_group! {
|
||||
name = benches;
|
||||
config = Criterion::default();
|
||||
targets = keys_query, room_key_sharing, devices_missing_sessions_collecting,
|
||||
}
|
||||
|
||||
criterion_main!(benches);
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
use std::{pin::Pin, sync::Arc};
|
||||
|
||||
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
|
||||
use matrix_sdk::{
|
||||
RoomInfo, RoomState, SqliteEventCacheStore, StateStore,
|
||||
store::StoreConfig,
|
||||
sync::{JoinedRoomUpdate, RoomUpdates},
|
||||
test_utils::client::MockClientBuilder,
|
||||
};
|
||||
use matrix_sdk_base::event_cache::store::{DynEventCacheStore, IntoEventCacheStore, MemoryStore};
|
||||
use matrix_sdk_test::{ALICE, event_factory::EventFactory};
|
||||
use ruma::{
|
||||
EventId, RoomId, event_id,
|
||||
events::{relation::RelationType, room::message::RoomMessageEventContentWithoutRelation},
|
||||
room_id,
|
||||
};
|
||||
use tempfile::tempdir;
|
||||
use tokio::runtime::Builder;
|
||||
|
||||
type StoreBuilder = Box<dyn Fn() -> Pin<Box<dyn Future<Output = Arc<DynEventCacheStore>>>>>;
|
||||
|
||||
fn handle_room_updates(c: &mut Criterion) {
|
||||
// Create a new asynchronous runtime.
|
||||
let runtime = Builder::new_multi_thread()
|
||||
.enable_time()
|
||||
.enable_io()
|
||||
.build()
|
||||
.expect("Failed to create an asynchronous runtime");
|
||||
|
||||
let mut group = c.benchmark_group("Event cache room updates");
|
||||
group.sample_size(10);
|
||||
|
||||
const NUM_EVENTS: usize = 1000;
|
||||
|
||||
for num_rooms in [1, 10, 100] {
|
||||
// Add some joined rooms, each with NUM_EVENTS in it, to the sync response.
|
||||
let mut room_updates = RoomUpdates::default();
|
||||
|
||||
let mut changes = matrix_sdk::StateChanges::default();
|
||||
|
||||
for i in 0..num_rooms {
|
||||
let room_id = RoomId::parse(format!("!room{i}:example.com")).unwrap();
|
||||
let event_factory = EventFactory::new().room(&room_id).sender(&ALICE);
|
||||
|
||||
let mut joined_room_update = JoinedRoomUpdate::default();
|
||||
for j in 0..NUM_EVENTS {
|
||||
let event_id = EventId::parse(format!("$ev{i}_{j}")).unwrap();
|
||||
let event =
|
||||
event_factory.text_msg(format!("Message {j}")).event_id(&event_id).into();
|
||||
joined_room_update.timeline.events.push(event);
|
||||
}
|
||||
room_updates.joined.insert(room_id.clone(), joined_room_update);
|
||||
|
||||
changes.add_room(RoomInfo::new(&room_id, RoomState::Joined));
|
||||
}
|
||||
|
||||
// Declare new stores for this set of events.
|
||||
let temp_dir = Arc::new(tempdir().unwrap());
|
||||
|
||||
let store_builders: Vec<(_, StoreBuilder)> = vec![
|
||||
(
|
||||
"memory",
|
||||
Box::new(|| Box::pin(async { MemoryStore::default().into_event_cache_store() })),
|
||||
),
|
||||
(
|
||||
"SQLite",
|
||||
Box::new(move || {
|
||||
let temp_dir = temp_dir.clone();
|
||||
Box::pin(async move {
|
||||
// Remove all the files in the temp_dir, to reset the event cache state.
|
||||
for entry in temp_dir.path().read_dir().unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
// If it's a directory, remove it recursively.
|
||||
std::fs::remove_dir_all(path).unwrap();
|
||||
} else {
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Recreate a new store.
|
||||
SqliteEventCacheStore::open(temp_dir.path().join("bench"), None)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_event_cache_store()
|
||||
})
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
let state_store = runtime.block_on(async {
|
||||
let state_store = matrix_sdk::MemoryStore::new();
|
||||
state_store.save_changes(&changes).await.unwrap();
|
||||
Arc::new(state_store)
|
||||
});
|
||||
|
||||
for (store_name, store_builder) in &store_builders {
|
||||
let client = runtime.block_on(async {
|
||||
let event_cache_store = store_builder().await;
|
||||
|
||||
let client = MockClientBuilder::new(None)
|
||||
.on_builder(|builder| {
|
||||
builder.store_config(
|
||||
StoreConfig::new("cross-process-store-locks-holder-name".to_owned())
|
||||
.state_store(state_store.clone())
|
||||
.event_cache_store(event_cache_store.clone()),
|
||||
)
|
||||
})
|
||||
.build()
|
||||
.await;
|
||||
|
||||
client.event_cache().subscribe().unwrap();
|
||||
|
||||
client
|
||||
});
|
||||
|
||||
// Define a state store with all rooms known in it.
|
||||
// Define the throughput.
|
||||
group.throughput(Throughput::Elements(num_rooms));
|
||||
|
||||
// Bench the handling of room updates.
|
||||
group.bench_function(
|
||||
BenchmarkId::new(
|
||||
format!("Event cache room updates[{store_name}]"),
|
||||
format!("room count: {num_rooms}"),
|
||||
),
|
||||
|bencher| {
|
||||
bencher.to_async(&runtime).iter(
|
||||
// The routine itself.
|
||||
|| {
|
||||
let room_updates = room_updates.clone();
|
||||
let client = client.clone();
|
||||
|
||||
async move {
|
||||
client.event_cache().clear_all_rooms().await.unwrap();
|
||||
|
||||
client
|
||||
.event_cache()
|
||||
.handle_room_updates(room_updates.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
group.finish()
|
||||
}
|
||||
|
||||
fn find_event_relations(c: &mut Criterion) {
|
||||
// Number of other events to saturate the DB, but that will not be affected by
|
||||
// the benchmark. A small multiple of this number will be added.
|
||||
// When running locally, run with more events than in Codespeed CI.
|
||||
#[cfg(feature = "codspeed")]
|
||||
const NUM_OTHER_EVENTS: usize = 100;
|
||||
#[cfg(not(feature = "codspeed"))]
|
||||
const NUM_OTHER_EVENTS: usize = 1000;
|
||||
|
||||
// Create a new asynchronous runtime.
|
||||
let runtime = Builder::new_multi_thread()
|
||||
.enable_time()
|
||||
.enable_io()
|
||||
.build()
|
||||
.expect("Failed to create an asynchronous runtime");
|
||||
|
||||
let mut group = c.benchmark_group("Event cache room updates");
|
||||
group.sample_size(10);
|
||||
|
||||
let room_id = room_id!("!room:ben.ch");
|
||||
let other_room_id = room_id!("!other-room:ben.ch");
|
||||
|
||||
// Make the state store aware of the room, so that `client.get_room()` works
|
||||
// with it.
|
||||
let mut changes = matrix_sdk::StateChanges::default();
|
||||
changes.add_room(RoomInfo::new(room_id, RoomState::Joined));
|
||||
changes.add_room(RoomInfo::new(other_room_id, RoomState::Joined));
|
||||
let state_store = runtime.block_on(async {
|
||||
let state_store = matrix_sdk::MemoryStore::new();
|
||||
state_store.save_changes(&changes).await.unwrap();
|
||||
Arc::new(state_store)
|
||||
});
|
||||
|
||||
for num_related_events in [10, 100, 1000] {
|
||||
// Prefill the event cache store with one event and N related events.
|
||||
let mut room_updates = RoomUpdates::default();
|
||||
|
||||
let event_factory = EventFactory::new().room(room_id).sender(&ALICE);
|
||||
|
||||
let mut joined_room_update = JoinedRoomUpdate::default();
|
||||
|
||||
// Add the target event.
|
||||
let target_event_id = event_id!("$target");
|
||||
let target_event =
|
||||
event_factory.text_msg("hello world").event_id(target_event_id).into_event();
|
||||
joined_room_update.timeline.events.push(target_event);
|
||||
|
||||
// Add the numerous edits.
|
||||
for i in 0..num_related_events {
|
||||
let event_id = EventId::parse(format!("$edit{i}")).unwrap();
|
||||
let event = event_factory
|
||||
.text_msg(format!("* edit {i}"))
|
||||
.edit(
|
||||
target_event_id,
|
||||
RoomMessageEventContentWithoutRelation::text_plain(format!("edit {i}")),
|
||||
)
|
||||
.event_id(&event_id)
|
||||
.into();
|
||||
joined_room_update.timeline.events.push(event);
|
||||
}
|
||||
|
||||
// Add other events, in the same room, without a relation.
|
||||
for i in 0..NUM_OTHER_EVENTS {
|
||||
let event_id = EventId::parse(format!("$msg{i}")).unwrap();
|
||||
let event =
|
||||
event_factory.text_msg(format!("unrelated message {i}")).event_id(&event_id).into();
|
||||
joined_room_update.timeline.events.push(event);
|
||||
}
|
||||
|
||||
// Add other events, in the same room, related to other events.
|
||||
let other_target_event_id = event_id!("$other_target");
|
||||
let other_target_event =
|
||||
event_factory.text_msg("hello world").event_id(other_target_event_id).into_event();
|
||||
joined_room_update.timeline.events.push(other_target_event);
|
||||
|
||||
for i in 0..NUM_OTHER_EVENTS {
|
||||
let event_id = EventId::parse(format!("$unrelated{i}")).unwrap();
|
||||
let event =
|
||||
event_factory.reaction(other_target_event_id, "👍").event_id(&event_id).into();
|
||||
joined_room_update.timeline.events.push(event);
|
||||
}
|
||||
|
||||
room_updates.joined.insert(room_id.to_owned(), joined_room_update);
|
||||
|
||||
// Add other events, in another room.
|
||||
let mut other_joined_room_update = JoinedRoomUpdate::default();
|
||||
let event_factory = event_factory.room(other_room_id);
|
||||
for i in 0..NUM_OTHER_EVENTS {
|
||||
let event_id = EventId::parse(format!("$other_room{i}")).unwrap();
|
||||
let event = event_factory.text_msg(format!("hi {i}")).event_id(&event_id).into();
|
||||
other_joined_room_update.timeline.events.push(event);
|
||||
}
|
||||
room_updates.joined.insert(other_room_id.to_owned(), other_joined_room_update);
|
||||
|
||||
changes.add_room(RoomInfo::new(room_id, RoomState::Joined));
|
||||
|
||||
// Declare new stores for this set of events.
|
||||
let temp_dir = Arc::new(tempdir().unwrap());
|
||||
|
||||
let stores = vec![
|
||||
("memory", MemoryStore::default().into_event_cache_store()),
|
||||
(
|
||||
"SQLite",
|
||||
runtime.block_on(async {
|
||||
SqliteEventCacheStore::open(temp_dir.path().join("bench"), None)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_event_cache_store()
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
for (store_name, event_cache_store) in stores {
|
||||
let (client, room_event_cache, _drop_handles) = runtime.block_on(async {
|
||||
let client = MockClientBuilder::new(None)
|
||||
.on_builder(|builder| {
|
||||
builder.store_config(
|
||||
StoreConfig::new("cross-process-store-locks-holder-name".to_owned())
|
||||
.state_store(state_store.clone())
|
||||
.event_cache_store(event_cache_store),
|
||||
)
|
||||
})
|
||||
.build()
|
||||
.await;
|
||||
|
||||
client.event_cache().subscribe().unwrap();
|
||||
|
||||
// Sync the updates before starting the benchmark.
|
||||
let mut update_recv = client.event_cache().subscribe_to_room_generic_updates();
|
||||
|
||||
client.event_cache().handle_room_updates(room_updates.clone()).await.unwrap();
|
||||
|
||||
// Wait for the event cache to notify us of the room updates.
|
||||
let update = update_recv.recv().await.unwrap();
|
||||
assert!(update.room_id == room_id || update.room_id == other_room_id);
|
||||
|
||||
let update = update_recv.recv().await.unwrap();
|
||||
assert!(update.room_id == room_id || update.room_id == other_room_id);
|
||||
|
||||
let room = client.get_room(room_id).unwrap();
|
||||
let room_event_cache = room.event_cache().await.unwrap();
|
||||
|
||||
(client, room_event_cache.0, room_event_cache.1)
|
||||
});
|
||||
|
||||
// Define the throughput.
|
||||
group.throughput(Throughput::Elements(num_related_events));
|
||||
|
||||
for filter in [None, Some(vec![RelationType::Replacement])] {
|
||||
group.bench_function(
|
||||
BenchmarkId::new(
|
||||
format!("Event cache find_event_relations[{store_name}]"),
|
||||
format!(
|
||||
"{num_related_events} events, {} filter",
|
||||
if filter.is_some() { "edits" } else { "#no" },
|
||||
),
|
||||
),
|
||||
|bencher| {
|
||||
bencher.to_async(&runtime).iter_batched(
|
||||
// The setup.
|
||||
|| (room_event_cache.clone(), filter.clone()),
|
||||
// The routine itself.
|
||||
|(room_event_cache, filter)| async move {
|
||||
let (target, relations) = room_event_cache
|
||||
.find_event_with_relations(target_event_id, filter)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(target.event_id().as_deref().unwrap(), target_event_id);
|
||||
assert_eq!(relations.len(), num_related_events as usize);
|
||||
},
|
||||
criterion::BatchSize::PerIteration,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let _guard = runtime.enter();
|
||||
drop(room_event_cache);
|
||||
drop(client);
|
||||
drop(_drop_handles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let _guard = runtime.enter();
|
||||
drop(state_store);
|
||||
}
|
||||
|
||||
group.finish()
|
||||
}
|
||||
|
||||
criterion_group! {
|
||||
name = event_cache;
|
||||
config = Criterion::default();
|
||||
targets = handle_room_updates, find_event_relations,
|
||||
}
|
||||
|
||||
criterion_main!(event_cache);
|
||||
@@ -1,16 +1,16 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput};
|
||||
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
|
||||
use matrix_sdk::{
|
||||
linked_chunk::{lazy_loader, LinkedChunk, LinkedChunkId, Update},
|
||||
SqliteEventCacheStore,
|
||||
linked_chunk::{LinkedChunk, LinkedChunkId, Update, lazy_loader},
|
||||
};
|
||||
use matrix_sdk_base::event_cache::{
|
||||
store::{DynEventCacheStore, IntoEventCacheStore, MemoryStore, DEFAULT_CHUNK_CAPACITY},
|
||||
Event, Gap,
|
||||
store::{DEFAULT_CHUNK_CAPACITY, DynEventCacheStore, IntoEventCacheStore, MemoryStore},
|
||||
};
|
||||
use matrix_sdk_test::{event_factory::EventFactory, ALICE};
|
||||
use ruma::{room_id, EventId};
|
||||
use matrix_sdk_test::{ALICE, event_factory::EventFactory};
|
||||
use ruma::{EventId, room_id};
|
||||
use tempfile::tempdir;
|
||||
use tokio::runtime::Builder;
|
||||
|
||||
@@ -20,6 +20,11 @@ enum Operation {
|
||||
PushGapBack(Gap),
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "codspeed"))]
|
||||
const NUMBER_OF_EVENTS: &[u64] = &[10, 100, 1000, 10_000, 100_000];
|
||||
#[cfg(feature = "codspeed")]
|
||||
const NUMBER_OF_EVENTS: &[u64] = &[10, 100, 1000];
|
||||
|
||||
fn writing(c: &mut Criterion) {
|
||||
// Create a new asynchronous runtime.
|
||||
let runtime = Builder::new_multi_thread()
|
||||
@@ -32,10 +37,10 @@ fn writing(c: &mut Criterion) {
|
||||
let linked_chunk_id = LinkedChunkId::Room(room_id);
|
||||
let event_factory = EventFactory::new().room(room_id).sender(&ALICE);
|
||||
|
||||
let mut group = c.benchmark_group("writing");
|
||||
let mut group = c.benchmark_group("Linked chunk writing");
|
||||
group.sample_size(10).measurement_time(Duration::from_secs(30));
|
||||
|
||||
for number_of_events in [10, 100, 1000, 10_000, 100_000] {
|
||||
for &number_of_events in NUMBER_OF_EVENTS {
|
||||
let sqlite_temp_dir = tempdir().unwrap();
|
||||
|
||||
// Declare new stores for this set of events.
|
||||
@@ -96,7 +101,7 @@ fn writing(c: &mut Criterion) {
|
||||
|
||||
// Get a bencher.
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new(store_name, number_of_events),
|
||||
BenchmarkId::new(format!("Linked chunk writing [{store_name}]"), number_of_events),
|
||||
&operations,
|
||||
|bencher, operations| {
|
||||
// Bench the routine.
|
||||
@@ -149,10 +154,10 @@ fn reading(c: &mut Criterion) {
|
||||
let linked_chunk_id = LinkedChunkId::Room(room_id);
|
||||
let event_factory = EventFactory::new().room(room_id).sender(&ALICE);
|
||||
|
||||
let mut group = c.benchmark_group("reading");
|
||||
let mut group = c.benchmark_group("Linked chunk reading");
|
||||
group.sample_size(10);
|
||||
|
||||
for num_events in [10, 100, 1000, 10_000, 100_000] {
|
||||
for &num_events in NUMBER_OF_EVENTS {
|
||||
let sqlite_temp_dir = tempdir().unwrap();
|
||||
|
||||
// Declare new stores for this set of events.
|
||||
@@ -187,11 +192,14 @@ fn reading(c: &mut Criterion) {
|
||||
|
||||
while events.peek().is_some() {
|
||||
let events_chunk = events.by_ref().take(80).collect::<Vec<_>>();
|
||||
|
||||
if events_chunk.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
lc.push_items_back(events_chunk);
|
||||
lc.push_gap_back(Gap { prev_token: format!("gap{num_gaps}") });
|
||||
|
||||
num_gaps += 1;
|
||||
}
|
||||
|
||||
@@ -205,30 +213,47 @@ fn reading(c: &mut Criterion) {
|
||||
// Define the throughput.
|
||||
group.throughput(Throughput::Elements(num_events));
|
||||
|
||||
// Get a bencher.
|
||||
group.bench_function(BenchmarkId::new(store_name, num_events), |bencher| {
|
||||
// Bench the routine.
|
||||
bencher.to_async(&runtime).iter(|| async {
|
||||
// Load the last chunk first,
|
||||
let (last_chunk, chunk_id_gen) =
|
||||
store.load_last_chunk(linked_chunk_id).await.unwrap();
|
||||
// Bench the lazy loader.
|
||||
group.bench_function(
|
||||
BenchmarkId::new(format!("Linked chunk lazy loader[{store_name}]"), num_events),
|
||||
|bencher| {
|
||||
// Bench the routine.
|
||||
bencher.to_async(&runtime).iter(|| async {
|
||||
// Load the last chunk first,
|
||||
let (last_chunk, chunk_id_gen) =
|
||||
store.load_last_chunk(linked_chunk_id).await.unwrap();
|
||||
|
||||
let mut lc =
|
||||
lazy_loader::from_last_chunk::<128, _, _>(last_chunk, chunk_id_gen)
|
||||
.expect("no error when reconstructing the linked chunk")
|
||||
.expect("there is a linked chunk in the store");
|
||||
let mut lc =
|
||||
lazy_loader::from_last_chunk::<128, _, _>(last_chunk, chunk_id_gen)
|
||||
.expect("no error when reconstructing the linked chunk")
|
||||
.expect("there is a linked chunk in the store");
|
||||
|
||||
// Then load until the start of the linked chunk.
|
||||
let mut cur_chunk_id = lc.chunks().next().unwrap().identifier();
|
||||
while let Some(prev) =
|
||||
store.load_previous_chunk(linked_chunk_id, cur_chunk_id).await.unwrap()
|
||||
{
|
||||
cur_chunk_id = prev.identifier;
|
||||
lazy_loader::insert_new_first_chunk(&mut lc, prev)
|
||||
.expect("no error when linking the previous lazy-loaded chunk");
|
||||
}
|
||||
})
|
||||
});
|
||||
// Then load until the start of the linked chunk.
|
||||
let mut cur_chunk_id = lc.chunks().next().unwrap().identifier();
|
||||
while let Some(prev) =
|
||||
store.load_previous_chunk(linked_chunk_id, cur_chunk_id).await.unwrap()
|
||||
{
|
||||
cur_chunk_id = prev.identifier;
|
||||
lazy_loader::insert_new_first_chunk(&mut lc, prev)
|
||||
.expect("no error when linking the previous lazy-loaded chunk");
|
||||
}
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
// Bench the metadata loader.
|
||||
group.bench_function(
|
||||
BenchmarkId::new(format!("Linked chunk metadata loader[{store_name}]"), num_events),
|
||||
|bencher| {
|
||||
// Bench the routine.
|
||||
bencher.to_async(&runtime).iter(|| async {
|
||||
let _metadata = store
|
||||
.load_all_chunks_metadata(linked_chunk_id)
|
||||
.await
|
||||
.expect("metadata must load");
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
{
|
||||
let _guard = runtime.enter();
|
||||
@@ -240,21 +265,9 @@ fn reading(c: &mut Criterion) {
|
||||
group.finish()
|
||||
}
|
||||
|
||||
fn criterion() -> Criterion {
|
||||
#[cfg(target_os = "linux")]
|
||||
let criterion = Criterion::default().with_profiler(pprof::criterion::PProfProfiler::new(
|
||||
100,
|
||||
pprof::criterion::Output::Flamegraph(None),
|
||||
));
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let criterion = Criterion::default();
|
||||
|
||||
criterion
|
||||
}
|
||||
|
||||
criterion_group! {
|
||||
name = event_cache;
|
||||
config = criterion();
|
||||
config = Criterion::default();
|
||||
targets = writing, reading,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
|
||||
use matrix_sdk::{store::RoomLoadSettings, test_utils::mocks::MatrixMockServer};
|
||||
use matrix_sdk_base::{
|
||||
store::StoreConfig, BaseClient, RoomInfo, RoomState, SessionMeta, StateChanges, StateStore,
|
||||
ThreadingSupport,
|
||||
BaseClient, RoomInfo, RoomState, SessionMeta, StateChanges, StateStore, ThreadingSupport,
|
||||
store::StoreConfig,
|
||||
};
|
||||
use matrix_sdk_sqlite::SqliteStateStore;
|
||||
use matrix_sdk_test::{event_factory::EventFactory, JoinedRoomBuilder, StateTestEvent};
|
||||
use matrix_sdk_test::{JoinedRoomBuilder, StateTestEvent, event_factory::EventFactory};
|
||||
use matrix_sdk_ui::timeline::{TimelineBuilder, TimelineFocus};
|
||||
use ruma::{
|
||||
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId,
|
||||
api::client::membership::get_member_events,
|
||||
device_id,
|
||||
events::room::member::{MembershipState, RoomMemberEvent},
|
||||
mxc_uri, owned_room_id, owned_user_id,
|
||||
serde::Raw,
|
||||
user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId,
|
||||
user_id,
|
||||
};
|
||||
use serde_json::json;
|
||||
use tokio::runtime::Builder;
|
||||
@@ -84,7 +85,7 @@ pub fn receive_all_members_benchmark(c: &mut Criterion) {
|
||||
group.throughput(Throughput::Elements(count as u64));
|
||||
group.sample_size(50);
|
||||
|
||||
group.bench_function(BenchmarkId::new("receive_members", name), |b| {
|
||||
group.bench_function(BenchmarkId::new("Handle /members request [SQLite]", name), |b| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
base_client.receive_all_members(&room_id, &request, &response).await.unwrap();
|
||||
});
|
||||
@@ -164,11 +165,11 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) {
|
||||
|
||||
let count = PINNED_EVENTS_COUNT;
|
||||
let name = format!("{count} pinned events");
|
||||
let mut group = c.benchmark_group("Test");
|
||||
let mut group = c.benchmark_group("Load pinned events");
|
||||
group.throughput(Throughput::Elements(count as u64));
|
||||
group.sample_size(10);
|
||||
|
||||
group.bench_function(BenchmarkId::new("load_pinned_events", name), |b| {
|
||||
group.bench_function(BenchmarkId::new("Load pinned events [memory]", name), |b| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
|
||||
assert!(!pinned_event_ids.is_empty());
|
||||
@@ -207,24 +208,9 @@ pub fn load_pinned_events_benchmark(c: &mut Criterion) {
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn criterion() -> Criterion {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
Criterion::default().with_profiler(pprof::criterion::PProfProfiler::new(
|
||||
100,
|
||||
pprof::criterion::Output::Flamegraph(None),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
Criterion::default()
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group! {
|
||||
name = room;
|
||||
config = criterion();
|
||||
config = Criterion::default();
|
||||
targets = receive_all_members_benchmark, load_pinned_events_benchmark,
|
||||
}
|
||||
criterion_main!(room);
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
|
||||
use matrix_sdk::{
|
||||
authentication::matrix::MatrixSession, config::StoreConfig, Client, RoomInfo, RoomState,
|
||||
SessionTokens, StateChanges,
|
||||
Client, RoomInfo, RoomState, SessionTokens, StateChanges,
|
||||
authentication::matrix::MatrixSession, config::StoreConfig,
|
||||
};
|
||||
use matrix_sdk_base::{store::MemoryStore, SessionMeta, StateStore as _};
|
||||
use matrix_sdk_base::{SessionMeta, StateStore as _, store::MemoryStore};
|
||||
use matrix_sdk_sqlite::SqliteStateStore;
|
||||
use ruma::{device_id, user_id, RoomId};
|
||||
use ruma::{RoomId, device_id, user_id};
|
||||
use tokio::runtime::Builder;
|
||||
|
||||
fn criterion() -> Criterion {
|
||||
#[cfg(target_os = "linux")]
|
||||
let criterion = Criterion::default().with_profiler(pprof::criterion::PProfProfiler::new(
|
||||
100,
|
||||
pprof::criterion::Output::Flamegraph(None),
|
||||
));
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let criterion = Criterion::default();
|
||||
|
||||
criterion
|
||||
}
|
||||
|
||||
/// Number of joined rooms in the benchmark.
|
||||
const NUM_JOINED_ROOMS: usize = 10000;
|
||||
|
||||
@@ -30,7 +17,7 @@ const NUM_JOINED_ROOMS: usize = 10000;
|
||||
const NUM_STRIPPED_JOINED_ROOMS: usize = 10000;
|
||||
|
||||
pub fn restore_session(c: &mut Criterion) {
|
||||
let runtime = Builder::new_multi_thread().build().expect("Can't create runtime");
|
||||
let runtime = Builder::new_multi_thread().enable_time().build().expect("Can't create runtime");
|
||||
|
||||
// Create a fake list of changes, and a session to recover from.
|
||||
let mut changes = StateChanges::default();
|
||||
@@ -58,13 +45,11 @@ pub fn restore_session(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Client reload");
|
||||
group.throughput(Throughput::Elements(100));
|
||||
|
||||
const NAME: &str = "restore a session";
|
||||
|
||||
// Memory
|
||||
let mem_store = Arc::new(MemoryStore::new());
|
||||
runtime.block_on(mem_store.save_changes(&changes)).expect("initial filling of mem failed");
|
||||
|
||||
group.bench_with_input(BenchmarkId::new("memory store", NAME), &mem_store, |b, store| {
|
||||
group.bench_with_input("Restore session [memory store]", &mem_store, |b, store| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
let client = Client::builder()
|
||||
.homeserver_url("https://matrix.example.com")
|
||||
@@ -92,7 +77,7 @@ pub fn restore_session(c: &mut Criterion) {
|
||||
.expect("initial filling of sqlite failed");
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new(format!("sqlite store {encrypted_suffix}"), NAME),
|
||||
BenchmarkId::new("Restore session [SQLite]", encrypted_suffix),
|
||||
&sqlite_store,
|
||||
|b, store| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
@@ -124,7 +109,7 @@ pub fn restore_session(c: &mut Criterion) {
|
||||
|
||||
criterion_group! {
|
||||
name = benches;
|
||||
config = criterion();
|
||||
config = Criterion::default();
|
||||
targets = restore_session
|
||||
}
|
||||
criterion_main!(benches);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
|
||||
use matrix_sdk::test_utils::mocks::MatrixMockServer;
|
||||
use matrix_sdk_test::{event_factory::EventFactory, JoinedRoomBuilder, StateTestEvent};
|
||||
use matrix_sdk_test::{JoinedRoomBuilder, StateTestEvent, event_factory::EventFactory};
|
||||
use matrix_sdk_ui::timeline::TimelineBuilder;
|
||||
use ruma::{
|
||||
events::room::message::RoomMessageEventContentWithoutRelation, owned_room_id, owned_user_id,
|
||||
EventId,
|
||||
EventId, events::room::message::RoomMessageEventContentWithoutRelation, owned_room_id,
|
||||
owned_user_id,
|
||||
};
|
||||
use tokio::runtime::Builder;
|
||||
|
||||
@@ -94,12 +94,12 @@ pub fn create_timeline_with_initial_events(c: &mut Criterion) {
|
||||
room
|
||||
});
|
||||
|
||||
let mut group = c.benchmark_group("Test");
|
||||
let mut group = c.benchmark_group("Create a timeline");
|
||||
group.throughput(Throughput::Elements(NUM_EVENTS as _));
|
||||
group.sample_size(10);
|
||||
|
||||
group.bench_function(
|
||||
BenchmarkId::new("create_timeline_with_initial_events", format!("{NUM_EVENTS} events")),
|
||||
BenchmarkId::new("Create a timeline with initial events", format!("{NUM_EVENTS} events")),
|
||||
|b| {
|
||||
b.to_async(&runtime).iter(|| async {
|
||||
let timeline = TimelineBuilder::new(&room)
|
||||
@@ -117,24 +117,9 @@ pub fn create_timeline_with_initial_events(c: &mut Criterion) {
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn criterion() -> Criterion {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
Criterion::default().with_profiler(pprof::criterion::PProfProfiler::new(
|
||||
100,
|
||||
pprof::criterion::Output::Flamegraph(None),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
Criterion::default()
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group! {
|
||||
name = room;
|
||||
config = criterion();
|
||||
config = Criterion::default();
|
||||
targets = create_timeline_with_initial_events
|
||||
}
|
||||
criterion_main!(room);
|
||||
|
||||
@@ -23,14 +23,20 @@ path = "uniffi-bindgen.rs"
|
||||
default = ["bundled-sqlite"]
|
||||
bundled-sqlite = ["matrix-sdk-sqlite/bundled"]
|
||||
|
||||
# Enable experimental support for encrypting state events; see
|
||||
# https://github.com/matrix-org/matrix-rust-sdk/issues/5397.
|
||||
experimental-encrypted-state-events = [
|
||||
"matrix-sdk-crypto/experimental-encrypted-state-events",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
futures-util.workspace = true
|
||||
hmac = "0.12.1"
|
||||
hmac.workspace = true
|
||||
http.workspace = true
|
||||
matrix-sdk-common = { workspace = true, features = ["uniffi"] }
|
||||
matrix-sdk-ffi-macros.workspace = true
|
||||
pbkdf2 = "0.12.2"
|
||||
pbkdf2.workspace = true
|
||||
rand.workspace = true
|
||||
ruma.workspace = true
|
||||
serde.workspace = true
|
||||
@@ -56,17 +62,17 @@ workspace = true
|
||||
features = ["crypto-store"]
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.43.1"
|
||||
workspace = true
|
||||
default-features = false
|
||||
features = ["rt-multi-thread"]
|
||||
|
||||
[build-dependencies]
|
||||
uniffi = { workspace = true, features = ["build"] }
|
||||
vergen = { version = "8.2.5", features = ["build", "git", "gitcl"] }
|
||||
vergen-gitcl = { workspace = true, features = ["build"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches2.workspace = true
|
||||
tempfile = "3.8.0"
|
||||
tempfile.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use vergen::EmitBuilder;
|
||||
use vergen_gitcl::{Emitter, GitclBuilder};
|
||||
|
||||
/// Adds a temporary workaround for an issue with the Rust compiler and Android
|
||||
/// in x86_64 devices: https://github.com/rust-lang/rust/issues/109717.
|
||||
@@ -59,7 +59,8 @@ fn get_clang_major_version(clang_path: &Path) -> String {
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
setup_x86_64_android_workaround();
|
||||
|
||||
EmitBuilder::builder().git_sha(true).git_describe(true, false, None).emit()?;
|
||||
let git_config = GitclBuilder::default().sha(true).describe(true, false, None).build()?;
|
||||
Emitter::default().add_instructions(&git_config)?.emit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use matrix_sdk_crypto::{
|
||||
RehydratedDevice as InnerRehydratedDevice,
|
||||
},
|
||||
store::types::DehydratedDeviceKey as InnerDehydratedDeviceKey,
|
||||
DecryptionSettings,
|
||||
};
|
||||
use ruma::{api::client::dehydrated_device, events::AnyToDeviceEvent, serde::Raw, OwnedDeviceId};
|
||||
use serde_json::json;
|
||||
@@ -154,9 +155,13 @@ impl Drop for RehydratedDevice {
|
||||
|
||||
#[matrix_sdk_ffi_macros::export]
|
||||
impl RehydratedDevice {
|
||||
pub fn receive_events(&self, events: String) -> Result<(), crate::CryptoStoreError> {
|
||||
pub fn receive_events(
|
||||
&self,
|
||||
events: String,
|
||||
decryption_settings: &DecryptionSettings,
|
||||
) -> Result<(), crate::CryptoStoreError> {
|
||||
let events: Vec<Raw<AnyToDeviceEvent>> = serde_json::from_str(&events)?;
|
||||
self.runtime.block_on(self.inner.receive_events(events))?;
|
||||
self.runtime.block_on(self.inner.receive_events(events, decryption_settings))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -665,6 +665,9 @@ impl From<HistoryVisibility> for RustHistoryVisibility {
|
||||
pub struct EncryptionSettings {
|
||||
/// The encryption algorithm that should be used in the room.
|
||||
pub algorithm: EventEncryptionAlgorithm,
|
||||
/// Whether state event encryption is enabled.
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
pub encrypt_state_events: bool,
|
||||
/// How long can the room key be used before it should be rotated. Time in
|
||||
/// seconds.
|
||||
pub rotation_period: u64,
|
||||
@@ -694,6 +697,8 @@ impl From<EncryptionSettings> for RustEncryptionSettings {
|
||||
|
||||
RustEncryptionSettings {
|
||||
algorithm: v.algorithm.into(),
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
encrypt_state_events: false,
|
||||
rotation_period: Duration::from_secs(v.rotation_period),
|
||||
rotation_period_msgs: v.rotation_period_msgs,
|
||||
history_visibility: v.history_visibility.into(),
|
||||
@@ -910,6 +915,10 @@ impl From<matrix_sdk_crypto::CrossSigningStatus> for CrossSigningStatus {
|
||||
pub struct RoomSettings {
|
||||
/// The encryption algorithm that should be used in the room.
|
||||
pub algorithm: EventEncryptionAlgorithm,
|
||||
/// Whether state event encryption is enabled.
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
#[serde(default)]
|
||||
pub encrypt_state_events: bool,
|
||||
/// Should untrusted devices receive the room key, or should they be
|
||||
/// excluded from the conversation.
|
||||
pub only_allow_trusted_devices: bool,
|
||||
@@ -920,7 +929,12 @@ impl TryFrom<RustRoomSettings> for RoomSettings {
|
||||
|
||||
fn try_from(value: RustRoomSettings) -> Result<Self, Self::Error> {
|
||||
let algorithm = value.algorithm.try_into()?;
|
||||
Ok(Self { algorithm, only_allow_trusted_devices: value.only_allow_trusted_devices })
|
||||
Ok(Self {
|
||||
algorithm,
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
encrypt_state_events: value.encrypt_state_events,
|
||||
only_allow_trusted_devices: value.only_allow_trusted_devices,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1173,6 +1187,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
Some(RoomSettings {
|
||||
algorithm: EventEncryptionAlgorithm::OlmV1Curve25519AesSha2,
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
encrypt_state_events: false,
|
||||
only_allow_trusted_devices: true
|
||||
}),
|
||||
settings1
|
||||
@@ -1182,6 +1198,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
Some(RoomSettings {
|
||||
algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
encrypt_state_events: false,
|
||||
only_allow_trusted_devices: false
|
||||
}),
|
||||
settings2
|
||||
|
||||
@@ -18,7 +18,8 @@ use matrix_sdk_crypto::{
|
||||
olm::ExportedRoomKey,
|
||||
store::types::{BackupDecryptionKey, Changes},
|
||||
types::requests::ToDeviceRequest,
|
||||
DecryptionSettings, LocalTrust, OlmMachine as InnerMachine, UserIdentity as SdkUserIdentity,
|
||||
CollectStrategy, DecryptionSettings, LocalTrust, OlmMachine as InnerMachine,
|
||||
UserIdentity as SdkUserIdentity,
|
||||
};
|
||||
use ruma::{
|
||||
api::{
|
||||
@@ -38,7 +39,7 @@ use ruma::{
|
||||
},
|
||||
events::{
|
||||
key::verification::VerificationMethod, room::message::MessageType, AnyMessageLikeEvent,
|
||||
AnySyncMessageLikeEvent, MessageLikeEvent,
|
||||
AnySyncMessageLikeEvent, AnyTimelineEvent, MessageLikeEvent,
|
||||
},
|
||||
serde::Raw,
|
||||
to_device::DeviceIdOrAllDevices,
|
||||
@@ -526,6 +527,7 @@ impl OlmMachine {
|
||||
key_counts: HashMap<String, i32>,
|
||||
unused_fallback_keys: Option<Vec<String>>,
|
||||
next_batch_token: String,
|
||||
decryption_settings: &DecryptionSettings,
|
||||
) -> Result<SyncChangesResult, CryptoStoreError> {
|
||||
let to_device: ToDevice = serde_json::from_str(&events)?;
|
||||
let device_changes: RumaDeviceLists = device_changes.into();
|
||||
@@ -544,15 +546,17 @@ impl OlmMachine {
|
||||
let unused_fallback_keys: Option<Vec<OneTimeKeyAlgorithm>> =
|
||||
unused_fallback_keys.map(|u| u.into_iter().map(OneTimeKeyAlgorithm::from).collect());
|
||||
|
||||
let (to_device_events, room_key_infos) = self.runtime.block_on(
|
||||
self.inner.receive_sync_changes(matrix_sdk_crypto::EncryptionSyncChanges {
|
||||
to_device_events: to_device.events,
|
||||
changed_devices: &device_changes,
|
||||
one_time_keys_counts: &key_counts,
|
||||
unused_fallback_keys: unused_fallback_keys.as_deref(),
|
||||
next_batch_token: Some(next_batch_token),
|
||||
}),
|
||||
)?;
|
||||
let (to_device_events, room_key_infos) =
|
||||
self.runtime.block_on(self.inner.receive_sync_changes(
|
||||
matrix_sdk_crypto::EncryptionSyncChanges {
|
||||
to_device_events: to_device.events,
|
||||
changed_devices: &device_changes,
|
||||
one_time_keys_counts: &key_counts,
|
||||
unused_fallback_keys: unused_fallback_keys.as_deref(),
|
||||
next_batch_token: Some(next_batch_token),
|
||||
},
|
||||
decryption_settings,
|
||||
))?;
|
||||
|
||||
let to_device_events = to_device_events
|
||||
.into_iter()
|
||||
@@ -829,6 +833,7 @@ impl OlmMachine {
|
||||
device_id: String,
|
||||
event_type: String,
|
||||
content: String,
|
||||
share_strategy: CollectStrategy,
|
||||
) -> Result<Option<Request>, CryptoStoreError> {
|
||||
let user_id = parse_user_id(&user_id)?;
|
||||
let device_id = device_id.as_str().into();
|
||||
@@ -837,8 +842,11 @@ impl OlmMachine {
|
||||
let device = self.runtime.block_on(self.inner.get_device(&user_id, device_id, None))?;
|
||||
|
||||
if let Some(device) = device {
|
||||
let encrypted_content =
|
||||
self.runtime.block_on(device.encrypt_event_raw(&event_type, &content))?;
|
||||
let encrypted_content = self.runtime.block_on(device.encrypt_event_raw(
|
||||
&event_type,
|
||||
&content,
|
||||
share_strategy,
|
||||
))?;
|
||||
|
||||
let request = ToDeviceRequest::new(
|
||||
user_id.as_ref(),
|
||||
@@ -894,7 +902,7 @@ impl OlmMachine {
|
||||
))?;
|
||||
|
||||
if handle_verification_events {
|
||||
if let Ok(e) = decrypted.event.deserialize() {
|
||||
if let Ok(AnyTimelineEvent::MessageLike(e)) = decrypted.event.deserialize() {
|
||||
match &e {
|
||||
AnyMessageLikeEvent::RoomMessage(MessageLikeEvent::Original(
|
||||
original_event,
|
||||
|
||||
@@ -27,7 +27,7 @@ use ruma::{
|
||||
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
|
||||
},
|
||||
assign,
|
||||
events::EventContent,
|
||||
events::MessageLikeEventContent,
|
||||
OwnedTransactionId, UserId,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -6,6 +6,88 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Unreleased] - ReleaseDate
|
||||
|
||||
## [0.14.0] - 2025-09-04
|
||||
|
||||
### Features:
|
||||
|
||||
- Add `LowPriority` and `NonLowPriority` variants to `RoomListEntriesDynamicFilterKind` for filtering
|
||||
rooms based on their low priority status. These filters allow clients to show only low priority rooms
|
||||
or exclude low priority rooms from the room list.
|
||||
([#5508](https://github.com/matrix-org/matrix-rust-sdk/pull/5508))
|
||||
- Add `room_version` and `privileged_creators_role` to `RoomInfo` ([#5449](https://github.com/matrix-org/matrix-rust-sdk/pull/5449)).
|
||||
- The [`unstable-hydra`] feature has been enabled, which enables room v12 changes in the SDK.
|
||||
([#5450](https://github.com/matrix-org/matrix-rust-sdk/pull/5450)).
|
||||
- Add experimental support for
|
||||
[MSC4306](https://github.com/matrix-org/matrix-spec-proposals/pull/4306), with the
|
||||
`Room::fetch_thread_subscription()` and `Room::set_thread_subscription()` methods.
|
||||
([#5442](https://github.com/matrix-org/matrix-rust-sdk/pull/5442))
|
||||
- [**breaking**] [`GalleryUploadParameters::reply`] and [`UploadParameters::reply`] have been both
|
||||
replaced with a new optional `in_reply_to` field, that's a string which will be parsed into an
|
||||
`OwnedEventId` when sending the event. The thread relationship will be automatically filled in,
|
||||
based on the timeline focus.
|
||||
([5427](https://github.com/matrix-org/matrix-rust-sdk/pull/5427))
|
||||
- [**breaking**] [`Timeline::send_reply()`] now automatically fills in the thread relationship,
|
||||
based on the timeline focus. As a result, it only takes an `OwnedEventId` parameter, instead of
|
||||
the `Reply` type. The proper way to start a thread is now thus to create a threaded-focused
|
||||
timeline, and then use `Timeline::send()`.
|
||||
([5427](https://github.com/matrix-org/matrix-rust-sdk/pull/5427))
|
||||
- Add `HomeserverLoginDetails::supports_sso_login` for legacy SSO support information.
|
||||
This is primarily for Element X to give a dedicated error message in case
|
||||
it connects a homeserver with only this method available.
|
||||
([#5222](https://github.com/matrix-org/matrix-rust-sdk/pull/5222))
|
||||
|
||||
### Breaking changes:
|
||||
|
||||
- The timeline will now always use the send queue to upload medias, so the
|
||||
`UploadParameters::use_send_queue` bool has been removed. Make sure to listen to the send queue's
|
||||
error updates, and to handle send queue restarts.
|
||||
([#5525](https://github.com/matrix-org/matrix-rust-sdk/pull/5525))
|
||||
- Support for the legacy media upload progress has been disabled. Media upload progress is
|
||||
available through the send queue, and can be enabled thanks to
|
||||
`Client::enable_send_queue_upload_progress()`.
|
||||
([#5525](https://github.com/matrix-org/matrix-rust-sdk/pull/5525))
|
||||
- `TimelineDiff` is now exported as a true `uniffi::Enum` instead of the weird `uniffi::Object` hybrid. This matches
|
||||
both `RoomDirectorySearchEntryUpdate` and `RoomListEntriesUpdate` and can be used in the same way.
|
||||
([#5474](https://github.com/matrix-org/matrix-rust-sdk/pull/5474))
|
||||
- The `creator` field of `RoomInfo` has been renamed to `creators` and can now contain a list of
|
||||
user IDs, to reflect that a room can now have several creators, as introduced in room version 12.
|
||||
([#5436](https://github.com/matrix-org/matrix-rust-sdk/pull/5436))
|
||||
- The `PowerLevel` type was introduced to represent power levels instead of `i64` to differentiate
|
||||
the infinite power level of creators, as introduced in room version 12. It is used in
|
||||
`suggested_role_for_power_level`, `suggested_power_level_for_role` and `RoomMember`.
|
||||
([#5436](https://github.com/matrix-org/matrix-rust-sdk/pull/5436))
|
||||
- `Client::get_url` now returns a `Vec<u8>` instead of a `String`. It also throws an error when the
|
||||
response isn't status code 200 OK, instead of providing the error in the response body.
|
||||
([#5438](https://github.com/matrix-org/matrix-rust-sdk/pull/5438))
|
||||
- `RoomPreview::info()` doesn't return a result anymore. All unknown join rules are handled in the
|
||||
`JoinRule::Custom` variant.
|
||||
([#5337](https://github.com/matrix-org/matrix-rust-sdk/pull/5337))
|
||||
- The `reason` argument of `Room::report_room` is now required, do to a clarification in the spec.
|
||||
([#5337](https://github.com/matrix-org/matrix-rust-sdk/pull/5337))
|
||||
- `PublicRoomJoinRule` has more variants, supporting all the known values from the spec.
|
||||
([#5337](https://github.com/matrix-org/matrix-rust-sdk/pull/5337))
|
||||
- The fields of `MediaPreviewConfig` are both optional, allowing to use the type for room account
|
||||
data as well as global account data.
|
||||
([#5337](https://github.com/matrix-org/matrix-rust-sdk/pull/5337))
|
||||
- The `event_id` field of `PredecessorRoom` was removed, due to its removal in the Matrix
|
||||
specification with MSC4291.
|
||||
([#5419](https://github.com/matrix-org/matrix-rust-sdk/pull/5419))
|
||||
- `Client::url_for_oidc` now allows requesting additional scopes for the OAuth2 authorization code grant.
|
||||
([#5395](https://github.com/matrix-org/matrix-rust-sdk/pull/5395))
|
||||
- `Client::url_for_oidc` now allows passing an optional existing device id from a previous login call.
|
||||
([#5394](https://github.com/matrix-org/matrix-rust-sdk/pull/5394))
|
||||
- `ClientBuilder::build_with_qr_code` has been removed. Instead, the Client should be built by passing
|
||||
`QrCodeData::server_name` to `ClientBuilder::server_name_or_homeserver_url`, after which QR login can be performed by
|
||||
calling `Client::login_with_qr_code`. ([#5388](https://github.com/matrix-org/matrix-rust-sdk/pull/5388))
|
||||
- The MSRV has been bumped to Rust 1.88.
|
||||
([#5431](https://github.com/matrix-org/matrix-rust-sdk/pull/5431))
|
||||
- `Room::send_call_notification` and `Room::send_call_notification_if_needed` have been removed, since the event type they send is outdated, and `Client` is not actually supposed to be able to join MatrixRTC sessions (yet). In practice, users of these methods probably already rely on another MatrixRTC implementation to participate in sessions, and such an implementation should be capable of sending notifications itself.
|
||||
- The `GalleryItemInfo` variants now take an `UploadSource` rather than a `String` path to enable uploading
|
||||
from bytes directly.
|
||||
([#5529](https://github.com/matrix-org/matrix-rust-sdk/pull/5529))
|
||||
- Media and gallery uploads now use `UploadSource` to specify the thumbnail.
|
||||
([#5530](https://github.com/matrix-org/matrix-rust-sdk/pull/5530))
|
||||
|
||||
## [0.13.0] - 2025-07-10
|
||||
|
||||
### Features
|
||||
@@ -72,7 +154,8 @@ Additions:
|
||||
we can automatically update the UI.
|
||||
- `Client::get_max_media_upload_size` to get the max size of a request sent to the homeserver so we can tweak our media
|
||||
uploads by compressing/transcoding the media.
|
||||
- Add `ClientBuilder::enable_share_history_on_invite` to enable experimental support for sharing encrypted room history on invite, per [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268).
|
||||
- Add `ClientBuilder::enable_share_history_on_invite` to enable experimental support for sharing encrypted room history
|
||||
on invite, per [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268).
|
||||
([#5141](https://github.com/matrix-org/matrix-rust-sdk/pull/5141))
|
||||
- Support for adding a Sentry layer to the FFI bindings has been added. Only `tracing` statements with
|
||||
the field `sentry=true` will be forwarded to Sentry, in addition to default Sentry filters.
|
||||
@@ -165,7 +248,8 @@ Breaking changes:
|
||||
- The `dynamic_registrations_file` field of `OidcConfiguration` was removed.
|
||||
Clients are supposed to re-register with the homeserver for every login.
|
||||
|
||||
- `RoomPreview::own_membership_details` is now `RoomPreview::member_with_sender_info`, takes any user id and returns an `Option<RoomMemberWithSenderInfo>`.
|
||||
- `RoomPreview::own_membership_details` is now `RoomPreview::member_with_sender_info`, takes any user id and returns an
|
||||
`Option<RoomMemberWithSenderInfo>`.
|
||||
|
||||
Additions:
|
||||
|
||||
@@ -180,9 +264,11 @@ Additions:
|
||||
- Add `Timeline::send_thread_reply` for clients that need to start threads
|
||||
themselves.
|
||||
([4819](https://github.com/matrix-org/matrix-rust-sdk/pull/4819))
|
||||
- Add `ClientBuilder::session_pool_max_size`, `::session_cache_size` and `::session_journal_size_limit` to control the stores configuration, especially their memory consumption
|
||||
- Add `ClientBuilder::session_pool_max_size`, `::session_cache_size` and `::session_journal_size_limit` to control the
|
||||
stores configuration, especially their memory consumption
|
||||
([#4870](https://github.com/matrix-org/matrix-rust-sdk/pull/4870/))
|
||||
- Add `ClientBuilder::system_is_memory_constrained` to indicate that the system
|
||||
has less memory available than the current standard
|
||||
([#4894](https://github.com/matrix-org/matrix-rust-sdk/pull/4894))
|
||||
- Add `Room::member_with_sender_info` to get both a room member's info and for the user who sent the `m.room.member` event the `RoomMember` is based on.
|
||||
- Add `Room::member_with_sender_info` to get both a room member's info and for the user who sent the `m.room.member`
|
||||
event the `RoomMember` is based on.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "matrix-sdk-ffi"
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
keywords = ["matrix", "chat", "messaging", "ffi"]
|
||||
@@ -38,7 +38,6 @@ sentry = ["dep:sentry", "dep:sentry-tracing"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
as_variant.workspace = true
|
||||
extension-trait = "1.0.1"
|
||||
eyeball-im.workspace = true
|
||||
futures-util.workspace = true
|
||||
@@ -58,10 +57,10 @@ matrix-sdk-ffi-macros.workspace = true
|
||||
matrix-sdk-ui = { workspace = true, features = ["uniffi"] }
|
||||
mime = "0.3.16"
|
||||
once_cell.workspace = true
|
||||
ruma = { workspace = true, features = ["html", "unstable-unspecified", "unstable-msc3488", "compat-unset-avatar", "unstable-msc3245-v1-compat", "unstable-msc4278"] }
|
||||
ruma = { workspace = true, features = ["html", "unstable-msc3488", "compat-unset-avatar", "unstable-msc3245-v1-compat", "unstable-msc4278", "unstable-hydra"] }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
sentry = { version = "0.36.0", optional = true, default-features = false, features = [
|
||||
sentry = { workspace = true, optional = true, default-features = false, features = [
|
||||
# Most default features enabled otherwise.
|
||||
"backtrace",
|
||||
"contexts",
|
||||
@@ -69,15 +68,16 @@ sentry = { version = "0.36.0", optional = true, default-features = false, featur
|
||||
"reqwest",
|
||||
"sentry-debug-images",
|
||||
] }
|
||||
sentry-tracing = { version = "0.36.0", optional = true }
|
||||
sentry-tracing = { workspace = true, optional = true }
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-appender = { version = "0.2.2" }
|
||||
tracing-appender.workspace = true
|
||||
tracing-core.workspace = true
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
url.workspace = true
|
||||
uuid = { version = "1.4.1", features = ["v4"] }
|
||||
zeroize.workspace = true
|
||||
oauth2.workspace = true
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
@@ -92,9 +92,12 @@ uniffi = { workspace = true, features = ["tokio"] }
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
paranoid-android = "0.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
similar-asserts.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
uniffi = { workspace = true, features = ["build"] }
|
||||
vergen = { version = "8.1.3", features = ["build", "git", "gitcl"] }
|
||||
vergen-gitcl = { workspace = true, features = ["build"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::{
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use vergen::EmitBuilder;
|
||||
use vergen_gitcl::{Emitter, GitclBuilder};
|
||||
|
||||
/// Adds a temporary workaround for an issue with the Rust compiler and Android
|
||||
/// in x86_64 devices: https://github.com/rust-lang/rust/issues/109717.
|
||||
@@ -59,6 +59,9 @@ fn get_clang_major_version(clang_path: &Path) -> String {
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
setup_x86_64_android_workaround();
|
||||
uniffi::generate_scaffolding("./src/api.udl").expect("Building the UDL file failed");
|
||||
EmitBuilder::builder().git_sha(true).emit()?;
|
||||
|
||||
let git_config = GitclBuilder::default().sha(true).build()?;
|
||||
Emitter::default().add_instructions(&git_config)?.emit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ pub struct HomeserverLoginDetails {
|
||||
pub(crate) sliding_sync_version: SlidingSyncVersion,
|
||||
pub(crate) supports_oidc_login: bool,
|
||||
pub(crate) supported_oidc_prompts: Vec<OidcPrompt>,
|
||||
pub(crate) supports_sso_login: bool,
|
||||
pub(crate) supports_password_login: bool,
|
||||
}
|
||||
|
||||
@@ -43,6 +44,11 @@ impl HomeserverLoginDetails {
|
||||
self.supports_oidc_login
|
||||
}
|
||||
|
||||
/// Whether the current homeserver supports login using legacy SSO.
|
||||
pub fn supports_sso_login(&self) -> bool {
|
||||
self.supports_sso_login
|
||||
}
|
||||
|
||||
/// The prompts advertised by the authentication issuer for use in the login
|
||||
/// URL.
|
||||
pub fn supported_oidc_prompts(&self) -> Vec<OidcPrompt> {
|
||||
|
||||
@@ -39,7 +39,7 @@ use matrix_sdk::{
|
||||
},
|
||||
sliding_sync::Version as SdkSlidingSyncVersion,
|
||||
store::RoomLoadSettings as SdkRoomLoadSettings,
|
||||
AuthApi, AuthSession, Client as MatrixClient, SessionChange, SessionTokens,
|
||||
Account, AuthApi, AuthSession, Client as MatrixClient, SessionChange, SessionTokens,
|
||||
STATE_STORE_DATABASE_NAME,
|
||||
};
|
||||
use matrix_sdk_common::{stream::StreamExt, SendOutsideWasm, SyncOutsideWasm};
|
||||
@@ -48,11 +48,18 @@ use matrix_sdk_ui::{
|
||||
NotificationClient as MatrixNotificationClient,
|
||||
NotificationProcessSetup as MatrixNotificationProcessSetup,
|
||||
},
|
||||
spaces::SpaceService as UISpaceService,
|
||||
unable_to_decrypt_hook::UtdHookManager,
|
||||
};
|
||||
use mime::Mime;
|
||||
use oauth2::Scope;
|
||||
use ruma::{
|
||||
api::client::{alias::get_alias, error::ErrorKind, uiaa::UserIdentifier},
|
||||
api::client::{
|
||||
alias::get_alias,
|
||||
error::ErrorKind,
|
||||
profile::{AvatarUrl, DisplayName},
|
||||
uiaa::UserIdentifier,
|
||||
},
|
||||
events::{
|
||||
direct::DirectEventContent,
|
||||
fully_read::FullyReadEventContent,
|
||||
@@ -74,11 +81,11 @@ use ruma::{
|
||||
},
|
||||
tag::TagEventContent,
|
||||
GlobalAccountDataEvent as RumaGlobalAccountDataEvent,
|
||||
GlobalAccountDataEventType as RumaGlobalAccountDataEventType,
|
||||
RoomAccountDataEvent as RumaRoomAccountDataEvent,
|
||||
},
|
||||
push::{HttpPusherData as RumaHttpPusherData, PushFormat as RumaPushFormat},
|
||||
OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName,
|
||||
room_version_rules::AuthorizationRules,
|
||||
OwnedDeviceId, OwnedServerName, RoomAliasId, RoomOrAliasId, ServerName,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
@@ -96,6 +103,7 @@ use crate::{
|
||||
encryption::Encryption,
|
||||
notification::NotificationClient,
|
||||
notification_settings::NotificationSettings,
|
||||
qr_code::{HumanQrLoginError, QrCodeData, QrLoginProgressListener},
|
||||
room::{RoomHistoryVisibility, RoomInfoListener},
|
||||
room_directory_search::RoomDirectorySearch,
|
||||
room_preview::RoomPreview,
|
||||
@@ -104,6 +112,7 @@ use crate::{
|
||||
MediaPreviews, MediaSource, RoomAccountDataEvent, RoomAccountDataEventType,
|
||||
},
|
||||
runtime::get_runtime_handle,
|
||||
spaces::SpaceService,
|
||||
sync_service::{SyncService, SyncServiceBuilder},
|
||||
task_handle::TaskHandle,
|
||||
utd::{UnableToDecryptDelegate, UtdHook},
|
||||
@@ -339,7 +348,24 @@ impl Client {
|
||||
}
|
||||
};
|
||||
|
||||
let supports_password_login = self.supports_password_login().await.ok().unwrap_or(false);
|
||||
let login_types = self.inner.matrix_auth().get_login_types().await.ok();
|
||||
let supports_password_login = login_types
|
||||
.as_ref()
|
||||
.map(|login_types| {
|
||||
login_types.flows.iter().any(|login_type| {
|
||||
matches!(login_type, get_login_types::v3::LoginType::Password(_))
|
||||
})
|
||||
})
|
||||
.unwrap_or(false);
|
||||
let supports_sso_login = login_types
|
||||
.as_ref()
|
||||
.map(|login_types| {
|
||||
login_types
|
||||
.flows
|
||||
.iter()
|
||||
.any(|login_type| matches!(login_type, get_login_types::v3::LoginType::Sso(_)))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
let sliding_sync_version = self.sliding_sync_version();
|
||||
|
||||
Arc::new(HomeserverLoginDetails {
|
||||
@@ -347,6 +373,7 @@ impl Client {
|
||||
sliding_sync_version,
|
||||
supports_oidc_login,
|
||||
supported_oidc_prompts,
|
||||
supports_sso_login,
|
||||
supports_password_login,
|
||||
})
|
||||
}
|
||||
@@ -456,16 +483,39 @@ impl Client {
|
||||
/// However, it should be noted that when providing a user ID as a hint
|
||||
/// for MAS (with no upstream provider), then the format to use is defined
|
||||
/// by [MSC4198]: https://github.com/matrix-org/matrix-spec-proposals/pull/4198
|
||||
///
|
||||
/// * `device_id` - The unique ID that will be associated with the session.
|
||||
/// If not set, a random one will be generated. It can be an existing
|
||||
/// device ID from a previous login call. Note that this should be done
|
||||
/// only if the client also holds the corresponding encryption keys.
|
||||
///
|
||||
/// * `additional_scopes` - Additional scopes to request from the
|
||||
/// authorization server, e.g. "urn:matrix:client:com.example.msc9999.foo".
|
||||
/// The scopes for API access and the device ID according to the
|
||||
/// [specification](https://spec.matrix.org/v1.15/client-server-api/#allocated-scope-tokens)
|
||||
/// are always requested.
|
||||
pub async fn url_for_oidc(
|
||||
&self,
|
||||
oidc_configuration: &OidcConfiguration,
|
||||
prompt: Option<OidcPrompt>,
|
||||
login_hint: Option<String>,
|
||||
device_id: Option<String>,
|
||||
additional_scopes: Option<Vec<String>>,
|
||||
) -> Result<Arc<OAuthAuthorizationData>, OidcError> {
|
||||
let registration_data = oidc_configuration.registration_data()?;
|
||||
let redirect_uri = oidc_configuration.redirect_uri()?;
|
||||
|
||||
let mut url_builder = self.inner.oauth().login(redirect_uri, None, Some(registration_data));
|
||||
let device_id = device_id.map(OwnedDeviceId::from);
|
||||
|
||||
let additional_scopes =
|
||||
additional_scopes.map(|scopes| scopes.into_iter().map(Scope::new).collect::<Vec<_>>());
|
||||
|
||||
let mut url_builder = self.inner.oauth().login(
|
||||
redirect_uri,
|
||||
device_id,
|
||||
Some(registration_data),
|
||||
additional_scopes,
|
||||
);
|
||||
|
||||
if let Some(prompt) = prompt {
|
||||
url_builder = url_builder.prompt(vec![prompt.into()]);
|
||||
@@ -494,6 +544,45 @@ impl Client {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Log in using the provided [`QrCodeData`]. The `Client` must be built
|
||||
/// by providing [`QrCodeData::server_name`] as the server name for this
|
||||
/// login to succeed.
|
||||
///
|
||||
/// This method uses the login mechanism described in [MSC4108]. As such
|
||||
/// this method requires OAuth 2.0 support as well as sliding sync support.
|
||||
///
|
||||
/// The usage of the progress_listener is required to transfer the
|
||||
/// [`CheckCode`] to the existing client.
|
||||
///
|
||||
/// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
|
||||
pub async fn login_with_qr_code(
|
||||
self: Arc<Self>,
|
||||
qr_code_data: &QrCodeData,
|
||||
oidc_configuration: &OidcConfiguration,
|
||||
progress_listener: Box<dyn QrLoginProgressListener>,
|
||||
) -> Result<(), HumanQrLoginError> {
|
||||
let registration_data = oidc_configuration
|
||||
.registration_data()
|
||||
.map_err(|_| HumanQrLoginError::OidcMetadataInvalid)?;
|
||||
|
||||
let oauth = self.inner.oauth();
|
||||
let login = oauth.login_with_qr_code(&qr_code_data.inner, Some(®istration_data));
|
||||
|
||||
let mut progress = login.subscribe_to_progress();
|
||||
|
||||
// We create this task, which will get cancelled once it's dropped, just in case
|
||||
// the progress stream doesn't end.
|
||||
let _progress_task = TaskHandle::new(get_runtime_handle().spawn(async move {
|
||||
while let Some(state) = progress.next().await {
|
||||
progress_listener.on_update(state.into());
|
||||
}
|
||||
}));
|
||||
|
||||
login.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Restores the client from a `Session`.
|
||||
///
|
||||
/// It reloads the entire set of rooms from the previous session.
|
||||
@@ -539,6 +628,12 @@ impl Client {
|
||||
self.inner.send_queue().set_enabled(enable).await;
|
||||
}
|
||||
|
||||
/// Enables or disables progress reporting for media uploads in the send
|
||||
/// queue.
|
||||
pub fn enable_send_queue_upload_progress(&self, enable: bool) {
|
||||
self.inner.send_queue().enable_upload_progress(enable);
|
||||
}
|
||||
|
||||
/// Subscribe to the global enablement status of the send queue, at the
|
||||
/// client-wide level.
|
||||
///
|
||||
@@ -694,11 +789,24 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows generic GET requests to be made through the SDKs internal HTTP
|
||||
/// client
|
||||
pub async fn get_url(&self, url: String) -> Result<String, ClientError> {
|
||||
let http_client = self.inner.http_client();
|
||||
Ok(http_client.get(url).send().await?.text().await?)
|
||||
/// Allows generic GET requests to be made through the SDK's internal HTTP
|
||||
/// client. This is useful when the caller's native HTTP client wouldn't
|
||||
/// have the same configuration (such as certificates, proxies, etc.) This
|
||||
/// method returns the raw bytes of the response, so that any kind of
|
||||
/// resource can be fetched including images, files, etc.
|
||||
///
|
||||
/// Note: When an HTTP error occurs, the error response can be found in the
|
||||
/// `ClientError::Generic`'s `details` field.
|
||||
pub async fn get_url(&self, url: String) -> Result<Vec<u8>, ClientError> {
|
||||
let response = self.inner.http_client().get(url).send().await?;
|
||||
if response.status().is_success() {
|
||||
Ok(response.bytes().await?.into())
|
||||
} else {
|
||||
Err(ClientError::Generic {
|
||||
msg: response.status().to_string(),
|
||||
details: response.text().await.ok(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Empty the server version and unstable features cache.
|
||||
@@ -744,18 +852,6 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Whether or not the client's homeserver supports the password login flow.
|
||||
pub(crate) async fn supports_password_login(&self) -> anyhow::Result<bool> {
|
||||
let login_types = self.inner.matrix_auth().get_login_types().await?;
|
||||
let supports_password = login_types
|
||||
.flows
|
||||
.iter()
|
||||
.any(|login_type| matches!(login_type, get_login_types::v3::LoginType::Password(_)));
|
||||
Ok(supports_password)
|
||||
}
|
||||
}
|
||||
|
||||
#[matrix_sdk_ffi_macros::export]
|
||||
impl Client {
|
||||
/// The sliding sync version.
|
||||
@@ -1144,15 +1240,8 @@ impl Client {
|
||||
}
|
||||
|
||||
pub async fn get_profile(&self, user_id: String) -> Result<UserProfile, ClientError> {
|
||||
let owned_user_id = UserId::parse(user_id.clone())?;
|
||||
|
||||
let response = self.inner.account().fetch_user_profile_of(&owned_user_id).await?;
|
||||
|
||||
Ok(UserProfile {
|
||||
user_id,
|
||||
display_name: response.displayname.clone(),
|
||||
avatar_url: response.avatar_url.as_ref().map(|url| url.to_string()),
|
||||
})
|
||||
let user_id = <&UserId>::try_from(user_id.as_str())?;
|
||||
UserProfile::fetch(&self.inner.account(), user_id).await
|
||||
}
|
||||
|
||||
pub async fn notification_client(
|
||||
@@ -1170,6 +1259,11 @@ impl Client {
|
||||
SyncServiceBuilder::new((*self.inner).clone(), self.utd_hook_manager.get().cloned())
|
||||
}
|
||||
|
||||
pub fn space_service(&self) -> Arc<SpaceService> {
|
||||
let inner = UISpaceService::new((*self.inner).clone());
|
||||
Arc::new(SpaceService::new(inner))
|
||||
}
|
||||
|
||||
pub async fn get_notification_settings(&self) -> Arc<NotificationSettings> {
|
||||
let inner = self.inner.notification_settings().await;
|
||||
|
||||
@@ -1183,13 +1277,10 @@ impl Client {
|
||||
// Ignored users
|
||||
|
||||
pub async fn ignored_users(&self) -> Result<Vec<String>, ClientError> {
|
||||
if let Some(raw_content) = self
|
||||
.inner
|
||||
.account()
|
||||
.fetch_account_data(RumaGlobalAccountDataEventType::IgnoredUserList)
|
||||
.await?
|
||||
if let Some(raw_content) =
|
||||
self.inner.account().fetch_account_data_static::<IgnoredUserListEventContent>().await?
|
||||
{
|
||||
let content = raw_content.deserialize_as::<IgnoredUserListEventContent>()?;
|
||||
let content = raw_content.deserialize()?;
|
||||
let user_ids: Vec<String> =
|
||||
content.ignored_users.keys().map(|id| id.to_string()).collect();
|
||||
|
||||
@@ -1532,6 +1623,14 @@ impl Client {
|
||||
.any(|focus| matches!(focus, RtcFocusInfo::LiveKit(_))))
|
||||
}
|
||||
|
||||
/// Get server vendor information from the federation API.
|
||||
///
|
||||
/// This method retrieves information about the server's name and version
|
||||
/// by calling the `/_matrix/federation/v1/version` endpoint.
|
||||
pub async fn server_vendor_info(&self) -> Result<matrix_sdk::ServerVendorInfo, ClientError> {
|
||||
Ok(self.inner.server_vendor_info(None).await?)
|
||||
}
|
||||
|
||||
/// Subscribe to changes in the media preview configuration.
|
||||
pub async fn subscribe_to_media_preview_config(
|
||||
&self,
|
||||
@@ -1565,7 +1664,7 @@ impl Client {
|
||||
) -> Result<Option<MediaPreviews>, ClientError> {
|
||||
let configuration = self.inner.account().get_media_preview_config_event_content().await?;
|
||||
match configuration {
|
||||
Some(configuration) => Ok(Some(configuration.media_previews.into())),
|
||||
Some(configuration) => Ok(configuration.media_previews.map(Into::into)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
@@ -1586,7 +1685,7 @@ impl Client {
|
||||
) -> Result<Option<InviteAvatars>, ClientError> {
|
||||
let configuration = self.inner.account().get_media_preview_config_event_content().await?;
|
||||
match configuration {
|
||||
Some(configuration) => Ok(Some(configuration.invite_avatars.into())),
|
||||
Some(configuration) => Ok(configuration.invite_avatars.map(Into::into)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
@@ -1718,6 +1817,18 @@ pub struct UserProfile {
|
||||
pub avatar_url: Option<String>,
|
||||
}
|
||||
|
||||
impl UserProfile {
|
||||
/// Fetch the profile for the given user ID, using the given [`Account`]
|
||||
/// API.
|
||||
pub(crate) async fn fetch(account: &Account, user_id: &UserId) -> Result<Self, ClientError> {
|
||||
let response = account.fetch_user_profile_of(user_id).await?;
|
||||
let display_name = response.get_static::<DisplayName>()?;
|
||||
let avatar_url = response.get_static::<AvatarUrl>()?.map(|url| url.to_string());
|
||||
|
||||
Ok(UserProfile { user_id: user_id.to_string(), display_name, avatar_url })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&search_users::v3::User> for UserProfile {
|
||||
fn from(value: &search_users::v3::User) -> Self {
|
||||
UserProfile {
|
||||
@@ -1832,7 +1943,7 @@ pub struct PowerLevels {
|
||||
|
||||
impl From<PowerLevels> for RoomPowerLevelsEventContent {
|
||||
fn from(value: PowerLevels) -> Self {
|
||||
let mut power_levels = RoomPowerLevelsEventContent::new();
|
||||
let mut power_levels = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
|
||||
|
||||
if let Some(users_default) = value.users_default {
|
||||
power_levels.users_default = users_default.into();
|
||||
@@ -2402,9 +2513,7 @@ impl TryFrom<AllowRule> for RumaAllowRule {
|
||||
match value {
|
||||
AllowRule::RoomMembership { room_id } => {
|
||||
let room_id = RoomId::parse(room_id)?;
|
||||
Ok(Self::RoomMembership(ruma::events::room::join_rules::RoomMembership::new(
|
||||
room_id,
|
||||
)))
|
||||
Ok(Self::RoomMembership(ruma::room::RoomMembership::new(room_id)))
|
||||
}
|
||||
AllowRule::Custom { json } => Ok(Self::_Custom(Box::new(serde_json::from_str(&json)?))),
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
use std::{fs, num::NonZeroUsize, path::Path, sync::Arc, time::Duration};
|
||||
|
||||
use futures_util::StreamExt;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use matrix_sdk::reqwest::Certificate;
|
||||
use matrix_sdk::{
|
||||
crypto::{
|
||||
types::qr_login::QrCodeModeData, CollectStrategy, DecryptionSettings, TrustRequirement,
|
||||
},
|
||||
crypto::{CollectStrategy, DecryptionSettings, TrustRequirement},
|
||||
encryption::{BackupDownloadStrategy, EncryptionSettings},
|
||||
event_cache::EventCacheError,
|
||||
ruma::{ServerName, UserId},
|
||||
@@ -22,15 +19,7 @@ use tracing::{debug, error};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use super::client::Client;
|
||||
use crate::{
|
||||
authentication::OidcConfiguration,
|
||||
client::ClientSessionDelegate,
|
||||
error::ClientError,
|
||||
helpers::unwrap_or_clone_arc,
|
||||
qr_code::{HumanQrLoginError, QrCodeData, QrLoginProgressListener},
|
||||
runtime::get_runtime_handle,
|
||||
task_handle::TaskHandle,
|
||||
};
|
||||
use crate::{client::ClientSessionDelegate, error::ClientError, helpers::unwrap_or_clone_arc};
|
||||
|
||||
/// A list of bytes containing a certificate in DER or PEM form.
|
||||
pub type CertificateBytes = Vec<u8>;
|
||||
@@ -141,9 +130,14 @@ pub struct ClientBuilder {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
additional_root_certificates: Vec<Vec<u8>>,
|
||||
|
||||
threads_enabled: bool,
|
||||
threading_support: ThreadingSupport,
|
||||
}
|
||||
|
||||
/// The timeout applies to each read operation, and resets after a successful
|
||||
/// read. This is more appropriate for detecting stalled connections when the
|
||||
/// size isn’t known beforehand.
|
||||
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
#[matrix_sdk_ffi_macros::export]
|
||||
impl ClientBuilder {
|
||||
#[uniffi::constructor]
|
||||
@@ -179,7 +173,7 @@ impl ClientBuilder {
|
||||
},
|
||||
enable_share_history_on_invite: false,
|
||||
request_config: Default::default(),
|
||||
threads_enabled: false,
|
||||
threading_support: ThreadingSupport::Disabled,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -393,9 +387,20 @@ impl ClientBuilder {
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
pub fn threads_enabled(self: Arc<Self>, enabled: bool) -> Arc<Self> {
|
||||
/// Whether the client should support threads client-side or not, and enable
|
||||
/// experimental support for MSC4306 (threads subscriptions) or not.
|
||||
pub fn threads_enabled(
|
||||
self: Arc<Self>,
|
||||
enabled: bool,
|
||||
thread_subscriptions: bool,
|
||||
) -> Arc<Self> {
|
||||
let mut builder = unwrap_or_clone_arc(self);
|
||||
builder.threads_enabled = enabled;
|
||||
let support = if enabled {
|
||||
ThreadingSupport::Enabled { with_subscriptions: thread_subscriptions }
|
||||
} else {
|
||||
ThreadingSupport::Disabled
|
||||
};
|
||||
builder.threading_support = support;
|
||||
Arc::new(builder)
|
||||
}
|
||||
|
||||
@@ -550,6 +555,7 @@ impl ClientBuilder {
|
||||
if let Some(timeout) = config.timeout {
|
||||
updated_config = updated_config.timeout(Duration::from_millis(timeout));
|
||||
}
|
||||
updated_config = updated_config.read_timeout(DEFAULT_READ_TIMEOUT);
|
||||
if let Some(max_concurrent_requests) = config.max_concurrent_requests {
|
||||
if max_concurrent_requests > 0 {
|
||||
updated_config = updated_config.max_concurrent_requests(NonZeroUsize::new(
|
||||
@@ -564,14 +570,25 @@ impl ClientBuilder {
|
||||
inner_builder = inner_builder.request_config(updated_config);
|
||||
}
|
||||
|
||||
inner_builder = inner_builder.with_threading_support(if builder.threads_enabled {
|
||||
ThreadingSupport::Enabled
|
||||
} else {
|
||||
ThreadingSupport::Disabled
|
||||
});
|
||||
inner_builder = inner_builder.with_threading_support(builder.threading_support);
|
||||
|
||||
let sdk_client = inner_builder.build().await?;
|
||||
|
||||
// Disable retries for this request to prevent it from being retried
|
||||
// indefinitely
|
||||
let config = sdk_client.request_config().disable_retry();
|
||||
|
||||
// Log server version information at info level.
|
||||
if let Ok(server_info) = sdk_client.server_vendor_info(Some(config)).await {
|
||||
tracing::info!(
|
||||
server_name = %server_info.server_name,
|
||||
version = %server_info.version,
|
||||
"Connected to Matrix server"
|
||||
);
|
||||
} else {
|
||||
tracing::warn!("Could not retrieve server version information");
|
||||
}
|
||||
|
||||
Ok(Arc::new(
|
||||
Client::new(
|
||||
sdk_client,
|
||||
@@ -582,60 +599,6 @@ impl ClientBuilder {
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Finish the building of the client and attempt to log in using the
|
||||
/// provided [`QrCodeData`].
|
||||
///
|
||||
/// This method will build the client and immediately attempt to log the
|
||||
/// client in using the provided [`QrCodeData`] using the login
|
||||
/// mechanism described in [MSC4108]. As such this methods requires OAuth
|
||||
/// 2.0 support as well as sliding sync support.
|
||||
///
|
||||
/// The usage of the progress_listener is required to transfer the
|
||||
/// [`CheckCode`] to the existing client.
|
||||
///
|
||||
/// [MSC4108]: https://github.com/matrix-org/matrix-spec-proposals/pull/4108
|
||||
pub async fn build_with_qr_code(
|
||||
self: Arc<Self>,
|
||||
qr_code_data: &QrCodeData,
|
||||
oidc_configuration: &OidcConfiguration,
|
||||
progress_listener: Box<dyn QrLoginProgressListener>,
|
||||
) -> Result<Arc<Client>, HumanQrLoginError> {
|
||||
let QrCodeModeData::Reciprocate { server_name } = &qr_code_data.inner.mode_data else {
|
||||
return Err(HumanQrLoginError::OtherDeviceNotSignedIn);
|
||||
};
|
||||
|
||||
let builder = self.server_name_or_homeserver_url(server_name.to_owned());
|
||||
|
||||
let client = builder.build().await.map_err(|e| match e {
|
||||
ClientBuildError::SlidingSync(_) => HumanQrLoginError::SlidingSyncNotAvailable,
|
||||
_ => {
|
||||
error!("Couldn't build the client {e:?}");
|
||||
HumanQrLoginError::Unknown
|
||||
}
|
||||
})?;
|
||||
|
||||
let registration_data = oidc_configuration
|
||||
.registration_data()
|
||||
.map_err(|_| HumanQrLoginError::OidcMetadataInvalid)?;
|
||||
|
||||
let oauth = client.inner.oauth();
|
||||
let login = oauth.login_with_qr_code(&qr_code_data.inner, Some(®istration_data));
|
||||
|
||||
let mut progress = login.subscribe_to_progress();
|
||||
|
||||
// We create this task, which will get cancelled once it's dropped, just in case
|
||||
// the progress stream doesn't end.
|
||||
let _progress_task = TaskHandle::new(get_runtime_handle().spawn(async move {
|
||||
while let Some(state) = progress.next().await {
|
||||
progress_listener.on_update(state.into());
|
||||
}
|
||||
}));
|
||||
|
||||
login.await?;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
|
||||
@@ -25,6 +25,7 @@ mod room_preview;
|
||||
mod ruma;
|
||||
mod runtime;
|
||||
mod session_verification;
|
||||
mod spaces;
|
||||
mod sync_service;
|
||||
mod task_handle;
|
||||
mod timeline;
|
||||
|
||||
@@ -172,7 +172,7 @@ impl TryFrom<SdkPushCondition> for PushCondition {
|
||||
Self::RoomMemberCount { prefix: is.prefix.into(), count: is.count.into() }
|
||||
}
|
||||
SdkPushCondition::SenderNotificationPermission { key } => {
|
||||
Self::SenderNotificationPermission { key }
|
||||
Self::SenderNotificationPermission { key: key.to_string() }
|
||||
}
|
||||
SdkPushCondition::EventPropertyIs { key, value } => {
|
||||
Self::EventPropertyIs { key, value: value.into() }
|
||||
@@ -197,7 +197,7 @@ impl From<PushCondition> for SdkPushCondition {
|
||||
},
|
||||
},
|
||||
PushCondition::SenderNotificationPermission { key } => {
|
||||
Self::SenderNotificationPermission { key }
|
||||
Self::SenderNotificationPermission { key: key.into() }
|
||||
}
|
||||
PushCondition::EventPropertyIs { key, value } => {
|
||||
Self::EventPropertyIs { key, value: value.into() }
|
||||
|
||||
@@ -271,6 +271,7 @@ enum LogTarget {
|
||||
MatrixSdkBaseEventCache,
|
||||
MatrixSdkBaseSlidingSync,
|
||||
MatrixSdkBaseStoreAmbiguityMap,
|
||||
MatrixSdkBaseResponseProcessors,
|
||||
|
||||
// SDK common modules.
|
||||
MatrixSdkCommonStoreLocks,
|
||||
@@ -300,6 +301,7 @@ impl LogTarget {
|
||||
LogTarget::MatrixSdkBaseEventCache => "matrix_sdk_base::event_cache",
|
||||
LogTarget::MatrixSdkBaseSlidingSync => "matrix_sdk_base::sliding_sync",
|
||||
LogTarget::MatrixSdkBaseStoreAmbiguityMap => "matrix_sdk_base::store::ambiguity_map",
|
||||
LogTarget::MatrixSdkBaseResponseProcessors => "matrix_sdk_base::response_processors",
|
||||
LogTarget::MatrixSdkCommonStoreLocks => "matrix_sdk_common::store_locks",
|
||||
LogTarget::MatrixSdk => "matrix_sdk",
|
||||
LogTarget::MatrixSdkClient => "matrix_sdk::client",
|
||||
@@ -336,6 +338,7 @@ const DEFAULT_TARGET_LOG_LEVELS: &[(LogTarget, LogLevel)] = &[
|
||||
(LogTarget::MatrixSdkCommonStoreLocks, LogLevel::Warn),
|
||||
(LogTarget::MatrixSdkBaseStoreAmbiguityMap, LogLevel::Warn),
|
||||
(LogTarget::MatrixSdkUiNotificationClient, LogLevel::Info),
|
||||
(LogTarget::MatrixSdkBaseResponseProcessors, LogLevel::Debug),
|
||||
];
|
||||
|
||||
const IMMUTABLE_LOG_TARGETS: &[LogTarget] = &[
|
||||
@@ -358,6 +361,8 @@ pub enum TraceLogPacks {
|
||||
Timeline,
|
||||
/// Enables all the logs relevant to the notification client.
|
||||
NotificationClient,
|
||||
/// Enables all the logs relevant to sync profiling.
|
||||
SyncProfiling,
|
||||
}
|
||||
|
||||
impl TraceLogPacks {
|
||||
@@ -373,6 +378,12 @@ impl TraceLogPacks {
|
||||
TraceLogPacks::SendQueue => &[LogTarget::MatrixSdkSendQueue],
|
||||
TraceLogPacks::Timeline => &[LogTarget::MatrixSdkUiTimeline],
|
||||
TraceLogPacks::NotificationClient => &[LogTarget::MatrixSdkUiNotificationClient],
|
||||
TraceLogPacks::SyncProfiling => &[
|
||||
LogTarget::MatrixSdkSlidingSync,
|
||||
LogTarget::MatrixSdkBaseSlidingSync,
|
||||
LogTarget::MatrixSdkBaseResponseProcessors,
|
||||
LogTarget::MatrixSdkCrypto,
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -675,6 +686,8 @@ fn setup_lightweight_tokio_runtime() {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use similar_asserts::assert_eq;
|
||||
|
||||
use super::build_tracing_filter;
|
||||
use crate::platform::TraceLogPacks;
|
||||
|
||||
@@ -713,6 +726,7 @@ mod tests {
|
||||
matrix_sdk_common::store_locks=warn,
|
||||
matrix_sdk_base::store::ambiguity_map=warn,
|
||||
matrix_sdk_ui::notification_client=info,
|
||||
matrix_sdk_base::response_processors=debug,
|
||||
super_duper_app=error"#
|
||||
.split('\n')
|
||||
.map(|s| s.trim())
|
||||
@@ -756,6 +770,7 @@ mod tests {
|
||||
matrix_sdk_common::store_locks=warn,
|
||||
matrix_sdk_base::store::ambiguity_map=warn,
|
||||
matrix_sdk_ui::notification_client=trace,
|
||||
matrix_sdk_base::response_processors=trace,
|
||||
super_duper_app=trace,
|
||||
some_other_span=trace"#
|
||||
.split('\n')
|
||||
@@ -800,6 +815,7 @@ mod tests {
|
||||
matrix_sdk_common::store_locks=warn,
|
||||
matrix_sdk_base::store::ambiguity_map=warn,
|
||||
matrix_sdk_ui::notification_client=info,
|
||||
matrix_sdk_base::response_processors=debug,
|
||||
super_duper_app=info"#
|
||||
.split('\n')
|
||||
.map(|s| s.trim())
|
||||
|
||||
@@ -44,11 +44,11 @@ use crate::{
|
||||
live_location_share::{LastLocation, LiveLocationShare},
|
||||
room_member::{RoomMember, RoomMemberWithSenderInfo},
|
||||
room_preview::RoomPreview,
|
||||
ruma::{ImageInfo, LocationContent, Mentions, NotifyType},
|
||||
ruma::{ImageInfo, LocationContent},
|
||||
runtime::get_runtime_handle,
|
||||
timeline::{
|
||||
configuration::{TimelineConfiguration, TimelineFilter},
|
||||
EventTimelineItem, ReceiptType, SendHandle, Timeline,
|
||||
EventTimelineItem, LatestEventValue, ReceiptType, SendHandle, Timeline,
|
||||
},
|
||||
utils::{u64_to_uint, AsyncRuntimeDropped},
|
||||
TaskHandle,
|
||||
@@ -304,6 +304,10 @@ impl Room {
|
||||
self.inner.latest_event_item().await.map(Into::into)
|
||||
}
|
||||
|
||||
async fn new_latest_event(&self) -> LatestEventValue {
|
||||
self.inner.new_latest_event().await.into()
|
||||
}
|
||||
|
||||
pub async fn latest_encryption_state(&self) -> Result<EncryptionState, ClientError> {
|
||||
Ok(self.inner.latest_encryption_state().await?)
|
||||
}
|
||||
@@ -484,7 +488,7 @@ impl Room {
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the room is not found or on rate limit
|
||||
pub async fn report_room(&self, reason: Option<String>) -> Result<(), ClientError> {
|
||||
pub async fn report_room(&self, reason: String) -> Result<(), ClientError> {
|
||||
self.inner.report_room(reason).await?;
|
||||
|
||||
Ok(())
|
||||
@@ -720,53 +724,6 @@ impl Room {
|
||||
Ok(self.inner.matrix_to_event_permalink(event_id).await?.to_string())
|
||||
}
|
||||
|
||||
/// This will only send a call notification event if appropriate.
|
||||
///
|
||||
/// This function is supposed to be called whenever the user creates a room
|
||||
/// call. It will send a `m.call.notify` event if:
|
||||
/// - there is not yet a running call.
|
||||
///
|
||||
/// It will configure the notify type: ring or notify based on:
|
||||
/// - is this a DM room -> ring
|
||||
/// - is this a group with more than one other member -> notify
|
||||
///
|
||||
/// Returns:
|
||||
/// - `Ok(true)` if the event was successfully sent.
|
||||
/// - `Ok(false)` if we didn't send it because it was unnecessary.
|
||||
/// - `Err(_)` if sending the event failed.
|
||||
pub async fn send_call_notification_if_needed(&self) -> Result<bool, ClientError> {
|
||||
Ok(self.inner.send_call_notification_if_needed().await?)
|
||||
}
|
||||
|
||||
/// Send a call notification event in the current room.
|
||||
///
|
||||
/// This is only supposed to be used in **custom** situations where the user
|
||||
/// explicitly chooses to send a `m.call.notify` event to invite/notify
|
||||
/// someone explicitly in unusual conditions. The default should be to
|
||||
/// use `send_call_notification_if_necessary` just before a new room call is
|
||||
/// created/joined.
|
||||
///
|
||||
/// One example could be that the UI allows to start a call with a subset of
|
||||
/// users of the room members first. And then later on the user can
|
||||
/// invite more users to the call.
|
||||
pub async fn send_call_notification(
|
||||
&self,
|
||||
call_id: String,
|
||||
application: RtcApplicationType,
|
||||
notify_type: NotifyType,
|
||||
mentions: Mentions,
|
||||
) -> Result<(), ClientError> {
|
||||
self.inner
|
||||
.send_call_notification(
|
||||
call_id,
|
||||
application.into(),
|
||||
notify_type.into(),
|
||||
mentions.into(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns whether the send queue for that particular room is enabled or
|
||||
/// not.
|
||||
pub fn is_send_queue_enabled(&self) -> bool {
|
||||
@@ -1139,6 +1096,59 @@ impl Room {
|
||||
|
||||
Ok(Arc::new(RoomPreview::new(AsyncRuntimeDropped::new(client), room_preview)))
|
||||
}
|
||||
|
||||
/// Set a MSC4306 subscription to a thread in this room, based on the thread
|
||||
/// root event id.
|
||||
///
|
||||
/// If `subscribed` is `true`, it will subscribe to the thread, with a
|
||||
/// precision that the subscription was manually requested by the user
|
||||
/// (i.e. not automatic).
|
||||
///
|
||||
/// If the thread was already subscribed to (resp. unsubscribed from), while
|
||||
/// trying to subscribe to it (resp. unsubscribe from it), it will do
|
||||
/// nothing, i.e. subscribing (resp. unsubscribing) to a thread is an
|
||||
/// idempotent operation.
|
||||
pub async fn set_thread_subscription(
|
||||
&self,
|
||||
thread_root_event_id: String,
|
||||
subscribed: bool,
|
||||
) -> Result<(), ClientError> {
|
||||
let thread_root = EventId::parse(thread_root_event_id)?;
|
||||
if subscribed {
|
||||
// This is a manual subscription.
|
||||
let automatic = None;
|
||||
self.inner.subscribe_thread(thread_root, automatic).await?;
|
||||
} else {
|
||||
self.inner.unsubscribe_thread(thread_root).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the current MSC4306 thread subscription for the given thread root
|
||||
/// in this room.
|
||||
///
|
||||
/// Returns `None` if the thread doesn't exist, or isn't subscribed to, or
|
||||
/// the server can't handle MSC4306; otherwise, returns the thread
|
||||
/// subscription status.
|
||||
pub async fn fetch_thread_subscription(
|
||||
&self,
|
||||
thread_root_event_id: String,
|
||||
) -> Result<Option<ThreadSubscription>, ClientError> {
|
||||
let thread_root = EventId::parse(thread_root_event_id)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.fetch_thread_subscription(thread_root)
|
||||
.await?
|
||||
.map(|sub| ThreadSubscription { automatic: sub.automatic }))
|
||||
}
|
||||
}
|
||||
|
||||
/// A thread subscription (MSC4306).
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct ThreadSubscription {
|
||||
/// Whether the thread subscription happened automatically (e.g. after a
|
||||
/// mention) or if it was manually requested by the user.
|
||||
automatic: bool,
|
||||
}
|
||||
|
||||
/// A listener for receiving new live location shares in a room.
|
||||
@@ -1504,13 +1514,10 @@ impl From<SdkSuccessorRoom> for SuccessorRoom {
|
||||
pub struct PredecessorRoom {
|
||||
/// The ID of the replacement room.
|
||||
pub room_id: String,
|
||||
|
||||
/// The event ID of the last known event in the predecesssor room.
|
||||
pub last_event_id: String,
|
||||
}
|
||||
|
||||
impl From<SdkPredecessorRoom> for PredecessorRoom {
|
||||
fn from(value: SdkPredecessorRoom) -> Self {
|
||||
Self { room_id: value.room_id.to_string(), last_event_id: value.last_event_id.to_string() }
|
||||
Self { room_id: value.room_id.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::{
|
||||
pub struct RoomInfo {
|
||||
id: String,
|
||||
encryption_state: EncryptionState,
|
||||
creator: Option<String>,
|
||||
creators: Option<Vec<String>>,
|
||||
/// The room's name from the room state event if received from sync, or one
|
||||
/// that's been computed otherwise.
|
||||
display_name: Option<String>,
|
||||
@@ -74,6 +74,11 @@ pub struct RoomInfo {
|
||||
///
|
||||
/// Can be missing if the room power levels event is missing from the store.
|
||||
power_levels: Option<Arc<RoomPowerLevels>>,
|
||||
/// This room's version.
|
||||
room_version: Option<String>,
|
||||
/// Whether creators are privileged over every other user (have infinite
|
||||
/// power level).
|
||||
privileged_creators_role: bool,
|
||||
}
|
||||
|
||||
impl RoomInfo {
|
||||
@@ -102,7 +107,9 @@ impl RoomInfo {
|
||||
Ok(Self {
|
||||
id: room.room_id().to_string(),
|
||||
encryption_state: room.encryption_state(),
|
||||
creator: room.creator().as_ref().map(ToString::to_string),
|
||||
creators: room
|
||||
.creators()
|
||||
.map(|creators| creators.into_iter().map(Into::into).collect()),
|
||||
display_name: room.cached_display_name().map(|name| name.to_string()),
|
||||
raw_name: room.name(),
|
||||
topic: room.topic(),
|
||||
@@ -150,6 +157,12 @@ impl RoomInfo {
|
||||
join_rule,
|
||||
history_visibility: room.history_visibility_or_default().try_into()?,
|
||||
power_levels: power_levels.map(Arc::new),
|
||||
room_version: room.version().map(|version| version.to_string()),
|
||||
privileged_creators_role: room
|
||||
.version()
|
||||
.and_then(|version| version.rules())
|
||||
.map(|rules| rules.authorization.explicitly_privilege_room_creators)
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,15 +28,21 @@ use crate::{error::ClientError, runtime::get_runtime_handle, task_handle::TaskHa
|
||||
pub enum PublicRoomJoinRule {
|
||||
Public,
|
||||
Knock,
|
||||
Restricted,
|
||||
KnockRestricted,
|
||||
Invite,
|
||||
}
|
||||
|
||||
impl TryFrom<ruma::directory::PublicRoomJoinRule> for PublicRoomJoinRule {
|
||||
impl TryFrom<ruma::room::JoinRuleKind> for PublicRoomJoinRule {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: ruma::directory::PublicRoomJoinRule) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: ruma::room::JoinRuleKind) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
ruma::directory::PublicRoomJoinRule::Public => Ok(Self::Public),
|
||||
ruma::directory::PublicRoomJoinRule::Knock => Ok(Self::Knock),
|
||||
ruma::room::JoinRuleKind::Public => Ok(Self::Public),
|
||||
ruma::room::JoinRuleKind::Knock => Ok(Self::Knock),
|
||||
ruma::room::JoinRuleKind::Restricted => Ok(Self::Restricted),
|
||||
ruma::room::JoinRuleKind::KnockRestricted => Ok(Self::KnockRestricted),
|
||||
ruma::room::JoinRuleKind::Invite => Ok(Self::Invite),
|
||||
rule => Err(format!("unsupported join rule: {rule:?}")),
|
||||
}
|
||||
}
|
||||
@@ -149,11 +155,6 @@ impl RoomDirectorySearch {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct RoomDirectorySearchEntriesResult {
|
||||
pub entries_stream: Arc<TaskHandle>,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum RoomDirectorySearchEntryUpdate {
|
||||
Append { values: Vec<RoomDescription> },
|
||||
|
||||
@@ -16,8 +16,9 @@ use matrix_sdk_ui::{
|
||||
room_list_service::filters::{
|
||||
new_filter_all, new_filter_any, new_filter_category, new_filter_deduplicate_versions,
|
||||
new_filter_favourite, new_filter_fuzzy_match_room_name, new_filter_invite,
|
||||
new_filter_joined, new_filter_non_left, new_filter_none,
|
||||
new_filter_normalized_match_room_name, new_filter_unread, BoxedFilterFn, RoomCategory,
|
||||
new_filter_joined, new_filter_low_priority, new_filter_non_left, new_filter_none,
|
||||
new_filter_normalized_match_room_name, new_filter_not, new_filter_space, new_filter_unread,
|
||||
BoxedFilterFn, RoomCategory,
|
||||
},
|
||||
unable_to_decrypt_hook::UtdHookManager,
|
||||
};
|
||||
@@ -454,10 +455,15 @@ impl RoomListDynamicEntriesController {
|
||||
pub enum RoomListEntriesDynamicFilterKind {
|
||||
All { filters: Vec<RoomListEntriesDynamicFilterKind> },
|
||||
Any { filters: Vec<RoomListEntriesDynamicFilterKind> },
|
||||
NonSpace,
|
||||
NonLeft,
|
||||
// Not { filter: RoomListEntriesDynamicFilterKind } - requires recursive enum
|
||||
// support in uniffi https://github.com/mozilla/uniffi-rs/issues/396
|
||||
Joined,
|
||||
Unread,
|
||||
Favourite,
|
||||
LowPriority,
|
||||
NonLowPriority,
|
||||
Invite,
|
||||
Category { expect: RoomListFilterCategory },
|
||||
None,
|
||||
@@ -493,9 +499,12 @@ impl From<RoomListEntriesDynamicFilterKind> for BoxedFilterFn {
|
||||
filters.into_iter().map(|filter| BoxedFilterFn::from(filter)).collect(),
|
||||
)),
|
||||
Kind::NonLeft => Box::new(new_filter_non_left()),
|
||||
Kind::NonSpace => Box::new(new_filter_not(Box::new(new_filter_space()))),
|
||||
Kind::Joined => Box::new(new_filter_joined()),
|
||||
Kind::Unread => Box::new(new_filter_unread()),
|
||||
Kind::Favourite => Box::new(new_filter_favourite()),
|
||||
Kind::LowPriority => Box::new(new_filter_low_priority()),
|
||||
Kind::NonLowPriority => Box::new(new_filter_not(Box::new(new_filter_low_priority()))),
|
||||
Kind::Invite => Box::new(new_filter_invite()),
|
||||
Kind::Category { expect } => Box::new(new_filter_category(expect.into())),
|
||||
Kind::None => Box::new(new_filter_none()),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use matrix_sdk::room::{RoomMember as SdkRoomMember, RoomMemberRole};
|
||||
use ruma::UserId;
|
||||
use ruma::{events::room::power_levels::UserPowerLevel, UserId};
|
||||
|
||||
use crate::error::{ClientError, NotYetImplemented};
|
||||
|
||||
@@ -57,16 +57,25 @@ impl TryFrom<matrix_sdk::ruma::events::room::member::MembershipState> for Member
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the suggested role for the given power level.
|
||||
///
|
||||
/// Returns an error if the value of the power level is out of range for numbers
|
||||
/// accepted in canonical JSON.
|
||||
#[matrix_sdk_ffi_macros::export]
|
||||
pub fn suggested_role_for_power_level(power_level: i64) -> RoomMemberRole {
|
||||
pub fn suggested_role_for_power_level(
|
||||
power_level: PowerLevel,
|
||||
) -> Result<RoomMemberRole, ClientError> {
|
||||
// It's not possible to expose the constructor on the Enum through Uniffi ☹️
|
||||
RoomMemberRole::suggested_role_for_power_level(power_level)
|
||||
Ok(RoomMemberRole::suggested_role_for_power_level(power_level.try_into()?))
|
||||
}
|
||||
|
||||
/// Get the suggested power level for the given role.
|
||||
///
|
||||
/// Returns an error if the value of the power level is unsupported.
|
||||
#[matrix_sdk_ffi_macros::export]
|
||||
pub fn suggested_power_level_for_role(role: RoomMemberRole) -> i64 {
|
||||
pub fn suggested_power_level_for_role(role: RoomMemberRole) -> Result<PowerLevel, ClientError> {
|
||||
// It's not possible to expose methods on an Enum through Uniffi ☹️
|
||||
role.suggested_power_level()
|
||||
Ok(role.suggested_power_level().try_into()?)
|
||||
}
|
||||
|
||||
/// Generates a `matrix.to` permalink to the given userID.
|
||||
@@ -83,8 +92,8 @@ pub struct RoomMember {
|
||||
pub avatar_url: Option<String>,
|
||||
pub membership: MembershipState,
|
||||
pub is_name_ambiguous: bool,
|
||||
pub power_level: i64,
|
||||
pub normalized_power_level: i64,
|
||||
pub power_level: PowerLevel,
|
||||
pub normalized_power_level: PowerLevel,
|
||||
pub is_ignored: bool,
|
||||
pub suggested_role_for_power_level: RoomMemberRole,
|
||||
pub membership_change_reason: Option<String>,
|
||||
@@ -100,8 +109,8 @@ impl TryFrom<SdkRoomMember> for RoomMember {
|
||||
avatar_url: m.avatar_url().map(|a| a.to_string()),
|
||||
membership: m.membership().clone().try_into()?,
|
||||
is_name_ambiguous: m.name_ambiguous(),
|
||||
power_level: m.power_level(),
|
||||
normalized_power_level: m.normalized_power_level(),
|
||||
power_level: m.power_level().try_into()?,
|
||||
normalized_power_level: m.normalized_power_level().try_into()?,
|
||||
is_ignored: m.is_ignored(),
|
||||
suggested_role_for_power_level: m.suggested_role_for_power_level(),
|
||||
membership_change_reason: m.event().reason().map(|s| s.to_owned()),
|
||||
@@ -130,3 +139,42 @@ impl TryFrom<matrix_sdk::room::RoomMemberWithSenderInfo> for RoomMemberWithSende
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Enum)]
|
||||
pub enum PowerLevel {
|
||||
/// The user is a room creator and has infinite power level.
|
||||
///
|
||||
/// This power level was introduced in room version 12.
|
||||
Infinite,
|
||||
|
||||
/// The user has the given power level.
|
||||
Value { value: i64 },
|
||||
}
|
||||
|
||||
impl TryFrom<UserPowerLevel> for PowerLevel {
|
||||
type Error = NotYetImplemented;
|
||||
|
||||
fn try_from(value: UserPowerLevel) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
UserPowerLevel::Infinite => Ok(Self::Infinite),
|
||||
UserPowerLevel::Int(value) => Ok(Self::Value { value: value.into() }),
|
||||
_ => Err(NotYetImplemented),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PowerLevel> for UserPowerLevel {
|
||||
type Error = ClientError;
|
||||
|
||||
fn try_from(value: PowerLevel) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
PowerLevel::Infinite => Self::Infinite,
|
||||
PowerLevel::Value { value } => {
|
||||
Self::Int(value.try_into().map_err(|err| ClientError::Generic {
|
||||
msg: "Power level is out of range".to_owned(),
|
||||
details: Some(format!("{err:?}")),
|
||||
})?)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use anyhow::Context as _;
|
||||
use matrix_sdk::{room_preview::RoomPreview as SdkRoomPreview, Client};
|
||||
use ruma::{room::RoomType as RumaRoomType, space::SpaceRoomJoinRule};
|
||||
use tracing::warn;
|
||||
use ruma::room::{JoinRuleSummary, RoomType as RumaRoomType};
|
||||
|
||||
use crate::{
|
||||
client::JoinRule,
|
||||
client::{AllowRule, JoinRule},
|
||||
error::ClientError,
|
||||
room::{Membership, RoomHero},
|
||||
room_member::{RoomMember, RoomMemberWithSenderInfo},
|
||||
@@ -22,9 +21,9 @@ pub struct RoomPreview {
|
||||
#[matrix_sdk_ffi_macros::export]
|
||||
impl RoomPreview {
|
||||
/// Returns the room info the preview contains.
|
||||
pub fn info(&self) -> Result<RoomPreviewInfo, ClientError> {
|
||||
pub fn info(&self) -> RoomPreviewInfo {
|
||||
let info = &self.inner;
|
||||
Ok(RoomPreviewInfo {
|
||||
RoomPreviewInfo {
|
||||
room_id: info.room_id.to_string(),
|
||||
canonical_alias: info.canonical_alias.as_ref().map(|alias| alias.to_string()),
|
||||
name: info.name.clone(),
|
||||
@@ -32,21 +31,16 @@ impl RoomPreview {
|
||||
avatar_url: info.avatar_url.as_ref().map(|url| url.to_string()),
|
||||
num_joined_members: info.num_joined_members,
|
||||
num_active_members: info.num_active_members,
|
||||
room_type: info.room_type.as_ref().into(),
|
||||
room_type: info.room_type.clone().into(),
|
||||
is_history_world_readable: info.is_world_readable,
|
||||
membership: info.state.map(|state| state.into()),
|
||||
join_rule: info
|
||||
.join_rule
|
||||
.as_ref()
|
||||
.map(TryInto::try_into)
|
||||
.transpose()
|
||||
.map_err(|_| anyhow::anyhow!("unhandled SpaceRoomJoinRule kind"))?,
|
||||
join_rule: info.join_rule.clone().map(Into::into),
|
||||
is_direct: info.is_direct,
|
||||
heroes: info
|
||||
.heroes
|
||||
.as_ref()
|
||||
.map(|heroes| heroes.iter().map(|h| h.to_owned().into()).collect()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Leave the room if the room preview state is either joined, invited or
|
||||
@@ -122,23 +116,29 @@ pub struct RoomPreviewInfo {
|
||||
pub heroes: Option<Vec<RoomHero>>,
|
||||
}
|
||||
|
||||
impl TryFrom<&SpaceRoomJoinRule> for JoinRule {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(join_rule: &SpaceRoomJoinRule) -> Result<Self, ()> {
|
||||
Ok(match join_rule {
|
||||
SpaceRoomJoinRule::Invite => JoinRule::Invite,
|
||||
SpaceRoomJoinRule::Knock => JoinRule::Knock,
|
||||
SpaceRoomJoinRule::Private => JoinRule::Private,
|
||||
SpaceRoomJoinRule::Restricted => JoinRule::Restricted { rules: Vec::new() },
|
||||
SpaceRoomJoinRule::KnockRestricted => JoinRule::KnockRestricted { rules: Vec::new() },
|
||||
SpaceRoomJoinRule::Public => JoinRule::Public,
|
||||
SpaceRoomJoinRule::_Custom(_) => JoinRule::Custom { repr: join_rule.to_string() },
|
||||
_ => {
|
||||
warn!("unhandled SpaceRoomJoinRule: {join_rule}");
|
||||
return Err(());
|
||||
}
|
||||
})
|
||||
impl From<JoinRuleSummary> for JoinRule {
|
||||
fn from(join_rule: JoinRuleSummary) -> Self {
|
||||
match join_rule {
|
||||
JoinRuleSummary::Invite => JoinRule::Invite,
|
||||
JoinRuleSummary::Knock => JoinRule::Knock,
|
||||
JoinRuleSummary::Private => JoinRule::Private,
|
||||
JoinRuleSummary::Restricted(summary) => JoinRule::Restricted {
|
||||
rules: summary
|
||||
.allowed_room_ids
|
||||
.iter()
|
||||
.map(|room_id| AllowRule::RoomMembership { room_id: room_id.to_string() })
|
||||
.collect(),
|
||||
},
|
||||
JoinRuleSummary::KnockRestricted(summary) => JoinRule::KnockRestricted {
|
||||
rules: summary
|
||||
.allowed_room_ids
|
||||
.iter()
|
||||
.map(|room_id| AllowRule::RoomMembership { room_id: room_id.to_string() })
|
||||
.collect(),
|
||||
},
|
||||
JoinRuleSummary::Public => JoinRule::Public,
|
||||
_ => JoinRule::Custom { repr: join_rule.as_str().to_owned() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,8 +153,8 @@ pub enum RoomType {
|
||||
Custom { value: String },
|
||||
}
|
||||
|
||||
impl From<Option<&RumaRoomType>> for RoomType {
|
||||
fn from(value: Option<&RumaRoomType>) -> Self {
|
||||
impl From<Option<RumaRoomType>> for RoomType {
|
||||
fn from(value: Option<RumaRoomType>) -> Self {
|
||||
match value {
|
||||
Some(RumaRoomType::Space) => RoomType::Space,
|
||||
Some(RumaRoomType::_Custom(_)) => RoomType::Custom {
|
||||
|
||||
@@ -1486,17 +1486,17 @@ impl From<RumaSecretStorageV1AesHmacSha2Properties> for SecretStorageV1AesHmacSh
|
||||
#[derive(Clone, uniffi::Record, Default)]
|
||||
pub struct MediaPreviewConfig {
|
||||
/// The media previews setting for the user.
|
||||
pub media_previews: MediaPreviews,
|
||||
pub media_previews: Option<MediaPreviews>,
|
||||
|
||||
/// The invite avatars setting for the user.
|
||||
pub invite_avatars: InviteAvatars,
|
||||
pub invite_avatars: Option<InviteAvatars>,
|
||||
}
|
||||
|
||||
impl From<MediaPreviewConfigEventContent> for MediaPreviewConfig {
|
||||
fn from(value: MediaPreviewConfigEventContent) -> Self {
|
||||
Self {
|
||||
media_previews: value.media_previews.into(),
|
||||
invite_avatars: value.invite_avatars.into(),
|
||||
media_previews: value.media_previews.map(Into::into),
|
||||
invite_avatars: value.invite_avatars.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,18 +254,14 @@ impl SessionVerificationController {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(sender_profile) = self.account.fetch_user_profile_of(sender).await else {
|
||||
let Ok(sender_profile) = UserProfile::fetch(&self.account, sender).await else {
|
||||
error!("Failed fetching user profile for verification request");
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(delegate) = &*self.delegate.read().unwrap() {
|
||||
delegate.did_receive_verification_request(SessionVerificationRequestDetails {
|
||||
sender_profile: UserProfile {
|
||||
user_id: request.other_user_id().to_string(),
|
||||
display_name: sender_profile.displayname,
|
||||
avatar_url: sender_profile.avatar_url.as_ref().map(|url| url.to_string()),
|
||||
},
|
||||
sender_profile,
|
||||
flow_id: request.flow_id().into(),
|
||||
device_id: other_device_data.device_id().into(),
|
||||
device_display_name: other_device_data.display_name().map(str::to_string),
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
// Copyright 2025 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use eyeball_im::VectorDiff;
|
||||
use futures_util::{pin_mut, StreamExt};
|
||||
use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
|
||||
use matrix_sdk_ui::spaces::{
|
||||
room_list::SpaceRoomListPaginationState, SpaceRoom as UISpaceRoom,
|
||||
SpaceRoomList as UISpaceRoomList, SpaceService as UISpaceService,
|
||||
};
|
||||
use ruma::RoomId;
|
||||
|
||||
use crate::{
|
||||
client::JoinRule,
|
||||
error::ClientError,
|
||||
room::{Membership, RoomHero},
|
||||
room_preview::RoomType,
|
||||
runtime::get_runtime_handle,
|
||||
TaskHandle,
|
||||
};
|
||||
|
||||
/// The main entry point into the Spaces facilities.
|
||||
///
|
||||
/// The spaces service is responsible for retrieving one's joined rooms,
|
||||
/// building a graph out of their `m.space.parent` and `m.space.child` state
|
||||
/// events, and providing access to the top-level spaces and their children.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct SpaceService {
|
||||
inner: UISpaceService,
|
||||
}
|
||||
|
||||
impl SpaceService {
|
||||
/// Creates a new `SpaceService` instance.
|
||||
pub(crate) fn new(inner: UISpaceService) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[matrix_sdk_ffi_macros::export]
|
||||
impl SpaceService {
|
||||
/// Returns a list of all the top-level joined spaces. It will eagerly
|
||||
/// compute the latest version and also notify subscribers if there were
|
||||
/// any changes.
|
||||
pub async fn joined_spaces(&self) -> Vec<SpaceRoom> {
|
||||
self.inner.joined_spaces().await.into_iter().map(Into::into).collect()
|
||||
}
|
||||
|
||||
/// Subscribes to updates on the joined spaces list. If space rooms are
|
||||
/// joined or left, the stream will yield diffs that reflect the changes.
|
||||
pub async fn subscribe_to_joined_spaces(
|
||||
&self,
|
||||
listener: Box<dyn SpaceServiceJoinedSpacesListener>,
|
||||
) -> Arc<TaskHandle> {
|
||||
let (initial_values, mut stream) = self.inner.subscribe_to_joined_spaces().await;
|
||||
|
||||
listener.on_update(vec![SpaceListUpdate::Reset {
|
||||
values: initial_values.into_iter().map(Into::into).collect(),
|
||||
}]);
|
||||
|
||||
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
|
||||
while let Some(diffs) = stream.next().await {
|
||||
listener.on_update(diffs.into_iter().map(Into::into).collect());
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
/// Returns a `SpaceRoomList` for the given space ID.
|
||||
#[allow(clippy::unused_async)]
|
||||
// This method doesn't need to be async but if its not the FFI layer panics
|
||||
// with "there is no no reactor running, must be called from the context
|
||||
// of a Tokio 1.x runtime" error because the underlying constructor spawns
|
||||
// an async task.
|
||||
pub async fn space_room_list(
|
||||
&self,
|
||||
space_id: String,
|
||||
) -> Result<Arc<SpaceRoomList>, ClientError> {
|
||||
let space_id = RoomId::parse(space_id)?;
|
||||
Ok(Arc::new(SpaceRoomList::new(self.inner.space_room_list(space_id))))
|
||||
}
|
||||
}
|
||||
|
||||
/// The `SpaceRoomList`represents a paginated list of direct rooms
|
||||
/// that belong to a particular space.
|
||||
///
|
||||
/// It can be used to paginate through the list (and have live updates on the
|
||||
/// pagination state) as well as subscribe to changes as rooms are joined or
|
||||
/// left.
|
||||
///
|
||||
/// The `SpaceRoomList` also automatically subscribes to client room changes
|
||||
/// and updates the list accordingly as rooms are joined or left.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct SpaceRoomList {
|
||||
inner: UISpaceRoomList,
|
||||
}
|
||||
|
||||
impl SpaceRoomList {
|
||||
/// Creates a new `SpaceRoomList` for the underlying UI crate room list.
|
||||
fn new(inner: UISpaceRoomList) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[matrix_sdk_ffi_macros::export]
|
||||
impl SpaceRoomList {
|
||||
/// Returns if the room list is currently paginating or not.
|
||||
pub fn pagination_state(&self) -> SpaceRoomListPaginationState {
|
||||
self.inner.pagination_state()
|
||||
}
|
||||
|
||||
/// Subscribe to pagination updates.
|
||||
pub fn subscribe_to_pagination_state_updates(
|
||||
&self,
|
||||
listener: Box<dyn SpaceRoomListPaginationStateListener>,
|
||||
) -> Arc<TaskHandle> {
|
||||
let pagination_state = self.inner.subscribe_to_pagination_state_updates();
|
||||
|
||||
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
|
||||
pin_mut!(pagination_state);
|
||||
|
||||
while let Some(state) = pagination_state.next().await {
|
||||
listener.on_update(state);
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
/// Return the current list of rooms.
|
||||
pub fn rooms(&self) -> Vec<SpaceRoom> {
|
||||
self.inner.rooms().into_iter().map(Into::into).collect()
|
||||
}
|
||||
|
||||
/// Subscribes to room list updates.
|
||||
pub fn subscribe_to_room_update(
|
||||
&self,
|
||||
listener: Box<dyn SpaceRoomListEntriesListener>,
|
||||
) -> Arc<TaskHandle> {
|
||||
let (initial_values, mut stream) = self.inner.subscribe_to_room_updates();
|
||||
|
||||
listener.on_update(vec![SpaceListUpdate::Reset {
|
||||
values: initial_values.into_iter().map(Into::into).collect(),
|
||||
}]);
|
||||
|
||||
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
|
||||
while let Some(diffs) = stream.next().await {
|
||||
listener.on_update(diffs.into_iter().map(Into::into).collect());
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
/// Ask the list to retrieve the next page if the end hasn't been reached
|
||||
/// yet. Otherwise it no-ops.
|
||||
pub async fn paginate(&self) -> Result<(), ClientError> {
|
||||
self.inner.paginate().await.map_err(ClientError::from)
|
||||
}
|
||||
}
|
||||
|
||||
#[matrix_sdk_ffi_macros::export(callback_interface)]
|
||||
pub trait SpaceRoomListPaginationStateListener: SendOutsideWasm + SyncOutsideWasm + Debug {
|
||||
fn on_update(&self, pagination_state: SpaceRoomListPaginationState);
|
||||
}
|
||||
|
||||
#[matrix_sdk_ffi_macros::export(callback_interface)]
|
||||
pub trait SpaceRoomListEntriesListener: SendOutsideWasm + SyncOutsideWasm + Debug {
|
||||
fn on_update(&self, rooms: Vec<SpaceListUpdate>);
|
||||
}
|
||||
|
||||
#[matrix_sdk_ffi_macros::export(callback_interface)]
|
||||
pub trait SpaceServiceJoinedSpacesListener: SendOutsideWasm + SyncOutsideWasm + Debug {
|
||||
fn on_update(&self, room_updates: Vec<SpaceListUpdate>);
|
||||
}
|
||||
|
||||
/// Structure representing a room in a space and aggregated information
|
||||
/// relevant to the UI layer.
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct SpaceRoom {
|
||||
/// The ID of the room.
|
||||
pub room_id: String,
|
||||
/// The canonical alias of the room, if any.
|
||||
pub canonical_alias: Option<String>,
|
||||
/// The name of the room, if any.
|
||||
pub name: Option<String>,
|
||||
/// The topic of the room, if any.
|
||||
pub topic: Option<String>,
|
||||
/// The URL for the room's avatar, if one is set.
|
||||
pub avatar_url: Option<String>,
|
||||
/// The type of room from `m.room.create`, if any.
|
||||
pub room_type: RoomType,
|
||||
/// The number of members joined to the room.
|
||||
pub num_joined_members: u64,
|
||||
/// The join rule of the room.
|
||||
pub join_rule: Option<JoinRule>,
|
||||
/// Whether the room may be viewed by users without joining.
|
||||
pub world_readable: Option<bool>,
|
||||
/// Whether guest users may join the room and participate in it.
|
||||
pub guest_can_join: bool,
|
||||
|
||||
/// The number of children room this has, if a space.
|
||||
pub children_count: u64,
|
||||
/// Whether this room is joined, left etc.
|
||||
pub state: Option<Membership>,
|
||||
/// A list of room members considered to be heroes.
|
||||
pub heroes: Option<Vec<RoomHero>>,
|
||||
}
|
||||
|
||||
impl From<UISpaceRoom> for SpaceRoom {
|
||||
fn from(room: UISpaceRoom) -> Self {
|
||||
Self {
|
||||
room_id: room.room_id.into(),
|
||||
canonical_alias: room.canonical_alias.map(|alias| alias.into()),
|
||||
name: room.name,
|
||||
topic: room.topic,
|
||||
avatar_url: room.avatar_url.map(|url| url.into()),
|
||||
room_type: room.room_type.into(),
|
||||
num_joined_members: room.num_joined_members,
|
||||
join_rule: room.join_rule.map(Into::into),
|
||||
world_readable: room.world_readable,
|
||||
guest_can_join: room.guest_can_join,
|
||||
children_count: room.children_count,
|
||||
state: room.state.map(Into::into),
|
||||
heroes: room.heroes.map(|heroes| heroes.into_iter().map(Into::into).collect()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum SpaceListUpdate {
|
||||
Append { values: Vec<SpaceRoom> },
|
||||
Clear,
|
||||
PushFront { value: SpaceRoom },
|
||||
PushBack { value: SpaceRoom },
|
||||
PopFront,
|
||||
PopBack,
|
||||
Insert { index: u32, value: SpaceRoom },
|
||||
Set { index: u32, value: SpaceRoom },
|
||||
Remove { index: u32 },
|
||||
Truncate { length: u32 },
|
||||
Reset { values: Vec<SpaceRoom> },
|
||||
}
|
||||
|
||||
impl From<VectorDiff<UISpaceRoom>> for SpaceListUpdate {
|
||||
fn from(diff: VectorDiff<UISpaceRoom>) -> Self {
|
||||
match diff {
|
||||
VectorDiff::Append { values } => {
|
||||
Self::Append { values: values.into_iter().map(|v| v.into()).collect() }
|
||||
}
|
||||
VectorDiff::Clear => Self::Clear,
|
||||
VectorDiff::PushFront { value } => Self::PushFront { value: value.into() },
|
||||
VectorDiff::PushBack { value } => Self::PushBack { value: value.into() },
|
||||
VectorDiff::PopFront => Self::PopFront,
|
||||
VectorDiff::PopBack => Self::PopBack,
|
||||
VectorDiff::Insert { index, value } => {
|
||||
Self::Insert { index: index as u32, value: value.into() }
|
||||
}
|
||||
VectorDiff::Set { index, value } => {
|
||||
Self::Set { index: index as u32, value: value.into() }
|
||||
}
|
||||
VectorDiff::Remove { index } => Self::Remove { index: index as u32 },
|
||||
VectorDiff::Truncate { length } => Self::Truncate { length: length as u32 },
|
||||
VectorDiff::Reset { values } => {
|
||||
Self::Reset { values: values.into_iter().map(|v| v.into()).collect() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,15 @@ impl SyncService {
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
/// Force expiring both sliding sync sessions.
|
||||
///
|
||||
/// This ensures that the sync service is stopped before expiring both
|
||||
/// sessions. It should be used sparingly, as it will cause a restart of
|
||||
/// the sessions on the server as well.
|
||||
pub async fn expire_sessions(&self) {
|
||||
self.inner.expire_sessions().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Object)]
|
||||
|
||||
@@ -15,28 +15,24 @@
|
||||
use std::{collections::HashMap, fmt::Write as _, fs, panic, sync::Arc};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use as_variant::as_variant;
|
||||
use eyeball_im::VectorDiff;
|
||||
use futures_util::pin_mut;
|
||||
use matrix_sdk::{
|
||||
attachment::{
|
||||
AttachmentConfig, AttachmentInfo, BaseAudioInfo, BaseFileInfo, BaseImageInfo,
|
||||
BaseVideoInfo, Thumbnail,
|
||||
AttachmentInfo, BaseAudioInfo, BaseFileInfo, BaseImageInfo, BaseVideoInfo, Thumbnail,
|
||||
},
|
||||
deserialized_responses::{ShieldState as SdkShieldState, ShieldStateCode},
|
||||
event_cache::RoomPaginationStatus,
|
||||
room::{
|
||||
edit::EditedContent as SdkEditedContent,
|
||||
reply::{EnforceThread, Reply},
|
||||
},
|
||||
room::edit::EditedContent as SdkEditedContent,
|
||||
};
|
||||
use matrix_sdk_common::{
|
||||
executor::{AbortHandle, JoinHandle},
|
||||
stream::StreamExt,
|
||||
};
|
||||
use matrix_sdk_ui::timeline::{
|
||||
self, AttachmentSource, EventItemOrigin, Profile, TimelineDetails,
|
||||
TimelineUniqueId as SdkTimelineUniqueId,
|
||||
self, AttachmentConfig, AttachmentSource, EventItemOrigin,
|
||||
LatestEventValue as UiLatestEventValue, MediaUploadProgress as SdkMediaUploadProgress, Profile,
|
||||
TimelineDetails, TimelineUniqueId as SdkTimelineUniqueId,
|
||||
};
|
||||
use mime::Mime;
|
||||
use reply::{EmbeddedEventDetails, InReplyToDetails};
|
||||
@@ -52,8 +48,7 @@ use ruma::{
|
||||
},
|
||||
},
|
||||
room::message::{
|
||||
LocationMessageEventContent, MessageType, ReplyWithinThread,
|
||||
RoomMessageEventContentWithoutRelation,
|
||||
LocationMessageEventContent, MessageType, RoomMessageEventContentWithoutRelation,
|
||||
},
|
||||
AnyMessageLikeEventContent,
|
||||
},
|
||||
@@ -66,10 +61,8 @@ use uuid::Uuid;
|
||||
use self::content::TimelineItemContent;
|
||||
pub use self::msg_like::MessageContent;
|
||||
use crate::{
|
||||
client::ProgressWatcher,
|
||||
error::{ClientError, RoomError},
|
||||
event::EventOrTransactionId,
|
||||
helpers::unwrap_or_clone_arc,
|
||||
ruma::{
|
||||
AssetType, AudioInfo, FileInfo, FormattedBody, ImageInfo, Mentions, PollKind,
|
||||
ThumbnailInfo, VideoInfo,
|
||||
@@ -105,45 +98,40 @@ impl Timeline {
|
||||
params: UploadParameters,
|
||||
attachment_info: AttachmentInfo,
|
||||
mime_type: Option<String>,
|
||||
progress_watcher: Option<Box<dyn ProgressWatcher>>,
|
||||
thumbnail: Option<Thumbnail>,
|
||||
) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
|
||||
let mime_str = mime_type.as_ref().ok_or(RoomError::InvalidAttachmentMimeType)?;
|
||||
|
||||
let mime_type =
|
||||
mime_str.parse::<Mime>().map_err(|_| RoomError::InvalidAttachmentMimeType)?;
|
||||
|
||||
let in_reply_to_event_id = params
|
||||
.in_reply_to
|
||||
.map(EventId::parse)
|
||||
.transpose()
|
||||
.map_err(|_| RoomError::InvalidRepliedToEventId)?;
|
||||
|
||||
let formatted_caption = formatted_body_from(
|
||||
params.caption.as_deref(),
|
||||
params.formatted_caption.map(Into::into),
|
||||
);
|
||||
|
||||
let attachment_config = AttachmentConfig::new()
|
||||
.thumbnail(thumbnail)
|
||||
.info(attachment_info)
|
||||
.caption(params.caption)
|
||||
.formatted_caption(formatted_caption)
|
||||
.mentions(params.mentions.map(Into::into))
|
||||
.reply(params.reply_params.map(|p| p.try_into()).transpose()?);
|
||||
let attachment_config = AttachmentConfig {
|
||||
info: Some(attachment_info),
|
||||
thumbnail,
|
||||
caption: params.caption,
|
||||
formatted_caption,
|
||||
mentions: params.mentions.map(Into::into),
|
||||
in_reply_to: in_reply_to_event_id,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let handle = SendAttachmentJoinHandle::new(get_runtime_handle().spawn(async move {
|
||||
let mut request =
|
||||
self.inner.send_attachment(params.source, mime_type, attachment_config);
|
||||
|
||||
if params.use_send_queue {
|
||||
request = request.use_send_queue();
|
||||
}
|
||||
|
||||
if let Some(progress_watcher) = progress_watcher {
|
||||
let mut subscriber = request.subscribe_to_send_progress();
|
||||
get_runtime_handle().spawn(async move {
|
||||
while let Some(progress) = subscriber.next().await {
|
||||
progress_watcher.transmission_progress(progress.into());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
request.await.map_err(|_| RoomError::FailedSendingAttachment)?;
|
||||
Ok(())
|
||||
self.inner
|
||||
.send_attachment(params.source, mime_type, attachment_config)
|
||||
.use_send_queue()
|
||||
.await
|
||||
.map_err(|_| RoomError::FailedSendingAttachment)
|
||||
}));
|
||||
|
||||
Ok(handle)
|
||||
@@ -151,15 +139,19 @@ impl Timeline {
|
||||
}
|
||||
|
||||
fn build_thumbnail_info(
|
||||
thumbnail_path: Option<String>,
|
||||
thumbnail_source: Option<UploadSource>,
|
||||
thumbnail_info: Option<ThumbnailInfo>,
|
||||
) -> Result<Option<Thumbnail>, RoomError> {
|
||||
match (thumbnail_path, thumbnail_info) {
|
||||
match (thumbnail_source, thumbnail_info) {
|
||||
(None, None) => Ok(None),
|
||||
|
||||
(Some(thumbnail_path), Some(thumbnail_info)) => {
|
||||
let thumbnail_data =
|
||||
fs::read(thumbnail_path).map_err(|_| RoomError::InvalidThumbnailData)?;
|
||||
(Some(thumbnail_source), Some(thumbnail_info)) => {
|
||||
let thumbnail_data = match thumbnail_source {
|
||||
UploadSource::File { filename } => {
|
||||
fs::read(filename).map_err(|_| RoomError::InvalidThumbnailData)?
|
||||
}
|
||||
UploadSource::Data { bytes, .. } => bytes,
|
||||
};
|
||||
|
||||
let height = thumbnail_info
|
||||
.height
|
||||
@@ -189,7 +181,7 @@ fn build_thumbnail_info(
|
||||
}
|
||||
|
||||
_ => {
|
||||
warn!("Ignoring thumbnail because either the thumbnail path or info isn't defined");
|
||||
warn!("Ignoring thumbnail because either the thumbnail source or info isn't defined");
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
@@ -205,16 +197,12 @@ pub struct UploadParameters {
|
||||
formatted_caption: Option<FormattedBody>,
|
||||
/// Optional intentional mentions to be sent with the media.
|
||||
mentions: Option<Mentions>,
|
||||
/// Optional parameters for sending the media as (threaded) reply.
|
||||
reply_params: Option<ReplyParameters>,
|
||||
/// Should the media be sent with the send queue, or synchronously?
|
||||
///
|
||||
/// Watching progress only works with the synchronous method, at the moment.
|
||||
use_send_queue: bool,
|
||||
/// Optional Event ID to reply to.
|
||||
in_reply_to: Option<String>,
|
||||
}
|
||||
|
||||
/// A source for uploading a file
|
||||
#[derive(uniffi::Enum)]
|
||||
#[derive(Clone, uniffi::Enum)]
|
||||
pub enum UploadSource {
|
||||
/// Upload source is a file on disk
|
||||
File {
|
||||
@@ -239,34 +227,47 @@ impl From<UploadSource> for AttachmentSource {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct ReplyParameters {
|
||||
/// The ID of the event to reply to.
|
||||
event_id: String,
|
||||
/// Whether to enforce a thread relation.
|
||||
enforce_thread: bool,
|
||||
/// If enforcing a threaded relation, whether the message is a reply on a
|
||||
/// thread.
|
||||
reply_within_thread: bool,
|
||||
/// This type represents the progress of a media (consisting of a file and
|
||||
/// possibly a thumbnail) being uploaded.
|
||||
#[derive(Clone, Copy, uniffi::Record)]
|
||||
pub struct MediaUploadProgress {
|
||||
/// The index of the media within the transaction. A file and its
|
||||
/// thumbnail share the same index. Will always be 0 for non-gallery
|
||||
/// media uploads.
|
||||
pub index: u64,
|
||||
|
||||
/// The current combined upload progress for both the file and,
|
||||
/// if it exists, its thumbnail.
|
||||
pub progress: AbstractProgress,
|
||||
}
|
||||
|
||||
impl TryInto<Reply> for ReplyParameters {
|
||||
type Error = RoomError;
|
||||
impl From<SdkMediaUploadProgress> for MediaUploadProgress {
|
||||
fn from(value: SdkMediaUploadProgress) -> Self {
|
||||
Self { index: value.index, progress: value.progress.into() }
|
||||
}
|
||||
}
|
||||
|
||||
fn try_into(self) -> Result<Reply, Self::Error> {
|
||||
let event_id =
|
||||
EventId::parse(&self.event_id).map_err(|_| RoomError::InvalidRepliedToEventId)?;
|
||||
let enforce_thread = if self.enforce_thread {
|
||||
EnforceThread::Threaded(if self.reply_within_thread {
|
||||
ReplyWithinThread::Yes
|
||||
} else {
|
||||
ReplyWithinThread::No
|
||||
})
|
||||
} else {
|
||||
EnforceThread::MaybeThreaded
|
||||
};
|
||||
/// Progress of an operation in abstract units.
|
||||
///
|
||||
/// Contrary to [`TransmissionProgress`], this allows tracking the progress
|
||||
/// of sending or receiving a payload in estimated pseudo units representing a
|
||||
/// percentage. This is helpful in cases where the exact progress in bytes isn't
|
||||
/// known, for instance, because encryption (which changes the size) happens on
|
||||
/// the fly.
|
||||
#[derive(Clone, Copy, uniffi::Record)]
|
||||
pub struct AbstractProgress {
|
||||
/// How many units were already transferred.
|
||||
pub current: u64,
|
||||
/// How many units there are in total.
|
||||
pub total: u64,
|
||||
}
|
||||
|
||||
Ok(Reply { event_id, enforce_thread })
|
||||
impl From<matrix_sdk::send_queue::AbstractProgress> for AbstractProgress {
|
||||
fn from(value: matrix_sdk::send_queue::AbstractProgress) -> Self {
|
||||
Self {
|
||||
current: value.current.try_into().unwrap_or(u64::MAX),
|
||||
total: value.total.try_into().unwrap_or(u64::MAX),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,17 +282,14 @@ impl Timeline {
|
||||
// handled by the caller. See #3535 for details.
|
||||
|
||||
// First, pass all the items as a reset update.
|
||||
listener.on_update(vec![Arc::new(TimelineDiff::new(VectorDiff::Reset {
|
||||
values: timeline_items,
|
||||
}))]);
|
||||
listener.on_update(vec![TimelineDiff::new(VectorDiff::Reset { values: timeline_items })]);
|
||||
|
||||
Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
|
||||
pin_mut!(timeline_stream);
|
||||
|
||||
// Then forward new items.
|
||||
while let Some(diffs) = timeline_stream.next().await {
|
||||
listener
|
||||
.on_update(diffs.into_iter().map(|d| Arc::new(TimelineDiff::new(d))).collect());
|
||||
listener.on_update(diffs.into_iter().map(TimelineDiff::new).collect());
|
||||
}
|
||||
})))
|
||||
}
|
||||
@@ -386,53 +384,38 @@ impl Timeline {
|
||||
pub fn send_image(
|
||||
self: Arc<Self>,
|
||||
params: UploadParameters,
|
||||
thumbnail_path: Option<String>,
|
||||
thumbnail_source: Option<UploadSource>,
|
||||
image_info: ImageInfo,
|
||||
progress_watcher: Option<Box<dyn ProgressWatcher>>,
|
||||
) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
|
||||
let attachment_info = AttachmentInfo::Image(
|
||||
BaseImageInfo::try_from(&image_info).map_err(|_| RoomError::InvalidAttachmentData)?,
|
||||
);
|
||||
let thumbnail = build_thumbnail_info(thumbnail_path, image_info.thumbnail_info)?;
|
||||
self.send_attachment(
|
||||
params,
|
||||
attachment_info,
|
||||
image_info.mimetype,
|
||||
progress_watcher,
|
||||
thumbnail,
|
||||
)
|
||||
let thumbnail = build_thumbnail_info(thumbnail_source, image_info.thumbnail_info)?;
|
||||
self.send_attachment(params, attachment_info, image_info.mimetype, thumbnail)
|
||||
}
|
||||
|
||||
pub fn send_video(
|
||||
self: Arc<Self>,
|
||||
params: UploadParameters,
|
||||
thumbnail_path: Option<String>,
|
||||
thumbnail_source: Option<UploadSource>,
|
||||
video_info: VideoInfo,
|
||||
progress_watcher: Option<Box<dyn ProgressWatcher>>,
|
||||
) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
|
||||
let attachment_info = AttachmentInfo::Video(
|
||||
BaseVideoInfo::try_from(&video_info).map_err(|_| RoomError::InvalidAttachmentData)?,
|
||||
);
|
||||
let thumbnail = build_thumbnail_info(thumbnail_path, video_info.thumbnail_info)?;
|
||||
self.send_attachment(
|
||||
params,
|
||||
attachment_info,
|
||||
video_info.mimetype,
|
||||
progress_watcher,
|
||||
thumbnail,
|
||||
)
|
||||
let thumbnail = build_thumbnail_info(thumbnail_source, video_info.thumbnail_info)?;
|
||||
self.send_attachment(params, attachment_info, video_info.mimetype, thumbnail)
|
||||
}
|
||||
|
||||
pub fn send_audio(
|
||||
self: Arc<Self>,
|
||||
params: UploadParameters,
|
||||
audio_info: AudioInfo,
|
||||
progress_watcher: Option<Box<dyn ProgressWatcher>>,
|
||||
) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
|
||||
let attachment_info = AttachmentInfo::Audio(
|
||||
BaseAudioInfo::try_from(&audio_info).map_err(|_| RoomError::InvalidAttachmentData)?,
|
||||
);
|
||||
self.send_attachment(params, attachment_info, audio_info.mimetype, progress_watcher, None)
|
||||
self.send_attachment(params, attachment_info, audio_info.mimetype, None)
|
||||
}
|
||||
|
||||
pub fn send_voice_message(
|
||||
@@ -440,26 +423,24 @@ impl Timeline {
|
||||
params: UploadParameters,
|
||||
audio_info: AudioInfo,
|
||||
waveform: Vec<u16>,
|
||||
progress_watcher: Option<Box<dyn ProgressWatcher>>,
|
||||
) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
|
||||
let attachment_info = AttachmentInfo::Voice {
|
||||
audio_info: BaseAudioInfo::try_from(&audio_info)
|
||||
.map_err(|_| RoomError::InvalidAttachmentData)?,
|
||||
waveform: Some(waveform),
|
||||
};
|
||||
self.send_attachment(params, attachment_info, audio_info.mimetype, progress_watcher, None)
|
||||
self.send_attachment(params, attachment_info, audio_info.mimetype, None)
|
||||
}
|
||||
|
||||
pub fn send_file(
|
||||
self: Arc<Self>,
|
||||
params: UploadParameters,
|
||||
file_info: FileInfo,
|
||||
progress_watcher: Option<Box<dyn ProgressWatcher>>,
|
||||
) -> Result<Arc<SendAttachmentJoinHandle>, RoomError> {
|
||||
let attachment_info = AttachmentInfo::File(
|
||||
BaseFileInfo::try_from(&file_info).map_err(|_| RoomError::InvalidAttachmentData)?,
|
||||
);
|
||||
self.send_attachment(params, attachment_info, file_info.mimetype, progress_watcher, None)
|
||||
self.send_attachment(params, attachment_info, file_info.mimetype, None)
|
||||
}
|
||||
|
||||
pub async fn create_poll(
|
||||
@@ -529,9 +510,10 @@ impl Timeline {
|
||||
pub async fn send_reply(
|
||||
&self,
|
||||
msg: Arc<RoomMessageEventContentWithoutRelation>,
|
||||
reply_params: ReplyParameters,
|
||||
event_id: String,
|
||||
) -> Result<(), ClientError> {
|
||||
self.inner.send_reply((*msg).clone(), reply_params.try_into()?).await?;
|
||||
let event_id = EventId::parse(&event_id).map_err(|_| RoomError::InvalidRepliedToEventId)?;
|
||||
self.inner.send_reply((*msg).clone(), event_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -585,7 +567,7 @@ impl Timeline {
|
||||
description: Option<String>,
|
||||
zoom_level: Option<u8>,
|
||||
asset_type: Option<AssetType>,
|
||||
reply_params: Option<ReplyParameters>,
|
||||
replied_to_event_id: Option<String>,
|
||||
) -> Result<(), ClientError> {
|
||||
let mut location_event_message_content =
|
||||
LocationMessageEventContent::new(body, geo_uri.clone());
|
||||
@@ -604,8 +586,8 @@ impl Timeline {
|
||||
MessageType::Location(location_event_message_content),
|
||||
);
|
||||
|
||||
if let Some(reply_params) = reply_params {
|
||||
self.send_reply(Arc::new(room_message_event_content), reply_params).await
|
||||
if let Some(replied_to_event_id) = replied_to_event_id {
|
||||
self.send_reply(Arc::new(room_message_event_content), replied_to_event_id).await
|
||||
} else {
|
||||
self.send(Arc::new(room_message_event_content)).await?;
|
||||
Ok(())
|
||||
@@ -818,7 +800,7 @@ pub enum FocusEventError {
|
||||
|
||||
#[matrix_sdk_ffi_macros::export(callback_interface)]
|
||||
pub trait TimelineListener: SyncOutsideWasm + SendOutsideWasm {
|
||||
fn on_update(&self, diff: Vec<Arc<TimelineDiff>>);
|
||||
fn on_update(&self, diff: Vec<TimelineDiff>);
|
||||
}
|
||||
|
||||
#[matrix_sdk_ffi_macros::export(callback_interface)]
|
||||
@@ -826,7 +808,7 @@ pub trait PaginationStatusListener: SyncOutsideWasm + SendOutsideWasm {
|
||||
fn on_update(&self, status: RoomPaginationStatus);
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Object)]
|
||||
#[derive(Clone, uniffi::Enum)]
|
||||
pub enum TimelineDiff {
|
||||
Append { values: Vec<Arc<TimelineItem>> },
|
||||
Clear,
|
||||
@@ -834,10 +816,10 @@ pub enum TimelineDiff {
|
||||
PushBack { value: Arc<TimelineItem> },
|
||||
PopFront,
|
||||
PopBack,
|
||||
Insert { index: usize, value: Arc<TimelineItem> },
|
||||
Set { index: usize, value: Arc<TimelineItem> },
|
||||
Remove { index: usize },
|
||||
Truncate { length: usize },
|
||||
Insert { index: u32, value: Arc<TimelineItem> },
|
||||
Set { index: u32, value: Arc<TimelineItem> },
|
||||
Remove { index: u32 },
|
||||
Truncate { length: u32 },
|
||||
Reset { values: Vec<Arc<TimelineItem>> },
|
||||
}
|
||||
|
||||
@@ -848,14 +830,18 @@ impl TimelineDiff {
|
||||
Self::Append { values: values.into_iter().map(TimelineItem::from_arc).collect() }
|
||||
}
|
||||
VectorDiff::Clear => Self::Clear,
|
||||
VectorDiff::Insert { index, value } => {
|
||||
Self::Insert { index, value: TimelineItem::from_arc(value) }
|
||||
VectorDiff::Insert { index, value } => Self::Insert {
|
||||
index: u32::try_from(index).unwrap(),
|
||||
value: TimelineItem::from_arc(value),
|
||||
},
|
||||
VectorDiff::Set { index, value } => Self::Set {
|
||||
index: u32::try_from(index).unwrap(),
|
||||
value: TimelineItem::from_arc(value),
|
||||
},
|
||||
VectorDiff::Truncate { length } => {
|
||||
Self::Truncate { length: u32::try_from(length).unwrap() }
|
||||
}
|
||||
VectorDiff::Set { index, value } => {
|
||||
Self::Set { index, value: TimelineItem::from_arc(value) }
|
||||
}
|
||||
VectorDiff::Truncate { length } => Self::Truncate { length },
|
||||
VectorDiff::Remove { index } => Self::Remove { index },
|
||||
VectorDiff::Remove { index } => Self::Remove { index: u32::try_from(index).unwrap() },
|
||||
VectorDiff::PushBack { value } => {
|
||||
Self::PushBack { value: TimelineItem::from_arc(value) }
|
||||
}
|
||||
@@ -871,94 +857,6 @@ impl TimelineDiff {
|
||||
}
|
||||
}
|
||||
|
||||
#[matrix_sdk_ffi_macros::export]
|
||||
impl TimelineDiff {
|
||||
pub fn change(&self) -> TimelineChange {
|
||||
match self {
|
||||
Self::Append { .. } => TimelineChange::Append,
|
||||
Self::Insert { .. } => TimelineChange::Insert,
|
||||
Self::Set { .. } => TimelineChange::Set,
|
||||
Self::Remove { .. } => TimelineChange::Remove,
|
||||
Self::PushBack { .. } => TimelineChange::PushBack,
|
||||
Self::PushFront { .. } => TimelineChange::PushFront,
|
||||
Self::PopBack => TimelineChange::PopBack,
|
||||
Self::PopFront => TimelineChange::PopFront,
|
||||
Self::Clear => TimelineChange::Clear,
|
||||
Self::Truncate { .. } => TimelineChange::Truncate,
|
||||
Self::Reset { .. } => TimelineChange::Reset,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(self: Arc<Self>) -> Option<Vec<Arc<TimelineItem>>> {
|
||||
let this = unwrap_or_clone_arc(self);
|
||||
as_variant!(this, Self::Append { values } => values)
|
||||
}
|
||||
|
||||
pub fn insert(self: Arc<Self>) -> Option<InsertData> {
|
||||
let this = unwrap_or_clone_arc(self);
|
||||
as_variant!(this, Self::Insert { index, value } => {
|
||||
InsertData { index: index.try_into().unwrap(), item: value }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set(self: Arc<Self>) -> Option<SetData> {
|
||||
let this = unwrap_or_clone_arc(self);
|
||||
as_variant!(this, Self::Set { index, value } => {
|
||||
SetData { index: index.try_into().unwrap(), item: value }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove(&self) -> Option<u32> {
|
||||
as_variant!(self, Self::Remove { index } => (*index).try_into().unwrap())
|
||||
}
|
||||
|
||||
pub fn push_back(self: Arc<Self>) -> Option<Arc<TimelineItem>> {
|
||||
let this = unwrap_or_clone_arc(self);
|
||||
as_variant!(this, Self::PushBack { value } => value)
|
||||
}
|
||||
|
||||
pub fn push_front(self: Arc<Self>) -> Option<Arc<TimelineItem>> {
|
||||
let this = unwrap_or_clone_arc(self);
|
||||
as_variant!(this, Self::PushFront { value } => value)
|
||||
}
|
||||
|
||||
pub fn reset(self: Arc<Self>) -> Option<Vec<Arc<TimelineItem>>> {
|
||||
let this = unwrap_or_clone_arc(self);
|
||||
as_variant!(this, Self::Reset { values } => values)
|
||||
}
|
||||
|
||||
pub fn truncate(&self) -> Option<u32> {
|
||||
as_variant!(self, Self::Truncate { length } => (*length).try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct InsertData {
|
||||
pub index: u32,
|
||||
pub item: Arc<TimelineItem>,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
pub struct SetData {
|
||||
pub index: u32,
|
||||
pub item: Arc<TimelineItem>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, uniffi::Enum)]
|
||||
pub enum TimelineChange {
|
||||
Append,
|
||||
Clear,
|
||||
Insert,
|
||||
Set,
|
||||
Remove,
|
||||
PushBack,
|
||||
PushFront,
|
||||
PopBack,
|
||||
PopFront,
|
||||
Truncate,
|
||||
Reset,
|
||||
}
|
||||
|
||||
#[derive(Clone, uniffi::Record)]
|
||||
pub struct TimelineUniqueId {
|
||||
id: String,
|
||||
@@ -1018,7 +916,11 @@ impl TimelineItem {
|
||||
#[derive(Clone, uniffi::Enum)]
|
||||
pub enum EventSendState {
|
||||
/// The local event has not been sent yet.
|
||||
NotSentYet,
|
||||
NotSentYet {
|
||||
/// The progress of the sending operation, if the event involves a media
|
||||
/// upload.
|
||||
progress: Option<MediaUploadProgress>,
|
||||
},
|
||||
|
||||
/// The local event has been sent to the server, but unsuccessfully: The
|
||||
/// sending has failed.
|
||||
@@ -1043,7 +945,9 @@ impl From<&matrix_sdk_ui::timeline::EventSendState> for EventSendState {
|
||||
use matrix_sdk_ui::timeline::EventSendState::*;
|
||||
|
||||
match value {
|
||||
NotSentYet => Self::NotSentYet,
|
||||
NotSentYet { progress } => {
|
||||
Self::NotSentYet { progress: progress.clone().map(|p| p.into()) }
|
||||
}
|
||||
SendingFailed { error, is_recoverable } => {
|
||||
let as_queue_wedge_error: matrix_sdk::QueueWedgeError = (&**error).into();
|
||||
Self::SendingFailed {
|
||||
@@ -1381,6 +1285,44 @@ impl LazyTimelineItemProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mimic the [`UiLatestEventValue`] type.
|
||||
#[derive(Clone, uniffi::Enum)]
|
||||
pub enum LatestEventValue {
|
||||
None,
|
||||
Remote {
|
||||
timestamp: Timestamp,
|
||||
sender: String,
|
||||
is_own: bool,
|
||||
profile: ProfileDetails,
|
||||
content: TimelineItemContent,
|
||||
},
|
||||
Local {
|
||||
timestamp: Timestamp,
|
||||
content: TimelineItemContent,
|
||||
is_sending: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<UiLatestEventValue> for LatestEventValue {
|
||||
fn from(value: UiLatestEventValue) -> Self {
|
||||
match value {
|
||||
UiLatestEventValue::None => Self::None,
|
||||
UiLatestEventValue::Remote { timestamp, sender, is_own, profile, content } => {
|
||||
Self::Remote {
|
||||
timestamp: timestamp.into(),
|
||||
sender: sender.to_string(),
|
||||
is_own,
|
||||
profile: profile.into(),
|
||||
content: content.into(),
|
||||
}
|
||||
}
|
||||
UiLatestEventValue::Local { timestamp, content, is_sending } => {
|
||||
Self::Local { timestamp: timestamp.into(), content: content.into(), is_sending }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-msc4274")]
|
||||
mod galleries {
|
||||
use std::{panic, sync::Arc};
|
||||
@@ -1394,6 +1336,7 @@ mod galleries {
|
||||
use matrix_sdk_common::executor::{AbortHandle, JoinHandle};
|
||||
use matrix_sdk_ui::timeline::GalleryConfig;
|
||||
use mime::Mime;
|
||||
use ruma::EventId;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::error;
|
||||
|
||||
@@ -1401,7 +1344,7 @@ mod galleries {
|
||||
error::RoomError,
|
||||
ruma::{AudioInfo, FileInfo, FormattedBody, ImageInfo, Mentions, VideoInfo},
|
||||
runtime::get_runtime_handle,
|
||||
timeline::{build_thumbnail_info, ReplyParameters, Timeline},
|
||||
timeline::{build_thumbnail_info, Timeline, UploadSource},
|
||||
};
|
||||
|
||||
#[derive(uniffi::Record)]
|
||||
@@ -1412,37 +1355,37 @@ mod galleries {
|
||||
formatted_caption: Option<FormattedBody>,
|
||||
/// Optional intentional mentions to be sent with the gallery.
|
||||
mentions: Option<Mentions>,
|
||||
/// Optional parameters for sending the media as (threaded) reply.
|
||||
reply_params: Option<ReplyParameters>,
|
||||
/// Optional Event ID to reply to.
|
||||
in_reply_to: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(uniffi::Enum)]
|
||||
pub enum GalleryItemInfo {
|
||||
Audio {
|
||||
audio_info: AudioInfo,
|
||||
filename: String,
|
||||
source: UploadSource,
|
||||
caption: Option<String>,
|
||||
formatted_caption: Option<FormattedBody>,
|
||||
},
|
||||
File {
|
||||
file_info: FileInfo,
|
||||
filename: String,
|
||||
source: UploadSource,
|
||||
caption: Option<String>,
|
||||
formatted_caption: Option<FormattedBody>,
|
||||
},
|
||||
Image {
|
||||
image_info: ImageInfo,
|
||||
filename: String,
|
||||
source: UploadSource,
|
||||
caption: Option<String>,
|
||||
formatted_caption: Option<FormattedBody>,
|
||||
thumbnail_path: Option<String>,
|
||||
thumbnail_source: Option<UploadSource>,
|
||||
},
|
||||
Video {
|
||||
video_info: VideoInfo,
|
||||
filename: String,
|
||||
source: UploadSource,
|
||||
caption: Option<String>,
|
||||
formatted_caption: Option<FormattedBody>,
|
||||
thumbnail_path: Option<String>,
|
||||
thumbnail_source: Option<UploadSource>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1456,12 +1399,12 @@ mod galleries {
|
||||
}
|
||||
}
|
||||
|
||||
fn filename(&self) -> &String {
|
||||
fn source(&self) -> &UploadSource {
|
||||
match self {
|
||||
GalleryItemInfo::Audio { filename, .. } => filename,
|
||||
GalleryItemInfo::File { filename, .. } => filename,
|
||||
GalleryItemInfo::Image { filename, .. } => filename,
|
||||
GalleryItemInfo::Video { filename, .. } => filename,
|
||||
GalleryItemInfo::File { source, .. } => source,
|
||||
GalleryItemInfo::Audio { source, .. } => source,
|
||||
GalleryItemInfo::Image { source, .. } => source,
|
||||
GalleryItemInfo::Video { source, .. } => source,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1507,11 +1450,17 @@ mod galleries {
|
||||
fn thumbnail(&self) -> Result<Option<Thumbnail>, RoomError> {
|
||||
match self {
|
||||
GalleryItemInfo::Audio { .. } | GalleryItemInfo::File { .. } => Ok(None),
|
||||
GalleryItemInfo::Image { image_info, thumbnail_path, .. } => {
|
||||
build_thumbnail_info(thumbnail_path.clone(), image_info.thumbnail_info.clone())
|
||||
GalleryItemInfo::Image { image_info, thumbnail_source, .. } => {
|
||||
build_thumbnail_info(
|
||||
thumbnail_source.as_ref().cloned(),
|
||||
image_info.thumbnail_info.clone(),
|
||||
)
|
||||
}
|
||||
GalleryItemInfo::Video { video_info, thumbnail_path, .. } => {
|
||||
build_thumbnail_info(thumbnail_path.clone(), video_info.thumbnail_info.clone())
|
||||
GalleryItemInfo::Video { video_info, thumbnail_source, .. } => {
|
||||
build_thumbnail_info(
|
||||
thumbnail_source.as_ref().cloned(),
|
||||
video_info.thumbnail_info.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1527,7 +1476,7 @@ mod galleries {
|
||||
let mime_type =
|
||||
mime_str.parse::<Mime>().map_err(|_| RoomError::InvalidAttachmentMimeType)?;
|
||||
Ok(matrix_sdk_ui::timeline::GalleryItemInfo {
|
||||
source: self.filename().into(),
|
||||
source: self.source().clone().into(),
|
||||
content_type: mime_type,
|
||||
attachment_info: self.attachment_info()?,
|
||||
caption: self.caption().clone(),
|
||||
@@ -1598,11 +1547,18 @@ mod galleries {
|
||||
params.formatted_caption.map(Into::into),
|
||||
);
|
||||
|
||||
let in_reply_to = params
|
||||
.in_reply_to
|
||||
.as_ref()
|
||||
.map(EventId::parse)
|
||||
.transpose()
|
||||
.map_err(|_| RoomError::InvalidRepliedToEventId)?;
|
||||
|
||||
let mut gallery_config = GalleryConfig::new()
|
||||
.caption(params.caption)
|
||||
.formatted_caption(formatted_caption)
|
||||
.mentions(params.mentions.map(Into::into))
|
||||
.reply(params.reply_params.map(|p| p.try_into()).transpose()?);
|
||||
.in_reply_to(in_reply_to);
|
||||
|
||||
for item_info in item_infos {
|
||||
gallery_config = gallery_config.add_item(item_info.try_into()?);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use matrix_sdk::crypto::types::events::UtdCause;
|
||||
use ruma::events::{room::MediaSource as RumaMediaSource, EventContent};
|
||||
use ruma::events::{room::MediaSource as RumaMediaSource, MessageLikeEventContent};
|
||||
|
||||
use super::{
|
||||
content::Reaction,
|
||||
|
||||
@@ -197,6 +197,15 @@ pub fn get_element_call_required_permissions(
|
||||
.chain(read_send.clone())
|
||||
.collect(),
|
||||
send: vec![
|
||||
// To notify other users that a call has started.
|
||||
WidgetEventFilter::MessageLikeWithType {
|
||||
event_type: "org.matrix.msc4075.rtc.notification".to_owned(),
|
||||
},
|
||||
// Also for call notifications, except this is the deprecated fallback type which
|
||||
// Element Call still sends.
|
||||
WidgetEventFilter::MessageLikeWithType {
|
||||
event_type: MessageLikeEventType::CallNotify.to_string(),
|
||||
},
|
||||
// To send the call participation state event (main MatrixRTC event).
|
||||
// This is required for legacy state events (using only one event for all devices with
|
||||
// a membership array). TODO: remove once legacy call member events are
|
||||
@@ -211,6 +220,12 @@ pub fn get_element_call_required_permissions(
|
||||
event_type: StateEventType::CallMember.to_string(),
|
||||
state_key: format!("{own_user_id}_{own_device_id}"),
|
||||
},
|
||||
// Same as above for [MSC3779] and [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143),
|
||||
// with application suffix
|
||||
WidgetEventFilter::StateWithTypeAndStateKey {
|
||||
event_type: StateEventType::CallMember.to_string(),
|
||||
state_key: format!("{own_user_id}_{own_device_id}_m.call"),
|
||||
},
|
||||
// The same as above but with an underscore.
|
||||
// To work around the issue that state events starting with `@` have to be Matrix id's
|
||||
// but we use mxId+deviceId.
|
||||
@@ -218,6 +233,11 @@ pub fn get_element_call_required_permissions(
|
||||
event_type: StateEventType::CallMember.to_string(),
|
||||
state_key: format!("_{own_user_id}_{own_device_id}"),
|
||||
},
|
||||
// Same as above for [MSC4143], with application suffix
|
||||
WidgetEventFilter::StateWithTypeAndStateKey {
|
||||
event_type: StateEventType::CallMember.to_string(),
|
||||
state_key: format!("_{own_user_id}_{own_device_id}_m.call"),
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.chain(read_send)
|
||||
@@ -497,9 +517,15 @@ mod tests {
|
||||
cap_assert(
|
||||
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@my_user:my_domain.org_ABCDEFGHI",
|
||||
);
|
||||
cap_assert(
|
||||
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@my_user:my_domain.org_ABCDEFGHI_m.call",
|
||||
);
|
||||
cap_assert(
|
||||
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#_@my_user:my_domain.org_ABCDEFGHI",
|
||||
);
|
||||
cap_assert(
|
||||
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#_@my_user:my_domain.org_ABCDEFGHI_m.call",
|
||||
);
|
||||
cap_assert("org.matrix.msc2762.send.event:org.matrix.rageshake_request");
|
||||
cap_assert("org.matrix.msc2762.send.event:io.element.call.encryption_keys");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"rust-analyzer.checkOnSave.command": "clippy",
|
||||
"rust-analyzer.checkOnSave.command": "check",
|
||||
"rust-analyzer.rustfmt.extraArgs": ["+nightly"]
|
||||
}
|
||||
|
||||
@@ -6,6 +6,74 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Unreleased] - ReleaseDate
|
||||
|
||||
## [0.14.0] - 2025-09-04
|
||||
|
||||
### Features
|
||||
- Add `SyncResponse::RoomUpdates::is_empty` to check if there were any room updates.
|
||||
([#5593](https://github.com/matrix-org/matrix-rust-sdk/pull/5593))
|
||||
- Add `EncryptionState::StateEncrypted` to represent rooms supporting encrypted
|
||||
state events. Feature-gated behind `experimental-encrypted-state-events`.
|
||||
([#5523](https://github.com/matrix-org/matrix-rust-sdk/pull/5523))
|
||||
- [**breaking**] The `state` field of `JoinedRoomUpdate` and `LeftRoomUpdate`
|
||||
now uses the `State` enum, depending on whether the state changes were
|
||||
received in the `state` field or the `state_after` field.
|
||||
([#5488](https://github.com/matrix-org/matrix-rust-sdk/pull/5488))
|
||||
- [**breaking**] `RoomCreateWithCreatorEventContent` has a new field
|
||||
`additional_creators` that allows to specify additional room creators beside
|
||||
the user sending the `m.room.create` event, introduced with room version 12.
|
||||
([#5436](https://github.com/matrix-org/matrix-rust-sdk/pull/5436))
|
||||
- [**breaking**] The `RoomInfo` method now remembers the inviter at the time
|
||||
when the `BaseClient::room_joined()` method was called. The caller is
|
||||
responsible to remember the inviter before a server request to join the room
|
||||
is made. The `RoomInfo::invite_accepted_at` method was removed, the
|
||||
`RoomInfo::invite_details` method returns both the timestamp and the
|
||||
inviter.
|
||||
([#5390](https://github.com/matrix-org/matrix-rust-sdk/pull/5390))
|
||||
|
||||
### Refactor
|
||||
- [**breaking**] The `Stripped` variants of `RawAnySyncOrStrippedTimelineEvent`,
|
||||
`RawAnySyncOrStrippedState` and `AnySyncOrStrippedState` use `StrippedState`
|
||||
instead of `AnyStrippedStateEvent`.
|
||||
([#5473](https://github.com/matrix-org/matrix-rust-sdk/pull/5473))
|
||||
- [**breaking**] The `stripped_state` field of `StateChanges` uses
|
||||
`StrippedState` instead of `AnyStrippedStateEvent`.
|
||||
([#5473](https://github.com/matrix-org/matrix-rust-sdk/pull/5473))
|
||||
- [**breaking**] `RelationalLinkedChunk::items` now takes a `RoomId` instead of an
|
||||
`&OwnedLinkedChunkId` parameter.
|
||||
([#5445](https://github.com/matrix-org/matrix-rust-sdk/pull/5445))
|
||||
- [**breaking**] Add an `IsPrefix = False` bound to the
|
||||
`get_state_event_static()`, `get_state_event_static_for_key()` and
|
||||
`get_state_events_static()`, `get_account_data_event_static()` and
|
||||
`get_room_account_data_event_static` methods of `StateStoreExt`. These methods
|
||||
only worked for events where the full event type is statically-known, and this
|
||||
is now enforced at compile-time. The matching non-`static` methods of
|
||||
`StateStore` can be used instead for event types with a variable suffix.
|
||||
([#5444](https://github.com/matrix-org/matrix-rust-sdk/pull/5444))
|
||||
- [**breaking**] `SyncOrStrippedState<RoomPowerLevelsEventContent>::power_levels()`
|
||||
takes `AuthorizationRules` and a list of creators, because creators can have
|
||||
infinite power levels, as introduced in room version 12.
|
||||
([#5436](https://github.com/matrix-org/matrix-rust-sdk/pull/5436))
|
||||
- [**breaking**] `RoomMember::power_level()` and
|
||||
`RoomMember::normalized_power_level()` now use `UserPowerLevel` to represent
|
||||
power levels instead of `i64` to differentiate the infinite power level of
|
||||
creators, as introduced in room version 12.
|
||||
([#5436](https://github.com/matrix-org/matrix-rust-sdk/pull/5436))
|
||||
- [**breaking**] The `creator()` methods of `Room` and `RoomInfo` have been
|
||||
renamed to `creators()` and can now return a list of user IDs, to reflect that
|
||||
a room can have several creators, as introduced in room version 12.
|
||||
([#5436](https://github.com/matrix-org/matrix-rust-sdk/pull/5436))
|
||||
- [**breaking**] `RoomInfo::room_version_or_default()` was replaced with
|
||||
`room_version_rules_or_default()`. The room version should only be used for
|
||||
display purposes. The rules contain flags for all the differences in behavior
|
||||
between all known room versions.
|
||||
([#5337](https://github.com/matrix-org/matrix-rust-sdk/pull/5337))
|
||||
- [**breaking**] `MinimalStateEvent::redact()` takes `RedactionRules` instead of
|
||||
a `RoomVersionId`.
|
||||
([#5337](https://github.com/matrix-org/matrix-rust-sdk/pull/5337))
|
||||
- [**breaking**] The `event_id` field of `PredecessorRoom` was removed, due to
|
||||
its removal in the Matrix specification with MSC4291.
|
||||
([#5419](https://github.com/matrix-org/matrix-rust-sdk/pull/5419))
|
||||
|
||||
## [0.13.0] - 2025-07-10
|
||||
|
||||
### Features
|
||||
@@ -50,8 +118,8 @@ No notable changes in this release.
|
||||
- `EventCacheStoreMedia` has a new method `last_media_cleanup_time_inner`
|
||||
- There are new `'static` bounds in `MediaService` for the media cache stores
|
||||
- `event_cache::store::MemoryStore` implements `Clone`.
|
||||
- `BaseClient` now has a `handle_verification_events` field which is `true` by
|
||||
default and can be negated so the `NotificationClient` won't handle received
|
||||
- `BaseClient` now has a `handle_verification_events` field which is `true` by
|
||||
default and can be negated so the `NotificationClient` won't handle received
|
||||
verification events too, causing errors in the `VerificationMachine`.
|
||||
- [**breaking**] `Room::is_encryption_state_synced` has been removed
|
||||
([#4777](https://github.com/matrix-org/matrix-rust-sdk/pull/4777))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
authors = ["Damir Jelić <poljar@termina.org.uk>"]
|
||||
description = "The base component to build a Matrix client library."
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
|
||||
license = "Apache-2.0"
|
||||
@@ -9,7 +9,7 @@ name = "matrix-sdk-base"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-org/matrix-rust-sdk"
|
||||
rust-version.workspace = true
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -25,8 +25,21 @@ js = [
|
||||
"matrix-sdk-store-encryption/js",
|
||||
]
|
||||
qrcode = ["matrix-sdk-crypto?/qrcode"]
|
||||
automatic-room-key-forwarding = ["matrix-sdk-crypto?/automatic-room-key-forwarding"]
|
||||
experimental-send-custom-to-device = ["matrix-sdk-crypto?/experimental-send-custom-to-device"]
|
||||
automatic-room-key-forwarding = [
|
||||
"matrix-sdk-crypto?/automatic-room-key-forwarding",
|
||||
]
|
||||
experimental-send-custom-to-device = [
|
||||
"matrix-sdk-crypto?/experimental-send-custom-to-device",
|
||||
]
|
||||
|
||||
# Enable experimental support for encrypting state events; see
|
||||
# https://github.com/matrix-org/matrix-rust-sdk/issues/5397.
|
||||
experimental-encrypted-state-events = [
|
||||
"e2e-encryption",
|
||||
"ruma/unstable-msc3414",
|
||||
"matrix-sdk-crypto?/experimental-encrypted-state-events"
|
||||
]
|
||||
|
||||
uniffi = ["dep:uniffi", "matrix-sdk-crypto?/uniffi", "matrix-sdk-common/uniffi"]
|
||||
|
||||
# Private feature, see
|
||||
@@ -58,7 +71,7 @@ assert_matches = { workspace = true, optional = true }
|
||||
assert_matches2 = { workspace = true, optional = true }
|
||||
async-trait.workspace = true
|
||||
bitflags = { workspace = true, features = ["serde"] }
|
||||
decancer = "3.3.0"
|
||||
decancer = "3.3.3"
|
||||
eyeball = { workspace = true, features = ["async-lock"] }
|
||||
eyeball-im.workspace = true
|
||||
futures-util.workspace = true
|
||||
@@ -69,7 +82,7 @@ matrix-sdk-crypto = { workspace = true, optional = true }
|
||||
matrix-sdk-store-encryption.workspace = true
|
||||
matrix-sdk-test = { workspace = true, optional = true }
|
||||
once_cell.workspace = true
|
||||
regex = "1.11.1"
|
||||
regex = "1.11.2"
|
||||
ruma = { workspace = true, features = [
|
||||
"canonical-json",
|
||||
"unstable-msc2867",
|
||||
@@ -93,6 +106,7 @@ assign = "1.1.1"
|
||||
futures-executor.workspace = true
|
||||
http.workspace = true
|
||||
matrix-sdk-test.workspace = true
|
||||
matrix-sdk-test-utils.workspace = true
|
||||
similar-asserts.workspace = true
|
||||
stream_assert.workspace = true
|
||||
|
||||
@@ -101,6 +115,7 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dev-dependencies]
|
||||
wasm-bindgen-test.workspace = true
|
||||
gloo-timers = { workspace = true, features = ["futures"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -24,35 +24,37 @@ use std::{
|
||||
use eyeball::{SharedObservable, Subscriber};
|
||||
use eyeball_im::{Vector, VectorDiff};
|
||||
use futures_util::Stream;
|
||||
use matrix_sdk_common::timer;
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use matrix_sdk_crypto::{
|
||||
store::DynCryptoStore, types::requests::ToDeviceRequest, CollectStrategy, DecryptionSettings,
|
||||
EncryptionSettings, OlmError, OlmMachine, TrustRequirement,
|
||||
CollectStrategy, DecryptionSettings, EncryptionSettings, OlmError, OlmMachine,
|
||||
TrustRequirement, store::DynCryptoStore, types::requests::ToDeviceRequest,
|
||||
};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use ruma::events::room::{history_visibility::HistoryVisibility, member::MembershipState};
|
||||
#[cfg(doc)]
|
||||
use ruma::DeviceId;
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use ruma::events::room::{history_visibility::HistoryVisibility, member::MembershipState};
|
||||
use ruma::{
|
||||
MilliSecondsSinceUnixEpoch, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
api::client::{self as api, sync::sync_events::v5},
|
||||
events::{
|
||||
StateEvent, StateEventType,
|
||||
ignored_user_list::IgnoredUserListEventContent,
|
||||
push_rules::{PushRulesEvent, PushRulesEventContent},
|
||||
room::member::SyncRoomMemberEvent,
|
||||
StateEvent, StateEventType,
|
||||
},
|
||||
push::Ruleset,
|
||||
time::Instant,
|
||||
OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
use tokio::sync::{broadcast, Mutex};
|
||||
use tokio::sync::{Mutex, broadcast};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use tracing::{debug, enabled, info, instrument, warn, Level};
|
||||
use tracing::{Level, debug, enabled, info, instrument, warn};
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use crate::RoomMemberships;
|
||||
use crate::{
|
||||
InviteAcceptanceDetails, RoomStateFilter, SessionMeta,
|
||||
deserialized_responses::DisplayName,
|
||||
error::{Error, Result},
|
||||
event_cache::store::EventCacheStoreLock,
|
||||
@@ -61,12 +63,11 @@ use crate::{
|
||||
Room, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomMembersUpdate, RoomState,
|
||||
},
|
||||
store::{
|
||||
ambiguity_map::AmbiguityCache, BaseStateStore, DynStateStore, MemoryStore,
|
||||
Result as StoreResult, RoomLoadSettings, StateChanges, StateStoreDataKey,
|
||||
StateStoreDataValue, StateStoreExt, StoreConfig,
|
||||
BaseStateStore, DynStateStore, MemoryStore, Result as StoreResult, RoomLoadSettings,
|
||||
StateChanges, StateStoreDataKey, StateStoreDataValue, StateStoreExt, StoreConfig,
|
||||
ambiguity_map::AmbiguityCache,
|
||||
},
|
||||
sync::{RoomUpdates, SyncResponse},
|
||||
RoomStateFilter, SessionMeta,
|
||||
};
|
||||
|
||||
/// A no (network) IO client implementation.
|
||||
@@ -76,7 +77,7 @@ use crate::{
|
||||
/// rather through `matrix_sdk::Client`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use matrix_sdk_base::{store::StoreConfig, BaseClient, ThreadingSupport};
|
||||
/// use matrix_sdk_base::{BaseClient, ThreadingSupport, store::StoreConfig};
|
||||
///
|
||||
/// let client = BaseClient::new(
|
||||
/// StoreConfig::new("cross-process-holder-name".to_owned()),
|
||||
@@ -151,9 +152,16 @@ impl fmt::Debug for BaseClient {
|
||||
/// explicitly opted into).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ThreadingSupport {
|
||||
/// Threading enabled
|
||||
Enabled,
|
||||
/// Threading disabled
|
||||
/// Threading enabled.
|
||||
Enabled {
|
||||
/// Enable client-wide thread subscriptions support (MSC4306 / MSC4308).
|
||||
///
|
||||
/// This may cause filtering out of thread subscriptions, and loading
|
||||
/// the thread subscriptions via the sliding sync extension,
|
||||
/// when the room list service is being used.
|
||||
with_subscriptions: bool,
|
||||
},
|
||||
/// Threading disabled.
|
||||
Disabled,
|
||||
}
|
||||
|
||||
@@ -273,7 +281,9 @@ impl BaseClient {
|
||||
|
||||
/// Get a stream of all the rooms changes, in addition to the existing
|
||||
/// rooms.
|
||||
pub fn rooms_stream(&self) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>>) {
|
||||
pub fn rooms_stream(
|
||||
&self,
|
||||
) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + use<>) {
|
||||
self.state_store.rooms_stream()
|
||||
}
|
||||
|
||||
@@ -432,21 +442,36 @@ impl BaseClient {
|
||||
///
|
||||
/// Update the internal and cached state accordingly. Return the final Room.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `room_id` - The unique ID identifying the joined room.
|
||||
/// * `inviter` - When joining this room in response to an invitation, the
|
||||
/// inviter should be recorded before sending the join request to the
|
||||
/// server. Providing the inviter here ensures that the
|
||||
/// [`InviteAcceptanceDetails`] are stored for this room.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use matrix_sdk_base::{BaseClient, store::StoreConfig, RoomState, ThreadingSupport};
|
||||
/// # use ruma::OwnedRoomId;
|
||||
/// # use ruma::{OwnedRoomId, OwnedUserId, RoomId};
|
||||
/// # async {
|
||||
/// # let client = BaseClient::new(StoreConfig::new("example".to_owned()), ThreadingSupport::Disabled);
|
||||
/// # async fn send_join_request() -> anyhow::Result<OwnedRoomId> { todo!() }
|
||||
/// # async fn maybe_get_inviter(room_id: &RoomId) -> anyhow::Result<Option<OwnedUserId>> { todo!() }
|
||||
/// # let room_id: &RoomId = todo!();
|
||||
/// let maybe_inviter = maybe_get_inviter(room_id).await?;
|
||||
/// let room_id = send_join_request().await?;
|
||||
/// let room = client.room_joined(&room_id).await?;
|
||||
/// let room = client.room_joined(&room_id, maybe_inviter).await?;
|
||||
///
|
||||
/// assert_eq!(room.state(), RoomState::Joined);
|
||||
/// # anyhow::Ok(()) };
|
||||
/// # matrix_sdk_test::TestResult::Ok(()) };
|
||||
/// ```
|
||||
pub async fn room_joined(&self, room_id: &RoomId) -> Result<Room> {
|
||||
pub async fn room_joined(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
inviter: Option<OwnedUserId>,
|
||||
) -> Result<Room> {
|
||||
let room = self.state_store.get_or_create_room(
|
||||
room_id,
|
||||
RoomState::Joined,
|
||||
@@ -459,10 +484,15 @@ impl BaseClient {
|
||||
let _sync_lock = self.sync_lock().lock().await;
|
||||
|
||||
let mut room_info = room.clone_info();
|
||||
let previous_state = room.state();
|
||||
|
||||
room_info.mark_as_joined();
|
||||
room_info.mark_state_partially_synced();
|
||||
room_info.mark_members_missing(); // the own member event changed
|
||||
|
||||
// If our previous state was an invite and we're now in the joined state, this
|
||||
// means that the user has explicitly accepted the invite. Let's
|
||||
// remember when this has happened.
|
||||
// means that the user has explicitly accepted an invite. Let's
|
||||
// remember some details about the invite.
|
||||
//
|
||||
// This is somewhat of a workaround for our lack of cryptographic membership.
|
||||
// Later on we will decide if historic room keys should be accepted
|
||||
@@ -470,14 +500,16 @@ impl BaseClient {
|
||||
// key bundle shortly after, we might accept it. If we don't do
|
||||
// this, the homeserver could trick us into accepting any historic room key
|
||||
// bundle.
|
||||
if room.state() == RoomState::Invited {
|
||||
room_info.set_invite_accepted_now();
|
||||
if previous_state == RoomState::Invited
|
||||
&& let Some(inviter) = inviter
|
||||
{
|
||||
let details = InviteAcceptanceDetails {
|
||||
invite_accepted_at: MilliSecondsSinceUnixEpoch::now(),
|
||||
inviter,
|
||||
};
|
||||
room_info.set_invite_acceptance_details(details);
|
||||
}
|
||||
|
||||
room_info.mark_as_joined();
|
||||
room_info.mark_state_partially_synced();
|
||||
room_info.mark_members_missing(); // the own member event changed
|
||||
|
||||
let mut changes = StateChanges::default();
|
||||
changes.add_room(room_info.clone());
|
||||
|
||||
@@ -569,7 +601,12 @@ impl BaseClient {
|
||||
let processors::e2ee::to_device::Output {
|
||||
processed_to_device_events: to_device,
|
||||
room_key_updates,
|
||||
} = processors::e2ee::to_device::from_sync_v2(&response, olm_machine.as_ref()).await?;
|
||||
} = processors::e2ee::to_device::from_sync_v2(
|
||||
&response,
|
||||
olm_machine.as_ref(),
|
||||
&self.decryption_settings,
|
||||
)
|
||||
.await?;
|
||||
|
||||
processors::latest_event::decrypt_from_rooms(
|
||||
&mut context,
|
||||
@@ -595,14 +632,25 @@ impl BaseClient {
|
||||
.events
|
||||
.into_iter()
|
||||
.map(|raw| {
|
||||
use matrix_sdk_common::deserialized_responses::{
|
||||
ProcessedToDeviceEvent, ToDeviceUnableToDecryptInfo,
|
||||
ToDeviceUnableToDecryptReason,
|
||||
};
|
||||
|
||||
if let Ok(Some(event_type)) = raw.get_field::<String>("type") {
|
||||
if event_type == "m.room.encrypted" {
|
||||
matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent::UnableToDecrypt(raw)
|
||||
ProcessedToDeviceEvent::UnableToDecrypt {
|
||||
encrypted_event: raw,
|
||||
utd_info: ToDeviceUnableToDecryptInfo {
|
||||
reason: ToDeviceUnableToDecryptReason::EncryptionIsDisabled,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent::PlainText(raw)
|
||||
ProcessedToDeviceEvent::PlainText(raw)
|
||||
}
|
||||
} else {
|
||||
matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent::Invalid(raw) // Exclude events with no type
|
||||
// Exclude events with no type
|
||||
ProcessedToDeviceEvent::Invalid(raw)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@@ -837,14 +885,11 @@ impl BaseClient {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let StateEvent::Original(e) = &member {
|
||||
if let Some(d) = &e.content.displayname {
|
||||
let display_name = DisplayName::new(d);
|
||||
ambiguity_map
|
||||
.entry(display_name)
|
||||
.or_default()
|
||||
.insert(member.state_key().clone());
|
||||
}
|
||||
if let StateEvent::Original(e) = &member
|
||||
&& let Some(d) = &e.content.displayname
|
||||
{
|
||||
let display_name = DisplayName::new(d);
|
||||
ambiguity_map.entry(display_name).or_default().insert(member.state_key().clone());
|
||||
}
|
||||
|
||||
let sync_member: SyncRoomMemberEvent = member.clone().into();
|
||||
@@ -1016,9 +1061,10 @@ impl BaseClient {
|
||||
&self,
|
||||
global_account_data_processor: &processors::account_data::Global,
|
||||
) -> Result<Ruleset> {
|
||||
let _timer = timer!(Level::TRACE, "get_push_rules");
|
||||
if let Some(event) = global_account_data_processor
|
||||
.push_rules()
|
||||
.and_then(|ev| ev.deserialize_as::<PushRulesEvent>().ok())
|
||||
.and_then(|ev| ev.deserialize_as_unchecked::<PushRulesEvent>().ok())
|
||||
{
|
||||
Ok(event.content.global)
|
||||
} else if let Some(event) = self
|
||||
@@ -1131,16 +1177,16 @@ impl From<&v5::Request> for RequestedRequiredStates {
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use assert_matches2::assert_let;
|
||||
use assert_matches2::{assert_let, assert_matches};
|
||||
use futures_util::FutureExt as _;
|
||||
use matrix_sdk_test::{
|
||||
async_test, event_factory::EventFactory, ruma_response_from_json, InvitedRoomBuilder,
|
||||
LeftRoomBuilder, StateTestEvent, StrippedStateTestEvent, SyncResponseBuilder, BOB,
|
||||
BOB, InvitedRoomBuilder, LeftRoomBuilder, StateTestEvent, StrippedStateTestEvent,
|
||||
SyncResponseBuilder, async_test, event_factory::EventFactory, ruma_response_from_json,
|
||||
};
|
||||
use ruma::{
|
||||
api::client::{self as api, sync::sync_events::v5},
|
||||
event_id,
|
||||
events::{room::member::MembershipState, StateEventType},
|
||||
events::{StateEventType, room::member::MembershipState},
|
||||
room_id,
|
||||
serde::Raw,
|
||||
user_id,
|
||||
@@ -1149,10 +1195,10 @@ mod tests {
|
||||
|
||||
use super::{BaseClient, RequestedRequiredStates};
|
||||
use crate::{
|
||||
RoomDisplayName, RoomState, SessionMeta,
|
||||
client::ThreadingSupport,
|
||||
store::{RoomLoadSettings, StateStoreExt, StoreConfig},
|
||||
test_utils::logged_in_base_client,
|
||||
RoomDisplayName, RoomState, SessionMeta,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -1662,18 +1708,10 @@ mod tests {
|
||||
let mut subscriber = client.subscribe_to_ignore_user_list_changes();
|
||||
assert!(subscriber.next().now_or_never().is_none());
|
||||
|
||||
let f = EventFactory::new();
|
||||
let mut sync_builder = SyncResponseBuilder::new();
|
||||
let response = sync_builder
|
||||
.add_global_account_data_event(matrix_sdk_test::GlobalAccountDataTestEvent::Custom(
|
||||
json!({
|
||||
"content": {
|
||||
"ignored_users": {
|
||||
*BOB: {}
|
||||
}
|
||||
},
|
||||
"type": "m.ignored_user_list",
|
||||
}),
|
||||
))
|
||||
.add_global_account_data(f.ignored_user_list([(*BOB).into()]))
|
||||
.build_sync_response();
|
||||
client.receive_sync_response(response).await.unwrap();
|
||||
|
||||
@@ -1682,16 +1720,7 @@ mod tests {
|
||||
|
||||
// Receive the same response.
|
||||
let response = sync_builder
|
||||
.add_global_account_data_event(matrix_sdk_test::GlobalAccountDataTestEvent::Custom(
|
||||
json!({
|
||||
"content": {
|
||||
"ignored_users": {
|
||||
*BOB: {}
|
||||
}
|
||||
},
|
||||
"type": "m.ignored_user_list",
|
||||
}),
|
||||
))
|
||||
.add_global_account_data(f.ignored_user_list([(*BOB).into()]))
|
||||
.build_sync_response();
|
||||
client.receive_sync_response(response).await.unwrap();
|
||||
|
||||
@@ -1699,16 +1728,8 @@ mod tests {
|
||||
assert!(subscriber.next().now_or_never().is_none());
|
||||
|
||||
// Now remove Bob from the ignored list.
|
||||
let response = sync_builder
|
||||
.add_global_account_data_event(matrix_sdk_test::GlobalAccountDataTestEvent::Custom(
|
||||
json!({
|
||||
"content": {
|
||||
"ignored_users": {}
|
||||
},
|
||||
"type": "m.ignored_user_list",
|
||||
}),
|
||||
))
|
||||
.build_sync_response();
|
||||
let response =
|
||||
sync_builder.add_global_account_data(f.ignored_user_list([])).build_sync_response();
|
||||
client.receive_sync_response(response).await.unwrap();
|
||||
|
||||
assert_let!(Some(ignored) = subscriber.next().await);
|
||||
@@ -1721,17 +1742,9 @@ mod tests {
|
||||
let client = logged_in_base_client(None).await;
|
||||
|
||||
let mut sync_builder = SyncResponseBuilder::new();
|
||||
let f = EventFactory::new();
|
||||
let response = sync_builder
|
||||
.add_global_account_data_event(matrix_sdk_test::GlobalAccountDataTestEvent::Custom(
|
||||
json!({
|
||||
"content": {
|
||||
"ignored_users": {
|
||||
ignored_user_id: {}
|
||||
}
|
||||
},
|
||||
"type": "m.ignored_user_list",
|
||||
}),
|
||||
))
|
||||
.add_global_account_data(f.ignored_user_list([ignored_user_id.to_owned()]))
|
||||
.build_sync_response();
|
||||
client.receive_sync_response(response).await.unwrap();
|
||||
|
||||
@@ -1739,8 +1752,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_joined_at_timestamp_is_set() {
|
||||
let client = logged_in_base_client(None).await;
|
||||
async fn test_invite_details_are_set() {
|
||||
let user_id = user_id!("@alice:localhost");
|
||||
let client = logged_in_base_client(Some(user_id)).await;
|
||||
let invited_room_id = room_id!("!invited:localhost");
|
||||
let unknown_room_id = room_id!("!unknown:localhost");
|
||||
|
||||
@@ -1757,27 +1771,41 @@ mod tests {
|
||||
.expect("The sync should have created a room in the invited state");
|
||||
|
||||
assert_eq!(invited_room.state(), RoomState::Invited);
|
||||
assert!(invited_room.inner.get().invite_accepted_at().is_none());
|
||||
assert!(invited_room.invite_acceptance_details().is_none());
|
||||
|
||||
// Now we join the room.
|
||||
let joined_room = client
|
||||
.room_joined(invited_room_id)
|
||||
.room_joined(invited_room_id, Some(user_id.to_owned()))
|
||||
.await
|
||||
.expect("We should be able to mark a room as joined");
|
||||
|
||||
// Yup, there's a timestamp now.
|
||||
// Yup, we now have some invite details.
|
||||
assert_eq!(joined_room.state(), RoomState::Joined);
|
||||
assert!(joined_room.inner.get().invite_accepted_at().is_some());
|
||||
assert_matches!(joined_room.invite_acceptance_details(), Some(details));
|
||||
assert_eq!(details.inviter, user_id);
|
||||
|
||||
// If we didn't know about the room before the join, we assume that there wasn't
|
||||
// an invite and we don't record the timestamp.
|
||||
assert!(client.get_room(unknown_room_id).is_none());
|
||||
let unknown_room = client
|
||||
.room_joined(unknown_room_id)
|
||||
.room_joined(unknown_room_id, Some(user_id.to_owned()))
|
||||
.await
|
||||
.expect("We should be able to mark a room as joined");
|
||||
|
||||
assert_eq!(unknown_room.state(), RoomState::Joined);
|
||||
assert!(unknown_room.inner.get().invite_accepted_at().is_none());
|
||||
assert!(unknown_room.invite_acceptance_details().is_none());
|
||||
|
||||
sync_builder.clear();
|
||||
let response =
|
||||
sync_builder.add_left_room(LeftRoomBuilder::new(invited_room_id)).build_sync_response();
|
||||
client.receive_sync_response(response).await.unwrap();
|
||||
|
||||
// Now that we left the room, we shouldn't have any details anymore.
|
||||
let left_room = client
|
||||
.get_room(invited_room_id)
|
||||
.expect("The sync should have created a room in the invited state");
|
||||
|
||||
assert_eq!(left_room.state(), RoomState::Left);
|
||||
assert!(left_room.invite_acceptance_details().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,17 +20,18 @@ pub use matrix_sdk_common::deserialized_responses::*;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use ruma::{
|
||||
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, UInt, UserId,
|
||||
events::{
|
||||
AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, EventContentFromType,
|
||||
PossiblyRedactedStateEventContent, RedactContent, RedactedStateEventContent,
|
||||
StateEventContent, StaticStateEventContent, StrippedStateEvent, SyncStateEvent,
|
||||
room::{
|
||||
member::{MembershipState, RoomMemberEvent, RoomMemberEventContent},
|
||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
},
|
||||
AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, EventContentFromType,
|
||||
PossiblyRedactedStateEventContent, RedactContent, RedactedStateEventContent,
|
||||
StateEventContent, StaticStateEventContent, StrippedStateEvent, SyncStateEvent,
|
||||
},
|
||||
room_version_rules::AuthorizationRules,
|
||||
serde::Raw,
|
||||
EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, UInt, UserId,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use unicode_normalization::UnicodeNormalization;
|
||||
@@ -304,8 +305,8 @@ impl RawAnySyncOrStrippedState {
|
||||
C::Redacted: RedactedStateEventContent,
|
||||
{
|
||||
match self {
|
||||
Self::Sync(raw) => RawSyncOrStrippedState::Sync(raw.cast()),
|
||||
Self::Stripped(raw) => RawSyncOrStrippedState::Stripped(raw.cast()),
|
||||
Self::Sync(raw) => RawSyncOrStrippedState::Sync(raw.cast_unchecked()),
|
||||
Self::Stripped(raw) => RawSyncOrStrippedState::Stripped(raw.cast_unchecked()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -517,10 +518,14 @@ impl MemberEvent {
|
||||
|
||||
impl SyncOrStrippedState<RoomPowerLevelsEventContent> {
|
||||
/// The power levels of the event.
|
||||
pub fn power_levels(&self) -> RoomPowerLevels {
|
||||
pub fn power_levels(
|
||||
&self,
|
||||
rules: &AuthorizationRules,
|
||||
creators: Vec<OwnedUserId>,
|
||||
) -> RoomPowerLevels {
|
||||
match self {
|
||||
Self::Sync(e) => e.power_levels(),
|
||||
Self::Stripped(e) => e.power_levels(),
|
||||
Self::Sync(e) => e.power_levels(rules, creators),
|
||||
Self::Stripped(e) => e.power_levels(rules, creators),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,31 +17,34 @@
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use assert_matches2::assert_let;
|
||||
use matrix_sdk_common::{
|
||||
deserialized_responses::{
|
||||
AlgorithmInfo, DecryptedRoomEvent, EncryptionInfo, TimelineEvent, TimelineEventKind,
|
||||
VerificationState,
|
||||
},
|
||||
linked_chunk::{
|
||||
lazy_loader, ChunkContent, ChunkIdentifier as CId, LinkedChunkId, Position, Update,
|
||||
ChunkContent, ChunkIdentifier as CId, LinkedChunkId, Position, Update, lazy_loader,
|
||||
},
|
||||
};
|
||||
use matrix_sdk_test::{event_factory::EventFactory, ALICE, DEFAULT_TEST_ROOM_ID};
|
||||
use matrix_sdk_test::{ALICE, DEFAULT_TEST_ROOM_ID, event_factory::EventFactory};
|
||||
use ruma::{
|
||||
EventId, RoomId,
|
||||
api::client::media::get_content_thumbnail::v3::Method,
|
||||
event_id,
|
||||
events::{
|
||||
AnyMessageLikeEvent, AnyTimelineEvent,
|
||||
relation::RelationType,
|
||||
room::{message::RoomMessageEventContentWithoutRelation, MediaSource},
|
||||
room::{MediaSource, message::RoomMessageEventContentWithoutRelation},
|
||||
},
|
||||
mxc_uri,
|
||||
push::Action,
|
||||
room_id, uint, EventId, RoomId,
|
||||
room_id, uint,
|
||||
};
|
||||
|
||||
use super::{media::IgnoreMediaRetentionPolicy, DynEventCacheStore};
|
||||
use super::{DynEventCacheStore, media::IgnoreMediaRetentionPolicy};
|
||||
use crate::{
|
||||
event_cache::{store::DEFAULT_CHUNK_CAPACITY, Gap},
|
||||
event_cache::{Gap, store::DEFAULT_CHUNK_CAPACITY},
|
||||
media::{MediaFormat, MediaRequestParameters, MediaThumbnailSettings},
|
||||
};
|
||||
|
||||
@@ -74,7 +77,7 @@ pub fn make_test_event_with_event_id(
|
||||
if let Some(event_id) = event_id {
|
||||
builder = builder.event_id(event_id);
|
||||
}
|
||||
let event = builder.into_raw_timeline().cast();
|
||||
let event = builder.into_raw();
|
||||
|
||||
TimelineEvent::from_decrypted(
|
||||
DecryptedRoomEvent { event, encryption_info, unsigned_encryption_info: None },
|
||||
@@ -103,7 +106,7 @@ pub fn check_test_event(event: &TimelineEvent, text: &str) {
|
||||
|
||||
// Check event.
|
||||
let deserialized = d.event.deserialize().unwrap();
|
||||
assert_matches!(deserialized, ruma::events::AnyMessageLikeEvent::RoomMessage(msg) => {
|
||||
assert_matches!(deserialized, AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::RoomMessage(msg)) => {
|
||||
assert_eq!(msg.as_original().unwrap().content.body(), text);
|
||||
});
|
||||
});
|
||||
@@ -153,6 +156,10 @@ pub trait EventCacheStoreIntegrationTests {
|
||||
|
||||
/// Test that saving an event works as expected.
|
||||
async fn test_save_event(&self);
|
||||
|
||||
/// Test multiple things related to distinguishing a thread linked chunk
|
||||
/// from a room linked chunk.
|
||||
async fn test_thread_vs_room_linked_chunk(&self);
|
||||
}
|
||||
|
||||
impl EventCacheStoreIntegrationTests for DynEventCacheStore {
|
||||
@@ -760,31 +767,39 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore {
|
||||
.unwrap();
|
||||
|
||||
// Sanity check: both linked chunks can be reloaded.
|
||||
assert!(lazy_loader::from_all_chunks::<3, _, _>(
|
||||
self.load_all_chunks(linked_chunk_id0).await.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
.is_some());
|
||||
assert!(lazy_loader::from_all_chunks::<3, _, _>(
|
||||
self.load_all_chunks(linked_chunk_id1).await.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
.is_some());
|
||||
assert!(
|
||||
lazy_loader::from_all_chunks::<3, _, _>(
|
||||
self.load_all_chunks(linked_chunk_id0).await.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
.is_some()
|
||||
);
|
||||
assert!(
|
||||
lazy_loader::from_all_chunks::<3, _, _>(
|
||||
self.load_all_chunks(linked_chunk_id1).await.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
.is_some()
|
||||
);
|
||||
|
||||
// Clear the chunks.
|
||||
self.clear_all_linked_chunks().await.unwrap();
|
||||
|
||||
// Both rooms now have no linked chunk.
|
||||
assert!(lazy_loader::from_all_chunks::<3, _, _>(
|
||||
self.load_all_chunks(linked_chunk_id0).await.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
assert!(lazy_loader::from_all_chunks::<3, _, _>(
|
||||
self.load_all_chunks(linked_chunk_id1).await.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
assert!(
|
||||
lazy_loader::from_all_chunks::<3, _, _>(
|
||||
self.load_all_chunks(linked_chunk_id0).await.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
lazy_loader::from_all_chunks::<3, _, _>(
|
||||
self.load_all_chunks(linked_chunk_id1).await.unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
async fn test_remove_room(&self) {
|
||||
@@ -970,19 +985,21 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore {
|
||||
assert_eq!(event.event_id(), event_comte.event_id());
|
||||
|
||||
// Now let's try to find an event that exists, but not in the expected room.
|
||||
assert!(self
|
||||
.find_event(room_id, event_gruyere.event_id().unwrap().as_ref())
|
||||
.await
|
||||
.expect("failed to query for finding an event")
|
||||
.is_none());
|
||||
assert!(
|
||||
self.find_event(room_id, event_gruyere.event_id().unwrap().as_ref())
|
||||
.await
|
||||
.expect("failed to query for finding an event")
|
||||
.is_none()
|
||||
);
|
||||
|
||||
// Clearing the rooms also clears the event's storage.
|
||||
self.clear_all_linked_chunks().await.expect("failed to clear all rooms chunks");
|
||||
assert!(self
|
||||
.find_event(room_id, event_comte.event_id().unwrap().as_ref())
|
||||
.await
|
||||
.expect("failed to query for finding an event")
|
||||
.is_none());
|
||||
assert!(
|
||||
self.find_event(room_id, event_comte.event_id().unwrap().as_ref())
|
||||
.await
|
||||
.expect("failed to query for finding an event")
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
async fn test_find_event_relations(&self) {
|
||||
@@ -1029,12 +1046,16 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore {
|
||||
let relations = self.find_event_relations(room_id, eid1, None).await.unwrap();
|
||||
assert_eq!(relations.len(), 2);
|
||||
// The position is `None` for items outside the linked chunk.
|
||||
assert!(relations
|
||||
.iter()
|
||||
.any(|(ev, pos)| ev.event_id().as_deref() == Some(edit_eid1) && pos.is_none()));
|
||||
assert!(relations
|
||||
.iter()
|
||||
.any(|(ev, pos)| ev.event_id().as_deref() == Some(reaction_eid1) && pos.is_none()));
|
||||
assert!(
|
||||
relations
|
||||
.iter()
|
||||
.any(|(ev, pos)| ev.event_id().as_deref() == Some(edit_eid1) && pos.is_none())
|
||||
);
|
||||
assert!(
|
||||
relations
|
||||
.iter()
|
||||
.any(|(ev, pos)| ev.event_id().as_deref() == Some(reaction_eid1) && pos.is_none())
|
||||
);
|
||||
|
||||
// Finding relations with a filter only returns a subset.
|
||||
let relations = self
|
||||
@@ -1088,9 +1109,11 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore {
|
||||
}));
|
||||
|
||||
// But it's still not set for the other related events.
|
||||
assert!(relations
|
||||
.iter()
|
||||
.any(|(ev, pos)| ev.event_id().as_deref() == Some(edit_eid1) && pos.is_none()));
|
||||
assert!(
|
||||
relations
|
||||
.iter()
|
||||
.any(|(ev, pos)| ev.event_id().as_deref() == Some(edit_eid1) && pos.is_none())
|
||||
);
|
||||
}
|
||||
|
||||
async fn test_save_event(&self) {
|
||||
@@ -1123,16 +1146,151 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore {
|
||||
assert_eq!(event.event_id(), event_gruyere.event_id());
|
||||
|
||||
// But they won't be returned when searching in the wrong room.
|
||||
assert!(self
|
||||
.find_event(another_room_id, event_comte.event_id().unwrap().as_ref())
|
||||
assert!(
|
||||
self.find_event(another_room_id, event_comte.event_id().unwrap().as_ref())
|
||||
.await
|
||||
.expect("failed to query for finding an event")
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
self.find_event(room_id, event_gruyere.event_id().unwrap().as_ref())
|
||||
.await
|
||||
.expect("failed to query for finding an event")
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
async fn test_thread_vs_room_linked_chunk(&self) {
|
||||
let room_id = room_id!("!r0:matrix.org");
|
||||
|
||||
let event = |msg: &str| make_test_event(room_id, msg);
|
||||
|
||||
let thread1_ev = event("comté");
|
||||
let thread2_ev = event("gruyère");
|
||||
let thread2_ev2 = event("beaufort");
|
||||
let room_ev = event("brillat savarin triple crème");
|
||||
|
||||
let thread_root1 = event("thread1");
|
||||
let thread_root2 = event("thread2");
|
||||
|
||||
// Add one event in a thread linked chunk.
|
||||
self.handle_linked_chunk_updates(
|
||||
LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap().as_ref()),
|
||||
vec![
|
||||
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
|
||||
Update::PushItems {
|
||||
at: Position::new(CId::new(0), 0),
|
||||
items: vec![thread1_ev.clone()],
|
||||
},
|
||||
],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Add one event in another thread linked chunk (same room).
|
||||
self.handle_linked_chunk_updates(
|
||||
LinkedChunkId::Thread(room_id, thread_root2.event_id().unwrap().as_ref()),
|
||||
vec![
|
||||
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
|
||||
Update::PushItems {
|
||||
at: Position::new(CId::new(0), 0),
|
||||
items: vec![thread2_ev.clone(), thread2_ev2.clone()],
|
||||
},
|
||||
],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Add another event to the room linked chunk.
|
||||
self.handle_linked_chunk_updates(
|
||||
LinkedChunkId::Room(room_id),
|
||||
vec![
|
||||
Update::NewItemsChunk { previous: None, new: CId::new(0), next: None },
|
||||
Update::PushItems {
|
||||
at: Position::new(CId::new(0), 0),
|
||||
items: vec![room_ev.clone()],
|
||||
},
|
||||
],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// All the events can be found with `find_event()` for the room.
|
||||
self.find_event(room_id, thread2_ev.event_id().unwrap().as_ref())
|
||||
.await
|
||||
.expect("failed to query for finding an event")
|
||||
.is_none());
|
||||
assert!(self
|
||||
.find_event(room_id, event_gruyere.event_id().unwrap().as_ref())
|
||||
.expect("failed to find thread1_ev");
|
||||
|
||||
self.find_event(room_id, thread2_ev.event_id().unwrap().as_ref())
|
||||
.await
|
||||
.expect("failed to query for finding an event")
|
||||
.is_none());
|
||||
.expect("failed to find thread2_ev");
|
||||
|
||||
self.find_event(room_id, thread2_ev2.event_id().unwrap().as_ref())
|
||||
.await
|
||||
.expect("failed to query for finding an event")
|
||||
.expect("failed to find thread2_ev2");
|
||||
|
||||
self.find_event(room_id, room_ev.event_id().unwrap().as_ref())
|
||||
.await
|
||||
.expect("failed to query for finding an event")
|
||||
.expect("failed to find room_ev");
|
||||
|
||||
// Finding duplicates operates based on the linked chunk id.
|
||||
let dups = self
|
||||
.filter_duplicated_events(
|
||||
LinkedChunkId::Thread(room_id, thread_root1.event_id().unwrap().as_ref()),
|
||||
vec![
|
||||
thread1_ev.event_id().unwrap().to_owned(),
|
||||
room_ev.event_id().unwrap().to_owned(),
|
||||
],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(dups.len(), 1);
|
||||
assert_eq!(dups[0].0, thread1_ev.event_id().unwrap());
|
||||
|
||||
// Loading all chunks operates based on the linked chunk id.
|
||||
let all_chunks = self
|
||||
.load_all_chunks(LinkedChunkId::Thread(
|
||||
room_id,
|
||||
thread_root2.event_id().unwrap().as_ref(),
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(all_chunks.len(), 1);
|
||||
assert_eq!(all_chunks[0].identifier, CId::new(0));
|
||||
assert_let!(ChunkContent::Items(observed_items) = all_chunks[0].content.clone());
|
||||
assert_eq!(observed_items.len(), 2);
|
||||
assert_eq!(observed_items[0].event_id(), thread2_ev.event_id());
|
||||
assert_eq!(observed_items[1].event_id(), thread2_ev2.event_id());
|
||||
|
||||
// Loading the metadata of all chunks operates based on the linked chunk
|
||||
// id.
|
||||
let metas = self
|
||||
.load_all_chunks_metadata(LinkedChunkId::Thread(
|
||||
room_id,
|
||||
thread_root2.event_id().unwrap().as_ref(),
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(metas.len(), 1);
|
||||
assert_eq!(metas[0].identifier, CId::new(0));
|
||||
assert_eq!(metas[0].num_items, 2);
|
||||
|
||||
// Loading the last chunk operates based on the linked chunk id.
|
||||
let (last_chunk, _chunk_identifier_generator) = self
|
||||
.load_last_chunk(LinkedChunkId::Thread(
|
||||
room_id,
|
||||
thread_root1.event_id().unwrap().as_ref(),
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
let last_chunk = last_chunk.unwrap();
|
||||
assert_eq!(last_chunk.identifier, CId::new(0));
|
||||
assert_let!(ChunkContent::Items(observed_items) = last_chunk.content);
|
||||
assert_eq!(observed_items.len(), 1);
|
||||
assert_eq!(observed_items[0].event_id(), thread1_ev.event_id());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1155,8 +1313,8 @@ impl EventCacheStoreIntegrationTests for DynEventCacheStore {
|
||||
/// mod tests {
|
||||
/// use super::{EventCacheStore, EventCacheStoreResult, MyStore};
|
||||
///
|
||||
/// async fn get_event_cache_store(
|
||||
/// ) -> EventCacheStoreResult<impl EventCacheStore> {
|
||||
/// async fn get_event_cache_store()
|
||||
/// -> EventCacheStoreResult<impl EventCacheStore> {
|
||||
/// Ok(MyStore::new())
|
||||
/// }
|
||||
///
|
||||
@@ -1258,6 +1416,13 @@ macro_rules! event_cache_store_integration_tests {
|
||||
get_event_cache_store().await.unwrap().into_event_cache_store();
|
||||
event_cache_store.test_save_event().await;
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_thread_vs_room_linked_chunk() {
|
||||
let event_cache_store =
|
||||
get_event_cache_store().await.unwrap().into_event_cache_store();
|
||||
event_cache_store.test_thread_vs_room_linked_chunk().await;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1268,11 +1433,14 @@ macro_rules! event_cache_store_integration_tests {
|
||||
#[macro_export]
|
||||
macro_rules! event_cache_store_integration_tests_time {
|
||||
() => {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
mod event_cache_store_integration_tests_time {
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
|
||||
use gloo_timers::future::sleep;
|
||||
use matrix_sdk_test::async_test;
|
||||
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
|
||||
use tokio::time::sleep;
|
||||
use $crate::event_cache::store::IntoEventCacheStore;
|
||||
|
||||
use super::get_event_cache_store;
|
||||
@@ -1301,26 +1469,26 @@ macro_rules! event_cache_store_integration_tests_time {
|
||||
assert!(!acquired5);
|
||||
|
||||
// That's a nice test we got here, go take a little nap.
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
sleep(Duration::from_millis(50)).await;
|
||||
|
||||
// Still too early.
|
||||
let acquired55 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
|
||||
assert!(!acquired55);
|
||||
|
||||
// Ok you can take another nap then.
|
||||
tokio::time::sleep(Duration::from_millis(250)).await;
|
||||
sleep(Duration::from_millis(250)).await;
|
||||
|
||||
// At some point, we do get the lock.
|
||||
let acquired6 = store.try_take_leased_lock(0, "key", "bob").await.unwrap();
|
||||
assert!(acquired6);
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
sleep(Duration::from_millis(1)).await;
|
||||
|
||||
// The other gets it almost immediately too.
|
||||
let acquired7 = store.try_take_leased_lock(0, "key", "alice").await.unwrap();
|
||||
assert!(acquired7);
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
sleep(Duration::from_millis(1)).await;
|
||||
|
||||
// But when we take a longer lease...
|
||||
let acquired8 = store.try_take_leased_lock(300, "key", "bob").await.unwrap();
|
||||
|
||||
@@ -22,7 +22,7 @@ use ruma::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
media_service::IgnoreMediaRetentionPolicy, EventCacheStoreMedia, MediaRetentionPolicy,
|
||||
EventCacheStoreMedia, MediaRetentionPolicy, media_service::IgnoreMediaRetentionPolicy,
|
||||
};
|
||||
use crate::media::{MediaFormat, MediaRequestParameters};
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ use std::{fmt, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use matrix_sdk_common::{
|
||||
executor::{spawn, JoinHandle},
|
||||
locks::Mutex,
|
||||
AsyncTraitDeps, SendOutsideWasm, SyncOutsideWasm,
|
||||
executor::{JoinHandle, spawn},
|
||||
locks::Mutex,
|
||||
};
|
||||
use ruma::{time::SystemTime, MxcUri};
|
||||
use ruma::{MxcUri, time::SystemTime};
|
||||
use tokio::sync::Mutex as AsyncMutex;
|
||||
use tracing::error;
|
||||
|
||||
@@ -538,15 +538,15 @@ mod tests {
|
||||
use matrix_sdk_common::locks::Mutex;
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::{
|
||||
MxcUri, OwnedMxcUri,
|
||||
events::room::MediaSource,
|
||||
mxc_uri,
|
||||
time::{Duration, SystemTime},
|
||||
MxcUri, OwnedMxcUri,
|
||||
};
|
||||
|
||||
use super::{EventCacheStoreMedia, IgnoreMediaRetentionPolicy, MediaService, TimeProvider};
|
||||
use crate::{
|
||||
event_cache::store::{media::MediaRetentionPolicy, EventCacheStoreError},
|
||||
event_cache::store::{EventCacheStoreError, media::MediaRetentionPolicy},
|
||||
media::{MediaFormat, MediaRequestParameters, UniqueKey},
|
||||
};
|
||||
|
||||
|
||||
@@ -21,23 +21,22 @@ use std::{
|
||||
use async_trait::async_trait;
|
||||
use matrix_sdk_common::{
|
||||
linked_chunk::{
|
||||
relational::RelationalLinkedChunk, ChunkIdentifier, ChunkIdentifierGenerator,
|
||||
ChunkMetadata, LinkedChunkId, OwnedLinkedChunkId, Position, RawChunk, Update,
|
||||
ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId, Position,
|
||||
RawChunk, Update, relational::RelationalLinkedChunk,
|
||||
},
|
||||
ring_buffer::RingBuffer,
|
||||
store_locks::memory_store_helper::try_take_leased_lock,
|
||||
};
|
||||
use ruma::{
|
||||
EventId, MxcUri, OwnedEventId, OwnedMxcUri, RoomId,
|
||||
events::relation::RelationType,
|
||||
time::{Instant, SystemTime},
|
||||
EventId, MxcUri, OwnedEventId, OwnedMxcUri, RoomId,
|
||||
};
|
||||
use tracing::error;
|
||||
|
||||
use super::{
|
||||
compute_filters_string, extract_event_relation,
|
||||
EventCacheStore, EventCacheStoreError, Result, compute_filters_string, extract_event_relation,
|
||||
media::{EventCacheStoreMedia, IgnoreMediaRetentionPolicy, MediaRetentionPolicy, MediaService},
|
||||
EventCacheStore, EventCacheStoreError, Result,
|
||||
};
|
||||
use crate::{
|
||||
event_cache::{Event, Gap},
|
||||
@@ -223,11 +222,9 @@ impl EventCacheStore for MemoryStore {
|
||||
) -> Result<Option<Event>, Self::Error> {
|
||||
let inner = self.inner.read().unwrap();
|
||||
|
||||
let target_linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
|
||||
|
||||
let event = inner
|
||||
.events
|
||||
.items(&target_linked_chunk_id)
|
||||
.items(room_id)
|
||||
.find_map(|(event, _pos)| (event.event_id()? == event_id).then_some(event.clone()));
|
||||
|
||||
Ok(event)
|
||||
@@ -241,13 +238,11 @@ impl EventCacheStore for MemoryStore {
|
||||
) -> Result<Vec<(Event, Option<Position>)>, Self::Error> {
|
||||
let inner = self.inner.read().unwrap();
|
||||
|
||||
let target_linked_chunk_id = OwnedLinkedChunkId::Room(room_id.to_owned());
|
||||
|
||||
let filters = compute_filters_string(filters);
|
||||
|
||||
let related_events = inner
|
||||
.events
|
||||
.items(&target_linked_chunk_id)
|
||||
.items(room_id)
|
||||
.filter_map(|(event, pos)| {
|
||||
// Must have a relation.
|
||||
let (related_to, rel_type) = extract_event_relation(event.raw())?;
|
||||
|
||||
@@ -33,9 +33,9 @@ use matrix_sdk_common::store_locks::{
|
||||
};
|
||||
pub use matrix_sdk_store_encryption::Error as StoreEncryptionError;
|
||||
use ruma::{
|
||||
events::{relation::RelationType, AnySyncTimelineEvent},
|
||||
serde::Raw,
|
||||
OwnedEventId,
|
||||
events::{AnySyncTimelineEvent, relation::RelationType},
|
||||
serde::Raw,
|
||||
};
|
||||
use tracing::trace;
|
||||
|
||||
@@ -43,7 +43,7 @@ use tracing::trace;
|
||||
pub use self::integration_tests::EventCacheStoreIntegrationTests;
|
||||
pub use self::{
|
||||
memory_store::MemoryStore,
|
||||
traits::{DynEventCacheStore, EventCacheStore, IntoEventCacheStore, DEFAULT_CHUNK_CAPACITY},
|
||||
traits::{DEFAULT_CHUNK_CAPACITY, DynEventCacheStore, EventCacheStore, IntoEventCacheStore},
|
||||
};
|
||||
|
||||
/// The high-level public type to represent an `EventCacheStore` lock.
|
||||
@@ -236,11 +236,7 @@ pub fn compute_filters_string(filters: Option<&[RelationType]>) -> Option<Vec<St
|
||||
filter
|
||||
.iter()
|
||||
.map(|f| {
|
||||
if *f == RelationType::Replacement {
|
||||
"m.replace".to_owned()
|
||||
} else {
|
||||
f.to_string()
|
||||
}
|
||||
if *f == RelationType::Replacement { "m.replace".to_owned() } else { f.to_string() }
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
|
||||
@@ -16,17 +16,17 @@ use std::{fmt, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use matrix_sdk_common::{
|
||||
AsyncTraitDeps,
|
||||
linked_chunk::{
|
||||
ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId, Position,
|
||||
RawChunk, Update,
|
||||
},
|
||||
AsyncTraitDeps,
|
||||
};
|
||||
use ruma::{events::relation::RelationType, EventId, MxcUri, OwnedEventId, RoomId};
|
||||
use ruma::{EventId, MxcUri, OwnedEventId, RoomId, events::relation::RelationType};
|
||||
|
||||
use super::{
|
||||
media::{IgnoreMediaRetentionPolicy, MediaRetentionPolicy},
|
||||
EventCacheStoreError,
|
||||
media::{IgnoreMediaRetentionPolicy, MediaRetentionPolicy},
|
||||
};
|
||||
use crate::{
|
||||
event_cache::{Event, Gap},
|
||||
@@ -128,6 +128,9 @@ pub trait EventCacheStore: AsyncTraitDeps {
|
||||
) -> Result<Vec<(OwnedEventId, Position)>, Self::Error>;
|
||||
|
||||
/// Find an event by its ID in a room.
|
||||
///
|
||||
/// This method must return events saved either in any linked chunks, *or*
|
||||
/// events saved "out-of-band" with the [`Self::save_event`] method.
|
||||
async fn find_event(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
@@ -147,6 +150,9 @@ pub trait EventCacheStore: AsyncTraitDeps {
|
||||
///
|
||||
/// An additional filter can be provided to only retrieve related events for
|
||||
/// a certain relationship.
|
||||
///
|
||||
/// This method must return events saved either in any linked chunks, *or*
|
||||
/// events saved "out-of-band" with the [`Self::save_event`] method.
|
||||
async fn find_event_relations(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
@@ -238,7 +244,7 @@ pub trait EventCacheStore: AsyncTraitDeps {
|
||||
///
|
||||
/// * `uri` - The `MxcUri` of the media file.
|
||||
async fn get_media_content_for_uri(&self, uri: &MxcUri)
|
||||
-> Result<Option<Vec<u8>>, Self::Error>;
|
||||
-> Result<Option<Vec<u8>>, Self::Error>;
|
||||
|
||||
/// Remove all the media files' content associated to an `MxcUri` from the
|
||||
/// media store.
|
||||
@@ -464,6 +470,12 @@ pub trait IntoEventCacheStore {
|
||||
fn into_event_cache_store(self) -> Arc<DynEventCacheStore>;
|
||||
}
|
||||
|
||||
impl IntoEventCacheStore for Arc<DynEventCacheStore> {
|
||||
fn into_event_cache_store(self) -> Arc<DynEventCacheStore> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoEventCacheStore for T
|
||||
where
|
||||
T: EventCacheStore + Sized + 'static,
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
//! use as a [crate::Room::latest_event].
|
||||
|
||||
use matrix_sdk_common::deserialized_responses::TimelineEvent;
|
||||
use ruma::{MilliSecondsSinceUnixEpoch, MxcUri, OwnedEventId};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use ruma::{
|
||||
UserId,
|
||||
events::{
|
||||
AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent,
|
||||
call::{invite::SyncCallInviteEvent, notify::SyncCallNotifyEvent},
|
||||
poll::unstable_start::SyncUnstablePollStartEvent,
|
||||
relation::RelationType,
|
||||
@@ -14,14 +17,43 @@ use ruma::{
|
||||
power_levels::RoomPowerLevels,
|
||||
},
|
||||
sticker::SyncStickerEvent,
|
||||
AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent,
|
||||
},
|
||||
UserId,
|
||||
};
|
||||
use ruma::{MxcUri, OwnedEventId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::MinimalRoomMemberEvent;
|
||||
use crate::{MinimalRoomMemberEvent, store::SerializableEventContent};
|
||||
|
||||
/// A latest event value!
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub enum LatestEventValue {
|
||||
/// No value has been computed yet, or no candidate value was found.
|
||||
#[default]
|
||||
None,
|
||||
|
||||
/// The latest event represents a remote event.
|
||||
Remote(RemoteLatestEventValue),
|
||||
|
||||
/// The latest event represents a local event that is sending.
|
||||
LocalIsSending(LocalLatestEventValue),
|
||||
|
||||
/// The latest event represents a local event that cannot be sent, either
|
||||
/// because a previous local event, or this local event cannot be sent.
|
||||
LocalCannotBeSent(LocalLatestEventValue),
|
||||
}
|
||||
|
||||
/// Represents the value for [`LatestEventValue::Remote`].
|
||||
pub type RemoteLatestEventValue = TimelineEvent;
|
||||
|
||||
/// Represents the value for [`LatestEventValue::LocalIsSending`] and
|
||||
/// [`LatestEventValue::LocalCannotBeSent`].
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LocalLatestEventValue {
|
||||
/// The time where the event has been created (by this module).
|
||||
pub timestamp: MilliSecondsSinceUnixEpoch,
|
||||
|
||||
/// The content of the local event.
|
||||
pub content: SerializableEventContent,
|
||||
}
|
||||
|
||||
/// Represents a decision about whether an event could be stored as the latest
|
||||
/// event in a room. Variants starting with Yes indicate that this message could
|
||||
@@ -125,21 +157,21 @@ pub fn is_suitable_for_latest_event<'a>(
|
||||
AnySyncTimelineEvent::State(state) => {
|
||||
// But we make an exception for knocked state events *if* the current user
|
||||
// can either accept or decline them
|
||||
if let AnySyncStateEvent::RoomMember(member) = state {
|
||||
if matches!(member.membership(), MembershipState::Knock) {
|
||||
let can_accept_or_decline_knocks = match power_levels_info {
|
||||
Some((own_user_id, room_power_levels)) => {
|
||||
room_power_levels.user_can_invite(own_user_id)
|
||||
|| room_power_levels.user_can_kick(own_user_id)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// The current user can act on the knock changes, so they should be
|
||||
// displayed
|
||||
if can_accept_or_decline_knocks {
|
||||
return PossibleLatestEvent::YesKnockedStateEvent(member);
|
||||
if let AnySyncStateEvent::RoomMember(member) = state
|
||||
&& matches!(member.membership(), MembershipState::Knock)
|
||||
{
|
||||
let can_accept_or_decline_knocks = match power_levels_info {
|
||||
Some((own_user_id, room_power_levels)) => {
|
||||
room_power_levels.user_can_invite(own_user_id)
|
||||
|| room_power_levels.user_can_kick(own_user_id)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// The current user can act on the knock changes, so they should be
|
||||
// displayed
|
||||
if can_accept_or_decline_knocks {
|
||||
return PossibleLatestEvent::YesKnockedStateEvent(member);
|
||||
}
|
||||
}
|
||||
PossibleLatestEvent::NoUnsupportedEventType
|
||||
@@ -311,13 +343,17 @@ mod tests {
|
||||
use ruma::serde::Raw;
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use ruma::{
|
||||
MilliSecondsSinceUnixEpoch, UInt, VoipVersionId,
|
||||
events::{
|
||||
AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent, EmptyStateKey,
|
||||
Mentions, MessageLikeUnsigned, OriginalSyncMessageLikeEvent, OriginalSyncStateEvent,
|
||||
RedactedSyncMessageLikeEvent, RedactedUnsigned, StateUnsigned, SyncMessageLikeEvent,
|
||||
call::{
|
||||
SessionDescription,
|
||||
invite::{CallInviteEventContent, SyncCallInviteEvent},
|
||||
notify::{
|
||||
ApplicationType, CallNotifyEventContent, NotifyType, SyncCallNotifyEvent,
|
||||
},
|
||||
SessionDescription,
|
||||
},
|
||||
poll::{
|
||||
unstable_response::{
|
||||
@@ -330,6 +366,7 @@ mod tests {
|
||||
},
|
||||
relation::Replacement,
|
||||
room::{
|
||||
ImageInfo, MediaSource,
|
||||
encrypted::{
|
||||
EncryptedEventScheme, OlmV1Curve25519AesSha2Content, RoomEncryptedEventContent,
|
||||
SyncRoomEncryptedEvent,
|
||||
@@ -339,22 +376,16 @@ mod tests {
|
||||
Relation, RoomMessageEventContent, SyncRoomMessageEvent,
|
||||
},
|
||||
topic::{RoomTopicEventContent, SyncRoomTopicEvent},
|
||||
ImageInfo, MediaSource,
|
||||
},
|
||||
sticker::{StickerEventContent, SyncStickerEvent},
|
||||
AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent, EmptyStateKey,
|
||||
Mentions, MessageLikeUnsigned, OriginalSyncMessageLikeEvent, OriginalSyncStateEvent,
|
||||
RedactedSyncMessageLikeEvent, RedactedUnsigned, StateUnsigned, SyncMessageLikeEvent,
|
||||
UnsignedRoomRedactionEvent,
|
||||
},
|
||||
owned_event_id, owned_mxc_uri, owned_user_id, MilliSecondsSinceUnixEpoch, UInt,
|
||||
VoipVersionId,
|
||||
owned_event_id, owned_mxc_uri, owned_user_id,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use super::LatestEvent;
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use super::{is_suitable_for_latest_event, PossibleLatestEvent};
|
||||
use super::{PossibleLatestEvent, is_suitable_for_latest_event};
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
#[test]
|
||||
@@ -501,7 +532,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_redacted_messages_are_suitable() {
|
||||
// Ruma does not allow constructing UnsignedRoomRedactionEvent instances.
|
||||
let room_redaction_event: UnsignedRoomRedactionEvent = serde_json::from_value(json!({
|
||||
let room_redaction_event = serde_json::from_value(json!({
|
||||
"content": {},
|
||||
"event_id": "$redaction",
|
||||
"sender": "@x:y.za",
|
||||
|
||||
@@ -55,20 +55,21 @@ pub use http;
|
||||
pub use matrix_sdk_crypto as crypto;
|
||||
pub use once_cell;
|
||||
pub use room::{
|
||||
apply_redaction, EncryptionState, PredecessorRoom, Room, RoomCreateWithCreatorEventContent,
|
||||
RoomDisplayName, RoomHero, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons,
|
||||
RoomMember, RoomMembersUpdate, RoomMemberships, RoomState, RoomStateFilter, SuccessorRoom,
|
||||
EncryptionState, InviteAcceptanceDetails, PredecessorRoom, Room,
|
||||
RoomCreateWithCreatorEventContent, RoomDisplayName, RoomHero, RoomInfo, RoomInfoNotableUpdate,
|
||||
RoomInfoNotableUpdateReasons, RoomMember, RoomMembersUpdate, RoomMemberships, RoomState,
|
||||
RoomStateFilter, SuccessorRoom, apply_redaction,
|
||||
};
|
||||
pub use store::{
|
||||
ComposerDraft, ComposerDraftType, QueueWedgeError, StateChanges, StateStore, StateStoreDataKey,
|
||||
StateStoreDataValue, StoreError,
|
||||
StateStoreDataValue, StoreError, ThreadSubscriptionCatchupToken,
|
||||
};
|
||||
pub use utils::{
|
||||
MinimalRoomMemberEvent, MinimalStateEvent, OriginalMinimalStateEvent, RedactedMinimalStateEvent,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
matrix_sdk_test::init_tracing_for_tests!();
|
||||
matrix_sdk_test_utils::init_tracing_for_tests!();
|
||||
|
||||
/// The Matrix user session info.
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
//! Common types for [media content](https://matrix.org/docs/spec/client_server/r0.6.1#id66).
|
||||
|
||||
use ruma::{
|
||||
MxcUri, UInt,
|
||||
api::client::media::get_content_thumbnail::v3::Method,
|
||||
events::{
|
||||
room::{
|
||||
MediaSource,
|
||||
message::{
|
||||
AudioMessageEventContent, FileMessageEventContent, ImageMessageEventContent,
|
||||
LocationMessageEventContent, VideoMessageEventContent,
|
||||
},
|
||||
MediaSource,
|
||||
},
|
||||
sticker::StickerEventContent,
|
||||
},
|
||||
MxcUri, UInt,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -127,15 +127,15 @@ use matrix_sdk_common::{
|
||||
serde_helpers::extract_thread_root,
|
||||
};
|
||||
use ruma::{
|
||||
EventId, OwnedEventId, OwnedUserId, RoomId, UserId,
|
||||
events::{
|
||||
AnySyncMessageLikeEvent, AnySyncTimelineEvent, OriginalSyncMessageLikeEvent,
|
||||
SyncMessageLikeEvent,
|
||||
poll::{start::PollStartEventContent, unstable_start::UnstablePollStartEventContent},
|
||||
receipt::{ReceiptEventContent, ReceiptThread, ReceiptType},
|
||||
room::message::Relation,
|
||||
AnySyncMessageLikeEvent, AnySyncTimelineEvent, OriginalSyncMessageLikeEvent,
|
||||
SyncMessageLikeEvent,
|
||||
},
|
||||
serde::Raw,
|
||||
EventId, OwnedEventId, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, instrument, trace, warn};
|
||||
@@ -212,7 +212,7 @@ impl RoomReadReceipts {
|
||||
user_id: &UserId,
|
||||
threading_support: ThreadingSupport,
|
||||
) {
|
||||
if matches!(threading_support, ThreadingSupport::Enabled)
|
||||
if matches!(threading_support, ThreadingSupport::Enabled { .. })
|
||||
&& extract_thread_root(event.raw()).is_some()
|
||||
{
|
||||
return;
|
||||
@@ -264,15 +264,15 @@ impl RoomReadReceipts {
|
||||
// Sliding sync sometimes sends the same event multiple times, so it can be at
|
||||
// the beginning and end of a batch, for instance. In that case, just reset
|
||||
// every time we see the event matching the receipt.
|
||||
if let Some(event_id) = event.event_id() {
|
||||
if event_id == receipt_event_id {
|
||||
// Bingo! Switch over to the counting state, after resetting the
|
||||
// previous counts.
|
||||
trace!("Found the event the receipt was referring to! Starting to count.");
|
||||
self.reset();
|
||||
counting_receipts = true;
|
||||
continue;
|
||||
}
|
||||
if let Some(event_id) = event.event_id()
|
||||
&& event_id == receipt_event_id
|
||||
{
|
||||
// Bingo! Switch over to the counting state, after resetting the
|
||||
// previous counts.
|
||||
trace!("Found the event the receipt was referring to! Starting to count.");
|
||||
self.reset();
|
||||
counting_receipts = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if counting_receipts {
|
||||
@@ -387,17 +387,17 @@ impl ReceiptSelector {
|
||||
// Now consider new receipts.
|
||||
for (event_id, receipts) in &receipt_event.0 {
|
||||
for ty in [ReceiptType::Read, ReceiptType::ReadPrivate] {
|
||||
if let Some(receipt) = receipts.get(&ty).and_then(|receipts| receipts.get(user_id))
|
||||
if let Some(receipts) = receipts.get(&ty)
|
||||
&& let Some(receipt) = receipts.get(user_id)
|
||||
&& matches!(receipt.thread, ReceiptThread::Main | ReceiptThread::Unthreaded)
|
||||
{
|
||||
if matches!(receipt.thread, ReceiptThread::Main | ReceiptThread::Unthreaded) {
|
||||
trace!(%event_id, "found new candidate");
|
||||
if let Some(event_pos) = self.event_id_to_pos.get(event_id) {
|
||||
self.try_select_later(event_id, *event_pos);
|
||||
} else {
|
||||
// It's a new pending receipt.
|
||||
trace!(%event_id, "stashed as pending");
|
||||
pending.push(event_id.clone());
|
||||
}
|
||||
trace!(%event_id, "found new candidate");
|
||||
if let Some(event_pos) = self.event_id_to_pos.get(event_id) {
|
||||
self.try_select_later(event_id, *event_pos);
|
||||
} else {
|
||||
// It's a new pending receipt.
|
||||
trace!(%event_id, "stashed as pending");
|
||||
pending.push(event_id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -631,20 +631,20 @@ mod tests {
|
||||
use matrix_sdk_common::{deserialized_responses::TimelineEvent, ring_buffer::RingBuffer};
|
||||
use matrix_sdk_test::event_factory::EventFactory;
|
||||
use ruma::{
|
||||
event_id,
|
||||
EventId, UserId, event_id,
|
||||
events::{
|
||||
receipt::{ReceiptThread, ReceiptType},
|
||||
room::{member::MembershipState, message::MessageType},
|
||||
},
|
||||
owned_event_id, owned_user_id,
|
||||
push::Action,
|
||||
room_id, user_id, EventId, UserId,
|
||||
room_id, user_id,
|
||||
};
|
||||
|
||||
use super::compute_unread_counts;
|
||||
use crate::{
|
||||
read_receipts::{marks_as_unread, ReceiptSelector, RoomReadReceipts},
|
||||
ThreadingSupport,
|
||||
read_receipts::{ReceiptSelector, RoomReadReceipts, marks_as_unread},
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -805,9 +805,9 @@ mod tests {
|
||||
// When provided with no events, we report not finding the event to which the
|
||||
// receipt relates.
|
||||
let mut receipts = RoomReadReceipts::default();
|
||||
assert!(receipts
|
||||
.find_and_process_events(ev0, user_id, &[], ThreadingSupport::Disabled)
|
||||
.not());
|
||||
assert!(
|
||||
receipts.find_and_process_events(ev0, user_id, &[], ThreadingSupport::Disabled).not()
|
||||
);
|
||||
assert_eq!(receipts.num_unread, 0);
|
||||
assert_eq!(receipts.num_notifications, 0);
|
||||
assert_eq!(receipts.num_mentions, 0);
|
||||
@@ -828,14 +828,16 @@ mod tests {
|
||||
num_mentions: 37,
|
||||
..Default::default()
|
||||
};
|
||||
assert!(receipts
|
||||
.find_and_process_events(
|
||||
ev0,
|
||||
user_id,
|
||||
&[make_event(event_id!("$1"))],
|
||||
ThreadingSupport::Disabled
|
||||
)
|
||||
.not());
|
||||
assert!(
|
||||
receipts
|
||||
.find_and_process_events(
|
||||
ev0,
|
||||
user_id,
|
||||
&[make_event(event_id!("$1"))],
|
||||
ThreadingSupport::Disabled
|
||||
)
|
||||
.not()
|
||||
);
|
||||
assert_eq!(receipts.num_unread, 42);
|
||||
assert_eq!(receipts.num_notifications, 13);
|
||||
assert_eq!(receipts.num_mentions, 37);
|
||||
@@ -867,18 +869,20 @@ mod tests {
|
||||
num_mentions: 37,
|
||||
..Default::default()
|
||||
};
|
||||
assert!(receipts
|
||||
.find_and_process_events(
|
||||
ev0,
|
||||
user_id,
|
||||
&[
|
||||
make_event(event_id!("$1")),
|
||||
make_event(event_id!("$2")),
|
||||
make_event(event_id!("$3"))
|
||||
],
|
||||
ThreadingSupport::Disabled
|
||||
)
|
||||
.not());
|
||||
assert!(
|
||||
receipts
|
||||
.find_and_process_events(
|
||||
ev0,
|
||||
user_id,
|
||||
&[
|
||||
make_event(event_id!("$1")),
|
||||
make_event(event_id!("$2")),
|
||||
make_event(event_id!("$3"))
|
||||
],
|
||||
ThreadingSupport::Disabled
|
||||
)
|
||||
.not()
|
||||
);
|
||||
assert_eq!(receipts.num_unread, 42);
|
||||
assert_eq!(receipts.num_notifications, 13);
|
||||
assert_eq!(receipts.num_mentions, 37);
|
||||
@@ -1617,23 +1621,23 @@ mod tests {
|
||||
receipts.process_event(
|
||||
&make_event(own_alice, event_id!("$some_thread_root")),
|
||||
own_alice,
|
||||
ThreadingSupport::Enabled,
|
||||
ThreadingSupport::Enabled { with_subscriptions: false },
|
||||
);
|
||||
receipts.process_event(
|
||||
&make_event(own_alice, event_id!("$some_other_thread_root")),
|
||||
own_alice,
|
||||
ThreadingSupport::Enabled,
|
||||
ThreadingSupport::Enabled { with_subscriptions: false },
|
||||
);
|
||||
|
||||
receipts.process_event(
|
||||
&make_event(bob, event_id!("$some_thread_root")),
|
||||
own_alice,
|
||||
ThreadingSupport::Enabled,
|
||||
ThreadingSupport::Enabled { with_subscriptions: false },
|
||||
);
|
||||
receipts.process_event(
|
||||
&make_event(bob, event_id!("$some_other_thread_root")),
|
||||
own_alice,
|
||||
ThreadingSupport::Enabled,
|
||||
ThreadingSupport::Enabled { with_subscriptions: false },
|
||||
);
|
||||
|
||||
assert_eq!(receipts.num_unread, 0);
|
||||
@@ -1644,7 +1648,7 @@ mod tests {
|
||||
receipts.process_event(
|
||||
&EventFactory::new().text_msg("A").sender(bob).event_id(event_id!("$ida")).into_event(),
|
||||
own_alice,
|
||||
ThreadingSupport::Enabled,
|
||||
ThreadingSupport::Enabled { with_subscriptions: false },
|
||||
);
|
||||
|
||||
assert_eq!(receipts.num_unread, 1);
|
||||
|
||||
@@ -17,17 +17,18 @@ use std::{
|
||||
mem,
|
||||
};
|
||||
|
||||
use matrix_sdk_common::timer;
|
||||
use ruma::{
|
||||
RoomId,
|
||||
events::{
|
||||
direct::OwnedDirectUserIdentifier, AnyGlobalAccountDataEvent, GlobalAccountDataEventType,
|
||||
AnyGlobalAccountDataEvent, GlobalAccountDataEventType, direct::OwnedDirectUserIdentifier,
|
||||
},
|
||||
serde::Raw,
|
||||
RoomId,
|
||||
};
|
||||
use tracing::{debug, instrument, trace, warn};
|
||||
|
||||
use super::super::Context;
|
||||
use crate::{store::BaseStateStore, RoomInfo, StateChanges};
|
||||
use crate::{RoomInfo, StateChanges, store::BaseStateStore};
|
||||
|
||||
/// Create the [`Global`] account data processor.
|
||||
pub fn global(events: &[Raw<AnyGlobalAccountDataEvent>]) -> Global {
|
||||
@@ -43,6 +44,8 @@ pub struct Global {
|
||||
impl Global {
|
||||
/// Creates a new processor for global account data.
|
||||
fn process(events: &[Raw<AnyGlobalAccountDataEvent>]) -> Self {
|
||||
let _timer = timer!(tracing::Level::TRACE, "Global::process (global account data)");
|
||||
|
||||
let mut raw_by_type = BTreeMap::new();
|
||||
let mut parsed_events = Vec::new();
|
||||
|
||||
@@ -102,10 +105,10 @@ impl Global {
|
||||
|
||||
// Update the direct targets of rooms if they changed.
|
||||
for (room_id, new_direct_targets) in new_dms {
|
||||
if let Some(old_direct_targets) = old_dms.remove(&room_id) {
|
||||
if old_direct_targets == new_direct_targets {
|
||||
continue;
|
||||
}
|
||||
if let Some(old_direct_targets) = old_dms.remove(&room_id)
|
||||
&& old_direct_targets == new_direct_targets
|
||||
{
|
||||
continue;
|
||||
}
|
||||
trace!(?room_id, targets = ?new_direct_targets, "Marking room as direct room");
|
||||
map_info(room_id, state_changes, state_store, |info| {
|
||||
@@ -125,6 +128,8 @@ impl Global {
|
||||
|
||||
/// Applies the processed data to the state changes and the state store.
|
||||
pub async fn apply(mut self, context: &mut Context, state_store: &BaseStateStore) {
|
||||
let _timer = timer!(tracing::Level::TRACE, "Global::apply (global account data)");
|
||||
|
||||
// Fill in the content of `changes.account_data`.
|
||||
mem::swap(&mut context.state_changes.account_data, &mut self.raw_by_type);
|
||||
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
mod global;
|
||||
mod room;
|
||||
|
||||
pub use global::{global, Global};
|
||||
pub use global::{Global, global};
|
||||
pub use room::for_room;
|
||||
|
||||
@@ -13,20 +13,20 @@
|
||||
// limitations under the License.
|
||||
|
||||
use ruma::{
|
||||
events::{marked_unread::MarkedUnreadEventContent, AnyRoomAccountDataEvent},
|
||||
serde::Raw,
|
||||
RoomId,
|
||||
events::{AnyRoomAccountDataEvent, marked_unread::MarkedUnreadEventContent},
|
||||
serde::Raw,
|
||||
};
|
||||
use tracing::{instrument, warn};
|
||||
|
||||
use super::super::{Context, RoomInfoNotableUpdates};
|
||||
use crate::{
|
||||
room::AccountDataSource, store::BaseStateStore, RoomInfo, RoomInfoNotableUpdateReasons,
|
||||
StateChanges,
|
||||
RoomInfo, RoomInfoNotableUpdateReasons, StateChanges, room::AccountDataSource,
|
||||
store::BaseStateStore,
|
||||
};
|
||||
|
||||
#[instrument(skip_all, fields(?room_id))]
|
||||
pub async fn for_room(
|
||||
pub fn for_room(
|
||||
context: &mut Context,
|
||||
room_id: &RoomId,
|
||||
events: &[Raw<AnyRoomAccountDataEvent>],
|
||||
|
||||
@@ -13,22 +13,25 @@
|
||||
// limitations under the License.
|
||||
|
||||
use eyeball::SharedObservable;
|
||||
use matrix_sdk_common::timer;
|
||||
use ruma::{
|
||||
events::{ignored_user_list::IgnoredUserListEvent, GlobalAccountDataEventType},
|
||||
events::{GlobalAccountDataEventType, ignored_user_list::IgnoredUserListEvent},
|
||||
serde::Raw,
|
||||
};
|
||||
use tracing::{error, instrument, trace};
|
||||
|
||||
use super::Context;
|
||||
use crate::{
|
||||
store::{BaseStateStore, StateStoreExt as _},
|
||||
Result,
|
||||
store::{BaseStateStore, StateStoreExt as _},
|
||||
};
|
||||
|
||||
/// Save the [`StateChanges`] from the [`Context`] inside the [`BaseStateStore`]
|
||||
/// only! The changes aren't applied on the in-memory rooms.
|
||||
#[instrument(skip_all)]
|
||||
pub async fn save_only(context: Context, state_store: &BaseStateStore) -> Result<()> {
|
||||
let _timer = timer!(tracing::Level::TRACE, "_method");
|
||||
|
||||
save_changes(&context, state_store, None).await?;
|
||||
broadcast_room_info_notable_updates(&context, state_store);
|
||||
|
||||
@@ -44,6 +47,8 @@ pub async fn save_and_apply(
|
||||
ignore_user_list_changes: &SharedObservable<Vec<String>>,
|
||||
sync_token: Option<String>,
|
||||
) -> Result<()> {
|
||||
let _timer = timer!(tracing::Level::TRACE, "_method");
|
||||
|
||||
trace!("ready to submit changes to store");
|
||||
|
||||
let previous_ignored_user_list =
|
||||
@@ -80,7 +85,7 @@ fn apply_changes(
|
||||
if let Some(event) =
|
||||
context.state_changes.account_data.get(&GlobalAccountDataEventType::IgnoredUserList)
|
||||
{
|
||||
match event.deserialize_as::<IgnoredUserListEvent>() {
|
||||
match event.deserialize_as_unchecked::<IgnoredUserListEvent>() {
|
||||
Ok(event) => {
|
||||
let user_ids: Vec<String> =
|
||||
event.content.ignored_users.keys().map(|id| id.to_string()).collect();
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
use matrix_sdk_common::deserialized_responses::TimelineEvent;
|
||||
use matrix_sdk_crypto::RoomEventDecryptionResult;
|
||||
use ruma::{events::AnySyncTimelineEvent, serde::Raw, RoomId};
|
||||
use ruma::{RoomId, events::AnySyncTimelineEvent, serde::Raw};
|
||||
|
||||
use super::{super::verification, E2EE};
|
||||
use crate::Result;
|
||||
@@ -35,7 +35,7 @@ pub async fn sync_timeline_event(
|
||||
|
||||
Ok(Some(
|
||||
match olm
|
||||
.try_decrypt_room_event(event.cast_ref(), room_id, e2ee.decryption_settings)
|
||||
.try_decrypt_room_event(event.cast_ref_unchecked(), room_id, e2ee.decryption_settings)
|
||||
.await?
|
||||
{
|
||||
RoomEventDecryptionResult::Decrypted(decrypted) => {
|
||||
|
||||
@@ -14,13 +14,17 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent;
|
||||
use matrix_sdk_crypto::{store::types::RoomKeyInfo, EncryptionSyncChanges, OlmMachine};
|
||||
use matrix_sdk_common::deserialized_responses::{
|
||||
ProcessedToDeviceEvent, ToDeviceUnableToDecryptInfo, ToDeviceUnableToDecryptReason,
|
||||
};
|
||||
use matrix_sdk_crypto::{
|
||||
DecryptionSettings, EncryptionSyncChanges, OlmMachine, store::types::RoomKeyInfo,
|
||||
};
|
||||
use ruma::{
|
||||
api::client::sync::sync_events::{v3, v5, DeviceLists},
|
||||
OneTimeKeyAlgorithm, UInt,
|
||||
api::client::sync::sync_events::{DeviceLists, v3, v5},
|
||||
events::AnyToDeviceEvent,
|
||||
serde::Raw,
|
||||
OneTimeKeyAlgorithm, UInt,
|
||||
};
|
||||
|
||||
use crate::Result;
|
||||
@@ -34,6 +38,7 @@ pub async fn from_msc4186(
|
||||
to_device: Option<&v5::response::ToDevice>,
|
||||
e2ee: &v5::response::E2EE,
|
||||
olm_machine: Option<&OlmMachine>,
|
||||
decryption_settings: &DecryptionSettings,
|
||||
) -> Result<Output> {
|
||||
process(
|
||||
olm_machine,
|
||||
@@ -42,6 +47,7 @@ pub async fn from_msc4186(
|
||||
&e2ee.device_one_time_keys_count,
|
||||
e2ee.device_unused_fallback_key_types.as_deref(),
|
||||
to_device.as_ref().map(|to_device| to_device.next_batch.clone()),
|
||||
decryption_settings,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -54,6 +60,7 @@ pub async fn from_msc4186(
|
||||
pub async fn from_sync_v2(
|
||||
response: &v3::Response,
|
||||
olm_machine: Option<&OlmMachine>,
|
||||
decryption_settings: &DecryptionSettings,
|
||||
) -> Result<Output> {
|
||||
process(
|
||||
olm_machine,
|
||||
@@ -62,6 +69,7 @@ pub async fn from_sync_v2(
|
||||
&response.device_one_time_keys_count,
|
||||
response.device_unused_fallback_key_types.as_deref(),
|
||||
Some(response.next_batch.clone()),
|
||||
decryption_settings,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -77,6 +85,7 @@ async fn process(
|
||||
one_time_keys_counts: &BTreeMap<OneTimeKeyAlgorithm, UInt>,
|
||||
unused_fallback_keys: Option<&[OneTimeKeyAlgorithm]>,
|
||||
next_batch_token: Option<String>,
|
||||
decryption_settings: &DecryptionSettings,
|
||||
) -> Result<Output> {
|
||||
let encryption_sync_changes = EncryptionSyncChanges {
|
||||
to_device_events,
|
||||
@@ -92,7 +101,7 @@ async fn process(
|
||||
// This makes sure that we have the decryption keys for the room
|
||||
// events at hand.
|
||||
let (events, room_key_updates) =
|
||||
olm_machine.receive_sync_changes(encryption_sync_changes).await?;
|
||||
olm_machine.receive_sync_changes(encryption_sync_changes, decryption_settings).await?;
|
||||
|
||||
Output { processed_to_device_events: events, room_key_updates: Some(room_key_updates) }
|
||||
} else {
|
||||
@@ -107,7 +116,12 @@ async fn process(
|
||||
.map(|raw| {
|
||||
if let Ok(Some(event_type)) = raw.get_field::<String>("type") {
|
||||
if event_type == "m.room.encrypted" {
|
||||
ProcessedToDeviceEvent::UnableToDecrypt(raw)
|
||||
ProcessedToDeviceEvent::UnableToDecrypt {
|
||||
encrypted_event: raw,
|
||||
utd_info: ToDeviceUnableToDecryptInfo {
|
||||
reason: ToDeviceUnableToDecryptReason::NoOlmMachine,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
ProcessedToDeviceEvent::PlainText(raw)
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use matrix_sdk_common::timer;
|
||||
use matrix_sdk_crypto::OlmMachine;
|
||||
use ruma::{OwnedUserId, RoomId};
|
||||
|
||||
use crate::{store::BaseStateStore, EncryptionState, Result, RoomMemberships};
|
||||
use crate::{EncryptionState, Result, RoomMemberships, store::BaseStateStore};
|
||||
|
||||
/// Update tracked users, if the room is encrypted.
|
||||
pub async fn update(
|
||||
@@ -25,12 +26,11 @@ pub async fn update(
|
||||
room_encryption_state: EncryptionState,
|
||||
user_ids_to_track: &BTreeSet<OwnedUserId>,
|
||||
) -> Result<()> {
|
||||
if room_encryption_state.is_encrypted() {
|
||||
if let Some(olm) = olm_machine {
|
||||
if !user_ids_to_track.is_empty() {
|
||||
olm.update_tracked_users(user_ids_to_track.iter().map(AsRef::as_ref)).await?
|
||||
}
|
||||
}
|
||||
if room_encryption_state.is_encrypted()
|
||||
&& let Some(olm) = olm_machine
|
||||
&& !user_ids_to_track.is_empty()
|
||||
{
|
||||
olm.update_tracked_users(user_ids_to_track.iter().map(AsRef::as_ref)).await?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -46,19 +46,21 @@ pub async fn update_or_set_if_room_is_newly_encrypted(
|
||||
room_id: &RoomId,
|
||||
state_store: &BaseStateStore,
|
||||
) -> Result<()> {
|
||||
if new_room_encryption_state.is_encrypted() {
|
||||
if let Some(olm) = olm_machine {
|
||||
if !previous_room_encryption_state.is_encrypted() {
|
||||
// The room turned on encryption in this sync, we need
|
||||
// to also get all the existing users and mark them for
|
||||
// tracking.
|
||||
let user_ids = state_store.get_user_ids(room_id, RoomMemberships::ACTIVE).await?;
|
||||
olm.update_tracked_users(user_ids.iter().map(AsRef::as_ref)).await?
|
||||
}
|
||||
let _timer = timer!(tracing::Level::TRACE, "update_or_set_if_room_is_newly_encrypted");
|
||||
|
||||
if !user_ids_to_track.is_empty() {
|
||||
olm.update_tracked_users(user_ids_to_track.iter().map(AsRef::as_ref)).await?;
|
||||
}
|
||||
if new_room_encryption_state.is_encrypted()
|
||||
&& let Some(olm) = olm_machine
|
||||
{
|
||||
if !previous_room_encryption_state.is_encrypted() {
|
||||
// The room turned on encryption in this sync, we need
|
||||
// to also get all the existing users and mark them for
|
||||
// tracking.
|
||||
let user_ids = state_store.get_user_ids(room_id, RoomMemberships::ACTIVE).await?;
|
||||
olm.update_tracked_users(user_ids.iter().map(AsRef::as_ref)).await?
|
||||
}
|
||||
|
||||
if !user_ids_to_track.is_empty() {
|
||||
olm.update_tracked_users(user_ids_to_track.iter().map(AsRef::as_ref)).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use ruma::{events::AnySyncEphemeralRoomEvent, serde::Raw, RoomId};
|
||||
use ruma::{RoomId, events::AnySyncEphemeralRoomEvent, serde::Raw};
|
||||
use tracing::info;
|
||||
|
||||
use super::Context;
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
use matrix_sdk_common::deserialized_responses::TimelineEvent;
|
||||
use matrix_sdk_crypto::RoomEventDecryptionResult;
|
||||
use ruma::{events::AnySyncTimelineEvent, serde::Raw, RoomId};
|
||||
use ruma::{RoomId, events::AnySyncTimelineEvent, serde::Raw};
|
||||
|
||||
use super::{e2ee::E2EE, verification, Context};
|
||||
use super::{Context, e2ee::E2EE, verification};
|
||||
use crate::{
|
||||
latest_event::{is_suitable_for_latest_event, LatestEvent, PossibleLatestEvent},
|
||||
Result, Room,
|
||||
latest_event::{LatestEvent, PossibleLatestEvent, is_suitable_for_latest_event},
|
||||
};
|
||||
|
||||
/// Decrypt any [`Room::latest_encrypted_events`] for a particular set of
|
||||
@@ -111,7 +111,7 @@ async fn decrypt_sync_room_event(
|
||||
let event = match e2ee
|
||||
.olm_machine
|
||||
.expect("An `OlmMachine` is expected")
|
||||
.try_decrypt_room_event(event.cast_ref(), room_id, e2ee.decryption_settings)
|
||||
.try_decrypt_room_event(event.cast_ref_unchecked(), room_id, e2ee.decryption_settings)
|
||||
.await?
|
||||
{
|
||||
RoomEventDecryptionResult::Decrypted(decrypted) => {
|
||||
@@ -137,11 +137,11 @@ async fn decrypt_sync_room_event(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use matrix_sdk_test::{
|
||||
async_test, event_factory::EventFactory, JoinedRoomBuilder, SyncResponseBuilder,
|
||||
JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
|
||||
};
|
||||
use ruma::{event_id, events::room::member::MembershipState, room_id, user_id};
|
||||
|
||||
use super::{decrypt_from_rooms, Context, E2EE};
|
||||
use super::{Context, E2EE, decrypt_from_rooms};
|
||||
use crate::{room::RoomInfoNotableUpdateReasons, test_utils::logged_in_base_client};
|
||||
|
||||
#[async_test]
|
||||
@@ -192,11 +192,13 @@ mod tests {
|
||||
assert!(room.latest_encrypted_events().is_empty());
|
||||
assert!(room.latest_event().is_none());
|
||||
assert!(context.state_changes.room_infos.is_empty());
|
||||
assert!(!context
|
||||
.room_info_notable_updates
|
||||
.get(room_id)
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.contains(RoomInfoNotableUpdateReasons::LATEST_EVENT));
|
||||
assert!(
|
||||
!context
|
||||
.room_info_notable_updates
|
||||
.get(room_id)
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.contains(RoomInfoNotableUpdateReasons::LATEST_EVENT)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ruma::{
|
||||
OwnedRoomId,
|
||||
push::{Action, PushConditionRoomCtx, Ruleset},
|
||||
serde::Raw,
|
||||
OwnedRoomId, RoomId,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -43,25 +43,21 @@ impl<'a> Notification<'a> {
|
||||
|
||||
fn push_notification(
|
||||
&mut self,
|
||||
room_id: &RoomId,
|
||||
room_id: OwnedRoomId,
|
||||
actions: Vec<Action>,
|
||||
event: RawAnySyncOrStrippedTimelineEvent,
|
||||
) {
|
||||
self.notifications
|
||||
.entry(room_id.to_owned())
|
||||
.or_default()
|
||||
.push(sync::Notification { actions, event });
|
||||
self.notifications.entry(room_id).or_default().push(sync::Notification { actions, event });
|
||||
}
|
||||
|
||||
/// Push a new [`sync::Notification`] in [`Self::notifications`] from
|
||||
/// `event` if and only if `predicate` returns `true` for at least one of
|
||||
/// the [`Action`]s associated to this event and this
|
||||
/// `push_condition_room_ctx`. (based on `Self::push_rules`).
|
||||
/// `push_condition_room_ctx`. (based on [`Self::push_rules`]).
|
||||
///
|
||||
/// This method returns the fetched [`Action`]s.
|
||||
pub fn push_notification_from_event_if<E, P>(
|
||||
pub async fn push_notification_from_event_if<E, P>(
|
||||
&mut self,
|
||||
room_id: &RoomId,
|
||||
push_condition_room_ctx: &PushConditionRoomCtx,
|
||||
event: &Raw<E>,
|
||||
predicate: P,
|
||||
@@ -70,10 +66,14 @@ impl<'a> Notification<'a> {
|
||||
Raw<E>: Into<RawAnySyncOrStrippedTimelineEvent>,
|
||||
P: Fn(&Action) -> bool,
|
||||
{
|
||||
let actions = self.push_rules.get_actions(event, push_condition_room_ctx);
|
||||
let actions = self.push_rules.get_actions(event, push_condition_room_ctx).await;
|
||||
|
||||
if actions.iter().any(predicate) {
|
||||
self.push_notification(room_id, actions.to_owned(), event.clone().into());
|
||||
self.push_notification(
|
||||
push_condition_room_ctx.room_id.clone(),
|
||||
actions.to_owned(),
|
||||
event.clone().into(),
|
||||
);
|
||||
}
|
||||
|
||||
actions
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
use ruma::{
|
||||
events::{
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
SyncStateEvent,
|
||||
},
|
||||
RoomId,
|
||||
events::{
|
||||
SyncStateEvent,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
};
|
||||
|
||||
use super::Context;
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use matrix_sdk_common::timer;
|
||||
|
||||
use super::super::Context;
|
||||
use crate::{
|
||||
room::UpdatedRoomDisplayName, store::BaseStateStore, sync::RoomUpdates,
|
||||
RoomInfoNotableUpdateReasons,
|
||||
RoomInfoNotableUpdateReasons, room::UpdatedRoomDisplayName, store::BaseStateStore,
|
||||
sync::RoomUpdates,
|
||||
};
|
||||
|
||||
pub async fn update_for_rooms(
|
||||
@@ -23,6 +25,8 @@ pub async fn update_for_rooms(
|
||||
room_updates: &RoomUpdates,
|
||||
state_store: &BaseStateStore,
|
||||
) {
|
||||
let _timer = timer!(tracing::Level::TRACE, "display_name::update_for_rooms");
|
||||
|
||||
for room in room_updates.iter_all_room_ids().filter_map(|room_id| state_store.room(room_id)) {
|
||||
// Compute the display name. If it's different, let's register the `RoomInfo` in
|
||||
// the `StateChanges`.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use ruma::RoomId;
|
||||
use tokio::sync::broadcast::Sender;
|
||||
|
||||
use crate::{store::ambiguity_map::AmbiguityCache, RequestedRequiredStates, RoomInfoNotableUpdate};
|
||||
use crate::{RequestedRequiredStates, RoomInfoNotableUpdate, store::ambiguity_map::AmbiguityCache};
|
||||
|
||||
pub mod display_name;
|
||||
pub mod msc4186;
|
||||
|
||||
@@ -15,19 +15,19 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ruma::{
|
||||
api::client::sync::sync_events::v5 as http,
|
||||
events::{receipt::ReceiptEventContent, AnySyncEphemeralRoomEvent, SyncEphemeralRoomEvent},
|
||||
serde::Raw,
|
||||
OwnedRoomId, RoomId,
|
||||
api::client::sync::sync_events::v5 as http,
|
||||
events::{AnySyncEphemeralRoomEvent, SyncEphemeralRoomEvent, receipt::ReceiptEventContent},
|
||||
serde::Raw,
|
||||
};
|
||||
|
||||
use super::super::super::{
|
||||
account_data::for_room as account_data_for_room, ephemeral_events::dispatch_receipt, Context,
|
||||
Context, account_data::for_room as account_data_for_room, ephemeral_events::dispatch_receipt,
|
||||
};
|
||||
use crate::{
|
||||
RoomState,
|
||||
store::BaseStateStore,
|
||||
sync::{JoinedRoomUpdate, RoomUpdates},
|
||||
RoomState,
|
||||
};
|
||||
|
||||
/// Dispatch the ephemeral events in the `extensions.typing` part of the
|
||||
@@ -51,22 +51,20 @@ pub fn dispatch_receipt_ephemeral_event_for_room(
|
||||
context: &mut Context,
|
||||
room_id: &RoomId,
|
||||
receipt: &Raw<SyncEphemeralRoomEvent<ReceiptEventContent>>,
|
||||
joined_room_update: &mut JoinedRoomUpdate,
|
||||
) {
|
||||
let receipt: Raw<AnySyncEphemeralRoomEvent> = receipt.cast_ref().clone();
|
||||
|
||||
dispatch_receipt(context, &receipt, room_id);
|
||||
joined_room_update.ephemeral.push(receipt);
|
||||
}
|
||||
|
||||
pub async fn room_account_data(
|
||||
pub fn room_account_data(
|
||||
context: &mut Context,
|
||||
account_data: &http::response::AccountData,
|
||||
room_updates: &mut RoomUpdates,
|
||||
state_store: &BaseStateStore,
|
||||
) {
|
||||
for (room_id, raw) in &account_data.rooms {
|
||||
account_data_for_room(context, room_id, raw, state_store).await;
|
||||
account_data_for_room(context, room_id, raw, state_store);
|
||||
|
||||
if let Some(room) = state_store.room(room_id) {
|
||||
match room.state() {
|
||||
|
||||
@@ -20,36 +20,35 @@ use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use matrix_sdk_common::deserialized_responses::TimelineEvent;
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use ruma::events::StateEventType;
|
||||
use matrix_sdk_common::timer;
|
||||
use ruma::{
|
||||
JsOption, OwnedRoomId, RoomId, UserId,
|
||||
api::client::sync::sync_events::{
|
||||
v3::{InviteState, InvitedRoom, KnockState, KnockedRoom},
|
||||
v5 as http,
|
||||
},
|
||||
assign,
|
||||
events::{
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
serde::Raw,
|
||||
JsOption, OwnedRoomId, RoomId, UserId,
|
||||
};
|
||||
use tokio::sync::broadcast::Sender;
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use super::super::e2ee;
|
||||
use super::{
|
||||
super::{notification, state_events, timeline, Context},
|
||||
super::{Context, notification, state_events, timeline},
|
||||
RoomCreationData,
|
||||
};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use crate::StateChanges;
|
||||
use crate::{
|
||||
store::BaseStateStore,
|
||||
sync::{InvitedRoomUpdate, JoinedRoomUpdate, KnockedRoomUpdate, LeftRoomUpdate},
|
||||
Result, Room, RoomHero, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons,
|
||||
RoomState,
|
||||
store::BaseStateStore,
|
||||
sync::{InvitedRoomUpdate, JoinedRoomUpdate, KnockedRoomUpdate, LeftRoomUpdate, State},
|
||||
};
|
||||
|
||||
/// Represent any kind of room updates.
|
||||
@@ -69,6 +68,8 @@ pub async fn update_any_room(
|
||||
#[cfg(feature = "e2e-encryption")] e2ee: e2ee::E2EE<'_>,
|
||||
notification: notification::Notification<'_>,
|
||||
) -> Result<Option<(RoomInfo, RoomUpdateKind)>> {
|
||||
let _timer = timer!(tracing::Level::TRACE, "update_any_room");
|
||||
|
||||
let RoomCreationData {
|
||||
room_id,
|
||||
room_info_notable_update_sender,
|
||||
@@ -81,8 +82,8 @@ pub async fn update_any_room(
|
||||
// Don't read state events from the `timeline` field, because they might be
|
||||
// incomplete or staled already. We must only read state events from
|
||||
// `required_state`.
|
||||
let (raw_state_events, state_events) =
|
||||
state_events::sync::collect(&room_response.required_state);
|
||||
let state = State::from_msc4186(room_response.required_state.clone());
|
||||
let (raw_state_events, state_events) = state.collect(&[]);
|
||||
|
||||
let state_store = notification.state_store;
|
||||
|
||||
@@ -119,6 +120,8 @@ pub async fn update_any_room(
|
||||
ambiguity_cache,
|
||||
&mut new_user_ids,
|
||||
state_store,
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
e2ee.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -191,7 +194,7 @@ pub async fn update_any_room(
|
||||
room_info,
|
||||
RoomUpdateKind::Joined(JoinedRoomUpdate::new(
|
||||
timeline,
|
||||
raw_state_events,
|
||||
state,
|
||||
room_account_data.cloned().unwrap_or_default(),
|
||||
ephemeral,
|
||||
notification_count,
|
||||
@@ -204,7 +207,7 @@ pub async fn update_any_room(
|
||||
room_info,
|
||||
RoomUpdateKind::Left(LeftRoomUpdate::new(
|
||||
timeline,
|
||||
raw_state_events,
|
||||
state,
|
||||
room_account_data.cloned().unwrap_or_default(),
|
||||
ambiguity_changes,
|
||||
)),
|
||||
@@ -243,10 +246,10 @@ fn membership(
|
||||
// We need to find the membership event since it could be for either an invited
|
||||
// or knocked room.
|
||||
let membership_event = state_events.1.iter().find_map(|event| {
|
||||
if let AnyStrippedStateEvent::RoomMember(membership_event) = event {
|
||||
if membership_event.state_key == user_id {
|
||||
return Some(membership_event.content.clone());
|
||||
}
|
||||
if let AnyStrippedStateEvent::RoomMember(membership_event) = event
|
||||
&& membership_event.state_key == user_id
|
||||
{
|
||||
return Some(membership_event.content.clone());
|
||||
}
|
||||
None
|
||||
});
|
||||
@@ -434,26 +437,18 @@ pub(crate) async fn cache_latest_events(
|
||||
|
||||
use crate::{
|
||||
deserialized_responses::DisplayName,
|
||||
latest_event::{is_suitable_for_latest_event, LatestEvent, PossibleLatestEvent},
|
||||
latest_event::{LatestEvent, PossibleLatestEvent, is_suitable_for_latest_event},
|
||||
store::ambiguity_map::is_display_name_ambiguous,
|
||||
};
|
||||
|
||||
let _timer = timer!(tracing::Level::TRACE, "cache_latest_events");
|
||||
|
||||
let mut encrypted_events =
|
||||
Vec::with_capacity(room.latest_encrypted_events.read().unwrap().capacity());
|
||||
|
||||
// Try to get room power levels from the current changes
|
||||
let power_levels_from_changes = || {
|
||||
let state_changes = changes?.state.get(room_info.room_id())?;
|
||||
let room_power_levels_state =
|
||||
state_changes.get(&StateEventType::RoomPowerLevels)?.values().next()?;
|
||||
match room_power_levels_state.deserialize().ok()? {
|
||||
AnySyncStateEvent::RoomPowerLevels(ev) => Some(ev.power_levels()),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
// If we didn't get any info, try getting it from local data
|
||||
let power_levels = match power_levels_from_changes() {
|
||||
// Try to get room power levels from the current changes. If we didn't get any
|
||||
// info, try getting it from local data.
|
||||
let power_levels = match changes.and_then(|changes| changes.power_levels(room_info.room_id())) {
|
||||
Some(power_levels) => Some(power_levels),
|
||||
None => room.power_levels().await.ok(),
|
||||
};
|
||||
@@ -509,17 +504,17 @@ pub(crate) async fn cache_latest_events(
|
||||
}
|
||||
|
||||
// Otherwise, look up the sender's profile from the `Store`.
|
||||
if sender_profile.is_none() {
|
||||
if let Some(store) = store {
|
||||
sender_profile = store
|
||||
.get_profile(room.room_id(), timeline_event.sender())
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
if sender_profile.is_none()
|
||||
&& let Some(store) = store
|
||||
{
|
||||
sender_profile = store
|
||||
.get_profile(room.room_id(), timeline_event.sender())
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
// TODO: need to update `sender_name_is_ambiguous`,
|
||||
// but how?
|
||||
}
|
||||
// TODO: need to update `sender_name_is_ambiguous`,
|
||||
// but how?
|
||||
}
|
||||
|
||||
let latest_event = Box::new(LatestEvent::new_with_sender_details(
|
||||
@@ -563,3 +558,11 @@ pub(crate) async fn cache_latest_events(
|
||||
// the latest is last
|
||||
room.latest_encrypted_events.write().unwrap().extend(encrypted_events.into_iter().rev());
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Construct a [`State`] from the state changes for a joined or left room
|
||||
/// from a response of the Simplified Sliding Sync endpoint.
|
||||
fn from_msc4186(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
|
||||
Self::After(events)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,20 +15,23 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use ruma::{
|
||||
api::client::sync::sync_events::v3::{InvitedRoom, JoinedRoom, KnockedRoom, LeftRoom},
|
||||
OwnedRoomId, OwnedUserId, RoomId,
|
||||
api::client::sync::sync_events::v3::{
|
||||
InvitedRoom, JoinedRoom, KnockedRoom, LeftRoom, State as RumaState,
|
||||
},
|
||||
};
|
||||
use tokio::sync::broadcast::Sender;
|
||||
use tracing::error;
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use super::super::e2ee;
|
||||
use super::{
|
||||
super::{account_data, ephemeral_events, notification, state_events, timeline, Context},
|
||||
super::{Context, account_data, ephemeral_events, notification, state_events, timeline},
|
||||
RoomCreationData,
|
||||
};
|
||||
use crate::{
|
||||
sync::{InvitedRoomUpdate, JoinedRoomUpdate, KnockedRoomUpdate, LeftRoomUpdate},
|
||||
Result, RoomInfoNotableUpdate, RoomState,
|
||||
sync::{InvitedRoomUpdate, JoinedRoomUpdate, KnockedRoomUpdate, LeftRoomUpdate, State},
|
||||
};
|
||||
|
||||
/// Process updates of a joined room.
|
||||
@@ -61,10 +64,11 @@ pub async fn update_joined_room(
|
||||
room_info.mark_state_fully_synced();
|
||||
room_info.handle_encryption_state(requested_required_states.for_room(room_id));
|
||||
|
||||
let (raw_state_events, state_events) = state_events::sync::collect(&joined_room.state.events);
|
||||
|
||||
let mut new_user_ids = BTreeSet::new();
|
||||
|
||||
let state = State::from_sync_v2(joined_room.state);
|
||||
let (raw_state_events, state_events) = state.collect(&joined_room.timeline.events);
|
||||
|
||||
state_events::sync::dispatch(
|
||||
context,
|
||||
(&raw_state_events, &state_events),
|
||||
@@ -72,6 +76,8 @@ pub async fn update_joined_room(
|
||||
ambiguity_cache,
|
||||
&mut new_user_ids,
|
||||
state_store,
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
e2ee.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -81,19 +87,6 @@ pub async fn update_joined_room(
|
||||
room_info.mark_members_missing();
|
||||
}
|
||||
|
||||
let (raw_state_events_from_timeline, state_events_from_timeline) =
|
||||
state_events::sync::collect_from_timeline(&joined_room.timeline.events);
|
||||
|
||||
state_events::sync::dispatch(
|
||||
context,
|
||||
(&raw_state_events_from_timeline, &state_events_from_timeline),
|
||||
&mut room_info,
|
||||
ambiguity_cache,
|
||||
&mut new_user_ids,
|
||||
state_store,
|
||||
)
|
||||
.await?;
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
let olm_machine = e2ee.olm_machine;
|
||||
|
||||
@@ -111,7 +104,7 @@ pub async fn update_joined_room(
|
||||
// Save the new `RoomInfo`.
|
||||
context.state_changes.add_room(room_info);
|
||||
|
||||
account_data::for_room(context, room_id, &joined_room.account_data.events, state_store).await;
|
||||
account_data::for_room(context, room_id, &joined_room.account_data.events, state_store);
|
||||
|
||||
// `processors::account_data::from_room` might have updated the `RoomInfo`.
|
||||
// Let's fetch it again.
|
||||
@@ -145,7 +138,7 @@ pub async fn update_joined_room(
|
||||
|
||||
Ok(JoinedRoomUpdate::new(
|
||||
timeline,
|
||||
joined_room.state.events,
|
||||
state,
|
||||
joined_room.account_data.events,
|
||||
joined_room.ephemeral.events,
|
||||
notification_count,
|
||||
@@ -179,7 +172,8 @@ pub async fn update_left_room(
|
||||
room_info.mark_state_partially_synced();
|
||||
room_info.handle_encryption_state(requested_required_states.for_room(room_id));
|
||||
|
||||
let (raw_state_events, state_events) = state_events::sync::collect(&left_room.state.events);
|
||||
let state = State::from_sync_v2(left_room.state);
|
||||
let (raw_state_events, state_events) = state.collect(&left_room.timeline.events);
|
||||
|
||||
state_events::sync::dispatch(
|
||||
context,
|
||||
@@ -188,19 +182,8 @@ pub async fn update_left_room(
|
||||
ambiguity_cache,
|
||||
&mut (),
|
||||
state_store,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (raw_state_events_from_timeline, state_events_from_timeline) =
|
||||
state_events::sync::collect_from_timeline(&left_room.timeline.events);
|
||||
|
||||
state_events::sync::dispatch(
|
||||
context,
|
||||
(&raw_state_events_from_timeline, &state_events_from_timeline),
|
||||
&mut room_info,
|
||||
ambiguity_cache,
|
||||
&mut (),
|
||||
state_store,
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
e2ee.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -218,16 +201,11 @@ pub async fn update_left_room(
|
||||
// Save the new `RoomInfo`.
|
||||
context.state_changes.add_room(room_info);
|
||||
|
||||
account_data::for_room(context, room_id, &left_room.account_data.events, state_store).await;
|
||||
account_data::for_room(context, room_id, &left_room.account_data.events, state_store);
|
||||
|
||||
let ambiguity_changes = ambiguity_cache.changes.remove(room_id).unwrap_or_default();
|
||||
|
||||
Ok(LeftRoomUpdate::new(
|
||||
timeline,
|
||||
left_room.state.events,
|
||||
left_room.account_data.events,
|
||||
ambiguity_changes,
|
||||
))
|
||||
Ok(LeftRoomUpdate::new(timeline, state, left_room.account_data.events, ambiguity_changes))
|
||||
}
|
||||
|
||||
/// Process updates of an invited room.
|
||||
@@ -301,3 +279,19 @@ pub async fn update_knocked_room(
|
||||
|
||||
Ok(knocked_room)
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Construct a [`State`] from the state changes for a joined or left room
|
||||
/// from a response of the sync v2 endpoint.
|
||||
fn from_sync_v2(state: RumaState) -> Self {
|
||||
match state {
|
||||
RumaState::Before(state) => Self::Before(state.events),
|
||||
RumaState::After(state) => Self::After(state.events),
|
||||
// We shouldn't receive other variants because they are opt-in.
|
||||
state => {
|
||||
error!("Unsupported State variant received for joined room: {state:?}");
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use ruma::{
|
||||
RoomId,
|
||||
events::{
|
||||
room::{create::RoomCreateEventContent, tombstone::RoomTombstoneEventContent},
|
||||
AnySyncStateEvent, SyncStateEvent,
|
||||
room::{create::RoomCreateEventContent, tombstone::RoomTombstoneEventContent},
|
||||
},
|
||||
serde::Raw,
|
||||
RoomId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tracing::warn;
|
||||
@@ -33,41 +33,45 @@ pub mod sync {
|
||||
use std::{collections::BTreeSet, iter};
|
||||
|
||||
use ruma::{
|
||||
events::{
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
AnySyncTimelineEvent, SyncStateEvent,
|
||||
},
|
||||
OwnedUserId, RoomId, UserId,
|
||||
events::{
|
||||
AnySyncTimelineEvent, SyncStateEvent,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
};
|
||||
use tracing::{error, instrument};
|
||||
|
||||
use super::{super::profiles, AnySyncStateEvent, Context, Raw};
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
use crate::response_processors::e2ee;
|
||||
use crate::{
|
||||
store::{ambiguity_map::AmbiguityCache, BaseStateStore, Result as StoreResult},
|
||||
RoomInfo,
|
||||
store::{BaseStateStore, Result as StoreResult, ambiguity_map::AmbiguityCache},
|
||||
sync::State,
|
||||
};
|
||||
|
||||
/// Collect [`AnySyncStateEvent`] to [`AnySyncStateEvent`].
|
||||
pub fn collect(
|
||||
raw_events: &[Raw<AnySyncStateEvent>],
|
||||
) -> (Vec<Raw<AnySyncStateEvent>>, Vec<AnySyncStateEvent>) {
|
||||
super::collect(raw_events)
|
||||
}
|
||||
|
||||
/// Collect [`AnySyncTimelineEvent`] to [`AnySyncStateEvent`].
|
||||
///
|
||||
/// A [`AnySyncTimelineEvent`] can represent either message-like events or
|
||||
/// state events. The message-like events are filtered out.
|
||||
pub fn collect_from_timeline(
|
||||
raw_events: &[Raw<AnySyncTimelineEvent>],
|
||||
) -> (Vec<Raw<AnySyncStateEvent>>, Vec<AnySyncStateEvent>) {
|
||||
super::collect(raw_events.iter().filter_map(|raw_event| {
|
||||
// Only state events have a `state_key` field.
|
||||
match raw_event.get_field::<&str>("state_key") {
|
||||
Ok(Some(_)) => Some(raw_event.cast_ref()),
|
||||
_ => None,
|
||||
impl State {
|
||||
/// Collect all the state changes to update the local state, from this
|
||||
/// [`State`] and from the given timeline, if necessary.
|
||||
///
|
||||
/// The events that fail to deserialize are logged and filtered out.
|
||||
pub(crate) fn collect(
|
||||
&self,
|
||||
timeline: &[Raw<AnySyncTimelineEvent>],
|
||||
) -> (Vec<Raw<AnySyncStateEvent>>, Vec<AnySyncStateEvent>) {
|
||||
match self {
|
||||
Self::Before(events) => {
|
||||
super::collect(events.iter().chain(timeline.iter().filter_map(|raw_event| {
|
||||
// Only state events have a `state_key` field.
|
||||
match raw_event.get_field::<&str>("state_key") {
|
||||
Ok(Some(_)) => Some(raw_event.cast_ref_unchecked()),
|
||||
_ => None,
|
||||
}
|
||||
})))
|
||||
}
|
||||
Self::After(events) => super::collect(events),
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatch the sync state events.
|
||||
@@ -87,6 +91,7 @@ pub mod sync {
|
||||
ambiguity_cache: &mut AmbiguityCache,
|
||||
new_users: &mut U,
|
||||
state_store: &BaseStateStore,
|
||||
#[cfg(feature = "experimental-encrypted-state-events")] e2ee: e2ee::E2EE<'_>,
|
||||
) -> StoreResult<()>
|
||||
where
|
||||
U: NewUsers,
|
||||
@@ -107,24 +112,16 @@ pub mod sync {
|
||||
}
|
||||
|
||||
AnySyncStateEvent::RoomCreate(create) => {
|
||||
if super::is_create_event_valid(
|
||||
let edited_create = super::validate_create_event_predecessor(
|
||||
context,
|
||||
room_info.room_id(),
|
||||
create,
|
||||
state_store,
|
||||
) {
|
||||
room_info.handle_state_event(event);
|
||||
} else {
|
||||
error!(
|
||||
room_id = ?room_info.room_id(),
|
||||
?create,
|
||||
"`m.create.tombstone` event is invalid, it creates a loop"
|
||||
);
|
||||
);
|
||||
|
||||
// Do not add the event to `room_info`.
|
||||
// Do not add the event to `context.state_changes.state`.
|
||||
continue;
|
||||
}
|
||||
room_info.handle_state_event(
|
||||
edited_create.map(Into::into).as_ref().unwrap_or(event),
|
||||
);
|
||||
}
|
||||
|
||||
AnySyncStateEvent::RoomTombstone(tombstone) => {
|
||||
@@ -148,6 +145,55 @@ pub mod sync {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
AnySyncStateEvent::RoomEncrypted(SyncStateEvent::Original(outer)) => {
|
||||
use matrix_sdk_crypto::RoomEventDecryptionResult;
|
||||
use tracing::{trace, warn};
|
||||
|
||||
trace!(event_id = ?outer.event_id, "Received encrypted state event, attempting decryption...");
|
||||
|
||||
let Some(olm_machine) = e2ee.olm_machine else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let decrypted_event = olm_machine
|
||||
.try_decrypt_room_event(
|
||||
raw_event.cast_ref_unchecked(),
|
||||
&room_info.room_id,
|
||||
e2ee.decryption_settings,
|
||||
)
|
||||
.await
|
||||
.expect("OlmMachine was not started");
|
||||
|
||||
// Skip state events that failed to decrypt.
|
||||
let RoomEventDecryptionResult::Decrypted(decrypted_event) = decrypted_event
|
||||
else {
|
||||
warn!(event_id = ?outer.event_id, "Failed to decrypt state event");
|
||||
continue;
|
||||
};
|
||||
|
||||
// Cast to `AnySyncTimelineEvent`, safe since this is a supertype of
|
||||
// `AnyTimelineEvent`.
|
||||
let deserialized_event = match decrypted_event
|
||||
.event
|
||||
.deserialize_as::<AnySyncTimelineEvent>()
|
||||
{
|
||||
Ok(event) => event,
|
||||
Err(err) => {
|
||||
warn!(event_id = ?outer.event_id, "Failed to decrypt state event: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure decrypted event is actually a state event.
|
||||
let AnySyncTimelineEvent::State(event) = deserialized_event else {
|
||||
continue;
|
||||
};
|
||||
|
||||
trace!(event_id = ?outer.event_id, "Decrypted state event successfully.");
|
||||
room_info.handle_state_event(&event);
|
||||
}
|
||||
|
||||
_ => {
|
||||
room_info.handle_state_event(event);
|
||||
}
|
||||
@@ -166,7 +212,7 @@ pub mod sync {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Dispatch a [`RoomMemberEventContent>`] state event.
|
||||
/// Dispatch a [`RoomMemberEventContent`] state event.
|
||||
async fn dispatch_room_member<U>(
|
||||
context: &mut Context,
|
||||
room_id: &RoomId,
|
||||
@@ -192,7 +238,7 @@ pub mod sync {
|
||||
}
|
||||
|
||||
/// A trait to collect new users in [`dispatch`].
|
||||
trait NewUsers {
|
||||
pub(crate) trait NewUsers {
|
||||
/// Insert a new user in the collection of new users.
|
||||
fn insert(&mut self, user_id: &UserId);
|
||||
}
|
||||
@@ -221,7 +267,7 @@ pub mod stripped {
|
||||
};
|
||||
use crate::{Result, Room, RoomInfo};
|
||||
|
||||
/// Collect [`AnyStrippedStateEvent`] to [`AnyStrippedStateEvent`].
|
||||
/// Collect [`Raw<AnyStrippedStateEvent>`] to [`AnyStrippedStateEvent`].
|
||||
pub fn collect(
|
||||
raw_events: &[Raw<AnyStrippedStateEvent>],
|
||||
) -> (Vec<Raw<AnyStrippedStateEvent>>, Vec<AnyStrippedStateEvent>) {
|
||||
@@ -270,19 +316,17 @@ pub mod stripped {
|
||||
// We need to check for notifications after we have handled all state
|
||||
// events, to make sure we have the full push context.
|
||||
if let Some(push_condition_room_ctx) =
|
||||
timeline::get_push_room_context(context, room, room_info, notification.state_store)
|
||||
.await?
|
||||
timeline::get_push_room_context(context, room, room_info).await?
|
||||
{
|
||||
let room_id = room.room_id();
|
||||
|
||||
// Check every event again for notification.
|
||||
for event in state_events.values().flat_map(|map| map.values()) {
|
||||
notification.push_notification_from_event_if(
|
||||
room_id,
|
||||
&push_condition_room_ctx,
|
||||
event,
|
||||
Action::should_notify,
|
||||
);
|
||||
notification
|
||||
.push_notification_from_event_if(
|
||||
&push_condition_room_ctx,
|
||||
event,
|
||||
Action::should_notify,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,31 +351,47 @@ where
|
||||
.unzip()
|
||||
}
|
||||
|
||||
/// Check if `m.room.create` isn't creating a loop of rooms.
|
||||
pub fn is_create_event_valid(
|
||||
/// Check if the `predecessor` in `m.room.create` isn't creating a loop of
|
||||
/// rooms.
|
||||
///
|
||||
/// If it is, we return a clone of the event with the predecessor removed.
|
||||
pub fn validate_create_event_predecessor(
|
||||
context: &mut Context,
|
||||
room_id: &RoomId,
|
||||
event: &SyncStateEvent<RoomCreateEventContent>,
|
||||
state_store: &BaseStateStore,
|
||||
) -> bool {
|
||||
) -> Option<SyncStateEvent<RoomCreateEventContent>> {
|
||||
let mut already_seen = BTreeSet::new();
|
||||
already_seen.insert(room_id.to_owned());
|
||||
|
||||
let Some(mut predecessor_room_id) = event
|
||||
.as_original()
|
||||
.and_then(|event| Some(event.content.predecessor.as_ref()?.room_id.clone()))
|
||||
// Redacted and non-redacted create events use the same content type.
|
||||
let content = match event {
|
||||
SyncStateEvent::Original(event) => &event.content,
|
||||
SyncStateEvent::Redacted(event) => &event.content,
|
||||
};
|
||||
|
||||
let Some(mut predecessor_room_id) =
|
||||
content.predecessor.as_ref().map(|predecessor| predecessor.room_id.clone())
|
||||
else {
|
||||
// `true` means no problem. No predecessor = no problem here.
|
||||
return true;
|
||||
// No predecessor = no problem here.
|
||||
return None;
|
||||
};
|
||||
|
||||
loop {
|
||||
// We must check immediately if the `predecessor_room_id` is in `already_seen`
|
||||
// in case of a room is created and marks itself as its predecessor in a single
|
||||
// sync.
|
||||
if already_seen.contains(AsRef::<RoomId>::as_ref(&predecessor_room_id)) {
|
||||
if already_seen.contains(&predecessor_room_id) {
|
||||
// Ahhh, there is a loop with `m.room.create` events!
|
||||
return false;
|
||||
// We remove the predecessor so that we don't process it later.
|
||||
let mut event = event.clone();
|
||||
|
||||
match &mut event {
|
||||
SyncStateEvent::Original(event) => event.content.predecessor.take(),
|
||||
SyncStateEvent::Redacted(event) => event.content.predecessor.take(),
|
||||
};
|
||||
|
||||
return Some(event);
|
||||
}
|
||||
|
||||
already_seen.insert(predecessor_room_id.clone());
|
||||
@@ -356,7 +416,7 @@ pub fn is_create_event_valid(
|
||||
predecessor_room_id = next_predecessor_room_id;
|
||||
}
|
||||
|
||||
true
|
||||
None
|
||||
}
|
||||
|
||||
/// Check if `m.room.tombstone` isn't creating a loop of rooms.
|
||||
@@ -410,16 +470,17 @@ pub fn is_tombstone_event_valid(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use assert_matches2::assert_matches;
|
||||
use matrix_sdk_test::{
|
||||
async_test, event_factory::EventFactory, JoinedRoomBuilder, StateTestEvent,
|
||||
SyncResponseBuilder, DEFAULT_TEST_ROOM_ID,
|
||||
DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, StateTestEvent, SyncResponseBuilder, TestResult,
|
||||
async_test, event_factory::EventFactory,
|
||||
};
|
||||
use ruma::{event_id, room_id, user_id, RoomVersionId};
|
||||
use ruma::{RoomVersionId, event_id, room_id, user_id};
|
||||
|
||||
use crate::test_utils::logged_in_base_client;
|
||||
|
||||
#[async_test]
|
||||
async fn test_not_possible_to_overwrite_m_room_create() {
|
||||
async fn test_not_possible_to_overwrite_m_room_create() -> TestResult {
|
||||
let sender = user_id!("@mnt_io:matrix.org");
|
||||
let event_factory = EventFactory::new().sender(sender);
|
||||
let mut response_builder = SyncResponseBuilder::new();
|
||||
@@ -437,14 +498,14 @@ mod tests {
|
||||
.add_joined_room(
|
||||
JoinedRoomBuilder::new(room_id_0)
|
||||
.add_timeline_event(
|
||||
event_factory.create(sender, RoomVersionId::try_from("42").unwrap()),
|
||||
event_factory.create(sender, RoomVersionId::try_from("42")?),
|
||||
)
|
||||
.add_timeline_event(
|
||||
event_factory.create(sender, RoomVersionId::try_from("43").unwrap()),
|
||||
event_factory.create(sender, RoomVersionId::try_from("43")?),
|
||||
),
|
||||
)
|
||||
.add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
|
||||
event_factory.create(sender, RoomVersionId::try_from("44").unwrap()),
|
||||
event_factory.create(sender, RoomVersionId::try_from("44")?),
|
||||
))
|
||||
.add_joined_room(JoinedRoomBuilder::new(room_id_2))
|
||||
.build_sync_response();
|
||||
@@ -472,13 +533,13 @@ mod tests {
|
||||
{
|
||||
let response = response_builder
|
||||
.add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
|
||||
event_factory.create(sender, RoomVersionId::try_from("45").unwrap()),
|
||||
event_factory.create(sender, RoomVersionId::try_from("45")?),
|
||||
))
|
||||
.add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
|
||||
event_factory.create(sender, RoomVersionId::try_from("46").unwrap()),
|
||||
event_factory.create(sender, RoomVersionId::try_from("46")?),
|
||||
))
|
||||
.add_joined_room(JoinedRoomBuilder::new(room_id_2).add_timeline_event(
|
||||
event_factory.create(sender, RoomVersionId::try_from("47").unwrap()),
|
||||
event_factory.create(sender, RoomVersionId::try_from("47")?),
|
||||
))
|
||||
.build_sync_response();
|
||||
|
||||
@@ -502,6 +563,8 @@ mod tests {
|
||||
"47"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
@@ -519,7 +582,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_check_room_upgrades_no_error() {
|
||||
async fn test_check_room_upgrades_no_error() -> TestResult {
|
||||
let sender = user_id!("@mnt_io:matrix.org");
|
||||
let event_factory = EventFactory::new().sender(sender);
|
||||
let mut response_builder = SyncResponseBuilder::new();
|
||||
@@ -534,7 +597,7 @@ mod tests {
|
||||
let response = response_builder
|
||||
.add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
|
||||
// Room 0 has no predecessor.
|
||||
event_factory.create(sender, RoomVersionId::try_from("41").unwrap()),
|
||||
event_factory.create(sender, RoomVersionId::try_from("41")?),
|
||||
))
|
||||
.build_sync_response();
|
||||
|
||||
@@ -558,8 +621,8 @@ mod tests {
|
||||
JoinedRoomBuilder::new(room_id_1).add_timeline_event(
|
||||
// Predecessor of room 1 is room 0.
|
||||
event_factory
|
||||
.create(sender, RoomVersionId::try_from("42").unwrap())
|
||||
.predecessor(room_id_0, tombstone_event_id),
|
||||
.create(sender, RoomVersionId::try_from("42")?)
|
||||
.predecessor(room_id_0),
|
||||
),
|
||||
)
|
||||
.build_sync_response();
|
||||
@@ -597,8 +660,8 @@ mod tests {
|
||||
JoinedRoomBuilder::new(room_id_2).add_timeline_event(
|
||||
// Predecessor of room 2 is room 1.
|
||||
event_factory
|
||||
.create(sender, RoomVersionId::try_from("43").unwrap())
|
||||
.predecessor(room_id_1, tombstone_event_id),
|
||||
.create(sender, RoomVersionId::try_from("43")?)
|
||||
.predecessor(room_id_1),
|
||||
),
|
||||
)
|
||||
.build_sync_response();
|
||||
@@ -627,10 +690,12 @@ mod tests {
|
||||
);
|
||||
assert!(room_2.successor_room().is_none(), "room 2 must not have a successor");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_check_room_upgrades_no_loop_within_misordered_rooms() {
|
||||
async fn test_check_room_upgrades_no_loop_within_misordered_rooms() -> TestResult {
|
||||
let sender = user_id!("@mnt_io:matrix.org");
|
||||
let event_factory = EventFactory::new().sender(sender);
|
||||
let mut response_builder = SyncResponseBuilder::new();
|
||||
@@ -651,7 +716,7 @@ mod tests {
|
||||
JoinedRoomBuilder::new(room_id_0)
|
||||
.add_timeline_event(
|
||||
// No predecessor for room 0.
|
||||
event_factory.create(sender, RoomVersionId::try_from("41").unwrap()),
|
||||
event_factory.create(sender, RoomVersionId::try_from("41")?),
|
||||
)
|
||||
.add_timeline_event(
|
||||
// Successor of room 0 is room 1.
|
||||
@@ -666,8 +731,8 @@ mod tests {
|
||||
.add_timeline_event(
|
||||
// Predecessor of room 1 is room 0.
|
||||
event_factory
|
||||
.create(sender, RoomVersionId::try_from("42").unwrap())
|
||||
.predecessor(room_id_0, event_id!("$ev0")),
|
||||
.create(sender, RoomVersionId::try_from("42")?)
|
||||
.predecessor(room_id_0),
|
||||
)
|
||||
.add_timeline_event(
|
||||
// Successor of room 1 is room 2.
|
||||
@@ -681,8 +746,8 @@ mod tests {
|
||||
JoinedRoomBuilder::new(room_id_2).add_timeline_event(
|
||||
// Predecessor of room 2 is room 1.
|
||||
event_factory
|
||||
.create(sender, RoomVersionId::try_from("43").unwrap())
|
||||
.predecessor(room_id_1, event_id!("$ev1")),
|
||||
.create(sender, RoomVersionId::try_from("43")?)
|
||||
.predecessor(room_id_1),
|
||||
),
|
||||
)
|
||||
.build_sync_response();
|
||||
@@ -732,10 +797,12 @@ mod tests {
|
||||
);
|
||||
assert!(room_2.successor_room().is_none(), "room 2 must not have a successor");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_check_room_upgrades_shortest_invalid_successor() {
|
||||
async fn test_check_room_upgrades_shortest_invalid_successor() -> TestResult {
|
||||
let sender = user_id!("@mnt_io:matrix.org");
|
||||
let event_factory = EventFactory::new().sender(sender);
|
||||
let mut response_builder = SyncResponseBuilder::new();
|
||||
@@ -767,10 +834,12 @@ mod tests {
|
||||
assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
|
||||
assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_check_room_upgrades_invalid_successor() {
|
||||
async fn test_check_room_upgrades_invalid_successor() -> TestResult {
|
||||
let sender = user_id!("@mnt_io:matrix.org");
|
||||
let event_factory = EventFactory::new().sender(sender);
|
||||
let mut response_builder = SyncResponseBuilder::new();
|
||||
@@ -792,8 +861,8 @@ mod tests {
|
||||
JoinedRoomBuilder::new(room_id_1).add_timeline_event(
|
||||
// Predecessor of room 1 is room 0.
|
||||
event_factory
|
||||
.create(sender, RoomVersionId::try_from("42").unwrap())
|
||||
.predecessor(room_id_0, tombstone_event_id),
|
||||
.create(sender, RoomVersionId::try_from("42")?)
|
||||
.predecessor(room_id_0),
|
||||
),
|
||||
)
|
||||
.build_sync_response();
|
||||
@@ -832,8 +901,8 @@ mod tests {
|
||||
.add_timeline_event(
|
||||
// Predecessor of room 2 is room 1.
|
||||
event_factory
|
||||
.create(sender, RoomVersionId::try_from("43").unwrap())
|
||||
.predecessor(room_id_1, tombstone_event_id),
|
||||
.create(sender, RoomVersionId::try_from("43")?)
|
||||
.predecessor(room_id_1),
|
||||
)
|
||||
.add_timeline_event(
|
||||
// Successor of room 2 is room 0.
|
||||
@@ -880,10 +949,12 @@ mod tests {
|
||||
// this state event is missing because it creates a loop
|
||||
assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_check_room_upgrades_shortest_invalid_predecessor() {
|
||||
async fn test_check_room_upgrades_shortest_invalid_predecessor() -> TestResult {
|
||||
let sender = user_id!("@mnt_io:matrix.org");
|
||||
let event_factory = EventFactory::new().sender(sender);
|
||||
let mut response_builder = SyncResponseBuilder::new();
|
||||
@@ -900,8 +971,8 @@ mod tests {
|
||||
// No successor.
|
||||
JoinedRoomBuilder::new(room_id_0).add_timeline_event(
|
||||
event_factory
|
||||
.create(sender, RoomVersionId::try_from("42").unwrap())
|
||||
.predecessor(room_id_0, tombstone_event_id)
|
||||
.create(sender, RoomVersionId::try_from("42")?)
|
||||
.predecessor(room_id_0)
|
||||
.event_id(tombstone_event_id),
|
||||
),
|
||||
)
|
||||
@@ -910,16 +981,19 @@ mod tests {
|
||||
// The sync doesn't fail but…
|
||||
assert!(client.receive_sync_response(response).await.is_ok());
|
||||
|
||||
// … the state event has not been saved.
|
||||
// … the predecessor has not been saved.
|
||||
let room_0 = client.get_room(room_id_0).unwrap();
|
||||
|
||||
assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
|
||||
assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
|
||||
assert_matches!(room_0.create_content(), Some(_), "room 0 must have a create content");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_check_room_upgrades_shortest_loop() {
|
||||
async fn test_check_room_upgrades_shortest_loop() -> TestResult {
|
||||
let sender = user_id!("@mnt_io:matrix.org");
|
||||
let event_factory = EventFactory::new().sender(sender);
|
||||
let mut response_builder = SyncResponseBuilder::new();
|
||||
@@ -942,8 +1016,8 @@ mod tests {
|
||||
.add_timeline_event(
|
||||
// Predecessor of room 0 is room 0
|
||||
event_factory
|
||||
.create(sender, RoomVersionId::try_from("42").unwrap())
|
||||
.predecessor(room_id_0, tombstone_event_id),
|
||||
.create(sender, RoomVersionId::try_from("42")?)
|
||||
.predecessor(room_id_0),
|
||||
),
|
||||
)
|
||||
.build_sync_response();
|
||||
@@ -951,16 +1025,19 @@ mod tests {
|
||||
// The sync doesn't fail but…
|
||||
assert!(client.receive_sync_response(response).await.is_ok());
|
||||
|
||||
// … the state event has not been saved.
|
||||
// … the tombstone event and the predecessor have not been saved.
|
||||
let room_0 = client.get_room(room_id_0).unwrap();
|
||||
|
||||
assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
|
||||
assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
|
||||
assert_matches!(room_0.create_content(), Some(_), "room 0 must have a create content");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_check_room_upgrades_loop() {
|
||||
async fn test_check_room_upgrades_loop() -> TestResult {
|
||||
let sender = user_id!("@mnt_io:matrix.org");
|
||||
let event_factory = EventFactory::new().sender(sender);
|
||||
let mut response_builder = SyncResponseBuilder::new();
|
||||
@@ -982,8 +1059,8 @@ mod tests {
|
||||
.add_timeline_event(
|
||||
// Predecessor of room 0 is room 2
|
||||
event_factory
|
||||
.create(sender, RoomVersionId::try_from("42").unwrap())
|
||||
.predecessor(room_id_2, event_id!("$ev2")),
|
||||
.create(sender, RoomVersionId::try_from("42")?)
|
||||
.predecessor(room_id_2),
|
||||
)
|
||||
.add_timeline_event(
|
||||
// Successor of room 0 is room 1
|
||||
@@ -997,8 +1074,8 @@ mod tests {
|
||||
.add_timeline_event(
|
||||
// Predecessor of room 1 is room 0
|
||||
event_factory
|
||||
.create(sender, RoomVersionId::try_from("43").unwrap())
|
||||
.predecessor(room_id_0, event_id!("$ev0")),
|
||||
.create(sender, RoomVersionId::try_from("43")?)
|
||||
.predecessor(room_id_0),
|
||||
)
|
||||
.add_timeline_event(
|
||||
// Successor of room 1 is room 2
|
||||
@@ -1012,8 +1089,8 @@ mod tests {
|
||||
.add_timeline_event(
|
||||
// Predecessor of room 2 is room 1
|
||||
event_factory
|
||||
.create(sender, RoomVersionId::try_from("44").unwrap())
|
||||
.predecessor(room_id_1, event_id!("$ev1")),
|
||||
.create(sender, RoomVersionId::try_from("44")?)
|
||||
.predecessor(room_id_1),
|
||||
)
|
||||
.add_timeline_event(
|
||||
// Successor of room 2 is room 0
|
||||
@@ -1060,11 +1137,14 @@ mod tests {
|
||||
// this state event is missing because it creates a loop
|
||||
assert!(room_2.predecessor_room().is_none(), "room 2 must not have a predecessor");
|
||||
assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
|
||||
assert_matches!(room_2.create_content(), Some(_), "room 2 must have a create content");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_state_events_after_sync() {
|
||||
async fn test_state_events_after_sync() -> TestResult {
|
||||
// Given a room
|
||||
let user_id = user_id!("@u:u.to");
|
||||
|
||||
@@ -1081,10 +1161,11 @@ mod tests {
|
||||
.add_joined_room(
|
||||
JoinedRoomBuilder::new(&DEFAULT_TEST_ROOM_ID)
|
||||
.add_timeline_event(room_name)
|
||||
.add_state_event(StateTestEvent::Create)
|
||||
.add_state_event(StateTestEvent::PowerLevels),
|
||||
)
|
||||
.build_sync_response();
|
||||
client.receive_sync_response(response).await.unwrap();
|
||||
client.receive_sync_response(response).await?;
|
||||
|
||||
let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Just-created room not found!");
|
||||
|
||||
@@ -1093,5 +1174,7 @@ mod tests {
|
||||
|
||||
// ensure that we have the topic
|
||||
assert_eq!(room.topic().unwrap(), "this is the test topic in the timeline");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,30 +12,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use matrix_sdk_common::deserialized_responses::TimelineEvent;
|
||||
use matrix_sdk_common::{deserialized_responses::TimelineEvent, timer};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use ruma::events::SyncMessageLikeEvent;
|
||||
use ruma::{
|
||||
events::{
|
||||
room::power_levels::{
|
||||
RoomPowerLevelsEvent, RoomPowerLevelsEventContent, StrippedRoomPowerLevelsEvent,
|
||||
},
|
||||
AnyStrippedStateEvent, AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent,
|
||||
StateEventType,
|
||||
},
|
||||
UInt, UserId, assign,
|
||||
events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent},
|
||||
push::{Action, PushConditionRoomCtx},
|
||||
UInt, UserId,
|
||||
};
|
||||
use tracing::{instrument, trace, warn};
|
||||
|
||||
use super::{Context, notification};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use super::{e2ee, verification};
|
||||
use super::{notification, Context};
|
||||
use crate::{
|
||||
store::{BaseStateStore, StateStoreExt as _},
|
||||
sync::Timeline,
|
||||
Result, Room, RoomInfo,
|
||||
};
|
||||
use crate::{Result, Room, RoomInfo, sync::Timeline};
|
||||
|
||||
/// Process a set of sync timeline event, and create a [`Timeline`].
|
||||
///
|
||||
@@ -44,6 +34,7 @@ use crate::{
|
||||
/// - will process verification,
|
||||
/// - will process redaction,
|
||||
/// - will process notification.
|
||||
#[allow(clippy::extra_unused_lifetimes)]
|
||||
#[instrument(skip_all, fields(room_id = ?room_info.room_id))]
|
||||
pub async fn build<'notification, 'e2ee>(
|
||||
context: &mut Context,
|
||||
@@ -53,9 +44,10 @@ pub async fn build<'notification, 'e2ee>(
|
||||
mut notification: notification::Notification<'notification>,
|
||||
#[cfg(feature = "e2e-encryption")] e2ee: e2ee::E2EE<'e2ee>,
|
||||
) -> Result<Timeline> {
|
||||
let _timer = timer!(tracing::Level::TRACE, "build a timeline from sync");
|
||||
|
||||
let mut timeline = Timeline::new(timeline_inputs.limited, timeline_inputs.prev_batch);
|
||||
let mut push_condition_room_ctx =
|
||||
get_push_room_context(context, room, room_info, notification.state_store).await?;
|
||||
let mut push_condition_room_ctx = get_push_room_context(context, room, room_info).await?;
|
||||
let room_id = room.room_id();
|
||||
|
||||
for raw_event in timeline_inputs.raw_events {
|
||||
@@ -76,16 +68,18 @@ pub async fn build<'notification, 'e2ee>(
|
||||
AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomRedaction(
|
||||
redaction_event,
|
||||
)) => {
|
||||
let room_version = room_info.room_version_or_default();
|
||||
let redaction_rules = room_info.room_version_rules_or_default().redaction;
|
||||
|
||||
if let Some(redacts) = redaction_event.redacts(&room_version) {
|
||||
room_info
|
||||
.handle_redaction(redaction_event, timeline_event.raw().cast_ref());
|
||||
if let Some(redacts) = redaction_event.redacts(&redaction_rules) {
|
||||
room_info.handle_redaction(
|
||||
redaction_event,
|
||||
timeline_event.raw().cast_ref_unchecked(),
|
||||
);
|
||||
|
||||
context.state_changes.add_redaction(
|
||||
room_id,
|
||||
redacts,
|
||||
timeline_event.raw().clone().cast(),
|
||||
timeline_event.raw().clone().cast_unchecked(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -134,17 +128,17 @@ pub async fn build<'notification, 'e2ee>(
|
||||
)
|
||||
} else {
|
||||
push_condition_room_ctx =
|
||||
get_push_room_context(context, room, room_info, notification.state_store)
|
||||
.await?;
|
||||
get_push_room_context(context, room, room_info).await?;
|
||||
}
|
||||
|
||||
if let Some(push_condition_room_ctx) = &push_condition_room_ctx {
|
||||
let actions = notification.push_notification_from_event_if(
|
||||
room_id,
|
||||
push_condition_room_ctx,
|
||||
timeline_event.raw(),
|
||||
Action::should_notify,
|
||||
);
|
||||
let actions = notification
|
||||
.push_notification_from_event_if(
|
||||
push_condition_room_ctx,
|
||||
timeline_event.raw(),
|
||||
Action::should_notify,
|
||||
)
|
||||
.await;
|
||||
|
||||
timeline_event.set_push_actions(actions.to_owned());
|
||||
}
|
||||
@@ -207,23 +201,13 @@ fn update_push_room_context(
|
||||
push_rules.member_count = UInt::new(room_info.active_members_count()).unwrap_or(UInt::MAX);
|
||||
|
||||
// TODO: Use if let chain once stable
|
||||
if let Some(AnySyncStateEvent::RoomMember(member)) =
|
||||
context.state_changes.state.get(room_id).and_then(|events| {
|
||||
events.get(&StateEventType::RoomMember)?.get(user_id.as_str())?.deserialize().ok()
|
||||
})
|
||||
{
|
||||
push_rules.user_display_name = member
|
||||
.as_original()
|
||||
.and_then(|ev| ev.content.displayname.clone())
|
||||
.unwrap_or_else(|| user_id.localpart().to_owned())
|
||||
if let Some(member) = context.state_changes.member(room_id, user_id) {
|
||||
push_rules.user_display_name =
|
||||
member.content.displayname.unwrap_or_else(|| user_id.localpart().to_owned())
|
||||
}
|
||||
|
||||
if let Some(AnySyncStateEvent::RoomPowerLevels(event)) =
|
||||
context.state_changes.state.get(room_id).and_then(|types| {
|
||||
types.get(&StateEventType::RoomPowerLevels)?.get("")?.deserialize().ok()
|
||||
})
|
||||
{
|
||||
push_rules.power_levels = Some(event.power_levels().into());
|
||||
if let Some(power_levels) = context.state_changes.power_levels(room_id) {
|
||||
push_rules.power_levels = Some(power_levels.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +222,6 @@ pub async fn get_push_room_context(
|
||||
context: &Context,
|
||||
room: &Room,
|
||||
room_info: &RoomInfo,
|
||||
state_store: &BaseStateStore,
|
||||
) -> Result<Option<PushConditionRoomCtx>> {
|
||||
let room_id = room.room_id();
|
||||
let user_id = room.own_user_id();
|
||||
@@ -246,19 +229,7 @@ pub async fn get_push_room_context(
|
||||
let member_count = room_info.active_members_count();
|
||||
|
||||
// TODO: Use if let chain once stable
|
||||
let user_display_name = if let Some(AnySyncStateEvent::RoomMember(member)) =
|
||||
context.state_changes.state.get(room_id).and_then(|events| {
|
||||
events.get(&StateEventType::RoomMember)?.get(user_id.as_str())?.deserialize().ok()
|
||||
}) {
|
||||
member
|
||||
.as_original()
|
||||
.and_then(|ev| ev.content.displayname.clone())
|
||||
.unwrap_or_else(|| user_id.localpart().to_owned())
|
||||
} else if let Some(AnyStrippedStateEvent::RoomMember(member)) =
|
||||
context.state_changes.stripped_state.get(room_id).and_then(|events| {
|
||||
events.get(&StateEventType::RoomMember)?.get(user_id.as_str())?.deserialize().ok()
|
||||
})
|
||||
{
|
||||
let user_display_name = if let Some(member) = context.state_changes.member(room_id, user_id) {
|
||||
member.content.displayname.unwrap_or_else(|| user_id.localpart().to_owned())
|
||||
} else if let Some(member) = Box::pin(room.get_member(user_id)).await? {
|
||||
member.name().to_owned()
|
||||
@@ -267,38 +238,19 @@ pub async fn get_push_room_context(
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let power_levels = if let Some(event) =
|
||||
context.state_changes.state.get(room_id).and_then(|types| {
|
||||
types
|
||||
.get(&StateEventType::RoomPowerLevels)?
|
||||
.get("")?
|
||||
.deserialize_as::<RoomPowerLevelsEvent>()
|
||||
.ok()
|
||||
}) {
|
||||
Some(event.power_levels().into())
|
||||
} else if let Some(event) =
|
||||
context.state_changes.stripped_state.get(room_id).and_then(|types| {
|
||||
types
|
||||
.get(&StateEventType::RoomPowerLevels)?
|
||||
.get("")?
|
||||
.deserialize_as::<StrippedRoomPowerLevelsEvent>()
|
||||
.ok()
|
||||
})
|
||||
{
|
||||
Some(event.power_levels().into())
|
||||
let power_levels = if let Some(power_levels) = context.state_changes.power_levels(room_id) {
|
||||
Some(power_levels)
|
||||
} else {
|
||||
state_store
|
||||
.get_state_event_static::<RoomPowerLevelsEventContent>(room_id)
|
||||
.await?
|
||||
.and_then(|e| e.deserialize().ok())
|
||||
.map(|event| event.power_levels().into())
|
||||
room.power_levels().await.ok()
|
||||
};
|
||||
|
||||
Ok(Some(PushConditionRoomCtx {
|
||||
user_id: user_id.to_owned(),
|
||||
room_id: room_id.to_owned(),
|
||||
member_count: UInt::new(member_count).unwrap_or(UInt::MAX),
|
||||
user_display_name,
|
||||
power_levels,
|
||||
}))
|
||||
Ok(Some(assign!(
|
||||
PushConditionRoomCtx::new(
|
||||
room_id.to_owned(),
|
||||
UInt::new(member_count).unwrap_or(UInt::MAX),
|
||||
user_id.to_owned(),
|
||||
user_display_name
|
||||
),
|
||||
{ power_levels: power_levels.map(Into::into) }
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
use ruma::{
|
||||
events::{
|
||||
room::message::MessageType, AnySyncMessageLikeEvent, AnySyncTimelineEvent,
|
||||
SyncMessageLikeEvent,
|
||||
},
|
||||
RoomId,
|
||||
events::{
|
||||
AnySyncMessageLikeEvent, AnySyncTimelineEvent, SyncMessageLikeEvent,
|
||||
room::message::MessageType,
|
||||
},
|
||||
};
|
||||
|
||||
use super::e2ee::E2EE;
|
||||
|
||||
@@ -43,18 +43,18 @@ mod tests {
|
||||
use assign::assign;
|
||||
use matrix_sdk_test::{ALICE, BOB, CAROL};
|
||||
use ruma::{
|
||||
device_id, event_id,
|
||||
DeviceId, EventId, MilliSecondsSinceUnixEpoch, OwnedUserId, UserId, device_id, event_id,
|
||||
events::{
|
||||
AnySyncStateEvent, StateUnsigned, SyncStateEvent,
|
||||
call::member::{
|
||||
ActiveFocus, ActiveLivekitFocus, Application, CallApplicationContent,
|
||||
CallMemberEventContent, CallMemberStateKey, Focus, LegacyMembershipData,
|
||||
LegacyMembershipDataInit, LivekitFocus, OriginalSyncCallMemberEvent,
|
||||
},
|
||||
AnySyncStateEvent, StateUnsigned, SyncStateEvent,
|
||||
},
|
||||
room_id,
|
||||
time::SystemTime,
|
||||
user_id, DeviceId, EventId, MilliSecondsSinceUnixEpoch, OwnedUserId, UserId,
|
||||
user_id,
|
||||
};
|
||||
use similar_asserts::assert_eq;
|
||||
|
||||
@@ -133,17 +133,23 @@ mod tests {
|
||||
"https://lk.org".to_owned(),
|
||||
))];
|
||||
let focus_active = ActiveFocus::Livekit(ActiveLivekitFocus::new());
|
||||
|
||||
let (content, state_key) = match init_data {
|
||||
Some(InitData { device_id, minutes_ago }) => (
|
||||
CallMemberEventContent::new(
|
||||
application,
|
||||
device_id.to_owned(),
|
||||
focus_active,
|
||||
foci_preferred,
|
||||
Some(timestamp(minutes_ago)),
|
||||
),
|
||||
CallMemberStateKey::new(user_id.to_owned(), Some(device_id.to_owned()), false),
|
||||
),
|
||||
Some(InitData { device_id, minutes_ago }) => {
|
||||
let member_id = format!("{device_id}_m.call");
|
||||
(
|
||||
CallMemberEventContent::new(
|
||||
application,
|
||||
device_id.to_owned(),
|
||||
focus_active,
|
||||
foci_preferred,
|
||||
Some(timestamp(minutes_ago)),
|
||||
None,
|
||||
),
|
||||
CallMemberStateKey::new(user_id.to_owned(), Some(member_id), false),
|
||||
)
|
||||
}
|
||||
|
||||
None => (
|
||||
CallMemberEventContent::new_empty(None),
|
||||
CallMemberStateKey::new(user_id.to_owned(), None, false),
|
||||
|
||||
@@ -12,15 +12,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use matrix_sdk_common::ROOM_VERSION_RULES_FALLBACK;
|
||||
use ruma::{
|
||||
assign,
|
||||
OwnedUserId, RoomVersionId, assign,
|
||||
events::{
|
||||
EmptyStateKey, RedactContent, RedactedStateEventContent, StateEventType,
|
||||
macros::EventContent,
|
||||
room::create::{PreviousRoom, RoomCreateEventContent},
|
||||
EmptyStateKey, RedactContent, RedactedStateEventContent,
|
||||
},
|
||||
room::RoomType,
|
||||
OwnedUserId, RoomVersionId,
|
||||
room_version_rules::RedactionRules,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -69,18 +70,38 @@ pub struct RoomCreateWithCreatorEventContent {
|
||||
/// This is currently only used for spaces.
|
||||
#[serde(skip_serializing_if = "Option::is_none", rename = "type")]
|
||||
pub room_type: Option<RoomType>,
|
||||
|
||||
/// Additional room creators, considered to have "infinite" power level, in
|
||||
/// room versions 12 onwards.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub additional_creators: Vec<OwnedUserId>,
|
||||
}
|
||||
|
||||
impl RoomCreateWithCreatorEventContent {
|
||||
/// Constructs a `RoomCreateWithCreatorEventContent` with the given original
|
||||
/// content and sender.
|
||||
pub fn from_event_content(content: RoomCreateEventContent, sender: OwnedUserId) -> Self {
|
||||
let RoomCreateEventContent { federate, room_version, predecessor, room_type, .. } = content;
|
||||
Self { creator: sender, federate, room_version, predecessor, room_type }
|
||||
let RoomCreateEventContent {
|
||||
federate,
|
||||
room_version,
|
||||
predecessor,
|
||||
room_type,
|
||||
additional_creators,
|
||||
..
|
||||
} = content;
|
||||
Self {
|
||||
creator: sender,
|
||||
federate,
|
||||
room_version,
|
||||
predecessor,
|
||||
room_type,
|
||||
additional_creators,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_event_content(self) -> (RoomCreateEventContent, OwnedUserId) {
|
||||
let Self { creator, federate, room_version, predecessor, room_type } = self;
|
||||
let Self { creator, federate, room_version, predecessor, room_type, additional_creators } =
|
||||
self;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let content = assign!(RoomCreateEventContent::new_v11(), {
|
||||
@@ -89,10 +110,25 @@ impl RoomCreateWithCreatorEventContent {
|
||||
room_version,
|
||||
predecessor,
|
||||
room_type,
|
||||
additional_creators,
|
||||
});
|
||||
|
||||
(content, creator)
|
||||
}
|
||||
|
||||
/// Get the creators of the room from this content, according to the room
|
||||
/// version.
|
||||
pub(crate) fn creators(&self) -> Vec<OwnedUserId> {
|
||||
let rules = self.room_version.rules().unwrap_or(ROOM_VERSION_RULES_FALLBACK);
|
||||
|
||||
if rules.authorization.explicitly_privilege_room_creators {
|
||||
std::iter::once(self.creator.clone())
|
||||
.chain(self.additional_creators.iter().cloned())
|
||||
.collect()
|
||||
} else {
|
||||
vec![self.creator.clone()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Redacted form of [`RoomCreateWithCreatorEventContent`].
|
||||
@@ -100,15 +136,19 @@ pub type RedactedRoomCreateWithCreatorEventContent = RoomCreateWithCreatorEventC
|
||||
|
||||
impl RedactedStateEventContent for RedactedRoomCreateWithCreatorEventContent {
|
||||
type StateKey = EmptyStateKey;
|
||||
|
||||
fn event_type(&self) -> StateEventType {
|
||||
StateEventType::RoomCreate
|
||||
}
|
||||
}
|
||||
|
||||
impl RedactContent for RoomCreateWithCreatorEventContent {
|
||||
type Redacted = RedactedRoomCreateWithCreatorEventContent;
|
||||
|
||||
fn redact(self, version: &RoomVersionId) -> Self::Redacted {
|
||||
fn redact(self, rules: &RedactionRules) -> Self::Redacted {
|
||||
let (content, sender) = self.into_event_content();
|
||||
// Use Ruma's redaction algorithm.
|
||||
let content = content.redact(version);
|
||||
let content = content.redact(rules);
|
||||
Self::from_event_content(content, sender)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,17 +17,17 @@ use std::fmt;
|
||||
use as_variant::as_variant;
|
||||
use regex::Regex;
|
||||
use ruma::{
|
||||
events::{member_hints::MemberHintsEventContent, SyncStateEvent},
|
||||
OwnedMxcUri, OwnedUserId, UserId,
|
||||
events::{SyncStateEvent, member_hints::MemberHintsEventContent},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
use super::{Room, RoomMemberships};
|
||||
use crate::{
|
||||
RoomMember, RoomState,
|
||||
deserialized_responses::SyncOrStrippedState,
|
||||
store::{Result as StoreResult, StateStoreExt},
|
||||
RoomMember, RoomState,
|
||||
};
|
||||
|
||||
impl Room {
|
||||
@@ -485,11 +485,7 @@ fn compute_display_name_from_heroes(
|
||||
|
||||
// User is alone.
|
||||
if num_joined_invited <= 1 {
|
||||
if names.is_empty() {
|
||||
RoomDisplayName::Empty
|
||||
} else {
|
||||
RoomDisplayName::EmptyWas(names)
|
||||
}
|
||||
if names.is_empty() { RoomDisplayName::Empty } else { RoomDisplayName::EmptyWas(names) }
|
||||
} else {
|
||||
RoomDisplayName::Calculated(names)
|
||||
}
|
||||
@@ -513,26 +509,27 @@ mod tests {
|
||||
|
||||
use matrix_sdk_test::{async_test, event_factory::EventFactory};
|
||||
use ruma::{
|
||||
UserId,
|
||||
api::client::sync::sync_events::v3::RoomSummary as RumaSummary,
|
||||
assign,
|
||||
events::{
|
||||
StateEventType,
|
||||
room::{
|
||||
canonical_alias::RoomCanonicalAliasEventContent,
|
||||
member::{MembershipState, RoomMemberEventContent, StrippedRoomMemberEvent},
|
||||
name::RoomNameEventContent,
|
||||
},
|
||||
StateEventType,
|
||||
},
|
||||
room_alias_id, room_id,
|
||||
serde::Raw,
|
||||
user_id, UserId,
|
||||
user_id,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use super::{compute_display_name_from_heroes, Room, RoomDisplayName};
|
||||
use super::{Room, RoomDisplayName, compute_display_name_from_heroes};
|
||||
use crate::{
|
||||
store::MemoryStore, MinimalStateEvent, OriginalMinimalStateEvent, RoomState, StateChanges,
|
||||
StateStore,
|
||||
MinimalStateEvent, OriginalMinimalStateEvent, RoomState, StateChanges, StateStore,
|
||||
store::MemoryStore,
|
||||
};
|
||||
|
||||
fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
|
||||
@@ -554,7 +551,7 @@ mod tests {
|
||||
"state_key": user_id,
|
||||
});
|
||||
|
||||
Raw::new(&ev_json).unwrap().cast()
|
||||
Raw::new(&ev_json).unwrap().cast_unchecked()
|
||||
}
|
||||
|
||||
fn make_canonical_alias_event() -> MinimalStateEvent<RoomCanonicalAliasEventContent> {
|
||||
|
||||
@@ -36,6 +36,11 @@ pub enum EncryptionState {
|
||||
/// The room is encrypted.
|
||||
Encrypted,
|
||||
|
||||
/// The room is encrypted, additionally requiring state events to be
|
||||
/// encrypted.
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
StateEncrypted,
|
||||
|
||||
/// The room is not encrypted.
|
||||
NotEncrypted,
|
||||
|
||||
@@ -46,10 +51,25 @@ pub enum EncryptionState {
|
||||
|
||||
impl EncryptionState {
|
||||
/// Check whether `EncryptionState` is [`Encrypted`][Self::Encrypted].
|
||||
#[cfg(not(feature = "experimental-encrypted-state-events"))]
|
||||
pub fn is_encrypted(&self) -> bool {
|
||||
matches!(self, Self::Encrypted)
|
||||
}
|
||||
|
||||
/// Check whether `EncryptionState` is [`Encrypted`][Self::Encrypted] or
|
||||
/// [`StateEncrypted`][Self::StateEncrypted].
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
pub fn is_encrypted(&self) -> bool {
|
||||
matches!(self, Self::Encrypted | Self::StateEncrypted)
|
||||
}
|
||||
|
||||
/// Check whether `EncryptionState` is
|
||||
/// [`StateEncrypted`][Self::StateEncrypted].
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
pub fn is_state_encrypted(&self) -> bool {
|
||||
matches!(self, Self::StateEncrypted)
|
||||
}
|
||||
|
||||
/// Check whether `EncryptionState` is [`Unknown`][Self::Unknown].
|
||||
pub fn is_unknown(&self) -> bool {
|
||||
matches!(self, Self::Unknown)
|
||||
@@ -68,17 +88,18 @@ mod tests {
|
||||
use assert_matches::assert_matches;
|
||||
use matrix_sdk_test::ALICE;
|
||||
use ruma::{
|
||||
EventEncryptionAlgorithm, MilliSecondsSinceUnixEpoch, OwnedEventId,
|
||||
events::{
|
||||
room::encryption::{OriginalSyncRoomEncryptionEvent, RoomEncryptionEventContent},
|
||||
AnySyncStateEvent, EmptyStateKey, StateUnsigned, SyncStateEvent,
|
||||
room::encryption::{OriginalSyncRoomEncryptionEvent, RoomEncryptionEventContent},
|
||||
},
|
||||
room_id,
|
||||
time::SystemTime,
|
||||
user_id, EventEncryptionAlgorithm, MilliSecondsSinceUnixEpoch, OwnedEventId,
|
||||
user_id,
|
||||
};
|
||||
|
||||
use super::{EncryptionState, Room};
|
||||
use crate::{store::MemoryStore, RoomState};
|
||||
use crate::{RoomState, store::MemoryStore};
|
||||
|
||||
fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
|
||||
let store = Arc::new(MemoryStore::new());
|
||||
@@ -154,5 +175,16 @@ mod tests {
|
||||
assert!(EncryptionState::Unknown.is_encrypted().not());
|
||||
assert!(EncryptionState::Encrypted.is_encrypted());
|
||||
assert!(EncryptionState::NotEncrypted.is_encrypted().not());
|
||||
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
{
|
||||
assert!(EncryptionState::StateEncrypted.is_unknown().not());
|
||||
assert!(EncryptionState::StateEncrypted.is_encrypted());
|
||||
|
||||
assert!(EncryptionState::Unknown.is_state_encrypted().not());
|
||||
assert!(EncryptionState::Encrypted.is_state_encrypted().not());
|
||||
assert!(EncryptionState::StateEncrypted.is_state_encrypted());
|
||||
assert!(EncryptionState::NotEncrypted.is_state_encrypted().not());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,19 +16,19 @@ use std::collections::BTreeMap;
|
||||
|
||||
use eyeball::{AsyncLock, ObservableWriteGuard};
|
||||
use ruma::{
|
||||
events::{
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
StateEventType, SyncStateEvent,
|
||||
},
|
||||
OwnedEventId, OwnedUserId,
|
||||
events::{
|
||||
StateEventType, SyncStateEvent,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
};
|
||||
use tracing::warn;
|
||||
|
||||
use super::Room;
|
||||
use crate::{
|
||||
StateStoreDataKey, StateStoreDataValue, StoreError,
|
||||
deserialized_responses::{MemberEvent, RawMemberEvent, SyncOrStrippedState},
|
||||
store::{Result as StoreResult, StateStoreExt},
|
||||
StateStoreDataKey, StateStoreDataValue, StoreError,
|
||||
};
|
||||
|
||||
impl Room {
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
use std::{collections::BTreeMap, num::NonZeroUsize};
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use ruma::{events::AnySyncTimelineEvent, serde::Raw, OwnedRoomId};
|
||||
use ruma::{OwnedRoomId, events::AnySyncTimelineEvent, serde::Raw};
|
||||
|
||||
use super::Room;
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use super::RoomInfoNotableUpdateReasons;
|
||||
use crate::latest_event::LatestEvent;
|
||||
use crate::latest_event::{LatestEvent, LatestEventValue};
|
||||
|
||||
impl Room {
|
||||
/// The size of the latest_encrypted_events RingBuffer
|
||||
@@ -34,6 +34,11 @@ impl Room {
|
||||
self.inner.read().latest_event.as_deref().cloned()
|
||||
}
|
||||
|
||||
/// Return the [`LatestEventValue`] of this room.
|
||||
pub fn new_latest_event(&self) -> LatestEventValue {
|
||||
self.inner.read().new_latest_event.clone()
|
||||
}
|
||||
|
||||
/// Return the most recent few encrypted events. When the keys come through
|
||||
/// to decrypt these, the most recent relevant one will replace
|
||||
/// latest_event. (We can't tell which one is relevant until
|
||||
@@ -88,12 +93,12 @@ mod tests_with_e2e_encryption {
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{
|
||||
BaseClient, Room, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomState,
|
||||
SessionMeta, StateChanges,
|
||||
client::ThreadingSupport,
|
||||
latest_event::LatestEvent,
|
||||
response_processors as processors,
|
||||
store::{MemoryStore, RoomLoadSettings, StoreConfig},
|
||||
BaseClient, Room, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomState,
|
||||
SessionMeta, StateChanges,
|
||||
};
|
||||
|
||||
fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
|
||||
|
||||
@@ -20,24 +20,24 @@ use std::{
|
||||
|
||||
use bitflags::bitflags;
|
||||
use ruma::{
|
||||
MxcUri, OwnedUserId, UserId,
|
||||
events::{
|
||||
MessageLikeEventType, StateEventType,
|
||||
ignored_user_list::IgnoredUserListEventContent,
|
||||
presence::PresenceEvent,
|
||||
room::{
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
power_levels::{PowerLevelAction, RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
power_levels::{PowerLevelAction, RoomPowerLevels, UserPowerLevel},
|
||||
},
|
||||
MessageLikeEventType, StateEventType,
|
||||
},
|
||||
MxcUri, OwnedUserId, UserId,
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
use super::Room;
|
||||
use crate::{
|
||||
deserialized_responses::{DisplayName, MemberEvent, SyncOrStrippedState},
|
||||
store::{ambiguity_map::is_display_name_ambiguous, Result as StoreResult, StateStoreExt},
|
||||
MinimalRoomMemberEvent,
|
||||
deserialized_responses::{DisplayName, MemberEvent},
|
||||
store::{Result as StoreResult, StateStoreExt, ambiguity_map::is_display_name_ambiguous},
|
||||
};
|
||||
|
||||
impl Room {
|
||||
@@ -166,13 +166,7 @@ impl Room {
|
||||
display_names: &'a [DisplayName],
|
||||
) -> StoreResult<MemberRoomInfo<'a>> {
|
||||
let max_power_level = self.max_power_level();
|
||||
let room_creator = self.inner.read().creator().map(ToOwned::to_owned);
|
||||
|
||||
let power_levels = self
|
||||
.store
|
||||
.get_state_event_static(self.room_id())
|
||||
.await?
|
||||
.and_then(|e| e.deserialize().ok());
|
||||
let power_levels = self.power_levels_or_default().await;
|
||||
|
||||
let users_display_names =
|
||||
self.store.get_users_with_display_names(self.room_id(), display_names).await?;
|
||||
@@ -188,7 +182,6 @@ impl Room {
|
||||
Ok(MemberRoomInfo {
|
||||
power_levels: power_levels.into(),
|
||||
max_power_level,
|
||||
room_creator,
|
||||
users_display_names,
|
||||
ignored_users,
|
||||
})
|
||||
@@ -205,9 +198,8 @@ pub struct RoomMember {
|
||||
pub(crate) profile: Arc<Option<MinimalRoomMemberEvent>>,
|
||||
#[allow(dead_code)]
|
||||
pub(crate) presence: Arc<Option<PresenceEvent>>,
|
||||
pub(crate) power_levels: Arc<Option<SyncOrStrippedState<RoomPowerLevelsEventContent>>>,
|
||||
pub(crate) power_levels: Arc<RoomPowerLevels>,
|
||||
pub(crate) max_power_level: i64,
|
||||
pub(crate) is_room_creator: bool,
|
||||
pub(crate) display_name_ambiguous: bool,
|
||||
pub(crate) is_ignored: bool,
|
||||
}
|
||||
@@ -219,15 +211,9 @@ impl RoomMember {
|
||||
presence: Option<PresenceEvent>,
|
||||
room_info: &MemberRoomInfo<'_>,
|
||||
) -> Self {
|
||||
let MemberRoomInfo {
|
||||
power_levels,
|
||||
max_power_level,
|
||||
room_creator,
|
||||
users_display_names,
|
||||
ignored_users,
|
||||
} = room_info;
|
||||
let MemberRoomInfo { power_levels, max_power_level, users_display_names, ignored_users } =
|
||||
room_info;
|
||||
|
||||
let is_room_creator = room_creator.as_deref() == Some(event.user_id());
|
||||
let display_name = event.display_name();
|
||||
let display_name_ambiguous = users_display_names
|
||||
.get(&display_name)
|
||||
@@ -240,7 +226,6 @@ impl RoomMember {
|
||||
presence: presence.into(),
|
||||
power_levels: power_levels.clone(),
|
||||
max_power_level: *max_power_level,
|
||||
is_room_creator,
|
||||
display_name_ambiguous,
|
||||
is_ignored,
|
||||
}
|
||||
@@ -270,11 +255,7 @@ impl RoomMember {
|
||||
/// This returns either the display name or the local part of the user id if
|
||||
/// the member didn't set a display name.
|
||||
pub fn name(&self) -> &str {
|
||||
if let Some(d) = self.display_name() {
|
||||
d
|
||||
} else {
|
||||
self.user_id().localpart()
|
||||
}
|
||||
if let Some(d) = self.display_name() { d } else { self.user_id().localpart() }
|
||||
}
|
||||
|
||||
/// Get the avatar url of the member, if there is one.
|
||||
@@ -289,22 +270,27 @@ impl RoomMember {
|
||||
/// Get the normalized power level of this member.
|
||||
///
|
||||
/// The normalized power level depends on the maximum power level that can
|
||||
/// be found in a certain room, positive values are always in the range of
|
||||
/// 0-100.
|
||||
pub fn normalized_power_level(&self) -> i64 {
|
||||
/// be found in a certain room, positive values that are not `Infinite` are
|
||||
/// always in the range of 0-100.
|
||||
pub fn normalized_power_level(&self) -> UserPowerLevel {
|
||||
let UserPowerLevel::Int(power_level) = self.power_level() else {
|
||||
return UserPowerLevel::Infinite;
|
||||
};
|
||||
|
||||
let mut power_level = i64::from(power_level);
|
||||
|
||||
if self.max_power_level > 0 {
|
||||
(self.power_level() * 100) / self.max_power_level
|
||||
} else {
|
||||
self.power_level()
|
||||
power_level = (power_level * 100) / self.max_power_level;
|
||||
}
|
||||
|
||||
UserPowerLevel::Int(
|
||||
power_level.try_into().expect("normalized power level should fit in Int"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the power level of this member.
|
||||
pub fn power_level(&self) -> i64 {
|
||||
(*self.power_levels)
|
||||
.as_ref()
|
||||
.map(|e| e.power_levels().for_user(self.user_id()).into())
|
||||
.unwrap_or_else(|| if self.is_room_creator { 100 } else { 0 })
|
||||
pub fn power_level(&self) -> UserPowerLevel {
|
||||
self.power_levels.for_user(self.user_id())
|
||||
}
|
||||
|
||||
/// Whether this user can ban other users based on the power levels.
|
||||
@@ -377,11 +363,8 @@ impl RoomMember {
|
||||
self.can_do_impl(|pls| pls.user_can_do(self.user_id(), action))
|
||||
}
|
||||
|
||||
fn can_do_impl(&self, f: impl FnOnce(RoomPowerLevels) -> bool) -> bool {
|
||||
match &*self.power_levels {
|
||||
Some(event) => f(event.power_levels()),
|
||||
None => self.is_room_creator,
|
||||
}
|
||||
fn can_do_impl(&self, f: impl FnOnce(&RoomPowerLevels) -> bool) -> bool {
|
||||
f(&self.power_levels)
|
||||
}
|
||||
|
||||
/// Is the name that the member uses ambiguous in the room.
|
||||
@@ -405,9 +388,8 @@ impl RoomMember {
|
||||
|
||||
// Information about the room a member is in.
|
||||
pub(crate) struct MemberRoomInfo<'a> {
|
||||
pub(crate) power_levels: Arc<Option<SyncOrStrippedState<RoomPowerLevelsEventContent>>>,
|
||||
pub(crate) power_levels: Arc<RoomPowerLevels>,
|
||||
pub(crate) max_power_level: i64,
|
||||
pub(crate) room_creator: Option<OwnedUserId>,
|
||||
pub(crate) users_display_names: HashMap<&'a DisplayName, BTreeSet<OwnedUserId>>,
|
||||
pub(crate) ignored_users: Option<BTreeSet<OwnedUserId>>,
|
||||
}
|
||||
|
||||
@@ -44,11 +44,12 @@ use matrix_sdk_common::ring_buffer::RingBuffer;
|
||||
pub use members::{RoomMember, RoomMembersUpdate, RoomMemberships};
|
||||
pub(crate) use room_info::SyncInfo;
|
||||
pub use room_info::{
|
||||
apply_redaction, BaseRoomInfo, RoomInfo, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons,
|
||||
BaseRoomInfo, InviteAcceptanceDetails, RoomInfo, RoomInfoNotableUpdate,
|
||||
RoomInfoNotableUpdateReasons, apply_redaction,
|
||||
};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use ruma::{events::AnySyncTimelineEvent, serde::Raw};
|
||||
use ruma::{
|
||||
EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId,
|
||||
RoomVersionId, UserId,
|
||||
events::{
|
||||
direct::OwnedDirectUserIdentifier,
|
||||
receipt::{Receipt, ReceiptThread, ReceiptType},
|
||||
@@ -57,12 +58,13 @@ use ruma::{
|
||||
guest_access::GuestAccess,
|
||||
history_visibility::HistoryVisibility,
|
||||
join_rules::JoinRule,
|
||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent, RoomPowerLevelsSource},
|
||||
},
|
||||
},
|
||||
room::RoomType,
|
||||
EventId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use ruma::{events::AnySyncTimelineEvent, serde::Raw};
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use state::{RoomState, RoomStateFilter};
|
||||
pub(crate) use tags::RoomNotableTags;
|
||||
@@ -71,12 +73,12 @@ pub use tombstone::{PredecessorRoom, SuccessorRoom};
|
||||
use tracing::{info, instrument, warn};
|
||||
|
||||
use crate::{
|
||||
Error, MinimalStateEvent,
|
||||
deserialized_responses::MemberEvent,
|
||||
notification_settings::RoomNotificationMode,
|
||||
read_receipts::RoomReadReceipts,
|
||||
store::{DynStateStore, Result as StoreResult, StateStoreExt},
|
||||
sync::UnreadNotificationsCount,
|
||||
Error, MinimalStateEvent,
|
||||
};
|
||||
|
||||
/// The underlying room data structure collecting state for joined, left and
|
||||
@@ -154,9 +156,9 @@ impl Room {
|
||||
&self.room_id
|
||||
}
|
||||
|
||||
/// Get a copy of the room creator.
|
||||
pub fn creator(&self) -> Option<OwnedUserId> {
|
||||
self.inner.read().creator().map(ToOwned::to_owned)
|
||||
/// Get a copy of the room creators.
|
||||
pub fn creators(&self) -> Option<Vec<OwnedUserId>> {
|
||||
self.inner.read().creators()
|
||||
}
|
||||
|
||||
/// Get our own user id.
|
||||
@@ -361,13 +363,32 @@ impl Room {
|
||||
|
||||
/// Get the current power levels of this room.
|
||||
pub async fn power_levels(&self) -> Result<RoomPowerLevels, Error> {
|
||||
Ok(self
|
||||
let power_levels_content = self
|
||||
.store
|
||||
.get_state_event_static::<RoomPowerLevelsEventContent>(self.room_id())
|
||||
.await?
|
||||
.ok_or(Error::InsufficientData)?
|
||||
.deserialize()?
|
||||
.power_levels())
|
||||
.deserialize()?;
|
||||
let creators = self.creators().ok_or(Error::InsufficientData)?;
|
||||
let rules = self.inner.read().room_version_rules_or_default();
|
||||
|
||||
Ok(power_levels_content.power_levels(&rules.authorization, creators))
|
||||
}
|
||||
|
||||
/// Get the current power levels of this room, or a sensible default if they
|
||||
/// are not known.
|
||||
pub async fn power_levels_or_default(&self) -> RoomPowerLevels {
|
||||
if let Ok(power_levels) = self.power_levels().await {
|
||||
return power_levels;
|
||||
}
|
||||
|
||||
// As a fallback, create the default power levels of a room.
|
||||
let rules = self.inner.read().room_version_rules_or_default();
|
||||
RoomPowerLevels::new(
|
||||
RoomPowerLevelsSource::None,
|
||||
&rules.authorization,
|
||||
self.creators().into_iter().flatten(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the `m.room.name` of this room.
|
||||
@@ -450,6 +471,11 @@ impl Room {
|
||||
self.inner.read().base_info.is_marked_unread
|
||||
}
|
||||
|
||||
/// Returns the [`RoomVersionId`] of the room, if known.
|
||||
pub fn version(&self) -> Option<RoomVersionId> {
|
||||
self.inner.read().room_version().cloned()
|
||||
}
|
||||
|
||||
/// Returns the recency stamp of the room.
|
||||
///
|
||||
/// Please read `RoomInfo::recency_stamp` to learn more.
|
||||
@@ -457,9 +483,20 @@ impl Room {
|
||||
self.inner.read().recency_stamp
|
||||
}
|
||||
|
||||
/// Returns the details about an invite to this room if the invite has been
|
||||
/// accepted by this specific client.
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Some` if an invite has been accepted by this specific client.
|
||||
/// - `None` if we didn't join this room using an invite or the invite
|
||||
/// wasn't accepted by this client.
|
||||
pub fn invite_acceptance_details(&self) -> Option<InviteAcceptanceDetails> {
|
||||
self.inner.read().invite_acceptance_details.clone()
|
||||
}
|
||||
|
||||
/// Get a `Stream` of loaded pinned events for this room.
|
||||
/// If no pinned events are found a single empty `Vec` will be returned.
|
||||
pub fn pinned_event_ids_stream(&self) -> impl Stream<Item = Vec<OwnedEventId>> {
|
||||
pub fn pinned_event_ids_stream(&self) -> impl Stream<Item = Vec<OwnedEventId>> + use<> {
|
||||
self.inner
|
||||
.subscribe()
|
||||
.map(|i| i.base_info.pinned_events.map(|c| c.pinned).unwrap_or_default())
|
||||
|
||||
@@ -14,16 +14,22 @@
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
sync::{Arc, atomic::AtomicBool},
|
||||
};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use eyeball::Subscriber;
|
||||
use matrix_sdk_common::{deserialized_responses::TimelineEventKind, ROOM_VERSION_FALLBACK};
|
||||
use matrix_sdk_common::{
|
||||
ROOM_VERSION_FALLBACK, ROOM_VERSION_RULES_FALLBACK, deserialized_responses::TimelineEventKind,
|
||||
};
|
||||
use ruma::{
|
||||
EventId, MilliSecondsSinceUnixEpoch, MxcUri, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId,
|
||||
OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId,
|
||||
api::client::sync::sync_events::v3::RoomSummary as RumaSummary,
|
||||
assign,
|
||||
events::{
|
||||
AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, StateEventType,
|
||||
SyncStateEvent,
|
||||
beacon_info::BeaconInfoEventContent,
|
||||
call::member::{CallMemberEventContent, CallMemberStateKey, MembershipData},
|
||||
direct::OwnedDirectUserIdentifier,
|
||||
@@ -41,31 +47,40 @@ use ruma::{
|
||||
topic::RoomTopicEventContent,
|
||||
},
|
||||
tag::{TagEventContent, TagName, Tags},
|
||||
AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, RedactContent,
|
||||
RedactedStateEventContent, StateEventType, StaticStateEventContent, SyncStateEvent,
|
||||
},
|
||||
room::RoomType,
|
||||
room_version_rules::{AuthorizationRules, RedactionRules, RoomVersionRules},
|
||||
serde::Raw,
|
||||
EventId, MilliSecondsSinceUnixEpoch, MxcUri, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId,
|
||||
OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, field::debug, info, instrument, warn};
|
||||
use tracing::{debug, error, field::debug, info, instrument, warn};
|
||||
|
||||
use super::{
|
||||
AccountDataSource, EncryptionState, Room, RoomCreateWithCreatorEventContent, RoomDisplayName,
|
||||
RoomHero, RoomNotableTags, RoomState, RoomSummary,
|
||||
};
|
||||
use crate::{
|
||||
MinimalStateEvent, OriginalMinimalStateEvent,
|
||||
deserialized_responses::RawSyncOrStrippedState,
|
||||
latest_event::LatestEvent,
|
||||
latest_event::{LatestEvent, LatestEventValue},
|
||||
notification_settings::RoomNotificationMode,
|
||||
read_receipts::RoomReadReceipts,
|
||||
store::{DynStateStore, StateStoreExt},
|
||||
sync::UnreadNotificationsCount,
|
||||
MinimalStateEvent, OriginalMinimalStateEvent,
|
||||
};
|
||||
|
||||
/// A struct remembering details of an invite and if the invite has been
|
||||
/// accepted on this particular client.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct InviteAcceptanceDetails {
|
||||
/// A timestamp remembering when we observed the user accepting an invite
|
||||
/// using this client.
|
||||
pub invite_accepted_at: MilliSecondsSinceUnixEpoch,
|
||||
|
||||
/// The user ID of the person that invited us.
|
||||
pub inviter: OwnedUserId,
|
||||
}
|
||||
|
||||
impl Room {
|
||||
/// Subscribe to the inner `RoomInfo`.
|
||||
pub fn subscribe_info(&self) -> Subscriber<RoomInfo> {
|
||||
@@ -215,7 +230,8 @@ impl BaseRoomInfo {
|
||||
self.tombstone = Some(t.into());
|
||||
}
|
||||
AnySyncStateEvent::RoomPowerLevels(p) => {
|
||||
self.max_power_level = p.power_levels().max().into();
|
||||
// The rules and creators do not affect the max power level.
|
||||
self.max_power_level = p.power_levels(&AuthorizationRules::V1, vec![]).max().into();
|
||||
}
|
||||
AnySyncStateEvent::CallMember(m) => {
|
||||
let Some(o_ev) = m.as_original() else {
|
||||
@@ -291,7 +307,8 @@ impl BaseRoomInfo {
|
||||
self.tombstone = Some(t.into());
|
||||
}
|
||||
AnyStrippedStateEvent::RoomPowerLevels(p) => {
|
||||
self.max_power_level = p.power_levels().max().into();
|
||||
// The rules and creators do not affect the max power level.
|
||||
self.max_power_level = p.power_levels(&AuthorizationRules::V1, vec![]).max().into();
|
||||
}
|
||||
AnyStrippedStateEvent::CallMember(_) => {
|
||||
// Ignore stripped call state events. Rooms that are not in Joined or Left state
|
||||
@@ -310,27 +327,48 @@ impl BaseRoomInfo {
|
||||
}
|
||||
|
||||
pub(super) fn handle_redaction(&mut self, redacts: &EventId) {
|
||||
let room_version = self.room_version().unwrap_or(&ROOM_VERSION_FALLBACK).to_owned();
|
||||
let redaction_rules = self
|
||||
.room_version()
|
||||
.and_then(|room_version| room_version.rules())
|
||||
.unwrap_or(ROOM_VERSION_RULES_FALLBACK)
|
||||
.redaction;
|
||||
|
||||
// FIXME: Use let chains once available to get rid of unwrap()s
|
||||
if self.avatar.has_event_id(redacts) {
|
||||
self.avatar.as_mut().unwrap().redact(&room_version);
|
||||
} else if self.canonical_alias.has_event_id(redacts) {
|
||||
self.canonical_alias.as_mut().unwrap().redact(&room_version);
|
||||
} else if self.create.has_event_id(redacts) {
|
||||
self.create.as_mut().unwrap().redact(&room_version);
|
||||
} else if self.guest_access.has_event_id(redacts) {
|
||||
self.guest_access.as_mut().unwrap().redact(&room_version);
|
||||
} else if self.history_visibility.has_event_id(redacts) {
|
||||
self.history_visibility.as_mut().unwrap().redact(&room_version);
|
||||
} else if self.join_rules.has_event_id(redacts) {
|
||||
self.join_rules.as_mut().unwrap().redact(&room_version);
|
||||
} else if self.name.has_event_id(redacts) {
|
||||
self.name.as_mut().unwrap().redact(&room_version);
|
||||
} else if self.tombstone.has_event_id(redacts) {
|
||||
self.tombstone.as_mut().unwrap().redact(&room_version);
|
||||
} else if self.topic.has_event_id(redacts) {
|
||||
self.topic.as_mut().unwrap().redact(&room_version);
|
||||
if let Some(ev) = &mut self.avatar
|
||||
&& ev.event_id() == Some(redacts)
|
||||
{
|
||||
ev.redact(&redaction_rules);
|
||||
} else if let Some(ev) = &mut self.canonical_alias
|
||||
&& ev.event_id() == Some(redacts)
|
||||
{
|
||||
ev.redact(&redaction_rules);
|
||||
} else if let Some(ev) = &mut self.create
|
||||
&& ev.event_id() == Some(redacts)
|
||||
{
|
||||
ev.redact(&redaction_rules);
|
||||
} else if let Some(ev) = &mut self.guest_access
|
||||
&& ev.event_id() == Some(redacts)
|
||||
{
|
||||
ev.redact(&redaction_rules);
|
||||
} else if let Some(ev) = &mut self.history_visibility
|
||||
&& ev.event_id() == Some(redacts)
|
||||
{
|
||||
ev.redact(&redaction_rules);
|
||||
} else if let Some(ev) = &mut self.join_rules
|
||||
&& ev.event_id() == Some(redacts)
|
||||
{
|
||||
ev.redact(&redaction_rules);
|
||||
} else if let Some(ev) = &mut self.name
|
||||
&& ev.event_id() == Some(redacts)
|
||||
{
|
||||
ev.redact(&redaction_rules);
|
||||
} else if let Some(ev) = &mut self.tombstone
|
||||
&& ev.event_id() == Some(redacts)
|
||||
{
|
||||
ev.redact(&redaction_rules);
|
||||
} else if let Some(ev) = &mut self.topic
|
||||
&& ev.event_id() == Some(redacts)
|
||||
{
|
||||
ev.redact(&redaction_rules);
|
||||
} else {
|
||||
self.rtc_member_events
|
||||
.retain(|_, member_event| member_event.event_id() != Some(redacts));
|
||||
@@ -377,20 +415,6 @@ impl Default for BaseRoomInfo {
|
||||
}
|
||||
}
|
||||
|
||||
trait OptionExt {
|
||||
fn has_event_id(&self, ev_id: &EventId) -> bool;
|
||||
}
|
||||
|
||||
impl<C> OptionExt for Option<MinimalStateEvent<C>>
|
||||
where
|
||||
C: StaticStateEventContent + RedactContent,
|
||||
C::Redacted: RedactedStateEventContent,
|
||||
{
|
||||
fn has_event_id(&self, ev_id: &EventId) -> bool {
|
||||
self.as_ref().is_some_and(|ev| ev.event_id() == Some(ev_id))
|
||||
}
|
||||
}
|
||||
|
||||
/// The underlying pure data structure for joined and left rooms.
|
||||
///
|
||||
/// Holds all the info needed to persist a room into the state store.
|
||||
@@ -429,8 +453,16 @@ pub struct RoomInfo {
|
||||
pub(crate) encryption_state_synced: bool,
|
||||
|
||||
/// The last event send by sliding sync
|
||||
///
|
||||
/// TODO(@hywan): Remove.
|
||||
pub(crate) latest_event: Option<Box<LatestEvent>>,
|
||||
|
||||
/// The latest event value of this room.
|
||||
///
|
||||
/// TODO(@hywan): Rename to `latest_event`.
|
||||
#[serde(default)]
|
||||
pub(crate) new_latest_event: LatestEventValue,
|
||||
|
||||
/// Information about read receipts for this room.
|
||||
#[serde(default)]
|
||||
pub(crate) read_receipts: RoomReadReceipts,
|
||||
@@ -439,11 +471,11 @@ pub struct RoomInfo {
|
||||
/// room state.
|
||||
pub(crate) base_info: Box<BaseRoomInfo>,
|
||||
|
||||
/// Did we already warn about an unknown room version in
|
||||
/// [`RoomInfo::room_version_or_default`]? This is done to avoid
|
||||
/// spamming about unknown room versions in the log for the same room.
|
||||
/// Whether we already warned about unknown room version rules in
|
||||
/// [`RoomInfo::room_version_rules_or_default`]. This is done to avoid
|
||||
/// spamming about unknown room versions rules in the log for the same room.
|
||||
#[serde(skip)]
|
||||
pub(crate) warned_about_unknown_room_version: Arc<AtomicBool>,
|
||||
pub(crate) warned_about_unknown_room_version_rules: Arc<AtomicBool>,
|
||||
|
||||
/// Cached display name, useful for sync access.
|
||||
///
|
||||
@@ -471,7 +503,7 @@ pub struct RoomInfo {
|
||||
/// This is useful to remember if the user accepted this a join on this
|
||||
/// specific client.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub(crate) invite_accepted_at: Option<MilliSecondsSinceUnixEpoch>,
|
||||
pub(crate) invite_acceptance_details: Option<InviteAcceptanceDetails>,
|
||||
}
|
||||
|
||||
impl RoomInfo {
|
||||
@@ -488,13 +520,14 @@ impl RoomInfo {
|
||||
sync_info: SyncInfo::NoState,
|
||||
encryption_state_synced: false,
|
||||
latest_event: None,
|
||||
new_latest_event: LatestEventValue::default(),
|
||||
read_receipts: Default::default(),
|
||||
base_info: Box::new(BaseRoomInfo::new()),
|
||||
warned_about_unknown_room_version: Arc::new(false.into()),
|
||||
warned_about_unknown_room_version_rules: Arc::new(false.into()),
|
||||
cached_display_name: None,
|
||||
cached_user_defined_notification_mode: None,
|
||||
recency_stamp: None,
|
||||
invite_accepted_at: None,
|
||||
invite_acceptance_details: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,6 +558,12 @@ impl RoomInfo {
|
||||
|
||||
/// Set the membership RoomState of this Room
|
||||
pub fn set_state(&mut self, room_state: RoomState) {
|
||||
if self.state() != RoomState::Joined && self.invite_acceptance_details.is_some() {
|
||||
error!(room_id = %self.room_id, "The RoomInfo contains invite acceptance details but the room is not in the joined state");
|
||||
}
|
||||
// Changing our state removes the invite details since we can't know that they
|
||||
// are relevant anymore.
|
||||
self.invite_acceptance_details = None;
|
||||
self.room_state = room_state;
|
||||
}
|
||||
|
||||
@@ -586,6 +625,7 @@ impl RoomInfo {
|
||||
}
|
||||
|
||||
/// Returns the encryption state of this room.
|
||||
#[cfg(not(feature = "experimental-encrypted-state-events"))]
|
||||
pub fn encryption_state(&self) -> EncryptionState {
|
||||
if !self.encryption_state_synced {
|
||||
EncryptionState::Unknown
|
||||
@@ -596,6 +636,26 @@ impl RoomInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the encryption state of this room.
|
||||
#[cfg(feature = "experimental-encrypted-state-events")]
|
||||
pub fn encryption_state(&self) -> EncryptionState {
|
||||
if !self.encryption_state_synced {
|
||||
EncryptionState::Unknown
|
||||
} else {
|
||||
self.base_info
|
||||
.encryption
|
||||
.as_ref()
|
||||
.map(|state| {
|
||||
if state.encrypt_state_events {
|
||||
EncryptionState::StateEncrypted
|
||||
} else {
|
||||
EncryptionState::Encrypted
|
||||
}
|
||||
})
|
||||
.unwrap_or(EncryptionState::NotEncrypted)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the encryption event content in this room.
|
||||
pub fn set_encryption_event(&mut self, event: Option<RoomEncryptionEventContent>) {
|
||||
self.base_info.encryption = event;
|
||||
@@ -652,9 +712,9 @@ impl RoomInfo {
|
||||
event: &SyncRoomRedactionEvent,
|
||||
_raw: &Raw<SyncRoomRedactionEvent>,
|
||||
) {
|
||||
let room_version = self.room_version_or_default();
|
||||
let redaction_rules = self.room_version_rules_or_default().redaction;
|
||||
|
||||
let Some(redacts) = event.redacts(&room_version) else {
|
||||
let Some(redacts) = event.redacts(&redaction_rules) else {
|
||||
info!("Can't apply redaction, redacts field is missing");
|
||||
return;
|
||||
};
|
||||
@@ -663,7 +723,7 @@ impl RoomInfo {
|
||||
if let Some(latest_event) = &mut self.latest_event {
|
||||
tracing::trace!("Checking if redaction applies to latest event");
|
||||
if latest_event.event_id().as_deref() == Some(redacts) {
|
||||
match apply_redaction(latest_event.event().raw(), _raw, &room_version) {
|
||||
match apply_redaction(latest_event.event().raw(), _raw, &redaction_rules) {
|
||||
Some(redacted) => {
|
||||
// Even if the original event was encrypted, redaction removes all its
|
||||
// fields so it cannot possibly be successfully decrypted after redaction.
|
||||
@@ -758,10 +818,8 @@ impl RoomInfo {
|
||||
self.summary.invited_member_count = count;
|
||||
}
|
||||
|
||||
/// Mark that the user has accepted an invite and remember when this has
|
||||
/// happened using a timestamp set to [`MilliSecondsSinceUnixEpoch::now()`].
|
||||
pub(crate) fn set_invite_accepted_now(&mut self) {
|
||||
self.invite_accepted_at = Some(MilliSecondsSinceUnixEpoch::now());
|
||||
pub(crate) fn set_invite_acceptance_details(&mut self, details: InviteAcceptanceDetails) {
|
||||
self.invite_acceptance_details = Some(details);
|
||||
}
|
||||
|
||||
/// Returns the timestamp when an invite to this room has been accepted by
|
||||
@@ -770,8 +828,8 @@ impl RoomInfo {
|
||||
/// # Returns
|
||||
/// - `Some` if the invite has been accepted by this specific client.
|
||||
/// - `None` if the invite has not been accepted
|
||||
pub fn invite_accepted_at(&self) -> Option<MilliSecondsSinceUnixEpoch> {
|
||||
self.invite_accepted_at
|
||||
pub fn invite_acceptance_details(&self) -> Option<InviteAcceptanceDetails> {
|
||||
self.invite_acceptance_details.clone()
|
||||
}
|
||||
|
||||
/// Updates the room heroes.
|
||||
@@ -826,24 +884,26 @@ impl RoomInfo {
|
||||
self.base_info.room_version()
|
||||
}
|
||||
|
||||
/// Get the room version of this room, or a sensible default.
|
||||
/// Get the room version rules of this room, or a sensible default.
|
||||
///
|
||||
/// Will warn (at most once) if the room creation event is missing from this
|
||||
/// [`RoomInfo`].
|
||||
pub fn room_version_or_default(&self) -> RoomVersionId {
|
||||
/// Will warn (at most once) if the room create event is missing from this
|
||||
/// [`RoomInfo`] or if the room version is unsupported.
|
||||
pub fn room_version_rules_or_default(&self) -> RoomVersionRules {
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
self.base_info.room_version().cloned().unwrap_or_else(|| {
|
||||
if self
|
||||
.warned_about_unknown_room_version
|
||||
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
warn!("Unknown room version, falling back to {ROOM_VERSION_FALLBACK}");
|
||||
}
|
||||
self.base_info.room_version().and_then(|room_version| room_version.rules()).unwrap_or_else(
|
||||
|| {
|
||||
if self
|
||||
.warned_about_unknown_room_version_rules
|
||||
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
warn!("Unable to get the room version rules, defaulting to rules for room version {ROOM_VERSION_FALLBACK}");
|
||||
}
|
||||
|
||||
ROOM_VERSION_FALLBACK
|
||||
})
|
||||
ROOM_VERSION_RULES_FALLBACK
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the room type of this room.
|
||||
@@ -854,11 +914,11 @@ impl RoomInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the creator of this room.
|
||||
pub fn creator(&self) -> Option<&UserId> {
|
||||
/// Get the creators of this room.
|
||||
pub fn creators(&self) -> Option<Vec<OwnedUserId>> {
|
||||
match self.base_info.create.as_ref()? {
|
||||
MinimalStateEvent::Original(ev) => Some(&ev.content.creator),
|
||||
MinimalStateEvent::Redacted(ev) => Some(&ev.content.creator),
|
||||
MinimalStateEvent::Original(ev) => Some(ev.content.creators()),
|
||||
MinimalStateEvent::Redacted(ev) => Some(ev.content.creators()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -983,6 +1043,11 @@ impl RoomInfo {
|
||||
self.latest_event.as_deref()
|
||||
}
|
||||
|
||||
/// Sets the new `LatestEventValue`.
|
||||
pub fn set_new_latest_event(&mut self, new_value: LatestEventValue) {
|
||||
self.new_latest_event = new_value;
|
||||
}
|
||||
|
||||
/// Updates the recency stamp of this room.
|
||||
///
|
||||
/// Please read [`Self::recency_stamp`] to learn more.
|
||||
@@ -1093,9 +1158,9 @@ pub(crate) enum SyncInfo {
|
||||
pub fn apply_redaction(
|
||||
event: &Raw<AnySyncTimelineEvent>,
|
||||
raw_redaction: &Raw<SyncRoomRedactionEvent>,
|
||||
room_version: &RoomVersionId,
|
||||
rules: &RedactionRules,
|
||||
) -> Option<Raw<AnySyncTimelineEvent>> {
|
||||
use ruma::canonical_json::{redact_in_place, RedactedBecause};
|
||||
use ruma::canonical_json::{RedactedBecause, redact_in_place};
|
||||
|
||||
let mut event_json = match event.deserialize_as() {
|
||||
Ok(json) => json,
|
||||
@@ -1113,7 +1178,7 @@ pub fn apply_redaction(
|
||||
}
|
||||
};
|
||||
|
||||
let redact_result = redact_in_place(&mut event_json, room_version, Some(redacted_because));
|
||||
let redact_result = redact_in_place(&mut event_json, rules, Some(redacted_because));
|
||||
|
||||
if let Err(e) = redact_result {
|
||||
warn!("Failed to redact event: {e}");
|
||||
@@ -1121,12 +1186,12 @@ pub fn apply_redaction(
|
||||
}
|
||||
|
||||
let raw = Raw::new(&event_json).expect("CanonicalJsonObject must be serializable");
|
||||
Some(raw.cast())
|
||||
Some(raw.cast_unchecked())
|
||||
}
|
||||
|
||||
/// Indicates that a notable update of `RoomInfo` has been applied, and why.
|
||||
///
|
||||
/// A room info notable update is an update that can be interested for other
|
||||
/// A room info notable update is an update that can be interesting for other
|
||||
/// parts of the code. This mechanism is used in coordination with
|
||||
/// [`BaseClient::room_info_notable_update_receiver`][baseclient] (and
|
||||
/// `Room::inner` plus `Room::room_info_notable_update_sender`) where `RoomInfo`
|
||||
@@ -1188,10 +1253,11 @@ impl Default for RoomInfoNotableUpdateReasons {
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use matrix_sdk_common::deserialized_responses::TimelineEvent;
|
||||
use matrix_sdk_test::{
|
||||
async_test,
|
||||
test_json::{sync_events::PINNED_EVENTS, TAG},
|
||||
test_json::{TAG, sync_events::PINNED_EVENTS},
|
||||
};
|
||||
use ruma::{
|
||||
assign, events::room::pinned_events::RoomPinnedEventsEventContent, owned_event_id,
|
||||
@@ -1200,14 +1266,14 @@ mod tests {
|
||||
use serde_json::json;
|
||||
use similar_asserts::assert_eq;
|
||||
|
||||
use super::{BaseRoomInfo, RoomInfo, SyncInfo};
|
||||
use super::{BaseRoomInfo, LatestEventValue, RoomInfo, SyncInfo};
|
||||
use crate::{
|
||||
RoomDisplayName, RoomHero, RoomState, StateChanges,
|
||||
latest_event::LatestEvent,
|
||||
notification_settings::RoomNotificationMode,
|
||||
room::{RoomNotableTags, RoomSummary},
|
||||
store::{IntoStateStore, MemoryStore},
|
||||
sync::UnreadNotificationsCount,
|
||||
RoomDisplayName, RoomHero, RoomState, StateChanges,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -1239,15 +1305,16 @@ mod tests {
|
||||
latest_event: Some(Box::new(LatestEvent::new(TimelineEvent::from_plaintext(
|
||||
Raw::from_json_string(json!({"sender": "@u:i.uk"}).to_string()).unwrap(),
|
||||
)))),
|
||||
new_latest_event: LatestEventValue::None,
|
||||
base_info: Box::new(
|
||||
assign!(BaseRoomInfo::new(), { pinned_events: Some(RoomPinnedEventsEventContent::new(vec![owned_event_id!("$a")])) }),
|
||||
),
|
||||
read_receipts: Default::default(),
|
||||
warned_about_unknown_room_version: Arc::new(false.into()),
|
||||
warned_about_unknown_room_version_rules: Arc::new(false.into()),
|
||||
cached_display_name: None,
|
||||
cached_user_defined_notification_mode: None,
|
||||
recency_stamp: Some(42),
|
||||
invite_accepted_at: None,
|
||||
invite_acceptance_details: None,
|
||||
};
|
||||
|
||||
let info_json = json!({
|
||||
@@ -1277,6 +1344,7 @@ mod tests {
|
||||
"thread_summary": "None"
|
||||
},
|
||||
},
|
||||
"new_latest_event": "None",
|
||||
"base_info": {
|
||||
"avatar": null,
|
||||
"canonical_alias": null,
|
||||
@@ -1387,11 +1455,11 @@ mod tests {
|
||||
// Add events to the store.
|
||||
let mut changes = StateChanges::default();
|
||||
|
||||
let raw_tag_event = Raw::new(&*TAG).unwrap().cast();
|
||||
let raw_tag_event = Raw::new(&*TAG).unwrap().cast_unchecked();
|
||||
let tag_event = raw_tag_event.deserialize().unwrap();
|
||||
changes.add_room_account_data(&room_info.room_id, tag_event, raw_tag_event);
|
||||
|
||||
let raw_pinned_events_event = Raw::new(&*PINNED_EVENTS).unwrap().cast();
|
||||
let raw_pinned_events_event = Raw::new(&*PINNED_EVENTS).unwrap().cast_unchecked();
|
||||
let pinned_events_event = raw_pinned_events_event.deserialize().unwrap();
|
||||
changes.add_state_event(&room_info.room_id, pinned_events_event, raw_pinned_events_event);
|
||||
|
||||
@@ -1472,6 +1540,7 @@ mod tests {
|
||||
assert_eq!(info.sync_info, SyncInfo::FullySynced);
|
||||
assert!(info.encryption_state_synced);
|
||||
assert!(info.latest_event.is_none());
|
||||
assert_matches!(info.new_latest_event, LatestEventValue::None);
|
||||
assert!(info.base_info.avatar.is_none());
|
||||
assert!(info.base_info.canonical_alias.is_none());
|
||||
assert!(info.base_info.create.is_none());
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
use bitflags::bitflags;
|
||||
use ruma::events::{tag::Tags, AnyRoomAccountDataEvent, RoomAccountDataEventType};
|
||||
use ruma::events::{AnyRoomAccountDataEvent, RoomAccountDataEventType, tag::Tags};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::Room;
|
||||
@@ -82,10 +82,10 @@ mod tests {
|
||||
|
||||
use super::{super::BaseRoomInfo, RoomNotableTags};
|
||||
use crate::{
|
||||
BaseClient, RoomState, SessionMeta,
|
||||
client::ThreadingSupport,
|
||||
response_processors as processors,
|
||||
store::{RoomLoadSettings, StoreConfig},
|
||||
BaseClient, RoomState, SessionMeta,
|
||||
};
|
||||
|
||||
#[async_test]
|
||||
@@ -132,13 +132,12 @@ mod tests {
|
||||
"type": "m.tag",
|
||||
}))
|
||||
.unwrap()
|
||||
.cast();
|
||||
.cast_unchecked();
|
||||
|
||||
// When the new tag is handled and applied.
|
||||
let mut context = processors::Context::default();
|
||||
|
||||
processors::account_data::for_room(&mut context, room_id, &[tag_raw], &client.state_store)
|
||||
.await;
|
||||
processors::account_data::for_room(&mut context, room_id, &[tag_raw], &client.state_store);
|
||||
|
||||
processors::changes::save_and_apply(
|
||||
context.clone(),
|
||||
@@ -164,10 +163,9 @@ mod tests {
|
||||
"type": "m.tag"
|
||||
}))
|
||||
.unwrap()
|
||||
.cast();
|
||||
.cast_unchecked();
|
||||
|
||||
processors::account_data::for_room(&mut context, room_id, &[tag_raw], &client.state_store)
|
||||
.await;
|
||||
processors::account_data::for_room(&mut context, room_id, &[tag_raw], &client.state_store);
|
||||
|
||||
processors::changes::save_and_apply(
|
||||
context,
|
||||
@@ -230,13 +228,12 @@ mod tests {
|
||||
"type": "m.tag"
|
||||
}))
|
||||
.unwrap()
|
||||
.cast();
|
||||
.cast_unchecked();
|
||||
|
||||
// When the new tag is handled and applied.
|
||||
let mut context = processors::Context::default();
|
||||
|
||||
processors::account_data::for_room(&mut context, room_id, &[tag_raw], &client.state_store)
|
||||
.await;
|
||||
processors::account_data::for_room(&mut context, room_id, &[tag_raw], &client.state_store);
|
||||
|
||||
processors::changes::save_and_apply(
|
||||
context.clone(),
|
||||
@@ -262,10 +259,9 @@ mod tests {
|
||||
"type": "m.tag"
|
||||
}))
|
||||
.unwrap()
|
||||
.cast();
|
||||
.cast_unchecked();
|
||||
|
||||
processors::account_data::for_room(&mut context, room_id, &[tag_raw], &client.state_store)
|
||||
.await;
|
||||
processors::account_data::for_room(&mut context, room_id, &[tag_raw], &client.state_store);
|
||||
|
||||
processors::changes::save_and_apply(
|
||||
context,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
use std::ops::Not;
|
||||
|
||||
use ruma::{events::room::tombstone::RoomTombstoneEventContent, OwnedEventId, OwnedRoomId};
|
||||
use ruma::{OwnedRoomId, events::room::tombstone::RoomTombstoneEventContent};
|
||||
|
||||
use super::Room;
|
||||
|
||||
@@ -67,12 +67,9 @@ impl Room {
|
||||
/// [`m.room.tombstone`]: https://spec.matrix.org/v1.14/client-server-api/#mroomtombstone
|
||||
/// [`m.room.create`]: https://spec.matrix.org/v1.14/client-server-api/#mroomcreate
|
||||
pub fn predecessor_room(&self) -> Option<PredecessorRoom> {
|
||||
self.create_content().and_then(|content_event| content_event.predecessor).map(
|
||||
|predecessor| PredecessorRoom {
|
||||
room_id: predecessor.room_id,
|
||||
last_event_id: predecessor.event_id,
|
||||
},
|
||||
)
|
||||
self.create_content()
|
||||
.and_then(|content_event| content_event.predecessor)
|
||||
.map(|predecessor| PredecessorRoom { room_id: predecessor.room_id })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,9 +101,6 @@ pub struct SuccessorRoom {
|
||||
pub struct PredecessorRoom {
|
||||
/// The ID of the old room.
|
||||
pub room_id: OwnedRoomId,
|
||||
|
||||
/// The event ID of the last known event in the predecesssor room.
|
||||
pub last_event_id: OwnedEventId,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -115,11 +109,11 @@ mod tests {
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use matrix_sdk_test::{
|
||||
async_test, event_factory::EventFactory, JoinedRoomBuilder, SyncResponseBuilder,
|
||||
JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
|
||||
};
|
||||
use ruma::{event_id, room_id, user_id, RoomVersionId};
|
||||
use ruma::{RoomVersionId, room_id, user_id};
|
||||
|
||||
use crate::{test_utils::logged_in_base_client, RoomState};
|
||||
use crate::{RoomState, test_utils::logged_in_base_client};
|
||||
|
||||
#[async_test]
|
||||
async fn test_no_successor_room() {
|
||||
@@ -232,7 +226,6 @@ mod tests {
|
||||
let sender = user_id!("@mnt_io:matrix.org");
|
||||
let room_id = room_id!("!r1");
|
||||
let predecessor_room_id = room_id!("!r0");
|
||||
let predecessor_last_event_id = event_id!("$ev42");
|
||||
let room = client.get_or_create_room(room_id, RoomState::Joined);
|
||||
|
||||
let mut sync_builder = SyncResponseBuilder::new();
|
||||
@@ -241,7 +234,7 @@ mod tests {
|
||||
JoinedRoomBuilder::new(room_id).add_timeline_event(
|
||||
EventFactory::new()
|
||||
.create(sender, RoomVersionId::V11)
|
||||
.predecessor(predecessor_room_id, predecessor_last_event_id)
|
||||
.predecessor(predecessor_room_id)
|
||||
.into_raw_sync(),
|
||||
),
|
||||
)
|
||||
@@ -252,7 +245,6 @@ mod tests {
|
||||
assert!(room.create_content().is_some());
|
||||
assert_matches!(room.predecessor_room(), Some(predecessor_room) => {
|
||||
assert_eq!(predecessor_room.room_id, predecessor_room_id);
|
||||
assert_eq!(predecessor_room.last_event_id, predecessor_last_event_id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,19 +16,22 @@
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent;
|
||||
use matrix_sdk_common::deserialized_responses::TimelineEvent;
|
||||
use ruma::{api::client::sync::sync_events::v5 as http, OwnedRoomId};
|
||||
use matrix_sdk_common::{deserialized_responses::TimelineEvent, timer};
|
||||
use ruma::{
|
||||
OwnedRoomId, api::client::sync::sync_events::v5 as http, events::receipt::SyncReceiptEvent,
|
||||
serde::Raw,
|
||||
};
|
||||
use tracing::{instrument, trace};
|
||||
|
||||
use super::BaseClient;
|
||||
use crate::{
|
||||
RequestedRequiredStates,
|
||||
error::Result,
|
||||
read_receipts::compute_unread_counts,
|
||||
response_processors as processors,
|
||||
room::RoomInfoNotableUpdateReasons,
|
||||
store::ambiguity_map::AmbiguityCache,
|
||||
sync::{RoomUpdates, SyncResponse},
|
||||
RequestedRequiredStates,
|
||||
};
|
||||
|
||||
impl BaseClient {
|
||||
@@ -63,8 +66,13 @@ impl BaseClient {
|
||||
let mut context = processors::Context::default();
|
||||
|
||||
let processors::e2ee::to_device::Output { processed_to_device_events, room_key_updates } =
|
||||
processors::e2ee::to_device::from_msc4186(to_device, e2ee, olm_machine.as_ref())
|
||||
.await?;
|
||||
processors::e2ee::to_device::from_msc4186(
|
||||
to_device,
|
||||
e2ee,
|
||||
olm_machine.as_ref(),
|
||||
&self.decryption_settings,
|
||||
)
|
||||
.await?;
|
||||
|
||||
processors::latest_event::decrypt_from_rooms(
|
||||
&mut context,
|
||||
@@ -119,6 +127,8 @@ impl BaseClient {
|
||||
return Ok(SyncResponse::default());
|
||||
}
|
||||
|
||||
let _timer = timer!(tracing::Level::TRACE, "_method");
|
||||
|
||||
let mut context = processors::Context::default();
|
||||
|
||||
let state_store = self.state_store.clone();
|
||||
@@ -202,8 +212,7 @@ impl BaseClient {
|
||||
&extensions.account_data,
|
||||
&mut room_updates,
|
||||
&self.state_store,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
|
||||
global_account_data_processor.apply(&mut context, &state_store).await;
|
||||
|
||||
@@ -248,27 +257,27 @@ impl BaseClient {
|
||||
&self,
|
||||
room_id: &OwnedRoomId,
|
||||
response: &http::Response,
|
||||
sync_response: &mut SyncResponse,
|
||||
new_sync_events: Vec<TimelineEvent>,
|
||||
room_previous_events: Vec<TimelineEvent>,
|
||||
) -> Result<()> {
|
||||
) -> Result<Option<Raw<SyncReceiptEvent>>> {
|
||||
let mut context = processors::Context::default();
|
||||
|
||||
let mut save_context = false;
|
||||
|
||||
// Get or create the `JoinedRoomUpdate`, so that we can push the receipt
|
||||
// ephemeral event, and compute the unread counts.
|
||||
let joined_room_update = sync_response.rooms.joined.entry(room_id.to_owned()).or_default();
|
||||
|
||||
// Handle the receipt ephemeral event.
|
||||
if let Some(receipt_ephemeral_event) = response.extensions.receipts.rooms.get(room_id) {
|
||||
let receipt_ephemeral_event = if let Some(receipt_ephemeral_event) =
|
||||
response.extensions.receipts.rooms.get(room_id)
|
||||
{
|
||||
processors::room::msc4186::extensions::dispatch_receipt_ephemeral_event_for_room(
|
||||
&mut context,
|
||||
room_id,
|
||||
receipt_ephemeral_event,
|
||||
joined_room_update,
|
||||
);
|
||||
save_context = true;
|
||||
}
|
||||
Some(receipt_ephemeral_event.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let user_id = &self.session_meta().expect("logged in user").user_id;
|
||||
|
||||
@@ -282,7 +291,7 @@ impl BaseClient {
|
||||
room_id,
|
||||
context.state_changes.receipts.get(room_id),
|
||||
room_previous_events,
|
||||
&joined_room_update.timeline.events,
|
||||
&new_sync_events,
|
||||
&mut room_info.read_receipts,
|
||||
self.threading_support,
|
||||
);
|
||||
@@ -304,7 +313,7 @@ impl BaseClient {
|
||||
processors::changes::save_only(context, &self.state_store).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(receipt_ephemeral_event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,9 +332,12 @@ mod tests {
|
||||
};
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::{
|
||||
JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
|
||||
api::client::sync::sync_events::UnreadNotificationsCount,
|
||||
assign, event_id,
|
||||
events::{
|
||||
AnySyncMessageLikeEvent, AnySyncTimelineEvent, GlobalAccountDataEventContent,
|
||||
StateEventContent, StateEventType,
|
||||
direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
|
||||
room::{
|
||||
avatar::RoomAvatarEventContent,
|
||||
@@ -336,12 +348,10 @@ mod tests {
|
||||
name::RoomNameEventContent,
|
||||
pinned_events::RoomPinnedEventsEventContent,
|
||||
},
|
||||
AnySyncMessageLikeEvent, AnySyncTimelineEvent, GlobalAccountDataEventContent,
|
||||
StateEventContent, StateEventType,
|
||||
},
|
||||
mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id, room_alias_id, room_id,
|
||||
serde::Raw,
|
||||
uint, user_id, JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
|
||||
uint, user_id,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
@@ -349,15 +359,15 @@ mod tests {
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use super::processors::room::msc4186::cache_latest_events;
|
||||
use crate::{
|
||||
BaseClient, EncryptionState, RequestedRequiredStates, RoomInfoNotableUpdate, RoomState,
|
||||
SessionMeta,
|
||||
client::ThreadingSupport,
|
||||
room::{RoomHero, RoomInfoNotableUpdateReasons},
|
||||
store::{RoomLoadSettings, StoreConfig},
|
||||
test_utils::logged_in_base_client,
|
||||
BaseClient, EncryptionState, RequestedRequiredStates, RoomInfoNotableUpdate, RoomState,
|
||||
SessionMeta,
|
||||
};
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
use crate::{store::MemoryStore, Room};
|
||||
use crate::{Room, store::MemoryStore};
|
||||
|
||||
#[async_test]
|
||||
async fn test_notification_count_set() {
|
||||
@@ -580,8 +590,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room(
|
||||
) {
|
||||
async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room()
|
||||
{
|
||||
// Given a logged-in client,
|
||||
let client = logged_in_base_client(None).await;
|
||||
let room_id = room_id!("!r:e.uk");
|
||||
@@ -604,8 +614,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room(
|
||||
) {
|
||||
async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room()
|
||||
{
|
||||
// Given a logged-in client,
|
||||
let client = logged_in_base_client(None).await;
|
||||
let room_id = room_id!("!r:e.uk");
|
||||
@@ -623,7 +633,7 @@ mod tests {
|
||||
"state_key": user_id,
|
||||
}))
|
||||
.expect("Failed to make raw event")
|
||||
.cast();
|
||||
.cast_unchecked();
|
||||
room.invite_state = Some(vec![event]);
|
||||
|
||||
let response = response_with_room(room_id, room);
|
||||
@@ -810,7 +820,9 @@ mod tests {
|
||||
create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
|
||||
|
||||
// (Sanity: B is a direct target, and is in Join state)
|
||||
assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
|
||||
assert!(
|
||||
direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
|
||||
);
|
||||
assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
|
||||
|
||||
// When B leaves
|
||||
@@ -819,13 +831,15 @@ mod tests {
|
||||
// Then B is still a direct target, and is in Leave state (B is a direct target
|
||||
// because we want to return to our old DM in the UI even if the other
|
||||
// user left, so we can reinvite them. See https://github.com/matrix-org/matrix-rust-sdk/issues/2017)
|
||||
assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
|
||||
assert!(
|
||||
direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
|
||||
);
|
||||
assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets(
|
||||
) {
|
||||
async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets()
|
||||
{
|
||||
let room_id = room_id!("!r:e.uk");
|
||||
let user_a_id = user_id!("@a:e.uk");
|
||||
let user_b_id = user_id!("@b:e.uk");
|
||||
@@ -835,7 +849,9 @@ mod tests {
|
||||
create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
|
||||
|
||||
// (Sanity: B is a direct target, and is in Invite state)
|
||||
assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
|
||||
assert!(
|
||||
direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
|
||||
);
|
||||
assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
|
||||
|
||||
// When B declines the invitation (i.e. leaves)
|
||||
@@ -844,7 +860,9 @@ mod tests {
|
||||
// Then B is still a direct target, and is in Leave state (B is a direct target
|
||||
// because we want to return to our old DM in the UI even if the other
|
||||
// user left, so we can reinvite them. See https://github.com/matrix-org/matrix-rust-sdk/issues/2017)
|
||||
assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
|
||||
assert!(
|
||||
direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
|
||||
);
|
||||
assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
|
||||
}
|
||||
|
||||
@@ -862,7 +880,9 @@ mod tests {
|
||||
assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
|
||||
|
||||
// (Sanity: B is a direct target, and is in Join state)
|
||||
assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
|
||||
assert!(
|
||||
direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
|
||||
);
|
||||
assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
|
||||
|
||||
let room = client.get_room(room_id).unwrap();
|
||||
@@ -886,7 +906,9 @@ mod tests {
|
||||
assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
|
||||
|
||||
// (Sanity: B is a direct target, and is in Join state)
|
||||
assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
|
||||
assert!(
|
||||
direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
|
||||
);
|
||||
assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
|
||||
|
||||
let room = client.get_room(room_id).unwrap();
|
||||
@@ -1062,8 +1084,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response(
|
||||
) {
|
||||
async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response()
|
||||
{
|
||||
// Given a logged-in client
|
||||
let client = logged_in_base_client(None).await;
|
||||
let room_id = room_id!("!r:e.uk");
|
||||
@@ -1302,6 +1324,17 @@ mod tests {
|
||||
let client = logged_in_base_client(Some(own_user_id)).await;
|
||||
let room_id = room_id!("!r:e.uk");
|
||||
|
||||
// The room create event.
|
||||
let create = json!({
|
||||
"sender":"@ignacio:example.com",
|
||||
"state_key":"",
|
||||
"type":"m.room.create",
|
||||
"event_id": "$idc",
|
||||
"origin_server_ts": 12344415,
|
||||
"content":{ "room_version": "11" },
|
||||
"room_id": room_id,
|
||||
});
|
||||
|
||||
// Give the current user invite or kick permissions in this room
|
||||
let power_levels = json!({
|
||||
"sender":"@alice:example.com",
|
||||
@@ -1327,7 +1360,10 @@ mod tests {
|
||||
// When the sliding sync response contains a timeline
|
||||
let events = &[knock_event];
|
||||
let mut room = room_with_timeline(events);
|
||||
room.required_state.push(Raw::new(&power_levels).unwrap().cast());
|
||||
room.required_state.extend([
|
||||
Raw::new(&create).unwrap().cast_unchecked(),
|
||||
Raw::new(&power_levels).unwrap().cast_unchecked(),
|
||||
]);
|
||||
let response = response_with_room(room_id, room);
|
||||
client
|
||||
.process_sliding_sync(&response, &RequestedRequiredStates::default())
|
||||
@@ -1375,7 +1411,7 @@ mod tests {
|
||||
// When the sliding sync response contains a timeline
|
||||
let events = &[knock_event];
|
||||
let mut room = room_with_timeline(events);
|
||||
room.required_state.push(Raw::new(&power_levels).unwrap().cast());
|
||||
room.required_state.push(Raw::new(&power_levels).unwrap().cast_unchecked());
|
||||
let response = response_with_room(room_id, room);
|
||||
client
|
||||
.process_sliding_sync(&response, &RequestedRequiredStates::default())
|
||||
@@ -1417,6 +1453,7 @@ mod tests {
|
||||
assert!(client_room.latest_event().is_none());
|
||||
}
|
||||
|
||||
#[cfg(feature = "e2e-encryption")]
|
||||
#[async_test]
|
||||
async fn test_cached_latest_event_can_be_redacted() {
|
||||
// Given a logged-in client
|
||||
@@ -1905,18 +1942,20 @@ mod tests {
|
||||
|
||||
// Send sliding sync response containing a membership event with 'join' value.
|
||||
let room_id = room_id!("!r:e.uk");
|
||||
let events = vec![Raw::from_json_string(
|
||||
json!({
|
||||
"type": "m.room.member",
|
||||
"event_id": "$3",
|
||||
"content": { "membership": "join" },
|
||||
"sender": "@u:h.uk",
|
||||
"origin_server_ts": 12344445,
|
||||
"state_key": "@u:e.uk",
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap()];
|
||||
let events = vec![
|
||||
Raw::from_json_string(
|
||||
json!({
|
||||
"type": "m.room.member",
|
||||
"event_id": "$3",
|
||||
"content": { "membership": "join" },
|
||||
"sender": "@u:h.uk",
|
||||
"origin_server_ts": 12344445,
|
||||
"state_key": "@u:e.uk",
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap(),
|
||||
];
|
||||
let room = assign!(http::response::Room::new(), {
|
||||
required_state: events,
|
||||
});
|
||||
@@ -1936,18 +1975,20 @@ mod tests {
|
||||
);
|
||||
assert!(room_info_notable_update_stream.is_empty());
|
||||
|
||||
let events = vec![Raw::from_json_string(
|
||||
json!({
|
||||
"type": "m.room.member",
|
||||
"event_id": "$3",
|
||||
"content": { "membership": "leave" },
|
||||
"sender": "@u:h.uk",
|
||||
"origin_server_ts": 12344445,
|
||||
"state_key": "@u:e.uk",
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap()];
|
||||
let events = vec![
|
||||
Raw::from_json_string(
|
||||
json!({
|
||||
"type": "m.room.member",
|
||||
"event_id": "$3",
|
||||
"content": { "membership": "leave" },
|
||||
"sender": "@u:h.uk",
|
||||
"origin_server_ts": 12344445,
|
||||
"state_key": "@u:e.uk",
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap(),
|
||||
];
|
||||
let room = assign!(http::response::Room::new(), {
|
||||
required_state: events,
|
||||
});
|
||||
@@ -2003,17 +2044,19 @@ mod tests {
|
||||
// When I receive a sliding sync response containing one update about an unread
|
||||
// marker,
|
||||
let room_id = room_id!("!r:e.uk");
|
||||
let room_account_data_events = vec![Raw::from_json_string(
|
||||
json!({
|
||||
"type": "m.marked_unread",
|
||||
"event_id": "$1",
|
||||
"content": { "unread": true },
|
||||
"sender": client.session_meta().unwrap().user_id,
|
||||
"origin_server_ts": 12344445,
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap()];
|
||||
let room_account_data_events = vec![
|
||||
Raw::from_json_string(
|
||||
json!({
|
||||
"type": "m.marked_unread",
|
||||
"event_id": "$1",
|
||||
"content": { "unread": true },
|
||||
"sender": client.session_meta().unwrap().user_id,
|
||||
"origin_server_ts": 12344445,
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap(),
|
||||
];
|
||||
let mut response = response_with_room(room_id, http::response::Room::new());
|
||||
response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
|
||||
|
||||
@@ -2047,17 +2090,19 @@ mod tests {
|
||||
assert!(room_info_notable_update_stream.is_empty());
|
||||
|
||||
// …Unless its value changes!
|
||||
let room_account_data_events = vec![Raw::from_json_string(
|
||||
json!({
|
||||
"type": "m.marked_unread",
|
||||
"event_id": "$1",
|
||||
"content": { "unread": false },
|
||||
"sender": client.session_meta().unwrap().user_id,
|
||||
"origin_server_ts": 12344445,
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap()];
|
||||
let room_account_data_events = vec![
|
||||
Raw::from_json_string(
|
||||
json!({
|
||||
"type": "m.marked_unread",
|
||||
"event_id": "$1",
|
||||
"content": { "unread": false },
|
||||
"sender": client.session_meta().unwrap().user_id,
|
||||
"origin_server_ts": 12344445,
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap(),
|
||||
];
|
||||
response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
|
||||
client
|
||||
.process_sliding_sync(&response, &RequestedRequiredStates::default())
|
||||
@@ -2109,17 +2154,19 @@ mod tests {
|
||||
// When I receive a sliding sync response containing one update about an
|
||||
// unstable unread marker,
|
||||
let room_id = room_id!("!r:e.uk");
|
||||
let unstable_room_account_data_events = vec![Raw::from_json_string(
|
||||
json!({
|
||||
"type": "com.famedly.marked_unread",
|
||||
"event_id": "$1",
|
||||
"content": { "unread": true },
|
||||
"sender": client.session_meta().unwrap().user_id,
|
||||
"origin_server_ts": 12344445,
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap()];
|
||||
let unstable_room_account_data_events = vec![
|
||||
Raw::from_json_string(
|
||||
json!({
|
||||
"type": "com.famedly.marked_unread",
|
||||
"event_id": "$1",
|
||||
"content": { "unread": true },
|
||||
"sender": client.session_meta().unwrap().user_id,
|
||||
"origin_server_ts": 12344445,
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap(),
|
||||
];
|
||||
let mut response = response_with_room(room_id, http::response::Room::new());
|
||||
response
|
||||
.extensions
|
||||
@@ -2143,17 +2190,19 @@ mod tests {
|
||||
assert!(room_info_notable_update_stream.is_empty());
|
||||
|
||||
// When I receive a sliding sync response with a stable unread marker update,
|
||||
let stable_room_account_data_events = vec![Raw::from_json_string(
|
||||
json!({
|
||||
"type": "m.marked_unread",
|
||||
"event_id": "$1",
|
||||
"content": { "unread": false },
|
||||
"sender": client.session_meta().unwrap().user_id,
|
||||
"origin_server_ts": 12344445,
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap()];
|
||||
let stable_room_account_data_events = vec![
|
||||
Raw::from_json_string(
|
||||
json!({
|
||||
"type": "m.marked_unread",
|
||||
"event_id": "$1",
|
||||
"content": { "unread": false },
|
||||
"sender": client.session_meta().unwrap().user_id,
|
||||
"origin_server_ts": 12344445,
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap(),
|
||||
];
|
||||
response
|
||||
.extensions
|
||||
.account_data
|
||||
@@ -2198,17 +2247,19 @@ mod tests {
|
||||
|
||||
// Finally, when I receive a sliding sync response with a stable unread marker
|
||||
// update again,
|
||||
let stable_room_account_data_events = vec![Raw::from_json_string(
|
||||
json!({
|
||||
"type": "m.marked_unread",
|
||||
"event_id": "$3",
|
||||
"content": { "unread": true },
|
||||
"sender": client.session_meta().unwrap().user_id,
|
||||
"origin_server_ts": 12344445,
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap()];
|
||||
let stable_room_account_data_events = vec![
|
||||
Raw::from_json_string(
|
||||
json!({
|
||||
"type": "m.marked_unread",
|
||||
"event_id": "$3",
|
||||
"content": { "unread": true },
|
||||
"sender": client.session_meta().unwrap().user_id,
|
||||
"origin_server_ts": 12344445,
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap(),
|
||||
];
|
||||
response
|
||||
.extensions
|
||||
.account_data
|
||||
@@ -2708,7 +2759,7 @@ mod tests {
|
||||
"state_key": invitee,
|
||||
}))
|
||||
.expect("Failed to make raw event")
|
||||
.cast();
|
||||
.cast_unchecked();
|
||||
|
||||
room.invite_state = Some(vec![evt]);
|
||||
|
||||
@@ -2736,7 +2787,7 @@ mod tests {
|
||||
"state_key": knocker,
|
||||
}))
|
||||
.expect("Failed to make raw event")
|
||||
.cast();
|
||||
.cast_unchecked();
|
||||
|
||||
room.invite_state = Some(vec![evt]);
|
||||
}
|
||||
@@ -2771,7 +2822,7 @@ mod tests {
|
||||
"content": content,
|
||||
}))
|
||||
.expect("Failed to create account data event")
|
||||
.cast()
|
||||
.cast_unchecked()
|
||||
}
|
||||
|
||||
fn make_state_event<C: StateEventContent, E>(
|
||||
@@ -2796,6 +2847,6 @@ mod tests {
|
||||
"unsigned": unsigned,
|
||||
}))
|
||||
.expect("Failed to create state event")
|
||||
.cast()
|
||||
.cast_unchecked()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,17 +18,14 @@ use std::{
|
||||
};
|
||||
|
||||
use ruma::{
|
||||
events::{
|
||||
room::member::{MembershipState, SyncRoomMemberEvent},
|
||||
StateEventType,
|
||||
},
|
||||
OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
events::room::member::{MembershipState, SyncRoomMemberEvent},
|
||||
};
|
||||
use tracing::{instrument, trace};
|
||||
|
||||
use super::{DynStateStore, Result, StateChanges};
|
||||
use crate::{
|
||||
deserialized_responses::{AmbiguityChange, DisplayName, RawMemberEvent},
|
||||
deserialized_responses::{AmbiguityChange, DisplayName, SyncOrStrippedState},
|
||||
store::StateStoreExt,
|
||||
};
|
||||
|
||||
@@ -45,11 +42,7 @@ impl DisplayNameUsers {
|
||||
fn remove(&mut self, user_id: &UserId) -> Option<OwnedUserId> {
|
||||
self.users.remove(user_id);
|
||||
|
||||
if self.user_count() == 1 {
|
||||
self.users.iter().next().cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if self.user_count() == 1 { self.users.iter().next().cloned() } else { None }
|
||||
}
|
||||
|
||||
/// Add the given [`UserId`] from the map, marking that the [`UserId`]
|
||||
@@ -188,17 +181,13 @@ impl AmbiguityCache {
|
||||
) -> Result<Option<String>> {
|
||||
let user_id = new_event.state_key();
|
||||
|
||||
let old_event = if let Some(m) = changes
|
||||
.state
|
||||
.get(room_id)
|
||||
.and_then(|events| events.get(&StateEventType::RoomMember)?.get(user_id.as_str()))
|
||||
{
|
||||
Some(RawMemberEvent::Sync(m.clone().cast()))
|
||||
let old_event = if let Some(member) = changes.member(room_id, user_id) {
|
||||
Some(SyncOrStrippedState::Stripped(member))
|
||||
} else {
|
||||
self.store.get_member_event(room_id, user_id).await?
|
||||
self.store.get_member_event(room_id, user_id).await?.and_then(|r| r.deserialize().ok())
|
||||
};
|
||||
|
||||
let Some(Ok(old_event)) = old_event.map(|r| r.deserialize()) else { return Ok(None) };
|
||||
let Some(old_event) = old_event else { return Ok(None) };
|
||||
|
||||
if is_member_active(old_event.membership()) {
|
||||
let display_name = if let Some(d) = changes
|
||||
@@ -313,7 +302,7 @@ impl AmbiguityCache {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use matrix_sdk_test::async_test;
|
||||
use ruma::{room_id, server_name, user_id, EventId};
|
||||
use ruma::{EventId, room_id, server_name, user_id};
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,33 +19,36 @@ use std::{
|
||||
|
||||
use async_trait::async_trait;
|
||||
use growable_bloom_filter::GrowableBloom;
|
||||
use matrix_sdk_common::ROOM_VERSION_FALLBACK;
|
||||
use matrix_sdk_common::{ROOM_VERSION_FALLBACK, ROOM_VERSION_RULES_FALLBACK};
|
||||
use ruma::{
|
||||
canonical_json::{redact, RedactedBecause},
|
||||
CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedMxcUri,
|
||||
OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UserId,
|
||||
canonical_json::{RedactedBecause, redact},
|
||||
events::{
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
|
||||
AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
presence::PresenceEvent,
|
||||
receipt::{Receipt, ReceiptThread, ReceiptType},
|
||||
room::member::{MembershipState, StrippedRoomMemberEvent, SyncRoomMemberEvent},
|
||||
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
|
||||
AnySyncStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
time::Instant,
|
||||
CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedMxcUri,
|
||||
OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UserId,
|
||||
};
|
||||
use tracing::{debug, instrument, warn};
|
||||
|
||||
use super::{
|
||||
send_queue::{ChildTransactionId, QueuedRequest, SentRequestKey},
|
||||
traits::{ComposerDraft, ServerInfo},
|
||||
DependentQueuedRequest, DependentQueuedRequestKind, QueuedRequestKind, Result, RoomInfo,
|
||||
RoomLoadSettings, StateChanges, StateStore, StoreError,
|
||||
send_queue::{ChildTransactionId, QueuedRequest, SentRequestKey},
|
||||
traits::{ComposerDraft, ServerInfo},
|
||||
};
|
||||
use crate::{
|
||||
deserialized_responses::{DisplayName, RawAnySyncOrStrippedState},
|
||||
store::QueueWedgeError,
|
||||
MinimalRoomMemberEvent, RoomMemberships, StateStoreDataKey, StateStoreDataValue,
|
||||
deserialized_responses::{DisplayName, RawAnySyncOrStrippedState},
|
||||
store::{
|
||||
QueueWedgeError, StoredThreadSubscription,
|
||||
traits::{ThreadSubscriptionCatchupToken, compare_thread_subscription_bump_stamps},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -58,6 +61,7 @@ struct MemoryStoreInner {
|
||||
server_info: Option<ServerInfo>,
|
||||
filters: HashMap<String, String>,
|
||||
utd_hook_manager_data: Option<GrowableBloom>,
|
||||
one_time_key_uploaded_error: bool,
|
||||
account_data: HashMap<GlobalAccountDataEventType, Raw<AnyGlobalAccountDataEvent>>,
|
||||
profiles: HashMap<OwnedRoomId, HashMap<OwnedUserId, MinimalRoomMemberEvent>>,
|
||||
display_names: HashMap<OwnedRoomId, HashMap<DisplayName, BTreeSet<OwnedUserId>>>,
|
||||
@@ -75,7 +79,6 @@ struct MemoryStoreInner {
|
||||
OwnedRoomId,
|
||||
HashMap<(String, Option<String>), HashMap<OwnedUserId, (OwnedEventId, Receipt)>>,
|
||||
>,
|
||||
|
||||
room_event_receipts: HashMap<
|
||||
OwnedRoomId,
|
||||
HashMap<(String, Option<String>), HashMap<OwnedEventId, HashMap<OwnedUserId, Receipt>>>,
|
||||
@@ -84,6 +87,8 @@ struct MemoryStoreInner {
|
||||
send_queue_events: BTreeMap<OwnedRoomId, Vec<QueuedRequest>>,
|
||||
dependent_send_queue_events: BTreeMap<OwnedRoomId, Vec<DependentQueuedRequest>>,
|
||||
seen_knock_requests: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, OwnedUserId>>,
|
||||
thread_subscriptions: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, StoredThreadSubscription>>,
|
||||
thread_subscriptions_catchup_tokens: Option<Vec<ThreadSubscriptionCatchupToken>>,
|
||||
}
|
||||
|
||||
/// In-memory, non-persistent implementation of the `StateStore`.
|
||||
@@ -146,6 +151,7 @@ impl StateStore for MemoryStore {
|
||||
|
||||
async fn get_kv_data(&self, key: StateStoreDataKey<'_>) -> Result<Option<StateStoreDataValue>> {
|
||||
let inner = self.inner.read().unwrap();
|
||||
|
||||
Ok(match key {
|
||||
StateStoreDataKey::SyncToken => {
|
||||
inner.sync_token.clone().map(StateStoreDataValue::SyncToken)
|
||||
@@ -167,6 +173,9 @@ impl StateStore for MemoryStore {
|
||||
StateStoreDataKey::UtdHookManagerData => {
|
||||
inner.utd_hook_manager_data.clone().map(StateStoreDataValue::UtdHookManagerData)
|
||||
}
|
||||
StateStoreDataKey::OneTimeKeyAlreadyUploaded => inner
|
||||
.one_time_key_uploaded_error
|
||||
.then_some(StateStoreDataValue::OneTimeKeyAlreadyUploaded),
|
||||
StateStoreDataKey::ComposerDraft(room_id, thread_root) => {
|
||||
let key = (room_id.to_owned(), thread_root.map(ToOwned::to_owned));
|
||||
inner.composer_drafts.get(&key).cloned().map(StateStoreDataValue::ComposerDraft)
|
||||
@@ -176,6 +185,10 @@ impl StateStore for MemoryStore {
|
||||
.get(room_id)
|
||||
.cloned()
|
||||
.map(StateStoreDataValue::SeenKnockRequests),
|
||||
StateStoreDataKey::ThreadSubscriptionsCatchupTokens => inner
|
||||
.thread_subscriptions_catchup_tokens
|
||||
.clone()
|
||||
.map(StateStoreDataValue::ThreadSubscriptionsCatchupTokens),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -217,6 +230,9 @@ impl StateStore for MemoryStore {
|
||||
.expect("Session data not the hook manager data"),
|
||||
);
|
||||
}
|
||||
StateStoreDataKey::OneTimeKeyAlreadyUploaded => {
|
||||
inner.one_time_key_uploaded_error = true;
|
||||
}
|
||||
StateStoreDataKey::ComposerDraft(room_id, thread_root) => {
|
||||
inner.composer_drafts.insert(
|
||||
(room_id.to_owned(), thread_root.map(ToOwned::to_owned)),
|
||||
@@ -236,6 +252,12 @@ impl StateStore for MemoryStore {
|
||||
.expect("Session data is not a set of seen join request ids"),
|
||||
);
|
||||
}
|
||||
StateStoreDataKey::ThreadSubscriptionsCatchupTokens => {
|
||||
inner.thread_subscriptions_catchup_tokens =
|
||||
Some(value.into_thread_subscriptions_catchup_tokens().expect(
|
||||
"Session data is not a list of thread subscription catchup tokens",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -256,6 +278,9 @@ impl StateStore for MemoryStore {
|
||||
inner.recently_visited_rooms.remove(user_id);
|
||||
}
|
||||
StateStoreDataKey::UtdHookManagerData => inner.utd_hook_manager_data = None,
|
||||
StateStoreDataKey::OneTimeKeyAlreadyUploaded => {
|
||||
inner.one_time_key_uploaded_error = false
|
||||
}
|
||||
StateStoreDataKey::ComposerDraft(room_id, thread_root) => {
|
||||
let key = (room_id.to_owned(), thread_root.map(ToOwned::to_owned));
|
||||
inner.composer_drafts.remove(&key);
|
||||
@@ -263,6 +288,9 @@ impl StateStore for MemoryStore {
|
||||
StateStoreDataKey::SeenKnockRequests(room_id) => {
|
||||
inner.seen_knock_requests.remove(room_id);
|
||||
}
|
||||
StateStoreDataKey::ThreadSubscriptionsCatchupTokens => {
|
||||
inner.thread_subscriptions_catchup_tokens = None;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -333,15 +361,16 @@ impl StateStore for MemoryStore {
|
||||
inner.stripped_room_state.remove(room);
|
||||
|
||||
if *event_type == StateEventType::RoomMember {
|
||||
let event = match raw_event.deserialize_as::<SyncRoomMemberEvent>() {
|
||||
Ok(ev) => ev,
|
||||
Err(e) => {
|
||||
let event_id: Option<String> =
|
||||
raw_event.get_field("event_id").ok().flatten();
|
||||
debug!(event_id, "Failed to deserialize member event: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let event =
|
||||
match raw_event.deserialize_as_unchecked::<SyncRoomMemberEvent>() {
|
||||
Ok(ev) => ev,
|
||||
Err(e) => {
|
||||
let event_id: Option<String> =
|
||||
raw_event.get_field("event_id").ok().flatten();
|
||||
debug!(event_id, "Failed to deserialize member event: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
inner.stripped_members.remove(room);
|
||||
|
||||
@@ -375,18 +404,19 @@ impl StateStore for MemoryStore {
|
||||
.insert(state_key.to_owned(), raw_event.clone());
|
||||
|
||||
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_unchecked::<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;
|
||||
}
|
||||
};
|
||||
|
||||
inner
|
||||
.stripped_members
|
||||
@@ -413,14 +443,12 @@ impl StateStore for MemoryStore {
|
||||
.insert(user_id.clone(), (event_id.clone(), receipt.clone()))
|
||||
{
|
||||
// Remove the old receipt from the room event receipts
|
||||
if let Some(receipt_map) = inner.room_event_receipts.get_mut(room) {
|
||||
if let Some(event_map) =
|
||||
if let Some(receipt_map) = inner.room_event_receipts.get_mut(room)
|
||||
&& let Some(event_map) =
|
||||
receipt_map.get_mut(&(receipt_type.to_string(), thread.clone()))
|
||||
{
|
||||
if let Some(user_map) = event_map.get_mut(&old_event) {
|
||||
user_map.remove(user_id);
|
||||
}
|
||||
}
|
||||
&& let Some(user_map) = event_map.get_mut(&old_event)
|
||||
{
|
||||
user_map.remove(user_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,35 +467,35 @@ impl StateStore for MemoryStore {
|
||||
}
|
||||
}
|
||||
|
||||
let make_room_version = |room_info: &HashMap<OwnedRoomId, RoomInfo>, room_id| {
|
||||
room_info.get(room_id).map(|info| info.room_version_or_default()).unwrap_or_else(|| {
|
||||
let make_redaction_rules = |room_info: &HashMap<OwnedRoomId, RoomInfo>, room_id| {
|
||||
room_info.get(room_id).map(|info| info.room_version_rules_or_default()).unwrap_or_else(|| {
|
||||
warn!(
|
||||
?room_id,
|
||||
"Unable to find the room version, assuming {ROOM_VERSION_FALLBACK}"
|
||||
"Unable to get the room version rules, defaulting to rules for room version {ROOM_VERSION_FALLBACK}"
|
||||
);
|
||||
ROOM_VERSION_FALLBACK
|
||||
})
|
||||
ROOM_VERSION_RULES_FALLBACK
|
||||
}).redaction
|
||||
};
|
||||
|
||||
let inner = &mut *inner;
|
||||
for (room_id, redactions) in &changes.redactions {
|
||||
let mut room_version = None;
|
||||
let mut redaction_rules = None;
|
||||
|
||||
if let Some(room) = inner.room_state.get_mut(room_id) {
|
||||
for ref_room_mu in room.values_mut() {
|
||||
for raw_evt in ref_room_mu.values_mut() {
|
||||
if let Ok(Some(event_id)) = raw_evt.get_field::<OwnedEventId>("event_id") {
|
||||
if let Some(redaction) = redactions.get(&event_id) {
|
||||
let redacted = redact(
|
||||
raw_evt.deserialize_as::<CanonicalJsonObject>()?,
|
||||
room_version.get_or_insert_with(|| {
|
||||
make_room_version(&inner.room_info, room_id)
|
||||
}),
|
||||
Some(RedactedBecause::from_raw_event(redaction)?),
|
||||
)
|
||||
.map_err(StoreError::Redaction)?;
|
||||
*raw_evt = Raw::new(&redacted)?.cast();
|
||||
}
|
||||
if let Ok(Some(event_id)) = raw_evt.get_field::<OwnedEventId>("event_id")
|
||||
&& let Some(redaction) = redactions.get(&event_id)
|
||||
{
|
||||
let redacted = redact(
|
||||
raw_evt.deserialize_as::<CanonicalJsonObject>()?,
|
||||
redaction_rules.get_or_insert_with(|| {
|
||||
make_redaction_rules(&inner.room_info, room_id)
|
||||
}),
|
||||
Some(RedactedBecause::from_raw_event(redaction)?),
|
||||
)
|
||||
.map_err(StoreError::Redaction)?;
|
||||
*raw_evt = Raw::new(&redacted)?.cast_unchecked();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -754,6 +782,7 @@ impl StateStore for MemoryStore {
|
||||
inner.room_event_receipts.remove(room_id);
|
||||
inner.send_queue_events.remove(room_id);
|
||||
inner.dependent_send_queue_events.remove(room_id);
|
||||
inner.thread_subscriptions.remove(room_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -938,10 +967,6 @@ impl StateStore for MemoryStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// List all the dependent send queue events.
|
||||
///
|
||||
/// This returns absolutely all the dependent send queue events, whether
|
||||
/// they have an event id or not.
|
||||
async fn load_dependent_queued_requests(
|
||||
&self,
|
||||
room: &RoomId,
|
||||
@@ -955,6 +980,64 @@ impl StateStore for MemoryStore {
|
||||
.cloned()
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
async fn upsert_thread_subscription(
|
||||
&self,
|
||||
room: &RoomId,
|
||||
thread_id: &EventId,
|
||||
mut new: StoredThreadSubscription,
|
||||
) -> Result<(), Self::Error> {
|
||||
let mut inner = self.inner.write().unwrap();
|
||||
let room_subs = inner.thread_subscriptions.entry(room.to_owned()).or_default();
|
||||
|
||||
if let Some(previous) = room_subs.get(thread_id) {
|
||||
// Nothing to do.
|
||||
if *previous == new {
|
||||
return Ok(());
|
||||
}
|
||||
if !compare_thread_subscription_bump_stamps(previous.bump_stamp, &mut new.bump_stamp) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
room_subs.insert(thread_id.to_owned(), new);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_thread_subscription(
|
||||
&self,
|
||||
room: &RoomId,
|
||||
thread_id: &EventId,
|
||||
) -> Result<Option<StoredThreadSubscription>, Self::Error> {
|
||||
let inner = self.inner.read().unwrap();
|
||||
Ok(inner
|
||||
.thread_subscriptions
|
||||
.get(room)
|
||||
.and_then(|subscriptions| subscriptions.get(thread_id))
|
||||
.copied())
|
||||
}
|
||||
|
||||
async fn remove_thread_subscription(
|
||||
&self,
|
||||
room: &RoomId,
|
||||
thread_id: &EventId,
|
||||
) -> Result<(), Self::Error> {
|
||||
let mut inner = self.inner.write().unwrap();
|
||||
|
||||
let Some(room_subs) = inner.thread_subscriptions.get_mut(room) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
room_subs.remove(thread_id);
|
||||
|
||||
if room_subs.is_empty() {
|
||||
// If there are no more subscriptions for this room, remove the room entry.
|
||||
inner.thread_subscriptions.remove(room);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user