diff --git a/lib/src/database/database_api.dart b/lib/src/database/database_api.dart index 669b18be..32fc8c01 100644 --- a/lib/src/database/database_api.dart +++ b/lib/src/database/database_api.dart @@ -78,7 +78,12 @@ abstract class DatabaseApi { Future> getUsers(int clientId, Room room); - Future> getEventList(int clientId, Room room); + Future> getEventList( + int clientId, + Room room, { + int start = 0, + int limit, + }); Future getFile(Uri mxcUri); diff --git a/lib/src/database/hive_database.dart b/lib/src/database/hive_database.dart index e94a2026..2a4a6005 100644 --- a/lib/src/database/hive_database.dart +++ b/lib/src/database/hive_database.dart @@ -308,26 +308,48 @@ class FamedlySdkHiveDatabase extends DatabaseApi { return Event.fromJson(convertToJson(raw), room); } - @override - Future> getEventList(int clientId, Room room) async { - final List timelineEventIds = - (await _timelineFragmentsBox.get(MultiKey(room.id, '').toString()) ?? - []); - final List sendingEventIds = (await _timelineFragmentsBox - .get(MultiKey(room.id, 'SENDING').toString()) ?? - []); - final eventIds = sendingEventIds + timelineEventIds; - final events = await Future.wait(eventIds - .map( - (eventId) async => Event.fromJson( - convertToJson( - await _eventsBox.get(MultiKey(room.id, eventId).toString()), + /// Loads a whole list of events at once from the store for a specific room + Future> _getEventsByIds(List eventIds, Room room) => + Future.wait(eventIds + .map( + (eventId) async => Event.fromJson( + convertToJson( + await _eventsBox.get(MultiKey(room.id, eventId).toString()), + ), + room, ), - room, - ), - ) - .toList()); - return events; + ) + .toList()); + + @override + Future> getEventList( + int clientId, + Room room, { + int start = 0, + int? limit, + }) async { + // Get the synced event IDs from the store + final timelineKey = MultiKey(room.id, '').toString(); + final timelineEventIds = + (await _timelineFragmentsBox.get(timelineKey) as List? ?? []); + + // Get the local stored SENDING events from the store + late final List sendingEventIds; + if (start != 0) { + sendingEventIds = []; + } else { + final sendingTimelineKey = MultiKey(room.id, 'SENDING').toString(); + sendingEventIds = + (await _timelineFragmentsBox.get(sendingTimelineKey) as List? ?? []); + } + + // Combine those two lists while respecting the start and limit parameters. + final end = min( + timelineEventIds.length, start + (limit ?? timelineEventIds.length)); + final eventIds = + sendingEventIds + timelineEventIds.getRange(start, end).toList(); + + return await _getEventsByIds(eventIds.cast(), room); } @override diff --git a/lib/src/room.dart b/lib/src/room.dart index 9bdf0b53..3dfcddf5 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -387,7 +387,7 @@ class Room { /// The default count of how much events should be requested when requesting the /// history of this room. - static const int defaultHistoryCount = 100; + static const int defaultHistoryCount = 30; /// Calculates the displayname. First checks if there is a name, then checks for a canonical alias and /// then generates a name from the heroes. @@ -931,7 +931,8 @@ class Room { /// be received maximum. When the request is answered, [onHistoryReceived] will be triggered **before** /// the historical events will be published in the onEvent stream. Future requestHistory( - {int historyCount = defaultHistoryCount, onHistoryReceived}) async { + {int historyCount = defaultHistoryCount, + void Function() onHistoryReceived}) async { if (prev_batch == null) { throw 'Tried to request history without a prev_batch token'; } @@ -1069,7 +1070,11 @@ class Room { await postLoad(); var events; if (client.database != null) { - events = await client.database.getEventList(client.id, this); + events = await client.database.getEventList( + client.id, + this, + limit: defaultHistoryCount, + ); } else { events = []; } diff --git a/lib/src/timeline.dart b/lib/src/timeline.dart index 4635e429..7dde6927 100644 --- a/lib/src/timeline.dart +++ b/lib/src/timeline.dart @@ -64,7 +64,6 @@ class Timeline { // This ensures that the entire history fetching only triggers `onUpdate` only *once*, // even if /sync's complete while history is being proccessed. bool _collectHistoryUpdates = false; - final Set _historyUpdates = {}; bool get canRequestHistory { if (events.isEmpty) return true; @@ -73,32 +72,36 @@ class Timeline { Future requestHistory( {int historyCount = Room.defaultHistoryCount}) async { - if (!isRequestingHistory) { - isRequestingHistory = true; - await room.requestHistory( - historyCount: historyCount, - onHistoryReceived: () { - _collectHistoryUpdates = true; - }, - ); - try { - await Future.delayed(const Duration(seconds: 2)); - _proccessHistoryUpdates(); - } finally { - _collectHistoryUpdates = false; - isRequestingHistory = false; - } + if (isRequestingHistory) { + return; } - } + isRequestingHistory = true; + onUpdate?.call(); - void _proccessHistoryUpdates() async { - _collectHistoryUpdates = false; - for (final update in _historyUpdates) { - _handleEventUpdate(await update.decrypt(room, store: true), - update: false); + try { + // Look up for events in hive first + final eventsFromStore = await room.client.database?.getEventList( + room.client.id, + room, + start: events.length, + limit: Room.defaultHistoryCount, + ); + if (eventsFromStore.isNotEmpty) { + events.addAll(eventsFromStore); + } else { + Logs().v('No more events found in the store. Request from server...'); + await room.requestHistory( + historyCount: historyCount, + onHistoryReceived: () { + _collectHistoryUpdates = true; + }, + ); + } + } finally { + _collectHistoryUpdates = false; + isRequestingHistory = false; + onUpdate?.call(); } - _historyUpdates.clear(); - if (onUpdate != null) onUpdate(); } Timeline({this.room, List events, this.onUpdate, this.onInsert}) @@ -247,12 +250,6 @@ class Timeline { try { if (eventUpdate.roomID != room.id) return; - if (eventUpdate.type == EventUpdateType.history && - _collectHistoryUpdates) { - _historyUpdates.add(eventUpdate); - return; - } - if (eventUpdate.type != EventUpdateType.timeline && eventUpdate.type != EventUpdateType.history) { return; @@ -318,7 +315,7 @@ class Timeline { if (onInsert != null) onInsert(0); } } - if (update) { + if (update && !_collectHistoryUpdates) { if (onUpdate != null) onUpdate(); } } catch (e, s) { diff --git a/test/fake_matrix_api.dart b/test/fake_matrix_api.dart index 6776fc8c..5f0f5b73 100644 --- a/test/fake_matrix_api.dart +++ b/test/fake_matrix_api.dart @@ -1337,7 +1337,7 @@ class FakeMatrixApi extends MockClient { (var req) => messagesResponse, '/client/r0/rooms/!localpart%3Aserver.abc/messages?from=&dir=b&limit=10&filter=%7B%22lazy_load_members%22%3Atrue%7D': (var req) => messagesResponse, - '/client/r0/rooms/!1234%3Aexample.com/messages?from=1234&dir=b&limit=100&filter=%7B%22lazy_load_members%22%3Atrue%7D': + '/client/r0/rooms/!1234%3Aexample.com/messages?from=1234&dir=b&limit=30&filter=%7B%22lazy_load_members%22%3Atrue%7D': (var req) => messagesResponse, '/client/versions': (var req) => { 'versions': [