package com.intellectualcrafters.plot.commands; import com.intellectualcrafters.plot.PS; import com.intellectualcrafters.plot.config.C; import com.intellectualcrafters.plot.object.ChunkLoc; import com.intellectualcrafters.plot.object.Location; import com.intellectualcrafters.plot.object.Plot; import com.intellectualcrafters.plot.object.PlotPlayer; import com.intellectualcrafters.plot.object.RegionWrapper; import com.intellectualcrafters.plot.object.RunnableVal; import com.intellectualcrafters.plot.object.RunnableVal2; import com.intellectualcrafters.plot.util.ChunkManager; import com.intellectualcrafters.plot.util.MainUtil; import com.intellectualcrafters.plot.util.TaskManager; import com.intellectualcrafters.plot.util.WorldUtil; import com.intellectualcrafters.plot.util.block.GlobalBlockQueue; import com.intellectualcrafters.plot.util.block.LocalBlockQueue; import com.intellectualcrafters.plot.util.expiry.ExpireManager; import com.plotsquared.general.commands.CommandDeclaration; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.Set; @CommandDeclaration( command = "trim", permission = "plots.admin", description = "Delete unmodified portions of your plotworld", usage = "/plot trim <world> [regenerate]", requiredType = RequiredType.CONSOLE, category = CommandCategory.ADMINISTRATION) public class Trim extends SubCommand { public static ArrayList<Plot> expired = null; private static volatile boolean TASK = false; public static boolean getBulkRegions(final ArrayList<ChunkLoc> empty, final String world, final Runnable whenDone) { if (Trim.TASK) { return false; } TaskManager.runTaskAsync(new Runnable() { @Override public void run() { String directory = world + File.separator + "region"; File folder = new File(PS.get().IMP.getWorldContainer(), directory); File[] regionFiles = folder.listFiles(); for (File file : regionFiles) { String name = file.getName(); if (name.endsWith("mca")) { if (file.getTotalSpace() <= 8192) { checkMca(name); } else { Path path = Paths.get(file.getPath()); try { BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class); long creation = attr.creationTime().toMillis(); long modification = file.lastModified(); long diff = Math.abs(creation - modification); if (diff < 10000) { checkMca(name); } } catch (IOException ignored) {} } } } Trim.TASK = false; TaskManager.runTaskAsync(whenDone); } private void checkMca(String name) { try { String[] split = name.split("\\."); int x = Integer.parseInt(split[1]); int z = Integer.parseInt(split[2]); ChunkLoc loc = new ChunkLoc(x, z); empty.add(loc); } catch (NumberFormatException ignored) { PS.debug("INVALID MCA: " + name); } } }); Trim.TASK = true; return true; } /** * Runs the result task with the parameters (viable, nonViable). * @param world The world * @param result (viable = .mcr to trim, nonViable = .mcr keep) * @return */ public static boolean getTrimRegions(String world, final RunnableVal2<Set<ChunkLoc>, Set<ChunkLoc>> result) { if (result == null) { return false; } MainUtil.sendMessage(null, "Collecting region data..."); ArrayList<Plot> plots = new ArrayList<>(); plots.addAll(PS.get().getPlots(world)); if (ExpireManager.IMP != null) { plots.removeAll(ExpireManager.IMP.getPendingExpired()); } result.value1 = new HashSet<>(ChunkManager.manager.getChunkChunks(world)); result.value2 = new HashSet<>(); MainUtil.sendMessage(null, " - MCA #: " + result.value1.size()); MainUtil.sendMessage(null, " - CHUNKS: " + (result.value1.size() * 1024) + " (max)"); MainUtil.sendMessage(null, " - TIME ESTIMATE: 12 Parsecs"); TaskManager.objectTask(plots, new RunnableVal<Plot>() { @Override public void run(Plot plot) { Location pos1 = plot.getBottom(); Location pos2 = plot.getTop(); int ccx1 = pos1.getX() >> 9; int ccz1 = pos1.getZ() >> 9; int ccx2 = pos2.getX() >> 9; int ccz2 = pos2.getZ() >> 9; for (int x = ccx1; x <= ccx2; x++) { for (int z = ccz1; z <= ccz2; z++) { ChunkLoc loc = new ChunkLoc(x, z); if (result.value1.remove(loc)) { result.value2.add(loc); } } } } }, result); return true; } @Override public boolean onCommand(final PlotPlayer player, String[] args) { if (args.length == 0) { C.COMMAND_SYNTAX.send(player, getUsage()); return false; } final String world = args[0]; if (!WorldUtil.IMP.isWorld(world) || !PS.get().hasPlotArea(world)) { MainUtil.sendMessage(player, C.NOT_VALID_WORLD); return false; } if (Trim.TASK) { C.TRIM_IN_PROGRESS.send(player); return false; } Trim.TASK = true; final boolean regen = args.length == 2 && Boolean.parseBoolean(args[1]); getTrimRegions(world, new RunnableVal2<Set<ChunkLoc>, Set<ChunkLoc>>() { @Override public void run(Set<ChunkLoc> viable, final Set<ChunkLoc> nonViable) { Runnable regenTask; if (regen) { PS.log("Starting regen task:"); PS.log(" - This is a VERY slow command"); PS.log(" - It will say `Trim done!` when complete"); regenTask = new Runnable() { @Override public void run() { if (nonViable.isEmpty()) { Trim.TASK = false; player.sendMessage("Trim done!"); return; } Iterator<ChunkLoc> iterator = nonViable.iterator(); ChunkLoc mcr = iterator.next(); iterator.remove(); int cbx = mcr.x << 5; int cbz = mcr.z << 5; // get all 1024 chunks HashSet<ChunkLoc> chunks = new HashSet<>(); for (int x = cbx; x < cbx + 32; x++) { for (int z = cbz; z < cbz + 32; z++) { ChunkLoc loc = new ChunkLoc(x, z); chunks.add(loc); } } int bx = cbx << 4; int bz = cbz << 4; RegionWrapper region = new RegionWrapper(bx, bx + 511, bz, bz + 511); for (Plot plot : PS.get().getPlots(world)) { Location bot = plot.getBottomAbs(); Location top = plot.getExtendedTopAbs(); RegionWrapper plotReg = new RegionWrapper(bot.getX(), top.getX(), bot.getZ(), top.getZ()); if (!region.intersects(plotReg)) { continue; } for (int x = plotReg.minX >> 4; x <= plotReg.maxX >> 4; x++) { for (int z = plotReg.minZ >> 4; z <= plotReg.maxZ >> 4; z++) { ChunkLoc loc = new ChunkLoc(x, z); chunks.remove(loc); } } } final LocalBlockQueue queue = GlobalBlockQueue.IMP.getNewQueue(world, false); TaskManager.objectTask(chunks, new RunnableVal<ChunkLoc>() { @Override public void run(ChunkLoc value) { queue.regenChunk(value.x, value.z); } }, this); } }; } else { regenTask = new Runnable() { @Override public void run() { Trim.TASK = false; player.sendMessage("Trim done!"); } }; } ChunkManager.manager.deleteRegionFiles(world, viable, regenTask); } }); return true; } }