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

Bulk import #1973

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
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
177 changes: 177 additions & 0 deletions lib/blocs/bulk_import/bulk_import_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import 'dart:async';

import 'package:ardrive/authentication/ardrive_auth.dart';
import 'package:ardrive/blocs/bulk_import/bulk_import_event.dart';
import 'package:ardrive/blocs/bulk_import/bulk_import_state.dart';
import 'package:ardrive/core/arfs/use_cases/bulk_import_files.dart';
import 'package:ardrive/manifests/domain/repositories/manifest_repository.dart';
import 'package:ardrive/utils/logger.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

/// BLoC for managing the bulk import process.
class BulkImportBloc extends Bloc<BulkImportEvent, BulkImportState> {
final BulkImportFiles _bulkImportFiles;
final ManifestRepository _manifestRepository;
final ArDriveAuth _ardriveAuth;

BulkImportBloc({
required BulkImportFiles bulkImportFiles,
required ManifestRepository manifestRepository,
required ArDriveAuth ardriveAuth,
}) : _bulkImportFiles = bulkImportFiles,
_manifestRepository = manifestRepository,
_ardriveAuth = ardriveAuth,
super(const BulkImportInitial()) {
on<StartManifestBulkImport>(_onStartManifestBulkImport);
on<CancelBulkImport>(_onCancelBulkImport);
on<ResetBulkImport>(_onResetBulkImport);
}

Future<void> _onStartManifestBulkImport(
StartManifestBulkImport event,
Emitter<BulkImportState> emit,
) async {
try {
emit(BulkImportLoadingManifest(event.manifestTxId));

// Get manifest data
final manifestResult =
await _manifestRepository.getManifest(event.manifestTxId);

if (manifestResult.failure != null) {
emit(BulkImportError(
'Failed to load manifest: ${manifestResult.failure!.message}',
));
return;
}

final manifest = manifestResult.manifest!;

// Convert manifest paths to ManifestFileEntry objects
final files = manifest.paths.entries.map((entry) {
final path = entry.key;
final data = entry.value;
return ManifestFileEntry(
id: data['id'] as String,
path: path,
name: path.split('/').last,
dataTxId: data['id'] as String,
contentType:
data['contentType'] as String? ?? 'application/octet-stream',
size: data['size'] as int? ?? 0,
);
}).toList();

// Start importing files
emit(BulkImportInProgress(
manifestTxId: event.manifestTxId,
fileIds: files.map((f) => f.id).toList(),
processedFiles: 0,
failedFiles: const [],
currentFileName: 'Starting import...',
failedPaths: const [],
isCreatingFolderHierarchy: true,
));

var processedFiles = 0;
final failedPaths = <String>[];

// Import files from manifest
try {
// Update progress
emit(BulkImportInProgress(
manifestTxId: event.manifestTxId,
fileIds: files.map((f) => f.id).toList(),
processedFiles: processedFiles,
failedFiles: failedPaths,
currentFileName: files.first.name,
failedPaths: failedPaths,
isCreatingFolderHierarchy: true,
));

// Import file
await _bulkImportFiles(
driveId: event.driveId,
parentFolderId: event.parentFolderId,
files: files,
onCreateFolderHierarchyStart: () {
final state = this.state;

if (state is BulkImportInProgress) {
emit(state.copyWith(
isCreatingFolderHierarchy: true,
));
}
},
onFileProgress: (fileName) {
final state = this.state;
processedFiles++;

if (state is BulkImportInProgress) {
emit(state.copyWith(
currentFileName: fileName,
processedFiles: processedFiles,
));
}
},
onCreateFolderHierarchyEnd: () {
final state = this.state;

if (state is BulkImportInProgress) {
emit(state.copyWith(
isCreatingFolderHierarchy: false,
));
}
},
wallet: _ardriveAuth.currentUser.wallet,
userCipherKey: _ardriveAuth.currentUser.cipherKey,
);

processedFiles++;
} catch (e) {
failedPaths.add(files.first.path);
logger.e('Failed to import file: ${files.first.path}', e);
}

final totalFiles = files.length;
final successfulFiles = processedFiles;
final failedFiles = failedPaths;

// Handle result
if (successfulFiles == 0) {
// All files failed to import
emit(const BulkImportError(
'Failed to import any files. Please check the manifest and try again.',
));
} else {
// Complete or partial success
emit(BulkImportSuccess(
manifestTxId: event.manifestTxId,
totalFiles: totalFiles,
successfulFiles: successfulFiles,
failedFiles: failedFiles.length,
));
}
} catch (e) {
logger.e('Error during bulk import', e);
emit(BulkImportError(
'An unexpected error occurred during the import process. Please try again.',
e,
));
}
}

void _onCancelBulkImport(
CancelBulkImport event,
Emitter<BulkImportState> emit,
) {
emit(const BulkImportInitial());
}

void _onResetBulkImport(
ResetBulkImport event,
Emitter<BulkImportState> emit,
) {
emit(const BulkImportInitial());
}
}
33 changes: 33 additions & 0 deletions lib/blocs/bulk_import/bulk_import_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:equatable/equatable.dart';

/// Events for the bulk import process.
abstract class BulkImportEvent extends Equatable {
const BulkImportEvent();

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

/// Event to start a bulk import using a manifest transaction ID
class StartManifestBulkImport extends BulkImportEvent {
final String manifestTxId;
final String driveId;
final String parentFolderId;

const StartManifestBulkImport({
required this.manifestTxId,
required this.driveId,
required this.parentFolderId,
});

@override
List<Object> get props => [manifestTxId, driveId, parentFolderId];
}

/// Event to cancel the bulk import process
class CancelBulkImport extends BulkImportEvent {
const CancelBulkImport();
}

/// Event to reset the bulk import state to initial.
class ResetBulkImport extends BulkImportEvent {}
139 changes: 139 additions & 0 deletions lib/blocs/bulk_import/bulk_import_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import 'package:equatable/equatable.dart';

/// States for the bulk import process.
abstract class BulkImportState extends Equatable {
const BulkImportState();

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

/// Initial state before any bulk import action
class BulkImportInitial extends BulkImportState {
const BulkImportInitial();
}

/// State when downloading and parsing manifest data
class BulkImportLoadingManifest extends BulkImportState {
final String manifestTxId;

const BulkImportLoadingManifest(this.manifestTxId);

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

/// State when importing files from manifest
class BulkImportInProgress extends BulkImportState {
final String manifestTxId;
final List<String> fileIds;
final int processedFiles;
final List<String> failedFiles;
final String currentFileName;
final List<String> failedPaths;
final bool isCreatingFolderHierarchy;

const BulkImportInProgress({
required this.manifestTxId,
required this.fileIds,
required this.processedFiles,
required this.failedFiles,
required this.currentFileName,
required this.failedPaths,
required this.isCreatingFolderHierarchy,
});

@override
List<Object> get props => [
manifestTxId,
fileIds,
processedFiles,
failedFiles,
currentFileName,
failedPaths,
isCreatingFolderHierarchy,
];

// copy with
BulkImportInProgress copyWith({
bool? isCreatingFolderHierarchy,
String? currentFileName,
int? processedFiles,
}) {
return BulkImportInProgress(
manifestTxId: manifestTxId,
fileIds: fileIds,
processedFiles: processedFiles ?? this.processedFiles,
failedFiles: failedFiles,
currentFileName: currentFileName ?? this.currentFileName,
failedPaths: failedPaths,
isCreatingFolderHierarchy:
isCreatingFolderHierarchy ?? this.isCreatingFolderHierarchy,
);
}

double get progress => fileIds.isEmpty ? 0 : processedFiles / fileIds.length;
}

/// State when bulk import is completed
class BulkImportSuccess extends BulkImportState {
final String manifestTxId;
final int totalFiles;
final int successfulFiles;
final int failedFiles;

const BulkImportSuccess({
required this.manifestTxId,
required this.totalFiles,
required this.successfulFiles,
required this.failedFiles,
});

@override
List<Object> get props => [
manifestTxId,
totalFiles,
successfulFiles,
failedFiles,
];
}

/// State when bulk import encounters an error
class BulkImportError extends BulkImportState {
final String message;
final Object? error;

const BulkImportError(this.message, [this.error]);

@override
List<Object?> get props => [message, error];
}

class BulkImportResolvingPaths extends BulkImportState {
final int totalPaths;
final int processedPaths;

const BulkImportResolvingPaths({
required this.totalPaths,
required this.processedPaths,
});

@override
List<Object?> get props => [totalPaths, processedPaths];
}

class BulkImportCreatingFolders extends BulkImportState {
final int totalFolders;
final int processedFolders;
final String currentFolderPath;

const BulkImportCreatingFolders({
required this.totalFolders,
required this.processedFolders,
required this.currentFolderPath,
});

@override
List<Object?> get props =>
[totalFolders, processedFolders, currentFolderPath];
}
Loading
Loading