mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2025-06-11 20:13:55 +00:00
Re-apply 374ad99
Accidentally fixed the EntryMaker issue before I pulled 374ad99
. Should reflect upstream now.
This commit is contained in:
committed by
IronApollo
parent
8b97a11fa4
commit
415e91b519
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user