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