diff --git a/lib/storage/constants.dart b/lib/storage/constants.dart index 91f21bf44..2c0cf10c7 100644 --- a/lib/storage/constants.dart +++ b/lib/storage/constants.dart @@ -1,5 +1,12 @@ class StorageKeys { - static const String RECEIPTS = 'RECEIPTS'; - static const String REDACTIONS = 'REDACTIONS'; + static const String AUTH = 'auth'; static const String ROOMS = 'rooms'; + static const String USERS = 'users'; + static const String MEDIA = 'MEDIA'; + static const String CRYPTO = 'crypto'; + static const String EVENTS = 'events'; + static const String MESSAGES = 'messages'; + static const String RECEIPTS = 'receipts'; + static const String REACTIONS = 'reactions'; + static const String REDACTIONS = 'redactions'; } diff --git a/lib/storage/index.dart b/lib/storage/index.dart index 5bfdcb7bd..5626bd0bd 100644 --- a/lib/storage/index.dart +++ b/lib/storage/index.dart @@ -12,6 +12,7 @@ import 'package:sqflite/sqflite.dart' as sqflite; import 'package:sqflite_common_ffi/sqflite_ffi.dart' as sqflite_ffi; import 'package:syphon/store/auth/storage.dart'; import 'package:syphon/store/crypto/storage.dart'; +import 'package:syphon/store/events/ephemeral/m.read/model.dart'; import 'package:syphon/store/events/messages/model.dart'; import 'package:syphon/store/events/reactions/model.dart'; import 'package:syphon/store/events/receipts/storage.dart'; @@ -114,58 +115,65 @@ Future deleteStorage() async { * TODO: need pagination for pretty much all of these */ Future> loadStorage(Database storage) async { - final auth = await loadAuth( - storage: storage, - ); + try { + final auth = await loadAuth( + storage: storage, + ); - final rooms = await loadRooms( - storage: storage, - ); + final rooms = await loadRooms( + storage: storage, + ); - final users = await loadUsers( - storage: storage, - ); + final users = await loadUsers( + storage: storage, + ); - final media = await loadMediaAll( - storage: storage, - ); + final media = await loadMediaAll( + storage: storage, + ); + + final crypto = await loadCrypto( + storage: storage, + ); - final crypto = await loadCrypto( - storage: storage, - ); + final redactions = await loadRedactions( + storage: storage, + ); - final redactions = await loadRedactions( - storage: storage, - ); + Map> messages = Map(); + Map> reactions = Map(); + Map> receipts = Map(); - final receipts = await loadReceipts( - storage: storage, - ); + for (Room room in rooms.values) { + messages[room.id] = await loadMessages( + room.messageIds, + storage: storage, + ); - Map> messages = Map(); - Map> reactions = Map(); + reactions.addAll(await loadReactions( + room.messageIds, + storage: storage, + )); - for (Room room in rooms.values) { - messages[room.id] = await loadMessages( - room.messageIds, - storage: storage, - ); + receipts[room.id] = await loadReceipts( + room.messageIds, + storage: storage, + ); + } - reactions.addAll(await loadReactions( - room.messageIds, - storage: storage, - )); + return { + 'auth': auth, + 'users': users, + 'rooms': rooms, + 'media': media, + 'crypto': crypto, + 'messages': messages.isNotEmpty ? messages : null, + 'reactions': reactions, + 'redactions': redactions, + 'receipts': receipts, + }; + } catch (error) { + printError('[loadStorage] ${error.toString()}'); + return {}; } - - return { - 'auth': auth, - 'users': users, - 'rooms': rooms, - 'media': media, - 'crypto': crypto, - 'messages': messages.isNotEmpty ? messages : null, - 'reactions': reactions, - 'redactions': redactions, - 'receipts': receipts, - }; } diff --git a/lib/store/auth/storage.dart b/lib/store/auth/storage.dart index 27d47d423..c52eb6c9d 100644 --- a/lib/store/auth/storage.dart +++ b/lib/store/auth/storage.dart @@ -2,18 +2,17 @@ import 'dart:convert'; import 'package:sembast/sembast.dart'; import 'package:syphon/global/print.dart'; +import 'package:syphon/storage/constants.dart'; import 'package:syphon/store/auth/state.dart'; -const String AUTH = 'auth'; - Future saveAuth( AuthStore authStore, { Database storage, }) async { - final store = StoreRef(AUTH); + final store = StoreRef(StorageKeys.AUTH); return await storage.transaction((txn) async { - final record = store.record(AUTH); + final record = store.record(StorageKeys.AUTH); await record.put(txn, json.encode(authStore)); }); } @@ -26,9 +25,9 @@ Future saveAuth( */ Future loadAuth({Database storage}) async { try { - final store = StoreRef(AUTH); + final store = StoreRef(StorageKeys.AUTH); - final auth = await store.record(AUTH).get(storage); + final auth = await store.record(StorageKeys.AUTH).get(storage); if (auth == null) { return null; diff --git a/lib/store/crypto/storage.dart b/lib/store/crypto/storage.dart index fb9d8a97f..71954ec6e 100644 --- a/lib/store/crypto/storage.dart +++ b/lib/store/crypto/storage.dart @@ -3,10 +3,9 @@ import 'dart:convert'; import 'package:redux/redux.dart'; import 'package:sembast/sembast.dart'; import 'package:syphon/global/print.dart'; +import 'package:syphon/storage/constants.dart'; import 'package:syphon/store/crypto/state.dart'; -const String CRYPTO = 'crypto'; - /** * Save Crypto Store * @@ -17,10 +16,10 @@ Future saveCrypto( CryptoStore cryptoStore, { Database storage, }) async { - final store = StoreRef(CRYPTO); + final store = StoreRef(StorageKeys.CRYPTO); return await storage.transaction((txn) async { - final record = store.record(CRYPTO); + final record = store.record(StorageKeys.CRYPTO); await record.put(txn, json.encode(cryptoStore)); }); } @@ -33,9 +32,9 @@ Future saveCrypto( */ Future loadCrypto({Database storage}) async { try { - final store = StoreRef(CRYPTO); + final store = StoreRef(StorageKeys.CRYPTO); - final crypto = await store.record(CRYPTO).get(storage); + final crypto = await store.record(StorageKeys.CRYPTO).get(storage); if (crypto == null) { return null; diff --git a/lib/store/events/parsers.dart b/lib/store/events/parsers.dart index 751fd7060..369f9d440 100644 --- a/lib/store/events/parsers.dart +++ b/lib/store/events/parsers.dart @@ -15,6 +15,21 @@ import 'package:syphon/global/libs/matrix/constants.dart'; import 'package:syphon/store/events/messages/model.dart'; import 'package:syphon/store/events/reactions/model.dart'; import 'package:syphon/store/rooms/room/model.dart'; +import 'package:syphon/store/user/model.dart'; + +Room parseRoom(Map params) { + Map json = params['json']; + Room room = params['room']; + User currentUser = params['currentUser']; + String lastSince = params['lastSince']; + + // TODO: eventually remove the need for this with modular parsers + return room.fromSync( + json: json, + currentUser: currentUser, + lastSince: lastSince, + ); +} Map parseMessages({ Room room, diff --git a/lib/store/events/receipts/storage.dart b/lib/store/events/receipts/storage.dart index 74e104928..a10215090 100644 --- a/lib/store/events/receipts/storage.dart +++ b/lib/store/events/receipts/storage.dart @@ -12,15 +12,20 @@ import 'package:syphon/store/events/redaction/model.dart'; /// /// Future saveReceipts( - String roomId, Map receipts, { Database storage, + bool ready, }) async { final store = StoreRef(StorageKeys.RECEIPTS); + // TODO: the initial sync loads way too many read receipts + if (!ready) return; + return await storage.transaction((txn) async { - final record = store.record(roomId); - await record.put(txn, json.encode(receipts)); + for (String key in receipts.keys) { + final record = store.record(key); + await record.put(txn, json.encode(receipts[key])); + } }); } @@ -29,23 +34,25 @@ Future saveReceipts( /// /// Iterates through /// -Future>> loadReceipts({ +Future> loadReceipts( + List messageIds, { Database storage, }) async { - final store = StoreRef(StorageKeys.RECEIPTS); - - final receipts = Map>(); - - final roomReceipts = await store.find(storage); - - for (RecordSnapshot record in roomReceipts) { - final testing = await json.decode(record.value); - final mapped = Map.from(testing); - final Map converted = mapped.map( - (key, value) => MapEntry(key, ReadReceipt.fromJson(value)), - ); - receipts[record.key] = converted; + try { + final store = StoreRef(StorageKeys.RECEIPTS); + + final receiptsMap = Map(); + final records = await store.records(messageIds).getSnapshots(storage); + + for (RecordSnapshot record in records ?? []) { + if (record != null) { + final receipt = ReadReceipt.fromJson(await json.decode(record.value)); + receiptsMap.putIfAbsent(record.key, () => receipt); + } + } + return receiptsMap; + } catch (error) { + printError(error.toString()); + return Map(); } - - return receipts; } diff --git a/lib/store/events/storage.dart b/lib/store/events/storage.dart index b9b5087ba..b98d3e4fe 100644 --- a/lib/store/events/storage.dart +++ b/lib/store/events/storage.dart @@ -3,23 +3,18 @@ import 'dart:convert'; import 'package:sembast/sembast.dart'; import 'package:syphon/global/algos.dart'; import 'package:syphon/global/print.dart'; +import 'package:syphon/storage/constants.dart'; import 'package:syphon/store/events/ephemeral/m.read/model.dart'; import 'package:syphon/store/events/model.dart'; import 'package:syphon/store/events/messages/model.dart'; import 'package:syphon/store/events/reactions/model.dart'; import 'package:syphon/store/events/redaction/model.dart'; -const String EVENTS = 'events'; -const String MESSAGES = 'messages'; -const String RECEIPTS = 'receipts'; -const String REACTIONS = 'reactions'; -const String REDACTIONS = 'redactions'; - Future saveEvents( List events, { Database storage, }) async { - final store = StoreRef(EVENTS); + final store = StoreRef(StorageKeys.EVENTS); return await storage.transaction((txn) async { for (Event event in events) { @@ -34,8 +29,8 @@ Future deleteEvents( Database storage, }) async { final stores = [ - StoreRef(MESSAGES), - StoreRef(REACTIONS), + StoreRef(StorageKeys.MESSAGES), + StoreRef(StorageKeys.REACTIONS), ]; return await Future.wait(stores.map((store) async { @@ -59,7 +54,7 @@ Future saveRedactions( Database storage, }) async { try { - final store = StoreRef(REDACTIONS); + final store = StoreRef(StorageKeys.REDACTIONS); return await storage.transaction((txn) async { for (Redaction redaction in redactions) { @@ -83,7 +78,7 @@ Future saveRedactions( Future> loadRedactions({ Database storage, }) async { - final store = StoreRef(REDACTIONS); + final store = StoreRef(StorageKeys.REDACTIONS); final redactions = Map(); @@ -111,7 +106,7 @@ Future saveReactions( Database storage, }) async { try { - final store = StoreRef(REACTIONS); + final store = StoreRef(StorageKeys.REACTIONS); return await storage.transaction((txn) async { for (Reaction reaction in reactions) { @@ -157,28 +152,34 @@ Future>> loadReactions( List messageIds, { Database storage, }) async { - final store = StoreRef(REACTIONS); - final reactionsMap = Map>(); - final reactionsRecords = - await store.records(messageIds).getSnapshots(storage); - - for (RecordSnapshot reactionList in reactionsRecords ?? []) { - if (reactionList != null) { - final reactions = List.from(await json.decode(reactionList.value)) - .map((json) => Reaction.fromJson(json)) - .toList(); - reactionsMap.putIfAbsent(reactionList.key, () => reactions); + try { + final store = StoreRef(StorageKeys.REACTIONS); + final reactionsMap = Map>(); + final reactionsRecords = + await store.records(messageIds).getSnapshots(storage); + + for (RecordSnapshot reactionList + in reactionsRecords ?? []) { + if (reactionList != null) { + final reactions = List.from(await json.decode(reactionList.value)) + .map((json) => Reaction.fromJson(json)) + .toList(); + reactionsMap.putIfAbsent(reactionList.key, () => reactions); + } } - } - return reactionsMap; + return reactionsMap; + } catch (error) { + printError(error); + return Map(); + } } Future saveMessages( List messages, { Database storage, }) async { - final store = StoreRef(MESSAGES); + final store = StoreRef(StorageKeys.MESSAGES); return await storage.transaction((txn) async { for (Message message in messages) { @@ -189,7 +190,7 @@ Future saveMessages( } Future loadMessage(String eventId, {Database storage}) async { - final store = StoreRef(MESSAGES); + final store = StoreRef(StorageKeys.MESSAGES); final message = await store.record(eventId).get(storage); @@ -211,7 +212,7 @@ Future> loadMessages( final List messages = []; try { - final store = StoreRef(MESSAGES); + final store = StoreRef(StorageKeys.MESSAGES); // TODO: properly paginate through cold storage messages instead of loading all final messageIds = eventIds ?? []; //.skip(offset).take(limit).toList(); @@ -225,6 +226,6 @@ Future> loadMessages( return messages; } catch (error) { printError(error.toString(), title: 'loadMessages'); - return null; + return List(); } } diff --git a/lib/store/media/storage.dart b/lib/store/media/storage.dart index 32a47ba5e..a06deeaf1 100644 --- a/lib/store/media/storage.dart +++ b/lib/store/media/storage.dart @@ -4,14 +4,13 @@ import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:sembast/sembast.dart'; import 'package:syphon/global/print.dart'; - -const String MEDIA = 'MEDIA'; +import 'package:syphon/storage/constants.dart'; Future checkMedia( String mxcUri, { Database storage, }) async { - final store = StoreRef(MEDIA); + final store = StoreRef(StorageKeys.MEDIA); return await store.record(mxcUri).exists(storage); } @@ -21,7 +20,7 @@ Future saveMedia( Uint8List data, { Database storage, }) async { - final store = StoreRef(MEDIA); + final store = StoreRef(StorageKeys.MEDIA); return await storage.transaction((txn) async { final record = store.record(mxcUri); @@ -39,7 +38,7 @@ Future loadMedia({ Database storage, }) async { try { - final store = StoreRef(MEDIA); + final store = StoreRef(StorageKeys.MEDIA); final mediaData = await store.record(mxcUri).get(storage); @@ -64,7 +63,7 @@ Future> loadMediaAll({ }) async { try { final Map media = {}; - final store = StoreRef(MEDIA); + final store = StoreRef(StorageKeys.MEDIA); final mediaDataAll = await store.find(storage); diff --git a/lib/store/rooms/actions.dart b/lib/store/rooms/actions.dart index 3bc023a90..2e339e776 100644 --- a/lib/store/rooms/actions.dart +++ b/lib/store/rooms/actions.dart @@ -15,6 +15,7 @@ import 'package:syphon/global/print.dart'; import 'package:syphon/storage/index.dart'; import 'package:syphon/store/events/ephemeral/m.read/model.dart'; import 'package:syphon/store/events/messages/model.dart'; +import 'package:syphon/store/events/parsers.dart'; import 'package:syphon/store/events/reactions/model.dart'; import 'package:syphon/store/events/receipts/storage.dart'; import 'package:syphon/store/events/redaction/model.dart'; @@ -126,6 +127,7 @@ ThunkAction syncRooms(Map roomData) { // init new store containers final rooms = store.state.roomStore.rooms ?? Map(); final user = store.state.authStore.user; + final synced = store.state.syncStore.synced; final lastSince = store.state.syncStore.lastSince; // syncing null data happens sometimes? @@ -133,10 +135,8 @@ ThunkAction syncRooms(Map roomData) { return; } - // update those that exist or add a new room - return await Future.forEach(roomData.keys, (id) async { + await Future.wait(roomData.keys.map((id) async { final json = roomData[id]; - // use pre-existing values where available Room room = rooms.containsKey(id) ? rooms[id] : Room(id: id); // First past to decrypt encrypted events @@ -147,15 +147,16 @@ ThunkAction syncRooms(Map roomData) { ); } - // TODO: eventually remove the need for this with modular parsers - room = room.fromSync( - json: json, - currentUser: user, - lastSince: lastSince, - ); + // parse room and events + room = await compute(parseRoom, { + 'json': json, + 'room': room, + 'currentUser': user, + 'lastSince': lastSince, + }); printDebug( - '[syncRooms] ${room.name} ids ${room.messagesNew.length} | messages ${room.messageIds.length} | ${room.limited}', + '[syncRooms] ${room.name} initial: $synced limited: ${room.limited} total messages: ${room.messageIds.length}', ); // update cold storage @@ -165,7 +166,7 @@ ThunkAction syncRooms(Map roomData) { saveMessages(room.messagesNew, storage: Storage.main), saveReactions(room.reactions, storage: Storage.main), saveRedactions(room.redactions, storage: Storage.main), - saveReceipts(room.id, room.readReceipts, storage: Storage.main), + saveReceipts(room.readReceipts, storage: Storage.main, ready: synced), ]); // update store @@ -208,7 +209,7 @@ ThunkAction syncRooms(Map roomData) { from: room.prevHash, )); } - }); + })); }; } @@ -248,10 +249,10 @@ ThunkAction fetchRoom(String roomId) { '${roomId}': { 'state': { 'events': stateEvents, - 'prev_batch': messageEvents['from'], }, 'timeline': { 'events': messageEvents['chunk'], + 'prev_batch': messageEvents['from'], } }, })); @@ -270,7 +271,7 @@ ThunkAction fetchRoom(String roomId) { * Takes a negligible amount of time * */ -ThunkAction fetchRooms() { +ThunkAction fetchRooms({bool syncState = false}) { return (Store store) async { try { final data = await MatrixApi.fetchRoomIds( @@ -288,15 +289,19 @@ ThunkAction fetchRooms() { final joinedRooms = joinedRoomsRaw.map((id) => Room(id: id)).toList(); final fullJoinedRooms = joinedRooms.map((room) async { try { - final stateEvents = await MatrixApi.fetchStateEvents( - protocol: protocol, - homeserver: store.state.authStore.user.homeserver, - accessToken: store.state.authStore.user.accessToken, - roomId: room.id, - ); + var stateEvents; + + if (syncState) { + stateEvents = await MatrixApi.fetchStateEvents( + protocol: protocol, + homeserver: store.state.authStore.user.homeserver, + accessToken: store.state.authStore.user.accessToken, + roomId: room.id, + ); - if (!(stateEvents is List) && stateEvents['errcode'] != null) { - throw stateEvents['error']; + if (!(stateEvents is List) && stateEvents['errcode'] != null) { + throw stateEvents['error']; + } } final messageEvents = await compute( @@ -313,11 +318,11 @@ ThunkAction fetchRooms() { await store.dispatch(syncRooms({ '${room.id}': { 'state': { - 'events': stateEvents, - 'prev_batch': messageEvents['from'], + 'events': stateEvents ?? [], }, 'timeline': { 'events': messageEvents['chunk'], + 'prev_batch': messageEvents['from'], } }, })); diff --git a/lib/store/rooms/room/model.dart b/lib/store/rooms/room/model.dart index 139d7675b..242c48dc6 100644 --- a/lib/store/rooms/room/model.dart +++ b/lib/store/rooms/room/model.dart @@ -316,8 +316,8 @@ class Room { prevHash = json['timeline']['prev_batch']; if (limited != null) { - debugPrint( - '[fromSync] LIMITED ${limited} ${lastHash} ${prevHash}', + printDebug( + '[fromSync] ${this.id} limited ${limited} lastHash ${lastHash != null} prevHash ${prevHash != null}', ); } @@ -644,7 +644,7 @@ class Room { nextHash: nextHash ?? this.nextHash, ); } catch (error) { - debugPrint('[fromMessageEvents] $error'); + printError('[fromMessageEvents] $error'); return this; } } diff --git a/lib/store/sync/actions.dart b/lib/store/sync/actions.dart index 50805b577..b8482da34 100644 --- a/lib/store/sync/actions.dart +++ b/lib/store/sync/actions.dart @@ -193,13 +193,13 @@ ThunkAction setBackgrounded(bool backgrounded) { ThunkAction fetchSync({String since, bool forceFull = false}) { return (Store store) async { try { - debugPrint('[fetchSync] starting sync'); + debugPrint('[fetchSync] *** starting sync *** '); store.dispatch(SetSyncing(syncing: true)); final isFullSync = since == null; var filterId; if (isFullSync) { - debugPrint('[fetchSync] running full sync'); + debugPrint('[fetchSync] *** full sync running *** '); } // Normal matrix /sync call to the homeserver (Threaded) @@ -250,7 +250,7 @@ ThunkAction fetchSync({String since, bool forceFull = false}) { )); if (isFullSync) { - debugPrint('[fetchSync] full sync completed'); + debugPrint('[fetchSync] *** full sync completed ***'); } } catch (error) { store.dispatch(SetOffline(offline: true)); diff --git a/lib/store/user/storage.dart b/lib/store/user/storage.dart index 0bca3c151..d7518338f 100644 --- a/lib/store/user/storage.dart +++ b/lib/store/user/storage.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:sembast/sembast.dart'; import 'package:syphon/global/print.dart'; +import 'package:syphon/storage/constants.dart'; import 'package:syphon/store/user/model.dart'; Future saveUsers( @@ -9,7 +10,7 @@ Future saveUsers( Database cache, Database storage, }) async { - final store = StoreRef('users'); + final store = StoreRef(StorageKeys.USERS); return await storage.transaction((txn) async { for (User user in users.values) { @@ -33,7 +34,7 @@ Future> loadUsers({ final Map users = {}; try { - final store = StoreRef('users'); + final store = StoreRef(StorageKeys.USERS); final count = await store.count(storage); final finder = Finder( diff --git a/lib/views/home/chat/index.dart b/lib/views/home/chat/index.dart index fa6643149..1403b9470 100644 --- a/lib/views/home/chat/index.dart +++ b/lib/views/home/chat/index.dart @@ -852,7 +852,7 @@ class _Props extends Equatable { }, onLoadFirstBatch: () { final room = selectRoom(id: roomId, state: store.state); - printDebug('[onLoadFirstBatch]'); + printDebug('[onLoadFirstBatch] ${room.id}'); store.dispatch( fetchMessageEvents( room: room, diff --git a/lib/views/widgets/messages/message-typing.dart b/lib/views/widgets/messages/message-typing.dart index 01084efe8..80854d8b7 100644 --- a/lib/views/widgets/messages/message-typing.dart +++ b/lib/views/widgets/messages/message-typing.dart @@ -86,9 +86,8 @@ class MessageTypingState extends State } if (widget.usersTyping.length > 0) { - printDebug('[MessageTypingWidget] ${widget.usersTyping.length}'); final usernamesTyping = widget.usersTyping; - userTyping = widget.roomUsers[usernamesTyping[0]]; + userTyping = widget.roomUsers[usernamesTyping[0]] ?? User(); } return Opacity( diff --git a/pubspec.yaml b/pubspec.yaml index 0991c2eea..66477e922 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,7 +47,7 @@ description: a privacy focused matrix client # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 # followed by an optional build number separated by a +. -version: 0.1.6+160 +version: 0.1.6+161 environment: sdk: ">=2.9.0 <3.0.0" # <- modified to solve build_runner