mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 08:12:46 +02:00
Compare commits
18 Commits
v1.3.5
...
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
|
||||
|
||||
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 ✨:
|
||||
- Change password (#528)
|
||||
- Cross-Signing | Support SSSS secret sharing (#944)
|
||||
- Cross-Signing | Verify new session from existing session (#1134)
|
||||
- Cross-Signing | Bootstraping cross signing with 4S from mobile (#985)
|
||||
- Save media files to Gallery (#973)
|
||||
|
||||
|
||||
Improvements 🙌:
|
||||
- Verification DM / Handle concurrent .start after .ready (#794)
|
||||
- Reimplementation of multiple attachment picker
|
||||
- Cross-Signing | Update Shield Logic for DM (#963)
|
||||
- Cross-Signing | Complete security new session design update (#1135)
|
||||
- Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201)
|
||||
- Cross-Signing | Gossip key backup recovery key (#1200)
|
||||
- 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-Sign | QR code scan confirmation screens design update (#1187)
|
||||
- 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 🐛:
|
||||
- Fix summary notification staying after "mark as read"
|
||||
- Missing avatar/displayname after verification request message (#841)
|
||||
- Crypto | RiotX sometimes rotate the current device keys (#1170)
|
||||
- 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)
|
||||
- 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)
|
||||
- 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 🗣:
|
||||
-
|
||||
|
||||
SDK API changes ⚠️:
|
||||
- Increase targetSdkVersion to 29
|
||||
- Implementation of SqlCryptoStore on top of SQLDelight
|
||||
|
||||
Build 🧱:
|
||||
- Compile with Android SDK 29 (Android Q)
|
||||
-
|
||||
|
||||
Other changes:
|
||||
- Add a setting to prevent screenshots of the application, disabled by default (#1027)
|
||||
- Increase File Logger capacities ( + use dev log preferences)
|
||||
|
||||
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 clear cache (#408) and Logout (#205)
|
||||
- Fix `(edited)` link can be copied to clipboard (#402)
|
||||
- KeyBackup / SSSS | Should get the key from SSSS instead of asking recovery Key (#1163)
|
||||
|
||||
Build:
|
||||
- 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.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
|
||||
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
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
@@ -53,7 +53,7 @@ allprojects {
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||
// 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 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.3.3'
|
||||
|
||||
// Paging
|
||||
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.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import im.vector.matrix.android.api.util.toOptional
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import kotlinx.coroutines.rx2.asObservable
|
||||
|
||||
class RxRoom(private val room: Room) {
|
||||
|
||||
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
|
||||
return room.getRoomSummaryLive()
|
||||
.asObservable()
|
||||
.startWithCallable { room.roomSummary().toOptional() }
|
||||
return room.getRoomSummaryLive().asObservable()
|
||||
}
|
||||
|
||||
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
|
||||
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()
|
||||
.startWithCallable {
|
||||
room.getEventAnnotationsSummary(eventId).toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
|
||||
return room.getTimeLineEventLive(eventId).asObservable()
|
||||
.startWithCallable {
|
||||
room.getTimeLineEvent(eventId).toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveStateEvent(eventType: String, stateKey: String): Observable<Optional<Event>> {
|
||||
return room.getStateEventLive(eventType, stateKey).asObservable()
|
||||
.startWithCallable {
|
||||
room.getStateEvent(eventType, stateKey).toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveReadMarker(): Observable<Optional<String>> {
|
||||
@@ -93,7 +83,7 @@ class RxRoom(private val room: Room) {
|
||||
}
|
||||
|
||||
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.pushers.Pusher
|
||||
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.create.CreateRoomParams
|
||||
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 io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import kotlinx.coroutines.rx2.asObservable
|
||||
|
||||
class RxSession(private val session: Session) {
|
||||
|
||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
|
||||
return session.getRoomSummariesLive(queryParams).asObservable()
|
||||
.startWithCallable {
|
||||
session.getRoomSummaries(queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
|
||||
return session.getGroupSummariesLive(queryParams).asObservable()
|
||||
.startWithCallable {
|
||||
session.getGroupSummaries(queryParams)
|
||||
}
|
||||
}
|
||||
|
||||
fun liveBreadcrumbs(): Observable<List<RoomSummary>> {
|
||||
fun liveBreadcrumbs(): Observable<List<Breadcrumb>> {
|
||||
return session.getBreadcrumbsLive().asObservable()
|
||||
.startWithCallable {
|
||||
session.getBreadcrumbs()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveSyncState(): Observable<SyncState> {
|
||||
@@ -68,9 +61,6 @@ class RxSession(private val session: Session) {
|
||||
|
||||
fun liveUser(userId: String): Observable<Optional<User>> {
|
||||
return session.getUserLive(userId).asObservable()
|
||||
.startWithCallable {
|
||||
session.getUser(userId).toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveUsers(): Observable<List<User>> {
|
||||
@@ -125,9 +115,6 @@ class RxSession(private val session: Session) {
|
||||
|
||||
fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
|
||||
return session.getLiveAccountDataEvents(types).asObservable()
|
||||
.startWithCallable {
|
||||
session.getAccountDataEvents(types)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -71,6 +71,10 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst 'META-INF/kotlinx-coroutines-core.kotlin_module'
|
||||
}
|
||||
}
|
||||
|
||||
static def gitRevision() {
|
||||
@@ -125,6 +129,11 @@ dependencies {
|
||||
// Database
|
||||
implementation 'com.github.Zhuinden:realm-monarchy:0.5.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
|
||||
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 kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main,
|
||||
Executors.newSingleThreadExecutor().asCoroutineDispatcher())
|
||||
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main, Main, Main)
|
||||
|
@@ -18,14 +18,18 @@ package im.vector.matrix.android.internal.crypto.verification.qrcode
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
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.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.CryptoTestHelper
|
||||
import im.vector.matrix.android.common.TestConstants
|
||||
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.junit.Assert.assertEquals
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -229,4 +233,46 @@ class VerificationTest : InstrumentedTest {
|
||||
|
||||
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
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
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.Optional
|
||||
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AccountDataService {
|
||||
/**
|
||||
@@ -32,7 +32,7 @@ interface AccountDataService {
|
||||
/**
|
||||
* 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
|
||||
@@ -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
|
||||
*/
|
||||
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
|
||||
|
@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.group
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
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.
|
||||
@@ -48,5 +49,5 @@ interface GroupService {
|
||||
* Get a live list of group summaries. This list is refreshed as soon as the data changes.
|
||||
* @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(
|
||||
val groupId: String,
|
||||
val membership: Membership,
|
||||
val displayName: String = "",
|
||||
val shortDescription: String = "",
|
||||
val avatarUrl: String = "",
|
||||
val roomIds: List<String> = emptyList(),
|
||||
val userIds: List<String> = emptyList()
|
||||
val displayName: String? = null,
|
||||
val shortDescription: String? = null,
|
||||
val avatarUrl: String? = null
|
||||
)
|
||||
|
@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.pushers
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.util.UUID
|
||||
|
||||
interface PushersService {
|
||||
@@ -72,9 +73,9 @@ interface PushersService {
|
||||
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
|
||||
|
@@ -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.typing.TypingService
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* This interface defines methods to interact within a room.
|
||||
@@ -56,7 +57,7 @@ interface Room :
|
||||
* A live [RoomSummary] associated with the 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
|
||||
|
@@ -18,10 +18,12 @@ package im.vector.matrix.android.api.session.room
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
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.create.CreateRoomParams
|
||||
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 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.
|
||||
* @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
|
||||
* @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
|
||||
* @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.
|
||||
@@ -102,5 +104,5 @@ interface RoomService {
|
||||
searchOnServer: Boolean,
|
||||
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]
|
||||
*/
|
||||
data class RoomSummaryQueryParams(
|
||||
val fromGroupId: String?,
|
||||
val displayName: QueryStringValue,
|
||||
val canonicalAlias: QueryStringValue,
|
||||
val memberships: List<Membership>
|
||||
@@ -35,11 +36,13 @@ data class RoomSummaryQueryParams(
|
||||
|
||||
class Builder {
|
||||
|
||||
var fromGroupId: String? = null
|
||||
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
|
||||
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
|
||||
var memberships: List<Membership> = Membership.all()
|
||||
|
||||
fun build() = RoomSummaryQueryParams(
|
||||
fromGroupId = fromGroupId,
|
||||
displayName = displayName,
|
||||
canonicalAlias = canonicalAlias,
|
||||
memberships = memberships
|
||||
|
@@ -20,6 +20,8 @@ import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
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.Optional
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* 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?
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param queryParams the params to query for
|
||||
@@ -50,9 +60,10 @@ interface MembershipService {
|
||||
/**
|
||||
* Return all the roomMembers of the room filtered by memberships
|
||||
* @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
|
||||
|
||||
|
@@ -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 avatarUrl: String = "",
|
||||
val canonicalAlias: String? = null,
|
||||
val aliases: List<String> = emptyList(),
|
||||
val isDirect: Boolean = false,
|
||||
val joinedMembersCount: 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.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* In some cases, events may wish to reference other events.
|
||||
@@ -113,12 +114,12 @@ interface RelationService {
|
||||
* @param eventId the eventId to look for EventAnnotationsSummary
|
||||
* @return the EventAnnotationsSummary found
|
||||
*/
|
||||
fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary?
|
||||
fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary
|
||||
|
||||
/**
|
||||
* Get a LiveData of EventAnnotationsSummary for the specified eventId
|
||||
* @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
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface RoomPushRuleService {
|
||||
|
||||
fun getLiveRoomNotificationState(): LiveData<RoomNotificationState>
|
||||
fun getRoomNotificationStateLive(): Flow<RoomNotificationState>
|
||||
|
||||
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.session.room.model.ReadReceipt
|
||||
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.
|
||||
@@ -55,16 +56,16 @@ interface ReadService {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
fun getMyReadReceiptLive(): LiveData<Optional<String>>
|
||||
fun getMyReadReceiptLive(): Flow<Optional<String>>
|
||||
|
||||
/**
|
||||
* Returns a live list of read receipts for a given 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
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface DraftService {
|
||||
|
||||
@@ -36,5 +36,5 @@ interface DraftService {
|
||||
* 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
|
||||
*/
|
||||
fun getDraftsLive(): LiveData<List<UserDraft>>
|
||||
fun getDraftsLive(): Flow<List<UserDraft>>
|
||||
}
|
||||
|
@@ -16,10 +16,10 @@
|
||||
|
||||
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.session.events.model.Event
|
||||
import im.vector.matrix.android.api.util.Optional
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface StateService {
|
||||
|
||||
@@ -30,5 +30,5 @@ interface StateService {
|
||||
|
||||
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
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
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.
|
||||
@@ -38,5 +38,5 @@ interface TimelineService {
|
||||
|
||||
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.util.Cancelable
|
||||
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.
|
||||
@@ -48,27 +49,27 @@ interface UserService {
|
||||
/**
|
||||
* Observe a live user from a userId
|
||||
* @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
|
||||
* @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.
|
||||
* @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
|
||||
*/
|
||||
fun getIgnoredUsersLive(): LiveData<List<User>>
|
||||
fun getIgnoredUsersLive(): Flow<List<User>>
|
||||
|
||||
/**
|
||||
* Ignore users
|
||||
|
@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.util
|
||||
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
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.RoomSummary
|
||||
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 Breadcrumb.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
||||
|
||||
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
|
||||
|
||||
// If no name is available, use room alias as Riot-Web does
|
||||
|
@@ -17,16 +17,19 @@
|
||||
package im.vector.matrix.android.internal.auth
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.sqldelight.android.AndroidSqliteDriver
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||
import im.vector.matrix.android.internal.auth.db.AuthRealmMigration
|
||||
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
||||
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
|
||||
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
||||
import im.vector.matrix.android.internal.auth.realm.AuthRealmMigration
|
||||
import im.vector.matrix.android.internal.auth.realm.AuthRealmModule
|
||||
import im.vector.matrix.android.internal.auth.sqlite.AuthSchema
|
||||
import im.vector.matrix.android.internal.auth.sqlite.SqlitePendingSessionStore
|
||||
import im.vector.matrix.android.internal.auth.sqlite.SqliteSessionParamsStore
|
||||
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 java.io.File
|
||||
|
||||
@@ -39,16 +42,16 @@ internal abstract class AuthModule {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@AuthDatabase
|
||||
fun providesRealmConfiguration(context: Context, realmKeysUtils: RealmKeysUtils): RealmConfiguration {
|
||||
@im.vector.matrix.android.internal.di.RealmAuthDatabase
|
||||
@MatrixScope
|
||||
fun providesRealmConfiguration(context: Context, databaseKeysUtils: DatabaseKeysUtils): RealmConfiguration {
|
||||
val old = File(context.filesDir, "matrix-sdk-auth")
|
||||
if (old.exists()) {
|
||||
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
|
||||
}
|
||||
|
||||
return RealmConfiguration.Builder()
|
||||
.apply {
|
||||
realmKeysUtils.configureEncryption(this, DB_ALIAS)
|
||||
databaseKeysUtils.configureEncryption(this, DB_ALIAS)
|
||||
}
|
||||
.name("matrix-sdk-auth.realm")
|
||||
.modules(AuthRealmModule())
|
||||
@@ -56,13 +59,22 @@ internal abstract class AuthModule {
|
||||
.migration(AuthRealmMigration)
|
||||
.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
|
||||
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
|
||||
abstract fun bindSessionParamsStore(sessionParamsStore: SqliteSessionParamsStore): SessionParamsStore
|
||||
|
||||
@Binds
|
||||
abstract fun bindPendingSessionStore(pendingSessionStore: RealmPendingSessionStore): PendingSessionStore
|
||||
abstract fun bindPendingSessionStore(pendingSessionStore: SqlitePendingSessionStore): PendingSessionStore
|
||||
|
||||
@Binds
|
||||
abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService
|
||||
|
@@ -20,13 +20,7 @@ import android.net.Uri
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
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.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.data.*
|
||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||
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.auth.data.LoginFlowResponse
|
||||
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.registration.DefaultRegistrationWizard
|
||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
@@ -114,7 +108,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
}
|
||||
|
||||
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
return withContext(coroutineDispatchers.computation) {
|
||||
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||
|
||||
// First check the homeserver version
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
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
|
||||
|
@@ -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.data.PasswordLoginParams
|
||||
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.AddThreePidRegistrationResponse
|
||||
import im.vector.matrix.android.internal.auth.registration.RegisterAddThreePidTask
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* 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.sessionId
|
@@ -14,7 +14,7 @@
|
||||
* 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
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.auth.db
|
||||
package im.vector.matrix.android.internal.auth.realm
|
||||
|
||||
import io.realm.RealmObject
|
||||
|
@@ -14,11 +14,12 @@
|
||||
* 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 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 javax.inject.Inject
|
||||
|
@@ -14,17 +14,18 @@
|
||||
* 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.registration.PendingSessionData
|
||||
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.RealmConfiguration
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RealmPendingSessionStore @Inject constructor(private val mapper: PendingSessionMapper,
|
||||
@AuthDatabase
|
||||
@RealmAuthDatabase
|
||||
private val realmConfiguration: RealmConfiguration
|
||||
) : PendingSessionStore {
|
||||
|
@@ -14,14 +14,14 @@
|
||||
* 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.SessionParams
|
||||
import im.vector.matrix.android.api.auth.data.sessionId
|
||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||
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.RealmConfiguration
|
||||
import io.realm.exceptions.RealmPrimaryKeyConstraintException
|
||||
@@ -29,7 +29,7 @@ import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper,
|
||||
@AuthDatabase
|
||||
@RealmAuthDatabase
|
||||
private val realmConfiguration: RealmConfiguration
|
||||
) : SessionParamsStore {
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* 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.annotations.PrimaryKey
|
@@ -14,8 +14,9 @@
|
||||
* 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 im.vector.matrix.android.api.auth.data.Credentials
|
||||
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 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? {
|
||||
if (entity == 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.SessionCreator
|
||||
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.task.launchToCallback
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
|
@@ -14,12 +14,12 @@
|
||||
* 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.internal.auth.login.ResetPasswordData
|
||||
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
|
@@ -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
|
||||
|
||||
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.Module
|
||||
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.crosssigning.CrossSigningService
|
||||
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.UpdateKeysBackupVersionTask
|
||||
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.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.DefaultClaimOneTimeKeysForUsersDevice
|
||||
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.UploadSignaturesTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
|
||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||
import im.vector.matrix.android.internal.di.CryptoDatabase
|
||||
import im.vector.matrix.android.internal.database.DatabaseKeysUtils
|
||||
import im.vector.matrix.android.internal.di.RealmCryptoDatabase
|
||||
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
||||
import im.vector.matrix.android.internal.di.UserMd5
|
||||
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.RealmClearCacheTask
|
||||
import im.vector.matrix.sqldelight.crypto.CrossSigningInfoEntity
|
||||
import im.vector.matrix.sqldelight.crypto.CryptoDatabase
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -108,15 +116,15 @@ internal abstract class CryptoModule {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@CryptoDatabase
|
||||
@RealmCryptoDatabase
|
||||
@SessionScope
|
||||
fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
|
||||
@UserMd5 userMd5: String,
|
||||
realmKeysUtils: RealmKeysUtils): RealmConfiguration {
|
||||
databaseKeysUtils: DatabaseKeysUtils): RealmConfiguration {
|
||||
return RealmConfiguration.Builder()
|
||||
.directory(directory)
|
||||
.apply {
|
||||
realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
|
||||
databaseKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
|
||||
}
|
||||
.name("crypto_store.realm")
|
||||
.modules(RealmCryptoStoreModule())
|
||||
@@ -125,6 +133,31 @@ internal abstract class CryptoModule {
|
||||
.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
|
||||
@Provides
|
||||
@SessionScope
|
||||
@@ -134,8 +167,8 @@ internal abstract class CryptoModule {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@CryptoDatabase
|
||||
fun providesClearCacheTask(@CryptoDatabase
|
||||
@RealmCryptoDatabase
|
||||
fun providesClearCacheTask(@RealmCryptoDatabase
|
||||
realmConfiguration: RealmConfiguration): ClearCacheTask {
|
||||
return RealmClearCacheTask(realmConfiguration)
|
||||
}
|
||||
@@ -243,7 +276,7 @@ internal abstract class CryptoModule {
|
||||
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
|
||||
|
||||
@Binds
|
||||
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore
|
||||
abstract fun bindCryptoStore(store: SqlCryptoStore): IMXCryptoStore
|
||||
|
||||
@Binds
|
||||
abstract fun bindComputeShieldTrustTask(task: DefaultComputeTrustTask): ComputeTrustTask
|
||||
|
@@ -23,7 +23,6 @@ import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.squareup.moshi.Types
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
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.session.crypto.CryptoService
|
||||
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.USER_SIGNING_KEY_SSSS_NAME
|
||||
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.DeviceTrustLevel
|
||||
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.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.*
|
||||
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.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.KeysUploadResponse
|
||||
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.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
|
||||
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.tasks.*
|
||||
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.model.EventEntityFields
|
||||
import im.vector.matrix.android.internal.database.query.whereType
|
||||
import im.vector.matrix.android.internal.database.repository.CurrentStateEventDataSource
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import im.vector.matrix.android.internal.extensions.foldToCallback
|
||||
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.util.JsonCanonicalizer
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.util.fetchCopied
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import im.vector.matrix.sqldelight.session.SessionDatabase
|
||||
import kotlinx.coroutines.*
|
||||
import org.matrix.olm.OlmManager
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
@@ -159,7 +140,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
private val setDeviceNameTask: SetDeviceNameTask,
|
||||
private val uploadKeysTask: UploadKeysTask,
|
||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||
private val monarchy: Monarchy,
|
||||
private val sessionDatabase: SessionDatabase,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val cryptoCoroutineScope: CoroutineScope
|
||||
@@ -178,16 +159,16 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
|
||||
fun onStateEvent(roomId: String, event: Event) {
|
||||
when {
|
||||
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(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_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||
}
|
||||
}
|
||||
|
||||
fun onLiveEvent(roomId: String, event: Event) {
|
||||
when {
|
||||
event.getClearType() == EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||
event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(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_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event)
|
||||
}
|
||||
}
|
||||
@@ -412,7 +393,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
}
|
||||
|
||||
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>> {
|
||||
@@ -508,7 +489,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
|
||||
val alg: IMXEncrypting = when (algorithm) {
|
||||
MXCRYPTO_ALGORITHM_MEGOLM -> megolmEncryptionFactory.create(roomId)
|
||||
else -> olmEncryptionFactory.create(roomId)
|
||||
else -> olmEncryptionFactory.create(roomId)
|
||||
}
|
||||
|
||||
synchronized(roomEncryptors) {
|
||||
@@ -542,12 +523,10 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
* @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM
|
||||
*/
|
||||
override fun isRoomEncrypted(roomId: String): Boolean {
|
||||
val encryptionEvent = monarchy.fetchCopied { realm ->
|
||||
EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
|
||||
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
|
||||
.findFirst()
|
||||
}
|
||||
return encryptionEvent != null
|
||||
return sessionDatabase.eventQueries
|
||||
.findWithContent(roomId = roomId, content = "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
|
||||
.executeAsList()
|
||||
.firstOrNull() != null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -706,17 +685,17 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
onRoomKeyEvent(event)
|
||||
}
|
||||
EventType.REQUEST_SECRET,
|
||||
EventType.ROOM_KEY_REQUEST -> {
|
||||
EventType.ROOM_KEY_REQUEST -> {
|
||||
// save audit trail
|
||||
cryptoStore.saveGossipingEvent(event)
|
||||
// Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete)
|
||||
incomingGossipingRequestManager.onGossipingRequestEvent(event)
|
||||
}
|
||||
EventType.SEND_SECRET -> {
|
||||
EventType.SEND_SECRET -> {
|
||||
cryptoStore.saveGossipingEvent(event)
|
||||
onSecretSendReceived(event)
|
||||
}
|
||||
else -> {
|
||||
else -> {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
@@ -767,30 +746,19 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
return
|
||||
}
|
||||
|
||||
if (!handleSDKLevelGossip(existingRequest.secretName, secretContent.secretValue)) {
|
||||
// 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) {
|
||||
when (existingRequest.secretName) {
|
||||
SELF_SIGNING_KEY_SSSS_NAME -> {
|
||||
crossSigningService.onSecretSSKGossip(secretValue)
|
||||
true
|
||||
crossSigningService.onSecretSSKGossip(secretContent.secretValue)
|
||||
return
|
||||
}
|
||||
USER_SIGNING_KEY_SSSS_NAME -> {
|
||||
crossSigningService.onSecretUSKGossip(secretValue)
|
||||
true
|
||||
crossSigningService.onSecretUSKGossip(secretContent.secretValue)
|
||||
return
|
||||
}
|
||||
KEYBACKUP_SECRET_SSSS_NAME -> {
|
||||
keysBackupService.onSecretKeyGossip(secretValue)
|
||||
true
|
||||
else -> {
|
||||
// Ask to application layer?
|
||||
Timber.v("## onSecretSend() : secret not handled by SDK")
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -804,29 +772,24 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
val params = LoadRoomMembersTask.Params(roomId)
|
||||
try {
|
||||
loadRoomMembersTask.execute(params)
|
||||
} catch (throwable: Throwable) {
|
||||
Timber.e(throwable, "## onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
|
||||
} finally {
|
||||
val userIds = getRoomUserIds(roomId)
|
||||
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> {
|
||||
var userIds: List<String> = emptyList()
|
||||
monarchy.doWithRealm { realm ->
|
||||
// Check whether the event content must be encrypted for the invited members.
|
||||
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser()
|
||||
&& shouldEncryptForInvitedMembers(roomId)
|
||||
// Check whether the event content must be encrypted for the invited members.
|
||||
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser()
|
||||
&& shouldEncryptForInvitedMembers(roomId)
|
||||
|
||||
userIds = if (encryptForInvitedMembers) {
|
||||
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds()
|
||||
} else {
|
||||
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds()
|
||||
}
|
||||
return if (encryptForInvitedMembers) {
|
||||
RoomMemberHelper(sessionDatabase, roomId).getActiveRoomMemberIds()
|
||||
} else {
|
||||
RoomMemberHelper(sessionDatabase, roomId).getJoinedRoomMemberIds()
|
||||
}
|
||||
return userIds
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -15,23 +15,18 @@
|
||||
*/
|
||||
package im.vector.matrix.android.internal.crypto.crosssigning
|
||||
|
||||
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.query.where
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.internal.database.mapper.map
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import im.vector.matrix.sqldelight.session.SessionDatabase
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ShieldTrustUpdater @Inject constructor(
|
||||
@@ -39,35 +34,23 @@ internal class ShieldTrustUpdater @Inject constructor(
|
||||
private val computeTrustTask: ComputeTrustTask,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
@SessionDatabase private val sessionRealmConfiguration: RealmConfiguration,
|
||||
private val roomSummaryUpdater: RoomSummaryUpdater
|
||||
) {
|
||||
private val sessionDatabase: SessionDatabase) {
|
||||
|
||||
companion object {
|
||||
private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD")
|
||||
}
|
||||
|
||||
private val backgroundSessionRealm = AtomicReference<Realm>()
|
||||
|
||||
private val isStarted = AtomicBoolean()
|
||||
|
||||
fun start() {
|
||||
if (isStarted.compareAndSet(false, true)) {
|
||||
eventBus.register(this)
|
||||
BACKGROUND_HANDLER.post {
|
||||
backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
if (isStarted.compareAndSet(true, false)) {
|
||||
eventBus.unregister(this)
|
||||
BACKGROUND_HANDLER.post {
|
||||
backgroundSessionRealm.getAndSet(null).also {
|
||||
it?.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,16 +59,7 @@ internal class ShieldTrustUpdater @Inject constructor(
|
||||
if (!isStarted.get()) {
|
||||
return
|
||||
}
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.crypto) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
taskExecutor.executorScope.updateTrustLevel(update.roomId, update.userIds)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -93,47 +67,31 @@ internal class ShieldTrustUpdater @Inject constructor(
|
||||
if (!isStarted.get()) {
|
||||
return
|
||||
}
|
||||
|
||||
onCryptoDevicesChange(update.userIds)
|
||||
}
|
||||
|
||||
private fun onCryptoDevicesChange(users: List<String>) {
|
||||
BACKGROUND_HANDLER.post {
|
||||
val impactedRoomsId = backgroundSessionRealm.get()?.where(RoomMemberSummaryEntity::class.java)
|
||||
?.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray())
|
||||
?.findAll()
|
||||
?.map { it.roomId }
|
||||
?.distinct()
|
||||
|
||||
val map = HashMap<String, List<String>>()
|
||||
impactedRoomsId?.forEach { roomId ->
|
||||
backgroundSessionRealm.get()?.let { realm ->
|
||||
RoomMemberSummaryEntity.where(realm, roomId)
|
||||
.findAll()
|
||||
.let { results ->
|
||||
map[roomId] = results.map { it.userId }
|
||||
}
|
||||
}
|
||||
val impactedRoomsId = sessionDatabase.roomMemberSummaryQueries.getAllRoomIdsFromUsers(users).executeAsList()
|
||||
impactedRoomsId.forEach { roomId ->
|
||||
val allUsersFromRoom = sessionDatabase.roomMemberSummaryQueries.getAllUserIdFromRoom(
|
||||
memberships = Membership.all().map(),
|
||||
excludedIds = emptyList(),
|
||||
roomId = roomId
|
||||
).executeAsList()
|
||||
taskExecutor.executorScope.updateTrustLevel(roomId, allUsersFromRoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map.forEach { entry ->
|
||||
val roomId = entry.key
|
||||
val userList = entry.value
|
||||
taskExecutor.executorScope.launch {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
try {
|
||||
// Can throw if the crypto database has been closed in between, in this case log and ignore?
|
||||
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList))
|
||||
BACKGROUND_HANDLER.post {
|
||||
backgroundSessionRealm.get()?.executeTransaction { realm ->
|
||||
roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust)
|
||||
}
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun CoroutineScope.updateTrustLevel(roomId: String, userIds: List<String>) {
|
||||
launch(coroutineDispatchers.dbTransaction) {
|
||||
try {
|
||||
// Can throw if the crypto database has been closed in between, in this case log and ignore?
|
||||
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userIds))
|
||||
sessionDatabase.roomSummaryQueries.setRoomEncryptionTrustLevel(updatedTrust.name, roomId)
|
||||
} 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.store.IMXCryptoStore
|
||||
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.UserId
|
||||
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
|
||||
*/
|
||||
private fun onServerDataRetrieved(count: Int?, hash: String?) {
|
||||
cryptoStore.setKeysBackupData(KeysBackupDataEntity()
|
||||
.apply {
|
||||
backupLastServerNumberOfKeys = count
|
||||
backupLastServerHash = hash
|
||||
}
|
||||
cryptoStore.setKeysBackupData(
|
||||
im.vector.matrix.android.internal.crypto.model.rest.KeysBackupData(
|
||||
backupLastServerNumberOfKeys = count,
|
||||
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.OlmInboundGroupSessionWrapper
|
||||
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.store.db.model.KeysBackupDataEntity
|
||||
import org.matrix.olm.OlmAccount
|
||||
|
||||
/**
|
||||
@@ -104,14 +104,14 @@ internal interface IMXCryptoStore {
|
||||
/**
|
||||
* Get the current keys backup local data
|
||||
*/
|
||||
fun getKeysBackupData(): KeysBackupDataEntity?
|
||||
fun getKeysBackupData(): KeysBackupData?
|
||||
|
||||
/**
|
||||
* Set the keys backup local 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)
|
||||
|
@@ -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.OlmInboundGroupSessionWrapper
|
||||
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.toEntity
|
||||
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.getOrCreate
|
||||
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.RealmCryptoDatabase
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
@@ -90,7 +91,7 @@ import kotlin.collections.set
|
||||
|
||||
@SessionScope
|
||||
internal class RealmCryptoStore @Inject constructor(
|
||||
@CryptoDatabase private val realmConfiguration: RealmConfiguration,
|
||||
@RealmCryptoDatabase private val realmConfiguration: RealmConfiguration,
|
||||
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) {
|
||||
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) {
|
||||
if (keysBackupData == null) {
|
||||
// Clear the table
|
||||
@@ -720,7 +726,10 @@ internal class RealmCryptoStore @Inject constructor(
|
||||
.deleteAllFromRealm()
|
||||
} else {
|
||||
// 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.MXCryptoError
|
||||
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.LocalEcho
|
||||
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.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.api.session.room.model.message.*
|
||||
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.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.di.DeviceId
|
||||
import im.vector.matrix.android.internal.di.UserId
|
||||
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 java.util.ArrayList
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface RoomVerificationUpdateTask : Task<RoomVerificationUpdateTask.Params, Unit> {
|
||||
data class Params(
|
||||
val events: List<Event>,
|
||||
val verificationService: DefaultVerificationService,
|
||||
val cryptoService: CryptoService
|
||||
val eventInsertNotifications: List<EventInsertNotification>
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
||||
@UserId private val userId: String,
|
||||
@DeviceId private val deviceId: String?,
|
||||
private val sessionDatabase: SessionDatabase,
|
||||
private val verificationService: DefaultVerificationService,
|
||||
private val cryptoService: CryptoService) : RoomVerificationUpdateTask {
|
||||
|
||||
companion object {
|
||||
@@ -57,8 +55,15 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
||||
|
||||
override suspend fun execute(params: RoomVerificationUpdateTask.Params) {
|
||||
// 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}")
|
||||
|
||||
// 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?
|
||||
// for now decrypt sync
|
||||
try {
|
||||
val result = cryptoService.decryptEvent(event, "")
|
||||
val result = cryptoService.decryptEvent(event, event.roomId + UUID.randomUUID().toString())
|
||||
event.mxDecryptionResult = OlmDecryptionResult(
|
||||
payload = result.clearEvent,
|
||||
senderKey = result.senderCurve25519Key,
|
||||
@@ -83,7 +88,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
||||
)
|
||||
} catch (e: MXCryptoError) {
|
||||
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()}")
|
||||
@@ -112,7 +117,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
||||
// The verification is started from another device
|
||||
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
|
||||
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
|
||||
params.verificationService.onRoomRequestHandledByOtherDevice(event)
|
||||
verificationService.onRoomRequestHandledByOtherDevice(event)
|
||||
}
|
||||
}
|
||||
} else if (EventType.KEY_VERIFICATION_READY == event.getClearType()) {
|
||||
@@ -121,13 +126,13 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
|
||||
// The verification is started from another device
|
||||
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
|
||||
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()) {
|
||||
relatesToEventId?.let {
|
||||
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_READY,
|
||||
EventType.KEY_VERIFICATION_DONE -> {
|
||||
params.verificationService.onRoomEvent(event)
|
||||
verificationService.onRoomEvent(event)
|
||||
}
|
||||
EventType.MESSAGE -> {
|
||||
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
|
||||
|
||||
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.LocalEcho
|
||||
import im.vector.matrix.android.internal.crypto.tasks.DefaultRoomVerificationUpdateTask
|
||||
import im.vector.matrix.android.internal.crypto.tasks.RoomVerificationUpdateTask
|
||||
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||
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 im.vector.matrix.android.internal.database.SqlLiveEntityObserver
|
||||
import im.vector.matrix.sqldelight.session.EventInsertNotification
|
||||
import im.vector.matrix.sqldelight.session.SessionDatabase
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class VerificationMessageLiveObserver @Inject constructor(
|
||||
@SessionDatabase realmConfiguration: RealmConfiguration,
|
||||
private val roomVerificationUpdateTask: DefaultRoomVerificationUpdateTask,
|
||||
private val cryptoService: CryptoService,
|
||||
private val verificationService: DefaultVerificationService,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
|
||||
sessionDatabase: SessionDatabase,
|
||||
private val roomVerificationUpdateTask: DefaultRoomVerificationUpdateTask
|
||||
) : SqlLiveEntityObserver<EventInsertNotification>(sessionDatabase) {
|
||||
|
||||
override val query = Monarchy.Query {
|
||||
EventEntity.whereTypes(it, listOf(
|
||||
EventType.KEY_VERIFICATION_START,
|
||||
EventType.KEY_VERIFICATION_ACCEPT,
|
||||
EventType.KEY_VERIFICATION_KEY,
|
||||
EventType.KEY_VERIFICATION_MAC,
|
||||
EventType.KEY_VERIFICATION_CANCEL,
|
||||
EventType.KEY_VERIFICATION_DONE,
|
||||
EventType.KEY_VERIFICATION_READY,
|
||||
EventType.MESSAGE,
|
||||
EventType.ENCRYPTED)
|
||||
)
|
||||
override val query = sessionDatabase.observerTriggerQueries.getAllEventInsertNotifications(
|
||||
types = listOf(
|
||||
EventType.KEY_VERIFICATION_START,
|
||||
EventType.KEY_VERIFICATION_ACCEPT,
|
||||
EventType.KEY_VERIFICATION_KEY,
|
||||
EventType.KEY_VERIFICATION_MAC,
|
||||
EventType.KEY_VERIFICATION_CANCEL,
|
||||
EventType.KEY_VERIFICATION_DONE,
|
||||
EventType.KEY_VERIFICATION_READY,
|
||||
EventType.MESSAGE,
|
||||
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
|
||||
|
||||
import com.squareup.sqldelight.Transacter
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -22,7 +24,7 @@ import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withContext
|
||||
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 ->
|
||||
bgRealm.beginTransaction()
|
||||
val result: T
|
||||
@@ -43,3 +45,22 @@ suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspen
|
||||
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.util.Base64
|
||||
import androidx.sqlite.db.SupportSQLiteOpenHelper
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.internal.session.securestorage.SecretStoringUtils
|
||||
import io.realm.RealmConfiguration
|
||||
import net.sqlcipher.database.SupportFactory
|
||||
import timber.log.Timber
|
||||
import java.security.SecureRandom
|
||||
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
|
||||
* 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,
|
||||
private val secretStoringUtils: SecretStoringUtils) {
|
||||
internal class DatabaseKeysUtils @Inject constructor(context: Context,
|
||||
private val secretStoringUtils: SecretStoringUtils) {
|
||||
|
||||
private val rng = SecureRandom()
|
||||
|
||||
@@ -85,7 +87,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
|
||||
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)) {
|
||||
Timber.i("Found key for alias:$alias")
|
||||
extractKeyForDatabase(alias)
|
||||
@@ -98,7 +100,16 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
|
||||
val log = key.joinToString("") { "%02x".format(it) }
|
||||
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)
|
||||
}
|
||||
|
@@ -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
|
||||
|
||||
import android.content.Context
|
||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
||||
import im.vector.matrix.android.internal.di.SessionId
|
||||
import im.vector.matrix.android.internal.di.UserMd5
|
||||
import im.vector.matrix.android.internal.session.SessionModule
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val REALM_SHOULD_CLEAR_FLAG_ = "REALM_SHOULD_CLEAR_FLAG_"
|
||||
private const val REALM_NAME = "disk_store.realm"
|
||||
|
||||
/**
|
||||
* 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(
|
||||
private val realmKeysUtils: RealmKeysUtils,
|
||||
private val databaseKeysUtils: DatabaseKeysUtils,
|
||||
@SessionFilesDirectory val directory: File,
|
||||
@SessionId val sessionId: String,
|
||||
@UserMd5 val userMd5: String,
|
||||
context: Context) {
|
||||
|
||||
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
|
||||
@UserMd5 val userMd5: String) {
|
||||
|
||||
fun create(): RealmConfiguration {
|
||||
val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
||||
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()
|
||||
return RealmConfiguration.Builder()
|
||||
.directory(directory)
|
||||
.name(REALM_NAME)
|
||||
.apply {
|
||||
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||
databaseKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||
}
|
||||
.modules(SessionRealmModule())
|
||||
.deleteRealmIfMigrationNeeded()
|
||||
.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
|
||||
|
||||
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.internal.database.model.ChunkEntity
|
||||
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.database.model.*
|
||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||
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.Sort
|
||||
import io.realm.kotlin.createObject
|
||||
import timber.log.Timber
|
||||
|
||||
internal fun ChunkEntity.deleteOnCascade() {
|
||||
@@ -85,57 +74,47 @@ internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.addTimelineEvent(roomId: String,
|
||||
eventEntity: EventEntity,
|
||||
direction: PaginationDirection,
|
||||
roomMemberContentsByUser: Map<String, RoomMemberContent?>) {
|
||||
val eventId = eventEntity.eventId
|
||||
if (timelineEvents.find(eventId) != null) {
|
||||
return
|
||||
}
|
||||
val displayIndex = nextDisplayIndex(direction)
|
||||
val localId = TimelineEventEntity.nextId(realm)
|
||||
val senderId = eventEntity.sender ?: ""
|
||||
|
||||
internal fun SessionDatabase.addTimelineEvent(roomId: String,
|
||||
chunkId: Long,
|
||||
event: Event,
|
||||
direction: PaginationDirection,
|
||||
roomMemberContentsByUser: Map<String, RoomMemberContent?>) {
|
||||
val eventId = event.eventId ?: "$roomId-$chunkId-${System.currentTimeMillis()}"
|
||||
val displayIndex = timelineEventQueries.nextDisplayIndex(direction, chunkId)
|
||||
val senderId = event.senderId ?: ""
|
||||
val roomMemberContent = roomMemberContentsByUser[senderId]
|
||||
// Update RR for the sender of a new message with a dummy one
|
||||
val readReceiptsSummaryEntity = handleReadReceipts(realm, roomId, eventEntity, senderId)
|
||||
val timelineEventEntity = realm.createObject<TimelineEventEntity>().apply {
|
||||
this.localId = localId
|
||||
this.root = eventEntity
|
||||
this.eventId = eventId
|
||||
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
|
||||
}
|
||||
handleReadReceipts(this, roomId, eventId, event.originServerTs, senderId)
|
||||
val isDisplayNameUnique = if (roomMemberContent?.displayName != null) {
|
||||
computeIsUnique(this, roomId, chunkId, 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(
|
||||
realm: Realm,
|
||||
sessionDatabase: SessionDatabase,
|
||||
roomId: String,
|
||||
isLastForward: Boolean,
|
||||
chunkId: Long,
|
||||
myRoomMemberContent: RoomMemberContent,
|
||||
roomMemberContentsByUser: Map<String, RoomMemberContent?>
|
||||
): Boolean {
|
||||
val isHistoricalUnique = roomMemberContentsByUser.values.find {
|
||||
it != myRoomMemberContent && it?.displayName == myRoomMemberContent.displayName
|
||||
} == null
|
||||
val isLastForward = sessionDatabase.chunkQueries.isLastForward(chunkId).executeAsOne()
|
||||
return if (isLastForward) {
|
||||
val isLiveUnique = RoomMemberSummaryEntity
|
||||
.where(realm, roomId)
|
||||
.equalTo(RoomMemberSummaryEntityFields.DISPLAY_NAME, myRoomMemberContent.displayName)
|
||||
.findAll().none {
|
||||
!roomMemberContentsByUser.containsKey(it.userId)
|
||||
}
|
||||
val countMembersWithName = sessionDatabase.roomMemberSummaryQueries.countMembersWithNameInRoom(myRoomMemberContent.displayName, roomId).executeAsOne()
|
||||
val isLiveUnique = countMembersWithName == 1L
|
||||
isHistoricalUnique && isLiveUnique
|
||||
} else {
|
||||
isHistoricalUnique
|
||||
@@ -143,6 +122,7 @@ private fun computeIsUnique(
|
||||
}
|
||||
|
||||
private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEntity: TimelineEventEntity, direction: PaginationDirection) {
|
||||
/*
|
||||
val eventId = timelineEventEntity.eventId
|
||||
if (timelineEvents.find(eventId) != null) {
|
||||
return
|
||||
@@ -162,36 +142,30 @@ private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEnt
|
||||
this.isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName
|
||||
}
|
||||
timelineEvents.add(copied)
|
||||
*/
|
||||
}
|
||||
|
||||
private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventEntity, senderId: String): ReadReceiptsSummaryEntity {
|
||||
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst()
|
||||
?: realm.createObject<ReadReceiptsSummaryEntity>(eventEntity.eventId).apply {
|
||||
this.roomId = roomId
|
||||
}
|
||||
val originServerTs = eventEntity.originServerTs
|
||||
private fun handleReadReceipts(
|
||||
sessionDatabase: SessionDatabase,
|
||||
roomId: String,
|
||||
eventId: String,
|
||||
originServerTs: Long?,
|
||||
senderId: String) {
|
||||
if (originServerTs != null) {
|
||||
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 (timestampOfEvent > readReceiptOfSender.originServerTs) {
|
||||
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst()
|
||||
readReceiptOfSender.eventId = eventEntity.eventId
|
||||
readReceiptOfSender.originServerTs = timestampOfEvent
|
||||
previousReceiptsSummary?.readReceipts?.remove(readReceiptOfSender)
|
||||
readReceiptsSummaryEntity.readReceipts.add(readReceiptOfSender)
|
||||
if (oldTimestamp == null || timestampOfEvent > oldTimestamp) {
|
||||
sessionDatabase.readReceiptQueries.updateReadReceipt(eventId, timestampOfEvent, roomId, senderId)
|
||||
}
|
||||
}
|
||||
return readReceiptsSummaryEntity
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.nextDisplayIndex(direction: PaginationDirection): Int {
|
||||
internal fun TimelineEventQueries.nextDisplayIndex(direction: PaginationDirection, chunkId: Long): Int {
|
||||
return when (direction) {
|
||||
PaginationDirection.FORWARDS -> {
|
||||
(timelineEvents.where().max(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) + 1
|
||||
}
|
||||
PaginationDirection.BACKWARDS -> {
|
||||
(timelineEvents.where().min(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) - 1
|
||||
}
|
||||
PaginationDirection.FORWARDS -> (getMaxDisplayIndex(chunkId).executeAsOneOrNull()?.max_display_index?.toInt()
|
||||
?: 0) + 1
|
||||
PaginationDirection.BACKWARDS -> (getMinDisplayIndex(chunkId).executeAsOneOrNull()?.min_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
|
||||
|
||||
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
|
||||
*/
|
||||
internal object DraftMapper {
|
||||
internal class DraftMapper @Inject constructor() {
|
||||
|
||||
fun map(entity: DraftEntity): UserDraft {
|
||||
return when (entity.draftMode) {
|
||||
DraftEntity.MODE_REGULAR -> UserDraft.REGULAR(entity.content)
|
||||
DraftEntity.MODE_EDIT -> UserDraft.EDIT(entity.linkedEventId, entity.content)
|
||||
DraftEntity.MODE_QUOTE -> UserDraft.QUOTE(entity.linkedEventId, entity.content)
|
||||
DraftEntity.MODE_REPLY -> UserDraft.REPLY(entity.linkedEventId, entity.content)
|
||||
else -> null
|
||||
fun map(content: String,
|
||||
draft_mode: String,
|
||||
linked_event_id: String): UserDraft {
|
||||
return when (draft_mode) {
|
||||
DraftMode.MODE_REGULAR -> UserDraft.REGULAR(content)
|
||||
DraftMode.MODE_EDIT -> UserDraft.EDIT(linked_event_id, content)
|
||||
DraftMode.MODE_QUOTE -> UserDraft.QUOTE(linked_event_id, content)
|
||||
DraftMode.MODE_REPLY -> UserDraft.REPLY(linked_event_id, content)
|
||||
else -> null
|
||||
} ?: 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
|
||||
|
||||
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||
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
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.*
|
||||
|
||||
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 {
|
||||
val eventAnnotationsSummaryEntity = EventAnnotationsSummaryEntity()
|
||||
eventAnnotationsSummaryEntity.eventId = annotationsSummary.eventId
|
||||
eventAnnotationsSummaryEntity.roomId = roomId
|
||||
eventAnnotationsSummaryEntity.editSummary = annotationsSummary.editSummary?.let {
|
||||
EditAggregatedSummaryEntity(
|
||||
ContentMapper.map(it.aggregatedContent),
|
||||
RealmList<String>().apply { addAll(it.sourceEvents) },
|
||||
RealmList<String>().apply { addAll(it.localEchos) },
|
||||
it.lastEditTs
|
||||
)
|
||||
}
|
||||
eventAnnotationsSummaryEntity.reactionsSummary = annotationsSummary.reactionsSummary.let {
|
||||
RealmList<ReactionAggregatedSummaryEntity>().apply {
|
||||
addAll(it.map {
|
||||
ReactionAggregatedSummaryEntity(
|
||||
it.key,
|
||||
it.count,
|
||||
it.addedByMe,
|
||||
it.firstTimestamp,
|
||||
RealmList<String>().apply { addAll(it.sourceEvents) },
|
||||
RealmList<String>().apply { addAll(it.localEchoEvents) }
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
eventAnnotationsSummaryEntity.referencesSummaryEntity = annotationsSummary.referencesAggregatedSummary?.let {
|
||||
ReferencesAggregatedSummaryEntity(
|
||||
it.eventId,
|
||||
ContentMapper.map(it.content),
|
||||
RealmList<String>().apply { addAll(it.sourceEvents) },
|
||||
RealmList<String>().apply { addAll(it.localEchos) }
|
||||
)
|
||||
}
|
||||
eventAnnotationsSummaryEntity.pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
|
||||
PollResponseAggregatedSummaryEntityMapper.map(it)
|
||||
}
|
||||
return eventAnnotationsSummaryEntity
|
||||
fun mapReactionSummary(key: String,
|
||||
count: Long,
|
||||
added_by_me: Boolean,
|
||||
first_timestamp: Long,
|
||||
source_event_ids: List<String>,
|
||||
source_local_echo_ids: List<String>): ReactionAggregatedSummary {
|
||||
return ReactionAggregatedSummary(
|
||||
key = key,
|
||||
count = count.toInt(),
|
||||
addedByMe = added_by_me,
|
||||
firstTimestamp = first_timestamp,
|
||||
sourceEvents = source_event_ids,
|
||||
localEchoEvents = source_local_echo_ids
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun mapEditSummary(aggregated_content: String?,
|
||||
last_edit_ts: Long,
|
||||
source_event_ids: List<String>,
|
||||
source_local_echo_ids: List<String>): EditAggregatedSummary {
|
||||
return EditAggregatedSummary(
|
||||
ContentMapper.map(aggregated_content),
|
||||
source_event_ids,
|
||||
source_local_echo_ids,
|
||||
last_edit_ts
|
||||
)
|
||||
}
|
||||
|
||||
fun mapReferencesSummary(event_id: String,
|
||||
content: String?,
|
||||
source_event_ids: List<String>,
|
||||
source_local_echo_ids: List<String>): ReferencesAggregatedSummary {
|
||||
return ReferencesAggregatedSummary(
|
||||
event_id,
|
||||
ContentMapper.map(content),
|
||||
source_event_ids,
|
||||
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)
|
||||
val eventEntity = EventEntity()
|
||||
// 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.content = ContentMapper.map(event.content)
|
||||
val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent
|
||||
@@ -48,6 +49,34 @@ internal object EventMapper {
|
||||
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 {
|
||||
val ud = eventEntity.unsignedData
|
||||
?.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 {
|
||||
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 {
|
||||
return EventMapper.map(this, roomId).apply {
|
||||
this.sendState = sendState
|
||||
|
@@ -16,24 +16,34 @@
|
||||
|
||||
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.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(
|
||||
groupSummaryEntity.groupId,
|
||||
groupSummaryEntity.membership,
|
||||
groupSummaryEntity.displayName,
|
||||
groupSummaryEntity.shortDescription,
|
||||
groupSummaryEntity.avatarUrl,
|
||||
groupSummaryEntity.roomIds.toList(),
|
||||
groupSummaryEntity.userIds.toList()
|
||||
groupId = group_id,
|
||||
membership = membership.map(),
|
||||
displayName = display_name ?: "",
|
||||
shortDescription = short_description ?: "",
|
||||
avatarUrl = avatar_url ?: ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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(
|
||||
kind = entity.kind,
|
||||
iz = entity.iz,
|
||||
@@ -30,12 +32,14 @@ internal object PushConditionMapper {
|
||||
)
|
||||
}
|
||||
|
||||
fun map(domain: PushCondition): PushConditionEntity {
|
||||
return PushConditionEntity(
|
||||
kind = domain.kind,
|
||||
iz = domain.iz,
|
||||
key = domain.key,
|
||||
pattern = domain.pattern
|
||||
fun map(ruleId: String, pushCondition: PushCondition): PushConditionEntity {
|
||||
return PushConditionEntity.Impl(
|
||||
kind = pushCondition.kind,
|
||||
iz = pushCondition.iz,
|
||||
key = pushCondition.key,
|
||||
pattern = pushCondition.pattern,
|
||||
rule_id = ruleId
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -17,14 +17,16 @@ package im.vector.matrix.android.internal.database.mapper
|
||||
|
||||
import com.squareup.moshi.Types
|
||||
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.PushRule
|
||||
import im.vector.matrix.android.internal.database.model.PushRuleEntity
|
||||
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 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))
|
||||
|
||||
@@ -33,10 +35,10 @@ internal object PushRulesMapper {
|
||||
|
||||
fun mapContentRule(pushrule: PushRuleEntity): PushRule {
|
||||
return PushRule(
|
||||
actions = fromActionStr(pushrule.actionsStr),
|
||||
default = pushrule.default,
|
||||
enabled = pushrule.enabled,
|
||||
ruleId = pushrule.ruleId,
|
||||
actions = fromActionStr(pushrule.action_str),
|
||||
default = pushrule.is_default,
|
||||
enabled = pushrule.is_enabled,
|
||||
ruleId = pushrule.rule_id,
|
||||
conditions = listOf(
|
||||
PushCondition(Condition.Kind.EventMatch.value, "content.body", pushrule.pattern)
|
||||
)
|
||||
@@ -44,58 +46,60 @@ internal object PushRulesMapper {
|
||||
}
|
||||
|
||||
private fun fromActionStr(actionsStr: String?): List<Any> {
|
||||
try {
|
||||
return actionsStr?.let { moshiActionsAdapter.fromJson(it) } ?: emptyList()
|
||||
return try {
|
||||
actionsStr?.let { moshiActionsAdapter.fromJson(it) } ?: emptyList()
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "## failed to map push rule actions <$actionsStr>")
|
||||
return emptyList()
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
fun mapRoomRule(pushrule: PushRuleEntity): PushRule {
|
||||
return PushRule(
|
||||
actions = fromActionStr(pushrule.actionsStr),
|
||||
default = pushrule.default,
|
||||
enabled = pushrule.enabled,
|
||||
ruleId = pushrule.ruleId,
|
||||
actions = fromActionStr(pushrule.action_str),
|
||||
default = pushrule.is_default,
|
||||
enabled = pushrule.is_enabled,
|
||||
ruleId = pushrule.rule_id,
|
||||
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 {
|
||||
return PushRule(
|
||||
actions = fromActionStr(pushrule.actionsStr),
|
||||
default = pushrule.default,
|
||||
enabled = pushrule.enabled,
|
||||
ruleId = pushrule.ruleId,
|
||||
actions = fromActionStr(pushrule.action_str),
|
||||
default = pushrule.is_default,
|
||||
enabled = pushrule.is_enabled,
|
||||
ruleId = pushrule.rule_id,
|
||||
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(
|
||||
actions = fromActionStr(pushrule.actionsStr),
|
||||
default = pushrule.default,
|
||||
enabled = pushrule.enabled,
|
||||
ruleId = pushrule.ruleId,
|
||||
conditions = pushrule.conditions?.map { PushConditionMapper.map(it) }
|
||||
actions = fromActionStr(pushrule.action_str),
|
||||
default = pushrule.is_default,
|
||||
enabled = pushrule.is_enabled,
|
||||
ruleId = pushrule.rule_id,
|
||||
conditions = conditions.map {
|
||||
pushConditionMapper.map(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun map(pushRule: PushRule): PushRuleEntity {
|
||||
return PushRuleEntity(
|
||||
actionsStr = moshiActionsAdapter.toJson(pushRule.actions),
|
||||
default = pushRule.default ?: false,
|
||||
enabled = pushRule.enabled,
|
||||
ruleId = pushRule.ruleId,
|
||||
fun map(scope: String, kind: RuleKind, pushRule: PushRule): PushRuleEntity {
|
||||
return PushRuleEntity.Impl(
|
||||
action_str = moshiActionsAdapter.toJson(pushRule.actions),
|
||||
is_default = pushRule.default ?: false,
|
||||
is_enabled = pushRule.enabled,
|
||||
rule_id = pushRule.ruleId,
|
||||
pattern = pushRule.pattern,
|
||||
conditions = pushRule.conditions?.let {
|
||||
RealmList(*pushRule.conditions.map { PushConditionMapper.map(it) }.toTypedArray())
|
||||
} ?: RealmList()
|
||||
scope = scope,
|
||||
kind = kind.name
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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.PusherData
|
||||
import im.vector.matrix.android.internal.database.model.PusherDataEntity
|
||||
import im.vector.matrix.android.internal.database.model.PusherEntity
|
||||
import im.vector.matrix.android.api.session.pushers.PusherState
|
||||
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(
|
||||
pushKey = pushEntity.pushKey,
|
||||
kind = pushEntity.kind ?: "",
|
||||
appId = pushEntity.appId,
|
||||
appDisplayName = pushEntity.appDisplayName,
|
||||
deviceDisplayName = pushEntity.deviceDisplayName,
|
||||
profileTag = pushEntity.profileTag,
|
||||
lang = pushEntity.lang,
|
||||
data = PusherData(pushEntity.data?.url, pushEntity.data?.format),
|
||||
state = pushEntity.state
|
||||
pushKey = push_key,
|
||||
kind = kind ?: "",
|
||||
appId = app_id,
|
||||
appDisplayName = app_display_name,
|
||||
deviceDisplayName = device_display_name,
|
||||
profileTag = profile_tag,
|
||||
lang = lang,
|
||||
data = PusherData(data_url, data_format),
|
||||
state = PusherState.valueOf(state)
|
||||
)
|
||||
}
|
||||
|
||||
fun map(pusher: JsonPusher): PusherEntity {
|
||||
return PusherEntity(
|
||||
pushKey = pusher.pushKey,
|
||||
fun map(pusher: JsonPusher, state: PusherState): PusherEntity {
|
||||
return PusherEntity.Impl(
|
||||
push_key = pusher.pushKey,
|
||||
kind = pusher.kind,
|
||||
appId = pusher.appId,
|
||||
appDisplayName = pusher.appDisplayName,
|
||||
deviceDisplayName = pusher.deviceDisplayName,
|
||||
profileTag = pusher.profileTag,
|
||||
app_id = pusher.appId,
|
||||
app_display_name = pusher.appDisplayName,
|
||||
device_display_name = pusher.deviceDisplayName,
|
||||
profile_tag = pusher.profileTag,
|
||||
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.internal.database.model.ReadReceiptsSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.UserEntity
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||
import io.realm.Realm
|
||||
import im.vector.matrix.android.internal.di.RealmSessionDatabase
|
||||
import io.realm.RealmConfiguration
|
||||
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> {
|
||||
if (readReceiptsSummaryEntity == null) {
|
||||
return emptyList()
|
||||
return emptyList()
|
||||
/*if (readReceiptsSummaryEntity == null) {
|
||||
|
||||
}
|
||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||
val readReceipts = readReceiptsSummaryEntity.readReceipts
|
||||
@@ -39,6 +37,6 @@ internal class ReadReceiptsSummaryMapper @Inject constructor(@SessionDatabase pr
|
||||
?: return@mapNotNull null
|
||||
ReadReceipt(user.asDomain(), it.originServerTs.toLong())
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
@@ -16,10 +16,13 @@
|
||||
|
||||
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.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 {
|
||||
return RoomMemberSummary(
|
||||
@@ -29,8 +32,31 @@ internal object RoomMemberSummaryMapper {
|
||||
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 {
|
||||
return RoomMemberSummaryMapper.map(this)
|
||||
return RoomMemberSummaryMapper().map(this)
|
||||
}
|
||||
|
@@ -16,47 +16,82 @@
|
||||
|
||||
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.VersioningState
|
||||
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
|
||||
|
||||
internal class RoomSummaryMapper @Inject constructor(private val timelineEventMapper: TimelineEventMapper) {
|
||||
|
||||
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)
|
||||
}
|
||||
internal class RoomSummaryMapper @Inject constructor() {
|
||||
|
||||
fun map(roomSummaryWithTimeline: RoomSummaryWithTimeline, tags: List<RoomTag>): RoomSummary {
|
||||
return RoomSummary(
|
||||
roomId = roomSummaryEntity.roomId,
|
||||
displayName = roomSummaryEntity.displayName ?: "",
|
||||
topic = roomSummaryEntity.topic ?: "",
|
||||
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
||||
isDirect = roomSummaryEntity.isDirect,
|
||||
latestPreviewableEvent = latestEvent,
|
||||
joinedMembersCount = roomSummaryEntity.joinedMembersCount,
|
||||
invitedMembersCount = roomSummaryEntity.invitedMembersCount,
|
||||
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
|
||||
highlightCount = roomSummaryEntity.highlightCount,
|
||||
notificationCount = roomSummaryEntity.notificationCount,
|
||||
hasUnreadMessages = roomSummaryEntity.hasUnreadMessages,
|
||||
roomId = roomSummaryWithTimeline.summary_room_id,
|
||||
displayName = roomSummaryWithTimeline.display_name ?: "",
|
||||
topic = roomSummaryWithTimeline.topic ?: "",
|
||||
avatarUrl = roomSummaryWithTimeline.avatar_url ?: "",
|
||||
isDirect = roomSummaryWithTimeline.is_direct,
|
||||
latestPreviewableEvent = createTimelineEvent(roomSummaryWithTimeline),
|
||||
joinedMembersCount = roomSummaryWithTimeline.joined_members_count,
|
||||
invitedMembersCount = roomSummaryWithTimeline.invited_members_count,
|
||||
otherMemberIds = emptyList(),
|
||||
highlightCount = roomSummaryWithTimeline.highlight_count,
|
||||
notificationCount = roomSummaryWithTimeline.notification_count,
|
||||
hasUnreadMessages = roomSummaryWithTimeline.has_unread,
|
||||
versioningState = VersioningState.valueOf(roomSummaryWithTimeline.versioning_state),
|
||||
tags = tags,
|
||||
membership = roomSummaryEntity.membership,
|
||||
versioningState = roomSummaryEntity.versioningState,
|
||||
readMarkerId = roomSummaryEntity.readMarkerId,
|
||||
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(),
|
||||
canonicalAlias = roomSummaryEntity.canonicalAlias,
|
||||
aliases = roomSummaryEntity.aliases.toList(),
|
||||
isEncrypted = roomSummaryEntity.isEncrypted,
|
||||
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(),
|
||||
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
|
||||
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,
|
||||
inviterId = roomSummaryEntity.inviterId
|
||||
membership = roomSummaryWithTimeline.membership.map(),
|
||||
readMarkerId = roomSummaryWithTimeline.read_marker_id,
|
||||
userDrafts = emptyList(),
|
||||
canonicalAlias = roomSummaryWithTimeline.canonical_alias,
|
||||
inviterId = roomSummaryWithTimeline.inviter_id,
|
||||
isEncrypted = roomSummaryWithTimeline.is_encrypted,
|
||||
typingRoomMemberIds = emptyList(),//roomSummaryEntity.typingUserIds.toList(),
|
||||
breadcrumbsIndex = roomSummaryWithTimeline.breadcrumb_index ?: -1,
|
||||
roomEncryptionTrustLevel = roomSummaryWithTimeline.room_encryption_trust_level?.let {
|
||||
try {
|
||||
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
|
||||
|
||||
import com.squareup.sqldelight.db.SqlCursor
|
||||
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.internal.database.model.TimelineEventEntity
|
||||
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 {
|
||||
val readReceipts = if (buildReadReceipts) {
|
||||
correctedReadReceipts ?: timelineEventEntity.readReceipts
|
||||
?.let {
|
||||
readReceiptsSummaryMapper.map(it)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
fun map(cursor: SqlCursor): TimelineEvent = map(
|
||||
cursor.getLong(0)!!,
|
||||
cursor.getLong(1)!!,
|
||||
cursor.getLong(2)!!.toInt(),
|
||||
cursor.getString(3),
|
||||
cursor.getString(4),
|
||||
cursor.getLong(5)!! == 1L,
|
||||
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(
|
||||
root = timelineEventEntity.root?.asDomain()
|
||||
?: Event("", timelineEventEntity.eventId),
|
||||
eventId = timelineEventEntity.eventId,
|
||||
annotations = timelineEventEntity.annotations?.asDomain(),
|
||||
localId = timelineEventEntity.localId,
|
||||
displayIndex = timelineEventEntity.displayIndex,
|
||||
senderName = timelineEventEntity.senderName,
|
||||
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
||||
senderAvatar = timelineEventEntity.senderAvatar,
|
||||
readReceipts = readReceipts
|
||||
?.distinctBy {
|
||||
it.user
|
||||
}?.sortedByDescending {
|
||||
it.originServerTs
|
||||
} ?: emptyList()
|
||||
root = event,
|
||||
eventId = event_id,
|
||||
annotations = null,
|
||||
displayIndex = display_index,
|
||||
isUniqueDisplayName = is_unique_display_name,
|
||||
localId = local_id,
|
||||
readReceipts = emptyList(),
|
||||
senderAvatar = sender_avatar,
|
||||
senderName = sender_name
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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
|
||||
|
||||
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 {
|
||||
return User(
|
||||
userEntity.userId,
|
||||
userEntity.displayName,
|
||||
userEntity.avatarUrl
|
||||
userEntity.user_id,
|
||||
userEntity.display_name,
|
||||
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 {
|
||||
return UserMapper.map(this)
|
||||
return UserMapper().map(this)
|
||||
}
|
||||
|
@@ -18,6 +18,6 @@ package im.vector.matrix.android.internal.database.query
|
||||
|
||||
internal object FilterContent {
|
||||
|
||||
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 EDIT_TYPE = """ '{%"m.relates_to"%"rel_type":%"m.replace"%}' """
|
||||
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
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class AuthDatabase
|
||||
annotation class RealmAuthDatabase
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class SessionDatabase
|
||||
annotation class RealmSessionDatabase
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class CryptoDatabase
|
||||
annotation class RealmCryptoDatabase
|
||||
|
@@ -20,6 +20,7 @@ import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import dagger.Module
|
||||
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.createBackgroundHandler
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -36,11 +37,14 @@ internal object MatrixModule {
|
||||
@Provides
|
||||
@MatrixScope
|
||||
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,
|
||||
main = Dispatchers.Main,
|
||||
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