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

Generated by: LCOV version 2.0-1