Skip to content

Commit 3bf2b25

Browse files
committed
feat: Change file metadata scanning to store and access metadata through an interface
- Extract generic implementation from FileSetRepository - Implement FileMetadataSetRepository for storing FileMetadata sets - Make controllers single-use - Implement ArchivedFileMetadataSetRepository for storing ArchivedFileMetadata sets - Implement repository for change status checking - Integrate the new repositories with the backup and restore controllers - Update tests Resolves #752 {minor} Signed-off-by: Esta Nagy <nagyesta@gmail.com>
1 parent 0876365 commit 3bf2b25

File tree

68 files changed

+3053
-1831
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+3053
-1831
lines changed

.idea/codeStyles/Project.xml

Lines changed: 10 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

file-barj-core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ val copyLegalDocs = tasks.register<Copy>("copyLegalDocs") {
5959
rename("artifacts.json", "dependency-licenses.json")
6060
rename("bom.json", "SBOM.json")
6161
}.get()
62+
tasks.cyclonedxDirectBom.get().finalizedBy(":file-barj-stream-io:jar")
6263
copyLegalDocs.dependsOn(tasks.licensee)
6364
copyLegalDocs.dependsOn(tasks.cyclonedxDirectBom)
6465
tasks.javadoc.get().dependsOn(copyLegalDocs)

file-barj-core/src/main/java/com/github/nagyesta/filebarj/core/backup/pipeline/BackupController.java

Lines changed: 176 additions & 103 deletions
Large diffs are not rendered by default.

file-barj-core/src/main/java/com/github/nagyesta/filebarj/core/backup/pipeline/ParallelBackupPipeline.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ public class ParallelBackupPipeline extends BaseBackupPipeline<ParallelBarjCargo
3333
* @param threadCount The number of threads
3434
* @throws IOException When the stream cannot be created due to an I/O error
3535
*/
36-
public ParallelBackupPipeline(final @NotNull BackupIncrementManifest manifest,
37-
final int threadCount) throws IOException {
36+
public ParallelBackupPipeline(
37+
final @NotNull BackupIncrementManifest manifest,
38+
final int threadCount) throws IOException {
3839
super(manifest, convert(manifest, threadCount));
3940
}
4041

file-barj-core/src/main/java/com/github/nagyesta/filebarj/core/backup/worker/DefaultBackupScopePartitioner.java

Lines changed: 0 additions & 70 deletions
This file was deleted.

file-barj-core/src/main/java/com/github/nagyesta/filebarj/core/backup/worker/PosixFileMetadataParser.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ public class PosixFileMetadataParser implements FileMetadataParser {
3838
public @NotNull FileMetadata parse(
3939
final @NonNull File file,
4040
final @NonNull BackupJobConfiguration configuration) {
41+
final var absolutePath = BackupPath.of(file.toPath().toAbsolutePath());
4142
if (!Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS)) {
4243
return FileMetadata.builder()
4344
.id(UUID.randomUUID())
44-
.absolutePath(BackupPath.of(file.toPath().toAbsolutePath()))
45+
.absolutePath(absolutePath)
4546
.fileType(FileType.MISSING)
4647
.status(Change.DELETED)
4748
.build();
@@ -52,7 +53,7 @@ public class PosixFileMetadataParser implements FileMetadataParser {
5253
return FileMetadata.builder()
5354
.id(UUID.randomUUID())
5455
.fileSystemKey(Optional.ofNullable(basicAttributes.fileKey()).map(String::valueOf).orElse(null))
55-
.absolutePath(BackupPath.of(file.toPath().toAbsolutePath()))
56+
.absolutePath(absolutePath)
5657
.owner(posixFileAttributes.owner())
5758
.group(posixFileAttributes.group())
5859
.posixPermissions(posixFileAttributes.permissions())

file-barj-core/src/main/java/com/github/nagyesta/filebarj/core/common/BackupSourceScanner.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import com.fasterxml.jackson.annotation.JsonIgnore;
44
import com.github.nagyesta.filebarj.core.config.BackupSource;
55
import com.github.nagyesta.filebarj.core.model.BackupPath;
6-
import com.github.nagyesta.filebarj.core.persistence.FileSetRepository;
7-
import com.github.nagyesta.filebarj.core.persistence.entities.FileSetId;
6+
import com.github.nagyesta.filebarj.core.persistence.FilePathSetRepository;
7+
import com.github.nagyesta.filebarj.core.persistence.entities.FilePathSetId;
88
import lombok.NonNull;
99
import org.apache.commons.io.FilenameUtils;
1010
import org.jetbrains.annotations.NotNull;
@@ -32,17 +32,17 @@ public class BackupSourceScanner {
3232
*/
3333
private static final Set<String> EXCLUDE_NO_FILES = Set.of();
3434

35-
private final FileSetRepository fileSetRepository;
35+
private final FilePathSetRepository filePathSetRepository;
3636
private final BackupSource backupSource;
3737
private final Path backupSourceOsPath;
3838
private final SortedSet<String> includePatterns;
3939
private final SortedSet<String> excludePatterns;
4040
private final boolean shouldUsePatterns;
4141

4242
public BackupSourceScanner(
43-
final @NonNull FileSetRepository fileSetRepository,
43+
final @NonNull FilePathSetRepository filePathSetRepository,
4444
final @NonNull BackupSource backupSource) {
45-
this.fileSetRepository = fileSetRepository;
45+
this.filePathSetRepository = filePathSetRepository;
4646
this.backupSource = backupSource;
4747
this.backupSourceOsPath = backupSource.getPath().toOsPath();
4848
final var originalIncludes = backupSource.getIncludePatterns();
@@ -55,11 +55,11 @@ public BackupSourceScanner(
5555
/**
5656
* Lists the matching {@link Path} entries.
5757
*
58-
* @param resultFileSetId the Id of the file se where we should collect the results
58+
* @param resultFilePathSetId the Id of the file se where we should collect the results
5959
*/
6060
@JsonIgnore
61-
public void listMatchingFilePaths(final @NonNull FileSetId resultFileSetId) {
62-
try (var tempFileSetId = fileSetRepository.createFileSet()) {
61+
public void listMatchingFilePaths(final @NonNull FilePathSetId resultFilePathSetId) {
62+
try (var tempFileSetId = filePathSetRepository.createFileSet()) {
6363
var current = Optional.of(backupSourceOsPath);
6464
var parentDirsFromBackupSource = List.<Path>of();
6565
Path lastParent = null;
@@ -69,8 +69,8 @@ public void listMatchingFilePaths(final @NonNull FileSetId resultFileSetId) {
6969
lastParent = currentPath.getParent();
7070
parentDirsFromBackupSource = findParentsFromBackupSource(currentPath);
7171
}
72-
listRemainingFiles(currentPath, parentDirsFromBackupSource, resultFileSetId, tempFileSetId);
73-
current = fileSetRepository.takeFirst(tempFileSetId);
72+
listRemainingFiles(currentPath, parentDirsFromBackupSource, resultFilePathSetId, tempFileSetId);
73+
current = filePathSetRepository.takeFirst(tempFileSetId);
7474
}
7575
}
7676
}
@@ -88,8 +88,8 @@ private List<Path> findParentsFromBackupSource(final @NotNull Path path) {
8888
private void listRemainingFiles(
8989
final @NotNull Path currentPath,
9090
final @NotNull List<Path> parentDirsFromBackupSource,
91-
final @NotNull FileSetId sourceFileSetIt,
92-
final @NotNull FileSetId tempFileSetId) {
91+
final @NotNull FilePathSetId sourceFileSetIt,
92+
final @NotNull FilePathSetId tempFilePathSetId) {
9393
if (!currentPath.toFile().exists()) {
9494
return;
9595
}
@@ -101,21 +101,21 @@ private void listRemainingFiles(
101101
return;
102102
}
103103
//add all parents if they are not saved yet
104-
fileSetRepository.appendTo(sourceFileSetIt, parentDirsFromBackupSource);
105-
fileSetRepository.appendTo(sourceFileSetIt, currentPath);
104+
filePathSetRepository.appendTo(sourceFileSetIt, parentDirsFromBackupSource);
105+
filePathSetRepository.appendTo(sourceFileSetIt, currentPath);
106106
} else {
107107
if (isCurrentDirectoryInSourceSetByPatterns(currentPath)) {
108108
//always add directories if the include patterns match and the exclude patterns do not match
109-
fileSetRepository.appendTo(sourceFileSetIt, parentDirsFromBackupSource);
110-
fileSetRepository.appendTo(sourceFileSetIt, currentPath);
109+
filePathSetRepository.appendTo(sourceFileSetIt, parentDirsFromBackupSource);
110+
filePathSetRepository.appendTo(sourceFileSetIt, currentPath);
111111
}
112112
if (hasChildren(currentPath)) {
113113
Optional.ofNullable(currentPath.toFile().listFiles())
114114
.stream()
115115
.flatMap(Arrays::stream)
116116
.map(File::toPath)
117117
.filter(child -> !shouldIgnoreByExcludePattern(child))
118-
.forEach((Path child) -> fileSetRepository.appendTo(tempFileSetId, child));
118+
.forEach((Path child) -> filePathSetRepository.appendTo(tempFilePathSetId, child));
119119
}
120120
}
121121
}

file-barj-core/src/main/java/com/github/nagyesta/filebarj/core/common/BaseFileMetadataChangeDetector.java

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
import com.github.nagyesta.filebarj.core.model.FileMetadata;
55
import com.github.nagyesta.filebarj.core.model.enums.Change;
66
import com.github.nagyesta.filebarj.core.model.enums.FileType;
7+
import com.github.nagyesta.filebarj.core.persistence.FileMetadataSetRepository;
8+
import com.github.nagyesta.filebarj.core.persistence.entities.FileMetadataSetId;
79
import lombok.NonNull;
10+
import org.apache.commons.lang3.function.TriFunction;
811
import org.jetbrains.annotations.NotNull;
912
import org.jetbrains.annotations.Nullable;
1013

@@ -17,26 +20,23 @@
1720
*/
1821
public abstract class BaseFileMetadataChangeDetector<T> implements FileMetadataChangeDetector {
1922

20-
private final SortedMap<String, Map<UUID, FileMetadata>> filesFromManifests;
21-
private final SortedMap<String, Map<T, List<FileMetadata>>> contentIndex;
22-
private final Map<String, FileMetadata> nameIndex;
23+
private final FileMetadataSetRepository repository;
24+
private final SortedMap<String, FileMetadataSetId> filesFromManifests;
2325
private final PermissionComparisonStrategy permissionComparisonStrategy;
2426

2527
/**
2628
* Creates a new instance with the previous manifests.
2729
*
30+
* @param repository The repository we can use to access the metadata
2831
* @param filesFromManifests The files found in the previous manifests
2932
* @param permissionStrategy The permission comparison strategy
3033
*/
3134
protected BaseFileMetadataChangeDetector(
32-
final @NotNull Map<String, Map<UUID, FileMetadata>> filesFromManifests,
35+
final @NotNull FileMetadataSetRepository repository,
36+
final @NotNull Map<String, FileMetadataSetId> filesFromManifests,
3337
final @Nullable PermissionComparisonStrategy permissionStrategy) {
38+
this.repository = repository;
3439
this.filesFromManifests = new TreeMap<>(filesFromManifests);
35-
final SortedMap<String, Map<T, List<FileMetadata>>> contentIndexSet = new TreeMap<>();
36-
final Map<String, FileMetadata> nameIndexMap = new TreeMap<>();
37-
index(this.filesFromManifests, contentIndexSet, nameIndexMap);
38-
this.contentIndex = contentIndexSet;
39-
this.nameIndex = nameIndexMap;
4040
this.permissionComparisonStrategy = Objects.requireNonNullElse(permissionStrategy, PermissionComparisonStrategy.STRICT);
4141
}
4242

@@ -54,23 +54,24 @@ public boolean hasMetadataChanged(
5454
@Override
5555
public boolean isFromLastIncrement(
5656
final @NonNull FileMetadata fileMetadata) {
57-
return filesFromManifests.get(filesFromManifests.lastKey()).containsKey(fileMetadata.getId());
57+
return repository.containsFileId(filesFromManifests.get(filesFromManifests.lastKey()), fileMetadata.getId());
5858
}
5959

6060
@Override
6161
public @Nullable FileMetadata findMostRelevantPreviousVersion(
6262
final @NonNull FileMetadata currentMetadata) {
6363
final var increments = filesFromManifests.keySet().stream().sorted(Comparator.reverseOrder()).toList();
64-
final var previousSamePath = nameIndex.getOrDefault(currentMetadata.getAbsolutePath().toString(), null);
64+
final var previousSamePath = findPreviousVersionByAbsolutePath(currentMetadata.getAbsolutePath());
6565
if (previousSamePath != null && !hasContentChanged(previousSamePath, currentMetadata)) {
6666
return previousSamePath;
6767
}
6868
for (final var increment : increments) {
69-
final var index = contentIndex.get(increment);
69+
final var index = filesFromManifests.get(increment);
7070
final var key = getPrimaryContentCriteria(currentMetadata);
71-
if (index.containsKey(key)) {
71+
final var files = findFilesByPrimaryContentCriteria().apply(repository, index, key);
72+
if (!files.isEmpty()) {
7273
final var byPath = new TreeMap<BackupPath, FileMetadata>();
73-
index.get(key).stream()
74+
files.stream()
7475
.filter(metadata -> !hasContentChanged(metadata, currentMetadata))
7576
.forEach(metadata -> byPath.put(metadata.getAbsolutePath(), metadata));
7677
if (!byPath.isEmpty()) {
@@ -84,7 +85,17 @@ public boolean isFromLastIncrement(
8485
@Override
8586
public @Nullable FileMetadata findPreviousVersionByAbsolutePath(
8687
final @NonNull BackupPath absolutePath) {
87-
return nameIndex.get(absolutePath.toString());
88+
return filesFromManifests.keySet()
89+
.stream().sorted(Comparator.reverseOrder())
90+
.map(increment -> {
91+
final var fileMetadataSetId = filesFromManifests.get(increment);
92+
return repository.findFileByPath(fileMetadataSetId, absolutePath)
93+
.filter(metadata -> metadata.getStatus() != Change.DELETED);
94+
})
95+
.filter(Optional::isPresent)
96+
.map(Optional::get)
97+
.findFirst()
98+
.orElse(null);
8899
}
89100

90101
@Override
@@ -114,22 +125,11 @@ public boolean isFromLastIncrement(
114125
*/
115126
protected abstract T getPrimaryContentCriteria(@NotNull FileMetadata metadata);
116127

117-
private void index(
118-
final @NotNull SortedMap<String, Map<UUID, FileMetadata>> filesFromManifests,
119-
final @NotNull SortedMap<String, Map<T, List<FileMetadata>>> contentIndexMap,
120-
final @NotNull Map<String, FileMetadata> nameIndexMap) {
121-
filesFromManifests.forEach((increment, files) -> files
122-
.forEach((uuid, metadata) -> contentIndexMap
123-
.computeIfAbsent(increment, k -> new HashMap<>())
124-
.computeIfAbsent(getPrimaryContentCriteria(metadata), k -> new ArrayList<>())
125-
.add(metadata)));
126-
//populate files in reverse manifest order to ensure each file has the latest metadata saved
127-
filesFromManifests.keySet().stream()
128-
.sorted(Comparator.reverseOrder())
129-
.map(filesFromManifests::get)
130-
.forEachOrdered(files -> files.entrySet().stream()
131-
.filter(entry -> entry.getValue().getStatus() != Change.DELETED)
132-
//put the file only if it is not already in the index
133-
.forEach(entry -> nameIndexMap.putIfAbsent(entry.getValue().getAbsolutePath().toString(), entry.getValue())));
134-
}
128+
/**
129+
* Returns a function that can find the files from the repository by matching based on the primary content criteria.
130+
*
131+
* @return filter function
132+
*/
133+
protected abstract
134+
TriFunction<FileMetadataSetRepository, FileMetadataSetId, T, Set<FileMetadata>> findFilesByPrimaryContentCriteria();
135135
}

0 commit comments

Comments
 (0)