Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schematic caching system for suggestions #2542

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.internal.SchematicsEventListener;
import com.sk89q.worldedit.internal.expression.invoke.ReturnException;
import com.sk89q.worldedit.internal.schematic.SchematicsManager;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.scripting.CraftScriptContext;
Expand Down Expand Up @@ -127,6 +128,7 @@ public final class WorldEdit {
EvenMoreExecutors.newBoundedCachedThreadPool(0, 1, 20, "WorldEdit Task Executor - %s"));
private final Supervisor supervisor = new SimpleSupervisor();
private final AssetLoaders assetLoaders = new AssetLoaders(this);
private final SchematicsManager schematicsManager = new SchematicsManager(this);

private final BlockFactory blockFactory = new BlockFactory(this);
private final ItemFactory itemFactory = new ItemFactory(this);
Expand Down Expand Up @@ -262,6 +264,15 @@ public AssetLoaders getAssetLoaders() {
return assetLoaders;
}

/**
* Return the Schematics Manager instance.
*
* @return the schematics manager instance
*/
public SchematicsManager getSchematicsManager() {
return schematicsManager;
}

/**
* Gets the path to a file. This method will check to see if the filename
* has valid characters and has an extension. It also prevents directory
Expand Down Expand Up @@ -396,8 +407,10 @@ private File getSafeFileWithExtension(File dir, String filename, String extensio
return new File(dir, filename);
}

private static final java.util.regex.Pattern SAFE_FILENAME_REGEX = java.util.regex.Pattern.compile("^[A-Za-z0-9_\\- \\./\\\\'\\$@~!%\\^\\*\\(\\)\\[\\]\\+\\{\\},\\?]+\\.[A-Za-z0-9]+$");

private boolean checkFilename(String filename) {
return filename.matches("^[A-Za-z0-9_\\- \\./\\\\'\\$@~!%\\^\\*\\(\\)\\[\\]\\+\\{\\},\\?]+\\.[A-Za-z0-9]+$");
return SAFE_FILENAME_REGEX.matcher(filename).matches();
Comment on lines +410 to +413
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This set of characters doesn't make sense, it's not safe for windows (contains ?, *, doesn't ban COM and others), and POSIX will generally take anything. It also excludes extended Unicode characters for some reason, despite those being perfectly fine.

Can we just drop checking this aside from "you must have an extension", i.e. ^.+\..+$?

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter;
import com.sk89q.worldedit.extent.clipboard.io.share.ClipboardShareDestination;
import com.sk89q.worldedit.extent.clipboard.io.share.ClipboardShareMetadata;
import com.sk89q.worldedit.internal.annotation.SchematicPath;
import com.sk89q.worldedit.internal.schematic.SchematicsManager;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.session.ClipboardHolder;
Expand Down Expand Up @@ -71,8 +73,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
Expand Down Expand Up @@ -108,14 +108,17 @@ public SchematicCommands(WorldEdit worldEdit) {
)
@CommandPermissions({"worldedit.clipboard.load", "worldedit.schematic.load"})
public void load(Actor actor, LocalSession session,
@SchematicPath
@Arg(desc = "File name.")
String filename,
Path schematic,
@Arg(desc = "Format name.", def = "sponge")
ClipboardFormat format) throws FilenameException {
LocalConfiguration config = worldEdit.getConfiguration();

File dir = worldEdit.getWorkingDirectoryPath(config.saveDir).toFile();
File f = worldEdit.getSafeOpenFile(actor, dir, filename,
// Schematic.path is relative, so treat it as filename
String filename = schematic.toString();
File schematicsRoot = worldEdit.getSchematicsManager().getRoot().toFile();
File f = worldEdit.getSafeOpenFile(actor, schematicsRoot, filename,
BuiltInClipboardFormat.SPONGE_V3_SCHEMATIC.getPrimaryFileExtension(),
ClipboardFormats.getFileExtensionArray());

Expand Down Expand Up @@ -246,11 +249,14 @@ public void share(Actor actor, LocalSession session,
)
@CommandPermissions("worldedit.schematic.delete")
public void delete(Actor actor,
@SchematicPath
@Arg(desc = "File name.")
String filename) throws WorldEditException {
Path schematic) throws WorldEditException {
LocalConfiguration config = worldEdit.getConfiguration();
File dir = worldEdit.getWorkingDirectoryPath(config.saveDir).toFile();

// Schematic.path is relative, so treat it as filename
String filename = schematic.toString();
File f = worldEdit.getSafeOpenFile(actor,
dir, filename, "schematic", ClipboardFormats.getFileExtensionArray());

Expand Down Expand Up @@ -314,7 +320,6 @@ public void list(Actor actor,
if (oldFirst && newFirst) {
throw new StopExecutionException(TextComponent.of("Cannot sort by oldest and newest."));
}
final String saveDir = worldEdit.getConfiguration().saveDir;
Comparator<Path> pathComparator;
String flag;
if (oldFirst) {
Expand All @@ -331,7 +336,7 @@ public void list(Actor actor,
? "//schem list -p %page%" + flag : null;

WorldEditAsyncCommandBuilder.createAndSendMessage(actor,
new SchematicListTask(saveDir, pathComparator, page, pageCommand),
new SchematicListTask(pathComparator::compare, page, pageCommand),
SubtleFormat.wrap("(Please wait... gathering schematic list.)"));
}

Expand Down Expand Up @@ -399,6 +404,7 @@ private static class SchematicSaveTask extends SchematicOutputTask<Void> {
public Void call() throws Exception {
try {
writeToOutputStream(new FileOutputStream(file));
WorldEdit.getInstance().getSchematicsManager().update();
LOGGER.info(actor.getName() + " saved " + file.getCanonicalPath() + (overwrite ? " (overwriting previous file)" : ""));
} catch (IOException e) {
file.delete();
Expand Down Expand Up @@ -439,46 +445,31 @@ public Consumer<Actor> call() throws Exception {
private static class SchematicListTask implements Callable<Component> {
private final Comparator<Path> pathComparator;
private final int page;
private final Path rootDir;
private final String pageCommand;

SchematicListTask(String prefix, Comparator<Path> pathComparator, int page, String pageCommand) {
SchematicListTask(Comparator<Path> pathComparator, int page, String pageCommand) {
this.pathComparator = pathComparator;
this.page = page;
this.rootDir = WorldEdit.getInstance().getWorkingDirectoryPath(prefix);
this.pageCommand = pageCommand;
}

@Override
public Component call() throws Exception {
Path resolvedRoot = rootDir.toRealPath();
List<Path> fileList = allFiles(resolvedRoot);
SchematicsManager schematicsManager = WorldEdit.getInstance().getSchematicsManager();
// Copy this to a mutable list, we're sorting it below.
List<Path> fileList = new ArrayList<>(schematicsManager.getSchematicPaths());

if (fileList.isEmpty()) {
return ErrorFormat.wrap("No schematics found.");
}

fileList.sort(pathComparator);

PaginationBox paginationBox = new SchematicPaginationBox(resolvedRoot, fileList, pageCommand);
PaginationBox paginationBox = new SchematicPaginationBox(schematicsManager.getRoot(), fileList, pageCommand);
return paginationBox.create(page);
}
}

private static List<Path> allFiles(Path root) throws IOException {
List<Path> pathList = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(root)) {
for (Path path : stream) {
if (Files.isDirectory(path)) {
pathList.addAll(allFiles(path));
} else {
pathList.add(path);
}
}
}
return pathList;
}

private static class SchematicPaginationBox extends PaginationBox {
private final Path rootDir;
private final List<Path> files;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.sk89q.worldedit.command.argument;

import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.internal.annotation.SchematicPath;
import com.sk89q.worldedit.internal.schematic.SchematicsManager;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.util.io.file.FilenameException;
import org.enginehub.piston.CommandManager;
import org.enginehub.piston.converter.ArgumentConverter;
import org.enginehub.piston.converter.ConversionResult;
import org.enginehub.piston.converter.FailedConversion;
import org.enginehub.piston.converter.SuccessfulConversion;
import org.enginehub.piston.inject.InjectedValueAccess;
import org.enginehub.piston.inject.Key;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import static org.enginehub.piston.converter.SuggestionHelper.limitByPrefix;

public class SchematicConverter implements ArgumentConverter<Path> {

public static void register(WorldEdit worldEdit, CommandManager commandManager) {
commandManager.registerConverter(Key.of(Path.class, SchematicPath.class), new SchematicConverter(worldEdit));
}

private final WorldEdit worldEdit;

private SchematicConverter(WorldEdit worldEdit) {
this.worldEdit = worldEdit;
}

private final TextComponent choices = TextComponent.of("schematic filename");

@Override
public Component describeAcceptableArguments() {
return choices;
}

@Override
public List<String> getSuggestions(String input, InjectedValueAccess context) {
SchematicsManager schematicsManager = worldEdit.getSchematicsManager();
Path schematicsRootPath = schematicsManager.getRoot();

return limitByPrefix(schematicsManager.getSchematicPaths().stream()
.map(s -> schematicsRootPath.relativize(s).toString()), input);
}

@Override
public ConversionResult<Path> convert(String s, InjectedValueAccess injectedValueAccess) {
Path schematicsRoot = worldEdit.getSchematicsManager().getRoot();
// resolve as subpath of schematicsRoot
Path schematicPath = schematicsRoot.resolve(s).toAbsolutePath();
// then check whether it is still a subpath to rule out "../"
if (!schematicPath.startsWith(schematicsRoot)) {
return FailedConversion.from(new FilenameException(s));
}
// check whether the file exists
if (Files.exists(schematicPath)) {
// continue as relative path to schematicsRoot
schematicPath = schematicsRoot.relativize(schematicPath);
return SuccessfulConversion.fromSingle(schematicPath);
} else {
return FailedConversion.from(new FilenameException(s));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ void uninitialize(PlatformManager platformManager, Platform platform) {
@Override
void initialize(PlatformManager platformManager, Platform platform) {
WorldEdit.getInstance().getAssetLoaders().init();
WorldEdit.getInstance().getSchematicsManager().init();
}

@Override
void uninitialize(PlatformManager platformManager, Platform platform) {
WorldEdit.getInstance().getSchematicsManager().uninit();
WorldEdit.getInstance().getAssetLoaders().uninit();
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import com.sk89q.worldedit.command.argument.OffsetConverter;
import com.sk89q.worldedit.command.argument.RegionFactoryConverter;
import com.sk89q.worldedit.command.argument.RegistryConverter;
import com.sk89q.worldedit.command.argument.SchematicConverter;
import com.sk89q.worldedit.command.argument.SelectorChoiceConverter;
import com.sk89q.worldedit.command.argument.SideEffectConverter;
import com.sk89q.worldedit.command.argument.SideEffectSetConverter;
Expand Down Expand Up @@ -232,6 +233,7 @@ private void registerArgumentConverters() {
ClipboardFormatConverter.register(commandManager);
ClipboardShareDestinationConverter.register(commandManager);
SelectorChoiceConverter.register(commandManager);
SchematicConverter.register(worldEdit, commandManager);
}

private void registerAlwaysInjectedValues() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.sk89q.worldedit.internal.annotation;

import org.enginehub.piston.inject.InjectAnnotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to denote an argument as a schematic path.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@InjectAnnotation
public @interface SchematicPath {
}
Loading