sendFileEvent method
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.
Optionally specify extraContent to tack on to the event.
In case file is a MatrixImageFile or a MatrixVideoFile (with
Client.customVideoThumbnailGenerator set), thumbnail is automatically
computed unless it is explicitly provided.
Set shrinkImageMaxDimension to for example 1600 if you want to shrink
your image before sending. This is ignored if the File is not a
MatrixImageFile.
Implementation
Future<String?> sendFileEvent(
MatrixFile file, {
String? txid,
Event? inReplyTo,
String? editEventId,
int? shrinkImageMaxDimension,
MatrixImageFile? thumbnail,
Map<String, dynamic>? extraContent,
String? threadRootEventId,
String? threadLastEventId,
/// Displays an event in the timeline with the transaction ID as the event
/// ID and a status of SENDING, SENT or ERROR until it gets replaced by
/// the sync event. Using this can display a different sort order of events
/// as the sync event does replace but not relocate the pending event.
bool displayPendingEvent = true,
}) async {
txid ??= client.generateUniqueTransactionId();
await client.database.storeFile(
Uri(scheme: 'cache', host: 'file', path: txid),
file.bytes,
DateTime.now().millisecondsSinceEpoch,
);
if (thumbnail != null) {
await client.database.storeFile(
Uri(scheme: 'cache', host: 'thumbnail', path: txid),
thumbnail.bytes,
DateTime.now().millisecondsSinceEpoch,
);
}
// Create a fake Event object as a placeholder for the uploading file:
final syncUpdate = SyncUpdate(
nextBatch: '',
rooms: RoomsUpdate(
join: {
id: JoinedRoomUpdate(
timeline: TimelineUpdate(
events: [
MatrixEvent(
content: {
'msgtype': file.msgType,
'body': file.name,
'filename': file.name,
'info': file.info,
...?extraContent,
},
type: EventTypes.Message,
eventId: txid,
senderId: client.userID!,
originServerTs: DateTime.now(),
unsigned: {
messageSendingStatusKey: EventStatus.sending.intValue,
'transaction_id': txid,
...FileSendRequestCredentials(
inReplyTo: inReplyTo?.eventId,
editEventId: editEventId,
shrinkImageMaxDimension: shrinkImageMaxDimension,
extraContent: extraContent,
).toJson(),
},
),
],
),
),
},
),
);
await _handleFakeSync(syncUpdate);
MatrixFile uploadFile = file; // ignore: omit_local_variable_types
// computing the thumbnail in case we can
if (file is MatrixImageFile &&
(thumbnail == null || shrinkImageMaxDimension != null)) {
syncUpdate
.rooms!
.join!
.values
.first
.timeline!
.events!
.first
.unsigned![fileSendingStatusKey] =
FileSendingStatus.generatingThumbnail.name;
try {
thumbnail ??= await file.generateThumbnail(
nativeImplementations: client.nativeImplementations,
customImageResizer: client.customImageResizer,
);
if (shrinkImageMaxDimension != null) {
file = await MatrixImageFile.shrink(
bytes: file.bytes,
name: file.name,
maxDimension: shrinkImageMaxDimension,
customImageResizer: client.customImageResizer,
nativeImplementations: client.nativeImplementations,
);
}
if (thumbnail != null && file.size < thumbnail.size) {
thumbnail = null; // in this case, the thumbnail is not usefull
}
} catch (e, s) {
Logs().e('Unable to shrink image before sending!', e, s);
}
}
final customVideoThumbnailGenerator = client.customVideoThumbnailGenerator;
if (file is MatrixVideoFile &&
thumbnail == null &&
customVideoThumbnailGenerator != null) {
syncUpdate
.rooms!
.join!
.values
.first
.timeline!
.events!
.first
.unsigned![fileSendingStatusKey] =
FileSendingStatus.generatingThumbnail.name;
try {
final videoThumbnail = await customVideoThumbnailGenerator(
MatrixVideoThumbnailArguments(
bytes: file.bytes,
fileName: file.name,
mimeType: file.mimeType,
),
);
if (videoThumbnail != null) {
final thumbnailMimeType = videoThumbnail.mimeType;
// Only append a file extension if the mimetype is actually known,
// instead of guessing one:
final thumbnailExtension = thumbnailMimeType == null
? null
: extensionFromMime(thumbnailMimeType);
thumbnail = MatrixImageFile(
bytes: videoThumbnail.bytes,
name:
'${file.name}.thumbnail${thumbnailExtension == null ? '' : '.$thumbnailExtension'}',
mimeType: thumbnailMimeType,
width: videoThumbnail.width,
height: videoThumbnail.height,
blurhash: videoThumbnail.blurhash,
);
await client.database.storeFile(
Uri(scheme: 'cache', host: 'thumbnail', path: txid),
thumbnail.bytes,
DateTime.now().millisecondsSinceEpoch,
);
file = MatrixVideoFile(
bytes: file.bytes,
name: file.name,
mimeType: file.mimeType,
width: file.width ?? videoThumbnail.originalWidth,
height: file.height ?? videoThumbnail.originalHeight,
duration: file.duration ?? videoThumbnail.duration,
);
syncUpdate
.rooms!
.join!
.values
.first
.timeline!
.events!
.first
.content['info'] = {
...file.info,
'thumbnail_info': thumbnail.info,
};
}
} catch (e, s) {
Logs().e('Unable to generate video thumbnail before sending!', e, s);
}
}
// Check media config of the server before sending the file. Stop if the
// Media config is unreachable or the file is bigger than the given maxsize.
try {
final mediaConfig = await client.getConfig();
final maxMediaSize = mediaConfig.mUploadSize;
if (maxMediaSize != null && maxMediaSize < file.bytes.lengthInBytes) {
throw FileTooBigMatrixException(file.bytes.lengthInBytes, maxMediaSize);
}
} catch (e) {
Logs().d('Config error while sending file', e);
syncUpdate
.rooms!
.join!
.values
.first
.timeline!
.events!
.first
.unsigned![messageSendingStatusKey] =
EventStatus.error.intValue;
await _handleFakeSync(syncUpdate);
rethrow;
}
MatrixFile? uploadThumbnail =
thumbnail; // ignore: omit_local_variable_types
EncryptedFile? encryptedFile;
EncryptedFile? encryptedThumbnail;
if (encrypted && client.fileEncryptionEnabled) {
syncUpdate
.rooms!
.join!
.values
.first
.timeline!
.events!
.first
.unsigned![fileSendingStatusKey] =
FileSendingStatus.encrypting.name;
await _handleFakeSync(syncUpdate);
encryptedFile = await file.encrypt();
uploadFile = encryptedFile.toMatrixFile();
if (thumbnail != null) {
encryptedThumbnail = await thumbnail.encrypt();
uploadThumbnail = encryptedThumbnail.toMatrixFile();
}
}
Uri? uploadResp, thumbnailUploadResp;
final timeoutDate = DateTime.now().add(client.sendTimelineEventTimeout);
syncUpdate
.rooms!
.join!
.values
.first
.timeline!
.events!
.first
.unsigned![fileSendingStatusKey] =
FileSendingStatus.uploading.name;
while (uploadResp == null ||
(uploadThumbnail != null && thumbnailUploadResp == null)) {
try {
uploadResp = await client.uploadContent(
uploadFile.bytes,
filename: uploadFile.name,
contentType: uploadFile.mimeType,
);
thumbnailUploadResp = uploadThumbnail != null
? await client.uploadContent(
uploadThumbnail.bytes,
filename: uploadThumbnail.name,
contentType: uploadThumbnail.mimeType,
)
: null;
} on MatrixException catch (_) {
syncUpdate
.rooms!
.join!
.values
.first
.timeline!
.events!
.first
.unsigned![messageSendingStatusKey] =
EventStatus.error.intValue;
await _handleFakeSync(syncUpdate);
rethrow;
} catch (_) {
if (DateTime.now().isAfter(timeoutDate)) {
syncUpdate
.rooms!
.join!
.values
.first
.timeline!
.events!
.first
.unsigned![messageSendingStatusKey] =
EventStatus.error.intValue;
await _handleFakeSync(syncUpdate);
rethrow;
}
Logs().v('Send File into room failed. Try again...');
await Future.delayed(Duration(seconds: 1));
}
}
// Send event
final content = <String, dynamic>{
'msgtype': file.msgType,
'body': file.name,
'filename': file.name,
if (encryptedFile == null) 'url': uploadResp.toString(),
if (encryptedFile != null)
'file': {
'url': uploadResp.toString(),
'mimetype': file.mimeType,
'v': 'v2',
'key': {
'alg': 'A256CTR',
'ext': true,
'k': encryptedFile.k,
'key_ops': ['encrypt', 'decrypt'],
'kty': 'oct',
},
'iv': encryptedFile.iv,
'hashes': {'sha256': encryptedFile.sha256},
},
'info': {
...file.info,
if (thumbnail != null && encryptedThumbnail == null)
'thumbnail_url': thumbnailUploadResp.toString(),
if (thumbnail != null && encryptedThumbnail != null)
'thumbnail_file': {
'url': thumbnailUploadResp.toString(),
'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,
if (thumbnail?.blurhash != null &&
((file is MatrixImageFile && file.blurhash == null) ||
file is MatrixVideoFile))
'xyz.amorgan.blurhash': thumbnail!.blurhash,
},
...?extraContent,
};
final eventId = await sendEvent(
content,
txid: txid,
inReplyTo: inReplyTo,
editEventId: editEventId,
threadRootEventId: threadRootEventId,
threadLastEventId: threadLastEventId,
displayPendingEvent: displayPendingEvent,
);
await client.database.deleteFile(
Uri(scheme: 'cache', host: 'file', path: txid),
);
if (thumbnail != null) {
await client.database.deleteFile(
Uri(scheme: 'cache', host: 'thumbnail', path: txid),
);
}
return eventId;
}