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

Generated by: LCOV version 1.14