Merge branch 'matrixfile-refactor-all' into 'master'

MatrixFile refactoring for thumbnails

See merge request famedly/famedlysdk!371
This commit is contained in:
Christian Pauly 2020-06-29 12:00:26 +00:00
commit 0ac7aec071
9 changed files with 141 additions and 184 deletions

View file

@ -449,7 +449,7 @@ class Client {
/// Uploads a new user avatar for this user. /// Uploads a new user avatar for this user.
Future<void> setAvatar(MatrixFile file) async { Future<void> setAvatar(MatrixFile file) async {
final uploadResp = await api.upload(file.bytes, file.path); final uploadResp = await api.upload(file.bytes, file.name);
await api.setAvatarUrl(userID, Uri.parse(uploadResp)); await api.setAvatarUrl(userID, Uri.parse(uploadResp));
return; return;
} }

View file

@ -424,7 +424,7 @@ class Event extends MatrixEvent {
encryptedFile.sha256 = fileMap['hashes']['sha256']; encryptedFile.sha256 = fileMap['hashes']['sha256'];
uint8list = await decryptFile(encryptedFile); uint8list = await decryptFile(encryptedFile);
} }
return MatrixFile(bytes: uint8list, path: '/$body'); return MatrixFile(bytes: uint8list, name: body);
} }
/// Returns a localized String representation of this event. For a /// Returns a localized String representation of this event. For a

View file

@ -25,9 +25,7 @@ import 'package:famedlysdk/src/event.dart';
import 'package:famedlysdk/src/utils/event_update.dart'; import 'package:famedlysdk/src/utils/event_update.dart';
import 'package:famedlysdk/src/utils/room_update.dart'; import 'package:famedlysdk/src/utils/room_update.dart';
import 'package:famedlysdk/src/utils/matrix_file.dart'; import 'package:famedlysdk/src/utils/matrix_file.dart';
import 'package:image/image.dart';
import 'package:matrix_file_e2ee/matrix_file_e2ee.dart'; import 'package:matrix_file_e2ee/matrix_file_e2ee.dart';
import 'package:mime/mime.dart';
import 'package:html_unescape/html_unescape.dart'; import 'package:html_unescape/html_unescape.dart';
import './user.dart'; import './user.dart';
@ -504,79 +502,53 @@ class Room {
return sendEvent(event, txid: txid, inReplyTo: inReplyTo); return sendEvent(event, txid: txid, inReplyTo: inReplyTo);
} }
/// Sends a [file] to this room after uploading it. The [msgType] is optional /// Sends a [file] to this room after uploading it. Returns the mxc uri of
/// and will be detected by the mimetype of the file. Returns the mxc uri of
/// the uploaded file. If [waitUntilSent] is true, the future will wait until /// the uploaded file. If [waitUntilSent] is true, the future will wait until
/// the message event has received the server. Otherwise the future will only /// the message event has received the server. Otherwise the future will only
/// wait until the file has been uploaded. /// wait until the file has been uploaded.
Future<String> sendFileEvent( Future<String> sendFileEvent(
MatrixFile file, { MatrixFile file, {
String msgType,
String txid, String txid,
Event inReplyTo, Event inReplyTo,
Map<String, dynamic> info,
bool waitUntilSent = false, bool waitUntilSent = false,
MatrixFile thumbnail, MatrixImageFile thumbnail,
}) async { }) async {
Image fileImage; MatrixFile uploadFile = file; // ignore: omit_local_variable_types
Image thumbnailImage; MatrixFile uploadThumbnail = thumbnail; // ignore: omit_local_variable_types
EncryptedFile encryptedThumbnail;
String thumbnailUploadResp;
var fileName = file.path.split('/').last;
final mimeType = lookupMimeType(file.path, headerBytes: file.bytes) ?? '';
if (msgType == null) {
final metaType = (mimeType).split('/')[0];
switch (metaType) {
case 'image':
case 'audio':
case 'video':
msgType = 'm.$metaType';
break;
default:
msgType = 'm.file';
break;
}
}
if (msgType == 'm.image') {
fileImage = decodeImage(file.bytes.toList());
if (thumbnail != null) {
thumbnailImage = decodeImage(thumbnail.bytes.toList());
}
}
final sendEncrypted = encrypted && client.fileEncryptionEnabled;
EncryptedFile encryptedFile; EncryptedFile encryptedFile;
if (sendEncrypted) { EncryptedFile encryptedThumbnail;
if (encrypted && client.fileEncryptionEnabled) {
encryptedFile = await file.encrypt(); encryptedFile = await file.encrypt();
uploadFile = encryptedFile.toMatrixFile();
if (thumbnail != null) { if (thumbnail != null) {
encryptedThumbnail = await thumbnail.encrypt(); encryptedThumbnail = await thumbnail.encrypt();
uploadThumbnail = encryptedThumbnail.toMatrixFile();
} }
} }
final uploadResp = await client.api.upload( final uploadResp = await client.api.upload(
file.bytes, uploadFile.bytes,
file.path, uploadFile.name,
contentType: sendEncrypted ? 'application/octet-stream' : null, contentType: uploadFile.mimeType,
); );
if (thumbnail != null) { final thumbnailUploadResp = uploadThumbnail != null
thumbnailUploadResp = await client.api.upload( ? await client.api.upload(
thumbnail.bytes, uploadThumbnail.bytes,
thumbnail.path, uploadThumbnail.name,
contentType: sendEncrypted ? 'application/octet-stream' : null, contentType: uploadThumbnail.mimeType,
); )
} : null;
// Send event // Send event
var content = <String, dynamic>{ var content = <String, dynamic>{
'msgtype': msgType, 'msgtype': file.msgType,
'body': fileName, 'body': file.name,
'filename': fileName, 'filename': file.name,
if (!sendEncrypted) 'url': uploadResp, if (encryptedFile == null) 'url': uploadResp,
if (sendEncrypted) if (encryptedFile != null)
'file': { 'file': {
'url': uploadResp, 'url': uploadResp,
'mimetype': mimeType, 'mimetype': file.mimeType,
'v': 'v2', 'v': 'v2',
'key': { 'key': {
'alg': 'A256CTR', 'alg': 'A256CTR',
@ -588,37 +560,27 @@ class Room {
'iv': encryptedFile.iv, 'iv': encryptedFile.iv,
'hashes': {'sha256': encryptedFile.sha256} 'hashes': {'sha256': encryptedFile.sha256}
}, },
'info': info ?? 'info': {
{ ...file.info,
'mimetype': mimeType, if (thumbnail != null && encryptedThumbnail == null)
'size': file.size, 'thumbnail_url': thumbnailUploadResp,
if (fileImage != null) 'h': fileImage.height, if (thumbnail != null && encryptedThumbnail != null)
if (fileImage != null) 'w': fileImage.width, 'thumbnail_file': {
if (thumbnailUploadResp != null && !sendEncrypted) 'url': thumbnailUploadResp,
'thumbnail_url': thumbnailUploadResp, 'mimetype': thumbnail.mimeType,
if (thumbnailUploadResp != null && sendEncrypted) 'v': 'v2',
'thumbnail_file': { 'key': {
'url': thumbnailUploadResp, 'alg': 'A256CTR',
'mimetype': mimeType, 'ext': true,
'v': 'v2', 'k': encryptedThumbnail.k,
'key': { 'key_ops': ['encrypt', 'decrypt'],
'alg': 'A256CTR', 'kty': 'oct'
'ext': true, },
'k': encryptedThumbnail.k, 'iv': encryptedThumbnail.iv,
'key_ops': ['encrypt', 'decrypt'], 'hashes': {'sha256': encryptedThumbnail.sha256}
'kty': 'oct' },
}, if (thumbnail != null) 'thumbnail_info': thumbnail.info,
'iv': encryptedThumbnail.iv, }
'hashes': {'sha256': encryptedThumbnail.sha256}
},
if (thumbnailImage != null)
'thumbnail_info': {
'h': thumbnailImage.height,
'mimetype': mimeType,
'size': thumbnail.size,
'w': thumbnailImage.width,
}
}
}; };
final sendResponse = sendEvent( final sendResponse = sendEvent(
content, content,
@ -631,79 +593,6 @@ class Room {
return uploadResp; return uploadResp;
} }
/// Sends an audio file to this room and returns the mxc uri.
Future<String> sendAudioEvent(MatrixFile file,
{String txid, Event inReplyTo}) async {
return await sendFileEvent(file,
msgType: 'm.audio', txid: txid, inReplyTo: inReplyTo);
}
/// Sends an image to this room and returns the mxc uri.
Future<String> sendImageEvent(MatrixFile file,
{String txid, int width, int height, Event inReplyTo}) async {
return await sendFileEvent(file,
msgType: 'm.image',
txid: txid,
inReplyTo: inReplyTo,
info: {
'size': file.size,
'mimetype': lookupMimeType(file.path, headerBytes: file.bytes),
'w': width,
'h': height,
});
}
/// Sends an video to this room and returns the mxc uri.
Future<String> sendVideoEvent(MatrixFile file,
{String txid,
int videoWidth,
int videoHeight,
int duration,
MatrixFile thumbnail,
int thumbnailWidth,
int thumbnailHeight,
Event inReplyTo}) async {
var info = <String, dynamic>{
'size': file.size,
'mimetype': lookupMimeType(file.path, headerBytes: file.bytes),
};
if (videoWidth != null) {
info['w'] = videoWidth;
}
if (thumbnailHeight != null) {
info['h'] = thumbnailHeight;
}
if (duration != null) {
info['duration'] = duration;
}
if (thumbnail != null && !(encrypted && client.encryptionEnabled)) {
final thumbnailUploadResp = await client.api.upload(
thumbnail.bytes,
thumbnail.path,
);
info['thumbnail_url'] = thumbnailUploadResp;
info['thumbnail_info'] = {
'size': thumbnail.size,
'mimetype':
lookupMimeType(thumbnail.path, headerBytes: thumbnail.bytes),
};
if (thumbnailWidth != null) {
info['thumbnail_info']['w'] = thumbnailWidth;
}
if (thumbnailHeight != null) {
info['thumbnail_info']['h'] = thumbnailHeight;
}
}
return await sendFileEvent(
file,
msgType: 'm.video',
txid: txid,
inReplyTo: inReplyTo,
info: info,
);
}
/// Sends an event to this room with this json as a content. Returns the /// Sends an event to this room with this json as a content. Returns the
/// event ID generated from the server. /// event ID generated from the server.
Future<String> sendEvent(Map<String, dynamic> content, Future<String> sendEvent(Map<String, dynamic> content,
@ -1236,7 +1125,7 @@ class Room {
/// Uploads a new user avatar for this room. Returns the event ID of the new /// Uploads a new user avatar for this room. Returns the event ID of the new
/// m.room.avatar event. /// m.room.avatar event.
Future<String> setAvatar(MatrixFile file) async { Future<String> setAvatar(MatrixFile file) async {
final uploadResp = await client.api.upload(file.bytes, file.path); final uploadResp = await client.api.upload(file.bytes, file.name);
return await client.api.sendState( return await client.api.sendState(
id, id,
EventTypes.RoomAvatar, EventTypes.RoomAvatar,

View file

@ -2,20 +2,100 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:matrix_file_e2ee/matrix_file_e2ee.dart'; import 'package:matrix_file_e2ee/matrix_file_e2ee.dart';
import 'package:mime/mime.dart';
class MatrixFile { class MatrixFile {
Uint8List bytes; Uint8List bytes;
String path; String name;
String mimeType;
/// Encrypts this file, changes the [bytes] and returns the /// Encrypts this file and returns the
/// encryption information as an [EncryptedFile]. /// encryption information as an [EncryptedFile].
Future<EncryptedFile> encrypt() async { Future<EncryptedFile> encrypt() async {
var encryptFile2 = encryptFile(bytes); return await encryptFile(bytes);
final encryptedFile = await encryptFile2; }
bytes = encryptedFile.data;
return encryptedFile; MatrixFile({this.bytes, this.name, this.mimeType}) {
mimeType ??= lookupMimeType(name, headerBytes: bytes);
name = name.split('/').last.toLowerCase();
} }
MatrixFile({this.bytes, String path}) : path = path.toLowerCase();
int get size => bytes.length; int get size => bytes.length;
String get msgType => 'm.file';
Map<String, dynamic> get info => ({
'mimetype': mimeType,
'size': size,
});
}
class MatrixImageFile extends MatrixFile {
int width;
int height;
String blurhash;
MatrixImageFile(
{Uint8List bytes,
String name,
String mimeType,
this.width,
this.height,
this.blurhash})
: super(bytes: bytes, name: name, mimeType: mimeType);
@override
String get msgType => 'm.image';
@override
Map<String, dynamic> get info => ({
...super.info,
if (width != null) 'w': width,
if (height != null) 'h': height,
if (blurhash != null) 'xyz.amorgan.blurhash': blurhash,
});
}
class MatrixVideoFile extends MatrixFile {
int width;
int height;
int duration;
MatrixVideoFile(
{Uint8List bytes,
String name,
String mimeType,
this.width,
this.height,
this.duration})
: super(bytes: bytes, name: name, mimeType: mimeType);
@override
String get msgType => 'm.video';
@override
Map<String, dynamic> get info => ({
...super.info,
if (width != null) 'w': width,
if (height != null) 'h': height,
if (duration != null) 'duration': duration,
});
}
class MatrixAudioFile extends MatrixFile {
int duration;
MatrixAudioFile(
{Uint8List bytes, String name, String mimeType, this.duration})
: super(bytes: bytes, name: name, mimeType: mimeType);
@override
String get msgType => 'm.audio';
@override
Map<String, dynamic> get info => ({
...super.info,
if (duration != null) 'duration': duration,
});
}
extension ToMatrixFile on EncryptedFile {
MatrixFile toMatrixFile() {
return MatrixFile(
bytes: data, name: 'crypt', mimeType: 'application/octet-stream');
}
} }

View file

@ -274,13 +274,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.4" version: "3.1.4"
image:
dependency: "direct main"
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.12"
io: io:
dependency: transitive dependency: transitive
description: description:

View file

@ -11,7 +11,6 @@ dependencies:
http: ^0.12.1 http: ^0.12.1
mime: ^0.9.6 mime: ^0.9.6
canonical_json: ^1.0.0 canonical_json: ^1.0.0
image: ^2.1.4
markdown: ^2.1.3 markdown: ^2.1.3
html_unescape: ^1.0.1+3 html_unescape: ^1.0.1+3
moor: ^3.0.2 moor: ^3.0.2

View file

@ -343,8 +343,7 @@ void main() {
}); });
test('setAvatar', () async { test('setAvatar', () async {
final testFile = final testFile = MatrixFile(bytes: Uint8List(0), name: 'file.jpeg');
MatrixFile(bytes: Uint8List(0), path: 'fake/path/file.jpeg');
await matrix.setAvatar(testFile); await matrix.setAvatar(testFile);
}); });

View file

@ -28,7 +28,7 @@ void main() {
test('Decrypt', () async { test('Decrypt', () async {
final text = 'hello world'; final text = 'hello world';
final file = MatrixFile( final file = MatrixFile(
path: '/path/to/file.txt', name: 'file.txt',
bytes: Uint8List.fromList(text.codeUnits), bytes: Uint8List.fromList(text.codeUnits),
); );
var olmEnabled = true; var olmEnabled = true;

View file

@ -333,8 +333,7 @@ void main() {
}); });
test('setAvatar', () async { test('setAvatar', () async {
final testFile = final testFile = MatrixFile(bytes: Uint8List(0), name: 'file.jpeg');
MatrixFile(bytes: Uint8List(0), path: 'fake/path/file.jpeg');
final dynamic resp = await room.setAvatar(testFile); final dynamic resp = await room.setAvatar(testFile);
expect(resp, 'YUwRidLecu:example.com'); expect(resp, 'YUwRidLecu:example.com');
}); });
@ -361,10 +360,8 @@ void main() {
});*/ });*/
test('sendFileEvent', () async { test('sendFileEvent', () async {
final testFile = final testFile = MatrixFile(bytes: Uint8List(0), name: 'file.jpeg');
MatrixFile(bytes: Uint8List(0), path: 'fake/path/file.jpeg'); final dynamic resp = await room.sendFileEvent(testFile, txid: 'testtxid');
final dynamic resp = await room.sendFileEvent(testFile,
msgType: 'm.file', txid: 'testtxid');
expect(resp, 'mxc://example.com/AQwafuaFswefuhsfAFAgsw'); expect(resp, 'mxc://example.com/AQwafuaFswefuhsfAFAgsw');
}); });