From 9087f087753d5972ab892d47c4900961b48f344c Mon Sep 17 00:00:00 2001 From: Lukas Lihotzki Date: Mon, 23 Aug 2021 11:05:47 +0200 Subject: [PATCH 1/6] chore: mxc url in Uri --- lib/src/client.dart | 6 +++--- lib/src/database/database_api.dart | 4 ++-- lib/src/database/hive_database.dart | 4 ++-- lib/src/event.dart | 28 +++++++++++++--------------- lib/src/room.dart | 10 +++++----- pubspec.yaml | 2 +- test/client_test.dart | 2 +- test/database_api_test.dart | 9 +++++---- test/event_test.dart | 18 ++++++++++-------- test/room_test.dart | 2 +- 10 files changed, 43 insertions(+), 42 deletions(-) diff --git a/lib/src/client.dart b/lib/src/client.dart index 45ae727d..686177be 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -688,9 +688,9 @@ class Client extends MatrixApi { } /// Uploads a file and automatically caches it in the database, if it is small enough - /// and returns the mxc url as a string. + /// and returns the mxc url. @override - Future uploadContent(Uint8List file, + Future uploadContent(Uint8List file, {String filename, String contentType}) async { final mxc = await super .uploadContent(file, filename: filename, contentType: contentType); @@ -721,7 +721,7 @@ class Client extends MatrixApi { /// Uploads a new user avatar for this user. Future setAvatar(MatrixFile file) async { final uploadResp = await uploadContent(file.bytes, filename: file.name); - await setAvatarUrl(userID, Uri.parse(uploadResp)); + await setAvatarUrl(userID, uploadResp); return; } diff --git a/lib/src/database/database_api.dart b/lib/src/database/database_api.dart index d1065ad3..fa7c2266 100644 --- a/lib/src/database/database_api.dart +++ b/lib/src/database/database_api.dart @@ -81,9 +81,9 @@ abstract class DatabaseApi { Future> getEventList(int clientId, Room room); - Future getFile(String mxcUri); + Future getFile(Uri mxcUri); - Future storeFile(String mxcUri, Uint8List bytes, int time); + Future storeFile(Uri mxcUri, Uint8List bytes, int time); Future storeSyncFilterId(String syncFilterId, int clientId); diff --git a/lib/src/database/hive_database.dart b/lib/src/database/hive_database.dart index 92c2f030..5752caa6 100644 --- a/lib/src/database/hive_database.dart +++ b/lib/src/database/hive_database.dart @@ -329,7 +329,7 @@ class FamedlySdkHiveDatabase extends DatabaseApi { } @override - Future getFile(String mxcUri) async { + Future getFile(Uri mxcUri) async { return null; } @@ -869,7 +869,7 @@ class FamedlySdkHiveDatabase extends DatabaseApi { } @override - Future storeFile(String mxcUri, Uint8List bytes, int time) async { + Future storeFile(Uri mxcUri, Uint8List bytes, int time) async { return; } diff --git a/lib/src/event.dart b/lib/src/event.dart index ffdb006c..0d3c478b 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -401,16 +401,16 @@ class Event extends MatrixEvent { : ''); /// Gets the underyling mxc url of an attachment of a file event, or null if not present - String get attachmentMxcUrl => - isAttachmentEncrypted ? content['file']['url'] : content['url']; + Uri get attachmentMxcUrl => Uri.parse( + isAttachmentEncrypted ? content['file']['url'] : content['url']); /// Gets the underyling mxc url of a thumbnail of a file event, or null if not present - String get thumbnailMxcUrl => isThumbnailEncrypted + Uri get thumbnailMxcUrl => Uri.parse(isThumbnailEncrypted ? infoMap['thumbnail_file']['url'] - : infoMap['thumbnail_url']; + : infoMap['thumbnail_url']); /// Gets the mxc url of an attachemnt/thumbnail of a file event, taking sizes into account, or null if not present - String attachmentOrThumbnailMxcUrl({bool getThumbnail = false}) { + Uri attachmentOrThumbnailMxcUrl({bool getThumbnail = false}) { if (getThumbnail && infoMap['size'] is int && thumbnailInfoMap['size'] is int && @@ -479,8 +479,8 @@ class Event extends MatrixEvent { throw ("This event has the type '$type' and so it can't contain an attachment."); } final mxcUrl = attachmentOrThumbnailMxcUrl(getThumbnail: getThumbnail); - if (!(mxcUrl is String)) { - throw ("This event hasn't any attachment or thumbnail."); + if (mxcUrl == null) { + throw "This event hasn't any attachment or thumbnail."; } getThumbnail = mxcUrl != attachmentMxcUrl; // Is this file storeable? @@ -507,8 +507,8 @@ class Event extends MatrixEvent { throw ("This event has the type '$type' and so it can't contain an attachment."); } final mxcUrl = attachmentOrThumbnailMxcUrl(getThumbnail: getThumbnail); - if (!(mxcUrl is String)) { - throw ("This event hasn't any attachment or thumbnail."); + if (mxcUrl == null) { + throw "This event hasn't any attachment or thumbnail."; } getThumbnail = mxcUrl != attachmentMxcUrl; final isEncrypted = @@ -517,7 +517,6 @@ class Event extends MatrixEvent { if (isEncrypted && !room.client.encryptionEnabled) { throw ('Encryption is not enabled in your Client.'); } - final mxContent = Uri.parse(mxcUrl); Uint8List uint8list; @@ -528,7 +527,7 @@ class Event extends MatrixEvent { thisInfoMap['size'] <= room.client.database.maxFileSize; if (storeable) { - uint8list = await room.client.database.getFile(mxContent.toString()); + uint8list = await room.client.database.getFile(mxcUrl); } // Download the file @@ -536,13 +535,12 @@ class Event extends MatrixEvent { downloadCallback ??= (Uri url) async { return (await http.get(url)).bodyBytes; }; - uint8list = - await downloadCallback(mxContent.getDownloadLink(room.client)); + uint8list = await downloadCallback(mxcUrl.getDownloadLink(room.client)); storeable = storeable && uint8list.lengthInBytes < room.client.database.maxFileSize; if (storeable) { - await room.client.database.storeFile(mxContent.toString(), uint8list, - DateTime.now().millisecondsSinceEpoch); + await room.client.database.storeFile( + mxcUrl, uint8list, DateTime.now().millisecondsSinceEpoch); } } diff --git a/lib/src/room.dart b/lib/src/room.dart index 09d5a5da..2284f5de 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -630,7 +630,7 @@ class Room { /// the message event has received the server. Otherwise the future will only /// wait until the file has been uploaded. /// Optionally specify [extraContent] to tack on to the event. - Future sendFileEvent( + Future sendFileEvent( MatrixFile file, { String txid, Event inReplyTo, @@ -670,10 +670,10 @@ class Room { 'msgtype': file.msgType, 'body': file.name, 'filename': file.name, - if (encryptedFile == null) 'url': uploadResp, + if (encryptedFile == null) 'url': uploadResp.toString(), if (encryptedFile != null) 'file': { - 'url': uploadResp, + 'url': uploadResp.toString(), 'mimetype': file.mimeType, 'v': 'v2', 'key': { @@ -692,7 +692,7 @@ class Room { 'thumbnail_url': thumbnailUploadResp, if (thumbnail != null && encryptedThumbnail != null) 'thumbnail_file': { - 'url': thumbnailUploadResp, + 'url': thumbnailUploadResp.toString(), 'mimetype': thumbnail.mimeType, 'v': 'v2', 'key': { @@ -1322,7 +1322,7 @@ class Room { id, EventTypes.RoomAvatar, '', - {'url': uploadResp}, + {'url': uploadResp.toString()}, ); } diff --git a/pubspec.yaml b/pubspec.yaml index 7b2caa3c..f52b1fff 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: crypto: ^3.0.0 base58check: ^2.0.0 olm: ^2.0.0 - matrix_api_lite: ^0.4.1 + matrix_api_lite: ^0.4.2 hive: ^2.0.4 ffi: ^1.0.0 js: ^0.6.3 diff --git a/test/client_test.dart b/test/client_test.dart index 88e14d7a..a8010055 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -645,7 +645,7 @@ void main() { final client = await getClient(); final response = await client.uploadContent(Uint8List(0), filename: 'file.jpeg'); - expect(response, 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'); + expect(response.toString(), 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'); expect(await client.database.getFile(response) != null, client.database.supportsFileStoring); await client.dispose(closeDatabase: true); diff --git a/test/database_api_test.dart b/test/database_api_test.dart index 94fe12c0..12fc5a4d 100644 --- a/test/database_api_test.dart +++ b/test/database_api_test.dart @@ -67,16 +67,17 @@ void testDatabase(Future futureDatabase, int clientId) { expect(toDeviceQueue.isEmpty, true); }); test('storeFile', () async { - await database.storeFile('mxc://test', Uint8List.fromList([0]), 0); - final file = await database.getFile('mxc://test'); + await database.storeFile( + Uri.parse('mxc://test'), Uint8List.fromList([0]), 0); + final file = await database.getFile(Uri.parse('mxc://test')); expect(file != null, database.supportsFileStoring); }); test('getFile', () async { - await database.getFile('mxc://test'); + await database.getFile(Uri.parse('mxc://test')); }); test('deleteOldFiles', () async { await database.deleteOldFiles(1); - final file = await database.getFile('mxc://test'); + final file = await database.getFile(Uri.parse('mxc://test')); expect(file == null, true); }); test('storeRoomUpdate', () async { diff --git a/test/event_test.dart b/test/event_test.dart index 73160904..7349c60a 100644 --- a/test/event_test.dart +++ b/test/event_test.dart @@ -1090,8 +1090,9 @@ void main() { var buffer = await event.downloadAndDecryptAttachment( downloadCallback: downloadCallback); expect(buffer.bytes, FILE_BUFF); - expect(event.attachmentOrThumbnailMxcUrl(), 'mxc://example.org/file'); - expect(event.attachmentOrThumbnailMxcUrl(getThumbnail: true), + expect(event.attachmentOrThumbnailMxcUrl().toString(), + 'mxc://example.org/file'); + expect(event.attachmentOrThumbnailMxcUrl(getThumbnail: true).toString(), 'mxc://example.org/file'); event = Event.fromJson({ @@ -1118,10 +1119,11 @@ void main() { expect(event.isThumbnailEncrypted, false); expect(event.attachmentMimetype, 'application/octet-stream'); expect(event.thumbnailMimetype, 'thumbnail/mimetype'); - expect(event.attachmentMxcUrl, 'mxc://example.org/file'); - expect(event.thumbnailMxcUrl, 'mxc://example.org/thumb'); - expect(event.attachmentOrThumbnailMxcUrl(), 'mxc://example.org/file'); - expect(event.attachmentOrThumbnailMxcUrl(getThumbnail: true), + expect(event.attachmentMxcUrl.toString(), 'mxc://example.org/file'); + expect(event.thumbnailMxcUrl.toString(), 'mxc://example.org/thumb'); + expect(event.attachmentOrThumbnailMxcUrl().toString(), + 'mxc://example.org/file'); + expect(event.attachmentOrThumbnailMxcUrl(getThumbnail: true).toString(), 'mxc://example.org/thumb'); expect(event.getAttachmentUrl().toString(), 'https://fakeserver.notexisting/_matrix/media/r0/download/example.org/file'); @@ -1238,8 +1240,8 @@ void main() { expect(event.isThumbnailEncrypted, true); expect(event.attachmentMimetype, 'text/plain'); expect(event.thumbnailMimetype, 'text/plain'); - expect(event.attachmentMxcUrl, 'mxc://example.com/file'); - expect(event.thumbnailMxcUrl, 'mxc://example.com/thumb'); + expect(event.attachmentMxcUrl.toString(), 'mxc://example.com/file'); + expect(event.thumbnailMxcUrl.toString(), 'mxc://example.com/thumb'); buffer = await event.downloadAndDecryptAttachment( downloadCallback: downloadCallback); expect(buffer.bytes, FILE_BUFF_DEC); diff --git a/test/room_test.dart b/test/room_test.dart index c3956cb3..28fb6777 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -587,7 +587,7 @@ void main() { test('sendFileEvent', () async { final testFile = MatrixFile(bytes: Uint8List(0), name: 'file.jpeg'); final dynamic resp = await room.sendFileEvent(testFile, txid: 'testtxid'); - expect(resp, 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'); + expect(resp.toString(), 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'); }); test('pushRuleState', () async { From cec08b377587b24bcea306694d85b2a2d4bed321 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 25 Aug 2021 09:52:57 +0200 Subject: [PATCH 2/6] feat: cache archived rooms to access them with `getRoomById` getRoomById searches now in the local cache for the given room and returns null if not found. If you have loaded the [archive] before, it can also return archived rooms. This should make it much easier to display archived rooms in the client. --- lib/src/client.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/src/client.dart b/lib/src/client.dart index 686177be..f7141b76 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -275,10 +275,14 @@ class Client extends MatrixApi { return null; } + /// Searches in the local cache for the given room and returns null if not + /// found. If you have loaded the [archive] before, it can also return + /// archived rooms. Room getRoomById(String id) { - for (final room in rooms) { + for (final room in [...rooms, ..._archivedRooms]) { if (room.id == id) return room; } + return null; } @@ -645,8 +649,10 @@ class Client extends MatrixApi { avatarUrl: profile.avatarUrl); } + final List _archivedRooms = []; + Future> get archive async { - final archiveList = []; + _archivedRooms.clear(); final syncResp = await sync( filter: '{"room":{"include_leave":true,"timeline":{"limit":10}}}', timeout: 0, @@ -681,10 +687,10 @@ class Client extends MatrixApi { )); } } - archiveList.add(leftRoom); + _archivedRooms.add(leftRoom); } } - return archiveList; + return _archivedRooms; } /// Uploads a file and automatically caches it in the database, if it is small enough From 2f35277e476d2d808e8477d0630d3646ae578090 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 25 Aug 2021 10:36:58 +0200 Subject: [PATCH 3/6] refactor: Change name of archive getter to function This is more intuitive because it is a function that loads something from the server and doesnt directly return something. --- lib/src/client.dart | 7 +++++-- test/client_test.dart | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/src/client.dart b/lib/src/client.dart index f7141b76..8033f54c 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -276,7 +276,7 @@ class Client extends MatrixApi { } /// Searches in the local cache for the given room and returns null if not - /// found. If you have loaded the [archive] before, it can also return + /// found. If you have loaded the [loadArchive()] before, it can also return /// archived rooms. Room getRoomById(String id) { for (final room in [...rooms, ..._archivedRooms]) { @@ -651,7 +651,10 @@ class Client extends MatrixApi { final List _archivedRooms = []; - Future> get archive async { + @Deprecated('Use [loadArchive()] instead.') + Future> get archive => loadArchive(); + + Future> loadArchive() async { _archivedRooms.clear(); final syncResp = await sync( filter: '{"room":{"include_leave":true,"timeline":{"limit":10}}}', diff --git a/test/client_test.dart b/test/client_test.dart index a8010055..52d9584b 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -351,7 +351,7 @@ void main() { }); test('get archive', () async { - final archive = await matrix.archive; + final archive = await matrix.loadArchive(); await Future.delayed(Duration(milliseconds: 50)); expect(archive.length, 2); From e1343e9c837fdf1f31b8ff5815c2f04291e21cb5 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 25 Aug 2021 10:37:01 +0200 Subject: [PATCH 4/6] fix: requestHistory() for archived rooms Using JoinedRoomUpdate() in a fake sync for archived rooms when requesting the history leads to the problem that the room is stored as a joined room in the store which is wrong. --- lib/src/room.dart | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/src/room.dart b/lib/src/room.dart index 2284f5de..cf03db34 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -959,12 +959,22 @@ class Room { await client.handleSync( SyncUpdate(nextBatch: '') ..rooms = (RoomsUpdate() - ..join = ({}..[id] = (JoinedRoomUpdate() - ..state = resp.state - ..timeline = (TimelineUpdate() - ..limited = false - ..events = resp.chunk - ..prevBatch = resp.end)))), + ..join = membership == Membership.join + ? ({}..[id] = ((JoinedRoomUpdate() + ..state = resp.state + ..timeline = (TimelineUpdate() + ..limited = false + ..events = resp.chunk + ..prevBatch = resp.end)))) + : null + ..leave = membership != Membership.join + ? ({}..[id] = ((LeftRoomUpdate() + ..state = resp.state + ..timeline = (TimelineUpdate() + ..limited = false + ..events = resp.chunk + ..prevBatch = resp.end)))) + : null), sortAtTheEnd: true); }; From 5621c9bdb050d4cca9d6fbe28b2938c561d03248 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 25 Aug 2021 11:06:26 +0200 Subject: [PATCH 5/6] fix: sortAtTheEnd for LeftRoomUpdate was not set This fixes a bug where requesting history on archived room leads to a wrong sorted timeline. --- lib/src/client.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/client.dart b/lib/src/client.dart index 8033f54c..68f76790 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -1305,7 +1305,8 @@ class Client extends MatrixApi { await _handleRoomEvents( id, room.timeline.events.map((i) => i.toJson()).toList(), - EventUpdateType.timeline); + EventUpdateType.timeline, + sortAtTheEnd: sortAtTheEnd); handledEvents = true; } if (room.accountData?.isNotEmpty ?? false) { From c80bf25ee4342fdc08cb3c87151032ab7542f742 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Wed, 25 Aug 2021 11:26:19 +0200 Subject: [PATCH 6/6] chore: Bump version --- CHANGELOG.md | 5 +++++ pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2841622e..98940d23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [0.3.2] - 20nd Aug 2021 +- feat: cache archived rooms to access them with `getRoomById` +- fix: requestHistory() for archived rooms +- refactor: Change name of archive getter to function + ## [0.3.1] - 20nd Aug 2021 - hotfix: Opt-out null safety for crypto files because of an error in web diff --git a/pubspec.yaml b/pubspec.yaml index f52b1fff..36fbbc7e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: matrix description: Matrix Dart SDK -version: 0.3.1 +version: 0.3.2 homepage: https://famedly.com environment: