From e3bd0cf139ede671ce2d09e20bfa84584b918ed3 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Mon, 6 Dec 2021 11:09:50 +0100 Subject: [PATCH 1/2] fix: Only save state events from sync processing in-memory if needed If we dump all state events from sync into memory then we needlessly clog up our memory, potentially running out of ram. This is useless as when opening the timeline we post-load the unimportant state events anyways. So, this PR makes sure that only the state events of post-loaded rooms and important state events land in-memory when processing a sync request. --- lib/src/client.dart | 21 ++++-- test/client_test.dart | 146 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 5 deletions(-) diff --git a/lib/src/client.dart b/lib/src/client.dart index d2b9bc88..710fcd59 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -1706,12 +1706,23 @@ class Client extends MatrixApi { ), ); } else { - if (stateEvent.type != EventTypes.Message || - stateEvent.relationshipType != RelationshipTypes.edit || - stateEvent.relationshipEventId == room.lastEvent?.eventId || - ((room.lastEvent?.relationshipType == RelationshipTypes.edit && + // We want to set state the in-memory cache for the room with the new event. + // To do this, we have to respect to not save edits, unless they edit the + // current last event. + // Additionally, we only store the event in-memory if the room has either been + // post-loaded or the event is animportant state event. + final noMessageOrNoEdit = stateEvent.type != EventTypes.Message || + stateEvent.relationshipType != RelationshipTypes.edit; + final editingLastEvent = + stateEvent.relationshipEventId == room.lastEvent?.eventId; + final consecutiveEdit = + room.lastEvent?.relationshipType == RelationshipTypes.edit && stateEvent.relationshipEventId == - room.lastEvent?.relationshipEventId))) { + room.lastEvent?.relationshipEventId; + final importantOrRoomLoaded = + !room.partial || importantStateEvents.contains(stateEvent.type); + if ((noMessageOrNoEdit || editingLastEvent || consecutiveEdit) && + importantOrRoomLoaded) { room.setState(stateEvent); } } diff --git a/test/client_test.dart b/test/client_test.dart index 7e11af25..7c18bca8 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -348,6 +348,152 @@ void main() { expect(archive[1].name, 'The room name 2'); }); + test('sync state event in-memory handling', () async { + final roomId = '!726s6s6q:example.com'; + final room = matrix.getRoomById(roomId)!; + // put an important state event in-memory + await matrix.handleSync(SyncUpdate.fromJson({ + 'next_batch': 'fakesync', + 'rooms': { + 'join': { + roomId: { + 'state': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.room.name', + 'content': {'name': 'foxies'}, + 'state_key': '', + 'origin_server_ts': 1417731086799, + 'event_id': '66697273743033:example.com' + } + ] + } + } + } + } + })); + expect(room.getState('m.room.name')?.content['name'], 'foxies'); + + // drop an unimportant state event from in-memory handling + await matrix.handleSync(SyncUpdate.fromJson({ + 'next_batch': 'fakesync', + 'rooms': { + 'join': { + roomId: { + 'state': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'com.famedly.custom', + 'content': {'name': 'foxies'}, + 'state_key': '', + 'origin_server_ts': 1417731086799, + 'event_id': '66697273743033:example.com' + } + ] + } + } + } + } + })); + expect(room.getState('com.famedly.custom'), null); + + // persist normal room messages + await matrix.handleSync(SyncUpdate.fromJson({ + 'next_batch': 'fakesync', + 'rooms': { + 'join': { + roomId: { + 'timeline': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.room.message', + 'content': { + 'msgtype': 'm.text', + 'body': 'meow' + }, + 'origin_server_ts': 1417731086799, + 'event_id': '\$last:example.com' + } + ] + } + } + } + } + })); + expect(room.getState('m.room.message')!.content['body'], 'meow'); + + // ignore edits + await matrix.handleSync(SyncUpdate.fromJson({ + 'next_batch': 'fakesync', + 'rooms': { + 'join': { + roomId: { + 'timeline': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.room.message', + 'content': { + 'msgtype': 'm.text', + 'body': '* floooof', + 'm.new_content': { + 'msgtype': 'm.text', + 'body': 'floooof', + }, + 'm.relates_to': { + 'rel_type': 'm.replace', + 'event_id': '\$other:example.com' + }, + }, + 'origin_server_ts': 1417731086799, + 'event_id': '\$edit:example.com' + } + ] + } + } + } + } + })); + expect(room.getState('m.room.message')!.content['body'], 'meow'); + + // accept edits to the last event + await matrix.handleSync(SyncUpdate.fromJson({ + 'next_batch': 'fakesync', + 'rooms': { + 'join': { + roomId: { + 'timeline': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.room.message', + 'content': { + 'msgtype': 'm.text', + 'body': '* floooof', + 'm.new_content': { + 'msgtype': 'm.text', + 'body': 'floooof', + }, + 'm.relates_to': { + 'rel_type': 'm.replace', + 'event_id': '\$last:example.com' + }, + }, + 'origin_server_ts': 1417731086799, + 'event_id': '\$edit:example.com' + } + ] + } + } + } + } + })); + expect(room.getState('m.room.message')!.content['body'], '* floooof'); + }); + test('getProfileFromUserId', () async { final profile = await matrix.getProfileFromUserId('@getme:example.com', getFromRooms: false); From b009ada0acf1d9d18981338c40b601ff6a6f0bc7 Mon Sep 17 00:00:00 2001 From: Sorunome Date: Mon, 6 Dec 2021 11:10:24 +0100 Subject: [PATCH 2/2] fix: Allow consecutive edits for state events in-memory The lastEvent was incorrect when trying to process an edit of an edit. This fixes that by allowing consecutive edits for the last event. --- lib/src/room.dart | 4 +++- test/client_test.dart | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/src/room.dart b/lib/src/room.dart index 20e8abc0..c7117716 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -188,7 +188,9 @@ class Room { state.relationshipType == RelationshipTypes.edit && lastEvent != null && !state.matchesEventOrTransactionId(lastEvent.eventId) && - lastEvent.eventId != state.relationshipEventId) { + lastEvent.eventId != state.relationshipEventId && + !(lastEvent.relationshipType == RelationshipTypes.edit && + lastEvent.relationshipEventId == state.relationshipEventId)) { return; } diff --git a/test/client_test.dart b/test/client_test.dart index 7c18bca8..521f20f6 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -492,6 +492,40 @@ void main() { } })); expect(room.getState('m.room.message')!.content['body'], '* floooof'); + + // accepts a consecutive edit + await matrix.handleSync(SyncUpdate.fromJson({ + 'next_batch': 'fakesync', + 'rooms': { + 'join': { + roomId: { + 'timeline': { + 'events': [ + { + 'sender': '@alice:example.com', + 'type': 'm.room.message', + 'content': { + 'msgtype': 'm.text', + 'body': '* foxies', + 'm.new_content': { + 'msgtype': 'm.text', + 'body': 'foxies', + }, + 'm.relates_to': { + 'rel_type': 'm.replace', + 'event_id': '\$last:example.com' + }, + }, + 'origin_server_ts': 1417731086799, + 'event_id': '\$edit2:example.com' + } + ] + } + } + } + } + })); + expect(room.getState('m.room.message')!.content['body'], '* foxies'); }); test('getProfileFromUserId', () async {