storeEventUpdate method

  1. @override
Future<void> storeEventUpdate(
  1. EventUpdate eventUpdate,
  2. Client client
)
override

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;

  final tmpRoom = client.getRoomById(eventUpdate.roomID) ??
      Room(id: eventUpdate.roomID, client: client);

  // In case of this is a redaction event
  if (eventUpdate.content['type'] == EventTypes.Redaction) {
    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(
          TupleKey(eventUpdate.roomID, event.eventId).toString(),
          event.toJson());

      if (tmpRoom.lastEvent?.eventId == event.eventId) {
        await _roomStateBox.put(
          TupleKey(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 prevEvent = await _eventsBox
        .get(TupleKey(eventUpdate.roomID, eventId).toString());
    final prevStatus = prevEvent == null
        ? null
        : () {
            final json = copyMap(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(TupleKey(eventUpdate.roomID, eventId).toString(),
        eventUpdate.content);

    // Update timeline fragments
    final key = TupleKey(eventUpdate.roomID, status.isSent ? '' : 'SENDING')
        .toString();

    final eventIds =
        List<String>.from(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 = TupleKey(eventUpdate.roomID, 'SENDING').toString();
      final eventIds =
          List<String>.from(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(
          TupleKey(
            eventUpdate.roomID,
            eventUpdate.content['state_key'],
          ).toString(),
          eventUpdate.content);
    } else {
      final key = TupleKey(
        eventUpdate.roomID,
        eventUpdate.content['type'],
      ).toString();
      final stateMap = copyMap(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(
      TupleKey(
        eventUpdate.roomID,
        eventUpdate.content['type'],
      ).toString(),
      eventUpdate.content,
    );
  }
}