Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-11-19 09:20:08 +01:00
Cherry-pick to fix EntryMaker issue
Dieser Commit ist enthalten in:
Ursprung
735a37ffd0
Commit
ff47e6f717
@ -120,19 +120,7 @@ fun Project.applyShadowConfiguration() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val CLASSPATH = listOf("truezip", "truevfs", "js")
|
val CLASSPATH = listOf("truezip", "truevfs", "js")
|
||||||
.map { "$it.jar" }
|
.map { "$it.jar" }
|
||||||
.flatMap { listOf(it, "WorldEdit/$it") }
|
.flatMap { listOf(it, "WorldEdit/$it") }
|
||||||
.joinToString(separator = " ")
|
.joinToString(separator = " ")
|
||||||
|
|
||||||
fun Project.addJarManifest(includeClasspath: Boolean = false) {
|
|
||||||
tasks.named<Jar>("jar") {
|
|
||||||
val attributes = mutableMapOf(
|
|
||||||
"WorldEdit-Version" to project(":worldedit-core").version
|
|
||||||
)
|
|
||||||
if (includeClasspath) {
|
|
||||||
attributes["Class-Path"] = CLASSPATH
|
|
||||||
}
|
|
||||||
manifest.attributes(attributes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -4,9 +4,9 @@ object Versions {
|
|||||||
const val TEXT = "3.0.3"
|
const val TEXT = "3.0.3"
|
||||||
const val TEXT_EXTRAS = "3.0.3"
|
const val TEXT_EXTRAS = "3.0.3"
|
||||||
const val PISTON = "0.5.2"
|
const val PISTON = "0.5.2"
|
||||||
const val AUTO_VALUE = "1.7"
|
const val AUTO_VALUE = "1.6.5"
|
||||||
const val JUNIT = "5.6.1"
|
const val JUNIT = "5.5.0"
|
||||||
const val MOCKITO = "3.3.3"
|
const val MOCKITO = "3.0.0"
|
||||||
const val LOGBACK = "1.2.3"
|
const val LOGBACK = "1.2.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE module PUBLIC
|
<!DOCTYPE module PUBLIC
|
||||||
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
"http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd">
|
||||||
<module name="Checker">
|
<module name="Checker">
|
||||||
<!-- Tabs are strictly banned -->
|
<!-- Tabs are strictly banned -->
|
||||||
<module name="FileTabCharacter"/>
|
<module name="FileTabCharacter"/>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<!DOCTYPE import-control PUBLIC
|
<!DOCTYPE import-control PUBLIC
|
||||||
"-//Checkstyle//DTD ImportControl Configuration 1.4//EN"
|
"-//Puppy Crawl//DTD Import Control 1.1//EN"
|
||||||
"https://checkstyle.org/dtds/import_control_1_4.dtd">
|
"http://checkstyle.sourceforge.net/dtds/import_control_1_1.dtd">
|
||||||
|
|
||||||
|
|
||||||
<import-control pkg="com.sk89q">
|
<import-control pkg="com.sk89q">
|
||||||
<allow pkg="java"/>
|
<allow pkg="java"/>
|
||||||
|
@ -75,7 +75,12 @@ tasks.named<Copy>("processResources") {
|
|||||||
exclude("**/worldedit-adapters.jar")
|
exclude("**/worldedit-adapters.jar")
|
||||||
}
|
}
|
||||||
|
|
||||||
addJarManifest(includeClasspath = true)
|
tasks.named<Jar>("jar") {
|
||||||
|
manifest {
|
||||||
|
attributes("Class-Path" to CLASSPATH,
|
||||||
|
"WorldEdit-Version" to project.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tasks.named<ShadowJar>("shadowJar") {
|
tasks.named<ShadowJar>("shadowJar") {
|
||||||
from(zipTree("src/main/resources/worldedit-adapters.jar").matching {
|
from(zipTree("src/main/resources/worldedit-adapters.jar").matching {
|
||||||
|
@ -77,8 +77,10 @@ import com.sk89q.worldedit.world.block.BaseBlock;
|
|||||||
import com.sk89q.worldedit.world.block.BlockState;
|
import com.sk89q.worldedit.world.block.BlockState;
|
||||||
import com.sk89q.worldedit.world.item.ItemType;
|
import com.sk89q.worldedit.world.item.ItemType;
|
||||||
import com.sk89q.worldedit.world.item.ItemTypes;
|
import com.sk89q.worldedit.world.item.ItemTypes;
|
||||||
import com.sk89q.worldedit.world.snapshot.Snapshot;
|
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@ -140,7 +142,8 @@ public class LocalSession implements TextureHolder {
|
|||||||
private transient int maxTimeoutTime;
|
private transient int maxTimeoutTime;
|
||||||
private transient boolean useInventory;
|
private transient boolean useInventory;
|
||||||
private transient com.sk89q.worldedit.world.snapshot.Snapshot snapshot;
|
private transient com.sk89q.worldedit.world.snapshot.Snapshot snapshot;
|
||||||
private transient com.sk89q.worldedit.world.snapshot.experimental.Snapshot snapshotExperimental; private transient boolean hasCUISupport = false;
|
private transient Snapshot snapshotExperimental;
|
||||||
|
private transient boolean hasCUISupport = false;
|
||||||
private transient int cuiVersion = -1;
|
private transient int cuiVersion = -1;
|
||||||
private transient boolean fastMode = false;
|
private transient boolean fastMode = false;
|
||||||
private transient Mask mask;
|
private transient Mask mask;
|
||||||
@ -977,8 +980,7 @@ public class LocalSession implements TextureHolder {
|
|||||||
*
|
*
|
||||||
* @return the snapshot
|
* @return the snapshot
|
||||||
*/
|
*/
|
||||||
public @Nullable
|
public @Nullable Snapshot getSnapshotExperimental() {
|
||||||
com.sk89q.worldedit.world.snapshot.experimental.Snapshot getSnapshotExperimental() {
|
|
||||||
return snapshotExperimental;
|
return snapshotExperimental;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -987,7 +989,7 @@ public class LocalSession implements TextureHolder {
|
|||||||
*
|
*
|
||||||
* @param snapshotExperimental a snapshot
|
* @param snapshotExperimental a snapshot
|
||||||
*/
|
*/
|
||||||
public void setSnapshotExperimental(@Nullable com.sk89q.worldedit.world.snapshot.experimental.Snapshot snapshotExperimental) {
|
public void setSnapshotExperimental(@Nullable Snapshot snapshotExperimental) {
|
||||||
this.snapshotExperimental = snapshotExperimental;
|
this.snapshotExperimental = snapshotExperimental;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,9 +190,9 @@ class LegacySnapshotCommands {
|
|||||||
public Component getComponent(int number) {
|
public Component getComponent(int number) {
|
||||||
final Snapshot snapshot = snapshots.get(number);
|
final Snapshot snapshot = snapshots.get(number);
|
||||||
return TextComponent.of(number + 1 + ". ", TextColor.GOLD)
|
return TextComponent.of(number + 1 + ". ", TextColor.GOLD)
|
||||||
.append(TextComponent.of(snapshot.getName(), TextColor.LIGHT_PURPLE)
|
.append(TextComponent.of(snapshot.getName(), TextColor.LIGHT_PURPLE)
|
||||||
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to use")))
|
.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to use")))
|
||||||
.clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/snap use " + snapshot.getName())));
|
.clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/snap use " + snapshot.getName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -48,7 +48,7 @@ class LegacySnapshotUtilCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void restore(Actor actor, World world, LocalSession session, EditSession editSession,
|
void restore(Actor actor, World world, LocalSession session, EditSession editSession,
|
||||||
String snapshotName) throws WorldEditException {
|
String snapshotName) throws WorldEditException {
|
||||||
LocalConfiguration config = we.getConfiguration();
|
LocalConfiguration config = we.getConfiguration();
|
||||||
|
|
||||||
Region region = session.getSelection(world);
|
Region region = session.getSelection(world);
|
||||||
|
@ -31,29 +31,31 @@ import com.sk89q.worldedit.extension.platform.Actor;
|
|||||||
import com.sk89q.worldedit.regions.Region;
|
import com.sk89q.worldedit.regions.Region;
|
||||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||||
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
|
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
|
||||||
import com.sk89q.worldedit.world.DataException;
|
|
||||||
import com.sk89q.worldedit.world.World;
|
import com.sk89q.worldedit.world.World;
|
||||||
import com.sk89q.worldedit.world.snapshot.InvalidSnapshotException;
|
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
||||||
import com.sk89q.worldedit.world.snapshot.Snapshot;
|
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotRestore;
|
||||||
import com.sk89q.worldedit.world.snapshot.SnapshotRestore;
|
|
||||||
import com.sk89q.worldedit.world.storage.ChunkStore;
|
|
||||||
import com.sk89q.worldedit.world.storage.MissingWorldException;
|
|
||||||
import org.enginehub.piston.annotation.Command;
|
import org.enginehub.piston.annotation.Command;
|
||||||
import org.enginehub.piston.annotation.CommandContainer;
|
import org.enginehub.piston.annotation.CommandContainer;
|
||||||
import org.enginehub.piston.annotation.param.Arg;
|
import org.enginehub.piston.annotation.param.Arg;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static com.sk89q.worldedit.command.SnapshotCommands.checkSnapshotsConfigured;
|
||||||
|
import static com.sk89q.worldedit.command.SnapshotCommands.resolveSnapshotName;
|
||||||
import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION;
|
import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION;
|
||||||
|
|
||||||
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
|
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
|
||||||
public class SnapshotUtilCommands {
|
public class SnapshotUtilCommands {
|
||||||
|
|
||||||
private final WorldEdit we;
|
private final WorldEdit we;
|
||||||
|
private final LegacySnapshotUtilCommands legacy;
|
||||||
|
|
||||||
public SnapshotUtilCommands(WorldEdit we) {
|
public SnapshotUtilCommands(WorldEdit we) {
|
||||||
this.we = we;
|
this.we = we;
|
||||||
|
this.legacy = new LegacySnapshotUtilCommands(we);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(
|
@Command(
|
||||||
@ -65,12 +67,12 @@ public class SnapshotUtilCommands {
|
|||||||
@CommandPermissions("worldedit.snapshots.restore")
|
@CommandPermissions("worldedit.snapshots.restore")
|
||||||
public void restore(Actor actor, World world, LocalSession session, EditSession editSession,
|
public void restore(Actor actor, World world, LocalSession session, EditSession editSession,
|
||||||
@Arg(name = "snapshot", desc = "The snapshot to restore", def = "")
|
@Arg(name = "snapshot", desc = "The snapshot to restore", def = "")
|
||||||
String snapshotName) throws WorldEditException {
|
String snapshotName) throws WorldEditException, IOException {
|
||||||
|
|
||||||
LocalConfiguration config = we.getConfiguration();
|
LocalConfiguration config = we.getConfiguration();
|
||||||
|
checkSnapshotsConfigured(config);
|
||||||
|
|
||||||
if (config.snapshotRepo == null) {
|
if (config.snapshotRepo != null) {
|
||||||
actor.printError(TranslatableComponent.of("worldedit.restore.not-configured"));
|
legacy.restore(actor, world, session, editSession, snapshotName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,58 +80,41 @@ public class SnapshotUtilCommands {
|
|||||||
Snapshot snapshot;
|
Snapshot snapshot;
|
||||||
|
|
||||||
if (snapshotName != null) {
|
if (snapshotName != null) {
|
||||||
try {
|
URI uri = resolveSnapshotName(config, snapshotName);
|
||||||
snapshot = config.snapshotRepo.getSnapshot(snapshotName);
|
Optional<Snapshot> snapOpt = config.snapshotDatabase.getSnapshot(uri);
|
||||||
} catch (InvalidSnapshotException e) {
|
if (!snapOpt.isPresent()) {
|
||||||
actor.printError(TranslatableComponent.of("worldedit.restore.not-available"));
|
actor.printError(TranslatableComponent.of("worldedit.restore.not-available"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
snapshot = snapOpt.get();
|
||||||
} else {
|
} else {
|
||||||
snapshot = session.getSnapshot();
|
snapshot = session.getSnapshotExperimental();
|
||||||
}
|
}
|
||||||
|
|
||||||
// No snapshot set?
|
// No snapshot set?
|
||||||
if (snapshot == null) {
|
if (snapshot == null) {
|
||||||
try {
|
try (Stream<Snapshot> snapshotStream =
|
||||||
snapshot = config.snapshotRepo.getDefaultSnapshot(world.getName());
|
config.snapshotDatabase.getSnapshotsNewestFirst(world.getName())) {
|
||||||
|
snapshot = snapshotStream
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
if (snapshot == null) {
|
if (snapshot == null) {
|
||||||
actor.printError(TranslatableComponent.of("worldedit.restore.none-found-console"));
|
actor.printError(TranslatableComponent.of(
|
||||||
|
"worldedit.restore.none-for-specific-world",
|
||||||
// Okay, let's toss some debugging information!
|
TextComponent.of(world.getName())
|
||||||
File dir = config.snapshotRepo.getDirectory();
|
));
|
||||||
|
|
||||||
try {
|
|
||||||
WorldEdit.logger.info("WorldEdit found no snapshots: looked in: "
|
|
||||||
+ dir.getCanonicalPath());
|
|
||||||
} catch (IOException e) {
|
|
||||||
WorldEdit.logger.info("WorldEdit found no snapshots: looked in "
|
|
||||||
+ "(NON-RESOLVABLE PATH - does it exist?): "
|
|
||||||
+ dir.getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (MissingWorldException ex) {
|
|
||||||
actor.printError(TranslatableComponent.of("worldedit.restore.none-for-world"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
actor.printInfo(TranslatableComponent.of(
|
||||||
ChunkStore chunkStore;
|
"worldedit.restore.loaded",
|
||||||
|
TextComponent.of(snapshot.getInfo().getDisplayName())
|
||||||
// Load chunk store
|
));
|
||||||
try {
|
|
||||||
chunkStore = snapshot.getChunkStore();
|
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.restore.loaded", TextComponent.of(snapshot.getName())));
|
|
||||||
} catch (DataException | IOException e) {
|
|
||||||
actor.printError(TranslatableComponent.of("worldedit.restore.failed", TextComponent.of(e.getMessage())));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Restore snapshot
|
// Restore snapshot
|
||||||
SnapshotRestore restore = new SnapshotRestore(chunkStore, editSession, region);
|
SnapshotRestore restore = new SnapshotRestore(snapshot, editSession, region);
|
||||||
//player.print(restore.getChunksAffected() + " chunk(s) will be loaded.");
|
//player.print(restore.getChunksAffected() + " chunk(s) will be loaded.");
|
||||||
|
|
||||||
restore.restore();
|
restore.restore();
|
||||||
@ -146,12 +131,12 @@ public class SnapshotUtilCommands {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
actor.printInfo(TranslatableComponent.of("worldedit.restore.restored",
|
actor.printInfo(TranslatableComponent.of("worldedit.restore.restored",
|
||||||
TextComponent.of(restore.getMissingChunks().size()),
|
TextComponent.of(restore.getMissingChunks().size()),
|
||||||
TextComponent.of(restore.getErrorChunks().size())));
|
TextComponent.of(restore.getErrorChunks().size())));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
chunkStore.close();
|
snapshot.close();
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,8 +76,13 @@ public interface RegionSelector {
|
|||||||
/**
|
/**
|
||||||
* Tell the player information about his/her primary selection.
|
* Tell the player information about his/her primary selection.
|
||||||
*
|
*
|
||||||
|
<<<<<<< HEAD
|
||||||
* @param actor the actor
|
* @param actor the actor
|
||||||
* @param session the session
|
* @param session the session
|
||||||
|
=======
|
||||||
|
* @param actor the actor
|
||||||
|
* @param session the session
|
||||||
|
>>>>>>> 18a55bc14... Add new experimental snapshot API (#524)
|
||||||
* @param position position
|
* @param position position
|
||||||
*/
|
*/
|
||||||
void explainPrimarySelection(Actor actor, LocalSession session, BlockVector3 position);
|
void explainPrimarySelection(Actor actor, LocalSession session, BlockVector3 position);
|
||||||
|
@ -27,6 +27,7 @@ import com.sk89q.worldedit.LocalSession;
|
|||||||
import com.sk89q.worldedit.util.report.Unreported;
|
import com.sk89q.worldedit.util.report.Unreported;
|
||||||
import com.sk89q.worldedit.world.registry.LegacyMapper;
|
import com.sk89q.worldedit.world.registry.LegacyMapper;
|
||||||
import com.sk89q.worldedit.world.snapshot.SnapshotRepository;
|
import com.sk89q.worldedit.world.snapshot.SnapshotRepository;
|
||||||
|
import com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabase;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -37,6 +38,10 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
@ -118,9 +118,8 @@ public class YAMLConfiguration extends LocalConfiguration {
|
|||||||
serverSideCUI = config.getBoolean("server-side-cui", true);
|
serverSideCUI = config.getBoolean("server-side-cui", true);
|
||||||
|
|
||||||
String snapshotsDir = config.getString("snapshots.directory", "");
|
String snapshotsDir = config.getString("snapshots.directory", "");
|
||||||
if (!snapshotsDir.isEmpty()) {
|
boolean experimentalSnapshots = config.getBoolean("snapshots.experimental", false);
|
||||||
snapshotRepo = new SnapshotRepository(snapshotsDir);
|
initializeSnapshotConfiguration(snapshotsDir, experimentalSnapshots);
|
||||||
}
|
|
||||||
|
|
||||||
String type = config.getString("shell-save-type", "").trim();
|
String type = config.getString("shell-save-type", "").trim();
|
||||||
shellSaveType = type.isEmpty() ? null : type;
|
shellSaveType = type.isEmpty() ? null : type;
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
package com.sk89q.worldedit.util.function;
|
package com.sk89q.worldedit.util.function;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* I/O runnable type.
|
* I/O runnable type.
|
||||||
@ -28,16 +27,6 @@ import java.io.UncheckedIOException;
|
|||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface IORunnable {
|
public interface IORunnable {
|
||||||
|
|
||||||
static Runnable unchecked(IORunnable runnable) {
|
|
||||||
return () -> {
|
|
||||||
try {
|
|
||||||
runnable.run();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void run() throws IOException;
|
void run() throws IOException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
package com.sk89q.worldedit.util.io.file;
|
package com.sk89q.worldedit.util.io.file;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.FileSystem;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ -35,6 +34,6 @@ public interface ArchiveNioSupport {
|
|||||||
* @param archive the archive to open
|
* @param archive the archive to open
|
||||||
* @return the path for the root of the archive, if available
|
* @return the path for the root of the archive, if available
|
||||||
*/
|
*/
|
||||||
Optional<ArchiveDir> tryOpenAsDir(Path archive) throws IOException;
|
Optional<Path> tryOpenAsDir(Path archive) throws IOException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.util.io.file;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Something that can provide access to an archive file as a file system.
|
||||||
|
*/
|
||||||
|
public interface ArchiveNioSupport {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to open the given archive as a file system.
|
||||||
|
*
|
||||||
|
* @param archive the archive to open
|
||||||
|
* @return the path for the root of the archive, if available
|
||||||
|
*/
|
||||||
|
Optional<Path> tryOpenAsDir(Path archive) throws IOException;
|
||||||
|
|
||||||
|
}
|
@ -45,9 +45,9 @@ public class ArchiveNioSupports {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<ArchiveDir> tryOpenAsDir(Path archive) throws IOException {
|
public static Optional<Path> tryOpenAsDir(Path archive) throws IOException {
|
||||||
for (ArchiveNioSupport support : SUPPORTS) {
|
for (ArchiveNioSupport support : SUPPORTS) {
|
||||||
Optional<ArchiveDir> fs = support.tryOpenAsDir(archive);
|
Optional<Path> fs = support.tryOpenAsDir(archive);
|
||||||
if (fs.isPresent()) {
|
if (fs.isPresent()) {
|
||||||
return fs;
|
return fs;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ package com.sk89q.worldedit.util.io.file;
|
|||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import net.java.truevfs.access.TArchiveDetector;
|
import net.java.truevfs.access.TArchiveDetector;
|
||||||
import net.java.truevfs.access.TFileSystem;
|
|
||||||
import net.java.truevfs.access.TPath;
|
import net.java.truevfs.access.TPath;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -46,28 +45,15 @@ public final class TrueVfsArchiveNioSupport implements ArchiveNioSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<ArchiveDir> tryOpenAsDir(Path archive) throws IOException {
|
public Optional<Path> tryOpenAsDir(Path archive) throws IOException {
|
||||||
String fileName = archive.getFileName().toString();
|
String fileName = archive.getFileName().toString();
|
||||||
int dot = fileName.indexOf('.');
|
int dot = fileName.indexOf('.');
|
||||||
if (dot < 0 || dot >= fileName.length() || !ALLOWED_EXTENSIONS
|
if (dot < 0 || dot >= fileName.length() || !ALLOWED_EXTENSIONS.contains(fileName.substring(dot + 1))) {
|
||||||
.contains(fileName.substring(dot + 1))) {
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
TFileSystem fileSystem = new TPath(archive).getFileSystem();
|
TPath root = new TPath(archive).getFileSystem().getPath("/");
|
||||||
TPath root = fileSystem.getPath("/");
|
return Optional.of(ArchiveNioSupports.skipRootSameName(
|
||||||
Path realRoot = ArchiveNioSupports.skipRootSameName(
|
|
||||||
root, fileName.substring(0, dot)
|
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,28 +37,17 @@ public final class ZipArchiveNioSupport implements ArchiveNioSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<ArchiveDir> tryOpenAsDir(Path archive) throws IOException {
|
public Optional<Path> tryOpenAsDir(Path archive) throws IOException {
|
||||||
if (!archive.getFileName().toString().endsWith(".zip")) {
|
if (!archive.getFileName().toString().endsWith(".zip")) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
FileSystem zipFs = FileSystems.newFileSystem(
|
FileSystem zipFs = FileSystems.newFileSystem(
|
||||||
archive, getClass().getClassLoader()
|
archive, getClass().getClassLoader()
|
||||||
);
|
);
|
||||||
Path root = ArchiveNioSupports.skipRootSameName(
|
return Optional.of(ArchiveNioSupports.skipRootSameName(
|
||||||
zipFs.getPath("/"), archive.getFileName().toString()
|
zipFs.getPath("/"), archive.getFileName().toString()
|
||||||
.replaceFirst("\\.zip$", "")
|
.replaceFirst("\\.zip$", "")
|
||||||
);
|
));
|
||||||
return Optional.of(new ArchiveDir() {
|
|
||||||
@Override
|
|
||||||
public Path getPath() {
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
zipFs.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,31 +21,34 @@ package com.sk89q.worldedit.world.snapshot.experimental.fs;
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.net.UrlEscapers;
|
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.function.IORunnable;
|
||||||
import com.sk89q.worldedit.util.io.Closer;
|
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.ArchiveNioSupport;
|
||||||
import com.sk89q.worldedit.util.io.file.MorePaths;
|
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.FileNameDateTimeParser;
|
||||||
import com.sk89q.worldedit.util.time.ModificationDateTimeParser;
|
import com.sk89q.worldedit.util.time.ModificationDateTimeParser;
|
||||||
import com.sk89q.worldedit.util.time.SnapshotDateTimeParser;
|
import com.sk89q.worldedit.util.time.SnapshotDateTimeParser;
|
||||||
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
||||||
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotDatabase;
|
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotDatabase;
|
||||||
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotInfo;
|
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotInfo;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
@ -55,6 +58,8 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||||||
*/
|
*/
|
||||||
public class FileSystemSnapshotDatabase implements SnapshotDatabase {
|
public class FileSystemSnapshotDatabase implements SnapshotDatabase {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(FileSystemSnapshotDatabase.class);
|
||||||
|
|
||||||
private static final String SCHEME = "snapfs";
|
private static final String SCHEME = "snapfs";
|
||||||
|
|
||||||
private static final List<SnapshotDateTimeParser> DATE_TIME_PARSERS =
|
private static final List<SnapshotDateTimeParser> DATE_TIME_PARSERS =
|
||||||
@ -97,24 +102,15 @@ public class FileSystemSnapshotDatabase implements SnapshotDatabase {
|
|||||||
this.archiveNioSupport = archiveNioSupport;
|
this.archiveNioSupport = archiveNioSupport;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
private SnapshotInfo createSnapshotInfo(Path fullPath, Path realPath) {
|
||||||
* When this code says "idPath" it is the path that uniquely identifies that snapshot.
|
// Try full for parsing out of file name, real for parsing mod time.
|
||||||
* A snapshot can be looked up by its idPath.
|
ZonedDateTime date = tryParseDateInternal(fullPath).orElseGet(() -> tryParseDate(realPath));
|
||||||
*
|
return SnapshotInfo.create(createUri(fullPath.toString()), date);
|
||||||
* 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 idPath, Path ioPath, @Nullable Closer closeCallback) {
|
private Snapshot createSnapshot(Path fullPath, Path realPath, @Nullable IORunnable closeCallback) {
|
||||||
return new FolderSnapshot(
|
return new FolderSnapshot(
|
||||||
createSnapshotInfo(idPath, ioPath), ioPath, closeCallback
|
createSnapshotInfo(fullPath, realPath), realPath, closeCallback
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,31 +128,27 @@ public class FileSystemSnapshotDatabase implements SnapshotDatabase {
|
|||||||
if (!name.getScheme().equals(SCHEME)) {
|
if (!name.getScheme().equals(SCHEME)) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
return getSnapshot(name.getSchemeSpecificPart());
|
// drop the / in the path to make it absolute
|
||||||
}
|
Path rawResolved = root.resolve(name.getSchemeSpecificPart());
|
||||||
|
|
||||||
private Optional<Snapshot> getSnapshot(String id) throws IOException {
|
|
||||||
Path rawResolved = root.resolve(id);
|
|
||||||
// Catch trickery with paths:
|
// Catch trickery with paths:
|
||||||
Path ioPath = rawResolved.normalize();
|
Path realPath = rawResolved.normalize();
|
||||||
if (!ioPath.startsWith(root)) {
|
if (!realPath.startsWith(root)) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
Path idPath = root.relativize(ioPath);
|
Optional<Snapshot> result = tryRegularFileSnapshot(root.relativize(realPath), realPath);
|
||||||
Optional<Snapshot> result = tryRegularFileSnapshot(idPath);
|
|
||||||
if (result.isPresent()) {
|
if (result.isPresent()) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (!Files.isDirectory(ioPath)) {
|
if (!Files.isDirectory(realPath)) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
return Optional.of(createSnapshot(idPath, ioPath, null));
|
return Optional.of(createSnapshot(root.relativize(realPath), realPath, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Snapshot> tryRegularFileSnapshot(Path idPath) throws IOException {
|
private Optional<Snapshot> tryRegularFileSnapshot(Path fullPath, Path realPath) throws IOException {
|
||||||
Closer closer = Closer.create();
|
Closer closer = Closer.create();
|
||||||
Path root = this.root;
|
Path root = this.root;
|
||||||
Path relative = idPath;
|
Path relative = root.relativize(realPath);
|
||||||
Iterator<Path> iterator = null;
|
Iterator<Path> iterator = null;
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -164,7 +156,6 @@ public class FileSystemSnapshotDatabase implements SnapshotDatabase {
|
|||||||
iterator = MorePaths.iterPaths(relative).iterator();
|
iterator = MorePaths.iterPaths(relative).iterator();
|
||||||
}
|
}
|
||||||
if (!iterator.hasNext()) {
|
if (!iterator.hasNext()) {
|
||||||
closer.close();
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
Path relativeNext = iterator.next();
|
Path relativeNext = iterator.next();
|
||||||
@ -173,17 +164,18 @@ public class FileSystemSnapshotDatabase implements SnapshotDatabase {
|
|||||||
// This will never be it.
|
// This will never be it.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Optional<ArchiveDir> newRootOpt = archiveNioSupport.tryOpenAsDir(next);
|
Optional<Path> newRootOpt = archiveNioSupport.tryOpenAsDir(next);
|
||||||
if (newRootOpt.isPresent()) {
|
if (newRootOpt.isPresent()) {
|
||||||
ArchiveDir archiveDir = newRootOpt.get();
|
root = newRootOpt.get();
|
||||||
root = archiveDir.getPath();
|
if (root.getFileSystem() != FileSystems.getDefault()) {
|
||||||
closer.register(archiveDir);
|
closer.register(root.getFileSystem());
|
||||||
|
}
|
||||||
// Switch path to path inside the archive
|
// Switch path to path inside the archive
|
||||||
relative = root.resolve(relativeNext.relativize(relative).toString());
|
relative = root.resolve(relativeNext.relativize(relative).toString());
|
||||||
iterator = null;
|
iterator = null;
|
||||||
// Check if it exists, if so open snapshot
|
// Check if it exists, if so open snapshot
|
||||||
if (Files.exists(relative)) {
|
if (Files.exists(relative)) {
|
||||||
return Optional.of(createSnapshot(idPath, relative, closer));
|
return Optional.of(createSnapshot(fullPath, relative, closer::close));
|
||||||
}
|
}
|
||||||
// Otherwise, we may have more archives to open.
|
// Otherwise, we may have more archives to open.
|
||||||
// Keep searching!
|
// Keep searching!
|
||||||
@ -199,97 +191,119 @@ public class FileSystemSnapshotDatabase implements SnapshotDatabase {
|
|||||||
/*
|
/*
|
||||||
There are a few possible snapshot formats we accept:
|
There are a few possible snapshot formats we accept:
|
||||||
- a world directory, identified by <worldName>/level.dat
|
- a world directory, identified by <worldName>/level.dat
|
||||||
|
<<<<<<< HEAD
|
||||||
- a directory with the world name, but no level.dat
|
- 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
|
- inside must be a timestamped directory/archive, which then has one of the two world
|
||||||
formats inside of it!
|
formats inside of it!
|
||||||
|
=======
|
||||||
|
>>>>>>> 18a55bc14... Add new experimental snapshot API (#524)
|
||||||
- a world archive, identified by <worldName>.ext
|
- a world archive, identified by <worldName>.ext
|
||||||
* does not need to have level.dat inside
|
* does not need to have level.dat inside
|
||||||
- a timestamped directory, identified by <stamp>, that can have
|
- a timestamped directory, identified by <stamp>, that can have
|
||||||
- the two world formats described above, inside the directory
|
- the two world formats described above, inside the directory
|
||||||
- a timestamped archive, identified by <stamp>.ext, that can have
|
- a timestamped archive, identified by <stamp>.ext, that can have
|
||||||
- the same as timestamped directory, but inside the archive.
|
- 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,
|
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
|
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.
|
with some files, e.g. world.qux.zip/world.qux is invalid, but world.qux.zip/world isn't.
|
||||||
*/
|
*/
|
||||||
return SafeFiles.noLeakFileList(root)
|
return Stream.of(
|
||||||
.flatMap(IOFunction.unchecked(entry -> {
|
listWorldEntries(Paths.get(""), root, worldName),
|
||||||
String worldEntry = getWorldEntry(worldName, entry);
|
listTimestampedEntries(Paths.get(""), root, worldName)
|
||||||
if (worldEntry != null) {
|
).flatMap(Function.identity());
|
||||||
return Stream.of(worldEntry);
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
String fileName = SafeFiles.canonicalFileName(entry);
|
// Try world archive
|
||||||
if (fileName.equals(worldName)
|
if (Files.isRegularFile(candidate)
|
||||||
&& Files.isDirectory(entry)
|
&& fileName.startsWith(worldName + ".")) {
|
||||||
&& !Files.exists(entry.resolve("level.dat"))) {
|
logger.debug("Archive!");
|
||||||
// world dir with timestamp entries
|
try {
|
||||||
return listTimestampedEntries(worldName, entry)
|
return tryRegularFileSnapshot(
|
||||||
.map(id -> worldName + "/" + id);
|
fullPath.resolve(fileName), candidate
|
||||||
|
).map(Stream::of).orElse(null);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return getTimestampedEntries(worldName, entry);
|
logger.debug("Nothing!");
|
||||||
}))
|
return null;
|
||||||
.map(IOFunction.unchecked(id ->
|
});
|
||||||
getSnapshot(id)
|
|
||||||
.orElseThrow(() ->
|
|
||||||
new AssertionError("Could not find discovered snapshot: " + id)
|
|
||||||
)
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<String> listTimestampedEntries(String worldName, Path directory) throws IOException {
|
private boolean isSameDirectoryName(String fileName, String worldName) {
|
||||||
return SafeFiles.noLeakFileList(directory)
|
if (fileName.lastIndexOf('/') == fileName.length() - 1) {
|
||||||
.flatMap(IOFunction.unchecked(entry -> getTimestampedEntries(worldName, entry)));
|
fileName = fileName.substring(0, fileName.length() - 1);
|
||||||
|
}
|
||||||
|
return fileName.equalsIgnoreCase(worldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<String> getTimestampedEntries(String worldName, Path entry) throws IOException {
|
private Stream<Snapshot> listTimestampedEntries(Path fullPath, Path root, String worldName) throws IOException {
|
||||||
ZonedDateTime dateTime = FileNameDateTimeParser.getInstance().detectDateTime(entry);
|
logger.debug("Timestamp check in: {}", root);
|
||||||
if (dateTime == null) {
|
return Files.list(root)
|
||||||
// nothing available at this path
|
.filter(candidate -> {
|
||||||
return Stream.of();
|
ZonedDateTime date = FileNameDateTimeParser.getInstance().detectDateTime(candidate);
|
||||||
}
|
return date != null;
|
||||||
String fileName = SafeFiles.canonicalFileName(entry);
|
})
|
||||||
if (Files.isDirectory(entry)) {
|
.flatMap(candidate -> {
|
||||||
// timestamped directory, find worlds inside
|
logger.debug("Timestamp trying: {}", candidate);
|
||||||
return listWorldEntries(worldName, entry)
|
// Try timestamped directory
|
||||||
.map(id -> fileName + "/" + id);
|
if (Files.isDirectory(candidate)) {
|
||||||
}
|
logger.debug("Timestamped directory");
|
||||||
if (!Files.isRegularFile(entry)) {
|
try {
|
||||||
// not an archive either?
|
return listWorldEntries(
|
||||||
return Stream.of();
|
fullPath.resolve(candidate.getFileName().toString()), candidate, worldName
|
||||||
}
|
);
|
||||||
Optional<ArchiveDir> asArchive = archiveNioSupport.tryOpenAsDir(entry);
|
} catch (IOException e) {
|
||||||
if (asArchive.isPresent()) {
|
throw new UncheckedIOException(e);
|
||||||
// timestamped archive
|
}
|
||||||
ArchiveDir dir = asArchive.get();
|
}
|
||||||
return listWorldEntries(worldName, dir.getPath())
|
// Otherwise archive, get it as a directory & unpack it
|
||||||
.map(id -> fileName + "/" + id)
|
try {
|
||||||
.onClose(IORunnable.unchecked(dir::close));
|
Optional<Path> newRoot = archiveNioSupport.tryOpenAsDir(candidate);
|
||||||
}
|
if (!newRoot.isPresent()) {
|
||||||
return Stream.of();
|
logger.debug("Nothing!");
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
private Stream<String> listWorldEntries(String worldName, Path directory) throws IOException {
|
logger.debug("Timestamped archive!");
|
||||||
return SafeFiles.noLeakFileList(directory)
|
return listWorldEntries(
|
||||||
.map(IOFunction.unchecked(entry -> getWorldEntry(worldName, entry)))
|
fullPath.resolve(candidate.getFileName().toString()),
|
||||||
.filter(Objects::nonNull);
|
newRoot.get(),
|
||||||
}
|
worldName
|
||||||
|
);
|
||||||
private String getWorldEntry(String worldName, Path entry) throws IOException {
|
} catch (IOException e) {
|
||||||
String fileName = SafeFiles.canonicalFileName(entry);
|
throw new UncheckedIOException(e);
|
||||||
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.jnbt.CompoundTag;
|
||||||
import com.sk89q.worldedit.math.BlockVector2;
|
import com.sk89q.worldedit.math.BlockVector2;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import com.sk89q.worldedit.util.io.Closer;
|
import com.sk89q.worldedit.util.function.IORunnable;
|
||||||
import com.sk89q.worldedit.world.DataException;
|
import com.sk89q.worldedit.world.DataException;
|
||||||
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
||||||
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotInfo;
|
import com.sk89q.worldedit.world.snapshot.experimental.SnapshotInfo;
|
||||||
@ -95,9 +95,9 @@ public class FolderSnapshot implements Snapshot {
|
|||||||
private final SnapshotInfo info;
|
private final SnapshotInfo info;
|
||||||
private final Path folder;
|
private final Path folder;
|
||||||
private final AtomicReference<Object> regionFolder = new AtomicReference<>();
|
private final AtomicReference<Object> regionFolder = new AtomicReference<>();
|
||||||
private final @Nullable Closer closeCallback;
|
private final @Nullable IORunnable closeCallback;
|
||||||
|
|
||||||
public FolderSnapshot(SnapshotInfo info, Path folder, @Nullable Closer closeCallback) {
|
public FolderSnapshot(SnapshotInfo info, Path folder, @Nullable IORunnable closeCallback) {
|
||||||
this.info = info;
|
this.info = info;
|
||||||
// This is required to force TrueVfs to properly resolve parents.
|
// This is required to force TrueVfs to properly resolve parents.
|
||||||
// Kinda odd, but whatever works.
|
// Kinda odd, but whatever works.
|
||||||
@ -160,7 +160,7 @@ public class FolderSnapshot implements Snapshot {
|
|||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
if (closeCallback != null) {
|
if (closeCallback != null) {
|
||||||
closeCallback.close();
|
closeCallback.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* WorldEdit, a Minecraft world manipulation toolkit
|
||||||
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||||
|
* Copyright (C) WorldEdit team and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser General Public License as published by the
|
||||||
|
* Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sk89q.worldedit.world.snapshot.experimental.fs;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.sk89q.worldedit.world.storage.LegacyChunkStore;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.file.FileSystem;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
|
||||||
|
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.CHUNK_DATA;
|
||||||
|
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.CHUNK_POS;
|
||||||
|
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.FORMATTER;
|
||||||
|
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.REGION_DATA;
|
||||||
|
|
||||||
|
interface EntryMaker<T> {
|
||||||
|
EntryMaker<ZonedDateTime> TIMESTAMPED_DIR = (directory, time) -> {
|
||||||
|
Path timestampedDir = directory.resolve(time.format(FORMATTER));
|
||||||
|
Files.createDirectories(timestampedDir);
|
||||||
|
return timestampedDir;
|
||||||
|
};
|
||||||
|
EntryMaker<ZonedDateTime> TIMESTAMPED_ARCHIVE = (directory, time) -> {
|
||||||
|
Path zipFile = directory.resolve(time.format(FORMATTER) + ".zip");
|
||||||
|
try (FileSystem zipFs = FileSystems.newFileSystem(
|
||||||
|
URI.create("jar:" + zipFile.toUri() + "!/"),
|
||||||
|
ImmutableMap.of("create", "true")
|
||||||
|
)) {
|
||||||
|
TIMESTAMPED_DIR.createEntry(zipFs.getPath("/"), time);
|
||||||
|
}
|
||||||
|
return zipFile;
|
||||||
|
};
|
||||||
|
EntryMaker<String> WORLD_DIR = (directory, worldName) -> {
|
||||||
|
Path worldDir = directory.resolve(worldName);
|
||||||
|
Files.createDirectories(worldDir);
|
||||||
|
Files.createFile(worldDir.resolve("level.dat"));
|
||||||
|
Path regionFolder = worldDir.resolve("region");
|
||||||
|
Files.createDirectory(regionFolder);
|
||||||
|
Files.write(regionFolder.resolve("r.0.0.mca"), REGION_DATA);
|
||||||
|
Files.write(regionFolder.resolve("r.1.1.mcr"), REGION_DATA);
|
||||||
|
return worldDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DimInfo {
|
||||||
|
final String worldName;
|
||||||
|
final int dim;
|
||||||
|
|
||||||
|
DimInfo(String worldName, int dim) {
|
||||||
|
this.worldName = worldName;
|
||||||
|
this.dim = dim;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryMaker<DimInfo> WORLD_DIM_DIR = (directory, dimInfo) -> {
|
||||||
|
Path worldDir = directory.resolve(dimInfo.worldName);
|
||||||
|
Files.createDirectories(worldDir);
|
||||||
|
Files.createFile(worldDir.resolve("level.dat"));
|
||||||
|
Path dimFolder = worldDir.resolve("DIM" + dimInfo.dim).resolve("region");
|
||||||
|
Files.createDirectories(dimFolder);
|
||||||
|
Files.write(dimFolder.resolve("r.0.0.mca"), REGION_DATA);
|
||||||
|
Files.write(dimFolder.resolve("r.1.1.mcr"), REGION_DATA);
|
||||||
|
return worldDir;
|
||||||
|
};
|
||||||
|
EntryMaker<String> WORLD_NO_REGION_DIR = (directory, worldName) -> {
|
||||||
|
Path worldDir = directory.resolve(worldName);
|
||||||
|
Files.createDirectories(worldDir);
|
||||||
|
Files.createFile(worldDir.resolve("level.dat"));
|
||||||
|
Files.write(worldDir.resolve("r.0.0.mca"), REGION_DATA);
|
||||||
|
Files.write(worldDir.resolve("r.1.1.mcr"), REGION_DATA);
|
||||||
|
return worldDir;
|
||||||
|
};
|
||||||
|
EntryMaker<String> WORLD_LEGACY_DIR = (directory, worldName) -> {
|
||||||
|
Path worldDir = directory.resolve(worldName);
|
||||||
|
Files.createDirectories(worldDir);
|
||||||
|
Files.createFile(worldDir.resolve("level.dat"));
|
||||||
|
Path chunkFile = worldDir.resolve(LegacyChunkStore.getFilename(
|
||||||
|
CHUNK_POS.toBlockVector2(), "/"
|
||||||
|
));
|
||||||
|
Files.createDirectories(chunkFile.getParent());
|
||||||
|
Files.write(chunkFile, CHUNK_DATA);
|
||||||
|
chunkFile = worldDir.resolve(LegacyChunkStore.getFilename(
|
||||||
|
CHUNK_POS.add(32, 0, 32).toBlockVector2(), "/"
|
||||||
|
));
|
||||||
|
Files.createDirectories(chunkFile.getParent());
|
||||||
|
Files.write(chunkFile, CHUNK_DATA);
|
||||||
|
return worldDir;
|
||||||
|
};
|
||||||
|
EntryMaker<String> WORLD_ARCHIVE = (directory, worldName) -> {
|
||||||
|
Path tempDir = Files.createTempDirectory("worldedit-fs-snap-db" + worldName);
|
||||||
|
Path temp = tempDir.resolve(worldName + ".zip");
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(temp);
|
||||||
|
try (FileSystem zipFs = FileSystems.newFileSystem(
|
||||||
|
URI.create("jar:" + temp.toUri() + "!/"),
|
||||||
|
ImmutableMap.of("create", "true")
|
||||||
|
)) {
|
||||||
|
WORLD_DIR.createEntry(zipFs.getPath("/"), worldName);
|
||||||
|
}
|
||||||
|
Path zipFile = directory.resolve(worldName + ".zip");
|
||||||
|
Files.copy(temp, zipFile);
|
||||||
|
return zipFile;
|
||||||
|
} finally {
|
||||||
|
Files.deleteIfExists(temp);
|
||||||
|
Files.deleteIfExists(tempDir);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Path createEntry(Path directory, T name) throws IOException;
|
||||||
|
|
||||||
|
}
|
@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
package com.sk89q.worldedit.world.snapshot.experimental.fs;
|
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.util.io.file.ArchiveNioSupport;
|
||||||
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
||||||
|
|
||||||
@ -30,7 +28,6 @@ import java.net.URI;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
@ -70,34 +67,19 @@ class FSSDContext {
|
|||||||
String worldName = Paths.get(name).getFileName().toString();
|
String worldName = Paths.get(name).getFileName().toString();
|
||||||
// Without an extension
|
// Without an extension
|
||||||
worldName = worldName.split("\\.")[0];
|
worldName = worldName.split("\\.")[0];
|
||||||
List<Snapshot> snapshots;
|
List<Snapshot> snapshots = db.getSnapshots(worldName).collect(toList());
|
||||||
try (Stream<Snapshot> snapshotStream = db.getSnapshots(worldName)) {
|
assertTrue(1 >= snapshots.size(),
|
||||||
snapshots = snapshotStream.collect(toList());
|
"Too many snapshots matched for " + worldName);
|
||||||
}
|
return requireSnapshot(name, snapshots.stream().findAny().orElse(null));
|
||||||
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) throws IOException {
|
Snapshot requireSnapshot(String name, @Nullable Snapshot snapshot) {
|
||||||
assertNotNull(snapshot, "No snapshot for " + name);
|
assertNotNull(snapshot, "No snapshot for " + name);
|
||||||
try {
|
assertEquals(name, snapshot.getInfo().getDisplayName());
|
||||||
assertEquals(name, snapshot.getInfo().getDisplayName());
|
|
||||||
} catch (Throwable t) {
|
|
||||||
Closer closer = Closer.create();
|
|
||||||
closer.register(snapshot);
|
|
||||||
throw closer.rethrowAndClose(t);
|
|
||||||
}
|
|
||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArchiveDir getRootOfArchive(Path archive) throws IOException {
|
Path getRootOfArchive(Path archive) throws IOException {
|
||||||
return archiveNioSupport.tryOpenAsDir(archive)
|
return archiveNioSupport.tryOpenAsDir(archive)
|
||||||
.orElseThrow(() -> new AssertionError("No archive opener for " + archive));
|
.orElseThrow(() -> new AssertionError("No archive opener for " + archive));
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ package com.sk89q.worldedit.world.snapshot.experimental.fs;
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
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.DataException;
|
||||||
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
||||||
import org.junit.jupiter.api.DynamicNode;
|
import org.junit.jupiter.api.DynamicNode;
|
||||||
@ -30,6 +29,7 @@ import org.junit.jupiter.api.DynamicTest;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.attribute.FileTime;
|
import java.nio.file.attribute.FileTime;
|
||||||
@ -38,8 +38,8 @@ import java.util.List;
|
|||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
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.CHUNK_TAG;
|
||||||
|
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.CHUNK_POS;
|
||||||
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.TIME_ONE;
|
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.TIME_TWO;
|
||||||
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.WORLD_ALPHA;
|
import static com.sk89q.worldedit.world.snapshot.experimental.fs.FileSystemSnapshotDatabaseTest.WORLD_ALPHA;
|
||||||
@ -102,11 +102,16 @@ enum FSSDTestType {
|
|||||||
List<DynamicTest> getTests(FSSDContext context) throws IOException {
|
List<DynamicTest> getTests(FSSDContext context) throws IOException {
|
||||||
Path worldArchive = EntryMaker.WORLD_ARCHIVE
|
Path worldArchive = EntryMaker.WORLD_ARCHIVE
|
||||||
.createEntry(context.db.getRoot(), WORLD_ALPHA);
|
.createEntry(context.db.getRoot(), WORLD_ALPHA);
|
||||||
try (ArchiveDir rootOfArchive = context.getRootOfArchive(worldArchive)) {
|
Path rootOfArchive = context.getRootOfArchive(worldArchive);
|
||||||
|
try {
|
||||||
Files.setLastModifiedTime(
|
Files.setLastModifiedTime(
|
||||||
rootOfArchive.getPath(),
|
rootOfArchive,
|
||||||
FileTime.from(TIME_ONE.toInstant())
|
FileTime.from(TIME_ONE.toInstant())
|
||||||
);
|
);
|
||||||
|
} finally {
|
||||||
|
if (rootOfArchive.getFileSystem() != FileSystems.getDefault()) {
|
||||||
|
rootOfArchive.getFileSystem().close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return singleSnapTest(context, WORLD_ALPHA + ".zip", TIME_ONE);
|
return singleSnapTest(context, WORLD_ALPHA + ".zip", TIME_ONE);
|
||||||
}
|
}
|
||||||
@ -139,9 +144,14 @@ enum FSSDTestType {
|
|||||||
Path root = context.db.getRoot();
|
Path root = context.db.getRoot();
|
||||||
Path timestampedArchive = EntryMaker.TIMESTAMPED_ARCHIVE
|
Path timestampedArchive = EntryMaker.TIMESTAMPED_ARCHIVE
|
||||||
.createEntry(root, TIME_ONE);
|
.createEntry(root, TIME_ONE);
|
||||||
try (ArchiveDir timestampedDir = context.getRootOfArchive(timestampedArchive)) {
|
Path timestampedDir = context.getRootOfArchive(timestampedArchive);
|
||||||
EntryMaker.WORLD_DIR.createEntry(timestampedDir.getPath(), WORLD_ALPHA);
|
try {
|
||||||
EntryMaker.WORLD_ARCHIVE.createEntry(timestampedDir.getPath(), WORLD_BETA);
|
EntryMaker.WORLD_DIR.createEntry(timestampedDir, WORLD_ALPHA);
|
||||||
|
EntryMaker.WORLD_ARCHIVE.createEntry(timestampedDir, WORLD_BETA);
|
||||||
|
} finally {
|
||||||
|
if (timestampedDir.getFileSystem() != FileSystems.getDefault()) {
|
||||||
|
timestampedDir.getFileSystem().close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ImmutableList.of(
|
return ImmutableList.of(
|
||||||
dynamicContainer("world dir",
|
dynamicContainer("world dir",
|
||||||
@ -251,18 +261,16 @@ enum FSSDTestType {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
List<DynamicTest> singleSnapTest(FSSDContext context, String name,
|
private static List<DynamicTest> singleSnapTest(FSSDContext context, String name,
|
||||||
ZonedDateTime time) {
|
ZonedDateTime time) {
|
||||||
return ImmutableList.of(
|
return ImmutableList.of(
|
||||||
dynamicTest("return a valid snapshot for " + name, () -> {
|
dynamicTest("return a valid snapshot for " + name, () -> {
|
||||||
try (Snapshot snapshot = context.requireSnapshot(name)) {
|
Snapshot snapshot = context.requireSnapshot(name);
|
||||||
assertValidSnapshot(time, snapshot);
|
assertValidSnapshot(time, snapshot);
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
dynamicTest("list a valid snapshot for " + name, () -> {
|
dynamicTest("list a valid snapshot for " + name, () -> {
|
||||||
try (Snapshot snapshot = context.requireListsSnapshot(name)) {
|
Snapshot snapshot = context.requireListsSnapshot(name);
|
||||||
assertValidSnapshot(time, snapshot);
|
assertValidSnapshot(time, snapshot);
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ import com.sk89q.worldedit.util.io.file.ZipArchiveNioSupport;
|
|||||||
import com.sk89q.worldedit.world.DataException;
|
import com.sk89q.worldedit.world.DataException;
|
||||||
import com.sk89q.worldedit.world.storage.ChunkStoreHelper;
|
import com.sk89q.worldedit.world.storage.ChunkStoreHelper;
|
||||||
import com.sk89q.worldedit.world.storage.McRegionReader;
|
import com.sk89q.worldedit.world.storage.McRegionReader;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.DynamicNode;
|
import org.junit.jupiter.api.DynamicNode;
|
||||||
@ -77,8 +76,6 @@ class FileSystemSnapshotDatabaseTest {
|
|||||||
.atZone(ZoneId.systemDefault());
|
.atZone(ZoneId.systemDefault());
|
||||||
static final ZonedDateTime TIME_TWO = TIME_ONE.minusDays(1);
|
static final ZonedDateTime TIME_TWO = TIME_ONE.minusDays(1);
|
||||||
|
|
||||||
private static Path TEMP_DIR;
|
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
static void setUpStatic() throws IOException, DataException {
|
static void setUpStatic() throws IOException, DataException {
|
||||||
try (InputStream in = Resources.getResource("world_region.mca.gzip").openStream();
|
try (InputStream in = Resources.getResource("world_region.mca.gzip").openStream();
|
||||||
@ -107,17 +104,10 @@ class FileSystemSnapshotDatabaseTest {
|
|||||||
} finally {
|
} finally {
|
||||||
reader.close();
|
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 {
|
private static Path newTempDb() throws IOException {
|
||||||
return Files.createTempDirectory(TEMP_DIR, "db");
|
return Files.createTempDirectory("worldedit-fs-snap-db");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void deleteTree(Path root) throws IOException {
|
private static void deleteTree(Path root) throws IOException {
|
||||||
@ -185,6 +175,7 @@ class FileSystemSnapshotDatabaseTest {
|
|||||||
try {
|
try {
|
||||||
Path dbRoot = root.resolve("snapshots");
|
Path dbRoot = root.resolve("snapshots");
|
||||||
Files.createDirectories(dbRoot);
|
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));
|
return type.getNamedTests(new FSSDContext(nioSupport, dbRoot));
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
deleteTree(root);
|
deleteTree(root);
|
||||||
|
BIN
worldedit-core/src/test/resources/world_region.mca.gzip
Normale Datei
BIN
worldedit-core/src/test/resources/world_region.mca.gzip
Normale Datei
Binäre Datei nicht angezeigt.
@ -61,7 +61,16 @@ tasks.named<Copy>("processResources") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
addJarManifest(includeClasspath = true)
|
addJarManifest(includeClasspath = true)
|
||||||
|
=======
|
||||||
|
tasks.named<Jar>("jar") {
|
||||||
|
manifest {
|
||||||
|
attributes("Class-Path" to CLASSPATH,
|
||||||
|
"WorldEdit-Version" to project.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>>>>>>> 18a55bc14... Add new experimental snapshot API (#524)
|
||||||
|
|
||||||
tasks.named<ShadowJar>("shadowJar") {
|
tasks.named<ShadowJar>("shadowJar") {
|
||||||
archiveClassifier.set("dist-dev")
|
archiveClassifier.set("dist-dev")
|
||||||
|
@ -87,6 +87,7 @@ tasks.named<ShadowJar>("shadowJar") {
|
|||||||
include(dependency("org.slf4j:slf4j-api"))
|
include(dependency("org.slf4j:slf4j-api"))
|
||||||
include(dependency("org.apache.logging.log4j:log4j-slf4j-impl"))
|
include(dependency("org.apache.logging.log4j:log4j-slf4j-impl"))
|
||||||
include(dependency("de.schlichtherle:truezip"))
|
include(dependency("de.schlichtherle:truezip"))
|
||||||
|
include(dependency("net.java.truevfs:truevfs-profile-default_2.13"))
|
||||||
include(dependency("org.mozilla:rhino"))
|
include(dependency("org.mozilla:rhino"))
|
||||||
}
|
}
|
||||||
minimize {
|
minimize {
|
||||||
|
@ -25,7 +25,16 @@ sponge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
addJarManifest(includeClasspath = true)
|
addJarManifest(includeClasspath = true)
|
||||||
|
=======
|
||||||
|
tasks.named<Jar>("jar") {
|
||||||
|
manifest {
|
||||||
|
attributes("Class-Path" to CLASSPATH,
|
||||||
|
"WorldEdit-Version" to project.version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>>>>>>> 18a55bc14... Add new experimental snapshot API (#524)
|
||||||
|
|
||||||
tasks.named<ShadowJar>("shadowJar") {
|
tasks.named<ShadowJar>("shadowJar") {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -122,9 +122,8 @@ public class ConfigurateConfiguration extends LocalConfiguration {
|
|||||||
showHelpInfo = node.getNode("show-help-on-first-use").getBoolean(true);
|
showHelpInfo = node.getNode("show-help-on-first-use").getBoolean(true);
|
||||||
|
|
||||||
String snapshotsDir = node.getNode("snapshots", "directory").getString("");
|
String snapshotsDir = node.getNode("snapshots", "directory").getString("");
|
||||||
if (!snapshotsDir.isEmpty()) {
|
boolean experimentalSnapshots = node.getNode("snapshots", "experimental").getBoolean(false);
|
||||||
snapshotRepo = new SnapshotRepository(snapshotsDir);
|
initializeSnapshotConfiguration(snapshotsDir, experimentalSnapshots);
|
||||||
}
|
|
||||||
|
|
||||||
String type = node.getNode("shell-save-type").getString("").trim();
|
String type = node.getNode("shell-save-type").getString("").trim();
|
||||||
shellSaveType = type.equals("") ? null : type;
|
shellSaveType = type.equals("") ? null : type;
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren