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

Generated by: LCOV version 2.0-1