Skip to content

Commit

Permalink
TW-1578: Refactor download attachments on web
Browse files Browse the repository at this point in the history
  • Loading branch information
nqhhdev authored and hoangdat committed Apr 8, 2024
1 parent 7729d10 commit 5429082
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 16 deletions.
38 changes: 22 additions & 16 deletions lib/utils/manager/download_manager/download_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,17 +137,22 @@ class DownloadManager {
required StreamController<Either<Failure, Success>> streamController,
required CancelToken cancelToken,
}) {
if (!PlatformInfos.isWeb) {
_addTaskToWorkerQueueNative(
event,
getThumbnail,
streamController,
cancelToken,
if (PlatformInfos.isWeb) {
_addTaskToWorkerQueueWeb(
event: event,
streamController: streamController,
getThumbnail: getThumbnail,
cancelToken: cancelToken,
);
return;
}

_addTaskToWorkerQueueWeb(event, streamController);
_addTaskToWorkerQueueNative(
event,
getThumbnail,
streamController,
cancelToken,
);
}

void _addTaskToWorkerQueueNative(
Expand Down Expand Up @@ -180,20 +185,21 @@ class DownloadManager {
);
}

void _addTaskToWorkerQueueWeb(
Event event,
StreamController<Either<Failure, Success>> streamController,
) {
void _addTaskToWorkerQueueWeb({
required Event event,
required StreamController<Either<Failure, Success>> streamController,
getThumbnail = false,
required CancelToken cancelToken,
}) {
workingQueue.addTask(
Task(
id: event.eventId,
runnable: () async {
try {
final matrixFile = await event.downloadAndDecryptAttachment();
streamController.add(
Right(
DownloadMatrixFileSuccessState(matrixFile: matrixFile),
),
await event.downloadAttachmentWeb(
getThumbnail: getThumbnail,
downloadStreamController: streamController,
cancelToken: cancelToken,
);
} catch (e) {
Logs().e('DownloadManager::download(): $e');
Expand Down
188 changes: 188 additions & 0 deletions lib/utils/matrix_sdk_extensions/download_file_extension.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
Expand Down Expand Up @@ -286,6 +287,193 @@ extension DownloadFileExtension on Event {
);
}

Future<MatrixFile?> downloadAttachmentWeb({
getThumbnail = false,
required StreamController<Either<Failure, Success>>
downloadStreamController,
CancelToken? cancelToken,
}) async {
if (!canContainAttachment()) {
throw (
"downloadAttachmentWeb: This event has the type '$type' and so it can't contain an attachment.",
);
}

if (isSending()) {
final localFile = room.sendingFilePlaceholders[eventId];
if (localFile != null) return localFile;
}

final mxcUrl = getAttachmentOrThumbnailMxcUrl(getThumbnail: getThumbnail);
if (mxcUrl == null) {
throw "downloadAttachmentWeb: This event hasn't any attachment or thumbnail.";
}

final isFileEncrypted =
getThumbnail ? isThumbnailEncrypted : isAttachmentEncrypted;
if (isEncryptionDisabled(isFileEncrypted)) {
throw (
'downloadAttachmentWeb: Encryption is not enabled in your Client.',
);
}

final storeable = isFileStoreable(getThumbnail: getThumbnail);

Uint8List? uint8list;

if (storeable) {
uint8list = await room.client.database?.getFile(mxcUrl);
}

if (uint8list != null) {
return MatrixFile(
bytes: await _decryptAttachmentWeb(uint8list: uint8list),
name: body,
);
}

return await _handleDownloadAttachmentWeb(
mxcUrl: mxcUrl,
downloadStreamController: downloadStreamController,
getThumbnail: getThumbnail,
cancelToken: cancelToken,
storeable: storeable,
);
}

Future<MatrixFile?> _handleDownloadAttachmentWeb({
required Uri mxcUrl,
required StreamController<Either<Failure, Success>>
downloadStreamController,
bool getThumbnail = false,
CancelToken? cancelToken,
bool storeable = true,
}) async {
try {
final database = room.client.database;
final mediaAPI = getIt<MediaAPI>();
final downloadLink = mxcUrl.getDownloadLink(room.client);
final uint8List = await mediaAPI.downloadAttachmentWeb(
uri: downloadLink,
onReceiveProgress: (receive, total) {
downloadStreamController.add(
Right(
DownloadingFileState(
receive: receive,
total: total,
),
),
);
},
cancelToken: cancelToken,
);
if (database != null &&
storeable &&
uint8List.lengthInBytes < database.maxFileSize) {
await database.storeFile(
mxcUrl,
uint8List,
DateTime.now().millisecondsSinceEpoch,
);
}

await _handleDownloadAttachmentWebSuccess(
uint8List,
downloadStreamController,
);
return MatrixFile(name: body);
} catch (e) {
if (e is CancelRequestException) {
Logs().i("_handleDownloadAttachmentWeb: user cancel the download");
}
Logs().e("_handleDownloadAttachmentWeb: $e");
}
return null;
}

Future<void> _handleDownloadAttachmentWebSuccess(
Uint8List uint8list,
StreamController<Either<Failure, Success>> streamController,
) async {
if (isAttachmentEncrypted) {
await _handleDecryptedAttachmentWeb(
streamController: streamController,
uint8list: uint8list,
);
} else {
streamController.add(
Right(
DownloadMatrixFileSuccessState(
matrixFile: MatrixFile(bytes: uint8list, name: body),
),
),
);
}
return;
}

Future<void> _handleDecryptedAttachmentWeb({
required StreamController<Either<Failure, Success>> streamController,
required Uint8List uint8list,
bool getThumbnail = false,
}) async {
streamController.add(
const Right(
DecryptingFileState(),
),
);
try {
final decryptedFile = await _decryptAttachmentWeb(
uint8list: uint8list,
getThumbnail: getThumbnail,
);
if (decryptedFile == null) {
throw Exception(
'_handleDownloadAttachmentWeb:: decryptedFile is null',
);
}
streamController.add(
Right(
DownloadMatrixFileSuccessState(
matrixFile: MatrixFile(bytes: decryptedFile, name: body),
),
),
);
} catch (e) {
Logs().e(
'_handleDownloadAttachmentWeb:: $e',
);
streamController.add(
Left(
DownloadFileFailureState(exception: e),
),
);
}
}

Future<Uint8List?> _decryptAttachmentWeb({
required Uint8List uint8list,
bool getThumbnail = false,
}) async {
final fileMap = getThumbnail ? infoMap['thumbnail_file'] : content['file'];
if (!fileMap['key']['key_ops'].contains('decrypt')) {
throw ("Missing 'decrypt' in 'key_ops'.");
}
final encryptedFile = EncryptedFile(
data: uint8list,
iv: fileMap['iv'],
k: fileMap['key']['k'],
sha256: fileMap['hashes']['sha256'],
);
final decryptAttachment =
await room.client.nativeImplementations.decryptFile(encryptedFile);
if (decryptAttachment == null) {
throw ('Unable to decrypt file');
}

return decryptAttachment;
}

Future<FileInfo?> getMediaFileInfo({
getThumbnail = false,
ProgressCallback? progressCallback,
Expand Down

0 comments on commit 5429082

Please sign in to comment.