createIncrementalBackup function
Implementation
Future<IncrementalBackupResult> createIncrementalBackup({
required Directory sourceDirectory,
required Directory backupDirectory,
Future<void> Function(BackupProgress progress)? onProgress,
}) async {
await backupDirectory.create(recursive: true);
final existingBackups = await listManagedLocalBackups(backupDirectory);
final stateFile =
File('${backupDirectory.path}/$_incrementalBackupStateName');
final stateFileExists = await stateFile.exists();
var forceFullBackup =
await File('${backupDirectory.path}/$_forceFullBackupMarkerName')
.exists();
var previousState =
forceFullBackup ? null : await _readState(backupDirectory);
if (!forceFullBackup && stateFileExists && previousState == null) {
forceFullBackup = true;
}
if (previousState != null &&
!_isBackupChainComplete(previousState.backupChain, existingBackups)) {
previousState = null;
forceFullBackup = true;
}
final snapshot = await _readSourceSnapshot(
sourceDirectory,
reusableSignatures: previousState?.files,
);
previousState ??= forceFullBackup
? const _BackupState(files: {}, backupChain: [])
: await _stateFromLegacyBackup(snapshot, existingBackups);
previousState = await _recoverMissingContentHashes(
previousState,
snapshot,
backupDirectory,
);
previousState = await _recoverMissingComparisonHashes(
previousState,
backupDirectory,
);
previousState = _withoutOperationalBackupFiles(previousState);
final localIdMappings = await _readLocalIdMappings(sourceDirectory);
final remappedState = _remapBackupState(
previousState,
localIdMappings,
snapshot.signatures.keys.toSet(),
);
previousState = remappedState.state;
final equivalentImageRemap = _remapEquivalentImagePaths(
previousState,
snapshot,
);
previousState = equivalentImageRemap.state;
final movedFiles = <String, String>{
...previousState.pendingMovedFiles,
...remappedState.movedFiles,
...equivalentImageRemap.movedFiles,
};
final changedPaths = snapshot.signatures.keys
.where((path) => previousState!.files[path] != snapshot.signatures[path])
.toList()
..sort();
final deletedPaths = previousState.files.keys
.where((path) => !snapshot.signatures.containsKey(path))
.toList()
..sort();
debugPrint(
'Incremental backup plan: ${snapshot.signatures.length} files, '
'${changedPaths.length} changed, ${deletedPaths.length} deleted',
);
if (changedPaths.isEmpty && deletedPaths.isEmpty) {
await _writeState(
backupDirectory,
_BackupState(
files: snapshot.signatures,
backupChain: previousState.backupChain,
pendingMovedFiles: movedFiles,
),
);
return const IncrementalBackupResult(
backupFile: null,
changedFileCount: 0,
deletedFileCount: 0,
);
}
final backupFile = await _nextBackupFile(backupDirectory);
final temporaryFile = File('${backupFile.path}.partial');
File? completedBackup;
try {
final manifest = {
'version': 1,
'createdAt': DateTime.now().toUtc().toIso8601String(),
'baseBackup': previousState.backupChain.isEmpty
? null
: previousState.backupChain.last,
'changedFiles': changedPaths,
'deletedFiles': deletedPaths,
'movedFiles': movedFiles,
'snapshot': snapshot.signatures.map(
(path, signature) => MapEntry(path, signature.toJson()),
),
'backupChain': [
...previousState.backupChain,
_basename(backupFile.path),
],
};
await _writeArchiveInBackground(
sourceDirectory: sourceDirectory,
temporaryFile: temporaryFile,
changedPaths: changedPaths,
manifestJson: jsonEncode(manifest),
readableStructure: _buildReadableBackupStructure(snapshot),
onProgress: onProgress,
);
completedBackup = await temporaryFile.rename(backupFile.path);
final nextState = _BackupState(
files: snapshot.signatures,
backupChain: [
...previousState.backupChain,
_basename(completedBackup.path),
],
);
await _writeState(backupDirectory, nextState);
final forceMarker =
File('${backupDirectory.path}/$_forceFullBackupMarkerName');
if (await forceMarker.exists()) {
await forceMarker.delete();
}
if (changedPaths.isEmpty) {
await onProgress?.call(
const BackupProgress(
progress: _archiveProgressLimit,
currentFile: 'Gelöschte Dateien',
eta: 'weniger als 10 s',
processedFiles: 0,
totalFiles: 0,
),
);
}
await onProgress?.call(
BackupProgress(
progress: 1,
currentFile: 'Backup abgeschlossen',
eta: '0 s',
processedFiles: changedPaths.length,
totalFiles: changedPaths.length,
),
);
return IncrementalBackupResult(
backupFile: completedBackup,
changedFileCount: changedPaths.length,
deletedFileCount: deletedPaths.length,
);
} catch (_) {
if (await temporaryFile.exists()) {
await temporaryFile.delete();
}
if (completedBackup != null && await completedBackup.exists()) {
await completedBackup.delete();
}
rethrow;
}
}