diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index 9c0dfd65..7e8591af 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -81,6 +81,9 @@ export class RoomViewModel extends ViewModel { dispose() { super.dispose(); this._room.off("change", this._onRoomChange); + if (this._room.isArchived) { + this._room.release(); + } if (this._clearUnreadTimout) { this._clearUnreadTimout.abort(); this._clearUnreadTimout = null; diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 20f303fb..527b7e6c 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -56,6 +56,7 @@ export class Session { this._sessionInfo = sessionInfo; this._rooms = new ObservableMap(); this._roomUpdateCallback = (room, params) => this._rooms.update(room.id, params); + this._activeArchivedRooms = new Map(); this._invites = new ObservableMap(); this._inviteUpdateCallback = (invite, params) => this._invites.update(invite.id, params); this._user = new User(sessionInfo.userId); @@ -399,18 +400,21 @@ export class Session { } /** @internal */ - createArchivedRoom(roomId) { - return new ArchivedRoom({ + _createArchivedRoom(roomId) { + const room = new ArchivedRoom({ roomId, getSyncToken: this._getSyncToken, storage: this._storage, emitCollectionChange: () => {}, + releaseCallback: () => this._activeArchivedRooms.delete(roomId), hsApi: this._hsApi, mediaRepository: this._mediaRepository, user: this._user, createRoomEncryption: this._createRoomEncryption, platform: this._platform }); + this._activeArchivedRooms.set(roomId, room); + return room; } get invites() { @@ -641,6 +645,8 @@ export class Session { return RoomStatus.joined; } else { const isInvited = !!this._invites.get(roomId); + const txn = await this._storage.readTxn([this._storage.storeNames.archivedRoomSummary]); + const isArchived = await txn.archivedRoomSummary.has(roomId); if (isInvited && isArchived) { return RoomStatus.invitedAndArchived; } else if (isInvited) { @@ -668,13 +674,18 @@ export class Session { loadArchivedRoom(roomId, log = null) { return this._platform.logger.wrapOrRun(log, "loadArchivedRoom", async log => { log.set("id", roomId); + const activeArchivedRoom = this._activeArchivedRooms.get(roomId); + if (activeArchivedRoom) { + activeArchivedRoom.retain(); + return activeArchivedRoom; + } const txn = await this._storage.readTxn([ this._storage.storeNames.archivedRoomSummary, this._storage.storeNames.roomMembers, ]); const summary = await txn.archivedRoomSummary.get(roomId); if (summary) { - const room = this.createArchivedRoom(roomId); + const room = this._createArchivedRoom(roomId); await room.load(summary, txn, log); return room; } diff --git a/src/matrix/Sync.js b/src/matrix/Sync.js index cde134e4..ac0da4b2 100644 --- a/src/matrix/Sync.js +++ b/src/matrix/Sync.js @@ -309,7 +309,10 @@ export class Sync { _afterSync(sessionState, inviteStates, roomStates, archivedRoomStates, log) { log.wrap("session", log => this._session.afterSync(sessionState.changes, log), log.level.Detail); for(let ars of archivedRoomStates) { - log.wrap("archivedRoom", log => ars.archivedRoom.afterSync(ars.changes, log), log.level.Detail); + log.wrap("archivedRoom", log => { + ars.archivedRoom.afterSync(ars.changes, log); + ars.archivedRoom.release(); + }, log.level.Detail); } for(let rs of roomStates) { log.wrap("room", log => rs.room.afterSync(rs.changes, log), log.level.Detail); @@ -407,12 +410,12 @@ export class Sync { // when adding a joined room during incremental sync, // always create the archived room to write the removal // of the archived summary - archivedRoom = this._session.createArchivedRoom(roomId); + archivedRoom = await this._session.loadArchivedRoom(roomId, log); } else if (membership === "leave") { if (roomState) { // we still have a roomState, so we just left it // in this case, create a new archivedRoom - archivedRoom = this._session.createArchivedRoom(roomId); + archivedRoom = await this._session.loadArchivedRoom(roomId, log); } else { // this is an update of an already left room, restore // it from storage first, so we can increment it. diff --git a/src/matrix/room/ArchivedRoom.js b/src/matrix/room/ArchivedRoom.js index a7f6525e..fa95270b 100644 --- a/src/matrix/room/ArchivedRoom.js +++ b/src/matrix/room/ArchivedRoom.js @@ -21,6 +21,10 @@ import {RoomMember} from "./members/RoomMember.js"; export class ArchivedRoom extends BaseRoom { constructor(options) { super(options); + // archived rooms are reference counted, + // as they are not kept in memory when not needed + this._releaseCallback = options.releaseCallback; + this._retentionCount = 1; /** Some details from our own member event when being kicked or banned. We can't get this from the member store, because we don't store the reason field there. @@ -29,6 +33,17 @@ export class ArchivedRoom extends BaseRoom { this._kickedBy = null; } + retain() { + this._retentionCount += 1; + } + + release() { + this._retentionCount -= 1; + if (this._retentionCount === 0) { + this._releaseCallback(); + } + } + async _getKickAuthor(sender, txn) { const senderMember = await txn.roomMembers.get(this.id, sender); if (senderMember) {