diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitCommand.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitCommand.java index 66ed2d63..083fe330 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitCommand.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitCommand.java @@ -23,7 +23,7 @@ public class BukkitCommand implements CommandExecutor { BBC.NO_PERM.send(plr, this.cmd.getPerm()); return true; } - this.cmd.execute(plr, args); + this.cmd.executeSafe(plr, args); return true; } } diff --git a/core/src/main/java/com/boydti/fawe/command/Rollback.java b/core/src/main/java/com/boydti/fawe/command/Rollback.java index 0686c6fc..3142ffe4 100644 --- a/core/src/main/java/com/boydti/fawe/command/Rollback.java +++ b/core/src/main/java/com/boydti/fawe/command/Rollback.java @@ -13,9 +13,11 @@ import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.TaskManager; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.blocks.ItemType; import com.sk89q.worldedit.world.World; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.UUID; public class Rollback extends FaweCommand { @@ -44,25 +46,33 @@ public class Rollback extends FaweCommand { } player.deleteMeta("rollback"); final FaweLocation origin = player.getLocation(); - rollback(player, Arrays.copyOfRange(args, 1, args.length), new RunnableVal>() { + rollback(player, !player.hasPermission("fawe.rollback.deep"), Arrays.copyOfRange(args, 1, args.length), new RunnableVal>() { @Override public void run(List edits) { long total = 0; - player.sendMessage("&d=== Edits ==="); + player.sendMessage("&d=| Username | Bounds | Distance | Changes | Age |="); for (DiskStorageHistory edit : edits) { - int[] headerAndFooter = edit.readHeaderAndFooter(new RegionWrapper(origin.x, origin.x, origin.z, origin.z)); - RegionWrapper region = new RegionWrapper(headerAndFooter[0], headerAndFooter[2], headerAndFooter[1], headerAndFooter[3]); - int dx = region.distanceX(origin.x); - int dz = region.distanceZ(origin.z); + DiskStorageHistory.DiskStorageSummary summary = edit.summarize(new RegionWrapper(origin.x, origin.x, origin.z, origin.z), !player.hasPermission("fawe.rollback.deep")); + RegionWrapper region = new RegionWrapper(summary.minX, summary.maxX, summary.minZ, summary.maxZ); + int distance = region.distance(origin.x, origin.z); String name = Fawe.imp().getName(edit.getUUID()); long seconds = (System.currentTimeMillis() - edit.getBDFile().lastModified()) / 1000; total += edit.getBDFile().length(); - player.sendMessage(name + " : " + dx + "," + dz + " : " + MainUtil.secToTime(seconds)); + int size = summary.getSize(); + Map percents = summary.getPercents(); + StringBuilder percentString = new StringBuilder(); + String prefix = ""; + for (Map.Entry entry : percents.entrySet()) { + percentString.append(prefix).append(entry.getValue()).append("% ").append(ItemType.toName(entry.getKey())); + prefix = ", "; + } + player.sendMessage("&c" + name + " | " + region + " | " + distance + "m | " + size + " | " + MainUtil.secToTime(seconds)); + player.sendMessage("&8 - &7(" + percentString + ")"); } - player.sendMessage("&d============="); + player.sendMessage("&d=================================================="); player.sendMessage("&dSize: " + (((double) (total / 1024)) / 1000) + "MB"); player.sendMessage("&dTo rollback: /frb undo"); - player.sendMessage("&d============="); + player.sendMessage("&d=================================================="); player.setMeta("rollback", edits); } }); @@ -70,6 +80,10 @@ public class Rollback extends FaweCommand { } case "undo": case "revert": { + if (!player.hasPermission("fawe.rollback.perform")) { + BBC.NO_PERM.send(player, "fawe.rollback.perform"); + return false; + } final List edits = (List) player.getMeta("rollback"); player.deleteMeta("rollback"); if (edits == null) { @@ -97,7 +111,7 @@ public class Rollback extends FaweCommand { return true; } - public void rollback(final FawePlayer player, final String[] args, final RunnableVal> result) { + public void rollback(final FawePlayer player, final boolean shallow, final String[] args, final RunnableVal> result) { TaskManager.IMP.async(new Runnable() { @Override public void run() { @@ -148,9 +162,13 @@ public class Rollback extends FaweCommand { } } FaweLocation origin = player.getLocation(); - List edits = MainUtil.getBDFiles(origin, user, radius, time); + List edits = MainUtil.getBDFiles(origin, user, radius, time, shallow); + if (edits == null) { + player.sendMessage("&cToo broad, try refining your search!"); + return; + } if (edits.size() == 0) { - player.sendMessage("No edits found!"); + player.sendMessage("&cNo edits found!"); return; } result.run(edits); diff --git a/core/src/main/java/com/boydti/fawe/object/FaweCommand.java b/core/src/main/java/com/boydti/fawe/object/FaweCommand.java index 211d50ac..6c3ab7d3 100644 --- a/core/src/main/java/com/boydti/fawe/object/FaweCommand.java +++ b/core/src/main/java/com/boydti/fawe/object/FaweCommand.java @@ -1,5 +1,7 @@ package com.boydti.fawe.object; +import com.boydti.fawe.config.BBC; + public abstract class FaweCommand { public final String perm; @@ -11,5 +13,21 @@ public abstract class FaweCommand { return this.perm; } + public boolean executeSafe(final FawePlayer player, final String... args) { + if (player == null) { + execute(player, args); + return true; + } else { + if (player.getMeta("fawe_action") != null) { + BBC.WORLDEDIT_COMMAND_LIMIT.send(player); + return false; + } + player.setMeta("fawe_action", true); + boolean result = execute(player, args); + player.deleteMeta("fawe_action"); + return result; + } + } + public abstract boolean execute(final FawePlayer player, final String... args); } diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java b/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java index a007ac66..de19f0d4 100644 --- a/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java +++ b/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java @@ -34,6 +34,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.UUID; @@ -355,19 +356,21 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet { return osENTT; } - int fx; - int fz; - public int[] readHeaderAndFooter(RegionWrapper requiredRegion) { - if (fx == 0 && fz == 0 && bdFile.exists()) { + private DiskStorageSummary summary; + + public DiskStorageSummary summarize(RegionWrapper requiredRegion, boolean shallow) { + if (summary != null) { + return summary; + } + if (bdFile.exists()) { if ((ox != 0 || oz != 0) && !requiredRegion.isIn(ox, oz)) { - return new int[] {ox, oz, ox, oz}; + return summary = new DiskStorageSummary(ox, oz); } - try { - FileInputStream fis = new FileInputStream(bdFile); + try (FileInputStream fis = new FileInputStream(bdFile)) { LZ4Factory factory = LZ4Factory.fastestInstance(); LZ4Compressor compressor = factory.fastCompressor(); - final InputStream gis; + final LZ4InputStream gis; if (Settings.COMPRESSION_LEVEL > 0) { gis = new LZ4InputStream(new LZ4InputStream(fis)); } else { @@ -375,37 +378,31 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet { } ox = ((gis.read() << 24) + (gis.read() << 16) + (gis.read() << 8) + (gis.read() << 0)); oz = ((gis.read() << 24) + (gis.read() << 16) + (gis.read() << 8) + (gis.read() << 0)); + summary = new DiskStorageSummary(ox, oz); if (!requiredRegion.isIn(ox, oz)) { fis.close(); gis.close(); - return new int[] {ox, oz, ox, oz}; + return summary; } - byte[] even = new byte[9]; - byte[] odd = new byte[9]; - byte[] result = null; + byte[] buffer = new byte[9]; int i = 0; - while (true) { - if ((i++ & 1) == 0) { - if (gis.read(even) == -1) { - result = odd; - break; - } - } else { - if (gis.read(odd) == -1) { - result = even; - break; - } + while (!shallow || gis.hasBytesAvailableInDecompressedBuffer(9)) { + if (gis.read(buffer) == -1) { + fis.close(); + gis.close(); + return summary; } + int x = ((byte) buffer[0] & 0xFF) + ((byte) buffer[1] << 8) + ox; + int z = ((byte) buffer[2] & 0xFF) + ((byte) buffer[3] << 8) + oz; + int combined1 = buffer[7]; + int combined2 = buffer[8]; + summary.add(x, z, ((combined2 << 4) + (combined1 >> 4))); } - fx = ((byte) result[0] & 0xFF) + ((byte) result[1] << 8) + ox; - fz = ((byte) result[2] & 0xFF) + ((byte) result[3] << 8) + oz; - fis.close(); - gis.close(); } catch (IOException e) { e.printStackTrace(); } } - return new int[] {ox, oz, fx, fz}; + return summary; } public IntegerPair readHeader() { @@ -555,4 +552,72 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet { flush(); return size.get(); } + + public static class DiskStorageSummary { + + private final int z; + private final int x; + public int[] blocks; + + public int minX; + public int minZ; + + public int maxX; + public int maxZ; + + public DiskStorageSummary(int x, int z) { + blocks = new int[256]; + this.x = x; + this.z = z; + minX = x; + maxX = x; + minZ = z; + maxZ = z; + } + + public void add(int x, int z, int id) { + blocks[id]++; + if (x < minX) { + minX = x; + } else if (x > maxX) { + maxX = x; + } + if (z < minZ) { + minZ = z; + } else if (z > maxZ) { + maxZ = z; + } + } + + public HashMap getBlocks() { + HashMap map = new HashMap<>(); + for (int i = 0; i < blocks.length; i++) { + if (blocks[i] != 0) { + map.put(i, blocks[i]); + } + } + return map; + } + + public Map getPercents() { + HashMap map = getBlocks(); + int count = getSize(); + HashMap newMap = new HashMap(); + for (Map.Entry entry : map.entrySet()) { + int id = entry.getKey(); + int changes = entry.getValue(); + double percent = ((changes * 1000l) / count) / 10d; + newMap.put(id, percent); + } + return newMap; + } + + public int getSize() { + int count = 0; + for (int i = 0; i < blocks.length; i++) { + count += blocks[i]; + } + return count; + } + } } 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 8290666d..111780b8 100644 --- a/core/src/main/java/com/boydti/fawe/util/MainUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/MainUtil.java @@ -139,7 +139,7 @@ public class MainUtil { return time; } - public static List getBDFiles(FaweLocation origin, UUID user, int radius, long timediff) { + public static List getBDFiles(FaweLocation origin, UUID user, int radius, long timediff, boolean shallow) { File history = new File(Fawe.imp().getDirectory(), "history" + File.separator + origin.world); if (!history.exists()) { return new ArrayList<>(); @@ -168,6 +168,9 @@ public class MainUtil { } } } + if (files.size() > 512) { + return null; + } World world = origin.getWorld(); Collections.sort(files, new Comparator() { @Override @@ -180,11 +183,12 @@ public class MainUtil { for (File file : files) { UUID uuid = UUID.fromString(file.getParentFile().getName()); DiskStorageHistory dsh = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0])); - int[] headerAndFooter = dsh.readHeaderAndFooter(new RegionWrapper(origin.x - 512, origin.x + 512, origin.z - 512, origin.z + 512)); - RegionWrapper region = new RegionWrapper(headerAndFooter[0], headerAndFooter[2], headerAndFooter[1], headerAndFooter[3]); + DiskStorageHistory.DiskStorageSummary summary = dsh.summarize(new RegionWrapper(origin.x - 512, origin.x + 512, origin.z - 512, origin.z + 512), shallow); + RegionWrapper region = new RegionWrapper(summary.minX, summary.maxX, summary.minZ, summary.maxZ); if (region.distance(origin.x, origin.z) <= radius) { result.add(dsh); } + } return result; } diff --git a/core/src/main/java/net/jpountz/lz4/LZ4InputStream.java b/core/src/main/java/net/jpountz/lz4/LZ4InputStream.java index 012c8071..1e1ef192 100644 --- a/core/src/main/java/net/jpountz/lz4/LZ4InputStream.java +++ b/core/src/main/java/net/jpountz/lz4/LZ4InputStream.java @@ -75,6 +75,10 @@ public class LZ4InputStream extends InputStream { return n - numBytesRemainingToSkip; } + public boolean hasBytesAvailableInDecompressedBuffer(int bytes) { + return decompressedBufferPosition + bytes <= decompressedBufferLength; + } + private boolean ensureBytesAvailableInDecompressedBuffer() throws IOException { while (decompressedBufferPosition >= decompressedBufferLength) { if (!fillBuffer()) { diff --git a/forge/src/main/java/com/boydti/fawe/forge/SpongeCommand.java b/forge/src/main/java/com/boydti/fawe/forge/SpongeCommand.java index 7c8cd99d..f7f1c139 100644 --- a/forge/src/main/java/com/boydti/fawe/forge/SpongeCommand.java +++ b/forge/src/main/java/com/boydti/fawe/forge/SpongeCommand.java @@ -30,7 +30,7 @@ public class SpongeCommand implements CommandCallable { BBC.NO_PERM.send(plr, this.cmd.getPerm()); return CommandResult.success(); } - this.cmd.execute(plr, args.split(" ")); + this.cmd.executeSafe(plr, args.split(" ")); return CommandResult.success(); }