LCOV - code coverage report
Current view: top level - lib/src/database - hive_collections_database.dart (source / functions) Coverage Total Hit
Test: merged.info Lines: 64.8 % 784 508
Test Date: 2025-01-14 13:39:53 Functions: - 0 0

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

Generated by: LCOV version 2.0-1