TF-675 Handle upload attachment use isolate
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
|
||||
import 'package:core/data/network/dio_client.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart';
|
||||
|
||||
class UploadFileArguments with EquatableMixin {
|
||||
|
||||
final DioClient dioClient;
|
||||
final UploadTaskId uploadId;
|
||||
final FileInfo fileInfo;
|
||||
final Uri uploadUri;
|
||||
final CancelToken? cancelToken;
|
||||
|
||||
UploadFileArguments(
|
||||
this.dioClient,
|
||||
this.uploadId,
|
||||
this.fileInfo,
|
||||
this.uploadUri,
|
||||
{this.cancelToken});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [uploadId, fileInfo, uploadUri];
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:dio/dio.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/upload/data/model/upload_file_arguments.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:worker_manager/worker_manager.dart' as worker;
|
||||
|
||||
class FileUploader {
|
||||
|
||||
final DioClient _dioClient;
|
||||
final worker.Executor _isolateExecutor;
|
||||
|
||||
FileUploader(this._dioClient, this._isolateExecutor);
|
||||
|
||||
Future<Attachment?> uploadAttachment(
|
||||
UploadTaskId uploadId,
|
||||
StreamController<Either<Failure, Success>> onSendController,
|
||||
FileInfo fileInfo,
|
||||
Uri uploadUri,
|
||||
{CancelToken? cancelToken}
|
||||
) async {
|
||||
if (BuildUtils.isWeb) {
|
||||
return _handleUploadAttachmentActionOnWeb(
|
||||
uploadId,
|
||||
onSendController,
|
||||
fileInfo,
|
||||
uploadUri,
|
||||
cancelToken: cancelToken);
|
||||
} else {
|
||||
final attachmentUploaded = await _isolateExecutor.execute(
|
||||
arg1: UploadFileArguments(
|
||||
_dioClient,
|
||||
uploadId,
|
||||
fileInfo,
|
||||
uploadUri,
|
||||
cancelToken: cancelToken),
|
||||
fun1: _handleUploadAttachmentAction,
|
||||
notification: (value) {
|
||||
if (value is Success) {
|
||||
log('FileUploader::uploadAttachment(): onUpdateProgress: $value');
|
||||
onSendController.add(Right(value));
|
||||
}
|
||||
});
|
||||
return attachmentUploaded;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Attachment?> _handleUploadAttachmentAction(
|
||||
UploadFileArguments argsUpload,
|
||||
worker.TypeSendPort sendPort
|
||||
) async {
|
||||
try {
|
||||
final dioClient = argsUpload.dioClient;
|
||||
final fileInfo = argsUpload.fileInfo;
|
||||
final uploadUri = argsUpload.uploadUri;
|
||||
final cancelToken = argsUpload.cancelToken;
|
||||
|
||||
final headerParam = dioClient.getHeaders();
|
||||
headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType;
|
||||
headerParam[HttpHeaders.contentLengthHeader] = fileInfo.fileSize;
|
||||
|
||||
final resultJson = await argsUpload.dioClient.post(
|
||||
Uri.decodeFull(uploadUri.toString()),
|
||||
options: Options(headers: headerParam),
|
||||
data: fileInfo.readStream,
|
||||
cancelToken: cancelToken,
|
||||
onSendProgress: (progress, total) {
|
||||
log('FileUploader::_handleUploadAttachmentAction():onSendProgress: [${argsUpload.uploadId.id}] = $progress');
|
||||
sendPort.send(UploadingAttachmentUploadState(
|
||||
argsUpload.uploadId,
|
||||
progress,
|
||||
fileInfo.fileSize));
|
||||
});
|
||||
|
||||
if (cancelToken?.isCancelled == true) {
|
||||
log('FileUploader::_handleUploadAttachmentAction(): cancelToken');
|
||||
return null;
|
||||
}
|
||||
|
||||
final decodeJson = resultJson is Map ? resultJson : jsonDecode(resultJson);
|
||||
final uploadResponse = UploadResponse.fromJson(decodeJson);
|
||||
log('FileUploader::_handleUploadAttachmentAction(): ${uploadResponse.toString()}');
|
||||
return uploadResponse.toAttachment(fileInfo.fileName);
|
||||
} catch (e) {
|
||||
log('FileUploader::_handleUploadAttachmentAction(): ERROR: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Attachment?> _handleUploadAttachmentActionOnWeb(
|
||||
UploadTaskId uploadId,
|
||||
StreamController<Either<Failure, Success>> onSendController,
|
||||
FileInfo fileInfo,
|
||||
Uri uploadUri,
|
||||
{CancelToken? cancelToken}
|
||||
) async {
|
||||
try {
|
||||
final headerParam = _dioClient.getHeaders();
|
||||
headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType;
|
||||
headerParam[HttpHeaders.contentLengthHeader] = fileInfo.fileSize;
|
||||
|
||||
final resultJson = await _dioClient.post(
|
||||
Uri.decodeFull(uploadUri.toString()),
|
||||
options: Options(headers: headerParam),
|
||||
data: fileInfo.readStream,
|
||||
cancelToken: cancelToken,
|
||||
onSendProgress: (progress, total) {
|
||||
log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: [$uploadId] = $progress');
|
||||
onSendController.add(Right(UploadingAttachmentUploadState(
|
||||
uploadId, progress, fileInfo.fileSize)));
|
||||
}
|
||||
);
|
||||
|
||||
if (cancelToken?.isCancelled == true) {
|
||||
log('FileUploader::_handleUploadAttachmentAction(): cancelToken');
|
||||
return null;
|
||||
}
|
||||
|
||||
final decodeJson = resultJson is Map ? resultJson : jsonDecode(resultJson);
|
||||
final uploadResponse = UploadResponse.fromJson(decodeJson);
|
||||
log('FileUploader::_handleUploadAttachmentActionOnWeb(): ${uploadResponse.toString()}');
|
||||
return uploadResponse.toAttachment(fileInfo.fileName);
|
||||
} catch (e) {
|
||||
log('FileUploader::_handleUploadAttachmentActionOnWeb(): $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:model/upload/file_info.dart';
|
||||
import 'package:tmail_ui_user/features/upload/data/network/file_uploader.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:dio/dio.dart';
|
||||
|
||||
class UploadAttachment extends Equatable {
|
||||
|
||||
final UploadTaskId uploadTaskId;
|
||||
final FileInfo fileInfo;
|
||||
final Uri uploadUri;
|
||||
final FileUploader fileUploader;
|
||||
final CancelToken? cancelToken;
|
||||
|
||||
final StreamController<Either<Failure, Success>> _progressStateController
|
||||
= StreamController<Either<Failure, Success>>.broadcast();
|
||||
Stream<Either<Failure, Success>> get progressState => _progressStateController.stream;
|
||||
|
||||
UploadAttachment(
|
||||
this.uploadTaskId,
|
||||
this.fileInfo,
|
||||
this.uploadUri,
|
||||
this.fileUploader,
|
||||
{this.cancelToken});
|
||||
|
||||
void _updateEvent(Either<Failure, Success> flowUploadState) {
|
||||
_progressStateController.add(flowUploadState);
|
||||
}
|
||||
|
||||
Future<void> upload() async {
|
||||
log('UploadFile::upload(): $uploadTaskId');
|
||||
_updateEvent(Right(PendingAttachmentUploadState(uploadTaskId, 0, fileInfo.fileSize)));
|
||||
|
||||
final attachment = await fileUploader.uploadAttachment(
|
||||
uploadTaskId,
|
||||
_progressStateController,
|
||||
fileInfo,
|
||||
uploadUri,
|
||||
cancelToken: cancelToken);
|
||||
|
||||
if (cancelToken?.isCancelled == true) {
|
||||
_updateEvent(Left(CancelAttachmentUploadState(uploadTaskId)));
|
||||
await _progressStateController.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (attachment != null) {
|
||||
_updateEvent(Right(SuccessAttachmentUploadState(uploadTaskId, attachment)));
|
||||
} else {
|
||||
_updateEvent(Left(ErrorAttachmentUploadState(uploadTaskId)));
|
||||
}
|
||||
await _progressStateController.close();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [uploadTaskId, fileInfo, uploadUri];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class UploadTaskId extends Equatable {
|
||||
final String id;
|
||||
|
||||
const UploadTaskId(this.id);
|
||||
|
||||
factory UploadTaskId.undefined() => const UploadTaskId('');
|
||||
|
||||
@override
|
||||
List<Object> get props => [id];
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:model/email/attachment.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart';
|
||||
|
||||
class PendingAttachmentUploadState extends Success {
|
||||
final UploadTaskId uploadId;
|
||||
final int progress;
|
||||
final int total;
|
||||
|
||||
PendingAttachmentUploadState(this.uploadId, this.progress, this.total);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [progress, total];
|
||||
}
|
||||
|
||||
class UploadingAttachmentUploadState extends Success {
|
||||
final UploadTaskId uploadId;
|
||||
final int progress;
|
||||
final int total;
|
||||
|
||||
UploadingAttachmentUploadState(this.uploadId, this.progress, this.total);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [progress, total];
|
||||
}
|
||||
|
||||
class SuccessAttachmentUploadState extends Success {
|
||||
final UploadTaskId uploadId;
|
||||
final Attachment attachment;
|
||||
|
||||
SuccessAttachmentUploadState(this.uploadId, this.attachment);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [uploadId, attachment];
|
||||
}
|
||||
|
||||
class ErrorAttachmentUploadState extends Failure {
|
||||
final UploadTaskId uploadId;
|
||||
|
||||
ErrorAttachmentUploadState(this.uploadId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [uploadId];
|
||||
}
|
||||
|
||||
class CancelAttachmentUploadState extends Failure {
|
||||
final UploadTaskId uploadId;
|
||||
|
||||
CancelAttachmentUploadState(this.uploadId);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [uploadId];
|
||||
}
|
||||
Reference in New Issue
Block a user