LCOV - code coverage report
Current view: top level - lib/src/database - hive_database.dart (source / functions) Hit Total Coverage
Test: merged.info Lines: 487 709 68.7 %
Date: 2024-11-12 07:37:08 Functions: 0 0 -

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

Generated by: LCOV version 1.14