DefaultFileManager.java
- package net.morimekta.providence.storage.dir;
- import net.morimekta.util.FileUtil;
- import javax.annotation.Nonnull;
- import java.io.IOException;
- import java.io.UncheckedIOException;
- import java.nio.file.FileVisitResult;
- import java.nio.file.FileVisitor;
- import java.nio.file.Files;
- import java.nio.file.Path;
- import java.nio.file.attribute.BasicFileAttributes;
- import java.util.Collection;
- import java.util.HashSet;
- import java.util.function.Function;
- /**
- * File manager for the {@link net.morimekta.providence.storage.DirectoryMessageStore}
- * and {@link net.morimekta.providence.storage.DirectoryMessageListStore} store
- * classes that keeps all files in a plain directory tree, and keeps a <code>.tmp</code>
- * directory for temporary files.
- *
- * @param <K> The key type.
- */
- public class DefaultFileManager<K> implements FileManager<K> {
- private static final String TMP_DIR = ".tmp";
- private final Path directory;
- private final Path tempDir;
- private final Function<Path, K> keyParser;
- private final Function<K, Path> keyBuilder;
- public DefaultFileManager(@Nonnull Path directory,
- @Nonnull Function<K, Path> keyBuilder,
- @Nonnull Function<Path, K> keyParser) {
- try {
- if (!Files.isDirectory(directory)) {
- throw new IllegalArgumentException("Not a directory: " + directory.toString());
- }
- this.directory = FileUtil.readCanonicalPath(directory);
- this.tempDir = this.directory.resolve(TMP_DIR);
- if (!Files.exists(tempDir)) {
- Files.createDirectories(tempDir);
- } else if (!Files.isDirectory(tempDir)) {
- throw new IllegalStateException("File blocking temp directory: " + tempDir.toString());
- }
- this.keyBuilder = keyBuilder;
- this.keyParser = keyParser;
- } catch (IOException e) {
- throw new UncheckedIOException(e.getMessage(), e);
- }
- }
- @Override
- public Path getFileFor(@Nonnull K key) {
- Path path = keyBuilder.apply(key);
- return directory.resolve(validatePath(path, path));
- }
- @Override
- public Path tmpFileFor(@Nonnull K key) {
- Path path = keyBuilder.apply(key);
- return tempDir.resolve(validatePath(path, path));
- }
- @Override
- public Collection<K> initialKeySet() {
- try {
- HashSet<K> set = new HashSet<>();
- Files.walkFileTree(directory, new PathFileVisitor(set));
- return set;
- } catch (IOException e) {
- throw new IllegalStateException("Storage directory no longer a directory.", e);
- }
- }
- private Path validatePath(Path key, Path originalKey) {
- if (key.isAbsolute()) {
- throw new IllegalArgumentException("Absolute path key not allowed: " + originalKey);
- }
- if (key.getFileName().toString().isEmpty()) {
- throw new IllegalArgumentException("Empty segment in path key: " + originalKey);
- }
- // Make sure that no '.' or '..' part of the path. Meaning each part of the
- // path must be non-hidden entity.
- if (key.getFileName().toString().startsWith(".")) {
- throw new IllegalArgumentException("Special '.' not allowed in path segment start: " + originalKey);
- }
- if (key.getParent() != null) {
- validatePath(key.getParent(), originalKey);
- }
- return key;
- }
- private class PathFileVisitor implements FileVisitor<Path> {
- private final HashSet<K> set;
- public PathFileVisitor(HashSet<K> set) {this.set = set;}
- @Override
- public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes basicFileAttributes) {
- if (basicFileAttributes.isSymbolicLink() || path.getFileName().startsWith(".")) {
- return FileVisitResult.SKIP_SUBTREE;
- }
- return FileVisitResult.CONTINUE;
- }
- @Override
- public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes) {
- if (basicFileAttributes.isSymbolicLink()) {
- return FileVisitResult.CONTINUE;
- }
- set.add(keyParser.apply(directory.relativize(path)));
- return FileVisitResult.CONTINUE;
- }
- @Override
- public FileVisitResult visitFileFailed(Path path, IOException e) {
- return FileVisitResult.TERMINATE;
- }
- @Override
- public FileVisitResult postVisitDirectory(Path path, IOException e) {
- return FileVisitResult.CONTINUE;
- }
- }
- }