refactor: BREAKING Migrate database to new lastEvent storage method

This changes the way how the last event is stored
for each room. It is now stored next to the
room event itself in the rooms box and no longer
stored like a room state. This way we need to
bump the database version which will cause an
inital sync for the client. Be aware of this when
updating the SDK!
This commit is contained in:
Krille 2024-03-14 15:22:35 +01:00
parent 20073ddd49
commit b90b902218
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652
6 changed files with 25 additions and 60 deletions

View File

@ -216,8 +216,6 @@ class Client extends MatrixApi {
importantStateEvents.addAll([
EventTypes.RoomName,
EventTypes.RoomAvatar,
EventTypes.Message,
EventTypes.Encrypted,
EventTypes.Encryption,
EventTypes.RoomCanonicalAlias,
EventTypes.RoomTombstone,
@ -2507,7 +2505,8 @@ class Client extends MatrixApi {
// Is this event redacting the last event?
if (event.type == EventTypes.Redaction &&
event.redacts == room.lastEvent?.eventId) {
(event.content.tryGet<String>('redacts') ?? event.redacts) ==
room.lastEvent?.eventId) {
room.lastEvent?.setRedactionEvent(event);
break;
}

View File

@ -35,7 +35,7 @@ import 'package:matrix/src/utils/run_benchmarked.dart';
/// This database does not support file caching!
class HiveCollectionsDatabase extends DatabaseApi {
static const int version = 6;
static const int version = 7;
final String name;
final String? path;
final HiveCipher? key;
@ -1125,10 +1125,8 @@ class HiveCollectionsDatabase extends DatabaseApi {
await removeEvent(transactionId, eventUpdate.roomID);
}
}
final stateKey =
client.roomPreviewLastEvents.contains(eventUpdate.content['type'])
? ''
: eventUpdate.content['state_key'];
final stateKey = eventUpdate.content['state_key'];
// Store a common state event
if (stateKey != null) {
if (eventUpdate.content['type'] == EventTypes.RoomMember) {

View File

@ -41,7 +41,7 @@ import 'package:matrix/src/utils/run_benchmarked.dart';
@Deprecated(
'Use [HiveCollectionsDatabase] instead. Don\'t forget to properly migrate!')
class FamedlySdkHiveDatabase extends DatabaseApi with ZoneTransactionMixin {
static const int version = 5;
static const int version = 6;
final String name;
late Box _clientBox;
late Box _accountDataBox;
@ -1057,10 +1057,7 @@ class FamedlySdkHiveDatabase extends DatabaseApi with ZoneTransactionMixin {
}
}
final stateKey =
client.roomPreviewLastEvents.contains(eventUpdate.content['type'])
? ''
: eventUpdate.content['state_key'];
final stateKey = eventUpdate.content['state_key'];
// Store a common state event
if (stateKey != null) {
if (eventUpdate.content['type'] == EventTypes.RoomMember) {

View File

@ -37,7 +37,7 @@ import 'package:matrix/src/database/indexeddb_box.dart'
if (dart.library.io) 'package:matrix/src/database/sqflite_box.dart';
class MatrixSdkDatabase extends DatabaseApi {
static const int version = 6;
static const int version = 7;
final String name;
late BoxCollection _collection;
late Box<String> _clientBox;
@ -1084,10 +1084,7 @@ class MatrixSdkDatabase extends DatabaseApi {
}
}
final stateKey =
client.roomPreviewLastEvents.contains(eventUpdate.content['type'])
? ''
: eventUpdate.content['state_key'];
final stateKey = eventUpdate.content['state_key'];
// Store a common state event
if (stateKey != null) {
if (eventUpdate.content['type'] == EventTypes.RoomMember) {

View File

@ -170,18 +170,14 @@ class Room {
Logs().w('Tried to set state event for wrong room!');
return;
}
if (stateKey == null && !client.importantStateEvents.contains(state.type)) {
if (stateKey == null) {
Logs().w(
'Tried to set a non state event with type "${state.type}" as state event for a room',
);
return;
}
// We still store events without a state key to load legacy lastEvent
// candidates from the database. This can be changed once we either
// implemented a database migration for legacy lastEvent candidates or
// we just waited some time (written at March 13th 2024).
(states[state.type] ??= {})[stateKey ?? ''] = state;
(states[state.type] ??= {})[stateKey] = state;
client.onRoomState.add(state);
}
@ -374,43 +370,21 @@ class Room {
Event? get lastEvent {
if (_lastEvent != null) return _lastEvent;
// !Everything below is the deprecated way of fetching the last event!
// as lastEvent calculation is based on the state events we unfortunately cannot
// use sortOrder here: With many state events we just know which ones are the
// newest ones, without knowing in which order they actually happened. As such,
// using the origin_server_ts is the best guess for this algorithm. While not
// perfect, it is only used for the room preview in the room list and sorting
// said room list, so it should be good enough.
// Just pick the newest state event as an indicator for when the last
// activity was in this room. This is better than nothing:
var lastTime = DateTime.fromMillisecondsSinceEpoch(0);
final lastEvents =
client.roomPreviewLastEvents.map(getState).whereType<Event>();
Event? lastEvent;
states.forEach((final String key, final entry) {
final state = entry[''];
if (state == null) return;
if (state.originServerTs.millisecondsSinceEpoch >
lastTime.millisecondsSinceEpoch) {
lastTime = state.originServerTs;
lastEvent = state;
}
});
var lastEvent = lastEvents.isEmpty
? null
: lastEvents.reduce((a, b) {
if (a.originServerTs == b.originServerTs) {
// if two events have the same sort order we want to give encrypted events a lower priority
// This is so that if the same event exists in the state both encrypted *and* unencrypted,
// the unencrypted one is picked
return a.type == EventTypes.Encrypted ? b : a;
}
return a.originServerTs.millisecondsSinceEpoch >
b.originServerTs.millisecondsSinceEpoch
? a
: b;
});
if (lastEvent == null) {
states.forEach((final String key, final entry) {
final state = entry[''];
if (state == null) return;
if (state.originServerTs.millisecondsSinceEpoch >
lastTime.millisecondsSinceEpoch) {
lastTime = state.originServerTs;
lastEvent = state;
}
});
}
return lastEvent;
}

View File

@ -1292,7 +1292,7 @@ void main() {
},
room,
));
expect(room.getState('m.room.message') == null, false);
expect(room.getState('m.room.message') == null, true);
});
test('Widgets', () {