Files
workavia-mail-front/lib/features/upload/data/network/file_uploader.dart
T
2026-04-09 18:26:19 +07:00

232 lines
8.3 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:core/data/network/dio_client.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/file_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
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 {
static const String uploadAttachmentExtraKey = 'upload-attachment';
static const String streamDataExtraKey = 'streamData';
static const String filePathExtraKey = 'path';
final DioClient _dioClient;
final FileUtils _fileUtils;
FileUploader(
this._dioClient,
this._fileUtils,
);
Future<Attachment> uploadAttachment(
UploadTaskId uploadId,
FileInfo fileInfo,
Uri uploadUri,
{
CancelToken? cancelToken,
StreamController<Either<Failure, Success>>? onSendController,
}
) async {
if (PlatformInfo.isWeb || Platform.numberOfProcessors == 1) {
return _handleUploadAttachmentActionOnMainIsolate(
uploadId,
fileInfo,
uploadUri,
cancelToken: cancelToken,
onSendController: onSendController,
);
} else {
final rootIsolateToken = RootIsolateToken.instance;
if (rootIsolateToken == null) {
throw const CanNotGetRootIsolateToken();
}
final args = UploadFileArguments(
_dioClient,
_fileUtils,
uploadId,
fileInfo,
uploadUri,
rootIsolateToken,
);
return await worker.workerManager.executeWithPort<Attachment, Success>(
_buildUploadClosure(args),
onMessage: (value) {
log('FileUploader::uploadAttachment(): onUpdateProgress: $value');
onSendController?.add(Right(value));
},
)
.then((value) => value)
.catchError((error) => throw error);
}
}
static Future<Attachment> Function(worker.SendPort) _buildUploadClosure(
UploadFileArguments args,
) => (sendPort) => _handleUploadAttachmentAction(args, sendPort);
static Future<Attachment> _handleUploadAttachmentAction(
UploadFileArguments argsUpload,
worker.SendPort 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
),
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(
resultJson: resultJson,
fileName: argsUpload.fileInfo.fileName,
fileCharset: fileCharset?.toLowerCase());
} else {
return _parsingResponse(
resultJson: resultJson,
fileName: argsUpload.fileInfo.fileName);
}
} on DioException catch (exception) {
logWarning('FileUploader::_handleUploadAttachmentAction():DioException: $exception');
throw exception.copyWith(
requestOptions: exception.requestOptions.copyWith(data: ''));
} catch (exception) {
logWarning('FileUploader::_handleUploadAttachmentAction():OtherException: $exception');
rethrow;
}
}
Future<Attachment> _handleUploadAttachmentActionOnMainIsolate(
UploadTaskId uploadId,
FileInfo fileInfo,
Uri uploadUri,
{
CancelToken? cancelToken,
StreamController<Either<Failure, Success>>? onSendController,
}
) async {
final headerParam = _dioClient.getHeaders();
headerParam[HttpHeaders.contentTypeHeader] = fileInfo.mimeType;
headerParam[HttpHeaders.contentLengthHeader] = fileInfo.fileSize;
final mapExtra = <String, dynamic>{
uploadAttachmentExtraKey: {
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::_handleUploadAttachmentActionOnMainIsolate():onSendProgress: FILE[${uploadId.id}] : { PROGRESS = $count | TOTAL = $total}');
onSendController?.add(
Right(UploadingAttachmentUploadState(
uploadId,
count,
fileInfo.fileSize
))
);
}
);
log('FileUploader::_handleUploadAttachmentActionOnMainIsolate(): RESULT_JSON = $resultJson');
if (fileInfo.mimeType == FileUtils.TEXT_PLAIN_MIME_TYPE) {
final fileCharset = await _fileUtils.getCharsetFromBytes(fileInfo.bytes!);
return _parsingResponse(
resultJson: resultJson,
fileName: fileInfo.fileName,
fileCharset: fileCharset.toLowerCase());
} else {
return _parsingResponse(
resultJson: resultJson,
fileName: fileInfo.fileName);
}
}
static Attachment _parsingResponse({
dynamic resultJson,
required String fileName,
String? fileCharset
}) {
if (resultJson != null) {
final decodeJson = resultJson is Map ? resultJson : jsonDecode(resultJson);
final uploadResponse = UploadResponse.fromJson(decodeJson);
log('FileUploader::_parsingResponse(): UploadResponse = $uploadResponse');
return uploadResponse.toAttachment(
nameFile: fileName,
charset: fileCharset);
} else {
logWarning('FileUploader::_parsingResponse(): DataResponseIsNullException');
throw const DataResponseIsNullException();
}
}
}