Skip to content

Commit

Permalink
Merge pull request #1736 from ardriveapp/PE-6113-fix
Browse files Browse the repository at this point in the history
PE-6113: add a modal to filter success uploads
  • Loading branch information
thiagocarvalhodev authored Aug 19, 2024
2 parents f592959 + 4c36450 commit e269382
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 56 deletions.
4 changes: 3 additions & 1 deletion lib/blocs/upload/enums/conflicting_files_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
/// `Skip` Will ignore the files and don't upload them.
///
/// `Replace` will upload the conflicting file and replace the existent.
enum UploadActions { skip, replace }
///
/// `SkipSuccessfulUploads` will skip the files that were successfully uploaded.
enum UploadActions { skip, skipSuccessfulUploads, replace }
73 changes: 66 additions & 7 deletions lib/blocs/upload/upload_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ class UploadCubit extends Cubit<UploadState> {
/// Map of conflicting file ids keyed by their file names.
final Map<String, String> conflictingFiles = {};
final List<String> conflictingFolders = [];
List<String> failedFiles = [];

UploadCubit({
required this.driveId,
Expand Down Expand Up @@ -312,32 +313,81 @@ class UploadCubit extends Cubit<UploadState> {
checkConflicts();
}

Future<void> checkConflictingFiles() async {
Future<void> checkConflictingFiles({
bool checkFailedFiles = true,
}) async {
emit(UploadPreparationInProgress());

_removeFilesWithFolderNameConflicts();

for (final file in files) {
final fileName = file.ioFile.name;
final existingFileId = await _driveDao
final existingFileIds = await _driveDao
.filesInFolderWithName(
driveId: _targetDrive.id,
parentFolderId: file.parentFolderId,
name: fileName,
)
.map((f) => f.id)
.getSingleOrNull();
.get();

if (existingFileIds.isNotEmpty) {
final existingFileId = existingFileIds.first;

if (existingFileId != null) {
logger.d('Found conflicting file. Existing file id: $existingFileId');
conflictingFiles[file.getIdentifier()] = existingFileId;
}
}

if (conflictingFiles.isNotEmpty) {
if (checkFailedFiles) {
failedFiles.clear();

conflictingFiles.forEach((key, value) {
logger.d('Checking if file $key has failed');
});

for (final fileNameKey in conflictingFiles.keys) {
final fileId = conflictingFiles[fileNameKey];

final fileRevision = await _driveDao
.latestFileRevisionByFileId(
driveId: driveId,
fileId: fileId!,
)
.getSingleOrNull();

final status = _driveDao.select(_driveDao.networkTransactions)
..where((tbl) => tbl.id.equals(fileRevision!.dataTxId));

final transaction = await status.getSingleOrNull();

logger.d('Transaction status: ${transaction?.status}');

if (transaction?.status == TransactionStatus.failed) {
failedFiles.add(fileNameKey);
}
}

logger.d('Failed files: $failedFiles');

if (failedFiles.isNotEmpty) {
emit(
UploadConflictWithFailedFiles(
areAllFilesConflicting: conflictingFiles.length == files.length,
conflictingFileNames: conflictingFiles.keys.toList(),
conflictingFileNamesForFailedFiles: failedFiles,
),
);
return;
}
}

emit(
UploadFileConflict(
areAllFilesConflicting: conflictingFiles.length == files.length,
conflictingFileNames: conflictingFiles.keys.toList(),
conflictingFileNamesForFailedFiles: const [],
),
);
} else {
Expand Down Expand Up @@ -369,19 +419,20 @@ class UploadCubit extends Cubit<UploadState> {
)
.map((f) => f.id)
.getSingleOrNull();
final existingFileId = await _driveDao
final existingFileIds = await _driveDao
.filesInFolderWithName(
driveId: driveId,
name: folder.name,
parentFolderId: folder.parentFolderId,
)
.map((f) => f.id)
.getSingleOrNull();
.get();

if (existingFolderId != null) {
folder.id = existingFolderId;
foldersToSkip.add(folder);
}
if (existingFileId != null) {
if (existingFileIds.isNotEmpty) {
conflictingFolders.add(folder.name);
}
}
Expand Down Expand Up @@ -419,6 +470,8 @@ class UploadCubit extends Cubit<UploadState> {

if (uploadAction == UploadActions.skip) {
_removeFilesWithFileNameConflicts();
} else if (uploadAction == UploadActions.skipSuccessfulUploads) {
_removeSuccessfullyUploadedFiles();
}

logger.d(
Expand Down Expand Up @@ -939,6 +992,12 @@ class UploadCubit extends Cubit<UploadState> {
);
}

void _removeSuccessfullyUploadedFiles() {
files.removeWhere(
(file) => !failedFiles.contains(file.getIdentifier()),
);
}

void _removeFilesWithFolderNameConflicts() {
files.removeWhere((file) => conflictingFolders.contains(file.ioFile.name));
}
Expand Down
12 changes: 12 additions & 0 deletions lib/blocs/upload/upload_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,33 @@ class UploadSigningInProgress extends UploadState {

class UploadFileConflict extends UploadState {
final List<String> conflictingFileNames;
final List<String> conflictingFileNamesForFailedFiles;

final bool areAllFilesConflicting;

UploadFileConflict({
required this.conflictingFileNames,
required this.areAllFilesConflicting,
required this.conflictingFileNamesForFailedFiles,
});

@override
List<Object> get props => [conflictingFileNames];
}

class UploadConflictWithFailedFiles extends UploadFileConflict {
UploadConflictWithFailedFiles({
required super.conflictingFileNames,
required super.areAllFilesConflicting,
super.conflictingFileNamesForFailedFiles = const <String>[],
});
}

class UploadFolderNameConflict extends UploadFileConflict {
UploadFolderNameConflict({
required super.conflictingFileNames,
required super.areAllFilesConflicting,
super.conflictingFileNamesForFailedFiles = const <String>[],
});
}

Expand Down
138 changes: 93 additions & 45 deletions lib/components/upload_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -288,63 +288,111 @@ class _UploadFormState extends State<UploadForm> {
),
],
);
} else if (state is UploadFileConflict) {
} else if (state is UploadConflictWithFailedFiles) {
return ArDriveStandardModal(
title: appLocalizationsOf(context)
.duplicateFiles(state.conflictingFileNames.length),
content: SizedBox(
width: kMediumDialogWidth,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appLocalizationsOf(context)
.filesWithTheSameNameAlreadyExists(
state.conflictingFileNames.length,
title: 'Retry Failed Uploads?',
content: SizedBox(
width: kMediumDialogWidth,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'There are ${state.conflictingFileNamesForFailedFiles.length} file(s) marked with a red dot, indicating they failed to upload. Would you like to retry uploading these files by replacing the failed versions? This action will only affect the failed uploads and will not alter any successfully uploaded files. Alternatively, you can choose to skip these files and proceed with the others.',
style: ArDriveTypography.body.buttonNormalRegular(),
),
const SizedBox(height: 16),
Text(
'Conflicting files',
style: ArDriveTypography.body.buttonNormalRegular(),
),
const SizedBox(height: 8),
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 320),
child: SingleChildScrollView(
child: Text(
state.conflictingFileNamesForFailedFiles
.join(', \n'),
style: ArDriveTypography.body.buttonNormalRegular(),
),
style: ArDriveTypography.body.buttonNormalRegular(),
),
const SizedBox(height: 16),
Text(
appLocalizationsOf(context).conflictingFiles,
style: ArDriveTypography.body.buttonNormalRegular(),
),
],
),
),
actions: [
ModalAction(
action: () => context
.read<UploadCubit>()
.checkConflictingFiles(checkFailedFiles: false),
title: appLocalizationsOf(context).skipEmphasized,
),
ModalAction(
action: () => context
.read<UploadCubit>()
.prepareUploadPlanAndCostEstimates(
uploadAction: UploadActions.skipSuccessfulUploads),
title: 'Replace failed uploads',
),
],
);
} else if (state is UploadFileConflict) {
return ArDriveStandardModal(
title: appLocalizationsOf(context)
.duplicateFiles(state.conflictingFileNames.length),
content: SizedBox(
width: kMediumDialogWidth,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appLocalizationsOf(context)
.filesWithTheSameNameAlreadyExists(
state.conflictingFileNames.length,
),
const SizedBox(height: 8),
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 320),
child: SingleChildScrollView(
child: Text(
state.conflictingFileNames.join(', \n'),
style:
ArDriveTypography.body.buttonNormalRegular(),
),
style: ArDriveTypography.body.buttonNormalRegular(),
),
const SizedBox(height: 16),
Text(
appLocalizationsOf(context).conflictingFiles,
style: ArDriveTypography.body.buttonNormalRegular(),
),
const SizedBox(height: 8),
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 320),
child: SingleChildScrollView(
child: Text(
state.conflictingFileNames.join(', \n'),
style: ArDriveTypography.body.buttonNormalRegular(),
),
),
],
),
),
actions: [
if (!state.areAllFilesConflicting)
ModalAction(
action: () => context
.read<UploadCubit>()
.prepareUploadPlanAndCostEstimates(
uploadAction: UploadActions.skip),
title: appLocalizationsOf(context).skipEmphasized,
),
ModalAction(
action: () => Navigator.of(context).pop(false),
title: appLocalizationsOf(context).cancelEmphasized,
),
],
),
),
actions: [
if (!state.areAllFilesConflicting)
ModalAction(
action: () => context
.read<UploadCubit>()
.prepareUploadPlanAndCostEstimates(
uploadAction: UploadActions.replace),
title: appLocalizationsOf(context).replaceEmphasized,
uploadAction: UploadActions.skip),
title: appLocalizationsOf(context).skipEmphasized,
),
]);
ModalAction(
action: () => Navigator.of(context).pop(false),
title: appLocalizationsOf(context).cancelEmphasized,
),
ModalAction(
action: () => context
.read<UploadCubit>()
.prepareUploadPlanAndCostEstimates(
uploadAction: UploadActions.replace),
title: appLocalizationsOf(context).replaceEmphasized,
),
],
);
} else if (state is UploadFileTooLarge) {
return ArDriveStandardModal(
title: appLocalizationsOf(context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ query FirstDriveEntityWithIdOwner($driveId: String!, $after: String) {
tags: [
{ name: "Drive-Id", values: [$driveId] }
{ name: "Entity-Type", values: ["drive"] }
{ name: "Drive-Privacy", values: ["public", "private"]}
]
) {
edges {
Expand Down
13 changes: 10 additions & 3 deletions test/blocs/upload_cubit_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,12 @@ void main() {
const TypeMatcher<UploadPreparationInProgress>(),
UploadFileConflict(
areAllFilesConflicting: true,
conflictingFileNames: const ['${tRootFolderId}1']),
conflictingFileNames: const [
'${tRootFolderId}1'
],
conflictingFileNamesForFailedFiles: const [
'${tRootFolderId}1'
]),
]);

blocTest<UploadCubit, UploadState>(
Expand All @@ -362,8 +367,10 @@ void main() {
const TypeMatcher<UploadPreparationInitialized>(),
const TypeMatcher<UploadPreparationInProgress>(),
UploadFileConflict(
areAllFilesConflicting: false,
conflictingFileNames: const ['${tRootFolderId}1'])
areAllFilesConflicting: false,
conflictingFileNames: const ['${tRootFolderId}1'],
conflictingFileNamesForFailedFiles: const ['${tRootFolderId}1'],
)
]);

blocTest<UploadCubit, UploadState>(
Expand Down

0 comments on commit e269382

Please sign in to comment.