forked from GitHub-Mirror/riotX-android
Merge branch 'develop' into feature/crypto
This commit is contained in:
@ -48,7 +48,7 @@ android {
|
||||
// TODO Set to false
|
||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
||||
// Set to BODY instead of NONE to enable logging
|
||||
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.HEADERS"
|
||||
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
|
||||
}
|
||||
|
||||
release {
|
||||
|
@ -15,7 +15,9 @@
|
||||
*/
|
||||
package im.vector.matrix.android.api.session.room.model.relation
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
|
||||
/**
|
||||
@ -91,4 +93,5 @@ interface RelationService {
|
||||
*/
|
||||
fun replyToMessage(eventReplied: Event, replyText: String): Cancelable?
|
||||
|
||||
fun getEventSummaryLive(eventId: String): LiveData<List<EventAnnotationsSummary>>
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
|
||||
package im.vector.matrix.android.internal.database.mapper
|
||||
|
||||
import com.squareup.moshi.JsonDataException
|
||||
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.internal.database.model.EventEntity
|
||||
@ -46,8 +47,16 @@ internal object EventMapper {
|
||||
|
||||
fun map(eventEntity: EventEntity): Event {
|
||||
//TODO proxy the event to only parse unsigned data when accessed?
|
||||
var ud = if (eventEntity.unsignedData.isNullOrBlank()) null
|
||||
else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(eventEntity.unsignedData)
|
||||
val ud = if (eventEntity.unsignedData.isNullOrBlank()) {
|
||||
null
|
||||
} else {
|
||||
try {
|
||||
MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(eventEntity.unsignedData)
|
||||
} catch (t: JsonDataException) {
|
||||
null
|
||||
}
|
||||
|
||||
}
|
||||
return Event(
|
||||
type = eventEntity.type,
|
||||
eventId = eventEntity.eventId,
|
||||
|
@ -19,12 +19,10 @@ package im.vector.matrix.android.internal.database.model
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import io.realm.RealmObject
|
||||
import io.realm.RealmResults
|
||||
import io.realm.annotations.Ignore
|
||||
import io.realm.annotations.Index
|
||||
import io.realm.annotations.LinkingObjects
|
||||
import io.realm.annotations.PrimaryKey
|
||||
import java.util.*
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(),
|
||||
@Index var eventId: String = "",
|
||||
@ -51,10 +49,14 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI
|
||||
|
||||
private var sendStateStr: String = SendState.UNKNOWN.name
|
||||
|
||||
@delegate:Ignore
|
||||
var sendState: SendState by Delegates.observable(SendState.valueOf(sendStateStr)) { _, _, newValue ->
|
||||
sendStateStr = newValue.name
|
||||
}
|
||||
var sendState: SendState
|
||||
get() {
|
||||
return SendState.valueOf(sendStateStr)
|
||||
}
|
||||
set(value) {
|
||||
sendStateStr = value.name
|
||||
}
|
||||
|
||||
|
||||
companion object
|
||||
|
||||
|
@ -27,7 +27,7 @@ import im.vector.matrix.android.internal.database.model.*
|
||||
import im.vector.matrix.android.internal.database.query.create
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import im.vector.matrix.android.internal.util.tryTransactionAsync
|
||||
import im.vector.matrix.android.internal.util.tryTransactionSync
|
||||
import io.realm.Realm
|
||||
import timber.log.Timber
|
||||
|
||||
@ -44,61 +44,79 @@ internal interface EventRelationsAggregationTask : Task<EventRelationsAggregatio
|
||||
*/
|
||||
internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarchy) : EventRelationsAggregationTask {
|
||||
|
||||
//OPT OUT serer aggregation until API mature enough
|
||||
private val SHOULD_HANDLE_SERVER_AGREGGATION = false
|
||||
|
||||
override suspend fun execute(params: EventRelationsAggregationTask.Params): Try<Unit> {
|
||||
return monarchy.tryTransactionAsync { realm ->
|
||||
update(realm, params.events, params.userId)
|
||||
val events = params.events
|
||||
val userId = params.userId
|
||||
return monarchy.tryTransactionSync { realm ->
|
||||
Timber.v(">>> DefaultEventRelationsAggregationTask[${params.hashCode()}] called with ${events.size} events")
|
||||
update(realm, events, userId)
|
||||
Timber.v("<<< DefaultEventRelationsAggregationTask[${params.hashCode()}] finished")
|
||||
}
|
||||
}
|
||||
|
||||
private fun update(realm: Realm, events: List<Pair<Event, SendState>>, userId: String) {
|
||||
events.forEach { pair ->
|
||||
val roomId = pair.first.roomId ?: return@forEach
|
||||
val event = pair.first
|
||||
val sendState = pair.second
|
||||
val isLocalEcho = sendState == SendState.UNSENT
|
||||
when (event.type) {
|
||||
EventType.REACTION -> {
|
||||
//we got a reaction!!
|
||||
Timber.v("###REACTION in room $roomId")
|
||||
handleReaction(event, roomId, realm, userId, isLocalEcho)
|
||||
try { //Temporary catch, should be removed
|
||||
val roomId = pair.first.roomId
|
||||
if (roomId == null) {
|
||||
Timber.w("Event has no room id ${pair.first.eventId}")
|
||||
return@forEach
|
||||
}
|
||||
EventType.MESSAGE -> {
|
||||
if (event.unsignedData?.relations?.annotations != null) {
|
||||
Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}")
|
||||
handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm)
|
||||
} else {
|
||||
val content: MessageContent? = event.content.toModel()
|
||||
if (content?.relatesTo?.type == RelationType.REPLACE) {
|
||||
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
|
||||
//A replace!
|
||||
handleReplace(realm, event, content, roomId, isLocalEcho)
|
||||
}
|
||||
val event = pair.first
|
||||
val sendState = pair.second
|
||||
val isLocalEcho = sendState == SendState.UNSENT
|
||||
when (event.type) {
|
||||
EventType.REACTION -> {
|
||||
//we got a reaction!!
|
||||
Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}")
|
||||
handleReaction(event, roomId, realm, userId, isLocalEcho)
|
||||
}
|
||||
|
||||
}
|
||||
EventType.REDACTION -> {
|
||||
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
|
||||
?: return
|
||||
when (eventToPrune.type) {
|
||||
EventType.MESSAGE -> {
|
||||
Timber.d("REDACTION for message ${eventToPrune.eventId}")
|
||||
val unsignedData = EventMapper.map(eventToPrune).unsignedData
|
||||
?: UnsignedData(null, null)
|
||||
|
||||
//was this event a m.replace
|
||||
val contentModel = ContentMapper.map(eventToPrune.content)?.toModel<MessageContent>()
|
||||
if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) {
|
||||
handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm)
|
||||
EventType.MESSAGE -> {
|
||||
if (event.unsignedData?.relations?.annotations != null) {
|
||||
Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}")
|
||||
handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm)
|
||||
} else {
|
||||
val content: MessageContent? = event.content.toModel()
|
||||
if (content?.relatesTo?.type == RelationType.REPLACE) {
|
||||
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
|
||||
//A replace!
|
||||
handleReplace(realm, event, content, roomId, isLocalEcho)
|
||||
}
|
||||
|
||||
}
|
||||
EventType.REACTION -> {
|
||||
handleReactionRedact(eventToPrune, realm, userId)
|
||||
|
||||
}
|
||||
EventType.REDACTION -> {
|
||||
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
|
||||
?: return@forEach
|
||||
when (eventToPrune.type) {
|
||||
EventType.MESSAGE -> {
|
||||
Timber.d("REDACTION for message ${eventToPrune.eventId}")
|
||||
val unsignedData = EventMapper.map(eventToPrune).unsignedData
|
||||
?: UnsignedData(null, null)
|
||||
|
||||
//was this event a m.replace
|
||||
val contentModel = ContentMapper.map(eventToPrune.content)?.toModel<MessageContent>()
|
||||
if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) {
|
||||
handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm)
|
||||
}
|
||||
|
||||
}
|
||||
EventType.REACTION -> {
|
||||
handleReactionRedact(eventToPrune, realm, userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> Timber.v("UnHandled event ${event.eventId}")
|
||||
}
|
||||
|
||||
} catch (t: Throwable) {
|
||||
Timber.e(t, "## Should not happen ")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun handleReplace(realm: Realm, event: Event, content: MessageContent, roomId: String, isLocalEcho: Boolean) {
|
||||
@ -108,7 +126,7 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
|
||||
//ok, this is a replace
|
||||
var existing = EventAnnotationsSummaryEntity.where(realm, targetEventId).findFirst()
|
||||
if (existing == null) {
|
||||
Timber.v("###REPLACE creating no relation summary for ${targetEventId}")
|
||||
Timber.v("###REPLACE creating new relation summary for ${targetEventId}")
|
||||
existing = EventAnnotationsSummaryEntity.create(realm, targetEventId)
|
||||
existing.roomId = roomId
|
||||
}
|
||||
@ -116,7 +134,7 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
|
||||
//we have it
|
||||
val existingSummary = existing.editSummary
|
||||
if (existingSummary == null) {
|
||||
Timber.v("###REPLACE no edit summary for ${targetEventId}, creating one (localEcho:$isLocalEcho)")
|
||||
Timber.v("###REPLACE new edit summary for ${targetEventId}, creating one (localEcho:$isLocalEcho)")
|
||||
//create the edit summary
|
||||
val editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java)
|
||||
editSummary.lastEditTs = event.originServerTs ?: System.currentTimeMillis()
|
||||
@ -155,82 +173,92 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
|
||||
}
|
||||
|
||||
private fun handleInitialAggregatedRelations(event: Event, roomId: String, aggregation: AggregatedAnnotation, realm: Realm) {
|
||||
aggregation.chunk?.forEach {
|
||||
if (it.type == EventType.REACTION) {
|
||||
val eventId = event.eventId ?: ""
|
||||
val existing = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
||||
if (existing == null) {
|
||||
val eventSummary = EventAnnotationsSummaryEntity.create(realm, eventId)
|
||||
eventSummary.roomId = roomId
|
||||
val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
|
||||
sum.key = it.key
|
||||
sum.firstTimestamp = event.originServerTs ?: 0 //TODO how to maintain order?
|
||||
sum.count = it.count
|
||||
eventSummary.reactionsSummary.add(sum)
|
||||
} else {
|
||||
//TODO how to handle that
|
||||
if (SHOULD_HANDLE_SERVER_AGREGGATION) {
|
||||
aggregation.chunk?.forEach {
|
||||
if (it.type == EventType.REACTION) {
|
||||
val eventId = event.eventId ?: ""
|
||||
val existing = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
||||
if (existing == null) {
|
||||
val eventSummary = EventAnnotationsSummaryEntity.create(realm, eventId)
|
||||
eventSummary.roomId = roomId
|
||||
val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
|
||||
sum.key = it.key
|
||||
sum.firstTimestamp = event.originServerTs ?: 0 //TODO how to maintain order?
|
||||
sum.count = it.count
|
||||
eventSummary.reactionsSummary.add(sum)
|
||||
} else {
|
||||
//TODO how to handle that
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleReaction(event: Event, roomId: String, realm: Realm, userId: String, isLocalEcho: Boolean) {
|
||||
event.content.toModel<ReactionContent>()?.let { content ->
|
||||
//rel_type must be m.annotation
|
||||
if (RelationType.ANNOTATION == content.relatesTo?.type) {
|
||||
val reaction = content.relatesTo.key
|
||||
val eventId = content.relatesTo.eventId
|
||||
val eventSummary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
||||
?: EventAnnotationsSummaryEntity.create(realm, eventId).apply { this.roomId = roomId }
|
||||
val content = event.content.toModel<ReactionContent>()
|
||||
if (content == null) {
|
||||
Timber.e("Malformed reaction content ${event.content}")
|
||||
return
|
||||
}
|
||||
//rel_type must be m.annotation
|
||||
if (RelationType.ANNOTATION == content.relatesTo?.type) {
|
||||
val reaction = content.relatesTo.key
|
||||
val relatedEventID = content.relatesTo.eventId
|
||||
val reactionEventId = event.eventId
|
||||
Timber.v("Reaction $reactionEventId relates to $relatedEventID")
|
||||
val eventSummary = EventAnnotationsSummaryEntity.where(realm, relatedEventID).findFirst()
|
||||
?: EventAnnotationsSummaryEntity.create(realm, relatedEventID).apply { this.roomId = roomId }
|
||||
|
||||
var sum = eventSummary.reactionsSummary.find { it.key == reaction }
|
||||
val txId = event.unsignedData?.transactionId
|
||||
if (isLocalEcho && txId.isNullOrBlank()) {
|
||||
Timber.w("Received a local echo with no transaction ID")
|
||||
}
|
||||
if (sum == null) {
|
||||
sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
|
||||
sum.key = reaction
|
||||
sum.firstTimestamp = event.originServerTs ?: 0
|
||||
if (isLocalEcho) {
|
||||
Timber.v("Adding local echo reaction $reaction")
|
||||
sum.sourceLocalEcho.add(txId)
|
||||
sum.count = 1
|
||||
} else {
|
||||
Timber.v("Adding synced reaction $reaction")
|
||||
sum.count = 1
|
||||
sum.sourceEvents.add(event.eventId)
|
||||
}
|
||||
sum.addedByMe = sum.addedByMe || (userId == event.sender)
|
||||
eventSummary.reactionsSummary.add(sum)
|
||||
var sum = eventSummary.reactionsSummary.find { it.key == reaction }
|
||||
val txId = event.unsignedData?.transactionId
|
||||
if (isLocalEcho && txId.isNullOrBlank()) {
|
||||
Timber.w("Received a local echo with no transaction ID")
|
||||
}
|
||||
if (sum == null) {
|
||||
sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
|
||||
sum.key = reaction
|
||||
sum.firstTimestamp = event.originServerTs ?: 0
|
||||
if (isLocalEcho) {
|
||||
Timber.v("Adding local echo reaction $reaction")
|
||||
sum.sourceLocalEcho.add(txId)
|
||||
sum.count = 1
|
||||
} else {
|
||||
//is this a known event (is possible? pagination?)
|
||||
if (!sum.sourceEvents.contains(eventId)) {
|
||||
Timber.v("Adding synced reaction $reaction")
|
||||
sum.count = 1
|
||||
sum.sourceEvents.add(reactionEventId)
|
||||
}
|
||||
sum.addedByMe = sum.addedByMe || (userId == event.sender)
|
||||
eventSummary.reactionsSummary.add(sum)
|
||||
} else {
|
||||
//is this a known event (is possible? pagination?)
|
||||
if (!sum.sourceEvents.contains(reactionEventId)) {
|
||||
|
||||
//check if it's not the sync of a local echo
|
||||
if (!isLocalEcho && sum.sourceLocalEcho.contains(txId)) {
|
||||
//ok it has already been counted, just sync the list, do not touch count
|
||||
Timber.v("Ignoring synced of local echo for reaction $reaction")
|
||||
sum.sourceLocalEcho.remove(txId)
|
||||
sum.sourceEvents.add(event.eventId)
|
||||
//check if it's not the sync of a local echo
|
||||
if (!isLocalEcho && sum.sourceLocalEcho.contains(txId)) {
|
||||
//ok it has already been counted, just sync the list, do not touch count
|
||||
Timber.v("Ignoring synced of local echo for reaction $reaction")
|
||||
sum.sourceLocalEcho.remove(txId)
|
||||
sum.sourceEvents.add(reactionEventId)
|
||||
} else {
|
||||
sum.count += 1
|
||||
if (isLocalEcho) {
|
||||
Timber.v("Adding local echo reaction $reaction")
|
||||
sum.sourceLocalEcho.add(txId)
|
||||
} else {
|
||||
sum.count += 1
|
||||
if (isLocalEcho) {
|
||||
Timber.v("Adding local echo reaction $reaction")
|
||||
sum.sourceLocalEcho.add(txId)
|
||||
} else {
|
||||
Timber.v("Adding synced reaction $reaction")
|
||||
sum.sourceEvents.add(event.eventId)
|
||||
}
|
||||
|
||||
sum.addedByMe = sum.addedByMe || (userId == event.sender)
|
||||
Timber.v("Adding synced reaction $reaction")
|
||||
sum.sourceEvents.add(reactionEventId)
|
||||
}
|
||||
|
||||
sum.addedByMe = sum.addedByMe || (userId == event.sender)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
Timber.e("Unknwon relation type ${content.relatesTo?.type} for event ${event.eventId}")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,11 +46,11 @@ internal class EventRelationsAggregationUpdater(monarchy: Monarchy,
|
||||
|
||||
override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
|
||||
Timber.v("EventRelationsAggregationUpdater called with ${inserted.size} insertions")
|
||||
val inserted = inserted
|
||||
.mapNotNull { it.asDomain() to it.sendState }
|
||||
val domainInserted = inserted
|
||||
.map { it.asDomain() to it.sendState }
|
||||
|
||||
val params = EventRelationsAggregationTask.Params(
|
||||
inserted,
|
||||
domainInserted,
|
||||
credentials.userId
|
||||
)
|
||||
|
||||
|
@ -15,15 +15,19 @@
|
||||
*/
|
||||
package im.vector.matrix.android.internal.session.room.relation
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.internal.database.helper.addSendingEvent
|
||||
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.EventAnnotationsSummaryEntity
|
||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
@ -169,6 +173,18 @@ internal class DefaultRelationService(private val roomId: String,
|
||||
return CancelableWork(workRequest.id)
|
||||
}
|
||||
|
||||
|
||||
override fun getEventSummaryLive(eventId: String): LiveData<List<EventAnnotationsSummary>> {
|
||||
return monarchy.findAllMappedWithChanges(
|
||||
{ realm ->
|
||||
EventAnnotationsSummaryEntity.where(realm, eventId)
|
||||
},
|
||||
{
|
||||
it.asDomain()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the event in database as a local echo.
|
||||
* SendState is set to UNSENT and it's added to a the sendingTimelineEvents list of the room.
|
||||
|
@ -38,7 +38,12 @@ internal class TaskExecutor(private val coroutineDispatchers: MatrixCoroutineDis
|
||||
task.execute(task.params)
|
||||
}
|
||||
}
|
||||
resultOrFailure.fold({ task.callback.onFailure(it) }, { task.callback.onSuccess(it) })
|
||||
resultOrFailure.fold({
|
||||
Timber.d(it, "Task failed")
|
||||
task.callback.onFailure(it)
|
||||
}, {
|
||||
task.callback.onSuccess(it)
|
||||
})
|
||||
}
|
||||
return CancelableCoroutine(job)
|
||||
}
|
||||
|
Reference in New Issue
Block a user