diff --git a/lib/blocs/upload/upload_cubit.dart b/lib/blocs/upload/upload_cubit.dart index 97ee77f3d..8362bafbb 100644 --- a/lib/blocs/upload/upload_cubit.dart +++ b/lib/blocs/upload/upload_cubit.dart @@ -112,7 +112,9 @@ class UploadCubit extends Cubit { UploadMethod? _manifestUploadMethod; bool _isManifestsUploadCancelled = false; - bool _isUploadingCustomManifest = false; + + /// if true, the file will change its content type to `application/x.arweave-manifest+json` + bool _uploadFileAsCustomManifest = false; void updateManifestSelection(List selections) { _selectedManifestModels.clear(); @@ -130,7 +132,7 @@ class UploadCubit extends Cubit { } void setIsUploadingCustomManifest(bool value) { - _isUploadingCustomManifest = value; + _uploadFileAsCustomManifest = value; emit((state as UploadReady).copyWith(isUploadingCustomManifest: value)); } @@ -449,9 +451,8 @@ class UploadCubit extends Cubit { bool showArnsCheckbox = false; if (_targetDrive.isPublic && _files.length == 1) { - final isACustomManifest = await isCustomManifest(_files.first.ioFile); - - logger.d('Is a custom manifest: $isACustomManifest'); + final fileIsACustomManifest = + await isCustomManifest(_files.first.ioFile); emit( UploadReady( @@ -473,8 +474,8 @@ class UploadCubit extends Cubit { arnsRecords: _ants, showReviewButtonText: false, selectedManifestSelections: _selectedManifestModels, - isCustomManifest: isACustomManifest, - isUploadingCustomManifest: false, + shouldShowCustomManifestCheckbox: fileIsACustomManifest, + uploadFileAsCustomManifest: false, ), ); @@ -519,8 +520,9 @@ class UploadCubit extends Cubit { canShowSettings: showSettings, showReviewButtonText: false, selectedManifestSelections: _selectedManifestModels, - isCustomManifest: false, // only applies for single file uploads - isUploadingCustomManifest: false, + uploadFileAsCustomManifest: false, + // only applies for single file uploads + shouldShowCustomManifestCheckbox: false, ), ); } @@ -1099,7 +1101,7 @@ class UploadCubit extends Cubit { return; } - if (_isUploadingCustomManifest) { + if (_uploadFileAsCustomManifest) { final fileWithCustomContentType = await IOFile.fromData( await _files.first.ioFile.readAsBytes(), name: _files.first.ioFile.name, diff --git a/lib/blocs/upload/upload_state.dart b/lib/blocs/upload/upload_state.dart index ea89a5b35..2f24e32df 100644 --- a/lib/blocs/upload/upload_state.dart +++ b/lib/blocs/upload/upload_state.dart @@ -115,8 +115,9 @@ class UploadReady extends UploadState { final bool isArConnect; final bool showReviewButtonText; - final bool isCustomManifest; - final bool isUploadingCustomManifest; + final bool shouldShowCustomManifestCheckbox; + final bool uploadFileAsCustomManifest; + UploadReady({ required this.paymentInfo, required this.uploadIsPublic, @@ -137,8 +138,8 @@ class UploadReady extends UploadState { required this.arnsRecords, required this.showReviewButtonText, required this.selectedManifestSelections, - required this.isCustomManifest, - required this.isUploadingCustomManifest, + required this.shouldShowCustomManifestCheckbox, + required this.uploadFileAsCustomManifest, }); // copyWith @@ -189,9 +190,10 @@ class UploadReady extends UploadState { showReviewButtonText: showReviewButtonText ?? this.showReviewButtonText, selectedManifestSelections: selectedManifestSelections ?? this.selectedManifestSelections, - isCustomManifest: isCustomManifest ?? this.isCustomManifest, - isUploadingCustomManifest: - isUploadingCustomManifest ?? this.isUploadingCustomManifest, + shouldShowCustomManifestCheckbox: + isCustomManifest ?? this.shouldShowCustomManifestCheckbox, + uploadFileAsCustomManifest: + isUploadingCustomManifest ?? this.uploadFileAsCustomManifest, ); } diff --git a/lib/components/upload_form.dart b/lib/components/upload_form.dart index 9db202434..46300290d 100644 --- a/lib/components/upload_form.dart +++ b/lib/components/upload_form.dart @@ -1975,11 +1975,11 @@ class _UploadReadyWidget extends StatelessWidget { ), ), ], - if (state.isCustomManifest) ...[ + if (state.shouldShowCustomManifestCheckbox) ...[ const SizedBox(height: 8), ArDriveCheckBox( title: 'Convert this file to an Arweave manifest.', - checked: state.isUploadingCustomManifest, + checked: state.uploadFileAsCustomManifest, useNewIcons: true, titleStyle: typography.paragraphNormal( fontWeight: ArFontWeight.semiBold, diff --git a/lib/utils/is_custom_manifest.dart b/lib/utils/is_custom_manifest.dart index a5793778a..41e2201bb 100644 --- a/lib/utils/is_custom_manifest.dart +++ b/lib/utils/is_custom_manifest.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:ardrive/utils/logger.dart'; import 'package:ardrive_io/ardrive_io.dart'; /// Checks if a file is an Arweave manifest file by examining its content type and contents. @@ -7,16 +8,33 @@ import 'package:ardrive_io/ardrive_io.dart'; /// Returns true if the file has JSON content type and contains the string "arweave/paths", /// which indicates it follows the Arweave path manifest specification. Future isCustomManifest(IOFile file) async { - if (file.contentType == 'application/json') { - /// Read the first 100 bytes of the file - final first100Bytes = file.openReadStream(0, 100); + try { + if (file.contentType == 'application/json') { + final fileLength = await file.length; - await for (var bytes in first100Bytes) { - // verify if file contains "arweave/paths"f - if (utf8.decode(bytes).contains('arweave/paths')) { + int bytesToRead = 100; + + if (fileLength < bytesToRead) { + bytesToRead = fileLength; + } + + /// Read the first 100 bytes of the file + final first100Bytes = file.openReadStream(0, bytesToRead); + + String content = ''; + + await for (var bytes in first100Bytes) { + content += utf8.decode(bytes); + } + + /// verify if file contains "arweave/paths" + if (content.contains('arweave/paths')) { return true; } } + return false; + } catch (e) { + logger.e('Error checking if file is a custom manifest', e); + return false; } - return false; } diff --git a/test/utils/is_custom_manifest_test.dart b/test/utils/is_custom_manifest_test.dart index 8d5e4d71a..23fbfebaf 100644 --- a/test/utils/is_custom_manifest_test.dart +++ b/test/utils/is_custom_manifest_test.dart @@ -27,7 +27,7 @@ void main() { when(() => mockFile.contentType).thenReturn('application/json'); when(() => mockFile.openReadStream(any(), any())) .thenAnswer((_) => Stream.value(Uint8List.fromList(bytes))); - + when(() => mockFile.length).thenReturn(bytes.length); // Act final result = await isCustomManifest(mockFile); @@ -45,19 +45,20 @@ void main() { when(() => mockFile.contentType).thenReturn('application/json'); when(() => mockFile.openReadStream(any(), any())) .thenAnswer((_) => Stream.value(Uint8List.fromList(bytes))); + when(() => mockFile.length).thenReturn(bytes.length); // Act final result = await isCustomManifest(mockFile); // Assert expect(result, false); - verify(() => mockFile.openReadStream(0, 100)).called(1); + verify(() => mockFile.openReadStream(0, 33)).called(1); }); test('returns false when file is not JSON', () async { // Arrange when(() => mockFile.contentType).thenReturn('text/plain'); - + when(() => mockFile.length).thenReturn(0); // Act final result = await isCustomManifest(mockFile); @@ -71,13 +72,13 @@ void main() { when(() => mockFile.contentType).thenReturn('application/json'); when(() => mockFile.openReadStream(any(), any())) .thenAnswer((_) => Stream.value(Uint8List(0))); - + when(() => mockFile.length).thenReturn(0); // Act final result = await isCustomManifest(mockFile); // Assert expect(result, false); - verify(() => mockFile.openReadStream(0, 100)).called(1); + verify(() => mockFile.openReadStream(0, 0)).called(1); }); }); }