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