diff --git a/lib/data/network/media/media_api.dart b/lib/data/network/media/media_api.dart index 33b8279017..66fe80d0ed 100644 --- a/lib/data/network/media/media_api.dart +++ b/lib/data/network/media/media_api.dart @@ -73,7 +73,7 @@ class MediaAPI { ); } - Future downloadAttachmentWeb({ + Future downloadFileWeb({ required Uri uri, CancelToken? cancelToken, ProgressCallback? onReceiveProgress, diff --git a/lib/utils/exception/download_file_web_exception.dart b/lib/utils/exception/download_file_web_exception.dart new file mode 100644 index 0000000000..849ab0859f --- /dev/null +++ b/lib/utils/exception/download_file_web_exception.dart @@ -0,0 +1,10 @@ +class DownloadFileWebException implements Exception { + final dynamic error; + + DownloadFileWebException({ + this.error, + }); + + @override + String toString() => error; +} diff --git a/lib/utils/manager/download_manager/download_manager.dart b/lib/utils/manager/download_manager/download_manager.dart index 2530bf2728..26205d2c5e 100644 --- a/lib/utils/manager/download_manager/download_manager.dart +++ b/lib/utils/manager/download_manager/download_manager.dart @@ -10,6 +10,7 @@ import 'package:fluffychat/utils/manager/download_manager/download_file_info.dar import 'package:fluffychat/utils/manager/download_manager/download_file_state.dart'; import 'package:fluffychat/utils/manager/download_manager/downloading_worker_queue.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/download_file_extension.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/download_file_web_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/task_queue/task.dart'; import 'package:matrix/matrix.dart'; @@ -141,7 +142,6 @@ class DownloadManager { _addTaskToWorkerQueueWeb( event: event, streamController: streamController, - getThumbnail: getThumbnail, cancelToken: cancelToken, ); return; @@ -188,7 +188,6 @@ class DownloadManager { void _addTaskToWorkerQueueWeb({ required Event event, required StreamController> streamController, - getThumbnail = false, required CancelToken cancelToken, }) { workingQueue.addTask( @@ -196,8 +195,7 @@ class DownloadManager { id: event.eventId, runnable: () async { try { - await event.downloadAttachmentWeb( - getThumbnail: getThumbnail, + await event.downloadFileWeb( downloadStreamController: streamController, cancelToken: cancelToken, ); diff --git a/lib/utils/matrix_sdk_extensions/download_file_extension.dart b/lib/utils/matrix_sdk_extensions/download_file_extension.dart index 7f21682314..ae96fb11a6 100644 --- a/lib/utils/matrix_sdk_extensions/download_file_extension.dart +++ b/lib/utils/matrix_sdk_extensions/download_file_extension.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:io'; -import 'dart:typed_data'; import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; @@ -287,193 +286,6 @@ extension DownloadFileExtension on Event { ); } - Future downloadAttachmentWeb({ - getThumbnail = false, - required StreamController> - 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 _handleDownloadAttachmentWeb({ - required Uri mxcUrl, - required StreamController> - downloadStreamController, - bool getThumbnail = false, - CancelToken? cancelToken, - bool storeable = true, - }) async { - try { - final database = room.client.database; - final mediaAPI = getIt(); - 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 _handleDownloadAttachmentWebSuccess( - Uint8List uint8list, - StreamController> streamController, - ) async { - if (isAttachmentEncrypted) { - await _handleDecryptedAttachmentWeb( - streamController: streamController, - uint8list: uint8list, - ); - } else { - streamController.add( - Right( - DownloadMatrixFileSuccessState( - matrixFile: MatrixFile(bytes: uint8list, name: body), - ), - ), - ); - } - return; - } - - Future _handleDecryptedAttachmentWeb({ - required StreamController> 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 _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 getMediaFileInfo({ getThumbnail = false, ProgressCallback? progressCallback, diff --git a/lib/utils/matrix_sdk_extensions/download_file_web_extension.dart b/lib/utils/matrix_sdk_extensions/download_file_web_extension.dart new file mode 100644 index 0000000000..6e5de2574a --- /dev/null +++ b/lib/utils/matrix_sdk_extensions/download_file_web_extension.dart @@ -0,0 +1,204 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:dartz/dartz.dart'; +import 'package:dio/dio.dart'; +import 'package:fluffychat/app_state/failure.dart'; +import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/data/network/media/cancel_exception.dart'; +import 'package:fluffychat/data/network/media/media_api.dart'; +import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/utils/exception/download_file_web_exception.dart'; +import 'package:fluffychat/utils/manager/download_manager/download_file_state.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/download_file_extension.dart'; +import 'package:matrix/matrix.dart'; + +extension DownloadFileWebExtension on Event { + Future downloadFileWeb({ + required StreamController> + downloadStreamController, + CancelToken? cancelToken, + }) async { + if (!canContainAttachment()) { + throw DownloadFileWebException( + error: + "downloadFileWeb: 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(); + if (mxcUrl == null) { + throw DownloadFileWebException( + error: + "downloadFileWeb: This event hasn't any attachment or thumbnail.", + ); + } + + final isFileEncrypted = isAttachmentEncrypted; + if (isEncryptionDisabled(isFileEncrypted)) { + throw DownloadFileWebException( + error: 'downloadFileWeb: Encryption is not enabled in your Client.', + ); + } + + final storeable = isFileStoreable(); + + 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 _handleDownloadFileWeb( + mxcUrl: mxcUrl, + downloadStreamController: downloadStreamController, + cancelToken: cancelToken, + storeable: storeable, + ); + } + + Future _handleDownloadFileWeb({ + required Uri mxcUrl, + required StreamController> + downloadStreamController, + CancelToken? cancelToken, + bool storeable = true, + }) async { + try { + final database = room.client.database; + final mediaAPI = getIt(); + final downloadLink = mxcUrl.getDownloadLink(room.client); + final uint8List = await mediaAPI.downloadFileWeb( + 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 _handleDownloadFileWebSuccess( + uint8List, + downloadStreamController, + ); + return MatrixFile(name: body); + } catch (e) { + if (e is CancelRequestException) { + Logs().i("_handleDownloadFileWeb: user cancel the download"); + } + Logs().e("_handleDownloadFileWeb: $e"); + } + return null; + } + + Future _handleDownloadFileWebSuccess( + Uint8List uint8list, + StreamController> streamController, + ) async { + if (isAttachmentEncrypted) { + await _handleDecryptedFileWeb( + streamController: streamController, + uint8list: uint8list, + ); + } else { + streamController.add( + Right( + DownloadMatrixFileSuccessState( + matrixFile: MatrixFile(bytes: uint8list, name: body), + ), + ), + ); + } + return; + } + + Future _handleDecryptedFileWeb({ + required StreamController> streamController, + required Uint8List uint8list, + }) async { + streamController.add( + const Right( + DecryptingFileState(), + ), + ); + try { + final decryptedFile = await _decryptAttachmentWeb( + uint8list: uint8list, + ); + if (decryptedFile == null) { + throw DownloadFileWebException( + error: '_handleDecryptedFileWeb:: decryptedFile is null', + ); + } + streamController.add( + Right( + DownloadMatrixFileSuccessState( + matrixFile: MatrixFile(bytes: decryptedFile, name: body), + ), + ), + ); + } catch (e) { + Logs().e( + '_handleDecryptedFileWeb:: $e', + ); + streamController.add( + Left( + DownloadFileFailureState(exception: e), + ), + ); + } + } + + Future _decryptAttachmentWeb({ + required Uint8List uint8list, + }) async { + final dynamic fileMap = content['file']; + if (!fileMap['key']['key_ops'].contains('decrypt')) { + throw DownloadFileWebException( + error: "_decryptAttachmentWeb: 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 DownloadFileWebException( + error: '_decryptAttachmentWeb: Unable to decrypt file', + ); + } + + return decryptAttachment; + } +} diff --git a/lib/widgets/file_widget/download_file_tile_widget.dart b/lib/widgets/file_widget/download_file_tile_widget.dart index 973cd77578..c74469fb6f 100644 --- a/lib/widgets/file_widget/download_file_tile_widget.dart +++ b/lib/widgets/file_widget/download_file_tile_widget.dart @@ -1,6 +1,5 @@ import 'package:fluffychat/presentation/model/chat/downloading_state_presentation_model.dart'; import 'package:fluffychat/utils/extension/mime_type_extension.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/file_widget/circular_loading_download_widget.dart'; import 'package:fluffychat/widgets/file_widget/file_tile_widget.dart'; import 'package:fluffychat/widgets/file_widget/message_file_tile_style.dart'; @@ -78,7 +77,7 @@ class DownloadFileTileWidget extends StatelessWidget { ), ), InkWell( - onTap: PlatformInfos.isWeb ? null : onCancelDownload, + onTap: onCancelDownload, child: Container( width: style.downloadIconSize, decoration: BoxDecoration(