Clipboard upload/download

This commit is contained in:
Jesse Boyd 2016-06-10 18:47:55 +10:00
parent 50e4231125
commit 7bc7f5841f
7 changed files with 235 additions and 81 deletions

View File

@ -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<OutputStream>() {
@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

View File

@ -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"),

View File

@ -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<String>());
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;
}

View File

@ -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<OutputStream> 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<String, Tag> value = ReflectionUtils.getMap(tag.getValue());
value.put("x", new IntTag(x));

View File

@ -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 = "",

View File

@ -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,31 +89,36 @@ 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 {
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 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;
}
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.");
} else {
final FileInputStream fis = closer.register(new FileInputStream(f));
final BufferedInputStream bis = closer.register(new BufferedInputStream(fis));
final ClipboardReader reader = format.getReader(bis);
}
in = new FileInputStream(f);
}
in = new BufferedInputStream(in);
final ClipboardReader reader = format.getReader(in);
final WorldData worldData = player.getWorld().getWorldData();
final Clipboard clipboard;
@ -120,16 +130,18 @@ public class SchematicCommands {
clipboard = reader.read(player.getWorld().getWorldData());
}
session.setClipboard(new ClipboardHolder(clipboard, worldData));
log.info(player.getName() + " loaded " + filePath);
BBC.SCHEMATIC_LOADED.send(player, filename);
}
} catch (final IOException e) {
player.printError("Schematic could not read or it does not exist: " + e.getMessage());
} 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 {
if (in != null) {
try {
closer.close();
} catch (final IOException ignored) {}
in.close();
} catch (IOException ignored) {}
}
}
}
@ -138,17 +150,26 @@ 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 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;
}
}
try {
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();
@ -164,20 +185,8 @@ public class SchematicCommands {
target = clipboard;
}
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;
}
}
final FileOutputStream fos = closer.register(new FileOutputStream(f));
final BufferedOutputStream bos = closer.register(new BufferedOutputStream(fos));
final ClipboardWriter writer = closer.register(format.getWriter(bos));
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 {
@ -185,13 +194,14 @@ public class SchematicCommands {
}
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) {}
}
}

View File

@ -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);
}