mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 00:02:48 +02:00
Compare commits
18 Commits
v1.6.24
...
feature/sq
Author | SHA1 | Date | |
---|---|---|---|
|
9903a299b9 | ||
|
9a4cad1e45 | ||
|
649b4496a6 | ||
|
617b76c0d5 | ||
|
9415d9b5c5 | ||
|
3ba5e97392 | ||
|
0ef5ae38d0 | ||
|
c53f9359d7 | ||
|
09c98c32f0 | ||
|
fc90844556 | ||
|
0ca7e6202c | ||
|
96162218e4 | ||
|
ffaacdf436 | ||
|
39fdda3715 | ||
|
af171b3b7e | ||
|
3dd9693f59 | ||
|
8b0845a76b | ||
|
8e896600c9 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@
|
|||||||
/tmp
|
/tmp
|
||||||
|
|
||||||
ktlint
|
ktlint
|
||||||
|
.idea
|
||||||
|
1
.idea/sqldelight/matrix-sdk-sqldelight/.sqldelight
generated
Normal file
1
.idea/sqldelight/matrix-sdk-sqldelight/.sqldelight
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"databases":[{"packageName":"im.vector.matrix.sqldelight.auth","compilationUnits":[{"name":"main","sourceFolders":[{"path":"src/main/sqldelight/auth","dependency":false}]}],"outputDirectory":"build/generated/sqldelight/code/AuthDatabase","className":"AuthDatabase","dependencies":[]},{"packageName":"im.vector.matrix.sqldelight.crypto","compilationUnits":[{"name":"main","sourceFolders":[{"path":"src/main/sqldelight/crypto","dependency":false}]}],"outputDirectory":"build/generated/sqldelight/code/CryptoDatabase","className":"CryptoDatabase","dependencies":[]},{"packageName":"im.vector.matrix.sqldelight.session","compilationUnits":[{"name":"main","sourceFolders":[{"path":"src/main/sqldelight/session","dependency":false}]}],"outputDirectory":"build/generated/sqldelight/code/SessionDatabase","className":"SessionDatabase","dependencies":[]}]}
|
19
CHANGES.md
19
CHANGES.md
@@ -2,52 +2,40 @@ Changes in RiotX 0.19.0 (2020-XX-XX)
|
|||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features ✨:
|
Features ✨:
|
||||||
- Change password (#528)
|
|
||||||
- Cross-Signing | Support SSSS secret sharing (#944)
|
- Cross-Signing | Support SSSS secret sharing (#944)
|
||||||
- Cross-Signing | Verify new session from existing session (#1134)
|
- Cross-Signing | Verify new session from existing session (#1134)
|
||||||
- Cross-Signing | Bootstraping cross signing with 4S from mobile (#985)
|
- Cross-Signing | Bootstraping cross signing with 4S from mobile (#985)
|
||||||
- Save media files to Gallery (#973)
|
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- Verification DM / Handle concurrent .start after .ready (#794)
|
- Verification DM / Handle concurrent .start after .ready (#794)
|
||||||
- Reimplementation of multiple attachment picker
|
|
||||||
- Cross-Signing | Update Shield Logic for DM (#963)
|
- Cross-Signing | Update Shield Logic for DM (#963)
|
||||||
- Cross-Signing | Complete security new session design update (#1135)
|
- Cross-Signing | Complete security new session design update (#1135)
|
||||||
- Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201)
|
- Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201)
|
||||||
- Cross-Signing | Gossip key backup recovery key (#1200)
|
- Cross-Signing | Gossip key backup recovery key (#1200)
|
||||||
- Show room encryption status as a bubble tile (#1078)
|
- Show room encryption status as a bubble tile (#1078)
|
||||||
- UX/UI | Add indicator to home tab on invite (#957)
|
|
||||||
- Cross-Signing | Restore history after recover from passphrase (#1214)
|
- Cross-Signing | Restore history after recover from passphrase (#1214)
|
||||||
- Cross-Sign | QR code scan confirmation screens design update (#1187)
|
- Cross-Sign | QR code scan confirmation screens design update (#1187)
|
||||||
- Emoji Verification | It's not the same butterfly! (#1220)
|
- Emoji Verification | It's not the same butterfly! (#1220)
|
||||||
- Cross-Signing | Composer decoration: shields (#1077)
|
|
||||||
- Cross-Signing | Migrate existing keybackup to cross signing with 4S from mobile (#1197)
|
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- Fix summary notification staying after "mark as read"
|
|
||||||
- Missing avatar/displayname after verification request message (#841)
|
- Missing avatar/displayname after verification request message (#841)
|
||||||
- Crypto | RiotX sometimes rotate the current device keys (#1170)
|
- Crypto | RiotX sometimes rotate the current device keys (#1170)
|
||||||
- RiotX can't restore cross signing keys saved by web in SSSS (#1174)
|
- RiotX can't restore cross signing keys saved by web in SSSS (#1174)
|
||||||
- Cross- Signing | After signin in new session, verification paper trail in DM is off (#1191)
|
- Cross- Signing | After signin in new session, verification paper trail in DM is off (#1191)
|
||||||
- Failed to encrypt message in room (message stays in red), [thanks to pwr22] (#925)
|
- Failed to encrypt message in room (message stays in red), [thanks to pwr22] (#925)
|
||||||
- Cross-Signing | web <-> riotX After QR code scan, gossiping fails (#1210)
|
- Cross-Signing | web <-> riotX After QR code scan, gossiping fails (#1210)
|
||||||
- Fix crash when trying to download file without internet connection (#1229)
|
|
||||||
- Local echo are not updated in timeline (for failed & encrypted states)
|
|
||||||
- Render image event even if thumbnail_info does not have mimetype defined (#1209)
|
|
||||||
- RiotX now uses as many threads as it needs to do work and send messages (#1221)
|
|
||||||
- Fix issue with media path (#1227)
|
|
||||||
|
|
||||||
Translations 🗣:
|
Translations 🗣:
|
||||||
-
|
-
|
||||||
|
|
||||||
SDK API changes ⚠️:
|
SDK API changes ⚠️:
|
||||||
- Increase targetSdkVersion to 29
|
- Implementation of SqlCryptoStore on top of SQLDelight
|
||||||
|
|
||||||
Build 🧱:
|
Build 🧱:
|
||||||
- Compile with Android SDK 29 (Android Q)
|
-
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
- Add a setting to prevent screenshots of the application, disabled by default (#1027)
|
|
||||||
- Increase File Logger capacities ( + use dev log preferences)
|
- Increase File Logger capacities ( + use dev log preferences)
|
||||||
|
|
||||||
Changes in RiotX 0.18.1 (2020-03-17)
|
Changes in RiotX 0.18.1 (2020-03-17)
|
||||||
@@ -456,7 +444,6 @@ Bugfix:
|
|||||||
- Fix messages with empty `in_reply_to` not rendering (#447)
|
- Fix messages with empty `in_reply_to` not rendering (#447)
|
||||||
- Fix clear cache (#408) and Logout (#205)
|
- Fix clear cache (#408) and Logout (#205)
|
||||||
- Fix `(edited)` link can be copied to clipboard (#402)
|
- Fix `(edited)` link can be copied to clipboard (#402)
|
||||||
- KeyBackup / SSSS | Should get the key from SSSS instead of asking recovery Key (#1163)
|
|
||||||
|
|
||||||
Build:
|
Build:
|
||||||
- Split APK: generate one APK per arch, to reduce APK size of about 30%
|
- Split APK: generate one APK per arch, to reduce APK size of about 30%
|
||||||
|
@@ -16,7 +16,7 @@ buildscript {
|
|||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
|
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
|
||||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.9.5'
|
classpath 'com.google.android.gms:oss-licenses-plugin:0.9.5'
|
||||||
|
classpath 'com.squareup.sqldelight:gradle-plugin:1.2.2'
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ allprojects {
|
|||||||
|
|
||||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||||
// Warnings are potential errors, so stop ignoring them
|
// Warnings are potential errors, so stop ignoring them
|
||||||
kotlinOptions.allWarningsAsErrors = true
|
kotlinOptions.allWarningsAsErrors = false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -38,6 +38,8 @@ dependencies {
|
|||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.3.3'
|
||||||
|
|
||||||
// Paging
|
// Paging
|
||||||
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
|
||||||
|
|
||||||
|
@@ -27,44 +27,34 @@ import im.vector.matrix.android.api.session.room.notification.RoomNotificationSt
|
|||||||
import im.vector.matrix.android.api.session.room.send.UserDraft
|
import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.api.util.toOptional
|
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
import kotlinx.coroutines.rx2.asObservable
|
||||||
|
|
||||||
class RxRoom(private val room: Room) {
|
class RxRoom(private val room: Room) {
|
||||||
|
|
||||||
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
||||||
return room.getRoomSummaryLive()
|
return room.getRoomSummaryLive().asObservable()
|
||||||
.asObservable()
|
|
||||||
.startWithCallable { room.roomSummary().toOptional() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
||||||
return room.getRoomMembersLive(queryParams).asObservable()
|
return room.getRoomMembersLive(queryParams).asObservable()
|
||||||
.startWithCallable {
|
|
||||||
room.getRoomMembers(queryParams)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
|
fun liveRoomMember(userId: String): Observable<Optional<RoomMemberSummary>> {
|
||||||
|
return room.getRoomMemberLive(userId).asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
|
||||||
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
|
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
|
||||||
.startWithCallable {
|
|
||||||
room.getEventAnnotationsSummary(eventId).toOptional()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
|
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
|
||||||
return room.getTimeLineEventLive(eventId).asObservable()
|
return room.getTimeLineEventLive(eventId).asObservable()
|
||||||
.startWithCallable {
|
|
||||||
room.getTimeLineEvent(eventId).toOptional()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveStateEvent(eventType: String, stateKey: String): Observable<Optional<Event>> {
|
fun liveStateEvent(eventType: String, stateKey: String): Observable<Optional<Event>> {
|
||||||
return room.getStateEventLive(eventType, stateKey).asObservable()
|
return room.getStateEventLive(eventType, stateKey).asObservable()
|
||||||
.startWithCallable {
|
|
||||||
room.getStateEvent(eventType, stateKey).toOptional()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveReadMarker(): Observable<Optional<String>> {
|
fun liveReadMarker(): Observable<Optional<String>> {
|
||||||
@@ -93,7 +83,7 @@ class RxRoom(private val room: Room) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun liveNotificationState(): Observable<RoomNotificationState> {
|
fun liveNotificationState(): Observable<RoomNotificationState> {
|
||||||
return room.getLiveRoomNotificationState().asObservable()
|
return room.getRoomNotificationStateLive().asObservable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
|
|||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||||
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Breadcrumb
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
@@ -34,28 +35,20 @@ import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
|||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
import kotlinx.coroutines.rx2.asObservable
|
||||||
|
|
||||||
class RxSession(private val session: Session) {
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||||
return session.getRoomSummariesLive(queryParams).asObservable()
|
return session.getRoomSummariesLive(queryParams).asObservable()
|
||||||
.startWithCallable {
|
|
||||||
session.getRoomSummaries(queryParams)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
||||||
return session.getGroupSummariesLive(queryParams).asObservable()
|
return session.getGroupSummariesLive(queryParams).asObservable()
|
||||||
.startWithCallable {
|
|
||||||
session.getGroupSummaries(queryParams)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
fun liveBreadcrumbs(): Observable<List<Breadcrumb>> {
|
||||||
return session.getBreadcrumbsLive().asObservable()
|
return session.getBreadcrumbsLive().asObservable()
|
||||||
.startWithCallable {
|
|
||||||
session.getBreadcrumbs()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveSyncState(): Observable<SyncState> {
|
fun liveSyncState(): Observable<SyncState> {
|
||||||
@@ -68,9 +61,6 @@ class RxSession(private val session: Session) {
|
|||||||
|
|
||||||
fun liveUser(userId: String): Observable<Optional<User>> {
|
fun liveUser(userId: String): Observable<Optional<User>> {
|
||||||
return session.getUserLive(userId).asObservable()
|
return session.getUserLive(userId).asObservable()
|
||||||
.startWithCallable {
|
|
||||||
session.getUser(userId).toOptional()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveUsers(): Observable<List<User>> {
|
fun liveUsers(): Observable<List<User>> {
|
||||||
@@ -125,9 +115,6 @@ class RxSession(private val session: Session) {
|
|||||||
|
|
||||||
fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
|
fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
|
||||||
return session.getLiveAccountDataEvents(types).asObservable()
|
return session.getLiveAccountDataEvents(types).asObservable()
|
||||||
.startWithCallable {
|
|
||||||
session.getAccountDataEvents(types)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -71,6 +71,10 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "1.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
pickFirst 'META-INF/kotlinx-coroutines-core.kotlin_module'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static def gitRevision() {
|
static def gitRevision() {
|
||||||
@@ -125,6 +129,11 @@ dependencies {
|
|||||||
// Database
|
// Database
|
||||||
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
||||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||||
|
implementation "com.squareup.sqldelight:android-driver:1.2.2"
|
||||||
|
implementation 'net.zetetic:android-database-sqlcipher:4.3.0'
|
||||||
|
implementation project(':matrix-sdk-sqldelight')
|
||||||
|
implementation "com.squareup.sqldelight:coroutines-extensions:1.2.1"
|
||||||
|
implementation "com.squareup.sqldelight:android-paging-extensions:1.2.1"
|
||||||
|
|
||||||
// Work
|
// Work
|
||||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||||
|
@@ -18,8 +18,5 @@ package im.vector.matrix.android
|
|||||||
|
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
|
|
||||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main,
|
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main, Main, Main)
|
||||||
Executors.newSingleThreadExecutor().asCoroutineDispatcher())
|
|
||||||
|
@@ -18,14 +18,18 @@ package im.vector.matrix.android.internal.crypto.verification.qrcode
|
|||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
|
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||||
import im.vector.matrix.android.common.CommonTestHelper
|
import im.vector.matrix.android.common.CommonTestHelper
|
||||||
import im.vector.matrix.android.common.CryptoTestHelper
|
import im.vector.matrix.android.common.CryptoTestHelper
|
||||||
import im.vector.matrix.android.common.TestConstants
|
import im.vector.matrix.android.common.TestConstants
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
|
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -229,4 +233,46 @@ class VerificationTest : InstrumentedTest {
|
|||||||
|
|
||||||
cryptoTestData.cleanUp(mTestHelper)
|
cryptoTestData.cleanUp(mTestHelper)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_alice_sends_text_message_and_bob_can_decrypt() {
|
||||||
|
val lock = CountDownLatch(1)
|
||||||
|
|
||||||
|
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
|
||||||
|
val roomFromBobPOV = bobSession.getRoom(cryptoTestData.roomId)!!
|
||||||
|
|
||||||
|
aliceSession.getRoom(cryptoTestData.roomId)!!.sendTextMessage("test")
|
||||||
|
|
||||||
|
val bobEventsListener = object : Timeline.Listener {
|
||||||
|
override fun onTimelineFailure(throwable: Throwable) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||||
|
if (snapshot.isNotEmpty() && snapshot.first().root.isEncrypted()) {
|
||||||
|
lock.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20))
|
||||||
|
bobTimeline.start()
|
||||||
|
bobTimeline.addListener(bobEventsListener)
|
||||||
|
|
||||||
|
mTestHelper.await(lock)
|
||||||
|
|
||||||
|
bobTimeline.getTimelineEventAtIndex(0)?.root?.let {
|
||||||
|
val decryptionResult = bobSession.cryptoService().decryptEvent(it, "")
|
||||||
|
val text = (decryptionResult.clearEvent["content"] as Map<*, *>)["body"]
|
||||||
|
assertEquals("test", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,12 +16,12 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.accountdata
|
package im.vector.matrix.android.api.session.accountdata
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.events.model.Content
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface AccountDataService {
|
interface AccountDataService {
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +32,7 @@ interface AccountDataService {
|
|||||||
/**
|
/**
|
||||||
* Observe the account data with the provided type
|
* Observe the account data with the provided type
|
||||||
*/
|
*/
|
||||||
fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>>
|
fun getLiveAccountDataEvent(type: String): Flow<Optional<UserAccountDataEvent>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the account data with the provided types. The return list can have a different size that
|
* Retrieve the account data with the provided types. The return list can have a different size that
|
||||||
@@ -44,7 +44,7 @@ interface AccountDataService {
|
|||||||
/**
|
/**
|
||||||
* Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed
|
* Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed
|
||||||
*/
|
*/
|
||||||
fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>>
|
fun getLiveAccountDataEvents(types: Set<String>): Flow<List<UserAccountDataEvent>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the account data with the provided type and the provided account data content
|
* Update the account data with the provided type and the provided account data content
|
||||||
|
@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.group
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to get groups. It's implemented at the session level.
|
* This interface defines methods to get groups. It's implemented at the session level.
|
||||||
@@ -48,5 +49,5 @@ interface GroupService {
|
|||||||
* Get a live list of group summaries. This list is refreshed as soon as the data changes.
|
* Get a live list of group summaries. This list is refreshed as soon as the data changes.
|
||||||
* @return the [LiveData] of [GroupSummary]
|
* @return the [LiveData] of [GroupSummary]
|
||||||
*/
|
*/
|
||||||
fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): LiveData<List<GroupSummary>>
|
fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): Flow<List<GroupSummary>>
|
||||||
}
|
}
|
||||||
|
@@ -25,9 +25,7 @@ import im.vector.matrix.android.api.session.room.model.Membership
|
|||||||
data class GroupSummary(
|
data class GroupSummary(
|
||||||
val groupId: String,
|
val groupId: String,
|
||||||
val membership: Membership,
|
val membership: Membership,
|
||||||
val displayName: String = "",
|
val displayName: String? = null,
|
||||||
val shortDescription: String = "",
|
val shortDescription: String? = null,
|
||||||
val avatarUrl: String = "",
|
val avatarUrl: String? = null
|
||||||
val roomIds: List<String> = emptyList(),
|
|
||||||
val userIds: List<String> = emptyList()
|
|
||||||
)
|
)
|
||||||
|
@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.pushers
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
interface PushersService {
|
interface PushersService {
|
||||||
@@ -72,9 +73,9 @@ interface PushersService {
|
|||||||
fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable
|
fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current pushers, as a LiveData
|
* Get the current pushers, as a Flow
|
||||||
*/
|
*/
|
||||||
fun getPushersLive(): LiveData<List<Pusher>>
|
fun getPushersLive(): Flow<List<Pusher>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current pushers
|
* Get the current pushers
|
||||||
|
@@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.room.state.StateService
|
|||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||||
import im.vector.matrix.android.api.session.room.typing.TypingService
|
import im.vector.matrix.android.api.session.room.typing.TypingService
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to interact within a room.
|
* This interface defines methods to interact within a room.
|
||||||
@@ -56,7 +57,7 @@ interface Room :
|
|||||||
* A live [RoomSummary] associated with the room
|
* A live [RoomSummary] associated with the room
|
||||||
* You can observe this summary to get dynamic data from this room.
|
* You can observe this summary to get dynamic data from this room.
|
||||||
*/
|
*/
|
||||||
fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>>
|
fun getRoomSummaryLive(): Flow<Optional<RoomSummary>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A current snapshot of [RoomSummary] associated with the room
|
* A current snapshot of [RoomSummary] associated with the room
|
||||||
|
@@ -18,10 +18,12 @@ package im.vector.matrix.android.api.session.room
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Breadcrumb
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to get rooms. It's implemented at the session level.
|
* This interface defines methods to get rooms. It's implemented at the session level.
|
||||||
@@ -67,21 +69,21 @@ interface RoomService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
|
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
|
||||||
* @return the [LiveData] of List[RoomSummary]
|
* @return the [Flow] of List[RoomSummary]
|
||||||
*/
|
*/
|
||||||
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>>
|
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): Flow<List<RoomSummary>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a snapshot list of Breadcrumbs
|
* Get a snapshot list of Breadcrumbs
|
||||||
* @return the immutable list of [RoomSummary]
|
* @return the immutable list of [Breadcrumb]
|
||||||
*/
|
*/
|
||||||
fun getBreadcrumbs(): List<RoomSummary>
|
fun getBreadcrumbs(): List<Breadcrumb>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a live list of Breadcrumbs
|
* Get a live list of Breadcrumbs
|
||||||
* @return the [LiveData] of [RoomSummary]
|
* @return the [Flow] of [Breadcrumb]
|
||||||
*/
|
*/
|
||||||
fun getBreadcrumbsLive(): LiveData<List<RoomSummary>>
|
fun getBreadcrumbsLive(): Flow<List<Breadcrumb>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inform the Matrix SDK that a room is displayed.
|
* Inform the Matrix SDK that a room is displayed.
|
||||||
@@ -102,5 +104,5 @@ interface RoomService {
|
|||||||
searchOnServer: Boolean,
|
searchOnServer: Boolean,
|
||||||
callback: MatrixCallback<Optional<String>>): Cancelable
|
callback: MatrixCallback<Optional<String>>): Cancelable
|
||||||
|
|
||||||
fun getExistingDirectRoomWithUser(otherUserId: String) : Room?
|
fun getExistingDirectRoomWithUser(otherUserId: String): Room?
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,7 @@ fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {
|
|||||||
* [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService]
|
* [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService]
|
||||||
*/
|
*/
|
||||||
data class RoomSummaryQueryParams(
|
data class RoomSummaryQueryParams(
|
||||||
|
val fromGroupId: String?,
|
||||||
val displayName: QueryStringValue,
|
val displayName: QueryStringValue,
|
||||||
val canonicalAlias: QueryStringValue,
|
val canonicalAlias: QueryStringValue,
|
||||||
val memberships: List<Membership>
|
val memberships: List<Membership>
|
||||||
@@ -35,11 +36,13 @@ data class RoomSummaryQueryParams(
|
|||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
|
|
||||||
|
var fromGroupId: String? = null
|
||||||
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
|
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
|
||||||
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
|
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
|
||||||
var memberships: List<Membership> = Membership.all()
|
var memberships: List<Membership> = Membership.all()
|
||||||
|
|
||||||
fun build() = RoomSummaryQueryParams(
|
fun build() = RoomSummaryQueryParams(
|
||||||
|
fromGroupId = fromGroupId,
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
canonicalAlias = canonicalAlias,
|
canonicalAlias = canonicalAlias,
|
||||||
memberships = memberships
|
memberships = memberships
|
||||||
|
@@ -20,6 +20,8 @@ import androidx.lifecycle.LiveData
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to handling membership. It's implemented at the room level.
|
* This interface defines methods to handling membership. It's implemented at the room level.
|
||||||
@@ -40,6 +42,14 @@ interface MembershipService {
|
|||||||
*/
|
*/
|
||||||
fun getRoomMember(userId: String): RoomMemberSummary?
|
fun getRoomMember(userId: String): RoomMemberSummary?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a live version of an optionnal roomMember with the given userId.
|
||||||
|
* @param userId the userId param to look for
|
||||||
|
*
|
||||||
|
* @return a [Flow] of [Optional] [RoomMemberSummary] with userId
|
||||||
|
*/
|
||||||
|
fun getRoomMemberLive(userId: String): Flow<Optional<RoomMemberSummary>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return all the roomMembers of the room with params
|
* Return all the roomMembers of the room with params
|
||||||
* @param queryParams the params to query for
|
* @param queryParams the params to query for
|
||||||
@@ -50,9 +60,10 @@ interface MembershipService {
|
|||||||
/**
|
/**
|
||||||
* Return all the roomMembers of the room filtered by memberships
|
* Return all the roomMembers of the room filtered by memberships
|
||||||
* @param queryParams the params to query for
|
* @param queryParams the params to query for
|
||||||
* @return a [LiveData] of roomMember list.
|
* @return a [Flow] of roomMember list.
|
||||||
*/
|
*/
|
||||||
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMemberSummary>>
|
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): Flow<List<RoomMemberSummary>>
|
||||||
|
|
||||||
|
|
||||||
fun getNumberOfJoinedMembers(): Int
|
fun getNumberOfJoinedMembers(): Int
|
||||||
|
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This data class holds data about a breadcrumb.
|
||||||
|
*/
|
||||||
|
data class Breadcrumb(
|
||||||
|
val roomId: String,
|
||||||
|
val displayName: String = "",
|
||||||
|
val avatarUrl: String = "",
|
||||||
|
val notificationCount: Int = 0,
|
||||||
|
val highlightCount: Int = 0,
|
||||||
|
val hasUnreadMessages: Boolean = false,
|
||||||
|
val userDrafts: List<UserDraft> = emptyList(),
|
||||||
|
var isEncrypted: Boolean,
|
||||||
|
val typingRoomMemberIds: List<String> = emptyList()
|
||||||
|
)
|
@@ -31,7 +31,6 @@ data class RoomSummary constructor(
|
|||||||
val topic: String = "",
|
val topic: String = "",
|
||||||
val avatarUrl: String = "",
|
val avatarUrl: String = "",
|
||||||
val canonicalAlias: String? = null,
|
val canonicalAlias: String? = null,
|
||||||
val aliases: List<String> = emptyList(),
|
|
||||||
val isDirect: Boolean = false,
|
val isDirect: Boolean = false,
|
||||||
val joinedMembersCount: Int? = 0,
|
val joinedMembersCount: Int? = 0,
|
||||||
val invitedMembersCount: Int? = 0,
|
val invitedMembersCount: Int? = 0,
|
||||||
|
@@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
|||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In some cases, events may wish to reference other events.
|
* In some cases, events may wish to reference other events.
|
||||||
@@ -113,12 +114,12 @@ interface RelationService {
|
|||||||
* @param eventId the eventId to look for EventAnnotationsSummary
|
* @param eventId the eventId to look for EventAnnotationsSummary
|
||||||
* @return the EventAnnotationsSummary found
|
* @return the EventAnnotationsSummary found
|
||||||
*/
|
*/
|
||||||
fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary?
|
fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a LiveData of EventAnnotationsSummary for the specified eventId
|
* Get a LiveData of EventAnnotationsSummary for the specified eventId
|
||||||
* @param eventId the eventId to look for EventAnnotationsSummary
|
* @param eventId the eventId to look for EventAnnotationsSummary
|
||||||
* @return the LiveData of EventAnnotationsSummary
|
* @return a [Flow] of EventAnnotationsSummary
|
||||||
*/
|
*/
|
||||||
fun getEventAnnotationsSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>>
|
fun getEventAnnotationsSummaryLive(eventId: String): Flow<EventAnnotationsSummary>
|
||||||
}
|
}
|
||||||
|
@@ -16,13 +16,13 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.notification
|
package im.vector.matrix.android.api.session.room.notification
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface RoomPushRuleService {
|
interface RoomPushRuleService {
|
||||||
|
|
||||||
fun getLiveRoomNotificationState(): LiveData<RoomNotificationState>
|
fun getRoomNotificationStateLive(): Flow<RoomNotificationState>
|
||||||
|
|
||||||
fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable
|
fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
|
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
|
||||||
@@ -55,16 +56,16 @@ interface ReadService {
|
|||||||
/**
|
/**
|
||||||
* Returns a live read marker id for the room.
|
* Returns a live read marker id for the room.
|
||||||
*/
|
*/
|
||||||
fun getReadMarkerLive(): LiveData<Optional<String>>
|
fun getReadMarkerLive(): Flow<Optional<String>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a live read receipt id for the room.
|
* Returns a live read receipt id for the room.
|
||||||
*/
|
*/
|
||||||
fun getMyReadReceiptLive(): LiveData<Optional<String>>
|
fun getMyReadReceiptLive(): Flow<Optional<String>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a live list of read receipts for a given event
|
* Returns a live list of read receipts for a given event
|
||||||
* @param eventId: the event
|
* @param eventId: the event
|
||||||
*/
|
*/
|
||||||
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
|
fun getEventReadReceiptsLive(eventId: String): Flow<List<ReadReceipt>>
|
||||||
}
|
}
|
||||||
|
@@ -16,9 +16,9 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.send
|
package im.vector.matrix.android.api.session.room.send
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface DraftService {
|
interface DraftService {
|
||||||
|
|
||||||
@@ -36,5 +36,5 @@ interface DraftService {
|
|||||||
* Return the current drafts if any, as a live data
|
* Return the current drafts if any, as a live data
|
||||||
* The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts
|
* The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts
|
||||||
*/
|
*/
|
||||||
fun getDraftsLive(): LiveData<List<UserDraft>>
|
fun getDraftsLive(): Flow<List<UserDraft>>
|
||||||
}
|
}
|
||||||
|
@@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.state
|
package im.vector.matrix.android.api.session.room.state
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface StateService {
|
interface StateService {
|
||||||
|
|
||||||
@@ -30,5 +30,5 @@ interface StateService {
|
|||||||
|
|
||||||
fun getStateEvent(eventType: String, stateKey: String): Event?
|
fun getStateEvent(eventType: String, stateKey: String): Event?
|
||||||
|
|
||||||
fun getStateEventLive(eventType: String, stateKey: String): LiveData<Optional<Event>>
|
fun getStateEventLive(eventType: String, stateKey: String): Flow<Optional<Event>>
|
||||||
}
|
}
|
||||||
|
@@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.timeline
|
package im.vector.matrix.android.api.session.room.timeline
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to interact with the timeline. It's implemented at the room level.
|
* This interface defines methods to interact with the timeline. It's implemented at the room level.
|
||||||
@@ -38,5 +38,5 @@ interface TimelineService {
|
|||||||
|
|
||||||
fun getTimeLineEvent(eventId: String): TimelineEvent?
|
fun getTimeLineEvent(eventId: String): TimelineEvent?
|
||||||
|
|
||||||
fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>>
|
fun getTimeLineEventLive(eventId: String): Flow<Optional<TimelineEvent>>
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.api.util.Optional
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to get users. It's implemented at the session level.
|
* This interface defines methods to get users. It's implemented at the session level.
|
||||||
@@ -48,27 +49,27 @@ interface UserService {
|
|||||||
/**
|
/**
|
||||||
* Observe a live user from a userId
|
* Observe a live user from a userId
|
||||||
* @param userId the userId to look for.
|
* @param userId the userId to look for.
|
||||||
* @return a LiveData of user with userId
|
* @return a Flow of user with userId
|
||||||
*/
|
*/
|
||||||
fun getUserLive(userId: String): LiveData<Optional<User>>
|
fun getUserLive(userId: String): Flow<Optional<User>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observe a live list of users sorted alphabetically
|
* Observe a live list of users sorted alphabetically
|
||||||
* @return a Livedata of users
|
* @return a Flow of users
|
||||||
*/
|
*/
|
||||||
fun getUsersLive(): LiveData<List<User>>
|
fun getUsersLive(): Flow<List<User>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
|
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
|
||||||
* @param filter the filter. It will look into userId and displayName.
|
* @param filter the filter. It will look into userId and displayName.
|
||||||
* @return a Livedata of users
|
* @return a Flow of users
|
||||||
*/
|
*/
|
||||||
fun getPagedUsersLive(filter: String? = null): LiveData<PagedList<User>>
|
fun getPagedUsersLive(filter: String? = null): Flow<PagedList<User>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get list of ignored users
|
* Get list of ignored users
|
||||||
*/
|
*/
|
||||||
fun getIgnoredUsersLive(): LiveData<List<User>>
|
fun getIgnoredUsersLive(): Flow<List<User>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ignore users
|
* Ignore users
|
||||||
|
@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.util
|
|||||||
|
|
||||||
import im.vector.matrix.android.BuildConfig
|
import im.vector.matrix.android.BuildConfig
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Breadcrumb
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||||
@@ -148,6 +149,8 @@ fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, ava
|
|||||||
|
|
||||||
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
||||||
|
|
||||||
|
fun Breadcrumb.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
||||||
|
|
||||||
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
|
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
|
||||||
|
|
||||||
// If no name is available, use room alias as Riot-Web does
|
// If no name is available, use room alias as Riot-Web does
|
||||||
|
@@ -17,16 +17,19 @@
|
|||||||
package im.vector.matrix.android.internal.auth
|
package im.vector.matrix.android.internal.auth
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import com.squareup.sqldelight.android.AndroidSqliteDriver
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||||
import im.vector.matrix.android.internal.auth.db.AuthRealmMigration
|
import im.vector.matrix.android.internal.auth.realm.AuthRealmMigration
|
||||||
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
import im.vector.matrix.android.internal.auth.realm.AuthRealmModule
|
||||||
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
|
import im.vector.matrix.android.internal.auth.sqlite.AuthSchema
|
||||||
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
import im.vector.matrix.android.internal.auth.sqlite.SqlitePendingSessionStore
|
||||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
import im.vector.matrix.android.internal.auth.sqlite.SqliteSessionParamsStore
|
||||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
import im.vector.matrix.android.internal.database.DatabaseKeysUtils
|
||||||
|
import im.vector.matrix.android.internal.di.MatrixScope
|
||||||
|
import im.vector.matrix.sqldelight.auth.AuthDatabase
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -39,16 +42,16 @@ internal abstract class AuthModule {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@AuthDatabase
|
@im.vector.matrix.android.internal.di.RealmAuthDatabase
|
||||||
fun providesRealmConfiguration(context: Context, realmKeysUtils: RealmKeysUtils): RealmConfiguration {
|
@MatrixScope
|
||||||
|
fun providesRealmConfiguration(context: Context, databaseKeysUtils: DatabaseKeysUtils): RealmConfiguration {
|
||||||
val old = File(context.filesDir, "matrix-sdk-auth")
|
val old = File(context.filesDir, "matrix-sdk-auth")
|
||||||
if (old.exists()) {
|
if (old.exists()) {
|
||||||
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
|
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return RealmConfiguration.Builder()
|
return RealmConfiguration.Builder()
|
||||||
.apply {
|
.apply {
|
||||||
realmKeysUtils.configureEncryption(this, DB_ALIAS)
|
databaseKeysUtils.configureEncryption(this, DB_ALIAS)
|
||||||
}
|
}
|
||||||
.name("matrix-sdk-auth.realm")
|
.name("matrix-sdk-auth.realm")
|
||||||
.modules(AuthRealmModule())
|
.modules(AuthRealmModule())
|
||||||
@@ -56,13 +59,22 @@ internal abstract class AuthModule {
|
|||||||
.migration(AuthRealmMigration)
|
.migration(AuthRealmMigration)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@MatrixScope
|
||||||
|
fun providesAuthDatabase(context: Context, authSchema: AuthSchema, databaseKeysUtils: DatabaseKeysUtils): AuthDatabase {
|
||||||
|
val supportFactory = databaseKeysUtils.createEncryptedSQLiteOpenHelperFactory(DB_ALIAS)
|
||||||
|
val driver = AndroidSqliteDriver(authSchema, context, "matrix-sdk-auth.db", factory = supportFactory)
|
||||||
|
return AuthDatabase.invoke(driver)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
|
abstract fun bindSessionParamsStore(sessionParamsStore: SqliteSessionParamsStore): SessionParamsStore
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindPendingSessionStore(pendingSessionStore: RealmPendingSessionStore): PendingSessionStore
|
abstract fun bindPendingSessionStore(pendingSessionStore: SqlitePendingSessionStore): PendingSessionStore
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService
|
abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService
|
||||||
|
@@ -20,13 +20,7 @@ import android.net.Uri
|
|||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.*
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
|
||||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
|
||||||
import im.vector.matrix.android.api.auth.data.Versions
|
|
||||||
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
|
|
||||||
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
|
|
||||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
@@ -35,7 +29,7 @@ import im.vector.matrix.android.api.util.Cancelable
|
|||||||
import im.vector.matrix.android.internal.SessionManager
|
import im.vector.matrix.android.internal.SessionManager
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||||
import im.vector.matrix.android.internal.auth.data.RiotConfig
|
import im.vector.matrix.android.internal.auth.data.RiotConfig
|
||||||
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
|
||||||
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
|
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
|
||||||
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
|
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
|
||||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
@@ -114,7 +108,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||||
return withContext(coroutineDispatchers.io) {
|
return withContext(coroutineDispatchers.computation) {
|
||||||
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||||
|
|
||||||
// First check the homeserver version
|
// First check the homeserver version
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.auth
|
package im.vector.matrix.android.internal.auth
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store for elements when doing login or registration
|
* Store for elements when doing login or registration
|
||||||
|
@@ -30,7 +30,7 @@ import im.vector.matrix.android.internal.auth.PendingSessionStore
|
|||||||
import im.vector.matrix.android.internal.auth.SessionCreator
|
import im.vector.matrix.android.internal.auth.SessionCreator
|
||||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
||||||
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
|
||||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
|
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
|
||||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
|
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
|
||||||
import im.vector.matrix.android.internal.auth.registration.RegisterAddThreePidTask
|
import im.vector.matrix.android.internal.auth.registration.RegisterAddThreePidTask
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
package im.vector.matrix.android.internal.auth.realm
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.sessionId
|
import im.vector.matrix.android.api.auth.data.sessionId
|
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
package im.vector.matrix.android.internal.auth.realm
|
||||||
|
|
||||||
import io.realm.annotations.RealmModule
|
import io.realm.annotations.RealmModule
|
||||||
|
|
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
package im.vector.matrix.android.internal.auth.realm
|
||||||
|
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
|
|
@@ -14,11 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
package im.vector.matrix.android.internal.auth.realm
|
||||||
|
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.internal.auth.login.ResetPasswordData
|
import im.vector.matrix.android.internal.auth.login.ResetPasswordData
|
||||||
|
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
|
||||||
import im.vector.matrix.android.internal.auth.registration.ThreePidData
|
import im.vector.matrix.android.internal.auth.registration.ThreePidData
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@@ -14,17 +14,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
package im.vector.matrix.android.internal.auth.realm
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.auth.PendingSessionStore
|
import im.vector.matrix.android.internal.auth.PendingSessionStore
|
||||||
|
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
|
||||||
import im.vector.matrix.android.internal.database.awaitTransaction
|
import im.vector.matrix.android.internal.database.awaitTransaction
|
||||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
import im.vector.matrix.android.internal.di.RealmAuthDatabase
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RealmPendingSessionStore @Inject constructor(private val mapper: PendingSessionMapper,
|
internal class RealmPendingSessionStore @Inject constructor(private val mapper: PendingSessionMapper,
|
||||||
@AuthDatabase
|
@RealmAuthDatabase
|
||||||
private val realmConfiguration: RealmConfiguration
|
private val realmConfiguration: RealmConfiguration
|
||||||
) : PendingSessionStore {
|
) : PendingSessionStore {
|
||||||
|
|
@@ -14,14 +14,14 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
package im.vector.matrix.android.internal.auth.realm
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.auth.data.sessionId
|
import im.vector.matrix.android.api.auth.data.sessionId
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.database.awaitTransaction
|
import im.vector.matrix.android.internal.database.awaitTransaction
|
||||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
import im.vector.matrix.android.internal.di.RealmAuthDatabase
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.exceptions.RealmPrimaryKeyConstraintException
|
import io.realm.exceptions.RealmPrimaryKeyConstraintException
|
||||||
@@ -29,7 +29,7 @@ import timber.log.Timber
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper,
|
internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper,
|
||||||
@AuthDatabase
|
@RealmAuthDatabase
|
||||||
private val realmConfiguration: RealmConfiguration
|
private val realmConfiguration: RealmConfiguration
|
||||||
) : SessionParamsStore {
|
) : SessionParamsStore {
|
||||||
|
|
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
package im.vector.matrix.android.internal.auth.realm
|
||||||
|
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
@@ -14,8 +14,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
package im.vector.matrix.android.internal.auth.realm
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonDataException
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
@@ -28,6 +29,15 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
|||||||
private val credentialsAdapter = moshi.adapter(Credentials::class.java)
|
private val credentialsAdapter = moshi.adapter(Credentials::class.java)
|
||||||
private val homeServerConnectionConfigAdapter = moshi.adapter(HomeServerConnectionConfig::class.java)
|
private val homeServerConnectionConfigAdapter = moshi.adapter(HomeServerConnectionConfig::class.java)
|
||||||
|
|
||||||
|
fun map(credentialsJson: String, homeServerConnectionConfigJson: String, isTokenValid: Boolean): SessionParams {
|
||||||
|
val credentials = credentialsAdapter.fromJson(credentialsJson)
|
||||||
|
val homeServerConnectionConfig = homeServerConnectionConfigAdapter.fromJson(homeServerConnectionConfigJson)
|
||||||
|
if (credentials == null || homeServerConnectionConfig == null) {
|
||||||
|
throw JsonDataException()
|
||||||
|
}
|
||||||
|
return SessionParams(credentials, homeServerConnectionConfig, isTokenValid)
|
||||||
|
}
|
||||||
|
|
||||||
fun map(entity: SessionParamsEntity?): SessionParams? {
|
fun map(entity: SessionParamsEntity?): SessionParams? {
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
return null
|
return null
|
@@ -29,7 +29,6 @@ import im.vector.matrix.android.internal.auth.AuthAPI
|
|||||||
import im.vector.matrix.android.internal.auth.PendingSessionStore
|
import im.vector.matrix.android.internal.auth.PendingSessionStore
|
||||||
import im.vector.matrix.android.internal.auth.SessionCreator
|
import im.vector.matrix.android.internal.auth.SessionCreator
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||||
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
|
||||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.task.launchToCallback
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
@@ -14,12 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
package im.vector.matrix.android.internal.auth.registration
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.internal.auth.login.ResetPasswordData
|
import im.vector.matrix.android.internal.auth.login.ResetPasswordData
|
||||||
import im.vector.matrix.android.internal.auth.registration.ThreePidData
|
import im.vector.matrix.android.internal.auth.registration.ThreePidData
|
||||||
import java.util.UUID
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class holds all pending data when creating a session, either by login or by register
|
* This class holds all pending data when creating a session, either by login or by register
|
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.auth.sqlite
|
||||||
|
|
||||||
|
import com.squareup.sqldelight.db.SqlDriver
|
||||||
|
import im.vector.matrix.sqldelight.auth.AuthDatabase
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class AuthSchema @Inject constructor(@im.vector.matrix.android.internal.di.RealmAuthDatabase private val realmConfiguration: RealmConfiguration) : SqlDriver.Schema by AuthDatabase.Schema {
|
||||||
|
|
||||||
|
override fun create(driver: SqlDriver) {
|
||||||
|
AuthDatabase.Schema.create(driver)
|
||||||
|
AuthDatabase(driver).apply {
|
||||||
|
val sessionParamsQueries = this.sessionParamsQueries
|
||||||
|
sessionParamsQueries.transaction {
|
||||||
|
Realm.getInstance(realmConfiguration).use {
|
||||||
|
it.where(im.vector.matrix.android.internal.auth.realm.SessionParamsEntity::class.java).findAll().forEach { realmSessionParams ->
|
||||||
|
sessionParamsQueries.insert(realmSessionParams.toSqlModel())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun im.vector.matrix.android.internal.auth.realm.SessionParamsEntity.toSqlModel(): im.vector.matrix.sqldelight.auth.SessionParamsEntity {
|
||||||
|
return im.vector.matrix.sqldelight.auth.SessionParamsEntity.Impl(
|
||||||
|
sessionId,
|
||||||
|
userId,
|
||||||
|
credentialsJson,
|
||||||
|
homeServerConnectionConfigJson,
|
||||||
|
isTokenValid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.auth.sqlite
|
||||||
|
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
|
import im.vector.matrix.android.internal.auth.login.ResetPasswordData
|
||||||
|
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
|
||||||
|
import im.vector.matrix.android.internal.auth.registration.ThreePidData
|
||||||
|
import im.vector.matrix.sqldelight.auth.PendingSessionEntity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class SqlitePendingSessionMapper @Inject constructor(moshi: Moshi) {
|
||||||
|
|
||||||
|
private val homeServerConnectionConfigAdapter = moshi.adapter(HomeServerConnectionConfig::class.java)
|
||||||
|
private val resetPasswordDataAdapter = moshi.adapter(ResetPasswordData::class.java)
|
||||||
|
private val threePidDataAdapter = moshi.adapter(ThreePidData::class.java)
|
||||||
|
|
||||||
|
fun map(entity: PendingSessionEntity?): PendingSessionData? {
|
||||||
|
if (entity == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val homeServerConnectionConfig = homeServerConnectionConfigAdapter.fromJson(entity.home_server_connection_config_json)!!
|
||||||
|
val resetPasswordData = entity.reset_password_data_json?.let { resetPasswordDataAdapter.fromJson(it) }
|
||||||
|
val threePidData = entity.current_three_pid_data_json?.let { threePidDataAdapter.fromJson(it) }
|
||||||
|
|
||||||
|
return PendingSessionData(
|
||||||
|
homeServerConnectionConfig = homeServerConnectionConfig,
|
||||||
|
clientSecret = entity.client_secret,
|
||||||
|
sendAttempt = entity.send_attempts,
|
||||||
|
resetPasswordData = resetPasswordData,
|
||||||
|
currentSession = entity.current_session,
|
||||||
|
isRegistrationStarted = entity.is_registration_started,
|
||||||
|
currentThreePidData = threePidData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun map(sessionData: PendingSessionData?): PendingSessionEntity? {
|
||||||
|
if (sessionData == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val homeServerConnectionConfigJson = homeServerConnectionConfigAdapter.toJson(sessionData.homeServerConnectionConfig)
|
||||||
|
val resetPasswordDataJson = resetPasswordDataAdapter.toJson(sessionData.resetPasswordData)
|
||||||
|
val currentThreePidDataJson = threePidDataAdapter.toJson(sessionData.currentThreePidData)
|
||||||
|
|
||||||
|
return PendingSessionEntity.Impl(
|
||||||
|
home_server_connection_config_json = homeServerConnectionConfigJson,
|
||||||
|
client_secret = sessionData.clientSecret,
|
||||||
|
send_attempts = sessionData.sendAttempt,
|
||||||
|
reset_password_data_json = resetPasswordDataJson,
|
||||||
|
current_session = sessionData.currentSession,
|
||||||
|
is_registration_started = sessionData.isRegistrationStarted,
|
||||||
|
current_three_pid_data_json = currentThreePidDataJson
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.auth.sqlite
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.auth.PendingSessionStore
|
||||||
|
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import im.vector.matrix.sqldelight.auth.PendingSessionQueries
|
||||||
|
import im.vector.matrix.sqldelight.auth.AuthDatabase
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class SqlitePendingSessionStore @Inject constructor(database: AuthDatabase,
|
||||||
|
private val mapper: SqlitePendingSessionMapper,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers) : PendingSessionStore {
|
||||||
|
|
||||||
|
private val pendingSessionQueries: PendingSessionQueries = database.pendingSessionQueries
|
||||||
|
|
||||||
|
override suspend fun savePendingSessionData(pendingSessionData: PendingSessionData) = withContext(coroutineDispatchers.dbTransaction) {
|
||||||
|
val pendingSessionEntity = mapper.map(pendingSessionData)
|
||||||
|
if (pendingSessionEntity != null) {
|
||||||
|
pendingSessionQueries.transaction {
|
||||||
|
pendingSessionQueries.delete()
|
||||||
|
pendingSessionQueries.insertOrUpdate(pendingSessionEntity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPendingSessionData(): PendingSessionData? {
|
||||||
|
val pendingSessionEntity = pendingSessionQueries.get().executeAsOneOrNull()
|
||||||
|
return mapper.map(pendingSessionEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete() = withContext(coroutineDispatchers.dbTransaction) {
|
||||||
|
pendingSessionQueries.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.auth.sqlite
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonDataException
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
|
import im.vector.matrix.android.api.auth.data.sessionId
|
||||||
|
import im.vector.matrix.sqldelight.auth.SessionParamsEntity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class SqliteSessionParamsMapper @Inject constructor(moshi: Moshi) {
|
||||||
|
|
||||||
|
val credentialsAdapter = moshi.adapter(Credentials::class.java)
|
||||||
|
private val homeServerConnectionConfigAdapter = moshi.adapter(HomeServerConnectionConfig::class.java)
|
||||||
|
|
||||||
|
fun map(credentialsJson: String, homeServerConnectionConfigJson: String, isTokenValid: Boolean): SessionParams {
|
||||||
|
val credentials = credentialsAdapter.fromJson(credentialsJson)
|
||||||
|
val homeServerConnectionConfig = homeServerConnectionConfigAdapter.fromJson(homeServerConnectionConfigJson)
|
||||||
|
if (credentials == null || homeServerConnectionConfig == null) {
|
||||||
|
throw JsonDataException()
|
||||||
|
}
|
||||||
|
return SessionParams(credentials, homeServerConnectionConfig, isTokenValid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun map(sessionParams: SessionParams?): SessionParamsEntity? {
|
||||||
|
if (sessionParams == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val credentialsJson = credentialsAdapter.toJson(sessionParams.credentials)
|
||||||
|
val homeServerConnectionConfigJson = homeServerConnectionConfigAdapter.toJson(sessionParams.homeServerConnectionConfig)
|
||||||
|
if (credentialsJson == null || homeServerConnectionConfigJson == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return SessionParamsEntity.Impl(
|
||||||
|
session_id = sessionParams.credentials.sessionId(),
|
||||||
|
user_id = sessionParams.credentials.userId,
|
||||||
|
credentials_json = credentialsJson,
|
||||||
|
home_server_connection_config_json = homeServerConnectionConfigJson,
|
||||||
|
is_token_valid = sessionParams.isTokenValid)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.auth.sqlite
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
|
import im.vector.matrix.android.api.auth.data.sessionId
|
||||||
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
|
import im.vector.matrix.sqldelight.auth.SessionParamsQueries
|
||||||
|
import im.vector.matrix.sqldelight.auth.AuthDatabase
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class SqliteSessionParamsStore @Inject constructor(database: AuthDatabase,
|
||||||
|
private val mapper: SqliteSessionParamsMapper) : SessionParamsStore {
|
||||||
|
|
||||||
|
private val sessionParamsQueries: SessionParamsQueries = database.sessionParamsQueries
|
||||||
|
|
||||||
|
override fun get(sessionId: String): SessionParams? {
|
||||||
|
return sessionParamsQueries.getSessionParamsWithId(sessionId) { credentials_json, home_server_connection_config_json, is_token_valid ->
|
||||||
|
mapper.map(credentials_json, home_server_connection_config_json, is_token_valid)
|
||||||
|
}.executeAsOneOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLast(): SessionParams? {
|
||||||
|
return getAll().lastOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAll(): List<SessionParams> {
|
||||||
|
return sessionParamsQueries.getAllSessionParams { credentials_json, home_server_connection_config_json, is_token_valid ->
|
||||||
|
mapper.map(credentials_json, home_server_connection_config_json, is_token_valid)
|
||||||
|
}.executeAsList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun save(sessionParams: SessionParams) {
|
||||||
|
val sessionParamsEntity = mapper.map(sessionParams)
|
||||||
|
if (sessionParamsEntity != null) {
|
||||||
|
sessionParamsQueries.insert(sessionParamsEntity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setTokenInvalid(sessionId: String) {
|
||||||
|
sessionParamsQueries.setTokenInvalid(sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateCredentials(newCredentials: Credentials) {
|
||||||
|
val newCredentialsJson = mapper.credentialsAdapter.toJson(newCredentials)
|
||||||
|
sessionParamsQueries.updateCredentials(newCredentialsJson, newCredentials.sessionId())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(sessionId: String) {
|
||||||
|
sessionParamsQueries.delete(sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteAll() {
|
||||||
|
sessionParamsQueries.deleteAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
package im.vector.matrix.android.internal.concurrency
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.ThreadFactory
|
||||||
|
|
||||||
|
internal class NamedThreadFactory(private val name: String) : ThreadFactory {
|
||||||
|
|
||||||
|
override fun newThread(runnable: Runnable): Thread {
|
||||||
|
return Thread(runnable, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun newNamedSingleThreadExecutor(name: String): Executor {
|
||||||
|
return Executors.newSingleThreadExecutor(NamedThreadFactory(name))
|
||||||
|
}
|
@@ -16,9 +16,15 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto
|
package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import com.squareup.sqldelight.ColumnAdapter
|
||||||
|
import com.squareup.sqldelight.android.AndroidSqliteDriver
|
||||||
|
import com.squareup.sqldelight.logs.LogSqliteDriver
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
import im.vector.matrix.android.BuildConfig
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
|
||||||
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
||||||
@@ -55,9 +61,9 @@ import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessio
|
|||||||
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
|
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
|
import im.vector.matrix.android.internal.crypto.store.db.SqlCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceTask
|
||||||
@@ -86,13 +92,15 @@ import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
|
|||||||
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
|
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
|
import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
|
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
|
||||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
import im.vector.matrix.android.internal.database.DatabaseKeysUtils
|
||||||
import im.vector.matrix.android.internal.di.CryptoDatabase
|
import im.vector.matrix.android.internal.di.RealmCryptoDatabase
|
||||||
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
||||||
import im.vector.matrix.android.internal.di.UserMd5
|
import im.vector.matrix.android.internal.di.UserMd5
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
||||||
import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
|
import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
|
||||||
|
import im.vector.matrix.sqldelight.crypto.CrossSigningInfoEntity
|
||||||
|
import im.vector.matrix.sqldelight.crypto.CryptoDatabase
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
@@ -108,15 +116,15 @@ internal abstract class CryptoModule {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@CryptoDatabase
|
@RealmCryptoDatabase
|
||||||
@SessionScope
|
@SessionScope
|
||||||
fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
|
fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
|
||||||
@UserMd5 userMd5: String,
|
@UserMd5 userMd5: String,
|
||||||
realmKeysUtils: RealmKeysUtils): RealmConfiguration {
|
databaseKeysUtils: DatabaseKeysUtils): RealmConfiguration {
|
||||||
return RealmConfiguration.Builder()
|
return RealmConfiguration.Builder()
|
||||||
.directory(directory)
|
.directory(directory)
|
||||||
.apply {
|
.apply {
|
||||||
realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
|
databaseKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
|
||||||
}
|
}
|
||||||
.name("crypto_store.realm")
|
.name("crypto_store.realm")
|
||||||
.modules(RealmCryptoStoreModule())
|
.modules(RealmCryptoStoreModule())
|
||||||
@@ -125,6 +133,31 @@ internal abstract class CryptoModule {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@SessionScope
|
||||||
|
fun providesCryptoDatabase(context: Context, @SessionFilesDirectory directory: File): CryptoDatabase {
|
||||||
|
val name = "${directory.name}-matrix-sdk-crypto.db"
|
||||||
|
val driver = if (BuildConfig.DEBUG) {
|
||||||
|
LogSqliteDriver(AndroidSqliteDriver(CryptoDatabase.Schema, context, name)) { log ->
|
||||||
|
Log.d("SQLite", log)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AndroidSqliteDriver(CryptoDatabase.Schema, context, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
val listOfStringAdapter = object : ColumnAdapter<List<String>, String> {
|
||||||
|
override fun decode(databaseValue: String) = databaseValue.split(",")
|
||||||
|
override fun encode(value: List<String>) = value.joinToString(separator = ",")
|
||||||
|
}
|
||||||
|
return CryptoDatabase(
|
||||||
|
driver = driver,
|
||||||
|
crossSigningInfoEntityAdapter = CrossSigningInfoEntity.Adapter(
|
||||||
|
usagesAdapter = listOfStringAdapter
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@SessionScope
|
@SessionScope
|
||||||
@@ -134,8 +167,8 @@ internal abstract class CryptoModule {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@CryptoDatabase
|
@RealmCryptoDatabase
|
||||||
fun providesClearCacheTask(@CryptoDatabase
|
fun providesClearCacheTask(@RealmCryptoDatabase
|
||||||
realmConfiguration: RealmConfiguration): ClearCacheTask {
|
realmConfiguration: RealmConfiguration): ClearCacheTask {
|
||||||
return RealmClearCacheTask(realmConfiguration)
|
return RealmClearCacheTask(realmConfiguration)
|
||||||
}
|
}
|
||||||
@@ -243,7 +276,7 @@ internal abstract class CryptoModule {
|
|||||||
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
|
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore
|
abstract fun bindCryptoStore(store: SqlCryptoStore): IMXCryptoStore
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindComputeShieldTrustTask(task: DefaultComputeTrustTask): ComputeTrustTask
|
abstract fun bindComputeShieldTrustTask(task: DefaultComputeTrustTask): ComputeTrustTask
|
||||||
|
@@ -23,7 +23,6 @@ import android.os.Handler
|
|||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.NoOpMatrixCallback
|
import im.vector.matrix.android.api.NoOpMatrixCallback
|
||||||
@@ -33,7 +32,6 @@ import im.vector.matrix.android.api.failure.Failure
|
|||||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
||||||
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
||||||
import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener
|
import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener
|
||||||
@@ -53,11 +51,7 @@ import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFa
|
|||||||
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
|
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
|
||||||
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService
|
import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.*
|
||||||
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent
|
import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent
|
||||||
@@ -65,19 +59,11 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
|||||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
import im.vector.matrix.android.internal.crypto.model.toRest
|
|
||||||
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.*
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
|
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
|
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
|
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
|
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
|
|
||||||
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
|
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.repository.CurrentStateEventDataSource
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
|
||||||
import im.vector.matrix.android.internal.database.query.whereType
|
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
@@ -89,13 +75,8 @@ import im.vector.matrix.android.internal.task.TaskThread
|
|||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.fetchCopied
|
import im.vector.matrix.sqldelight.session.SessionDatabase
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.cancelChildren
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
@@ -159,7 +140,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
private val setDeviceNameTask: SetDeviceNameTask,
|
private val setDeviceNameTask: SetDeviceNameTask,
|
||||||
private val uploadKeysTask: UploadKeysTask,
|
private val uploadKeysTask: UploadKeysTask,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val monarchy: Monarchy,
|
private val sessionDatabase: SessionDatabase,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val cryptoCoroutineScope: CoroutineScope
|
private val cryptoCoroutineScope: CoroutineScope
|
||||||
@@ -178,16 +159,16 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
|
|
||||||
fun onStateEvent(roomId: String, event: Event) {
|
fun onStateEvent(roomId: String, event: Event) {
|
||||||
when {
|
when {
|
||||||
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onLiveEvent(roomId: String, event: Event) {
|
fun onLiveEvent(roomId: String, event: Event) {
|
||||||
when {
|
when {
|
||||||
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||||
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -412,7 +393,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||||
return cryptoStore.getUserDevices(userId)?.map { it.value }?.sortedBy { it.deviceId } ?: emptyList()
|
return cryptoStore.getUserDevices(userId)?.map { it.value } ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
|
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
|
||||||
@@ -508,7 +489,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
|
|
||||||
val alg: IMXEncrypting = when (algorithm) {
|
val alg: IMXEncrypting = when (algorithm) {
|
||||||
MXCRYPTO_ALGORITHM_MEGOLM -> megolmEncryptionFactory.create(roomId)
|
MXCRYPTO_ALGORITHM_MEGOLM -> megolmEncryptionFactory.create(roomId)
|
||||||
else -> olmEncryptionFactory.create(roomId)
|
else -> olmEncryptionFactory.create(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized(roomEncryptors) {
|
synchronized(roomEncryptors) {
|
||||||
@@ -542,12 +523,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM
|
* @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
*/
|
*/
|
||||||
override fun isRoomEncrypted(roomId: String): Boolean {
|
override fun isRoomEncrypted(roomId: String): Boolean {
|
||||||
val encryptionEvent = monarchy.fetchCopied { realm ->
|
return sessionDatabase.eventQueries
|
||||||
EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
|
.findWithContent(roomId = roomId, content = "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
|
||||||
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
|
.executeAsList()
|
||||||
.findFirst()
|
.firstOrNull() != null
|
||||||
}
|
|
||||||
return encryptionEvent != null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -706,17 +685,17 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
onRoomKeyEvent(event)
|
onRoomKeyEvent(event)
|
||||||
}
|
}
|
||||||
EventType.REQUEST_SECRET,
|
EventType.REQUEST_SECRET,
|
||||||
EventType.ROOM_KEY_REQUEST -> {
|
EventType.ROOM_KEY_REQUEST -> {
|
||||||
// save audit trail
|
// save audit trail
|
||||||
cryptoStore.saveGossipingEvent(event)
|
cryptoStore.saveGossipingEvent(event)
|
||||||
// Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete)
|
// Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete)
|
||||||
incomingGossipingRequestManager.onGossipingRequestEvent(event)
|
incomingGossipingRequestManager.onGossipingRequestEvent(event)
|
||||||
}
|
}
|
||||||
EventType.SEND_SECRET -> {
|
EventType.SEND_SECRET -> {
|
||||||
cryptoStore.saveGossipingEvent(event)
|
cryptoStore.saveGossipingEvent(event)
|
||||||
onSecretSendReceived(event)
|
onSecretSendReceived(event)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -767,30 +746,19 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!handleSDKLevelGossip(existingRequest.secretName, secretContent.secretValue)) {
|
when (existingRequest.secretName) {
|
||||||
// TODO Ask to application layer?
|
|
||||||
Timber.v("## onSecretSend() : secret not handled by SDK")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if handled by SDK, otherwise should be sent to application layer
|
|
||||||
*/
|
|
||||||
private fun handleSDKLevelGossip(secretName: String?, secretValue: String): Boolean {
|
|
||||||
return when (secretName) {
|
|
||||||
SELF_SIGNING_KEY_SSSS_NAME -> {
|
SELF_SIGNING_KEY_SSSS_NAME -> {
|
||||||
crossSigningService.onSecretSSKGossip(secretValue)
|
crossSigningService.onSecretSSKGossip(secretContent.secretValue)
|
||||||
true
|
return
|
||||||
}
|
}
|
||||||
USER_SIGNING_KEY_SSSS_NAME -> {
|
USER_SIGNING_KEY_SSSS_NAME -> {
|
||||||
crossSigningService.onSecretUSKGossip(secretValue)
|
crossSigningService.onSecretUSKGossip(secretContent.secretValue)
|
||||||
true
|
return
|
||||||
}
|
}
|
||||||
KEYBACKUP_SECRET_SSSS_NAME -> {
|
else -> {
|
||||||
keysBackupService.onSecretKeyGossip(secretValue)
|
// Ask to application layer?
|
||||||
true
|
Timber.v("## onSecretSend() : secret not handled by SDK")
|
||||||
}
|
}
|
||||||
else -> false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -804,29 +772,24 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
val params = LoadRoomMembersTask.Params(roomId)
|
val params = LoadRoomMembersTask.Params(roomId)
|
||||||
try {
|
try {
|
||||||
loadRoomMembersTask.execute(params)
|
loadRoomMembersTask.execute(params)
|
||||||
} catch (throwable: Throwable) {
|
|
||||||
Timber.e(throwable, "## onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
|
|
||||||
} finally {
|
|
||||||
val userIds = getRoomUserIds(roomId)
|
val userIds = getRoomUserIds(roomId)
|
||||||
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
|
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.e(throwable, "## onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRoomUserIds(roomId: String): List<String> {
|
private fun getRoomUserIds(roomId: String): List<String> {
|
||||||
var userIds: List<String> = emptyList()
|
// Check whether the event content must be encrypted for the invited members.
|
||||||
monarchy.doWithRealm { realm ->
|
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser()
|
||||||
// Check whether the event content must be encrypted for the invited members.
|
&& shouldEncryptForInvitedMembers(roomId)
|
||||||
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser()
|
|
||||||
&& shouldEncryptForInvitedMembers(roomId)
|
|
||||||
|
|
||||||
userIds = if (encryptForInvitedMembers) {
|
return if (encryptForInvitedMembers) {
|
||||||
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
|
RoomMemberHelper(sessionDatabase, roomId).getActiveRoomMemberIds()
|
||||||
} else {
|
} else {
|
||||||
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
|
RoomMemberHelper(sessionDatabase, roomId).getJoinedRoomMemberIds()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return userIds
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -15,23 +15,18 @@
|
|||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.crosssigning
|
package im.vector.matrix.android.internal.crypto.crosssigning
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
|
import im.vector.matrix.android.internal.database.mapper.map
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
|
||||||
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
||||||
import io.realm.Realm
|
import im.vector.matrix.sqldelight.session.SessionDatabase
|
||||||
import io.realm.RealmConfiguration
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class ShieldTrustUpdater @Inject constructor(
|
internal class ShieldTrustUpdater @Inject constructor(
|
||||||
@@ -39,35 +34,23 @@ internal class ShieldTrustUpdater @Inject constructor(
|
|||||||
private val computeTrustTask: ComputeTrustTask,
|
private val computeTrustTask: ComputeTrustTask,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
@SessionDatabase private val sessionRealmConfiguration: RealmConfiguration,
|
private val sessionDatabase: SessionDatabase) {
|
||||||
private val roomSummaryUpdater: RoomSummaryUpdater
|
|
||||||
) {
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD")
|
private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD")
|
||||||
}
|
}
|
||||||
|
|
||||||
private val backgroundSessionRealm = AtomicReference<Realm>()
|
|
||||||
|
|
||||||
private val isStarted = AtomicBoolean()
|
private val isStarted = AtomicBoolean()
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
if (isStarted.compareAndSet(false, true)) {
|
if (isStarted.compareAndSet(false, true)) {
|
||||||
eventBus.register(this)
|
eventBus.register(this)
|
||||||
BACKGROUND_HANDLER.post {
|
|
||||||
backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
if (isStarted.compareAndSet(true, false)) {
|
if (isStarted.compareAndSet(true, false)) {
|
||||||
eventBus.unregister(this)
|
eventBus.unregister(this)
|
||||||
BACKGROUND_HANDLER.post {
|
|
||||||
backgroundSessionRealm.getAndSet(null).also {
|
|
||||||
it?.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,16 +59,7 @@ internal class ShieldTrustUpdater @Inject constructor(
|
|||||||
if (!isStarted.get()) {
|
if (!isStarted.get()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
taskExecutor.executorScope.launch(coroutineDispatchers.crypto) {
|
taskExecutor.executorScope.updateTrustLevel(update.roomId, update.userIds)
|
||||||
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds))
|
|
||||||
// We need to send that back to session base
|
|
||||||
|
|
||||||
BACKGROUND_HANDLER.post {
|
|
||||||
backgroundSessionRealm.get()?.executeTransaction { realm ->
|
|
||||||
roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
@@ -93,47 +67,31 @@ internal class ShieldTrustUpdater @Inject constructor(
|
|||||||
if (!isStarted.get()) {
|
if (!isStarted.get()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
onCryptoDevicesChange(update.userIds)
|
onCryptoDevicesChange(update.userIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onCryptoDevicesChange(users: List<String>) {
|
private fun onCryptoDevicesChange(users: List<String>) {
|
||||||
BACKGROUND_HANDLER.post {
|
BACKGROUND_HANDLER.post {
|
||||||
val impactedRoomsId = backgroundSessionRealm.get()?.where(RoomMemberSummaryEntity::class.java)
|
val impactedRoomsId = sessionDatabase.roomMemberSummaryQueries.getAllRoomIdsFromUsers(users).executeAsList()
|
||||||
?.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray())
|
impactedRoomsId.forEach { roomId ->
|
||||||
?.findAll()
|
val allUsersFromRoom = sessionDatabase.roomMemberSummaryQueries.getAllUserIdFromRoom(
|
||||||
?.map { it.roomId }
|
memberships = Membership.all().map(),
|
||||||
?.distinct()
|
excludedIds = emptyList(),
|
||||||
|
roomId = roomId
|
||||||
val map = HashMap<String, List<String>>()
|
).executeAsList()
|
||||||
impactedRoomsId?.forEach { roomId ->
|
taskExecutor.executorScope.updateTrustLevel(roomId, allUsersFromRoom)
|
||||||
backgroundSessionRealm.get()?.let { realm ->
|
|
||||||
RoomMemberSummaryEntity.where(realm, roomId)
|
|
||||||
.findAll()
|
|
||||||
.let { results ->
|
|
||||||
map[roomId] = results.map { it.userId }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
map.forEach { entry ->
|
private fun CoroutineScope.updateTrustLevel(roomId: String, userIds: List<String>) {
|
||||||
val roomId = entry.key
|
launch(coroutineDispatchers.dbTransaction) {
|
||||||
val userList = entry.value
|
try {
|
||||||
taskExecutor.executorScope.launch {
|
// Can throw if the crypto database has been closed in between, in this case log and ignore?
|
||||||
withContext(coroutineDispatchers.crypto) {
|
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userIds))
|
||||||
try {
|
sessionDatabase.roomSummaryQueries.setRoomEncryptionTrustLevel(updatedTrust.name, roomId)
|
||||||
// Can throw if the crypto database has been closed in between, in this case log and ignore?
|
} catch (failure: Throwable) {
|
||||||
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList))
|
Timber.e(failure)
|
||||||
BACKGROUND_HANDLER.post {
|
|
||||||
backgroundSessionRealm.get()?.executeTransaction { realm ->
|
|
||||||
roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
Timber.e(failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -69,7 +69,6 @@ import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
|
|||||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.SavedKeyBackupKeyInfo
|
import im.vector.matrix.android.internal.crypto.store.SavedKeyBackupKeyInfo
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
|
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||||
@@ -1154,11 +1153,11 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
* Update the DB with data fetch from the server
|
* Update the DB with data fetch from the server
|
||||||
*/
|
*/
|
||||||
private fun onServerDataRetrieved(count: Int?, hash: String?) {
|
private fun onServerDataRetrieved(count: Int?, hash: String?) {
|
||||||
cryptoStore.setKeysBackupData(KeysBackupDataEntity()
|
cryptoStore.setKeysBackupData(
|
||||||
.apply {
|
im.vector.matrix.android.internal.crypto.model.rest.KeysBackupData(
|
||||||
backupLastServerNumberOfKeys = count
|
backupLastServerNumberOfKeys = count,
|
||||||
backupLastServerHash = hash
|
backupLastServerHash = hash
|
||||||
}
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
|
internal data class KeysBackupData(val backupLastServerHash: String?, val backupLastServerNumberOfKeys: Int?)
|
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent class representing an room key action request
|
||||||
|
* Note: this class cannot be abstract because of [org.matrix.androidsdk.core.JsonUtils.toRoomKeyShare]
|
||||||
|
*/
|
||||||
|
internal open class RoomKeyShare : SendToDeviceObject {
|
||||||
|
|
||||||
|
var action: String? = null
|
||||||
|
|
||||||
|
@Json(name = "requesting_device_id")
|
||||||
|
var requestingDeviceId: String? = null
|
||||||
|
|
||||||
|
@Json(name = "request_id")
|
||||||
|
var requestId: String? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ACTION_SHARE_REQUEST = "request"
|
||||||
|
const val ACTION_SHARE_CANCELLATION = "request_cancellation"
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.crypto.model.rest
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing an room key request cancellation content
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal class RoomKeyShareCancellation : RoomKeyShare() {
|
||||||
|
init {
|
||||||
|
action = ACTION_SHARE_CANCELLATION
|
||||||
|
}
|
||||||
|
}
|
@@ -32,8 +32,8 @@ import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
|||||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.KeysBackupData
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
|
|
||||||
import org.matrix.olm.OlmAccount
|
import org.matrix.olm.OlmAccount
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,14 +104,14 @@ internal interface IMXCryptoStore {
|
|||||||
/**
|
/**
|
||||||
* Get the current keys backup local data
|
* Get the current keys backup local data
|
||||||
*/
|
*/
|
||||||
fun getKeysBackupData(): KeysBackupDataEntity?
|
fun getKeysBackupData(): KeysBackupData?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the keys backup local data
|
* Set the keys backup local data
|
||||||
*
|
*
|
||||||
* @param keysBackupData the keys backup local data, or null to erase data
|
* @param keysBackupData the keys backup local data, or null to erase data
|
||||||
*/
|
*/
|
||||||
fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?)
|
fun setKeysBackupData(keysBackupData: KeysBackupData?)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the devices statuses map (userId -> tracking status)
|
* @return the devices statuses map (userId -> tracking status)
|
||||||
|
@@ -41,6 +41,7 @@ import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
|||||||
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||||
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.KeysBackupData
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
import im.vector.matrix.android.internal.crypto.model.toEntity
|
import im.vector.matrix.android.internal.crypto.model.toEntity
|
||||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
@@ -74,8 +75,8 @@ import im.vector.matrix.android.internal.crypto.store.db.query.get
|
|||||||
import im.vector.matrix.android.internal.crypto.store.db.query.getById
|
import im.vector.matrix.android.internal.crypto.store.db.query.getById
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate
|
import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate
|
||||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||||
import im.vector.matrix.android.internal.di.CryptoDatabase
|
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
import im.vector.matrix.android.internal.di.RealmCryptoDatabase
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
@@ -90,7 +91,7 @@ import kotlin.collections.set
|
|||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class RealmCryptoStore @Inject constructor(
|
internal class RealmCryptoStore @Inject constructor(
|
||||||
@CryptoDatabase private val realmConfiguration: RealmConfiguration,
|
@RealmCryptoDatabase private val realmConfiguration: RealmConfiguration,
|
||||||
private val credentials: Credentials) : IMXCryptoStore {
|
private val credentials: Credentials) : IMXCryptoStore {
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
@@ -705,13 +706,18 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getKeysBackupData(): KeysBackupDataEntity? {
|
override fun getKeysBackupData(): KeysBackupData? {
|
||||||
return doRealmQueryAndCopy(realmConfiguration) {
|
return doRealmQueryAndCopy(realmConfiguration) {
|
||||||
it.where<KeysBackupDataEntity>().findFirst()
|
it.where<KeysBackupDataEntity>().findFirst()
|
||||||
|
}?.let { entity ->
|
||||||
|
return KeysBackupData(
|
||||||
|
backupLastServerHash = entity.backupLastServerHash,
|
||||||
|
backupLastServerNumberOfKeys = entity.backupLastServerNumberOfKeys
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?) {
|
override fun setKeysBackupData(keysBackupData: KeysBackupData?) {
|
||||||
doRealmTransaction(realmConfiguration) {
|
doRealmTransaction(realmConfiguration) {
|
||||||
if (keysBackupData == null) {
|
if (keysBackupData == null) {
|
||||||
// Clear the table
|
// Clear the table
|
||||||
@@ -720,7 +726,10 @@ internal class RealmCryptoStore @Inject constructor(
|
|||||||
.deleteAllFromRealm()
|
.deleteAllFromRealm()
|
||||||
} else {
|
} else {
|
||||||
// Only one object
|
// Only one object
|
||||||
it.copyToRealmOrUpdate(keysBackupData)
|
it.copyToRealmOrUpdate(it.createObject(KeysBackupDataEntity::class.java).apply {
|
||||||
|
backupLastServerHash = keysBackupData.backupLastServerHash
|
||||||
|
backupLastServerNumberOfKeys = keysBackupData.backupLastServerNumberOfKeys
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.crypto.store.db
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.squareup.moshi.Types
|
||||||
|
import im.vector.matrix.android.api.extensions.tryThis
|
||||||
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
|
import im.vector.matrix.android.internal.crypto.GossipRequestType
|
||||||
|
import im.vector.matrix.android.internal.crypto.GossipingRequestState
|
||||||
|
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||||
|
import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest
|
||||||
|
import im.vector.matrix.android.internal.crypto.IncomingShareRequestCommon
|
||||||
|
import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequest
|
||||||
|
import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState
|
||||||
|
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
||||||
|
import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
import im.vector.matrix.android.internal.di.SerializeNulls
|
||||||
|
import im.vector.matrix.sqldelight.crypto.CrossSigningInfoEntity
|
||||||
|
import im.vector.matrix.sqldelight.crypto.DeviceInfoEntity
|
||||||
|
import im.vector.matrix.sqldelight.crypto.GetByUserId
|
||||||
|
import im.vector.matrix.sqldelight.crypto.IncomingGossipingRequestEntity
|
||||||
|
import im.vector.matrix.sqldelight.crypto.OutgoingGossipingRequestEntity
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
object SqliteCryptoMapper {
|
||||||
|
|
||||||
|
private val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
|
||||||
|
private val listMigrationAdapter = moshi.adapter<List<String>>(Types.newParameterizedType(
|
||||||
|
List::class.java,
|
||||||
|
String::class.java,
|
||||||
|
Any::class.java
|
||||||
|
))
|
||||||
|
private val mapMigrationAdapter = moshi.adapter<JsonDict>(Types.newParameterizedType(
|
||||||
|
Map::class.java,
|
||||||
|
String::class.java,
|
||||||
|
Any::class.java
|
||||||
|
))
|
||||||
|
private val mapOfStringMigrationAdapter = moshi.adapter<Map<String, Map<String, String>>>(Types.newParameterizedType(
|
||||||
|
Map::class.java,
|
||||||
|
String::class.java,
|
||||||
|
Any::class.java
|
||||||
|
))
|
||||||
|
private val recipientsDataMapper: JsonAdapter<Map<String, List<String>>> =
|
||||||
|
MoshiProvider
|
||||||
|
.providesMoshi()
|
||||||
|
.adapter<Map<String, List<String>>>(
|
||||||
|
Types.newParameterizedType(Map::class.java, String::class.java, List::class.java)
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun mapToEntity(deviceInfo: CryptoDeviceInfo): DeviceInfoEntity {
|
||||||
|
return DeviceInfoEntity.Impl(
|
||||||
|
user_id = deviceInfo.userId,
|
||||||
|
device_id = deviceInfo.deviceId,
|
||||||
|
identity_key = deviceInfo.identityKey(),
|
||||||
|
algorithm_list_json = listMigrationAdapter.toJson(deviceInfo.algorithms),
|
||||||
|
keys_map_json = mapMigrationAdapter.toJson(deviceInfo.keys),
|
||||||
|
signature_map_json = mapMigrationAdapter.toJson(deviceInfo.signatures),
|
||||||
|
is_blocked = deviceInfo.isBlocked,
|
||||||
|
locally_verified = deviceInfo.trustLevel?.locallyVerified == true,
|
||||||
|
cross_signed_verified = deviceInfo.trustLevel?.crossSigningVerified == true,
|
||||||
|
unsigned_map_json = mapMigrationAdapter.toJson(deviceInfo.unsigned)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun mapToModel(deviceInfoEntity: DeviceInfoEntity): CryptoDeviceInfo {
|
||||||
|
return CryptoDeviceInfo(
|
||||||
|
deviceId = deviceInfoEntity.device_id,
|
||||||
|
userId = deviceInfoEntity.user_id,
|
||||||
|
algorithms = deviceInfoEntity.algorithm_list_json?.let {
|
||||||
|
try {
|
||||||
|
listMigrationAdapter.fromJson(it)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keys = deviceInfoEntity.keys_map_json?.let {
|
||||||
|
try {
|
||||||
|
moshi.adapter<Map<String, String>>(Types.newParameterizedType(
|
||||||
|
Map::class.java,
|
||||||
|
String::class.java,
|
||||||
|
Any::class.java
|
||||||
|
)).fromJson(it)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
signatures = deviceInfoEntity.signature_map_json?.let {
|
||||||
|
try {
|
||||||
|
mapOfStringMigrationAdapter.fromJson(it)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unsigned = deviceInfoEntity.unsigned_map_json?.let {
|
||||||
|
try {
|
||||||
|
mapMigrationAdapter.fromJson(it)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trustLevel = DeviceTrustLevel(deviceInfoEntity.cross_signed_verified, deviceInfoEntity.locally_verified),
|
||||||
|
isBlocked = deviceInfoEntity.is_blocked
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun mapToModel(crossSigningInfoEntity: GetByUserId): CryptoCrossSigningKey {
|
||||||
|
val pubKey = crossSigningInfoEntity.public_key_base64 ?: ""
|
||||||
|
return CryptoCrossSigningKey(
|
||||||
|
userId = crossSigningInfoEntity.user_id,
|
||||||
|
signatures = crossSigningInfoEntity.signatures?.let { deserializeSignaturesFromSqlite(it) },
|
||||||
|
trustLevel = DeviceTrustLevel(
|
||||||
|
crossSigningInfoEntity.cross_signed_verified,
|
||||||
|
crossSigningInfoEntity.locally_verified
|
||||||
|
),
|
||||||
|
keys = mapOf("ed25519:$pubKey" to pubKey),
|
||||||
|
usages = crossSigningInfoEntity.usages
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun mapToEntity(cryptoCrossSigningKey: CryptoCrossSigningKey): CrossSigningInfoEntity {
|
||||||
|
return CrossSigningInfoEntity.Impl(
|
||||||
|
user_id = cryptoCrossSigningKey.userId,
|
||||||
|
signatures = serializeSignaturesForSqlite(cryptoCrossSigningKey.signatures),
|
||||||
|
public_key_base64 = cryptoCrossSigningKey.unpaddedBase64PublicKey,
|
||||||
|
usages = cryptoCrossSigningKey.usages ?: emptyList(),
|
||||||
|
locally_verified = false,
|
||||||
|
cross_signed_verified = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun serializeSignaturesForSqlite(signatures: Map<String, Map<String, String>>?): String {
|
||||||
|
return mapMigrationAdapter.toJson(signatures)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deserializeSignaturesFromSqlite(signatures: String):Map<String, Map<String, String>>? {
|
||||||
|
return try {
|
||||||
|
mapOfStringMigrationAdapter.fromJson(signatures)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e(failure)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toOutgoingGossipingRequest(entity: OutgoingGossipingRequestEntity): OutgoingGossipingRequest {
|
||||||
|
return when (entity.type) {
|
||||||
|
GossipRequestType.KEY.name -> {
|
||||||
|
OutgoingRoomKeyRequest(
|
||||||
|
requestBody = getRequestedKeyInfo(entity.type, entity.requested_info),
|
||||||
|
recipients = getRecipientsMap(entity) ?: emptyMap(),
|
||||||
|
requestId = entity.request_id,
|
||||||
|
state = tryThis { OutgoingGossipingRequestState.valueOf(entity.requested_info!!) }
|
||||||
|
?: OutgoingGossipingRequestState.UNSENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GossipRequestType.SECRET.name -> {
|
||||||
|
OutgoingSecretRequest(
|
||||||
|
secretName = getRequestedSecretName(entity.type, entity.requested_info),
|
||||||
|
recipients = getRecipientsMap(entity) ?: emptyMap(),
|
||||||
|
requestId = entity.request_id,
|
||||||
|
state = tryThis { OutgoingGossipingRequestState.valueOf(entity.requested_info!!) }
|
||||||
|
?: OutgoingGossipingRequestState.UNSENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> OutgoingRoomKeyRequest(
|
||||||
|
requestBody = getRequestedKeyInfo(entity.type, entity.requested_info),
|
||||||
|
recipients = getRecipientsMap(entity) ?: emptyMap(),
|
||||||
|
requestId = entity.request_id,
|
||||||
|
state = OutgoingGossipingRequestState.UNSENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toIncomingGossipingRequest(entity: IncomingGossipingRequestEntity): IncomingShareRequestCommon {
|
||||||
|
return when (GossipRequestType.valueOf(entity.type)) {
|
||||||
|
GossipRequestType.KEY -> {
|
||||||
|
IncomingRoomKeyRequest(
|
||||||
|
requestBody = getRequestedKeyInfo(entity.type, entity.requested_info_str),
|
||||||
|
deviceId = entity.other_device_id,
|
||||||
|
userId = entity.other_user_id,
|
||||||
|
requestId = entity.request_id,
|
||||||
|
state = entity.request_state?.let { GossipingRequestState.valueOf(it) }
|
||||||
|
?: GossipingRequestState.NONE,
|
||||||
|
localCreationTimestamp = entity.local_creation_timestamp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GossipRequestType.SECRET -> {
|
||||||
|
IncomingSecretShareRequest(
|
||||||
|
secretName = getRequestedSecretName(entity.type, entity.requested_info_str),
|
||||||
|
deviceId = entity.other_device_id,
|
||||||
|
userId = entity.other_user_id,
|
||||||
|
requestId = entity.request_id,
|
||||||
|
localCreationTimestamp = entity.local_creation_timestamp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRecipientsMap(entity: OutgoingGossipingRequestEntity): Map<String, List<String>>? {
|
||||||
|
return entity.recipients_data?.let { recipientsDataMapper.fromJson(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRecipientsData(recipients: Map<String, List<String>>): String? {
|
||||||
|
return recipientsDataMapper.toJson(recipients)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRequestedKeyInfo(type: String, requestedInfoStr: String?): RoomKeyRequestBody? {
|
||||||
|
return if (type == GossipRequestType.KEY.name) {
|
||||||
|
RoomKeyRequestBody.fromJson(requestedInfoStr)
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRequestedSecretName(type: String, requestedInfoStr: String?): String? {
|
||||||
|
return if (type == GossipRequestType.SECRET.name) {
|
||||||
|
requestedInfoStr
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
package im.vector.matrix.android.internal.crypto.store.db
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import im.vector.matrix.android.internal.util.CompatUtil
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.ObjectInputStream
|
||||||
|
import java.io.ObjectOutputStream
|
||||||
|
import java.util.zip.GZIPInputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize any Serializable object, zip it and convert to Base64 String
|
||||||
|
*/
|
||||||
|
fun serializeForSqlite(o: Any?): String? {
|
||||||
|
if (o == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val baos = ByteArrayOutputStream()
|
||||||
|
val gzis = CompatUtil.createGzipOutputStream(baos)
|
||||||
|
val out = ObjectOutputStream(gzis)
|
||||||
|
|
||||||
|
out.writeObject(o)
|
||||||
|
out.close()
|
||||||
|
|
||||||
|
return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do the opposite of serializeForSqlite.
|
||||||
|
*/
|
||||||
|
fun <T> deserializeFromSqlite(string: String?): T? {
|
||||||
|
if (string == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val decodedB64 = Base64.decode(string.toByteArray(), Base64.DEFAULT)
|
||||||
|
|
||||||
|
val bais = ByteArrayInputStream(decodedB64)
|
||||||
|
val gzis = GZIPInputStream(bais)
|
||||||
|
val ois = ObjectInputStream(gzis)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val result = ois.readObject() as T
|
||||||
|
|
||||||
|
ois.close()
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
@@ -0,0 +1,56 @@
|
|||||||
|
// /*
|
||||||
|
// * Copyright 2018 New Vector Ltd
|
||||||
|
// *
|
||||||
|
// * 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.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
// package im.vector.matrix.android.internal.crypto.store.db.model
|
||||||
|
//
|
||||||
|
// import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||||
|
// import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
// import io.realm.RealmObject
|
||||||
|
//
|
||||||
|
// internal open class IncomingRoomKeyRequestEntity(
|
||||||
|
// var requestId: String? = null,
|
||||||
|
// var userId: String? = null,
|
||||||
|
// var deviceId: String? = null,
|
||||||
|
// // RoomKeyRequestBody fields
|
||||||
|
// var requestBodyAlgorithm: String? = null,
|
||||||
|
// var requestBodyRoomId: String? = null,
|
||||||
|
// var requestBodySenderKey: String? = null,
|
||||||
|
// var requestBodySessionId: String? = null
|
||||||
|
// ) : RealmObject() {
|
||||||
|
//
|
||||||
|
// fun toIncomingRoomKeyRequest(): IncomingRoomKeyRequest {
|
||||||
|
// return IncomingRoomKeyRequest(
|
||||||
|
// requestId = requestId,
|
||||||
|
// userId = userId,
|
||||||
|
// deviceId = deviceId,
|
||||||
|
// requestBody = RoomKeyRequestBody(
|
||||||
|
// algorithm = requestBodyAlgorithm,
|
||||||
|
// roomId = requestBodyRoomId,
|
||||||
|
// senderKey = requestBodySenderKey,
|
||||||
|
// sessionId = requestBodySessionId
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun putRequestBody(requestBody: RoomKeyRequestBody?) {
|
||||||
|
// requestBody?.let {
|
||||||
|
// requestBodyAlgorithm = it.algorithm
|
||||||
|
// requestBodyRoomId = it.roomId
|
||||||
|
// requestBodySenderKey = it.senderKey
|
||||||
|
// requestBodySessionId = it.sessionId
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
@@ -0,0 +1,37 @@
|
|||||||
|
// /*
|
||||||
|
// * Copyright (c) 2020 New Vector Ltd
|
||||||
|
// *
|
||||||
|
// * 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.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
// package im.vector.matrix.android.internal.crypto.store.db.model
|
||||||
|
//
|
||||||
|
// import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest
|
||||||
|
// import io.realm.RealmObject
|
||||||
|
//
|
||||||
|
// internal open class IncomingSecretRequestEntity(
|
||||||
|
// var requestId: String? = null,
|
||||||
|
// var userId: String? = null,
|
||||||
|
// var deviceId: String? = null,
|
||||||
|
// var secretName: String? = null
|
||||||
|
// ) : RealmObject() {
|
||||||
|
//
|
||||||
|
// fun toIncomingSecretShareRequest(): IncomingSecretShareRequest {
|
||||||
|
// return IncomingSecretShareRequest(
|
||||||
|
// requestId = requestId,
|
||||||
|
// userId = userId,
|
||||||
|
// deviceId = deviceId,
|
||||||
|
// secretName = secretName
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
@@ -0,0 +1,77 @@
|
|||||||
|
// /*
|
||||||
|
// * Copyright 2018 New Vector Ltd
|
||||||
|
// *
|
||||||
|
// * 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.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
// package im.vector.matrix.android.internal.crypto.store.db.model
|
||||||
|
//
|
||||||
|
// import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
|
||||||
|
// import im.vector.matrix.android.internal.crypto.ShareRequestState
|
||||||
|
// import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
|
// import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
|
||||||
|
// import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
|
||||||
|
// import io.realm.RealmObject
|
||||||
|
// import io.realm.annotations.PrimaryKey
|
||||||
|
//
|
||||||
|
// internal open class OutgoingRoomKeyRequestEntity(
|
||||||
|
// @PrimaryKey var requestId: String? = null,
|
||||||
|
// var cancellationTxnId: String? = null,
|
||||||
|
// // Serialized Json
|
||||||
|
// var recipientsData: String? = null,
|
||||||
|
// // RoomKeyRequestBody fields
|
||||||
|
// var requestBodyAlgorithm: String? = null,
|
||||||
|
// var requestBodyRoomId: String? = null,
|
||||||
|
// var requestBodySenderKey: String? = null,
|
||||||
|
// var requestBodySessionId: String? = null,
|
||||||
|
// // State
|
||||||
|
// var state: Int = 0
|
||||||
|
// ) : RealmObject() {
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Convert to OutgoingRoomKeyRequest
|
||||||
|
// */
|
||||||
|
// fun toOutgoingRoomKeyRequest(): OutgoingRoomKeyRequest {
|
||||||
|
// val cancellationTxnId = this.cancellationTxnId
|
||||||
|
// return OutgoingRoomKeyRequest(
|
||||||
|
// RoomKeyRequestBody(
|
||||||
|
// algorithm = requestBodyAlgorithm,
|
||||||
|
// roomId = requestBodyRoomId,
|
||||||
|
// senderKey = requestBodySenderKey,
|
||||||
|
// sessionId = requestBodySessionId
|
||||||
|
// ),
|
||||||
|
// getRecipients()!!,
|
||||||
|
// requestId!!,
|
||||||
|
// ShareRequestState.from(state)
|
||||||
|
// ).apply {
|
||||||
|
// this.cancellationTxnId = cancellationTxnId
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun getRecipients(): List<Map<String, String>>? {
|
||||||
|
// return deserializeFromRealm(recipientsData)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun putRecipients(recipients: List<Map<String, String>>?) {
|
||||||
|
// recipientsData = serializeForRealm(recipients)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun putRequestBody(requestBody: RoomKeyRequestBody?) {
|
||||||
|
// requestBody?.let {
|
||||||
|
// requestBodyAlgorithm = it.algorithm
|
||||||
|
// requestBodyRoomId = it.roomId
|
||||||
|
// requestBodySenderKey = it.senderKey
|
||||||
|
// requestBodySessionId = it.sessionId
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
@@ -0,0 +1,63 @@
|
|||||||
|
// /*
|
||||||
|
// * Copyright (c) 2020 New Vector Ltd
|
||||||
|
// *
|
||||||
|
// * 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.
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
// package im.vector.matrix.android.internal.crypto.store.db.model
|
||||||
|
//
|
||||||
|
// import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest
|
||||||
|
// import im.vector.matrix.android.internal.crypto.ShareRequestState
|
||||||
|
// import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm
|
||||||
|
// import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm
|
||||||
|
// import io.realm.RealmObject
|
||||||
|
// import io.realm.annotations.PrimaryKey
|
||||||
|
//
|
||||||
|
// internal open class OutgoingSecretRequestEntity(
|
||||||
|
// @PrimaryKey var requestId: String? = null,
|
||||||
|
// var cancellationTxnId: String? = null,
|
||||||
|
// // Serialized Json
|
||||||
|
// var recipientsData: String? = null,
|
||||||
|
// // RoomKeyRequestBody fields
|
||||||
|
// var secretName: String? = null,
|
||||||
|
// // State
|
||||||
|
// var state: Int = 0
|
||||||
|
// ) : RealmObject() {
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Convert to OutgoingRoomKeyRequest
|
||||||
|
// */
|
||||||
|
// fun toOutgoingSecretRequest(): OutgoingSecretRequest {
|
||||||
|
// val cancellationTxnId = this.cancellationTxnId
|
||||||
|
// return OutgoingSecretRequest(
|
||||||
|
// secretName,
|
||||||
|
// getRecipients() ?: emptyList(),
|
||||||
|
// requestId!!,
|
||||||
|
// ShareRequestState.from(state)
|
||||||
|
// ).apply {
|
||||||
|
// this.cancellationTxnId = cancellationTxnId
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// private fun getRecipients(): List<Map<String, String>>? {
|
||||||
|
// return try {
|
||||||
|
// deserializeFromRealm(recipientsData)
|
||||||
|
// } catch (failure: Throwable) {
|
||||||
|
// null
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun putRecipients(recipients: List<Map<String, String>>?) {
|
||||||
|
// recipientsData = serializeForRealm(recipients)
|
||||||
|
// }
|
||||||
|
// }
|
@@ -19,35 +19,33 @@ package im.vector.matrix.android.internal.crypto.tasks
|
|||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
import im.vector.matrix.android.api.session.crypto.verification.VerificationService
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.*
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageRelationContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationReadyContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
|
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent
|
|
||||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
|
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.di.DeviceId
|
import im.vector.matrix.android.internal.di.DeviceId
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import im.vector.matrix.sqldelight.session.EventInsertNotification
|
||||||
|
import im.vector.matrix.sqldelight.session.SessionDatabase
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.ArrayList
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface RoomVerificationUpdateTask : Task<RoomVerificationUpdateTask.Params, Unit> {
|
internal interface RoomVerificationUpdateTask : Task<RoomVerificationUpdateTask.Params, Unit> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val events: List<Event>,
|
val eventInsertNotifications: List<EventInsertNotification>
|
||||||
val verificationService: DefaultVerificationService,
|
|
||||||
val cryptoService: CryptoService
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
@DeviceId private val deviceId: String?,
|
@DeviceId private val deviceId: String?,
|
||||||
|
private val sessionDatabase: SessionDatabase,
|
||||||
|
private val verificationService: DefaultVerificationService,
|
||||||
private val cryptoService: CryptoService) : RoomVerificationUpdateTask {
|
private val cryptoService: CryptoService) : RoomVerificationUpdateTask {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -57,8 +55,15 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
|||||||
|
|
||||||
override suspend fun execute(params: RoomVerificationUpdateTask.Params) {
|
override suspend fun execute(params: RoomVerificationUpdateTask.Params) {
|
||||||
// TODO ignore initial sync or back pagination?
|
// TODO ignore initial sync or back pagination?
|
||||||
|
params.eventInsertNotifications.forEach { eventInsertNotification ->
|
||||||
|
val eventId = eventInsertNotification.event_id
|
||||||
|
val isLocalEcho = LocalEcho.isLocalEchoId(eventId)
|
||||||
|
if (isLocalEcho) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
val event = sessionDatabase.eventQueries.select(eventId).executeAsOneOrNull()?.asDomain()
|
||||||
|
?: return@forEach
|
||||||
|
|
||||||
params.events.forEach { event ->
|
|
||||||
Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}")
|
Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}")
|
||||||
|
|
||||||
// If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
|
// If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
|
||||||
@@ -74,7 +79,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
|||||||
// TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
// TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
||||||
// for now decrypt sync
|
// for now decrypt sync
|
||||||
try {
|
try {
|
||||||
val result = cryptoService.decryptEvent(event, "")
|
val result = cryptoService.decryptEvent(event, event.roomId + UUID.randomUUID().toString())
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
event.mxDecryptionResult = OlmDecryptionResult(
|
||||||
payload = result.clearEvent,
|
payload = result.clearEvent,
|
||||||
senderKey = result.senderCurve25519Key,
|
senderKey = result.senderCurve25519Key,
|
||||||
@@ -83,7 +88,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
|||||||
)
|
)
|
||||||
} catch (e: MXCryptoError) {
|
} catch (e: MXCryptoError) {
|
||||||
Timber.e("## SAS Failed to decrypt event: ${event.eventId}")
|
Timber.e("## SAS Failed to decrypt event: ${event.eventId}")
|
||||||
params.verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event)
|
verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}")
|
Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}")
|
||||||
@@ -112,7 +117,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
|||||||
// The verification is started from another device
|
// The verification is started from another device
|
||||||
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
|
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
|
||||||
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
|
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
|
||||||
params.verificationService.onRoomRequestHandledByOtherDevice(event)
|
verificationService.onRoomRequestHandledByOtherDevice(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (EventType.KEY_VERIFICATION_READY == event.getClearType()) {
|
} else if (EventType.KEY_VERIFICATION_READY == event.getClearType()) {
|
||||||
@@ -121,13 +126,13 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
|||||||
// The verification is started from another device
|
// The verification is started from another device
|
||||||
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
|
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
|
||||||
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
|
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
|
||||||
params.verificationService.onRoomRequestHandledByOtherDevice(event)
|
verificationService.onRoomRequestHandledByOtherDevice(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) {
|
} else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) {
|
||||||
relatesToEventId?.let {
|
relatesToEventId?.let {
|
||||||
transactionsHandledByOtherDevice.remove(it)
|
transactionsHandledByOtherDevice.remove(it)
|
||||||
params.verificationService.onRoomRequestHandledByOtherDevice(event)
|
verificationService.onRoomRequestHandledByOtherDevice(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,11 +153,11 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
|||||||
EventType.KEY_VERIFICATION_CANCEL,
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
EventType.KEY_VERIFICATION_READY,
|
EventType.KEY_VERIFICATION_READY,
|
||||||
EventType.KEY_VERIFICATION_DONE -> {
|
EventType.KEY_VERIFICATION_DONE -> {
|
||||||
params.verificationService.onRoomEvent(event)
|
verificationService.onRoomEvent(event)
|
||||||
}
|
}
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) {
|
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) {
|
||||||
params.verificationService.onRoomRequestReceived(event)
|
verificationService.onRoomRequestReceived(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,59 +15,37 @@
|
|||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.crypto.verification
|
package im.vector.matrix.android.internal.crypto.verification
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DefaultRoomVerificationUpdateTask
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultRoomVerificationUpdateTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.RoomVerificationUpdateTask
|
import im.vector.matrix.android.internal.crypto.tasks.RoomVerificationUpdateTask
|
||||||
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
import im.vector.matrix.android.internal.database.SqlLiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.sqldelight.session.EventInsertNotification
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.sqldelight.session.SessionDatabase
|
||||||
import im.vector.matrix.android.internal.database.query.whereTypes
|
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
|
||||||
import io.realm.OrderedCollectionChangeSet
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import io.realm.RealmResults
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class VerificationMessageLiveObserver @Inject constructor(
|
internal class VerificationMessageLiveObserver @Inject constructor(
|
||||||
@SessionDatabase realmConfiguration: RealmConfiguration,
|
sessionDatabase: SessionDatabase,
|
||||||
private val roomVerificationUpdateTask: DefaultRoomVerificationUpdateTask,
|
private val roomVerificationUpdateTask: DefaultRoomVerificationUpdateTask
|
||||||
private val cryptoService: CryptoService,
|
) : SqlLiveEntityObserver<EventInsertNotification>(sessionDatabase) {
|
||||||
private val verificationService: DefaultVerificationService,
|
|
||||||
private val taskExecutor: TaskExecutor
|
|
||||||
) : RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
|
|
||||||
|
|
||||||
override val query = Monarchy.Query {
|
override val query = sessionDatabase.observerTriggerQueries.getAllEventInsertNotifications(
|
||||||
EventEntity.whereTypes(it, listOf(
|
types = listOf(
|
||||||
EventType.KEY_VERIFICATION_START,
|
EventType.KEY_VERIFICATION_START,
|
||||||
EventType.KEY_VERIFICATION_ACCEPT,
|
EventType.KEY_VERIFICATION_ACCEPT,
|
||||||
EventType.KEY_VERIFICATION_KEY,
|
EventType.KEY_VERIFICATION_KEY,
|
||||||
EventType.KEY_VERIFICATION_MAC,
|
EventType.KEY_VERIFICATION_MAC,
|
||||||
EventType.KEY_VERIFICATION_CANCEL,
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
EventType.KEY_VERIFICATION_DONE,
|
EventType.KEY_VERIFICATION_DONE,
|
||||||
EventType.KEY_VERIFICATION_READY,
|
EventType.KEY_VERIFICATION_READY,
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
EventType.ENCRYPTED)
|
EventType.ENCRYPTED)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun handleChanges(results: List<EventInsertNotification>) {
|
||||||
|
val params = RoomVerificationUpdateTask.Params(results)
|
||||||
|
roomVerificationUpdateTask.execute(params)
|
||||||
|
val notificationIds = results.map { it.event_id }
|
||||||
|
sessionDatabase.observerTriggerQueries.deleteEventInsertNotifications(notificationIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onChange(results: RealmResults<EventEntity>, changeSet: OrderedCollectionChangeSet) {
|
|
||||||
// Should we ignore when it's an initial sync?
|
|
||||||
val events = changeSet.insertions
|
|
||||||
.asSequence()
|
|
||||||
.mapNotNull { results[it]?.asDomain() }
|
|
||||||
.filterNot {
|
|
||||||
// ignore local echos
|
|
||||||
LocalEcho.isLocalEchoId(it.eventId ?: "")
|
|
||||||
}
|
|
||||||
.toList()
|
|
||||||
|
|
||||||
roomVerificationUpdateTask.configureWith(
|
|
||||||
RoomVerificationUpdateTask.Params(events, verificationService, cryptoService)
|
|
||||||
).executeBy(taskExecutor)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.database
|
package im.vector.matrix.android.internal.database
|
||||||
|
|
||||||
|
import com.squareup.sqldelight.Transacter
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -22,7 +24,7 @@ import kotlinx.coroutines.isActive
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T) = withContext(Dispatchers.Default) {
|
suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T) = withContext(Dispatchers.IO) {
|
||||||
Realm.getInstance(config).use { bgRealm ->
|
Realm.getInstance(config).use { bgRealm ->
|
||||||
bgRealm.beginTransaction()
|
bgRealm.beginTransaction()
|
||||||
val result: T
|
val result: T
|
||||||
@@ -43,3 +45,22 @@ suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspen
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal suspend fun <T : Transacter> T.awaitTransaction(dispatchers: MatrixCoroutineDispatchers, noEnclosing: Boolean = false, transaction: (db: T) -> Unit) = withContext(dispatchers.dbTransaction) {
|
||||||
|
transaction(noEnclosing = noEnclosing) {
|
||||||
|
try {
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
transaction(this@awaitTransaction)
|
||||||
|
if (isActive) {
|
||||||
|
val end = System.currentTimeMillis()
|
||||||
|
val time = end - start
|
||||||
|
Timber.v("Execute transaction in $time millis")
|
||||||
|
} else {
|
||||||
|
rollback()
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Timber.e(exception)
|
||||||
|
rollback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -17,9 +17,11 @@ package im.vector.matrix.android.internal.database
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
||||||
import im.vector.matrix.android.BuildConfig
|
import im.vector.matrix.android.BuildConfig
|
||||||
import im.vector.matrix.android.internal.session.securestorage.SecretStoringUtils
|
import im.vector.matrix.android.internal.session.securestorage.SecretStoringUtils
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
import net.sqlcipher.database.SupportFactory
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -36,8 +38,8 @@ import javax.inject.Inject
|
|||||||
* then we generate a random secret key. The database key is encrypted with the secret key; The secret
|
* then we generate a random secret key. The database key is encrypted with the secret key; The secret
|
||||||
* key is encrypted with the public RSA key and stored with the encrypted key in the shared pref
|
* key is encrypted with the public RSA key and stored with the encrypted key in the shared pref
|
||||||
*/
|
*/
|
||||||
internal class RealmKeysUtils @Inject constructor(context: Context,
|
internal class DatabaseKeysUtils @Inject constructor(context: Context,
|
||||||
private val secretStoringUtils: SecretStoringUtils) {
|
private val secretStoringUtils: SecretStoringUtils) {
|
||||||
|
|
||||||
private val rng = SecureRandom()
|
private val rng = SecureRandom()
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
|
|||||||
return Base64.decode(b64!!, Base64.NO_PADDING)
|
return Base64.decode(b64!!, Base64.NO_PADDING)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun configureEncryption(realmConfigurationBuilder: RealmConfiguration.Builder, alias: String) {
|
private fun getOrCreateEncryptionKey(alias: String): ByteArray {
|
||||||
val key = if (hasKeyForDatabase(alias)) {
|
val key = if (hasKeyForDatabase(alias)) {
|
||||||
Timber.i("Found key for alias:$alias")
|
Timber.i("Found key for alias:$alias")
|
||||||
extractKeyForDatabase(alias)
|
extractKeyForDatabase(alias)
|
||||||
@@ -98,7 +100,16 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
|
|||||||
val log = key.joinToString("") { "%02x".format(it) }
|
val log = key.joinToString("") { "%02x".format(it) }
|
||||||
Timber.w("Database key for alias `$alias`: $log")
|
Timber.w("Database key for alias `$alias`: $log")
|
||||||
}
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createEncryptedSQLiteOpenHelperFactory(alias: String): SupportSQLiteOpenHelper.Factory {
|
||||||
|
val key = getOrCreateEncryptionKey(alias)
|
||||||
|
return SupportFactory(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun configureEncryption(realmConfigurationBuilder: RealmConfiguration.Builder, alias: String) {
|
||||||
|
val key = getOrCreateEncryptionKey(alias)
|
||||||
realmConfigurationBuilder.encryptionKey(key)
|
realmConfigurationBuilder.encryptionKey(key)
|
||||||
}
|
}
|
||||||
|
|
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.database
|
||||||
|
|
||||||
|
import com.squareup.sqldelight.Query
|
||||||
|
import im.vector.matrix.sqldelight.session.SessionDatabase
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
internal interface LiveEntityObserver {
|
||||||
|
fun start()
|
||||||
|
fun dispose()
|
||||||
|
fun cancelProcess()
|
||||||
|
fun isStarted(): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
internal abstract class SqlLiveEntityObserver<T : Any>(protected val sessionDatabase: SessionDatabase)
|
||||||
|
: LiveEntityObserver, Query.Listener {
|
||||||
|
|
||||||
|
protected val observerScope = CoroutineScope(SupervisorJob())
|
||||||
|
protected abstract val query: Query<T>
|
||||||
|
private val isStarted = AtomicBoolean(false)
|
||||||
|
|
||||||
|
protected abstract suspend fun handleChanges(results: List<T>)
|
||||||
|
|
||||||
|
override fun queryResultsChanged() {
|
||||||
|
observerScope.launch(Dispatchers.Default) {
|
||||||
|
val results = query.executeAsList()
|
||||||
|
if(results.isNotEmpty()) {
|
||||||
|
handleChanges(results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun start() {
|
||||||
|
if (isStarted.compareAndSet(false, true)) {
|
||||||
|
query.addListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
if (isStarted.compareAndSet(true, false)) {
|
||||||
|
query.removeListener(this)
|
||||||
|
observerScope.coroutineContext.cancelChildren()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancelProcess() {
|
||||||
|
observerScope.coroutineContext.cancelChildren()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isStarted(): Boolean {
|
||||||
|
return isStarted.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 New Vector Ltd
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.database
|
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
|
||||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
|
||||||
import io.realm.OrderedRealmCollectionChangeListener
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import io.realm.RealmObject
|
|
||||||
import io.realm.RealmResults
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.cancelChildren
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
|
|
||||||
internal interface LiveEntityObserver {
|
|
||||||
fun start()
|
|
||||||
fun dispose()
|
|
||||||
fun cancelProcess()
|
|
||||||
fun isStarted(): Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val realmConfiguration: RealmConfiguration)
|
|
||||||
: LiveEntityObserver, OrderedRealmCollectionChangeListener<RealmResults<T>> {
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND")
|
|
||||||
}
|
|
||||||
|
|
||||||
protected val observerScope = CoroutineScope(SupervisorJob())
|
|
||||||
protected abstract val query: Monarchy.Query<T>
|
|
||||||
private val isStarted = AtomicBoolean(false)
|
|
||||||
private val backgroundRealm = AtomicReference<Realm>()
|
|
||||||
private lateinit var results: AtomicReference<RealmResults<T>>
|
|
||||||
|
|
||||||
override fun start() {
|
|
||||||
if (isStarted.compareAndSet(false, true)) {
|
|
||||||
BACKGROUND_HANDLER.post {
|
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
|
||||||
backgroundRealm.set(realm)
|
|
||||||
val queryResults = query.createQuery(realm).findAll()
|
|
||||||
queryResults.addChangeListener(this)
|
|
||||||
results = AtomicReference(queryResults)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispose() {
|
|
||||||
if (isStarted.compareAndSet(true, false)) {
|
|
||||||
BACKGROUND_HANDLER.post {
|
|
||||||
results.getAndSet(null).removeAllChangeListeners()
|
|
||||||
backgroundRealm.getAndSet(null).also {
|
|
||||||
it.close()
|
|
||||||
}
|
|
||||||
observerScope.coroutineContext.cancelChildren()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancelProcess() {
|
|
||||||
observerScope.coroutineContext.cancelChildren()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isStarted(): Boolean {
|
|
||||||
return isStarted.get()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 New Vector Ltd
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.database
|
|
||||||
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmChangeListener
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
import io.realm.RealmResults
|
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.coroutines.withTimeout
|
|
||||||
|
|
||||||
internal suspend fun <T> awaitNotEmptyResult(realmConfiguration: RealmConfiguration,
|
|
||||||
timeoutMillis: Long,
|
|
||||||
builder: (Realm) -> RealmQuery<T>) {
|
|
||||||
withTimeout(timeoutMillis) {
|
|
||||||
// Confine Realm interaction to a single thread with Looper.
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
val latch = CompletableDeferred<Unit>()
|
|
||||||
|
|
||||||
Realm.getInstance(realmConfiguration).use { realm ->
|
|
||||||
val result = builder(realm).findAllAsync()
|
|
||||||
|
|
||||||
val listener = object : RealmChangeListener<RealmResults<T>> {
|
|
||||||
override fun onChange(it: RealmResults<T>) {
|
|
||||||
if (it.isNotEmpty()) {
|
|
||||||
result.removeChangeListener(this)
|
|
||||||
latch.complete(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.addChangeListener(listener)
|
|
||||||
try {
|
|
||||||
latch.await()
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
result.removeChangeListener(listener)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -16,79 +16,36 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database
|
package im.vector.matrix.android.internal.database
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||||
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
||||||
import im.vector.matrix.android.internal.di.SessionId
|
import im.vector.matrix.android.internal.di.SessionId
|
||||||
import im.vector.matrix.android.internal.di.UserMd5
|
import im.vector.matrix.android.internal.di.UserMd5
|
||||||
import im.vector.matrix.android.internal.session.SessionModule
|
import im.vector.matrix.android.internal.session.SessionModule
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val REALM_SHOULD_CLEAR_FLAG_ = "REALM_SHOULD_CLEAR_FLAG_"
|
|
||||||
private const val REALM_NAME = "disk_store.realm"
|
private const val REALM_NAME = "disk_store.realm"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is handling creation of RealmConfiguration for a session.
|
* This class is handling creation of RealmConfiguration for a session.
|
||||||
* It will handle corrupted realm by clearing the db file. It allows to just clear cache without losing your crypto keys.
|
|
||||||
* It's clearly not perfect but there is no way to catch the native crash.
|
|
||||||
*/
|
*/
|
||||||
internal class SessionRealmConfigurationFactory @Inject constructor(
|
internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||||
private val realmKeysUtils: RealmKeysUtils,
|
private val databaseKeysUtils: DatabaseKeysUtils,
|
||||||
@SessionFilesDirectory val directory: File,
|
@SessionFilesDirectory val directory: File,
|
||||||
@SessionId val sessionId: String,
|
@SessionId val sessionId: String,
|
||||||
@UserMd5 val userMd5: String,
|
@UserMd5 val userMd5: String) {
|
||||||
context: Context) {
|
|
||||||
|
|
||||||
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
|
|
||||||
|
|
||||||
fun create(): RealmConfiguration {
|
fun create(): RealmConfiguration {
|
||||||
val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
return RealmConfiguration.Builder()
|
||||||
if (shouldClearRealm) {
|
|
||||||
Timber.v("************************************************************")
|
|
||||||
Timber.v("The realm file session was corrupted and couldn't be loaded.")
|
|
||||||
Timber.v("The file has been deleted to recover.")
|
|
||||||
Timber.v("************************************************************")
|
|
||||||
deleteRealmFiles()
|
|
||||||
}
|
|
||||||
sharedPreferences
|
|
||||||
.edit()
|
|
||||||
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", true)
|
|
||||||
.apply()
|
|
||||||
|
|
||||||
val realmConfiguration = RealmConfiguration.Builder()
|
|
||||||
.compactOnLaunch()
|
|
||||||
.directory(directory)
|
.directory(directory)
|
||||||
.name(REALM_NAME)
|
.name(REALM_NAME)
|
||||||
.apply {
|
.apply {
|
||||||
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
databaseKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||||
}
|
}
|
||||||
.modules(SessionRealmModule())
|
.modules(SessionRealmModule())
|
||||||
.deleteRealmIfMigrationNeeded()
|
.deleteRealmIfMigrationNeeded()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
// Try creating a realm instance and if it succeeds we can clear the flag
|
|
||||||
Realm.getInstance(realmConfiguration).use {
|
|
||||||
Timber.v("Successfully create realm instance")
|
|
||||||
sharedPreferences
|
|
||||||
.edit()
|
|
||||||
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
return realmConfiguration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all the realm files of the session
|
|
||||||
private fun deleteRealmFiles() {
|
|
||||||
listOf(REALM_NAME, "$REALM_NAME.lock", "$REALM_NAME.note", "$REALM_NAME.management").forEach { file ->
|
|
||||||
try {
|
|
||||||
File(directory, file).deleteRecursively()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.e(e, "Unable to delete files")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.database
|
||||||
|
|
||||||
|
import com.squareup.sqldelight.Query
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
|
|
||||||
|
internal suspend fun Query<Boolean>.awaitResult(timeoutMillis: Long) = withTimeout(timeoutMillis) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val exists = executeAsOne()
|
||||||
|
if (exists) {
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
val latch = CompletableDeferred<Unit>()
|
||||||
|
val listener = object : Query.Listener {
|
||||||
|
override fun queryResultsChanged() {
|
||||||
|
if (executeAsOne()) {
|
||||||
|
latch.complete(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addListener(listener)
|
||||||
|
try {
|
||||||
|
latch.await()
|
||||||
|
} finally {
|
||||||
|
removeListener(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
package im.vector.matrix.android.internal.database.helper
|
||||||
|
|
||||||
|
import im.vector.matrix.sqldelight.session.Breadcrumbs
|
||||||
|
import im.vector.matrix.sqldelight.session.BreadcrumbsQueries
|
||||||
|
|
||||||
|
internal fun BreadcrumbsQueries.saveBreadcrumbs(breadcrumbs: List<String>) {
|
||||||
|
deleteAll()
|
||||||
|
breadcrumbs.forEachIndexed { index, roomId ->
|
||||||
|
insert(Breadcrumbs.Impl(room_id = roomId, breadcrumb_index = index))
|
||||||
|
}
|
||||||
|
}
|
@@ -16,26 +16,15 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database.helper
|
package im.vector.matrix.android.internal.database.helper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
import im.vector.matrix.android.api.session.room.model.RoomMemberContent
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.*
|
||||||
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntityFields
|
|
||||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
|
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
|
||||||
import im.vector.matrix.android.internal.database.query.find
|
|
||||||
import im.vector.matrix.android.internal.database.query.getOrCreate
|
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
|
||||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
|
import im.vector.matrix.sqldelight.session.SessionDatabase
|
||||||
|
import im.vector.matrix.sqldelight.session.TimelineEventQueries
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.Sort
|
import io.realm.Sort
|
||||||
import io.realm.kotlin.createObject
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
internal fun ChunkEntity.deleteOnCascade() {
|
internal fun ChunkEntity.deleteOnCascade() {
|
||||||
@@ -85,57 +74,47 @@ internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.addTimelineEvent(roomId: String,
|
internal fun SessionDatabase.addTimelineEvent(roomId: String,
|
||||||
eventEntity: EventEntity,
|
chunkId: Long,
|
||||||
direction: PaginationDirection,
|
event: Event,
|
||||||
roomMemberContentsByUser: Map<String, RoomMemberContent?>) {
|
direction: PaginationDirection,
|
||||||
val eventId = eventEntity.eventId
|
roomMemberContentsByUser: Map<String, RoomMemberContent?>) {
|
||||||
if (timelineEvents.find(eventId) != null) {
|
val eventId = event.eventId ?: "$roomId-$chunkId-${System.currentTimeMillis()}"
|
||||||
return
|
val displayIndex = timelineEventQueries.nextDisplayIndex(direction, chunkId)
|
||||||
}
|
val senderId = event.senderId ?: ""
|
||||||
val displayIndex = nextDisplayIndex(direction)
|
val roomMemberContent = roomMemberContentsByUser[senderId]
|
||||||
val localId = TimelineEventEntity.nextId(realm)
|
|
||||||
val senderId = eventEntity.sender ?: ""
|
|
||||||
|
|
||||||
// Update RR for the sender of a new message with a dummy one
|
// Update RR for the sender of a new message with a dummy one
|
||||||
val readReceiptsSummaryEntity = handleReadReceipts(realm, roomId, eventEntity, senderId)
|
handleReadReceipts(this, roomId, eventId, event.originServerTs, senderId)
|
||||||
val timelineEventEntity = realm.createObject<TimelineEventEntity>().apply {
|
val isDisplayNameUnique = if (roomMemberContent?.displayName != null) {
|
||||||
this.localId = localId
|
computeIsUnique(this, roomId, chunkId, roomMemberContent, roomMemberContentsByUser)
|
||||||
this.root = eventEntity
|
} else {
|
||||||
this.eventId = eventId
|
true
|
||||||
this.roomId = roomId
|
|
||||||
this.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
|
||||||
this.readReceipts = readReceiptsSummaryEntity
|
|
||||||
this.displayIndex = displayIndex
|
|
||||||
val roomMemberContent = roomMemberContentsByUser[senderId]
|
|
||||||
this.senderAvatar = roomMemberContent?.avatarUrl
|
|
||||||
this.senderName = roomMemberContent?.displayName
|
|
||||||
isUniqueDisplayName = if (roomMemberContent?.displayName != null) {
|
|
||||||
computeIsUnique(realm, roomId, isLastForward, roomMemberContent, roomMemberContentsByUser)
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
timelineEvents.add(timelineEventEntity)
|
timelineEventQueries.insert(
|
||||||
|
event_id = eventId,
|
||||||
|
sender_avatar = roomMemberContent?.avatarUrl,
|
||||||
|
chunk_id = chunkId,
|
||||||
|
room_id = roomId,
|
||||||
|
display_index = displayIndex,
|
||||||
|
is_unique_display_name = isDisplayNameUnique,
|
||||||
|
sender_name = roomMemberContent?.displayName
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeIsUnique(
|
private fun computeIsUnique(
|
||||||
realm: Realm,
|
sessionDatabase: SessionDatabase,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
isLastForward: Boolean,
|
chunkId: Long,
|
||||||
myRoomMemberContent: RoomMemberContent,
|
myRoomMemberContent: RoomMemberContent,
|
||||||
roomMemberContentsByUser: Map<String, RoomMemberContent?>
|
roomMemberContentsByUser: Map<String, RoomMemberContent?>
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val isHistoricalUnique = roomMemberContentsByUser.values.find {
|
val isHistoricalUnique = roomMemberContentsByUser.values.find {
|
||||||
it != myRoomMemberContent && it?.displayName == myRoomMemberContent.displayName
|
it != myRoomMemberContent && it?.displayName == myRoomMemberContent.displayName
|
||||||
} == null
|
} == null
|
||||||
|
val isLastForward = sessionDatabase.chunkQueries.isLastForward(chunkId).executeAsOne()
|
||||||
return if (isLastForward) {
|
return if (isLastForward) {
|
||||||
val isLiveUnique = RoomMemberSummaryEntity
|
val countMembersWithName = sessionDatabase.roomMemberSummaryQueries.countMembersWithNameInRoom(myRoomMemberContent.displayName, roomId).executeAsOne()
|
||||||
.where(realm, roomId)
|
val isLiveUnique = countMembersWithName == 1L
|
||||||
.equalTo(RoomMemberSummaryEntityFields.DISPLAY_NAME, myRoomMemberContent.displayName)
|
|
||||||
.findAll().none {
|
|
||||||
!roomMemberContentsByUser.containsKey(it.userId)
|
|
||||||
}
|
|
||||||
isHistoricalUnique && isLiveUnique
|
isHistoricalUnique && isLiveUnique
|
||||||
} else {
|
} else {
|
||||||
isHistoricalUnique
|
isHistoricalUnique
|
||||||
@@ -143,6 +122,7 @@ private fun computeIsUnique(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEntity: TimelineEventEntity, direction: PaginationDirection) {
|
private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEntity: TimelineEventEntity, direction: PaginationDirection) {
|
||||||
|
/*
|
||||||
val eventId = timelineEventEntity.eventId
|
val eventId = timelineEventEntity.eventId
|
||||||
if (timelineEvents.find(eventId) != null) {
|
if (timelineEvents.find(eventId) != null) {
|
||||||
return
|
return
|
||||||
@@ -162,36 +142,30 @@ private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEnt
|
|||||||
this.isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName
|
this.isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName
|
||||||
}
|
}
|
||||||
timelineEvents.add(copied)
|
timelineEvents.add(copied)
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventEntity, senderId: String): ReadReceiptsSummaryEntity {
|
private fun handleReadReceipts(
|
||||||
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst()
|
sessionDatabase: SessionDatabase,
|
||||||
?: realm.createObject<ReadReceiptsSummaryEntity>(eventEntity.eventId).apply {
|
roomId: String,
|
||||||
this.roomId = roomId
|
eventId: String,
|
||||||
}
|
originServerTs: Long?,
|
||||||
val originServerTs = eventEntity.originServerTs
|
senderId: String) {
|
||||||
if (originServerTs != null) {
|
if (originServerTs != null) {
|
||||||
val timestampOfEvent = originServerTs.toDouble()
|
val timestampOfEvent = originServerTs.toDouble()
|
||||||
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId)
|
val oldTimestamp = sessionDatabase.readReceiptQueries.getTimestampForUser(roomId, senderId).executeAsOneOrNull()
|
||||||
// If the synced RR is older, update
|
// If the synced RR is older, update
|
||||||
if (timestampOfEvent > readReceiptOfSender.originServerTs) {
|
if (oldTimestamp == null || timestampOfEvent > oldTimestamp) {
|
||||||
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst()
|
sessionDatabase.readReceiptQueries.updateReadReceipt(eventId, timestampOfEvent, roomId, senderId)
|
||||||
readReceiptOfSender.eventId = eventEntity.eventId
|
|
||||||
readReceiptOfSender.originServerTs = timestampOfEvent
|
|
||||||
previousReceiptsSummary?.readReceipts?.remove(readReceiptOfSender)
|
|
||||||
readReceiptsSummaryEntity.readReceipts.add(readReceiptOfSender)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return readReceiptsSummaryEntity
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.nextDisplayIndex(direction: PaginationDirection): Int {
|
internal fun TimelineEventQueries.nextDisplayIndex(direction: PaginationDirection, chunkId: Long): Int {
|
||||||
return when (direction) {
|
return when (direction) {
|
||||||
PaginationDirection.FORWARDS -> {
|
PaginationDirection.FORWARDS -> (getMaxDisplayIndex(chunkId).executeAsOneOrNull()?.max_display_index?.toInt()
|
||||||
(timelineEvents.where().max(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) + 1
|
?: 0) + 1
|
||||||
}
|
PaginationDirection.BACKWARDS -> (getMinDisplayIndex(chunkId).executeAsOneOrNull()?.min_display_index?.toInt()
|
||||||
PaginationDirection.BACKWARDS -> {
|
?: 0) - 1
|
||||||
(timelineEvents.where().min(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) - 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
package im.vector.matrix.android.internal.database.helper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||||
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
import im.vector.matrix.sqldelight.session.EventQueries
|
||||||
|
|
||||||
|
fun EventQueries.setDecryptionResult(result: MXEventDecryptionResult, eventId: String) {
|
||||||
|
val decryptionResult = OlmDecryptionResult(
|
||||||
|
payload = result.clearEvent,
|
||||||
|
senderKey = result.senderCurve25519Key,
|
||||||
|
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||||
|
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||||
|
)
|
||||||
|
val adapter = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java)
|
||||||
|
val json = adapter.toJson(decryptionResult)
|
||||||
|
setDecryptionResult(json, eventId)
|
||||||
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
package im.vector.matrix.android.internal.database.helper
|
||||||
|
|
||||||
|
import com.squareup.sqldelight.ColumnAdapter
|
||||||
|
|
||||||
|
|
||||||
|
private const val SEPARATOR = "__;__"
|
||||||
|
|
||||||
|
internal class ListOfStringColumnAdapter : ColumnAdapter<List<String>, String> {
|
||||||
|
override fun decode(databaseValue: String): List<String> {
|
||||||
|
return databaseValue.split(SEPARATOR).filter { it.isNotBlank() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encode(value: List<String>): String {
|
||||||
|
return value.joinToString(SEPARATOR)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,18 @@
|
|||||||
|
package im.vector.matrix.android.internal.database.helper
|
||||||
|
|
||||||
|
fun List<Any>.toInParams(): String{
|
||||||
|
return if (isEmpty()) {
|
||||||
|
"()"
|
||||||
|
} else {
|
||||||
|
val list = this
|
||||||
|
buildString {
|
||||||
|
append("(")
|
||||||
|
append("\"${list.first()}\"")
|
||||||
|
for (value in list.drop(1)) {
|
||||||
|
append(",")
|
||||||
|
append("\"$value\"")
|
||||||
|
}
|
||||||
|
append(')')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
package im.vector.matrix.android.internal.database.helper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||||
|
import im.vector.matrix.sqldelight.session.SessionDatabase
|
||||||
|
|
||||||
|
internal fun SessionDatabase.isEventRead(userId: String?,
|
||||||
|
roomId: String?,
|
||||||
|
eventId: String?): Boolean {
|
||||||
|
if (userId.isNullOrBlank() || roomId.isNullOrBlank() || eventId.isNullOrBlank()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (LocalEcho.isLocalEchoId(eventId)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
val eventToCheck = timelineEventQueries.getForReadQueries(eventId).executeAsOneOrNull()
|
||||||
|
return if (eventToCheck == null || eventToCheck.sender_id == userId) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
val readReceipt = readReceiptQueries.getEventIdForUser(roomId, userId).executeAsOneOrNull()
|
||||||
|
?: return false
|
||||||
|
val readReceiptIndex = timelineEventQueries.getForReadQueries(readReceipt).executeAsOneOrNull()?.display_index
|
||||||
|
?: Int.MIN_VALUE
|
||||||
|
val eventToCheckIndex = eventToCheck.display_index
|
||||||
|
eventToCheckIndex <= readReceiptIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun SessionDatabase.isReadMarkerMoreRecent(roomId: String?,
|
||||||
|
eventId: String?): Boolean {
|
||||||
|
if (roomId.isNullOrBlank() || eventId.isNullOrBlank()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val eventToCheck = timelineEventQueries.getForReadQueries(eventId).executeAsOneOrNull()
|
||||||
|
val readMarker = readMarkerQueries.get(roomId).executeAsOneOrNull()
|
||||||
|
?: return false
|
||||||
|
val readMarkerEvent = timelineEventQueries.getForReadQueries(readMarker).executeAsOneOrNull()
|
||||||
|
return if (eventToCheck?.chunk_id == readMarkerEvent?.chunk_id) {
|
||||||
|
val readMarkerIndex = readMarkerEvent?.display_index ?: Int.MIN_VALUE
|
||||||
|
val eventToCheckIndex = eventToCheck?.display_index ?: Int.MAX_VALUE
|
||||||
|
eventToCheckIndex <= readMarkerIndex
|
||||||
|
} else {
|
||||||
|
val chunkId = eventToCheck?.chunk_id ?: return false
|
||||||
|
chunkQueries.isLastForward(chunkId).executeAsOneOrNull() ?: false
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 New Vector Ltd
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Breadcrumb
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class BreadcrumbMapper @Inject constructor() {
|
||||||
|
|
||||||
|
fun map(room_id: String,
|
||||||
|
display_name: String?,
|
||||||
|
avatar_url: String?,
|
||||||
|
highlight_count: Int,
|
||||||
|
is_encrypted: Boolean,
|
||||||
|
notification_count: Int,
|
||||||
|
has_unread: Boolean): Breadcrumb {
|
||||||
|
|
||||||
|
return Breadcrumb(
|
||||||
|
roomId = room_id,
|
||||||
|
avatarUrl = avatar_url ?: "",
|
||||||
|
displayName = display_name ?: "",
|
||||||
|
hasUnreadMessages = has_unread,
|
||||||
|
highlightCount = highlight_count,
|
||||||
|
isEncrypted = is_encrypted,
|
||||||
|
notificationCount = notification_count,
|
||||||
|
typingRoomMemberIds = emptyList(),
|
||||||
|
userDrafts = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -17,29 +17,23 @@
|
|||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.send.UserDraft
|
import im.vector.matrix.android.api.session.room.send.UserDraft
|
||||||
import im.vector.matrix.android.internal.database.model.DraftEntity
|
import im.vector.matrix.sqldelight.session.DraftMode
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DraftEntity <-> UserDraft
|
* DraftEntity <-> UserDraft
|
||||||
*/
|
*/
|
||||||
internal object DraftMapper {
|
internal class DraftMapper @Inject constructor() {
|
||||||
|
|
||||||
fun map(entity: DraftEntity): UserDraft {
|
fun map(content: String,
|
||||||
return when (entity.draftMode) {
|
draft_mode: String,
|
||||||
DraftEntity.MODE_REGULAR -> UserDraft.REGULAR(entity.content)
|
linked_event_id: String): UserDraft {
|
||||||
DraftEntity.MODE_EDIT -> UserDraft.EDIT(entity.linkedEventId, entity.content)
|
return when (draft_mode) {
|
||||||
DraftEntity.MODE_QUOTE -> UserDraft.QUOTE(entity.linkedEventId, entity.content)
|
DraftMode.MODE_REGULAR -> UserDraft.REGULAR(content)
|
||||||
DraftEntity.MODE_REPLY -> UserDraft.REPLY(entity.linkedEventId, entity.content)
|
DraftMode.MODE_EDIT -> UserDraft.EDIT(linked_event_id, content)
|
||||||
else -> null
|
DraftMode.MODE_QUOTE -> UserDraft.QUOTE(linked_event_id, content)
|
||||||
|
DraftMode.MODE_REPLY -> UserDraft.REPLY(linked_event_id, content)
|
||||||
|
else -> null
|
||||||
} ?: UserDraft.REGULAR("")
|
} ?: UserDraft.REGULAR("")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun map(domain: UserDraft): DraftEntity {
|
|
||||||
return when (domain) {
|
|
||||||
is UserDraft.REGULAR -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REGULAR, linkedEventId = "")
|
|
||||||
is UserDraft.EDIT -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_EDIT, linkedEventId = domain.linkedEventId)
|
|
||||||
is UserDraft.QUOTE -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_QUOTE, linkedEventId = domain.linkedEventId)
|
|
||||||
is UserDraft.REPLY -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REPLY, linkedEventId = domain.linkedEventId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -16,94 +16,77 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.*
|
||||||
import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary
|
|
||||||
import im.vector.matrix.android.api.session.room.model.ReferencesAggregatedSummary
|
|
||||||
import im.vector.matrix.android.internal.database.model.EditAggregatedSummaryEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.ReferencesAggregatedSummaryEntity
|
|
||||||
import io.realm.RealmList
|
|
||||||
|
|
||||||
internal object EventAnnotationsSummaryMapper {
|
internal object EventAnnotationsSummaryMapper {
|
||||||
fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
|
|
||||||
return EventAnnotationsSummary(
|
|
||||||
eventId = annotationsSummary.eventId,
|
|
||||||
reactionsSummary = annotationsSummary.reactionsSummary.toList().map {
|
|
||||||
ReactionAggregatedSummary(
|
|
||||||
it.key,
|
|
||||||
it.count,
|
|
||||||
it.addedByMe,
|
|
||||||
it.firstTimestamp,
|
|
||||||
it.sourceEvents.toList(),
|
|
||||||
it.sourceLocalEcho.toList()
|
|
||||||
)
|
|
||||||
},
|
|
||||||
editSummary = annotationsSummary.editSummary?.let {
|
|
||||||
EditAggregatedSummary(
|
|
||||||
ContentMapper.map(it.aggregatedContent),
|
|
||||||
it.sourceEvents.toList(),
|
|
||||||
it.sourceLocalEchoEvents.toList(),
|
|
||||||
it.lastEditTs
|
|
||||||
)
|
|
||||||
},
|
|
||||||
referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
|
|
||||||
ReferencesAggregatedSummary(
|
|
||||||
it.eventId,
|
|
||||||
ContentMapper.map(it.content),
|
|
||||||
it.sourceEvents.toList(),
|
|
||||||
it.sourceLocalEcho.toList()
|
|
||||||
)
|
|
||||||
},
|
|
||||||
pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
|
|
||||||
PollResponseAggregatedSummaryEntityMapper.map(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
fun mapAnnotationsSummary(eventId: String,
|
||||||
|
reactions: List<ReactionAggregatedSummary>,
|
||||||
|
edit: EditAggregatedSummary?,
|
||||||
|
references: ReferencesAggregatedSummary?,
|
||||||
|
poll: PollResponseAggregatedSummary?): EventAnnotationsSummary {
|
||||||
|
return EventAnnotationsSummary(
|
||||||
|
eventId = eventId,
|
||||||
|
reactionsSummary = reactions,
|
||||||
|
referencesAggregatedSummary = references,
|
||||||
|
editSummary = edit,
|
||||||
|
pollResponseSummary = poll
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun map(annotationsSummary: EventAnnotationsSummary, roomId: String): EventAnnotationsSummaryEntity {
|
fun mapReactionSummary(key: String,
|
||||||
val eventAnnotationsSummaryEntity = EventAnnotationsSummaryEntity()
|
count: Long,
|
||||||
eventAnnotationsSummaryEntity.eventId = annotationsSummary.eventId
|
added_by_me: Boolean,
|
||||||
eventAnnotationsSummaryEntity.roomId = roomId
|
first_timestamp: Long,
|
||||||
eventAnnotationsSummaryEntity.editSummary = annotationsSummary.editSummary?.let {
|
source_event_ids: List<String>,
|
||||||
EditAggregatedSummaryEntity(
|
source_local_echo_ids: List<String>): ReactionAggregatedSummary {
|
||||||
ContentMapper.map(it.aggregatedContent),
|
return ReactionAggregatedSummary(
|
||||||
RealmList<String>().apply { addAll(it.sourceEvents) },
|
key = key,
|
||||||
RealmList<String>().apply { addAll(it.localEchos) },
|
count = count.toInt(),
|
||||||
it.lastEditTs
|
addedByMe = added_by_me,
|
||||||
)
|
firstTimestamp = first_timestamp,
|
||||||
}
|
sourceEvents = source_event_ids,
|
||||||
eventAnnotationsSummaryEntity.reactionsSummary = annotationsSummary.reactionsSummary.let {
|
localEchoEvents = source_local_echo_ids
|
||||||
RealmList<ReactionAggregatedSummaryEntity>().apply {
|
)
|
||||||
addAll(it.map {
|
}
|
||||||
ReactionAggregatedSummaryEntity(
|
|
||||||
it.key,
|
|
||||||
it.count,
|
fun mapEditSummary(aggregated_content: String?,
|
||||||
it.addedByMe,
|
last_edit_ts: Long,
|
||||||
it.firstTimestamp,
|
source_event_ids: List<String>,
|
||||||
RealmList<String>().apply { addAll(it.sourceEvents) },
|
source_local_echo_ids: List<String>): EditAggregatedSummary {
|
||||||
RealmList<String>().apply { addAll(it.localEchoEvents) }
|
return EditAggregatedSummary(
|
||||||
)
|
ContentMapper.map(aggregated_content),
|
||||||
})
|
source_event_ids,
|
||||||
}
|
source_local_echo_ids,
|
||||||
}
|
last_edit_ts
|
||||||
eventAnnotationsSummaryEntity.referencesSummaryEntity = annotationsSummary.referencesAggregatedSummary?.let {
|
)
|
||||||
ReferencesAggregatedSummaryEntity(
|
}
|
||||||
it.eventId,
|
|
||||||
ContentMapper.map(it.content),
|
fun mapReferencesSummary(event_id: String,
|
||||||
RealmList<String>().apply { addAll(it.sourceEvents) },
|
content: String?,
|
||||||
RealmList<String>().apply { addAll(it.localEchos) }
|
source_event_ids: List<String>,
|
||||||
)
|
source_local_echo_ids: List<String>): ReferencesAggregatedSummary {
|
||||||
}
|
return ReferencesAggregatedSummary(
|
||||||
eventAnnotationsSummaryEntity.pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
|
event_id,
|
||||||
PollResponseAggregatedSummaryEntityMapper.map(it)
|
ContentMapper.map(content),
|
||||||
}
|
source_event_ids,
|
||||||
return eventAnnotationsSummaryEntity
|
source_local_echo_ids
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mapPollSummary(content: String?,
|
||||||
|
closed_time: Long?,
|
||||||
|
nb_options: Int,
|
||||||
|
source_event_ids: List<String>,
|
||||||
|
source_local_echo_ids: List<String>): PollResponseAggregatedSummary {
|
||||||
|
return PollResponseAggregatedSummary(
|
||||||
|
aggregatedContent = ContentMapper.map(content).toModel(),
|
||||||
|
closedTime = closed_time,
|
||||||
|
localEchos = source_local_echo_ids,
|
||||||
|
sourceEvents = source_event_ids,
|
||||||
|
nbOptions = nb_options
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary {
|
|
||||||
return EventAnnotationsSummaryMapper.map(this)
|
|
||||||
}
|
|
||||||
|
@@ -33,7 +33,8 @@ internal object EventMapper {
|
|||||||
else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(event.unsignedData)
|
else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(event.unsignedData)
|
||||||
val eventEntity = EventEntity()
|
val eventEntity = EventEntity()
|
||||||
// TODO change this as we shouldn't use event everywhere
|
// TODO change this as we shouldn't use event everywhere
|
||||||
eventEntity.eventId = event.eventId ?: "$$roomId-${System.currentTimeMillis()}-${event.hashCode()}"
|
eventEntity.eventId = event.eventId
|
||||||
|
?: "$$roomId-${System.currentTimeMillis()}-${event.hashCode()}"
|
||||||
eventEntity.roomId = event.roomId ?: roomId
|
eventEntity.roomId = event.roomId ?: roomId
|
||||||
eventEntity.content = ContentMapper.map(event.content)
|
eventEntity.content = ContentMapper.map(event.content)
|
||||||
val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent
|
val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent
|
||||||
@@ -48,6 +49,34 @@ internal object EventMapper {
|
|||||||
return eventEntity
|
return eventEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun map(event: Event, roomId: String, sendState: SendState, ageLocalTs: Long?): im.vector.matrix.sqldelight.session.EventEntity {
|
||||||
|
val uds = if (event.unsignedData == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(event.unsignedData)
|
||||||
|
}
|
||||||
|
val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent
|
||||||
|
return im.vector.matrix.sqldelight.session.EventEntity.Impl(
|
||||||
|
// TODO change this as we shouldn't use event everywhere
|
||||||
|
event_id = event.eventId
|
||||||
|
?: "$$roomId-${System.currentTimeMillis()}-${event.hashCode()}",
|
||||||
|
room_id = event.roomId ?: roomId,
|
||||||
|
content = ContentMapper.map(event.content),
|
||||||
|
prev_content = ContentMapper.map(resolvedPrevContent),
|
||||||
|
state_key = event.stateKey,
|
||||||
|
type = event.type,
|
||||||
|
sender_id = event.senderId,
|
||||||
|
origin_server_ts = event.originServerTs,
|
||||||
|
redacts = event.redacts,
|
||||||
|
age = event.unsignedData?.age ?: event.originServerTs,
|
||||||
|
unsigned_data = uds,
|
||||||
|
age_local_ts = ageLocalTs,
|
||||||
|
send_state = sendState.name,
|
||||||
|
decryption_error_code = null,
|
||||||
|
decryption_result_json = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun map(eventEntity: EventEntity): Event {
|
fun map(eventEntity: EventEntity): Event {
|
||||||
val ud = eventEntity.unsignedData
|
val ud = eventEntity.unsignedData
|
||||||
?.takeIf { it.isNotBlank() }
|
?.takeIf { it.isNotBlank() }
|
||||||
@@ -87,12 +116,56 @@ internal object EventMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun map(eventEntity: im.vector.matrix.sqldelight.session.EventEntity): Event {
|
||||||
|
return Event(
|
||||||
|
type = eventEntity.type,
|
||||||
|
eventId = eventEntity.event_id,
|
||||||
|
content = ContentMapper.map(eventEntity.content),
|
||||||
|
prevContent = ContentMapper.map(eventEntity.prev_content),
|
||||||
|
originServerTs = eventEntity.origin_server_ts,
|
||||||
|
senderId = eventEntity.sender_id,
|
||||||
|
stateKey = eventEntity.state_key,
|
||||||
|
roomId = eventEntity.room_id,
|
||||||
|
unsignedData = UnsignedDataMapper.mapFromString(eventEntity.unsigned_data),
|
||||||
|
redacts = eventEntity.redacts
|
||||||
|
).also {
|
||||||
|
it.ageLocalTs = eventEntity.age_local_ts
|
||||||
|
it.sendState = SendState.valueOf(eventEntity.send_state)
|
||||||
|
it.setDecryptionValues(eventEntity.decryption_result_json, eventEntity.decryption_error_code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Event.setDecryptionValues(decryptionResultJson: String?, decryptionErrorCode: String?): Event {
|
||||||
|
return apply {
|
||||||
|
decryptionResultJson?.let { json ->
|
||||||
|
try {
|
||||||
|
mxDecryptionResult = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).fromJson(json)
|
||||||
|
} catch (t: JsonDataException) {
|
||||||
|
Timber.e(t, "Failed to parse decryption result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO get the full crypto error object
|
||||||
|
mCryptoError = decryptionErrorCode?.let { errorCode ->
|
||||||
|
MXCryptoError.ErrorType.valueOf(errorCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun EventEntity.asDomain(): Event {
|
internal fun EventEntity.asDomain(): Event {
|
||||||
return EventMapper.map(this)
|
return EventMapper.map(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun im.vector.matrix.sqldelight.session.EventEntity.asDomain(): Event {
|
||||||
|
return EventMapper.map(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Event.toSQLEntity(roomId: String, sendState: SendState, ageLocalTs: Long? = null): im.vector.matrix.sqldelight.session.EventEntity {
|
||||||
|
return EventMapper.map(this, roomId, sendState, ageLocalTs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long? = null): EventEntity {
|
internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long? = null): EventEntity {
|
||||||
return EventMapper.map(this, roomId).apply {
|
return EventMapper.map(this, roomId).apply {
|
||||||
this.sendState = sendState
|
this.sendState = sendState
|
||||||
|
@@ -16,24 +16,34 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
|
import com.squareup.sqldelight.db.SqlCursor
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
import im.vector.matrix.sqldelight.session.Memberships
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal object GroupSummaryMapper {
|
internal class GroupSummaryMapper @Inject constructor() {
|
||||||
|
|
||||||
|
fun map(cursor: SqlCursor): GroupSummary = GroupSummary(
|
||||||
|
groupId = cursor.getString(0)!!,
|
||||||
|
membership = Membership.valueOf(cursor.getString(4)!!),
|
||||||
|
displayName = cursor.getString(1) ?: "",
|
||||||
|
shortDescription = cursor.getString(2) ?: "",
|
||||||
|
avatarUrl = cursor.getString(3) ?: ""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun map(group_id: String,
|
||||||
|
display_name: String?,
|
||||||
|
short_description: String?,
|
||||||
|
avatar_url: String?,
|
||||||
|
membership: Memberships): GroupSummary {
|
||||||
|
|
||||||
fun map(groupSummaryEntity: GroupSummaryEntity): GroupSummary {
|
|
||||||
return GroupSummary(
|
return GroupSummary(
|
||||||
groupSummaryEntity.groupId,
|
groupId = group_id,
|
||||||
groupSummaryEntity.membership,
|
membership = membership.map(),
|
||||||
groupSummaryEntity.displayName,
|
displayName = display_name ?: "",
|
||||||
groupSummaryEntity.shortDescription,
|
shortDescription = short_description ?: "",
|
||||||
groupSummaryEntity.avatarUrl,
|
avatarUrl = avatar_url ?: ""
|
||||||
groupSummaryEntity.roomIds.toList(),
|
|
||||||
groupSummaryEntity.userIds.toList()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun GroupSummaryEntity.asDomain(): GroupSummary {
|
|
||||||
return GroupSummaryMapper.map(this)
|
|
||||||
}
|
|
||||||
|
@@ -0,0 +1,16 @@
|
|||||||
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
import im.vector.matrix.sqldelight.session.Memberships
|
||||||
|
|
||||||
|
fun Memberships.map(): Membership {
|
||||||
|
return Membership.valueOf(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Membership.map():Memberships {
|
||||||
|
return Memberships.valueOf(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun List<Membership>.map(): List<Memberships> = this.map {
|
||||||
|
it.map()
|
||||||
|
}
|
@@ -17,11 +17,13 @@
|
|||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
import im.vector.matrix.android.api.pushrules.rest.PushCondition
|
import im.vector.matrix.android.api.pushrules.rest.PushCondition
|
||||||
import im.vector.matrix.android.internal.database.model.PushConditionEntity
|
import im.vector.matrix.sqldelight.session.GetPushConditions
|
||||||
|
import im.vector.matrix.sqldelight.session.PushConditionEntity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal object PushConditionMapper {
|
internal class PushConditionMapper @Inject constructor() {
|
||||||
|
|
||||||
fun map(entity: PushConditionEntity): PushCondition {
|
fun map(entity: GetPushConditions): PushCondition {
|
||||||
return PushCondition(
|
return PushCondition(
|
||||||
kind = entity.kind,
|
kind = entity.kind,
|
||||||
iz = entity.iz,
|
iz = entity.iz,
|
||||||
@@ -30,12 +32,14 @@ internal object PushConditionMapper {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun map(domain: PushCondition): PushConditionEntity {
|
fun map(ruleId: String, pushCondition: PushCondition): PushConditionEntity {
|
||||||
return PushConditionEntity(
|
return PushConditionEntity.Impl(
|
||||||
kind = domain.kind,
|
kind = pushCondition.kind,
|
||||||
iz = domain.iz,
|
iz = pushCondition.iz,
|
||||||
key = domain.key,
|
key = pushCondition.key,
|
||||||
pattern = domain.pattern
|
pattern = pushCondition.pattern,
|
||||||
|
rule_id = ruleId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -17,14 +17,16 @@ package im.vector.matrix.android.internal.database.mapper
|
|||||||
|
|
||||||
import com.squareup.moshi.Types
|
import com.squareup.moshi.Types
|
||||||
import im.vector.matrix.android.api.pushrules.Condition
|
import im.vector.matrix.android.api.pushrules.Condition
|
||||||
|
import im.vector.matrix.android.api.pushrules.RuleKind
|
||||||
import im.vector.matrix.android.api.pushrules.rest.PushCondition
|
import im.vector.matrix.android.api.pushrules.rest.PushCondition
|
||||||
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
import im.vector.matrix.android.api.pushrules.rest.PushRule
|
||||||
import im.vector.matrix.android.internal.database.model.PushRuleEntity
|
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import io.realm.RealmList
|
import im.vector.matrix.sqldelight.session.GetPushConditions
|
||||||
|
import im.vector.matrix.sqldelight.session.PushRuleEntity
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal object PushRulesMapper {
|
internal class PushRulesMapper @Inject constructor(private val pushConditionMapper: PushConditionMapper) {
|
||||||
|
|
||||||
private val moshiActionsAdapter = MoshiProvider.providesMoshi().adapter<List<Any>>(Types.newParameterizedType(List::class.java, Any::class.java))
|
private val moshiActionsAdapter = MoshiProvider.providesMoshi().adapter<List<Any>>(Types.newParameterizedType(List::class.java, Any::class.java))
|
||||||
|
|
||||||
@@ -33,10 +35,10 @@ internal object PushRulesMapper {
|
|||||||
|
|
||||||
fun mapContentRule(pushrule: PushRuleEntity): PushRule {
|
fun mapContentRule(pushrule: PushRuleEntity): PushRule {
|
||||||
return PushRule(
|
return PushRule(
|
||||||
actions = fromActionStr(pushrule.actionsStr),
|
actions = fromActionStr(pushrule.action_str),
|
||||||
default = pushrule.default,
|
default = pushrule.is_default,
|
||||||
enabled = pushrule.enabled,
|
enabled = pushrule.is_enabled,
|
||||||
ruleId = pushrule.ruleId,
|
ruleId = pushrule.rule_id,
|
||||||
conditions = listOf(
|
conditions = listOf(
|
||||||
PushCondition(Condition.Kind.EventMatch.value, "content.body", pushrule.pattern)
|
PushCondition(Condition.Kind.EventMatch.value, "content.body", pushrule.pattern)
|
||||||
)
|
)
|
||||||
@@ -44,58 +46,60 @@ internal object PushRulesMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fromActionStr(actionsStr: String?): List<Any> {
|
private fun fromActionStr(actionsStr: String?): List<Any> {
|
||||||
try {
|
return try {
|
||||||
return actionsStr?.let { moshiActionsAdapter.fromJson(it) } ?: emptyList()
|
actionsStr?.let { moshiActionsAdapter.fromJson(it) } ?: emptyList()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Timber.e(e, "## failed to map push rule actions <$actionsStr>")
|
Timber.e(e, "## failed to map push rule actions <$actionsStr>")
|
||||||
return emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mapRoomRule(pushrule: PushRuleEntity): PushRule {
|
fun mapRoomRule(pushrule: PushRuleEntity): PushRule {
|
||||||
return PushRule(
|
return PushRule(
|
||||||
actions = fromActionStr(pushrule.actionsStr),
|
actions = fromActionStr(pushrule.action_str),
|
||||||
default = pushrule.default,
|
default = pushrule.is_default,
|
||||||
enabled = pushrule.enabled,
|
enabled = pushrule.is_enabled,
|
||||||
ruleId = pushrule.ruleId,
|
ruleId = pushrule.rule_id,
|
||||||
conditions = listOf(
|
conditions = listOf(
|
||||||
PushCondition(Condition.Kind.EventMatch.value, "room_id", pushrule.ruleId)
|
PushCondition(Condition.Kind.EventMatch.value, "room_id", pushrule.rule_id)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mapSenderRule(pushrule: PushRuleEntity): PushRule {
|
fun mapSenderRule(pushrule: PushRuleEntity): PushRule {
|
||||||
return PushRule(
|
return PushRule(
|
||||||
actions = fromActionStr(pushrule.actionsStr),
|
actions = fromActionStr(pushrule.action_str),
|
||||||
default = pushrule.default,
|
default = pushrule.is_default,
|
||||||
enabled = pushrule.enabled,
|
enabled = pushrule.is_enabled,
|
||||||
ruleId = pushrule.ruleId,
|
ruleId = pushrule.rule_id,
|
||||||
conditions = listOf(
|
conditions = listOf(
|
||||||
PushCondition(Condition.Kind.EventMatch.value, "user_id", pushrule.ruleId)
|
PushCondition(Condition.Kind.EventMatch.value, "user_id", pushrule.rule_id)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun map(pushrule: PushRuleEntity): PushRule {
|
fun map(pushrule: PushRuleEntity, conditions: List<GetPushConditions>): PushRule {
|
||||||
return PushRule(
|
return PushRule(
|
||||||
actions = fromActionStr(pushrule.actionsStr),
|
actions = fromActionStr(pushrule.action_str),
|
||||||
default = pushrule.default,
|
default = pushrule.is_default,
|
||||||
enabled = pushrule.enabled,
|
enabled = pushrule.is_enabled,
|
||||||
ruleId = pushrule.ruleId,
|
ruleId = pushrule.rule_id,
|
||||||
conditions = pushrule.conditions?.map { PushConditionMapper.map(it) }
|
conditions = conditions.map {
|
||||||
|
pushConditionMapper.map(it)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun map(pushRule: PushRule): PushRuleEntity {
|
fun map(scope: String, kind: RuleKind, pushRule: PushRule): PushRuleEntity {
|
||||||
return PushRuleEntity(
|
return PushRuleEntity.Impl(
|
||||||
actionsStr = moshiActionsAdapter.toJson(pushRule.actions),
|
action_str = moshiActionsAdapter.toJson(pushRule.actions),
|
||||||
default = pushRule.default ?: false,
|
is_default = pushRule.default ?: false,
|
||||||
enabled = pushRule.enabled,
|
is_enabled = pushRule.enabled,
|
||||||
ruleId = pushRule.ruleId,
|
rule_id = pushRule.ruleId,
|
||||||
pattern = pushRule.pattern,
|
pattern = pushRule.pattern,
|
||||||
conditions = pushRule.conditions?.let {
|
scope = scope,
|
||||||
RealmList(*pushRule.conditions.map { PushConditionMapper.map(it) }.toTypedArray())
|
kind = kind.name
|
||||||
} ?: RealmList()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -17,44 +17,48 @@ package im.vector.matrix.android.internal.database.mapper
|
|||||||
|
|
||||||
import im.vector.matrix.android.api.session.pushers.Pusher
|
import im.vector.matrix.android.api.session.pushers.Pusher
|
||||||
import im.vector.matrix.android.api.session.pushers.PusherData
|
import im.vector.matrix.android.api.session.pushers.PusherData
|
||||||
import im.vector.matrix.android.internal.database.model.PusherDataEntity
|
import im.vector.matrix.android.api.session.pushers.PusherState
|
||||||
import im.vector.matrix.android.internal.database.model.PusherEntity
|
|
||||||
import im.vector.matrix.android.internal.session.pushers.JsonPusher
|
import im.vector.matrix.android.internal.session.pushers.JsonPusher
|
||||||
|
import im.vector.matrix.sqldelight.session.PusherEntity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal object PushersMapper {
|
internal class PushersMapper @Inject constructor() {
|
||||||
|
|
||||||
fun map(pushEntity: PusherEntity): Pusher {
|
fun map(push_key: String,
|
||||||
|
kind: String?,
|
||||||
|
app_id: String,
|
||||||
|
app_display_name: String?,
|
||||||
|
device_display_name: String?,
|
||||||
|
profile_tag: String?,
|
||||||
|
lang: String?,
|
||||||
|
data_url: String?,
|
||||||
|
data_format: String?,
|
||||||
|
state: String): Pusher {
|
||||||
return Pusher(
|
return Pusher(
|
||||||
pushKey = pushEntity.pushKey,
|
pushKey = push_key,
|
||||||
kind = pushEntity.kind ?: "",
|
kind = kind ?: "",
|
||||||
appId = pushEntity.appId,
|
appId = app_id,
|
||||||
appDisplayName = pushEntity.appDisplayName,
|
appDisplayName = app_display_name,
|
||||||
deviceDisplayName = pushEntity.deviceDisplayName,
|
deviceDisplayName = device_display_name,
|
||||||
profileTag = pushEntity.profileTag,
|
profileTag = profile_tag,
|
||||||
lang = pushEntity.lang,
|
lang = lang,
|
||||||
data = PusherData(pushEntity.data?.url, pushEntity.data?.format),
|
data = PusherData(data_url, data_format),
|
||||||
state = pushEntity.state
|
state = PusherState.valueOf(state)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun map(pusher: JsonPusher): PusherEntity {
|
fun map(pusher: JsonPusher, state: PusherState): PusherEntity {
|
||||||
return PusherEntity(
|
return PusherEntity.Impl(
|
||||||
pushKey = pusher.pushKey,
|
push_key = pusher.pushKey,
|
||||||
kind = pusher.kind,
|
kind = pusher.kind,
|
||||||
appId = pusher.appId,
|
app_id = pusher.appId,
|
||||||
appDisplayName = pusher.appDisplayName,
|
app_display_name = pusher.appDisplayName,
|
||||||
deviceDisplayName = pusher.deviceDisplayName,
|
device_display_name = pusher.deviceDisplayName,
|
||||||
profileTag = pusher.profileTag,
|
profile_tag = pusher.profileTag,
|
||||||
lang = pusher.lang,
|
lang = pusher.lang,
|
||||||
data = PusherDataEntity(pusher.data?.url, pusher.data?.format)
|
data_url = pusher.data?.url,
|
||||||
|
data_format = pusher.data?.format,
|
||||||
|
state = state.name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun PusherEntity.asDomain(): Pusher {
|
|
||||||
return PushersMapper.map(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun JsonPusher.toEntity(): PusherEntity {
|
|
||||||
return PushersMapper.map(this)
|
|
||||||
}
|
|
||||||
|
@@ -0,0 +1,19 @@
|
|||||||
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ReadReceiptMapper @Inject constructor() {
|
||||||
|
|
||||||
|
fun map(user_id: String,
|
||||||
|
display_name: String?,
|
||||||
|
avatar_url: String?,
|
||||||
|
origin_server_ts: Double
|
||||||
|
): ReadReceipt {
|
||||||
|
val user = User(user_id, display_name, avatar_url)
|
||||||
|
return ReadReceipt(user, origin_server_ts.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -18,18 +18,16 @@ package im.vector.matrix.android.internal.database.mapper
|
|||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.UserEntity
|
import im.vector.matrix.android.internal.di.RealmSessionDatabase
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class ReadReceiptsSummaryMapper @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration) {
|
internal class ReadReceiptsSummaryMapper @Inject constructor(@RealmSessionDatabase private val realmConfiguration: RealmConfiguration) {
|
||||||
|
|
||||||
fun map(readReceiptsSummaryEntity: ReadReceiptsSummaryEntity?): List<ReadReceipt> {
|
fun map(readReceiptsSummaryEntity: ReadReceiptsSummaryEntity?): List<ReadReceipt> {
|
||||||
if (readReceiptsSummaryEntity == null) {
|
return emptyList()
|
||||||
return emptyList()
|
/*if (readReceiptsSummaryEntity == null) {
|
||||||
|
|
||||||
}
|
}
|
||||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
val readReceipts = readReceiptsSummaryEntity.readReceipts
|
val readReceipts = readReceiptsSummaryEntity.readReceipts
|
||||||
@@ -39,6 +37,6 @@ internal class ReadReceiptsSummaryMapper @Inject constructor(@SessionDatabase pr
|
|||||||
?: return@mapNotNull null
|
?: return@mapNotNull null
|
||||||
ReadReceipt(user.asDomain(), it.originServerTs.toLong())
|
ReadReceipt(user.asDomain(), it.originServerTs.toLong())
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,10 +16,13 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
|
||||||
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
|
||||||
|
import im.vector.matrix.sqldelight.session.Memberships
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal object RoomMemberSummaryMapper {
|
internal class RoomMemberSummaryMapper @Inject constructor() {
|
||||||
|
|
||||||
fun map(roomMemberSummaryEntity: RoomMemberSummaryEntity): RoomMemberSummary {
|
fun map(roomMemberSummaryEntity: RoomMemberSummaryEntity): RoomMemberSummary {
|
||||||
return RoomMemberSummary(
|
return RoomMemberSummary(
|
||||||
@@ -29,8 +32,31 @@ internal object RoomMemberSummaryMapper {
|
|||||||
membership = roomMemberSummaryEntity.membership
|
membership = roomMemberSummaryEntity.membership
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun map(roomMemberSummaryEntity: im.vector.matrix.sqldelight.session.RoomMemberSummaryEntity): RoomMemberSummary {
|
||||||
|
return RoomMemberSummary(
|
||||||
|
userId = roomMemberSummaryEntity.user_id,
|
||||||
|
avatarUrl = roomMemberSummaryEntity.avatar_url,
|
||||||
|
displayName = roomMemberSummaryEntity.display_name,
|
||||||
|
membership = Membership.valueOf(roomMemberSummaryEntity.membership.name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun map(room_id: String, user_id: String, display_name: String?, avatar_url: String?, membership: Memberships): RoomMemberSummary {
|
||||||
|
return RoomMemberSummary(
|
||||||
|
userId = user_id,
|
||||||
|
membership = membership.map(),
|
||||||
|
displayName = display_name,
|
||||||
|
avatarUrl = avatar_url
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun im.vector.matrix.sqldelight.session.RoomMemberSummaryEntity.asDomain(): RoomMemberSummary {
|
||||||
|
return RoomMemberSummaryMapper().map(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun RoomMemberSummaryEntity.asDomain(): RoomMemberSummary {
|
internal fun RoomMemberSummaryEntity.asDomain(): RoomMemberSummary {
|
||||||
return RoomMemberSummaryMapper.map(this)
|
return RoomMemberSummaryMapper().map(this)
|
||||||
}
|
}
|
||||||
|
@@ -16,47 +16,82 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.VersioningState
|
||||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.sqldelight.session.RoomSummaryWithTimeline
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomSummaryMapper @Inject constructor(private val timelineEventMapper: TimelineEventMapper) {
|
internal class RoomSummaryMapper @Inject constructor() {
|
||||||
|
|
||||||
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
|
||||||
val tags = roomSummaryEntity.tags.map {
|
|
||||||
RoomTag(it.tagName, it.tagOrder)
|
|
||||||
}
|
|
||||||
|
|
||||||
val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
|
|
||||||
timelineEventMapper.map(it, buildReadReceipts = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
fun map(roomSummaryWithTimeline: RoomSummaryWithTimeline, tags: List<RoomTag>): RoomSummary {
|
||||||
return RoomSummary(
|
return RoomSummary(
|
||||||
roomId = roomSummaryEntity.roomId,
|
roomId = roomSummaryWithTimeline.summary_room_id,
|
||||||
displayName = roomSummaryEntity.displayName ?: "",
|
displayName = roomSummaryWithTimeline.display_name ?: "",
|
||||||
topic = roomSummaryEntity.topic ?: "",
|
topic = roomSummaryWithTimeline.topic ?: "",
|
||||||
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
avatarUrl = roomSummaryWithTimeline.avatar_url ?: "",
|
||||||
isDirect = roomSummaryEntity.isDirect,
|
isDirect = roomSummaryWithTimeline.is_direct,
|
||||||
latestPreviewableEvent = latestEvent,
|
latestPreviewableEvent = createTimelineEvent(roomSummaryWithTimeline),
|
||||||
joinedMembersCount = roomSummaryEntity.joinedMembersCount,
|
joinedMembersCount = roomSummaryWithTimeline.joined_members_count,
|
||||||
invitedMembersCount = roomSummaryEntity.invitedMembersCount,
|
invitedMembersCount = roomSummaryWithTimeline.invited_members_count,
|
||||||
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
|
otherMemberIds = emptyList(),
|
||||||
highlightCount = roomSummaryEntity.highlightCount,
|
highlightCount = roomSummaryWithTimeline.highlight_count,
|
||||||
notificationCount = roomSummaryEntity.notificationCount,
|
notificationCount = roomSummaryWithTimeline.notification_count,
|
||||||
hasUnreadMessages = roomSummaryEntity.hasUnreadMessages,
|
hasUnreadMessages = roomSummaryWithTimeline.has_unread,
|
||||||
|
versioningState = VersioningState.valueOf(roomSummaryWithTimeline.versioning_state),
|
||||||
tags = tags,
|
tags = tags,
|
||||||
membership = roomSummaryEntity.membership,
|
membership = roomSummaryWithTimeline.membership.map(),
|
||||||
versioningState = roomSummaryEntity.versioningState,
|
readMarkerId = roomSummaryWithTimeline.read_marker_id,
|
||||||
readMarkerId = roomSummaryEntity.readMarkerId,
|
userDrafts = emptyList(),
|
||||||
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(),
|
canonicalAlias = roomSummaryWithTimeline.canonical_alias,
|
||||||
canonicalAlias = roomSummaryEntity.canonicalAlias,
|
inviterId = roomSummaryWithTimeline.inviter_id,
|
||||||
aliases = roomSummaryEntity.aliases.toList(),
|
isEncrypted = roomSummaryWithTimeline.is_encrypted,
|
||||||
isEncrypted = roomSummaryEntity.isEncrypted,
|
typingRoomMemberIds = emptyList(),//roomSummaryEntity.typingUserIds.toList(),
|
||||||
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(),
|
breadcrumbsIndex = roomSummaryWithTimeline.breadcrumb_index ?: -1,
|
||||||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
|
roomEncryptionTrustLevel = roomSummaryWithTimeline.room_encryption_trust_level?.let {
|
||||||
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,
|
try {
|
||||||
inviterId = roomSummaryEntity.inviterId
|
RoomEncryptionTrustLevel.valueOf(it)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createTimelineEvent(roomSummaryWithTimeline: RoomSummaryWithTimeline): TimelineEvent? {
|
||||||
|
val type = roomSummaryWithTimeline.type ?: return null
|
||||||
|
val roomId = roomSummaryWithTimeline.summary_room_id
|
||||||
|
val eventId = roomSummaryWithTimeline.event_id ?: return null
|
||||||
|
val displayIndex = roomSummaryWithTimeline.display_index ?: return null
|
||||||
|
val localId = roomSummaryWithTimeline.local_id ?: return null
|
||||||
|
val event = Event(
|
||||||
|
type = type,
|
||||||
|
roomId = roomId,
|
||||||
|
eventId = eventId,
|
||||||
|
content = ContentMapper.map(roomSummaryWithTimeline.content),
|
||||||
|
prevContent = ContentMapper.map(roomSummaryWithTimeline.prev_content),
|
||||||
|
originServerTs = roomSummaryWithTimeline.origin_server_ts,
|
||||||
|
senderId = roomSummaryWithTimeline.sender_id,
|
||||||
|
redacts = roomSummaryWithTimeline.redacts,
|
||||||
|
stateKey = roomSummaryWithTimeline.state_key,
|
||||||
|
unsignedData = null
|
||||||
|
).setDecryptionValues(roomSummaryWithTimeline.decryption_result_json, roomSummaryWithTimeline.decryption_error_code)
|
||||||
|
|
||||||
|
|
||||||
|
return TimelineEvent(
|
||||||
|
root = event,
|
||||||
|
eventId = eventId,
|
||||||
|
annotations = null,
|
||||||
|
displayIndex = displayIndex,
|
||||||
|
isUniqueDisplayName = roomSummaryWithTimeline.is_unique_display_name ?: false,
|
||||||
|
localId = localId,
|
||||||
|
readReceipts = emptyList(),
|
||||||
|
senderAvatar = roomSummaryWithTimeline.sender_avatar,
|
||||||
|
senderName = roomSummaryWithTimeline.sender_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -16,40 +16,87 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
|
import com.squareup.sqldelight.db.SqlCursor
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) {
|
internal class TimelineEventMapper @Inject constructor() {
|
||||||
|
|
||||||
fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true, correctedReadReceipts: List<ReadReceipt>? = null): TimelineEvent {
|
fun map(cursor: SqlCursor): TimelineEvent = map(
|
||||||
val readReceipts = if (buildReadReceipts) {
|
cursor.getLong(0)!!,
|
||||||
correctedReadReceipts ?: timelineEventEntity.readReceipts
|
cursor.getLong(1)!!,
|
||||||
?.let {
|
cursor.getLong(2)!!.toInt(),
|
||||||
readReceiptsSummaryMapper.map(it)
|
cursor.getString(3),
|
||||||
}
|
cursor.getString(4),
|
||||||
} else {
|
cursor.getLong(5)!! == 1L,
|
||||||
null
|
cursor.getString(6)!!,
|
||||||
|
cursor.getString(7)!!,
|
||||||
|
cursor.getString(8),
|
||||||
|
cursor.getString(9),
|
||||||
|
cursor.getString(10),
|
||||||
|
cursor.getString(11)!!,
|
||||||
|
cursor.getString(12)!!,
|
||||||
|
cursor.getLong(13),
|
||||||
|
cursor.getString(14),
|
||||||
|
cursor.getString(15),
|
||||||
|
cursor.getString(16),
|
||||||
|
cursor.getLong(17),
|
||||||
|
cursor.getLong(18),
|
||||||
|
cursor.getString(19),
|
||||||
|
cursor.getString(20)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun map(local_id: Long,
|
||||||
|
chunk_id: Long,
|
||||||
|
display_index: Int,
|
||||||
|
sender_name: String?,
|
||||||
|
sender_avatar: String?,
|
||||||
|
is_unique_display_name: Boolean,
|
||||||
|
event_id: String,
|
||||||
|
room_id: String,
|
||||||
|
content: String?,
|
||||||
|
prev_content: String?,
|
||||||
|
state_key: String?,
|
||||||
|
send_state: String,
|
||||||
|
type: String,
|
||||||
|
origin_server_ts: Long?,
|
||||||
|
sender_id: String?,
|
||||||
|
unsigned_data: String?,
|
||||||
|
redacts: String?,
|
||||||
|
age: Long?,
|
||||||
|
age_local_ts: Long?,
|
||||||
|
decryption_result_json: String?,
|
||||||
|
decryption_error_code: String?): TimelineEvent {
|
||||||
|
|
||||||
|
val event = Event(
|
||||||
|
type = type,
|
||||||
|
roomId = room_id,
|
||||||
|
eventId = event_id,
|
||||||
|
content = ContentMapper.map(content),
|
||||||
|
prevContent = ContentMapper.map(prev_content),
|
||||||
|
originServerTs = origin_server_ts,
|
||||||
|
senderId = sender_id,
|
||||||
|
redacts = redacts,
|
||||||
|
stateKey = state_key,
|
||||||
|
unsignedData = UnsignedDataMapper.mapFromString(unsigned_data)
|
||||||
|
).also {
|
||||||
|
it.ageLocalTs = age_local_ts
|
||||||
|
it.sendState = SendState.valueOf(send_state)
|
||||||
|
it.setDecryptionValues(decryption_result_json, decryption_error_code)
|
||||||
}
|
}
|
||||||
return TimelineEvent(
|
return TimelineEvent(
|
||||||
root = timelineEventEntity.root?.asDomain()
|
root = event,
|
||||||
?: Event("", timelineEventEntity.eventId),
|
eventId = event_id,
|
||||||
eventId = timelineEventEntity.eventId,
|
annotations = null,
|
||||||
annotations = timelineEventEntity.annotations?.asDomain(),
|
displayIndex = display_index,
|
||||||
localId = timelineEventEntity.localId,
|
isUniqueDisplayName = is_unique_display_name,
|
||||||
displayIndex = timelineEventEntity.displayIndex,
|
localId = local_id,
|
||||||
senderName = timelineEventEntity.senderName,
|
readReceipts = emptyList(),
|
||||||
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
senderAvatar = sender_avatar,
|
||||||
senderAvatar = timelineEventEntity.senderAvatar,
|
senderName = sender_name
|
||||||
readReceipts = readReceipts
|
|
||||||
?.distinctBy {
|
|
||||||
it.user
|
|
||||||
}?.sortedByDescending {
|
|
||||||
it.originServerTs
|
|
||||||
} ?: emptyList()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,21 @@
|
|||||||
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonDataException
|
||||||
|
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
||||||
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
internal object UnsignedDataMapper {
|
||||||
|
|
||||||
|
fun mapFromString(us: String?): UnsignedData? {
|
||||||
|
return us?.takeIf { it.isNotBlank() }
|
||||||
|
?.let {
|
||||||
|
try {
|
||||||
|
MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(it)
|
||||||
|
} catch (t: JsonDataException) {
|
||||||
|
Timber.e(t, "Failed to parse UnsignedData")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -17,19 +17,30 @@
|
|||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.matrix.android.internal.database.model.UserEntity
|
import im.vector.matrix.sqldelight.session.UserEntity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal object UserMapper {
|
internal class UserMapper @Inject constructor() {
|
||||||
|
|
||||||
fun map(userEntity: UserEntity): User {
|
fun map(userEntity: UserEntity): User {
|
||||||
return User(
|
return User(
|
||||||
userEntity.userId,
|
userEntity.user_id,
|
||||||
userEntity.displayName,
|
userEntity.display_name,
|
||||||
userEntity.avatarUrl
|
userEntity.avatar_url
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun map(user_id: String,
|
||||||
|
display_name: String?,
|
||||||
|
avatar_url: String?): User {
|
||||||
|
return User(
|
||||||
|
userId = user_id,
|
||||||
|
displayName = display_name,
|
||||||
|
avatarUrl = avatar_url
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun UserEntity.asDomain(): User {
|
internal fun UserEntity.asDomain(): User {
|
||||||
return UserMapper.map(this)
|
return UserMapper().map(this)
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,6 @@ package im.vector.matrix.android.internal.database.query
|
|||||||
|
|
||||||
internal object FilterContent {
|
internal object FilterContent {
|
||||||
|
|
||||||
internal const val EDIT_TYPE = """{*"m.relates_to"*"rel_type":*"m.replace"*}"""
|
internal const val EDIT_TYPE = """ '{%"m.relates_to"%"rel_type":%"m.replace"%}' """
|
||||||
internal const val RESPONSE_TYPE = """{*"m.relates_to"*"rel_type":*"m.response"*}"""
|
internal const val RESPONSE_TYPE = """ '{%"m.relates_to"%"rel_type":%"m.response"%}' """
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,43 @@
|
|||||||
|
package im.vector.matrix.android.internal.database.repository
|
||||||
|
|
||||||
|
import com.squareup.sqldelight.runtime.coroutines.asFlow
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import im.vector.matrix.android.api.util.toOptional
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
|
import im.vector.matrix.android.internal.extensions.mapToOneOptionnal
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import im.vector.matrix.sqldelight.session.EventEntity
|
||||||
|
import im.vector.matrix.sqldelight.session.SessionDatabase
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class CurrentStateEventDataSource @Inject constructor(private val sessionDatabase: SessionDatabase,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers ) {
|
||||||
|
|
||||||
|
fun getCurrentMapped(roomId: String, type: String, stateKey: String): Event? {
|
||||||
|
val entity = sessionDatabase.stateEventQueries.getCurrentStateEvent(roomId, type = type, stateKey = stateKey).executeAsOneOrNull()
|
||||||
|
return entity?.asDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentLiveMapped(roomId: String, type: String, stateKey: String): Flow<Optional<Event>> {
|
||||||
|
return sessionDatabase.stateEventQueries
|
||||||
|
.getCurrentStateEvent(roomId, type = type, stateKey = stateKey)
|
||||||
|
.asFlow()
|
||||||
|
.map {
|
||||||
|
it.executeAsOneOrNull()?.asDomain().toOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentLive(roomId: String, type: String, stateKey: String): Flow<Optional<EventEntity>> {
|
||||||
|
return sessionDatabase.stateEventQueries
|
||||||
|
.getCurrentStateEvent(roomId, type = type, stateKey = stateKey)
|
||||||
|
.asFlow()
|
||||||
|
.mapToOneOptionnal(coroutineDispatchers.dbQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrent(roomId: String, type: String, stateKey: String): EventEntity? {
|
||||||
|
return sessionDatabase.stateEventQueries.getCurrentStateEvent(roomId, type = type, stateKey = stateKey).executeAsOneOrNull()
|
||||||
|
}
|
||||||
|
}
|
@@ -20,12 +20,12 @@ import javax.inject.Qualifier
|
|||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class AuthDatabase
|
annotation class RealmAuthDatabase
|
||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class SessionDatabase
|
annotation class RealmSessionDatabase
|
||||||
|
|
||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class CryptoDatabase
|
annotation class RealmCryptoDatabase
|
||||||
|
@@ -20,6 +20,7 @@ import android.content.Context
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
import im.vector.matrix.android.internal.concurrency.newNamedSingleThreadExecutor
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -36,11 +37,14 @@ internal object MatrixModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@MatrixScope
|
@MatrixScope
|
||||||
fun providesMatrixCoroutineDispatchers(): MatrixCoroutineDispatchers {
|
fun providesMatrixCoroutineDispatchers(): MatrixCoroutineDispatchers {
|
||||||
return MatrixCoroutineDispatchers(io = Dispatchers.IO,
|
return MatrixCoroutineDispatchers(
|
||||||
|
dbTransaction = newNamedSingleThreadExecutor("db_transaction").asCoroutineDispatcher(),
|
||||||
|
dbQuery = newNamedSingleThreadExecutor("db_query").asCoroutineDispatcher(),
|
||||||
|
io = Dispatchers.IO,
|
||||||
computation = Dispatchers.Default,
|
computation = Dispatchers.Default,
|
||||||
main = Dispatchers.Main,
|
main = Dispatchers.Main,
|
||||||
crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(),
|
crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(),
|
||||||
dmVerif = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
dmVerif = newNamedSingleThreadExecutor("dm_verif").asCoroutineDispatcher()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
package im.vector.matrix.android.internal.extensions
|
||||||
|
|
||||||
|
import com.squareup.sqldelight.Query
|
||||||
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import im.vector.matrix.android.api.util.toOptional
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
fun <T : Any> Flow<Query<T>>.mapToOneOptionnal(context: CoroutineContext): Flow<Optional<T>> {
|
||||||
|
return map {
|
||||||
|
withContext(context) {
|
||||||
|
it.executeAsOneOrNull().toOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user