diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt index 6d345899..bc81a709 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt @@ -39,10 +39,11 @@ internal abstract class RealmLiveEntityObserver(protected val m if (changeSet == null) { return } - val indexes = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions - process(changeSet.realmResults, indexes) + val updateIndexes = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions + val deletionIndexes = changeSet.orderedCollectionChangeSet.deletions + process(changeSet.realmResults, updateIndexes, deletionIndexes) } - abstract fun process(results: RealmResults, indexes: IntArray) + abstract fun process(results: RealmResults, updateIndexes: IntArray, deletionIndexes: IntArray) } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index f07d753f..8f2d1ed9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -1,9 +1,9 @@ package im.vector.matrix.android.internal.session 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.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.session.events.prune.EventsPruner import im.vector.matrix.android.internal.session.group.DefaultGroupService @@ -66,7 +66,7 @@ internal class SessionModule(private val sessionParams: SessionParams) : Module scope(DefaultSession.SCOPE) { val roomSummaryUpdater = RoomSummaryUpdater(get(), get(), get(), get(), sessionParams.credentials) - val groupSummaryUpdater = GroupSummaryUpdater(get(), get()) + val groupSummaryUpdater = GroupSummaryUpdater(get()) val eventsPruner = EventsPruner(get()) listOf(roomSummaryUpdater, groupSummaryUpdater, eventsPruner) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/EventsPruner.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/EventsPruner.kt index bd98ea13..097ee6e3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/EventsPruner.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/EventsPruner.kt @@ -1,68 +1,35 @@ 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 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.RealmLiveEntityObserver 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 io.realm.Realm import io.realm.RealmResults +private const val PRUNE_EVENT_WORKER = "PRUNE_EVENT_WORKER" + internal class EventsPruner(monarchy: Monarchy) : RealmLiveEntityObserver(monarchy) { override val query = Monarchy.Query { EventEntity.where(it, type = EventType.REDACTION) } - override fun process(results: RealmResults, indexes: IntArray) { + override fun process(results: RealmResults, updateIndexes: IntArray, deletionIndexes: IntArray) { val redactionEvents = results.map { it.asDomain() } - monarchy.writeAsync { realm -> - indexes.forEach { index -> - val data = redactionEvents[index] - pruneEvent(realm, data) - } - } + val pruneEventWorkerParams = PruneEventWorkerParams(redactionEvents, updateIndexes.toList(), deletionIndexes.toList()) + val workData = pruneEventWorkerParams.toData() + + val sendWork = OneTimeWorkRequestBuilder() + .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> { - // 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) - } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/PruneEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/PruneEventWorker.kt new file mode 100644 index 00000000..6b2a11f0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/PruneEventWorker.kt @@ -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() + + 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> { + // 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) + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/PruneEventWorkerParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/PruneEventWorkerParams.kt new file mode 100644 index 00000000..53815f38 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/events/prune/PruneEventWorkerParams.kt @@ -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, + val updateIndexes: List, + val deletionIndexes: List +) { + + 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) + } + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataRequest.kt index 5b7c3001..4fd05c94 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataRequest.kt @@ -38,7 +38,7 @@ internal class GetGroupDataRequest( return CancelableCoroutine(job) } - private fun getGroupData(groupId: String): Try { + fun getGroupData(groupId: String): Try { return Try.monad().binding { val groupSummary = executeRequest { @@ -64,7 +64,7 @@ internal class GetGroupDataRequest( return monarchy .tryTransactionSync { realm -> val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst() - ?: realm.createObject(groupId) + ?: realm.createObject(groupId) groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: "" val name = groupSummary.profile?.name diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt new file mode 100644 index 00000000..4905b93d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt @@ -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() + + 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 { + return getGroupDataRequest.getGroupData(groupId) + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorkerParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorkerParams.kt new file mode 100644 index 00000000..5dd9eb87 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorkerParams.kt @@ -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, + val updateIndexes: List, + val deletionIndexes: List +) { + + 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) + } + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt index 1fb1b770..a9dead4e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt @@ -1,30 +1,37 @@ package im.vector.matrix.android.internal.session.group +import androidx.work.* 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.model.GroupEntity import im.vector.matrix.android.internal.database.query.where import io.realm.RealmResults -internal class GroupSummaryUpdater(monarchy: Monarchy, - private val getGroupDataRequest: GetGroupDataRequest +private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" + +internal class GroupSummaryUpdater(monarchy: Monarchy ) : RealmLiveEntityObserver(monarchy) { override val query = Monarchy.Query { GroupEntity.where(it) } - override fun process(results: RealmResults, indexes: IntArray) { - indexes.forEach { index -> - val data = results[index] - fetchGroupData(data) - } + private val workConstraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + override fun process(results: RealmResults, 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() + .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 {}) - } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index 7f6225d7..85049f49 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -28,10 +28,10 @@ internal class RoomSummaryUpdater(monarchy: Monarchy, override val query = Monarchy.Query { RoomEntity.where(it) } - override fun process(results: RealmResults, indexes: IntArray) { + override fun process(results: RealmResults, updateIndexes: IntArray, deletionIndexes: IntArray) { val rooms = results.map { it.asDomain() } monarchy.writeAsync { realm -> - indexes.forEach { index -> + updateIndexes.forEach { index -> val data = rooms[index] updateRoom(realm, data) }