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.minimessage.Template;
037import org.apache.logging.log4j.LogManager;
038import org.apache.logging.log4j.Logger;
039import org.checkerframework.checker.nullness.qual.NonNull;
040
041import java.util.HashMap;
042import java.util.HashSet;
043import java.util.Iterator;
044import java.util.Map.Entry;
045import java.util.UUID;
046import java.util.concurrent.atomic.AtomicBoolean;
047
048@CommandDeclaration(usage = "/plot purge world:<world> area:<area> id:<id> owner:<owner> shared:<shared> unknown:[true | false] clear:[true | false]",
049        command = "purge",
050        permission = "plots.admin",
051        category = CommandCategory.ADMINISTRATION,
052        requiredType = RequiredType.CONSOLE,
053        confirmation = true)
054public class Purge extends SubCommand {
055
056    private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + Purge.class.getSimpleName());
057
058    private final PlotAreaManager plotAreaManager;
059    private final PlotListener plotListener;
060
061    @Inject
062    public Purge(
063            final @NonNull PlotAreaManager plotAreaManager,
064            final @NonNull PlotListener plotListener
065    ) {
066        this.plotAreaManager = plotAreaManager;
067        this.plotListener = plotListener;
068    }
069
070    @Override
071    public boolean onCommand(final PlotPlayer<?> player, String[] args) {
072        if (args.length == 0) {
073            sendUsage(player);
074            return false;
075        }
076
077        String world = null;
078        PlotArea area = null;
079        PlotId id = null;
080        UUID owner = null;
081        UUID added = null;
082        boolean clear = false;
083        boolean unknown = false;
084        for (String arg : args) {
085            String[] split = arg.split(":");
086            if (split.length != 2) {
087                sendUsage(player);
088                return false;
089            }
090            switch (split[0].toLowerCase()) {
091                case "world":
092                case "w":
093                    world = split[1];
094                    break;
095                case "area":
096                case "a":
097                    area = this.plotAreaManager.getPlotAreaByString(split[1]);
098                    if (area == null) {
099                        player.sendMessage(
100                                TranslatableCaption.of("errors.not_valid_plot_world"),
101                                Template.of("value", split[1])
102                        );
103                        return false;
104                    }
105                    break;
106                case "plotid":
107                case "id":
108                    try {
109                        id = PlotId.fromString(split[1]);
110                    } catch (IllegalArgumentException ignored) {
111                        player.sendMessage(
112                                TranslatableCaption.of("invalid.not_valid_plot_id"),
113                                Template.of("value", split[1])
114                        );
115                        return false;
116                    }
117                    break;
118                case "owner":
119                case "o":
120                    UUIDMapping ownerMapping = PlotSquared.get().getImpromptuUUIDPipeline().getImmediately(split[1]);
121                    if (ownerMapping == null) {
122                        player.sendMessage(
123                                TranslatableCaption.of("errors.invalid_player"),
124                                Template.of("value", split[1])
125                        );
126                        return false;
127                    }
128                    owner = ownerMapping.getUuid();
129                    break;
130                case "shared":
131                case "s":
132                    UUIDMapping addedMapping = PlotSquared.get().getImpromptuUUIDPipeline().getImmediately(split[1]);
133                    if (addedMapping == null) {
134                        player.sendMessage(
135                                TranslatableCaption.of("errors.invalid_player"),
136                                Template.of("value", split[1])
137                        );
138                        return false;
139                    }
140                    added = addedMapping.getUuid();
141                    break;
142                case "clear":
143                case "c":
144                case "delete":
145                case "d":
146                case "del":
147                    clear = Boolean.parseBoolean(split[1]);
148                    break;
149                case "unknown":
150                case "?":
151                case "u":
152                    unknown = Boolean.parseBoolean(split[1]);
153                    break;
154                default:
155                    sendUsage(player);
156                    return false;
157            }
158        }
159        final HashSet<Plot> toDelete = new HashSet<>();
160        for (Plot plot : PlotQuery.newQuery().whereBasePlot()) {
161            if (world != null && !plot.getWorldName().equalsIgnoreCase(world)) {
162                continue;
163            }
164            if (area != null && !plot.getArea().equals(area)) {
165                continue;
166            }
167            if (id != null && !plot.getId().equals(id)) {
168                continue;
169            }
170            if (owner != null && !plot.isOwner(owner)) {
171                continue;
172            }
173            if (added != null && !plot.isAdded(added)) {
174                continue;
175            }
176            if (unknown) {
177                UUIDMapping uuidMapping = PlotSquared.get().getImpromptuUUIDPipeline().getImmediately(plot.getOwner());
178                if (uuidMapping != null) {
179                    continue;
180                }
181            }
182            toDelete.addAll(plot.getConnectedPlots());
183        }
184        if (PlotSquared.get().plots_tmp != null) {
185            for (Entry<String, HashMap<PlotId, Plot>> entry : PlotSquared.get().plots_tmp
186                    .entrySet()) {
187                String worldName = entry.getKey();
188                if (world != null && !world.equalsIgnoreCase(worldName)) {
189                    continue;
190                }
191                for (Entry<PlotId, Plot> entry2 : entry.getValue().entrySet()) {
192                    Plot plot = entry2.getValue();
193                    if (area != null && !plot.getArea().equals(area)) {
194                        continue;
195                    }
196                    if (id != null && !plot.getId().equals(id)) {
197                        continue;
198                    }
199                    if (owner != null && !plot.isOwner(owner)) {
200                        continue;
201                    }
202                    if (added != null && !plot.isAdded(added)) {
203                        continue;
204                    }
205                    if (unknown) {
206                        UUIDMapping addedMapping = PlotSquared.get().getImpromptuUUIDPipeline().getImmediately(plot.getOwner());
207                        if (addedMapping != null) {
208                            continue;
209                        }
210                    }
211                    toDelete.add(plot);
212                }
213            }
214        }
215        if (toDelete.isEmpty()) {
216            player.sendMessage(TranslatableCaption.of("invalid.found_no_plots"));
217            return false;
218        }
219        String cmd =
220                "/plot purge " + StringMan.join(args, " ") + " (" + toDelete.size() + " plots)";
221        boolean finalClear = clear;
222        Runnable run = () -> {
223            LOGGER.info("Calculating plots to purge, please wait...");
224            HashSet<Integer> ids = new HashSet<>();
225            Iterator<Plot> iterator = toDelete.iterator();
226            AtomicBoolean cleared = new AtomicBoolean(true);
227            Runnable runasync = new Runnable() {
228                @Override
229                public void run() {
230                    while (iterator.hasNext() && cleared.get()) {
231                        cleared.set(false);
232                        Plot plot = iterator.next();
233                        if (plot.temp != Integer.MAX_VALUE) {
234                            try {
235                                ids.add(plot.temp);
236                                if (finalClear) {
237                                    plot.getPlotModificationManager().clear(false, true, player, () -> {
238                                        LOGGER.info("Plot {} cleared by purge", plot.getId());
239                                    });
240                                } else {
241                                    plot.getPlotModificationManager().removeSign();
242                                }
243                                plot.getArea().removePlot(plot.getId());
244                                for (PlotPlayer<?> pp : plot.getPlayersInPlot()) {
245                                    Purge.this.plotListener.plotEntry(pp, plot);
246                                }
247                            } catch (NullPointerException e) {
248                                LOGGER.error("NullPointer during purge detected. This is likely"
249                                        + " because you are deleting a world that has been removed", e);
250                            }
251                        }
252                        cleared.set(true);
253                    }
254                    if (iterator.hasNext()) {
255                        TaskManager.runTaskAsync(this);
256                    } else {
257                        TaskManager.runTask(() -> {
258                            DBFunc.purgeIds(ids);
259                            player.sendMessage(
260                                    TranslatableCaption.of("purge.purge_success"),
261                                    Template.of("amount", ids.size() + "/" + toDelete.size())
262                            );
263                        });
264                    }
265                }
266            };
267            TaskManager.runTaskAsync(runasync);
268        };
269        if (hasConfirmation(player)) {
270            if (unknown) {
271                if (Settings.UUID.BACKGROUND_CACHING_ENABLED) {
272                    player.sendMessage(TranslatableCaption.of("purge.confirm_purge_unknown_bg_enabled"));
273                } else {
274                    player.sendMessage(TranslatableCaption.of("purge.confirm_purge_unknown_bg_disabled"));
275                }
276            }
277            CmdConfirm.addPending(player, cmd, run);
278        } else {
279            run.run();
280        }
281        return true;
282    }
283
284}