001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.core.command; 020 021import com.google.inject.Inject; 022import com.plotsquared.core.PlotSquared; 023import com.plotsquared.core.configuration.Settings; 024import com.plotsquared.core.configuration.caption.TranslatableCaption; 025import com.plotsquared.core.database.DBFunc; 026import com.plotsquared.core.listener.PlotListener; 027import com.plotsquared.core.player.PlotPlayer; 028import com.plotsquared.core.plot.Plot; 029import com.plotsquared.core.plot.PlotArea; 030import com.plotsquared.core.plot.PlotId; 031import com.plotsquared.core.plot.world.PlotAreaManager; 032import com.plotsquared.core.util.StringMan; 033import com.plotsquared.core.util.query.PlotQuery; 034import com.plotsquared.core.util.task.TaskManager; 035import com.plotsquared.core.uuid.UUIDMapping; 036import net.kyori.adventure.text.Component; 037import net.kyori.adventure.text.minimessage.tag.Tag; 038import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 039import org.apache.logging.log4j.LogManager; 040import org.apache.logging.log4j.Logger; 041import org.checkerframework.checker.nullness.qual.NonNull; 042 043import java.util.HashMap; 044import java.util.HashSet; 045import java.util.Iterator; 046import java.util.Map.Entry; 047import java.util.UUID; 048import java.util.concurrent.atomic.AtomicBoolean; 049 050@CommandDeclaration(usage = "/plot purge world:<world> area:<area> id:<id> owner:<owner> shared:<shared> unknown:[true | false] clear:[true | false]", 051 command = "purge", 052 permission = "plots.admin", 053 category = CommandCategory.ADMINISTRATION, 054 requiredType = RequiredType.CONSOLE, 055 confirmation = true) 056public class Purge extends SubCommand { 057 058 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + Purge.class.getSimpleName()); 059 060 private final PlotAreaManager plotAreaManager; 061 private final PlotListener plotListener; 062 063 @Inject 064 public Purge( 065 final @NonNull PlotAreaManager plotAreaManager, 066 final @NonNull PlotListener plotListener 067 ) { 068 this.plotAreaManager = plotAreaManager; 069 this.plotListener = plotListener; 070 } 071 072 @Override 073 public boolean onCommand(final PlotPlayer<?> player, String[] args) { 074 if (args.length == 0) { 075 sendUsage(player); 076 return false; 077 } 078 079 String world = null; 080 PlotArea area = null; 081 PlotId id = null; 082 UUID owner = null; 083 UUID added = null; 084 boolean clear = false; 085 boolean unknown = false; 086 for (String arg : args) { 087 String[] split = arg.split(":"); 088 if (split.length != 2) { 089 sendUsage(player); 090 return false; 091 } 092 switch (split[0].toLowerCase()) { 093 case "world", "w" -> world = split[1]; 094 case "area", "a" -> { 095 area = this.plotAreaManager.getPlotAreaByString(split[1]); 096 if (area == null) { 097 player.sendMessage( 098 TranslatableCaption.of("errors.not_valid_plot_world"), 099 TagResolver.resolver("value", Tag.inserting(Component.text(split[1]))) 100 ); 101 return false; 102 } 103 } 104 case "plotid", "id" -> { 105 try { 106 id = PlotId.fromString(split[1]); 107 } catch (IllegalArgumentException ignored) { 108 player.sendMessage( 109 TranslatableCaption.of("invalid.not_valid_plot_id"), 110 TagResolver.resolver("value", Tag.inserting(Component.text(split[1]))) 111 ); 112 return false; 113 } 114 } 115 case "owner", "o" -> { 116 UUIDMapping ownerMapping = PlotSquared.get().getImpromptuUUIDPipeline().getImmediately(split[1]); 117 if (ownerMapping == null) { 118 player.sendMessage( 119 TranslatableCaption.of("errors.invalid_player"), 120 TagResolver.resolver("value", Tag.inserting(Component.text(split[1]))) 121 ); 122 return false; 123 } 124 owner = ownerMapping.uuid(); 125 } 126 case "shared", "s" -> { 127 UUIDMapping addedMapping = PlotSquared.get().getImpromptuUUIDPipeline().getImmediately(split[1]); 128 if (addedMapping == null) { 129 player.sendMessage( 130 TranslatableCaption.of("errors.invalid_player"), 131 TagResolver.resolver("value", Tag.inserting(Component.text(split[1]))) 132 ); 133 return false; 134 } 135 added = addedMapping.uuid(); 136 } 137 case "clear", "c", "delete", "d", "del" -> clear = Boolean.parseBoolean(split[1]); 138 case "unknown", "?", "u" -> unknown = Boolean.parseBoolean(split[1]); 139 default -> { 140 sendUsage(player); 141 return false; 142 } 143 } 144 } 145 final HashSet<Plot> toDelete = new HashSet<>(); 146 for (Plot plot : PlotQuery.newQuery().whereBasePlot()) { 147 if (world != null && !plot.getWorldName().equalsIgnoreCase(world)) { 148 continue; 149 } 150 if (area != null && !plot.getArea().equals(area)) { 151 continue; 152 } 153 if (id != null && !plot.getId().equals(id)) { 154 continue; 155 } 156 if (owner != null && !plot.isOwner(owner)) { 157 continue; 158 } 159 if (added != null && !plot.isAdded(added)) { 160 continue; 161 } 162 if (unknown) { 163 UUIDMapping uuidMapping = PlotSquared.get().getImpromptuUUIDPipeline().getImmediately(plot.getOwner()); 164 if (uuidMapping != null) { 165 continue; 166 } 167 } 168 toDelete.addAll(plot.getConnectedPlots()); 169 } 170 if (PlotSquared.get().plots_tmp != null) { 171 for (Entry<String, HashMap<PlotId, Plot>> entry : PlotSquared.get().plots_tmp 172 .entrySet()) { 173 String worldName = entry.getKey(); 174 if (world != null && !world.equalsIgnoreCase(worldName)) { 175 continue; 176 } 177 for (Entry<PlotId, Plot> entry2 : entry.getValue().entrySet()) { 178 Plot plot = entry2.getValue(); 179 if (area != null && !plot.getArea().equals(area)) { 180 continue; 181 } 182 if (id != null && !plot.getId().equals(id)) { 183 continue; 184 } 185 if (owner != null && !plot.isOwner(owner)) { 186 continue; 187 } 188 if (added != null && !plot.isAdded(added)) { 189 continue; 190 } 191 if (unknown) { 192 UUIDMapping addedMapping = PlotSquared.get().getImpromptuUUIDPipeline().getImmediately(plot.getOwner()); 193 if (addedMapping != null) { 194 continue; 195 } 196 } 197 toDelete.add(plot); 198 } 199 } 200 } 201 if (toDelete.isEmpty()) { 202 player.sendMessage(TranslatableCaption.of("invalid.found_no_plots")); 203 return false; 204 } 205 String cmd = 206 "/plot purge " + StringMan.join(args, " ") + " (" + toDelete.size() + " plots)"; 207 boolean finalClear = clear; 208 Runnable run = () -> { 209 LOGGER.info("Calculating plots to purge, please wait..."); 210 HashSet<Integer> ids = new HashSet<>(); 211 Iterator<Plot> iterator = toDelete.iterator(); 212 AtomicBoolean cleared = new AtomicBoolean(true); 213 Runnable runasync = new Runnable() { 214 @Override 215 public void run() { 216 while (iterator.hasNext() && cleared.get()) { 217 cleared.set(false); 218 Plot plot = iterator.next(); 219 if (plot.temp != Integer.MAX_VALUE) { 220 try { 221 ids.add(plot.temp); 222 if (finalClear) { 223 plot.getPlotModificationManager().clear(false, true, player, 224 () -> LOGGER.info("Plot {} cleared by purge", plot.getId()) 225 ); 226 } else { 227 plot.getPlotModificationManager().removeSign(); 228 } 229 plot.getArea().removePlot(plot.getId()); 230 for (PlotPlayer<?> pp : plot.getPlayersInPlot()) { 231 Purge.this.plotListener.plotEntry(pp, plot); 232 } 233 } catch (NullPointerException e) { 234 LOGGER.error("NullPointer during purge detected. This is likely" 235 + " because you are deleting a world that has been removed", e); 236 } 237 } 238 cleared.set(true); 239 } 240 if (iterator.hasNext()) { 241 TaskManager.runTaskAsync(this); 242 } else { 243 TaskManager.runTask(() -> { 244 DBFunc.purgeIds(ids); 245 player.sendMessage( 246 TranslatableCaption.of("purge.purge_success"), 247 TagResolver.resolver( 248 "amount", 249 Tag.inserting(Component.text(ids.size() + "/" + toDelete.size())) 250 ) 251 ); 252 }); 253 } 254 } 255 }; 256 TaskManager.runTaskAsync(runasync); 257 }; 258 if (hasConfirmation(player)) { 259 if (unknown) { 260 if (Settings.UUID.BACKGROUND_CACHING_ENABLED) { 261 player.sendMessage(TranslatableCaption.of("purge.confirm_purge_unknown_bg_enabled")); 262 } else { 263 player.sendMessage(TranslatableCaption.of("purge.confirm_purge_unknown_bg_disabled")); 264 } 265 } 266 CmdConfirm.addPending(player, cmd, run); 267 } else { 268 run.run(); 269 } 270 return true; 271 } 272 273}