storeEventUpdate method
Stores an EventUpdate object in the database. Must be called inside of transaction.
Implementation
@override
Future<void> storeEventUpdate(EventUpdate eventUpdate, Client client) async {
// Ephemerals should not be stored
if (eventUpdate.type == EventUpdateType.ephemeral) return;
// In case of this is a redaction event
if (eventUpdate.content['type'] == EventTypes.Redaction) {
final tmpRoom = client.getRoomById(eventUpdate.roomID) ??
Room(id: eventUpdate.roomID, client: client);
final eventId = eventUpdate.content.tryGet<String>('redacts');
final event =
eventId != null ? await getEventById(eventId, tmpRoom) : null;
if (event != null) {
event.setRedactionEvent(Event.fromJson(eventUpdate.content, tmpRoom));
await _eventsBox.put(
MultiKey(eventUpdate.roomID, event.eventId).toString(),
event.toJson());
if (tmpRoom.lastEvent?.eventId == event.eventId) {
await _roomStateBox.put(
MultiKey(eventUpdate.roomID, event.type).toString(),
{'': event.toJson()},
);
}
}
}
// Store a common message event
if ({
EventUpdateType.timeline,
EventUpdateType.history,
EventUpdateType.decryptedTimelineQueue
}.contains(eventUpdate.type)) {
final eventId = eventUpdate.content['event_id'];
// Is this ID already in the store?
final Map? prevEvent = await _eventsBox
.get(MultiKey(eventUpdate.roomID, eventId).toString());
final prevStatus = prevEvent == null
? null
: () {
final json = convertToJson(prevEvent);
final statusInt = json.tryGet<int>('status') ??
json
.tryGetMap<String, dynamic>('unsigned')
?.tryGet<int>(messageSendingStatusKey);
return statusInt == null ? null : eventStatusFromInt(statusInt);
}();
// calculate the status
final newStatus = eventStatusFromInt(
eventUpdate.content.tryGet<int>('status') ??
eventUpdate.content
.tryGetMap<String, dynamic>('unsigned')
?.tryGet<int>(messageSendingStatusKey) ??
EventStatus.synced.intValue,
);
// Is this the response to a sending event which is already synced? Then
// there is nothing to do here.
if (!newStatus.isSynced && prevStatus != null && prevStatus.isSynced) {
return;
}
final status = newStatus.isError || prevStatus == null
? newStatus
: latestEventStatus(
prevStatus,
newStatus,
);
// Add the status and the sort order to the content so it get stored
eventUpdate.content['unsigned'] ??= <String, dynamic>{};
eventUpdate.content['unsigned'][messageSendingStatusKey] =
eventUpdate.content['status'] = status.intValue;
// In case this event has sent from this account we have a transaction ID
final transactionId = eventUpdate.content
.tryGetMap<String, dynamic>('unsigned')
?.tryGet<String>('transaction_id');
await _eventsBox.put(MultiKey(eventUpdate.roomID, eventId).toString(),
eventUpdate.content);
// Update timeline fragments
final key = MultiKey(eventUpdate.roomID, status.isSent ? '' : 'SENDING')
.toString();
final List eventIds = (await _timelineFragmentsBox.get(key) ?? []);
if (!eventIds.contains(eventId)) {
if (eventUpdate.type == EventUpdateType.history) {
eventIds.add(eventId);
} else {
eventIds.insert(0, eventId);
}
await _timelineFragmentsBox.put(key, eventIds);
} else if (status.isSynced &&
prevStatus != null &&
prevStatus.isSent &&
eventUpdate.type != EventUpdateType.history) {
// Status changes from 1 -> 2? Make sure event is correctly sorted.
eventIds.remove(eventId);
eventIds.insert(0, eventId);
}
// If event comes from server timeline, remove sending events with this ID
if (status.isSent) {
final key = MultiKey(eventUpdate.roomID, 'SENDING').toString();
final List eventIds = (await _timelineFragmentsBox.get(key) ?? []);
final i = eventIds.indexWhere((id) => id == eventId);
if (i != -1) {
await _timelineFragmentsBox.put(key, eventIds..removeAt(i));
}
}
// Is there a transaction id? Then delete the event with this id.
if (!status.isError && !status.isSending && transactionId != null) {
await removeEvent(transactionId, eventUpdate.roomID);
}
}
final stateKey =
client.roomPreviewLastEvents.contains(eventUpdate.content['type'])
? ''
: eventUpdate.content['state_key'];
// Store a common state event
if ({
EventUpdateType.timeline,
EventUpdateType.state,
EventUpdateType.inviteState
}.contains(eventUpdate.type) &&
stateKey != null) {
if (eventUpdate.content['type'] == EventTypes.RoomMember) {
await _roomMembersBox.put(
MultiKey(
eventUpdate.roomID,
eventUpdate.content['state_key'],
).toString(),
eventUpdate.content);
} else {
final key = MultiKey(
eventUpdate.roomID,
eventUpdate.content['type'],
).toString();
final Map stateMap = await _roomStateBox.get(key) ?? {};
// store state events and new messages, that either are not an edit or an edit of the lastest message
// An edit is an event, that has an edit relation to the latest event. In some cases for the second edit, we need to compare if both have an edit relation to the same event instead.
if (eventUpdate.content
.tryGetMap<String, dynamic>('content')
?.tryGetMap<String, dynamic>('m.relates_to') ==
null) {
stateMap[stateKey] = eventUpdate.content;
await _roomStateBox.put(key, stateMap);
} else {
final editedEventRelationshipEventId = eventUpdate.content
.tryGetMap<String, dynamic>('content')
?.tryGetMap<String, dynamic>('m.relates_to')
?.tryGet<String>('event_id');
final tmpRoom = client.getRoomById(eventUpdate.roomID) ??
Room(id: eventUpdate.roomID, client: client);
if (eventUpdate.content['type'] !=
EventTypes
.Message || // send anything other than a message
eventUpdate.content
.tryGetMap<String, dynamic>('content')
?.tryGetMap<String, dynamic>('m.relates_to')
?.tryGet<String>('rel_type') !=
RelationshipTypes
.edit || // replies are always latest anyway
editedEventRelationshipEventId ==
tmpRoom.lastEvent
?.eventId || // edit of latest (original event) event
(tmpRoom.lastEvent?.relationshipType ==
RelationshipTypes.edit &&
editedEventRelationshipEventId ==
tmpRoom.lastEvent
?.relationshipEventId) // edit of latest (edited event) event
) {
stateMap[stateKey] = eventUpdate.content;
await _roomStateBox.put(key, stateMap);
}
}
}
}
// Store a room account data event
if (eventUpdate.type == EventUpdateType.accountData) {
await _roomAccountDataBox.put(
MultiKey(
eventUpdate.roomID,
eventUpdate.content['type'],
).toString(),
eventUpdate.content,
);
}
}