LCOV - code coverage report
Current view: top level - lib/src/database - matrix_sdk_database.dart (source / functions) Hit Total Coverage
Test: merged.info Lines: 555 690 80.4 %
Date: 2024-05-13 12:56:47 Functions: 0 0 -

          Line data    Source code
       1             : /*
       2             :  *   Famedly Matrix SDK
       3             :  *   Copyright (C) 2019, 2020, 2021 Famedly GmbH
       4             :  *
       5             :  *   This program is free software: you can redistribute it and/or modify
       6             :  *   it under the terms of the GNU Affero General Public License as
       7             :  *   published by the Free Software Foundation, either version 3 of the
       8             :  *   License, or (at your option) any later version.
       9             :  *
      10             :  *   This program is distributed in the hope that it will be useful,
      11             :  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      13             :  *   GNU Affero General Public License for more details.
      14             :  *
      15             :  *   You should have received a copy of the GNU Affero General Public License
      16             :  *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
      17             :  */
      18             : 
      19             : import 'dart:async';
      20             : import 'dart:convert';
      21             : import 'dart:math';
      22             : 
      23             : import 'package:sqflite_common/sqflite.dart';
      24             : 
      25             : import 'package:matrix/encryption/utils/olm_session.dart';
      26             : import 'package:matrix/encryption/utils/outbound_group_session.dart';
      27             : import 'package:matrix/encryption/utils/ssss_cache.dart';
      28             : import 'package:matrix/encryption/utils/stored_inbound_group_session.dart';
      29             : import 'package:matrix/matrix.dart';
      30             : import 'package:matrix/src/utils/copy_map.dart';
      31             : import 'package:matrix/src/utils/queued_to_device_event.dart';
      32             : import 'package:matrix/src/utils/run_benchmarked.dart';
      33             : 
      34             : import 'package:matrix/src/database/indexeddb_box.dart'
      35             :     if (dart.library.io) 'package:matrix/src/database/sqflite_box.dart';
      36             : 
      37             : import 'package:matrix/src/database/database_file_storage_stub.dart'
      38             :     if (dart.library.io) 'package:matrix/src/database/database_file_storage_io.dart';
      39             : 
      40             : /// Database based on SQlite3 on native and IndexedDB on web. For native you
      41             : /// have to pass a `Database` object, which can be created with the sqflite
      42             : /// package like this:
      43             : /// ```dart
      44             : /// final database = await openDatabase('path/to/your/database');
      45             : /// ```
      46             : ///
      47             : /// **WARNING**: For android it seems like that the CursorWindow is too small for
      48             : /// large amounts of data if you are using SQFlite. Consider using a different
      49             : ///  package to open the database like
      50             : /// [sqflite_sqlcipher](https://pub.dev/packages/sqflite_sqlcipher) or
      51             : /// [sqflite_common_ffi](https://pub.dev/packages/sqflite_common_ffi).
      52             : /// Learn more at:
      53             : /// https://github.com/famedly/matrix-dart-sdk/issues/1642#issuecomment-1865827227
      54             : class MatrixSdkDatabase extends DatabaseApi with DatabaseFileStorage {
      55             :   static const int version = 7;
      56             :   final String name;
      57             :   late BoxCollection _collection;
      58             :   late Box<String> _clientBox;
      59             :   late Box<Map> _accountDataBox;
      60             :   late Box<Map> _roomsBox;
      61             :   late Box<Map> _toDeviceQueueBox;
      62             : 
      63             :   /// Key is a tuple as TupleKey(roomId, type) where stateKey can be
      64             :   /// an empty string. Must contain only states of type
      65             :   /// client.importantRoomStates.
      66             :   late Box<Map> _preloadRoomStateBox;
      67             : 
      68             :   /// Key is a tuple as TupleKey(roomId, type) where stateKey can be
      69             :   /// an empty string. Must NOT contain states of a type from
      70             :   /// client.importantRoomStates.
      71             :   late Box<Map> _nonPreloadRoomStateBox;
      72             : 
      73             :   /// Key is a tuple as TupleKey(roomId, userId)
      74             :   late Box<Map> _roomMembersBox;
      75             : 
      76             :   /// Key is a tuple as TupleKey(roomId, type)
      77             :   late Box<Map> _roomAccountDataBox;
      78             :   late Box<Map> _inboundGroupSessionsBox;
      79             :   late Box<Map> _outboundGroupSessionsBox;
      80             :   late Box<Map> _olmSessionsBox;
      81             : 
      82             :   /// Key is a tuple as TupleKey(userId, deviceId)
      83             :   late Box<Map> _userDeviceKeysBox;
      84             : 
      85             :   /// Key is the user ID as a String
      86             :   late Box<bool> _userDeviceKeysOutdatedBox;
      87             : 
      88             :   /// Key is a tuple as TupleKey(userId, publicKey)
      89             :   late Box<Map> _userCrossSigningKeysBox;
      90             :   late Box<Map> _ssssCacheBox;
      91             :   late Box<Map> _presencesBox;
      92             : 
      93             :   /// Key is a tuple as Multikey(roomId, fragmentId) while the default
      94             :   /// fragmentId is an empty String
      95             :   late Box<List> _timelineFragmentsBox;
      96             : 
      97             :   /// Key is a tuple as TupleKey(roomId, eventId)
      98             :   late Box<Map> _eventsBox;
      99             : 
     100             :   /// Key is a tuple as TupleKey(userId, deviceId)
     101             :   late Box<String> _seenDeviceIdsBox;
     102             : 
     103             :   late Box<String> _seenDeviceKeysBox;
     104             : 
     105             :   @override
     106             :   final int maxFileSize;
     107             : 
     108             :   // there was a field of type `dart:io:Directory` here. This one broke the
     109             :   // dart js standalone compiler. Migration via URI as file system identifier.
     110           0 :   @Deprecated(
     111             :       'Breaks support for web standalone. Use [fileStorageLocation] instead.')
     112           0 :   Object? get fileStoragePath => fileStorageLocation?.toFilePath();
     113             : 
     114             :   static const String _clientBoxName = 'box_client';
     115             : 
     116             :   static const String _accountDataBoxName = 'box_account_data';
     117             : 
     118             :   static const String _roomsBoxName = 'box_rooms';
     119             : 
     120             :   static const String _toDeviceQueueBoxName = 'box_to_device_queue';
     121             : 
     122             :   static const String _preloadRoomStateBoxName = 'box_preload_room_states';
     123             : 
     124             :   static const String _nonPreloadRoomStateBoxName =
     125             :       'box_non_preload_room_states';
     126             : 
     127             :   static const String _roomMembersBoxName = 'box_room_members';
     128             : 
     129             :   static const String _roomAccountDataBoxName = 'box_room_account_data';
     130             : 
     131             :   static const String _inboundGroupSessionsBoxName =
     132             :       'box_inbound_group_session';
     133             : 
     134             :   static const String _outboundGroupSessionsBoxName =
     135             :       'box_outbound_group_session';
     136             : 
     137             :   static const String _olmSessionsBoxName = 'box_olm_session';
     138             : 
     139             :   static const String _userDeviceKeysBoxName = 'box_user_device_keys';
     140             : 
     141             :   static const String _userDeviceKeysOutdatedBoxName =
     142             :       'box_user_device_keys_outdated';
     143             : 
     144             :   static const String _userCrossSigningKeysBoxName = 'box_cross_signing_keys';
     145             : 
     146             :   static const String _ssssCacheBoxName = 'box_ssss_cache';
     147             : 
     148             :   static const String _presencesBoxName = 'box_presences';
     149             : 
     150             :   static const String _timelineFragmentsBoxName = 'box_timeline_fragments';
     151             : 
     152             :   static const String _eventsBoxName = 'box_events';
     153             : 
     154             :   static const String _seenDeviceIdsBoxName = 'box_seen_device_ids';
     155             : 
     156             :   static const String _seenDeviceKeysBoxName = 'box_seen_device_keys';
     157             : 
     158             :   Database? database;
     159             : 
     160             :   /// Custom IdbFactory used to create the indexedDB. On IO platforms it would
     161             :   /// lead to an error to import "dart:indexed_db" so this is dynamically
     162             :   /// typed.
     163             :   final dynamic idbFactory;
     164             : 
     165             :   /// Custom SQFlite Database Factory used for high level operations on IO
     166             :   /// like delete. Set it if you want to use sqlite FFI.
     167             :   final DatabaseFactory? sqfliteFactory;
     168             : 
     169          32 :   MatrixSdkDatabase(
     170             :     this.name, {
     171             :     this.database,
     172             :     this.idbFactory,
     173             :     this.sqfliteFactory,
     174             :     this.maxFileSize = 0,
     175             :     // TODO : remove deprecated member migration on next major release
     176             :     @Deprecated(
     177             :         'Breaks support for web standalone. Use [fileStorageLocation] instead.')
     178             :     dynamic fileStoragePath,
     179             :     Uri? fileStorageLocation,
     180             :     Duration? deleteFilesAfterDuration,
     181             :   }) {
     182           0 :     final legacyPath = fileStoragePath?.path;
     183          32 :     this.fileStorageLocation = fileStorageLocation ??
     184          32 :         (legacyPath is String ? Uri.tryParse(legacyPath) : null);
     185          32 :     this.deleteFilesAfterDuration = deleteFilesAfterDuration;
     186             :   }
     187             : 
     188          32 :   Future<void> open() async {
     189          64 :     _collection = await BoxCollection.open(
     190          32 :       name,
     191             :       {
     192          32 :         _clientBoxName,
     193          32 :         _accountDataBoxName,
     194          32 :         _roomsBoxName,
     195          32 :         _toDeviceQueueBoxName,
     196          32 :         _preloadRoomStateBoxName,
     197          32 :         _nonPreloadRoomStateBoxName,
     198          32 :         _roomMembersBoxName,
     199          32 :         _roomAccountDataBoxName,
     200          32 :         _inboundGroupSessionsBoxName,
     201          32 :         _outboundGroupSessionsBoxName,
     202          32 :         _olmSessionsBoxName,
     203          32 :         _userDeviceKeysBoxName,
     204          32 :         _userDeviceKeysOutdatedBoxName,
     205          32 :         _userCrossSigningKeysBoxName,
     206          32 :         _ssssCacheBoxName,
     207          32 :         _presencesBoxName,
     208          32 :         _timelineFragmentsBoxName,
     209          32 :         _eventsBoxName,
     210          32 :         _seenDeviceIdsBoxName,
     211          32 :         _seenDeviceKeysBoxName,
     212             :       },
     213          32 :       sqfliteDatabase: database,
     214          32 :       sqfliteFactory: sqfliteFactory,
     215          32 :       idbFactory: idbFactory,
     216             :     );
     217          96 :     _clientBox = _collection.openBox<String>(
     218             :       _clientBoxName,
     219             :     );
     220          96 :     _accountDataBox = _collection.openBox<Map>(
     221             :       _accountDataBoxName,
     222             :     );
     223          96 :     _roomsBox = _collection.openBox<Map>(
     224             :       _roomsBoxName,
     225             :     );
     226          96 :     _preloadRoomStateBox = _collection.openBox(
     227             :       _preloadRoomStateBoxName,
     228             :     );
     229          96 :     _nonPreloadRoomStateBox = _collection.openBox(
     230             :       _nonPreloadRoomStateBoxName,
     231             :     );
     232          96 :     _roomMembersBox = _collection.openBox(
     233             :       _roomMembersBoxName,
     234             :     );
     235          96 :     _toDeviceQueueBox = _collection.openBox(
     236             :       _toDeviceQueueBoxName,
     237             :     );
     238          96 :     _roomAccountDataBox = _collection.openBox(
     239             :       _roomAccountDataBoxName,
     240             :     );
     241          96 :     _inboundGroupSessionsBox = _collection.openBox(
     242             :       _inboundGroupSessionsBoxName,
     243             :     );
     244          96 :     _outboundGroupSessionsBox = _collection.openBox(
     245             :       _outboundGroupSessionsBoxName,
     246             :     );
     247          96 :     _olmSessionsBox = _collection.openBox(
     248             :       _olmSessionsBoxName,
     249             :     );
     250          96 :     _userDeviceKeysBox = _collection.openBox(
     251             :       _userDeviceKeysBoxName,
     252             :     );
     253          96 :     _userDeviceKeysOutdatedBox = _collection.openBox(
     254             :       _userDeviceKeysOutdatedBoxName,
     255             :     );
     256          96 :     _userCrossSigningKeysBox = _collection.openBox(
     257             :       _userCrossSigningKeysBoxName,
     258             :     );
     259          96 :     _ssssCacheBox = _collection.openBox(
     260             :       _ssssCacheBoxName,
     261             :     );
     262          96 :     _presencesBox = _collection.openBox(
     263             :       _presencesBoxName,
     264             :     );
     265          96 :     _timelineFragmentsBox = _collection.openBox(
     266             :       _timelineFragmentsBoxName,
     267             :     );
     268          96 :     _eventsBox = _collection.openBox(
     269             :       _eventsBoxName,
     270             :     );
     271          96 :     _seenDeviceIdsBox = _collection.openBox(
     272             :       _seenDeviceIdsBoxName,
     273             :     );
     274          96 :     _seenDeviceKeysBox = _collection.openBox(
     275             :       _seenDeviceKeysBoxName,
     276             :     );
     277             : 
     278             :     // Check version and check if we need a migration
     279          96 :     final currentVersion = int.tryParse(await _clientBox.get('version') ?? '');
     280             :     if (currentVersion == null) {
     281          96 :       await _clientBox.put('version', version.toString());
     282           0 :     } else if (currentVersion != version) {
     283           0 :       await _migrateFromVersion(currentVersion);
     284             :     }
     285             : 
     286             :     return;
     287             :   }
     288             : 
     289           0 :   Future<void> _migrateFromVersion(int currentVersion) async {
     290           0 :     Logs().i('Migrate store database from version $currentVersion to $version');
     291           0 :     await clearCache();
     292           0 :     await _clientBox.put('version', version.toString());
     293             :   }
     294             : 
     295           8 :   @override
     296          16 :   Future<void> clear() => _collection.clear();
     297             : 
     298           3 :   @override
     299           6 :   Future<void> clearCache() => transaction(() async {
     300           6 :         await _roomsBox.clear();
     301           6 :         await _accountDataBox.clear();
     302           6 :         await _roomAccountDataBox.clear();
     303           6 :         await _preloadRoomStateBox.clear();
     304           6 :         await _nonPreloadRoomStateBox.clear();
     305           6 :         await _roomMembersBox.clear();
     306           6 :         await _eventsBox.clear();
     307           6 :         await _timelineFragmentsBox.clear();
     308           6 :         await _outboundGroupSessionsBox.clear();
     309           6 :         await _presencesBox.clear();
     310           6 :         await _clientBox.delete('prev_batch');
     311             :       });
     312             : 
     313           4 :   @override
     314           8 :   Future<void> clearSSSSCache() => _ssssCacheBox.clear();
     315             : 
     316          20 :   @override
     317          40 :   Future<void> close() async => _collection.close();
     318             : 
     319           2 :   @override
     320             :   Future<void> deleteFromToDeviceQueue(int id) async {
     321           6 :     await _toDeviceQueueBox.delete(id.toString());
     322             :     return;
     323             :   }
     324             : 
     325          30 :   @override
     326             :   Future<void> forgetRoom(String roomId) async {
     327         120 :     await _timelineFragmentsBox.delete(TupleKey(roomId, '').toString());
     328          60 :     final eventsBoxKeys = await _eventsBox.getAllKeys();
     329          30 :     for (final key in eventsBoxKeys) {
     330           0 :       final multiKey = TupleKey.fromString(key);
     331           0 :       if (multiKey.parts.first != roomId) continue;
     332           0 :       await _eventsBox.delete(key);
     333             :     }
     334          60 :     final preloadRoomStateBoxKeys = await _preloadRoomStateBox.getAllKeys();
     335          30 :     for (final key in preloadRoomStateBoxKeys) {
     336           0 :       final multiKey = TupleKey.fromString(key);
     337           0 :       if (multiKey.parts.first != roomId) continue;
     338           0 :       await _preloadRoomStateBox.delete(key);
     339             :     }
     340             :     final nonPreloadRoomStateBoxKeys =
     341          60 :         await _nonPreloadRoomStateBox.getAllKeys();
     342          30 :     for (final key in nonPreloadRoomStateBoxKeys) {
     343           0 :       final multiKey = TupleKey.fromString(key);
     344           0 :       if (multiKey.parts.first != roomId) continue;
     345           0 :       await _nonPreloadRoomStateBox.delete(key);
     346             :     }
     347          60 :     final roomMembersBoxKeys = await _roomMembersBox.getAllKeys();
     348          30 :     for (final key in roomMembersBoxKeys) {
     349           0 :       final multiKey = TupleKey.fromString(key);
     350           0 :       if (multiKey.parts.first != roomId) continue;
     351           0 :       await _roomMembersBox.delete(key);
     352             :     }
     353          60 :     final roomAccountDataBoxKeys = await _roomAccountDataBox.getAllKeys();
     354          30 :     for (final key in roomAccountDataBoxKeys) {
     355           0 :       final multiKey = TupleKey.fromString(key);
     356           0 :       if (multiKey.parts.first != roomId) continue;
     357           0 :       await _roomAccountDataBox.delete(key);
     358             :     }
     359          60 :     await _roomsBox.delete(roomId);
     360             :   }
     361             : 
     362          30 :   @override
     363             :   Future<Map<String, BasicEvent>> getAccountData() =>
     364          30 :       runBenchmarked<Map<String, BasicEvent>>('Get all account data from store',
     365          30 :           () async {
     366          30 :         final accountData = <String, BasicEvent>{};
     367          60 :         final raws = await _accountDataBox.getAllValues();
     368          32 :         for (final entry in raws.entries) {
     369           6 :           accountData[entry.key] = BasicEvent(
     370           2 :             type: entry.key,
     371           4 :             content: copyMap(entry.value),
     372             :           );
     373             :         }
     374             :         return accountData;
     375             :       });
     376             : 
     377          30 :   @override
     378             :   Future<Map<String, dynamic>?> getClient(String name) =>
     379          60 :       runBenchmarked('Get Client from store', () async {
     380          30 :         final map = <String, dynamic>{};
     381          60 :         final keys = await _clientBox.getAllKeys();
     382          60 :         for (final key in keys) {
     383          30 :           if (key == 'version') continue;
     384           4 :           final value = await _clientBox.get(key);
     385           2 :           if (value != null) map[key] = value;
     386             :         }
     387          30 :         if (map.isEmpty) return null;
     388             :         return map;
     389             :       });
     390             : 
     391           8 :   @override
     392             :   Future<Event?> getEventById(String eventId, Room room) async {
     393          40 :     final raw = await _eventsBox.get(TupleKey(room.id, eventId).toString());
     394             :     if (raw == null) return null;
     395           8 :     return Event.fromJson(copyMap(raw), room);
     396             :   }
     397             : 
     398             :   /// Loads a whole list of events at once from the store for a specific room
     399           6 :   Future<List<Event>> _getEventsByIds(List<String> eventIds, Room room) async {
     400             :     final keys = eventIds
     401           6 :         .map(
     402          12 :           (eventId) => TupleKey(room.id, eventId).toString(),
     403             :         )
     404           6 :         .toList();
     405          12 :     final rawEvents = await _eventsBox.getAll(keys);
     406             :     return rawEvents
     407           6 :         .whereType<Map>()
     408          15 :         .map((rawEvent) => Event.fromJson(copyMap(rawEvent), room))
     409           6 :         .toList();
     410             :   }
     411             : 
     412           6 :   @override
     413             :   Future<List<Event>> getEventList(
     414             :     Room room, {
     415             :     int start = 0,
     416             :     bool onlySending = false,
     417             :     int? limit,
     418             :   }) =>
     419          12 :       runBenchmarked<List<Event>>('Get event list', () async {
     420             :         // Get the synced event IDs from the store
     421          18 :         final timelineKey = TupleKey(room.id, '').toString();
     422             :         final timelineEventIds =
     423          15 :             (await _timelineFragmentsBox.get(timelineKey) ?? []);
     424             : 
     425             :         // Get the local stored SENDING events from the store
     426             :         late final List sendingEventIds;
     427           6 :         if (start != 0) {
     428           2 :           sendingEventIds = [];
     429             :         } else {
     430          18 :           final sendingTimelineKey = TupleKey(room.id, 'SENDING').toString();
     431             :           sendingEventIds =
     432          16 :               (await _timelineFragmentsBox.get(sendingTimelineKey) ?? []);
     433             :         }
     434             : 
     435             :         // Combine those two lists while respecting the start and limit parameters.
     436           9 :         final end = min(timelineEventIds.length,
     437           8 :             start + (limit ?? timelineEventIds.length));
     438           6 :         final eventIds = [
     439             :           ...sendingEventIds,
     440          10 :           if (!onlySending && start < timelineEventIds.length)
     441           3 :             ...timelineEventIds.getRange(start, end),
     442             :         ];
     443             : 
     444          12 :         return await _getEventsByIds(eventIds.cast<String>(), room);
     445             :       });
     446             : 
     447           6 :   @override
     448             :   Future<StoredInboundGroupSession?> getInboundGroupSession(
     449             :     String roomId,
     450             :     String sessionId,
     451             :   ) async {
     452          12 :     final raw = await _inboundGroupSessionsBox.get(sessionId);
     453             :     if (raw == null) return null;
     454           6 :     return StoredInboundGroupSession.fromJson(copyMap(raw));
     455             :   }
     456             : 
     457           6 :   @override
     458             :   Future<List<StoredInboundGroupSession>>
     459             :       getInboundGroupSessionsToUpload() async {
     460          12 :     final sessions = (await _inboundGroupSessionsBox.getAllValues())
     461           6 :         .values
     462          21 :         .where((rawSession) => rawSession['uploaded'] == false)
     463           6 :         .take(50)
     464           6 :         .map(
     465          10 :           (json) => StoredInboundGroupSession.fromJson(
     466           5 :             copyMap(json),
     467             :           ),
     468             :         )
     469           6 :         .toList();
     470             :     return sessions;
     471             :   }
     472             : 
     473           2 :   @override
     474             :   Future<List<String>> getLastSentMessageUserDeviceKey(
     475             :       String userId, String deviceId) async {
     476             :     final raw =
     477           8 :         await _userDeviceKeysBox.get(TupleKey(userId, deviceId).toString());
     478           1 :     if (raw == null) return <String>[];
     479           2 :     return <String>[raw['last_sent_message']];
     480             :   }
     481             : 
     482          24 :   @override
     483             :   Future<void> storeOlmSession(String identityKey, String sessionId,
     484             :       String pickle, int lastReceived) async {
     485          96 :     final rawSessions = copyMap((await _olmSessionsBox.get(identityKey)) ?? {});
     486          48 :     rawSessions[sessionId] = {
     487             :       'identity_key': identityKey,
     488             :       'pickle': pickle,
     489             :       'session_id': sessionId,
     490             :       'last_received': lastReceived,
     491             :     };
     492          48 :     await _olmSessionsBox.put(identityKey, rawSessions);
     493             :     return;
     494             :   }
     495             : 
     496          24 :   @override
     497             :   Future<List<OlmSession>> getOlmSessions(
     498             :       String identityKey, String userId) async {
     499          48 :     final rawSessions = await _olmSessionsBox.get(identityKey);
     500          29 :     if (rawSessions == null || rawSessions.isEmpty) return <OlmSession>[];
     501           5 :     return rawSessions.values
     502          20 :         .map((json) => OlmSession.fromJson(copyMap(json), userId))
     503           5 :         .toList();
     504             :   }
     505             : 
     506           2 :   @override
     507             :   Future<Map<String, Map>> getAllOlmSessions() =>
     508           4 :       _olmSessionsBox.getAllValues();
     509             : 
     510          11 :   @override
     511             :   Future<List<OlmSession>> getOlmSessionsForDevices(
     512             :       List<String> identityKeys, String userId) async {
     513          11 :     final sessions = await Future.wait(
     514          33 :         identityKeys.map((identityKey) => getOlmSessions(identityKey, userId)));
     515          33 :     return <OlmSession>[for (final sublist in sessions) ...sublist];
     516             :   }
     517             : 
     518           4 :   @override
     519             :   Future<OutboundGroupSession?> getOutboundGroupSession(
     520             :       String roomId, String userId) async {
     521           8 :     final raw = await _outboundGroupSessionsBox.get(roomId);
     522             :     if (raw == null) return null;
     523           4 :     return OutboundGroupSession.fromJson(copyMap(raw), userId);
     524             :   }
     525             : 
     526           2 :   @override
     527             :   Future<Room?> getSingleRoom(Client client, String roomId,
     528             :       {bool loadImportantStates = true}) async {
     529             :     // Get raw room from database:
     530           4 :     final roomData = await _roomsBox.get(roomId);
     531             :     if (roomData == null) return null;
     532           4 :     final room = Room.fromJson(copyMap(roomData), client);
     533             : 
     534             :     // Get important states:
     535             :     if (loadImportantStates) {
     536           2 :       final dbKeys = client.importantStateEvents
     537           8 :           .map((state) => TupleKey(roomId, state).toString())
     538           2 :           .toList();
     539           4 :       final rawStates = await _preloadRoomStateBox.getAll(dbKeys);
     540           4 :       for (final rawState in rawStates) {
     541           1 :         if (rawState == null || rawState[''] == null) continue;
     542           4 :         room.setState(Event.fromJson(copyMap(rawState['']), room));
     543             :       }
     544             :     }
     545             : 
     546             :     return room;
     547             :   }
     548             : 
     549          30 :   @override
     550             :   Future<List<Room>> getRoomList(Client client) =>
     551          60 :       runBenchmarked<List<Room>>('Get room list from store', () async {
     552          30 :         final rooms = <String, Room>{};
     553             : 
     554          60 :         final rawRooms = await _roomsBox.getAllValues();
     555             : 
     556          32 :         for (final raw in rawRooms.values) {
     557             :           // Get the room
     558           4 :           final room = Room.fromJson(copyMap(raw), client);
     559             : 
     560             :           // Add to the list and continue.
     561           4 :           rooms[room.id] = room;
     562             :         }
     563             : 
     564          60 :         final roomStatesDataRaws = await _preloadRoomStateBox.getAllValues();
     565          31 :         for (final entry in roomStatesDataRaws.entries) {
     566           2 :           final keys = TupleKey.fromString(entry.key);
     567           2 :           final roomId = keys.parts.first;
     568           1 :           final room = rooms[roomId];
     569             :           if (room == null) {
     570           0 :             Logs().w('Found event in store for unknown room', entry.value);
     571             :             continue;
     572             :           }
     573           1 :           final states = entry.value;
     574           1 :           final stateEvents = states.values
     575           4 :               .map((raw) => room.membership == Membership.invite
     576           2 :                   ? StrippedStateEvent.fromJson(copyMap(raw))
     577           2 :                   : Event.fromJson(copyMap(raw), room))
     578           1 :               .toList();
     579           2 :           for (final state in stateEvents) {
     580           1 :             room.setState(state);
     581             :           }
     582             :         }
     583             : 
     584             :         // Get the room account data
     585          60 :         final roomAccountDataRaws = await _roomAccountDataBox.getAllValues();
     586          31 :         for (final entry in roomAccountDataRaws.entries) {
     587           2 :           final keys = TupleKey.fromString(entry.key);
     588           1 :           final basicRoomEvent = BasicRoomEvent.fromJson(
     589           2 :             copyMap(entry.value),
     590             :           );
     591           2 :           final roomId = keys.parts.first;
     592           1 :           if (rooms.containsKey(roomId)) {
     593           4 :             rooms[roomId]!.roomAccountData[basicRoomEvent.type] =
     594             :                 basicRoomEvent;
     595             :           } else {
     596           0 :             Logs().w(
     597           0 :                 'Found account data for unknown room $roomId. Delete now...');
     598           0 :             await _roomAccountDataBox
     599           0 :                 .delete(TupleKey(roomId, basicRoomEvent.type).toString());
     600             :           }
     601             :         }
     602             : 
     603          60 :         return rooms.values.toList();
     604             :       });
     605             : 
     606          24 :   @override
     607             :   Future<SSSSCache?> getSSSSCache(String type) async {
     608          48 :     final raw = await _ssssCacheBox.get(type);
     609             :     if (raw == null) return null;
     610          16 :     return SSSSCache.fromJson(copyMap(raw));
     611             :   }
     612             : 
     613          30 :   @override
     614             :   Future<List<QueuedToDeviceEvent>> getToDeviceEventQueue() async {
     615          60 :     final raws = await _toDeviceQueueBox.getAllValues();
     616          62 :     final copiedRaws = raws.entries.map((entry) {
     617           4 :       final copiedRaw = copyMap(entry.value);
     618           6 :       copiedRaw['id'] = int.parse(entry.key);
     619           6 :       copiedRaw['content'] = jsonDecode(copiedRaw['content'] as String);
     620             :       return copiedRaw;
     621          30 :     }).toList();
     622          64 :     return copiedRaws.map((raw) => QueuedToDeviceEvent.fromJson(raw)).toList();
     623             :   }
     624             : 
     625           6 :   @override
     626             :   Future<List<Event>> getUnimportantRoomEventStatesForRoom(
     627             :       List<String> events, Room room) async {
     628          21 :     final keys = (await _nonPreloadRoomStateBox.getAllKeys()).where((key) {
     629           3 :       final tuple = TupleKey.fromString(key);
     630          21 :       return tuple.parts.first == room.id && !events.contains(tuple.parts[1]);
     631             :     });
     632             : 
     633           6 :     final unimportantEvents = <Event>[];
     634           9 :     for (final key in keys) {
     635           6 :       final states = await _nonPreloadRoomStateBox.get(key);
     636             :       if (states == null) continue;
     637           3 :       unimportantEvents.addAll(
     638          15 :           states.values.map((raw) => Event.fromJson(copyMap(raw), room)));
     639             :     }
     640             :     return unimportantEvents;
     641             :   }
     642             : 
     643          30 :   @override
     644             :   Future<User?> getUser(String userId, Room room) async {
     645             :     final state =
     646         150 :         await _roomMembersBox.get(TupleKey(room.id, userId).toString());
     647             :     if (state == null) return null;
     648          87 :     return Event.fromJson(copyMap(state), room).asUser;
     649             :   }
     650             : 
     651          30 :   @override
     652             :   Future<Map<String, DeviceKeysList>> getUserDeviceKeys(Client client) =>
     653          30 :       runBenchmarked<Map<String, DeviceKeysList>>(
     654          30 :           'Get all user device keys from store', () async {
     655             :         final deviceKeysOutdated =
     656          60 :             await _userDeviceKeysOutdatedBox.getAllValues();
     657          30 :         if (deviceKeysOutdated.isEmpty) {
     658          30 :           return {};
     659             :         }
     660           1 :         final res = <String, DeviceKeysList>{};
     661           2 :         final userDeviceKeys = await _userDeviceKeysBox.getAllValues();
     662             :         final userCrossSigningKeys =
     663           2 :             await _userCrossSigningKeysBox.getAllValues();
     664           2 :         for (final userId in deviceKeysOutdated.keys) {
     665           3 :           final deviceKeysBoxKeys = userDeviceKeys.keys.where((tuple) {
     666           1 :             final tupleKey = TupleKey.fromString(tuple);
     667           3 :             return tupleKey.parts.first == userId;
     668             :           });
     669             :           final crossSigningKeysBoxKeys =
     670           3 :               userCrossSigningKeys.keys.where((tuple) {
     671           1 :             final tupleKey = TupleKey.fromString(tuple);
     672           3 :             return tupleKey.parts.first == userId;
     673             :           });
     674           1 :           final childEntries = deviceKeysBoxKeys.map(
     675           1 :             (key) {
     676           1 :               final userDeviceKey = userDeviceKeys[key];
     677             :               if (userDeviceKey == null) return null;
     678           1 :               return copyMap(userDeviceKey);
     679             :             },
     680             :           );
     681           1 :           final crossSigningEntries = crossSigningKeysBoxKeys.map(
     682           1 :             (key) {
     683           1 :               final crossSigningKey = userCrossSigningKeys[key];
     684             :               if (crossSigningKey == null) return null;
     685           1 :               return copyMap(crossSigningKey);
     686             :             },
     687             :           );
     688           2 :           res[userId] = DeviceKeysList.fromDbJson(
     689           1 :               {
     690           1 :                 'client_id': client.id,
     691             :                 'user_id': userId,
     692           1 :                 'outdated': deviceKeysOutdated[userId],
     693             :               },
     694             :               childEntries
     695           2 :                   .where((c) => c != null)
     696           1 :                   .toList()
     697           1 :                   .cast<Map<String, dynamic>>(),
     698             :               crossSigningEntries
     699           2 :                   .where((c) => c != null)
     700           1 :                   .toList()
     701           1 :                   .cast<Map<String, dynamic>>(),
     702             :               client);
     703             :         }
     704             :         return res;
     705             :       });
     706             : 
     707          30 :   @override
     708             :   Future<List<User>> getUsers(Room room) async {
     709          30 :     final users = <User>[];
     710          60 :     final keys = (await _roomMembersBox.getAllKeys())
     711          30 :         .where((key) => TupleKey.fromString(key).parts.first == room.id)
     712          30 :         .toList();
     713          60 :     final states = await _roomMembersBox.getAll(keys);
     714          30 :     states.removeWhere((state) => state == null);
     715          30 :     for (final state in states) {
     716           0 :       users.add(Event.fromJson(copyMap(state!), room).asUser);
     717             :     }
     718             : 
     719             :     return users;
     720             :   }
     721             : 
     722          32 :   @override
     723             :   Future<int> insertClient(
     724             :       String name,
     725             :       String homeserverUrl,
     726             :       String token,
     727             :       DateTime? tokenExpiresAt,
     728             :       String? refreshToken,
     729             :       String userId,
     730             :       String? deviceId,
     731             :       String? deviceName,
     732             :       String? prevBatch,
     733             :       String? olmAccount) async {
     734          64 :     await transaction(() async {
     735          64 :       await _clientBox.put('homeserver_url', homeserverUrl);
     736          64 :       await _clientBox.put('token', token);
     737             :       if (tokenExpiresAt == null) {
     738          62 :         await _clientBox.delete('token_expires_at');
     739             :       } else {
     740           2 :         await _clientBox.put('token_expires_at',
     741           2 :             tokenExpiresAt.millisecondsSinceEpoch.toString());
     742             :       }
     743             :       if (refreshToken == null) {
     744          12 :         await _clientBox.delete('refresh_token');
     745             :       } else {
     746          60 :         await _clientBox.put('refresh_token', refreshToken);
     747             :       }
     748          64 :       await _clientBox.put('user_id', userId);
     749             :       if (deviceId == null) {
     750           4 :         await _clientBox.delete('device_id');
     751             :       } else {
     752          60 :         await _clientBox.put('device_id', deviceId);
     753             :       }
     754             :       if (deviceName == null) {
     755           4 :         await _clientBox.delete('device_name');
     756             :       } else {
     757          60 :         await _clientBox.put('device_name', deviceName);
     758             :       }
     759             :       if (prevBatch == null) {
     760          62 :         await _clientBox.delete('prev_batch');
     761             :       } else {
     762           4 :         await _clientBox.put('prev_batch', prevBatch);
     763             :       }
     764             :       if (olmAccount == null) {
     765          16 :         await _clientBox.delete('olm_account');
     766             :       } else {
     767          48 :         await _clientBox.put('olm_account', olmAccount);
     768             :       }
     769          64 :       await _clientBox.delete('sync_filter_id');
     770             :     });
     771             :     return 0;
     772             :   }
     773             : 
     774           2 :   @override
     775             :   Future<int> insertIntoToDeviceQueue(
     776             :       String type, String txnId, String content) async {
     777           4 :     final id = DateTime.now().millisecondsSinceEpoch;
     778           8 :     await _toDeviceQueueBox.put(id.toString(), {
     779             :       'type': type,
     780             :       'txn_id': txnId,
     781             :       'content': content,
     782             :     });
     783             :     return id;
     784             :   }
     785             : 
     786           5 :   @override
     787             :   Future<void> markInboundGroupSessionAsUploaded(
     788             :       String roomId, String sessionId) async {
     789           5 :     final raw = copyMap(
     790          10 :       await _inboundGroupSessionsBox.get(sessionId) ?? {},
     791             :     );
     792           5 :     if (raw.isEmpty) {
     793           0 :       Logs().w(
     794             :           'Tried to mark inbound group session as uploaded which was not found in the database!');
     795             :       return;
     796             :     }
     797           5 :     raw['uploaded'] = true;
     798          10 :     await _inboundGroupSessionsBox.put(sessionId, raw);
     799             :     return;
     800             :   }
     801             : 
     802           2 :   @override
     803             :   Future<void> markInboundGroupSessionsAsNeedingUpload() async {
     804           4 :     final keys = await _inboundGroupSessionsBox.getAllKeys();
     805           4 :     for (final sessionId in keys) {
     806           2 :       final raw = copyMap(
     807           4 :         await _inboundGroupSessionsBox.get(sessionId) ?? {},
     808             :       );
     809           2 :       if (raw.isEmpty) continue;
     810           2 :       raw['uploaded'] = false;
     811           4 :       await _inboundGroupSessionsBox.put(sessionId, raw);
     812             :     }
     813             :     return;
     814             :   }
     815             : 
     816          10 :   @override
     817             :   Future<void> removeEvent(String eventId, String roomId) async {
     818          40 :     await _eventsBox.delete(TupleKey(roomId, eventId).toString());
     819          20 :     final keys = await _timelineFragmentsBox.getAllKeys();
     820          20 :     for (final key in keys) {
     821          10 :       final multiKey = TupleKey.fromString(key);
     822          30 :       if (multiKey.parts.first != roomId) continue;
     823             :       final eventIds =
     824          30 :           List<String>.from(await _timelineFragmentsBox.get(key) ?? []);
     825          10 :       final prevLength = eventIds.length;
     826          30 :       eventIds.removeWhere((id) => id == eventId);
     827          20 :       if (eventIds.length < prevLength) {
     828          20 :         await _timelineFragmentsBox.put(key, eventIds);
     829             :       }
     830             :     }
     831             :     return;
     832             :   }
     833             : 
     834           2 :   @override
     835             :   Future<void> removeOutboundGroupSession(String roomId) async {
     836           4 :     await _outboundGroupSessionsBox.delete(roomId);
     837             :     return;
     838             :   }
     839             : 
     840           4 :   @override
     841             :   Future<void> removeUserCrossSigningKey(
     842             :       String userId, String publicKey) async {
     843           4 :     await _userCrossSigningKeysBox
     844          12 :         .delete(TupleKey(userId, publicKey).toString());
     845             :     return;
     846             :   }
     847             : 
     848           1 :   @override
     849             :   Future<void> removeUserDeviceKey(String userId, String deviceId) async {
     850           4 :     await _userDeviceKeysBox.delete(TupleKey(userId, deviceId).toString());
     851             :     return;
     852             :   }
     853             : 
     854           3 :   @override
     855             :   Future<void> setBlockedUserCrossSigningKey(
     856             :       bool blocked, String userId, String publicKey) async {
     857           3 :     final raw = copyMap(
     858           3 :       await _userCrossSigningKeysBox
     859           9 :               .get(TupleKey(userId, publicKey).toString()) ??
     860           0 :           {},
     861             :     );
     862           3 :     raw['blocked'] = blocked;
     863           6 :     await _userCrossSigningKeysBox.put(
     864           6 :       TupleKey(userId, publicKey).toString(),
     865             :       raw,
     866             :     );
     867             :     return;
     868             :   }
     869             : 
     870           3 :   @override
     871             :   Future<void> setBlockedUserDeviceKey(
     872             :       bool blocked, String userId, String deviceId) async {
     873           3 :     final raw = copyMap(
     874          12 :       await _userDeviceKeysBox.get(TupleKey(userId, deviceId).toString()) ?? {},
     875             :     );
     876           3 :     raw['blocked'] = blocked;
     877           6 :     await _userDeviceKeysBox.put(
     878           6 :       TupleKey(userId, deviceId).toString(),
     879             :       raw,
     880             :     );
     881             :     return;
     882             :   }
     883             : 
     884           1 :   @override
     885             :   Future<void> setLastActiveUserDeviceKey(
     886             :       int lastActive, String userId, String deviceId) async {
     887           1 :     final raw = copyMap(
     888           4 :       await _userDeviceKeysBox.get(TupleKey(userId, deviceId).toString()) ?? {},
     889             :     );
     890             : 
     891           1 :     raw['last_active'] = lastActive;
     892           2 :     await _userDeviceKeysBox.put(
     893           2 :       TupleKey(userId, deviceId).toString(),
     894             :       raw,
     895             :     );
     896             :   }
     897             : 
     898           7 :   @override
     899             :   Future<void> setLastSentMessageUserDeviceKey(
     900             :       String lastSentMessage, String userId, String deviceId) async {
     901           7 :     final raw = copyMap(
     902          28 :       await _userDeviceKeysBox.get(TupleKey(userId, deviceId).toString()) ?? {},
     903             :     );
     904           7 :     raw['last_sent_message'] = lastSentMessage;
     905          14 :     await _userDeviceKeysBox.put(
     906          14 :       TupleKey(userId, deviceId).toString(),
     907             :       raw,
     908             :     );
     909             :   }
     910             : 
     911           2 :   @override
     912             :   Future<void> setRoomPrevBatch(
     913             :       String? prevBatch, String roomId, Client client) async {
     914           4 :     final raw = await _roomsBox.get(roomId);
     915             :     if (raw == null) return;
     916           2 :     final room = Room.fromJson(copyMap(raw), client);
     917           1 :     room.prev_batch = prevBatch;
     918           3 :     await _roomsBox.put(roomId, room.toJson());
     919             :     return;
     920             :   }
     921             : 
     922           6 :   @override
     923             :   Future<void> setVerifiedUserCrossSigningKey(
     924             :       bool verified, String userId, String publicKey) async {
     925           6 :     final raw = copyMap(
     926           6 :       (await _userCrossSigningKeysBox
     927          18 :               .get(TupleKey(userId, publicKey).toString())) ??
     928           1 :           {},
     929             :     );
     930           6 :     raw['verified'] = verified;
     931          12 :     await _userCrossSigningKeysBox.put(
     932          12 :       TupleKey(userId, publicKey).toString(),
     933             :       raw,
     934             :     );
     935             :     return;
     936             :   }
     937             : 
     938           4 :   @override
     939             :   Future<void> setVerifiedUserDeviceKey(
     940             :       bool verified, String userId, String deviceId) async {
     941           4 :     final raw = copyMap(
     942          16 :       await _userDeviceKeysBox.get(TupleKey(userId, deviceId).toString()) ?? {},
     943             :     );
     944           4 :     raw['verified'] = verified;
     945           8 :     await _userDeviceKeysBox.put(
     946           8 :       TupleKey(userId, deviceId).toString(),
     947             :       raw,
     948             :     );
     949             :     return;
     950             :   }
     951             : 
     952          30 :   @override
     953             :   Future<void> storeAccountData(String type, String content) async {
     954          90 :     await _accountDataBox.put(type, jsonDecode(content));
     955             :     return;
     956             :   }
     957             : 
     958          32 :   @override
     959             :   Future<void> storeEventUpdate(EventUpdate eventUpdate, Client client) async {
     960             :     // Ephemerals should not be stored
     961          64 :     if (eventUpdate.type == EventUpdateType.ephemeral) return;
     962          64 :     final tmpRoom = client.getRoomById(eventUpdate.roomID) ??
     963          12 :         Room(id: eventUpdate.roomID, client: client);
     964             : 
     965             :     // In case of this is a redaction event
     966          96 :     if (eventUpdate.content['type'] == EventTypes.Redaction) {
     967           0 :       final eventId = eventUpdate.content.tryGet<String>('redacts');
     968             :       final event =
     969           0 :           eventId != null ? await getEventById(eventId, tmpRoom) : null;
     970             :       if (event != null) {
     971           0 :         event.setRedactionEvent(Event.fromJson(eventUpdate.content, tmpRoom));
     972           0 :         await _eventsBox.put(
     973           0 :             TupleKey(eventUpdate.roomID, event.eventId).toString(),
     974           0 :             event.toJson());
     975             : 
     976           0 :         if (tmpRoom.lastEvent?.eventId == event.eventId) {
     977           0 :           if (client.importantStateEvents.contains(event.type)) {
     978           0 :             await _preloadRoomStateBox.put(
     979           0 :               TupleKey(eventUpdate.roomID, event.type).toString(),
     980           0 :               {'': event.toJson()},
     981             :             );
     982             :           } else {
     983           0 :             await _nonPreloadRoomStateBox.put(
     984           0 :               TupleKey(eventUpdate.roomID, event.type).toString(),
     985           0 :               {'': event.toJson()},
     986             :             );
     987             :           }
     988             :         }
     989             :       }
     990             :     }
     991             : 
     992             :     // Store a common message event
     993          64 :     if ({EventUpdateType.timeline, EventUpdateType.history}
     994          64 :         .contains(eventUpdate.type)) {
     995          64 :       final eventId = eventUpdate.content['event_id'];
     996             :       // Is this ID already in the store?
     997          32 :       final prevEvent = await _eventsBox
     998         128 :           .get(TupleKey(eventUpdate.roomID, eventId).toString());
     999             :       final prevStatus = prevEvent == null
    1000             :           ? null
    1001           9 :           : () {
    1002           9 :               final json = copyMap(prevEvent);
    1003           9 :               final statusInt = json.tryGet<int>('status') ??
    1004             :                   json
    1005           0 :                       .tryGetMap<String, dynamic>('unsigned')
    1006           0 :                       ?.tryGet<int>(messageSendingStatusKey);
    1007           9 :               return statusInt == null ? null : eventStatusFromInt(statusInt);
    1008           9 :             }();
    1009             : 
    1010             :       // calculate the status
    1011          32 :       final newStatus = eventStatusFromInt(
    1012          64 :         eventUpdate.content.tryGet<int>('status') ??
    1013          32 :             eventUpdate.content
    1014          32 :                 .tryGetMap<String, dynamic>('unsigned')
    1015          29 :                 ?.tryGet<int>(messageSendingStatusKey) ??
    1016          32 :             EventStatus.synced.intValue,
    1017             :       );
    1018             : 
    1019             :       // Is this the response to a sending event which is already synced? Then
    1020             :       // there is nothing to do here.
    1021          36 :       if (!newStatus.isSynced && prevStatus != null && prevStatus.isSynced) {
    1022             :         return;
    1023             :       }
    1024             : 
    1025          32 :       final status = newStatus.isError || prevStatus == null
    1026             :           ? newStatus
    1027           7 :           : latestEventStatus(
    1028             :               prevStatus,
    1029             :               newStatus,
    1030             :             );
    1031             : 
    1032             :       // Add the status and the sort order to the content so it get stored
    1033          96 :       eventUpdate.content['unsigned'] ??= <String, dynamic>{};
    1034          96 :       eventUpdate.content['unsigned'][messageSendingStatusKey] =
    1035          96 :           eventUpdate.content['status'] = status.intValue;
    1036             : 
    1037             :       // In case this event has sent from this account we have a transaction ID
    1038          32 :       final transactionId = eventUpdate.content
    1039          32 :           .tryGetMap<String, dynamic>('unsigned')
    1040          32 :           ?.tryGet<String>('transaction_id');
    1041         160 :       await _eventsBox.put(TupleKey(eventUpdate.roomID, eventId).toString(),
    1042          32 :           eventUpdate.content);
    1043             : 
    1044             :       // Update timeline fragments
    1045          96 :       final key = TupleKey(eventUpdate.roomID, status.isSent ? '' : 'SENDING')
    1046          32 :           .toString();
    1047             : 
    1048             :       final eventIds =
    1049         128 :           List<String>.from(await _timelineFragmentsBox.get(key) ?? []);
    1050             : 
    1051          32 :       if (!eventIds.contains(eventId)) {
    1052          64 :         if (eventUpdate.type == EventUpdateType.history) {
    1053           1 :           eventIds.add(eventId);
    1054             :         } else {
    1055          32 :           eventIds.insert(0, eventId);
    1056             :         }
    1057          64 :         await _timelineFragmentsBox.put(key, eventIds);
    1058           7 :       } else if (status.isSynced &&
    1059             :           prevStatus != null &&
    1060           5 :           prevStatus.isSent &&
    1061          10 :           eventUpdate.type != EventUpdateType.history) {
    1062             :         // Status changes from 1 -> 2? Make sure event is correctly sorted.
    1063           5 :         eventIds.remove(eventId);
    1064           5 :         eventIds.insert(0, eventId);
    1065             :       }
    1066             : 
    1067             :       // If event comes from server timeline, remove sending events with this ID
    1068          32 :       if (status.isSent) {
    1069          96 :         final key = TupleKey(eventUpdate.roomID, 'SENDING').toString();
    1070             :         final eventIds =
    1071         128 :             List<String>.from(await _timelineFragmentsBox.get(key) ?? []);
    1072          50 :         final i = eventIds.indexWhere((id) => id == eventId);
    1073          64 :         if (i != -1) {
    1074           6 :           await _timelineFragmentsBox.put(key, eventIds..removeAt(i));
    1075             :         }
    1076             :       }
    1077             : 
    1078             :       // Is there a transaction id? Then delete the event with this id.
    1079          64 :       if (!status.isError && !status.isSending && transactionId != null) {
    1080          18 :         await removeEvent(transactionId, eventUpdate.roomID);
    1081             :       }
    1082             :     }
    1083             : 
    1084          64 :     final stateKey = eventUpdate.content['state_key'];
    1085             :     // Store a common state event
    1086             :     if (stateKey != null) {
    1087          87 :       if (eventUpdate.content['type'] == EventTypes.RoomMember) {
    1088          58 :         await _roomMembersBox.put(
    1089          29 :             TupleKey(
    1090          29 :               eventUpdate.roomID,
    1091          58 :               eventUpdate.content['state_key'],
    1092          29 :             ).toString(),
    1093          29 :             eventUpdate.content);
    1094             :       } else {
    1095          58 :         final type = eventUpdate.content['type'] as String;
    1096          58 :         final roomStateBox = client.importantStateEvents.contains(type)
    1097          29 :             ? _preloadRoomStateBox
    1098          29 :             : _nonPreloadRoomStateBox;
    1099          29 :         final key = TupleKey(
    1100          29 :           eventUpdate.roomID,
    1101             :           type,
    1102          29 :         ).toString();
    1103          87 :         final stateMap = copyMap(await roomStateBox.get(key) ?? {});
    1104             : 
    1105          58 :         stateMap[stateKey] = eventUpdate.content;
    1106          29 :         await roomStateBox.put(key, stateMap);
    1107             :       }
    1108             :     }
    1109             : 
    1110             :     // Store a room account data event
    1111          64 :     if (eventUpdate.type == EventUpdateType.accountData) {
    1112          58 :       await _roomAccountDataBox.put(
    1113          29 :         TupleKey(
    1114          29 :           eventUpdate.roomID,
    1115          58 :           eventUpdate.content['type'],
    1116          29 :         ).toString(),
    1117          29 :         eventUpdate.content,
    1118             :       );
    1119             :     }
    1120             :   }
    1121             : 
    1122          24 :   @override
    1123             :   Future<void> storeInboundGroupSession(
    1124             :       String roomId,
    1125             :       String sessionId,
    1126             :       String pickle,
    1127             :       String content,
    1128             :       String indexes,
    1129             :       String allowedAtIndex,
    1130             :       String senderKey,
    1131             :       String senderClaimedKey) async {
    1132          48 :     await _inboundGroupSessionsBox.put(
    1133             :         sessionId,
    1134          24 :         StoredInboundGroupSession(
    1135             :           roomId: roomId,
    1136             :           sessionId: sessionId,
    1137             :           pickle: pickle,
    1138             :           content: content,
    1139             :           indexes: indexes,
    1140             :           allowedAtIndex: allowedAtIndex,
    1141             :           senderKey: senderKey,
    1142             :           senderClaimedKeys: senderClaimedKey,
    1143             :           uploaded: false,
    1144          24 :         ).toJson());
    1145             :     return;
    1146             :   }
    1147             : 
    1148           5 :   @override
    1149             :   Future<void> storeOutboundGroupSession(
    1150             :       String roomId, String pickle, String deviceIds, int creationTime) async {
    1151          15 :     await _outboundGroupSessionsBox.put(roomId, <String, dynamic>{
    1152             :       'room_id': roomId,
    1153             :       'pickle': pickle,
    1154             :       'device_ids': deviceIds,
    1155             :       'creation_time': creationTime,
    1156             :     });
    1157             :     return;
    1158             :   }
    1159             : 
    1160          29 :   @override
    1161             :   Future<void> storePrevBatch(
    1162             :     String prevBatch,
    1163             :   ) async {
    1164          87 :     if ((await _clientBox.getAllKeys()).isEmpty) return;
    1165          58 :     await _clientBox.put('prev_batch', prevBatch);
    1166             :     return;
    1167             :   }
    1168             : 
    1169          30 :   @override
    1170             :   Future<void> storeRoomUpdate(String roomId, SyncRoomUpdate roomUpdate,
    1171             :       Event? lastEvent, Client client) async {
    1172             :     // Leave room if membership is leave
    1173          30 :     if (roomUpdate is LeftRoomUpdate) {
    1174          29 :       await forgetRoom(roomId);
    1175             :       return;
    1176             :     }
    1177          30 :     final membership = roomUpdate is LeftRoomUpdate
    1178             :         ? Membership.leave
    1179          30 :         : roomUpdate is InvitedRoomUpdate
    1180             :             ? Membership.invite
    1181             :             : Membership.join;
    1182             :     // Make sure room exists
    1183          60 :     final currentRawRoom = await _roomsBox.get(roomId);
    1184             :     if (currentRawRoom == null) {
    1185          60 :       await _roomsBox.put(
    1186             :           roomId,
    1187          30 :           roomUpdate is JoinedRoomUpdate
    1188          30 :               ? Room(
    1189             :                   client: client,
    1190             :                   id: roomId,
    1191             :                   membership: membership,
    1192             :                   highlightCount:
    1193          88 :                       roomUpdate.unreadNotifications?.highlightCount?.toInt() ??
    1194             :                           0,
    1195             :                   notificationCount: roomUpdate
    1196          59 :                           .unreadNotifications?.notificationCount
    1197          29 :                           ?.toInt() ??
    1198             :                       0,
    1199          59 :                   prev_batch: roomUpdate.timeline?.prevBatch,
    1200          30 :                   summary: roomUpdate.summary,
    1201             :                   lastEvent: lastEvent,
    1202          30 :                 ).toJson()
    1203          29 :               : Room(
    1204             :                   client: client,
    1205             :                   id: roomId,
    1206             :                   membership: membership,
    1207             :                   lastEvent: lastEvent,
    1208          29 :                 ).toJson());
    1209          10 :     } else if (roomUpdate is JoinedRoomUpdate) {
    1210          20 :       final currentRoom = Room.fromJson(copyMap(currentRawRoom), client);
    1211          20 :       await _roomsBox.put(
    1212             :           roomId,
    1213          10 :           Room(
    1214             :             client: client,
    1215             :             id: roomId,
    1216             :             membership: membership,
    1217             :             highlightCount:
    1218          12 :                 roomUpdate.unreadNotifications?.highlightCount?.toInt() ??
    1219          10 :                     currentRoom.highlightCount,
    1220             :             notificationCount:
    1221          12 :                 roomUpdate.unreadNotifications?.notificationCount?.toInt() ??
    1222          10 :                     currentRoom.notificationCount,
    1223             :             prev_batch:
    1224          30 :                 roomUpdate.timeline?.prevBatch ?? currentRoom.prev_batch,
    1225          30 :             summary: RoomSummary.fromJson(currentRoom.summary.toJson()
    1226          31 :               ..addAll(roomUpdate.summary?.toJson() ?? {})),
    1227             :             lastEvent: lastEvent,
    1228          10 :           ).toJson());
    1229             :     }
    1230             :   }
    1231             : 
    1232          29 :   @override
    1233             :   Future<void> deleteTimelineForRoom(String roomId) =>
    1234         116 :       _timelineFragmentsBox.delete(TupleKey(roomId, '').toString());
    1235             : 
    1236           8 :   @override
    1237             :   Future<void> storeSSSSCache(
    1238             :       String type, String keyId, String ciphertext, String content) async {
    1239          16 :     await _ssssCacheBox.put(
    1240             :         type,
    1241           8 :         SSSSCache(
    1242             :           type: type,
    1243             :           keyId: keyId,
    1244             :           ciphertext: ciphertext,
    1245             :           content: content,
    1246           8 :         ).toJson());
    1247             :   }
    1248             : 
    1249          30 :   @override
    1250             :   Future<void> storeSyncFilterId(
    1251             :     String syncFilterId,
    1252             :   ) async {
    1253          60 :     await _clientBox.put('sync_filter_id', syncFilterId);
    1254             :   }
    1255             : 
    1256          30 :   @override
    1257             :   Future<void> storeUserCrossSigningKey(String userId, String publicKey,
    1258             :       String content, bool verified, bool blocked) async {
    1259          60 :     await _userCrossSigningKeysBox.put(
    1260          60 :       TupleKey(userId, publicKey).toString(),
    1261          30 :       {
    1262             :         'user_id': userId,
    1263             :         'public_key': publicKey,
    1264             :         'content': content,
    1265             :         'verified': verified,
    1266             :         'blocked': blocked,
    1267             :       },
    1268             :     );
    1269             :   }
    1270             : 
    1271          30 :   @override
    1272             :   Future<void> storeUserDeviceKey(String userId, String deviceId,
    1273             :       String content, bool verified, bool blocked, int lastActive) async {
    1274         150 :     await _userDeviceKeysBox.put(TupleKey(userId, deviceId).toString(), {
    1275             :       'user_id': userId,
    1276             :       'device_id': deviceId,
    1277             :       'content': content,
    1278             :       'verified': verified,
    1279             :       'blocked': blocked,
    1280             :       'last_active': lastActive,
    1281             :       'last_sent_message': '',
    1282             :     });
    1283             :     return;
    1284             :   }
    1285             : 
    1286          30 :   @override
    1287             :   Future<void> storeUserDeviceKeysInfo(String userId, bool outdated) async {
    1288          60 :     await _userDeviceKeysOutdatedBox.put(userId, outdated);
    1289             :     return;
    1290             :   }
    1291             : 
    1292          32 :   @override
    1293             :   Future<void> transaction(Future<void> Function() action) =>
    1294          64 :       _collection.transaction(action);
    1295             : 
    1296           2 :   @override
    1297             :   Future<void> updateClient(
    1298             :     String homeserverUrl,
    1299             :     String token,
    1300             :     DateTime? tokenExpiresAt,
    1301             :     String? refreshToken,
    1302             :     String userId,
    1303             :     String? deviceId,
    1304             :     String? deviceName,
    1305             :     String? prevBatch,
    1306             :     String? olmAccount,
    1307             :   ) async {
    1308           4 :     await transaction(() async {
    1309           4 :       await _clientBox.put('homeserver_url', homeserverUrl);
    1310           4 :       await _clientBox.put('token', token);
    1311             :       if (tokenExpiresAt == null) {
    1312           0 :         await _clientBox.delete('token_expires_at');
    1313             :       } else {
    1314           4 :         await _clientBox.put('token_expires_at',
    1315           4 :             tokenExpiresAt.millisecondsSinceEpoch.toString());
    1316             :       }
    1317             :       if (refreshToken == null) {
    1318           0 :         await _clientBox.delete('refresh_token');
    1319             :       } else {
    1320           4 :         await _clientBox.put('refresh_token', refreshToken);
    1321             :       }
    1322           4 :       await _clientBox.put('user_id', userId);
    1323             :       if (deviceId == null) {
    1324           0 :         await _clientBox.delete('device_id');
    1325             :       } else {
    1326           4 :         await _clientBox.put('device_id', deviceId);
    1327             :       }
    1328             :       if (deviceName == null) {
    1329           0 :         await _clientBox.delete('device_name');
    1330             :       } else {
    1331           4 :         await _clientBox.put('device_name', deviceName);
    1332             :       }
    1333             :       if (prevBatch == null) {
    1334           0 :         await _clientBox.delete('prev_batch');
    1335             :       } else {
    1336           4 :         await _clientBox.put('prev_batch', prevBatch);
    1337             :       }
    1338             :       if (olmAccount == null) {
    1339           0 :         await _clientBox.delete('olm_account');
    1340             :       } else {
    1341           4 :         await _clientBox.put('olm_account', olmAccount);
    1342             :       }
    1343             :     });
    1344             :     return;
    1345             :   }
    1346             : 
    1347          24 :   @override
    1348             :   Future<void> updateClientKeys(
    1349             :     String olmAccount,
    1350             :   ) async {
    1351          48 :     await _clientBox.put('olm_account', olmAccount);
    1352             :     return;
    1353             :   }
    1354             : 
    1355           2 :   @override
    1356             :   Future<void> updateInboundGroupSessionAllowedAtIndex(
    1357             :       String allowedAtIndex, String roomId, String sessionId) async {
    1358           4 :     final raw = await _inboundGroupSessionsBox.get(sessionId);
    1359             :     if (raw == null) {
    1360           0 :       Logs().w(
    1361             :           'Tried to update inbound group session as uploaded which wasnt found in the database!');
    1362             :       return;
    1363             :     }
    1364           2 :     raw['allowed_at_index'] = allowedAtIndex;
    1365           4 :     await _inboundGroupSessionsBox.put(sessionId, raw);
    1366             :     return;
    1367             :   }
    1368             : 
    1369           3 :   @override
    1370             :   Future<void> updateInboundGroupSessionIndexes(
    1371             :       String indexes, String roomId, String sessionId) async {
    1372           6 :     final raw = await _inboundGroupSessionsBox.get(sessionId);
    1373             :     if (raw == null) {
    1374           0 :       Logs().w(
    1375             :           'Tried to update inbound group session indexes of a session which was not found in the database!');
    1376             :       return;
    1377             :     }
    1378           3 :     final json = copyMap(raw);
    1379           3 :     json['indexes'] = indexes;
    1380           6 :     await _inboundGroupSessionsBox.put(sessionId, json);
    1381             :     return;
    1382             :   }
    1383             : 
    1384           2 :   @override
    1385             :   Future<List<StoredInboundGroupSession>> getAllInboundGroupSessions() async {
    1386           4 :     final rawSessions = await _inboundGroupSessionsBox.getAllValues();
    1387           2 :     return rawSessions.values
    1388           5 :         .map((raw) => StoredInboundGroupSession.fromJson(copyMap(raw)))
    1389           2 :         .toList();
    1390             :   }
    1391             : 
    1392          29 :   @override
    1393             :   Future<void> addSeenDeviceId(
    1394             :     String userId,
    1395             :     String deviceId,
    1396             :     String publicKeys,
    1397             :   ) =>
    1398         116 :       _seenDeviceIdsBox.put(TupleKey(userId, deviceId).toString(), publicKeys);
    1399             : 
    1400          29 :   @override
    1401             :   Future<void> addSeenPublicKey(
    1402             :     String publicKey,
    1403             :     String deviceId,
    1404             :   ) =>
    1405          58 :       _seenDeviceKeysBox.put(publicKey, deviceId);
    1406             : 
    1407          29 :   @override
    1408             :   Future<String?> deviceIdSeen(userId, deviceId) async {
    1409             :     final raw =
    1410         116 :         await _seenDeviceIdsBox.get(TupleKey(userId, deviceId).toString());
    1411             :     if (raw == null) return null;
    1412             :     return raw;
    1413             :   }
    1414             : 
    1415          29 :   @override
    1416             :   Future<String?> publicKeySeen(String publicKey) async {
    1417          58 :     final raw = await _seenDeviceKeysBox.get(publicKey);
    1418             :     if (raw == null) return null;
    1419             :     return raw;
    1420             :   }
    1421             : 
    1422           0 :   @override
    1423             :   Future<String> exportDump() async {
    1424           0 :     final dataMap = {
    1425           0 :       _clientBoxName: await _clientBox.getAllValues(),
    1426           0 :       _accountDataBoxName: await _accountDataBox.getAllValues(),
    1427           0 :       _roomsBoxName: await _roomsBox.getAllValues(),
    1428           0 :       _preloadRoomStateBoxName: await _preloadRoomStateBox.getAllValues(),
    1429           0 :       _nonPreloadRoomStateBoxName: await _nonPreloadRoomStateBox.getAllValues(),
    1430           0 :       _roomMembersBoxName: await _roomMembersBox.getAllValues(),
    1431           0 :       _toDeviceQueueBoxName: await _toDeviceQueueBox.getAllValues(),
    1432           0 :       _roomAccountDataBoxName: await _roomAccountDataBox.getAllValues(),
    1433             :       _inboundGroupSessionsBoxName:
    1434           0 :           await _inboundGroupSessionsBox.getAllValues(),
    1435             :       _outboundGroupSessionsBoxName:
    1436           0 :           await _outboundGroupSessionsBox.getAllValues(),
    1437           0 :       _olmSessionsBoxName: await _olmSessionsBox.getAllValues(),
    1438           0 :       _userDeviceKeysBoxName: await _userDeviceKeysBox.getAllValues(),
    1439             :       _userDeviceKeysOutdatedBoxName:
    1440           0 :           await _userDeviceKeysOutdatedBox.getAllValues(),
    1441             :       _userCrossSigningKeysBoxName:
    1442           0 :           await _userCrossSigningKeysBox.getAllValues(),
    1443           0 :       _ssssCacheBoxName: await _ssssCacheBox.getAllValues(),
    1444           0 :       _presencesBoxName: await _presencesBox.getAllValues(),
    1445           0 :       _timelineFragmentsBoxName: await _timelineFragmentsBox.getAllValues(),
    1446           0 :       _eventsBoxName: await _eventsBox.getAllValues(),
    1447           0 :       _seenDeviceIdsBoxName: await _seenDeviceIdsBox.getAllValues(),
    1448           0 :       _seenDeviceKeysBoxName: await _seenDeviceKeysBox.getAllValues(),
    1449             :     };
    1450           0 :     final json = jsonEncode(dataMap);
    1451           0 :     await clear();
    1452             :     return json;
    1453             :   }
    1454             : 
    1455           0 :   @override
    1456             :   Future<bool> importDump(String export) async {
    1457             :     try {
    1458           0 :       await clear();
    1459           0 :       await open();
    1460           0 :       final json = Map.from(jsonDecode(export)).cast<String, Map>();
    1461           0 :       for (final key in json[_clientBoxName]!.keys) {
    1462           0 :         await _clientBox.put(key, json[_clientBoxName]![key]);
    1463             :       }
    1464           0 :       for (final key in json[_accountDataBoxName]!.keys) {
    1465           0 :         await _accountDataBox.put(key, json[_accountDataBoxName]![key]);
    1466             :       }
    1467           0 :       for (final key in json[_roomsBoxName]!.keys) {
    1468           0 :         await _roomsBox.put(key, json[_roomsBoxName]![key]);
    1469             :       }
    1470           0 :       for (final key in json[_preloadRoomStateBoxName]!.keys) {
    1471           0 :         await _preloadRoomStateBox.put(
    1472           0 :             key, json[_preloadRoomStateBoxName]![key]);
    1473             :       }
    1474           0 :       for (final key in json[_nonPreloadRoomStateBoxName]!.keys) {
    1475           0 :         await _nonPreloadRoomStateBox.put(
    1476           0 :             key, json[_nonPreloadRoomStateBoxName]![key]);
    1477             :       }
    1478           0 :       for (final key in json[_roomMembersBoxName]!.keys) {
    1479           0 :         await _roomMembersBox.put(key, json[_roomMembersBoxName]![key]);
    1480             :       }
    1481           0 :       for (final key in json[_toDeviceQueueBoxName]!.keys) {
    1482           0 :         await _toDeviceQueueBox.put(key, json[_toDeviceQueueBoxName]![key]);
    1483             :       }
    1484           0 :       for (final key in json[_roomAccountDataBoxName]!.keys) {
    1485           0 :         await _roomAccountDataBox.put(key, json[_roomAccountDataBoxName]![key]);
    1486             :       }
    1487           0 :       for (final key in json[_inboundGroupSessionsBoxName]!.keys) {
    1488           0 :         await _inboundGroupSessionsBox.put(
    1489           0 :             key, json[_inboundGroupSessionsBoxName]![key]);
    1490             :       }
    1491           0 :       for (final key in json[_outboundGroupSessionsBoxName]!.keys) {
    1492           0 :         await _outboundGroupSessionsBox.put(
    1493           0 :             key, json[_outboundGroupSessionsBoxName]![key]);
    1494             :       }
    1495           0 :       for (final key in json[_olmSessionsBoxName]!.keys) {
    1496           0 :         await _olmSessionsBox.put(key, json[_olmSessionsBoxName]![key]);
    1497             :       }
    1498           0 :       for (final key in json[_userDeviceKeysBoxName]!.keys) {
    1499           0 :         await _userDeviceKeysBox.put(key, json[_userDeviceKeysBoxName]![key]);
    1500             :       }
    1501           0 :       for (final key in json[_userDeviceKeysOutdatedBoxName]!.keys) {
    1502           0 :         await _userDeviceKeysOutdatedBox.put(
    1503           0 :             key, json[_userDeviceKeysOutdatedBoxName]![key]);
    1504             :       }
    1505           0 :       for (final key in json[_userCrossSigningKeysBoxName]!.keys) {
    1506           0 :         await _userCrossSigningKeysBox.put(
    1507           0 :             key, json[_userCrossSigningKeysBoxName]![key]);
    1508             :       }
    1509           0 :       for (final key in json[_ssssCacheBoxName]!.keys) {
    1510           0 :         await _ssssCacheBox.put(key, json[_ssssCacheBoxName]![key]);
    1511             :       }
    1512           0 :       for (final key in json[_presencesBoxName]!.keys) {
    1513           0 :         await _presencesBox.put(key, json[_presencesBoxName]![key]);
    1514             :       }
    1515           0 :       for (final key in json[_timelineFragmentsBoxName]!.keys) {
    1516           0 :         await _timelineFragmentsBox.put(
    1517           0 :             key, json[_timelineFragmentsBoxName]![key]);
    1518             :       }
    1519           0 :       for (final key in json[_seenDeviceIdsBoxName]!.keys) {
    1520           0 :         await _seenDeviceIdsBox.put(key, json[_seenDeviceIdsBoxName]![key]);
    1521             :       }
    1522           0 :       for (final key in json[_seenDeviceKeysBoxName]!.keys) {
    1523           0 :         await _seenDeviceKeysBox.put(key, json[_seenDeviceKeysBoxName]![key]);
    1524             :       }
    1525             :       return true;
    1526             :     } catch (e, s) {
    1527           0 :       Logs().e('Database import error: ', e, s);
    1528             :       return false;
    1529             :     }
    1530             :   }
    1531             : 
    1532           1 :   @override
    1533             :   Future<List<String>> getEventIdList(
    1534             :     Room room, {
    1535             :     int start = 0,
    1536             :     bool includeSending = false,
    1537             :     int? limit,
    1538             :   }) =>
    1539           2 :       runBenchmarked<List<String>>('Get event id list', () async {
    1540             :         // Get the synced event IDs from the store
    1541           3 :         final timelineKey = TupleKey(room.id, '').toString();
    1542           1 :         final timelineEventIds = List<String>.from(
    1543           2 :             (await _timelineFragmentsBox.get(timelineKey)) ?? []);
    1544             : 
    1545             :         // Get the local stored SENDING events from the store
    1546             :         late final List<String> sendingEventIds;
    1547             :         if (!includeSending) {
    1548           1 :           sendingEventIds = [];
    1549             :         } else {
    1550           0 :           final sendingTimelineKey = TupleKey(room.id, 'SENDING').toString();
    1551           0 :           sendingEventIds = List<String>.from(
    1552           0 :               (await _timelineFragmentsBox.get(sendingTimelineKey)) ?? []);
    1553             :         }
    1554             : 
    1555             :         // Combine those two lists while respecting the start and limit parameters.
    1556             :         // Create a new list object instead of concatonating list to prevent
    1557             :         // random type errors.
    1558           1 :         final eventIds = [
    1559             :           ...sendingEventIds,
    1560           1 :           ...timelineEventIds,
    1561             :         ];
    1562           0 :         if (limit != null && eventIds.length > limit) {
    1563           0 :           eventIds.removeRange(limit, eventIds.length);
    1564             :         }
    1565             : 
    1566             :         return eventIds;
    1567             :       });
    1568             : 
    1569          30 :   @override
    1570             :   Future<void> storePresence(String userId, CachedPresence presence) =>
    1571          90 :       _presencesBox.put(userId, presence.toJson());
    1572             : 
    1573           1 :   @override
    1574             :   Future<CachedPresence?> getPresence(String userId) async {
    1575           2 :     final rawPresence = await _presencesBox.get(userId);
    1576             :     if (rawPresence == null) return null;
    1577             : 
    1578           2 :     return CachedPresence.fromJson(copyMap(rawPresence));
    1579             :   }
    1580             : 
    1581           2 :   @override
    1582           2 :   Future<void> delete() => BoxCollection.delete(
    1583           2 :         name,
    1584           2 :         sqfliteFactory ?? idbFactory,
    1585             :       );
    1586             : }

Generated by: LCOV version 1.14