LCOV - code coverage report
Current view: top level - lib/src/database - hive_collections_database.dart (source / functions) Hit Total Coverage
Test: merged.info Lines: 495 779 63.5 %
Date: 2024-11-12 07:37:08 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           0 :   @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 important states:
     574             :     if (loadImportantStates) {
     575           1 :       final dbKeys = client.importantStateEvents
     576           4 :           .map((state) => TupleKey(roomId, state).toString())
     577           1 :           .toList();
     578           2 :       final rawStates = await _roomStateBox.getAll(dbKeys);
     579           2 :       for (final rawState in rawStates) {
     580           1 :         if (rawState == null || rawState[''] == null) continue;
     581           4 :         room.setState(Event.fromJson(copyMap(rawState['']), room));
     582             :       }
     583             :     }
     584             : 
     585             :     return room;
     586             :   }
     587             : 
     588           1 :   @override
     589             :   Future<List<Room>> getRoomList(Client client) =>
     590           2 :       runBenchmarked<List<Room>>('Get room list from store', () async {
     591           1 :         final rooms = <String, Room>{};
     592           1 :         final userID = client.userID;
     593             : 
     594           2 :         final rawRooms = await _roomsBox.getAllValues();
     595             : 
     596           1 :         final getRoomStateRequests = <String, Future<List>>{};
     597           1 :         final getRoomMembersRequests = <String, Future<List>>{};
     598             : 
     599           2 :         for (final raw in rawRooms.values) {
     600             :           // Get the room
     601           2 :           final room = Room.fromJson(copyMap(raw), client);
     602             :           // Get the "important" room states. All other states will be loaded once
     603             :           // `getUnimportantRoomStates()` is called.
     604           1 :           final dbKeys = client.importantStateEvents
     605           5 :               .map((state) => TupleKey(room.id, state).toString())
     606           1 :               .toList();
     607           4 :           getRoomStateRequests[room.id] = _roomStateBox.getAll(
     608             :             dbKeys,
     609             :           );
     610             : 
     611             :           // Add to the list and continue.
     612           2 :           rooms[room.id] = room;
     613             :         }
     614             : 
     615           2 :         for (final room in rooms.values) {
     616             :           // Add states to the room
     617           2 :           final statesList = await getRoomStateRequests[room.id];
     618             :           if (statesList != null) {
     619           2 :             for (final states in statesList) {
     620             :               if (states == null) continue;
     621           0 :               final stateEvents = states.values
     622           0 :                   .map(
     623           0 :                     (raw) => room.membership == Membership.invite
     624           0 :                         ? StrippedStateEvent.fromJson(copyMap(raw))
     625           0 :                         : Event.fromJson(copyMap(raw), room),
     626             :                   )
     627           0 :                   .toList();
     628           0 :               for (final state in stateEvents) {
     629           0 :                 room.setState(state);
     630             :               }
     631             :             }
     632             : 
     633             :             // now that we have the state we can continue
     634           0 :             final membersToPostload = <String>{if (userID != null) userID};
     635             :             // If the room is a direct chat, those IDs should be there too
     636           1 :             if (room.isDirectChat) {
     637           0 :               membersToPostload.add(room.directChatMatrixID!);
     638             :             }
     639             : 
     640             :             // the lastEvent message preview might have an author we need to fetch, if it is a group chat
     641           1 :             if (room.lastEvent != null && !room.isDirectChat) {
     642           0 :               membersToPostload.add(room.lastEvent!.senderId);
     643             :             }
     644             : 
     645             :             // if the room has no name and no canonical alias, its name is calculated
     646             :             // based on the heroes of the room
     647           1 :             if (room.getState(EventTypes.RoomName) == null &&
     648           1 :                 room.getState(EventTypes.RoomCanonicalAlias) == null) {
     649             :               // we don't have a name and no canonical alias, so we'll need to
     650             :               // post-load the heroes
     651           2 :               final heroes = room.summary.mHeroes;
     652             :               if (heroes != null) {
     653           1 :                 membersToPostload.addAll(heroes);
     654             :               }
     655             :             }
     656             :             // Load members
     657             :             final membersDbKeys = membersToPostload
     658           1 :                 .map((member) => TupleKey(room.id, member).toString())
     659           1 :                 .toList();
     660           4 :             getRoomMembersRequests[room.id] = _roomMembersBox.getAll(
     661             :               membersDbKeys,
     662             :             );
     663             :           }
     664             :         }
     665             : 
     666           2 :         for (final room in rooms.values) {
     667             :           // Add members to the room
     668           2 :           final members = await getRoomMembersRequests[room.id];
     669             :           if (members != null) {
     670           1 :             for (final member in members) {
     671             :               if (member == null) continue;
     672           0 :               room.setState(
     673           0 :                 room.membership == Membership.invite
     674           0 :                     ? StrippedStateEvent.fromJson(copyMap(member))
     675           0 :                     : Event.fromJson(copyMap(member), room),
     676             :               );
     677             :             }
     678             :           }
     679             :         }
     680             : 
     681             :         // Get the room account data
     682           2 :         final roomAccountDataRaws = await _roomAccountDataBox.getAllValues();
     683           1 :         for (final entry in roomAccountDataRaws.entries) {
     684           0 :           final keys = TupleKey.fromString(entry.key);
     685           0 :           final basicRoomEvent = BasicRoomEvent.fromJson(
     686           0 :             copyMap(entry.value),
     687             :           );
     688           0 :           final roomId = keys.parts.first;
     689           0 :           if (rooms.containsKey(roomId)) {
     690           0 :             rooms[roomId]!.roomAccountData[basicRoomEvent.type] =
     691             :                 basicRoomEvent;
     692             :           } else {
     693           0 :             Logs().w(
     694           0 :               'Found account data for unknown room $roomId. Delete now...',
     695             :             );
     696           0 :             await _roomAccountDataBox
     697           0 :                 .delete(TupleKey(roomId, basicRoomEvent.type).toString());
     698             :           }
     699             :         }
     700             : 
     701           2 :         return rooms.values.toList();
     702             :       });
     703             : 
     704           1 :   @override
     705             :   Future<SSSSCache?> getSSSSCache(String type) async {
     706           2 :     final raw = await _ssssCacheBox.get(type);
     707             :     if (raw == null) return null;
     708           2 :     return SSSSCache.fromJson(copyMap(raw));
     709             :   }
     710             : 
     711           1 :   @override
     712             :   Future<List<QueuedToDeviceEvent>> getToDeviceEventQueue() async {
     713           2 :     final raws = await _toDeviceQueueBox.getAllValues();
     714           3 :     final copiedRaws = raws.entries.map((entry) {
     715           2 :       final copiedRaw = copyMap(entry.value);
     716           3 :       copiedRaw['id'] = int.parse(entry.key);
     717           3 :       copiedRaw['content'] = jsonDecode(copiedRaw['content'] as String);
     718             :       return copiedRaw;
     719           1 :     }).toList();
     720           4 :     return copiedRaws.map((raw) => QueuedToDeviceEvent.fromJson(raw)).toList();
     721             :   }
     722             : 
     723           1 :   @override
     724             :   Future<List<Event>> getUnimportantRoomEventStatesForRoom(
     725             :     List<String> events,
     726             :     Room room,
     727             :   ) async {
     728           4 :     final keys = (await _roomStateBox.getAllKeys()).where((key) {
     729           1 :       final tuple = TupleKey.fromString(key);
     730           4 :       return tuple.parts.first == room.id && !events.contains(tuple.parts[1]);
     731             :     });
     732             : 
     733           1 :     final unimportantEvents = <Event>[];
     734           1 :     for (final key in keys) {
     735           0 :       final states = await _roomStateBox.get(key);
     736             :       if (states == null) continue;
     737           0 :       unimportantEvents.addAll(
     738           0 :         states.values.map((raw) => Event.fromJson(copyMap(raw), room)),
     739             :       );
     740             :     }
     741           2 :     return unimportantEvents.where((event) => event.stateKey != null).toList();
     742             :   }
     743             : 
     744           1 :   @override
     745             :   Future<User?> getUser(String userId, Room room) async {
     746             :     final state =
     747           5 :         await _roomMembersBox.get(TupleKey(room.id, userId).toString());
     748             :     if (state == null) return null;
     749           0 :     return Event.fromJson(copyMap(state), room).asUser;
     750             :   }
     751             : 
     752           1 :   @override
     753             :   Future<Map<String, DeviceKeysList>> getUserDeviceKeys(Client client) =>
     754           1 :       runBenchmarked<Map<String, DeviceKeysList>>(
     755           1 :           'Get all user device keys from store', () async {
     756             :         final deviceKeysOutdated =
     757           2 :             await _userDeviceKeysOutdatedBox.getAllKeys();
     758           1 :         if (deviceKeysOutdated.isEmpty) {
     759           1 :           return {};
     760             :         }
     761           0 :         final res = <String, DeviceKeysList>{};
     762           0 :         final userDeviceKeysBoxKeys = await _userDeviceKeysBox.getAllKeys();
     763             :         final userCrossSigningKeysBoxKeys =
     764           0 :             await _userCrossSigningKeysBox.getAllKeys();
     765           0 :         for (final userId in deviceKeysOutdated) {
     766           0 :           final deviceKeysBoxKeys = userDeviceKeysBoxKeys.where((tuple) {
     767           0 :             final tupleKey = TupleKey.fromString(tuple);
     768           0 :             return tupleKey.parts.first == userId;
     769             :           });
     770             :           final crossSigningKeysBoxKeys =
     771           0 :               userCrossSigningKeysBoxKeys.where((tuple) {
     772           0 :             final tupleKey = TupleKey.fromString(tuple);
     773           0 :             return tupleKey.parts.first == userId;
     774             :           });
     775           0 :           final childEntries = await Future.wait(
     776           0 :             deviceKeysBoxKeys.map(
     777           0 :               (key) async {
     778           0 :                 final userDeviceKey = await _userDeviceKeysBox.get(key);
     779             :                 if (userDeviceKey == null) return null;
     780           0 :                 return copyMap(userDeviceKey);
     781             :               },
     782             :             ),
     783             :           );
     784           0 :           final crossSigningEntries = await Future.wait(
     785           0 :             crossSigningKeysBoxKeys.map(
     786           0 :               (key) async {
     787           0 :                 final crossSigningKey = await _userCrossSigningKeysBox.get(key);
     788             :                 if (crossSigningKey == null) return null;
     789           0 :                 return copyMap(crossSigningKey);
     790             :               },
     791             :             ),
     792             :           );
     793           0 :           res[userId] = DeviceKeysList.fromDbJson(
     794           0 :             {
     795           0 :               'client_id': client.id,
     796             :               'user_id': userId,
     797           0 :               'outdated': await _userDeviceKeysOutdatedBox.get(userId),
     798             :             },
     799             :             childEntries
     800           0 :                 .where((c) => c != null)
     801           0 :                 .toList()
     802           0 :                 .cast<Map<String, dynamic>>(),
     803             :             crossSigningEntries
     804           0 :                 .where((c) => c != null)
     805           0 :                 .toList()
     806           0 :                 .cast<Map<String, dynamic>>(),
     807             :             client,
     808             :           );
     809             :         }
     810             :         return res;
     811             :       });
     812             : 
     813           1 :   @override
     814             :   Future<List<User>> getUsers(Room room) async {
     815           1 :     final users = <User>[];
     816           2 :     final keys = (await _roomMembersBox.getAllKeys())
     817           1 :         .where((key) => TupleKey.fromString(key).parts.first == room.id)
     818           1 :         .toList();
     819           2 :     final states = await _roomMembersBox.getAll(keys);
     820           1 :     states.removeWhere((state) => state == null);
     821           1 :     for (final state in states) {
     822           0 :       users.add(Event.fromJson(copyMap(state!), room).asUser);
     823             :     }
     824             : 
     825             :     return users;
     826             :   }
     827             : 
     828           1 :   @override
     829             :   Future<int> insertClient(
     830             :     String name,
     831             :     String homeserverUrl,
     832             :     String token,
     833             :     DateTime? tokenExpiresAt,
     834             :     String? refreshToken,
     835             :     String userId,
     836             :     String? deviceId,
     837             :     String? deviceName,
     838             :     String? prevBatch,
     839             :     String? olmAccount,
     840             :   ) async {
     841           2 :     await transaction(() async {
     842           2 :       await _clientBox.put('homeserver_url', homeserverUrl);
     843           2 :       await _clientBox.put('token', token);
     844           2 :       await _clientBox.put('user_id', userId);
     845             :       if (refreshToken == null) {
     846           0 :         await _clientBox.delete('refresh_token');
     847             :       } else {
     848           2 :         await _clientBox.put('refresh_token', refreshToken);
     849             :       }
     850             :       if (tokenExpiresAt == null) {
     851           0 :         await _clientBox.delete('token_expires_at');
     852             :       } else {
     853           2 :         await _clientBox.put(
     854             :           'token_expires_at',
     855           2 :           tokenExpiresAt.millisecondsSinceEpoch.toString(),
     856             :         );
     857             :       }
     858             :       if (deviceId == null) {
     859           0 :         await _clientBox.delete('device_id');
     860             :       } else {
     861           2 :         await _clientBox.put('device_id', deviceId);
     862             :       }
     863             :       if (deviceName == null) {
     864           0 :         await _clientBox.delete('device_name');
     865             :       } else {
     866           2 :         await _clientBox.put('device_name', deviceName);
     867             :       }
     868             :       if (prevBatch == null) {
     869           0 :         await _clientBox.delete('prev_batch');
     870             :       } else {
     871           2 :         await _clientBox.put('prev_batch', prevBatch);
     872             :       }
     873             :       if (olmAccount == null) {
     874           0 :         await _clientBox.delete('olm_account');
     875             :       } else {
     876           2 :         await _clientBox.put('olm_account', olmAccount);
     877             :       }
     878           2 :       await _clientBox.delete('sync_filter_id');
     879             :     });
     880             :     return 0;
     881             :   }
     882             : 
     883           1 :   @override
     884             :   Future<int> insertIntoToDeviceQueue(
     885             :     String type,
     886             :     String txnId,
     887             :     String content,
     888             :   ) async {
     889           2 :     final id = DateTime.now().millisecondsSinceEpoch;
     890           4 :     await _toDeviceQueueBox.put(id.toString(), {
     891             :       'type': type,
     892             :       'txn_id': txnId,
     893             :       'content': content,
     894             :     });
     895             :     return id;
     896             :   }
     897             : 
     898           1 :   @override
     899             :   Future<void> markInboundGroupSessionAsUploaded(
     900             :     String roomId,
     901             :     String sessionId,
     902             :   ) async {
     903           2 :     final raw = await _inboundGroupSessionsBox.get(sessionId);
     904             :     if (raw == null) {
     905           0 :       Logs().w(
     906             :         'Tried to mark inbound group session as uploaded which was not found in the database!',
     907             :       );
     908             :       return;
     909             :     }
     910           1 :     raw['uploaded'] = true;
     911           2 :     await _inboundGroupSessionsBox.put(sessionId, raw);
     912             :     return;
     913             :   }
     914             : 
     915           1 :   @override
     916             :   Future<void> markInboundGroupSessionsAsNeedingUpload() async {
     917           2 :     final keys = await _inboundGroupSessionsBox.getAllKeys();
     918           2 :     for (final sessionId in keys) {
     919           2 :       final raw = await _inboundGroupSessionsBox.get(sessionId);
     920             :       if (raw == null) continue;
     921           1 :       raw['uploaded'] = false;
     922           2 :       await _inboundGroupSessionsBox.put(sessionId, raw);
     923             :     }
     924             :     return;
     925             :   }
     926             : 
     927           1 :   @override
     928             :   Future<void> removeEvent(String eventId, String roomId) async {
     929           4 :     await _eventsBox.delete(TupleKey(roomId, eventId).toString());
     930           2 :     final keys = await _timelineFragmentsBox.getAllKeys();
     931           2 :     for (final key in keys) {
     932           1 :       final multiKey = TupleKey.fromString(key);
     933           3 :       if (multiKey.parts.first != roomId) continue;
     934           2 :       final eventIds = await _timelineFragmentsBox.get(key) ?? [];
     935           1 :       final prevLength = eventIds.length;
     936           3 :       eventIds.removeWhere((id) => id == eventId);
     937           2 :       if (eventIds.length < prevLength) {
     938           2 :         await _timelineFragmentsBox.put(key, eventIds);
     939             :       }
     940             :     }
     941             :     return;
     942             :   }
     943             : 
     944           0 :   @override
     945             :   Future<void> removeOutboundGroupSession(String roomId) async {
     946           0 :     await _outboundGroupSessionsBox.delete(roomId);
     947             :     return;
     948             :   }
     949             : 
     950           1 :   @override
     951             :   Future<void> removeUserCrossSigningKey(
     952             :     String userId,
     953             :     String publicKey,
     954             :   ) async {
     955           1 :     await _userCrossSigningKeysBox
     956           3 :         .delete(TupleKey(userId, publicKey).toString());
     957             :     return;
     958             :   }
     959             : 
     960           0 :   @override
     961             :   Future<void> removeUserDeviceKey(String userId, String deviceId) async {
     962           0 :     await _userDeviceKeysBox.delete(TupleKey(userId, deviceId).toString());
     963             :     return;
     964             :   }
     965             : 
     966           1 :   @override
     967             :   Future<void> setBlockedUserCrossSigningKey(
     968             :     bool blocked,
     969             :     String userId,
     970             :     String publicKey,
     971             :   ) async {
     972           1 :     final raw = await _userCrossSigningKeysBox
     973           3 :         .get(TupleKey(userId, publicKey).toString());
     974           1 :     raw!['blocked'] = blocked;
     975           2 :     await _userCrossSigningKeysBox.put(
     976           2 :       TupleKey(userId, publicKey).toString(),
     977             :       raw,
     978             :     );
     979             :     return;
     980             :   }
     981             : 
     982           1 :   @override
     983             :   Future<void> setBlockedUserDeviceKey(
     984             :     bool blocked,
     985             :     String userId,
     986             :     String deviceId,
     987             :   ) async {
     988             :     final raw =
     989           4 :         await _userDeviceKeysBox.get(TupleKey(userId, deviceId).toString());
     990           1 :     raw!['blocked'] = blocked;
     991           2 :     await _userDeviceKeysBox.put(
     992           2 :       TupleKey(userId, deviceId).toString(),
     993             :       raw,
     994             :     );
     995             :     return;
     996             :   }
     997             : 
     998           0 :   @override
     999             :   Future<void> setLastActiveUserDeviceKey(
    1000             :     int lastActive,
    1001             :     String userId,
    1002             :     String deviceId,
    1003             :   ) async {
    1004             :     final raw =
    1005           0 :         await _userDeviceKeysBox.get(TupleKey(userId, deviceId).toString());
    1006           0 :     raw!['last_active'] = lastActive;
    1007           0 :     await _userDeviceKeysBox.put(
    1008           0 :       TupleKey(userId, deviceId).toString(),
    1009             :       raw,
    1010             :     );
    1011             :   }
    1012             : 
    1013           0 :   @override
    1014             :   Future<void> setLastSentMessageUserDeviceKey(
    1015             :     String lastSentMessage,
    1016             :     String userId,
    1017             :     String deviceId,
    1018             :   ) async {
    1019             :     final raw =
    1020           0 :         await _userDeviceKeysBox.get(TupleKey(userId, deviceId).toString());
    1021           0 :     raw!['last_sent_message'] = lastSentMessage;
    1022           0 :     await _userDeviceKeysBox.put(
    1023           0 :       TupleKey(userId, deviceId).toString(),
    1024             :       raw,
    1025             :     );
    1026             :   }
    1027             : 
    1028           1 :   @override
    1029             :   Future<void> setRoomPrevBatch(
    1030             :     String? prevBatch,
    1031             :     String roomId,
    1032             :     Client client,
    1033             :   ) async {
    1034           2 :     final raw = await _roomsBox.get(roomId);
    1035             :     if (raw == null) return;
    1036           2 :     final room = Room.fromJson(copyMap(raw), client);
    1037           1 :     room.prev_batch = prevBatch;
    1038           3 :     await _roomsBox.put(roomId, room.toJson());
    1039             :     return;
    1040             :   }
    1041             : 
    1042           1 :   @override
    1043             :   Future<void> setVerifiedUserCrossSigningKey(
    1044             :     bool verified,
    1045             :     String userId,
    1046             :     String publicKey,
    1047             :   ) async {
    1048           1 :     final raw = (await _userCrossSigningKeysBox
    1049           3 :             .get(TupleKey(userId, publicKey).toString())) ??
    1050           0 :         {};
    1051           1 :     raw['verified'] = verified;
    1052           2 :     await _userCrossSigningKeysBox.put(
    1053           2 :       TupleKey(userId, publicKey).toString(),
    1054             :       raw,
    1055             :     );
    1056             :     return;
    1057             :   }
    1058             : 
    1059           1 :   @override
    1060             :   Future<void> setVerifiedUserDeviceKey(
    1061             :     bool verified,
    1062             :     String userId,
    1063             :     String deviceId,
    1064             :   ) async {
    1065             :     final raw =
    1066           4 :         await _userDeviceKeysBox.get(TupleKey(userId, deviceId).toString());
    1067           1 :     raw!['verified'] = verified;
    1068           2 :     await _userDeviceKeysBox.put(
    1069           2 :       TupleKey(userId, deviceId).toString(),
    1070             :       raw,
    1071             :     );
    1072             :     return;
    1073             :   }
    1074             : 
    1075           1 :   @override
    1076             :   Future<void> storeAccountData(String type, String content) async {
    1077           4 :     await _accountDataBox.put(type, copyMap(jsonDecode(content)));
    1078             :     return;
    1079             :   }
    1080             : 
    1081           1 :   @override
    1082             :   Future<void> storeEventUpdate(EventUpdate eventUpdate, Client client) async {
    1083             :     // Ephemerals should not be stored
    1084           2 :     if (eventUpdate.type == EventUpdateType.ephemeral) return;
    1085             : 
    1086           2 :     final tmpRoom = client.getRoomById(eventUpdate.roomID) ??
    1087           2 :         Room(id: eventUpdate.roomID, client: client);
    1088             : 
    1089             :     // In case of this is a redaction event
    1090           3 :     if (eventUpdate.content['type'] == EventTypes.Redaction) {
    1091           0 :       final eventId = eventUpdate.content.tryGet<String>('redacts');
    1092             :       final event =
    1093           0 :           eventId != null ? await getEventById(eventId, tmpRoom) : null;
    1094             :       if (event != null) {
    1095           0 :         event.setRedactionEvent(Event.fromJson(eventUpdate.content, tmpRoom));
    1096           0 :         await _eventsBox.put(
    1097           0 :           TupleKey(eventUpdate.roomID, event.eventId).toString(),
    1098           0 :           event.toJson(),
    1099             :         );
    1100             : 
    1101           0 :         if (tmpRoom.lastEvent?.eventId == event.eventId) {
    1102           0 :           await _roomStateBox.put(
    1103           0 :             TupleKey(eventUpdate.roomID, event.type).toString(),
    1104           0 :             {'': event.toJson()},
    1105             :           );
    1106             :         }
    1107             :       }
    1108             :     }
    1109             : 
    1110             :     // Store a common message event
    1111             :     if ({
    1112           1 :       EventUpdateType.timeline,
    1113           1 :       EventUpdateType.history,
    1114           1 :       EventUpdateType.decryptedTimelineQueue,
    1115           2 :     }.contains(eventUpdate.type)) {
    1116           2 :       final eventId = eventUpdate.content['event_id'];
    1117             :       // Is this ID already in the store?
    1118           1 :       final prevEvent = await _eventsBox
    1119           4 :           .get(TupleKey(eventUpdate.roomID, eventId).toString());
    1120             :       final prevStatus = prevEvent == null
    1121             :           ? null
    1122           0 :           : () {
    1123           0 :               final json = copyMap(prevEvent);
    1124           0 :               final statusInt = json.tryGet<int>('status') ??
    1125             :                   json
    1126           0 :                       .tryGetMap<String, dynamic>('unsigned')
    1127           0 :                       ?.tryGet<int>(messageSendingStatusKey);
    1128           0 :               return statusInt == null ? null : eventStatusFromInt(statusInt);
    1129           0 :             }();
    1130             : 
    1131             :       // calculate the status
    1132           1 :       final newStatus = eventStatusFromInt(
    1133           2 :         eventUpdate.content.tryGet<int>('status') ??
    1134           1 :             eventUpdate.content
    1135           1 :                 .tryGetMap<String, dynamic>('unsigned')
    1136           0 :                 ?.tryGet<int>(messageSendingStatusKey) ??
    1137           1 :             EventStatus.synced.intValue,
    1138             :       );
    1139             : 
    1140             :       // Is this the response to a sending event which is already synced? Then
    1141             :       // there is nothing to do here.
    1142           1 :       if (!newStatus.isSynced && prevStatus != null && prevStatus.isSynced) {
    1143             :         return;
    1144             :       }
    1145             : 
    1146           1 :       final status = newStatus.isError || prevStatus == null
    1147             :           ? newStatus
    1148           0 :           : latestEventStatus(
    1149             :               prevStatus,
    1150             :               newStatus,
    1151             :             );
    1152             : 
    1153             :       // Add the status and the sort order to the content so it get stored
    1154           3 :       eventUpdate.content['unsigned'] ??= <String, dynamic>{};
    1155           3 :       eventUpdate.content['unsigned'][messageSendingStatusKey] =
    1156           3 :           eventUpdate.content['status'] = status.intValue;
    1157             : 
    1158             :       // In case this event has sent from this account we have a transaction ID
    1159           1 :       final transactionId = eventUpdate.content
    1160           1 :           .tryGetMap<String, dynamic>('unsigned')
    1161           1 :           ?.tryGet<String>('transaction_id');
    1162           2 :       await _eventsBox.put(
    1163           3 :         TupleKey(eventUpdate.roomID, eventId).toString(),
    1164           1 :         eventUpdate.content,
    1165             :       );
    1166             : 
    1167             :       // Update timeline fragments
    1168           3 :       final key = TupleKey(eventUpdate.roomID, status.isSent ? '' : 'SENDING')
    1169           1 :           .toString();
    1170             : 
    1171             :       final eventIds =
    1172           4 :           List<String>.from(await _timelineFragmentsBox.get(key) ?? []);
    1173             : 
    1174           1 :       if (!eventIds.contains(eventId)) {
    1175           2 :         if (eventUpdate.type == EventUpdateType.history) {
    1176           1 :           eventIds.add(eventId);
    1177             :         } else {
    1178           1 :           eventIds.insert(0, eventId);
    1179             :         }
    1180           2 :         await _timelineFragmentsBox.put(key, eventIds);
    1181           0 :       } else if (status.isSynced &&
    1182             :           prevStatus != null &&
    1183           0 :           prevStatus.isSent &&
    1184           0 :           eventUpdate.type != EventUpdateType.history) {
    1185             :         // Status changes from 1 -> 2? Make sure event is correctly sorted.
    1186           0 :         eventIds.remove(eventId);
    1187           0 :         eventIds.insert(0, eventId);
    1188             :       }
    1189             : 
    1190             :       // If event comes from server timeline, remove sending events with this ID
    1191           1 :       if (status.isSent) {
    1192           3 :         final key = TupleKey(eventUpdate.roomID, 'SENDING').toString();
    1193             :         final eventIds =
    1194           4 :             List<String>.from(await _timelineFragmentsBox.get(key) ?? []);
    1195           1 :         final i = eventIds.indexWhere((id) => id == eventId);
    1196           2 :         if (i != -1) {
    1197           0 :           await _timelineFragmentsBox.put(key, eventIds..removeAt(i));
    1198             :         }
    1199             :       }
    1200             : 
    1201             :       // Is there a transaction id? Then delete the event with this id.
    1202           2 :       if (!status.isError && !status.isSending && transactionId != null) {
    1203           0 :         await removeEvent(transactionId, eventUpdate.roomID);
    1204             :       }
    1205             :     }
    1206             : 
    1207           2 :     final stateKey = eventUpdate.content['state_key'];
    1208             :     // Store a common state event
    1209             :     if (stateKey != null &&
    1210             :         // Don't store events as state updates when paginating backwards.
    1211           2 :         (eventUpdate.type == EventUpdateType.timeline ||
    1212           2 :             eventUpdate.type == EventUpdateType.state ||
    1213           2 :             eventUpdate.type == EventUpdateType.inviteState)) {
    1214           3 :       if (eventUpdate.content['type'] == EventTypes.RoomMember) {
    1215           0 :         await _roomMembersBox.put(
    1216           0 :           TupleKey(
    1217           0 :             eventUpdate.roomID,
    1218           0 :             eventUpdate.content['state_key'],
    1219           0 :           ).toString(),
    1220           0 :           eventUpdate.content,
    1221             :         );
    1222             :       } else {
    1223           1 :         final key = TupleKey(
    1224           1 :           eventUpdate.roomID,
    1225           2 :           eventUpdate.content['type'],
    1226           1 :         ).toString();
    1227           4 :         final stateMap = copyMap(await _roomStateBox.get(key) ?? {});
    1228             : 
    1229           2 :         stateMap[stateKey] = eventUpdate.content;
    1230           2 :         await _roomStateBox.put(key, stateMap);
    1231             :       }
    1232             :     }
    1233             : 
    1234             :     // Store a room account data event
    1235           2 :     if (eventUpdate.type == EventUpdateType.accountData) {
    1236           0 :       await _roomAccountDataBox.put(
    1237           0 :         TupleKey(
    1238           0 :           eventUpdate.roomID,
    1239           0 :           eventUpdate.content['type'],
    1240           0 :         ).toString(),
    1241           0 :         eventUpdate.content,
    1242             :       );
    1243             :     }
    1244             :   }
    1245             : 
    1246           1 :   @override
    1247             :   Future<void> storeFile(Uri mxcUri, Uint8List bytes, int time) async {
    1248             :     return;
    1249             :   }
    1250             : 
    1251           1 :   @override
    1252             :   Future<void> storeInboundGroupSession(
    1253             :     String roomId,
    1254             :     String sessionId,
    1255             :     String pickle,
    1256             :     String content,
    1257             :     String indexes,
    1258             :     String allowedAtIndex,
    1259             :     String senderKey,
    1260             :     String senderClaimedKey,
    1261             :   ) async {
    1262           2 :     await _inboundGroupSessionsBox.put(
    1263             :       sessionId,
    1264           1 :       StoredInboundGroupSession(
    1265             :         roomId: roomId,
    1266             :         sessionId: sessionId,
    1267             :         pickle: pickle,
    1268             :         content: content,
    1269             :         indexes: indexes,
    1270             :         allowedAtIndex: allowedAtIndex,
    1271             :         senderKey: senderKey,
    1272             :         senderClaimedKeys: senderClaimedKey,
    1273             :         uploaded: false,
    1274           1 :       ).toJson(),
    1275             :     );
    1276             :     return;
    1277             :   }
    1278             : 
    1279           1 :   @override
    1280             :   Future<void> storeOutboundGroupSession(
    1281             :     String roomId,
    1282             :     String pickle,
    1283             :     String deviceIds,
    1284             :     int creationTime,
    1285             :   ) async {
    1286           3 :     await _outboundGroupSessionsBox.put(roomId, <String, dynamic>{
    1287             :       'room_id': roomId,
    1288             :       'pickle': pickle,
    1289             :       'device_ids': deviceIds,
    1290             :       'creation_time': creationTime,
    1291             :     });
    1292             :     return;
    1293             :   }
    1294             : 
    1295           0 :   @override
    1296             :   Future<void> storePrevBatch(
    1297             :     String prevBatch,
    1298             :   ) async {
    1299           0 :     if ((await _clientBox.getAllKeys()).isEmpty) return;
    1300           0 :     await _clientBox.put('prev_batch', prevBatch);
    1301             :     return;
    1302             :   }
    1303             : 
    1304           1 :   @override
    1305             :   Future<void> storeRoomUpdate(
    1306             :     String roomId,
    1307             :     SyncRoomUpdate roomUpdate,
    1308             :     Event? lastEvent,
    1309             :     Client client,
    1310             :   ) async {
    1311             :     // Leave room if membership is leave
    1312           1 :     if (roomUpdate is LeftRoomUpdate) {
    1313           0 :       await forgetRoom(roomId);
    1314             :       return;
    1315             :     }
    1316           1 :     final membership = roomUpdate is LeftRoomUpdate
    1317             :         ? Membership.leave
    1318           1 :         : roomUpdate is InvitedRoomUpdate
    1319             :             ? Membership.invite
    1320             :             : Membership.join;
    1321             :     // Make sure room exists
    1322           2 :     final currentRawRoom = await _roomsBox.get(roomId);
    1323             :     if (currentRawRoom == null) {
    1324           2 :       await _roomsBox.put(
    1325             :         roomId,
    1326           1 :         roomUpdate is JoinedRoomUpdate
    1327           1 :             ? Room(
    1328             :                 client: client,
    1329             :                 id: roomId,
    1330             :                 membership: membership,
    1331             :                 highlightCount:
    1332           1 :                     roomUpdate.unreadNotifications?.highlightCount?.toInt() ??
    1333             :                         0,
    1334             :                 notificationCount: roomUpdate
    1335           1 :                         .unreadNotifications?.notificationCount
    1336           0 :                         ?.toInt() ??
    1337             :                     0,
    1338           1 :                 prev_batch: roomUpdate.timeline?.prevBatch,
    1339           1 :                 summary: roomUpdate.summary,
    1340             :                 lastEvent: lastEvent,
    1341           1 :               ).toJson()
    1342           0 :             : Room(
    1343             :                 client: client,
    1344             :                 id: roomId,
    1345             :                 membership: membership,
    1346             :                 lastEvent: lastEvent,
    1347           0 :               ).toJson(),
    1348             :       );
    1349           0 :     } else if (roomUpdate is JoinedRoomUpdate) {
    1350           0 :       final currentRoom = Room.fromJson(copyMap(currentRawRoom), client);
    1351           0 :       await _roomsBox.put(
    1352             :         roomId,
    1353           0 :         Room(
    1354             :           client: client,
    1355             :           id: roomId,
    1356             :           membership: membership,
    1357             :           highlightCount:
    1358           0 :               roomUpdate.unreadNotifications?.highlightCount?.toInt() ??
    1359           0 :                   currentRoom.highlightCount,
    1360             :           notificationCount:
    1361           0 :               roomUpdate.unreadNotifications?.notificationCount?.toInt() ??
    1362           0 :                   currentRoom.notificationCount,
    1363           0 :           prev_batch: roomUpdate.timeline?.prevBatch ?? currentRoom.prev_batch,
    1364           0 :           summary: RoomSummary.fromJson(
    1365           0 :             currentRoom.summary.toJson()
    1366           0 :               ..addAll(roomUpdate.summary?.toJson() ?? {}),
    1367             :           ),
    1368             :           lastEvent: lastEvent,
    1369           0 :         ).toJson(),
    1370             :       );
    1371             :     }
    1372             :   }
    1373             : 
    1374           0 :   @override
    1375             :   Future<void> deleteTimelineForRoom(String roomId) =>
    1376           0 :       _timelineFragmentsBox.delete(TupleKey(roomId, '').toString());
    1377             : 
    1378           1 :   @override
    1379             :   Future<void> storeSSSSCache(
    1380             :     String type,
    1381             :     String keyId,
    1382             :     String ciphertext,
    1383             :     String content,
    1384             :   ) async {
    1385           2 :     await _ssssCacheBox.put(
    1386             :       type,
    1387           1 :       SSSSCache(
    1388             :         type: type,
    1389             :         keyId: keyId,
    1390             :         ciphertext: ciphertext,
    1391             :         content: content,
    1392           1 :       ).toJson(),
    1393             :     );
    1394             :   }
    1395             : 
    1396           1 :   @override
    1397             :   Future<void> storeSyncFilterId(
    1398             :     String syncFilterId,
    1399             :   ) async {
    1400           2 :     await _clientBox.put('sync_filter_id', syncFilterId);
    1401             :   }
    1402             : 
    1403           1 :   @override
    1404             :   Future<void> storeUserCrossSigningKey(
    1405             :     String userId,
    1406             :     String publicKey,
    1407             :     String content,
    1408             :     bool verified,
    1409             :     bool blocked,
    1410             :   ) async {
    1411           2 :     await _userCrossSigningKeysBox.put(
    1412           2 :       TupleKey(userId, publicKey).toString(),
    1413           1 :       {
    1414             :         'user_id': userId,
    1415             :         'public_key': publicKey,
    1416             :         'content': content,
    1417             :         'verified': verified,
    1418             :         'blocked': blocked,
    1419             :       },
    1420             :     );
    1421             :   }
    1422             : 
    1423           1 :   @override
    1424             :   Future<void> storeUserDeviceKey(
    1425             :     String userId,
    1426             :     String deviceId,
    1427             :     String content,
    1428             :     bool verified,
    1429             :     bool blocked,
    1430             :     int lastActive,
    1431             :   ) async {
    1432           5 :     await _userDeviceKeysBox.put(TupleKey(userId, deviceId).toString(), {
    1433             :       'user_id': userId,
    1434             :       'device_id': deviceId,
    1435             :       'content': content,
    1436             :       'verified': verified,
    1437             :       'blocked': blocked,
    1438             :       'last_active': lastActive,
    1439             :       'last_sent_message': '',
    1440             :     });
    1441             :     return;
    1442             :   }
    1443             : 
    1444           1 :   @override
    1445             :   Future<void> storeUserDeviceKeysInfo(String userId, bool outdated) async {
    1446           2 :     await _userDeviceKeysOutdatedBox.put(userId, outdated);
    1447             :     return;
    1448             :   }
    1449             : 
    1450           1 :   @override
    1451             :   Future<void> transaction(Future<void> Function() action) =>
    1452           2 :       _collection.transaction(action);
    1453             : 
    1454           1 :   @override
    1455             :   Future<void> updateClient(
    1456             :     String homeserverUrl,
    1457             :     String token,
    1458             :     DateTime? tokenExpiresAt,
    1459             :     String? refreshToken,
    1460             :     String userId,
    1461             :     String? deviceId,
    1462             :     String? deviceName,
    1463             :     String? prevBatch,
    1464             :     String? olmAccount,
    1465             :   ) async {
    1466           2 :     await transaction(() async {
    1467           2 :       await _clientBox.put('homeserver_url', homeserverUrl);
    1468           2 :       await _clientBox.put('token', token);
    1469             :       if (tokenExpiresAt == null) {
    1470           0 :         await _clientBox.delete('token_expires_at');
    1471             :       } else {
    1472           2 :         await _clientBox.put(
    1473             :           'token_expires_at',
    1474           2 :           tokenExpiresAt.millisecondsSinceEpoch.toString(),
    1475             :         );
    1476             :       }
    1477             :       if (refreshToken == null) {
    1478           0 :         await _clientBox.delete('refresh_token');
    1479             :       } else {
    1480           2 :         await _clientBox.put('refresh_token', refreshToken);
    1481             :       }
    1482           2 :       await _clientBox.put('user_id', userId);
    1483             :       if (deviceId == null) {
    1484           0 :         await _clientBox.delete('device_id');
    1485             :       } else {
    1486           2 :         await _clientBox.put('device_id', deviceId);
    1487             :       }
    1488             :       if (deviceName == null) {
    1489           0 :         await _clientBox.delete('device_name');
    1490             :       } else {
    1491           2 :         await _clientBox.put('device_name', deviceName);
    1492             :       }
    1493             :       if (prevBatch == null) {
    1494           0 :         await _clientBox.delete('prev_batch');
    1495             :       } else {
    1496           2 :         await _clientBox.put('prev_batch', prevBatch);
    1497             :       }
    1498             :       if (olmAccount == null) {
    1499           0 :         await _clientBox.delete('olm_account');
    1500             :       } else {
    1501           2 :         await _clientBox.put('olm_account', olmAccount);
    1502             :       }
    1503             :     });
    1504             :     return;
    1505             :   }
    1506             : 
    1507           1 :   @override
    1508             :   Future<void> updateClientKeys(
    1509             :     String olmAccount,
    1510             :   ) async {
    1511           2 :     await _clientBox.put('olm_account', olmAccount);
    1512             :     return;
    1513             :   }
    1514             : 
    1515           1 :   @override
    1516             :   Future<void> updateInboundGroupSessionAllowedAtIndex(
    1517             :     String allowedAtIndex,
    1518             :     String roomId,
    1519             :     String sessionId,
    1520             :   ) async {
    1521           2 :     final raw = await _inboundGroupSessionsBox.get(sessionId);
    1522             :     if (raw == null) {
    1523           0 :       Logs().w(
    1524             :         'Tried to update inbound group session as uploaded which wasnt found in the database!',
    1525             :       );
    1526             :       return;
    1527             :     }
    1528           1 :     raw['allowed_at_index'] = allowedAtIndex;
    1529           2 :     await _inboundGroupSessionsBox.put(sessionId, raw);
    1530             :     return;
    1531             :   }
    1532             : 
    1533           1 :   @override
    1534             :   Future<void> updateInboundGroupSessionIndexes(
    1535             :     String indexes,
    1536             :     String roomId,
    1537             :     String sessionId,
    1538             :   ) async {
    1539           2 :     final raw = await _inboundGroupSessionsBox.get(sessionId);
    1540             :     if (raw == null) {
    1541           0 :       Logs().w(
    1542             :         'Tried to update inbound group session indexes of a session which was not found in the database!',
    1543             :       );
    1544             :       return;
    1545             :     }
    1546           1 :     final json = copyMap(raw);
    1547           1 :     json['indexes'] = indexes;
    1548           2 :     await _inboundGroupSessionsBox.put(sessionId, json);
    1549             :     return;
    1550             :   }
    1551             : 
    1552           1 :   @override
    1553             :   Future<List<StoredInboundGroupSession>> getAllInboundGroupSessions() async {
    1554           2 :     final rawSessions = await _inboundGroupSessionsBox.getAllValues();
    1555           1 :     return rawSessions.values
    1556           1 :         .map((raw) => StoredInboundGroupSession.fromJson(copyMap(raw)))
    1557           1 :         .toList();
    1558             :   }
    1559             : 
    1560           0 :   @override
    1561             :   Future<void> addSeenDeviceId(
    1562             :     String userId,
    1563             :     String deviceId,
    1564             :     String publicKeys,
    1565             :   ) =>
    1566           0 :       _seenDeviceIdsBox.put(TupleKey(userId, deviceId).toString(), publicKeys);
    1567             : 
    1568           0 :   @override
    1569             :   Future<void> addSeenPublicKey(
    1570             :     String publicKey,
    1571             :     String deviceId,
    1572             :   ) =>
    1573           0 :       _seenDeviceKeysBox.put(publicKey, deviceId);
    1574             : 
    1575           0 :   @override
    1576             :   Future<String?> deviceIdSeen(userId, deviceId) async {
    1577             :     final raw =
    1578           0 :         await _seenDeviceIdsBox.get(TupleKey(userId, deviceId).toString());
    1579             :     if (raw == null) return null;
    1580             :     return raw;
    1581             :   }
    1582             : 
    1583           0 :   @override
    1584             :   Future<String?> publicKeySeen(String publicKey) async {
    1585           0 :     final raw = await _seenDeviceKeysBox.get(publicKey);
    1586             :     if (raw == null) return null;
    1587             :     return raw;
    1588             :   }
    1589             : 
    1590           1 :   @override
    1591             :   Future<void> storePresence(String userId, CachedPresence presence) =>
    1592           3 :       _presencesBox.put(userId, presence.toJson());
    1593             : 
    1594           1 :   @override
    1595             :   Future<CachedPresence?> getPresence(String userId) async {
    1596           2 :     final rawPresence = await _presencesBox.get(userId);
    1597             :     if (rawPresence == null) return null;
    1598             : 
    1599           2 :     return CachedPresence.fromJson(copyMap(rawPresence));
    1600             :   }
    1601             : 
    1602           0 :   @override
    1603             :   Future<String> exportDump() async {
    1604           0 :     final dataMap = {
    1605           0 :       _clientBoxName: await _clientBox.getAllValues(),
    1606           0 :       _accountDataBoxName: await _accountDataBox.getAllValues(),
    1607           0 :       _roomsBoxName: await _roomsBox.getAllValues(),
    1608           0 :       _roomStateBoxName: await _roomStateBox.getAllValues(),
    1609           0 :       _roomMembersBoxName: await _roomMembersBox.getAllValues(),
    1610           0 :       _toDeviceQueueBoxName: await _toDeviceQueueBox.getAllValues(),
    1611           0 :       _roomAccountDataBoxName: await _roomAccountDataBox.getAllValues(),
    1612           0 :       _inboundGroupSessionsBoxName:
    1613           0 :           await _inboundGroupSessionsBox.getAllValues(),
    1614           0 :       _outboundGroupSessionsBoxName:
    1615           0 :           await _outboundGroupSessionsBox.getAllValues(),
    1616           0 :       _olmSessionsBoxName: await _olmSessionsBox.getAllValues(),
    1617           0 :       _userDeviceKeysBoxName: await _userDeviceKeysBox.getAllValues(),
    1618           0 :       _userDeviceKeysOutdatedBoxName:
    1619           0 :           await _userDeviceKeysOutdatedBox.getAllValues(),
    1620           0 :       _userCrossSigningKeysBoxName:
    1621           0 :           await _userCrossSigningKeysBox.getAllValues(),
    1622           0 :       _ssssCacheBoxName: await _ssssCacheBox.getAllValues(),
    1623           0 :       _presencesBoxName: await _presencesBox.getAllValues(),
    1624           0 :       _timelineFragmentsBoxName: await _timelineFragmentsBox.getAllValues(),
    1625           0 :       _eventsBoxName: await _eventsBox.getAllValues(),
    1626           0 :       _seenDeviceIdsBoxName: await _seenDeviceIdsBox.getAllValues(),
    1627           0 :       _seenDeviceKeysBoxName: await _seenDeviceKeysBox.getAllValues(),
    1628             :     };
    1629           0 :     final json = jsonEncode(dataMap);
    1630           0 :     await clear();
    1631             :     return json;
    1632             :   }
    1633             : 
    1634           0 :   @override
    1635             :   Future<bool> importDump(String export) async {
    1636             :     try {
    1637           0 :       await clear();
    1638           0 :       await open();
    1639           0 :       final json = Map.from(jsonDecode(export)).cast<String, Map>();
    1640           0 :       for (final key in json[_clientBoxName]!.keys) {
    1641           0 :         await _clientBox.put(key, json[_clientBoxName]![key]);
    1642             :       }
    1643           0 :       for (final key in json[_accountDataBoxName]!.keys) {
    1644           0 :         await _accountDataBox.put(key, json[_accountDataBoxName]![key]);
    1645             :       }
    1646           0 :       for (final key in json[_roomsBoxName]!.keys) {
    1647           0 :         await _roomsBox.put(key, json[_roomsBoxName]![key]);
    1648             :       }
    1649           0 :       for (final key in json[_roomStateBoxName]!.keys) {
    1650           0 :         await _roomStateBox.put(key, json[_roomStateBoxName]![key]);
    1651             :       }
    1652           0 :       for (final key in json[_roomMembersBoxName]!.keys) {
    1653           0 :         await _roomMembersBox.put(key, json[_roomMembersBoxName]![key]);
    1654             :       }
    1655           0 :       for (final key in json[_toDeviceQueueBoxName]!.keys) {
    1656           0 :         await _toDeviceQueueBox.put(key, json[_toDeviceQueueBoxName]![key]);
    1657             :       }
    1658           0 :       for (final key in json[_roomAccountDataBoxName]!.keys) {
    1659           0 :         await _roomAccountDataBox.put(key, json[_roomAccountDataBoxName]![key]);
    1660             :       }
    1661           0 :       for (final key in json[_inboundGroupSessionsBoxName]!.keys) {
    1662           0 :         await _inboundGroupSessionsBox.put(
    1663             :           key,
    1664           0 :           json[_inboundGroupSessionsBoxName]![key],
    1665             :         );
    1666             :       }
    1667           0 :       for (final key in json[_outboundGroupSessionsBoxName]!.keys) {
    1668           0 :         await _outboundGroupSessionsBox.put(
    1669             :           key,
    1670           0 :           json[_outboundGroupSessionsBoxName]![key],
    1671             :         );
    1672             :       }
    1673           0 :       for (final key in json[_olmSessionsBoxName]!.keys) {
    1674           0 :         await _olmSessionsBox.put(key, json[_olmSessionsBoxName]![key]);
    1675             :       }
    1676           0 :       for (final key in json[_userDeviceKeysBoxName]!.keys) {
    1677           0 :         await _userDeviceKeysBox.put(key, json[_userDeviceKeysBoxName]![key]);
    1678             :       }
    1679           0 :       for (final key in json[_userDeviceKeysOutdatedBoxName]!.keys) {
    1680           0 :         await _userDeviceKeysOutdatedBox.put(
    1681             :           key,
    1682           0 :           json[_userDeviceKeysOutdatedBoxName]![key],
    1683             :         );
    1684             :       }
    1685           0 :       for (final key in json[_userCrossSigningKeysBoxName]!.keys) {
    1686           0 :         await _userCrossSigningKeysBox.put(
    1687             :           key,
    1688           0 :           json[_userCrossSigningKeysBoxName]![key],
    1689             :         );
    1690             :       }
    1691           0 :       for (final key in json[_ssssCacheBoxName]!.keys) {
    1692           0 :         await _ssssCacheBox.put(key, json[_ssssCacheBoxName]![key]);
    1693             :       }
    1694           0 :       for (final key in json[_presencesBoxName]!.keys) {
    1695           0 :         await _presencesBox.put(key, json[_presencesBoxName]![key]);
    1696             :       }
    1697           0 :       for (final key in json[_timelineFragmentsBoxName]!.keys) {
    1698           0 :         await _timelineFragmentsBox.put(
    1699             :           key,
    1700           0 :           json[_timelineFragmentsBoxName]![key],
    1701             :         );
    1702             :       }
    1703           0 :       for (final key in json[_seenDeviceIdsBoxName]!.keys) {
    1704           0 :         await _seenDeviceIdsBox.put(key, json[_seenDeviceIdsBoxName]![key]);
    1705             :       }
    1706           0 :       for (final key in json[_seenDeviceKeysBoxName]!.keys) {
    1707           0 :         await _seenDeviceKeysBox.put(key, json[_seenDeviceKeysBoxName]![key]);
    1708             :       }
    1709             :       return true;
    1710             :     } catch (e, s) {
    1711           0 :       Logs().e('Database import error: ', e, s);
    1712             :       return false;
    1713             :     }
    1714             :   }
    1715             : 
    1716           0 :   @override
    1717             :   Future<void> storeWellKnown(DiscoveryInformation? discoveryInformation) {
    1718             :     if (discoveryInformation == null) {
    1719           0 :       return _clientBox.delete('discovery_information');
    1720             :     }
    1721           0 :     return _clientBox.put(
    1722             :       'discovery_information',
    1723           0 :       jsonEncode(discoveryInformation.toJson()),
    1724             :     );
    1725             :   }
    1726             : 
    1727           0 :   @override
    1728             :   Future<DiscoveryInformation?> getWellKnown() async {
    1729             :     final rawDiscoveryInformation =
    1730           0 :         await _clientBox.get('discovery_information');
    1731             :     if (rawDiscoveryInformation == null) return null;
    1732           0 :     return DiscoveryInformation.fromJson(jsonDecode(rawDiscoveryInformation));
    1733             :   }
    1734             : 
    1735           0 :   @override
    1736           0 :   Future<void> delete() => _collection.deleteFromDisk();
    1737             : 
    1738           0 :   @override
    1739             :   Future<void> markUserProfileAsOutdated(userId) async {
    1740             :     return;
    1741             :   }
    1742             : 
    1743           1 :   @override
    1744             :   Future<CachedProfileInformation?> getUserProfile(String userId) async {
    1745             :     return null;
    1746             :   }
    1747             : 
    1748           1 :   @override
    1749             :   Future<void> storeUserProfile(
    1750             :     String userId,
    1751             :     CachedProfileInformation profile,
    1752             :   ) async {
    1753             :     return;
    1754             :   }
    1755             : }
    1756             : 
    1757             : class TupleKey {
    1758             :   final List<String> parts;
    1759             : 
    1760          34 :   TupleKey(String key1, [String? key2, String? key3])
    1761          34 :       : parts = [
    1762             :           key1,
    1763          34 :           if (key2 != null) key2,
    1764           0 :           if (key3 != null) key3,
    1765             :         ];
    1766             : 
    1767           0 :   const TupleKey.byParts(this.parts);
    1768             : 
    1769          34 :   TupleKey.fromString(String multiKeyString)
    1770          68 :       : parts = multiKeyString.split('|').toList();
    1771             : 
    1772          34 :   @override
    1773          68 :   String toString() => parts.join('|');
    1774             : 
    1775           0 :   @override
    1776           0 :   bool operator ==(other) => parts.toString() == other.toString();
    1777             : 
    1778           0 :   @override
    1779           0 :   int get hashCode => Object.hashAll(parts);
    1780             : }

Generated by: LCOV version 1.14