Skip to content
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

PE-5761: Release ArDrive App v2.37.0 #1647

Merged
merged 11 commits into from
Mar 1, 2024
Merged
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
2 changes: 2 additions & 0 deletions android/fastlane/metadata/android/en-US/changelogs/112.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Enhances user experience and efficiency of Turbo uploads
- Fixes issue with upload folders using AR
5 changes: 4 additions & 1 deletion lib/components/upload_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -822,7 +822,10 @@ class _UploadFormState extends State<UploadForm> {
}

return ArDriveStandardModal(
width: kLargeDialogWidth,
hasCloseButton: true,
width: state.failedTasks != null
? kLargeDialogWidth
: kMediumDialogWidth,
title: 'Problem with Upload',
description: appLocalizationsOf(context).yourUploadFailed,
content: state.failedTasks != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ 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.dart';
import 'package:arweave/arweave.dart';
import 'package:ardrive_uploader/src/utils/logger.dart';
import 'package:arweave/arweave.dart';
import 'package:flutter/foundation.dart';

class TurboStreamedUpload implements StreamedUpload<UploadItem> {
Expand Down
102 changes: 72 additions & 30 deletions packages/ardrive_uploader/lib/src/turbo_upload_service.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:math';
import 'dart:typed_data';

import 'package:ardrive_uploader/src/exceptions.dart';
Expand All @@ -17,7 +18,6 @@ class TurboUploadService {
final r = RetryOptions(maxAttempts: 8);
final List<CancelToken> _cancelTokens = [];
final dio = Dio();
final dataItemConfirmationRetryDelay = Duration(seconds: 15);
final maxInFlightData = MiB(100).size;
Timer? onSendProgressTimer;

Expand Down Expand Up @@ -138,20 +138,23 @@ class TurboUploadService {

final finaliseInfo = await r.retry(
() => dio.post(
'$turboUploadUri/chunks/arweave/$uploadId/-1',
'$turboUploadUri/chunks/arweave/$uploadId/finalize',
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);
if (finaliseInfo.statusCode == 202) {
// TODO: Send this upload to a queue. We'd need to change the
// type of the returned data though. Perhaps the returned object
// could be an event emitter that the calling client can use to
// listen for async outcomes like finalization success/failure.
final confirmInfo = await _confirmUpload(
dataItemId: dataItem.id,
uploadId: uploadId,
dataItemSize: dataItem.dataItemSize,
);

onSendProgressTimer?.cancel();

return confirmInfo;
Expand All @@ -176,32 +179,56 @@ class TurboUploadService {
}
}

Future<Response> _confirmUpload(TxID dataItemId) async {
try {
logger.d('[$dataItemId] Confirming upload to Turbo');
// TODO: This funciton as designed should go away, but some incremental
// improvements that could be helpful:
// - Don't use recursion. Use a while loop instead.
// - Have a max retry count and/or max finalization time
// - Make retries based on an exponential backoff rather than a fixed delay
// - Make starting time for first wait based on some linear function of file size
// - Don't keep retrying infinitely in the case of errors.
Future<Response> _confirmUpload({
required TxID dataItemId,
required String uploadId,
required int dataItemSize,
}) async {
final fileSizeInGiB = (dataItemSize.toDouble() / GiB(1).size).ceil();
final maxWaitTime = Duration(minutes: fileSizeInGiB);
logger.d(
'[$dataItemId] Confirming upload to Turbo with uploadId $uploadId for up to ${maxWaitTime.inMinutes} minutes.');

final startTime = DateTime.now();
final cutoffTime = startTime.add(maxWaitTime);
int attemptCount = 0;

while (DateTime.now().isBefore(cutoffTime)) {
final response = await dio.get(
'$turboUploadUri/v1/tx/$dataItemId/status',
'$turboUploadUri/chunks/arweave/$uploadId/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);
final responseStatus = responseData['status'];
switch (responseStatus) {
case 'FINALIZED':
logger.d('[$dataItemId] DataItem confirmed!');
return response;
case 'UNDERFUNDED':
throw UploadCanceledException('Upload canceled. Underfunded.');
case 'ASSEMBLING':
case 'VALIDATING':
case 'FINALIZING':
final retryAfterDuration =
dataItemConfirmationRetryDelay(attemptCount++);
logger.d(
'[$dataItemId] DataItem not confirmed. Retrying in ${retryAfterDuration.inMilliseconds}ms');

await Future.delayed(retryAfterDuration);
default:
throw UploadCanceledException(
'Upload canceled. Finalization failed. Status: ${responseData['status']}');
}
} catch (e) {
await Future.delayed(dataItemConfirmationRetryDelay);

return _confirmUpload(dataItemId);
}
throw UploadCanceledException(
'Upload canceled. Finalization took too long.');
}

Future<void> cancel() {
Expand Down Expand Up @@ -299,3 +326,18 @@ Stream<Uint8List> streamToChunks(
yield buffer.toBytes();
}
}

final defaultBaseDuration = Duration(milliseconds: 250);

Duration dataItemConfirmationRetryDelay(
int iteration, {
Duration baseDuration = const Duration(milliseconds: 100),
Duration maxDuration = const Duration(seconds: 8),
}) {
return Duration(
milliseconds: min(
baseDuration.inMilliseconds * pow(2, iteration).toInt(),
maxDuration.inMilliseconds,
),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Future<List<DataItemResult>> createDataItemResultFromDataItemFiles(
Wallet wallet,
) async {
final List<DataItemResult> dataItemList = [];
final dataItemCount = 2;
final dataItemCount = dataItemList.length;
for (var i = 0; i < dataItemCount; i++) {
final dataItem = dataItems[i];
await createDataItemTaskEither(
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Secure, permanent storage

publish_to: 'none'

version: 2.36.0
version: 2.37.0

environment:
sdk: '>=3.0.2 <4.0.0'
Expand Down
Loading