diff --git a/.github/workflows/production.yaml b/.github/workflows/production.yaml index 7c408bf315..36e2464e9c 100644 --- a/.github/workflows/production.yaml +++ b/.github/workflows/production.yaml @@ -102,6 +102,8 @@ jobs: run: bundle exec fastlane deploy type:staging build-android: + # Skipped until fix the upload to turbo + if: false needs: pre-build runs-on: ubuntu-latest steps: diff --git a/lib/blocs/upload/limits.dart b/lib/blocs/upload/limits.dart index 26bf139d4d..fdad83984e 100644 --- a/lib/blocs/upload/limits.dart +++ b/lib/blocs/upload/limits.dart @@ -1,20 +1,14 @@ import 'package:ardrive_utils/ardrive_utils.dart'; -final privateFileSizeLimit = const GiB(65).size; +final fileSizeLimit = const GiB(65).size; +final fileSizeWarning = const GiB(5).size; -final largeFileUploadSizeThreshold = const MiB(500).size; - -final mobilePrivateFileSizeLimit = const GiB(10).size; +const maxBundleDataItemCount = 500; +const maxFilesPerBundle = maxBundleDataItemCount ~/ 2; +const maxFilesSizePerBundleUsingTurbo = 1; -final publicFileSafeSizeLimit = const GiB(5).size; -final nonChromeBrowserUploadSafeLimitUsingTurbo = const MiB(500).size; +final d2nBundleSizeLimit = const GiB(65).size; +final turboBundleSizeLimit = const GiB(10).size; int getBundleSizeLimit(bool isTurbo) => isTurbo ? turboBundleSizeLimit : d2nBundleSizeLimit; - -final d2nBundleSizeLimit = const GiB(65).size; -final turboBundleSizeLimit = const GiB(10).size; -final mobileBundleSizeLimit = const GiB(65).size; -const maxBundleDataItemCount = 500; -const maxFilesPerBundle = maxBundleDataItemCount ~/ 2; -const maxFilesSizePerBundleUsingTurbo = 1; diff --git a/lib/blocs/upload/upload_cubit.dart b/lib/blocs/upload/upload_cubit.dart index 9f866ff689..d281941d76 100644 --- a/lib/blocs/upload/upload_cubit.dart +++ b/lib/blocs/upload/upload_cubit.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:ardrive/authentication/ardrive_auth.dart'; import 'package:ardrive/blocs/blocs.dart'; -import 'package:ardrive/blocs/upload/limits.dart'; import 'package:ardrive/blocs/upload/models/models.dart'; import 'package:ardrive/blocs/upload/models/payment_method_info.dart'; import 'package:ardrive/blocs/upload/upload_file_checker.dart'; @@ -44,7 +43,7 @@ class UploadCubit extends Cubit { final ProfileCubit _profileCubit; final DriveDao _driveDao; final PstService _pst; - final UploadFileChecker _uploadFileChecker; + final UploadFileSizeChecker _uploadFileSizeChecker; final ArDriveAuth _auth; final ActivityTracker _activityTracker; final LicenseService _licenseService; @@ -98,7 +97,7 @@ class UploadCubit extends Cubit { required ProfileCubit profileCubit, required DriveDao driveDao, required PstService pst, - required UploadFileChecker uploadFileChecker, + required UploadFileSizeChecker uploadFileSizeChecker, required ArDriveAuth auth, required ArDriveUploadPreparationManager arDriveUploadManager, required ActivityTracker activityTracker, @@ -107,7 +106,7 @@ class UploadCubit extends Cubit { this.uploadFolders = false, this.isDragNDrop = false, }) : _profileCubit = profileCubit, - _uploadFileChecker = uploadFileChecker, + _uploadFileSizeChecker = uploadFileSizeChecker, _driveDao = driveDao, _pst = pst, _auth = auth, @@ -186,14 +185,14 @@ class UploadCubit extends Cubit { Future checkFilesAboveLimit() async { if (_isAPrivateUpload()) { - final tooLargeFiles = await _uploadFileChecker - .checkAndReturnFilesAbovePrivateLimit(files: files); + final largeFiles = + await _uploadFileSizeChecker.getFilesAboveSizeLimit(files: files); - if (tooLargeFiles.isNotEmpty) { + if (largeFiles.isNotEmpty) { emit( UploadFileTooLarge( - hasFilesToUpload: files.length > tooLargeFiles.length, - tooLargeFileNames: tooLargeFiles, + hasFilesToUpload: files.length > largeFiles.length, + tooLargeFileNames: largeFiles, isPrivate: _isAPrivateUpload(), ), ); @@ -402,13 +401,9 @@ class UploadCubit extends Cubit { if (_uploadMethod == UploadMethod.turbo) { await _verifyIfUploadContainsLargeFilesUsingTurbo(); - if ((_containsLargeTurboUpload ?? false) && - !hasEmittedWarning && - kIsWeb && - !await AppPlatform.isChrome()) { + if ((_containsLargeTurboUpload ?? false) && !hasEmittedWarning) { emit( UploadShowingWarning( - reason: UploadWarningReason.fileTooLargeOnNonChromeBrowser, uploadPlanForAR: uploadPlanForAr, uploadPlanForTurbo: uploadPlanForTurbo, ), @@ -688,11 +683,9 @@ class UploadCubit extends Cubit { if (_containsLargeTurboUpload == null) { _containsLargeTurboUpload = false; - for (var file in files) { - if (await file.ioFile.length >= largeFileUploadSizeThreshold) { - _containsLargeTurboUpload = true; - break; - } + if (await _uploadFileSizeChecker.hasFileAboveSizeLimit(files: files)) { + _containsLargeTurboUpload = true; + return; } } } @@ -814,8 +807,8 @@ class UploadCubit extends Cubit { Future skipLargeFilesAndCheckForConflicts() async { emit(UploadPreparationInProgress()); - final List filesToSkip = await _uploadFileChecker - .checkAndReturnFilesAbovePrivateLimit(files: files); + final List filesToSkip = + await _uploadFileSizeChecker.getFilesAboveSizeLimit(files: files); files.removeWhere( (file) => filesToSkip.contains(file.getIdentifier()), @@ -836,20 +829,16 @@ class UploadCubit extends Cubit { Future verifyFilesAboveWarningLimit() async { if (!_targetDrive.isPrivate) { - bool fileAboveWarningLimit = - await _uploadFileChecker.hasFileAboveSafePublicSizeLimit( - files: files, - ); - - if (fileAboveWarningLimit) { + if (await _uploadFileSizeChecker.hasFileAboveWarningSizeLimit( + files: files)) { emit(UploadShowingWarning( - reason: UploadWarningReason.fileTooLarge, uploadPlanForAR: null, uploadPlanForTurbo: null, )); return; } + await prepareUploadPlanAndCostEstimates(); } diff --git a/lib/blocs/upload/upload_file_checker.dart b/lib/blocs/upload/upload_file_checker.dart index 6e103502a0..ec28a74bbf 100644 --- a/lib/blocs/upload/upload_file_checker.dart +++ b/lib/blocs/upload/upload_file_checker.dart @@ -1,37 +1,51 @@ import 'package:ardrive/blocs/upload/models/upload_file.dart'; -class UploadFileChecker { - UploadFileChecker({ - required int publicFileSafeSizeLimit, - required int privateFileSafeSizeLimit, - }) : _publicFileSafeSizeLimit = publicFileSafeSizeLimit, - _privateFileSafeSizeLimit = privateFileSafeSizeLimit; - - final int _publicFileSafeSizeLimit; - final int _privateFileSafeSizeLimit; - - Future hasFileAboveSafePublicSizeLimit( - {required List files}) async { +class UploadFileSizeChecker { + UploadFileSizeChecker({ + required int fileSizeLimit, + required int fileSizeWarning, + }) : _fileSizeLimit = fileSizeLimit, + _fileSizeWarning = fileSizeWarning; + + final int _fileSizeLimit; + final int _fileSizeWarning; + + Future hasFileAboveSizeLimit({ + required List files, + }) => + _hasFileAboveLimit(files: files, limit: _fileSizeLimit); + + Future hasFileAboveWarningSizeLimit({ + required List files, + }) => + _hasFileAboveLimit(files: files, limit: _fileSizeWarning); + + Future> getFilesAboveSizeLimit({ + required List files, + }) async { + final filesAboveSizeLimit = []; + for (final file in files) { final fileSize = await file.ioFile.length; - if (fileSize > _publicFileSafeSizeLimit) { - return true; + if (fileSize > _fileSizeLimit) { + filesAboveSizeLimit.add(file.getIdentifier()); } } - return false; - } - Future> checkAndReturnFilesAbovePrivateLimit( - {required List files}) async { - final filesAbovePrivateLimit = []; + return filesAboveSizeLimit; + } + Future _hasFileAboveLimit({ + required List files, + required int limit, + }) async { for (final file in files) { final fileSize = await file.ioFile.length; - if (fileSize > _privateFileSafeSizeLimit) { - filesAbovePrivateLimit.add(file.getIdentifier()); + if (fileSize > limit) { + return true; } } - return filesAbovePrivateLimit; + return false; } } diff --git a/lib/blocs/upload/upload_state.dart b/lib/blocs/upload/upload_state.dart index 0157fab585..f2c1b07d6c 100644 --- a/lib/blocs/upload/upload_state.dart +++ b/lib/blocs/upload/upload_state.dart @@ -179,30 +179,19 @@ class UploadComplete extends UploadState {} class UploadWalletMismatch extends UploadState {} class UploadShowingWarning extends UploadState { - final UploadWarningReason reason; final UploadPlan? uploadPlanForAR; final UploadPlan? uploadPlanForTurbo; UploadShowingWarning({ - required this.reason, this.uploadPlanForAR, this.uploadPlanForTurbo, }); - - @override - List get props => [reason]; } class UploadCanceled extends UploadState {} class CancelD2NUploadWarning extends UploadState {} -enum UploadWarningReason { - /// The user is attempting to upload a file that is too large. - fileTooLarge, - fileTooLargeOnNonChromeBrowser, -} - enum UploadErrors { turboTimeout, unknown, diff --git a/lib/components/upload_form.dart b/lib/components/upload_form.dart index 86715b2296..dd831f5f69 100644 --- a/lib/components/upload_form.dart +++ b/lib/components/upload_form.dart @@ -148,7 +148,7 @@ Future promptToUpload( folder: ioFolder, arDriveUploadManager: context.read(), - uploadFileChecker: context.read(), + uploadFileSizeChecker: context.read(), driveId: driveId, parentFolderId: parentFolderId, files: selectedFiles, @@ -855,11 +855,7 @@ class _UploadFormState extends State { Text( appLocalizationsOf(context) .weDontRecommendUploadsAboveASafeLimit( - filesize( - state.reason == UploadWarningReason.fileTooLarge - ? publicFileSafeSizeLimit - : nonChromeBrowserUploadSafeLimitUsingTurbo, - ), + filesize(fileSizeWarning), ), style: ArDriveTypography.body.buttonNormalRegular(), ), @@ -873,10 +869,7 @@ class _UploadFormState extends State { ), ModalAction( action: () { - if (state.uploadPlanForAR != null && - state.reason == - UploadWarningReason - .fileTooLargeOnNonChromeBrowser) { + if (state.uploadPlanForAR != null) { return context.read().startUpload( uploadPlanForAr: state.uploadPlanForAR!, uploadPlanForTurbo: state.uploadPlanForTurbo, @@ -901,11 +894,9 @@ class _UploadFormState extends State { final progress = state.progress; return ArDriveStandardModal( actions: [ - ModalAction( - action: () { - if (state.uploadMethod == UploadMethod.ar && - state.progress.task.values.any( - (element) => element.status == UploadStatus.inProgress)) { + if (state.progress.hasUploadInProgress) + ModalAction( + action: () { _isShowingCancelDialog = true; final cubit = context.read(); @@ -955,15 +946,12 @@ class _UploadFormState extends State { }, ), ); - } else { - context.read().cancelUpload(); - } - }, - // TODO: localize - title: state.isCanceling - ? 'Canceling...' - : appLocalizationsOf(context).cancelEmphasized, - ), + }, + // TODO: localize + title: state.isCanceling + ? 'Canceling...' + : appLocalizationsOf(context).cancelEmphasized, + ), ], width: kLargeDialogWidth, title: @@ -980,9 +968,9 @@ class _UploadFormState extends State { child: Scrollbar( child: ListView.builder( shrinkWrap: true, - itemCount: progress.task.length, + itemCount: progress.tasks.length, itemBuilder: (BuildContext context, int index) { - final task = progress.task.values.elementAt(index); + final task = progress.tasks.values.elementAt(index); String? progressText; String status = ''; @@ -1005,6 +993,9 @@ class _UploadFormState extends State { case UploadStatus.encryting: status = 'Encrypting'; break; + case UploadStatus.finalizing: + status = 'Finalizing upload'; + break; case UploadStatus.complete: status = 'Complete'; break; @@ -1022,10 +1013,14 @@ class _UploadFormState extends State { 'We are preparing your upload. Preparation step 2/2'; } + final statusAvailableForShowingProgress = + task.status == UploadStatus.failed || + task.status == UploadStatus.inProgress || + task.status == UploadStatus.complete || + task.status == UploadStatus.finalizing; + if (task.isProgressAvailable) { - if (task.status == UploadStatus.inProgress || - task.status == UploadStatus.complete || - task.status == UploadStatus.failed) { + if (statusAvailableForShowingProgress) { if (task.uploadItem != null) { progressText = '${filesize(((task.uploadItem!.size) * task.progress).ceil())}/${filesize(task.uploadItem!.size)}'; @@ -1088,16 +1083,23 @@ class _UploadFormState extends State { const Duration(seconds: 1), child: Column( children: [ - Text( - status, - style: ArDriveTypography.body - .buttonNormalBold( - color: - ArDriveTheme.of(context) - .themeData - .colors - .themeFgOnDisabled, - ), + Row( + children: [ + Flexible( + child: Text( + status, + style: ArDriveTypography + .body + .buttonNormalBold( + color: ArDriveTheme + .of(context) + .themeData + .colors + .themeFgOnDisabled, + ), + ), + ), + ], ), ], ), @@ -1106,7 +1108,7 @@ class _UploadFormState extends State { Text( progressText, style: ArDriveTypography.body - .buttonNormalRegular( + .buttonNormalBold( color: ArDriveTheme.of(context) .themeData .colors @@ -1126,13 +1128,7 @@ class _UploadFormState extends State { MainAxisAlignment.end, children: [ if (task.isProgressAvailable && - (task.status == - UploadStatus.failed || - task.status == - UploadStatus.inProgress || - task.status == - UploadStatus - .complete)) ...[ + statusAvailableForShowingProgress) ...[ Flexible( flex: 2, child: ArDriveProgressBar( @@ -1230,12 +1226,13 @@ class _UploadFormState extends State { .copyWith(fontWeight: FontWeight.bold), ), // TODO: localize - Text( - 'Upload speed: ${filesize(state.progress.calculateUploadSpeed().toInt())}/s', - style: ArDriveTypography.body.buttonNormalBold( - color: - ArDriveTheme.of(context).themeData.colors.themeFgDefault), - ), + if (state.progress.hasUploadInProgress) + Text( + 'Upload speed: ${filesize(state.progress.calculateUploadSpeed().toInt())}/s', + style: ArDriveTypography.body.buttonNormalBold( + color: + ArDriveTheme.of(context).themeData.colors.themeFgDefault), + ), ], ), ); diff --git a/lib/core/upload/uploader.dart b/lib/core/upload/uploader.dart index ab5f7a6b34..93ce7ed104 100644 --- a/lib/core/upload/uploader.dart +++ b/lib/core/upload/uploader.dart @@ -465,6 +465,12 @@ class UploadPaymentEvaluator { _isTurboAvailableToUploadAllFiles, ); + if (uploadMethod == UploadMethod.turbo) { + totalSize = turboBundleSizes; + } else if (uploadMethod == UploadMethod.ar) { + totalSize = arBundleSizes + arFileSizes; + } + return UploadPaymentInfo( isTurboAvailable: _isTurboAvailableToUploadAllFiles, defaultPaymentMethod: uploadMethod, diff --git a/lib/main.dart b/lib/main.dart index 1c9243b652..e663b3d911 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -336,11 +336,10 @@ class AppState extends State { List get repositoryProviders => [ RepositoryProvider(create: (_) => _arweave), // repository provider for UploadFileChecker - RepositoryProvider( - create: (_) => UploadFileChecker( - privateFileSafeSizeLimit: - kIsWeb ? privateFileSizeLimit : mobilePrivateFileSizeLimit, - publicFileSafeSizeLimit: publicFileSafeSizeLimit, + RepositoryProvider( + create: (_) => UploadFileSizeChecker( + fileSizeWarning: fileSizeWarning, + fileSizeLimit: fileSizeLimit, ), ), RepositoryProvider(create: (_) => _arweave), diff --git a/packages/ardrive_uploader/build/unit_test_assets/FontManifest.json b/packages/ardrive_uploader/build/unit_test_assets/FontManifest.json deleted file mode 100644 index 0637a088a0..0000000000 --- a/packages/ardrive_uploader/build/unit_test_assets/FontManifest.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/packages/ardrive_uploader/lib/src/ardrive_uploader.dart b/packages/ardrive_uploader/lib/src/ardrive_uploader.dart index f756cb13b9..7afa15f367 100644 --- a/packages/ardrive_uploader/lib/src/ardrive_uploader.dart +++ b/packages/ardrive_uploader/lib/src/ardrive_uploader.dart @@ -5,8 +5,8 @@ import 'package:ardrive_uploader/ardrive_uploader.dart'; import 'package:ardrive_utils/ardrive_utils.dart'; import 'package:arweave/arweave.dart'; import 'package:cryptography/cryptography.dart' hide Cipher; -import 'package:flutter/foundation.dart'; import 'package:pst/pst.dart'; +import 'package:ardrive_uploader/src/utils/logger.dart'; enum UploadType { turbo, d2n } @@ -155,7 +155,7 @@ class _ArDriveUploader implements ArDriveUploader { SecretKey? driveKey, required UploadType type, }) async { - debugPrint('Creating a new upload controller using the upload type $type'); + logger.d('Creating a new upload controller using the upload type $type'); final dataBundler = _dataBundlerFactory.createDataBundler( type, diff --git a/packages/ardrive_uploader/lib/src/cost_calculator.dart b/packages/ardrive_uploader/lib/src/cost_calculator.dart index 2d3fa64055..4c1e9499b2 100644 --- a/packages/ardrive_uploader/lib/src/cost_calculator.dart +++ b/packages/ardrive_uploader/lib/src/cost_calculator.dart @@ -3,8 +3,8 @@ import 'package:arweave/arweave.dart'; import 'package:arweave/utils.dart'; // ignore: depend_on_referenced_packages import 'package:equatable/equatable.dart'; -import 'package:flutter/foundation.dart'; import 'package:pst/pst.dart'; +import 'package:ardrive_uploader/src/utils/logger.dart'; abstract class ArDriveUploadCostCalculator { Future calculateCost({required int totalSize}); @@ -73,7 +73,7 @@ class UploadCostEstimateCalculatorForAR extends ArDriveUploadCostCalculator { final arUploadCost = winstonToAr(totalCostAR); - debugPrint('Upload cost in AR: $arUploadCost'); + logger.d('Upload cost in AR: $arUploadCost'); final usdUploadCost = await _arCostToUsd.convertForUSD( double.parse(arUploadCost), diff --git a/packages/ardrive_uploader/lib/src/d2n_streamed_upload.dart b/packages/ardrive_uploader/lib/src/d2n_streamed_upload.dart index 8699f87994..0d549a933d 100644 --- a/packages/ardrive_uploader/lib/src/d2n_streamed_upload.dart +++ b/packages/ardrive_uploader/lib/src/d2n_streamed_upload.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'package:ardrive_uploader/ardrive_uploader.dart'; import 'package:ardrive_uploader/src/streamed_upload.dart'; +import 'package:ardrive_uploader/src/utils/logger.dart'; import 'package:arweave/arweave.dart'; -import 'package:flutter/foundation.dart'; class D2NStreamedUpload implements StreamedUpload { UploadAborter? _aborter; @@ -20,11 +20,11 @@ class D2NStreamedUpload implements StreamedUpload { /// It is possible to cancel an upload before starting the network request. if (_isCanceled) { - debugPrint('Upload canceled on D2NStreamedUpload'); + logger.d('Upload canceled on D2NStreamedUpload'); throw Exception('Upload canceled'); } - debugPrint('D2NStreamedUpload.send'); + logger.d('D2NStreamedUpload.send'); final progressStreamTask = await uploadTransaction((uploadItem).data).run(); Completer upload = Completer(); @@ -49,7 +49,7 @@ class D2NStreamedUpload implements StreamedUpload { upload.complete(StreamedUploadResult(success: true)); } catch (e) { - debugPrint('D2NStreamedUpload.send: error while uploading'); + logger.d('D2NStreamedUpload.send: error while uploading'); upload.complete(StreamedUploadResult(success: false)); } }); diff --git a/packages/ardrive_uploader/lib/src/factories.dart b/packages/ardrive_uploader/lib/src/factories.dart index c5d822abbd..77750631a3 100644 --- a/packages/ardrive_uploader/lib/src/factories.dart +++ b/packages/ardrive_uploader/lib/src/factories.dart @@ -4,7 +4,7 @@ import 'package:ardrive_uploader/src/d2n_streamed_upload.dart'; import 'package:ardrive_uploader/src/data_bundler.dart'; import 'package:ardrive_uploader/src/streamed_upload.dart'; import 'package:ardrive_uploader/src/turbo_streamed_upload.dart'; -import 'package:ardrive_uploader/src/turbo_upload_service_base.dart'; +import 'package:ardrive_uploader/src/turbo_upload_service.dart'; import 'package:arweave/arweave.dart'; import 'package:pst/pst.dart'; @@ -108,7 +108,7 @@ class StreamedUploadFactory { return D2NStreamedUpload(); } else if (type == UploadType.turbo) { return TurboStreamedUpload( - TurboUploadServiceImpl( + TurboUploadService( turboUploadUri: turboUploadUri, ), ); diff --git a/packages/ardrive_uploader/lib/src/turbo_streamed_upload.dart b/packages/ardrive_uploader/lib/src/turbo_streamed_upload.dart index 40458e4976..b69291dc99 100644 --- a/packages/ardrive_uploader/lib/src/turbo_streamed_upload.dart +++ b/packages/ardrive_uploader/lib/src/turbo_streamed_upload.dart @@ -1,23 +1,17 @@ -import 'package:arconnect/arconnect.dart'; import 'package:ardrive_uploader/ardrive_uploader.dart'; import 'package:ardrive_uploader/src/exceptions.dart'; import 'package:ardrive_uploader/src/streamed_upload.dart'; -import 'package:ardrive_uploader/src/turbo_upload_service_base.dart'; -import 'package:ardrive_utils/ardrive_utils.dart'; +import 'package:ardrive_uploader/src/turbo_upload_service.dart'; import 'package:arweave/arweave.dart'; +import 'package:ardrive_uploader/src/utils/logger.dart'; import 'package:flutter/foundation.dart'; -import 'package:uuid/uuid.dart'; class TurboStreamedUpload implements StreamedUpload { @visibleForTesting final TurboUploadService service; - final TabVisibilitySingleton _tabVisibility; StreamedUploadResult? _result; - TurboStreamedUpload( - this.service, { - TabVisibilitySingleton? tabVisibilitySingleton, - }) : _tabVisibility = tabVisibilitySingleton ?? TabVisibilitySingleton(); + TurboStreamedUpload(this.service); @override Future send( @@ -25,33 +19,6 @@ class TurboStreamedUpload implements StreamedUpload { Wallet wallet, Function(double)? onProgress, ) async { - final nonce = const Uuid().v4(); - - final publicKey = await safeArConnectAction( - _tabVisibility, - (_) async { - return wallet.getOwner(); - }, - ); - - final signature = await safeArConnectAction( - _tabVisibility, - (_) async { - return signNonceAndData( - nonce: nonce, - wallet: wallet, - ); - }, - ); - - int size = 0; - - final task = uploadItem.data as DataItemResult; - - await for (final data in task.streamGenerator()) { - size += data.length; - } - /// It is possible to cancel an upload before starting the network request. if (_isCanceled) { throw UploadCanceledException( @@ -60,31 +27,23 @@ class TurboStreamedUpload implements StreamedUpload { // gets the streamed request final streamedRequest = service - .postStream( + .post( wallet: wallet, - headers: { - 'x-nonce': nonce, - 'x-address': publicKey, - 'x-signature': signature, - }, dataItem: uploadItem.data, - size: size, onSendProgress: (progress) { onProgress?.call(progress); }) .then((value) async { _result = StreamedUploadResult(success: true); - - return value; }).onError((e, s) { - debugPrint('Error on TurboStreamedUpload.send: $e'); + logger.d('Error on TurboStreamedUpload.send: $e'); _result = StreamedUploadResult(success: false, error: e); }); await streamedRequest; - debugPrint( + logger.d( 'TurboStreamedUpload.send completed with result: ${_result?.success}'); return _result!; diff --git a/packages/ardrive_uploader/lib/src/turbo_upload_service.dart b/packages/ardrive_uploader/lib/src/turbo_upload_service.dart new file mode 100644 index 0000000000..8a90b10daa --- /dev/null +++ b/packages/ardrive_uploader/lib/src/turbo_upload_service.dart @@ -0,0 +1,301 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:ardrive_uploader/src/exceptions.dart'; +import 'package:ardrive_uploader/src/utils/logger.dart'; +import 'package:ardrive_utils/ardrive_utils.dart'; +import 'package:arweave/arweave.dart'; +import 'package:dio/dio.dart'; +import 'package:retry/retry.dart'; + +class TurboUploadService { + TurboUploadService({ + required this.turboUploadUri, + }); + + final Uri turboUploadUri; + final r = RetryOptions(maxAttempts: 8); + final List _cancelTokens = []; + final dio = Dio(); + final dataItemConfirmationRetryDelay = Duration(seconds: 15); + final maxInFlightData = MiB(100).size; + Timer? onSendProgressTimer; + + Future post({ + required DataItemResult dataItem, + required Wallet wallet, + Function(double)? onSendProgress, + Map? headers, + }) async { + logger.d('[${dataItem.id}] Uploading DataItem to Turbo'); + + final uploadInfo = await r.retry( + () => dio.get('$turboUploadUri/chunks/arweave/-1/-1'), + ); + + final uploadId = uploadInfo.data['id'] as String; + final uploadChunkSizeMinInBytes = uploadInfo.data['min'] as int; + final uploadChunkSizeMaxInBytes = uploadInfo.data['max'] as int; + final uploadChunkSizeInBytes = _calculateChunkSize( + dataSize: dataItem.dataItemSize, + minChunkSize: uploadChunkSizeMinInBytes, + maxChunkSize: uploadChunkSizeMaxInBytes, + ); + final maxUploadsInParallel = maxInFlightData ~/ uploadChunkSizeInBytes; + logger.d( + '[${dataItem.id}] Upload ID: $uploadId, Uploads in parallel: $maxUploadsInParallel, Chunk size: $uploadChunkSizeInBytes'); + + // (offset: sent bytes) map for in flight requests progress + Map inFlightRequestsBytesSent = {}; + int completedRequestsBytesSent = 0; + + if (onSendProgress != null) { + onSendProgressTimer = + Timer.periodic(Duration(milliseconds: 500), (timer) { + final inFlightBytesSent = inFlightRequestsBytesSent.isEmpty + ? 0 + : inFlightRequestsBytesSent.values.reduce((a, b) => a + b); + final totalBytesSent = completedRequestsBytesSent + inFlightBytesSent; + final progress = totalBytesSent / dataItem.dataItemSize; + + if (progress >= 1) { + timer.cancel(); + } + + onSendProgress(totalBytesSent / dataItem.dataItemSize); + }); + } + + await _processStream( + stream: dataItem.streamGenerator(), + chunkSize: uploadChunkSizeInBytes, + maxConcurrent: maxUploadsInParallel, + dataItemId: dataItem.id, (chunk, offset) async { + if (_isCanceled) { + throw UploadCanceledException('Upload canceled. Cant upload chunk.'); + } + + final cancelToken = CancelToken(); + + _cancelTokens.add(cancelToken); + + try { + logger.d('[${dataItem.id}] Uploading chunk. Offset: $offset'); + return r.retry(() { + return dio.post( + '$turboUploadUri/chunks/arweave/$uploadId/$offset', + data: chunk, + onSendProgress: (sent, total) { + if (onSendProgress != null) { + if (inFlightRequestsBytesSent[offset] == null) { + inFlightRequestsBytesSent[offset] = 0; + } else if (inFlightRequestsBytesSent[offset]! < sent) { + inFlightRequestsBytesSent[offset] = sent; + } + } + }, + options: Options( + headers: { + 'Content-Type': 'application/octet-stream', + 'Content-Length': chunk.length.toString(), + }..addAll(headers ?? const {}), + ), + cancelToken: cancelToken, + ); + }).then((response) { + _cancelTokens.remove(cancelToken); + + if (onSendProgress != null) { + inFlightRequestsBytesSent.remove(offset); + completedRequestsBytesSent += chunk.length; + } + + return response; + }, onError: (error) { + onSendProgressTimer?.cancel(); + _cancelTokens.remove(cancelToken); + throw error; + }); + } catch (e) { + if (_isCanceled) { + logger.d('[${dataItem.id}] Upload canceled'); + onSendProgressTimer?.cancel(); + cancelToken.cancel(); + } + + _cancelTokens.remove(cancelToken); + + rethrow; + } + }); + + final finalizeCancelToken = CancelToken(); + + try { + logger.d('[${dataItem.id}] Finalising upload to Turbo'); + + _cancelTokens.add(finalizeCancelToken); + + final finaliseInfo = await r.retry( + () => dio.post( + '$turboUploadUri/chunks/arweave/$uploadId/-1', + data: null, + cancelToken: finalizeCancelToken, + options: Options( + validateStatus: (int? status) { + return status != null && + ((status >= 200 && status < 300) || status == 504); + }, + ), + ), + ); + + if (finaliseInfo.statusCode == 504) { + final confirmInfo = await _confirmUpload(dataItem.id); + onSendProgressTimer?.cancel(); + + return confirmInfo; + } + + logger.d('[${dataItem.id}] Upload finalised'); + + onSendProgressTimer?.cancel(); + + return finaliseInfo; + } catch (e) { + if (e is DioException) { + logger.d('[${dataItem.id}] Finalising upload failed, ${e.type}'); + } else if (_isCanceled) { + logger.d('[${dataItem.id}] Upload canceled'); + finalizeCancelToken.cancel(); + } + + onSendProgressTimer?.cancel(); + + rethrow; + } + } + + Future _confirmUpload(TxID dataItemId) async { + try { + logger.d('[$dataItemId] Confirming upload to Turbo'); + final response = await dio.get( + '$turboUploadUri/v1/tx/$dataItemId/status', + ); + + final responseData = response.data; + + if (responseData['status'] == 'CONFIRMED' || + responseData['status'] == 'FINALIZED') { + logger.d('[$dataItemId] DataItem confirmed!'); + return response; + } else { + logger.d( + '[$dataItemId] DataItem not confirmed. Retrying in ${dataItemConfirmationRetryDelay.toString()}'); + + await Future.delayed(dataItemConfirmationRetryDelay); + + return _confirmUpload(dataItemId); + } + } catch (e) { + await Future.delayed(dataItemConfirmationRetryDelay); + + return _confirmUpload(dataItemId); + } + } + + Future cancel() { + logger.d('Stream closed'); + + for (var cancelToken in _cancelTokens) { + cancelToken.cancel(); + } + + _isCanceled = true; + onSendProgressTimer?.cancel(); + return Future.value(); + } + + bool _isCanceled = false; + + int _calculateChunkSize({ + required int dataSize, + required int minChunkSize, + required int maxChunkSize, + }) { + getValidChunkSize(int chunkSize) { + if (chunkSize < minChunkSize) { + return minChunkSize; + } else if (chunkSize > maxChunkSize) { + return maxChunkSize; + } else { + return chunkSize; + } + } + + if (dataSize < GiB(1).size) { + return getValidChunkSize(MiB(5).size); + } else if (dataSize <= GiB(2).size) { + return getValidChunkSize(MiB(25).size); + } else { + return getValidChunkSize(MiB(50).size); + } + } + + Future _processStream( + Future Function(Uint8List, int) processChunk, { + required Stream stream, + required int chunkSize, + required int maxConcurrent, + required TxID dataItemId, + }) async { + logger.d('[$dataItemId] Processing DataItem stream'); + final chunkedStream = streamToChunks(stream, chunkSize); + logger.d('[$dataItemId] Stream chunked'); + final runningTasks = []; + int offset = 0; + + await for (final chunk in chunkedStream) { + if (runningTasks.length >= maxConcurrent) { + logger.d('[$dataItemId] Waiting for a task to finish'); + await Future.any(runningTasks); + } + + logger.d('[$dataItemId] Starting new task. Offset: $offset'); + final task = processChunk(chunk, offset); + task.whenComplete(() { + logger.d('[$dataItemId] Task completed. Offset: $offset'); + runningTasks.remove(task); + }); + + runningTasks.add(task); + + offset += chunk.length; + } + + logger.d('[$dataItemId] Waiting for all tasks to finish'); + await Future.wait(runningTasks); + } +} + +Stream streamToChunks( + Stream stream, + int chunkSize, +) async* { + var buffer = BytesBuilder(); + + await for (var uint8list in stream) { + buffer.add(uint8list); + + while (buffer.length >= chunkSize) { + final currentBytes = buffer.takeBytes(); + yield Uint8List.fromList(currentBytes.sublist(0, chunkSize)); + + buffer.add(currentBytes.sublist(chunkSize)); + } + } + + if (buffer.length > 0) { + yield buffer.toBytes(); + } +} diff --git a/packages/ardrive_uploader/lib/src/turbo_upload_service_base.dart b/packages/ardrive_uploader/lib/src/turbo_upload_service_base.dart deleted file mode 100644 index 3f1e3df94d..0000000000 --- a/packages/ardrive_uploader/lib/src/turbo_upload_service_base.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:arweave/arweave.dart'; - -export 'package:ardrive_uploader/src/turbo_upload_service_dart_io.dart' - if (dart.library.html) 'package:ardrive_uploader/src/turbo_upload_service_web.dart'; - -abstract class TurboUploadService { - Future postStream({ - required DataItemResult dataItem, - required Wallet wallet, - Function(double p1)? onSendProgress, - required int size, - required Map headers, - }); - - Future cancel(); -} diff --git a/packages/ardrive_uploader/lib/src/turbo_upload_service_dart_io.dart b/packages/ardrive_uploader/lib/src/turbo_upload_service_dart_io.dart deleted file mode 100644 index 5536374eab..0000000000 --- a/packages/ardrive_uploader/lib/src/turbo_upload_service_dart_io.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:async'; - -import 'package:ardrive_uploader/src/turbo_upload_service_base.dart'; -import 'package:arweave/arweave.dart'; -import 'package:dio/dio.dart'; - -class TurboUploadServiceImpl implements TurboUploadService { - final Uri turboUploadUri; - - /// We are using Dio directly here. In the future we must adapt our ArDriveHTTP to support - /// streaming uploads. - // ArDriveHTTP httpClient; - - TurboUploadServiceImpl({ - required this.turboUploadUri, - }); - - CancelToken _cancelToken = CancelToken(); - - /// We are using Dio directly here. In the future we must adapt our ArDriveHTTP to support - /// streaming uploads. - /// This is a temporary solution. - @override - Future postStream({ - required DataItemResult dataItem, - required Wallet wallet, - Function(double)? onSendProgress, - required int size, - required Map headers, - }) async { - final url = '$turboUploadUri/v1/tx'; - - final dio = Dio(); - - try { - final response = await dio.post( - url, - onSendProgress: (sent, total) { - onSendProgress?.call(sent / total); - }, - data: dataItem.streamGenerator(), // Creates a Stream>. - options: Options( - headers: { - // stream - Headers.contentTypeHeader: 'application/octet-stream', - Headers.contentLengthHeader: size, // Set the content-length. - }..addAll(headers), - ), - cancelToken: _cancelToken, - ); - print('Response from turbo: ${response.statusCode}'); - - return response; - } catch (e) { - if (_isCanceled) { - _cancelToken = CancelToken(); - - _cancelToken.cancel(); - } - - rethrow; - } - } - - @override - Future cancel() { - _cancelToken.cancel(); - print('Stream closed'); - _isCanceled = true; - return Future.value(); - } - - bool _isCanceled = false; -} - -class TurboUploadExceptions implements Exception {} - -class TurboUploadTimeoutException implements TurboUploadExceptions {} diff --git a/packages/ardrive_uploader/lib/src/turbo_upload_service_web.dart b/packages/ardrive_uploader/lib/src/turbo_upload_service_web.dart deleted file mode 100644 index aea418f189..0000000000 --- a/packages/ardrive_uploader/lib/src/turbo_upload_service_web.dart +++ /dev/null @@ -1,225 +0,0 @@ -import 'dart:async'; - -import 'package:ardrive_uploader/src/exceptions.dart'; -import 'package:ardrive_uploader/src/turbo_upload_service_base.dart'; -import 'package:ardrive_utils/ardrive_utils.dart'; -import 'package:arweave/arweave.dart'; -import 'package:dio/dio.dart'; -import 'package:fetch_client/fetch_client.dart'; -import 'package:flutter/foundation.dart'; -import 'package:http/http.dart' as http; - -class TurboUploadServiceImpl implements TurboUploadService { - final Uri turboUploadUri; - - /// We are using Dio directly here. In the future we must adapt our ArDriveHTTP to support - /// streaming uploads. - // ArDriveHTTP httpClient; - - TurboUploadServiceImpl({ - required this.turboUploadUri, - }); - - final _fetchController = StreamController>(sync: false); - - CancelToken _cancelToken = CancelToken(); - - final client = FetchClient( - mode: RequestMode.cors, - streamRequests: true, - cache: RequestCache.noCache, - handleBackPressure: true, - ); - - @override - Future postStream({ - required DataItemResult dataItem, - required Wallet wallet, - Function(double p1)? onSendProgress, - required int size, - required Map headers, - }) { - // max of 500mib - if (dataItem.dataItemSize <= MiB(500).size) { - return _uploadWithDio( - dataItem: dataItem, - wallet: wallet, - onSendProgress: onSendProgress, - size: size, - headers: headers, - ); - } - - return _uploadStreamWithFetchClient( - dataItem: dataItem, - wallet: wallet, - onSendProgress: onSendProgress, - size: size, - headers: headers, - ); - } - - Future _uploadWithDio({ - required DataItemResult dataItem, - required Wallet wallet, - Function(double p1)? onSendProgress, - required int size, - required Map headers, - }) async { - try { - final url = '$turboUploadUri/v1/tx'; - - final controller = StreamController(); - - controller - .addStream(dataItem.streamGenerator()) - .then((value) => controller.close()); - - final dio = Dio(); - - final response = await dio.post( - url, - onSendProgress: (sent, total) { - onSendProgress?.call(sent / total); - }, - data: controller.stream, - options: Options( - headers: { - Headers.contentTypeHeader: 'application/octet-stream', - Headers.contentLengthHeader: size, - }..addAll(headers), - ), - cancelToken: _cancelToken, - ); - - debugPrint('Response from turbo: ${response.statusCode}'); - - return response; - } on DioException catch (e) { - if (_isCanceled) { - _cancelToken = CancelToken(); - - _cancelToken.cancel(); - } - - throw DioClientException( - message: e.message ?? '', - statusCode: e.response?.statusCode, - error: e, - ); - } catch (e) { - throw UnknownNetworkException( - message: e.toString(), - error: e, - ); - } - } - - Future _uploadStreamWithFetchClient({ - required DataItemResult dataItem, - required Wallet wallet, - Function(double)? onSendProgress, - required int size, - required Map headers, - }) async { - final url = '$turboUploadUri/v1/tx'; - - int bytesUploaded = 0; - - final request = ArDriveStreamedRequest( - 'POST', - Uri.parse(url), - _fetchController, - )..headers.addAll({ - 'content-type': 'application/octet-stream', - }); - - // pass through transformer so we can track progress - final transformer = StreamTransformer.fromHandlers( - handleData: (data, sink) { - bytesUploaded += data.length; - onSendProgress?.call(bytesUploaded / size); - sink.add(data); - }, - ); - - _fetchController - .addStream(dataItem.streamGenerator().transform(transformer)) - .then((value) { - request.sink.close(); - }); - - _fetchController.onPause = () { - debugPrint('Paused'); - }; - - _fetchController.onResume = () { - debugPrint('Resumed'); - }; - - try { - request.persistentConnection = false; - - debugPrint('is persistent connection?${request.persistentConnection}'); - - final response = await client.send(request); - - return response; - } on http.ClientException catch (e) { - throw FetchClientException( - message: e.message, - error: e, - ); - } catch (e) { - throw FetchClientException( - message: e.toString(), - error: e, - ); - } finally { - _fetchController.close(); - } - } - - @override - Future cancel() async { - _cancelToken.cancel(); - client.close(); - _fetchController.close(); - _isCanceled = true; - debugPrint('Stream for Dio or FetchClient is closed'); - } - - bool _isCanceled = false; -} - -class TurboUploadExceptions implements Exception {} - -class TurboUploadTimeoutException implements TurboUploadExceptions {} - -class ArDriveStreamedRequest extends http.BaseRequest { - /// The sink to which to write data that will be sent as the request body. - /// - /// This may be safely written to before the request is sent; the data will be - /// buffered. - /// - /// Closing this signals the end of the request. - EventSink> get sink => _controller.sink; - - /// The controller for [sink], from which [BaseRequest] will read data for - /// [finalize]. - final StreamController> _controller; - - /// Creates a new streaming request. - ArDriveStreamedRequest( - String method, Uri url, StreamController> controller) - : _controller = controller, - super(method, url); - - /// Freezes all mutable fields and returns a single-subscription [ByteStream] - /// that emits the data being written to [sink]. - @override - http.ByteStream finalize() { - super.finalize(); - return http.ByteStream(_controller.stream); - } -} diff --git a/packages/ardrive_uploader/lib/src/upload_controller.dart b/packages/ardrive_uploader/lib/src/upload_controller.dart index badcb38847..568fa01ef7 100644 --- a/packages/ardrive_uploader/lib/src/upload_controller.dart +++ b/packages/ardrive_uploader/lib/src/upload_controller.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:ardrive_io/ardrive_io.dart'; import 'package:ardrive_uploader/src/data_bundler.dart'; +import 'package:ardrive_uploader/src/utils/logger.dart'; import 'package:arweave/arweave.dart'; import 'package:cryptography/cryptography.dart'; -import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; import 'package:uuid/uuid.dart'; @@ -71,6 +71,7 @@ class _UploadController implements UploadController { final Map _completedTasks = {}; final Map _failedTasks = {}; final Map _canceledTasks = {}; + final Map _inProgressTasks = {}; int _totalSize = 0; int _numberOfItems = 0; @@ -110,7 +111,7 @@ class _UploadController implements UploadController { subscription.cancel(); }, onError: (err) { - debugPrint('Error on UploadController: $err'); + logger.d('Error on UploadController: $err'); subscription.cancel(); }, ); @@ -128,11 +129,15 @@ class _UploadController implements UploadController { final existingTask = tasks[taskId]; final uploadItem = task.uploadItem; + if (task.progress == 1 && task.status == UploadStatus.inProgress) { + task = task.copyWith(status: UploadStatus.finalizing); + } + + tasks[taskId] = task; _updateTaskStatus(task, taskId); _updateTotalSize(task, uploadItem, existingTask); _updateProgress(task, uploadItem, existingTask); - tasks[taskId] = task; _progressStream.add(_generateUploadProgress()); } @@ -162,7 +167,7 @@ class _UploadController implements UploadController { .where((element) => element.status == UploadStatus.notStarted) .toList(), onWorkerError: (e) { - debugPrint('Error on UploadWorker. Task: ${e.toString()}'); + logger.d('Error on UploadWorker. Task: ${e.toString()}'); final updatedTask = tasks[e.id]!; updateProgress(task: updatedTask.copyWith(status: UploadStatus.failed)); @@ -209,7 +214,7 @@ class _UploadController implements UploadController { }) { final worker = UploadWorker( onError: (task, e) { - debugPrint('Error on UploadWorker. Task: ${e.toString()}'); + logger.d('Error on UploadWorker. Task: ${e.toString()}'); final updatedTask = tasks[task.id]!; updateProgress(task: updatedTask.copyWith(status: UploadStatus.failed)); }, @@ -260,20 +265,16 @@ class _UploadController implements UploadController { _progressStream.close(); _progressStream = StreamController.broadcast(); - final failedTasks = - tasks.values.where((e) => e.status == UploadStatus.failed).toList(); - - if (failedTasks.isEmpty) { - return Future.value(); - } + tasks.clear(); + _completedTasks.clear(); + _canceledTasks.clear(); + _inProgressTasks.clear(); _resetUploadProgress(); - _failedTasks.clear(); - _completedTasks.clear(); - tasks.clear(); + _progressStream.add(UploadProgress.notStarted()); - for (var task in failedTasks) { + for (var task in _failedTasks.values) { addTask( task.copyWith( status: UploadStatus.notStarted, @@ -283,6 +284,14 @@ class _UploadController implements UploadController { ); } + logger.d('Retrying failed tasks.'); + + for (var task in tasks.values) { + logger.d('Task: ${task.id} and progress ${task.progress}'); + } + + _failedTasks.clear(); + init(); // creates a worker pool and initializes it with the tasks @@ -297,7 +306,7 @@ class _UploadController implements UploadController { updateProgress(task: updatedTask.copyWith(status: UploadStatus.failed)); - debugPrint('Unknown error on UploadWorker. Task: ${e.toString()}'); + logger.d('Unknown error on UploadWorker. Task: ${e.toString()}'); }, upload: (task) async { final uploadResult = await _uploadDispatcher.send( @@ -406,11 +415,17 @@ class _UploadController implements UploadController { } _completedTasks[taskId] = task; _totalUploadedItems += task.content!.length; + _inProgressTasks.remove(taskId); break; case UploadStatus.failed: _failedTasks[taskId] = task; + _inProgressTasks.remove(taskId); + break; + case UploadStatus.inProgress: + _inProgressTasks[taskId] = task; break; default: + _inProgressTasks.remove(taskId); break; } } @@ -439,7 +454,8 @@ class _UploadController implements UploadController { UploadProgress _generateUploadProgress() { final progressInPercentage = _totalProgress / tasks.length; return _uploadProgress.copyWith( - task: tasks, + hasUploadInProgress: _inProgressTasks.isNotEmpty, + tasks: tasks, progressInPercentage: progressInPercentage, totalSize: _totalSize, totalUploaded: _totalUploaded, @@ -472,6 +488,9 @@ enum UploadStatus { /// The upload is prepartion is done: the bundle is ready to be uploaded preparationDone, + // The upload is being finalized + finalizing, + /// The upload is complete complete, @@ -487,19 +506,22 @@ class UploadProgress { final double progressInPercentage; final int totalSize; final int totalUploaded; - final Map task; + final Map tasks; final int numberOfItems; final int numberOfUploadedItems; + final bool hasUploadInProgress; + DateTime? startTime; UploadProgress({ required this.progressInPercentage, required this.totalSize, - required this.task, + required this.tasks, required this.totalUploaded, required this.numberOfItems, required this.numberOfUploadedItems, + required this.hasUploadInProgress, this.startTime, }); @@ -507,29 +529,32 @@ class UploadProgress { return UploadProgress( progressInPercentage: 0, totalSize: 0, - task: {}, + tasks: {}, totalUploaded: 0, numberOfItems: 0, numberOfUploadedItems: 0, + hasUploadInProgress: false, ); } UploadProgress copyWith({ double? progressInPercentage, int? totalSize, - Map? task, + Map? tasks, int? totalUploaded, DateTime? startTime, int? numberOfItems, int? numberOfUploadedItems, + bool? hasUploadInProgress, }) { return UploadProgress( + hasUploadInProgress: hasUploadInProgress ?? this.hasUploadInProgress, numberOfUploadedItems: numberOfUploadedItems ?? this.numberOfUploadedItems, startTime: startTime ?? this.startTime, progressInPercentage: progressInPercentage ?? this.progressInPercentage, totalSize: totalSize ?? this.totalSize, - task: task ?? this.task, + tasks: tasks ?? this.tasks, totalUploaded: totalUploaded ?? this.totalUploaded, numberOfItems: numberOfItems ?? this.numberOfItems, ); @@ -581,7 +606,7 @@ class UploadWorker { return; } catch (e) { - debugPrint('catched error on upload worker: $e'); + logger.d('catched error on upload worker: $e'); onError(task, e); } } @@ -626,10 +651,10 @@ class WorkerPool { void _initializeWorkers() { for (var i = 0; i < numWorkers; i++) { - debugPrint('Initializing worker with index $i'); + logger.d('Initializing worker with index $i'); for (var j = 0; j < maxTasksPerWorker; j++) { - debugPrint('Assigning task $j to worker with index $i'); + logger.d('Assigning task $j to worker with index $i'); _assignNextTask(i); } @@ -893,7 +918,7 @@ class UploadDispatcher { }, ); - debugPrint( + logger.d( 'Uploading task ${task.id} with strategy: ${_uploadFileStrategy.runtimeType}'); await _uploadFileStrategy.upload( @@ -916,7 +941,7 @@ class UploadDispatcher { return UploadResult(success: true); } catch (e, stacktrace) { - debugPrint('Error on UploadDispatcher.send: $e $stacktrace'); + logger.d('Error on UploadDispatcher.send: $e $stacktrace'); return UploadResult( success: false, error: e, diff --git a/packages/ardrive_uploader/lib/src/upload_strategy.dart b/packages/ardrive_uploader/lib/src/upload_strategy.dart index d7055d72a2..619b44cad5 100644 --- a/packages/ardrive_uploader/lib/src/upload_strategy.dart +++ b/packages/ardrive_uploader/lib/src/upload_strategy.dart @@ -4,8 +4,8 @@ import 'package:ardrive_uploader/ardrive_uploader.dart'; import 'package:ardrive_uploader/src/data_bundler.dart'; import 'package:ardrive_uploader/src/exceptions.dart'; import 'package:ardrive_uploader/src/utils/data_bundler_utils.dart'; +import 'package:ardrive_uploader/src/utils/logger.dart'; import 'package:arweave/arweave.dart'; -import 'package:flutter/foundation.dart'; abstract class UploadFileStrategy { Future upload({ @@ -46,19 +46,19 @@ class UploadFileUsingDataItemFiles extends UploadFileStrategy { wallet, ); - debugPrint('metadata uploaded for the file: ${task.metadataUploaded}'); + logger.d('metadata uploaded for the file: ${task.metadataUploaded}'); /// uploads the metadata item if it hasn't been uploaded yet. It can happen /// that the metadata item is uploaded but the data item is not, so we need /// to check for that. if (!task.metadataUploaded) { - debugPrint('uploading metadata for the file'); + logger.d('uploading metadata for the file'); final metadataItem = dataItemResults[0]; /// The upload can be canceled while the bundle is being created if (verifyCancel()) { - debugPrint('Upload canceled while data item was being created'); + logger.d('Upload canceled while data item was being created'); throw UploadCanceledException( 'Upload canceled while metadata item was being created', ); @@ -76,7 +76,7 @@ class UploadFileUsingDataItemFiles extends UploadFileStrategy { // we don't need to update the progress of the metadata item }); - debugPrint('metadata upload result: $uploadResult'); + logger.d('metadata upload result: $uploadResult'); if (!uploadResult.success) { throw MetadataUploadException( @@ -126,7 +126,7 @@ class UploadFileUsingDataItemFiles extends UploadFileStrategy { /// The upload can be canceled while the bundle is being created if (verifyCancel()) { - debugPrint('Upload canceled while data item was being created'); + logger.d('Upload canceled while data item was being created'); throw UploadCanceledException( 'Upload canceled while data data item was being created', ); @@ -157,7 +157,7 @@ class UploadFileUsingDataItemFiles extends UploadFileStrategy { ); if (!result.success) { - debugPrint('Failed to upload data item. Error: ${result.error}'); + logger.d('Failed to upload data item. Error: ${result.error}'); throw DataUploadException( message: 'Failed to upload data item. Error: ${result.error}', error: result.error, @@ -240,7 +240,7 @@ class UploadFileUsingBundleStrategy extends UploadFileStrategy { /// The upload can be canceled while the bundle is being created if (verifyCancel()) { - debugPrint('Upload canceled while bundle was being created'); + logger.d('Upload canceled while bundle was being created'); throw UploadCanceledException('Upload canceled'); } @@ -330,7 +330,7 @@ class UploadFolderStructureAsBundleStrategy } if (verifyCancel()) { - debugPrint('Upload canceled after bundle creation and before upload'); + logger.d('Upload canceled after bundle creation and before upload'); throw UploadCanceledException('Upload canceled on bundle creation'); } diff --git a/packages/ardrive_uploader/lib/src/utils/logger.dart b/packages/ardrive_uploader/lib/src/utils/logger.dart new file mode 100644 index 0000000000..6d92017cfa --- /dev/null +++ b/packages/ardrive_uploader/lib/src/utils/logger.dart @@ -0,0 +1,7 @@ +import 'package:ardrive_logger/ardrive_logger.dart'; + +final logger = Logger( + logLevel: LogLevel.debug, + storeLogsInMemory: true, + logExporter: LogExporter(), +); diff --git a/packages/ardrive_uploader/pubspec.yaml b/packages/ardrive_uploader/pubspec.yaml index 315f2c32fd..4bf3579fca 100644 --- a/packages/ardrive_uploader/pubspec.yaml +++ b/packages/ardrive_uploader/pubspec.yaml @@ -9,10 +9,6 @@ environment: dependencies: flutter: sdk: flutter - ardrive_http: - git: - url: https://github.com/ardriveapp/ardrive_http.git - ref: v1.3.2 ardrive_io: git: url: https://github.com/ardriveapp/ardrive_io.git @@ -31,30 +27,19 @@ dependencies: path: ../arfs pst: path: ../pst - json_annotation: ^4.8.0 uuid: ^3.0.4 - system_info_plus: ^0.0.5 cryptography: ^2.5.0 rxdart: ^0.27.7 - dio: ^5.3.2 + dio: ^5.4.0 fpdart: ^1.1.0 - fetch_client: - git: - url: https://github.com/ardriveapp/fetch_client.git - ref: PE-4754-address-code-review-comments-on-uploader-downloader-implementations - http: ^1.1.0 equatable: ^2.0.5 + retry: ^3.1.2 + ardrive_logger: + path: ../ardrive_logger dev_dependencies: lints: ^2.0.0 test: ^1.21.0 - json_serializable: build_runner: ^2.0.4 mocktail: ^1.0.2 - -dependency_overrides: - fetch_client: - git: - url: https://github.com/ardriveapp/fetch_client.git - ref: master diff --git a/packages/ardrive_uploader/test/factories_test.dart b/packages/ardrive_uploader/test/factories_test.dart index cc0ce901d8..0fa8b00640 100644 --- a/packages/ardrive_uploader/test/factories_test.dart +++ b/packages/ardrive_uploader/test/factories_test.dart @@ -2,7 +2,6 @@ import 'package:ardrive_uploader/ardrive_uploader.dart'; import 'package:ardrive_uploader/src/d2n_streamed_upload.dart'; import 'package:ardrive_uploader/src/data_bundler.dart'; import 'package:ardrive_uploader/src/turbo_streamed_upload.dart'; -import 'package:ardrive_uploader/src/turbo_upload_service_base.dart'; import 'package:arweave/arweave.dart'; import 'package:mocktail/mocktail.dart'; import 'package:pst/pst.dart'; @@ -103,10 +102,9 @@ void main() { var streamedUpload = uploadFactory.fromUploadType(UploadType.turbo); expect(streamedUpload, isA()); - // Additional check to verify TurboUploadServiceImpl initialization - var turboUploadUri = ((streamedUpload as TurboStreamedUpload).service - as TurboUploadServiceImpl) - .turboUploadUri; + // Additional check to verify TurboUploadService initialization + var turboUploadUri = + (streamedUpload as TurboStreamedUpload).service.turboUploadUri; expect(turboUploadUri, equals(mockUri)); }); }); diff --git a/packages/ardrive_uploader/test/turbo_upload_service_test.dart b/packages/ardrive_uploader/test/turbo_upload_service_test.dart new file mode 100644 index 0000000000..4d871f4b36 --- /dev/null +++ b/packages/ardrive_uploader/test/turbo_upload_service_test.dart @@ -0,0 +1,42 @@ +import 'dart:typed_data'; + +import 'package:ardrive_uploader/src/turbo_upload_service.dart'; +import 'package:test/test.dart'; + +void main() { + group('TurboUploadService', () { + group('streamToChunks', () { + test('should return a stream of chunks of the specified size', () async { + Stream largeStream = Stream.fromIterable([ + Uint8List.fromList(List.generate(10, (i) => i)), + Uint8List.fromList(List.generate(2, (i) => 10 + i)), + ]); + + final chunks = await streamToChunks(largeStream, 5).toList(); + expect(chunks.length, 3); // 12 bytes in total, 3 chunks + expect(chunks[0].length, 5); // First chunk is 5 bytes + expect(chunks[1].length, 5); // Second chunk is 5 bytes + expect(chunks[2].length, 2); // Third chunk is 2 bytes + }); + + test( + 'should return one chunk if the stream is smaller than the chunk size', + () async { + Stream smallStream = Stream.fromIterable([ + Uint8List.fromList(List.generate(2, (i) => i)), + ]); + + final chunks = await streamToChunks(smallStream, 5).toList(); + expect(chunks.length, 1); // 2 bytes in total, 1 chunk + expect(chunks[0].length, 2); // First chunk is 2 bytes + }); + + test('should return an empty stream if the input stream is empty', + () async { + Stream emptyStream = Stream.fromIterable([]); + final chunks = await streamToChunks(emptyStream, 5).toList(); + expect(chunks.length, 0); // empty stream, 0 chunks + }); + }); + }); +} diff --git a/pubspec.lock b/pubspec.lock index 5632b509e1..60614457f3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -531,10 +531,10 @@ packages: dependency: "direct main" description: name: dio - sha256: "417e2a6f9d83ab396ec38ff4ea5da6c254da71e4db765ad737a42af6930140b7" + sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" url: "https://pub.dev" source: hosted - version: "5.3.3" + version: "5.4.0" dio_smart_retry: dependency: transitive description: @@ -639,26 +639,26 @@ packages: dependency: transitive description: name: file_selector - sha256: "84eaf3e034d647859167d1f01cfe7b6352488f34c1b4932635012b202014c25b" + sha256: "070062b9fca7482baf60af671219086e188e50dd80497393e7d58532b56215d3" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" file_selector_android: dependency: transitive description: name: file_selector_android - sha256: d41e165d6f798ca941d536e5dc93494d50e78c571c28ad60cfe0b0fefeb9f1e7 + sha256: a2d50f9de59861605b866467a55d681a0ca7bc108129c0bd33e74fa602dd0249 url: "https://pub.dev" source: hosted - version: "0.5.0+3" + version: "0.5.0+5" file_selector_ios: dependency: transitive description: name: file_selector_ios - sha256: b3fbdda64aa2e335df6e111f6b0f1bb968402ed81d2dd1fa4274267999aa32c2 + sha256: b015154e6d9fddbc4d08916794df170b44531798c8dd709a026df162d07ad81d url: "https://pub.dev" source: hosted - version: "0.5.1+6" + version: "0.5.1+8" file_selector_linux: dependency: transitive description: @@ -780,10 +780,10 @@ packages: dependency: transitive description: name: flutter_downloader - sha256: bc13eb52ce81822a94f107b2ea0541b0e28be350f0c064dfbb5c66e95d7791f4 + sha256: e130001cf85d8d7450b8318a4670c19e495dd8c102ad4b15a512e9405e7c451d url: "https://pub.dev" source: hosted - version: "1.11.3" + version: "1.11.6" flutter_driver: dependency: "direct dev" description: flutter @@ -1147,34 +1147,34 @@ packages: dependency: transitive description: name: image_picker - sha256: "7d7f2768df2a8b0a3cefa5ef4f84636121987d403130e70b17ef7e2cf650ba84" + sha256: "26222b01a0c9a2c8fe02fc90b8208bd3325da5ed1f4a2acabf75939031ac0bdd" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.7" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "0c7b83bbe2980c8a8e36e974f055e11e51675784e13a4762889feed0f3937ff2" + sha256: "39f2bfe497e495450c81abcd44b62f56c2a36a37a175da7d137b4454977b51b1" url: "https://pub.dev" source: hosted - version: "0.8.8+1" + version: "0.8.9+3" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "50bc9ae6a77eea3a8b11af5eb6c661eeb858fdd2f734c2a4fd17086922347ef7" + sha256: e2423c53a68b579a7c37a1eda967b8ae536c3d98518e5db95ca1fe5719a730a3 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: c5538cacefacac733c724be7484377923b476216ad1ead35a0d2eadcdc0fc497 + sha256: fadafce49e8569257a0cad56d24438a6fa1f0cbd7ee0af9b631f7492818a4ca3 url: "https://pub.dev" source: hosted - version: "0.8.8+2" + version: "0.8.9+1" image_picker_linux: dependency: transitive description: @@ -1195,10 +1195,10 @@ packages: dependency: transitive description: name: image_picker_platform_interface - sha256: ed9b00e63977c93b0d2d2b343685bed9c324534ba5abafbb3dfbd6a780b1b514 + sha256: "0e827c156e3a90edd3bbe7f6de048b39247b16e58173b08a835b7eb00aba239e" url: "https://pub.dev" source: hosted - version: "2.9.1" + version: "2.9.2" image_picker_windows: dependency: transitive description: @@ -1584,10 +1584,10 @@ packages: dependency: transitive description: name: permission_handler_android - sha256: ace7d15a3d1a4a0b91c041d01e5405df221edb9de9116525efc773c74e6fc790 + sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e url: "https://pub.dev" source: hosted - version: "11.0.5" + version: "11.1.0" permission_handler_apple: dependency: transitive description: @@ -1600,10 +1600,10 @@ packages: dependency: transitive description: name: permission_handler_platform_interface - sha256: f2343e9fa9c22ae4fd92d4732755bfe452214e7189afcc097380950cf567b4b2 + sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" url: "https://pub.dev" source: hosted - version: "3.11.5" + version: "3.12.0" permission_handler_windows: dependency: transitive description: @@ -2074,14 +2074,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" - system_info_plus: - dependency: transitive - description: - name: system_info_plus - sha256: b915c811c6605b802f3988859bc2bb79c95f735762a75b5451741f7a2b949d1b - url: "https://pub.dev" - source: hosted - version: "0.0.5" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d413939502..f16538d0cf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Secure, permanent storage publish_to: 'none' -version: 2.34.3 +version: 2.35.0 environment: sdk: '>=3.0.2 <4.0.0' diff --git a/test/blocs/upload/upload_file_checker_test.dart b/test/blocs/upload/upload_file_checker_test.dart index b1f594bdda..471c38bff4 100644 --- a/test/blocs/upload/upload_file_checker_test.dart +++ b/test/blocs/upload/upload_file_checker_test.dart @@ -5,12 +5,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - final UploadFileChecker uploadFileChecker = UploadFileChecker( - privateFileSafeSizeLimit: 100, - publicFileSafeSizeLimit: 100, + final UploadFileSizeChecker uploadFileChecker = UploadFileSizeChecker( + fileSizeWarning: 100, + fileSizeLimit: 100, ); - group('hasFileAboveSafePublicSizeLimit', () { + group('hasFileAboveSizeLimit', () { late IOFile ioFileAboveSafeLimit; late IOFile ioFileUnderSafeLimit; @@ -28,14 +28,14 @@ void main() { }); test('should return false if the list of files is empty', () async { - final result = await uploadFileChecker.hasFileAboveSafePublicSizeLimit( + final result = await uploadFileChecker.hasFileAboveSizeLimit( files: [], ); expect(result, false); }); test('should return false if no files are above the limit', () async { - final result = await uploadFileChecker.hasFileAboveSafePublicSizeLimit( + final result = await uploadFileChecker.hasFileAboveSizeLimit( files: [ UploadFile( ioFile: ioFileUnderSafeLimit, @@ -48,7 +48,7 @@ void main() { }); test('should return true if a file is above the limit', () async { - final result = await uploadFileChecker.hasFileAboveSafePublicSizeLimit( + final result = await uploadFileChecker.hasFileAboveSizeLimit( files: [ UploadFile( ioFile: ioFileAboveSafeLimit, @@ -64,7 +64,7 @@ void main() { expect(result, true); }); test('should return true when all files are above limit', () async { - final result = await uploadFileChecker.hasFileAboveSafePublicSizeLimit( + final result = await uploadFileChecker.hasFileAboveSizeLimit( files: [ UploadFile( ioFile: ioFileAboveSafeLimit, @@ -85,7 +85,7 @@ void main() { }); }); - group('checkAndReturnFilesAbovePrivateLimit', () { + group('hasFileAboveWarningSizeLimit', () { late IOFile ioFileAboveSafeLimit; late IOFile ioFileUnderSafeLimit; @@ -102,9 +102,15 @@ void main() { ); }); - test('should return empty list if no files are above the limit', () async { - final result = - await uploadFileChecker.checkAndReturnFilesAbovePrivateLimit( + test('should return false if the list of files is empty', () async { + final result = await uploadFileChecker.hasFileAboveWarningSizeLimit( + files: [], + ); + + expect(result, false); + }); + test('should return false if no files are above the limit', () async { + final result = await uploadFileChecker.hasFileAboveWarningSizeLimit( files: [ UploadFile( ioFile: ioFileUnderSafeLimit, @@ -113,17 +119,67 @@ void main() { ], ); - expect(result, []); + expect(result, false); }); - test('should return list of files above the limit', () async { - final result = - await uploadFileChecker.checkAndReturnFilesAbovePrivateLimit( + test('should return true if a file is above the limit', () async { + final result = await uploadFileChecker.hasFileAboveWarningSizeLimit( + files: [ + UploadFile( + ioFile: ioFileAboveSafeLimit, + parentFolderId: 'parentFolderId', + ), + UploadFile( + ioFile: ioFileUnderSafeLimit, + parentFolderId: 'parentFolderId', + ), + ], + ); + + expect(result, true); + }); + test('should return true when all files are above limit', () async { + final result = await uploadFileChecker.hasFileAboveWarningSizeLimit( files: [ UploadFile( ioFile: ioFileAboveSafeLimit, parentFolderId: 'parentFolderId', ), + UploadFile( + ioFile: ioFileAboveSafeLimit, + parentFolderId: 'parentFolderId', + ), + UploadFile( + ioFile: ioFileAboveSafeLimit, + parentFolderId: 'parentFolderId', + ), + ], + ); + + expect(result, true); + }); + }); + + group('getFilesAboveSizeLimit', () { + late IOFile ioFileAboveSafeLimit; + late IOFile ioFileUnderSafeLimit; + + setUp(() async { + ioFileAboveSafeLimit = await IOFile.fromData( + Uint8List(101), // one byte above the limit + name: 'test.txt', + lastModifiedDate: DateTime.now(), + ); + ioFileUnderSafeLimit = await IOFile.fromData( + Uint8List(99), // one byte under the limit + name: 'test.txt', + lastModifiedDate: DateTime.now(), + ); + }); + + test('should return empty list if no files are above the limit', () async { + final result = await uploadFileChecker.getFilesAboveSizeLimit( + files: [ UploadFile( ioFile: ioFileUnderSafeLimit, parentFolderId: 'parentFolderId', @@ -131,12 +187,11 @@ void main() { ], ); - expect(result, ['test.txt']); + expect(result, []); }); test('should return list of all files above the limit', () async { - final result = - await uploadFileChecker.checkAndReturnFilesAbovePrivateLimit( + final result = await uploadFileChecker.getFilesAboveSizeLimit( files: [ UploadFile( ioFile: ioFileAboveSafeLimit, @@ -161,8 +216,7 @@ void main() { }); test('should return empty list if the list of files is empty', () async { - final result = - await uploadFileChecker.checkAndReturnFilesAbovePrivateLimit( + final result = await uploadFileChecker.getFilesAboveSizeLimit( files: [], ); diff --git a/test/blocs/upload_cubit_test.dart b/test/blocs/upload_cubit_test.dart index 0ae333f3cd..019644aa08 100644 --- a/test/blocs/upload_cubit_test.dart +++ b/test/blocs/upload_cubit_test.dart @@ -37,7 +37,7 @@ class MockPstService extends Mock implements PstService {} class MockUploadPlanUtils extends Mock implements UploadPlanUtils {} -class MockUploadFileChecker extends Mock implements UploadFileChecker {} +class MockUploadFileSizeChecker extends Mock implements UploadFileSizeChecker {} class MockTurboUploadService extends Mock implements TurboUploadService {} @@ -63,7 +63,7 @@ void main() { late MockPstService mockPst; late MockUploadPlanUtils mockUploadPlanUtils; MockProfileCubit? mockProfileCubit; - late MockUploadFileChecker mockUploadFileChecker; + late MockUploadFileSizeChecker mockUploadFileSizeChecker; late MockArDriveAuth mockArDriveAuth; late MockUploadCostEstimateCalculatorForAR mockUploadCostEstimateCalculatorForAR; @@ -170,7 +170,7 @@ void main() { mockDriveDao = db.driveDao; mockProfileCubit = MockProfileCubit(); mockUploadPlanUtils = MockUploadPlanUtils(); - mockUploadFileChecker = MockUploadFileChecker(); + mockUploadFileSizeChecker = MockUploadFileSizeChecker(); mockArDriveAuth = MockArDriveAuth(); mockUploadCostEstimateCalculatorForAR = MockUploadCostEstimateCalculatorForAR(); @@ -208,7 +208,7 @@ void main() { ); // mock limit for UploadFileChecker - when(() => mockUploadFileChecker.hasFileAboveSafePublicSizeLimit( + when(() => mockUploadFileSizeChecker.hasFileAboveSizeLimit( files: any(named: 'files'))).thenAnswer((invocation) async => false); const double stubArToUsdFactor = 10; when(() => mockArweave.getArUsdConversionRateOrNull()).thenAnswer( @@ -250,7 +250,7 @@ void main() { return UploadCubit( activityTracker: MockActivityTracker(), arDriveUploadManager: mockArDriveUploadPreparationManager, - uploadFileChecker: mockUploadFileChecker, + uploadFileSizeChecker: mockUploadFileSizeChecker, driveId: tDriveId, parentFolderId: tRootFolderId, files: files, @@ -421,7 +421,7 @@ void main() { blocTest( 'should show the warning when file checker found files above safe limit', setUp: () { - when(() => mockUploadFileChecker.hasFileAboveSafePublicSizeLimit( + when(() => mockUploadFileSizeChecker.hasFileAboveWarningSizeLimit( files: any(named: 'files'))) .thenAnswer((invocation) async => true); }, @@ -439,7 +439,7 @@ void main() { blocTest( 'should show the warning when file checker found files above safe limit and emit UploadReady when user confirm the upload', setUp: () { - when(() => mockUploadFileChecker.hasFileAboveSafePublicSizeLimit( + when(() => mockUploadFileSizeChecker.hasFileAboveSizeLimit( files: any(named: 'files'))) .thenAnswer((invocation) async => true); }, @@ -461,7 +461,7 @@ void main() { blocTest( 'should not show the warning when file checker not found files above safe limit and emit UploadReady without user confirmation', setUp: () { - when(() => mockUploadFileChecker.hasFileAboveSafePublicSizeLimit( + when(() => mockUploadFileSizeChecker.hasFileAboveSizeLimit( files: any(named: 'files'))) .thenAnswer((invocation) async => false); }, @@ -579,9 +579,8 @@ void main() { parentFolderId: tRootFolderId); tTooLargeFiles = [tTooLargeFile]; - when(() => - mockUploadFileChecker.checkAndReturnFilesAbovePrivateLimit( - files: any(named: 'files'))) + when(() => mockUploadFileSizeChecker.getFilesAboveSizeLimit( + files: any(named: 'files'))) .thenAnswer((invocation) async => ['some_file.txt']); }, build: () { @@ -617,9 +616,8 @@ void main() { lastModifiedDate: tDefaultDate, name: 'some_file.txt'), parentFolderId: tRootFolderId); - when(() => - mockUploadFileChecker.checkAndReturnFilesAbovePrivateLimit( - files: any(named: 'files'))) + when(() => mockUploadFileSizeChecker.getFilesAboveSizeLimit( + files: any(named: 'files'))) .thenAnswer((invocation) async => ['some_file.txt']); }, build: () { @@ -698,9 +696,8 @@ void main() { parentFolderId: tRootFolderId); when(() => mockProfileCubit!.isCurrentProfileArConnect()) .thenAnswer((i) => Future.value(false)); - when(() => - mockUploadFileChecker.checkAndReturnFilesAbovePrivateLimit( - files: any(named: 'files'))) + when(() => mockUploadFileSizeChecker.getFilesAboveSizeLimit( + files: any(named: 'files'))) .thenAnswer((invocation) async => ['some_file.txt']); }, build: () { diff --git a/test/test_utils/mocks.dart b/test/test_utils/mocks.dart index 8aba523af7..2e2c9f0417 100644 --- a/test/test_utils/mocks.dart +++ b/test/test_utils/mocks.dart @@ -76,7 +76,7 @@ class MockArConnectService extends Mock implements ArConnectService {} class MockTabVisibilitySingleton extends Mock implements TabVisibilitySingleton {} -class MockUploadFileChecker extends Mock implements UploadFileChecker {} +class MockUploadFileSizeChecker extends Mock implements UploadFileSizeChecker {} class MockSecureKeyValueStore extends Mock implements SecureKeyValueStore {}