@@ -18,7 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
1818import { isNullOrUndefined } from "matrix-js-sdk/src/utils" ;
1919import DMRoomMap from "../../../utils/DMRoomMap" ;
2020import { EventEmitter } from "events" ;
21- import { arrayHasDiff , ArrayUtil } from "../../../utils/arrays" ;
21+ import { arrayDiff , arrayHasDiff , ArrayUtil } from "../../../utils/arrays" ;
2222import { getEnumValues } from "../../../utils/enums" ;
2323import { DefaultTagID , RoomUpdateCause , TagID } from "../models" ;
2424import {
@@ -57,6 +57,7 @@ export class Algorithm extends EventEmitter {
5757 private _cachedStickyRooms : ITagMap = { } ; // a clone of the _cachedRooms, with the sticky room
5858 private filteredRooms : ITagMap = { } ;
5959 private _stickyRoom : IStickyRoom = null ;
60+ private _lastStickyRoom : IStickyRoom = null ; // only not-null when changing the sticky room
6061 private sortAlgorithms : ITagSortingMap ;
6162 private listAlgorithms : IListOrderingMap ;
6263 private algorithms : IOrderingAlgorithmMap ;
@@ -162,9 +163,21 @@ export class Algorithm extends EventEmitter {
162163 }
163164
164165 private async updateStickyRoom ( val : Room ) {
166+ try {
167+ return await this . doUpdateStickyRoom ( val ) ;
168+ } finally {
169+ this . _lastStickyRoom = null ; // clear to indicate we're done changing
170+ }
171+ }
172+
173+ private async doUpdateStickyRoom ( val : Room ) {
165174 // Note throughout: We need async so we can wait for handleRoomUpdate() to do its thing,
166175 // otherwise we risk duplicating rooms.
167176
177+ // Set the last sticky room to indicate that we're in a change. The code throughout the
178+ // class can safely handle a null room, so this should be safe to do as a backup.
179+ this . _lastStickyRoom = this . _stickyRoom || < IStickyRoom > { } ;
180+
168181 // It's possible to have no selected room. In that case, clear the sticky room
169182 if ( ! val ) {
170183 if ( this . _stickyRoom ) {
@@ -179,7 +192,7 @@ export class Algorithm extends EventEmitter {
179192 }
180193
181194 // When we do have a room though, we expect to be able to find it
182- const tag = this . roomIdsToTags [ val . roomId ] [ 0 ] ;
195+ let tag = this . roomIdsToTags [ val . roomId ] [ 0 ] ;
183196 if ( ! tag ) throw new Error ( `${ val . roomId } does not belong to a tag and cannot be sticky` ) ;
184197
185198 // We specifically do NOT use the ordered rooms set as it contains the sticky room, which
@@ -196,19 +209,41 @@ export class Algorithm extends EventEmitter {
196209 // the same thing it no-ops. After we're done calling the algorithm, we'll issue
197210 // a new update for ourselves.
198211 const lastStickyRoom = this . _stickyRoom ;
199- this . _stickyRoom = null ;
212+ this . _stickyRoom = null ; // clear before we update the algorithm
200213 this . recalculateStickyRoom ( ) ;
201214
202215 // When we do have the room, re-add the old room (if needed) to the algorithm
203216 // and remove the sticky room from the algorithm. This is so the underlying
204217 // algorithm doesn't try and confuse itself with the sticky room concept.
205- if ( lastStickyRoom ) {
218+ // We don't add the new room if the sticky room isn't changing because that's
219+ // an easy way to cause duplication. We have to do room ID checks instead of
220+ // referential checks as the references can differ through the lifecycle.
221+ if ( lastStickyRoom && lastStickyRoom . room && lastStickyRoom . room . roomId !== val . roomId ) {
206222 // Lie to the algorithm and re-add the room to the algorithm
207223 await this . handleRoomUpdate ( lastStickyRoom . room , RoomUpdateCause . NewRoom ) ;
208224 }
209225 // Lie to the algorithm and remove the room from it's field of view
210226 await this . handleRoomUpdate ( val , RoomUpdateCause . RoomRemoved ) ;
211227
228+ // Check for tag & position changes while we're here. We also check the room to ensure
229+ // it is still the same room.
230+ if ( this . _stickyRoom ) {
231+ if ( this . _stickyRoom . room !== val ) {
232+ // Check the room IDs just in case
233+ if ( this . _stickyRoom . room . roomId === val . roomId ) {
234+ console . warn ( "Sticky room changed references" ) ;
235+ } else {
236+ throw new Error ( "Sticky room changed while the sticky room was changing" ) ;
237+ }
238+ }
239+
240+ console . warn ( `Sticky room changed tag & position from ${ tag } / ${ position } `
241+ + `to ${ this . _stickyRoom . tag } / ${ this . _stickyRoom . position } ` ) ;
242+
243+ tag = this . _stickyRoom . tag ;
244+ position = this . _stickyRoom . position ;
245+ }
246+
212247 // Now that we're done lying to the algorithm, we need to update our position
213248 // marker only if the user is moving further down the same list. If they're switching
214249 // lists, or moving upwards, the position marker will splice in just fine but if
@@ -560,7 +595,7 @@ export class Algorithm extends EventEmitter {
560595 /**
561596 * Updates the roomsToTags map
562597 */
563- protected updateTagsFromCache ( ) {
598+ private updateTagsFromCache ( ) {
564599 const newMap = { } ;
565600
566601 const tags = Object . keys ( this . cachedRooms ) ;
@@ -607,21 +642,94 @@ export class Algorithm extends EventEmitter {
607642 * processing.
608643 */
609644 public async handleRoomUpdate ( room : Room , cause : RoomUpdateCause ) : Promise < boolean > {
645+ // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
646+ console . log ( `Handle room update for ${ room . roomId } called with cause ${ cause } ` ) ;
610647 if ( ! this . algorithms ) throw new Error ( "Not ready: no algorithms to determine tags from" ) ;
611648
649+ // Note: check the isSticky against the room ID just in case the reference is wrong
650+ const isSticky = this . _stickyRoom && this . _stickyRoom . room && this . _stickyRoom . room . roomId === room . roomId ;
612651 if ( cause === RoomUpdateCause . NewRoom ) {
652+ const isForLastSticky = this . _lastStickyRoom && this . _lastStickyRoom . room === room ;
613653 const roomTags = this . roomIdsToTags [ room . roomId ] ;
614- if ( roomTags && roomTags . length > 0 ) {
654+ const hasTags = roomTags && roomTags . length > 0 ;
655+
656+ // Don't change the cause if the last sticky room is being re-added. If we fail to
657+ // pass the cause through as NewRoom, we'll fail to lie to the algorithm and thus
658+ // lose the room.
659+ if ( hasTags && ! isForLastSticky ) {
615660 console . warn ( `${ room . roomId } is reportedly new but is already known - assuming TagChange instead` ) ;
616661 cause = RoomUpdateCause . PossibleTagChange ;
617662 }
663+
664+ // If we have tags for a room and don't have the room referenced, the room reference
665+ // probably changed. We need to swap out the problematic reference.
666+ if ( hasTags && ! this . rooms . includes ( room ) && ! isSticky ) {
667+ console . warn ( `${ room . roomId } is missing from room array but is known - trying to find duplicate` ) ;
668+ this . rooms = this . rooms . map ( r => r . roomId === room . roomId ? room : r ) ;
669+
670+ // Sanity check
671+ if ( ! this . rooms . includes ( room ) ) {
672+ throw new Error ( `Failed to replace ${ room . roomId } with an updated reference` ) ;
673+ }
674+ }
675+
676+ // Like above, update the reference to the sticky room if we need to
677+ if ( hasTags && isSticky ) {
678+ // Go directly in and set the sticky room's new reference, being careful not
679+ // to trigger a sticky room update ourselves.
680+ this . _stickyRoom . room = room ;
681+ }
618682 }
619683
620684 if ( cause === RoomUpdateCause . PossibleTagChange ) {
621- // TODO: Be smarter and splice rather than regen the planet. https://github.com/vector-im/riot-web/issues/14035
622- // TODO: No-op if no change. https://github.com/vector-im/riot-web/issues/14035
623- await this . setKnownRooms ( this . rooms ) ;
624- return true ;
685+ let didTagChange = false ;
686+ const oldTags = this . roomIdsToTags [ room . roomId ] || [ ] ;
687+ const newTags = this . getTagsForRoom ( room ) ;
688+ const diff = arrayDiff ( oldTags , newTags ) ;
689+ if ( diff . removed . length > 0 || diff . added . length > 0 ) {
690+ for ( const rmTag of diff . removed ) {
691+ // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
692+ console . log ( `Removing ${ room . roomId } from ${ rmTag } ` ) ;
693+ const algorithm : OrderingAlgorithm = this . algorithms [ rmTag ] ;
694+ if ( ! algorithm ) throw new Error ( `No algorithm for ${ rmTag } ` ) ;
695+ await algorithm . handleRoomUpdate ( room , RoomUpdateCause . RoomRemoved ) ;
696+ }
697+ for ( const addTag of diff . added ) {
698+ // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
699+ console . log ( `Adding ${ room . roomId } to ${ addTag } ` ) ;
700+ const algorithm : OrderingAlgorithm = this . algorithms [ addTag ] ;
701+ if ( ! algorithm ) throw new Error ( `No algorithm for ${ addTag } ` ) ;
702+ await algorithm . handleRoomUpdate ( room , RoomUpdateCause . NewRoom ) ;
703+ }
704+
705+ // Update the tag map so we don't regen it in a moment
706+ this . roomIdsToTags [ room . roomId ] = newTags ;
707+
708+ // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
709+ console . log ( `Changing update cause for ${ room . roomId } to Timeline to sort rooms` ) ;
710+ cause = RoomUpdateCause . Timeline ;
711+ didTagChange = true ;
712+ } else {
713+ // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
714+ console . warn ( `Received no-op update for ${ room . roomId } - changing to Timeline update` ) ;
715+ cause = RoomUpdateCause . Timeline ;
716+ }
717+
718+ if ( didTagChange && isSticky ) {
719+ // Manually update the tag for the sticky room without triggering a sticky room
720+ // update. The update will be handled implicitly by the sticky room handling and
721+ // requires no changes on our part, if we're in the middle of a sticky room change.
722+ if ( this . _lastStickyRoom ) {
723+ this . _stickyRoom = {
724+ room,
725+ tag : this . roomIdsToTags [ room . roomId ] [ 0 ] ,
726+ position : 0 , // right at the top as it changed tags
727+ } ;
728+ } else {
729+ // We have to clear the lock as the sticky room change will trigger updates.
730+ await this . setStickyRoomAsync ( room ) ;
731+ }
732+ }
625733 }
626734
627735 // If the update is for a room change which might be the sticky room, prevent it. We
@@ -635,8 +743,9 @@ export class Algorithm extends EventEmitter {
635743 }
636744 }
637745
638- if ( cause === RoomUpdateCause . NewRoom && ! this . roomIdsToTags [ room . roomId ] ) {
639- console . log ( `[RoomListDebug] Updating tags for new room ${ room . roomId } (${ room . name } )` ) ;
746+ if ( ! this . roomIdsToTags [ room . roomId ] ) {
747+ // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
748+ console . log ( `[RoomListDebug] Updating tags for room ${ room . roomId } (${ room . name } )` ) ;
640749
641750 // Get the tags for the room and populate the cache
642751 const roomTags = this . getTagsForRoom ( room ) . filter ( t => ! isNullOrUndefined ( this . cachedRooms [ t ] ) ) ;
@@ -646,9 +755,15 @@ export class Algorithm extends EventEmitter {
646755 if ( ! roomTags . length ) throw new Error ( `Tags cannot be determined for ${ room . roomId } ` ) ;
647756
648757 this . roomIdsToTags [ room . roomId ] = roomTags ;
758+
759+ // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
760+ console . log ( `[RoomListDebug] Updated tags for ${ room . roomId } :` , roomTags ) ;
649761 }
650762
651- let tags = this . roomIdsToTags [ room . roomId ] ;
763+ // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
764+ console . log ( `[RoomListDebug] Reached algorithmic handling for ${ room . roomId } and cause ${ cause } ` ) ;
765+
766+ const tags = this . roomIdsToTags [ room . roomId ] ;
652767 if ( ! tags ) {
653768 console . warn ( `No tags known for "${ room . name } " (${ room . roomId } )` ) ;
654769 return false ;
@@ -668,6 +783,8 @@ export class Algorithm extends EventEmitter {
668783 changed = true ;
669784 }
670785
671- return true ;
786+ // TODO: Remove debug: https://github.com/vector-im/riot-web/issues/14035
787+ console . log ( `[RoomListDebug] Finished handling ${ room . roomId } with cause ${ cause } (changed=${ changed } )` ) ;
788+ return changed ;
672789 }
673790}
0 commit comments