Use WorkManager for process we don't want to loose

This commit is contained in:
ganfra 2018-11-09 09:43:12 +01:00
parent a5bd5c8fb1
commit 0f34daf3b3
10 changed files with 221 additions and 72 deletions

View File

@ -39,10 +39,11 @@ internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val m
if (changeSet == null) { if (changeSet == null) {
return return
} }
val indexes = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions val updateIndexes = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions
process(changeSet.realmResults, indexes) val deletionIndexes = changeSet.orderedCollectionChangeSet.deletions
process(changeSet.realmResults, updateIndexes, deletionIndexes)
} }


abstract fun process(results: RealmResults<T>, indexes: IntArray) abstract fun process(results: RealmResults<T>, updateIndexes: IntArray, deletionIndexes: IntArray)


} }

View File

@ -1,9 +1,9 @@
package im.vector.matrix.android.internal.session package im.vector.matrix.android.internal.session


import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.session.events.prune.EventsPruner import im.vector.matrix.android.internal.session.events.prune.EventsPruner
import im.vector.matrix.android.internal.session.group.DefaultGroupService import im.vector.matrix.android.internal.session.group.DefaultGroupService
@ -66,7 +66,7 @@ internal class SessionModule(private val sessionParams: SessionParams) : Module


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
val roomSummaryUpdater = RoomSummaryUpdater(get(), get(), get(), get(), sessionParams.credentials) val roomSummaryUpdater = RoomSummaryUpdater(get(), get(), get(), get(), sessionParams.credentials)
val groupSummaryUpdater = GroupSummaryUpdater(get(), get()) val groupSummaryUpdater = GroupSummaryUpdater(get())
val eventsPruner = EventsPruner(get()) val eventsPruner = EventsPruner(get())
listOf<LiveEntityObserver>(roomSummaryUpdater, groupSummaryUpdater, eventsPruner) listOf<LiveEntityObserver>(roomSummaryUpdater, groupSummaryUpdater, eventsPruner)
} }

View File

@ -1,68 +1,35 @@
package im.vector.matrix.android.internal.session.events.prune package im.vector.matrix.android.internal.session.events.prune


import arrow.core.Option import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
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.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.mapper.asEntity
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.where import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm
import io.realm.RealmResults import io.realm.RealmResults


private const val PRUNE_EVENT_WORKER = "PRUNE_EVENT_WORKER"

internal class EventsPruner(monarchy: Monarchy) : internal class EventsPruner(monarchy: Monarchy) :
RealmLiveEntityObserver<EventEntity>(monarchy) { RealmLiveEntityObserver<EventEntity>(monarchy) {


override val query = Monarchy.Query<EventEntity> { EventEntity.where(it, type = EventType.REDACTION) } override val query = Monarchy.Query<EventEntity> { EventEntity.where(it, type = EventType.REDACTION) }


override fun process(results: RealmResults<EventEntity>, indexes: IntArray) { override fun process(results: RealmResults<EventEntity>, updateIndexes: IntArray, deletionIndexes: IntArray) {
val redactionEvents = results.map { it.asDomain() } val redactionEvents = results.map { it.asDomain() }
monarchy.writeAsync { realm -> val pruneEventWorkerParams = PruneEventWorkerParams(redactionEvents, updateIndexes.toList(), deletionIndexes.toList())
indexes.forEach { index -> val workData = pruneEventWorkerParams.toData()
val data = redactionEvents[index]
pruneEvent(realm, data) val sendWork = OneTimeWorkRequestBuilder<PruneEventWorker>()
} .setInputData(workData)
} .build()

WorkManager.getInstance()
.beginUniqueWork(PRUNE_EVENT_WORKER, ExistingWorkPolicy.APPEND, sendWork)
.enqueue()
} }


private fun pruneEvent(realm: Realm, redactionEvent: Event?) {
if (redactionEvent == null || redactionEvent.redacts.isNullOrEmpty()) {
return
}
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()?.asDomain()
?: return

val allowedKeys = computeAllowedKeys(eventToPrune.type)
val prunedContent = allowedKeys.fold(
{ eventToPrune.content },
{ eventToPrune.content?.filterKeys { key -> it.contains(key) } }
)
val eventToPruneEntity = eventToPrune.copy(content = prunedContent).asEntity()
realm.insertOrUpdate(eventToPruneEntity)
}

private fun computeAllowedKeys(type: String): Option<List<String>> {
// Add filtered content, allowed keys in content depends on the event type
val result = when (type) {
EventType.STATE_ROOM_MEMBER -> listOf("membership")
EventType.STATE_ROOM_CREATE -> listOf("creator")
EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule")
EventType.STATE_ROOM_POWER_LEVELS -> listOf("users",
"users_default",
"events",
"events_default",
"state_default",
"ban",
"kick",
"redact",
"invite")
EventType.STATE_ROOM_ALIASES -> listOf("aliases")
EventType.STATE_CANONICAL_ALIAS -> listOf("alias")
EventType.FEEDBACK -> listOf("type", "target_event_id")
else -> null
}
return Option.fromNullable(result)
}
} }

View File

@ -0,0 +1,75 @@
package im.vector.matrix.android.internal.session.events.prune

import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import arrow.core.Option
import com.zhuinden.monarchy.Monarchy
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.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.asEntity
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.tryTransactionAsync
import io.realm.Realm
import org.koin.standalone.KoinComponent
import org.koin.standalone.inject

internal class PruneEventWorker(context: Context,
workerParameters: WorkerParameters
) : Worker(context, workerParameters), KoinComponent {

private val monarchy by inject<Monarchy>()

override fun doWork(): Result {
val params = PruneEventWorkerParams.fromData(inputData) ?: return Result.FAILURE
val result = monarchy.tryTransactionAsync { realm ->
params.updateIndexes.forEach { index ->
val data = params.redactionEvents[index]
pruneEvent(realm, data)
}
}
return result.fold({ Result.RETRY }, { Result.SUCCESS })
}

private fun pruneEvent(realm: Realm, redactionEvent: Event?) {
if (redactionEvent == null || redactionEvent.redacts.isNullOrEmpty()) {
return
}
val eventToPrune = EventEntity.where(realm, eventId = redactionEvent.redacts).findFirst()?.asDomain()
?: return

val allowedKeys = computeAllowedKeys(eventToPrune.type)
val prunedContent = allowedKeys.fold(
{ eventToPrune.content },
{ eventToPrune.content?.filterKeys { key -> it.contains(key) } }
)
val eventToPruneEntity = eventToPrune.copy(content = prunedContent).asEntity()
realm.insertOrUpdate(eventToPruneEntity)
}

private fun computeAllowedKeys(type: String): Option<List<String>> {
// Add filtered content, allowed keys in content depends on the event type
val result = when (type) {
EventType.STATE_ROOM_MEMBER -> listOf("membership")
EventType.STATE_ROOM_CREATE -> listOf("creator")
EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule")
EventType.STATE_ROOM_POWER_LEVELS -> listOf("users",
"users_default",
"events",
"events_default",
"state_default",
"ban",
"kick",
"redact",
"invite")
EventType.STATE_ROOM_ALIASES -> listOf("aliases")
EventType.STATE_CANONICAL_ALIAS -> listOf("alias")
EventType.FEEDBACK -> listOf("type", "target_event_id")
else -> null
}
return Option.fromNullable(result)
}

}

View File

@ -0,0 +1,35 @@
package im.vector.matrix.android.internal.session.events.prune

import androidx.work.Data
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.di.MoshiProvider

@JsonClass(generateAdapter = true)
internal data class PruneEventWorkerParams(
val redactionEvents: List<Event>,
val updateIndexes: List<Int>,
val deletionIndexes: List<Int>
) {

fun toData(): Data {
val json = adapter.toJson(this)
return Data.Builder().putString(KEY, json).build()
}

companion object {

private val moshi = MoshiProvider.providesMoshi()
private val adapter = moshi.adapter(PruneEventWorkerParams::class.java)
private const val KEY = "PruneEventWorkerParams"

fun fromData(data: Data): PruneEventWorkerParams? {
val json = data.getString(KEY)
return if (json == null) {
null
} else {
adapter.fromJson(json)
}
}
}
}

View File

@ -38,7 +38,7 @@ internal class GetGroupDataRequest(
return CancelableCoroutine(job) return CancelableCoroutine(job)
} }


private fun getGroupData(groupId: String): Try<Unit> { fun getGroupData(groupId: String): Try<Unit> {
return Try.monad().binding { return Try.monad().binding {


val groupSummary = executeRequest<GroupSummaryResponse> { val groupSummary = executeRequest<GroupSummaryResponse> {

View File

@ -0,0 +1,30 @@
package im.vector.matrix.android.internal.session.group

import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import arrow.core.Try
import org.koin.standalone.KoinComponent
import org.koin.standalone.inject

internal class GetGroupDataWorker(context: Context,
workerParameters: WorkerParameters
) : Worker(context, workerParameters), KoinComponent {

private val getGroupDataRequest by inject<GetGroupDataRequest>()

override fun doWork(): Result {
val params = GetGroupDataWorkerParams.fromData(inputData) ?: return Result.FAILURE
val results = params.updateIndexes.map { index ->
val groupId = params.groupIds[index]
fetchGroupData(groupId)
}
val isSuccessful = results.none { it.isFailure() }
return if (isSuccessful) Result.SUCCESS else Result.RETRY
}

private fun fetchGroupData(groupId: String): Try<Unit> {
return getGroupDataRequest.getGroupData(groupId)
}

}

View File

@ -0,0 +1,34 @@
package im.vector.matrix.android.internal.session.group

import androidx.work.Data
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.di.MoshiProvider

@JsonClass(generateAdapter = true)
internal data class GetGroupDataWorkerParams(
val groupIds: List<String>,
val updateIndexes: List<Int>,
val deletionIndexes: List<Int>
) {

fun toData(): Data {
val json = adapter.toJson(this)
return Data.Builder().putString(KEY, json).build()
}

companion object {

private val moshi = MoshiProvider.providesMoshi()
private val adapter = moshi.adapter(GetGroupDataWorkerParams::class.java)
private const val KEY = "GetGroupDataWorkerParams"

fun fromData(data: Data): GetGroupDataWorkerParams? {
val json = data.getString(KEY)
return if (json == null) {
null
} else {
adapter.fromJson(json)
}
}
}
}

View File

@ -1,30 +1,37 @@
package im.vector.matrix.android.internal.session.group package im.vector.matrix.android.internal.session.group


import androidx.work.*
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import io.realm.RealmResults import io.realm.RealmResults


internal class GroupSummaryUpdater(monarchy: Monarchy, private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER"
private val getGroupDataRequest: GetGroupDataRequest
internal class GroupSummaryUpdater(monarchy: Monarchy
) : RealmLiveEntityObserver<GroupEntity>(monarchy) { ) : RealmLiveEntityObserver<GroupEntity>(monarchy) {


override val query = Monarchy.Query<GroupEntity> { GroupEntity.where(it) } override val query = Monarchy.Query<GroupEntity> { GroupEntity.where(it) }


override fun process(results: RealmResults<GroupEntity>, indexes: IntArray) { private val workConstraints = Constraints.Builder()
indexes.forEach { index -> .setRequiredNetworkType(NetworkType.CONNECTED)
val data = results[index] .build()
fetchGroupData(data)
} override fun process(results: RealmResults<GroupEntity>, updateIndexes: IntArray, deletionIndexes: IntArray) {
val groupIds = results.map { it.groupId }
val getGroupDataWorkerParams = GetGroupDataWorkerParams(groupIds, updateIndexes.toList(), deletionIndexes.toList())
val workData = getGroupDataWorkerParams.toData()

val sendWork = OneTimeWorkRequestBuilder<GetGroupDataWorker>()
.setInputData(workData)
.setConstraints(workConstraints)
.build()

WorkManager.getInstance()
.beginUniqueWork(GET_GROUP_DATA_WORKER, ExistingWorkPolicy.APPEND, sendWork)
.enqueue()
} }


private fun fetchGroupData(data: GroupEntity?) {
if (data == null) {
return
}
getGroupDataRequest.execute(data.groupId, object : MatrixCallback<Boolean> {})
}


} }

View File

@ -28,10 +28,10 @@ internal class RoomSummaryUpdater(monarchy: Monarchy,


override val query = Monarchy.Query<RoomEntity> { RoomEntity.where(it) } override val query = Monarchy.Query<RoomEntity> { RoomEntity.where(it) }


override fun process(results: RealmResults<RoomEntity>, indexes: IntArray) { override fun process(results: RealmResults<RoomEntity>, updateIndexes: IntArray, deletionIndexes: IntArray) {
val rooms = results.map { it.asDomain() } val rooms = results.map { it.asDomain() }
monarchy.writeAsync { realm -> monarchy.writeAsync { realm ->
indexes.forEach { index -> updateIndexes.forEach { index ->
val data = rooms[index] val data = rooms[index]
updateRoom(realm, data) updateRoom(realm, data)
} }