Skip to content

TF-3739 Fix error when uploading several attachments on android #3751

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -242,15 +242,22 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
return null;
}

Future<TokenOIDC> _invokeRefreshTokenFromServer() {
log('AuthorizationInterceptors::_invokeRefreshTokenFromServer:');
return _authenticationClient.refreshingTokensOIDC(
_configOIDC!.clientId,
_configOIDC!.redirectUrl,
_configOIDC!.discoveryUrl,
_configOIDC!.scopes,
_token!.refreshToken
);
Future<TokenOIDC> _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<TokenOIDC> _getNewTokenForIOSPlatform() async {
Expand Down
36 changes: 0 additions & 36 deletions lib/features/upload/data/model/upload_file_arguments.dart

This file was deleted.

207 changes: 74 additions & 133 deletions lib/features/upload/data/network/file_uploader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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<Attachment> uploadAttachment(
UploadTaskId uploadId,
Expand All @@ -48,171 +38,122 @@ class FileUploader {
CancelToken? cancelToken,
StreamController<Either<Failure, Success>>? 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<Attachment> _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 = <String, dynamic>{
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');

rethrow;
}
}

Future<Attachment> _handleUploadAttachmentActionOnWeb(
UploadTaskId uploadId,
FileInfo fileInfo,
Uri uploadUri,
{
CancelToken? cancelToken,
StreamController<Either<Failure, Success>>? onSendController,
}
) async {
Options _getDioOptions(FileInfo fileInfo) {
final headerParam = _dioClient.getHeaders();
headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType;
headerParam[HttpHeaders.contentLengthHeader] = fileInfo.fileSize;

final mapExtra = <String, dynamic>{
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<List<int>>? _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<Either<Failure, Success>>? onSendController,
}) {
log('FileUploader::_onProgressChanged: FILE[${uploadId.id}] : { PROGRESS = $count | TOTAL = $total}');
onSendController?.add(Right(UploadingAttachmentUploadState(
uploadId,
count,
fileSize,
)));
}

Future<Attachment> _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
Expand Down
2 changes: 1 addition & 1 deletion lib/features/upload/domain/model/upload_attachment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class UploadAttachment with EquatableMixin {
_progressStateController.add(flowUploadState);
}

void upload() async {
Future<void> upload() async {
try {
log('UploadFile::upload(): $uploadTaskId');
_updateEvent(Right(PendingAttachmentUploadState(uploadTaskId, 0, fileInfo.fileSize)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,23 +222,24 @@ class UploadController extends BaseController {
Future<void> justUploadAttachmentsAction({
required List<FileInfo> uploadFiles,
required Uri uploadUri,
}) {
return Future.forEach<FileInfo>(uploadFiles, (uploadFile) async {
await uploadFileAction(uploadFile: uploadFile, uploadUri: uploadUri);
});
}) async {
await Future.wait(
uploadFiles.map((uploadFile) {
return uploadFileAction(uploadFile: uploadFile, uploadUri: uploadUri);
}),
);
}

Future<void> 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() {
Expand Down
Loading
Loading