LCOV - code coverage report
Current view: top level - lib/src/database - hive_collections_database.dart (source / functions) Hit Total Coverage
Test: merged.info Lines: 506 782 64.7 %
Date: 2025-01-06 12:44:40 Functions: 0 0 -

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

Generated by: LCOV version 1.14