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.
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));
return;
}

View file

@ -424,7 +424,7 @@ class Event extends MatrixEvent {
encryptedFile.sha256 = fileMap['hashes']['sha256'];
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

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/room_update.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:mime/mime.dart';
import 'package:html_unescape/html_unescape.dart';
import './user.dart';
@ -504,79 +502,53 @@ class Room {
return sendEvent(event, txid: txid, inReplyTo: inReplyTo);
}
/// Sends a [file] to this room after uploading it. The [msgType] is optional
/// and will be detected by the mimetype of the file. Returns the mxc uri of
/// Sends a [file] to this room after uploading it. Returns the mxc uri of
/// the uploaded file. If [waitUntilSent] is true, the future will wait until
/// the message event has received the server. Otherwise the future will only
/// wait until the file has been uploaded.
Future<String> sendFileEvent(
MatrixFile file, {
String msgType,
String txid,
Event inReplyTo,
Map<String, dynamic> info,
bool waitUntilSent = false,
MatrixFile thumbnail,
MatrixImageFile thumbnail,
}) async {
Image fileImage;
Image thumbnailImage;
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;
MatrixFile uploadFile = file; // ignore: omit_local_variable_types
MatrixFile uploadThumbnail = thumbnail; // ignore: omit_local_variable_types
EncryptedFile encryptedFile;
if (sendEncrypted) {
EncryptedFile encryptedThumbnail;
if (encrypted && client.fileEncryptionEnabled) {
encryptedFile = await file.encrypt();
uploadFile = encryptedFile.toMatrixFile();
if (thumbnail != null) {
encryptedThumbnail = await thumbnail.encrypt();
uploadThumbnail = encryptedThumbnail.toMatrixFile();
}
}
final uploadResp = await client.api.upload(
file.bytes,
file.path,
contentType: sendEncrypted ? 'application/octet-stream' : null,
uploadFile.bytes,
uploadFile.name,
contentType: uploadFile.mimeType,
);
if (thumbnail != null) {
thumbnailUploadResp = await client.api.upload(
thumbnail.bytes,
thumbnail.path,
contentType: sendEncrypted ? 'application/octet-stream' : null,
);
}
final thumbnailUploadResp = uploadThumbnail != null
? await client.api.upload(
uploadThumbnail.bytes,
uploadThumbnail.name,
contentType: uploadThumbnail.mimeType,
)
: null;
// Send event
var content = <String, dynamic>{
'msgtype': msgType,
'body': fileName,
'filename': fileName,
if (!sendEncrypted) 'url': uploadResp,
if (sendEncrypted)
'msgtype': file.msgType,
'body': file.name,
'filename': file.name,
if (encryptedFile == null) 'url': uploadResp,
if (encryptedFile != null)
'file': {
'url': uploadResp,
'mimetype': mimeType,
'mimetype': file.mimeType,
'v': 'v2',
'key': {
'alg': 'A256CTR',
@ -588,37 +560,27 @@ class Room {
'iv': encryptedFile.iv,
'hashes': {'sha256': encryptedFile.sha256}
},
'info': info ??
{
'mimetype': mimeType,
'size': file.size,
if (fileImage != null) 'h': fileImage.height,
if (fileImage != null) 'w': fileImage.width,
if (thumbnailUploadResp != null && !sendEncrypted)
'thumbnail_url': thumbnailUploadResp,
if (thumbnailUploadResp != null && sendEncrypted)
'thumbnail_file': {
'url': thumbnailUploadResp,
'mimetype': mimeType,
'v': 'v2',
'key': {
'alg': 'A256CTR',
'ext': true,
'k': encryptedThumbnail.k,
'key_ops': ['encrypt', 'decrypt'],
'kty': 'oct'
},
'iv': encryptedThumbnail.iv,
'hashes': {'sha256': encryptedThumbnail.sha256}
},
if (thumbnailImage != null)
'thumbnail_info': {
'h': thumbnailImage.height,
'mimetype': mimeType,
'size': thumbnail.size,
'w': thumbnailImage.width,
}
}
'info': {
...file.info,
if (thumbnail != null && encryptedThumbnail == null)
'thumbnail_url': thumbnailUploadResp,
if (thumbnail != null && encryptedThumbnail != null)
'thumbnail_file': {
'url': thumbnailUploadResp,
'mimetype': thumbnail.mimeType,
'v': 'v2',
'key': {
'alg': 'A256CTR',
'ext': true,
'k': encryptedThumbnail.k,
'key_ops': ['encrypt', 'decrypt'],
'kty': 'oct'
},
'iv': encryptedThumbnail.iv,
'hashes': {'sha256': encryptedThumbnail.sha256}
},
if (thumbnail != null) 'thumbnail_info': thumbnail.info,
}
};
final sendResponse = sendEvent(
content,
@ -631,79 +593,6 @@ class Room {
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
/// event ID generated from the server.
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
/// m.room.avatar event.
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(
id,
EventTypes.RoomAvatar,

View file

@ -2,20 +2,100 @@
import 'dart:typed_data';
import 'package:matrix_file_e2ee/matrix_file_e2ee.dart';
import 'package:mime/mime.dart';
class MatrixFile {
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].
Future<EncryptedFile> encrypt() async {
var encryptFile2 = encryptFile(bytes);
final encryptedFile = await encryptFile2;
bytes = encryptedFile.data;
return encryptedFile;
return await encryptFile(bytes);
}
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;
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"
source: hosted
version: "3.1.4"
image:
dependency: "direct main"
description:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.12"
io:
dependency: transitive
description:

View file

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

View file

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

View file

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

View file

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