From c14fe937ec51540b32c368b1c0a8a42a7d9ea4a2 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 19 Jul 2022 13:17:08 +0200 Subject: [PATCH 1/4] fix: Await unawaited stuff in voip code --- lib/src/voip/call.dart | 153 +++++++++++++++++++++-------------------- lib/src/voip/voip.dart | 32 ++++----- 2 files changed, 96 insertions(+), 89 deletions(-) diff --git a/lib/src/voip/call.dart b/lib/src/voip/call.dart index 4ed8765d..a6d20e6b 100644 --- a/lib/src/voip/call.dart +++ b/lib/src/voip/call.dart @@ -84,9 +84,9 @@ class WrappedMediaStream { renderer.srcObject = null; if (isLocal() && !isGroupCall && stream != null) { if (isWeb) { - stream!.getTracks().forEach((element) { - element.stop(); - }); + for (final element in stream!.getTracks()) { + await element.stop(); + } } await stream?.dispose(); stream = null; @@ -395,7 +395,7 @@ class CallSession { setCallState(CallState.kCreateOffer); final stream = await _getUserMedia(type); if (stream != null) { - addLocalStream(stream, SDPStreamMetadataPurpose.Usermedia); + await addLocalStream(stream, SDPStreamMetadataPurpose.Usermedia); } } @@ -407,7 +407,7 @@ class CallSession { if (!isGroupCall) { final stream = await _getUserMedia(type); if (stream != null) { - addLocalStream(stream, SDPStreamMetadataPurpose.Usermedia); + await addLocalStream(stream, SDPStreamMetadataPurpose.Usermedia); } } @@ -473,12 +473,12 @@ class CallSession { waitForLocalAVStream = false; - callFeeds.forEach((element) async { - addLocalStream( + for (final element in callFeeds) { + await addLocalStream( await voip.delegate.cloneStream(element.stream!), element.purpose); - }); + } - answer(); + await answer(); } Future placeCallWithStreams(List callFeeds, @@ -490,27 +490,27 @@ class CallSession { // create the peer connection now so it can be gathering candidates while we get user // media (assuming a candidate pool size is configured) await _preparePeerConnection(); - gotCallFeedsForInvite(callFeeds, requestScreenshareFeed); + await gotCallFeedsForInvite(callFeeds, requestScreenshareFeed); } - void gotCallFeedsForInvite(List callFeeds, - [bool requestScreenshareFeed = false]) { + Future gotCallFeedsForInvite(List callFeeds, + [bool requestScreenshareFeed = false]) async { if (successor != null) { - successor!.gotCallFeedsForAnswer(callFeeds); + await successor!.gotCallFeedsForAnswer(callFeeds); return; } if (state == CallState.kEnded) { - cleanUp(); + await cleanUp(); return; } - callFeeds.forEach((element) async { - addLocalStream( + for (final element in callFeeds) { + await addLocalStream( await voip.delegate.cloneStream(element.stream!), element.purpose); - }); + } if (requestScreenshareFeed) { - pc!.addTransceiver( + await pc!.addTransceiver( kind: RTCRtpMediaType.RTCRtpMediaTypeVideo, init: RTCRtpTransceiverInit(direction: TransceiverDirection.RecvOnly)); @@ -526,7 +526,7 @@ class CallSession { setCallState(CallState.kEnded); } - void onAnswerReceived( + Future onAnswerReceived( RTCSessionDescription answer, SDPStreamMetadata? metadata) async { if (metadata != null) { _updateRemoteSDPStreamMetadata(metadata); @@ -535,7 +535,9 @@ class CallSession { if (direction == CallDirection.kOutgoing) { setCallState(CallState.kConnecting); await pc!.setRemoteDescription(answer); - remoteCandidates.forEach((candidate) => pc!.addCandidate(candidate)); + for (final candidate in remoteCandidates) { + await pc!.addCandidate(candidate); + } } /// Send select_answer event. @@ -543,7 +545,7 @@ class CallSession { opts.room, callId, lifetimeMs, localPartyId, remotePartyId!); } - void onNegotiateReceived( + Future onNegotiateReceived( SDPStreamMetadata? metadata, RTCSessionDescription description) async { final polite = direction == CallDirection.kIncoming; @@ -611,13 +613,13 @@ class CallSession { }); } - void onSDPStreamMetadataReceived(SDPStreamMetadata metadata) async { + Future onSDPStreamMetadataReceived(SDPStreamMetadata metadata) async { _updateRemoteSDPStreamMetadata(metadata); fireCallEvent(CallEvent.kFeedsChanged); } - void onCandidatesReceived(List candidates) { - candidates.forEach((json) async { + Future onCandidatesReceived(List candidates) async { + for (final json in candidates) { final candidate = RTCIceCandidate( json['candidate'], json['sdpMid'] ?? '', @@ -633,16 +635,16 @@ class CallSession { } else { remoteCandidates.add(candidate); } - }); + } if (pc != null && pc!.iceConnectionState == RTCIceConnectionState.RTCIceConnectionStateDisconnected) { - restartIce(); + await restartIce(); } } - void onAssertedIdentityReceived(AssertedIdentity identity) async { + void onAssertedIdentityReceived(AssertedIdentity identity) { remoteAssertedIdentity = identity; fireCallEvent(CallEvent.kAssertedIdentityChanged); } @@ -674,7 +676,7 @@ class CallSession { setScreensharingEnabled(false); }; }); - addLocalStream(stream, SDPStreamMetadataPurpose.Screenshare); + await addLocalStream(stream, SDPStreamMetadataPurpose.Screenshare); return true; } catch (err) { fireCallEvent(CallEvent.kError); @@ -696,7 +698,7 @@ class CallSession { } } - void addLocalStream(MediaStream stream, String purpose, + Future addLocalStream(MediaStream stream, String purpose, {bool addToPeerConnection = true}) async { final existingStream = getLocalStreams.where((element) => element.purpose == purpose); @@ -722,14 +724,14 @@ class CallSession { if (addToPeerConnection) { if (purpose == SDPStreamMetadataPurpose.Screenshare) { screensharingSenders.clear(); - stream.getTracks().forEach((track) async { + for (final track in stream.getTracks()) { screensharingSenders.add(await pc!.addTrack(track, stream)); - }); + } } else if (purpose == SDPStreamMetadataPurpose.Usermedia) { usermediaSenders.clear(); - stream.getTracks().forEach((track) async { + for (final track in stream.getTracks()) { usermediaSenders.add(await pc!.addTrack(track, stream)); - }); + } } } @@ -744,7 +746,7 @@ class CallSession { fireCallEvent(CallEvent.kFeedsChanged); } - void _addRemoteStream(MediaStream stream) async { + Future _addRemoteStream(MediaStream stream) async { //final userId = remoteUser.id; final metadata = remoteSDPStreamMetadata!.sdpStreamMetadatas[stream.id]; if (metadata == null) { @@ -783,17 +785,17 @@ class CallSession { Logs().i('Pushed remote stream (id="${stream.id}", purpose=$purpose)'); } - void deleteAllStreams() { - streams.forEach((stream) async { + Future deleteAllStreams() async { + for (final stream in streams) { if (stream.isLocal() || groupCallId == null) { await stream.dispose(); } - }); + } streams.clear(); fireCallEvent(CallEvent.kFeedsChanged); } - void deleteFeedByStream(MediaStream stream) { + Future deleteFeedByStream(MediaStream stream) async { final index = streams.indexWhere((element) => element.stream!.id == stream.id); if (index == -1) { @@ -802,23 +804,23 @@ class CallSession { } final wstream = streams.elementAt(index); onStreamRemoved.add(wstream); - deleteStream(wstream); + await deleteStream(wstream); } - void deleteStream(WrappedMediaStream stream) { - stream.dispose(); + Future deleteStream(WrappedMediaStream stream) async { + await stream.dispose(); streams.removeAt(streams.indexOf(stream)); fireCallEvent(CallEvent.kFeedsChanged); } - void removeLocalStream(WrappedMediaStream callFeed) { + Future removeLocalStream(WrappedMediaStream callFeed) async { final senderArray = callFeed.purpose == SDPStreamMetadataPurpose.Usermedia ? usermediaSenders : screensharingSenders; - senderArray.forEach((element) async { + for (final element in senderArray) { await pc!.removeTrack(element); - }); + } if (callFeed.purpose == SDPStreamMetadataPurpose.Screenshare) { stopMediaStream(callFeed.stream); @@ -827,7 +829,7 @@ class CallSession { // Empty the array senderArray.removeRange(0, senderArray.length); onStreamRemoved.add(callFeed); - deleteStream(callFeed); + await deleteStream(callFeed); } void setCallState(CallState newState) { @@ -850,7 +852,7 @@ class CallSession { bool get isMicrophoneMuted => localUserMediaStream?.isAudioMuted() ?? false; - void setRemoteOnHold(bool onHold) async { + Future setRemoteOnHold(bool onHold) async { if (isRemoteOnHold == onHold) return; remoteOnHold = onHold; final transceivers = await pc!.getTransceivers(); @@ -859,7 +861,7 @@ class CallSession { ? TransceiverDirection.SendOnly : TransceiverDirection.SendRecv); } - _updateMuteStatus(); + await _updateMuteStatus(); fireCallEvent(CallEvent.kRemoteHoldUnhold); } @@ -884,7 +886,7 @@ class CallSession { return callOnHold; } - void answer() async { + Future answer() async { if (inviteOrAnswerSent) { return; } @@ -895,7 +897,9 @@ class CallSession { setCallState(CallState.kCreateAnswer); final answer = await pc!.createAnswer({}); - remoteCandidates.forEach((candidate) => pc!.addCandidate(candidate)); + for (final candidate in remoteCandidates) { + await pc!.addCandidate(candidate); + } final callCapabilities = CallCapabilities() ..dtmf = false @@ -924,7 +928,7 @@ class CallSession { /// This used to be done by calling hangup, but is a separate method and protocol /// event as of MSC2746. /// - void reject() { + Future reject() async { // stop play ringtone voip.delegate.stopRingtone(); @@ -933,15 +937,15 @@ class CallSession { return; } Logs().d('[VOIP] Rejecting call: $callId'); - terminate(CallParty.kLocal, CallErrorCode.UserHangup, true); - sendCallReject(room, callId, lifetimeMs, localPartyId); + await terminate(CallParty.kLocal, CallErrorCode.UserHangup, true); + await sendCallReject(room, callId, lifetimeMs, localPartyId); } - void hangup([String? reason, bool suppressEvent = true]) async { + Future hangup([String? reason, bool suppressEvent = true]) async { // stop play ringtone voip.delegate.stopRingtone(); - terminate( + await terminate( CallParty.kLocal, reason ?? CallErrorCode.UserHangup, !suppressEvent); try { @@ -953,7 +957,7 @@ class CallSession { } } - void sendDTMF(String tones) async { + Future sendDTMF(String tones) async { final senders = await pc!.getSenders(); for (final sender in senders) { if (sender.track != null && sender.track!.kind == 'audio') { @@ -964,7 +968,8 @@ class CallSession { Logs().e('Unable to find a track to send DTMF on'); } - void terminate(CallParty party, String reason, bool shouldEmit) async { + Future terminate( + CallParty party, String reason, bool shouldEmit) async { if (state == CallState.kEnded) { return; } @@ -981,7 +986,7 @@ class CallSession { setCallState(CallState.kEnded); voip.currentCID = null; voip.calls.remove(callId); - cleanUp(); + await cleanUp(); onCallHangup.add(this); @@ -991,7 +996,7 @@ class CallSession { } } - void onRejectReceived(String? reason) { + Future onRejectReceived(String? reason) async { Logs().v('[VOIP] Reject received for call ID ' + callId); // No need to check party_id for reject because if we'd received either // an answer or reject, we wouldn't be in state InviteSent @@ -1001,7 +1006,8 @@ class CallSession { CallState.kRinging == state; if (shouldTerminate) { - terminate(CallParty.kRemote, reason ?? CallErrorCode.UserHangup, true); + await terminate( + CallParty.kRemote, reason ?? CallErrorCode.UserHangup, true); } else { Logs().e('Call is in state: ${state.toString()}: ignoring reject'); } @@ -1018,7 +1024,8 @@ class CallSession { await pc!.setLocalDescription(offer); } catch (err) { Logs().d('Error setting local description! ${err.toString()}'); - terminate(CallParty.kLocal, CallErrorCode.SetLocalDescription, true); + await terminate( + CallParty.kLocal, CallErrorCode.SetLocalDescription, true); return; } @@ -1051,7 +1058,7 @@ class CallSession { } } - void onNegotiationNeeded() async { + Future onNegotiationNeeded() async { Logs().i('Negotiation is needed!'); makingOffer = true; try { @@ -1112,10 +1119,10 @@ class CallSession { terminate(CallParty.kRemote, CallErrorCode.AnsweredElsewhere, true); } - void cleanUp() async { - streams.forEach((stream) { - stream.dispose(); - }); + Future cleanUp() async { + for (final stream in streams) { + await stream.dispose(); + } streams.clear(); if (pc != null) { await pc!.close(); @@ -1123,7 +1130,7 @@ class CallSession { } } - void _updateMuteStatus() async { + Future _updateMuteStatus() async { final micShouldBeMuted = (localUserMediaStream != null && localUserMediaStream!.isAudioMuted()) || remoteOnHold; @@ -1141,9 +1148,9 @@ class CallSession { } void _setTracksEnabled(List tracks, bool enabled) { - tracks.forEach((track) async { + for (final track in tracks) { track.enabled = enabled; - }); + } } SDPStreamMetadata _getLocalSDPStreamMetadata() { @@ -1159,7 +1166,7 @@ class CallSession { return metadata; } - void restartIce() async { + Future restartIce() async { Logs().v('[VOIP] iceRestart.'); // Needs restart ice on session.pc and renegotiation. iceGatheringFinished = false; @@ -1229,7 +1236,7 @@ class CallSession { pc?.createDataChannel(label, dataChannelDict); } - void tryRemoveStopedStreams() { + Future tryRemoveStopedStreams() async { final removedStreams = {}; streams.forEach((stream) { if (stream.stopped) { @@ -1238,9 +1245,9 @@ class CallSession { }); streams .removeWhere((stream) => removedStreams.containsKey(stream.stream!.id)); - removedStreams.forEach((id, element) { - _removeStream(element.stream!); - }); + for (final element in removedStreams.entries) { + await _removeStream(element.value.stream!); + } } Future _removeStream(MediaStream stream) async { diff --git a/lib/src/voip/voip.dart b/lib/src/voip/voip.dart index fc836e23..134da839 100644 --- a/lib/src/voip/voip.dart +++ b/lib/src/voip/voip.dart @@ -219,7 +219,7 @@ class VoIP { delegate.playRingtone(); } - void onCallAnswer( + Future onCallAnswer( String roomId, String senderId, Map content) async { Logs().v('[VOIP] onCallAnswer => ${content.toString()}'); final String callId = content['call_id']; @@ -252,13 +252,13 @@ class VoIP { if (content[sdpStreamMetadataKey] != null) { metadata = SDPStreamMetadata.fromJson(content[sdpStreamMetadataKey]); } - call.onAnswerReceived(answer, metadata); + await call.onAnswerReceived(answer, metadata); } else { Logs().v('[VOIP] onCallAnswer: Session [$callId] not found!'); } } - void onCallCandidates( + Future onCallCandidates( String roomId, String senderId, Map content) async { if (senderId == client.userID) { // Ignore messages to yourself. @@ -273,13 +273,13 @@ class VoIP { 'Ignoring call candidates for room $roomId claiming to be for call in room ${call.room.id}'); return; } - call.onCandidatesReceived(content['candidates']); + await call.onCandidatesReceived(content['candidates']); } else { Logs().v('[VOIP] onCallCandidates: Session [$callId] not found!'); } } - void onCallHangup(String roomId, String _ /*senderId unused*/, + Future onCallHangup(String roomId, String _ /*senderId unused*/, Map content) async { // stop play ringtone, if this is an incoming call if (!delegate.isBackgroud) { @@ -295,7 +295,7 @@ class VoIP { return; } // hangup in any case, either if the other party hung up or we did on another device - call.terminate(CallParty.kRemote, + await call.terminate(CallParty.kRemote, content['reason'] ?? CallErrorCode.UserHangup, true); } else { Logs().v('[VOIP] onCallHangup: Session [$callId] not found!'); @@ -303,7 +303,7 @@ class VoIP { currentCID = null; } - void onCallReject( + Future onCallReject( String roomId, String senderId, Map content) async { if (senderId == client.userID) { // Ignore messages to yourself. @@ -319,13 +319,13 @@ class VoIP { 'Ignoring call reject for room $roomId claiming to be for call in room ${call.room.id}'); return; } - call.onRejectReceived(content['reason']); + await call.onRejectReceived(content['reason']); } else { Logs().v('[VOIP] onCallHangup: Session [$callId] not found!'); } } - void onCallReplaces( + Future onCallReplaces( String roomId, String senderId, Map content) async { if (senderId == client.userID) { // Ignore messages to yourself. @@ -344,7 +344,7 @@ class VoIP { } } - void onCallSelectAnswer( + Future onCallSelectAnswer( String roomId, String senderId, Map content) async { if (senderId == client.userID) { // Ignore messages to yourself. @@ -365,7 +365,7 @@ class VoIP { } } - void onSDPStreamMetadataChangedReceived( + Future onSDPStreamMetadataChangedReceived( String roomId, String senderId, Map content) async { if (senderId == client.userID) { // Ignore messages to yourself. @@ -385,12 +385,12 @@ class VoIP { Logs().d('SDP Stream metadata is null'); return; } - call.onSDPStreamMetadataReceived( + await call.onSDPStreamMetadataReceived( SDPStreamMetadata.fromJson(content[sdpStreamMetadataKey])); } } - void onAssertedIdentityReceived( + Future onAssertedIdentityReceived( String roomId, String senderId, Map content) async { if (senderId == client.userID) { // Ignore messages to yourself. @@ -415,7 +415,7 @@ class VoIP { } } - void onCallNegotiate( + Future onCallNegotiate( String roomId, String senderId, Map content) async { if (senderId == client.userID) { // Ignore messages to yourself. @@ -437,7 +437,7 @@ class VoIP { if (content[sdpStreamMetadataKey] != null) { metadata = SDPStreamMetadata.fromJson(content[sdpStreamMetadataKey]); } - call.onNegotiateReceived(metadata, + await call.onNegotiateReceived(metadata, RTCSessionDescription(description['sdp'], description['type'])); } catch (err) { Logs().e('Failed to complete negotiation ${err.toString()}'); @@ -564,7 +564,7 @@ class VoIP { return groupCalls[groupCallId]; } - void startGroupCalls() async { + Future startGroupCalls() async { final rooms = client.rooms; rooms.forEach((element) { createGroupCallForRoom(element); From d5fd03d52def095c08dd1e0f814daee5bcf22a8f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 19 Jul 2022 14:13:35 +0200 Subject: [PATCH 2/4] fix: (potentially) a race in the archive test --- test/client_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/client_test.dart b/test/client_test.dart index 045b15d0..aa4e7ddf 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -340,9 +340,9 @@ void main() { }); test('get archive', () async { + await matrix.abortSync(); final archive = await matrix.loadArchive(); - await Future.delayed(Duration(milliseconds: 50)); expect(archive.length, 2); expect(archive[0].id, '!5345234234:example.com'); expect(archive[0].membership, Membership.leave); From 7885281c729448d3746e108e0c1cabd51e24d7f4 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 13 Jul 2022 09:54:04 +0200 Subject: [PATCH 3/4] refactor: Use import sorter and ci templates --- .gitlab-ci.yml | 46 ++++--------------- lib/encryption/cross_signing.dart | 2 +- lib/encryption/encryption.dart | 2 +- lib/encryption/key_manager.dart | 10 ++-- lib/encryption/olm_manager.dart | 2 +- lib/encryption/ssss.dart | 4 +- lib/encryption/utils/bootstrap.dart | 6 +-- lib/encryption/utils/session_key.dart | 2 +- .../msc_2835_uia_login.dart | 1 + lib/src/client.dart | 12 ++--- lib/src/database/database_api.dart | 1 - lib/src/database/fluffybox_database.dart | 1 + .../database/hive_collections_database.dart | 1 + lib/src/database/hive_database.dart | 1 + lib/src/event.dart | 2 +- lib/src/room.dart | 4 +- lib/src/utils/crypto/crypto.dart | 2 +- lib/src/utils/crypto/encrypted_file.dart | 4 +- lib/src/utils/crypto/ffi.dart | 1 + lib/src/utils/crypto/js.dart | 2 +- lib/src/utils/crypto/native.dart | 3 +- lib/src/utils/crypto/subtle.dart | 3 +- lib/src/utils/device_keys_list.dart | 2 +- lib/src/utils/html_to_text.dart | 3 +- lib/src/utils/image_pack_extension.dart | 2 +- lib/src/utils/markdown.dart | 3 +- .../utils/matrix_default_localizations.dart | 1 + lib/src/utils/multilock.dart | 1 + lib/src/utils/queued_to_device_event.dart | 1 + lib/src/voip/call.dart | 2 +- lib/src/voip/group_call.dart | 3 +- lib/src/voip/voip.dart | 4 +- pubspec.yaml | 1 + test/canonical_json_test.dart | 3 +- test/client_test.dart | 5 +- test/commands_test.dart | 3 +- test/database_api_test.dart | 9 ++-- test/device_keys_list_test.dart | 5 +- test/encryption/bootstrap_test.dart | 7 ++- test/encryption/cross_signing_test.dart | 5 +- .../encrypt_decrypt_room_message_test.dart | 5 +- .../encrypt_decrypt_to_device_test.dart | 5 +- test/encryption/key_manager_test.dart | 5 +- test/encryption/key_request_test.dart | 4 +- test/encryption/key_verification_test.dart | 7 ++- test/encryption/olm_manager_test.dart | 15 +++--- test/encryption/online_key_backup_test.dart | 5 +- test/encryption/ssss_test.dart | 9 ++-- test/encryption/utils_test.dart | 3 +- test/event_test.dart | 6 +-- test/fake_client.dart | 3 +- test/fake_database.dart | 3 +- test/fake_matrix_api.dart | 1 + test/html_to_text_test.dart | 3 +- test/image_pack_test.dart | 1 + test/markdown_test.dart | 3 +- test/matrix_api/map_copy_extension_test.dart | 3 +- test/matrix_database_test.dart | 4 +- test/matrix_exception_test.dart | 5 +- test/matrix_file_test.dart | 8 ++-- test/matrix_id_string_extension_test.dart | 1 + test/matrix_localizations_test.dart | 4 +- test/multilock_test.dart | 3 +- test/mxc_uri_extension_test.dart | 3 +- test/push_notification.dart | 3 +- test/room_test.dart | 3 +- test/sync_filter_test.dart | 4 +- test/timeline_context_test.dart | 6 +-- test/timeline_test.dart | 7 ++- test/uia_test.dart | 4 +- test/user_test.dart | 3 +- test_driver/matrixsdk_test.dart | 4 +- 72 files changed, 151 insertions(+), 164 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b7aeaa85..d6e742c4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,7 @@ -stages: - - coverage - - builddocs - - deploy - - publish +include: + - project: "famedly/company/frontend/ci-templates" + ref: main + file: "/all.yml" workflow: rules: @@ -16,7 +15,7 @@ variables: coverage: tags: - linux - stage: coverage + stage: test image: registry.gitlab.com/famedly/company/frontend/flutter-dockerimages/flutter-linux/stable:${FLUTTER_IMAGE_TAG} dependencies: [] script: @@ -31,7 +30,7 @@ coverage: coverage_without_olm: tags: - linux - stage: coverage + stage: test image: dart dependencies: [] script: @@ -41,7 +40,7 @@ coverage_without_olm: e2ee_test: tags: - linux - stage: coverage + stage: test image: registry.gitlab.com/famedly/company/frontend/flutter-dockerimages/flutter-linux/stable:${FLUTTER_IMAGE_TAG} dependencies: [] script: @@ -51,35 +50,8 @@ e2ee_test: timeout: 16m resource_group: e2ee_test -code_analyze: - tags: - - docker - stage: coverage - image: dart - dependencies: [] - script: - - dart pub get - - dart format lib/ test/ test_driver/ --set-exit-if-changed - - dart analyze - -code_quality: - tags: - - docker - stage: coverage - image: dart - before_script: - - dart pub global activate dart_code_metrics - script: - - dart pub global run dart_code_metrics:metrics analyze lib -r gitlab > code-quality-report.json - artifacts: - reports: - codequality: code-quality-report.json - # also create an actual artifact for inspection purposes - paths: - - code-quality-report.json - dry-run: - stage: publish + stage: deploy image: dart script: - rm -rf ./docs @@ -88,7 +60,7 @@ dry-run: pub-dev: - stage: publish + stage: deploy image: dart dependencies: [ dry-run diff --git a/lib/encryption/cross_signing.dart b/lib/encryption/cross_signing.dart index 60d9a64c..ac4afed7 100644 --- a/lib/encryption/cross_signing.dart +++ b/lib/encryption/cross_signing.dart @@ -18,9 +18,9 @@ import 'dart:typed_data'; -import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:olm/olm.dart' as olm; +import 'package:matrix/encryption/utils/base64_unpadded.dart'; import '../matrix.dart'; import 'encryption.dart'; import 'ssss.dart'; diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index 73e30406..19c8f508 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -16,8 +16,8 @@ * along with this program. If not, see . */ -import 'dart:convert'; import 'dart:async'; +import 'dart:convert'; import 'package:olm/olm.dart' as olm; diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index 2182ae6f..5fc4c3fb 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -19,16 +19,16 @@ import 'dart:async'; import 'dart:convert'; +import 'package:collection/collection.dart'; +import 'package:olm/olm.dart' as olm; + import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; -import 'package:olm/olm.dart' as olm; -import 'package:collection/collection.dart'; - +import '../matrix.dart'; +import '../src/utils/run_in_root.dart'; import './encryption.dart'; import './utils/outbound_group_session.dart'; import './utils/session_key.dart'; -import '../matrix.dart'; -import '../src/utils/run_in_root.dart'; const megolmKey = EventTypes.MegolmBackup; diff --git a/lib/encryption/olm_manager.dart b/lib/encryption/olm_manager.dart index 24529a4e..0a040733 100644 --- a/lib/encryption/olm_manager.dart +++ b/lib/encryption/olm_manager.dart @@ -21,9 +21,9 @@ import 'dart:convert'; import 'package:async/async.dart'; import 'package:canonical_json/canonical_json.dart'; import 'package:collection/collection.dart'; -import 'package:matrix/matrix.dart'; import 'package:olm/olm.dart' as olm; +import 'package:matrix/matrix.dart'; import '../encryption/utils/json_signature_check_extension.dart'; import '../src/utils/run_in_root.dart'; import 'encryption.dart'; diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index 60e4dabf..9941b873 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -22,10 +22,10 @@ import 'dart:core'; import 'dart:typed_data'; import 'package:base58check/base58.dart'; -import 'package:crypto/crypto.dart'; import 'package:collection/collection.dart'; -import 'package:matrix/encryption/utils/base64_unpadded.dart'; +import 'package:crypto/crypto.dart'; +import 'package:matrix/encryption/utils/base64_unpadded.dart'; import '../matrix.dart'; import '../src/utils/crypto/crypto.dart' as uc; import '../src/utils/run_in_root.dart'; diff --git a/lib/encryption/utils/bootstrap.dart b/lib/encryption/utils/bootstrap.dart index 515d0744..62482dcc 100644 --- a/lib/encryption/utils/bootstrap.dart +++ b/lib/encryption/utils/bootstrap.dart @@ -22,10 +22,10 @@ import 'dart:typed_data'; import 'package:canonical_json/canonical_json.dart'; import 'package:olm/olm.dart' as olm; -import '../encryption.dart'; -import '../ssss.dart'; -import '../key_manager.dart'; import '../../matrix.dart'; +import '../encryption.dart'; +import '../key_manager.dart'; +import '../ssss.dart'; import 'base64_unpadded.dart'; enum BootstrapState { diff --git a/lib/encryption/utils/session_key.dart b/lib/encryption/utils/session_key.dart index 310b1397..e219f658 100644 --- a/lib/encryption/utils/session_key.dart +++ b/lib/encryption/utils/session_key.dart @@ -16,10 +16,10 @@ * along with this program. If not, see . */ -import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; import 'package:matrix_api_lite/src/utils/filter_map_extension.dart'; import 'package:olm/olm.dart' as olm; +import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; import '../../matrix.dart'; class SessionKey { diff --git a/lib/msc_extensions/msc_2835_uia_login/msc_2835_uia_login.dart b/lib/msc_extensions/msc_2835_uia_login/msc_2835_uia_login.dart index e90a858a..d5b435de 100644 --- a/lib/msc_extensions/msc_2835_uia_login/msc_2835_uia_login.dart +++ b/lib/msc_extensions/msc_2835_uia_login/msc_2835_uia_login.dart @@ -3,6 +3,7 @@ library msc_2835_uia_login; import 'dart:convert'; import 'package:http/http.dart' hide Client; + import 'package:matrix/matrix.dart'; extension UiaLogin on Client { diff --git a/lib/src/client.dart b/lib/src/client.dart index de045ba0..4715bc2b 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -21,19 +21,19 @@ import 'dart:convert'; import 'dart:core'; import 'dart:typed_data'; +import 'package:collection/collection.dart' show IterableExtension; import 'package:http/http.dart' as http; +import 'package:mime/mime.dart'; +import 'package:olm/olm.dart' as olm; +import 'package:random_string/random_string.dart'; + import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/run_in_root.dart'; import 'package:matrix/src/utils/sync_update_item_count.dart'; -import 'package:mime/mime.dart'; -import 'package:olm/olm.dart' as olm; -import 'package:collection/collection.dart' show IterableExtension; -import 'package:random_string/random_string.dart'; - import '../encryption.dart'; import '../matrix.dart'; -import 'utils/run_benchmarked.dart'; import 'utils/multilock.dart'; +import 'utils/run_benchmarked.dart'; typedef RoomSorter = int Function(Room a, Room b); diff --git a/lib/src/database/database_api.dart b/lib/src/database/database_api.dart index c8a65477..1d32f777 100644 --- a/lib/src/database/database_api.dart +++ b/lib/src/database/database_api.dart @@ -23,7 +23,6 @@ import 'package:matrix/encryption/utils/outbound_group_session.dart'; import 'package:matrix/encryption/utils/ssss_cache.dart'; import 'package:matrix/encryption/utils/stored_inbound_group_session.dart'; import 'package:matrix/src/utils/queued_to_device_event.dart'; - import '../../matrix.dart'; abstract class DatabaseApi { diff --git a/lib/src/database/fluffybox_database.dart b/lib/src/database/fluffybox_database.dart index 804dcdda..c94c3069 100644 --- a/lib/src/database/fluffybox_database.dart +++ b/lib/src/database/fluffybox_database.dart @@ -23,6 +23,7 @@ import 'dart:typed_data'; import 'package:fluffybox/fluffybox.dart'; import 'package:fluffybox/hive.dart' show HiveCipher; + import 'package:matrix/encryption/utils/olm_session.dart'; import 'package:matrix/encryption/utils/outbound_group_session.dart'; import 'package:matrix/encryption/utils/ssss_cache.dart'; diff --git a/lib/src/database/hive_collections_database.dart b/lib/src/database/hive_collections_database.dart index b9d10d26..2a5663b3 100644 --- a/lib/src/database/hive_collections_database.dart +++ b/lib/src/database/hive_collections_database.dart @@ -22,6 +22,7 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:hive/hive.dart'; + import 'package:matrix/encryption/utils/olm_session.dart'; import 'package:matrix/encryption/utils/outbound_group_session.dart'; import 'package:matrix/encryption/utils/ssss_cache.dart'; diff --git a/lib/src/database/hive_database.dart b/lib/src/database/hive_database.dart index d8b285c0..cb5f6386 100644 --- a/lib/src/database/hive_database.dart +++ b/lib/src/database/hive_database.dart @@ -22,6 +22,7 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:hive/hive.dart'; + import 'package:matrix/encryption/utils/olm_session.dart'; import 'package:matrix/encryption/utils/outbound_group_session.dart'; import 'package:matrix/encryption/utils/ssss_cache.dart'; diff --git a/lib/src/event.dart b/lib/src/event.dart index 6218cd97..385564b9 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -22,8 +22,8 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:html/parser.dart'; import 'package:http/http.dart' as http; -import 'package:matrix/src/utils/file_send_request_credentials.dart'; +import 'package:matrix/src/utils/file_send_request_credentials.dart'; import '../matrix.dart'; import 'utils/event_localizations.dart'; import 'utils/html_to_text.dart'; diff --git a/lib/src/room.dart b/lib/src/room.dart index 6a6bfac0..ad17445d 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -22,12 +22,12 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:html_unescape/html_unescape.dart'; + import 'package:matrix/src/models/timeline_chunk.dart'; +import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/crypto/crypto.dart'; import 'package:matrix/src/utils/file_send_request_credentials.dart'; -import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/space_child.dart'; - import '../matrix.dart'; import 'utils/markdown.dart'; import 'utils/marked_unread.dart'; diff --git a/lib/src/utils/crypto/crypto.dart b/lib/src/utils/crypto/crypto.dart index 7bbddf80..a97417ee 100644 --- a/lib/src/utils/crypto/crypto.dart +++ b/lib/src/utils/crypto/crypto.dart @@ -18,8 +18,8 @@ export 'native.dart' if (dart.library.js) 'js.dart'; -import 'dart:typed_data'; import 'dart:math'; +import 'dart:typed_data'; Uint8List secureRandomBytes(int len) { final rng = Random.secure(); diff --git a/lib/src/utils/crypto/encrypted_file.dart b/lib/src/utils/crypto/encrypted_file.dart index d3dd874b..5d5dcbc1 100644 --- a/lib/src/utils/crypto/encrypted_file.dart +++ b/lib/src/utils/crypto/encrypted_file.dart @@ -15,11 +15,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import 'dart:typed_data'; + import 'dart:convert'; +import 'dart:typed_data'; import 'package:matrix/encryption/utils/base64_unpadded.dart'; - import 'crypto.dart'; class EncryptedFile { diff --git a/lib/src/utils/crypto/ffi.dart b/lib/src/utils/crypto/ffi.dart index f4415c14..ed32e2c9 100644 --- a/lib/src/utils/crypto/ffi.dart +++ b/lib/src/utils/crypto/ffi.dart @@ -15,6 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + import 'dart:ffi'; import 'dart:io'; diff --git a/lib/src/utils/crypto/js.dart b/lib/src/utils/crypto/js.dart index b889720a..7b6ad5f2 100644 --- a/lib/src/utils/crypto/js.dart +++ b/lib/src/utils/crypto/js.dart @@ -3,8 +3,8 @@ import 'dart:typed_data'; -import 'subtle.dart'; import 'subtle.dart' as subtle; +import 'subtle.dart'; abstract class Hash { Hash._(this.name); diff --git a/lib/src/utils/crypto/native.dart b/lib/src/utils/crypto/native.dart index ce317859..e66c0b7a 100644 --- a/lib/src/utils/crypto/native.dart +++ b/lib/src/utils/crypto/native.dart @@ -1,6 +1,7 @@ import 'dart:async'; -import 'dart:typed_data'; import 'dart:ffi'; +import 'dart:typed_data'; + import 'package:ffi/ffi.dart'; import 'ffi.dart'; diff --git a/lib/src/utils/crypto/subtle.dart b/lib/src/utils/crypto/subtle.dart index a96ea31c..34997373 100644 --- a/lib/src/utils/crypto/subtle.dart +++ b/lib/src/utils/crypto/subtle.dart @@ -4,11 +4,12 @@ @JS() library subtle; -import 'package:js/js.dart'; import 'dart:async'; import 'dart:js_util'; import 'dart:typed_data'; +import 'package:js/js.dart'; + @JS() @anonymous class Pbkdf2Params { diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index baaa298e..7265dec4 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -20,9 +20,9 @@ import 'dart:convert'; import 'package:canonical_json/canonical_json.dart'; import 'package:collection/collection.dart' show IterableExtension; -import 'package:matrix/matrix.dart'; import 'package:olm/olm.dart' as olm; +import 'package:matrix/matrix.dart'; import '../../encryption.dart'; enum UserVerifiedStatus { verified, unknown, unknownDevice } diff --git a/lib/src/utils/html_to_text.dart b/lib/src/utils/html_to_text.dart index aa4d0bf2..ba216ccc 100644 --- a/lib/src/utils/html_to_text.dart +++ b/lib/src/utils/html_to_text.dart @@ -17,9 +17,8 @@ */ import 'package:collection/collection.dart'; - -import 'package:html/parser.dart'; import 'package:html/dom.dart'; +import 'package:html/parser.dart'; import 'package:html_unescape/html_unescape.dart'; class HtmlToText { diff --git a/lib/src/utils/image_pack_extension.dart b/lib/src/utils/image_pack_extension.dart index 92b3b924..d5b9e3ff 100644 --- a/lib/src/utils/image_pack_extension.dart +++ b/lib/src/utils/image_pack_extension.dart @@ -16,8 +16,8 @@ * along with this program. If not, see . */ -import 'package:slugify/slugify.dart'; import 'package:matrix_api_lite/matrix_api_lite.dart'; +import 'package:slugify/slugify.dart'; import '../room.dart'; diff --git a/lib/src/utils/markdown.dart b/lib/src/utils/markdown.dart index 0924e9dc..56507908 100644 --- a/lib/src/utils/markdown.dart +++ b/lib/src/utils/markdown.dart @@ -16,9 +16,10 @@ * along with this program. If not, see . */ -import 'package:markdown/markdown.dart'; import 'dart:convert'; +import 'package:markdown/markdown.dart'; + const htmlAttrEscape = HtmlEscape(HtmlEscapeMode.attribute); class LinebreakSyntax extends InlineSyntax { diff --git a/lib/src/utils/matrix_default_localizations.dart b/lib/src/utils/matrix_default_localizations.dart index 4ad60ab2..28736a41 100644 --- a/lib/src/utils/matrix_default_localizations.dart +++ b/lib/src/utils/matrix_default_localizations.dart @@ -15,6 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + import 'package:matrix/matrix.dart'; class MatrixDefaultLocalizations extends MatrixLocalizations { diff --git a/lib/src/utils/multilock.dart b/lib/src/utils/multilock.dart index 035232ef..852527e2 100644 --- a/lib/src/utils/multilock.dart +++ b/lib/src/utils/multilock.dart @@ -15,6 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + import 'dart:async'; /// Lock management class. It allows to lock and unlock multiple keys at once. The keys have diff --git a/lib/src/utils/queued_to_device_event.dart b/lib/src/utils/queued_to_device_event.dart index 3296510f..71657aec 100644 --- a/lib/src/utils/queued_to_device_event.dart +++ b/lib/src/utils/queued_to_device_event.dart @@ -15,6 +15,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + import 'dart:convert'; class QueuedToDeviceEvent { diff --git a/lib/src/voip/call.dart b/lib/src/voip/call.dart index 4ed8765d..0bd32497 100644 --- a/lib/src/voip/call.dart +++ b/lib/src/voip/call.dart @@ -19,9 +19,9 @@ import 'dart:async'; import 'dart:core'; -import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:webrtc_interface/webrtc_interface.dart'; +import 'package:matrix/src/utils/cached_stream_controller.dart'; import '../../matrix.dart'; /// https://github.com/matrix-org/matrix-doc/pull/2746 diff --git a/lib/src/voip/group_call.dart b/lib/src/voip/group_call.dart index 3646509c..eb60c7e7 100644 --- a/lib/src/voip/group_call.dart +++ b/lib/src/voip/group_call.dart @@ -19,9 +19,10 @@ import 'dart:async'; import 'dart:core'; +import 'package:webrtc_interface/webrtc_interface.dart'; + import 'package:matrix/matrix.dart'; import 'package:matrix/src/utils/cached_stream_controller.dart'; -import 'package:webrtc_interface/webrtc_interface.dart'; /// TODO(@duan): Need to add voice activity detection mechanism /// const int SPEAKING_THRESHOLD = -60; // dB diff --git a/lib/src/voip/voip.dart b/lib/src/voip/voip.dart index fc836e23..fa0b2549 100644 --- a/lib/src/voip/voip.dart +++ b/lib/src/voip/voip.dart @@ -1,9 +1,9 @@ import 'dart:core'; -import 'package:matrix/src/utils/cached_stream_controller.dart'; -import 'package:webrtc_interface/webrtc_interface.dart'; import 'package:sdp_transform/sdp_transform.dart' as sdp_transform; +import 'package:webrtc_interface/webrtc_interface.dart'; +import 'package:matrix/src/utils/cached_stream_controller.dart'; import '../../matrix.dart'; /// Delegate WebRTC basic functionality. diff --git a/pubspec.yaml b/pubspec.yaml index 3ae5821d..21dea1ae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,6 +32,7 @@ dependencies: fluffybox: ^0.4.3 dev_dependencies: + import_sorter: ^4.6.0 dart_code_metrics: ^4.10.1 pedantic: ^1.11.0 test: ^1.15.7 diff --git a/test/canonical_json_test.dart b/test/canonical_json_test.dart index 8787dce0..9cdddd36 100644 --- a/test/canonical_json_test.dart +++ b/test/canonical_json_test.dart @@ -17,9 +17,10 @@ */ import 'package:canonical_json/canonical_json.dart'; -import 'package:matrix/matrix.dart'; import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; + void main() { /// All Tests related to the ChatTime group('Canonical Json', () { diff --git a/test/client_test.dart b/test/client_test.dart index 045b15d0..9a732438 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -20,12 +20,11 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; -import 'package:matrix/matrix.dart'; - +import 'package:canonical_json/canonical_json.dart'; import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; -import 'package:canonical_json/canonical_json.dart'; +import 'package:matrix/matrix.dart'; import 'fake_client.dart'; import 'fake_database.dart'; import 'fake_matrix_api.dart'; diff --git a/test/commands_test.dart b/test/commands_test.dart index 10d7f103..053afc5d 100644 --- a/test/commands_test.dart +++ b/test/commands_test.dart @@ -18,8 +18,9 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; + import 'package:matrix/matrix.dart'; import 'fake_client.dart'; import 'fake_matrix_api.dart'; diff --git a/test/database_api_test.dart b/test/database_api_test.dart index 6a7fa540..43efdd84 100644 --- a/test/database_api_test.dart +++ b/test/database_api_test.dart @@ -15,14 +15,15 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + +import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; -import 'dart:async'; + +import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; import 'package:matrix/matrix.dart'; -import 'package:test/test.dart'; -import 'package:olm/olm.dart' as olm; - import 'fake_database.dart'; void main() { diff --git a/test/device_keys_list_test.dart b/test/device_keys_list_test.dart index a1bdc991..36b2ad8e 100644 --- a/test/device_keys_list_test.dart +++ b/test/device_keys_list_test.dart @@ -18,11 +18,10 @@ import 'dart:convert'; -import 'package:matrix/matrix.dart'; - -import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; import './fake_client.dart'; import './fake_matrix_api.dart'; diff --git a/test/encryption/bootstrap_test.dart b/test/encryption/bootstrap_test.dart index 69a643fe..428c795d 100644 --- a/test/encryption/bootstrap_test.dart +++ b/test/encryption/bootstrap_test.dart @@ -19,12 +19,11 @@ import 'dart:async'; import 'dart:convert'; -import 'package:matrix/matrix.dart'; -import 'package:matrix/encryption.dart'; - -import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; +import 'package:matrix/encryption.dart'; +import 'package:matrix/matrix.dart'; import '../fake_client.dart'; void main() { diff --git a/test/encryption/cross_signing_test.dart b/test/encryption/cross_signing_test.dart index 0ceb1e73..15bb22ca 100644 --- a/test/encryption/cross_signing_test.dart +++ b/test/encryption/cross_signing_test.dart @@ -18,11 +18,10 @@ import 'dart:convert'; -import 'package:matrix/matrix.dart'; - -import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; import '../fake_client.dart'; import '../fake_matrix_api.dart'; diff --git a/test/encryption/encrypt_decrypt_room_message_test.dart b/test/encryption/encrypt_decrypt_room_message_test.dart index bc2e007e..d150e3fc 100644 --- a/test/encryption/encrypt_decrypt_room_message_test.dart +++ b/test/encryption/encrypt_decrypt_room_message_test.dart @@ -16,11 +16,10 @@ * along with this program. If not, see . */ -import 'package:matrix/matrix.dart'; - -import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; import '../fake_client.dart'; void main() { diff --git a/test/encryption/encrypt_decrypt_to_device_test.dart b/test/encryption/encrypt_decrypt_to_device_test.dart index 42fb4b2e..10904eae 100644 --- a/test/encryption/encrypt_decrypt_to_device_test.dart +++ b/test/encryption/encrypt_decrypt_to_device_test.dart @@ -16,11 +16,10 @@ * along with this program. If not, see . */ -import 'package:matrix/matrix.dart'; - -import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; import '../fake_client.dart'; import '../fake_database.dart'; import '../fake_matrix_api.dart'; diff --git a/test/encryption/key_manager_test.dart b/test/encryption/key_manager_test.dart index f22cf5de..d9f0e77f 100644 --- a/test/encryption/key_manager_test.dart +++ b/test/encryption/key_manager_test.dart @@ -18,11 +18,10 @@ import 'dart:convert'; -import 'package:matrix/matrix.dart'; - -import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; import '../fake_client.dart'; import '../fake_matrix_api.dart'; diff --git a/test/encryption/key_request_test.dart b/test/encryption/key_request_test.dart index 5bb53769..4470e1b0 100644 --- a/test/encryption/key_request_test.dart +++ b/test/encryption/key_request_test.dart @@ -17,11 +17,11 @@ */ import 'dart:convert'; -import 'package:matrix/matrix.dart'; -import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; import '../fake_client.dart'; import '../fake_matrix_api.dart'; diff --git a/test/encryption/key_verification_test.dart b/test/encryption/key_verification_test.dart index 82eaf0bc..d6dc9aa0 100644 --- a/test/encryption/key_verification_test.dart +++ b/test/encryption/key_verification_test.dart @@ -19,12 +19,11 @@ import 'dart:async'; import 'dart:convert'; -import 'package:matrix/matrix.dart'; -import 'package:matrix/encryption.dart'; - -import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; +import 'package:matrix/encryption.dart'; +import 'package:matrix/matrix.dart'; import '../fake_client.dart'; import '../fake_database.dart'; import '../fake_matrix_api.dart'; diff --git a/test/encryption/olm_manager_test.dart b/test/encryption/olm_manager_test.dart index 28714270..5792b761 100644 --- a/test/encryption/olm_manager_test.dart +++ b/test/encryption/olm_manager_test.dart @@ -17,12 +17,12 @@ */ import 'dart:convert'; -import 'package:matrix/matrix.dart'; -import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; -import 'package:matrix/encryption/utils/json_signature_check_extension.dart'; +import 'package:test/test.dart'; +import 'package:matrix/encryption/utils/json_signature_check_extension.dart'; +import 'package:matrix/matrix.dart'; import '../fake_client.dart'; import '../fake_matrix_api.dart'; @@ -92,7 +92,7 @@ void main() { if (!olmEnabled) return; FakeMatrixApi.calledEndpoints.clear(); - client.encryption!.olmManager + await client.encryption!.olmManager .handleDeviceOneTimeKeysCount({'signed_curve25519': 20}, null); await FakeMatrixApi.firstWhereValue('/client/v3/keys/upload'); expect( @@ -100,7 +100,7 @@ void main() { true); FakeMatrixApi.calledEndpoints.clear(); - client.encryption!.olmManager + await client.encryption!.olmManager .handleDeviceOneTimeKeysCount({'signed_curve25519': 70}, null); await FakeMatrixApi.firstWhereValue('/client/v3/keys/upload') .timeout(Duration(milliseconds: 50), onTimeout: () => ''); @@ -109,7 +109,8 @@ void main() { false); FakeMatrixApi.calledEndpoints.clear(); - client.encryption!.olmManager.handleDeviceOneTimeKeysCount(null, []); + await client.encryption!.olmManager + .handleDeviceOneTimeKeysCount(null, []); await FakeMatrixApi.firstWhereValue('/client/v3/keys/upload'); expect( FakeMatrixApi.calledEndpoints.containsKey('/client/v3/keys/upload'), @@ -117,7 +118,7 @@ void main() { // this will upload keys because we assume the key count is 0, if the server doesn't send one FakeMatrixApi.calledEndpoints.clear(); - client.encryption!.olmManager + await client.encryption!.olmManager .handleDeviceOneTimeKeysCount(null, ['signed_curve25519']); await FakeMatrixApi.firstWhereValue('/client/v3/keys/upload'); expect( diff --git a/test/encryption/online_key_backup_test.dart b/test/encryption/online_key_backup_test.dart index a966545e..aa46ecfd 100644 --- a/test/encryption/online_key_backup_test.dart +++ b/test/encryption/online_key_backup_test.dart @@ -18,11 +18,10 @@ import 'dart:convert'; -import 'package:matrix/matrix.dart'; - -import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; import '../fake_client.dart'; import '../fake_matrix_api.dart'; diff --git a/test/encryption/ssss_test.dart b/test/encryption/ssss_test.dart index 301ddf0b..d7a9e7f4 100644 --- a/test/encryption/ssss_test.dart +++ b/test/encryption/ssss_test.dart @@ -16,16 +16,15 @@ * along with this program. If not, see . */ -import 'dart:typed_data'; import 'dart:convert'; import 'dart:math'; +import 'dart:typed_data'; -import 'package:matrix/matrix.dart'; -import 'package:matrix/encryption.dart'; - -import 'package:test/test.dart'; import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; +import 'package:matrix/encryption.dart'; +import 'package:matrix/matrix.dart'; import '../fake_client.dart'; import '../fake_matrix_api.dart'; diff --git a/test/encryption/utils_test.dart b/test/encryption/utils_test.dart index b21d0990..59e79a58 100644 --- a/test/encryption/utils_test.dart +++ b/test/encryption/utils_test.dart @@ -18,9 +18,10 @@ import 'dart:convert'; +import 'package:test/test.dart'; + import 'package:matrix/encryption/utils/base64_unpadded.dart'; import 'package:matrix/matrix.dart'; -import 'package:test/test.dart'; void main() { group('Utils', () { diff --git a/test/event_test.dart b/test/event_test.dart index e03cf41f..8cc4edd2 100644 --- a/test/event_test.dart +++ b/test/event_test.dart @@ -19,12 +19,12 @@ import 'dart:convert'; import 'dart:typed_data'; -import 'package:matrix/encryption.dart'; -import 'package:matrix/matrix.dart'; -import 'package:matrix/src/models/timeline_chunk.dart'; import 'package:olm/olm.dart' as olm; import 'package:test/test.dart'; +import 'package:matrix/encryption.dart'; +import 'package:matrix/matrix.dart'; +import 'package:matrix/src/models/timeline_chunk.dart'; import 'fake_client.dart'; import 'fake_matrix_api.dart'; diff --git a/test/fake_client.dart b/test/fake_client.dart index f7afd062..acfb5e04 100644 --- a/test/fake_client.dart +++ b/test/fake_client.dart @@ -17,9 +17,8 @@ */ import 'package:matrix/matrix.dart'; - -import 'fake_matrix_api.dart'; import 'fake_database.dart'; +import 'fake_matrix_api.dart'; const ssssPassphrase = 'nae7ahDiequ7ohniufah3ieS2je1thohX4xeeka7aixohsho9O'; const ssssKey = 'EsT9 RzbW VhPW yqNp cC7j ViiW 5TZB LuY4 ryyv 9guN Ysmr WDPH'; diff --git a/test/fake_database.dart b/test/fake_database.dart index 6c076fcb..5e78e25c 100644 --- a/test/fake_database.dart +++ b/test/fake_database.dart @@ -19,10 +19,11 @@ import 'dart:io'; import 'dart:math'; -import 'package:matrix/matrix.dart'; import 'package:file/memory.dart'; import 'package:hive/hive.dart'; +import 'package:matrix/matrix.dart'; + Future getDatabase(Client? _) => getHiveCollectionsDatabase(_); bool hiveInitialized = false; diff --git a/test/fake_matrix_api.dart b/test/fake_matrix_api.dart index eec6d180..ac706306 100644 --- a/test/fake_matrix_api.dart +++ b/test/fake_matrix_api.dart @@ -22,6 +22,7 @@ import 'dart:core'; import 'dart:math'; import 'package:http/http.dart'; + import 'package:matrix/matrix.dart' as sdk; import 'package:matrix/matrix.dart'; diff --git a/test/html_to_text_test.dart b/test/html_to_text_test.dart index 9dde56e7..27ba947a 100644 --- a/test/html_to_text_test.dart +++ b/test/html_to_text_test.dart @@ -16,9 +16,10 @@ * along with this program. If not, see . */ -import 'package:matrix/src/utils/html_to_text.dart'; import 'package:test/test.dart'; +import 'package:matrix/src/utils/html_to_text.dart'; + void main() { group('htmlToText', () { final testMap = { diff --git a/test/image_pack_test.dart b/test/image_pack_test.dart index 513dd7c5..c801d2c3 100644 --- a/test/image_pack_test.dart +++ b/test/image_pack_test.dart @@ -17,6 +17,7 @@ */ import 'package:test/test.dart'; + import 'package:matrix/matrix.dart'; import 'fake_client.dart'; diff --git a/test/markdown_test.dart b/test/markdown_test.dart index 2c3dab85..dac27912 100644 --- a/test/markdown_test.dart +++ b/test/markdown_test.dart @@ -16,9 +16,10 @@ * along with this program. If not, see . */ -import 'package:matrix/src/utils/markdown.dart'; import 'package:test/test.dart'; +import 'package:matrix/src/utils/markdown.dart'; + void main() { group('markdown', () { final emotePacks = { diff --git a/test/matrix_api/map_copy_extension_test.dart b/test/matrix_api/map_copy_extension_test.dart index e7027952..56a21936 100644 --- a/test/matrix_api/map_copy_extension_test.dart +++ b/test/matrix_api/map_copy_extension_test.dart @@ -16,9 +16,10 @@ * along with this program. If not, see . */ -import 'package:matrix/src/utils/map_copy_extension.dart'; import 'package:test/test.dart'; +import 'package:matrix/src/utils/map_copy_extension.dart'; + void main() { group('Map-copy-extension', () { test('it should work', () { diff --git a/test/matrix_database_test.dart b/test/matrix_database_test.dart index 0c88c612..6c333e9b 100644 --- a/test/matrix_database_test.dart +++ b/test/matrix_database_test.dart @@ -17,9 +17,9 @@ * */ -import 'package:matrix/matrix.dart'; - import 'package:test/test.dart'; + +import 'package:matrix/matrix.dart'; import 'fake_database.dart'; void main() { diff --git a/test/matrix_exception_test.dart b/test/matrix_exception_test.dart index 5378f325..f9769edd 100644 --- a/test/matrix_exception_test.dart +++ b/test/matrix_exception_test.dart @@ -15,11 +15,12 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import 'package:matrix/matrix.dart'; -import 'package:http/http.dart'; +import 'package:http/http.dart'; import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; + void main() { /// All Tests related to device keys group('Matrix Exception', () { diff --git a/test/matrix_file_test.dart b/test/matrix_file_test.dart index 7f6aef06..cd36b1ae 100644 --- a/test/matrix_file_test.dart +++ b/test/matrix_file_test.dart @@ -18,11 +18,11 @@ import 'dart:typed_data'; -import 'package:matrix/matrix.dart'; - -import 'package:test/test.dart'; -import 'package:olm/olm.dart' as olm; import 'package:http/http.dart' as http; +import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; + +import 'package:matrix/matrix.dart'; void main() { /// All Tests related to device keys diff --git a/test/matrix_id_string_extension_test.dart b/test/matrix_id_string_extension_test.dart index e5fd2842..5b9c0072 100644 --- a/test/matrix_id_string_extension_test.dart +++ b/test/matrix_id_string_extension_test.dart @@ -17,6 +17,7 @@ */ import 'package:test/test.dart'; + import 'package:matrix/src/utils/matrix_id_string_extension.dart'; void main() { diff --git a/test/matrix_localizations_test.dart b/test/matrix_localizations_test.dart index 53e9a6b7..4c72451b 100644 --- a/test/matrix_localizations_test.dart +++ b/test/matrix_localizations_test.dart @@ -15,9 +15,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import 'package:matrix/matrix.dart'; + import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; + void main() { /// All Tests related to device keys group('Matrix Localizations', () { diff --git a/test/multilock_test.dart b/test/multilock_test.dart index 292b1e4e..d973e87f 100644 --- a/test/multilock_test.dart +++ b/test/multilock_test.dart @@ -16,9 +16,10 @@ * along with this program. If not, see . */ -import 'package:matrix/src/utils/multilock.dart'; import 'package:test/test.dart'; +import 'package:matrix/src/utils/multilock.dart'; + void main() { group('lock', () { final lock = MultiLock(); diff --git a/test/mxc_uri_extension_test.dart b/test/mxc_uri_extension_test.dart index b2248a35..290265b2 100644 --- a/test/mxc_uri_extension_test.dart +++ b/test/mxc_uri_extension_test.dart @@ -16,10 +16,9 @@ * along with this program. If not, see . */ -import 'package:matrix/matrix.dart'; - import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; import 'fake_matrix_api.dart'; void main() { diff --git a/test/push_notification.dart b/test/push_notification.dart index 330a4c29..f35220da 100644 --- a/test/push_notification.dart +++ b/test/push_notification.dart @@ -1,8 +1,9 @@ import 'dart:convert'; -import 'package:matrix/matrix.dart'; import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; + void main() { group('Push Notification', () { Logs().level = Level.error; diff --git a/test/room_test.dart b/test/room_test.dart index b723a0e9..f6bb0481 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -19,10 +19,9 @@ import 'dart:convert'; import 'dart:typed_data'; -import 'package:matrix/matrix.dart'; - import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; import 'fake_client.dart'; import 'fake_matrix_api.dart'; diff --git a/test/sync_filter_test.dart b/test/sync_filter_test.dart index bb0b2630..60d1ed56 100644 --- a/test/sync_filter_test.dart +++ b/test/sync_filter_test.dart @@ -16,10 +16,10 @@ * along with this program. If not, see . */ -import 'package:matrix/matrix.dart'; - import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; + const updates = { 'empty': { 'next_batch': 'blah', diff --git a/test/timeline_context_test.dart b/test/timeline_context_test.dart index 20d6d3e3..466330a0 100644 --- a/test/timeline_context_test.dart +++ b/test/timeline_context_test.dart @@ -18,11 +18,11 @@ import 'dart:async'; +import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; + import 'package:matrix/matrix.dart'; import 'package:matrix/src/models/timeline_chunk.dart'; - -import 'package:test/test.dart'; -import 'package:olm/olm.dart' as olm; import 'fake_client.dart'; import 'fake_matrix_api.dart'; diff --git a/test/timeline_test.dart b/test/timeline_test.dart index 181aebfe..38b69d26 100644 --- a/test/timeline_test.dart +++ b/test/timeline_test.dart @@ -19,13 +19,12 @@ import 'dart:async'; import 'dart:math'; +import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; + import 'package:matrix/matrix.dart'; import 'package:matrix/src/models/timeline_chunk.dart'; - -import 'package:test/test.dart'; -import 'package:olm/olm.dart' as olm; import 'fake_client.dart'; -import 'fake_matrix_api.dart'; void main() { group('Timeline', () { diff --git a/test/uia_test.dart b/test/uia_test.dart index f8300936..fd733419 100644 --- a/test/uia_test.dart +++ b/test/uia_test.dart @@ -18,10 +18,10 @@ import 'dart:async'; -import 'package:matrix/matrix.dart'; - import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; + void main() { group('UIA', () { Logs().level = Level.error; diff --git a/test/user_test.dart b/test/user_test.dart index bead02cd..c88b75c1 100644 --- a/test/user_test.dart +++ b/test/user_test.dart @@ -16,10 +16,9 @@ * along with this program. If not, see . */ -import 'package:matrix/matrix.dart'; - import 'package:test/test.dart'; +import 'package:matrix/matrix.dart'; import 'fake_matrix_api.dart'; void main() { diff --git a/test_driver/matrixsdk_test.dart b/test_driver/matrixsdk_test.dart index 72844e51..e0354c7c 100644 --- a/test_driver/matrixsdk_test.dart +++ b/test_driver/matrixsdk_test.dart @@ -15,11 +15,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + import 'package:hive/hive.dart'; +import 'package:olm/olm.dart' as olm; + import 'package:matrix/matrix.dart'; import '../test/fake_database.dart'; import 'test_config.dart'; -import 'package:olm/olm.dart' as olm; void main() => test(); const String testMessage = 'Hello world'; From 2a019eaec3b2d29d5ff21d2d66bf6dfee390af15 Mon Sep 17 00:00:00 2001 From: Henri Carnot Date: Thu, 21 Jul 2022 14:14:17 +0000 Subject: [PATCH 4/4] feat: keep timeline history for archive rooms in memory --- lib/src/client.dart | 103 +++++++++++++++++++------- lib/src/room.dart | 73 +++++++++++------- lib/src/timeline.dart | 4 +- test/client_test.dart | 15 ---- test/fake_matrix_api.dart | 91 +++++++++++++++++++++-- test/room_archived_test.dart | 138 +++++++++++++++++++++++++++++++++++ 6 files changed, 346 insertions(+), 78 deletions(-) create mode 100644 test/room_archived_test.dart diff --git a/lib/src/client.dart b/lib/src/client.dart index ced85487..e3312a7b 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -32,6 +32,7 @@ import 'package:matrix/src/utils/run_in_root.dart'; import 'package:matrix/src/utils/sync_update_item_count.dart'; import '../encryption.dart'; import '../matrix.dart'; +import 'models/timeline_chunk.dart'; import 'utils/multilock.dart'; import 'utils/run_benchmarked.dart'; @@ -287,7 +288,7 @@ class Client extends MatrixApi { /// found. If you have loaded the [loadArchive()] before, it can also return /// archived rooms. Room? getRoomById(String id) { - for (final room in [...rooms, ..._archivedRooms]) { + for (final room in [...rooms, ..._archivedRooms.map((e) => e.room)]) { if (room.id == id) return room; } @@ -781,9 +782,35 @@ class Client extends MatrixApi { avatarUrl: profile.avatarUrl); } - final List _archivedRooms = []; + final List _archivedRooms = []; + /// Return an archive room containing the room and the timeline for a specific archived room. + ArchivedRoom? getArchiveRoomFromCache(String roomId) { + for (var i = 0; i < _archivedRooms.length; i++) { + final archive = _archivedRooms[i]; + if (archive.room.id == roomId) return archive; + } + return null; + } + + /// Remove all the archives stored in cache. + void clearArchivesFromCache() { + _archivedRooms.clear(); + } + + @Deprecated('Use [loadArchive()] instead.') + Future> get archive => loadArchive(); + + /// Fetch all the archived rooms from the server and return the list of the + /// room. If you want to have the Timelines bundled with it, use + /// loadArchiveWithTimeline instead. Future> loadArchive() async { + return (await loadArchiveWithTimeline()).map((e) => e.room).toList(); + } + + /// Fetch the archived rooms from the server and return them as a list of + /// [ArchivedRoom] objects containing the [Room] and the associated [Timeline]. + Future> loadArchiveWithTimeline() async { _archivedRooms.clear(); final syncResp = await sync( filter: '{"room":{"include_leave":true,"timeline":{"limit":10}}}', @@ -804,12 +831,32 @@ class Client extends MatrixApi { {}, ); + final timeline = Timeline( + room: leftRoom, + chunk: TimelineChunk( + events: room.timeline?.events?.reversed + .toList() // we display the event in the other sence + .map((e) => Event.fromMatrixEvent(e, leftRoom)) + .toList() ?? + [])); + + for (var i = 0; i < timeline.events.length; i++) { + // Try to decrypt encrypted events but don't update the database. + if (leftRoom.encrypted && leftRoom.client.encryptionEnabled) { + if (timeline.events[i].type == EventTypes.Encrypted) { + timeline.events[i] = await leftRoom.client.encryption! + .decryptRoomEvent(leftRoom.id, timeline.events[i]); + } + } + } + room.timeline?.events?.forEach((event) { leftRoom.setState(Event.fromMatrixEvent( event, leftRoom, )); }); + leftRoom.prev_batch = room.timeline?.prevBatch; room.state?.forEach((event) { leftRoom.setState(Event.fromMatrixEvent( @@ -817,7 +864,8 @@ class Client extends MatrixApi { leftRoom, )); }); - _archivedRooms.add(leftRoom); + + _archivedRooms.add(ArchivedRoom(room: leftRoom, timeline: timeline)); } } return _archivedRooms; @@ -1658,6 +1706,12 @@ class Client extends MatrixApi { await database?.storeRoomUpdate(id, syncRoomUpdate, this); final room = _updateRoomsByRoomUpdate(id, syncRoomUpdate); + final timelineUpdateType = direction != null + ? (direction == Direction.b + ? EventUpdateType.history + : EventUpdateType.timeline) + : EventUpdateType.timeline; + /// Handle now all room events and save them in the database if (syncRoomUpdate is JoinedRoomUpdate) { final state = syncRoomUpdate.state; @@ -1673,14 +1727,7 @@ class Client extends MatrixApi { final timelineEvents = syncRoomUpdate.timeline?.events; if (timelineEvents != null && timelineEvents.isNotEmpty) { - await _handleRoomEvents( - room, - timelineEvents, - direction != null - ? (direction == Direction.b - ? EventUpdateType.history - : EventUpdateType.timeline) - : EventUpdateType.timeline); + await _handleRoomEvents(room, timelineEvents, timelineUpdateType); } final ephemeral = syncRoomUpdate.ephemeral; @@ -1705,23 +1752,19 @@ class Client extends MatrixApi { if (syncRoomUpdate is LeftRoomUpdate) { final timelineEvents = syncRoomUpdate.timeline?.events; if (timelineEvents != null && timelineEvents.isNotEmpty) { - await _handleRoomEvents( - room, - timelineEvents, - EventUpdateType.timeline, - ); + await _handleRoomEvents(room, timelineEvents, timelineUpdateType, + store: false); } final accountData = syncRoomUpdate.accountData; if (accountData != null && accountData.isNotEmpty) { await _handleRoomEvents( - room, - accountData, - EventUpdateType.accountData, - ); + room, accountData, EventUpdateType.accountData, + store: false); } final state = syncRoomUpdate.state; if (state != null && state.isNotEmpty) { - await _handleRoomEvents(room, state, EventUpdateType.state); + await _handleRoomEvents(room, state, EventUpdateType.state, + store: false); } } @@ -1795,10 +1838,8 @@ class Client extends MatrixApi { } Future _handleRoomEvents( - Room room, - List events, - EventUpdateType type, - ) async { + Room room, List events, EventUpdateType type, + {bool store = true}) async { // Calling events can be omitted if they are outdated from the same sync. So // we collect them first before we handle them. final callEvents = {}; @@ -1833,7 +1874,7 @@ class Client extends MatrixApi { } } _updateRoomsByEventUpdate(room, update); - if (type != EventUpdateType.ephemeral) { + if (type != EventUpdateType.ephemeral && store) { await database?.storeEventUpdate(update, this); } if (encryptionEnabled) { @@ -1941,6 +1982,10 @@ class Client extends MatrixApi { // Does the chat already exist in the list rooms? if (!found && membership != Membership.leave) { + // Check if the room is not in the rooms in the invited list + if (_archivedRooms.isNotEmpty) { + _archivedRooms.removeWhere((archive) => archive.room.id == roomId); + } final position = membership == Membership.invite ? 0 : rooms.length; // Add the new chat to the list rooms.insert(position, room); @@ -2857,3 +2902,9 @@ class HomeserverSummary { required this.loginFlows, }); } + +class ArchivedRoom { + final Room room; + final Timeline timeline; + ArchivedRoom({required this.room, required this.timeline}); +} diff --git a/lib/src/room.dart b/lib/src/room.dart index ad17445d..47df513a 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -1136,6 +1136,9 @@ class Room { void Function()? onHistoryReceived, direction = Direction.b}) async { final prev_batch = this.prev_batch; + + final storeInDatabase = !isArchived; + if (prev_batch == null) { throw 'Tried to request history without a prev_batch token'; } @@ -1179,7 +1182,9 @@ class Room { state: resp.state, timeline: TimelineUpdate( limited: false, - events: resp.chunk, + events: direction == Direction.b + ? resp.chunk + : resp.chunk.reversed.toList(), prevBatch: direction == Direction.b ? resp.end : resp.start, @@ -1193,7 +1198,9 @@ class Room { if (client.database != null) { await client.database?.transaction(() async { - await client.database?.setRoomPrevBatch(resp.end!, id, client); + if (storeInDatabase) { + await client.database?.setRoomPrevBatch(resp.end!, id, client); + } await loadFn(); }); } else { @@ -1304,6 +1311,9 @@ class Room { return; } + /// Is the room archived + bool get isArchived => membership == Membership.leave; + /// Creates a timeline from the store. Returns a [Timeline] object. If you /// just want to update the whole timeline on every change, use the [onUpdate] /// callback. For updating only the parts that have changed, use the @@ -1319,35 +1329,41 @@ class Room { String? eventContextId}) async { await postLoad(); - final _events = await client.database?.getEventList( - this, - limit: defaultHistoryCount, - ); + var events; - var chunk = TimelineChunk(events: _events ?? []); + if (!isArchived) { + events = await client.database?.getEventList( + this, + limit: defaultHistoryCount, + ) ?? + []; + } else { + final archive = client.getArchiveRoomFromCache(id); + events = archive?.timeline.events.toList() ?? []; + } - if (_events != null) { - if (eventContextId != null) { - if (_events - .firstWhereOrNull((event) => event.eventId == eventContextId) != - null) { - chunk = TimelineChunk(events: _events); - } else { - chunk = await getEventContext(eventContextId) ?? - TimelineChunk(events: []); - } - } - - // Fetch all users from database we have got here. - if (eventContextId == null) { - for (final event in _events) { - if (getState(EventTypes.RoomMember, event.senderId) != null) continue; - final dbUser = await client.database?.getUser(event.senderId, this); - if (dbUser != null) setState(dbUser); - } + var chunk = TimelineChunk(events: events); + // Load the timeline arround eventContextId if set + if (eventContextId != null) { + if (events.firstWhereOrNull((event) => event.eventId == eventContextId) != + null) { + chunk = TimelineChunk(events: events); + } else { + chunk = + await getEventContext(eventContextId) ?? TimelineChunk(events: []); } } + // Fetch all users from database we have got here. + if (eventContextId == null) { + for (final event in events) { + if (getState(EventTypes.RoomMember, event.senderId) != null) continue; + final dbUser = await client.database?.getUser(event.senderId, this); + if (dbUser != null) setState(dbUser); + } + } + + // Try again to decrypt encrypted events and update the database. if (encrypted && client.encryptionEnabled) { // decrypt messages for (var i = 0; i < chunk.events.length; i++) { @@ -1364,8 +1380,9 @@ class Room { await client.database?.transaction(() async { for (var i = 0; i < chunk.events.length; i++) { if (chunk.events[i].content['can_request_session'] == true) { - chunk.events[i] = await client.encryption! - .decryptRoomEvent(id, chunk.events[i], store: true); + chunk.events[i] = await client.encryption!.decryptRoomEvent( + id, chunk.events[i], + store: !isArchived); } } }); diff --git a/lib/src/timeline.dart b/lib/src/timeline.dart index c85d206e..581bfdde 100644 --- a/lib/src/timeline.dart +++ b/lib/src/timeline.dart @@ -224,9 +224,7 @@ class Timeline { } // Try to decrypt encrypted events but don't update the database. - if (room.encrypted && - room.client.database != null && - room.client.encryptionEnabled) { + if (room.encrypted && room.client.encryptionEnabled) { for (var i = 0; i < newEvents.length; i++) { if (newEvents[i].type == EventTypes.Encrypted) { newEvents[i] = await room.client.encryption! diff --git a/test/client_test.dart b/test/client_test.dart index 3c32031a..fd39c64d 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -338,21 +338,6 @@ void main() { ); }); - test('get archive', () async { - await matrix.abortSync(); - final archive = await matrix.loadArchive(); - - expect(archive.length, 2); - expect(archive[0].id, '!5345234234:example.com'); - expect(archive[0].membership, Membership.leave); - expect(archive[0].name, 'The room name'); - expect(archive[0].lastEvent?.body, 'This is an example text message'); - expect(archive[0].roomAccountData.length, 1); - expect(archive[1].id, '!5345234235:example.com'); - expect(archive[1].membership, Membership.leave); - expect(archive[1].name, 'The room name 2'); - }); - test('sync state event in-memory handling', () async { final roomId = '!726s6s6q:example.com'; final room = matrix.getRoomById(roomId)!; diff --git a/test/fake_matrix_api.dart b/test/fake_matrix_api.dart index ac706306..5eea00d8 100644 --- a/test/fake_matrix_api.dart +++ b/test/fake_matrix_api.dart @@ -361,6 +361,65 @@ class FakeMatrixApi extends BaseClient { 'state': [], }; + static Map archivesMessageResponse = { + 'start': 't47429-4392820_219380_26003_2265', + 'end': 't47409-4357353_219380_26003_2265', + 'chunk': [ + { + 'content': { + 'body': 'This is an example text message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': 'This is an example text message' + }, + 'type': 'm.room.message', + 'event_id': '3143273582443PhrSn:example.org', + 'room_id': '!5345234234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234} + }, + { + 'content': {'name': 'The room name'}, + 'type': 'm.room.name', + 'event_id': '2143273582443PhrSn:example.org', + 'room_id': '!5345234234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824653, + 'unsigned': {'age': 1234}, + 'state_key': '' + }, + { + 'content': { + 'body': 'Gangnam Style', + 'url': 'mxc://example.org/a526eYUSFFxlgbQYZmo442', + 'info': { + 'thumbnail_url': 'mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe', + 'thumbnail_info': { + 'mimetype': 'image/jpeg', + 'size': 46144, + 'w': 300, + 'h': 300 + }, + 'w': 480, + 'h': 320, + 'duration': 2140786, + 'size': 1563685, + 'mimetype': 'video/mp4' + }, + 'msgtype': 'm.video' + }, + 'type': 'm.room.message', + 'event_id': '1143273582466PhrSn:example.org', + 'room_id': '!5345234234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824654, + 'unsigned': {'age': 1234} + } + ], + 'state': [], + }; + static Map syncResponse = { 'next_batch': Random().nextDouble().toString(), 'rooms': { @@ -888,19 +947,36 @@ class FakeMatrixApi extends BaseClient { 'events': [ { 'content': { - 'body': 'This is an example text message', + 'body': 'This is a second text example message', 'msgtype': 'm.text', 'format': 'org.matrix.custom.html', - 'formatted_body': 'This is an example text message' + 'formatted_body': + 'This is a second text example message' }, 'type': 'm.room.message', - 'event_id': '143273582443PhrSn:example.org', + 'event_id': '143274597446PhrSn:example.org', + 'room_id': '!5345234234:example.com', + 'sender': '@example:example.org', + 'origin_server_ts': 1432735824654, + 'unsigned': {'age': 1234} + }, + { + 'content': { + 'body': 'This is a first text example message', + 'msgtype': 'm.text', + 'format': 'org.matrix.custom.html', + 'formatted_body': + 'This is a first text example message' + }, + 'type': 'm.room.message', + 'event_id': '143274597443PhrSn:example.org', 'room_id': '!5345234234:example.com', 'sender': '@example:example.org', 'origin_server_ts': 1432735824653, 'unsigned': {'age': 1234} - }, - ] + } + ], + 'prev_batch': 't_1234a' }, 'state': { 'events': [ @@ -940,7 +1016,8 @@ class FakeMatrixApi extends BaseClient { 'state_key': '' }, ] - } + }, + 'prev_batch': 't_1234b' }, }, } @@ -1518,6 +1595,8 @@ class FakeMatrixApi extends BaseClient { (var req) => messagesResponseFutureEnd, '/client/v3/rooms/!1234%3Aexample.com/messages?from=t789&dir=f&limit=30&filter=%7B%22lazy_load_members%22%3Atrue%7D': (var req) => messagesResponseFutureEnd, + '/client/v3/rooms/!5345234234%3Aexample.com/messages?from=t_1234a&dir=b&limit=30&filter=%7B%22lazy_load_members%22%3Atrue%7D': + (var req) => archivesMessageResponse, '/client/versions': (var req) => { 'versions': [ 'v1.1', diff --git a/test/room_archived_test.dart b/test/room_archived_test.dart new file mode 100644 index 00000000..c99af5ab --- /dev/null +++ b/test/room_archived_test.dart @@ -0,0 +1,138 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2022 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'dart:async'; + +import 'package:olm/olm.dart' as olm; +import 'package:test/test.dart'; + +import 'package:matrix/matrix.dart'; +import 'fake_client.dart'; + +void main() { + group('Timeline', () { + Logs().level = Level.error; + var olmEnabled = true; + + final insertList = []; + + late Client client; + + setUp(() async { + try { + await olm.init(); + olm.get_library_version(); + } catch (e) { + olmEnabled = false; + Logs().w('[LibOlm] Failed to load LibOlm', e); + } + Logs().i('[LibOlm] Enabled: $olmEnabled'); + + client = await getClient(); + client.sendMessageTimeoutSeconds = 5; + + await client.abortSync(); + insertList.clear(); + }); + + tearDown(() => client.dispose().onError((e, s) {})); + + test('archive room not loaded', () async { + final archiveRoom = + client.getArchiveRoomFromCache('!5345234234:example.com'); + expect(archiveRoom, null); + }); + + test('get archive', () async { + final archive = await client.loadArchiveWithTimeline(); + + expect(archive.length, 2); + expect(client.rooms.length, 2); + expect(archive[0].room.id, '!5345234234:example.com'); + expect(archive[0].room.membership, Membership.leave); + expect(archive[0].room.name, 'The room name'); + expect(archive[0].room.lastEvent?.body, + 'This is a second text example message'); + expect(archive[0].room.roomAccountData.length, 1); + expect(archive[1].room.id, '!5345234235:example.com'); + expect(archive[1].room.membership, Membership.leave); + expect(archive[1].room.name, 'The room name 2'); + + final archiveRoom = + client.getArchiveRoomFromCache('!5345234234:example.com'); + expect(archiveRoom != null, true); + expect(archiveRoom!.timeline.events.length, 2); + }); + + test('request history', () async { + await client.loadArchiveWithTimeline(); + final archiveRoom = client.getRoomById('!5345234234:example.com'); + expect(archiveRoom != null, true); + + final timeline = await archiveRoom!.getTimeline(onInsert: insertList.add); + + expect(timeline.events.length, 2); + expect(timeline.events[0].eventId, '143274597443PhrSn:example.org'); + expect(timeline.events[1].eventId, '143274597446PhrSn:example.org'); + + await timeline.requestHistory(); + + expect(timeline.events.length, 5); + expect(timeline.events[0].eventId, '143274597443PhrSn:example.org'); + expect(timeline.events[1].eventId, '143274597446PhrSn:example.org'); + expect(timeline.events[2].eventId, '3143273582443PhrSn:example.org'); + expect(timeline.events[3].eventId, '2143273582443PhrSn:example.org'); + expect(timeline.events[4].eventId, '1143273582466PhrSn:example.org'); + expect(insertList.length, 3); + }); + + test('expect database to be empty', () async { + await client.loadArchiveWithTimeline(); + final archiveRoom = client.getRoomById('!5345234234:example.com'); + expect(archiveRoom != null, true); + + final eventsFromStore = await client.database?.getEventList( + archiveRoom!, + start: 0, + limit: Room.defaultHistoryCount, + ); + expect(eventsFromStore?.isEmpty, true); + }); + + test('discard room from archives when membership change', () async { + await client.loadArchiveWithTimeline(); + expect(client.getArchiveRoomFromCache('!5345234235:example.com') != null, + true); + await client.handleSync(SyncUpdate( + nextBatch: 't_456', + rooms: RoomsUpdate( + invite: {'!5345234235:example.com': InvitedRoomUpdate()}))); + expect(client.getArchiveRoomFromCache('!5345234235:example.com'), null); + }); + + test('clear archive', () async { + await client.loadArchiveWithTimeline(); + client.clearArchivesFromCache(); + expect(client.getArchiveRoomFromCache('!5345234234:example.com'), null); + }); + + test('logout', () async { + await client.logout(); + }); + }); +}