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