Accidentally fixed the EntryMaker issue before I pulled 374ad99. Should reflect upstream now.
This commit is contained in:
Octavia Togami 2020-04-05 12:17:26 -04:00 committed by IronApollo
parent 8b97a11fa4
commit 415e91b519
10 changed files with 209 additions and 168 deletions

View File

@ -20,6 +20,7 @@
package com.sk89q.worldedit.util.function;
import java.io.IOException;
import java.io.UncheckedIOException;
/**
* I/O runnable type.
@ -27,6 +28,16 @@ import java.io.IOException;
@FunctionalInterface
public interface IORunnable {
static Runnable unchecked(IORunnable runnable) {
return () -> {
try {
runnable.run();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
}
void run() throws IOException;
}

View File

@ -34,6 +34,6 @@ public interface ArchiveNioSupport {
* @param archive the archive to open
* @return the path for the root of the archive, if available
*/
Optional<Path> tryOpenAsDir(Path archive) throws IOException;
Optional<ArchiveDir> tryOpenAsDir(Path archive) throws IOException;
}

View File

@ -45,9 +45,9 @@ public class ArchiveNioSupports {
.build();
}
public static Optional<Path> tryOpenAsDir(Path archive) throws IOException {
public static Optional<ArchiveDir> tryOpenAsDir(Path archive) throws IOException {
for (ArchiveNioSupport support : SUPPORTS) {
Optional<Path> fs = support.tryOpenAsDir(archive);
Optional<ArchiveDir> fs = support.tryOpenAsDir(archive);
if (fs.isPresent()) {
return fs;
}

View File

@ -22,6 +22,7 @@ package com.sk89q.worldedit.util.io.file;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import net.java.truevfs.access.TArchiveDetector;
import net.java.truevfs.access.TFileSystem;
import net.java.truevfs.access.TPath;
import java.io.IOException;
@ -45,15 +46,28 @@ public final class TrueVfsArchiveNioSupport implements ArchiveNioSupport {
}
@Override
public Optional<Path> tryOpenAsDir(Path archive) throws IOException {
public Optional<ArchiveDir> tryOpenAsDir(Path archive) throws IOException {
String fileName = archive.getFileName().toString();
int dot = fileName.indexOf('.');
if (dot < 0 || dot >= fileName.length() || !ALLOWED_EXTENSIONS.contains(fileName.substring(dot + 1))) {
if (dot < 0 || dot >= fileName.length() || !ALLOWED_EXTENSIONS
.contains(fileName.substring(dot + 1))) {
return Optional.empty();
}
TPath root = new TPath(archive).getFileSystem().getPath("/");
return Optional.of(ArchiveNioSupports.skipRootSameName(
TFileSystem fileSystem = new TPath(archive).getFileSystem();
TPath root = fileSystem.getPath("/");
Path realRoot = ArchiveNioSupports.skipRootSameName(
root, fileName.substring(0, dot)
));
);
return Optional.of(new ArchiveDir() {
@Override
public Path getPath() {
return realRoot;
}
@Override
public void close() throws IOException {
fileSystem.close();
}
});
}
}

View File

@ -37,17 +37,28 @@ public final class ZipArchiveNioSupport implements ArchiveNioSupport {
}
@Override
public Optional<Path> tryOpenAsDir(Path archive) throws IOException {
public Optional<ArchiveDir> tryOpenAsDir(Path archive) throws IOException {
if (!archive.getFileName().toString().endsWith(".zip")) {
return Optional.empty();
}
FileSystem zipFs = FileSystems.newFileSystem(
archive, getClass().getClassLoader()
);
return Optional.of(ArchiveNioSupports.skipRootSameName(
Path root = ArchiveNioSupports.skipRootSameName(
zipFs.getPath("/"), archive.getFileName().toString()
.replaceFirst("\\.zip$", "")
));
);
return Optional.of(new ArchiveDir() {
@Override
public Path getPath() {
return root;
}
@Override
public void close() throws IOException {
zipFs.close();
}
});
}
}

View File

@ -21,34 +21,31 @@ package com.sk89q.worldedit.world.snapshot.experimental.fs;
import com.google.common.collect.ImmutableList;
import com.google.common.net.UrlEscapers;
import com.sk89q.worldedit.util.function.IOFunction;
import com.sk89q.worldedit.util.function.IORunnable;
import com.sk89q.worldedit.util.io.Closer;
import com.sk89q.worldedit.util.io.file.ArchiveDir;
import com.sk89q.worldedit.util.io.file.ArchiveNioSupport;
import com.sk89q.worldedit.util.io.file.MorePaths;
import com.sk89q.worldedit.util.io.file.SafeFiles;
import com.sk89q.worldedit.util.time.FileNameDateTimeParser;
import com.sk89q.worldedit.util.time.ModificationDateTimeParser;
import com.sk89q.worldedit.util.time.SnapshotDateTimeParser;
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotDatabase;
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZonedDateTime;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Function;
import java.util.stream.Stream;
import static com.google.common.base.Preconditions.checkArgument;
@ -58,8 +55,6 @@ import static com.google.common.base.Preconditions.checkArgument;
*/
public class FileSystemSnapshotDatabase implements SnapshotDatabase {
private static final Logger logger = LoggerFactory.getLogger(FileSystemSnapshotDatabase.class);
private static final String SCHEME = "snapfs";
private static final List<SnapshotDateTimeParser> DATE_TIME_PARSERS =
@ -102,15 +97,24 @@ public class FileSystemSnapshotDatabase implements SnapshotDatabase {
this.archiveNioSupport = archiveNioSupport;
}
private SnapshotInfo createSnapshotInfo(Path fullPath, Path realPath) {
// Try full for parsing out of file name, real for parsing mod time.
ZonedDateTime date = tryParseDateInternal(fullPath).orElseGet(() -> tryParseDate(realPath));
return SnapshotInfo.create(createUri(fullPath.toString()), date);
/*
* When this code says "idPath" it is the path that uniquely identifies that snapshot.
* A snapshot can be looked up by its idPath.
*
* When the code says "ioPath" it is the path that holds the world data, and can actually
* be read from proper. The "idPath" may not even exist, it is purely for the path components
* and not for IO.
*/
private SnapshotInfo createSnapshotInfo(Path idPath, Path ioPath) {
// Try ID for parsing out of file name, IO for parsing mod time.
ZonedDateTime date = tryParseDateInternal(idPath).orElseGet(() -> tryParseDate(ioPath));
return SnapshotInfo.create(createUri(idPath.toString()), date);
}
private Snapshot createSnapshot(Path fullPath, Path realPath, @Nullable IORunnable closeCallback) {
private Snapshot createSnapshot(Path idPath, Path ioPath, @Nullable Closer closeCallback) {
return new FolderSnapshot(
createSnapshotInfo(fullPath, realPath), realPath, closeCallback
createSnapshotInfo(idPath, ioPath), ioPath, closeCallback
);
}
@ -128,27 +132,31 @@ public class FileSystemSnapshotDatabase implements SnapshotDatabase {
if (!name.getScheme().equals(SCHEME)) {
return Optional.empty();
}
// drop the / in the path to make it absolute
Path rawResolved = root.resolve(name.getSchemeSpecificPart());
return getSnapshot(name.getSchemeSpecificPart());
}
private Optional<Snapshot> getSnapshot(String id) throws IOException {
Path rawResolved = root.resolve(id);
// Catch trickery with paths:
Path realPath = rawResolved.normalize();
if (!realPath.startsWith(root)) {
Path ioPath = rawResolved.normalize();
if (!ioPath.startsWith(root)) {
return Optional.empty();
}
Optional<Snapshot> result = tryRegularFileSnapshot(root.relativize(realPath), realPath);
Path idPath = root.relativize(ioPath);
Optional<Snapshot> result = tryRegularFileSnapshot(idPath);
if (result.isPresent()) {
return result;
}
if (!Files.isDirectory(realPath)) {
if (!Files.isDirectory(ioPath)) {
return Optional.empty();
}
return Optional.of(createSnapshot(root.relativize(realPath), realPath, null));
return Optional.of(createSnapshot(idPath, ioPath, null));
}
private Optional<Snapshot> tryRegularFileSnapshot(Path fullPath, Path realPath) throws IOException {
private Optional<Snapshot> tryRegularFileSnapshot(Path idPath) throws IOException {
Closer closer = Closer.create();
Path root = this.root;
Path relative = root.relativize(realPath);
Path relative = idPath;
Iterator<Path> iterator = null;
try {
while (true) {
@ -156,6 +164,7 @@ public class FileSystemSnapshotDatabase implements SnapshotDatabase {
iterator = MorePaths.iterPaths(relative).iterator();
}
if (!iterator.hasNext()) {
closer.close();
return Optional.empty();
}
Path relativeNext = iterator.next();
@ -164,18 +173,17 @@ public class FileSystemSnapshotDatabase implements SnapshotDatabase {
// This will never be it.
continue;
}
Optional<Path> newRootOpt = archiveNioSupport.tryOpenAsDir(next);
Optional<ArchiveDir> newRootOpt = archiveNioSupport.tryOpenAsDir(next);
if (newRootOpt.isPresent()) {
root = newRootOpt.get();
if (root.getFileSystem() != FileSystems.getDefault()) {
closer.register(root.getFileSystem());
}
ArchiveDir archiveDir = newRootOpt.get();
root = archiveDir.getPath();
closer.register(archiveDir);
// Switch path to path inside the archive
relative = root.resolve(relativeNext.relativize(relative).toString());
iterator = null;
// Check if it exists, if so open snapshot
if (Files.exists(relative)) {
return Optional.of(createSnapshot(fullPath, relative, closer::close));
return Optional.of(createSnapshot(idPath, relative, closer));
}
// Otherwise, we may have more archives to open.
// Keep searching!
@ -191,119 +199,96 @@ public class FileSystemSnapshotDatabase implements SnapshotDatabase {
/*
There are a few possible snapshot formats we accept:
- a world directory, identified by <worldName>/level.dat
<<<<<<< HEAD
- a directory with the world name, but no level.dat
- inside must be a timestamped directory/archive, which then has one of the two world
formats inside of it!
=======
>>>>>>> 18a55bc14... Add new experimental snapshot API (#524)
- a world archive, identified by <worldName>.ext
* does not need to have level.dat inside
- a timestamped directory, identified by <stamp>, that can have
- the two world formats described above, inside the directory
- a timestamped archive, identified by <stamp>.ext, that can have
- the same as timestamped directory, but inside the archive.
<<<<<<< HEAD
=======
- a directory with the world name, but no level.dat
- inside must be timestamped directory/archive, with the world inside that
>>>>>>> 18a55bc14... Add new experimental snapshot API (#524)
All archives may have a root directory with the same name as the archive,
minus the extensions. Due to extension detection methods, this won't work properly
with some files, e.g. world.qux.zip/world.qux is invalid, but world.qux.zip/world isn't.
*/
return Stream.of(
listWorldEntries(Paths.get(""), root, worldName),
listTimestampedEntries(Paths.get(""), root, worldName)
).flatMap(Function.identity());
return SafeFiles.noLeakFileList(root)
.flatMap(IOFunction.unchecked(entry -> {
String worldEntry = getWorldEntry(worldName, entry);
if (worldEntry != null) {
return Stream.of(worldEntry);
}
String fileName = SafeFiles.canonicalFileName(entry);
if (fileName.equals(worldName)
&& Files.isDirectory(entry)
&& !Files.exists(entry.resolve("level.dat"))) {
// world dir with timestamp entries
return listTimestampedEntries(worldName, entry)
.map(id -> worldName + "/" + id);
}
return getTimestampedEntries(worldName, entry);
}))
.map(IOFunction.unchecked(id ->
getSnapshot(id)
.orElseThrow(() ->
new AssertionError("Could not find discovered snapshot: " + id)
)
));
}
private Stream<Snapshot> listWorldEntries(Path fullPath, Path root, String worldName) throws IOException {
logger.debug("World check in: {}", root);
return Files.list(root)
.flatMap(candidate -> {
logger.debug("World trying: {}", candidate);
// Try world directory
String fileName = candidate.getFileName().toString();
if (isSameDirectoryName(fileName, worldName)) {
// Direct
if (Files.exists(candidate.resolve("level.dat"))) {
logger.debug("Direct!");
return Stream.of(createSnapshot(
fullPath.resolve(fileName), candidate, null
));
}
// Container for time-stamped entries
try {
return listTimestampedEntries(
fullPath.resolve(fileName), candidate, worldName
);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
// Try world archive
if (Files.isRegularFile(candidate)
&& fileName.startsWith(worldName + ".")) {
logger.debug("Archive!");
try {
return tryRegularFileSnapshot(
fullPath.resolve(fileName), candidate
).map(Stream::of).orElse(null);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
logger.debug("Nothing!");
return null;
});
private Stream<String> listTimestampedEntries(String worldName, Path directory) throws IOException {
return SafeFiles.noLeakFileList(directory)
.flatMap(IOFunction.unchecked(entry -> getTimestampedEntries(worldName, entry)));
}
private boolean isSameDirectoryName(String fileName, String worldName) {
if (fileName.lastIndexOf('/') == fileName.length() - 1) {
fileName = fileName.substring(0, fileName.length() - 1);
private Stream<String> getTimestampedEntries(String worldName, Path entry) throws IOException {
ZonedDateTime dateTime = FileNameDateTimeParser.getInstance().detectDateTime(entry);
if (dateTime == null) {
// nothing available at this path
return Stream.of();
}
return fileName.equalsIgnoreCase(worldName);
String fileName = SafeFiles.canonicalFileName(entry);
if (Files.isDirectory(entry)) {
// timestamped directory, find worlds inside
return listWorldEntries(worldName, entry)
.map(id -> fileName + "/" + id);
}
if (!Files.isRegularFile(entry)) {
// not an archive either?
return Stream.of();
}
Optional<ArchiveDir> asArchive = archiveNioSupport.tryOpenAsDir(entry);
if (asArchive.isPresent()) {
// timestamped archive
ArchiveDir dir = asArchive.get();
return listWorldEntries(worldName, dir.getPath())
.map(id -> fileName + "/" + id)
.onClose(IORunnable.unchecked(dir::close));
}
return Stream.of();
}
private Stream<Snapshot> listTimestampedEntries(Path fullPath, Path root, String worldName) throws IOException {
logger.debug("Timestamp check in: {}", root);
return Files.list(root)
.filter(candidate -> {
ZonedDateTime date = FileNameDateTimeParser.getInstance().detectDateTime(candidate);
return date != null;
})
.flatMap(candidate -> {
logger.debug("Timestamp trying: {}", candidate);
// Try timestamped directory
if (Files.isDirectory(candidate)) {
logger.debug("Timestamped directory");
try {
return listWorldEntries(
fullPath.resolve(candidate.getFileName().toString()), candidate, worldName
);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
// Otherwise archive, get it as a directory & unpack it
try {
Optional<Path> newRoot = archiveNioSupport.tryOpenAsDir(candidate);
if (!newRoot.isPresent()) {
logger.debug("Nothing!");
return null;
}
logger.debug("Timestamped archive!");
return listWorldEntries(
fullPath.resolve(candidate.getFileName().toString()),
newRoot.get(),
worldName
);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
private Stream<String> listWorldEntries(String worldName, Path directory) throws IOException {
return SafeFiles.noLeakFileList(directory)
.map(IOFunction.unchecked(entry -> getWorldEntry(worldName, entry)))
.filter(Objects::nonNull);
}
private String getWorldEntry(String worldName, Path entry) throws IOException {
String fileName = SafeFiles.canonicalFileName(entry);
if (fileName.equals(worldName) && Files.exists(entry.resolve("level.dat"))) {
// world directory
return worldName;
}
if (fileName.startsWith(worldName + ".") && Files.isRegularFile(entry)) {
Optional<ArchiveDir> asArchive = archiveNioSupport.tryOpenAsDir(entry);
if (asArchive.isPresent()) {
// world archive
asArchive.get().close();
return fileName;
}
}
return null;
}
}

View File

@ -22,7 +22,7 @@ package com.sk89q.worldedit.world.snapshot.experimental.fs;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.util.function.IORunnable;
import com.sk89q.worldedit.util.io.Closer;
import com.sk89q.worldedit.world.DataException;
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotInfo;
@ -95,9 +95,9 @@ public class FolderSnapshot implements Snapshot {
private final SnapshotInfo info;
private final Path folder;
private final AtomicReference<Object> regionFolder = new AtomicReference<>();
private final @Nullable IORunnable closeCallback;
private final @Nullable Closer closeCallback;
public FolderSnapshot(SnapshotInfo info, Path folder, @Nullable IORunnable closeCallback) {
public FolderSnapshot(SnapshotInfo info, Path folder, @Nullable Closer closeCallback) {
this.info = info;
// This is required to force TrueVfs to properly resolve parents.
// Kinda odd, but whatever works.
@ -160,7 +160,7 @@ public class FolderSnapshot implements Snapshot {
@Override
public void close() throws IOException {
if (closeCallback != null) {
closeCallback.run();
closeCallback.close();
}
}
}

View File

@ -19,6 +19,8 @@
package com.sk89q.worldedit.world.snapshot.experimental.fs;
import com.sk89q.worldedit.util.io.Closer;
import com.sk89q.worldedit.util.io.file.ArchiveDir;
import com.sk89q.worldedit.util.io.file.ArchiveNioSupport;
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
@ -28,6 +30,7 @@ import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Stream;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.stream.Collectors.toList;
@ -67,19 +70,34 @@ class FSSDContext {
String worldName = Paths.get(name).getFileName().toString();
// Without an extension
worldName = worldName.split("\\.")[0];
List<Snapshot> snapshots = db.getSnapshots(worldName).collect(toList());
assertTrue(1 >= snapshots.size(),
"Too many snapshots matched for " + worldName);
return requireSnapshot(name, snapshots.stream().findAny().orElse(null));
List<Snapshot> snapshots;
try (Stream<Snapshot> snapshotStream = db.getSnapshots(worldName)) {
snapshots = snapshotStream.collect(toList());
}
try {
assertTrue(snapshots.size() <= 1,
"Too many snapshots matched for " + worldName);
return requireSnapshot(name, snapshots.stream().findAny().orElse(null));
} catch (Throwable t) {
Closer closer = Closer.create();
snapshots.forEach(closer::register);
throw closer.rethrowAndClose(t);
}
}
Snapshot requireSnapshot(String name, @Nullable Snapshot snapshot) {
Snapshot requireSnapshot(String name, @Nullable Snapshot snapshot) throws IOException {
assertNotNull(snapshot, "No snapshot for " + name);
assertEquals(name, snapshot.getInfo().getDisplayName());
try {
assertEquals(name, snapshot.getInfo().getDisplayName());
} catch (Throwable t) {
Closer closer = Closer.create();
closer.register(snapshot);
throw closer.rethrowAndClose(t);
}
return snapshot;
}
Path getRootOfArchive(Path archive) throws IOException {
ArchiveDir getRootOfArchive(Path archive) throws IOException {
return archiveNioSupport.tryOpenAsDir(archive)
.orElseThrow(() -> new AssertionError("No archive opener for " + archive));
}

View File

@ -21,6 +21,7 @@ package com.sk89q.worldedit.world.snapshot.experimental.fs;
import com.google.common.collect.ImmutableList;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.util.io.file.ArchiveDir;
import com.sk89q.worldedit.world.DataException;
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
import org.junit.jupiter.api.DynamicNode;
@ -29,7 +30,6 @@ import org.junit.jupiter.api.DynamicTest;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
@ -38,8 +38,9 @@ import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Stream;
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.CHUNK_TAG;
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.CHUNK_POS;
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.CHUNK_TAG;
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.TIME_ONE;
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.TIME_TWO;
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.WORLD_ALPHA;
@ -102,16 +103,11 @@ enum FSSDTestType {
List<DynamicTest> getTests(FSSDContext context) throws IOException {
Path worldArchive = EntryMaker.WORLD_ARCHIVE
.createEntry(context.db.getRoot(), WORLD_ALPHA);
Path rootOfArchive = context.getRootOfArchive(worldArchive);
try {
try (ArchiveDir rootOfArchive = context.getRootOfArchive(worldArchive)) {
Files.setLastModifiedTime(
rootOfArchive,
rootOfArchive.getPath(),
FileTime.from(TIME_ONE.toInstant())
);
} finally {
if (rootOfArchive.getFileSystem() != FileSystems.getDefault()) {
rootOfArchive.getFileSystem().close();
}
}
return singleSnapTest(context, WORLD_ALPHA + ".zip", TIME_ONE);
}
@ -144,14 +140,9 @@ enum FSSDTestType {
Path root = context.db.getRoot();
Path timestampedArchive = EntryMaker.TIMESTAMPED_ARCHIVE
.createEntry(root, TIME_ONE);
Path timestampedDir = context.getRootOfArchive(timestampedArchive);
try {
EntryMaker.WORLD_DIR.createEntry(timestampedDir, WORLD_ALPHA);
EntryMaker.WORLD_ARCHIVE.createEntry(timestampedDir, WORLD_BETA);
} finally {
if (timestampedDir.getFileSystem() != FileSystems.getDefault()) {
timestampedDir.getFileSystem().close();
}
try (ArchiveDir timestampedDir = context.getRootOfArchive(timestampedArchive)) {
EntryMaker.WORLD_DIR.createEntry(timestampedDir.getPath(), WORLD_ALPHA);
EntryMaker.WORLD_ARCHIVE.createEntry(timestampedDir.getPath(), WORLD_BETA);
}
return ImmutableList.of(
dynamicContainer("world dir",
@ -261,16 +252,18 @@ enum FSSDTestType {
}
};
private static List<DynamicTest> singleSnapTest(FSSDContext context, String name,
List<DynamicTest> singleSnapTest(FSSDContext context, String name,
ZonedDateTime time) {
return ImmutableList.of(
dynamicTest("return a valid snapshot for " + name, () -> {
Snapshot snapshot = context.requireSnapshot(name);
assertValidSnapshot(time, snapshot);
try (Snapshot snapshot = context.requireSnapshot(name)) {
assertValidSnapshot(time, snapshot);
}
}),
dynamicTest("list a valid snapshot for " + name, () -> {
Snapshot snapshot = context.requireListsSnapshot(name);
assertValidSnapshot(time, snapshot);
try (Snapshot snapshot = context.requireListsSnapshot(name)) {
assertValidSnapshot(time, snapshot);
}
})
);
}

View File

@ -31,6 +31,7 @@ import com.sk89q.worldedit.util.io.file.ZipArchiveNioSupport;
import com.sk89q.worldedit.world.DataException;
import com.sk89q.worldedit.world.storage.ChunkStoreHelper;
import com.sk89q.worldedit.world.storage.McRegionReader;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DynamicNode;
@ -76,6 +77,8 @@ class FileSystemSnapshotDatabaseTest {
.atZone(ZoneId.systemDefault());
static final ZonedDateTime TIME_TWO = TIME_ONE.minusDays(1);
private static Path TEMP_DIR;
@BeforeAll
static void setUpStatic() throws IOException, DataException {
try (InputStream in = Resources.getResource("world_region.mca.gzip").openStream();
@ -104,10 +107,17 @@ class FileSystemSnapshotDatabaseTest {
} finally {
reader.close();
}
TEMP_DIR = Files.createTempDirectory("worldedit-fs-snap-dbs");
}
@AfterAll
static void afterAll() throws IOException {
deleteTree(TEMP_DIR);
}
private static Path newTempDb() throws IOException {
return Files.createTempDirectory("worldedit-fs-snap-db");
return Files.createTempDirectory(TEMP_DIR, "db");
}
private static void deleteTree(Path root) throws IOException {
@ -175,7 +185,6 @@ class FileSystemSnapshotDatabaseTest {
try {
Path dbRoot = root.resolve("snapshots");
Files.createDirectories(dbRoot);
// we leak `root` here, but I can't see a good way to clean it up.
return type.getNamedTests(new FSSDContext(nioSupport, dbRoot));
} catch (Throwable t) {
deleteTree(root);