From 45f1de60099f781fcd755cd1d001e649cf8ae685 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Tue, 18 Dec 2018 19:28:55 +1000 Subject: [PATCH] Move the pasting system over to WorldEdit, and add a /we report command. Currently only reports system status and the config. Need to add a platform reporter system. --- config/checkstyle/import-control.xml | 1 + .../worldedit/bukkit/BukkitConfiguration.java | 3 +- worldedit-core/build.gradle | 2 + .../java/com/sk89q/worldedit/WorldEdit.java | 19 +- .../worldedit/command/WorldEditCommands.java | 38 +- .../command/argument/ItemUseParser.java | 2 +- .../command/argument/NumberParser.java | 2 +- .../command/argument/StringParser.java | 2 +- .../command/util/AsyncCommandHelper.java | 135 +++++ .../command/util/FutureProgressListener.java | 54 ++ .../command/util/MessageFutureCallback.java | 114 ++++ .../command/util/MessageTimerTask.java | 46 ++ .../util/PropertiesConfiguration.java | 2 +- .../worldedit/util/YAMLConfiguration.java | 7 +- .../sk89q/worldedit/util/net/HttpRequest.java | 490 ++++++++++++++++++ .../util/paste/ActorCallbackPaste.java | 70 +++ .../worldedit/util/paste/EngineHubPaste.java | 78 +++ .../worldedit/util/paste/IncendoPaste.java | 280 ++++++++++ .../sk89q/worldedit/util/paste/Pastebin.java | 93 ++++ .../sk89q/worldedit/util/paste/Paster.java | 30 ++ .../sk89q/worldedit/util/paste/Pasters.java | 44 ++ .../worldedit/util/report/ConfigReport.java | 32 ++ .../util/report/HierarchyObjectReport.java | 32 ++ .../util/report/ShallowObjectReport.java | 5 +- .../worldedit/util/task/AbstractTask.java | 74 +++ .../util/task/FutureForwardingTask.java | 121 +++++ .../worldedit/util/task/SimpleSupervisor.java | 59 +++ .../sk89q/worldedit/util/task/Supervisor.java | 44 ++ .../com/sk89q/worldedit/util/task/Task.java | 97 ++++ .../util/task/TaskStateComparator.java | 43 ++ .../util/task/progress/Progress.java | 183 +++++++ .../util/task/progress/ProgressIterator.java | 100 ++++ .../task/progress/ProgressObservable.java | 34 ++ .../transform/BlockTransformExtentTest.java | 69 +++ .../config/ConfigurateConfiguration.java | 7 +- 35 files changed, 2395 insertions(+), 17 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/command/util/AsyncCommandHelper.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/command/util/FutureProgressListener.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageFutureCallback.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageTimerTask.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/net/HttpRequest.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/ActorCallbackPaste.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/EngineHubPaste.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/IncendoPaste.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pastebin.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Paster.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pasters.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ConfigReport.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/report/HierarchyObjectReport.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/task/AbstractTask.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/task/FutureForwardingTask.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/task/SimpleSupervisor.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Supervisor.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Task.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/task/TaskStateComparator.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/Progress.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressIterator.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressObservable.java create mode 100644 worldedit-core/src/test/java/com/sk89q/worldedit/extent/transform/BlockTransformExtentTest.java diff --git a/config/checkstyle/import-control.xml b/config/checkstyle/import-control.xml index 8b40e60d5..eedd07857 100644 --- a/config/checkstyle/import-control.xml +++ b/config/checkstyle/import-control.xml @@ -14,6 +14,7 @@ + diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java index 5a20cdfbc..494a464ea 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitConfiguration.java @@ -21,6 +21,7 @@ package com.sk89q.worldedit.bukkit; import com.sk89q.util.yaml.YAMLProcessor; import com.sk89q.worldedit.util.YAMLConfiguration; +import com.sk89q.worldedit.util.report.Unreported; import java.io.File; @@ -30,7 +31,7 @@ import java.io.File; public class BukkitConfiguration extends YAMLConfiguration { public boolean noOpPermissions = false; - private final WorldEditPlugin plugin; + @Unreported private final WorldEditPlugin plugin; public BukkitConfiguration(YAMLProcessor config, WorldEditPlugin plugin) { super(config, plugin.getLogger()); diff --git a/worldedit-core/build.gradle b/worldedit-core/build.gradle index c140d1989..a9ab23076 100644 --- a/worldedit-core/build.gradle +++ b/worldedit-core/build.gradle @@ -12,6 +12,8 @@ dependencies { compile 'com.thoughtworks.paranamer:paranamer:2.6' compile 'com.google.code.gson:gson:2.8.0' compile 'com.sk89q.lib:jlibnoise:1.0.0' + compile 'com.googlecode.json-simple:json-simple:1.1.1' + //compile 'net.sf.trove4j:trove4j:3.0.3' testCompile 'org.mockito:mockito-core:1.9.0-rc1' // Fawe depends diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java b/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java index 76f2377d3..4ccc9fdb3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/WorldEdit.java @@ -56,7 +56,9 @@ import com.sk89q.worldedit.util.io.file.FileSelectionAbortedException; import com.sk89q.worldedit.util.io.file.FilenameException; import com.sk89q.worldedit.util.io.file.FilenameResolutionException; import com.sk89q.worldedit.util.io.file.InvalidFilenameException; -import com.sk89q.worldedit.util.logging.WorldEditPrefixHandler; +import com.sk89q.worldedit.util.logging.WorldEditPrefixHandler; +import com.sk89q.worldedit.util.task.SimpleSupervisor; +import com.sk89q.worldedit.util.task.Supervisor; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.registry.BundledBlockData; @@ -100,7 +102,8 @@ public class WorldEdit { private final EventBus eventBus = new EventBus(); private final PlatformManager platformManager = new PlatformManager(this); private final EditSessionFactory editSessionFactory = new EditSessionFactory.EditSessionFactoryImpl(eventBus); - private final SessionManager sessions = new SessionManager(this); + private final SessionManager sessions = new SessionManager(this); + private final Supervisor supervisor = new SimpleSupervisor(); private final BlockFactory blockFactory = new BlockFactory(this); private final ItemFactory itemFactory = new ItemFactory(this); @@ -148,7 +151,17 @@ public class WorldEdit { */ public EventBus getEventBus() { return eventBus; - } + } + + /** + * Get the supervisor. + * + * @return the supervisor + */ + public Supervisor getSupervisor() { + return supervisor; + } + /** * Get the block factory from which new {@link BlockStateHolder}s can be diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java index 8caa07c09..c4e957bab 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java @@ -28,6 +28,7 @@ import com.boydti.fawe.util.HastebinUtility; import com.boydti.fawe.util.StringMan; import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.Updater; +import com.google.common.io.Files; import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandPermissions; @@ -40,6 +41,18 @@ import com.sk89q.worldedit.extension.platform.*; import java.io.IOException; import java.net.URL; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.extension.platform.PlatformManager; +import com.sk89q.worldedit.util.paste.ActorCallbackPaste; +import com.sk89q.worldedit.util.report.ConfigReport; +import com.sk89q.worldedit.util.report.ReportList; +import com.sk89q.worldedit.util.report.SystemInfoReport; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; @@ -189,7 +202,8 @@ public class WorldEditCommands { ) @CommandPermissions("worldedit.debugpaste") public void debugpaste(Actor actor) throws WorldEditException, IOException { - BBC.DOWNLOAD_LINK.send(actor, HastebinUtility.debugPaste()); +// BBC.DOWNLOAD_LINK.send(actor, HastebinUtility.debugPaste()); + ActorCallbackPaste.pastebin(we.getSupervisor(), actor, null, "FastAsyncWorldEdit report: " + BBC.DOWNLOAD_LINK); } @Command( @@ -213,6 +227,28 @@ public class WorldEditCommands { } } +// @Command(aliases = {"report"}, desc = "Writes a report on WorldEdit", flags = "p", max = 0) +// @CommandPermissions({"worldedit.report"}) +// public void report(Actor actor, CommandContext args) throws WorldEditException { +// ReportList report = new ReportList("Report"); +// report.add(new SystemInfoReport()); +// report.add(new ConfigReport()); +// String result = report.toString(); +// +// try { +// File dest = new File(we.getWorkingDirectoryFile(we.getConfiguration().saveDir), "report.txt"); +// Files.write(result, dest, Charset.forName("UTF-8")); +// actor.print("WorldEdit report written to " + dest.getAbsolutePath()); +// } catch (IOException e) { +// actor.printError("Failed to write report: " + e.getMessage()); +// } +// +// if (args.hasFlag('p')) { +// actor.checkPermission("worldedit.report.pastebin"); +// ActorCallbackPaste.pastebin(we.getSupervisor(), actor, result, "WorldEdit report: %s.report"); +// } +// } + @Command( aliases = {"cui"}, usage = "", diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/ItemUseParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/ItemUseParser.java index 816516978..f6da447a1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/ItemUseParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/ItemUseParser.java @@ -35,7 +35,7 @@ import com.sk89q.worldedit.world.World; public class ItemUseParser extends SimpleCommand> { - private final ItemParser itemParser = addParameter(new ItemParser("item", "minecraft:dye:15")); + private final ItemParser itemParser = addParameter(new ItemParser("item", "minecraft:bone_meal")); @Override public Contextual call(CommandArgs args, CommandLocals locals) throws CommandException { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/NumberParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/NumberParser.java index 9c7f2fed2..349089e50 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/NumberParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/NumberParser.java @@ -62,7 +62,7 @@ public class NumberParser implements CommandExecutor { @Override public List getSuggestions(CommandArgs args, CommandLocals locals) throws MissingArgumentException { String value = args.next(); - return value.isEmpty() && defaultSuggestion != null ? Lists.newArrayList(defaultSuggestion) : Collections.emptyList(); + return value.isEmpty() && defaultSuggestion != null ? Lists.newArrayList(defaultSuggestion) : Collections.emptyList(); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/StringParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/StringParser.java index 518024cd3..298f999e4 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/StringParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/StringParser.java @@ -57,7 +57,7 @@ public class StringParser implements CommandExecutor { @Override public List getSuggestions(CommandArgs args, CommandLocals locals) throws MissingArgumentException { String value = args.next(); - return value.isEmpty() && defaultSuggestion != null ? Lists.newArrayList(defaultSuggestion) : Collections.emptyList(); + return value.isEmpty() && defaultSuggestion != null ? Lists.newArrayList(defaultSuggestion) : Collections.emptyList(); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/AsyncCommandHelper.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/AsyncCommandHelper.java new file mode 100644 index 000000000..0f521336d --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/AsyncCommandHelper.java @@ -0,0 +1,135 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.command.util; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.task.FutureForwardingTask; +import com.sk89q.worldedit.util.task.Supervisor; +import com.sk89q.worldedit.world.World; + +import javax.annotation.Nullable; + +public class AsyncCommandHelper { + + private final ListenableFuture future; + private final Supervisor supervisor; + private final Actor sender; + @Nullable + private Object[] formatArgs; + + private AsyncCommandHelper(ListenableFuture future, Supervisor supervisor, Actor sender) { + checkNotNull(future); + checkNotNull(supervisor); + checkNotNull(sender); + + this.future = future; + this.supervisor = supervisor; + this.sender = sender; + } + + public AsyncCommandHelper formatUsing(Object... args) { + this.formatArgs = args; + return this; + } + + private String format(String message) { + if (formatArgs != null) { + return String.format(message, formatArgs); + } else { + return message; + } + } + + public AsyncCommandHelper registerWithSupervisor(String description) { + supervisor.monitor( + FutureForwardingTask.create( + future, format(description), sender)); + return this; + } + + public AsyncCommandHelper sendMessageAfterDelay(String message) { + FutureProgressListener.addProgressListener(future, sender, format(message)); + return this; + } + + public AsyncCommandHelper thenRespondWith(String success, String failure) { + // Send a response message + Futures.addCallback( + future, + new MessageFutureCallback.Builder(sender) + .onSuccess(format(success)) + .onFailure(format(failure)) + .build()); + return this; + } + + public AsyncCommandHelper thenTellErrorsOnly(String failure) { + // Send a response message + Futures.addCallback( + future, + new MessageFutureCallback.Builder(sender) + .onFailure(format(failure)) + .build()); + return this; + } + + public AsyncCommandHelper forRegionDataLoad(World world, boolean silent) { + checkNotNull(world); + + formatUsing(world.getName()); + registerWithSupervisor("Loading region data for '%s'"); + if (silent) { + thenTellErrorsOnly("Failed to load regions '%s'"); + } else { + sendMessageAfterDelay("(Please wait... loading the region data for '%s')"); + thenRespondWith( + "Loaded region data for '%s'", + "Failed to load regions '%s'"); + } + + return this; + } + + public AsyncCommandHelper forRegionDataSave(World world, boolean silent) { + checkNotNull(world); + + formatUsing(world.getName()); + registerWithSupervisor("Saving region data for '%s'"); + if (silent) { + thenTellErrorsOnly("Failed to save regions '%s'"); + } else { + sendMessageAfterDelay("(Please wait... saving the region data for '%s')"); + thenRespondWith( + "Saved region data for '%s'", + "Failed to load regions '%s'"); + } + + return this; + } + + public static AsyncCommandHelper wrap(ListenableFuture future, Supervisor supervisor, Actor sender) { + return new AsyncCommandHelper(future, supervisor, sender); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/FutureProgressListener.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/FutureProgressListener.java new file mode 100644 index 000000000..54d366ba4 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/FutureProgressListener.java @@ -0,0 +1,54 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.command.util; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import com.sk89q.worldedit.extension.platform.Actor; + +import java.util.Timer; + +public class FutureProgressListener implements Runnable { + + private static final Timer timer = new Timer(); + private static final int MESSAGE_DELAY = 1000; + + private final MessageTimerTask task; + + public FutureProgressListener(Actor sender, String message) { + checkNotNull(sender); + checkNotNull(message); + + task = new MessageTimerTask(sender, message); + timer.schedule(task, MESSAGE_DELAY); + } + + @Override + public void run() { + task.cancel(); + } + + public static void addProgressListener(ListenableFuture future, Actor sender, String message) { + future.addListener(new FutureProgressListener(sender, message), MoreExecutors.directExecutor()); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageFutureCallback.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageFutureCallback.java new file mode 100644 index 000000000..3abb73545 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageFutureCallback.java @@ -0,0 +1,114 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.command.util; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.util.concurrent.FutureCallback; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.command.parametric.ExceptionConverter; + +import javax.annotation.Nullable; + +public class MessageFutureCallback implements FutureCallback { + + private final ExceptionConverter exceptionConverter; + private final Actor sender; + @Nullable + private final String success; + @Nullable + private final String failure; + + private MessageFutureCallback(ExceptionConverter exceptionConverter, Actor sender, @Nullable String success, @Nullable String failure) { + this.exceptionConverter = exceptionConverter; + this.sender = sender; + this.success = success; + this.failure = failure; + } + + @Override + public void onSuccess(@Nullable V v) { + if (success != null) { + sender.print(success); + } + } + + @Override + public void onFailure(@Nullable Throwable throwable) { + try { + exceptionConverter.convert(throwable); + } catch (CommandException e) { + String failure = this.failure != null ? this.failure : "An error occurred"; + String message = e.getMessage() != null ? e.getMessage() : "An unknown error occurred. Please see the console!"; + sender.printError(failure + ": " + message); + } + } + + public static class Builder { + private final Actor sender; + @Nullable + private String success; + @Nullable + private String failure; + private ExceptionConverter exceptionConverter; + + public Builder(Actor sender) { + checkNotNull(sender); + + this.sender = sender; + } + + public Builder exceptionConverter(ExceptionConverter exceptionConverter) { + this.exceptionConverter = exceptionConverter; + return this; + } + + public Builder onSuccess(@Nullable String message) { + this.success = message; + return this; + } + + public Builder onFailure(@Nullable String message) { + this.failure = message; + return this; + } + + public MessageFutureCallback build() { + checkNotNull(exceptionConverter); + return new MessageFutureCallback<>(exceptionConverter, sender, success, failure); + } + } + + public static MessageFutureCallback createRegionLoadCallback(ExceptionConverter exceptionConverter, Actor sender) { + return new Builder(sender) + .exceptionConverter(exceptionConverter) + .onSuccess("Successfully load the region data.") + .build(); + } + + public static MessageFutureCallback createRegionSaveCallback(ExceptionConverter exceptionConverter, Actor sender) { + return new Builder(sender) + .exceptionConverter(exceptionConverter) + .onSuccess("Successfully saved the region data.") + .build(); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageTimerTask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageTimerTask.java new file mode 100644 index 000000000..4a2549318 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/util/MessageTimerTask.java @@ -0,0 +1,46 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.command.util; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.extension.platform.Actor; + +import java.util.TimerTask; + +public class MessageTimerTask extends TimerTask { + + private final Actor sender; + private final String message; + + MessageTimerTask(Actor sender, String message) { + checkNotNull(sender); + checkNotNull(message); + + this.sender = sender; + this.message = message; + } + + @Override + public void run() { + sender.printDebug(message); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java index 8f31a3c1e..4e100337d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java @@ -280,4 +280,4 @@ public class PropertiesConfiguration extends LocalConfiguration { } } -} +} \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java index bb67d5bb8..9b1d9fd6f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java @@ -24,6 +24,7 @@ import com.sk89q.util.yaml.YAMLProcessor; import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.session.SessionManager; +import com.sk89q.worldedit.util.report.Unreported; import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.item.ItemTypes; import com.sk89q.worldedit.world.snapshot.SnapshotRepository; @@ -40,8 +41,8 @@ import java.util.stream.Collectors; */ public class YAMLConfiguration extends LocalConfiguration { - protected final YAMLProcessor config; - protected final Logger logger; + @Unreported protected final YAMLProcessor config; + @Unreported protected final Logger logger; public YAMLConfiguration(YAMLProcessor config, Logger logger) { this.config = config; @@ -131,4 +132,4 @@ public class YAMLConfiguration extends LocalConfiguration { public void unload() { } -} +} \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/net/HttpRequest.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/net/HttpRequest.java new file mode 100644 index 000000000..8421210e4 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/net/HttpRequest.java @@ -0,0 +1,490 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.net; + +import com.sk89q.worldedit.util.io.Closer; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HttpRequest implements Closeable { + + private static final int CONNECT_TIMEOUT = 1000 * 5; + private static final int READ_TIMEOUT = 1000 * 5; + private static final int READ_BUFFER_SIZE = 1024 * 8; + + private final Map headers = new HashMap<>(); + private final String method; + private final URL url; + private String contentType; + private byte[] body; + private HttpURLConnection conn; + private InputStream inputStream; + + private long contentLength = -1; + private long readBytes = 0; + + /** + * Create a new HTTP request. + * + * @param method the method + * @param url the URL + */ + private HttpRequest(String method, URL url) { + this.method = method; + this.url = url; + } + + /** + * Submit data. + * + * @return this object + */ + public HttpRequest body(String data) { + body = data.getBytes(); + return this; + } + + /** + * Submit form data. + * + * @param form the form + * @return this object + */ + public HttpRequest bodyForm(Form form) { + contentType = "application/x-www-form-urlencoded"; + body = form.toString().getBytes(); + return this; + } + + /** + * Add a header. + * + * @param key the header key + * @param value the header value + * @return this object + */ + public HttpRequest header(String key, String value) { + if (key.equalsIgnoreCase("Content-Type")) { + contentType = value; + } else { + headers.put(key, value); + } + return this; + } + + /** + * Execute the request. + *

+ * After execution, {@link #close()} should be called. + * + * @return this object + * @throws java.io.IOException on I/O error + */ + public HttpRequest execute() throws IOException { + boolean successful = false; + + try { + if (conn != null) { + throw new IllegalArgumentException("Connection already executed"); + } + + conn = (HttpURLConnection) reformat(url).openConnection(); + conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Java)"); + + if (body != null) { + conn.setRequestProperty("Content-Type", contentType); + conn.setRequestProperty("Content-Length", Integer.toString(body.length)); + conn.setDoInput(true); + } + + for (Map.Entry entry : headers.entrySet()) { + conn.setRequestProperty(entry.getKey(), entry.getValue()); + } + + conn.setRequestMethod(method); + conn.setUseCaches(false); + conn.setDoOutput(true); + conn.setConnectTimeout(CONNECT_TIMEOUT); + conn.setReadTimeout(READ_TIMEOUT); + + conn.connect(); + + if (body != null) { + DataOutputStream out = new DataOutputStream(conn.getOutputStream()); + out.write(body); + out.flush(); + out.close(); + } + + inputStream = conn.getResponseCode() == HttpURLConnection.HTTP_OK ? + conn.getInputStream() : conn.getErrorStream(); + + successful = true; + } finally { + if (!successful) { + close(); + } + } + + return this; + } + + /** + * Require that the response code is one of the given response codes. + * + * @param codes a list of codes + * @return this object + * @throws java.io.IOException if there is an I/O error or the response code is not expected + */ + public HttpRequest expectResponseCode(int... codes) throws IOException { + int responseCode = getResponseCode(); + + for (int code : codes) { + if (code == responseCode) { + return this; + } + } + + close(); + throw new IOException("Did not get expected response code, got " + responseCode + " for " + url); + } + + /** + * Get the response code. + * + * @return the response code + * @throws java.io.IOException on I/O error + */ + public int getResponseCode() throws IOException { + if (conn == null) { + throw new IllegalArgumentException("No connection has been made"); + } + + return conn.getResponseCode(); + } + + /** + * Get the input stream. + * + * @return the input stream + */ + public InputStream getInputStream() { + return inputStream; + } + + /** + * Buffer the returned response. + * + * @return the buffered response + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public BufferedResponse returnContent() throws IOException, InterruptedException { + if (inputStream == null) { + throw new IllegalArgumentException("No input stream available"); + } + + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int b = 0; + while ((b = inputStream.read()) != -1) { + bos.write(b); + } + return new BufferedResponse(bos.toByteArray()); + } finally { + close(); + } + } + + /** + * Save the result to a file. + * + * @param file the file + * @return this object + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public HttpRequest saveContent(File file) throws IOException, InterruptedException { + Closer closer = Closer.create(); + + try { + FileOutputStream fos = closer.register(new FileOutputStream(file)); + BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos)); + + saveContent(bos); + } finally { + closer.close(); + } + + return this; + } + + /** + * Save the result to an output stream. + * + * @param out the output stream + * @return this object + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public HttpRequest saveContent(OutputStream out) throws IOException, InterruptedException { + BufferedInputStream bis; + + try { + String field = conn.getHeaderField("Content-Length"); + if (field != null) { + long len = Long.parseLong(field); + if (len >= 0) { // Let's just not deal with really big numbers + contentLength = len; + } + } + } catch (NumberFormatException ignored) { + } + + try { + bis = new BufferedInputStream(inputStream); + + byte[] data = new byte[READ_BUFFER_SIZE]; + int len = 0; + while ((len = bis.read(data, 0, READ_BUFFER_SIZE)) >= 0) { + out.write(data, 0, len); + readBytes += len; + } + } finally { + close(); + } + + return this; + } + + @Override + public void close() throws IOException { + if (conn != null) conn.disconnect(); + } + + /** + * Perform a GET request. + * + * @param url the URL + * @return a new request object + */ + public static HttpRequest get(URL url) { + return request("GET", url); + } + + /** + * Perform a POST request. + * + * @param url the URL + * @return a new request object + */ + public static HttpRequest post(URL url) { + return request("POST", url); + } + + /** + * Perform a request. + * + * @param method the method + * @param url the URL + * @return a new request object + */ + public static HttpRequest request(String method, URL url) { + return new HttpRequest(method, url); + } + + /** + * Create a new {@link java.net.URL} and throw a {@link RuntimeException} if the URL + * is not valid. + * + * @param url the url + * @return a URL object + * @throws RuntimeException if the URL is invalid + */ + public static URL url(String url) { + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + /** + * URL may contain spaces and other nasties that will cause a failure. + * + * @param existing the existing URL to transform + * @return the new URL, or old one if there was a failure + */ + private static URL reformat(URL existing) { + try { + URL url = new URL(existing.toString()); + URI uri = new URI( + url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), + url.getPath(), url.getQuery(), url.getRef()); + url = uri.toURL(); + return url; + } catch (MalformedURLException e) { + return existing; + } catch (URISyntaxException e) { + return existing; + } + } + + /** + * Used with {@link #bodyForm(Form)}. + */ + public final static class Form { + public final List elements = new ArrayList<>(); + + private Form() { + } + + /** + * Add a key/value to the form. + * + * @param key the key + * @param value the value + * @return this object + */ + public Form add(String key, String value) { + try { + elements.add(URLEncoder.encode(key, "UTF-8") + + "=" + URLEncoder.encode(value, "UTF-8")); + return this; + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + boolean first = true; + for (String element : elements) { + if (first) { + first = false; + } else { + builder.append("&"); + } + builder.append(element); + } + return builder.toString(); + } + + /** + * Create a new form. + * + * @return a new form + */ + public static Form create() { + return new Form(); + } + } + + /** + * Used to buffer the response in memory. + */ + public class BufferedResponse { + private final byte[] data; + + private BufferedResponse(byte[] data) { + this.data = data; + } + + /** + * Return the result as bytes. + * + * @return the data + */ + public byte[] asBytes() { + return data; + } + + /** + * Return the result as a string. + * + * @param encoding the encoding + * @return the string + * @throws java.io.IOException on I/O error + */ + public String asString(String encoding) throws IOException { + return new String(data, encoding); + } + + /** + * Save the result to a file. + * + * @param file the file + * @return this object + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public BufferedResponse saveContent(File file) throws IOException, InterruptedException { + Closer closer = Closer.create(); + file.getParentFile().mkdirs(); + + try { + FileOutputStream fos = closer.register(new FileOutputStream(file)); + BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos)); + + saveContent(bos); + } finally { + closer.close(); + } + + return this; + } + + /** + * Save the result to an output stream. + * + * @param out the output stream + * @return this object + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public BufferedResponse saveContent(OutputStream out) throws IOException, InterruptedException { + out.write(data); + + return this; + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/ActorCallbackPaste.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/ActorCallbackPaste.java new file mode 100644 index 000000000..ee98c4364 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/ActorCallbackPaste.java @@ -0,0 +1,70 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.paste; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.sk89q.worldedit.command.util.AsyncCommandHelper; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.task.Supervisor; + +import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ActorCallbackPaste { + + private static final Logger LOGGER = Logger.getLogger(ActorCallbackPaste.class.getSimpleName()); + + private ActorCallbackPaste() { + } + + /** + * Submit data to a pastebin service and inform the sender of + * success or failure. + * + * @param supervisor The supervisor instance + * @param sender The sender + * @param content The content + * @param successMessage The message, formatted with {@link String#format(String, Object...)} on success + */ + public static void pastebin(Supervisor supervisor, final Actor sender, String content, final String successMessage) { + ListenableFuture future = new IncendoPaste("fastasyncworldedit").paste(content); + + AsyncCommandHelper.wrap(future, supervisor, sender) + .registerWithSupervisor("Submitting content to a pastebin service...") + .sendMessageAfterDelay("(Please wait... sending output to pastebin...)"); + + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(URL url) { + sender.print(String.format(successMessage, url)); + } + + @Override + public void onFailure(Throwable throwable) { + LOGGER.log(Level.WARNING, "Failed to submit pastebin", throwable); + sender.printError("Failed to submit to a pastebin. Please see console for the error."); + } + }); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/EngineHubPaste.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/EngineHubPaste.java new file mode 100644 index 000000000..da6b701fd --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/EngineHubPaste.java @@ -0,0 +1,78 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.paste; + +import com.google.common.util.concurrent.ListenableFuture; +import com.sk89q.worldedit.util.net.HttpRequest; +import org.json.simple.JSONValue; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class EngineHubPaste implements Paster { + + private static final Pattern URL_PATTERN = Pattern.compile("https?://.+$"); + + @Override + public ListenableFuture paste(String content) { + return Pasters.getExecutor().submit(new PasteTask(content)); + } + + private final class PasteTask implements Callable { + private final String content; + + private PasteTask(String content) { + this.content = content; + } + + @Override + public URL call() throws IOException, InterruptedException { + HttpRequest.Form form = HttpRequest.Form.create(); + form.add("content", content); + form.add("from", "worldguard"); + + URL url = HttpRequest.url("http://paste.enginehub.org/paste"); + String result = HttpRequest.post(url) + .bodyForm(form) + .execute() + .expectResponseCode(200) + .returnContent() + .asString("UTF-8").trim(); + + Object object = JSONValue.parse(result); + if (object instanceof Map) { + @SuppressWarnings("unchecked") + String urlString = String.valueOf(((Map) object).get("url")); + Matcher m = URL_PATTERN.matcher(urlString); + + if (m.matches()) { + return new URL(urlString); + } + } + + throw new IOException("Failed to save paste; instead, got: " + result); + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/IncendoPaste.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/IncendoPaste.java new file mode 100644 index 000000000..634f77eb5 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/IncendoPaste.java @@ -0,0 +1,280 @@ +package com.sk89q.worldedit.util.paste; + +import com.boydti.fawe.Fawe; +import com.google.common.base.Charsets; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.util.*; +import java.util.concurrent.Callable; + +/** + * Single class paster for the Incendo paste service + * + * @author Sauilitired + */ +@SuppressWarnings({"unused", "WeakerAccess"}) +public final class IncendoPaste implements Paster{ + + /** + * Upload service URL + */ + public static final String UPLOAD_PATH = "https://incendo.org/paste/upload"; + /** + * Valid paste applications + */ + public static final Collection + VALID_APPLICATIONS = Arrays + .asList("plotsquared", "fastasyncworldedit", "incendopermissions", "kvantum"); + + private final Collection files = new ArrayList<>(); + private final String pasteApplication; + + /** + * Construct a new paster + * + * @param pasteApplication The application that is sending the paste + */ + public IncendoPaste(final String pasteApplication) { + if (pasteApplication == null || pasteApplication.isEmpty()) { + throw new IllegalArgumentException("paste application cannot be null, nor empty"); + } + if (!VALID_APPLICATIONS.contains(pasteApplication.toLowerCase(Locale.ENGLISH))) { + throw new IllegalArgumentException( + String.format("Unknown application name: %s", pasteApplication)); + } + this.pasteApplication = pasteApplication; + } + + @Override + public ListenableFuture paste(String content) { + return Pasters.getExecutor().submit(new PasteTask(content)); + } + + private final class PasteTask implements Callable{ + + private PasteTask(String content) {} + + @Override + public URL call() throws Exception { + return new URL(debugPaste()); + } + + } + + /** + * Get an immutable collection containing all the files that have been added to this paster + * + * @return Unmodifiable collection + */ + public final Collection getFiles() { + return Collections.unmodifiableCollection(this.files); + } + + /** + * Add a file to the paster + * + * @param file File to paste + */ + public void addFile(final PasteFile file) { + if (file == null) { + throw new IllegalArgumentException("File cannot be null"); + } + // Check to see that no duplicate files are submitted + for (final PasteFile pasteFile : this.files) { + if (pasteFile.fileName.equalsIgnoreCase(file.getFileName())) { + throw new IllegalArgumentException(String.format("Found duplicate file with name %s", + file.getFileName())); + } + } + this.files.add(file); + } + + /** + * Create a JSON string from the submitted information + * + * @return compiled JSON string + */ + private String toJsonString() { + final StringBuilder builder = new StringBuilder("{\n"); + builder.append("\"paste_application\": \"").append(this.pasteApplication).append("\",\n\"files\": \""); + Iterator fileIterator = this.files.iterator(); + while (fileIterator.hasNext()) { + final PasteFile file = fileIterator.next(); + builder.append(file.getFileName()); + if (fileIterator.hasNext()) { + builder.append(","); + } + } + builder.append("\",\n"); + fileIterator = this.files.iterator(); + while (fileIterator.hasNext()) { + final PasteFile file = fileIterator.next(); + builder.append("\"file-").append(file.getFileName()).append("\": \"") + .append(file.getContent().replaceAll("\"", "\\\\\"")).append("\""); + if (fileIterator.hasNext()) { + builder.append(",\n"); + } + } + builder.append("\n}"); + return builder.toString(); + } + + /** + * Upload the paste and return the status message + * + * @return Status message + * @throws Throwable any and all exceptions + */ + public final String upload() throws Throwable { + final URL url = new URL(UPLOAD_PATH); + final URLConnection connection = url.openConnection(); + final HttpURLConnection httpURLConnection = (HttpURLConnection) connection; + httpURLConnection.setRequestMethod("POST"); + httpURLConnection.setDoOutput(true); + final byte[] content = toJsonString().getBytes(Charsets.UTF_8); + httpURLConnection.setFixedLengthStreamingMode(content.length); + httpURLConnection.setRequestProperty("Content-Type", "application/json"); + httpURLConnection.setRequestProperty("Accept", "*/*"); + httpURLConnection.connect(); + try (final OutputStream stream = httpURLConnection.getOutputStream()) { + stream.write(content); + } + if (!httpURLConnection.getResponseMessage().contains("OK")) { + throw new IllegalStateException(String.format("Server returned status: %d %s", + httpURLConnection.getResponseCode(), httpURLConnection.getResponseMessage())); + } + final StringBuilder input = new StringBuilder(); + try (final BufferedReader inputStream = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()))) { + String line; + while ((line = inputStream.readLine()) != null) { + input.append(line).append("\n"); + } + } + return input.toString(); + } + + /** + * Simple class that represents a paste file + */ + public static class PasteFile { + + private final String fileName; + private final String content; + + /** + * Construct a new paste file + * + * @param fileName File name, cannot be empty, nor null + * @param content File content, cannot be empty, nor null + */ + public PasteFile(final String fileName, final String content) { + if (fileName == null || fileName.isEmpty()) { + throw new IllegalArgumentException("file name cannot be null, nor empty"); + } + if (content == null || content.isEmpty()) { + throw new IllegalArgumentException("content cannot be null, nor empty"); + } + this.fileName = fileName; + this.content = content; + } + + /** + * Get the file name + * + * @return File name + */ + public String getFileName() { + return this.fileName; + } + + /** + * Get the file content as a single string + * + * @return File content + */ + public String getContent() { + return this.content; + } + } + + public static String debugPaste() throws IOException { + final IncendoPaste incendoPaster = new IncendoPaste("fastasyncworldedit"); + + StringBuilder b = new StringBuilder(); + b.append( + "# Welcome to this paste\n# It is meant to provide us at IntellectualSites with better information about your " + + "problem\n"); + b.append("\n# Server Information\n"); + b.append("server.platform: ").append(Fawe.imp().getPlatform()).append('\n'); + b.append(Fawe.imp().getDebugInfo()).append('\n'); + b.append("\n\n# YAY! Now, let's see what we can find in your JVM\n"); + Runtime runtime = Runtime.getRuntime(); + b.append("memory.free: ").append(runtime.freeMemory()).append('\n'); + b.append("memory.max: ").append(runtime.maxMemory()).append('\n'); + b.append("java.specification.version: '").append(System.getProperty("java.specification.version")).append("'\n"); + b.append("java.vendor: '").append(System.getProperty("java.vendor")).append("'\n"); + b.append("java.version: '").append(System.getProperty("java.version")).append("'\n"); + b.append("os.arch: '").append(System.getProperty("os.arch")).append("'\n"); + b.append("os.name: '").append(System.getProperty("os.name")).append("'\n"); + b.append("os.version: '").append(System.getProperty("os.version")).append("'\n\n"); + b.append("# Okay :D Great. You are now ready to create your bug report!"); + b.append("\n# You can do so at https://github.com/boy0001/FastAsyncWorldedit/issues"); + b.append("\n# or via our Discord at https://discord.gg/ngZCzbU"); + incendoPaster.addFile(new IncendoPaste.PasteFile("information", b.toString())); + + try { + final File logFile = new File(Fawe.imp().getDirectory(), "../../logs/latest.log"); + final String file; + if (Files.size(logFile.toPath()) > 14_000_000) { + file = "too big :("; + } else { + file = readFile(logFile); + } + incendoPaster.addFile(new IncendoPaste.PasteFile("latest.log", file)); + } catch (IOException ignored) { + } + + incendoPaster.addFile(new PasteFile("config.yml", readFile(new File(Fawe.imp().getDirectory(), "config.yml")))); + incendoPaster.addFile(new PasteFile("message.yml", readFile(new File(Fawe.imp().getDirectory(), "message.yml")))); + incendoPaster.addFile(new PasteFile("commands.yml", readFile(new File(Fawe.imp().getDirectory(), "commands.yml")))); + + final String rawResponse; + try { + rawResponse = incendoPaster.upload(); + } catch (Throwable throwable) { + throw new IOException(String.format("Failed to upload files: %s", throwable.getMessage()), throwable); + } + final JsonObject jsonObject = new JsonParser().parse(rawResponse).getAsJsonObject(); + + if (jsonObject.has("created")) { + final String pasteId = jsonObject.get("paste_id").getAsString(); + return String.format("https://incendo.org/paste/view/%s", pasteId); + } else { + throw new IOException(String.format("Failed to upload files: %s", + jsonObject.get("response").getAsString())); + } + } + + private static String readFile(final File file) throws IOException { + final StringBuilder content = new StringBuilder(); + final List lines = new ArrayList<>(); + try (final BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + } + for (int i = Math.max(0, lines.size() - 1000); i < lines.size(); i++) { + content.append(lines.get(i)).append("\n"); + } + return content.toString(); + } + +} \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pastebin.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pastebin.java new file mode 100644 index 000000000..dcbef09b0 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pastebin.java @@ -0,0 +1,93 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.paste; + +import com.google.common.util.concurrent.ListenableFuture; +import com.sk89q.worldedit.util.net.HttpRequest; + +import java.io.IOException; +import java.net.URL; +import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Pastebin implements Paster { + + private static final Pattern URL_PATTERN = Pattern.compile("https?://pastebin.com/([^/]+)$"); + + private boolean mungingLinks = true; + + public boolean isMungingLinks() { + return mungingLinks; + } + + public void setMungingLinks(boolean mungingLinks) { + this.mungingLinks = mungingLinks; + } + + @Override + public ListenableFuture paste(String content) { + if (mungingLinks) { + content = content.replaceAll("http://", "http_//"); + } + + return Pasters.getExecutor().submit(new PasteTask(content)); + } + + private final class PasteTask implements Callable { + private final String content; + + private PasteTask(String content) { + this.content = content; + } + + @Override + public URL call() throws IOException, InterruptedException { + HttpRequest.Form form = HttpRequest.Form.create(); + form.add("api_option", "paste"); + form.add("api_dev_key", "4867eae74c6990dbdef07c543cf8f805"); + form.add("api_paste_code", content); + form.add("api_paste_private", "0"); + form.add("api_paste_name", ""); + form.add("api_paste_expire_date", "1W"); + form.add("api_paste_format", "text"); + form.add("api_user_key", ""); + + URL url = HttpRequest.url("http://pastebin.com/api/api_post.php"); + String result = HttpRequest.post(url) + .bodyForm(form) + .execute() + .expectResponseCode(200) + .returnContent() + .asString("UTF-8").trim(); + + Matcher m = URL_PATTERN.matcher(result); + + if (m.matches()) { + return new URL("http://pastebin.com/raw.php?i=" + m.group(1)); + } else if (result.matches("^https?://.+")) { + return new URL(result); + } else { + throw new IOException("Failed to save paste; instead, got: " + result); + } + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Paster.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Paster.java new file mode 100644 index 000000000..7a7d74cac --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Paster.java @@ -0,0 +1,30 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.paste; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.net.URL; + +public interface Paster { + + ListenableFuture paste(String content); + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pasters.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pasters.java new file mode 100644 index 000000000..b809f1836 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/paste/Pasters.java @@ -0,0 +1,44 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.paste; + +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; + +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +final class Pasters { + + private static final ListeningExecutorService executor = + MoreExecutors.listeningDecorator( + new ThreadPoolExecutor(0, Integer.MAX_VALUE, + 2L, TimeUnit.SECONDS, + new SynchronousQueue<>())); + + private Pasters() { + } + + static ListeningExecutorService getExecutor() { + return executor; + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ConfigReport.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ConfigReport.java new file mode 100644 index 000000000..1a4b486a0 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ConfigReport.java @@ -0,0 +1,32 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.report; + +import com.sk89q.worldedit.WorldEdit; + +public class ConfigReport extends DataReport { + + public ConfigReport() { + super("WorldEdit Configuration"); + + append("Configuration", new HierarchyObjectReport("Configuration", WorldEdit.getInstance().getConfiguration())); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/HierarchyObjectReport.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/HierarchyObjectReport.java new file mode 100644 index 000000000..522693919 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/HierarchyObjectReport.java @@ -0,0 +1,32 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.report; + +public class HierarchyObjectReport extends ShallowObjectReport { + + public HierarchyObjectReport(String title, Object object) { + super(title, object); + + Class type = object.getClass(); + while ((type = type.getSuperclass()) != null) { + scanClass(object, type); + } + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ShallowObjectReport.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ShallowObjectReport.java index b94b63e65..bb4abb1f3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ShallowObjectReport.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/report/ShallowObjectReport.java @@ -34,8 +34,10 @@ public class ShallowObjectReport extends DataReport { super(title); checkNotNull(object, "object"); - Class type = object.getClass(); + scanClass(object, object.getClass()); + } + void scanClass(Object object, Class type) { for (Field field : type.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers())) { continue; @@ -54,5 +56,4 @@ public class ShallowObjectReport extends DataReport { } } } - } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/AbstractTask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/AbstractTask.java new file mode 100644 index 000000000..e81e2bf22 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/AbstractTask.java @@ -0,0 +1,74 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task; + +import com.google.common.util.concurrent.AbstractFuture; + +import javax.annotation.Nullable; +import java.util.Date; +import java.util.UUID; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An abstract task that stores a name and owner. + * + * @param the type returned + */ +public abstract class AbstractTask extends AbstractFuture implements Task { + + private final UUID uniqueId = UUID.randomUUID(); + private final String name; + private final Object owner; + private final Date creationDate = new Date(); + + /** + * Create a new instance. + * + * @param name the name + * @param owner the owner + */ + protected AbstractTask(String name, @Nullable Object owner) { + checkNotNull(name); + this.name = name; + this.owner = owner; + } + + @Override + public UUID getUniqueId() { + return uniqueId; + } + + @Override + public String getName() { + return name; + } + + @Nullable + @Override + public Object getOwner() { + return owner; + } + + @Override + public Date getCreationDate() { + return creationDate; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/FutureForwardingTask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/FutureForwardingTask.java new file mode 100644 index 000000000..61f5d75a9 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/FutureForwardingTask.java @@ -0,0 +1,121 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task; + +import com.google.common.util.concurrent.ListenableFuture; +import com.sk89q.worldedit.util.task.progress.Progress; + +import javax.annotation.Nullable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A task that wraps a {@code ListenableFuture}. + * + *

{@link Task.State#SCHEDULED} is never returned because it is not possible + * to test whether the future has "started," so {@link Task.State#RUNNING} is + * returned in its place.

+ * + *

Use {@link #create(ListenableFuture, String, Object)} to create a new + * instance.

+ * + * @param the type returned + */ +public class FutureForwardingTask extends AbstractTask { + + private final ListenableFuture future; + + private FutureForwardingTask(ListenableFuture future, String name, @Nullable Object owner) { + super(name, owner); + checkNotNull(future); + this.future = future; + } + + @Override + public void addListener(Runnable listener, Executor executor) { + future.addListener(listener, executor); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return future.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return future.isCancelled(); + } + + @Override + public boolean isDone() { + return future.isDone(); + } + + @Override + public V get() throws InterruptedException, ExecutionException { + return future.get(); + } + + @Override + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return future.get(timeout, unit); + } + + @Override + public State getState() { + if (isCancelled()) { + return State.CANCELLED; + } else if (isDone()) { + try { + get(); + return State.SUCCEEDED; + } catch (InterruptedException e) { + return State.CANCELLED; + } catch (ExecutionException e) { + return State.FAILED; + } + } else { + return State.RUNNING; + } + } + + @Override + public Progress getProgress() { + return Progress.indeterminate(); + } + + /** + * Create a new instance. + * + * @param future the future + * @param name the name of the task + * @param owner the owner of the task, or {@code null} + * @param the type returned by the future + * @return a new instance + */ + public static FutureForwardingTask create(ListenableFuture future, String name, @Nullable Object owner) { + return new FutureForwardingTask<>(future, name, owner); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/SimpleSupervisor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/SimpleSupervisor.java new file mode 100644 index 000000000..0712cc5dd --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/SimpleSupervisor.java @@ -0,0 +1,59 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task; + +import com.google.common.util.concurrent.MoreExecutors; + +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An implementation of a {@code Supervisor}. + */ +public class SimpleSupervisor implements Supervisor { + + private final List> monitored = new ArrayList<>(); + private final Object lock = new Object(); + + @Override + public List> getTasks() { + synchronized (lock) { + return new ArrayList<>(monitored); + } + } + + @Override + public void monitor(final Task task) { + checkNotNull(task); + + synchronized (lock) { + monitored.add(task); + } + + task.addListener(() -> { + synchronized (lock) { + monitored.remove(task); + } + }, MoreExecutors.directExecutor()); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Supervisor.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Supervisor.java new file mode 100644 index 000000000..9b470700e --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Supervisor.java @@ -0,0 +1,44 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task; + +import java.util.List; + +/** + * Manages running tasks and informs users of their progress, but does not + * execute the task. + */ +public interface Supervisor { + + /** + * Get a list of running or queued tasks. + * + * @return a list of tasks + */ + List> getTasks(); + + /** + * Monitor the given task. + * + * @param task the task + */ + void monitor(Task task); + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Task.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Task.java new file mode 100644 index 000000000..169cf1718 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/Task.java @@ -0,0 +1,97 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task; + +import com.google.common.util.concurrent.ListenableFuture; +import com.sk89q.worldedit.util.task.progress.ProgressObservable; + +import javax.annotation.Nullable; +import java.util.Date; +import java.util.UUID; + +/** + * A task is a job that can be scheduled, run, or cancelled. Tasks can report + * on their own status. Tasks have owners. + */ +public interface Task extends ListenableFuture, ProgressObservable { + + /** + * Get the unique ID of this task. + * + * @return this task's unique ID + */ + UUID getUniqueId(); + + /** + * Get the name of the task so it can be printed to the user. + * + * @return the name of the task + */ + String getName(); + + /** + * Get the owner of the task. + * + * @return an owner object, if one is known or valid, otherwise {@code null} + */ + @Nullable + Object getOwner(); + + /** + * Get the state of the task. + * + * @return the state of the task + */ + State getState(); + + /** + * Get the time at which the task was created. + * + * @return a date + */ + Date getCreationDate(); + + /** + * Represents the state of a task. + */ + enum State { + /** + * The task has been scheduled to run but is not running yet. + */ + SCHEDULED, + /** + * The task has been cancelled and may be stopped or will stop. + */ + CANCELLED, + /** + * The task is currently running. + */ + RUNNING, + /** + * The task has failed. + */ + FAILED, + /** + * The task has succeeded. + */ + SUCCEEDED + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/TaskStateComparator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/TaskStateComparator.java new file mode 100644 index 000000000..38b77e73e --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/TaskStateComparator.java @@ -0,0 +1,43 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task; + +import java.util.Comparator; + +/** + * Compares task states according to the order of the {@link Task.State} + * enumeration. + */ +public class TaskStateComparator implements Comparator> { + + @Override + public int compare(com.sk89q.worldedit.util.task.Task o1, Task o2) { + int ordinal1 = o1.getState().ordinal(); + int ordinal2 = o2.getState().ordinal(); + if (ordinal1 < ordinal2) { + return -1; + } else if (ordinal1 > ordinal2) { + return 1; + } else { + return 0; + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/Progress.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/Progress.java new file mode 100644 index 000000000..d3d0571ae --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/Progress.java @@ -0,0 +1,183 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task.progress; + +import java.util.Arrays; +import java.util.Collection; + +/** + * A progress object describes the progress of an operation, specifying + * either a percentage of completion or a status of indeterminacy. + * + *

Progress objects are immutable.

+ * + *

To create a new instance, use one of the static constructors + * on this class.

+ */ +public abstract class Progress { + + /** + * Create a new instance. + */ + private Progress() { + } + + /** + * Return whether the current progress is indeterminate. + * + * @return true if indeterminate + */ + public abstract boolean isIndeterminate(); + + /** + * Get the progress percentage. + * + *

If {@link #isIndeterminate()} returns {@code true}, the behavior + * of this method is undefined.

+ * + * @return a number in the range [0, 1] + */ + public abstract double getProgress(); + + /** + * Get a static progress object that is indeterminate. + * + * @return a progress object + */ + public static Progress indeterminate() { + return INDETERMINATE; + } + + /** + * Get a static progress object that is complete. + * + * @return a progress object + */ + public static Progress completed() { + return COMPLETED; + } + + /** + * Create a new progress object with the given percentage. + * + * @param value the percentage, which will be clamped to [0, 1] + * @return a progress object + */ + public static Progress of(double value) { + if (value < 0) { + value = 0; + } else if (value > 1) { + value = 1; + } + + final double finalValue = value; + return new Progress() { + @Override + public boolean isIndeterminate() { + return false; + } + + @Override + public double getProgress() { + return finalValue; + } + }; + } + + /** + * Create a new progress object with progress split equally between the + * given progress objects. + * + * @param objects an array of progress objects + * @return a new progress value + */ + public static Progress split(Progress... objects) { + return split(Arrays.asList(objects)); + } + + /** + * Create a new progress object with progress split equally between the + * given progress objects. + * + * @param progress a collection of progress objects + * @return a new progress value + */ + public static Progress split(Collection progress) { + int count = 0; + double total = 0; + + for (Progress p : progress) { + if (p.isIndeterminate()) { + return indeterminate(); + } + total += p.getProgress(); + } + + return of(total / count); + } + + /** + * Create a new progress object with progress split equally between the + * given {@link ProgressObservable}s. + * + * @param observables an array of observables + * @return a new progress value + */ + public static Progress splitObservables(ProgressObservable... observables) { + return splitObservables(Arrays.asList(observables)); + } + + /** + * Create a new progress object with progress split equally between the + * given {@link ProgressObservable}s. + * + * @param observables a collection of observables + * @return a new progress value + */ + public static Progress splitObservables(Collection observables) { + int count = 0; + double total = 0; + + for (ProgressObservable observable : observables) { + Progress p = observable.getProgress(); + if (p.isIndeterminate()) { + return indeterminate(); + } + total += p.getProgress(); + } + + return of(total / count); + } + + private static final Progress COMPLETED = of(1); + + private static final Progress INDETERMINATE = new Progress() { + @Override + public boolean isIndeterminate() { + return true; + } + + @Override + public double getProgress() { + return 0; + } + }; + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressIterator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressIterator.java new file mode 100644 index 000000000..11eb34f32 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressIterator.java @@ -0,0 +1,100 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task.progress; + +import java.util.Iterator; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An iterator that keeps track of how many entries have been visited and + * calculates a "percent completed" using a provided total count. + * + *

The returned progress percentage will always be between 0 or 1 + * (inclusive). If the iterator returns more entries than the total count, + * then 100% will be returned for the progress.

+ * + * @param the type + */ +public class ProgressIterator implements Iterator, ProgressObservable { + + private final Iterator iterator; + private final int count; + private int visited = 0; + + /** + * Create a new instance. + * + * @param iterator the iterator + * @param count the count + */ + private ProgressIterator(Iterator iterator, int count) { + checkNotNull(iterator); + this.iterator = iterator; + this.count = count; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public V next() { + V value = iterator.next(); + visited++; + return value; + } + + @Override + public void remove() { + iterator.remove(); + } + + @Override + public Progress getProgress() { + return Progress.of(count > 0 ? Math.min(1, Math.max(0, (visited / (double) count))) : 1); + } + + /** + * Create a new instance. + * + * @param iterator the iterator + * @param count the number of objects + * @param the type + * @return an instance + */ + public static ProgressIterator create(Iterator iterator, int count) { + return new ProgressIterator(iterator, count); + } + + /** + * Create a new instance from a list. + * + * @param list a list + * @param the type + * @return an instance + */ + public static ProgressIterator create(List list) { + return create(list.iterator(), list.size()); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressObservable.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressObservable.java new file mode 100644 index 000000000..b109e12a2 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/task/progress/ProgressObservable.java @@ -0,0 +1,34 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.task.progress; + +/** + * An object that is able to report on its progress. + */ +public interface ProgressObservable { + + /** + * Get the current percentage of completion. + * + * @return a progress object + */ + Progress getProgress(); + +} diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/extent/transform/BlockTransformExtentTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/extent/transform/BlockTransformExtentTest.java new file mode 100644 index 000000000..e32b7c6cd --- /dev/null +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/extent/transform/BlockTransformExtentTest.java @@ -0,0 +1,69 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.extent.transform; + +import static org.junit.Assert.assertEquals; + +import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.math.transform.Transform; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +@Ignore("A platform is currently required to get properties, preventing this test.") +public class BlockTransformExtentTest { + + private static final Transform ROTATE_90 = new AffineTransform().rotateY(-90); + private static final Transform ROTATE_NEG_90 = new AffineTransform().rotateY(90); + private final Set ignored = new HashSet<>(); + + @Before + public void setUp() throws Exception { + BlockTypes.register(new BlockType("worldedit:test")); + } + + @Test + public void testTransform() throws Exception { + for (BlockType type : BlockType.REGISTRY.values()) { + if (ignored.contains(type)) { + continue; + } + + BlockState base = type.getDefaultState(); + BlockState rotated = base; + + for (int i = 1; i < 4; i++) { + rotated = BlockTransformExtent.transform(base, ROTATE_90); + } + assertEquals(base, rotated); + rotated = base; + for (int i = 1; i < 4; i++) { + rotated = BlockTransformExtent.transform(base, ROTATE_NEG_90); + } + assertEquals(base, rotated); + } + } +} \ No newline at end of file diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java index e38140bb4..6e1786132 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/config/ConfigurateConfiguration.java @@ -23,6 +23,7 @@ import com.google.common.reflect.TypeToken; import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.session.SessionManager; +import com.sk89q.worldedit.util.report.Unreported; import com.sk89q.worldedit.world.registry.LegacyMapper; import com.sk89q.worldedit.world.snapshot.SnapshotRepository; import ninja.leaping.configurate.ConfigurationOptions; @@ -36,10 +37,10 @@ import java.util.HashSet; public class ConfigurateConfiguration extends LocalConfiguration { - protected final ConfigurationLoader config; - protected final Logger logger; + @Unreported protected final ConfigurationLoader config; + @Unreported protected final Logger logger; - protected CommentedConfigurationNode node; + @Unreported protected CommentedConfigurationNode node; public ConfigurateConfiguration(ConfigurationLoader config, Logger logger) { this.config = config;