forked from GitHub-Mirror/riotX-android
Merge pull request #317 from vector-im/feature/realm_entity_rework
Feature/realm entity rework
This commit is contained in:
commit
b73d3b15f8
@ -25,8 +25,8 @@ import io.reactivex.schedulers.Schedulers
|
|||||||
|
|
||||||
class RxRoom(private val room: Room) {
|
class RxRoom(private val room: Room) {
|
||||||
|
|
||||||
fun liveRoomSummary(fetchLastEvent: Boolean): Observable<RoomSummary> {
|
fun liveRoomSummary(): Observable<RoomSummary> {
|
||||||
return room.liveRoomSummary(fetchLastEvent).asObservable().observeOn(Schedulers.computation())
|
return room.liveRoomSummary().asObservable().observeOn(Schedulers.computation())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveRoomMemberIds(): Observable<List<String>> {
|
fun liveRoomMemberIds(): Observable<List<String>> {
|
||||||
|
@ -26,8 +26,8 @@ import io.reactivex.schedulers.Schedulers
|
|||||||
|
|
||||||
class RxSession(private val session: Session) {
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
fun liveRoomSummaries(fetchLastEvents: Boolean): Observable<List<RoomSummary>> {
|
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
|
||||||
return session.liveRoomSummaries(fetchLastEvents).asObservable().observeOn(Schedulers.computation())
|
return session.liveRoomSummaries().asObservable().observeOn(Schedulers.computation())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
fun liveGroupSummaries(): Observable<List<GroupSummary>> {
|
||||||
|
@ -18,25 +18,6 @@ package im.vector.matrix.android.session.room.timeline
|
|||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
|
||||||
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.internal.crypto.CryptoManager
|
|
||||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
|
||||||
import im.vector.matrix.android.internal.session.room.EventRelationExtractor
|
|
||||||
import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor
|
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline
|
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
|
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
|
||||||
import im.vector.matrix.android.testCoroutineDispatchers
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import org.amshove.kluent.shouldEqual
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
|
|
||||||
internal class TimelineTest : InstrumentedTest {
|
internal class TimelineTest : InstrumentedTest {
|
||||||
|
|
||||||
|
@ -21,11 +21,10 @@ import com.squareup.moshi.Json
|
|||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
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.android.internal.di.MoshiProvider
|
||||||
|
import org.json.JSONObject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.HashMap
|
|
||||||
|
|
||||||
typealias Content = JsonDict
|
typealias Content = JsonDict
|
||||||
|
|
||||||
@ -79,6 +78,11 @@ data class Event(
|
|||||||
@Json(name = "redacts") val redacts: String? = null
|
@Json(name = "redacts") val redacts: String? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
var mxDecryptionResult: OlmDecryptionResult? = null
|
||||||
|
var mCryptoError: MXCryptoError.ErrorType? = null
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if event is a state event.
|
* Check if event is a state event.
|
||||||
* @return true if event is state event.
|
* @return true if event is state event.
|
||||||
@ -91,41 +95,41 @@ data class Event(
|
|||||||
// Crypto
|
// Crypto
|
||||||
//==============================================================================================================
|
//==============================================================================================================
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* For encrypted events, the plaintext payload for the event.
|
// * For encrypted events, the plaintext payload for the event.
|
||||||
* This is a small MXEvent instance with typically value for `type` and 'content' fields.
|
// * This is a small MXEvent instance with typically value for `type` and 'content' fields.
|
||||||
*/
|
// */
|
||||||
@Transient
|
// @Transient
|
||||||
var mClearEvent: Event? = null
|
// var mClearEvent: Event? = null
|
||||||
private set
|
// private set
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* Curve25519 key which we believe belongs to the sender of the event.
|
// * Curve25519 key which we believe belongs to the sender of the event.
|
||||||
* See `senderKey` property.
|
// * See `senderKey` property.
|
||||||
*/
|
// */
|
||||||
@Transient
|
// @Transient
|
||||||
private var mSenderCurve25519Key: String? = null
|
// private var mSenderCurve25519Key: String? = null
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* Ed25519 key which the sender of this event (for olm) or the creator of the megolm session (for megolm) claims to own.
|
// * Ed25519 key which the sender of this event (for olm) or the creator of the megolm session (for megolm) claims to own.
|
||||||
* See `claimedEd25519Key` property.
|
// * See `claimedEd25519Key` property.
|
||||||
*/
|
// */
|
||||||
@Transient
|
// @Transient
|
||||||
private var mClaimedEd25519Key: String? = null
|
// private var mClaimedEd25519Key: String? = null
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key.
|
// * Curve25519 keys of devices involved in telling us about the senderCurve25519Key and claimedEd25519Key.
|
||||||
* See `forwardingCurve25519KeyChain` property.
|
// * See `forwardingCurve25519KeyChain` property.
|
||||||
*/
|
// */
|
||||||
@Transient
|
// @Transient
|
||||||
private var mForwardingCurve25519KeyChain: List<String> = ArrayList()
|
// private var mForwardingCurve25519KeyChain: List<String> = ArrayList()
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* Decryption error
|
// * Decryption error
|
||||||
*/
|
// */
|
||||||
@Transient
|
// @Transient
|
||||||
var mCryptoError: MXCryptoError? = null
|
// var mCryptoError: MXCryptoError? = null
|
||||||
private set
|
// private set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if this event is encrypted.
|
* @return true if this event is encrypted.
|
||||||
@ -140,91 +144,151 @@ data class Event(
|
|||||||
*
|
*
|
||||||
* @param decryptionResult the decryption result, including the plaintext and some key info.
|
* @param decryptionResult the decryption result, including the plaintext and some key info.
|
||||||
*/
|
*/
|
||||||
internal fun setClearData(decryptionResult: MXEventDecryptionResult) {
|
// internal fun setClearData(decryptionResult: MXEventDecryptionResult?) {
|
||||||
mClearEvent = null
|
// mClearEvent = null
|
||||||
mCryptoError = null
|
// if (decryptionResult != null) {
|
||||||
|
// if (decryptionResult.clearEvent != null) {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
// val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
||||||
mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent)
|
// mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent)
|
||||||
|
//
|
||||||
if (mClearEvent != null) {
|
// if (mClearEvent != null) {
|
||||||
mSenderCurve25519Key = decryptionResult.senderCurve25519Key
|
// mSenderCurve25519Key = decryptionResult.senderCurve25519Key
|
||||||
mClaimedEd25519Key = decryptionResult.claimedEd25519Key
|
// mClaimedEd25519Key = decryptionResult.claimedEd25519Key
|
||||||
mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain
|
// mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain
|
||||||
|
//
|
||||||
// For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back
|
// // For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back
|
||||||
// in the clear event
|
// // in the clear event
|
||||||
try {
|
// try {
|
||||||
content?.get("m.relates_to")?.let { clearRelates ->
|
// content?.get("m.relates_to")?.let { clearRelates ->
|
||||||
mClearEvent = mClearEvent?.copy(
|
// mClearEvent = mClearEvent?.copy(
|
||||||
content = HashMap(mClearEvent!!.content).apply {
|
// content = HashMap(mClearEvent!!.content).apply {
|
||||||
this["m.relates_to"] = clearRelates
|
// this["m.relates_to"] = clearRelates
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
} catch (e: Exception) {
|
// } catch (e: Exception) {
|
||||||
Timber.e(e, "Unable to restore 'm.relates_to' the clear event")
|
// Timber.e(e, "Unable to restore 'm.relates_to' the clear event")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// mCryptoError = null
|
||||||
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The curve25519 key that sent this event.
|
* @return The curve25519 key that sent this event.
|
||||||
*/
|
*/
|
||||||
fun getSenderKey(): String? {
|
fun getSenderKey(): String? {
|
||||||
return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key
|
return mxDecryptionResult?.senderKey
|
||||||
|
// return mClearEvent?.mSenderCurve25519Key ?: mSenderCurve25519Key
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The additional keys the sender of this encrypted event claims to possess.
|
* @return The additional keys the sender of this encrypted event claims to possess.
|
||||||
*/
|
*/
|
||||||
fun getKeysClaimed(): Map<String, String> {
|
fun getKeysClaimed(): Map<String, String> {
|
||||||
val res = HashMap<String, String>()
|
return mxDecryptionResult?.keysClaimed ?: HashMap()
|
||||||
|
// val res = HashMap<String, String>()
|
||||||
val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key
|
//
|
||||||
|
// val claimedEd25519Key = if (null != mClearEvent) mClearEvent!!.mClaimedEd25519Key else mClaimedEd25519Key
|
||||||
if (null != claimedEd25519Key) {
|
//
|
||||||
res["ed25519"] = claimedEd25519Key
|
// if (null != claimedEd25519Key) {
|
||||||
}
|
// res["ed25519"] = claimedEd25519Key
|
||||||
|
// }
|
||||||
return res
|
//
|
||||||
|
// return res
|
||||||
}
|
}
|
||||||
|
//
|
||||||
/**
|
/**
|
||||||
* @return the event type
|
* @return the event type
|
||||||
*/
|
*/
|
||||||
fun getClearType(): String {
|
fun getClearType(): String {
|
||||||
return mClearEvent?.type ?: type
|
return mxDecryptionResult?.payload?.get("type")?.toString()
|
||||||
|
?: type//get("type")?.toString() ?: type
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the event content
|
* @return the event content
|
||||||
*/
|
*/
|
||||||
fun getClearContent(): Content? {
|
fun getClearContent(): Content? {
|
||||||
return mClearEvent?.content ?: content
|
return mxDecryptionResult?.payload?.get("content") as? Content ?: content
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* @return the linked crypto error
|
// * @return the linked crypto error
|
||||||
*/
|
// */
|
||||||
fun getCryptoError(): MXCryptoError? {
|
// fun getCryptoError(): MXCryptoError? {
|
||||||
return mCryptoError
|
// return mCryptoError
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Update the linked crypto error
|
||||||
|
// *
|
||||||
|
// * @param error the new crypto error.
|
||||||
|
// */
|
||||||
|
// fun setCryptoError(error: MXCryptoError?) {
|
||||||
|
// mCryptoError = error
|
||||||
|
// if (null != error) {
|
||||||
|
// mClearEvent = null
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
fun toContentStringWithIndent(): String {
|
||||||
|
val contentMap = this.toContent()?.toMutableMap() ?: HashMap()
|
||||||
|
contentMap.remove("mxDecryptionResult")
|
||||||
|
contentMap.remove("mCryptoError")
|
||||||
|
return JSONObject(contentMap).toString(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun toClearContentStringWithIndent(): String? {
|
||||||
* Update the linked crypto error
|
val contentMap = this.mxDecryptionResult?.payload?.toMutableMap()
|
||||||
*
|
val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
||||||
* @param error the new crypto error.
|
return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) }
|
||||||
*/
|
|
||||||
fun setCryptoError(error: MXCryptoError?) {
|
|
||||||
mCryptoError = error
|
|
||||||
if (null != error) {
|
|
||||||
mClearEvent = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells if the event is redacted
|
* Tells if the event is redacted
|
||||||
*/
|
*/
|
||||||
fun isRedacted() = unsignedData?.redactedEvent != null
|
fun isRedacted() = unsignedData?.redactedEvent != null
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as Event
|
||||||
|
|
||||||
|
if (type != other.type) return false
|
||||||
|
if (eventId != other.eventId) return false
|
||||||
|
if (content != other.content) return false
|
||||||
|
if (prevContent != other.prevContent) return false
|
||||||
|
if (originServerTs != other.originServerTs) return false
|
||||||
|
if (senderId != other.senderId) return false
|
||||||
|
if (stateKey != other.stateKey) return false
|
||||||
|
if (roomId != other.roomId) return false
|
||||||
|
if (unsignedData != other.unsignedData) return false
|
||||||
|
if (redacts != other.redacts) return false
|
||||||
|
if (mxDecryptionResult != other.mxDecryptionResult) return false
|
||||||
|
if (mCryptoError != other.mCryptoError) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = type.hashCode()
|
||||||
|
result = 31 * result + (eventId?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (content?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (prevContent?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (originServerTs?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (senderId?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (stateKey?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (roomId?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (unsignedData?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (redacts?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (mxDecryptionResult?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (mCryptoError?.hashCode() ?: 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
@ -47,8 +47,8 @@ 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 liveRoomSummary(fetchLastEvent: Boolean = false): LiveData<RoomSummary>
|
fun liveRoomSummary(): LiveData<RoomSummary>
|
||||||
|
|
||||||
fun roomSummary(fetchLastEvent: Boolean = false): RoomSummary?
|
fun roomSummary(): RoomSummary?
|
||||||
|
|
||||||
}
|
}
|
@ -43,6 +43,6 @@ 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 [RoomSummary]
|
* @return the [LiveData] of [RoomSummary]
|
||||||
*/
|
*/
|
||||||
fun liveRoomSummaries(fetchLastEvents: Boolean = true): LiveData<List<RoomSummary>>
|
fun liveRoomSummaries(): LiveData<List<RoomSummary>>
|
||||||
|
|
||||||
}
|
}
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.timeline
|
package im.vector.matrix.android.api.session.room.timeline
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
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.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
@ -34,7 +35,6 @@ data class TimelineEvent(
|
|||||||
val isUniqueDisplayName: Boolean,
|
val isUniqueDisplayName: Boolean,
|
||||||
val senderAvatar: String?,
|
val senderAvatar: String?,
|
||||||
val sendState: SendState,
|
val sendState: SendState,
|
||||||
val hasClearEventFlag: Boolean = false,
|
|
||||||
val annotations: EventAnnotationsSummary? = null
|
val annotations: EventAnnotationsSummary? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -16,30 +16,33 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.algorithms.olm
|
package im.vector.matrix.android.internal.crypto.algorithms.olm
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents the decryption result.
|
* This class represents the decryption result.
|
||||||
*/
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
data class OlmDecryptionResult(
|
data class OlmDecryptionResult(
|
||||||
/**
|
/**
|
||||||
* The decrypted payload (with properties 'type', 'content')
|
* The decrypted payload (with properties 'type', 'content')
|
||||||
*/
|
*/
|
||||||
val payload: JsonDict? = null,
|
@Json(name = "payload") val payload: JsonDict? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* keys that the sender of the event claims ownership of:
|
* keys that the sender of the event claims ownership of:
|
||||||
* map from key type to base64-encoded key.
|
* map from key type to base64-encoded key.
|
||||||
*/
|
*/
|
||||||
val keysClaimed: Map<String, String>? = null,
|
@Json(name = "keysClaimed") val keysClaimed: Map<String, String>? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The curve25519 key that the sender of the event is known to have ownership of.
|
* The curve25519 key that the sender of the event is known to have ownership of.
|
||||||
*/
|
*/
|
||||||
val senderKey: String? = null,
|
@Json(name = "senderKey") val senderKey: String? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Devices which forwarded this session to us (normally empty).
|
* Devices which forwarded this session to us (normally empty).
|
||||||
*/
|
*/
|
||||||
val forwardingCurve25519KeyChain: List<String>? = null
|
@Json(name = "forwardingCurve25519KeyChain") val forwardingCurve25519KeyChain: List<String>? = null
|
||||||
)
|
)
|
||||||
|
@ -31,8 +31,7 @@ class RealmLiveData<T : RealmModel>(private val realmConfiguration: RealmConfigu
|
|||||||
|
|
||||||
override fun onActive() {
|
override fun onActive() {
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
val realm = Realm.getInstance(realmConfiguration)
|
||||||
val results = query.invoke(realm).findAll()
|
val results = query.invoke(realm).findAllAsync()
|
||||||
value = results
|
|
||||||
results.addChangeListener(listener)
|
results.addChangeListener(listener)
|
||||||
this.realm = realm
|
this.realm = realm
|
||||||
this.results = results
|
this.results = results
|
||||||
|
@ -22,9 +22,11 @@ import im.vector.matrix.android.api.session.room.send.SendState
|
|||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.fastContains
|
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.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 io.realm.Sort
|
import io.realm.Sort
|
||||||
@ -32,12 +34,12 @@ import io.realm.Sort
|
|||||||
// By default if a chunk is empty we consider it unlinked
|
// By default if a chunk is empty we consider it unlinked
|
||||||
internal fun ChunkEntity.isUnlinked(): Boolean {
|
internal fun ChunkEntity.isUnlinked(): Boolean {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
return events.where().equalTo(EventEntityFields.IS_UNLINKED, false).findAll().isEmpty()
|
return timelineEvents.where().equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, false).findAll().isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.deleteOnCascade() {
|
internal fun ChunkEntity.deleteOnCascade() {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
this.events.deleteAllFromRealm()
|
this.timelineEvents.deleteAllFromRealm()
|
||||||
this.deleteFromRealm()
|
this.deleteFromRealm()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,21 +52,27 @@ internal fun ChunkEntity.merge(roomId: String,
|
|||||||
val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked
|
val isUnlinked = isCurrentChunkUnlinked && isChunkToMergeUnlinked
|
||||||
|
|
||||||
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
|
if (isCurrentChunkUnlinked && !isChunkToMergeUnlinked) {
|
||||||
this.events.forEach { it.isUnlinked = false }
|
this.timelineEvents.forEach { it.root?.isUnlinked = false }
|
||||||
}
|
}
|
||||||
val eventsToMerge: List<EventEntity>
|
val eventsToMerge: List<TimelineEventEntity>
|
||||||
if (direction == PaginationDirection.FORWARDS) {
|
if (direction == PaginationDirection.FORWARDS) {
|
||||||
this.nextToken = chunkToMerge.nextToken
|
this.nextToken = chunkToMerge.nextToken
|
||||||
this.isLastForward = chunkToMerge.isLastForward
|
this.isLastForward = chunkToMerge.isLastForward
|
||||||
eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
|
||||||
} else {
|
} else {
|
||||||
this.prevToken = chunkToMerge.prevToken
|
this.prevToken = chunkToMerge.prevToken
|
||||||
this.isLastBackward = chunkToMerge.isLastBackward
|
this.isLastBackward = chunkToMerge.isLastBackward
|
||||||
eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
eventsToMerge = chunkToMerge.timelineEvents.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
}
|
}
|
||||||
eventsToMerge.forEach {
|
val events = eventsToMerge.mapNotNull { it.root?.asDomain() }
|
||||||
add(roomId, it.asDomain(), direction, isUnlinked = isUnlinked)
|
val eventIds = ArrayList<String>()
|
||||||
|
events.forEach { event ->
|
||||||
|
add(roomId, event, direction, isUnlinked = isUnlinked)
|
||||||
|
if (event.eventId != null) {
|
||||||
|
eventIds.add(event.eventId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
updateSenderDataFor(eventIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.addAll(roomId: String,
|
internal fun ChunkEntity.addAll(roomId: String,
|
||||||
@ -74,19 +82,31 @@ internal fun ChunkEntity.addAll(roomId: String,
|
|||||||
// Set to true for Event retrieved from a Permalink (i.e. not linked to live Chunk)
|
// Set to true for Event retrieved from a Permalink (i.e. not linked to live Chunk)
|
||||||
isUnlinked: Boolean = false) {
|
isUnlinked: Boolean = false) {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
|
val eventIds = ArrayList<String>()
|
||||||
events.forEach { event ->
|
events.forEach { event ->
|
||||||
add(roomId, event, direction, stateIndexOffset, isUnlinked)
|
add(roomId, event, direction, stateIndexOffset, isUnlinked)
|
||||||
|
if (event.eventId != null) {
|
||||||
|
eventIds.add(event.eventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateSenderDataFor(eventIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ChunkEntity.updateSenderDataFor(eventIds: List<String>) {
|
||||||
|
for (eventId in eventIds) {
|
||||||
|
val timelineEventEntity = timelineEvents.find(eventId) ?: continue
|
||||||
|
timelineEventEntity.updateSenderData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.add(roomId: String,
|
private fun ChunkEntity.add(roomId: String,
|
||||||
event: Event,
|
event: Event,
|
||||||
direction: PaginationDirection,
|
direction: PaginationDirection,
|
||||||
stateIndexOffset: Int = 0,
|
stateIndexOffset: Int = 0,
|
||||||
isUnlinked: Boolean = false) {
|
isUnlinked: Boolean = false) {
|
||||||
|
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
if (event.eventId != null && events.fastContains(event.eventId)) {
|
if (event.eventId != null && timelineEvents.find(event.eventId) != null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var currentDisplayIndex = lastDisplayIndex(direction, 0)
|
var currentDisplayIndex = lastDisplayIndex(direction, 0)
|
||||||
@ -101,21 +121,27 @@ internal fun ChunkEntity.add(roomId: String,
|
|||||||
if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(event.getClearType())) {
|
if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(event.getClearType())) {
|
||||||
currentStateIndex += 1
|
currentStateIndex += 1
|
||||||
forwardsStateIndex = currentStateIndex
|
forwardsStateIndex = currentStateIndex
|
||||||
} else if (direction == PaginationDirection.BACKWARDS && events.isNotEmpty()) {
|
} else if (direction == PaginationDirection.BACKWARDS && timelineEvents.isNotEmpty()) {
|
||||||
val lastEventType = events.last()?.type ?: ""
|
val lastEventType = timelineEvents.last()?.root?.type ?: ""
|
||||||
if (EventType.isStateEvent(lastEventType)) {
|
if (EventType.isStateEvent(lastEventType)) {
|
||||||
currentStateIndex -= 1
|
currentStateIndex -= 1
|
||||||
backwardsStateIndex = currentStateIndex
|
backwardsStateIndex = currentStateIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val eventEntity = event.toEntity(roomId).apply {
|
|
||||||
this.stateIndex = currentStateIndex
|
val eventEntity = TimelineEventEntity().also {
|
||||||
this.isUnlinked = isUnlinked
|
it.root = event.toEntity(roomId).apply {
|
||||||
this.displayIndex = currentDisplayIndex
|
this.stateIndex = currentStateIndex
|
||||||
this.sendState = SendState.SYNCED
|
this.isUnlinked = isUnlinked
|
||||||
|
this.displayIndex = currentDisplayIndex
|
||||||
|
this.sendState = SendState.SYNCED
|
||||||
|
}
|
||||||
|
it.eventId = event.eventId ?: ""
|
||||||
|
it.roomId = roomId
|
||||||
|
it.annotations = EventAnnotationsSummaryEntity.where(realm, it.eventId).findFirst()
|
||||||
}
|
}
|
||||||
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size
|
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
|
||||||
events.add(position, eventEntity)
|
timelineEvents.add(position, eventEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
||||||
|
@ -21,8 +21,10 @@ import im.vector.matrix.android.api.session.room.send.SendState
|
|||||||
import im.vector.matrix.android.internal.database.mapper.toEntity
|
import im.vector.matrix.android.internal.database.mapper.toEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.fastContains
|
import im.vector.matrix.android.internal.database.query.fastContains
|
||||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||||
|
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||||
|
|
||||||
|
|
||||||
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
|
internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) {
|
||||||
@ -57,8 +59,20 @@ internal fun RoomEntity.addStateEvents(stateEvents: List<Event>,
|
|||||||
|
|
||||||
internal fun RoomEntity.addSendingEvent(event: Event) {
|
internal fun RoomEntity.addSendingEvent(event: Event) {
|
||||||
assertIsManaged()
|
assertIsManaged()
|
||||||
|
val senderId = event.senderId ?: return
|
||||||
val eventEntity = event.toEntity(roomId).apply {
|
val eventEntity = event.toEntity(roomId).apply {
|
||||||
this.sendState = SendState.UNSENT
|
this.sendState = SendState.UNSENT
|
||||||
}
|
}
|
||||||
sendingTimelineEvents.add(0, eventEntity)
|
val roomMembers = RoomMembers(realm, roomId)
|
||||||
|
val myUser = roomMembers.get(senderId)
|
||||||
|
val timelineEventEntity = TimelineEventEntity().also {
|
||||||
|
it.root = eventEntity
|
||||||
|
it.eventId = event.eventId ?: ""
|
||||||
|
it.roomId = roomId
|
||||||
|
it.senderName = myUser?.displayName
|
||||||
|
it.senderAvatar = myUser?.avatarUrl
|
||||||
|
it.isUniqueDisplayName = roomMembers.isUniqueDisplayName(myUser?.displayName)
|
||||||
|
it.senderMembershipEvent = roomMembers.queryRoomMemberEvent(senderId).findFirst()
|
||||||
|
}
|
||||||
|
sendingTimelineEvents.add(0, timelineEventEntity)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||||
|
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.RoomEntity
|
||||||
|
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.next
|
||||||
|
import im.vector.matrix.android.internal.database.query.prev
|
||||||
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||||
|
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
|
||||||
|
internal fun TimelineEventEntity.updateSenderData() {
|
||||||
|
assertIsManaged()
|
||||||
|
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return
|
||||||
|
val stateIndex = root?.stateIndex ?: return
|
||||||
|
val senderId = root?.sender ?: return
|
||||||
|
val chunkEntity = chunk?.firstOrNull() ?: return
|
||||||
|
val isUnlinked = chunkEntity.isUnlinked()
|
||||||
|
var senderMembershipEvent: EventEntity?
|
||||||
|
var senderRoomMemberContent: String?
|
||||||
|
when {
|
||||||
|
stateIndex <= 0 -> {
|
||||||
|
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root
|
||||||
|
senderRoomMemberContent = senderMembershipEvent?.prevContent
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root
|
||||||
|
senderRoomMemberContent = senderMembershipEvent?.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We fallback to untimelinedStateEvents if we can't find membership events in timeline
|
||||||
|
if (senderMembershipEvent == null) {
|
||||||
|
senderMembershipEvent = roomEntity.untimelinedStateEvents
|
||||||
|
.where()
|
||||||
|
.equalTo(EventEntityFields.STATE_KEY, senderId)
|
||||||
|
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
|
||||||
|
.prev(since = stateIndex)
|
||||||
|
senderRoomMemberContent = senderMembershipEvent?.content
|
||||||
|
}
|
||||||
|
val senderRoomMember: RoomMember? = ContentMapper.map(senderRoomMemberContent).toModel()
|
||||||
|
this.senderAvatar = senderRoomMember?.avatarUrl
|
||||||
|
this.senderName = senderRoomMember?.displayName
|
||||||
|
this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(senderRoomMember?.displayName)
|
||||||
|
this.senderMembershipEvent = senderMembershipEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun RealmList<TimelineEventEntity>.buildQuery(sender: String, isUnlinked: Boolean): RealmQuery<TimelineEventEntity> {
|
||||||
|
return where()
|
||||||
|
.equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, sender)
|
||||||
|
.equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.STATE_ROOM_MEMBER)
|
||||||
|
.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, isUnlinked)
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,10 @@ package im.vector.matrix.android.internal.database.mapper
|
|||||||
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary
|
import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary
|
||||||
|
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.EventAnnotationsSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntity
|
||||||
|
import io.realm.RealmList
|
||||||
|
|
||||||
internal object EventAnnotationsSummaryMapper {
|
internal object EventAnnotationsSummaryMapper {
|
||||||
fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
|
fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
|
||||||
@ -45,6 +48,35 @@ internal object EventAnnotationsSummaryMapper {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun map(annotationsSummary: EventAnnotationsSummary, roomId: String): EventAnnotationsSummaryEntity {
|
||||||
|
val eventAnnotationsSummaryEntity = EventAnnotationsSummaryEntity()
|
||||||
|
eventAnnotationsSummaryEntity.eventId = annotationsSummary.eventId
|
||||||
|
eventAnnotationsSummaryEntity.roomId = roomId
|
||||||
|
eventAnnotationsSummaryEntity.editSummary = annotationsSummary.editSummary?.let {
|
||||||
|
EditAggregatedSummaryEntity(
|
||||||
|
ContentMapper.map(it.aggregatedContent),
|
||||||
|
RealmList<String>().apply { addAll(it.sourceEvents) },
|
||||||
|
RealmList<String>().apply { addAll(it.localEchos) },
|
||||||
|
it.lastEditTs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
eventAnnotationsSummaryEntity.reactionsSummary = annotationsSummary.reactionsSummary?.let {
|
||||||
|
RealmList<ReactionAggregatedSummaryEntity>().apply {
|
||||||
|
addAll(it.map {
|
||||||
|
ReactionAggregatedSummaryEntity(
|
||||||
|
it.key,
|
||||||
|
it.count,
|
||||||
|
it.addedByMe,
|
||||||
|
it.firstTimestamp,
|
||||||
|
RealmList<String>().apply { addAll(it.sourceEvents) },
|
||||||
|
RealmList<String>().apply { addAll(it.localEchoEvents) }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eventAnnotationsSummaryEntity
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary {
|
internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary {
|
||||||
|
@ -17,10 +17,15 @@
|
|||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
import com.squareup.moshi.JsonDataException
|
import com.squareup.moshi.JsonDataException
|
||||||
|
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
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.events.model.UnsignedData
|
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
||||||
|
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||||
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmDecryption
|
||||||
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
internal object EventMapper {
|
internal object EventMapper {
|
||||||
@ -46,7 +51,6 @@ internal object EventMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun map(eventEntity: EventEntity): Event {
|
fun map(eventEntity: EventEntity): Event {
|
||||||
//TODO proxy the event to only parse unsigned data when accessed?
|
|
||||||
val ud = if (eventEntity.unsignedData.isNullOrBlank()) {
|
val ud = if (eventEntity.unsignedData.isNullOrBlank()) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
@ -68,7 +72,17 @@ internal object EventMapper {
|
|||||||
roomId = eventEntity.roomId,
|
roomId = eventEntity.roomId,
|
||||||
unsignedData = ud,
|
unsignedData = ud,
|
||||||
redacts = eventEntity.redacts
|
redacts = eventEntity.redacts
|
||||||
)
|
).also {
|
||||||
|
eventEntity.decryptionResultJson?.let { json ->
|
||||||
|
try {
|
||||||
|
it.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
|
||||||
|
it.mCryptoError = eventEntity.decryptionErrorCode?.let { MXCryptoError.ErrorType.valueOf(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,24 +16,39 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
|
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.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
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.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomSummaryMapper @Inject constructor(private val timelineEventFactory: TimelineEventFactory) {
|
internal class RoomSummaryMapper @Inject constructor(
|
||||||
|
val cryptoService: CryptoService
|
||||||
|
) {
|
||||||
|
|
||||||
fun map(roomSummaryEntity: RoomSummaryEntity, getLatestEvent: Boolean = false): RoomSummary {
|
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
||||||
val tags = roomSummaryEntity.tags.map {
|
val tags = roomSummaryEntity.tags.map {
|
||||||
RoomTag(it.tagName, it.tagOrder)
|
RoomTag(it.tagName, it.tagOrder)
|
||||||
}
|
}
|
||||||
val latestEvent = if (getLatestEvent) {
|
|
||||||
roomSummaryEntity.latestEvent?.let {
|
val latestEvent = roomSummaryEntity.latestEvent?.asDomain()
|
||||||
timelineEventFactory.create(it, it.realm)
|
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) {
|
||||||
|
//TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
||||||
|
//for now decrypt sync
|
||||||
|
try {
|
||||||
|
val result = cryptoService.decryptEvent(latestEvent.root, latestEvent.root.roomId + UUID.randomUUID().toString())
|
||||||
|
latestEvent.root.mxDecryptionResult = OlmDecryptionResult(
|
||||||
|
payload = result.clearEvent,
|
||||||
|
senderKey = result.senderCurve25519Key,
|
||||||
|
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||||
|
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||||
|
)
|
||||||
|
} catch (e: MXCryptoError) {
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
return RoomSummary(
|
return RoomSummary(
|
||||||
roomId = roomSummaryEntity.roomId,
|
roomId = roomSummaryEntity.roomId,
|
||||||
|
@ -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.mapper
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
|
|
||||||
|
internal object TimelineEventMapper {
|
||||||
|
|
||||||
|
fun map(timelineEvent: TimelineEvent, roomId: String): TimelineEventEntity {
|
||||||
|
val timelineEventEntity = TimelineEventEntity()
|
||||||
|
timelineEventEntity.root = timelineEvent.root.toEntity(roomId)
|
||||||
|
timelineEventEntity.eventId = timelineEvent.root.eventId ?: ""
|
||||||
|
timelineEventEntity.roomId = roomId
|
||||||
|
timelineEventEntity.annotations = timelineEvent.annotations?.let { EventAnnotationsSummaryMapper.map(it, roomId) }
|
||||||
|
return timelineEventEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent {
|
||||||
|
|
||||||
|
return TimelineEvent(
|
||||||
|
root = timelineEventEntity.root?.asDomain()
|
||||||
|
?: Event("", timelineEventEntity.eventId),
|
||||||
|
annotations = timelineEventEntity.annotations?.asDomain(),
|
||||||
|
localId = timelineEventEntity.localId,
|
||||||
|
displayIndex = timelineEventEntity.root?.displayIndex ?: 0,
|
||||||
|
senderName = timelineEventEntity.senderName,
|
||||||
|
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
||||||
|
senderAvatar = timelineEventEntity.senderAvatar,
|
||||||
|
sendState = timelineEventEntity.root?.sendState ?: SendState.UNKNOWN
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun TimelineEventEntity.asDomain(): TimelineEvent {
|
||||||
|
return TimelineEventMapper.map(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun TimelineEvent.toEntity(roomId: String): TimelineEventEntity {
|
||||||
|
return TimelineEventMapper.map(this, roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ import io.realm.annotations.LinkingObjects
|
|||||||
|
|
||||||
internal open class ChunkEntity(@Index var prevToken: String? = null,
|
internal open class ChunkEntity(@Index var prevToken: String? = null,
|
||||||
@Index var nextToken: String? = null,
|
@Index var nextToken: String? = null,
|
||||||
var events: RealmList<EventEntity> = RealmList(),
|
var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
||||||
@Index var isLastForward: Boolean = false,
|
@Index var isLastForward: Boolean = false,
|
||||||
@Index var isLastBackward: Boolean = false,
|
@Index var isLastBackward: Boolean = false,
|
||||||
var backwardsDisplayIndex: Int? = null,
|
var backwardsDisplayIndex: Int? = null,
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
package im.vector.matrix.android.internal.database.model
|
package im.vector.matrix.android.internal.database.model
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
|
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 io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
import io.realm.annotations.Index
|
import io.realm.annotations.Index
|
||||||
@ -26,7 +29,7 @@ import java.util.*
|
|||||||
|
|
||||||
internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(),
|
internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(),
|
||||||
@Index var eventId: String = "",
|
@Index var eventId: String = "",
|
||||||
var roomId: String = "",
|
@Index var roomId: String = "",
|
||||||
@Index var type: String = "",
|
@Index var type: String = "",
|
||||||
var content: String? = null,
|
var content: String? = null,
|
||||||
var prevContent: String? = null,
|
var prevContent: String? = null,
|
||||||
@ -38,7 +41,9 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI
|
|||||||
var redacts: String? = null,
|
var redacts: String? = null,
|
||||||
@Index var stateIndex: Int = 0,
|
@Index var stateIndex: Int = 0,
|
||||||
@Index var displayIndex: Int = 0,
|
@Index var displayIndex: Int = 0,
|
||||||
@Index var isUnlinked: Boolean = false
|
@Index var isUnlinked: Boolean = false,
|
||||||
|
var decryptionResultJson: String? = null,
|
||||||
|
var decryptionErrorCode: String? = null
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
enum class LinkFilterMode {
|
enum class LinkFilterMode {
|
||||||
@ -60,10 +65,23 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI
|
|||||||
|
|
||||||
companion object
|
companion object
|
||||||
|
|
||||||
@LinkingObjects("events")
|
|
||||||
val chunk: RealmResults<ChunkEntity>? = null
|
|
||||||
|
|
||||||
@LinkingObjects("untimelinedStateEvents")
|
@LinkingObjects("untimelinedStateEvents")
|
||||||
val room: RealmResults<RoomEntity>? = null
|
val room: RealmResults<RoomEntity>? = null
|
||||||
|
|
||||||
|
@LinkingObjects("root")
|
||||||
|
val timelineEventEntity: RealmResults<TimelineEventEntity>? = null
|
||||||
|
|
||||||
|
|
||||||
|
fun setDecryptionResult(result: MXEventDecryptionResult) {
|
||||||
|
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)
|
||||||
|
decryptionResultJson = adapter.toJson(decryptionResult)
|
||||||
|
decryptionErrorCode = null
|
||||||
|
timelineEventEntity?.firstOrNull()?.root = this
|
||||||
|
}
|
||||||
}
|
}
|
@ -26,7 +26,7 @@ import kotlin.properties.Delegates
|
|||||||
internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
internal open class RoomEntity(@PrimaryKey var roomId: String = "",
|
||||||
var chunks: RealmList<ChunkEntity> = RealmList(),
|
var chunks: RealmList<ChunkEntity> = RealmList(),
|
||||||
var untimelinedStateEvents: RealmList<EventEntity> = RealmList(),
|
var untimelinedStateEvents: RealmList<EventEntity> = RealmList(),
|
||||||
var sendingTimelineEvents: RealmList<EventEntity> = RealmList(),
|
var sendingTimelineEvents: RealmList<TimelineEventEntity> = RealmList(),
|
||||||
var areAllMembersLoaded: Boolean = false
|
var areAllMembersLoaded: Boolean = false
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
|||||||
var displayName: String? = "",
|
var displayName: String? = "",
|
||||||
var avatarUrl: String? = "",
|
var avatarUrl: String? = "",
|
||||||
var topic: String? = "",
|
var topic: String? = "",
|
||||||
var latestEvent: EventEntity? = null,
|
var latestEvent: TimelineEventEntity? = null,
|
||||||
var heroes: RealmList<String> = RealmList(),
|
var heroes: RealmList<String> = RealmList(),
|
||||||
var joinedMembersCount: Int? = 0,
|
var joinedMembersCount: Int? = 0,
|
||||||
var invitedMembersCount: Int? = 0,
|
var invitedMembersCount: Int? = 0,
|
||||||
|
@ -25,6 +25,7 @@ import io.realm.annotations.RealmModule
|
|||||||
classes = [
|
classes = [
|
||||||
ChunkEntity::class,
|
ChunkEntity::class,
|
||||||
EventEntity::class,
|
EventEntity::class,
|
||||||
|
TimelineEventEntity::class,
|
||||||
FilterEntity::class,
|
FilterEntity::class,
|
||||||
GroupEntity::class,
|
GroupEntity::class,
|
||||||
GroupSummaryEntity::class,
|
GroupSummaryEntity::class,
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Types
|
||||||
|
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||||
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.RealmResults
|
||||||
|
import io.realm.annotations.Index
|
||||||
|
import io.realm.annotations.LinkingObjects
|
||||||
|
import io.realm.annotations.PrimaryKey
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
internal open class TimelineEventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(),
|
||||||
|
@Index var eventId: String = "",
|
||||||
|
@Index var roomId: String = "",
|
||||||
|
var root: EventEntity? = null,
|
||||||
|
var annotations: EventAnnotationsSummaryEntity? = null,
|
||||||
|
var senderName: String? = null,
|
||||||
|
var isUniqueDisplayName: Boolean = false,
|
||||||
|
var senderAvatar: String? = null,
|
||||||
|
var senderMembershipEvent: EventEntity? = null
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
@LinkingObjects("timelineEvents")
|
||||||
|
val chunk: RealmResults<ChunkEntity>? = null
|
||||||
|
|
||||||
|
companion object
|
||||||
|
|
||||||
|
}
|
@ -49,7 +49,7 @@ internal fun ChunkEntity.Companion.findLastLiveChunkFromRoom(realm: Realm, roomI
|
|||||||
|
|
||||||
internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List<String>): RealmResults<ChunkEntity> {
|
internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List<String>): RealmResults<ChunkEntity> {
|
||||||
return realm.where<ChunkEntity>()
|
return realm.where<ChunkEntity>()
|
||||||
.`in`(ChunkEntityFields.EVENTS.EVENT_ID, eventIds.toTypedArray())
|
.`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, eventIds.toTypedArray())
|
||||||
.findAll()
|
.findAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.database.query
|
|||||||
|
|
||||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntityFields
|
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntityFields
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
@ -39,5 +40,9 @@ internal fun EventAnnotationsSummaryEntity.Companion.whereInRoom(realm: Realm, r
|
|||||||
|
|
||||||
internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, eventId: String): EventAnnotationsSummaryEntity {
|
internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, eventId: String): EventAnnotationsSummaryEntity {
|
||||||
val obj = realm.createObject(EventAnnotationsSummaryEntity::class.java, eventId)
|
val obj = realm.createObject(EventAnnotationsSummaryEntity::class.java, eventId)
|
||||||
|
//Denormalization
|
||||||
|
TimelineEventEntity.where(realm, eventId = eventId).findFirst()?.let {
|
||||||
|
it.annotations = obj
|
||||||
|
}
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
@ -16,12 +16,9 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.database.query
|
package im.vector.matrix.android.internal.database.query
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.database.helper.addSendingEvent
|
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.*
|
import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.*
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
@ -62,30 +59,6 @@ internal fun EventEntity.Companion.types(realm: Realm,
|
|||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun EventEntity.Companion.latestEvent(realm: Realm,
|
|
||||||
roomId: String,
|
|
||||||
includedTypes: List<String> = emptyList(),
|
|
||||||
excludedTypes: List<String> = emptyList()): EventEntity? {
|
|
||||||
|
|
||||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null
|
|
||||||
val eventList = if (roomEntity.sendingTimelineEvents.isNotEmpty()) {
|
|
||||||
roomEntity.sendingTimelineEvents
|
|
||||||
} else {
|
|
||||||
ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.events
|
|
||||||
}
|
|
||||||
val query = eventList?.where()
|
|
||||||
if (includedTypes.isNotEmpty()) {
|
|
||||||
query?.`in`(EventEntityFields.TYPE, includedTypes.toTypedArray())
|
|
||||||
} else if (excludedTypes.isNotEmpty()) {
|
|
||||||
query?.not()?.`in`(EventEntityFields.TYPE, excludedTypes.toTypedArray())
|
|
||||||
}
|
|
||||||
return query
|
|
||||||
?.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
|
||||||
?.findFirst()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? {
|
internal fun RealmQuery<EventEntity>.next(from: Int? = null, strict: Boolean = true): EventEntity? {
|
||||||
if (from != null) {
|
if (from != null) {
|
||||||
if (strict) {
|
if (strict) {
|
||||||
|
@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* 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.query
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.EventEntity.LinkFilterMode.*
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmQuery
|
||||||
|
import io.realm.Sort
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
|
internal fun TimelineEventEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<TimelineEventEntity> {
|
||||||
|
return realm.where<TimelineEventEntity>().equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun TimelineEventEntity.Companion.where(realm: Realm, eventIds: List<String>): RealmQuery<TimelineEventEntity> {
|
||||||
|
return realm.where<TimelineEventEntity>().`in`(TimelineEventEntityFields.EVENT_ID, eventIds.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun TimelineEventEntity.Companion.where(realm: Realm,
|
||||||
|
roomId: String? = null,
|
||||||
|
type: String? = null,
|
||||||
|
linkFilterMode: EventEntity.LinkFilterMode = LINKED_ONLY): RealmQuery<TimelineEventEntity> {
|
||||||
|
val query = realm.where<TimelineEventEntity>()
|
||||||
|
if (roomId != null) {
|
||||||
|
query.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
||||||
|
}
|
||||||
|
if (type != null) {
|
||||||
|
query.equalTo(TimelineEventEntityFields.ROOT.TYPE, type)
|
||||||
|
}
|
||||||
|
return when (linkFilterMode) {
|
||||||
|
LINKED_ONLY -> query.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, false)
|
||||||
|
UNLINKED_ONLY -> query.equalTo(TimelineEventEntityFields.ROOT.IS_UNLINKED, true)
|
||||||
|
BOTH -> query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List<TimelineEventEntity> {
|
||||||
|
return realm.where<TimelineEventEntity>()
|
||||||
|
.equalTo(TimelineEventEntityFields.SENDER_MEMBERSHIP_EVENT.EVENT_ID, senderMembershipEventId)
|
||||||
|
.findAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
||||||
|
roomId: String,
|
||||||
|
includedTypes: List<String> = emptyList(),
|
||||||
|
excludedTypes: List<String> = emptyList()): TimelineEventEntity? {
|
||||||
|
|
||||||
|
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null
|
||||||
|
val eventList = if (roomEntity.sendingTimelineEvents.isNotEmpty()) {
|
||||||
|
roomEntity.sendingTimelineEvents
|
||||||
|
} else {
|
||||||
|
ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.timelineEvents
|
||||||
|
}
|
||||||
|
val query = eventList?.where()
|
||||||
|
if (includedTypes.isNotEmpty()) {
|
||||||
|
query?.`in`(TimelineEventEntityFields.ROOT.TYPE, includedTypes.toTypedArray())
|
||||||
|
} else if (excludedTypes.isNotEmpty()) {
|
||||||
|
query?.not()?.`in`(TimelineEventEntityFields.ROOT.TYPE, excludedTypes.toTypedArray())
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
?.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
|
?.findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun RealmQuery<TimelineEventEntity>.next(from: Int? = null, strict: Boolean = true): TimelineEventEntity? {
|
||||||
|
if (from != null) {
|
||||||
|
if (strict) {
|
||||||
|
this.greaterThan(TimelineEventEntityFields.ROOT.STATE_INDEX, from)
|
||||||
|
} else {
|
||||||
|
this.greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.STATE_INDEX, from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
.sort(TimelineEventEntityFields.ROOT.STATE_INDEX, Sort.ASCENDING)
|
||||||
|
.findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun RealmQuery<TimelineEventEntity>.prev(since: Int? = null, strict: Boolean = false): TimelineEventEntity? {
|
||||||
|
if (since != null) {
|
||||||
|
if (strict) {
|
||||||
|
this.lessThan(TimelineEventEntityFields.ROOT.STATE_INDEX, since)
|
||||||
|
} else {
|
||||||
|
this.lessThanOrEqualTo(TimelineEventEntityFields.ROOT.STATE_INDEX, since)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
.sort(TimelineEventEntityFields.ROOT.STATE_INDEX, Sort.DESCENDING)
|
||||||
|
.findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun RealmList<TimelineEventEntity>.find(eventId: String): TimelineEventEntity? {
|
||||||
|
return this.where().equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, eventId).findFirst()
|
||||||
|
}
|
@ -33,8 +33,6 @@ import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
|||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.util.fetchCopied
|
|
||||||
import im.vector.matrix.android.internal.util.fetchCopyMap
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultRoom @Inject constructor(override val roomId: String,
|
internal class DefaultRoom @Inject constructor(override val roomId: String,
|
||||||
@ -55,12 +53,12 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
|||||||
RelationService by relationService,
|
RelationService by relationService,
|
||||||
MembershipService by roomMembersService {
|
MembershipService by roomMembersService {
|
||||||
|
|
||||||
override fun liveRoomSummary(fetchLastEvent: Boolean): LiveData<RoomSummary> {
|
override fun liveRoomSummary(): LiveData<RoomSummary> {
|
||||||
val liveRealmData = RealmLiveData<RoomSummaryEntity>(monarchy.realmConfiguration) { realm ->
|
val liveRealmData = RealmLiveData<RoomSummaryEntity>(monarchy.realmConfiguration) { realm ->
|
||||||
RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
|
RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
|
||||||
}
|
}
|
||||||
return Transformations.map(liveRealmData) { results ->
|
return Transformations.map(liveRealmData) { results ->
|
||||||
val roomSummaries = results.map { roomSummaryMapper.map(it, fetchLastEvent) }
|
val roomSummaries = results.map { roomSummaryMapper.map(it) }
|
||||||
|
|
||||||
if (roomSummaries.isEmpty()) {
|
if (roomSummaries.isEmpty()) {
|
||||||
// Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache
|
// Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache
|
||||||
@ -71,10 +69,10 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun roomSummary(fetchLastEvent: Boolean): RoomSummary? {
|
override fun roomSummary(): RoomSummary? {
|
||||||
return monarchy.fetchAllMappedSync(
|
return monarchy.fetchAllMappedSync(
|
||||||
{ realm -> RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
|
{ realm -> RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
|
||||||
{ roomSummaryMapper.map(it, fetchLastEvent) }
|
{ roomSummaryMapper.map(it) }
|
||||||
).firstOrNull()
|
).firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,10 +52,10 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
|||||||
return roomFactory.create(roomId)
|
return roomFactory.create(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun liveRoomSummaries(fetchLastEvents: Boolean): LiveData<List<RoomSummary>> {
|
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
|
||||||
return monarchy.findAllMappedWithChanges(
|
return monarchy.findAllMappedWithChanges(
|
||||||
{ realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
|
{ realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
|
||||||
{ roomSummaryMapper.map(it, fetchLastEvents) }
|
{ roomSummaryMapper.map(it) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,36 +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.session.room
|
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
|
||||||
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.query.where
|
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import io.realm.Realm
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches annotations (reactions, edits...) associated to a given eventEntity from the data layer.
|
|
||||||
*/
|
|
||||||
|
|
||||||
internal class EventRelationExtractor @Inject constructor() {
|
|
||||||
|
|
||||||
fun extractFrom(event: EventEntity, realm: Realm = event.realm): EventAnnotationsSummary? {
|
|
||||||
return EventAnnotationsSummaryEntity.where(realm, event.eventId).findFirst()?.asDomain()
|
|
||||||
}
|
|
||||||
}
|
|
@ -86,6 +86,13 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(private
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EventAnnotationsSummaryEntity.where(realm, event.eventId ?: "").findFirst()?.let {
|
||||||
|
TimelineEventEntity.where(realm,eventId = event.eventId ?: "").findFirst()?.let { tet ->
|
||||||
|
tet.annotations = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
EventType.REDACTION -> {
|
EventType.REDACTION -> {
|
||||||
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
|
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
|
||||||
|
@ -24,7 +24,6 @@ import im.vector.matrix.android.api.session.room.Room
|
|||||||
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
||||||
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
||||||
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor
|
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
|
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
|
import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
|
||||||
@ -39,7 +38,6 @@ import im.vector.matrix.android.internal.session.room.state.DefaultStateService
|
|||||||
import im.vector.matrix.android.internal.session.room.state.SendStateTask
|
import im.vector.matrix.android.internal.session.room.state.SendStateTask
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
|
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
|
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.InMemoryTimelineEventFactory
|
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -63,8 +61,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
|
|||||||
private val leaveRoomTask: LeaveRoomTask) {
|
private val leaveRoomTask: LeaveRoomTask) {
|
||||||
|
|
||||||
fun create(roomId: String): Room {
|
fun create(roomId: String): Room {
|
||||||
val timelineEventFactory = InMemoryTimelineEventFactory(SenderRoomMemberExtractor(), EventRelationExtractor(), cryptoService)
|
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, cryptoService, paginationTask)
|
||||||
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, cryptoService, paginationTask)
|
|
||||||
val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy)
|
val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy)
|
||||||
val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
|
val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
|
||||||
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
|
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
|
||||||
|
@ -138,10 +138,4 @@ internal abstract class RoomModule {
|
|||||||
@Binds
|
@Binds
|
||||||
abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService
|
abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService
|
||||||
|
|
||||||
@Binds
|
|
||||||
abstract fun bindSimpleTimelineEventFactory(timelineEventFactory: SimpleTimelineEventFactory): TimelineEventFactory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
abstract fun bindCacheableTimelineEventFactory(inMemoryTimelineEventFactory: InMemoryTimelineEventFactory): CacheableTimelineEventFactory
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
|||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.latestEvent
|
import im.vector.matrix.android.internal.database.query.latestEvent
|
||||||
import im.vector.matrix.android.internal.database.query.prev
|
import im.vector.matrix.android.internal.database.query.prev
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
@ -77,19 +78,19 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
roomSummaryEntity.highlightCount = unreadNotifications?.highlightCount ?: 0
|
roomSummaryEntity.highlightCount = unreadNotifications?.highlightCount ?: 0
|
||||||
roomSummaryEntity.notificationCount = unreadNotifications?.notificationCount ?:0
|
roomSummaryEntity.notificationCount = unreadNotifications?.notificationCount ?: 0
|
||||||
|
|
||||||
if (membership != null) {
|
if (membership != null) {
|
||||||
roomSummaryEntity.membership = membership
|
roomSummaryEntity.membership = membership
|
||||||
}
|
}
|
||||||
|
|
||||||
val lastEvent = EventEntity.latestEvent(realm, roomId, includedTypes = PREVIEWABLE_TYPES)
|
val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, includedTypes = PREVIEWABLE_TYPES)
|
||||||
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
|
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
|
||||||
val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId }
|
val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId }
|
||||||
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
|
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
|
||||||
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
|
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
|
||||||
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
|
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
|
||||||
roomSummaryEntity.latestEvent = lastEvent
|
roomSummaryEntity.latestEvent = latestEvent
|
||||||
roomSummaryEntity.otherMemberIds.clear()
|
roomSummaryEntity.otherMemberIds.clear()
|
||||||
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers.keys)
|
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers.keys)
|
||||||
}
|
}
|
||||||
|
@ -20,15 +20,16 @@ import arrow.core.Try
|
|||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.internal.database.helper.addStateEvents
|
import im.vector.matrix.android.internal.database.helper.addStateEvents
|
||||||
|
import im.vector.matrix.android.internal.database.helper.updateSenderData
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import im.vector.matrix.android.internal.session.room.RoomAPI
|
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||||
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
||||||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.util.tryTransactionSync
|
import im.vector.matrix.android.internal.util.tryTransactionSync
|
||||||
|
import io.realm.Realm
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -41,9 +42,9 @@ internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Boolea
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAPI: RoomAPI,
|
internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAPI: RoomAPI,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val syncTokenStore: SyncTokenStore,
|
private val syncTokenStore: SyncTokenStore,
|
||||||
private val roomSummaryUpdater: RoomSummaryUpdater
|
private val roomSummaryUpdater: RoomSummaryUpdater
|
||||||
) : LoadRoomMembersTask {
|
) : LoadRoomMembersTask {
|
||||||
|
|
||||||
override suspend fun execute(params: LoadRoomMembersTask.Params): Try<Boolean> {
|
override suspend fun execute(params: LoadRoomMembersTask.Params): Try<Boolean> {
|
||||||
@ -68,20 +69,20 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP
|
|||||||
|
|
||||||
val roomMembers = RoomMembers(realm, roomId).getLoaded()
|
val roomMembers = RoomMembers(realm, roomId).getLoaded()
|
||||||
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
|
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
|
||||||
|
|
||||||
roomEntity.addStateEvents(eventsToInsert)
|
roomEntity.addStateEvents(eventsToInsert)
|
||||||
|
roomEntity.chunks.flatMap { it.timelineEvents }.forEach {
|
||||||
|
it.updateSenderData()
|
||||||
|
}
|
||||||
roomEntity.areAllMembersLoaded = true
|
roomEntity.areAllMembersLoaded = true
|
||||||
|
|
||||||
roomSummaryUpdater.update(realm, roomId)
|
roomSummaryUpdater.update(realm, roomId)
|
||||||
}
|
}
|
||||||
.map { response }
|
.map { response }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun areAllMembersAlreadyLoaded(roomId: String): Boolean {
|
private fun areAllMembersAlreadyLoaded(roomId: String): Boolean {
|
||||||
return monarchy
|
return Realm.getInstance(monarchy.realmConfiguration).use {
|
||||||
.fetchAllCopiedSync { RoomEntity.where(it, roomId) }
|
RoomEntity.where(it, roomId).findFirst()?.areAllMembersLoaded ?: false
|
||||||
.firstOrNull()
|
}
|
||||||
?.areAllMembersLoaded ?: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -29,6 +29,10 @@ import io.realm.Realm
|
|||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
import io.realm.Sort
|
import io.realm.Sort
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is an helper around STATE_ROOM_MEMBER events.
|
||||||
|
* It allows to get the live membership of a user.
|
||||||
|
*/
|
||||||
internal class RoomMembers(private val realm: Realm,
|
internal class RoomMembers(private val realm: Realm,
|
||||||
private val roomId: String
|
private val roomId: String
|
||||||
) {
|
) {
|
||||||
|
@ -1,72 +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.session.room.membership
|
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
|
||||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
|
||||||
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.RoomEntity
|
|
||||||
import im.vector.matrix.android.internal.database.query.findIncludingEvent
|
|
||||||
import im.vector.matrix.android.internal.database.query.next
|
|
||||||
import im.vector.matrix.android.internal.database.query.prev
|
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmList
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class SenderRoomMemberExtractor @Inject constructor() {
|
|
||||||
|
|
||||||
fun extractFrom(event: EventEntity, realm: Realm = event.realm): RoomMember? {
|
|
||||||
val roomId = event.roomId
|
|
||||||
val sender = event.sender ?: return null
|
|
||||||
// If the event is unlinked we want to fetch unlinked state events
|
|
||||||
val unlinked = event.isUnlinked
|
|
||||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return null
|
|
||||||
|
|
||||||
// When not synced, we should grab the live RoomMember event
|
|
||||||
return if (event.sendState != SendState.SYNCED) {
|
|
||||||
RoomMembers(realm, roomId).get(sender)
|
|
||||||
} else {
|
|
||||||
val chunkEntity = ChunkEntity.findIncludingEvent(realm, event.eventId)
|
|
||||||
val content = when {
|
|
||||||
chunkEntity == null -> null
|
|
||||||
event.stateIndex <= 0 -> baseQuery(chunkEntity.events, sender, unlinked).next(from = event.stateIndex)?.prevContent
|
|
||||||
else -> baseQuery(chunkEntity.events, sender, unlinked).prev(since = event.stateIndex)?.content
|
|
||||||
}
|
|
||||||
val fallbackContent = content
|
|
||||||
?: baseQuery(roomEntity.untimelinedStateEvents, sender, unlinked).prev(since = event.stateIndex)?.content
|
|
||||||
ContentMapper.map(fallbackContent).toModel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun baseQuery(list: RealmList<EventEntity>,
|
|
||||||
sender: String,
|
|
||||||
isUnlinked: Boolean): RealmQuery<EventEntity> {
|
|
||||||
return list
|
|
||||||
.where()
|
|
||||||
.equalTo(EventEntityFields.STATE_KEY, sender)
|
|
||||||
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
|
|
||||||
.equalTo(EventEntityFields.IS_UNLINKED, isUnlinked)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
|||||||
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.types
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
@ -41,7 +42,7 @@ internal class EventsPruner @Inject constructor(@SessionDatabase realmConfigurat
|
|||||||
private val taskExecutor: TaskExecutor) :
|
private val taskExecutor: TaskExecutor) :
|
||||||
RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
|
RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
|
||||||
|
|
||||||
override val query = Monarchy.Query<EventEntity> { EventEntity.where(it, type = EventType.REDACTION) }
|
override val query = Monarchy.Query<EventEntity> { EventEntity.types(it, listOf(EventType.REDACTION)) }
|
||||||
|
|
||||||
override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
|
override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
|
||||||
Timber.v("Event pruner called with ${inserted.size} insertions")
|
Timber.v("Event pruner called with ${inserted.size} insertions")
|
||||||
|
@ -21,12 +21,14 @@ 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.UnsignedData
|
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
|
import im.vector.matrix.android.internal.database.helper.updateSenderData
|
||||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
||||||
import im.vector.matrix.android.internal.database.mapper.EventMapper
|
import im.vector.matrix.android.internal.database.mapper.EventMapper
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.findWithSenderMembershipEvent
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import im.vector.matrix.android.internal.util.tryTransactionSync
|
import im.vector.matrix.android.internal.util.tryTransactionSync
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
@ -59,13 +61,13 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M
|
|||||||
}
|
}
|
||||||
|
|
||||||
val redactionEventEntity = EventEntity.where(realm, eventId = redactionEvent.eventId
|
val redactionEventEntity = EventEntity.where(realm, eventId = redactionEvent.eventId
|
||||||
?: "").findFirst()
|
?: "").findFirst()
|
||||||
?: return
|
?: return
|
||||||
val isLocalEcho = redactionEventEntity.sendState == SendState.UNSENT
|
val isLocalEcho = redactionEventEntity.sendState == SendState.UNSENT
|
||||||
Timber.v("Redact event for ${redactionEvent.redacts} localEcho=$isLocalEcho")
|
Timber.v("Redact event for ${redactionEvent.redacts} localEcho=$isLocalEcho")
|
||||||
|
|
||||||
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()
|
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
val allowedKeys = computeAllowedKeys(eventToPrune.type)
|
val allowedKeys = computeAllowedKeys(eventToPrune.type)
|
||||||
if (allowedKeys.isNotEmpty()) {
|
if (allowedKeys.isNotEmpty()) {
|
||||||
@ -76,7 +78,7 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M
|
|||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
Timber.d("REDACTION for message ${eventToPrune.eventId}")
|
Timber.d("REDACTION for message ${eventToPrune.eventId}")
|
||||||
val unsignedData = EventMapper.map(eventToPrune).unsignedData
|
val unsignedData = EventMapper.map(eventToPrune).unsignedData
|
||||||
?: UnsignedData(null, null)
|
?: UnsignedData(null, null)
|
||||||
|
|
||||||
//was this event a m.replace
|
//was this event a m.replace
|
||||||
// val contentModel = ContentMapper.map(eventToPrune.content)?.toModel<MessageContent>()
|
// val contentModel = ContentMapper.map(eventToPrune.content)?.toModel<MessageContent>()
|
||||||
@ -94,28 +96,34 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (eventToPrune.type == EventType.STATE_ROOM_MEMBER) {
|
||||||
|
val timelineEventsToUpdate = TimelineEventEntity.findWithSenderMembershipEvent(realm, eventToPrune.eventId)
|
||||||
|
for (timelineEvent in timelineEventsToUpdate) {
|
||||||
|
timelineEvent.updateSenderData()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun computeAllowedKeys(type: String): List<String> {
|
private fun computeAllowedKeys(type: String): List<String> {
|
||||||
// Add filtered content, allowed keys in content depends on the event type
|
// Add filtered content, allowed keys in content depends on the event type
|
||||||
return when (type) {
|
return when (type) {
|
||||||
EventType.STATE_ROOM_MEMBER -> listOf("membership")
|
EventType.STATE_ROOM_MEMBER -> listOf("membership")
|
||||||
EventType.STATE_ROOM_CREATE -> listOf("creator")
|
EventType.STATE_ROOM_CREATE -> listOf("creator")
|
||||||
EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule")
|
EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule")
|
||||||
EventType.STATE_ROOM_POWER_LEVELS -> listOf("users",
|
EventType.STATE_ROOM_POWER_LEVELS -> listOf("users",
|
||||||
"users_default",
|
"users_default",
|
||||||
"events",
|
"events",
|
||||||
"events_default",
|
"events_default",
|
||||||
"state_default",
|
"state_default",
|
||||||
"ban",
|
"ban",
|
||||||
"kick",
|
"kick",
|
||||||
"redact",
|
"redact",
|
||||||
"invite")
|
"invite")
|
||||||
EventType.STATE_ROOM_ALIASES -> listOf("aliases")
|
EventType.STATE_ROOM_ALIASES -> listOf("aliases")
|
||||||
EventType.STATE_CANONICAL_ALIAS -> listOf("alias")
|
EventType.STATE_CANONICAL_ALIAS -> listOf("alias")
|
||||||
EventType.FEEDBACK -> listOf("type", "target_event_id")
|
EventType.FEEDBACK -> listOf("type", "target_event_id")
|
||||||
else -> emptyList()
|
else -> emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -21,8 +21,8 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.find
|
import im.vector.matrix.android.internal.database.query.find
|
||||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||||
import im.vector.matrix.android.internal.database.query.latestEvent
|
import im.vector.matrix.android.internal.database.query.latestEvent
|
||||||
@ -55,21 +55,21 @@ internal class DefaultReadService @Inject constructor(private val roomId: String
|
|||||||
setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor)
|
setReadMarkersTask.configureWith(params).dispatchTo(callback).executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLatestEvent(): EventEntity? {
|
private fun getLatestEvent(): TimelineEventEntity? {
|
||||||
return monarchy.fetchCopied { EventEntity.latestEvent(it, roomId) }
|
return monarchy.fetchCopied { TimelineEventEntity.latestEvent(it, roomId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isEventRead(eventId: String): Boolean {
|
override fun isEventRead(eventId: String): Boolean {
|
||||||
var isEventRead = false
|
var isEventRead = false
|
||||||
monarchy.doWithRealm {
|
monarchy.doWithRealm {
|
||||||
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
|
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
|
||||||
?: return@doWithRealm
|
?: return@doWithRealm
|
||||||
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
|
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
|
||||||
?: return@doWithRealm
|
?: return@doWithRealm
|
||||||
val readReceiptIndex = liveChunk.events.find(readReceipt.eventId)?.displayIndex
|
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
|
||||||
?: Int.MIN_VALUE
|
?: Int.MIN_VALUE
|
||||||
val eventToCheckIndex = liveChunk.events.find(eventId)?.displayIndex
|
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
|
||||||
?: Int.MAX_VALUE
|
?: Int.MAX_VALUE
|
||||||
isEventRead = eventToCheckIndex <= readReceiptIndex
|
isEventRead = eventToCheckIndex <= readReceiptIndex
|
||||||
}
|
}
|
||||||
return isEventRead
|
return isEventRead
|
||||||
|
@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.database.model.ChunkEntity
|
|||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.find
|
import im.vector.matrix.android.internal.database.query.find
|
||||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||||
import im.vector.matrix.android.internal.database.query.latestEvent
|
import im.vector.matrix.android.internal.database.query.latestEvent
|
||||||
@ -82,7 +83,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
|||||||
|
|
||||||
private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) {
|
private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) {
|
||||||
monarchy.tryTransactionAsync { realm ->
|
monarchy.tryTransactionAsync { realm ->
|
||||||
val isLatestReceived = EventEntity.latestEvent(realm, roomId)?.eventId == eventId
|
val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId)?.eventId == eventId
|
||||||
if (isLatestReceived) {
|
if (isLatestReceived) {
|
||||||
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
?: return@tryTransactionAsync
|
?: return@tryTransactionAsync
|
||||||
@ -99,9 +100,9 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
|
|||||||
?: return@doWithRealm
|
?: return@doWithRealm
|
||||||
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
|
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
|
||||||
?: return@doWithRealm
|
?: return@doWithRealm
|
||||||
val readReceiptIndex = liveChunk.events.find(readReceipt.eventId)?.displayIndex
|
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
|
||||||
?: Int.MIN_VALUE
|
?: Int.MIN_VALUE
|
||||||
val eventToCheckIndex = liveChunk.events.find(eventId)?.displayIndex
|
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
|
||||||
?: Int.MAX_VALUE
|
?: Int.MAX_VALUE
|
||||||
isEventRead = eventToCheckIndex <= readReceiptIndex
|
isEventRead = eventToCheckIndex <= readReceiptIndex
|
||||||
}
|
}
|
||||||
|
@ -19,19 +19,12 @@ package im.vector.matrix.android.internal.session.room.timeline
|
|||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
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.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
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.TimelineEvent
|
||||||
import im.vector.matrix.android.api.util.CancelableBag
|
import im.vector.matrix.android.api.util.CancelableBag
|
||||||
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
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.ChunkEntityFields
|
|
||||||
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.EventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
|
||||||
import im.vector.matrix.android.internal.database.query.findIncludingEvent
|
import im.vector.matrix.android.internal.database.query.findIncludingEvent
|
||||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
@ -41,13 +34,7 @@ import im.vector.matrix.android.internal.task.configureWith
|
|||||||
import im.vector.matrix.android.internal.util.Debouncer
|
import im.vector.matrix.android.internal.util.Debouncer
|
||||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
||||||
import im.vector.matrix.android.internal.util.createUIHandler
|
import im.vector.matrix.android.internal.util.createUIHandler
|
||||||
import io.realm.OrderedCollectionChangeSet
|
import io.realm.*
|
||||||
import io.realm.OrderedRealmCollectionChangeListener
|
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmConfiguration
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
import io.realm.RealmResults
|
|
||||||
import io.realm.Sort
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
@ -66,9 +53,8 @@ internal class DefaultTimeline(
|
|||||||
private val realmConfiguration: RealmConfiguration,
|
private val realmConfiguration: RealmConfiguration,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val contextOfEventTask: GetContextOfEventTask,
|
private val contextOfEventTask: GetContextOfEventTask,
|
||||||
private val timelineEventFactory: CacheableTimelineEventFactory,
|
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
private val cryptoService: CryptoService,
|
cryptoService: CryptoService,
|
||||||
private val allowedTypes: List<String>?
|
private val allowedTypes: List<String>?
|
||||||
) : Timeline {
|
) : Timeline {
|
||||||
|
|
||||||
@ -91,7 +77,7 @@ internal class DefaultTimeline(
|
|||||||
private val cancelableBag = CancelableBag()
|
private val cancelableBag = CancelableBag()
|
||||||
private val debouncer = Debouncer(mainHandler)
|
private val debouncer = Debouncer(mainHandler)
|
||||||
|
|
||||||
private lateinit var liveEvents: RealmResults<EventEntity>
|
private lateinit var liveEvents: RealmResults<TimelineEventEntity>
|
||||||
private var roomEntity: RoomEntity? = null
|
private var roomEntity: RoomEntity? = null
|
||||||
|
|
||||||
private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN
|
private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN
|
||||||
@ -103,9 +89,13 @@ internal class DefaultTimeline(
|
|||||||
private val forwardsPaginationState = AtomicReference(PaginationState())
|
private val forwardsPaginationState = AtomicReference(PaginationState())
|
||||||
|
|
||||||
|
|
||||||
|
private val timelineID = UUID.randomUUID().toString()
|
||||||
|
|
||||||
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
|
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
|
||||||
|
|
||||||
private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<EventEntity>> { results, changeSet ->
|
private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService)
|
||||||
|
|
||||||
|
private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<TimelineEventEntity>> { results, changeSet ->
|
||||||
if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) {
|
if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) {
|
||||||
handleInitialLoad()
|
handleInitialLoad()
|
||||||
} else {
|
} else {
|
||||||
@ -115,13 +105,12 @@ internal class DefaultTimeline(
|
|||||||
nextDisplayIndex = DISPLAY_INDEX_UNKNOWN
|
nextDisplayIndex = DISPLAY_INDEX_UNKNOWN
|
||||||
builtEvents.clear()
|
builtEvents.clear()
|
||||||
builtEventsIdMap.clear()
|
builtEventsIdMap.clear()
|
||||||
timelineEventFactory.clear()
|
|
||||||
}
|
}
|
||||||
changeSet.insertionRanges.forEach { range ->
|
changeSet.insertionRanges.forEach { range ->
|
||||||
val (startDisplayIndex, direction) = if (range.startIndex == 0) {
|
val (startDisplayIndex, direction) = if (range.startIndex == 0) {
|
||||||
Pair(liveEvents[range.length - 1]!!.displayIndex, Timeline.Direction.FORWARDS)
|
Pair(liveEvents[range.length - 1]!!.root!!.displayIndex, Timeline.Direction.FORWARDS)
|
||||||
} else {
|
} else {
|
||||||
Pair(liveEvents[range.startIndex]!!.displayIndex, Timeline.Direction.BACKWARDS)
|
Pair(liveEvents[range.startIndex]!!.root!!.displayIndex, Timeline.Direction.BACKWARDS)
|
||||||
}
|
}
|
||||||
val state = getPaginationState(direction)
|
val state = getPaginationState(direction)
|
||||||
if (state.isPaginating) {
|
if (state.isPaginating) {
|
||||||
@ -144,7 +133,7 @@ internal class DefaultTimeline(
|
|||||||
builtEventsIdMap[eventId]?.let { builtIndex ->
|
builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||||
//Update the relation of existing event
|
//Update the relation of existing event
|
||||||
builtEvents[builtIndex]?.let { te ->
|
builtEvents[builtIndex]?.let { te ->
|
||||||
builtEvents[builtIndex] = timelineEventFactory.create(eventEntity, eventEntity.realm)
|
builtEvents[builtIndex] = eventEntity.asDomain()
|
||||||
hasChanged = true
|
hasChanged = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,32 +163,32 @@ internal class DefaultTimeline(
|
|||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val newSessionListener = object : NewSessionListener {
|
// private val newSessionListener = object : NewSessionListener {
|
||||||
override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
|
// override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
|
||||||
if (roomId == this@DefaultTimeline.roomId) {
|
// if (roomId == this@DefaultTimeline.roomId) {
|
||||||
Timber.v("New session id detected for this room")
|
// Timber.v("New session id detected for this room")
|
||||||
BACKGROUND_HANDLER.post {
|
// BACKGROUND_HANDLER.post {
|
||||||
val realm = backgroundRealm.get()
|
// val realm = backgroundRealm.get()
|
||||||
var hasChange = false
|
// var hasChange = false
|
||||||
builtEvents.forEachIndexed { index, timelineEvent ->
|
// builtEvents.forEachIndexed { index, timelineEvent ->
|
||||||
if (timelineEvent.isEncrypted()) {
|
// if (timelineEvent.isEncrypted()) {
|
||||||
val eventContent = timelineEvent.root.content.toModel<EncryptedEventContent>()
|
// val eventContent = timelineEvent.root.content.toModel<EncryptedEventContent>()
|
||||||
if (eventContent?.sessionId == sessionId
|
// if (eventContent?.sessionId == sessionId
|
||||||
&& (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) {
|
// && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) {
|
||||||
//we need to rebuild this event
|
// //we need to rebuild this event
|
||||||
EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let {
|
// EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let {
|
||||||
builtEvents[index] = timelineEventFactory.create(it, realm)
|
// //builtEvents[index] = timelineEventFactory.create(it, realm)
|
||||||
hasChange = true
|
// hasChange = true
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if (hasChange) postSnapshot()
|
// if (hasChange) postSnapshot()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Public methods ******************************************************************************
|
// Public methods ******************************************************************************
|
||||||
|
|
||||||
@ -221,7 +210,7 @@ internal class DefaultTimeline(
|
|||||||
override fun start() {
|
override fun start() {
|
||||||
if (isStarted.compareAndSet(false, true)) {
|
if (isStarted.compareAndSet(false, true)) {
|
||||||
Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId")
|
Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId")
|
||||||
cryptoService.addNewSessionListener(newSessionListener)
|
eventDecryptor.start()
|
||||||
BACKGROUND_HANDLER.post {
|
BACKGROUND_HANDLER.post {
|
||||||
val realm = Realm.getInstance(realmConfiguration)
|
val realm = Realm.getInstance(realmConfiguration)
|
||||||
backgroundRealm.set(realm)
|
backgroundRealm.set(realm)
|
||||||
@ -234,7 +223,7 @@ internal class DefaultTimeline(
|
|||||||
}
|
}
|
||||||
|
|
||||||
liveEvents = buildEventQuery(realm)
|
liveEvents = buildEventQuery(realm)
|
||||||
.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
.findAllAsync()
|
.findAllAsync()
|
||||||
.also { it.addChangeListener(eventsChangeListener) }
|
.also { it.addChangeListener(eventsChangeListener) }
|
||||||
|
|
||||||
@ -249,7 +238,7 @@ internal class DefaultTimeline(
|
|||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
if (isStarted.compareAndSet(true, false)) {
|
if (isStarted.compareAndSet(true, false)) {
|
||||||
cryptoService.removeSessionListener(newSessionListener)
|
eventDecryptor.destroy()
|
||||||
Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId")
|
Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId")
|
||||||
BACKGROUND_HANDLER.post {
|
BACKGROUND_HANDLER.post {
|
||||||
cancelableBag.cancel()
|
cancelableBag.cancel()
|
||||||
@ -269,13 +258,13 @@ internal class DefaultTimeline(
|
|||||||
|
|
||||||
private fun hasMoreInCache(direction: Timeline.Direction): Boolean {
|
private fun hasMoreInCache(direction: Timeline.Direction): Boolean {
|
||||||
val localRealm = Realm.getInstance(realmConfiguration)
|
val localRealm = Realm.getInstance(realmConfiguration)
|
||||||
val eventEntity = buildEventQuery(localRealm).findFirst(direction) ?: return false
|
val timelineEventEntity = buildEventQuery(localRealm).findFirst(direction) ?: return false
|
||||||
val hasMoreInCache = if (direction == Timeline.Direction.FORWARDS) {
|
val hasMoreInCache = if (direction == Timeline.Direction.FORWARDS) {
|
||||||
val firstEvent = builtEvents.firstOrNull() ?: return true
|
val firstEvent = builtEvents.firstOrNull() ?: return true
|
||||||
firstEvent.displayIndex < eventEntity.displayIndex
|
firstEvent.displayIndex < timelineEventEntity.root!!.displayIndex
|
||||||
} else {
|
} else {
|
||||||
val lastEvent = builtEvents.lastOrNull() ?: return true
|
val lastEvent = builtEvents.lastOrNull() ?: return true
|
||||||
lastEvent.displayIndex > eventEntity.displayIndex
|
lastEvent.displayIndex > timelineEventEntity.root!!.displayIndex
|
||||||
}
|
}
|
||||||
localRealm.close()
|
localRealm.close()
|
||||||
return hasMoreInCache
|
return hasMoreInCache
|
||||||
@ -288,7 +277,7 @@ internal class DefaultTimeline(
|
|||||||
currentChunk.isLastForward
|
currentChunk.isLastForward
|
||||||
} else {
|
} else {
|
||||||
val eventEntity = buildEventQuery(localRealm).findFirst(direction)
|
val eventEntity = buildEventQuery(localRealm).findFirst(direction)
|
||||||
currentChunk.isLastBackward || eventEntity?.type == EventType.STATE_ROOM_CREATE
|
currentChunk.isLastBackward || eventEntity?.root?.type == EventType.STATE_ROOM_CREATE
|
||||||
}
|
}
|
||||||
localRealm.close()
|
localRealm.close()
|
||||||
return hasReachedEnd
|
return hasReachedEnd
|
||||||
@ -325,10 +314,9 @@ internal class DefaultTimeline(
|
|||||||
val sendingEvents = ArrayList<TimelineEvent>()
|
val sendingEvents = ArrayList<TimelineEvent>()
|
||||||
if (hasReachedEnd(Timeline.Direction.FORWARDS)) {
|
if (hasReachedEnd(Timeline.Direction.FORWARDS)) {
|
||||||
roomEntity?.sendingTimelineEvents
|
roomEntity?.sendingTimelineEvents
|
||||||
?.filter { allowedTypes?.contains(it.type) ?: false }
|
?.filter { allowedTypes?.contains(it.root?.type) ?: false }
|
||||||
?.forEach {
|
?.forEach {
|
||||||
val timelineEvent = timelineEventFactory.create(it, it.realm)
|
sendingEvents.add(it.asDomain())
|
||||||
sendingEvents.add(timelineEvent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sendingEvents
|
return sendingEvents
|
||||||
@ -361,11 +349,11 @@ internal class DefaultTimeline(
|
|||||||
private fun handleInitialLoad() {
|
private fun handleInitialLoad() {
|
||||||
var shouldFetchInitialEvent = false
|
var shouldFetchInitialEvent = false
|
||||||
val initialDisplayIndex = if (isLive) {
|
val initialDisplayIndex = if (isLive) {
|
||||||
liveEvents.firstOrNull()?.displayIndex
|
liveEvents.firstOrNull()?.root?.displayIndex
|
||||||
} else {
|
} else {
|
||||||
val initialEvent = liveEvents.where().equalTo(EventEntityFields.EVENT_ID, initialEventId).findFirst()
|
val initialEvent = liveEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId).findFirst()
|
||||||
shouldFetchInitialEvent = initialEvent == null
|
shouldFetchInitialEvent = initialEvent == null
|
||||||
initialEvent?.displayIndex
|
initialEvent?.root?.displayIndex
|
||||||
} ?: DISPLAY_INDEX_UNKNOWN
|
} ?: DISPLAY_INDEX_UNKNOWN
|
||||||
|
|
||||||
prevDisplayIndex = initialDisplayIndex
|
prevDisplayIndex = initialDisplayIndex
|
||||||
@ -390,9 +378,9 @@ internal class DefaultTimeline(
|
|||||||
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
|
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
|
||||||
val token = getTokenLive(direction) ?: return
|
val token = getTokenLive(direction) ?: return
|
||||||
val params = PaginationTask.Params(roomId = roomId,
|
val params = PaginationTask.Params(roomId = roomId,
|
||||||
from = token,
|
from = token,
|
||||||
direction = direction.toPaginationDirection(),
|
direction = direction.toPaginationDirection(),
|
||||||
limit = limit)
|
limit = limit)
|
||||||
|
|
||||||
Timber.v("Should fetch $limit items $direction")
|
Timber.v("Should fetch $limit items $direction")
|
||||||
cancelableBag += paginationTask.configureWith(params)
|
cancelableBag += paginationTask.configureWith(params)
|
||||||
@ -448,14 +436,20 @@ internal class DefaultTimeline(
|
|||||||
if (offsetResults.isEmpty()) {
|
if (offsetResults.isEmpty()) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
val offsetIndex = offsetResults.last()!!.displayIndex
|
val offsetIndex = offsetResults.last()!!.root!!.displayIndex
|
||||||
if (direction == Timeline.Direction.BACKWARDS) {
|
if (direction == Timeline.Direction.BACKWARDS) {
|
||||||
prevDisplayIndex = offsetIndex - 1
|
prevDisplayIndex = offsetIndex - 1
|
||||||
} else {
|
} else {
|
||||||
nextDisplayIndex = offsetIndex + 1
|
nextDisplayIndex = offsetIndex + 1
|
||||||
}
|
}
|
||||||
offsetResults.forEach { eventEntity ->
|
offsetResults.forEach { eventEntity ->
|
||||||
val timelineEvent = timelineEventFactory.create(eventEntity, eventEntity.realm)
|
val timelineEvent = eventEntity.asDomain()
|
||||||
|
|
||||||
|
if (timelineEvent.isEncrypted()
|
||||||
|
&& timelineEvent.root.mxDecryptionResult == null) {
|
||||||
|
timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) }
|
||||||
|
}
|
||||||
|
|
||||||
val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size
|
val position = if (direction == Timeline.Direction.FORWARDS) 0 else builtEvents.size
|
||||||
builtEvents.add(position, timelineEvent)
|
builtEvents.add(position, timelineEvent)
|
||||||
//Need to shift :/
|
//Need to shift :/
|
||||||
@ -472,16 +466,16 @@ internal class DefaultTimeline(
|
|||||||
*/
|
*/
|
||||||
private fun getOffsetResults(startDisplayIndex: Int,
|
private fun getOffsetResults(startDisplayIndex: Int,
|
||||||
direction: Timeline.Direction,
|
direction: Timeline.Direction,
|
||||||
count: Long): RealmResults<EventEntity> {
|
count: Long): RealmResults<TimelineEventEntity> {
|
||||||
val offsetQuery = liveEvents.where()
|
val offsetQuery = liveEvents.where()
|
||||||
if (direction == Timeline.Direction.BACKWARDS) {
|
if (direction == Timeline.Direction.BACKWARDS) {
|
||||||
offsetQuery
|
offsetQuery
|
||||||
.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
.lessThanOrEqualTo(EventEntityFields.DISPLAY_INDEX, startDisplayIndex)
|
.lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex)
|
||||||
} else {
|
} else {
|
||||||
offsetQuery
|
offsetQuery
|
||||||
.sort(EventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
|
||||||
.greaterThanOrEqualTo(EventEntityFields.DISPLAY_INDEX, startDisplayIndex)
|
.greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex)
|
||||||
}
|
}
|
||||||
return offsetQuery
|
return offsetQuery
|
||||||
.filterAllowedTypes()
|
.filterAllowedTypes()
|
||||||
@ -490,15 +484,15 @@ internal class DefaultTimeline(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun buildEventQuery(realm: Realm): RealmQuery<EventEntity> {
|
private fun buildEventQuery(realm: Realm): RealmQuery<TimelineEventEntity> {
|
||||||
return if (initialEventId == null) {
|
return if (initialEventId == null) {
|
||||||
EventEntity
|
TimelineEventEntity
|
||||||
.where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.LINKED_ONLY)
|
.where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.LINKED_ONLY)
|
||||||
.equalTo("${EventEntityFields.CHUNK}.${ChunkEntityFields.IS_LAST_FORWARD}", true)
|
.equalTo("${TimelineEventEntityFields.CHUNK}.${ChunkEntityFields.IS_LAST_FORWARD}", true)
|
||||||
} else {
|
} else {
|
||||||
EventEntity
|
TimelineEventEntity
|
||||||
.where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.BOTH)
|
.where(realm, roomId = roomId, linkFilterMode = EventEntity.LinkFilterMode.BOTH)
|
||||||
.`in`("${EventEntityFields.CHUNK}.${ChunkEntityFields.EVENTS.EVENT_ID}", arrayOf(initialEventId))
|
.`in`("${TimelineEventEntityFields.CHUNK}.${ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID}", arrayOf(initialEventId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -514,7 +508,7 @@ internal class DefaultTimeline(
|
|||||||
realm.executeTransaction {
|
realm.executeTransaction {
|
||||||
val unlinkedChunks = ChunkEntity
|
val unlinkedChunks = ChunkEntity
|
||||||
.where(it, roomId = roomId)
|
.where(it, roomId = roomId)
|
||||||
.equalTo(ChunkEntityFields.EVENTS.IS_UNLINKED, true)
|
.equalTo("${ChunkEntityFields.TIMELINE_EVENTS.ROOT}.${EventEntityFields.IS_UNLINKED}", true)
|
||||||
.findAll()
|
.findAll()
|
||||||
unlinkedChunks.deleteAllFromRealm()
|
unlinkedChunks.deleteAllFromRealm()
|
||||||
}
|
}
|
||||||
@ -537,19 +531,19 @@ internal class DefaultTimeline(
|
|||||||
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
|
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RealmQuery<EventEntity>.findFirst(direction: Timeline.Direction): EventEntity? {
|
private fun RealmQuery<TimelineEventEntity>.findFirst(direction: Timeline.Direction): TimelineEventEntity? {
|
||||||
return if (direction == Timeline.Direction.FORWARDS) {
|
return if (direction == Timeline.Direction.FORWARDS) {
|
||||||
sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
} else {
|
} else {
|
||||||
sort(EventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
|
||||||
}
|
}
|
||||||
.filterAllowedTypes()
|
.filterAllowedTypes()
|
||||||
.findFirst()
|
.findFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RealmQuery<EventEntity>.filterAllowedTypes(): RealmQuery<EventEntity> {
|
private fun RealmQuery<TimelineEventEntity>.filterAllowedTypes(): RealmQuery<TimelineEventEntity> {
|
||||||
if (allowedTypes != null) {
|
if (allowedTypes != null) {
|
||||||
`in`(EventEntityFields.TYPE, allowedTypes.toTypedArray())
|
`in`(TimelineEventEntityFields.ROOT.TYPE, allowedTypes.toTypedArray())
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -17,15 +17,15 @@
|
|||||||
package im.vector.matrix.android.internal.session.room.timeline
|
package im.vector.matrix.android.internal.session.room.timeline
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MediatorLiveData
|
import androidx.lifecycle.Transformations
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
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.room.timeline.Timeline
|
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.TimelineEvent
|
||||||
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.internal.database.RealmLiveData
|
import im.vector.matrix.android.internal.database.RealmLiveData
|
||||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.util.fetchCopyMap
|
import im.vector.matrix.android.internal.util.fetchCopyMap
|
||||||
@ -34,7 +34,6 @@ import javax.inject.Inject
|
|||||||
internal class DefaultTimelineService @Inject constructor(private val roomId: String,
|
internal class DefaultTimelineService @Inject constructor(private val roomId: String,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val timelineEventFactory: CacheableTimelineEventFactory,
|
|
||||||
private val contextOfEventTask: GetContextOfEventTask,
|
private val contextOfEventTask: GetContextOfEventTask,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val paginationTask: PaginationTask
|
private val paginationTask: PaginationTask
|
||||||
@ -42,46 +41,31 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St
|
|||||||
|
|
||||||
override fun createTimeline(eventId: String?, allowedTypes: List<String>?): Timeline {
|
override fun createTimeline(eventId: String?, allowedTypes: List<String>?): Timeline {
|
||||||
return DefaultTimeline(roomId,
|
return DefaultTimeline(roomId,
|
||||||
eventId,
|
eventId,
|
||||||
monarchy.realmConfiguration,
|
monarchy.realmConfiguration,
|
||||||
taskExecutor,
|
taskExecutor,
|
||||||
contextOfEventTask,
|
contextOfEventTask,
|
||||||
timelineEventFactory,
|
paginationTask,
|
||||||
paginationTask,
|
cryptoService,
|
||||||
cryptoService,
|
allowedTypes)
|
||||||
allowedTypes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTimeLineEvent(eventId: String): TimelineEvent? {
|
override fun getTimeLineEvent(eventId: String): TimelineEvent? {
|
||||||
return monarchy
|
return monarchy
|
||||||
.fetchCopyMap({
|
.fetchCopyMap({
|
||||||
EventEntity.where(it, eventId = eventId).findFirst()
|
TimelineEventEntity.where(it, eventId = eventId).findFirst()
|
||||||
}, { entity, realm ->
|
}, { entity, realm ->
|
||||||
timelineEventFactory.create(entity, realm)
|
entity.asDomain()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun liveTimeLineEvent(eventId: String): LiveData<TimelineEvent> {
|
override fun liveTimeLineEvent(eventId: String): LiveData<TimelineEvent> {
|
||||||
val liveEventEntity = RealmLiveData(monarchy.realmConfiguration) { realm ->
|
val liveData = RealmLiveData(monarchy.realmConfiguration) {
|
||||||
EventEntity.where(realm, eventId = eventId)
|
TimelineEventEntity.where(it, eventId = eventId)
|
||||||
}
|
}
|
||||||
val liveAnnotationsEntity = RealmLiveData(monarchy.realmConfiguration) { realm ->
|
return Transformations.map(liveData) {
|
||||||
EventAnnotationsSummaryEntity.where(realm, eventId = eventId)
|
it.firstOrNull()?.asDomain()
|
||||||
}
|
}
|
||||||
val result = MediatorLiveData<TimelineEvent>()
|
|
||||||
result.addSource(liveEventEntity) { realmResults ->
|
|
||||||
result.value = realmResults.firstOrNull()?.let { timelineEventFactory.create(it, it.realm) }
|
|
||||||
}
|
|
||||||
|
|
||||||
result.addSource(liveAnnotationsEntity) {
|
|
||||||
liveEventEntity.value?.let {
|
|
||||||
result.value = liveEventEntity.value?.let { realmResults ->
|
|
||||||
//recreate the timeline event
|
|
||||||
realmResults.firstOrNull()?.let { timelineEventFactory.create(it, it.realm) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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.session.room.timeline
|
||||||
|
|
||||||
|
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.events.model.toModel
|
||||||
|
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
||||||
|
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
|
||||||
|
internal class TimelineEventDecryptor(
|
||||||
|
private val realmConfiguration: RealmConfiguration,
|
||||||
|
private val timelineId: String,
|
||||||
|
private val cryptoService: CryptoService
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val newSessionListener = object : NewSessionListener {
|
||||||
|
override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
|
||||||
|
synchronized(unknownSessionsFailure) {
|
||||||
|
val toDecryptAgain = ArrayList<String>()
|
||||||
|
unknownSessionsFailure[sessionId]?.let { eventIds ->
|
||||||
|
toDecryptAgain.addAll(eventIds)
|
||||||
|
}
|
||||||
|
if (toDecryptAgain.isNotEmpty()) {
|
||||||
|
unknownSessionsFailure[sessionId]?.clear()
|
||||||
|
toDecryptAgain.forEach {
|
||||||
|
requestDecryption(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private val executor = Executors.newSingleThreadExecutor()
|
||||||
|
|
||||||
|
private val existingRequests = HashSet<String>()
|
||||||
|
private val unknownSessionsFailure = HashMap<String, MutableList<String>>()
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
cryptoService.addNewSessionListener(newSessionListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destroy() {
|
||||||
|
cryptoService.removeSessionListener(newSessionListener)
|
||||||
|
executor.shutdownNow()
|
||||||
|
unknownSessionsFailure.clear()
|
||||||
|
existingRequests.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestDecryption(eventId: String) {
|
||||||
|
synchronized(existingRequests) {
|
||||||
|
if (existingRequests.contains(eventId)) {
|
||||||
|
return Unit.also {
|
||||||
|
Timber.d("Skip Decryption request for event ${eventId}, already requested")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
existingRequests.add(eventId)
|
||||||
|
}
|
||||||
|
synchronized(unknownSessionsFailure) {
|
||||||
|
unknownSessionsFailure.values.forEach {
|
||||||
|
if (it.contains(eventId)) return@synchronized Unit.also {
|
||||||
|
Timber.d("Skip Decryption request for event ${eventId}, unknown session")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
executor.execute {
|
||||||
|
Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
|
realm.executeTransaction {
|
||||||
|
processDecryptRequest(eventId, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processDecryptRequest(eventId: String, realm: Realm) {
|
||||||
|
Timber.v("Decryption request for event ${eventId}")
|
||||||
|
val eventEntity = EventEntity.where(realm, eventId = eventId).findFirst()
|
||||||
|
?: return Unit.also {
|
||||||
|
Timber.d("Decryption request for unknown message")
|
||||||
|
}
|
||||||
|
val event = eventEntity.asDomain()
|
||||||
|
try {
|
||||||
|
val result = cryptoService.decryptEvent(event, timelineId)
|
||||||
|
Timber.v("Successfully decrypted event ${eventId}")
|
||||||
|
eventEntity.setDecryptionResult(result)
|
||||||
|
} catch (e: MXCryptoError) {
|
||||||
|
Timber.v("Failed to decrypte event ${eventId} ${e}")
|
||||||
|
if (e is MXCryptoError.Base && e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
||||||
|
//Keep track of unknown sessions to automatically try to decrypt on new session
|
||||||
|
eventEntity.decryptionErrorCode = e.errorType.name
|
||||||
|
event.content?.toModel<EncryptedEventContent>()?.let { content ->
|
||||||
|
content.sessionId?.let { sessionId ->
|
||||||
|
synchronized(unknownSessionsFailure) {
|
||||||
|
val list = unknownSessionsFailure[sessionId]
|
||||||
|
?: ArrayList<String>().also {
|
||||||
|
unknownSessionsFailure[sessionId] = it
|
||||||
|
}
|
||||||
|
list.add(eventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Timber.e(t, "Failed to decrypt event $eventId")
|
||||||
|
} finally {
|
||||||
|
synchronized(existingRequests) {
|
||||||
|
existingRequests.remove(eventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,162 +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.session.room.timeline
|
|
||||||
|
|
||||||
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.events.model.Event
|
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
|
||||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
|
||||||
import im.vector.matrix.android.internal.session.room.EventRelationExtractor
|
|
||||||
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
|
|
||||||
import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor
|
|
||||||
import io.realm.Realm
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal interface TimelineEventFactory {
|
|
||||||
fun create(eventEntity: EventEntity, realm: Realm): TimelineEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
internal interface CacheableTimelineEventFactory : TimelineEventFactory {
|
|
||||||
fun clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is responsible for building [TimelineEvent] returned by a [Timeline] through [TimelineService]
|
|
||||||
* It handles decryption, extracting additional data around an event as sender data and relation.
|
|
||||||
*/
|
|
||||||
internal class SimpleTimelineEventFactory @Inject constructor(private val roomMemberExtractor: SenderRoomMemberExtractor,
|
|
||||||
private val relationExtractor: EventRelationExtractor,
|
|
||||||
private val cryptoService: CryptoService
|
|
||||||
) : TimelineEventFactory {
|
|
||||||
|
|
||||||
override fun create(eventEntity: EventEntity, realm: Realm): TimelineEvent {
|
|
||||||
val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity, realm)
|
|
||||||
val relations = relationExtractor.extractFrom(eventEntity, realm)
|
|
||||||
|
|
||||||
val event = eventEntity.asDomain()
|
|
||||||
if (event.getClearType() == EventType.ENCRYPTED) {
|
|
||||||
handleEncryptedEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
val isUniqueDisplayName = RoomMembers(realm, eventEntity.roomId).isUniqueDisplayName(senderRoomMember?.displayName)
|
|
||||||
|
|
||||||
return TimelineEvent(
|
|
||||||
event,
|
|
||||||
eventEntity.localId,
|
|
||||||
eventEntity.displayIndex,
|
|
||||||
senderRoomMember?.displayName,
|
|
||||||
isUniqueDisplayName,
|
|
||||||
senderRoomMember?.avatarUrl,
|
|
||||||
eventEntity.sendState,
|
|
||||||
event.mClearEvent != null,
|
|
||||||
relations
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleEncryptedEvent(event: Event) {
|
|
||||||
Timber.v("Encrypted event: try to decrypt ${event.eventId}")
|
|
||||||
try {
|
|
||||||
val result = cryptoService.decryptEvent(event, UUID.randomUUID().toString())
|
|
||||||
event.setClearData(result)
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
Timber.e("Encrypted event: decryption failed")
|
|
||||||
if (failure is MXCryptoError) {
|
|
||||||
event.setCryptoError(failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class InMemoryTimelineEventFactory @Inject constructor(private val roomMemberExtractor: SenderRoomMemberExtractor,
|
|
||||||
private val relationExtractor: EventRelationExtractor,
|
|
||||||
private val cryptoService: CryptoService) : CacheableTimelineEventFactory {
|
|
||||||
|
|
||||||
private val timelineId = UUID.randomUUID().toString()
|
|
||||||
private val senderCache = mutableMapOf<String, SenderData>()
|
|
||||||
private val decryptionCache = mutableMapOf<String, MXEventDecryptionResult>()
|
|
||||||
|
|
||||||
override fun create(eventEntity: EventEntity, realm: Realm): TimelineEvent {
|
|
||||||
val sender = eventEntity.sender
|
|
||||||
val cacheKey = sender + eventEntity.localId
|
|
||||||
val senderData = senderCache.getOrPut(cacheKey) {
|
|
||||||
val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity, realm)
|
|
||||||
val isUniqueDisplayName = RoomMembers(realm, eventEntity.roomId).isUniqueDisplayName(senderRoomMember?.displayName)
|
|
||||||
|
|
||||||
SenderData(senderRoomMember?.displayName,
|
|
||||||
isUniqueDisplayName,
|
|
||||||
senderRoomMember?.avatarUrl)
|
|
||||||
}
|
|
||||||
val event = eventEntity.asDomain()
|
|
||||||
if (event.getClearType() == EventType.ENCRYPTED && !event.isRedacted()) {
|
|
||||||
handleEncryptedEvent(event, eventEntity.localId)
|
|
||||||
}
|
|
||||||
|
|
||||||
val relations = relationExtractor.extractFrom(eventEntity, realm)
|
|
||||||
return TimelineEvent(
|
|
||||||
event,
|
|
||||||
eventEntity.localId,
|
|
||||||
eventEntity.displayIndex,
|
|
||||||
senderData.senderName,
|
|
||||||
senderData.isUniqueDisplayName,
|
|
||||||
senderData.senderAvatar,
|
|
||||||
eventEntity.sendState,
|
|
||||||
event.mClearEvent != null,
|
|
||||||
relations
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleEncryptedEvent(event: Event, cacheKey: String) {
|
|
||||||
Timber.v("Encrypted event: try to decrypt ${event.eventId}")
|
|
||||||
val cachedDecryption = decryptionCache[cacheKey]
|
|
||||||
if (cachedDecryption != null) {
|
|
||||||
Timber.v("Encrypted event ${event.eventId} cached")
|
|
||||||
event.setClearData(cachedDecryption)
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
val result = cryptoService.decryptEvent(event, timelineId)
|
|
||||||
decryptionCache[cacheKey] = result
|
|
||||||
event.setClearData(result)
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
Timber.e("Encrypted event: decryption failed: $failure")
|
|
||||||
if (failure is MXCryptoError) {
|
|
||||||
event.setCryptoError(failure)
|
|
||||||
} else {
|
|
||||||
// Other error
|
|
||||||
Timber.e("Other error, should be handled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun clear() {
|
|
||||||
senderCache.clear()
|
|
||||||
decryptionCache.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class SenderData(
|
|
||||||
val senderName: String?,
|
|
||||||
val isUniqueDisplayName: Boolean,
|
|
||||||
val senderAvatar: String?
|
|
||||||
)
|
|
||||||
}
|
|
@ -128,6 +128,12 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
|
|||||||
nextToken = receivedChunk.start
|
nextToken = receivedChunk.start
|
||||||
prevToken = receivedChunk.end
|
prevToken = receivedChunk.end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ChunkEntity.find(realm, roomId, nextToken = nextToken) != null || ChunkEntity.find(realm, roomId, prevToken = prevToken) != null) {
|
||||||
|
Timber.v("Already inserted - SKIP")
|
||||||
|
return@tryTransactionSync
|
||||||
|
}
|
||||||
|
|
||||||
val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken)
|
val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken)
|
||||||
val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken)
|
val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken)
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ 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.MessageContent
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoManager
|
import im.vector.matrix.android.internal.crypto.CryptoManager
|
||||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||||
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
|
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
|
||||||
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
|
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
|
||||||
import im.vector.matrix.android.internal.session.sync.model.ToDeviceSyncResponse
|
import im.vector.matrix.android.internal.session.sync.model.ToDeviceSyncResponse
|
||||||
@ -39,7 +40,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager:
|
|||||||
// Decrypt event if necessary
|
// Decrypt event if necessary
|
||||||
decryptEvent(event, null)
|
decryptEvent(event, null)
|
||||||
if (TextUtils.equals(event.getClearType(), EventType.MESSAGE)
|
if (TextUtils.equals(event.getClearType(), EventType.MESSAGE)
|
||||||
&& event.mClearEvent?.content?.toModel<MessageContent>()?.type == "m.bad.encrypted") {
|
&& event.getClearContent()?.toModel<MessageContent>()?.type == "m.bad.encrypted") {
|
||||||
Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content)
|
Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content)
|
||||||
} else {
|
} else {
|
||||||
sasVerificationService.onToDeviceEvent(event)
|
sasVerificationService.onToDeviceEvent(event)
|
||||||
@ -66,11 +67,22 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager:
|
|||||||
try {
|
try {
|
||||||
result = cryptoManager.decryptEvent(event, timelineId ?: "")
|
result = cryptoManager.decryptEvent(event, timelineId ?: "")
|
||||||
} catch (exception: MXCryptoError) {
|
} catch (exception: MXCryptoError) {
|
||||||
event.setCryptoError(exception)
|
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType //setCryptoError(exception.cryptoError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null != result) {
|
if (null != result) {
|
||||||
event.setClearData(result)
|
// event.mxDecryptionResult = MXDecryptionResult(
|
||||||
|
// payload = result.clearEvent,
|
||||||
|
// keysClaimed = map
|
||||||
|
// )
|
||||||
|
//TODO persist that?
|
||||||
|
event.mxDecryptionResult = OlmDecryptionResult(
|
||||||
|
payload = result.clearEvent,
|
||||||
|
senderKey = result.senderCurve25519Key,
|
||||||
|
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||||
|
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||||
|
)
|
||||||
|
// event.setClearData(result)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
|||||||
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntity
|
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.EventEntityFields
|
||||||
|
import im.vector.matrix.android.internal.database.query.types
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
@ -38,7 +39,7 @@ internal class UserEntityUpdater @Inject constructor(@SessionDatabase realmConfi
|
|||||||
|
|
||||||
override val query = Monarchy.Query<EventEntity> {
|
override val query = Monarchy.Query<EventEntity> {
|
||||||
EventEntity
|
EventEntity
|
||||||
.where(it, type = EventType.STATE_ROOM_MEMBER)
|
.types(it, listOf(EventType.STATE_ROOM_MEMBER))
|
||||||
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
|
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
|
||||||
.distinct(EventEntityFields.STATE_KEY)
|
.distinct(EventEntityFields.STATE_KEY)
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ class PushrulesConditionTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun liveRoomSummaries(fetchLastEvents: Boolean): LiveData<List<RoomSummary>> {
|
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
|
||||||
return MutableLiveData()
|
return MutableLiveData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,11 +194,11 @@ class PushrulesConditionTest {
|
|||||||
return _numberOfJoinedMembers
|
return _numberOfJoinedMembers
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun liveRoomSummary(fetchLastEvent: Boolean): LiveData<RoomSummary> {
|
override fun liveRoomSummary(): LiveData<RoomSummary> {
|
||||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun roomSummary(fetchLastEvent: Boolean): RoomSummary? {
|
override fun roomSummary(): RoomSummary? {
|
||||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ class HomeActivityViewModel @AssistedInject constructor(@Assisted initialState:
|
|||||||
private fun observeRoomAndGroup() {
|
private fun observeRoomAndGroup() {
|
||||||
Observable
|
Observable
|
||||||
.combineLatest<List<RoomSummary>, Option<GroupSummary>, List<RoomSummary>>(
|
.combineLatest<List<RoomSummary>, Option<GroupSummary>, List<RoomSummary>>(
|
||||||
session.rx().liveRoomSummaries(fetchLastEvents = true).throttleLast(300, TimeUnit.MILLISECONDS),
|
session.rx().liveRoomSummaries().throttleLast(300, TimeUnit.MILLISECONDS),
|
||||||
selectedGroupStore.observe(),
|
selectedGroupStore.observe(),
|
||||||
BiFunction { rooms, selectedGroupOption ->
|
BiFunction { rooms, selectedGroupOption ->
|
||||||
val selectedGroup = selectedGroupOption.orNull()
|
val selectedGroup = selectedGroupOption.orNull()
|
||||||
|
@ -212,6 +212,7 @@ class HomeDetailFragment : VectorBaseFragment(), KeysBackupBanner.Delegate {
|
|||||||
is SyncState.RUNNING -> if (it.syncState.catchingUp) View.VISIBLE else View.GONE
|
is SyncState.RUNNING -> if (it.syncState.catchingUp) View.VISIBLE else View.GONE
|
||||||
else -> View.GONE
|
else -> View.GONE
|
||||||
}
|
}
|
||||||
|
syncProgressBarWrap.visibility = syncProgressBar.visibility
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -500,17 +500,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomSummary() {
|
private fun observeRoomSummary() {
|
||||||
room.rx().liveRoomSummary(false)
|
room.rx().liveRoomSummary()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.flatMap {
|
|
||||||
if (it.membership != Membership.INVITE || it.latestEvent != null) {
|
|
||||||
// Not an invitation, or already fetching last event
|
|
||||||
Observable.just(it)
|
|
||||||
} else {
|
|
||||||
// We need the last event
|
|
||||||
room.rx().liveRoomSummary(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.execute { async ->
|
.execute { async ->
|
||||||
copy(
|
copy(
|
||||||
asyncRoomSummary = async,
|
asyncRoomSummary = async,
|
||||||
|
@ -27,6 +27,8 @@ import im.vector.matrix.android.api.session.room.model.message.MessageImageConte
|
|||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
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.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.matrix.rx.RxRoom
|
import im.vector.matrix.rx.RxRoom
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
@ -174,11 +176,9 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
|||||||
//TODO sent by me or sufficient power level
|
//TODO sent by me or sufficient power level
|
||||||
}
|
}
|
||||||
|
|
||||||
this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, JSONObject(event.root.toContent()).toString(4)))
|
this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source,event.root.toContentStringWithIndent()))
|
||||||
if (event.isEncrypted()) {
|
if (event.isEncrypted()) {
|
||||||
val decryptedContent = event.root.mClearEvent.toContent()?.let {
|
val decryptedContent = event.root.toClearContentStringWithIndent() ?: stringProvider.getString(R.string.encryption_information_decryption_error)
|
||||||
JSONObject(it).toString(4)
|
|
||||||
} ?: stringProvider.getString(R.string.encryption_information_decryption_error)
|
|
||||||
this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, decryptedContent))
|
this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, decryptedContent))
|
||||||
}
|
}
|
||||||
this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, event.root.eventId))
|
this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, event.root.eventId))
|
||||||
|
@ -48,19 +48,15 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
|
|||||||
EventType.ENCRYPTED == event.root.getClearType() -> {
|
EventType.ENCRYPTED == event.root.getClearType() -> {
|
||||||
val cryptoError = event.root.mCryptoError
|
val cryptoError = event.root.mCryptoError
|
||||||
val errorDescription =
|
val errorDescription =
|
||||||
if (cryptoError is MXCryptoError.Base) {
|
if (cryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
||||||
if (cryptoError.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id)
|
||||||
stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id)
|
|
||||||
} else {
|
|
||||||
// TODO i18n
|
|
||||||
cryptoError.technicalMessage
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Cannot happen (for now)
|
// TODO i18n
|
||||||
"Other error"
|
cryptoError?.name
|
||||||
}
|
}
|
||||||
|
|
||||||
val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
|
val message = stringProvider.getString(R.string.encrypted_message).takeIf { cryptoError == null }
|
||||||
|
?: stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
|
||||||
val spannableStr = span(message) {
|
val spannableStr = span(message) {
|
||||||
textStyle = "italic"
|
textStyle = "italic"
|
||||||
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
|
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
|
||||||
|
@ -93,7 +93,10 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
|
|||||||
val date = latestEvent.root.localDateTime()
|
val date = latestEvent.root.localDateTime()
|
||||||
val currentDate = DateProvider.currentLocalDateTime()
|
val currentDate = DateProvider.currentLocalDateTime()
|
||||||
val isSameDay = date.toLocalDate() == currentDate.toLocalDate()
|
val isSameDay = date.toLocalDate() == currentDate.toLocalDate()
|
||||||
latestFormattedEvent = if (latestEvent.root.getClearType() == EventType.MESSAGE) {
|
latestFormattedEvent = if (latestEvent.root.isEncrypted()
|
||||||
|
&& latestEvent.root.mxDecryptionResult == null) {
|
||||||
|
stringProvider.getString(R.string.encrypted_message)
|
||||||
|
} else if (latestEvent.root.getClearType() == EventType.MESSAGE) {
|
||||||
val senderName = latestEvent.senderName() ?: latestEvent.root.senderId
|
val senderName = latestEvent.senderName() ?: latestEvent.root.senderId
|
||||||
val content = latestEvent.root.getClearContent()?.toModel<MessageContent>()
|
val content = latestEvent.root.getClearContent()?.toModel<MessageContent>()
|
||||||
val message = content?.body ?: ""
|
val message = content?.body ?: ""
|
||||||
|
@ -18,6 +18,8 @@ package im.vector.riotx.features.notifications
|
|||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
|
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.events.model.Event
|
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.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
@ -25,11 +27,13 @@ import im.vector.matrix.android.api.session.room.model.Membership
|
|||||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
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.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.riotx.BuildConfig
|
import im.vector.riotx.BuildConfig
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
|
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,7 +51,6 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
|
|||||||
val roomID = event.roomId ?: return null
|
val roomID = event.roomId ?: return null
|
||||||
val eventId = event.eventId ?: return null
|
val eventId = event.eventId ?: return null
|
||||||
val timelineEvent = session.getRoom(roomID)?.getTimeLineEvent(eventId) ?: return null
|
val timelineEvent = session.getRoom(roomID)?.getTimeLineEvent(eventId) ?: return null
|
||||||
|
|
||||||
when (event.getClearType()) {
|
when (event.getClearType()) {
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
return resolveMessageEvent(timelineEvent, session)
|
return resolveMessageEvent(timelineEvent, session)
|
||||||
@ -110,6 +113,22 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
|
|||||||
notifiableEvent.matrixID = session.sessionParams.credentials.userId
|
notifiableEvent.matrixID = session.sessionParams.credentials.userId
|
||||||
return notifiableEvent
|
return notifiableEvent
|
||||||
} else {
|
} else {
|
||||||
|
if (event.root.isEncrypted() && event.root.mxDecryptionResult == null) {
|
||||||
|
//TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
||||||
|
//for now decrypt sync
|
||||||
|
try {
|
||||||
|
val result = session.decryptEvent(event.root, event.root.roomId + UUID.randomUUID().toString())
|
||||||
|
event.root.mxDecryptionResult = OlmDecryptionResult(
|
||||||
|
payload = result.clearEvent,
|
||||||
|
senderKey = result.senderCurve25519Key,
|
||||||
|
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||||
|
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||||
|
)
|
||||||
|
} catch (e: MXCryptoError) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val body = event.annotations?.editSummary?.aggregatedContent?.toModel<MessageContent>()?.body
|
val body = event.annotations?.editSummary?.aggregatedContent?.toModel<MessageContent>()?.body
|
||||||
?: event.root.getClearContent().toModel<MessageContent>()?.body
|
?: event.root.getClearContent().toModel<MessageContent>()?.body
|
||||||
?: stringProvider.getString(R.string.notification_unknown_new_event)
|
?: stringProvider.getString(R.string.notification_unknown_new_event)
|
||||||
|
@ -86,7 +86,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState:
|
|||||||
private fun observeJoinedRooms() {
|
private fun observeJoinedRooms() {
|
||||||
session
|
session
|
||||||
.rx()
|
.rx()
|
||||||
.liveRoomSummaries(fetchLastEvents = false)
|
.liveRoomSummaries()
|
||||||
.subscribe { list ->
|
.subscribe { list ->
|
||||||
val joinedRoomIds = list
|
val joinedRoomIds = list
|
||||||
// Keep only joined room
|
// Keep only joined room
|
||||||
|
@ -54,7 +54,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted initialState: R
|
|||||||
private fun observeJoinedRooms() {
|
private fun observeJoinedRooms() {
|
||||||
session
|
session
|
||||||
.rx()
|
.rx()
|
||||||
.liveRoomSummaries(fetchLastEvents = false)
|
.liveRoomSummaries()
|
||||||
.subscribe { list ->
|
.subscribe { list ->
|
||||||
withState { state ->
|
withState { state ->
|
||||||
val isRoomJoined = list
|
val isRoomJoined = list
|
||||||
|
@ -44,18 +44,29 @@
|
|||||||
|
|
||||||
</androidx.appcompat.widget.Toolbar>
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
<ProgressBar
|
<!-- Trick to remove surrounding padding (clip frome wrapping frame) -->
|
||||||
android:id="@+id/syncProgressBar"
|
<FrameLayout
|
||||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
android:id="@+id/syncProgressBarWrap"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="3dp"
|
||||||
android:background="?riotx_header_panel_background"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/groupToolbar"
|
app:layout_constraintTop_toBottomOf="@id/groupToolbar"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/syncProgressBar"
|
||||||
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="14dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="?riotx_header_panel_background"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
||||||
<im.vector.riotx.core.ui.views.KeysBackupBanner
|
<im.vector.riotx.core.ui.views.KeysBackupBanner
|
||||||
android:id="@+id/homeKeysBackupBanner"
|
android:id="@+id/homeKeysBackupBanner"
|
||||||
@ -65,7 +76,7 @@
|
|||||||
android:minHeight="67dp"
|
android:minHeight="67dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/syncProgressBar" />
|
app:layout_constraintTop_toBottomOf="@id/syncProgressBarWrap" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/roomListContainer"
|
android:id="@+id/roomListContainer"
|
||||||
|
Loading…
Reference in New Issue
Block a user