From 7c3b78222501020c9fdd040dfea99dc6e50cc59a Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 26 May 2025 01:37:27 +0700 Subject: [PATCH] TF-3739 Fix error when uploading several attachments on android Signed-off-by: dab246 --- .../authorization_interceptors.dart | 25 ++- .../data/model/upload_file_arguments.dart | 36 --- .../upload/data/network/file_uploader.dart | 207 +++++++----------- .../domain/model/upload_attachment.dart | 2 +- .../controller/upload_controller.dart | 13 +- .../bindings/network/network_bindings.dart | 2 + .../network/network_isolate_binding.dart | 5 - 7 files changed, 100 insertions(+), 190 deletions(-) delete mode 100644 lib/features/upload/data/model/upload_file_arguments.dart diff --git a/lib/features/login/data/network/interceptors/authorization_interceptors.dart b/lib/features/login/data/network/interceptors/authorization_interceptors.dart index 8699df8b2c..76754b096d 100644 --- a/lib/features/login/data/network/interceptors/authorization_interceptors.dart +++ b/lib/features/login/data/network/interceptors/authorization_interceptors.dart @@ -242,15 +242,22 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper { return null; } - Future _invokeRefreshTokenFromServer() { - log('AuthorizationInterceptors::_invokeRefreshTokenFromServer:'); - return _authenticationClient.refreshingTokensOIDC( - _configOIDC!.clientId, - _configOIDC!.redirectUrl, - _configOIDC!.discoveryUrl, - _configOIDC!.scopes, - _token!.refreshToken - ); + Future _invokeRefreshTokenFromServer() async { + log('AuthorizationInterceptors::_invokeRefreshTokenFromServer: Start at ${DateTime.now()}'); + try { + final newToken = await _authenticationClient.refreshingTokensOIDC( + _configOIDC!.clientId, + _configOIDC!.redirectUrl, + _configOIDC!.discoveryUrl, + _configOIDC!.scopes, + _token!.refreshToken, + ); + log('AuthorizationInterceptors::_invokeRefreshTokenFromServer: Success at ${DateTime.now()} | NewToken = ${newToken.token}'); + return newToken; + } catch (e) { + logError('AuthorizationInterceptors::_invokeRefreshTokenFromServer: Failed at ${DateTime.now()} | Error: $e'); + rethrow; + } } Future _getNewTokenForIOSPlatform() async { diff --git a/lib/features/upload/data/model/upload_file_arguments.dart b/lib/features/upload/data/model/upload_file_arguments.dart deleted file mode 100644 index 426296f418..0000000000 --- a/lib/features/upload/data/model/upload_file_arguments.dart +++ /dev/null @@ -1,36 +0,0 @@ - -import 'package:core/data/network/dio_client.dart'; -import 'package:core/utils/file_utils.dart'; -import 'package:equatable/equatable.dart'; -import 'package:model/upload/file_info.dart'; -import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_messenger/background_isolate_binary_messenger.dart'; -import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart'; - -class UploadFileArguments with EquatableMixin { - - final DioClient dioClient; - final FileUtils fileUtils; - final UploadTaskId uploadId; - final FileInfo fileInfo; - final Uri uploadUri; - final RootIsolateToken isolateToken; - - UploadFileArguments( - this.dioClient, - this.fileUtils, - this.uploadId, - this.fileInfo, - this.uploadUri, - this.isolateToken, - ); - - @override - List get props => [ - dioClient, - fileUtils, - uploadId, - fileInfo, - uploadUri, - isolateToken, - ]; -} \ No newline at end of file diff --git a/lib/features/upload/data/network/file_uploader.dart b/lib/features/upload/data/network/file_uploader.dart index 754054d1a3..4a4a988041 100644 --- a/lib/features/upload/data/network/file_uploader.dart +++ b/lib/features/upload/data/network/file_uploader.dart @@ -15,14 +15,9 @@ import 'package:get/get_connect/http/src/request/request.dart'; import 'package:model/email/attachment.dart'; import 'package:model/upload/file_info.dart'; import 'package:model/upload/upload_response.dart'; -import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_messenger/background_isolate_binary_messenger.dart'; -import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; -import 'package:tmail_ui_user/features/upload/data/model/upload_file_arguments.dart'; import 'package:tmail_ui_user/features/upload/domain/exceptions/upload_exception.dart'; import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart'; import 'package:tmail_ui_user/features/upload/domain/state/attachment_upload_state.dart'; -import 'package:tmail_ui_user/main/exceptions/isolate_exception.dart'; -import 'package:worker_manager/worker_manager.dart' as worker; class FileUploader { @@ -31,14 +26,9 @@ class FileUploader { static const String filePathExtraKey = 'path'; final DioClient _dioClient; - final worker.Executor _isolateExecutor; final FileUtils _fileUtils; - FileUploader( - this._dioClient, - this._isolateExecutor, - this._fileUtils, - ); + FileUploader(this._dioClient, this._fileUtils); Future uploadAttachment( UploadTaskId uploadId, @@ -48,110 +38,40 @@ class FileUploader { CancelToken? cancelToken, StreamController>? onSendController, } - ) async { - if (PlatformInfo.isWeb) { - return _handleUploadAttachmentActionOnWeb( - uploadId, - fileInfo, - uploadUri, - cancelToken: cancelToken, - onSendController: onSendController, - ); - } else { - final rootIsolateToken = RootIsolateToken.instance; - if (rootIsolateToken == null) { - throw CanNotGetRootIsolateToken(); - } - - return await _isolateExecutor.execute( - arg1: UploadFileArguments( - _dioClient, - _fileUtils, - uploadId, - fileInfo, - uploadUri, - rootIsolateToken, - ), - fun1: _handleUploadAttachmentAction, - notification: (value) { - if (value is Success) { - log('FileUploader::uploadAttachment(): onUpdateProgress: $value'); - onSendController?.add(Right(value)); - } - } - ) - .then((value) => value) - .catchError((error) => throw error); - } - } - - static Future _handleUploadAttachmentAction( - UploadFileArguments argsUpload, - worker.TypeSendPort sendPort ) async { try { - final rootIsolateToken = argsUpload.isolateToken; - BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken); - await HiveCacheConfig.instance.setUp(); - - final headerParam = argsUpload.dioClient.getHeaders(); - headerParam[HttpHeaders.contentTypeHeader] = argsUpload.fileInfo.mimeType; - headerParam[HttpHeaders.contentLengthHeader] = argsUpload.fileInfo.fileSize; - - final mapExtra = { - uploadAttachmentExtraKey: { - if (argsUpload.fileInfo.filePath?.isNotEmpty == true) - filePathExtraKey: argsUpload.fileInfo.filePath, - if (argsUpload.fileInfo.bytes?.isNotEmpty == true) - streamDataExtraKey: BodyBytesStream.fromBytes(argsUpload.fileInfo.bytes!), - } - }; - - final resultJson = await argsUpload.dioClient.post( - Uri.decodeFull(argsUpload.uploadUri.toString()), - options: Options( - headers: headerParam, - extra: mapExtra + final resultJson = await _dioClient.post( + Uri.decodeFull(uploadUri.toString()), + options: _getDioOptions(fileInfo), + data: _getDataUploadRequest(fileInfo), + cancelToken: cancelToken, + onSendProgress: (count, total) => _onProgressChanged( + uploadId: uploadId, + count: count, + total: total, + fileSize: fileInfo.fileSize, + onSendController: onSendController, ), - data: argsUpload.fileInfo.filePath?.isNotEmpty == true - ? File(argsUpload.fileInfo.filePath!).openRead() - : argsUpload.fileInfo.bytes != null - ? BodyBytesStream.fromBytes(argsUpload.fileInfo.bytes!) - : null, - onSendProgress: (count, total) { - log('FileUploader::_handleUploadAttachmentAction():onSendProgress: FILE[${argsUpload.uploadId.id}] : { PROGRESS = $count | TOTAL = $total}'); - sendPort.send( - UploadingAttachmentUploadState( - argsUpload.uploadId, - count, - argsUpload.fileInfo.fileSize - ) - ); - } ); + log('FileUploader::_handleUploadAttachmentAction(): RESULT_JSON = $resultJson'); - if (argsUpload.fileInfo.mimeType == FileUtils.TEXT_PLAIN_MIME_TYPE) { - final fileBytes = argsUpload.fileInfo.filePath?.isNotEmpty == true - ? File(argsUpload.fileInfo.filePath!).readAsBytesSync() - : argsUpload.fileInfo.bytes; - - final fileCharset = fileBytes != null - ? await argsUpload.fileUtils.getCharsetFromBytes(fileBytes) - : null; - return _parsingResponse( + + if (fileInfo.mimeType == FileUtils.TEXT_PLAIN_MIME_TYPE) { + return _parsingJsonResponseWithFileTextPlain( resultJson: resultJson, - fileName: argsUpload.fileInfo.fileName, - fileCharset: fileCharset?.toLowerCase()); + fileInfo: fileInfo, + ); } else { return _parsingResponse( resultJson: resultJson, - fileName: argsUpload.fileInfo.fileName); + fileName: fileInfo.fileName, + ); } } on DioError catch (exception) { logError('FileUploader::_handleUploadAttachmentAction():DioError: $exception'); throw exception.copyWith( - requestOptions: exception.requestOptions.copyWith(data: '')); + requestOptions: exception.requestOptions.copyWith(data: '')); } catch (exception) { logError('FileUploader::_handleUploadAttachmentAction():OtherException: $exception'); @@ -159,60 +79,81 @@ class FileUploader { } } - Future _handleUploadAttachmentActionOnWeb( - UploadTaskId uploadId, - FileInfo fileInfo, - Uri uploadUri, - { - CancelToken? cancelToken, - StreamController>? onSendController, - } - ) async { + Options _getDioOptions(FileInfo fileInfo) { final headerParam = _dioClient.getHeaders(); headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType; headerParam[HttpHeaders.contentLengthHeader] = fileInfo.fileSize; final mapExtra = { uploadAttachmentExtraKey: { + if (fileInfo.filePath?.isNotEmpty == true) + filePathExtraKey: fileInfo.filePath, if (fileInfo.bytes?.isNotEmpty == true) streamDataExtraKey: BodyBytesStream.fromBytes(fileInfo.bytes!), } }; - final resultJson = await _dioClient.post( - Uri.decodeFull(uploadUri.toString()), - options: Options( - headers: headerParam, - extra: mapExtra - ), - data: BodyBytesStream.fromBytes(fileInfo.bytes!), - cancelToken: cancelToken, - onSendProgress: (count, total) { - log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: FILE[${uploadId.id}] : { PROGRESS = $count | TOTAL = $total}'); - onSendController?.add( - Right(UploadingAttachmentUploadState( - uploadId, - count, - fileInfo.fileSize - )) - ); + return Options(headers: headerParam, extra: mapExtra); + } + + Stream>? _getDataUploadRequest(FileInfo fileInfo) { + try { + if (PlatformInfo.isMobile && fileInfo.filePath?.isNotEmpty == true) { + return File(fileInfo.filePath!).openRead(); + } else if (fileInfo.bytes != null) { + return BodyBytesStream.fromBytes(fileInfo.bytes!); + } else { + return null; } - ); - log('FileUploader::_handleUploadAttachmentActionOnWeb(): RESULT_JSON = $resultJson'); - if (fileInfo.mimeType == FileUtils.TEXT_PLAIN_MIME_TYPE) { + } catch(e) { + logError('FileUploader::_getDataUploadRequest: Exception = $e'); + return null; + } + } + + void _onProgressChanged({ + required UploadTaskId uploadId, + required int count, + required int total, + required int fileSize, + StreamController>? onSendController, + }) { + log('FileUploader::_onProgressChanged: FILE[${uploadId.id}] : { PROGRESS = $count | TOTAL = $total}'); + onSendController?.add(Right(UploadingAttachmentUploadState( + uploadId, + count, + fileSize, + ))); + } + + Future _parsingJsonResponseWithFileTextPlain({ + dynamic resultJson, + required FileInfo fileInfo, + }) async { + if (PlatformInfo.isMobile && fileInfo.filePath?.isNotEmpty == true) { + final fileBytes = await File(fileInfo.filePath!).readAsBytes(); + final fileCharset = await _fileUtils.getCharsetFromBytes(fileBytes); + return _parsingResponse( + resultJson: resultJson, + fileName: fileInfo.fileName, + fileCharset: fileCharset.toLowerCase(), + ); + } else if (fileInfo.bytes?.isNotEmpty == true) { final fileCharset = await _fileUtils.getCharsetFromBytes(fileInfo.bytes!); return _parsingResponse( resultJson: resultJson, fileName: fileInfo.fileName, - fileCharset: fileCharset.toLowerCase()); + fileCharset: fileCharset.toLowerCase(), + ); } else { return _parsingResponse( resultJson: resultJson, - fileName: fileInfo.fileName); + fileName: fileInfo.fileName, + ); } } - static Attachment _parsingResponse({ + Attachment _parsingResponse({ dynamic resultJson, required String fileName, String? fileCharset diff --git a/lib/features/upload/domain/model/upload_attachment.dart b/lib/features/upload/domain/model/upload_attachment.dart index 150b8e289d..469392de15 100644 --- a/lib/features/upload/domain/model/upload_attachment.dart +++ b/lib/features/upload/domain/model/upload_attachment.dart @@ -38,7 +38,7 @@ class UploadAttachment with EquatableMixin { _progressStateController.add(flowUploadState); } - void upload() async { + Future upload() async { try { log('UploadFile::upload(): $uploadTaskId'); _updateEvent(Right(PendingAttachmentUploadState(uploadTaskId, 0, fileInfo.fileSize))); diff --git a/lib/features/upload/presentation/controller/upload_controller.dart b/lib/features/upload/presentation/controller/upload_controller.dart index f8dc8c37f9..4a7803e278 100644 --- a/lib/features/upload/presentation/controller/upload_controller.dart +++ b/lib/features/upload/presentation/controller/upload_controller.dart @@ -222,23 +222,24 @@ class UploadController extends BaseController { Future justUploadAttachmentsAction({ required List uploadFiles, required Uri uploadUri, - }) { - return Future.forEach(uploadFiles, (uploadFile) async { - await uploadFileAction(uploadFile: uploadFile, uploadUri: uploadUri); - }); + }) async { + await Future.wait( + uploadFiles.map((uploadFile) { + return uploadFileAction(uploadFile: uploadFile, uploadUri: uploadUri); + }), + ); } Future uploadFileAction({ required FileInfo uploadFile, required Uri uploadUri, - }) { + }) async { log('UploadController::_uploadFile():fileName: ${uploadFile.fileName} | mimeType: ${uploadFile.mimeType} | isInline: ${uploadFile.isInline} | fromFileShared: ${uploadFile.isShared}'); consumeState(_uploadAttachmentInteractor.execute( uploadFile, uploadUri, cancelToken: CancelToken(), )); - return Future.value(); } void _refreshListUploadAttachmentState() { diff --git a/lib/main/bindings/network/network_bindings.dart b/lib/main/bindings/network/network_bindings.dart index 618453273c..b72a8d173a 100644 --- a/lib/main/bindings/network/network_bindings.dart +++ b/lib/main/bindings/network/network_bindings.dart @@ -36,6 +36,7 @@ import 'package:tmail_ui_user/features/push_notification/data/network/web_socket import 'package:tmail_ui_user/features/quotas/data/network/quotas_api.dart'; import 'package:tmail_ui_user/features/server_settings/data/network/server_settings_api.dart'; import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; +import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; import 'package:tmail_ui_user/main/exceptions/send_email_exception_thrower.dart'; import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart'; @@ -127,6 +128,7 @@ class NetworkBindings extends Bindings { Get.put(ServerSettingsAPI(Get.find())); Get.put(WebSocketApi(Get.find())); Get.put(LinagoraEcosystemApi(Get.find())); + Get.put(FileUploader(Get.find(), Get.find())); } void _bindingConnection() { diff --git a/lib/main/bindings/network/network_isolate_binding.dart b/lib/main/bindings/network/network_isolate_binding.dart index d80686c43c..6322c8ddfe 100644 --- a/lib/main/bindings/network/network_isolate_binding.dart +++ b/lib/main/bindings/network/network_isolate_binding.dart @@ -101,11 +101,6 @@ class NetworkIsolateBindings extends Bindings { Get.find(tag: PlatformInfo.isMobile ? BindingTag.isolateTag : null), Get.find(), )); - Get.put(FileUploader( - Get.find(tag: PlatformInfo.isMobile ? BindingTag.isolateTag : null), - Get.find(), - Get.find(), - )); } void _bindingSharing() {