Merge branch 'krille/refactor-sync-function' into 'main'

Refactor Sync and remove pure JSON usage

See merge request famedly/company/frontend/famedlysdk!1062
This commit is contained in:
Krille Fear 2022-06-29 10:47:59 +00:00
commit 248aba1199
2 changed files with 148 additions and 117 deletions

View File

@ -1666,29 +1666,29 @@ class Client extends MatrixApi {
progress: ++handledRooms / rooms.length, progress: ++handledRooms / rooms.length,
)); ));
final id = entry.key; final id = entry.key;
final room = entry.value; final syncRoomUpdate = entry.value;
await database?.storeRoomUpdate(id, room, this); await database?.storeRoomUpdate(id, syncRoomUpdate, this);
_updateRoomsByRoomUpdate(id, room); final room = _updateRoomsByRoomUpdate(id, syncRoomUpdate);
/// Handle now all room events and save them in the database /// Handle now all room events and save them in the database
if (room is JoinedRoomUpdate) { if (syncRoomUpdate is JoinedRoomUpdate) {
final state = room.state; final state = syncRoomUpdate.state;
if (state != null && state.isNotEmpty) { if (state != null && state.isNotEmpty) {
// TODO: This method seems to be comperatively slow for some updates // TODO: This method seems to be comperatively slow for some updates
await _handleRoomEvents( await _handleRoomEvents(
id, room,
state.map((i) => i.toJson()).toList(), state,
EventUpdateType.state, EventUpdateType.state,
); );
} }
final timelineEvents = room.timeline?.events; final timelineEvents = syncRoomUpdate.timeline?.events;
if (timelineEvents != null && timelineEvents.isNotEmpty) { if (timelineEvents != null && timelineEvents.isNotEmpty) {
await _handleRoomEvents( await _handleRoomEvents(
id, room,
timelineEvents.map((i) => i.toJson()).toList(), timelineEvents,
direction != null direction != null
? (direction == Direction.b ? (direction == Direction.b
? EventUpdateType.history ? EventUpdateType.history
@ -1696,73 +1696,72 @@ class Client extends MatrixApi {
: EventUpdateType.timeline); : EventUpdateType.timeline);
} }
final ephemeral = room.ephemeral; final ephemeral = syncRoomUpdate.ephemeral;
if (ephemeral != null && ephemeral.isNotEmpty) { if (ephemeral != null && ephemeral.isNotEmpty) {
// TODO: This method seems to be comperatively slow for some updates // TODO: This method seems to be comperatively slow for some updates
await _handleEphemerals( await _handleEphemerals(
id, ephemeral.map((i) => i.toJson()).toList()); room,
ephemeral,
);
} }
final accountData = room.accountData; final accountData = syncRoomUpdate.accountData;
if (accountData != null && accountData.isNotEmpty) { if (accountData != null && accountData.isNotEmpty) {
await _handleRoomEvents( await _handleRoomEvents(
id, room,
accountData.map((i) => i.toJson()).toList(), accountData,
EventUpdateType.accountData); EventUpdateType.accountData,
);
} }
} }
if (room is LeftRoomUpdate) { if (syncRoomUpdate is LeftRoomUpdate) {
final timelineEvents = room.timeline?.events; final timelineEvents = syncRoomUpdate.timeline?.events;
if (timelineEvents != null && timelineEvents.isNotEmpty) { if (timelineEvents != null && timelineEvents.isNotEmpty) {
await _handleRoomEvents( await _handleRoomEvents(
id, room,
timelineEvents.map((i) => i.toJson()).toList(), timelineEvents,
EventUpdateType.timeline, EventUpdateType.timeline,
); );
} }
final accountData = room.accountData; final accountData = syncRoomUpdate.accountData;
if (accountData != null && accountData.isNotEmpty) { if (accountData != null && accountData.isNotEmpty) {
await _handleRoomEvents( await _handleRoomEvents(
id, room,
accountData.map((i) => i.toJson()).toList(), accountData,
EventUpdateType.accountData); EventUpdateType.accountData,
);
} }
final state = room.state; final state = syncRoomUpdate.state;
if (state != null && state.isNotEmpty) { if (state != null && state.isNotEmpty) {
await _handleRoomEvents( await _handleRoomEvents(room, state, EventUpdateType.state);
id, state.map((i) => i.toJson()).toList(), EventUpdateType.state);
} }
} }
if (room is InvitedRoomUpdate) { if (syncRoomUpdate is InvitedRoomUpdate) {
final state = room.inviteState; final state = syncRoomUpdate.inviteState;
if (state != null && state.isNotEmpty) { if (state != null && state.isNotEmpty) {
await _handleRoomEvents(id, state.map((i) => i.toJson()).toList(), await _handleRoomEvents(room, state, EventUpdateType.inviteState);
EventUpdateType.inviteState);
} }
} }
} }
} }
Future<void> _handleEphemerals(String id, List<dynamic> events) async { Future<void> _handleEphemerals(Room room, List<BasicRoomEvent> events) async {
for (final event in events) { for (final event in events) {
await _handleEvent(event, id, EventUpdateType.ephemeral); await _handleRoomEvents(room, [event], EventUpdateType.ephemeral);
// Receipt events are deltas between two states. We will create a // Receipt events are deltas between two states. We will create a
// fake room account data event for this and store the difference // fake room account data event for this and store the difference
// there. // there.
if (event['type'] == 'm.receipt') { if (event.type == 'm.receipt') {
var room = getRoomById(id);
room ??= Room(id: id, client: this);
final receiptStateContent = final receiptStateContent =
room.roomAccountData['m.receipt']?.content ?? {}; room.roomAccountData['m.receipt']?.content ?? {};
for (final eventEntry in event['content'].entries) { for (final eventEntry in event.content.entries) {
final String eventID = eventEntry.key; final eventID = eventEntry.key;
if (event['content'][eventID]['m.read'] != null) { if (event.content[eventID]['m.read'] != null) {
final Map<String, dynamic> userTimestampMap = final Map<String, dynamic> userTimestampMap =
event['content'][eventID]['m.read']; event.content[eventID]['m.read'];
for (final userTimestampMapEntry in userTimestampMap.entries) { for (final userTimestampMapEntry in userTimestampMap.entries) {
final mxid = userTimestampMapEntry.key; final mxid = userTimestampMapEntry.key;
@ -1783,48 +1782,48 @@ class Client extends MatrixApi {
} }
} }
} }
event['content'] = receiptStateContent; event.content = receiptStateContent;
await _handleEvent(event, id, EventUpdateType.accountData); await _handleRoomEvents(room, [event], EventUpdateType.accountData);
} }
} }
} }
Future<void> _handleRoomEvents( Future<void> _handleRoomEvents(
String chat_id, List<dynamic> events, EventUpdateType type) async { Room room,
for (final event in events) { List<BasicEvent> events,
await _handleEvent(event, chat_id, type); EventUpdateType type,
} ) async {
} // Calling events can be omitted if they are outdated from the same sync. So
// we collect them first before we handle them.
final callEvents = <Event>{};
Future<void> _handleEvent( for (final event in events) {
Map<String, dynamic> event, String roomID, EventUpdateType type) async {
if (event['type'] is String && event['content'] is Map<String, dynamic>) {
// The client must ignore any new m.room.encryption event to prevent // The client must ignore any new m.room.encryption event to prevent
// man-in-the-middle attacks! // man-in-the-middle attacks!
final room = getRoomById(roomID); if ((event.type == EventTypes.Encryption &&
if (room == null || room.encrypted &&
(event['type'] == EventTypes.Encryption && event.content['algorithm'] !=
room.encrypted && room.getState(EventTypes.Encryption)?.content['algorithm'])) {
event['content']['algorithm'] !=
room.getState(EventTypes.Encryption)?.content['algorithm'])) {
return; return;
} }
var update = EventUpdate(roomID: roomID, type: type, content: event); var update =
if (event['type'] == EventTypes.Encrypted && encryptionEnabled) { EventUpdate(roomID: room.id, type: type, content: event.toJson());
if (event.type == EventTypes.Encrypted && encryptionEnabled) {
update = await update.decrypt(room); update = await update.decrypt(room);
} }
if (event['type'] == EventTypes.Message && if (event.type == EventTypes.Message &&
!room.isDirectChat && !room.isDirectChat &&
database != null && database != null &&
room.getState(EventTypes.RoomMember, event['sender']) == null) { event is MatrixEvent &&
room.getState(EventTypes.RoomMember, event.senderId) == null) {
// In order to correctly render room list previews we need to fetch the member from the database // In order to correctly render room list previews we need to fetch the member from the database
final user = await database?.getUser(event['sender'], room); final user = await database?.getUser(event.senderId, room);
if (user != null) { if (user != null) {
room.setState(user); room.setState(user);
} }
} }
_updateRoomsByEventUpdate(update); _updateRoomsByEventUpdate(room, update);
if (type != EventUpdateType.ephemeral) { if (type != EventUpdateType.ephemeral) {
await database?.storeEventUpdate(update, this); await database?.storeEventUpdate(update, this);
} }
@ -1833,47 +1832,78 @@ class Client extends MatrixApi {
} }
onEvent.add(update); onEvent.add(update);
final rawUnencryptedEvent = update.content;
if (prevBatch != null && type == EventUpdateType.timeline) { if (prevBatch != null && type == EventUpdateType.timeline) {
if (rawUnencryptedEvent['type'] == EventTypes.CallInvite) { if (update.content.tryGet<String>('type')?.startsWith('m.call.') ??
onCallInvite.add(Event.fromJson(rawUnencryptedEvent, room)); false) {
} else if (rawUnencryptedEvent['type'] == EventTypes.CallHangup) { final callEvent = Event.fromJson(update.content, room);
onCallHangup.add(Event.fromJson(rawUnencryptedEvent, room)); final callId = callEvent.content.tryGet<String>('call_id');
} else if (rawUnencryptedEvent['type'] == EventTypes.CallAnswer) { callEvents.add(callEvent);
onCallAnswer.add(Event.fromJson(rawUnencryptedEvent, room));
} else if (rawUnencryptedEvent['type'] == EventTypes.CallCandidates) { // Call Invites should be omitted for a call that is already answered,
onCallCandidates.add(Event.fromJson(rawUnencryptedEvent, room)); // has ended, is rejectd or replaced.
} else if (rawUnencryptedEvent['type'] == EventTypes.CallSelectAnswer) { const callEndedEventTypes = {
onCallSelectAnswer.add(Event.fromJson(rawUnencryptedEvent, room)); EventTypes.CallAnswer,
} else if (rawUnencryptedEvent['type'] == EventTypes.CallReject) { EventTypes.CallHangup,
onCallReject.add(Event.fromJson(rawUnencryptedEvent, room)); EventTypes.CallReject,
} else if (rawUnencryptedEvent['type'] == EventTypes.CallNegotiate) { EventTypes.CallReplaces,
onCallNegotiate.add(Event.fromJson(rawUnencryptedEvent, room)); };
} else if (rawUnencryptedEvent['type'] == EventTypes.CallReplaces) { const ommitWhenCallEndedTypes = {
onCallReplaces.add(Event.fromJson(rawUnencryptedEvent, room)); EventTypes.CallInvite,
} else if (rawUnencryptedEvent['type'] == EventTypes.CallCandidates,
EventTypes.CallAssertedIdentity || EventTypes.CallNegotiate,
rawUnencryptedEvent['type'] == EventTypes.CallSDPStreamMetadataChanged,
EventTypes.CallAssertedIdentityPrefix) { EventTypes.CallSDPStreamMetadataChangedPrefix,
onAssertedIdentityReceived };
.add(Event.fromJson(rawUnencryptedEvent, room));
} else if (rawUnencryptedEvent['type'] == if (callEndedEventTypes.contains(callEvent.type)) {
EventTypes.CallSDPStreamMetadataChanged || callEvents.removeWhere((event) {
rawUnencryptedEvent['type'] == if (ommitWhenCallEndedTypes.contains(event.type) &&
EventTypes.CallSDPStreamMetadataChangedPrefix) { event.content.tryGet<String>('call_id') == callId) {
onSDPStreamMetadataChangedReceived Logs().v(
.add(Event.fromJson(rawUnencryptedEvent, room)); 'Ommit "${event.type}" event for an already terminated call');
// TODO(duan): Only used (org.matrix.msc3401.call) during the current test, return true;
// need to add GroupCallPrefix in matrix_api_lite }
} else if (rawUnencryptedEvent['type'] == EventTypes.GroupCallPrefix) { return false;
onGroupCallRequest.add(Event.fromJson(rawUnencryptedEvent, room)); });
}
} }
} }
} }
callEvents.forEach(_callStreamByCallEvent);
} }
void _updateRoomsByRoomUpdate(String roomId, SyncRoomUpdate chatUpdate) { void _callStreamByCallEvent(Event event) {
if (event.type == EventTypes.CallInvite) {
onCallInvite.add(event);
} else if (event.type == EventTypes.CallHangup) {
onCallHangup.add(event);
} else if (event.type == EventTypes.CallAnswer) {
onCallAnswer.add(event);
} else if (event.type == EventTypes.CallCandidates) {
onCallCandidates.add(event);
} else if (event.type == EventTypes.CallSelectAnswer) {
onCallSelectAnswer.add(event);
} else if (event.type == EventTypes.CallReject) {
onCallReject.add(event);
} else if (event.type == EventTypes.CallNegotiate) {
onCallNegotiate.add(event);
} else if (event.type == EventTypes.CallReplaces) {
onCallReplaces.add(event);
} else if (event.type == EventTypes.CallAssertedIdentity ||
event.type == EventTypes.CallAssertedIdentityPrefix) {
onAssertedIdentityReceived.add(event);
} else if (event.type == EventTypes.CallSDPStreamMetadataChanged ||
event.type == EventTypes.CallSDPStreamMetadataChangedPrefix) {
onSDPStreamMetadataChangedReceived.add(event);
// TODO(duan): Only used (org.matrix.msc3401.call) during the current test,
// need to add GroupCallPrefix in matrix_api_lite
} else if (event.type == EventTypes.GroupCallPrefix) {
onGroupCallRequest.add(event);
}
}
Room _updateRoomsByRoomUpdate(String roomId, SyncRoomUpdate chatUpdate) {
// Update the chat list item. // Update the chat list item.
// Search the room in the rooms // Search the room in the rooms
final roomIndex = rooms.indexWhere((r) => r.id == roomId); final roomIndex = rooms.indexWhere((r) => r.id == roomId);
@ -1884,24 +1914,27 @@ class Client extends MatrixApi {
? Membership.invite ? Membership.invite
: Membership.join; : Membership.join;
final room = found
? rooms[roomIndex]
: (chatUpdate is JoinedRoomUpdate
? Room(
id: roomId,
membership: membership,
prev_batch: chatUpdate.timeline?.prevBatch,
highlightCount:
chatUpdate.unreadNotifications?.highlightCount ?? 0,
notificationCount:
chatUpdate.unreadNotifications?.notificationCount ?? 0,
summary: chatUpdate.summary,
client: this,
)
: Room(id: roomId, membership: membership, client: this));
// Does the chat already exist in the list rooms? // Does the chat already exist in the list rooms?
if (!found && membership != Membership.leave) { if (!found && membership != Membership.leave) {
final position = membership == Membership.invite ? 0 : rooms.length; final position = membership == Membership.invite ? 0 : rooms.length;
// Add the new chat to the list // Add the new chat to the list
final newRoom = chatUpdate is JoinedRoomUpdate rooms.insert(position, room);
? Room(
id: roomId,
membership: membership,
prev_batch: chatUpdate.timeline?.prevBatch,
highlightCount:
chatUpdate.unreadNotifications?.highlightCount ?? 0,
notificationCount:
chatUpdate.unreadNotifications?.notificationCount ?? 0,
summary: chatUpdate.summary,
client: this,
)
: Room(id: roomId, membership: membership, client: this);
rooms.insert(position, newRoom);
} }
// If the membership is "leave" then remove the item and stop here // If the membership is "leave" then remove the item and stop here
else if (found && membership == Membership.leave) { else if (found && membership == Membership.leave) {
@ -1940,14 +1973,12 @@ class Client extends MatrixApi {
runInRoot(rooms[roomIndex].requestHistory); runInRoot(rooms[roomIndex].requestHistory);
} }
} }
return room;
} }
void _updateRoomsByEventUpdate(EventUpdate eventUpdate) { void _updateRoomsByEventUpdate(Room room, EventUpdate eventUpdate) {
if (eventUpdate.type == EventUpdateType.history) return; if (eventUpdate.type == EventUpdateType.history) return;
final room = getRoomById(eventUpdate.roomID);
if (room == null) return;
switch (eventUpdate.type) { switch (eventUpdate.type) {
case EventUpdateType.timeline: case EventUpdateType.timeline:
case EventUpdateType.state: case EventUpdateType.state:

View File

@ -239,7 +239,7 @@ void main() {
final eventUpdateList = await eventUpdateListFuture; final eventUpdateList = await eventUpdateListFuture;
expect(eventUpdateList.length, 14); expect(eventUpdateList.length, 18);
expect(eventUpdateList[0].content['type'], 'm.room.member'); expect(eventUpdateList[0].content['type'], 'm.room.member');
expect(eventUpdateList[0].roomID, '!726s6s6q:example.com'); expect(eventUpdateList[0].roomID, '!726s6s6q:example.com');