diff --git a/core/src/main/java/com/boydti/fawe/FaweAPI.java b/core/src/main/java/com/boydti/fawe/FaweAPI.java index 17f165bf..fbeedce9 100644 --- a/core/src/main/java/com/boydti/fawe/FaweAPI.java +++ b/core/src/main/java/com/boydti/fawe/FaweAPI.java @@ -4,11 +4,12 @@ import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.FaweLocation; import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.FaweQueue; import com.boydti.fawe.object.PseudoRandom; import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.object.changeset.DiskStorageHistory; import com.boydti.fawe.regions.FaweMaskManager; -import com.boydti.fawe.object.FaweQueue; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MemUtil; import com.boydti.fawe.util.SetQueue; @@ -23,11 +24,15 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; import com.sk89q.worldedit.world.World; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; @@ -41,6 +46,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; import javax.annotation.Nonnull; import org.bukkit.Chunk; import org.bukkit.Location; @@ -124,6 +130,28 @@ public class FaweAPI { return null; } + /** + * Upload the clipboard to the configured web interface + * @param clipboard The clipboard (may not be null) + * @param format The format to use (some formats may not be supported) + * @return The download URL or null + */ + public static URL upload(final Clipboard clipboard, final ClipboardFormat format) { + return MainUtil.upload(null, "clipboard", format.getExtension(), new RunnableVal() { + @Override + public void run(OutputStream value) { + try { + GZIPOutputStream gzip = new GZIPOutputStream(value, true); + ClipboardWriter writer = format.getWriter(gzip); + writer.write(clipboard, null); + gzip.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + /** * Get a list of supported protection plugin masks. * @return Set of FaweMaskManager diff --git a/core/src/main/java/com/boydti/fawe/config/BBC.java b/core/src/main/java/com/boydti/fawe/config/BBC.java index 7f9204af..faec6fc1 100644 --- a/core/src/main/java/com/boydti/fawe/config/BBC.java +++ b/core/src/main/java/com/boydti/fawe/config/BBC.java @@ -39,6 +39,10 @@ public enum BBC { WORLDEDIT_OOM_ADMIN("&cPossible options:\n&8 - &7//fast\n&8 - &7Do smaller edits\n&8 - &7Allocate more memory\n&8 - &7Disable this safeguard", "Info"), COMPRESSED("History compressed. Saved ~ %s0b (%s1x smaller)", "Info"), ACTION_COMPLETE("Action completed in %s0 seconds", "Info"), + GENERATING_LINK("Uploading %s, please wait...", "Web"), + GENERATING_LINK_FAILED("&cFailed to generate download link!", "Web"), + DOWNLOAD_LINK("%s", "Web"), + COMMAND_COPY("%s0 blocks were copied", "WorldEdit.Copy"), COMMAND_CUT("%s0 blocks were cut", "WorldEdit.Cut"), diff --git a/core/src/main/java/com/boydti/fawe/config/Settings.java b/core/src/main/java/com/boydti/fawe/config/Settings.java index 3956e6aa..2a2a8559 100644 --- a/core/src/main/java/com/boydti/fawe/config/Settings.java +++ b/core/src/main/java/com/boydti/fawe/config/Settings.java @@ -37,6 +37,7 @@ public class Settings { public static boolean ASYNC_LIGHTING = true; public static int PHYSICS_PER_TICK = 500000; public static int ITEMS_PER_TICK = 50000; + public static String WEB_URL = "http://empcraft.com/fawe/"; // Maybe confusing? // - `compression: false` just uses cheaper compression, but still compresses @@ -108,6 +109,7 @@ public class Settings { options.put("queue.max-wait-ms", QUEUE_MAX_WAIT); options.put("extent.allowed-plugins", new ArrayList()); options.put("extent.debug", EXTENT_DEBUG); + options.put("web.url", WEB_URL); options.put("metrics", METRICS); // Possibly confusing? - leave configurable since not entirely stable yet @@ -165,6 +167,8 @@ public class Settings { QUEUE_DISCARD_AFTER = config.getInt("queue.discard-after-ms", QUEUE_DISCARD_AFTER); COMBINE_HISTORY_STAGE = config.getBoolean("history.combine-stages", COMBINE_HISTORY_STAGE); + WEB_URL = config.getString("web.url"); + if (STORE_HISTORY_ON_DISK = config.getBoolean("history.use-disk")) { LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE; } diff --git a/core/src/main/java/com/boydti/fawe/util/MainUtil.java b/core/src/main/java/com/boydti/fawe/util/MainUtil.java index 8f99fceb..50ff2239 100644 --- a/core/src/main/java/com/boydti/fawe/util/MainUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/MainUtil.java @@ -2,6 +2,7 @@ package com.boydti.fawe.util; import com.boydti.fawe.Fawe; import com.boydti.fawe.config.BBC; +import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.object.RunnableVal; @@ -21,14 +22,22 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.lang.reflect.Array; import java.lang.reflect.Method; +import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.UUID; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -55,6 +64,69 @@ public class MainUtil { Fawe.debug(s); } + public static File getFile(File base, String path) { + if (Paths.get(path).isAbsolute()) { + return new File(path); + } + return new File(base, path); + } + + public static File getFile(File base, String path, String extension) { + return getFile(base, path.endsWith("." + extension) ? path : path + "." + extension); + } + + public static URL upload(UUID uuid, String file, String extension, final RunnableVal writeTask) { + if (writeTask == null) { + Fawe.debug("&cWrite task cannot be null"); + return null; + } + final String filename; + final String website; + if (uuid == null) { + uuid = UUID.randomUUID(); + website = Settings.WEB_URL + "upload.php?" + uuid; + filename = "plot." + extension; + } else { + website = Settings.WEB_URL + "save.php?" + uuid; + filename = file + '.' + extension; + } + final URL url; + try { + url = new URL(Settings.WEB_URL + "?key=" + uuid + "&type=" + extension); + String boundary = Long.toHexString(System.currentTimeMillis()); + URLConnection con = new URL(website).openConnection(); + con.setDoOutput(true); + con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); + try (OutputStream output = con.getOutputStream(); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8), true)) { + String CRLF = "\r\n"; + writer.append("--" + boundary).append(CRLF); + writer.append("Content-Disposition: form-data; name=\"param\"").append(CRLF); + writer.append("Content-Type: text/plain; charset=" + StandardCharsets.UTF_8.displayName()).append(CRLF); + String param = "value"; + writer.append(CRLF).append(param).append(CRLF).flush(); + writer.append("--" + boundary).append(CRLF); + writer.append("Content-Disposition: form-data; name=\"schematicFile\"; filename=\"" + filename + '"').append(CRLF); + writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(filename)).append(CRLF); + writer.append("Content-Transfer-Encoding: binary").append(CRLF); + writer.append(CRLF).flush(); + writeTask.value = output; + writeTask.run(); + output.flush(); + writer.append(CRLF).flush(); + writer.append("--" + boundary + "--").append(CRLF).flush(); + } + int responseCode = ((HttpURLConnection) con).getResponseCode(); + if (responseCode == 200) { + return url; + } + return null; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + public static void setPosition(CompoundTag tag, int x, int y, int z) { Map value = ReflectionUtils.getMap(tag.getValue()); value.put("x", new IntTag(x)); diff --git a/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java b/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java index b9f2fe41..d1b35fdb 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java @@ -19,10 +19,12 @@ package com.sk89q.worldedit.command; +import com.boydti.fawe.FaweAPI; import com.boydti.fawe.config.BBC; import com.boydti.fawe.object.RunnableVal2; import com.boydti.fawe.object.clipboard.LazyClipboard; import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.minecraft.util.commands.Logging; import com.sk89q.worldedit.BlockVector; @@ -38,6 +40,7 @@ import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; import com.sk89q.worldedit.function.block.BlockReplace; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.operation.ForwardExtentCopy; @@ -55,6 +58,7 @@ import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.command.binding.Switch; import com.sk89q.worldedit.util.command.parametric.Optional; +import java.net.URL; import java.util.Iterator; import java.util.List; @@ -204,6 +208,26 @@ public class ClipboardCommands { BBC.COMMAND_CUT.send(player, region.getArea()); } + @Command(aliases = { "download" }, desc = "Download your clipboard") + @Deprecated + @CommandPermissions({ "worldedit.clipboard.download"}) + public void download(final Player player, final LocalSession session, @Optional("schematic") final String formatName) throws CommandException, WorldEditException { + final ClipboardFormat format = ClipboardFormat.findByAlias(formatName); + if (format == null) { + player.printError("Unknown schematic format: " + formatName); + return; + } + ClipboardHolder holder = session.getClipboard(); + Clipboard clipboard = holder.getClipboard(); + BBC.GENERATING_LINK.send(player, formatName); + URL url = FaweAPI.upload(clipboard, format); + if (url == null) { + BBC.GENERATING_LINK_FAILED.send(player); + } else { + BBC.DOWNLOAD_LINK.send(player, url.toString()); + } + } + @Command( aliases = { "/paste" }, usage = "", diff --git a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java index fd78aeaf..2b6eeb10 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -20,6 +20,7 @@ package com.sk89q.worldedit.command; import com.boydti.fawe.config.BBC; +import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.schematic.StructureFormat; import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandContext; @@ -42,7 +43,6 @@ import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.util.command.parametric.Optional; -import com.sk89q.worldedit.util.io.Closer; import com.sk89q.worldedit.util.io.file.FilenameException; import com.sk89q.worldedit.util.io.file.FilenameResolutionException; import com.sk89q.worldedit.world.registry.WorldData; @@ -53,8 +53,13 @@ import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; import java.util.Arrays; import java.util.Comparator; +import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @@ -84,52 +89,59 @@ public class SchematicCommands { @CommandPermissions({ "worldedit.clipboard.load", "worldedit.schematic.load" }) public void load(final Player player, final LocalSession session, @Optional("schematic") final String formatName, final String filename) throws FilenameException { final LocalConfiguration config = this.worldEdit.getConfiguration(); - - final File dir = this.worldEdit.getWorkingDirectoryFile(config.saveDir); - final File f = this.worldEdit.getSafeOpenFile(player, dir, filename, "schematic", "schematic"); - - if (!f.exists()) { - player.printError("Schematic " + filename + " does not exist!"); - return; - } - final ClipboardFormat format = ClipboardFormat.findByAlias(formatName); if (format == null) { player.printError("Unknown schematic format: " + formatName); return; } - final Closer closer = Closer.create(); + InputStream in = null; try { - final String filePath = f.getCanonicalPath(); - final String dirPath = dir.getCanonicalPath(); - - if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { - player.printError("Clipboard file could not read or it does not exist."); + if (filename.startsWith("url:")) { + UUID uuid = UUID.fromString(filename.substring(4)); + URL base = new URL(Settings.WEB_URL); + URL url = new URL(base, "uploads/" + uuid + ".schematic"); + ReadableByteChannel rbc = Channels.newChannel(url.openStream()); + in = Channels.newInputStream(rbc); } else { - final FileInputStream fis = closer.register(new FileInputStream(f)); - final BufferedInputStream bis = closer.register(new BufferedInputStream(fis)); - final ClipboardReader reader = format.getReader(bis); - - final WorldData worldData = player.getWorld().getWorldData(); - final Clipboard clipboard; - if (reader instanceof SchematicReader) { - clipboard = ((SchematicReader) reader).read(player.getWorld().getWorldData(), player.getUniqueId()); - } else if (reader instanceof StructureFormat) { - clipboard = ((StructureFormat) reader).read(player.getWorld().getWorldData(), player.getUniqueId()); - } else { - clipboard = reader.read(player.getWorld().getWorldData()); + final File dir = this.worldEdit.getWorkingDirectoryFile(config.saveDir); + final File f = this.worldEdit.getSafeOpenFile(player, dir, filename, format.getExtension(), format.getExtension()); + if (!f.exists()) { + player.printError("Schematic " + filename + " does not exist!"); + return; } - session.setClipboard(new ClipboardHolder(clipboard, worldData)); - log.info(player.getName() + " loaded " + filePath); - BBC.SCHEMATIC_LOADED.send(player, filename); + final String filePath = f.getCanonicalPath(); + final String dirPath = dir.getCanonicalPath(); + if (!filePath.substring(0, dirPath.length()).equals(dirPath)) { + player.printError("Clipboard file could not read or it does not exist."); + } + in = new FileInputStream(f); } - } catch (final IOException e) { - player.printError("Schematic could not read or it does not exist: " + e.getMessage()); + in = new BufferedInputStream(in); + + final ClipboardReader reader = format.getReader(in); + + final WorldData worldData = player.getWorld().getWorldData(); + final Clipboard clipboard; + if (reader instanceof SchematicReader) { + clipboard = ((SchematicReader) reader).read(player.getWorld().getWorldData(), player.getUniqueId()); + } else if (reader instanceof StructureFormat) { + clipboard = ((StructureFormat) reader).read(player.getWorld().getWorldData(), player.getUniqueId()); + } else { + clipboard = reader.read(player.getWorld().getWorldData()); + } + session.setClipboard(new ClipboardHolder(clipboard, worldData)); + BBC.SCHEMATIC_LOADED.send(player, filename); + } catch (IllegalArgumentException e) { + player.printError("Unknown filename: " + filename); + } catch (IOException e) { + player.printError("File could not be read or it does not exist: " + e.getMessage()); log.log(Level.WARNING, "Failed to load a saved clipboard", e); } finally { - try { - closer.close(); - } catch (final IOException ignored) {} + if (in != null) { + try { + in.close(); + } catch (IOException ignored) {} + } } } @@ -138,60 +150,58 @@ public class SchematicCommands { @CommandPermissions({ "worldedit.clipboard.save", "worldedit.schematic.save" }) public void save(final Player player, final LocalSession session, @Optional("schematic") final String formatName, final String filename) throws CommandException, WorldEditException { final LocalConfiguration config = this.worldEdit.getConfiguration(); - - final File dir = this.worldEdit.getWorkingDirectoryFile(config.saveDir); - final ClipboardFormat format = ClipboardFormat.findByAlias(formatName); if (format == null) { player.printError("Unknown schematic format: " + formatName); return; } - final File f = this.worldEdit.getSafeSaveFile(player, dir, filename, "schematic", "schematic"); - - final ClipboardHolder holder = session.getClipboard(); - final Clipboard clipboard = holder.getClipboard(); - final Transform transform = holder.getTransform(); - final Clipboard target; - - // If we have a transform, bake it into the copy - if (!transform.isIdentity()) { - final FlattenedClipboardTransform result = FlattenedClipboardTransform.transform(clipboard, transform, holder.getWorldData()); - target = new BlockArrayClipboard(result.getTransformedRegion(), player.getUniqueId()); - target.setOrigin(clipboard.getOrigin()); - Operations.completeLegacy(result.copyTo(target)); - } else { - target = clipboard; + final File dir = this.worldEdit.getWorkingDirectoryFile(config.saveDir); + final File f = this.worldEdit.getSafeSaveFile(player, dir, filename, format.getExtension(), format.getExtension()); + final File parent = f.getParentFile(); + if ((parent != null) && !parent.exists()) { + if (!parent.mkdirs()) { + log.info("Could not create folder for schematics!"); + return; + } } - - final Closer closer = Closer.create(); try { - // Create parent directories - final File parent = f.getParentFile(); - if ((parent != null) && !parent.exists()) { - if (!parent.mkdirs()) { - log.info("Could not create folder for schematics!"); - return; + if (!f.exists()) { + f.createNewFile(); + } + try (FileOutputStream fos = new FileOutputStream(f)) { + final ClipboardHolder holder = session.getClipboard(); + final Clipboard clipboard = holder.getClipboard(); + final Transform transform = holder.getTransform(); + final Clipboard target; + + // If we have a transform, bake it into the copy + if (!transform.isIdentity()) { + final FlattenedClipboardTransform result = FlattenedClipboardTransform.transform(clipboard, transform, holder.getWorldData()); + target = new BlockArrayClipboard(result.getTransformedRegion(), player.getUniqueId()); + target.setOrigin(clipboard.getOrigin()); + Operations.completeLegacy(result.copyTo(target)); + } else { + target = clipboard; + } + + try (BufferedOutputStream bos = new BufferedOutputStream(fos)) { + try (ClipboardWriter writer = format.getWriter(bos)) { + if (writer instanceof StructureFormat) { + ((StructureFormat) writer).write(target, holder.getWorldData(), player.getName()); + } else { + writer.write(target, holder.getWorldData()); + } + log.info(player.getName() + " saved " + f.getCanonicalPath()); + BBC.SCHEMATIC_SAVED.send(player, filename); + } } } - - final FileOutputStream fos = closer.register(new FileOutputStream(f)); - final BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos)); - final ClipboardWriter writer = closer.register(format.getWriter(bos)); - if (writer instanceof StructureFormat) { - ((StructureFormat) writer).write(target, holder.getWorldData(), player.getName()); - } else { - writer.write(target, holder.getWorldData()); - } - log.info(player.getName() + " saved " + f.getCanonicalPath()); - BBC.SCHEMATIC_SAVED.send(player, filename); - } catch (final IOException e) { + } catch (IllegalArgumentException e) { + player.printError("Unknown filename: " + filename); + } catch (IOException e) { player.printError("Schematic could not written: " + e.getMessage()); log.log(Level.WARNING, "Failed to write a saved clipboard", e); - } finally { - try { - closer.close(); - } catch (final IOException ignored) {} } } diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java index 1bc38462..d4002df2 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java @@ -60,7 +60,13 @@ public enum ClipboardFormat { @Override public ClipboardWriter getWriter(OutputStream outputStream) throws IOException { - NBTOutputStream nbtStream = new NBTOutputStream(new GZIPOutputStream(outputStream)); + GZIPOutputStream gzip; + if (outputStream instanceof GZIPOutputStream) { + gzip = (GZIPOutputStream) outputStream; + } else { + gzip = new GZIPOutputStream(outputStream, true); + } + NBTOutputStream nbtStream = new NBTOutputStream(gzip); return new SchematicWriter(nbtStream); } @@ -103,7 +109,13 @@ public enum ClipboardFormat { @Override public ClipboardWriter getWriter(OutputStream outputStream) throws IOException { - NBTOutputStream nbtStream = new NBTOutputStream(new GZIPOutputStream(outputStream)); + GZIPOutputStream gzip; + if (outputStream instanceof GZIPOutputStream) { + gzip = (GZIPOutputStream) outputStream; + } else { + gzip = new GZIPOutputStream(outputStream, true); + } + NBTOutputStream nbtStream = new NBTOutputStream(gzip); return new StructureFormat(nbtStream); }