1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 16:22:41 +02:00

Compare commits

...

18 Commits

Author SHA1 Message Date
ganfra
9903a299b9 Sqlite: make it work with latest develop version 2020-04-23 20:34:26 +02:00
ganfra
9a4cad1e45 Merge sql crypto store implementation 2020-04-23 18:58:07 +02:00
ganfra
649b4496a6 Make rx module uses Flow/Rx converter 2020-04-23 18:55:10 +02:00
ganfra
617b76c0d5 Make sdk use sqldelight module 2020-04-23 18:53:06 +02:00
ganfra
9415d9b5c5 Configure project 2020-04-23 16:49:51 +02:00
ganfra
3ba5e97392 Introduce all the tables 2020-04-23 12:54:04 +02:00
onurays
0ef5ae38d0 Merge branch 'feature/cross_signing_as_task' into feature/sqldelight_crypto_store 2020-04-15 11:16:29 +03:00
onurays
c53f9359d7 Use json (de)serializer instead of object (de)serialization. 2020-04-13 13:43:39 +03:00
onurays
09c98c32f0 Check and insert metadata at init. 2020-04-09 18:49:10 +03:00
onurays
fc90844556 Check and insert metadata at init. 2020-04-09 18:48:46 +03:00
onurays
0ca7e6202c Use json (de)serializer instead of object (de)serialization. 2020-04-09 16:15:34 +03:00
onurays
96162218e4 Lint fixes. 2020-04-09 02:40:43 +03:00
onurays
ffaacdf436 Use SqlCryptoStore implementation instead of RealmCryptoStore. 2020-04-09 02:30:52 +03:00
onurays
39fdda3715 Realm entities removed. 2020-04-09 00:52:01 +03:00
onurays
af171b3b7e Complete storing user cross signing key function. 2020-04-08 18:05:07 +03:00
onurays
3dd9693f59 Changelog added. 2020-04-08 15:32:14 +03:00
onurays
8b0845a76b Key backup recovery key fields added to metadata. 2020-04-08 11:08:15 +03:00
onurays
8e896600c9 Initial implementation of SQLDelight CryptoStore. 2020-04-08 10:33:06 +03:00
267 changed files with 9581 additions and 4287 deletions

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@
/tmp /tmp
ktlint ktlint
.idea

View 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":[]}]}

View File

@@ -2,52 +2,40 @@ Changes in RiotX 0.19.0 (2020-XX-XX)
=================================================== ===================================================
Features ✨: Features ✨:
- Change password (#528)
- Cross-Signing | Support SSSS secret sharing (#944) - Cross-Signing | Support SSSS secret sharing (#944)
- Cross-Signing | Verify new session from existing session (#1134) - Cross-Signing | Verify new session from existing session (#1134)
- Cross-Signing | Bootstraping cross signing with 4S from mobile (#985) - Cross-Signing | Bootstraping cross signing with 4S from mobile (#985)
- Save media files to Gallery (#973)
Improvements 🙌: Improvements 🙌:
- Verification DM / Handle concurrent .start after .ready (#794) - Verification DM / Handle concurrent .start after .ready (#794)
- Reimplementation of multiple attachment picker
- Cross-Signing | Update Shield Logic for DM (#963) - Cross-Signing | Update Shield Logic for DM (#963)
- Cross-Signing | Complete security new session design update (#1135) - Cross-Signing | Complete security new session design update (#1135)
- Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201) - Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201)
- Cross-Signing | Gossip key backup recovery key (#1200) - Cross-Signing | Gossip key backup recovery key (#1200)
- Show room encryption status as a bubble tile (#1078) - Show room encryption status as a bubble tile (#1078)
- UX/UI | Add indicator to home tab on invite (#957)
- Cross-Signing | Restore history after recover from passphrase (#1214) - Cross-Signing | Restore history after recover from passphrase (#1214)
- Cross-Sign | QR code scan confirmation screens design update (#1187) - Cross-Sign | QR code scan confirmation screens design update (#1187)
- Emoji Verification | It's not the same butterfly! (#1220) - Emoji Verification | It's not the same butterfly! (#1220)
- Cross-Signing | Composer decoration: shields (#1077)
- Cross-Signing | Migrate existing keybackup to cross signing with 4S from mobile (#1197)
Bugfix 🐛: Bugfix 🐛:
- Fix summary notification staying after "mark as read"
- Missing avatar/displayname after verification request message (#841) - Missing avatar/displayname after verification request message (#841)
- Crypto | RiotX sometimes rotate the current device keys (#1170) - Crypto | RiotX sometimes rotate the current device keys (#1170)
- RiotX can't restore cross signing keys saved by web in SSSS (#1174) - RiotX can't restore cross signing keys saved by web in SSSS (#1174)
- Cross- Signing | After signin in new session, verification paper trail in DM is off (#1191) - Cross- Signing | After signin in new session, verification paper trail in DM is off (#1191)
- Failed to encrypt message in room (message stays in red), [thanks to pwr22] (#925) - Failed to encrypt message in room (message stays in red), [thanks to pwr22] (#925)
- Cross-Signing | web <-> riotX After QR code scan, gossiping fails (#1210) - Cross-Signing | web <-> riotX After QR code scan, gossiping fails (#1210)
- Fix crash when trying to download file without internet connection (#1229)
- Local echo are not updated in timeline (for failed & encrypted states)
- Render image event even if thumbnail_info does not have mimetype defined (#1209)
- RiotX now uses as many threads as it needs to do work and send messages (#1221)
- Fix issue with media path (#1227)
Translations 🗣: Translations 🗣:
- -
SDK API changes ⚠️: SDK API changes ⚠️:
- Increase targetSdkVersion to 29 - Implementation of SqlCryptoStore on top of SQLDelight
Build 🧱: Build 🧱:
- Compile with Android SDK 29 (Android Q) -
Other changes: Other changes:
- Add a setting to prevent screenshots of the application, disabled by default (#1027)
- Increase File Logger capacities ( + use dev log preferences) - Increase File Logger capacities ( + use dev log preferences)
Changes in RiotX 0.18.1 (2020-03-17) Changes in RiotX 0.18.1 (2020-03-17)
@@ -456,7 +444,6 @@ Bugfix:
- Fix messages with empty `in_reply_to` not rendering (#447) - Fix messages with empty `in_reply_to` not rendering (#447)
- Fix clear cache (#408) and Logout (#205) - Fix clear cache (#408) and Logout (#205)
- Fix `(edited)` link can be copied to clipboard (#402) - Fix `(edited)` link can be copied to clipboard (#402)
- KeyBackup / SSSS | Should get the key from SSSS instead of asking recovery Key (#1163)
Build: Build:
- Split APK: generate one APK per arch, to reduce APK size of about 30% - Split APK: generate one APK per arch, to reduce APK size of about 30%

View File

@@ -16,7 +16,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
classpath 'com.google.android.gms:oss-licenses-plugin:0.9.5' classpath 'com.google.android.gms:oss-licenses-plugin:0.9.5'
classpath 'com.squareup.sqldelight:gradle-plugin:1.2.2'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
} }
@@ -53,7 +53,7 @@ allprojects {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
// Warnings are potential errors, so stop ignoring them // Warnings are potential errors, so stop ignoring them
kotlinOptions.allWarningsAsErrors = true kotlinOptions.allWarningsAsErrors = false
} }
} }

View File

@@ -38,6 +38,8 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.3.3'
// Paging // Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.0" implementation "androidx.paging:paging-runtime-ktx:2.1.0"

View File

@@ -27,44 +27,34 @@ import im.vector.matrix.android.api.session.room.notification.RoomNotificationSt
import im.vector.matrix.android.api.session.room.send.UserDraft import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import kotlinx.coroutines.rx2.asObservable
class RxRoom(private val room: Room) { class RxRoom(private val room: Room) {
fun liveRoomSummary(): Observable<Optional<RoomSummary>> { fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
return room.getRoomSummaryLive() return room.getRoomSummaryLive().asObservable()
.asObservable()
.startWithCallable { room.roomSummary().toOptional() }
} }
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> { fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
return room.getRoomMembersLive(queryParams).asObservable() return room.getRoomMembersLive(queryParams).asObservable()
.startWithCallable {
room.getRoomMembers(queryParams)
}
} }
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> { fun liveRoomMember(userId: String): Observable<Optional<RoomMemberSummary>> {
return room.getEventAnnotationsSummaryLive(eventId).asObservable() return room.getRoomMemberLive(userId).asObservable()
.startWithCallable {
room.getEventAnnotationsSummary(eventId).toOptional()
} }
fun liveAnnotationSummary(eventId: String): Observable<EventAnnotationsSummary> {
return room.getEventAnnotationsSummaryLive(eventId).asObservable()
} }
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> { fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
return room.getTimeLineEventLive(eventId).asObservable() return room.getTimeLineEventLive(eventId).asObservable()
.startWithCallable {
room.getTimeLineEvent(eventId).toOptional()
}
} }
fun liveStateEvent(eventType: String, stateKey: String): Observable<Optional<Event>> { fun liveStateEvent(eventType: String, stateKey: String): Observable<Optional<Event>> {
return room.getStateEventLive(eventType, stateKey).asObservable() return room.getStateEventLive(eventType, stateKey).asObservable()
.startWithCallable {
room.getStateEvent(eventType, stateKey).toOptional()
}
} }
fun liveReadMarker(): Observable<Optional<String>> { fun liveReadMarker(): Observable<Optional<String>> {
@@ -93,7 +83,7 @@ class RxRoom(private val room: Room) {
} }
fun liveNotificationState(): Observable<RoomNotificationState> { fun liveNotificationState(): Observable<RoomNotificationState> {
return room.getLiveRoomNotificationState().asObservable() return room.getRoomNotificationStateLive().asObservable()
} }
} }

View File

@@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
import im.vector.matrix.android.api.session.room.model.Breadcrumb
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.sync.SyncState
@@ -34,28 +35,20 @@ import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import kotlinx.coroutines.rx2.asObservable
class RxSession(private val session: Session) { class RxSession(private val session: Session) {
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> { fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
return session.getRoomSummariesLive(queryParams).asObservable() return session.getRoomSummariesLive(queryParams).asObservable()
.startWithCallable {
session.getRoomSummaries(queryParams)
}
} }
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> { fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
return session.getGroupSummariesLive(queryParams).asObservable() return session.getGroupSummariesLive(queryParams).asObservable()
.startWithCallable {
session.getGroupSummaries(queryParams)
}
} }
fun liveBreadcrumbs(): Observable<List<RoomSummary>> { fun liveBreadcrumbs(): Observable<List<Breadcrumb>> {
return session.getBreadcrumbsLive().asObservable() return session.getBreadcrumbsLive().asObservable()
.startWithCallable {
session.getBreadcrumbs()
}
} }
fun liveSyncState(): Observable<SyncState> { fun liveSyncState(): Observable<SyncState> {
@@ -68,9 +61,6 @@ class RxSession(private val session: Session) {
fun liveUser(userId: String): Observable<Optional<User>> { fun liveUser(userId: String): Observable<Optional<User>> {
return session.getUserLive(userId).asObservable() return session.getUserLive(userId).asObservable()
.startWithCallable {
session.getUser(userId).toOptional()
}
} }
fun liveUsers(): Observable<List<User>> { fun liveUsers(): Observable<List<User>> {
@@ -125,9 +115,6 @@ class RxSession(private val session: Session) {
fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> { fun liveAccountData(types: Set<String>): Observable<List<UserAccountDataEvent>> {
return session.getLiveAccountDataEvents(types).asObservable() return session.getLiveAccountDataEvents(types).asObservable()
.startWithCallable {
session.getAccountDataEvents(types)
}
} }
} }

View File

@@ -71,6 +71,10 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
} }
packagingOptions {
pickFirst 'META-INF/kotlinx-coroutines-core.kotlin_module'
}
} }
static def gitRevision() { static def gitRevision() {
@@ -125,6 +129,11 @@ dependencies {
// Database // Database
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1' implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
kapt 'dk.ilios:realmfieldnameshelper:1.1.1' kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
implementation "com.squareup.sqldelight:android-driver:1.2.2"
implementation 'net.zetetic:android-database-sqlcipher:4.3.0'
implementation project(':matrix-sdk-sqldelight')
implementation "com.squareup.sqldelight:coroutines-extensions:1.2.1"
implementation "com.squareup.sqldelight:android-paging-extensions:1.2.1"
// Work // Work
implementation "androidx.work:work-runtime-ktx:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version"

View File

@@ -18,8 +18,5 @@ package im.vector.matrix.android
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main, Main, Main)
Executors.newSingleThreadExecutor().asCoroutineDispatcher())

View File

@@ -18,14 +18,18 @@ package im.vector.matrix.android.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod
import im.vector.matrix.android.api.session.crypto.verification.VerificationService import im.vector.matrix.android.api.session.crypto.verification.VerificationService
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.common.CommonTestHelper import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.TestConstants import im.vector.matrix.android.common.TestConstants
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest
import org.amshove.kluent.shouldBe import org.amshove.kluent.shouldBe
import org.junit.Assert.assertEquals
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -229,4 +233,46 @@ class VerificationTest : InstrumentedTest {
cryptoTestData.cleanUp(mTestHelper) cryptoTestData.cleanUp(mTestHelper)
} }
@Test
fun test_alice_sends_text_message_and_bob_can_decrypt() {
val lock = CountDownLatch(1)
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
val roomFromBobPOV = bobSession.getRoom(cryptoTestData.roomId)!!
aliceSession.getRoom(cryptoTestData.roomId)!!.sendTextMessage("test")
val bobEventsListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
// noop
}
override fun onNewTimelineEvents(eventIds: List<String>) {
// noop
}
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
if (snapshot.isNotEmpty() && snapshot.first().root.isEncrypted()) {
lock.countDown()
}
}
}
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20))
bobTimeline.start()
bobTimeline.addListener(bobEventsListener)
mTestHelper.await(lock)
bobTimeline.getTimelineEventAtIndex(0)?.root?.let {
val decryptionResult = bobSession.cryptoService().decryptEvent(it, "")
val text = (decryptionResult.clearEvent["content"] as Map<*, *>)["body"]
assertEquals("test", text)
}
}
} }

View File

@@ -16,12 +16,12 @@
package im.vector.matrix.android.api.session.accountdata package im.vector.matrix.android.api.session.accountdata
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
import kotlinx.coroutines.flow.Flow
interface AccountDataService { interface AccountDataService {
/** /**
@@ -32,7 +32,7 @@ interface AccountDataService {
/** /**
* Observe the account data with the provided type * Observe the account data with the provided type
*/ */
fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> fun getLiveAccountDataEvent(type: String): Flow<Optional<UserAccountDataEvent>>
/** /**
* Retrieve the account data with the provided types. The return list can have a different size that * Retrieve the account data with the provided types. The return list can have a different size that
@@ -44,7 +44,7 @@ interface AccountDataService {
/** /**
* Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed * Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed
*/ */
fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> fun getLiveAccountDataEvents(types: Set<String>): Flow<List<UserAccountDataEvent>>
/** /**
* Update the account data with the provided type and the provided account data content * Update the account data with the provided type and the provided account data content

View File

@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.group
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import kotlinx.coroutines.flow.Flow
/** /**
* This interface defines methods to get groups. It's implemented at the session level. * This interface defines methods to get groups. It's implemented at the session level.
@@ -48,5 +49,5 @@ interface GroupService {
* Get a live list of group summaries. This list is refreshed as soon as the data changes. * Get a live list of group summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of [GroupSummary] * @return the [LiveData] of [GroupSummary]
*/ */
fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): LiveData<List<GroupSummary>> fun getGroupSummariesLive(groupSummaryQueryParams: GroupSummaryQueryParams): Flow<List<GroupSummary>>
} }

View File

@@ -25,9 +25,7 @@ import im.vector.matrix.android.api.session.room.model.Membership
data class GroupSummary( data class GroupSummary(
val groupId: String, val groupId: String,
val membership: Membership, val membership: Membership,
val displayName: String = "", val displayName: String? = null,
val shortDescription: String = "", val shortDescription: String? = null,
val avatarUrl: String = "", val avatarUrl: String? = null
val roomIds: List<String> = emptyList(),
val userIds: List<String> = emptyList()
) )

View File

@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.pushers
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import kotlinx.coroutines.flow.Flow
import java.util.UUID import java.util.UUID
interface PushersService { interface PushersService {
@@ -72,9 +73,9 @@ interface PushersService {
fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable
/** /**
* Get the current pushers, as a LiveData * Get the current pushers, as a Flow
*/ */
fun getPushersLive(): LiveData<List<Pusher>> fun getPushersLive(): Flow<List<Pusher>>
/** /**
* Get the current pushers * Get the current pushers

View File

@@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.room.state.StateService
import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.api.session.room.typing.TypingService import im.vector.matrix.android.api.session.room.typing.TypingService
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/** /**
* This interface defines methods to interact within a room. * This interface defines methods to interact within a room.
@@ -56,7 +57,7 @@ interface Room :
* A live [RoomSummary] associated with the room * A live [RoomSummary] associated with the room
* You can observe this summary to get dynamic data from this room. * You can observe this summary to get dynamic data from this room.
*/ */
fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> fun getRoomSummaryLive(): Flow<Optional<RoomSummary>>
/** /**
* A current snapshot of [RoomSummary] associated with the room * A current snapshot of [RoomSummary] associated with the room

View File

@@ -18,10 +18,12 @@ package im.vector.matrix.android.api.session.room
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.model.Breadcrumb
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/** /**
* This interface defines methods to get rooms. It's implemented at the session level. * This interface defines methods to get rooms. It's implemented at the session level.
@@ -67,21 +69,21 @@ interface RoomService {
/** /**
* Get a live list of room summaries. This list is refreshed as soon as the data changes. * Get a live list of room summaries. This list is refreshed as soon as the data changes.
* @return the [LiveData] of List[RoomSummary] * @return the [Flow] of List[RoomSummary]
*/ */
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): Flow<List<RoomSummary>>
/** /**
* Get a snapshot list of Breadcrumbs * Get a snapshot list of Breadcrumbs
* @return the immutable list of [RoomSummary] * @return the immutable list of [Breadcrumb]
*/ */
fun getBreadcrumbs(): List<RoomSummary> fun getBreadcrumbs(): List<Breadcrumb>
/** /**
* Get a live list of Breadcrumbs * Get a live list of Breadcrumbs
* @return the [LiveData] of [RoomSummary] * @return the [Flow] of [Breadcrumb]
*/ */
fun getBreadcrumbsLive(): LiveData<List<RoomSummary>> fun getBreadcrumbsLive(): Flow<List<Breadcrumb>>
/** /**
* Inform the Matrix SDK that a room is displayed. * Inform the Matrix SDK that a room is displayed.

View File

@@ -28,6 +28,7 @@ fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {
* [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService] * [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService]
*/ */
data class RoomSummaryQueryParams( data class RoomSummaryQueryParams(
val fromGroupId: String?,
val displayName: QueryStringValue, val displayName: QueryStringValue,
val canonicalAlias: QueryStringValue, val canonicalAlias: QueryStringValue,
val memberships: List<Membership> val memberships: List<Membership>
@@ -35,11 +36,13 @@ data class RoomSummaryQueryParams(
class Builder { class Builder {
var fromGroupId: String? = null
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
var memberships: List<Membership> = Membership.all() var memberships: List<Membership> = Membership.all()
fun build() = RoomSummaryQueryParams( fun build() = RoomSummaryQueryParams(
fromGroupId = fromGroupId,
displayName = displayName, displayName = displayName,
canonicalAlias = canonicalAlias, canonicalAlias = canonicalAlias,
memberships = memberships memberships = memberships

View File

@@ -20,6 +20,8 @@ import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/** /**
* This interface defines methods to handling membership. It's implemented at the room level. * This interface defines methods to handling membership. It's implemented at the room level.
@@ -40,6 +42,14 @@ interface MembershipService {
*/ */
fun getRoomMember(userId: String): RoomMemberSummary? fun getRoomMember(userId: String): RoomMemberSummary?
/**
* Return a live version of an optionnal roomMember with the given userId.
* @param userId the userId param to look for
*
* @return a [Flow] of [Optional] [RoomMemberSummary] with userId
*/
fun getRoomMemberLive(userId: String): Flow<Optional<RoomMemberSummary>>
/** /**
* Return all the roomMembers of the room with params * Return all the roomMembers of the room with params
* @param queryParams the params to query for * @param queryParams the params to query for
@@ -50,9 +60,10 @@ interface MembershipService {
/** /**
* Return all the roomMembers of the room filtered by memberships * Return all the roomMembers of the room filtered by memberships
* @param queryParams the params to query for * @param queryParams the params to query for
* @return a [LiveData] of roomMember list. * @return a [Flow] of roomMember list.
*/ */
fun getRoomMembersLive(queryParams: RoomMemberQueryParams): LiveData<List<RoomMemberSummary>> fun getRoomMembersLive(queryParams: RoomMemberQueryParams): Flow<List<RoomMemberSummary>>
fun getNumberOfJoinedMembers(): Int fun getNumberOfJoinedMembers(): Int

View File

@@ -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()
)

View File

@@ -31,7 +31,6 @@ data class RoomSummary constructor(
val topic: String = "", val topic: String = "",
val avatarUrl: String = "", val avatarUrl: String = "",
val canonicalAlias: String? = null, val canonicalAlias: String? = null,
val aliases: List<String> = emptyList(),
val isDirect: Boolean = false, val isDirect: Boolean = false,
val joinedMembersCount: Int? = 0, val joinedMembersCount: Int? = 0,
val invitedMembersCount: Int? = 0, val invitedMembersCount: Int? = 0,

View File

@@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/** /**
* In some cases, events may wish to reference other events. * In some cases, events may wish to reference other events.
@@ -113,12 +114,12 @@ interface RelationService {
* @param eventId the eventId to look for EventAnnotationsSummary * @param eventId the eventId to look for EventAnnotationsSummary
* @return the EventAnnotationsSummary found * @return the EventAnnotationsSummary found
*/ */
fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary
/** /**
* Get a LiveData of EventAnnotationsSummary for the specified eventId * Get a LiveData of EventAnnotationsSummary for the specified eventId
* @param eventId the eventId to look for EventAnnotationsSummary * @param eventId the eventId to look for EventAnnotationsSummary
* @return the LiveData of EventAnnotationsSummary * @return a [Flow] of EventAnnotationsSummary
*/ */
fun getEventAnnotationsSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>> fun getEventAnnotationsSummaryLive(eventId: String): Flow<EventAnnotationsSummary>
} }

View File

@@ -16,13 +16,13 @@
package im.vector.matrix.android.api.session.room.notification package im.vector.matrix.android.api.session.room.notification
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import kotlinx.coroutines.flow.Flow
interface RoomPushRuleService { interface RoomPushRuleService {
fun getLiveRoomNotificationState(): LiveData<RoomNotificationState> fun getRoomNotificationStateLive(): Flow<RoomNotificationState>
fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable
} }

View File

@@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.model.ReadReceipt import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/** /**
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level. * This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
@@ -55,16 +56,16 @@ interface ReadService {
/** /**
* Returns a live read marker id for the room. * Returns a live read marker id for the room.
*/ */
fun getReadMarkerLive(): LiveData<Optional<String>> fun getReadMarkerLive(): Flow<Optional<String>>
/** /**
* Returns a live read receipt id for the room. * Returns a live read receipt id for the room.
*/ */
fun getMyReadReceiptLive(): LiveData<Optional<String>> fun getMyReadReceiptLive(): Flow<Optional<String>>
/** /**
* Returns a live list of read receipts for a given event * Returns a live list of read receipts for a given event
* @param eventId: the event * @param eventId: the event
*/ */
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> fun getEventReadReceiptsLive(eventId: String): Flow<List<ReadReceipt>>
} }

View File

@@ -16,9 +16,9 @@
package im.vector.matrix.android.api.session.room.send package im.vector.matrix.android.api.session.room.send
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import kotlinx.coroutines.flow.Flow
interface DraftService { interface DraftService {
@@ -36,5 +36,5 @@ interface DraftService {
* Return the current drafts if any, as a live data * Return the current drafts if any, as a live data
* The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts * The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts
*/ */
fun getDraftsLive(): LiveData<List<UserDraft>> fun getDraftsLive(): Flow<List<UserDraft>>
} }

View File

@@ -16,10 +16,10 @@
package im.vector.matrix.android.api.session.room.state package im.vector.matrix.android.api.session.room.state
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
interface StateService { interface StateService {
@@ -30,5 +30,5 @@ interface StateService {
fun getStateEvent(eventType: String, stateKey: String): Event? fun getStateEvent(eventType: String, stateKey: String): Event?
fun getStateEventLive(eventType: String, stateKey: String): LiveData<Optional<Event>> fun getStateEventLive(eventType: String, stateKey: String): Flow<Optional<Event>>
} }

View File

@@ -16,8 +16,8 @@
package im.vector.matrix.android.api.session.room.timeline package im.vector.matrix.android.api.session.room.timeline
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/** /**
* This interface defines methods to interact with the timeline. It's implemented at the room level. * This interface defines methods to interact with the timeline. It's implemented at the room level.
@@ -38,5 +38,5 @@ interface TimelineService {
fun getTimeLineEvent(eventId: String): TimelineEvent? fun getTimeLineEvent(eventId: String): TimelineEvent?
fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> fun getTimeLineEventLive(eventId: String): Flow<Optional<TimelineEvent>>
} }

View File

@@ -22,6 +22,7 @@ import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.Optional
import kotlinx.coroutines.flow.Flow
/** /**
* This interface defines methods to get users. It's implemented at the session level. * This interface defines methods to get users. It's implemented at the session level.
@@ -48,27 +49,27 @@ interface UserService {
/** /**
* Observe a live user from a userId * Observe a live user from a userId
* @param userId the userId to look for. * @param userId the userId to look for.
* @return a LiveData of user with userId * @return a Flow of user with userId
*/ */
fun getUserLive(userId: String): LiveData<Optional<User>> fun getUserLive(userId: String): Flow<Optional<User>>
/** /**
* Observe a live list of users sorted alphabetically * Observe a live list of users sorted alphabetically
* @return a Livedata of users * @return a Flow of users
*/ */
fun getUsersLive(): LiveData<List<User>> fun getUsersLive(): Flow<List<User>>
/** /**
* Observe a live [PagedList] of users sorted alphabetically. You can filter the users. * Observe a live [PagedList] of users sorted alphabetically. You can filter the users.
* @param filter the filter. It will look into userId and displayName. * @param filter the filter. It will look into userId and displayName.
* @return a Livedata of users * @return a Flow of users
*/ */
fun getPagedUsersLive(filter: String? = null): LiveData<PagedList<User>> fun getPagedUsersLive(filter: String? = null): Flow<PagedList<User>>
/** /**
* Get list of ignored users * Get list of ignored users
*/ */
fun getIgnoredUsersLive(): LiveData<List<User>> fun getIgnoredUsersLive(): Flow<List<User>>
/** /**
* Ignore users * Ignore users

View File

@@ -18,6 +18,7 @@ package im.vector.matrix.android.api.util
import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.Breadcrumb
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
@@ -148,6 +149,8 @@ fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, ava
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl) fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
fun Breadcrumb.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl) fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
// If no name is available, use room alias as Riot-Web does // If no name is available, use room alias as Riot-Web does

View File

@@ -17,16 +17,19 @@
package im.vector.matrix.android.internal.auth package im.vector.matrix.android.internal.auth
import android.content.Context import android.content.Context
import com.squareup.sqldelight.android.AndroidSqliteDriver
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.internal.auth.db.AuthRealmMigration import im.vector.matrix.android.internal.auth.realm.AuthRealmMigration
import im.vector.matrix.android.internal.auth.db.AuthRealmModule import im.vector.matrix.android.internal.auth.realm.AuthRealmModule
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore import im.vector.matrix.android.internal.auth.sqlite.AuthSchema
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore import im.vector.matrix.android.internal.auth.sqlite.SqlitePendingSessionStore
import im.vector.matrix.android.internal.database.RealmKeysUtils import im.vector.matrix.android.internal.auth.sqlite.SqliteSessionParamsStore
import im.vector.matrix.android.internal.di.AuthDatabase import im.vector.matrix.android.internal.database.DatabaseKeysUtils
import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.sqldelight.auth.AuthDatabase
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import java.io.File import java.io.File
@@ -39,16 +42,16 @@ internal abstract class AuthModule {
@JvmStatic @JvmStatic
@Provides @Provides
@AuthDatabase @im.vector.matrix.android.internal.di.RealmAuthDatabase
fun providesRealmConfiguration(context: Context, realmKeysUtils: RealmKeysUtils): RealmConfiguration { @MatrixScope
fun providesRealmConfiguration(context: Context, databaseKeysUtils: DatabaseKeysUtils): RealmConfiguration {
val old = File(context.filesDir, "matrix-sdk-auth") val old = File(context.filesDir, "matrix-sdk-auth")
if (old.exists()) { if (old.exists()) {
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm")) old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
} }
return RealmConfiguration.Builder() return RealmConfiguration.Builder()
.apply { .apply {
realmKeysUtils.configureEncryption(this, DB_ALIAS) databaseKeysUtils.configureEncryption(this, DB_ALIAS)
} }
.name("matrix-sdk-auth.realm") .name("matrix-sdk-auth.realm")
.modules(AuthRealmModule()) .modules(AuthRealmModule())
@@ -56,13 +59,22 @@ internal abstract class AuthModule {
.migration(AuthRealmMigration) .migration(AuthRealmMigration)
.build() .build()
} }
@JvmStatic
@Provides
@MatrixScope
fun providesAuthDatabase(context: Context, authSchema: AuthSchema, databaseKeysUtils: DatabaseKeysUtils): AuthDatabase {
val supportFactory = databaseKeysUtils.createEncryptedSQLiteOpenHelperFactory(DB_ALIAS)
val driver = AndroidSqliteDriver(authSchema, context, "matrix-sdk-auth.db", factory = supportFactory)
return AuthDatabase.invoke(driver)
}
} }
@Binds @Binds
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore abstract fun bindSessionParamsStore(sessionParamsStore: SqliteSessionParamsStore): SessionParamsStore
@Binds @Binds
abstract fun bindPendingSessionStore(pendingSessionStore: RealmPendingSessionStore): PendingSessionStore abstract fun bindPendingSessionStore(pendingSessionStore: SqlitePendingSessionStore): PendingSessionStore
@Binds @Binds
abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService

View File

@@ -20,13 +20,7 @@ import android.net.Uri
import dagger.Lazy import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.AuthenticationService import im.vector.matrix.android.api.auth.AuthenticationService
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.*
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.LoginFlowResult
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.data.Versions
import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedBySdk
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
import im.vector.matrix.android.api.auth.login.LoginWizard import im.vector.matrix.android.api.auth.login.LoginWizard
import im.vector.matrix.android.api.auth.registration.RegistrationWizard import im.vector.matrix.android.api.auth.registration.RegistrationWizard
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
@@ -35,7 +29,7 @@ import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
import im.vector.matrix.android.internal.auth.data.RiotConfig import im.vector.matrix.android.internal.auth.data.RiotConfig
import im.vector.matrix.android.internal.auth.db.PendingSessionData import im.vector.matrix.android.internal.auth.registration.PendingSessionData
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
import im.vector.matrix.android.internal.di.Unauthenticated import im.vector.matrix.android.internal.di.Unauthenticated
@@ -114,7 +108,7 @@ internal class DefaultAuthenticationService @Inject constructor(
} }
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
return withContext(coroutineDispatchers.io) { return withContext(coroutineDispatchers.computation) {
val authAPI = buildAuthAPI(homeServerConnectionConfig) val authAPI = buildAuthAPI(homeServerConnectionConfig)
// First check the homeserver version // First check the homeserver version

View File

@@ -16,7 +16,7 @@
package im.vector.matrix.android.internal.auth package im.vector.matrix.android.internal.auth
import im.vector.matrix.android.internal.auth.db.PendingSessionData import im.vector.matrix.android.internal.auth.registration.PendingSessionData
/** /**
* Store for elements when doing login or registration * Store for elements when doing login or registration

View File

@@ -30,7 +30,7 @@ import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.auth.SessionCreator import im.vector.matrix.android.internal.auth.SessionCreator
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.auth.data.ThreePidMedium import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import im.vector.matrix.android.internal.auth.db.PendingSessionData import im.vector.matrix.android.internal.auth.registration.PendingSessionData
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
import im.vector.matrix.android.internal.auth.registration.RegisterAddThreePidTask import im.vector.matrix.android.internal.auth.registration.RegisterAddThreePidTask

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.auth.db package im.vector.matrix.android.internal.auth.realm
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.api.auth.data.sessionId

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.auth.db package im.vector.matrix.android.internal.auth.realm
import io.realm.annotations.RealmModule import io.realm.annotations.RealmModule

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.auth.db package im.vector.matrix.android.internal.auth.realm
import io.realm.RealmObject import io.realm.RealmObject

View File

@@ -14,11 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.auth.db package im.vector.matrix.android.internal.auth.realm
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.auth.login.ResetPasswordData import im.vector.matrix.android.internal.auth.login.ResetPasswordData
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
import im.vector.matrix.android.internal.auth.registration.ThreePidData import im.vector.matrix.android.internal.auth.registration.ThreePidData
import javax.inject.Inject import javax.inject.Inject

View File

@@ -14,17 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.auth.db package im.vector.matrix.android.internal.auth.realm
import im.vector.matrix.android.internal.auth.PendingSessionStore import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.auth.registration.PendingSessionData
import im.vector.matrix.android.internal.database.awaitTransaction import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.di.AuthDatabase import im.vector.matrix.android.internal.di.RealmAuthDatabase
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import javax.inject.Inject import javax.inject.Inject
internal class RealmPendingSessionStore @Inject constructor(private val mapper: PendingSessionMapper, internal class RealmPendingSessionStore @Inject constructor(private val mapper: PendingSessionMapper,
@AuthDatabase @RealmAuthDatabase
private val realmConfiguration: RealmConfiguration private val realmConfiguration: RealmConfiguration
) : PendingSessionStore { ) : PendingSessionStore {

View File

@@ -14,14 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.auth.db package im.vector.matrix.android.internal.auth.realm
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.api.auth.data.sessionId
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.database.awaitTransaction import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.di.AuthDatabase import im.vector.matrix.android.internal.di.RealmAuthDatabase
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.exceptions.RealmPrimaryKeyConstraintException import io.realm.exceptions.RealmPrimaryKeyConstraintException
@@ -29,7 +29,7 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper, internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper,
@AuthDatabase @RealmAuthDatabase
private val realmConfiguration: RealmConfiguration private val realmConfiguration: RealmConfiguration
) : SessionParamsStore { ) : SessionParamsStore {

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.auth.db package im.vector.matrix.android.internal.auth.realm
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey

View File

@@ -14,8 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.auth.db package im.vector.matrix.android.internal.auth.realm
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
@@ -28,6 +29,15 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
private val credentialsAdapter = moshi.adapter(Credentials::class.java) private val credentialsAdapter = moshi.adapter(Credentials::class.java)
private val homeServerConnectionConfigAdapter = moshi.adapter(HomeServerConnectionConfig::class.java) private val homeServerConnectionConfigAdapter = moshi.adapter(HomeServerConnectionConfig::class.java)
fun map(credentialsJson: String, homeServerConnectionConfigJson: String, isTokenValid: Boolean): SessionParams {
val credentials = credentialsAdapter.fromJson(credentialsJson)
val homeServerConnectionConfig = homeServerConnectionConfigAdapter.fromJson(homeServerConnectionConfigJson)
if (credentials == null || homeServerConnectionConfig == null) {
throw JsonDataException()
}
return SessionParams(credentials, homeServerConnectionConfig, isTokenValid)
}
fun map(entity: SessionParamsEntity?): SessionParams? { fun map(entity: SessionParamsEntity?): SessionParams? {
if (entity == null) { if (entity == null) {
return null return null

View File

@@ -29,7 +29,6 @@ import im.vector.matrix.android.internal.auth.AuthAPI
import im.vector.matrix.android.internal.auth.PendingSessionStore import im.vector.matrix.android.internal.auth.PendingSessionStore
import im.vector.matrix.android.internal.auth.SessionCreator import im.vector.matrix.android.internal.auth.SessionCreator
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.auth.db.PendingSessionData
import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.task.launchToCallback import im.vector.matrix.android.internal.task.launchToCallback
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers

View File

@@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.internal.auth.db package im.vector.matrix.android.internal.auth.registration
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.auth.login.ResetPasswordData import im.vector.matrix.android.internal.auth.login.ResetPasswordData
import im.vector.matrix.android.internal.auth.registration.ThreePidData import im.vector.matrix.android.internal.auth.registration.ThreePidData
import java.util.UUID import java.util.*
/** /**
* This class holds all pending data when creating a session, either by login or by register * This class holds all pending data when creating a session, either by login or by register

View File

@@ -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
)
}
}

View File

@@ -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
)
}
}

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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()
}
}

View File

@@ -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))
}

View File

@@ -16,9 +16,15 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto
import android.content.Context
import android.util.Log
import com.squareup.sqldelight.ColumnAdapter
import com.squareup.sqldelight.android.AndroidSqliteDriver
import com.squareup.sqldelight.logs.LogSqliteDriver
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
@@ -55,9 +61,9 @@ import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessio
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
import im.vector.matrix.android.internal.crypto.store.db.SqlCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice import im.vector.matrix.android.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceTask import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceTask
@@ -86,13 +92,15 @@ import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
import im.vector.matrix.android.internal.database.RealmKeysUtils import im.vector.matrix.android.internal.database.DatabaseKeysUtils
import im.vector.matrix.android.internal.di.CryptoDatabase import im.vector.matrix.android.internal.di.RealmCryptoDatabase
import im.vector.matrix.android.internal.di.SessionFilesDirectory import im.vector.matrix.android.internal.di.SessionFilesDirectory
import im.vector.matrix.android.internal.di.UserMd5 import im.vector.matrix.android.internal.di.UserMd5
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.cache.ClearCacheTask import im.vector.matrix.android.internal.session.cache.ClearCacheTask
import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
import im.vector.matrix.sqldelight.crypto.CrossSigningInfoEntity
import im.vector.matrix.sqldelight.crypto.CryptoDatabase
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@@ -108,15 +116,15 @@ internal abstract class CryptoModule {
@JvmStatic @JvmStatic
@Provides @Provides
@CryptoDatabase @RealmCryptoDatabase
@SessionScope @SessionScope
fun providesRealmConfiguration(@SessionFilesDirectory directory: File, fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
@UserMd5 userMd5: String, @UserMd5 userMd5: String,
realmKeysUtils: RealmKeysUtils): RealmConfiguration { databaseKeysUtils: DatabaseKeysUtils): RealmConfiguration {
return RealmConfiguration.Builder() return RealmConfiguration.Builder()
.directory(directory) .directory(directory)
.apply { .apply {
realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5)) databaseKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
} }
.name("crypto_store.realm") .name("crypto_store.realm")
.modules(RealmCryptoStoreModule()) .modules(RealmCryptoStoreModule())
@@ -125,6 +133,31 @@ internal abstract class CryptoModule {
.build() .build()
} }
@JvmStatic
@Provides
@SessionScope
fun providesCryptoDatabase(context: Context, @SessionFilesDirectory directory: File): CryptoDatabase {
val name = "${directory.name}-matrix-sdk-crypto.db"
val driver = if (BuildConfig.DEBUG) {
LogSqliteDriver(AndroidSqliteDriver(CryptoDatabase.Schema, context, name)) { log ->
Log.d("SQLite", log)
}
} else {
AndroidSqliteDriver(CryptoDatabase.Schema, context, name)
}
val listOfStringAdapter = object : ColumnAdapter<List<String>, String> {
override fun decode(databaseValue: String) = databaseValue.split(",")
override fun encode(value: List<String>) = value.joinToString(separator = ",")
}
return CryptoDatabase(
driver = driver,
crossSigningInfoEntityAdapter = CrossSigningInfoEntity.Adapter(
usagesAdapter = listOfStringAdapter
)
)
}
@JvmStatic @JvmStatic
@Provides @Provides
@SessionScope @SessionScope
@@ -134,8 +167,8 @@ internal abstract class CryptoModule {
@JvmStatic @JvmStatic
@Provides @Provides
@CryptoDatabase @RealmCryptoDatabase
fun providesClearCacheTask(@CryptoDatabase fun providesClearCacheTask(@RealmCryptoDatabase
realmConfiguration: RealmConfiguration): ClearCacheTask { realmConfiguration: RealmConfiguration): ClearCacheTask {
return RealmClearCacheTask(realmConfiguration) return RealmClearCacheTask(realmConfiguration)
} }
@@ -243,7 +276,7 @@ internal abstract class CryptoModule {
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
@Binds @Binds
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore abstract fun bindCryptoStore(store: SqlCryptoStore): IMXCryptoStore
@Binds @Binds
abstract fun bindComputeShieldTrustTask(task: DefaultComputeTrustTask): ComputeTrustTask abstract fun bindComputeShieldTrustTask(task: DefaultComputeTrustTask): ComputeTrustTask

View File

@@ -23,7 +23,6 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.squareup.moshi.Types import com.squareup.moshi.Types
import com.zhuinden.monarchy.Monarchy
import dagger.Lazy import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.NoOpMatrixCallback import im.vector.matrix.android.api.NoOpMatrixCallback
@@ -33,7 +32,6 @@ import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener
@@ -53,11 +51,7 @@ import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFa
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.*
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent
import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent
@@ -65,19 +59,11 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.model.toRest
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask import im.vector.matrix.android.internal.crypto.tasks.*
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.repository.CurrentStateEventDataSource
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.whereType
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
@@ -89,13 +75,8 @@ import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.JsonCanonicalizer import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.fetchCopied import im.vector.matrix.sqldelight.session.SessionDatabase
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.olm.OlmManager import org.matrix.olm.OlmManager
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@@ -159,7 +140,7 @@ internal class DefaultCryptoService @Inject constructor(
private val setDeviceNameTask: SetDeviceNameTask, private val setDeviceNameTask: SetDeviceNameTask,
private val uploadKeysTask: UploadKeysTask, private val uploadKeysTask: UploadKeysTask,
private val loadRoomMembersTask: LoadRoomMembersTask, private val loadRoomMembersTask: LoadRoomMembersTask,
private val monarchy: Monarchy, private val sessionDatabase: SessionDatabase,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val cryptoCoroutineScope: CoroutineScope private val cryptoCoroutineScope: CoroutineScope
@@ -412,7 +393,7 @@ internal class DefaultCryptoService @Inject constructor(
} }
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> { override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDevices(userId)?.map { it.value }?.sortedBy { it.deviceId } ?: emptyList() return cryptoStore.getUserDevices(userId)?.map { it.value } ?: emptyList()
} }
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> { override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
@@ -542,12 +523,10 @@ internal class DefaultCryptoService @Inject constructor(
* @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM * @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM
*/ */
override fun isRoomEncrypted(roomId: String): Boolean { override fun isRoomEncrypted(roomId: String): Boolean {
val encryptionEvent = monarchy.fetchCopied { realm -> return sessionDatabase.eventQueries
EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) .findWithContent(roomId = roomId, content = "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"") .executeAsList()
.findFirst() .firstOrNull() != null
}
return encryptionEvent != null
} }
/** /**
@@ -767,30 +746,19 @@ internal class DefaultCryptoService @Inject constructor(
return return
} }
if (!handleSDKLevelGossip(existingRequest.secretName, secretContent.secretValue)) { when (existingRequest.secretName) {
// TODO Ask to application layer?
Timber.v("## onSecretSend() : secret not handled by SDK")
}
}
/**
* Returns true if handled by SDK, otherwise should be sent to application layer
*/
private fun handleSDKLevelGossip(secretName: String?, secretValue: String): Boolean {
return when (secretName) {
SELF_SIGNING_KEY_SSSS_NAME -> { SELF_SIGNING_KEY_SSSS_NAME -> {
crossSigningService.onSecretSSKGossip(secretValue) crossSigningService.onSecretSSKGossip(secretContent.secretValue)
true return
} }
USER_SIGNING_KEY_SSSS_NAME -> { USER_SIGNING_KEY_SSSS_NAME -> {
crossSigningService.onSecretUSKGossip(secretValue) crossSigningService.onSecretUSKGossip(secretContent.secretValue)
true return
} }
KEYBACKUP_SECRET_SSSS_NAME -> { else -> {
keysBackupService.onSecretKeyGossip(secretValue) // Ask to application layer?
true Timber.v("## onSecretSend() : secret not handled by SDK")
} }
else -> false
} }
} }
@@ -804,30 +772,25 @@ internal class DefaultCryptoService @Inject constructor(
val params = LoadRoomMembersTask.Params(roomId) val params = LoadRoomMembersTask.Params(roomId)
try { try {
loadRoomMembersTask.execute(params) loadRoomMembersTask.execute(params)
} catch (throwable: Throwable) {
Timber.e(throwable, "## onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
} finally {
val userIds = getRoomUserIds(roomId) val userIds = getRoomUserIds(roomId)
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds) setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
} catch (throwable: Throwable) {
Timber.e(throwable, "## onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ")
} }
} }
} }
private fun getRoomUserIds(roomId: String): List<String> { private fun getRoomUserIds(roomId: String): List<String> {
var userIds: List<String> = emptyList()
monarchy.doWithRealm { realm ->
// Check whether the event content must be encrypted for the invited members. // Check whether the event content must be encrypted for the invited members.
val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser() val encryptForInvitedMembers = isEncryptionEnabledForInvitedUser()
&& shouldEncryptForInvitedMembers(roomId) && shouldEncryptForInvitedMembers(roomId)
userIds = if (encryptForInvitedMembers) { return if (encryptForInvitedMembers) {
RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() RoomMemberHelper(sessionDatabase, roomId).getActiveRoomMemberIds()
} else { } else {
RoomMemberHelper(realm, roomId).getJoinedRoomMemberIds() RoomMemberHelper(sessionDatabase, roomId).getJoinedRoomMemberIds()
} }
} }
return userIds
}
/** /**
* Handle a change in the membership state of a member of a room. * Handle a change in the membership state of a member of a room.

View File

@@ -15,23 +15,18 @@
*/ */
package im.vector.matrix.android.internal.crypto.crosssigning package im.vector.matrix.android.internal.crypto.crosssigning
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields import im.vector.matrix.android.internal.database.mapper.map
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.createBackgroundHandler import im.vector.matrix.android.internal.util.createBackgroundHandler
import io.realm.Realm import im.vector.matrix.sqldelight.session.SessionDatabase
import io.realm.RealmConfiguration import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject import javax.inject.Inject
internal class ShieldTrustUpdater @Inject constructor( internal class ShieldTrustUpdater @Inject constructor(
@@ -39,35 +34,23 @@ internal class ShieldTrustUpdater @Inject constructor(
private val computeTrustTask: ComputeTrustTask, private val computeTrustTask: ComputeTrustTask,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
@SessionDatabase private val sessionRealmConfiguration: RealmConfiguration, private val sessionDatabase: SessionDatabase) {
private val roomSummaryUpdater: RoomSummaryUpdater
) {
companion object { companion object {
private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD") private val BACKGROUND_HANDLER = createBackgroundHandler("SHIELD_CRYPTO_DB_THREAD")
} }
private val backgroundSessionRealm = AtomicReference<Realm>()
private val isStarted = AtomicBoolean() private val isStarted = AtomicBoolean()
fun start() { fun start() {
if (isStarted.compareAndSet(false, true)) { if (isStarted.compareAndSet(false, true)) {
eventBus.register(this) eventBus.register(this)
BACKGROUND_HANDLER.post {
backgroundSessionRealm.set(Realm.getInstance(sessionRealmConfiguration))
}
} }
} }
fun stop() { fun stop() {
if (isStarted.compareAndSet(true, false)) { if (isStarted.compareAndSet(true, false)) {
eventBus.unregister(this) eventBus.unregister(this)
BACKGROUND_HANDLER.post {
backgroundSessionRealm.getAndSet(null).also {
it?.close()
}
}
} }
} }
@@ -76,16 +59,7 @@ internal class ShieldTrustUpdater @Inject constructor(
if (!isStarted.get()) { if (!isStarted.get()) {
return return
} }
taskExecutor.executorScope.launch(coroutineDispatchers.crypto) { taskExecutor.executorScope.updateTrustLevel(update.roomId, update.userIds)
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(update.userIds))
// We need to send that back to session base
BACKGROUND_HANDLER.post {
backgroundSessionRealm.get()?.executeTransaction { realm ->
roomSummaryUpdater.updateShieldTrust(realm, update.roomId, updatedTrust)
}
}
}
} }
@Subscribe @Subscribe
@@ -93,48 +67,32 @@ internal class ShieldTrustUpdater @Inject constructor(
if (!isStarted.get()) { if (!isStarted.get()) {
return return
} }
onCryptoDevicesChange(update.userIds) onCryptoDevicesChange(update.userIds)
} }
private fun onCryptoDevicesChange(users: List<String>) { private fun onCryptoDevicesChange(users: List<String>) {
BACKGROUND_HANDLER.post { BACKGROUND_HANDLER.post {
val impactedRoomsId = backgroundSessionRealm.get()?.where(RoomMemberSummaryEntity::class.java) val impactedRoomsId = sessionDatabase.roomMemberSummaryQueries.getAllRoomIdsFromUsers(users).executeAsList()
?.`in`(RoomMemberSummaryEntityFields.USER_ID, users.toTypedArray()) impactedRoomsId.forEach { roomId ->
?.findAll() val allUsersFromRoom = sessionDatabase.roomMemberSummaryQueries.getAllUserIdFromRoom(
?.map { it.roomId } memberships = Membership.all().map(),
?.distinct() excludedIds = emptyList(),
roomId = roomId
val map = HashMap<String, List<String>>() ).executeAsList()
impactedRoomsId?.forEach { roomId -> taskExecutor.executorScope.updateTrustLevel(roomId, allUsersFromRoom)
backgroundSessionRealm.get()?.let { realm ->
RoomMemberSummaryEntity.where(realm, roomId)
.findAll()
.let { results ->
map[roomId] = results.map { it.userId }
} }
} }
} }
map.forEach { entry -> private fun CoroutineScope.updateTrustLevel(roomId: String, userIds: List<String>) {
val roomId = entry.key launch(coroutineDispatchers.dbTransaction) {
val userList = entry.value
taskExecutor.executorScope.launch {
withContext(coroutineDispatchers.crypto) {
try { try {
// Can throw if the crypto database has been closed in between, in this case log and ignore? // Can throw if the crypto database has been closed in between, in this case log and ignore?
val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userList)) val updatedTrust = computeTrustTask.execute(ComputeTrustTask.Params(userIds))
BACKGROUND_HANDLER.post { sessionDatabase.roomSummaryQueries.setRoomEncryptionTrustLevel(updatedTrust.name, roomId)
backgroundSessionRealm.get()?.executeTransaction { realm ->
roomSummaryUpdater.updateShieldTrust(realm, roomId, updatedTrust)
}
}
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.e(failure) Timber.e(failure)
} }
} }
} }
} }
}
}
}

View File

@@ -69,7 +69,6 @@ import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.SavedKeyBackupKeyInfo import im.vector.matrix.android.internal.crypto.store.SavedKeyBackupKeyInfo
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.extensions.foldToCallback
@@ -1154,11 +1153,11 @@ internal class DefaultKeysBackupService @Inject constructor(
* Update the DB with data fetch from the server * Update the DB with data fetch from the server
*/ */
private fun onServerDataRetrieved(count: Int?, hash: String?) { private fun onServerDataRetrieved(count: Int?, hash: String?) {
cryptoStore.setKeysBackupData(KeysBackupDataEntity() cryptoStore.setKeysBackupData(
.apply { im.vector.matrix.android.internal.crypto.model.rest.KeysBackupData(
backupLastServerNumberOfKeys = count backupLastServerNumberOfKeys = count,
backupLastServerHash = hash backupLastServerHash = hash
} )
) )
} }

View File

@@ -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?)

View File

@@ -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"
}
}

View File

@@ -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
}
}

View File

@@ -32,8 +32,8 @@ import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
import im.vector.matrix.android.internal.crypto.model.rest.KeysBackupData
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity
import org.matrix.olm.OlmAccount import org.matrix.olm.OlmAccount
/** /**
@@ -104,14 +104,14 @@ internal interface IMXCryptoStore {
/** /**
* Get the current keys backup local data * Get the current keys backup local data
*/ */
fun getKeysBackupData(): KeysBackupDataEntity? fun getKeysBackupData(): KeysBackupData?
/** /**
* Set the keys backup local data * Set the keys backup local data
* *
* @param keysBackupData the keys backup local data, or null to erase data * @param keysBackupData the keys backup local data, or null to erase data
*/ */
fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?) fun setKeysBackupData(keysBackupData: KeysBackupData?)
/** /**
* @return the devices statuses map (userId -> tracking status) * @return the devices statuses map (userId -> tracking status)

View File

@@ -41,6 +41,7 @@ import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
import im.vector.matrix.android.internal.crypto.model.rest.KeysBackupData
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.model.toEntity import im.vector.matrix.android.internal.crypto.model.toEntity
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@@ -74,8 +75,8 @@ import im.vector.matrix.android.internal.crypto.store.db.query.get
import im.vector.matrix.android.internal.crypto.store.db.query.getById import im.vector.matrix.android.internal.crypto.store.db.query.getById
import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate
import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.di.CryptoDatabase
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.di.RealmCryptoDatabase
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
@@ -90,7 +91,7 @@ import kotlin.collections.set
@SessionScope @SessionScope
internal class RealmCryptoStore @Inject constructor( internal class RealmCryptoStore @Inject constructor(
@CryptoDatabase private val realmConfiguration: RealmConfiguration, @RealmCryptoDatabase private val realmConfiguration: RealmConfiguration,
private val credentials: Credentials) : IMXCryptoStore { private val credentials: Credentials) : IMXCryptoStore {
/* ========================================================================================== /* ==========================================================================================
@@ -705,13 +706,18 @@ internal class RealmCryptoStore @Inject constructor(
} }
} }
override fun getKeysBackupData(): KeysBackupDataEntity? { override fun getKeysBackupData(): KeysBackupData? {
return doRealmQueryAndCopy(realmConfiguration) { return doRealmQueryAndCopy(realmConfiguration) {
it.where<KeysBackupDataEntity>().findFirst() it.where<KeysBackupDataEntity>().findFirst()
}?.let { entity ->
return KeysBackupData(
backupLastServerHash = entity.backupLastServerHash,
backupLastServerNumberOfKeys = entity.backupLastServerNumberOfKeys
)
} }
} }
override fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?) { override fun setKeysBackupData(keysBackupData: KeysBackupData?) {
doRealmTransaction(realmConfiguration) { doRealmTransaction(realmConfiguration) {
if (keysBackupData == null) { if (keysBackupData == null) {
// Clear the table // Clear the table
@@ -720,7 +726,10 @@ internal class RealmCryptoStore @Inject constructor(
.deleteAllFromRealm() .deleteAllFromRealm()
} else { } else {
// Only one object // Only one object
it.copyToRealmOrUpdate(keysBackupData) it.copyToRealmOrUpdate(it.createObject(KeysBackupDataEntity::class.java).apply {
backupLastServerHash = keysBackupData.backupLastServerHash
backupLastServerNumberOfKeys = keysBackupData.backupLastServerNumberOfKeys
})
} }
} }
} }

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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
// }
// }
// }

View File

@@ -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
// )
// }
// }

View File

@@ -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
// }
// }
// }

View File

@@ -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)
// }
// }

View File

@@ -19,35 +19,33 @@ package im.vector.matrix.android.internal.crypto.tasks
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.crypto.verification.VerificationService import im.vector.matrix.android.api.session.crypto.verification.VerificationService
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.model.message.MessageRelationContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationReadyContent
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent
import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.di.DeviceId import im.vector.matrix.android.internal.di.DeviceId
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.sqldelight.session.EventInsertNotification
import im.vector.matrix.sqldelight.session.SessionDatabase
import timber.log.Timber import timber.log.Timber
import java.util.ArrayList import java.util.*
import javax.inject.Inject import javax.inject.Inject
internal interface RoomVerificationUpdateTask : Task<RoomVerificationUpdateTask.Params, Unit> { internal interface RoomVerificationUpdateTask : Task<RoomVerificationUpdateTask.Params, Unit> {
data class Params( data class Params(
val events: List<Event>, val eventInsertNotifications: List<EventInsertNotification>
val verificationService: DefaultVerificationService,
val cryptoService: CryptoService
) )
} }
internal class DefaultRoomVerificationUpdateTask @Inject constructor( internal class DefaultRoomVerificationUpdateTask @Inject constructor(
@UserId private val userId: String, @UserId private val userId: String,
@DeviceId private val deviceId: String?, @DeviceId private val deviceId: String?,
private val sessionDatabase: SessionDatabase,
private val verificationService: DefaultVerificationService,
private val cryptoService: CryptoService) : RoomVerificationUpdateTask { private val cryptoService: CryptoService) : RoomVerificationUpdateTask {
companion object { companion object {
@@ -57,8 +55,15 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
override suspend fun execute(params: RoomVerificationUpdateTask.Params) { override suspend fun execute(params: RoomVerificationUpdateTask.Params) {
// TODO ignore initial sync or back pagination? // TODO ignore initial sync or back pagination?
params.eventInsertNotifications.forEach { eventInsertNotification ->
val eventId = eventInsertNotification.event_id
val isLocalEcho = LocalEcho.isLocalEchoId(eventId)
if (isLocalEcho) {
return@forEach
}
val event = sessionDatabase.eventQueries.select(eventId).executeAsOneOrNull()?.asDomain()
?: return@forEach
params.events.forEach { event ->
Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}") Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}")
// If the request is in the future by more than 5 minutes or more than 10 minutes in the past, // If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
@@ -74,7 +79,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
// TODO use a global event decryptor? attache to session and that listen to new sessionId? // TODO use a global event decryptor? attache to session and that listen to new sessionId?
// for now decrypt sync // for now decrypt sync
try { try {
val result = cryptoService.decryptEvent(event, "") val result = cryptoService.decryptEvent(event, event.roomId + UUID.randomUUID().toString())
event.mxDecryptionResult = OlmDecryptionResult( event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent, payload = result.clearEvent,
senderKey = result.senderCurve25519Key, senderKey = result.senderCurve25519Key,
@@ -83,7 +88,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
) )
} catch (e: MXCryptoError) { } catch (e: MXCryptoError) {
Timber.e("## SAS Failed to decrypt event: ${event.eventId}") Timber.e("## SAS Failed to decrypt event: ${event.eventId}")
params.verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event) verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event)
} }
} }
Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}")
@@ -112,7 +117,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
// The verification is started from another device // The verification is started from another device
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
params.verificationService.onRoomRequestHandledByOtherDevice(event) verificationService.onRoomRequestHandledByOtherDevice(event)
} }
} }
} else if (EventType.KEY_VERIFICATION_READY == event.getClearType()) { } else if (EventType.KEY_VERIFICATION_READY == event.getClearType()) {
@@ -121,13 +126,13 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
// The verification is started from another device // The verification is started from another device
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
params.verificationService.onRoomRequestHandledByOtherDevice(event) verificationService.onRoomRequestHandledByOtherDevice(event)
} }
} }
} else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) { } else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) {
relatesToEventId?.let { relatesToEventId?.let {
transactionsHandledByOtherDevice.remove(it) transactionsHandledByOtherDevice.remove(it)
params.verificationService.onRoomRequestHandledByOtherDevice(event) verificationService.onRoomRequestHandledByOtherDevice(event)
} }
} }
@@ -148,11 +153,11 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor(
EventType.KEY_VERIFICATION_CANCEL, EventType.KEY_VERIFICATION_CANCEL,
EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_DONE -> { EventType.KEY_VERIFICATION_DONE -> {
params.verificationService.onRoomEvent(event) verificationService.onRoomEvent(event)
} }
EventType.MESSAGE -> { EventType.MESSAGE -> {
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) { if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) {
params.verificationService.onRoomRequestReceived(event) verificationService.onRoomRequestReceived(event)
} }
} }
} }

View File

@@ -15,34 +15,21 @@
*/ */
package im.vector.matrix.android.internal.crypto.verification package im.vector.matrix.android.internal.crypto.verification
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.internal.crypto.tasks.DefaultRoomVerificationUpdateTask import im.vector.matrix.android.internal.crypto.tasks.DefaultRoomVerificationUpdateTask
import im.vector.matrix.android.internal.crypto.tasks.RoomVerificationUpdateTask import im.vector.matrix.android.internal.crypto.tasks.RoomVerificationUpdateTask
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.SqlLiveEntityObserver
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.sqldelight.session.EventInsertNotification
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.sqldelight.session.SessionDatabase
import im.vector.matrix.android.internal.database.query.whereTypes
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import io.realm.OrderedCollectionChangeSet
import io.realm.RealmConfiguration
import io.realm.RealmResults
import javax.inject.Inject import javax.inject.Inject
internal class VerificationMessageLiveObserver @Inject constructor( internal class VerificationMessageLiveObserver @Inject constructor(
@SessionDatabase realmConfiguration: RealmConfiguration, sessionDatabase: SessionDatabase,
private val roomVerificationUpdateTask: DefaultRoomVerificationUpdateTask, private val roomVerificationUpdateTask: DefaultRoomVerificationUpdateTask
private val cryptoService: CryptoService, ) : SqlLiveEntityObserver<EventInsertNotification>(sessionDatabase) {
private val verificationService: DefaultVerificationService,
private val taskExecutor: TaskExecutor
) : RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
override val query = Monarchy.Query { override val query = sessionDatabase.observerTriggerQueries.getAllEventInsertNotifications(
EventEntity.whereTypes(it, listOf( types = listOf(
EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_START,
EventType.KEY_VERIFICATION_ACCEPT, EventType.KEY_VERIFICATION_ACCEPT,
EventType.KEY_VERIFICATION_KEY, EventType.KEY_VERIFICATION_KEY,
@@ -53,21 +40,12 @@ internal class VerificationMessageLiveObserver @Inject constructor(
EventType.MESSAGE, EventType.MESSAGE,
EventType.ENCRYPTED) EventType.ENCRYPTED)
) )
override suspend fun handleChanges(results: List<EventInsertNotification>) {
val params = RoomVerificationUpdateTask.Params(results)
roomVerificationUpdateTask.execute(params)
val notificationIds = results.map { it.event_id }
sessionDatabase.observerTriggerQueries.deleteEventInsertNotifications(notificationIds)
} }
override fun onChange(results: RealmResults<EventEntity>, changeSet: OrderedCollectionChangeSet) {
// Should we ignore when it's an initial sync?
val events = changeSet.insertions
.asSequence()
.mapNotNull { results[it]?.asDomain() }
.filterNot {
// ignore local echos
LocalEcho.isLocalEchoId(it.eventId ?: "")
}
.toList()
roomVerificationUpdateTask.configureWith(
RoomVerificationUpdateTask.Params(events, verificationService, cryptoService)
).executeBy(taskExecutor)
}
} }

View File

@@ -15,6 +15,8 @@
*/ */
package im.vector.matrix.android.internal.database package im.vector.matrix.android.internal.database
import com.squareup.sqldelight.Transacter
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -22,7 +24,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T) = withContext(Dispatchers.Default) { suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T) = withContext(Dispatchers.IO) {
Realm.getInstance(config).use { bgRealm -> Realm.getInstance(config).use { bgRealm ->
bgRealm.beginTransaction() bgRealm.beginTransaction()
val result: T val result: T
@@ -43,3 +45,22 @@ suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspen
result result
} }
} }
internal suspend fun <T : Transacter> T.awaitTransaction(dispatchers: MatrixCoroutineDispatchers, noEnclosing: Boolean = false, transaction: (db: T) -> Unit) = withContext(dispatchers.dbTransaction) {
transaction(noEnclosing = noEnclosing) {
try {
val start = System.currentTimeMillis()
transaction(this@awaitTransaction)
if (isActive) {
val end = System.currentTimeMillis()
val time = end - start
Timber.v("Execute transaction in $time millis")
} else {
rollback()
}
} catch (exception: Exception) {
Timber.e(exception)
rollback()
}
}
}

View File

@@ -17,9 +17,11 @@ package im.vector.matrix.android.internal.database
import android.content.Context import android.content.Context
import android.util.Base64 import android.util.Base64
import androidx.sqlite.db.SupportSQLiteOpenHelper
import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.internal.session.securestorage.SecretStoringUtils import im.vector.matrix.android.internal.session.securestorage.SecretStoringUtils
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import net.sqlcipher.database.SupportFactory
import timber.log.Timber import timber.log.Timber
import java.security.SecureRandom import java.security.SecureRandom
import javax.inject.Inject import javax.inject.Inject
@@ -36,7 +38,7 @@ import javax.inject.Inject
* then we generate a random secret key. The database key is encrypted with the secret key; The secret * then we generate a random secret key. The database key is encrypted with the secret key; The secret
* key is encrypted with the public RSA key and stored with the encrypted key in the shared pref * key is encrypted with the public RSA key and stored with the encrypted key in the shared pref
*/ */
internal class RealmKeysUtils @Inject constructor(context: Context, internal class DatabaseKeysUtils @Inject constructor(context: Context,
private val secretStoringUtils: SecretStoringUtils) { private val secretStoringUtils: SecretStoringUtils) {
private val rng = SecureRandom() private val rng = SecureRandom()
@@ -85,7 +87,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
return Base64.decode(b64!!, Base64.NO_PADDING) return Base64.decode(b64!!, Base64.NO_PADDING)
} }
fun configureEncryption(realmConfigurationBuilder: RealmConfiguration.Builder, alias: String) { private fun getOrCreateEncryptionKey(alias: String): ByteArray {
val key = if (hasKeyForDatabase(alias)) { val key = if (hasKeyForDatabase(alias)) {
Timber.i("Found key for alias:$alias") Timber.i("Found key for alias:$alias")
extractKeyForDatabase(alias) extractKeyForDatabase(alias)
@@ -98,7 +100,16 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
val log = key.joinToString("") { "%02x".format(it) } val log = key.joinToString("") { "%02x".format(it) }
Timber.w("Database key for alias `$alias`: $log") Timber.w("Database key for alias `$alias`: $log")
} }
return key
}
fun createEncryptedSQLiteOpenHelperFactory(alias: String): SupportSQLiteOpenHelper.Factory {
val key = getOrCreateEncryptionKey(alias)
return SupportFactory(key)
}
fun configureEncryption(realmConfigurationBuilder: RealmConfiguration.Builder, alias: String) {
val key = getOrCreateEncryptionKey(alias)
realmConfigurationBuilder.encryptionKey(key) realmConfigurationBuilder.encryptionKey(key)
} }

View File

@@ -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()
}
}

View File

@@ -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()
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -16,79 +16,36 @@
package im.vector.matrix.android.internal.database package im.vector.matrix.android.internal.database
import android.content.Context
import im.vector.matrix.android.internal.database.model.SessionRealmModule import im.vector.matrix.android.internal.database.model.SessionRealmModule
import im.vector.matrix.android.internal.di.SessionFilesDirectory import im.vector.matrix.android.internal.di.SessionFilesDirectory
import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.di.SessionId
import im.vector.matrix.android.internal.di.UserMd5 import im.vector.matrix.android.internal.di.UserMd5
import im.vector.matrix.android.internal.session.SessionModule import im.vector.matrix.android.internal.session.SessionModule
import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import timber.log.Timber
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
private const val REALM_SHOULD_CLEAR_FLAG_ = "REALM_SHOULD_CLEAR_FLAG_"
private const val REALM_NAME = "disk_store.realm" private const val REALM_NAME = "disk_store.realm"
/** /**
* This class is handling creation of RealmConfiguration for a session. * This class is handling creation of RealmConfiguration for a session.
* It will handle corrupted realm by clearing the db file. It allows to just clear cache without losing your crypto keys.
* It's clearly not perfect but there is no way to catch the native crash.
*/ */
internal class SessionRealmConfigurationFactory @Inject constructor( internal class SessionRealmConfigurationFactory @Inject constructor(
private val realmKeysUtils: RealmKeysUtils, private val databaseKeysUtils: DatabaseKeysUtils,
@SessionFilesDirectory val directory: File, @SessionFilesDirectory val directory: File,
@SessionId val sessionId: String, @SessionId val sessionId: String,
@UserMd5 val userMd5: String, @UserMd5 val userMd5: String) {
context: Context) {
private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE)
fun create(): RealmConfiguration { fun create(): RealmConfiguration {
val shouldClearRealm = sharedPreferences.getBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false) return RealmConfiguration.Builder()
if (shouldClearRealm) {
Timber.v("************************************************************")
Timber.v("The realm file session was corrupted and couldn't be loaded.")
Timber.v("The file has been deleted to recover.")
Timber.v("************************************************************")
deleteRealmFiles()
}
sharedPreferences
.edit()
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", true)
.apply()
val realmConfiguration = RealmConfiguration.Builder()
.compactOnLaunch()
.directory(directory) .directory(directory)
.name(REALM_NAME) .name(REALM_NAME)
.apply { .apply {
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) databaseKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
} }
.modules(SessionRealmModule()) .modules(SessionRealmModule())
.deleteRealmIfMigrationNeeded() .deleteRealmIfMigrationNeeded()
.build() .build()
// Try creating a realm instance and if it succeeds we can clear the flag
Realm.getInstance(realmConfiguration).use {
Timber.v("Successfully create realm instance")
sharedPreferences
.edit()
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
.apply()
}
return realmConfiguration
} }
// Delete all the realm files of the session
private fun deleteRealmFiles() {
listOf(REALM_NAME, "$REALM_NAME.lock", "$REALM_NAME.note", "$REALM_NAME.management").forEach { file ->
try {
File(directory, file).deleteRecursively()
} catch (e: Exception) {
Timber.e(e, "Unable to delete files")
}
}
}
} }

View File

@@ -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)
}
}
}

View File

@@ -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))
}
}

View File

@@ -16,26 +16,15 @@
package im.vector.matrix.android.internal.database.helper package im.vector.matrix.android.internal.database.helper
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.api.session.room.model.RoomMemberContent
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.*
import im.vector.matrix.android.internal.database.model.CurrentStateEventEntityFields
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntityFields
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.extensions.assertIsManaged import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.sqldelight.session.SessionDatabase
import im.vector.matrix.sqldelight.session.TimelineEventQueries
import io.realm.Realm import io.realm.Realm
import io.realm.Sort import io.realm.Sort
import io.realm.kotlin.createObject
import timber.log.Timber import timber.log.Timber
internal fun ChunkEntity.deleteOnCascade() { internal fun ChunkEntity.deleteOnCascade() {
@@ -85,57 +74,47 @@ internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity,
} }
} }
internal fun ChunkEntity.addTimelineEvent(roomId: String, internal fun SessionDatabase.addTimelineEvent(roomId: String,
eventEntity: EventEntity, chunkId: Long,
event: Event,
direction: PaginationDirection, direction: PaginationDirection,
roomMemberContentsByUser: Map<String, RoomMemberContent?>) { roomMemberContentsByUser: Map<String, RoomMemberContent?>) {
val eventId = eventEntity.eventId val eventId = event.eventId ?: "$roomId-$chunkId-${System.currentTimeMillis()}"
if (timelineEvents.find(eventId) != null) { val displayIndex = timelineEventQueries.nextDisplayIndex(direction, chunkId)
return val senderId = event.senderId ?: ""
}
val displayIndex = nextDisplayIndex(direction)
val localId = TimelineEventEntity.nextId(realm)
val senderId = eventEntity.sender ?: ""
// 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] val roomMemberContent = roomMemberContentsByUser[senderId]
this.senderAvatar = roomMemberContent?.avatarUrl // Update RR for the sender of a new message with a dummy one
this.senderName = roomMemberContent?.displayName handleReadReceipts(this, roomId, eventId, event.originServerTs, senderId)
isUniqueDisplayName = if (roomMemberContent?.displayName != null) { val isDisplayNameUnique = if (roomMemberContent?.displayName != null) {
computeIsUnique(realm, roomId, isLastForward, roomMemberContent, roomMemberContentsByUser) computeIsUnique(this, roomId, chunkId, roomMemberContent, roomMemberContentsByUser)
} else { } else {
true true
} }
} timelineEventQueries.insert(
timelineEvents.add(timelineEventEntity) event_id = eventId,
sender_avatar = roomMemberContent?.avatarUrl,
chunk_id = chunkId,
room_id = roomId,
display_index = displayIndex,
is_unique_display_name = isDisplayNameUnique,
sender_name = roomMemberContent?.displayName
)
} }
private fun computeIsUnique( private fun computeIsUnique(
realm: Realm, sessionDatabase: SessionDatabase,
roomId: String, roomId: String,
isLastForward: Boolean, chunkId: Long,
myRoomMemberContent: RoomMemberContent, myRoomMemberContent: RoomMemberContent,
roomMemberContentsByUser: Map<String, RoomMemberContent?> roomMemberContentsByUser: Map<String, RoomMemberContent?>
): Boolean { ): Boolean {
val isHistoricalUnique = roomMemberContentsByUser.values.find { val isHistoricalUnique = roomMemberContentsByUser.values.find {
it != myRoomMemberContent && it?.displayName == myRoomMemberContent.displayName it != myRoomMemberContent && it?.displayName == myRoomMemberContent.displayName
} == null } == null
val isLastForward = sessionDatabase.chunkQueries.isLastForward(chunkId).executeAsOne()
return if (isLastForward) { return if (isLastForward) {
val isLiveUnique = RoomMemberSummaryEntity val countMembersWithName = sessionDatabase.roomMemberSummaryQueries.countMembersWithNameInRoom(myRoomMemberContent.displayName, roomId).executeAsOne()
.where(realm, roomId) val isLiveUnique = countMembersWithName == 1L
.equalTo(RoomMemberSummaryEntityFields.DISPLAY_NAME, myRoomMemberContent.displayName)
.findAll().none {
!roomMemberContentsByUser.containsKey(it.userId)
}
isHistoricalUnique && isLiveUnique isHistoricalUnique && isLiveUnique
} else { } else {
isHistoricalUnique isHistoricalUnique
@@ -143,6 +122,7 @@ private fun computeIsUnique(
} }
private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEntity: TimelineEventEntity, direction: PaginationDirection) { private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEntity: TimelineEventEntity, direction: PaginationDirection) {
/*
val eventId = timelineEventEntity.eventId val eventId = timelineEventEntity.eventId
if (timelineEvents.find(eventId) != null) { if (timelineEvents.find(eventId) != null) {
return return
@@ -162,36 +142,30 @@ private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEnt
this.isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName this.isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName
} }
timelineEvents.add(copied) timelineEvents.add(copied)
*/
} }
private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventEntity, senderId: String): ReadReceiptsSummaryEntity { private fun handleReadReceipts(
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst() sessionDatabase: SessionDatabase,
?: realm.createObject<ReadReceiptsSummaryEntity>(eventEntity.eventId).apply { roomId: String,
this.roomId = roomId eventId: String,
} originServerTs: Long?,
val originServerTs = eventEntity.originServerTs senderId: String) {
if (originServerTs != null) { if (originServerTs != null) {
val timestampOfEvent = originServerTs.toDouble() val timestampOfEvent = originServerTs.toDouble()
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId) val oldTimestamp = sessionDatabase.readReceiptQueries.getTimestampForUser(roomId, senderId).executeAsOneOrNull()
// If the synced RR is older, update // If the synced RR is older, update
if (timestampOfEvent > readReceiptOfSender.originServerTs) { if (oldTimestamp == null || timestampOfEvent > oldTimestamp) {
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst() sessionDatabase.readReceiptQueries.updateReadReceipt(eventId, timestampOfEvent, roomId, senderId)
readReceiptOfSender.eventId = eventEntity.eventId
readReceiptOfSender.originServerTs = timestampOfEvent
previousReceiptsSummary?.readReceipts?.remove(readReceiptOfSender)
readReceiptsSummaryEntity.readReceipts.add(readReceiptOfSender)
} }
} }
return readReceiptsSummaryEntity
} }
internal fun ChunkEntity.nextDisplayIndex(direction: PaginationDirection): Int { internal fun TimelineEventQueries.nextDisplayIndex(direction: PaginationDirection, chunkId: Long): Int {
return when (direction) { return when (direction) {
PaginationDirection.FORWARDS -> { PaginationDirection.FORWARDS -> (getMaxDisplayIndex(chunkId).executeAsOneOrNull()?.max_display_index?.toInt()
(timelineEvents.where().max(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) + 1 ?: 0) + 1
} PaginationDirection.BACKWARDS -> (getMinDisplayIndex(chunkId).executeAsOneOrNull()?.min_display_index?.toInt()
PaginationDirection.BACKWARDS -> { ?: 0) - 1
(timelineEvents.where().min(TimelineEventEntityFields.DISPLAY_INDEX)?.toInt() ?: 0) - 1
}
} }
} }

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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(')')
}
}
}

View File

@@ -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
}
}

View File

@@ -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()
)
}
}

View File

@@ -17,29 +17,23 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.room.send.UserDraft import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.internal.database.model.DraftEntity import im.vector.matrix.sqldelight.session.DraftMode
import javax.inject.Inject
/** /**
* DraftEntity <-> UserDraft * DraftEntity <-> UserDraft
*/ */
internal object DraftMapper { internal class DraftMapper @Inject constructor() {
fun map(entity: DraftEntity): UserDraft { fun map(content: String,
return when (entity.draftMode) { draft_mode: String,
DraftEntity.MODE_REGULAR -> UserDraft.REGULAR(entity.content) linked_event_id: String): UserDraft {
DraftEntity.MODE_EDIT -> UserDraft.EDIT(entity.linkedEventId, entity.content) return when (draft_mode) {
DraftEntity.MODE_QUOTE -> UserDraft.QUOTE(entity.linkedEventId, entity.content) DraftMode.MODE_REGULAR -> UserDraft.REGULAR(content)
DraftEntity.MODE_REPLY -> UserDraft.REPLY(entity.linkedEventId, entity.content) DraftMode.MODE_EDIT -> UserDraft.EDIT(linked_event_id, content)
DraftMode.MODE_QUOTE -> UserDraft.QUOTE(linked_event_id, content)
DraftMode.MODE_REPLY -> UserDraft.REPLY(linked_event_id, content)
else -> null else -> null
} ?: UserDraft.REGULAR("") } ?: UserDraft.REGULAR("")
} }
fun map(domain: UserDraft): DraftEntity {
return when (domain) {
is UserDraft.REGULAR -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REGULAR, linkedEventId = "")
is UserDraft.EDIT -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_EDIT, linkedEventId = domain.linkedEventId)
is UserDraft.QUOTE -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_QUOTE, linkedEventId = domain.linkedEventId)
is UserDraft.REPLY -> DraftEntity(content = domain.text, draftMode = DraftEntity.MODE_REPLY, linkedEventId = domain.linkedEventId)
}
}
} }

View File

@@ -16,94 +16,77 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.*
import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary
import im.vector.matrix.android.api.session.room.model.ReferencesAggregatedSummary
import im.vector.matrix.android.internal.database.model.EditAggregatedSummaryEntity
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntity
import im.vector.matrix.android.internal.database.model.ReferencesAggregatedSummaryEntity
import io.realm.RealmList
internal object EventAnnotationsSummaryMapper { internal object EventAnnotationsSummaryMapper {
fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
fun mapAnnotationsSummary(eventId: String,
reactions: List<ReactionAggregatedSummary>,
edit: EditAggregatedSummary?,
references: ReferencesAggregatedSummary?,
poll: PollResponseAggregatedSummary?): EventAnnotationsSummary {
return EventAnnotationsSummary( return EventAnnotationsSummary(
eventId = annotationsSummary.eventId, eventId = eventId,
reactionsSummary = annotationsSummary.reactionsSummary.toList().map { reactionsSummary = reactions,
ReactionAggregatedSummary( referencesAggregatedSummary = references,
it.key, editSummary = edit,
it.count, pollResponseSummary = poll
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 map(annotationsSummary: EventAnnotationsSummary, roomId: String): EventAnnotationsSummaryEntity { fun mapReactionSummary(key: String,
val eventAnnotationsSummaryEntity = EventAnnotationsSummaryEntity() count: Long,
eventAnnotationsSummaryEntity.eventId = annotationsSummary.eventId added_by_me: Boolean,
eventAnnotationsSummaryEntity.roomId = roomId first_timestamp: Long,
eventAnnotationsSummaryEntity.editSummary = annotationsSummary.editSummary?.let { source_event_ids: List<String>,
EditAggregatedSummaryEntity( source_local_echo_ids: List<String>): ReactionAggregatedSummary {
ContentMapper.map(it.aggregatedContent), return ReactionAggregatedSummary(
RealmList<String>().apply { addAll(it.sourceEvents) }, key = key,
RealmList<String>().apply { addAll(it.localEchos) }, count = count.toInt(),
it.lastEditTs addedByMe = added_by_me,
firstTimestamp = first_timestamp,
sourceEvents = source_event_ids,
localEchoEvents = source_local_echo_ids
) )
} }
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
}
}
internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary {
return EventAnnotationsSummaryMapper.map(this) 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
)
}
} }

View File

@@ -33,7 +33,8 @@ internal object EventMapper {
else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(event.unsignedData) else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(event.unsignedData)
val eventEntity = EventEntity() val eventEntity = EventEntity()
// TODO change this as we shouldn't use event everywhere // TODO change this as we shouldn't use event everywhere
eventEntity.eventId = event.eventId ?: "$$roomId-${System.currentTimeMillis()}-${event.hashCode()}" eventEntity.eventId = event.eventId
?: "$$roomId-${System.currentTimeMillis()}-${event.hashCode()}"
eventEntity.roomId = event.roomId ?: roomId eventEntity.roomId = event.roomId ?: roomId
eventEntity.content = ContentMapper.map(event.content) eventEntity.content = ContentMapper.map(event.content)
val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent
@@ -48,6 +49,34 @@ internal object EventMapper {
return eventEntity return eventEntity
} }
fun map(event: Event, roomId: String, sendState: SendState, ageLocalTs: Long?): im.vector.matrix.sqldelight.session.EventEntity {
val uds = if (event.unsignedData == null) {
null
} else {
MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).toJson(event.unsignedData)
}
val resolvedPrevContent = event.prevContent ?: event.unsignedData?.prevContent
return im.vector.matrix.sqldelight.session.EventEntity.Impl(
// TODO change this as we shouldn't use event everywhere
event_id = event.eventId
?: "$$roomId-${System.currentTimeMillis()}-${event.hashCode()}",
room_id = event.roomId ?: roomId,
content = ContentMapper.map(event.content),
prev_content = ContentMapper.map(resolvedPrevContent),
state_key = event.stateKey,
type = event.type,
sender_id = event.senderId,
origin_server_ts = event.originServerTs,
redacts = event.redacts,
age = event.unsignedData?.age ?: event.originServerTs,
unsigned_data = uds,
age_local_ts = ageLocalTs,
send_state = sendState.name,
decryption_error_code = null,
decryption_result_json = null
)
}
fun map(eventEntity: EventEntity): Event { fun map(eventEntity: EventEntity): Event {
val ud = eventEntity.unsignedData val ud = eventEntity.unsignedData
?.takeIf { it.isNotBlank() } ?.takeIf { it.isNotBlank() }
@@ -87,12 +116,56 @@ internal object EventMapper {
} }
} }
} }
fun map(eventEntity: im.vector.matrix.sqldelight.session.EventEntity): Event {
return Event(
type = eventEntity.type,
eventId = eventEntity.event_id,
content = ContentMapper.map(eventEntity.content),
prevContent = ContentMapper.map(eventEntity.prev_content),
originServerTs = eventEntity.origin_server_ts,
senderId = eventEntity.sender_id,
stateKey = eventEntity.state_key,
roomId = eventEntity.room_id,
unsignedData = UnsignedDataMapper.mapFromString(eventEntity.unsigned_data),
redacts = eventEntity.redacts
).also {
it.ageLocalTs = eventEntity.age_local_ts
it.sendState = SendState.valueOf(eventEntity.send_state)
it.setDecryptionValues(eventEntity.decryption_result_json, eventEntity.decryption_error_code)
}
}
}
internal fun Event.setDecryptionValues(decryptionResultJson: String?, decryptionErrorCode: String?): Event {
return apply {
decryptionResultJson?.let { json ->
try {
mxDecryptionResult = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).fromJson(json)
} catch (t: JsonDataException) {
Timber.e(t, "Failed to parse decryption result")
}
}
// TODO get the full crypto error object
mCryptoError = decryptionErrorCode?.let { errorCode ->
MXCryptoError.ErrorType.valueOf(errorCode)
}
}
} }
internal fun EventEntity.asDomain(): Event { internal fun EventEntity.asDomain(): Event {
return EventMapper.map(this) return EventMapper.map(this)
} }
internal fun im.vector.matrix.sqldelight.session.EventEntity.asDomain(): Event {
return EventMapper.map(this)
}
internal fun Event.toSQLEntity(roomId: String, sendState: SendState, ageLocalTs: Long? = null): im.vector.matrix.sqldelight.session.EventEntity {
return EventMapper.map(this, roomId, sendState, ageLocalTs)
}
internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long? = null): EventEntity { internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long? = null): EventEntity {
return EventMapper.map(this, roomId).apply { return EventMapper.map(this, roomId).apply {
this.sendState = sendState this.sendState = sendState

View File

@@ -16,24 +16,34 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper
import com.squareup.sqldelight.db.SqlCursor
import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.sqldelight.session.Memberships
import javax.inject.Inject
internal object GroupSummaryMapper { internal class GroupSummaryMapper @Inject constructor() {
fun map(cursor: SqlCursor): GroupSummary = GroupSummary(
groupId = cursor.getString(0)!!,
membership = Membership.valueOf(cursor.getString(4)!!),
displayName = cursor.getString(1) ?: "",
shortDescription = cursor.getString(2) ?: "",
avatarUrl = cursor.getString(3) ?: ""
)
fun map(group_id: String,
display_name: String?,
short_description: String?,
avatar_url: String?,
membership: Memberships): GroupSummary {
fun map(groupSummaryEntity: GroupSummaryEntity): GroupSummary {
return GroupSummary( return GroupSummary(
groupSummaryEntity.groupId, groupId = group_id,
groupSummaryEntity.membership, membership = membership.map(),
groupSummaryEntity.displayName, displayName = display_name ?: "",
groupSummaryEntity.shortDescription, shortDescription = short_description ?: "",
groupSummaryEntity.avatarUrl, avatarUrl = avatar_url ?: ""
groupSummaryEntity.roomIds.toList(),
groupSummaryEntity.userIds.toList()
) )
} }
} }
internal fun GroupSummaryEntity.asDomain(): GroupSummary {
return GroupSummaryMapper.map(this)
}

View File

@@ -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()
}

View File

@@ -17,11 +17,13 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.pushrules.rest.PushCondition import im.vector.matrix.android.api.pushrules.rest.PushCondition
import im.vector.matrix.android.internal.database.model.PushConditionEntity import im.vector.matrix.sqldelight.session.GetPushConditions
import im.vector.matrix.sqldelight.session.PushConditionEntity
import javax.inject.Inject
internal object PushConditionMapper { internal class PushConditionMapper @Inject constructor() {
fun map(entity: PushConditionEntity): PushCondition { fun map(entity: GetPushConditions): PushCondition {
return PushCondition( return PushCondition(
kind = entity.kind, kind = entity.kind,
iz = entity.iz, iz = entity.iz,
@@ -30,12 +32,14 @@ internal object PushConditionMapper {
) )
} }
fun map(domain: PushCondition): PushConditionEntity { fun map(ruleId: String, pushCondition: PushCondition): PushConditionEntity {
return PushConditionEntity( return PushConditionEntity.Impl(
kind = domain.kind, kind = pushCondition.kind,
iz = domain.iz, iz = pushCondition.iz,
key = domain.key, key = pushCondition.key,
pattern = domain.pattern pattern = pushCondition.pattern,
rule_id = ruleId
) )
} }
} }

View File

@@ -17,14 +17,16 @@ package im.vector.matrix.android.internal.database.mapper
import com.squareup.moshi.Types import com.squareup.moshi.Types
import im.vector.matrix.android.api.pushrules.Condition import im.vector.matrix.android.api.pushrules.Condition
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.rest.PushCondition import im.vector.matrix.android.api.pushrules.rest.PushCondition
import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.database.model.PushRuleEntity
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import io.realm.RealmList import im.vector.matrix.sqldelight.session.GetPushConditions
import im.vector.matrix.sqldelight.session.PushRuleEntity
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
internal object PushRulesMapper { internal class PushRulesMapper @Inject constructor(private val pushConditionMapper: PushConditionMapper) {
private val moshiActionsAdapter = MoshiProvider.providesMoshi().adapter<List<Any>>(Types.newParameterizedType(List::class.java, Any::class.java)) private val moshiActionsAdapter = MoshiProvider.providesMoshi().adapter<List<Any>>(Types.newParameterizedType(List::class.java, Any::class.java))
@@ -33,10 +35,10 @@ internal object PushRulesMapper {
fun mapContentRule(pushrule: PushRuleEntity): PushRule { fun mapContentRule(pushrule: PushRuleEntity): PushRule {
return PushRule( return PushRule(
actions = fromActionStr(pushrule.actionsStr), actions = fromActionStr(pushrule.action_str),
default = pushrule.default, default = pushrule.is_default,
enabled = pushrule.enabled, enabled = pushrule.is_enabled,
ruleId = pushrule.ruleId, ruleId = pushrule.rule_id,
conditions = listOf( conditions = listOf(
PushCondition(Condition.Kind.EventMatch.value, "content.body", pushrule.pattern) PushCondition(Condition.Kind.EventMatch.value, "content.body", pushrule.pattern)
) )
@@ -44,58 +46,60 @@ internal object PushRulesMapper {
} }
private fun fromActionStr(actionsStr: String?): List<Any> { private fun fromActionStr(actionsStr: String?): List<Any> {
try { return try {
return actionsStr?.let { moshiActionsAdapter.fromJson(it) } ?: emptyList() actionsStr?.let { moshiActionsAdapter.fromJson(it) } ?: emptyList()
} catch (e: Throwable) { } catch (e: Throwable) {
Timber.e(e, "## failed to map push rule actions <$actionsStr>") Timber.e(e, "## failed to map push rule actions <$actionsStr>")
return emptyList() emptyList()
} }
} }
fun mapRoomRule(pushrule: PushRuleEntity): PushRule { fun mapRoomRule(pushrule: PushRuleEntity): PushRule {
return PushRule( return PushRule(
actions = fromActionStr(pushrule.actionsStr), actions = fromActionStr(pushrule.action_str),
default = pushrule.default, default = pushrule.is_default,
enabled = pushrule.enabled, enabled = pushrule.is_enabled,
ruleId = pushrule.ruleId, ruleId = pushrule.rule_id,
conditions = listOf( conditions = listOf(
PushCondition(Condition.Kind.EventMatch.value, "room_id", pushrule.ruleId) PushCondition(Condition.Kind.EventMatch.value, "room_id", pushrule.rule_id)
) )
) )
} }
fun mapSenderRule(pushrule: PushRuleEntity): PushRule { fun mapSenderRule(pushrule: PushRuleEntity): PushRule {
return PushRule( return PushRule(
actions = fromActionStr(pushrule.actionsStr), actions = fromActionStr(pushrule.action_str),
default = pushrule.default, default = pushrule.is_default,
enabled = pushrule.enabled, enabled = pushrule.is_enabled,
ruleId = pushrule.ruleId, ruleId = pushrule.rule_id,
conditions = listOf( conditions = listOf(
PushCondition(Condition.Kind.EventMatch.value, "user_id", pushrule.ruleId) PushCondition(Condition.Kind.EventMatch.value, "user_id", pushrule.rule_id)
) )
) )
} }
fun map(pushrule: PushRuleEntity): PushRule { fun map(pushrule: PushRuleEntity, conditions: List<GetPushConditions>): PushRule {
return PushRule( return PushRule(
actions = fromActionStr(pushrule.actionsStr), actions = fromActionStr(pushrule.action_str),
default = pushrule.default, default = pushrule.is_default,
enabled = pushrule.enabled, enabled = pushrule.is_enabled,
ruleId = pushrule.ruleId, ruleId = pushrule.rule_id,
conditions = pushrule.conditions?.map { PushConditionMapper.map(it) } conditions = conditions.map {
pushConditionMapper.map(it)
}
) )
} }
fun map(pushRule: PushRule): PushRuleEntity { fun map(scope: String, kind: RuleKind, pushRule: PushRule): PushRuleEntity {
return PushRuleEntity( return PushRuleEntity.Impl(
actionsStr = moshiActionsAdapter.toJson(pushRule.actions), action_str = moshiActionsAdapter.toJson(pushRule.actions),
default = pushRule.default ?: false, is_default = pushRule.default ?: false,
enabled = pushRule.enabled, is_enabled = pushRule.enabled,
ruleId = pushRule.ruleId, rule_id = pushRule.ruleId,
pattern = pushRule.pattern, pattern = pushRule.pattern,
conditions = pushRule.conditions?.let { scope = scope,
RealmList(*pushRule.conditions.map { PushConditionMapper.map(it) }.toTypedArray()) kind = kind.name
} ?: RealmList()
) )
} }
} }

View File

@@ -17,44 +17,48 @@ package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.pushers.PusherData import im.vector.matrix.android.api.session.pushers.PusherData
import im.vector.matrix.android.internal.database.model.PusherDataEntity import im.vector.matrix.android.api.session.pushers.PusherState
import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.session.pushers.JsonPusher import im.vector.matrix.android.internal.session.pushers.JsonPusher
import im.vector.matrix.sqldelight.session.PusherEntity
import javax.inject.Inject
internal object PushersMapper { internal class PushersMapper @Inject constructor() {
fun map(pushEntity: PusherEntity): Pusher { fun map(push_key: String,
kind: String?,
app_id: String,
app_display_name: String?,
device_display_name: String?,
profile_tag: String?,
lang: String?,
data_url: String?,
data_format: String?,
state: String): Pusher {
return Pusher( return Pusher(
pushKey = pushEntity.pushKey, pushKey = push_key,
kind = pushEntity.kind ?: "", kind = kind ?: "",
appId = pushEntity.appId, appId = app_id,
appDisplayName = pushEntity.appDisplayName, appDisplayName = app_display_name,
deviceDisplayName = pushEntity.deviceDisplayName, deviceDisplayName = device_display_name,
profileTag = pushEntity.profileTag, profileTag = profile_tag,
lang = pushEntity.lang, lang = lang,
data = PusherData(pushEntity.data?.url, pushEntity.data?.format), data = PusherData(data_url, data_format),
state = pushEntity.state state = PusherState.valueOf(state)
) )
} }
fun map(pusher: JsonPusher): PusherEntity { fun map(pusher: JsonPusher, state: PusherState): PusherEntity {
return PusherEntity( return PusherEntity.Impl(
pushKey = pusher.pushKey, push_key = pusher.pushKey,
kind = pusher.kind, kind = pusher.kind,
appId = pusher.appId, app_id = pusher.appId,
appDisplayName = pusher.appDisplayName, app_display_name = pusher.appDisplayName,
deviceDisplayName = pusher.deviceDisplayName, device_display_name = pusher.deviceDisplayName,
profileTag = pusher.profileTag, profile_tag = pusher.profileTag,
lang = pusher.lang, lang = pusher.lang,
data = PusherDataEntity(pusher.data?.url, pusher.data?.format) data_url = pusher.data?.url,
data_format = pusher.data?.format,
state = state.name
) )
} }
} }
internal fun PusherEntity.asDomain(): Pusher {
return PushersMapper.map(this)
}
internal fun JsonPusher.toEntity(): PusherEntity {
return PushersMapper.map(this)
}

View File

@@ -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())
}
}

View File

@@ -18,18 +18,16 @@ package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.room.model.ReadReceipt import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.di.RealmSessionDatabase
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionDatabase
import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import javax.inject.Inject import javax.inject.Inject
internal class ReadReceiptsSummaryMapper @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration) { internal class ReadReceiptsSummaryMapper @Inject constructor(@RealmSessionDatabase private val realmConfiguration: RealmConfiguration) {
fun map(readReceiptsSummaryEntity: ReadReceiptsSummaryEntity?): List<ReadReceipt> { fun map(readReceiptsSummaryEntity: ReadReceiptsSummaryEntity?): List<ReadReceipt> {
if (readReceiptsSummaryEntity == null) {
return emptyList() return emptyList()
/*if (readReceiptsSummaryEntity == null) {
} }
return Realm.getInstance(realmConfiguration).use { realm -> return Realm.getInstance(realmConfiguration).use { realm ->
val readReceipts = readReceiptsSummaryEntity.readReceipts val readReceipts = readReceiptsSummaryEntity.readReceipts
@@ -39,6 +37,6 @@ internal class ReadReceiptsSummaryMapper @Inject constructor(@SessionDatabase pr
?: return@mapNotNull null ?: return@mapNotNull null
ReadReceipt(user.asDomain(), it.originServerTs.toLong()) ReadReceipt(user.asDomain(), it.originServerTs.toLong())
} }
} }*/
} }
} }

View File

@@ -16,10 +16,13 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity import im.vector.matrix.android.internal.database.model.RoomMemberSummaryEntity
import im.vector.matrix.sqldelight.session.Memberships
import javax.inject.Inject
internal object RoomMemberSummaryMapper { internal class RoomMemberSummaryMapper @Inject constructor() {
fun map(roomMemberSummaryEntity: RoomMemberSummaryEntity): RoomMemberSummary { fun map(roomMemberSummaryEntity: RoomMemberSummaryEntity): RoomMemberSummary {
return RoomMemberSummary( return RoomMemberSummary(
@@ -29,8 +32,31 @@ internal object RoomMemberSummaryMapper {
membership = roomMemberSummaryEntity.membership membership = roomMemberSummaryEntity.membership
) )
} }
fun map(roomMemberSummaryEntity: im.vector.matrix.sqldelight.session.RoomMemberSummaryEntity): RoomMemberSummary {
return RoomMemberSummary(
userId = roomMemberSummaryEntity.user_id,
avatarUrl = roomMemberSummaryEntity.avatar_url,
displayName = roomMemberSummaryEntity.display_name,
membership = Membership.valueOf(roomMemberSummaryEntity.membership.name)
)
}
fun map(room_id: String, user_id: String, display_name: String?, avatar_url: String?, membership: Memberships): RoomMemberSummary {
return RoomMemberSummary(
userId = user_id,
membership = membership.map(),
displayName = display_name,
avatarUrl = avatar_url
)
}
}
internal fun im.vector.matrix.sqldelight.session.RoomMemberSummaryEntity.asDomain(): RoomMemberSummary {
return RoomMemberSummaryMapper().map(this)
} }
internal fun RoomMemberSummaryEntity.asDomain(): RoomMemberSummary { internal fun RoomMemberSummaryEntity.asDomain(): RoomMemberSummary {
return RoomMemberSummaryMapper.map(this) return RoomMemberSummaryMapper().map(this)
} }

View File

@@ -16,47 +16,82 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.VersioningState
import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.sqldelight.session.RoomSummaryWithTimeline
import javax.inject.Inject import javax.inject.Inject
internal class RoomSummaryMapper @Inject constructor(private val timelineEventMapper: TimelineEventMapper) { internal class RoomSummaryMapper @Inject constructor() {
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
val tags = roomSummaryEntity.tags.map {
RoomTag(it.tagName, it.tagOrder)
}
val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
timelineEventMapper.map(it, buildReadReceipts = false)
}
fun map(roomSummaryWithTimeline: RoomSummaryWithTimeline, tags: List<RoomTag>): RoomSummary {
return RoomSummary( return RoomSummary(
roomId = roomSummaryEntity.roomId, roomId = roomSummaryWithTimeline.summary_room_id,
displayName = roomSummaryEntity.displayName ?: "", displayName = roomSummaryWithTimeline.display_name ?: "",
topic = roomSummaryEntity.topic ?: "", topic = roomSummaryWithTimeline.topic ?: "",
avatarUrl = roomSummaryEntity.avatarUrl ?: "", avatarUrl = roomSummaryWithTimeline.avatar_url ?: "",
isDirect = roomSummaryEntity.isDirect, isDirect = roomSummaryWithTimeline.is_direct,
latestPreviewableEvent = latestEvent, latestPreviewableEvent = createTimelineEvent(roomSummaryWithTimeline),
joinedMembersCount = roomSummaryEntity.joinedMembersCount, joinedMembersCount = roomSummaryWithTimeline.joined_members_count,
invitedMembersCount = roomSummaryEntity.invitedMembersCount, invitedMembersCount = roomSummaryWithTimeline.invited_members_count,
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), otherMemberIds = emptyList(),
highlightCount = roomSummaryEntity.highlightCount, highlightCount = roomSummaryWithTimeline.highlight_count,
notificationCount = roomSummaryEntity.notificationCount, notificationCount = roomSummaryWithTimeline.notification_count,
hasUnreadMessages = roomSummaryEntity.hasUnreadMessages, hasUnreadMessages = roomSummaryWithTimeline.has_unread,
versioningState = VersioningState.valueOf(roomSummaryWithTimeline.versioning_state),
tags = tags, tags = tags,
membership = roomSummaryEntity.membership, membership = roomSummaryWithTimeline.membership.map(),
versioningState = roomSummaryEntity.versioningState, readMarkerId = roomSummaryWithTimeline.read_marker_id,
readMarkerId = roomSummaryEntity.readMarkerId, userDrafts = emptyList(),
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(), canonicalAlias = roomSummaryWithTimeline.canonical_alias,
canonicalAlias = roomSummaryEntity.canonicalAlias, inviterId = roomSummaryWithTimeline.inviter_id,
aliases = roomSummaryEntity.aliases.toList(), isEncrypted = roomSummaryWithTimeline.is_encrypted,
isEncrypted = roomSummaryEntity.isEncrypted, typingRoomMemberIds = emptyList(),//roomSummaryEntity.typingUserIds.toList(),
typingRoomMemberIds = roomSummaryEntity.typingUserIds.toList(), breadcrumbsIndex = roomSummaryWithTimeline.breadcrumb_index ?: -1,
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex, roomEncryptionTrustLevel = roomSummaryWithTimeline.room_encryption_trust_level?.let {
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel, try {
inviterId = roomSummaryEntity.inviterId RoomEncryptionTrustLevel.valueOf(it)
} catch (failure: Throwable) {
null
}
}
) )
} }
private fun createTimelineEvent(roomSummaryWithTimeline: RoomSummaryWithTimeline): TimelineEvent? {
val type = roomSummaryWithTimeline.type ?: return null
val roomId = roomSummaryWithTimeline.summary_room_id
val eventId = roomSummaryWithTimeline.event_id ?: return null
val displayIndex = roomSummaryWithTimeline.display_index ?: return null
val localId = roomSummaryWithTimeline.local_id ?: return null
val event = Event(
type = type,
roomId = roomId,
eventId = eventId,
content = ContentMapper.map(roomSummaryWithTimeline.content),
prevContent = ContentMapper.map(roomSummaryWithTimeline.prev_content),
originServerTs = roomSummaryWithTimeline.origin_server_ts,
senderId = roomSummaryWithTimeline.sender_id,
redacts = roomSummaryWithTimeline.redacts,
stateKey = roomSummaryWithTimeline.state_key,
unsignedData = null
).setDecryptionValues(roomSummaryWithTimeline.decryption_result_json, roomSummaryWithTimeline.decryption_error_code)
return TimelineEvent(
root = event,
eventId = eventId,
annotations = null,
displayIndex = displayIndex,
isUniqueDisplayName = roomSummaryWithTimeline.is_unique_display_name ?: false,
localId = localId,
readReceipts = emptyList(),
senderAvatar = roomSummaryWithTimeline.sender_avatar,
senderName = roomSummaryWithTimeline.sender_name
)
}
} }

View File

@@ -16,40 +16,87 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper
import com.squareup.sqldelight.db.SqlCursor
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.ReadReceipt import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import javax.inject.Inject import javax.inject.Inject
internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) { internal class TimelineEventMapper @Inject constructor() {
fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true, correctedReadReceipts: List<ReadReceipt>? = null): TimelineEvent { fun map(cursor: SqlCursor): TimelineEvent = map(
val readReceipts = if (buildReadReceipts) { cursor.getLong(0)!!,
correctedReadReceipts ?: timelineEventEntity.readReceipts cursor.getLong(1)!!,
?.let { cursor.getLong(2)!!.toInt(),
readReceiptsSummaryMapper.map(it) cursor.getString(3),
} cursor.getString(4),
} else { cursor.getLong(5)!! == 1L,
null cursor.getString(6)!!,
cursor.getString(7)!!,
cursor.getString(8),
cursor.getString(9),
cursor.getString(10),
cursor.getString(11)!!,
cursor.getString(12)!!,
cursor.getLong(13),
cursor.getString(14),
cursor.getString(15),
cursor.getString(16),
cursor.getLong(17),
cursor.getLong(18),
cursor.getString(19),
cursor.getString(20)
)
fun map(local_id: Long,
chunk_id: Long,
display_index: Int,
sender_name: String?,
sender_avatar: String?,
is_unique_display_name: Boolean,
event_id: String,
room_id: String,
content: String?,
prev_content: String?,
state_key: String?,
send_state: String,
type: String,
origin_server_ts: Long?,
sender_id: String?,
unsigned_data: String?,
redacts: String?,
age: Long?,
age_local_ts: Long?,
decryption_result_json: String?,
decryption_error_code: String?): TimelineEvent {
val event = Event(
type = type,
roomId = room_id,
eventId = event_id,
content = ContentMapper.map(content),
prevContent = ContentMapper.map(prev_content),
originServerTs = origin_server_ts,
senderId = sender_id,
redacts = redacts,
stateKey = state_key,
unsignedData = UnsignedDataMapper.mapFromString(unsigned_data)
).also {
it.ageLocalTs = age_local_ts
it.sendState = SendState.valueOf(send_state)
it.setDecryptionValues(decryption_result_json, decryption_error_code)
} }
return TimelineEvent( return TimelineEvent(
root = timelineEventEntity.root?.asDomain() root = event,
?: Event("", timelineEventEntity.eventId), eventId = event_id,
eventId = timelineEventEntity.eventId, annotations = null,
annotations = timelineEventEntity.annotations?.asDomain(), displayIndex = display_index,
localId = timelineEventEntity.localId, isUniqueDisplayName = is_unique_display_name,
displayIndex = timelineEventEntity.displayIndex, localId = local_id,
senderName = timelineEventEntity.senderName, readReceipts = emptyList(),
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, senderAvatar = sender_avatar,
senderAvatar = timelineEventEntity.senderAvatar, senderName = sender_name
readReceipts = readReceipts
?.distinctBy {
it.user
}?.sortedByDescending {
it.originServerTs
} ?: emptyList()
) )
} }
} }

View File

@@ -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
}
}
}
}

View File

@@ -17,19 +17,30 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.sqldelight.session.UserEntity
import javax.inject.Inject
internal object UserMapper { internal class UserMapper @Inject constructor() {
fun map(userEntity: UserEntity): User { fun map(userEntity: UserEntity): User {
return User( return User(
userEntity.userId, userEntity.user_id,
userEntity.displayName, userEntity.display_name,
userEntity.avatarUrl userEntity.avatar_url
)
}
fun map(user_id: String,
display_name: String?,
avatar_url: String?): User {
return User(
userId = user_id,
displayName = display_name,
avatarUrl = avatar_url
) )
} }
} }
internal fun UserEntity.asDomain(): User { internal fun UserEntity.asDomain(): User {
return UserMapper.map(this) return UserMapper().map(this)
} }

View File

@@ -18,6 +18,6 @@ package im.vector.matrix.android.internal.database.query
internal object FilterContent { internal object FilterContent {
internal const val EDIT_TYPE = """{*"m.relates_to"*"rel_type":*"m.replace"*}""" internal const val EDIT_TYPE = """ '{%"m.relates_to"%"rel_type":%"m.replace"%}' """
internal const val RESPONSE_TYPE = """{*"m.relates_to"*"rel_type":*"m.response"*}""" internal const val RESPONSE_TYPE = """ '{%"m.relates_to"%"rel_type":%"m.response"%}' """
} }

View File

@@ -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()
}
}

View File

@@ -20,12 +20,12 @@ import javax.inject.Qualifier
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class AuthDatabase annotation class RealmAuthDatabase
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class SessionDatabase annotation class RealmSessionDatabase
@Qualifier @Qualifier
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class CryptoDatabase annotation class RealmCryptoDatabase

View File

@@ -20,6 +20,7 @@ import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import im.vector.matrix.android.internal.concurrency.newNamedSingleThreadExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.createBackgroundHandler import im.vector.matrix.android.internal.util.createBackgroundHandler
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -36,11 +37,14 @@ internal object MatrixModule {
@Provides @Provides
@MatrixScope @MatrixScope
fun providesMatrixCoroutineDispatchers(): MatrixCoroutineDispatchers { fun providesMatrixCoroutineDispatchers(): MatrixCoroutineDispatchers {
return MatrixCoroutineDispatchers(io = Dispatchers.IO, return MatrixCoroutineDispatchers(
dbTransaction = newNamedSingleThreadExecutor("db_transaction").asCoroutineDispatcher(),
dbQuery = newNamedSingleThreadExecutor("db_query").asCoroutineDispatcher(),
io = Dispatchers.IO,
computation = Dispatchers.Default, computation = Dispatchers.Default,
main = Dispatchers.Main, main = Dispatchers.Main,
crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(), crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(),
dmVerif = Executors.newSingleThreadExecutor().asCoroutineDispatcher() dmVerif = newNamedSingleThreadExecutor("dm_verif").asCoroutineDispatcher()
) )
} }

View File

@@ -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