RoomSummaryUpdater.kt 22.8 KB
Newer Older
1
/*
2
 * Copyright 2020 The Matrix.org Foundation C.I.C.
3
 *
Benoit Marty's avatar
Benoit Marty committed
4
5
6
 * 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
7
 *
Benoit Marty's avatar
Benoit Marty committed
8
9
10
11
12
13
14
 *     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.
15
16
 */

Benoît Marty's avatar
Benoît Marty committed
17
package org.matrix.android.sdk.internal.session.room.summary
18

19
import io.realm.Realm
20
import io.realm.kotlin.createObject
Valere's avatar
Valere committed
21
import org.matrix.android.sdk.api.extensions.tryOrNull
Benoît Marty's avatar
Benoît Marty committed
22
23
24
25
26
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
Yoan Pintas's avatar
Yoan Pintas committed
27
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
Benoît Marty's avatar
Benoît Marty committed
28
29
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
Valere's avatar
Valere committed
30
31
import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
Benoît Marty's avatar
Benoît Marty committed
32
import org.matrix.android.sdk.api.session.room.send.SendState
Valere's avatar
Valere committed
33
import org.matrix.android.sdk.internal.crypto.EventDecryptor
Benoît Marty's avatar
Benoît Marty committed
34
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
Valere's avatar
Valere committed
35
import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
Benoît Marty's avatar
Benoît Marty committed
36
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
Valere's avatar
Valere committed
37
import org.matrix.android.sdk.internal.database.mapper.asDomain
Benoît Marty's avatar
Benoît Marty committed
38
39
40
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.EventEntityFields
Valere's avatar
Valere committed
41
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
Benoît Marty's avatar
Benoît Marty committed
42
43
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
Valere's avatar
Valere committed
44
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
Valere's avatar
Valere committed
45
46
import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntity
import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntity
Benoît Marty's avatar
Benoît Marty committed
47
48
49
50
51
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.database.query.getOrNull
import org.matrix.android.sdk.internal.database.query.isEventRead
Valere's avatar
Valere committed
52
import org.matrix.android.sdk.internal.database.query.where
Benoît Marty's avatar
Benoît Marty committed
53
54
import org.matrix.android.sdk.internal.database.query.whereType
import org.matrix.android.sdk.internal.di.UserId
Valere's avatar
Valere committed
55
import org.matrix.android.sdk.internal.extensions.clearWith
Valere's avatar
Valere committed
56
import org.matrix.android.sdk.internal.query.process
Benoît Marty's avatar
Benoît Marty committed
57
58
59
import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
Valere's avatar
Valere committed
60
61
import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
Benoît Marty's avatar
Benoît Marty committed
62
63
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications
64
import timber.log.Timber
65
import javax.inject.Inject
Valere's avatar
Valere committed
66
import kotlin.system.measureTimeMillis
67

68
69
70
71
internal class RoomSummaryUpdater @Inject constructor(
        @UserId private val userId: String,
        private val roomDisplayNameResolver: RoomDisplayNameResolver,
        private val roomAvatarResolver: RoomAvatarResolver,
Valere's avatar
Valere committed
72
        private val eventDecryptor: EventDecryptor,
Valere's avatar
Valere committed
73
74
        private val crossSigningService: DefaultCrossSigningService,
        private val stateEventDataSource: StateEventDataSource) {
75
76
77

    fun update(realm: Realm,
               roomId: String,
78
               membership: Membership? = null,
79
               roomSummary: RoomSyncSummary? = null,
80
               unreadNotifications: RoomSyncUnreadNotifications? = null,
81
               updateMembers: Boolean = false,
82
               inviterId: String? = null) {
83
        val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
84
85
86
87
88
89
90
91
92
93
94
95
        if (roomSummary != null) {
            if (roomSummary.heroes.isNotEmpty()) {
                roomSummaryEntity.heroes.clear()
                roomSummaryEntity.heroes.addAll(roomSummary.heroes)
            }
            if (roomSummary.invitedMembersCount != null) {
                roomSummaryEntity.invitedMembersCount = roomSummary.invitedMembersCount
            }
            if (roomSummary.joinedMembersCount != null) {
                roomSummaryEntity.joinedMembersCount = roomSummary.joinedMembersCount
            }
        }
96
        roomSummaryEntity.highlightCount = unreadNotifications?.highlightCount ?: 0
ganfra's avatar
ganfra committed
97
        roomSummaryEntity.notificationCount = unreadNotifications?.notificationCount ?: 0
98

99
100
101
        if (membership != null) {
            roomSummaryEntity.membership = membership
        }
102

103
        val lastNameEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root
104
105
106
        val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
        val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
        val lastAliasesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root
Valere's avatar
Valere committed
107
        val roomCreateEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CREATE, stateKey = "")?.root
Yoan Pintas's avatar
Yoan Pintas committed
108
        val joinRulesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_JOIN_RULES, stateKey = "")?.root
Valere's avatar
Valere committed
109
110
111

        val roomType = ContentMapper.map(roomCreateEvent?.content).toModel<RoomCreateContent>()?.type
        roomSummaryEntity.roomType = roomType
Valere's avatar
Valere committed
112
        Timber.v("## Space: Updating summary room [$roomId] roomType: [$roomType]")
113
114
115

        // Don't use current state for this one as we are only interested in having MXCRYPTO_ALGORITHM_MEGOLM event in the room
        val encryptionEvent = EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
116
                .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
117
                .isNotNull(EventEntityFields.STATE_KEY)
118
                .findFirst()
119

120
121
        val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)

Valere's avatar
Valere committed
122
123
124
125
126
        val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
        if (lastActivityFromEvent != null) {
            roomSummaryEntity.lastActivityTime = lastActivityFromEvent
        }

Benoit Marty's avatar
Benoit Marty committed
127
        roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
128
                // avoid this call if we are sure there are unread events
ganfra's avatar
ganfra committed
129
                || !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId)
ganfra's avatar
ganfra committed
130

131
        roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId)
ganfra's avatar
ganfra committed
132
        roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
133
        roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel<RoomNameContent>()?.name
134
        roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic
Benoit Marty's avatar
Benoit Marty committed
135
        roomSummaryEntity.joinRules = ContentMapper.map(joinRulesEvent?.content).toModel<RoomJoinRulesContent>()?.joinRules
Benoit Marty's avatar
Benoit Marty committed
136
        roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
ganfra's avatar
ganfra committed
137
138
139
        roomSummaryEntity.canonicalAlias = ContentMapper.map(lastCanonicalAliasEvent?.content).toModel<RoomCanonicalAliasContent>()
                ?.canonicalAlias

ganfra's avatar
ganfra committed
140
        val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases
ganfra's avatar
ganfra committed
141
                .orEmpty()
Valere's avatar
Valere committed
142
        roomSummaryEntity.updateAliases(roomAliases)
ganfra's avatar
ganfra committed
143
        roomSummaryEntity.isEncrypted = encryptionEvent != null
Valere's avatar
Valere committed
144
        roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs
ganfra's avatar
ganfra committed
145
146
147
148
149
150

        if (roomSummaryEntity.membership == Membership.INVITE && inviterId != null) {
            roomSummaryEntity.inviterId = inviterId
        } else if (roomSummaryEntity.membership != Membership.INVITE) {
            roomSummaryEntity.inviterId = null
        }
151
        roomSummaryEntity.updateHasFailedSending()
152

Valere's avatar
Valere committed
153
154
        val root = latestPreviewableEvent?.root
        if (root?.type == EventType.ENCRYPTED && root.decryptionResultJson == null) {
155
            Timber.v("Should decrypt ${latestPreviewableEvent.eventId}")
Valere's avatar
Valere committed
156
157
158
159
            // mmm i want to decrypt now or is it ok to do it async?
            tryOrNull {
                eventDecryptor.decryptEvent(root.asDomain(), "")
            }
160
                    ?.let { root.setDecryptionResult(it) }
161
162
        }

163
        if (updateMembers) {
164
            val otherRoomMembers = RoomMemberHelper(realm, roomId)
ganfra's avatar
ganfra committed
165
                    .queryActiveRoomMembersEvent()
166
                    .notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
167
                    .findAll()
168
                    .map { it.userId }
169
170
171

            roomSummaryEntity.otherMemberIds.clear()
            roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
Benoît Marty's avatar
Benoît Marty committed
172
            if (roomSummaryEntity.isEncrypted && otherRoomMembers.isNotEmpty()) {
Valere's avatar
Valere committed
173
                // mmm maybe we could only refresh shield instead of checking trust also?
174
                crossSigningService.onUsersDeviceUpdate(otherRoomMembers)
Valere's avatar
Valere committed
175
176
177
178
            }
        }
    }

179
180
181
182
183
184
185
    private fun RoomSummaryEntity.updateHasFailedSending() {
        hasFailedSending = TimelineEventEntity.findAllInRoomWithSendStates(realm, roomId, SendState.HAS_FAILED_STATES).isNotEmpty()
    }

    fun updateSendingInformation(realm: Realm, roomId: String) {
        val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
        roomSummaryEntity.updateHasFailedSending()
186
        roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
187
    }
Valere's avatar
Valere committed
188
189
190
191
192

    /**
     * Should be called at the end of the room sync, to check and validate all parent/child relations
     */
    fun validateSpaceRelationship(realm: Realm) {
Valere's avatar
Valere committed
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
        measureTimeMillis {
            val lookupMap = realm.where(RoomSummaryEntity::class.java)
                    .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
                    .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
                    // we order by roomID to be consistent when breaking parent/child cycles
                    .sort(RoomSummaryEntityFields.ROOM_ID)
                    .findAll().map {
                        it.flattenParentIds = null
                        it to emptyList<RoomSummaryEntity>().toMutableSet()
                    }
                    .toMap()

            lookupMap.keys.forEach { lookedUp ->
                if (lookedUp.roomType == RoomType.SPACE) {
                    // get childrens

                    lookedUp.children.clearWith { it.deleteFromRealm() }

                    RoomChildRelationInfo(realm, lookedUp.roomId).getDirectChildrenDescriptions().forEach { child ->

                        lookedUp.children.add(
                                realm.createObject<SpaceChildSummaryEntity>().apply {
                                    this.childRoomId = child.roomId
                                    this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst()
                                    this.order = child.order
                                    this.autoJoin = child.autoJoin
                                    this.viaServers.addAll(child.viaServers)
                                }
                        )

                        RoomSummaryEntity.where(realm, child.roomId)
                                .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
                                .findFirst()
                                ?.let { childSum ->
                                    lookupMap.entries.firstOrNull { it.key.roomId == lookedUp.roomId }?.let { entry ->
                                        if (entry.value.indexOfFirst { it.roomId == childSum.roomId } == -1) {
                                            // add looked up as a parent
                                            entry.value.add(childSum)
                                        }
Valere's avatar
Valere committed
232
233
                                    }
                                }
Valere's avatar
Valere committed
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
                    }
                } else {
                    lookedUp.parents.clearWith { it.deleteFromRealm() }
                    // can we check parent relations here??
                    RoomChildRelationInfo(realm, lookedUp.roomId).getParentDescriptions()
                            .map { parentInfo ->

                                lookedUp.parents.add(
                                        realm.createObject<SpaceParentSummaryEntity>().apply {
                                            this.parentRoomId = parentInfo.roomId
                                            this.parentSummaryEntity = RoomSummaryEntity.where(realm, parentInfo.roomId).findFirst()
                                            this.canonical = parentInfo.canonical
                                            this.viaServers.addAll(parentInfo.viaServers)
                                        }
                                )

                                RoomSummaryEntity.where(realm, parentInfo.roomId)
                                        .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
                                        .findFirst()
                                        ?.let { parentSum ->
                                            if (lookupMap[parentSum]?.indexOfFirst { it.roomId == lookedUp.roomId } == -1) {
                                                // add lookedup as a parent
                                                lookupMap[parentSum]?.add(lookedUp)
                                            }
                                        }
                            }
                }
Valere's avatar
Valere committed
261
262
            }

Valere's avatar
Valere committed
263
264
265
266
267
268
            // Simple algorithm to break cycles
            // Need more work to decide how to break, probably need to be as consistent as possible
            // and also find best way to root the tree

            val graph = Graph()
            lookupMap
269
270
                    // focus only on joined spaces, as room are just leaf
                    .filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN }
Valere's avatar
Valere committed
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
                    .forEach { (sum, children) ->
                        graph.getOrCreateNode(sum.roomId)
                        children.forEach {
                            graph.addEdge(it.roomId, sum.roomId)
                        }
                    }

            val backEdges = graph.findBackwardEdges()
            Timber.v("## SPACES: Cycle detected = ${backEdges.isNotEmpty()}")

            // break cycles
            backEdges.forEach { edge ->
                lookupMap.entries.find { it.key.roomId == edge.source.name }?.let {
                    it.value.removeAll { it.roomId == edge.destination.name }
                }
            }

            val acyclicGraph = graph.withoutEdges(backEdges)
//            Timber.v("## SPACES: acyclicGraph $acyclicGraph")
            val flattenSpaceParents = acyclicGraph.flattenDestination().map {
                it.key.name to it.value.map { it.name }
            }.toMap()
//            Timber.v("## SPACES: flattenSpaceParents ${flattenSpaceParents.map { it.key.name to it.value.map { it.name } }.joinToString("\n") {
//                it.first + ": [" + it.second.joinToString(",") + "]"
//            }}")

//            Timber.v("## SPACES: lookup map ${lookupMap.map { it.key.name to it.value.map { it.name } }.toMap()}")

            lookupMap.entries
300
                    .filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN  }
Valere's avatar
Valere committed
301
302
303
304
305
306
307
308
309
310
                    .forEach { entry ->
                        val parent = RoomSummaryEntity.where(realm, entry.key.roomId).findFirst()
                        if (parent != null) {
//                            Timber.v("## SPACES: check hierarchy of ${parent.name} id ${parent.roomId}")
//                            Timber.v("## SPACES: flat known parents of ${parent.name} are ${flattenSpaceParents[parent.roomId]}")
                            val flattenParentsIds = (flattenSpaceParents[parent.roomId] ?: emptyList()) + listOf(parent.roomId)
//                            Timber.v("## SPACES: flatten known parents of children of ${parent.name} are ${flattenParentsIds}")
                            entry.value.forEach { child ->
                                RoomSummaryEntity.where(realm, child.roomId).findFirst()?.let { childSum ->

Valere's avatar
Valere committed
311
//                                    Timber.w("## SPACES: ${childSum.name} is ${childSum.roomId} fc: ${childSum.flattenParentIds}")
Valere's avatar
Valere committed
312
313
314
315
316
317
318
319
320
321
//                                    var allParents = childSum.flattenParentIds ?: ""
                                    if (childSum.flattenParentIds == null) childSum.flattenParentIds = ""
                                    flattenParentsIds.forEach {
                                        if (childSum.flattenParentIds?.contains(it) != true) {
                                            childSum.flattenParentIds += "|$it"
                                        }
                                    }
//                                    childSum.flattenParentIds = "$allParents|"

//                                    Timber.v("## SPACES: flatten of ${childSum.name} is ${childSum.flattenParentIds}")
Valere's avatar
Valere committed
322
323
                                }
                            }
Valere's avatar
Valere committed
324
325
326
327
328
                        }
                    }

            // we need also to filter DMs...
            // it's more annoying as based on if the other members belong the space or not
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
            RoomSummaryEntity.where(realm)
                    .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
                    .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
                    .findAll()
                    .forEach { dmRoom ->
                        val relatedSpaces = lookupMap.keys
                                .filter { it.roomType == RoomType.SPACE }
                                .filter {
                                    dmRoom.otherMemberIds.toList().intersect(it.otherMemberIds.toList()).isNotEmpty()
                                }
                                .map { it.roomId }
                                .distinct()
                        val flattenRelated = mutableListOf<String>().apply {
                            addAll(relatedSpaces)
                            relatedSpaces.map { flattenSpaceParents[it] }.forEach {
                                if (it != null) addAll(it)
                            }
                        }.distinct()
                        if (flattenRelated.isEmpty()) {
                            dmRoom.flattenParentIds = null
                        } else {
                            dmRoom.flattenParentIds = "|${flattenRelated.joinToString("|")}|"
                        }
//                        Timber.v("## SPACES: flatten of ${dmRoom.otherMemberIds.joinToString(",")} is ${dmRoom.flattenParentIds}")
                    }
Valere's avatar
Valere committed
354

Valere's avatar
Valere committed
355
356
357
358
359
360
361
362
363
364
365
366
            // Maybe a good place to count the number of notifications for spaces?

            realm.where(RoomSummaryEntity::class.java)
                    .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
                    .equalTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
                    .findAll().forEach { space ->
                        // get all children
                        var highlightCount = 0
                        var notificationCount = 0
                        realm.where(RoomSummaryEntity::class.java)
                                .process(RoomSummaryEntityFields.MEMBERSHIP_STR, listOf(Membership.JOIN))
                                .notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
Valere's avatar
Valere committed
367
368
                                // also we do not count DM in here, because home space will already show them
                                .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
Valere's avatar
Valere committed
369
370
371
372
373
374
375
376
377
378
379
                                .contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, space.roomId)
                                .findAll().forEach {
                                    highlightCount += it.highlightCount
                                    notificationCount += it.notificationCount
                                }

                        space.highlightCount = highlightCount
                        space.notificationCount = notificationCount
                    }
            // xxx invites??

Valere's avatar
Valere committed
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
            // LEGACY GROUPS
            // lets mark rooms that belongs to groups
            val existingGroups = GroupSummaryEntity.where(realm).findAll()

            // For rooms
            realm.where(RoomSummaryEntity::class.java)
                    .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
                    .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
                    .findAll().forEach { room ->
                        val belongsTo = existingGroups.filter { it.roomIds.contains(room.roomId) }
                        room.groupIds = if (belongsTo.isEmpty()) {
                            null
                        } else {
                            "|${belongsTo.joinToString("|")}|"
                        }
                    }

            // For DMS
            realm.where(RoomSummaryEntity::class.java)
                    .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
                    .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
                    .findAll().forEach { room ->
                        val belongsTo = existingGroups.filter {
                            it.userIds.intersect(room.otherMemberIds).isNotEmpty()
                        }
                        room.groupIds = if (belongsTo.isEmpty()) {
                            null
                        } else {
                            "|${belongsTo.joinToString("|")}|"
                        }
                    }
Valere's avatar
Valere committed
411
412
        }.also {
            Timber.v("## SPACES: Finish checking room hierarchy in $it ms")
Valere's avatar
Valere committed
413
414
415
416
417
418
        }
    }

//    private fun isValidCanonical() : Boolean {
//
//    }
419
}