diff --git a/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitMain.java b/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitMain.java index 79d27ae33..85a216e9f 100644 --- a/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitMain.java +++ b/Bukkit/src/main/java/com/plotsquared/bukkit/BukkitMain.java @@ -52,6 +52,9 @@ import com.plotsquared.bukkit.util.uuid.OfflineUUIDWrapper; import com.plotsquared.bukkit.util.uuid.SQLUUIDHandler; import com.plotsquared.core.IPlotMain; import com.plotsquared.core.PlotSquared; +import com.plotsquared.core.backup.BackupManager; +import com.plotsquared.core.backup.NullBackupManager; +import com.plotsquared.core.backup.SimpleBackupManager; import com.plotsquared.core.configuration.Captions; import com.plotsquared.core.configuration.ChatFormatter; import com.plotsquared.core.configuration.ConfigurationNode; @@ -136,6 +139,7 @@ import static com.plotsquared.core.util.ReflectionUtils.getRefClass; public final class BukkitMain extends JavaPlugin implements Listener, IPlotMain { + private static final int BSTATS_ID = 1404; @Getter private static WorldEdit worldEdit; static { @@ -151,7 +155,7 @@ public final class BukkitMain extends JavaPlugin implements Listener, IPlotMain private Method methodUnloadChunk0; private boolean methodUnloadSetup = false; private boolean metricsStarted; - private static final int BSTATS_ID = 1404; + @Getter private BackupManager backupManager; @Override public int[] getServerVersion() { if (this.version == null) { @@ -229,6 +233,15 @@ public final class BukkitMain extends JavaPlugin implements Listener, IPlotMain e.printStackTrace(); } } + + try { + this.backupManager = new SimpleBackupManager(); + } catch (final Exception e) { + PlotSquared.log(Captions.PREFIX + "&6Failed to initialize backup manager"); + e.printStackTrace(); + PlotSquared.log(Captions.PREFIX + "&6Backup features will be disabled"); + this.backupManager = new NullBackupManager(); + } } private void unload() { @@ -886,4 +899,5 @@ public final class BukkitMain extends JavaPlugin implements Listener, IPlotMain ((WorldEditPlugin) Bukkit.getPluginManager().getPlugin("WorldEdit")); return wePlugin.wrapCommandSender(console); } + } diff --git a/Core/src/main/java/com/plotsquared/core/IPlotMain.java b/Core/src/main/java/com/plotsquared/core/IPlotMain.java index 1b04b220f..cf6c8fb95 100644 --- a/Core/src/main/java/com/plotsquared/core/IPlotMain.java +++ b/Core/src/main/java/com/plotsquared/core/IPlotMain.java @@ -25,6 +25,7 @@ */ package com.plotsquared.core; +import com.plotsquared.core.backup.BackupManager; import com.plotsquared.core.generator.GeneratorWrapper; import com.plotsquared.core.generator.HybridUtils; import com.plotsquared.core.generator.IndependentPlotGenerator; @@ -262,4 +263,12 @@ public interface IPlotMain extends ILogger { List, Boolean>> getPluginIds(); Actor getConsole(); + + /** + * Get the backup manager instance + * + * @return Backup manager + */ + BackupManager getBackupManager(); + } diff --git a/Core/src/main/java/com/plotsquared/core/backup/Backup.java b/Core/src/main/java/com/plotsquared/core/backup/Backup.java new file mode 100644 index 000000000..a84fc497f --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/backup/Backup.java @@ -0,0 +1,60 @@ +/* + * _____ _ _ _____ _ + * | __ \| | | | / ____| | | + * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | + * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | + * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | + * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| + * | | + * |_| + * PlotSquared plot management system for Minecraft + * Copyright (C) 2020 IntellectualSites + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.core.backup; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Object representing a plot backup. This does not actually contain the + * backup itself, it is just a pointer to an available backup + */ +@RequiredArgsConstructor(access = AccessLevel.PACKAGE) @Getter public class Backup { + + private final BackupProfile owner; + private final long creationTime; + @Nullable private final Path file; + + /** + * Delete the backup + */ + public void delete() { + if (file != null) { + try { + Files.deleteIfExists(file); + } catch (final IOException e) { + e.printStackTrace(); + } + } + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/backup/BackupManager.java b/Core/src/main/java/com/plotsquared/core/backup/BackupManager.java new file mode 100644 index 000000000..4ba4a44f4 --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/backup/BackupManager.java @@ -0,0 +1,95 @@ +/* + * _____ _ _ _____ _ + * | __ \| | | | / ____| | | + * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | + * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | + * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | + * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| + * | | + * |_| + * PlotSquared plot management system for Minecraft + * Copyright (C) 2020 IntellectualSites + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.core.backup; + +import com.plotsquared.core.PlotSquared; +import com.plotsquared.core.player.PlotPlayer; +import com.plotsquared.core.plot.Plot; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.file.Path; +import java.util.Objects; + +public interface BackupManager { + + /** + * This will perform an automatic backup of the plot iff the plot has an owner, + * automatic backups are enabled and the plot is not merged. + * Otherwise it will complete immediately. + * + * @param player Player that triggered the backup + * @param plot Plot to perform the automatic backup on + * @param whenDone Action that runs when the automatic backup has been completed + */ + static void backup(@Nullable PlotPlayer player, @NotNull final Plot plot, @NotNull Runnable whenDone) { + Objects.requireNonNull(PlotSquared.imp()).getBackupManager().automaticBackup(player, plot, whenDone); + } + + /** + * Get the backup profile for a plot based on its + * current owner (if there is one) + * + * @param plot Plot to get the backup profile for + * @return Backup profile + */ + @NotNull BackupProfile getProfile(@NotNull final Plot plot); + + /** + * This will perform an automatic backup of the plot iff the plot has an owner, + * automatic backups are enabled and the plot is not merged. + * Otherwise it will complete immediately. + * + * @param player Player that triggered the backup + * @param plot Plot to perform the automatic backup on + * @param whenDone Action that runs when the automatic backup has been completed + */ + void automaticBackup(@Nullable PlotPlayer player, @NotNull final Plot plot, @NotNull Runnable whenDone); + + /** + * Get the directory in which backups are stored + * + * @return Backup directory path + */ + @NotNull Path getBackupPath(); + + /** + * Get the maximum amount of backups that may be stored for + * a plot-owner combo + * + * @return Backup limit + */ + int getBackupLimit(); + + /** + * Returns true if (potentially) destructive actions should cause + * PlotSquared to create automatic plot backups + * + * @return True if automatic backups are enabled + */ + boolean shouldAutomaticallyBackup(); + +} diff --git a/Core/src/main/java/com/plotsquared/core/backup/BackupProfile.java b/Core/src/main/java/com/plotsquared/core/backup/BackupProfile.java new file mode 100644 index 000000000..6664246e4 --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/backup/BackupProfile.java @@ -0,0 +1,72 @@ +/* + * _____ _ _ _____ _ + * | __ \| | | | / ____| | | + * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | + * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | + * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | + * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| + * | | + * |_| + * PlotSquared plot management system for Minecraft + * Copyright (C) 2020 IntellectualSites + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.core.backup; + +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public interface BackupProfile { + + /** + * Asynchronously populate a list of available backups under this profile + * + * @return Future that will be completed with available backups + */ + @NotNull CompletableFuture> listBackups(); + + /** + * Remove all backups stored for this profile + */ + void destroy(); + + /** + * Get the directory containing the backups for this profile. + * This directory may not actually exist. + * + * @return Folder that contains the backups for this profile + */ + @NotNull Path getBackupDirectory(); + + /** + * Create a backup of the plot. If the profile is at the + * maximum backup capacity, the oldest backup will be deleted. + * + * @return Future that completes with the created backup. + */ + @NotNull CompletableFuture createBackup(); + + /** + * Restore a backup + * + * @param backup Backup to restore + * @return Future that completes when the backup has finished + */ + @NotNull CompletableFuture restoreBackup(@NotNull final Backup backup); + +} diff --git a/Core/src/main/java/com/plotsquared/core/backup/NullBackupManager.java b/Core/src/main/java/com/plotsquared/core/backup/NullBackupManager.java new file mode 100644 index 000000000..2d8c04a76 --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/backup/NullBackupManager.java @@ -0,0 +1,63 @@ +/* + * _____ _ _ _____ _ + * | __ \| | | | / ____| | | + * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | + * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | + * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | + * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| + * | | + * |_| + * PlotSquared plot management system for Minecraft + * Copyright (C) 2020 IntellectualSites + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.core.backup; + +import com.plotsquared.core.PlotSquared; +import com.plotsquared.core.player.PlotPlayer; +import com.plotsquared.core.plot.Plot; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.file.Path; +import java.util.Objects; + +/** + * {@inheritDoc} + */ +public class NullBackupManager implements BackupManager { + + @Override @NotNull public BackupProfile getProfile(@NotNull Plot plot) { + return new NullBackupProfile(); + } + + @Override public void automaticBackup(@Nullable PlotPlayer plotPlayer, + @NotNull Plot plot, @NotNull Runnable whenDone) { + whenDone.run(); + } + + @Override @NotNull public Path getBackupPath() { + return Objects.requireNonNull(PlotSquared.imp()).getDirectory().toPath(); + } + + @Override public int getBackupLimit() { + return 0; + } + + @Override public boolean shouldAutomaticallyBackup() { + return false; + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/backup/NullBackupProfile.java b/Core/src/main/java/com/plotsquared/core/backup/NullBackupProfile.java new file mode 100644 index 000000000..39c833579 --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/backup/NullBackupProfile.java @@ -0,0 +1,61 @@ +/* + * _____ _ _ _____ _ + * | __ \| | | | / ____| | | + * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | + * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | + * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | + * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| + * | | + * |_| + * PlotSquared plot management system for Minecraft + * Copyright (C) 2020 IntellectualSites + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.core.backup; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * Backup profile for a plot without an owner + * {@inheritDoc} + */ +public class NullBackupProfile implements BackupProfile { + + @Override @NotNull public CompletableFuture> listBackups() { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + @Override public void destroy(){ + } + + @Override @NotNull public Path getBackupDirectory() { + return new File(".").toPath(); + } + + @Override @NotNull public CompletableFuture createBackup() { + throw new UnsupportedOperationException("Cannot create backup of an unowned plot"); + } + + @Override @NotNull public CompletableFuture restoreBackup(@NotNull final Backup backup) { + return CompletableFuture.completedFuture(null); + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/backup/PlayerBackupProfile.java b/Core/src/main/java/com/plotsquared/core/backup/PlayerBackupProfile.java new file mode 100644 index 000000000..8853f3dff --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/backup/PlayerBackupProfile.java @@ -0,0 +1,186 @@ +/* + * _____ _ _ _____ _ + * | __ \| | | | / ____| | | + * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | + * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | + * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | + * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| + * | | + * |_| + * PlotSquared plot management system for Minecraft + * Copyright (C) 2020 IntellectualSites + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.core.backup; + +import com.plotsquared.core.configuration.Captions; +import com.plotsquared.core.plot.Plot; +import com.plotsquared.core.plot.schematic.Schematic; +import com.plotsquared.core.util.SchematicHandler; +import com.plotsquared.core.util.task.RunnableVal; +import com.plotsquared.core.util.task.TaskManager; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +/** + * A profile associated with a player (normally a plot owner) and a + * plot, which is used to store and retrieve plot backups + * {@inheritDoc} + */ +@RequiredArgsConstructor +public class PlayerBackupProfile implements BackupProfile { + + private final UUID owner; + private final Plot plot; + private final BackupManager backupManager; + + private volatile List backupCache; + private final Object backupLock = new Object(); + + private static boolean isValidFile(@NotNull final Path path) { + final String name = path.getFileName().toString(); + return name.endsWith(".schem") || name.endsWith(".schematic"); + } + + @Override @NotNull public CompletableFuture> listBackups() { + synchronized (this.backupLock) { + if (this.backupCache != null) { + return CompletableFuture.completedFuture(backupCache); + } + return CompletableFuture.supplyAsync(() -> { + final Path path = this.getBackupDirectory(); + if (!Files.exists(path)) { + try { + Files.createDirectories(path); + } catch (IOException e) { + e.printStackTrace(); + return Collections.emptyList(); + } + } + final List backups = new ArrayList<>(); + try { + Files.walk(path).filter(PlayerBackupProfile::isValidFile).forEach(file -> { + try { + final BasicFileAttributes basicFileAttributes = + Files.readAttributes(file, BasicFileAttributes.class); + backups.add( + new Backup(this, basicFileAttributes.creationTime().toMillis(), file)); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + backups.sort(Comparator.comparingLong(Backup::getCreationTime).reversed()); + return (this.backupCache = backups); + }); + } + } + + @Override public void destroy() { + this.listBackups().whenCompleteAsync((backups, error) -> { + if (error != null) { + error.printStackTrace(); + } + backups.forEach(Backup::delete); + this.backupCache = null; + }); + } + + @NotNull public Path getBackupDirectory() { + return resolve(resolve(resolve(backupManager.getBackupPath(), Objects.requireNonNull(plot.getArea().toString(), "plot area id")), + Objects.requireNonNull(plot.getId().toDashSeparatedString(), "plot id")), Objects.requireNonNull(owner.toString(), "owner")); + } + + private static Path resolve(@NotNull final Path parent, final String child) { + Path path = parent; + try { + if (!Files.exists(parent)) { + Files.createDirectory(parent); + } + path = parent.resolve(child); + if (!Files.exists(path)) { + Files.createDirectory(path); + } + } catch (final Exception e) { + e.printStackTrace(); + } + return path; + } + + @Override @NotNull public CompletableFuture createBackup() { + final CompletableFuture future = new CompletableFuture<>(); + this.listBackups().thenAcceptAsync(backups -> { + synchronized (this.backupLock) { + if (backups.size() == backupManager.getBackupLimit()) { + backups.get(backups.size() - 1).delete(); + } + final List plots = Collections.singletonList(plot); + final boolean result = SchematicHandler.manager.exportAll(plots, getBackupDirectory().toFile(), + "%world%-%id%-%owner%-" + System.currentTimeMillis(), () -> + future.complete(new Backup(this, System.currentTimeMillis(), null))); + if (!result) { + future.completeExceptionally(new RuntimeException("Failed to complete the backup")); + } + this.backupCache = null; + } + }); + return future; + } + + @Override @NotNull public CompletableFuture restoreBackup(@NotNull final Backup backup) { + final CompletableFuture future = new CompletableFuture<>(); + if (backup.getFile() == null || !Files.exists(backup.getFile())) { + future.completeExceptionally(new IllegalArgumentException("The specific backup does not exist")); + } else { + TaskManager.runTaskAsync(() -> { + Schematic schematic = null; + try { + schematic = SchematicHandler.manager.getSchematic(backup.getFile().toFile()); + } catch (SchematicHandler.UnsupportedFormatException e) { + e.printStackTrace(); + } + if (schematic == null) { + future.completeExceptionally(new IllegalArgumentException("The backup is non-existent or not in the correct format")); + } else { + SchematicHandler.manager.paste(schematic, plot, 0, 1, 0, false, new RunnableVal() { + @Override public void run(Boolean value) { + if (value) { + future.complete(null); + } else { + future.completeExceptionally(new RuntimeException(Captions.SCHEMATIC_PASTE_FAILED.toString())); + } + } + }); + } + }); + } + return future; + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/backup/SimpleBackupManager.java b/Core/src/main/java/com/plotsquared/core/backup/SimpleBackupManager.java new file mode 100644 index 000000000..57cbea1d8 --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/backup/SimpleBackupManager.java @@ -0,0 +1,131 @@ +/* + * _____ _ _ _____ _ + * | __ \| | | | / ____| | | + * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | + * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | + * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | + * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| + * | | + * |_| + * PlotSquared plot management system for Minecraft + * Copyright (C) 2020 IntellectualSites + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.core.backup; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.plotsquared.core.PlotSquared; +import com.plotsquared.core.configuration.Captions; +import com.plotsquared.core.configuration.Settings; +import com.plotsquared.core.player.PlotPlayer; +import com.plotsquared.core.plot.Plot; +import com.plotsquared.core.util.task.TaskManager; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +/** + * {@inheritDoc} + */ +@RequiredArgsConstructor public class SimpleBackupManager implements BackupManager { + + @Getter private final Path backupPath; + private final boolean automaticBackup; + @Getter private final int backupLimit; + private final Cache backupProfileCache = CacheBuilder.newBuilder() + .expireAfterAccess(3, TimeUnit.MINUTES).build(); + + public SimpleBackupManager() throws Exception { + this.backupPath = Objects.requireNonNull(PlotSquared.imp()).getDirectory().toPath().resolve("backups"); + if (!Files.exists(backupPath)) { + Files.createDirectory(backupPath); + } + this.automaticBackup = Settings.Backup.AUTOMATIC_BACKUPS; + this.backupLimit = Settings.Backup.BACKUP_LIMIT; + } + + @Override @NotNull public BackupProfile getProfile(@NotNull final Plot plot) { + if (plot.hasOwner() && !plot.isMerged()) { + try { + return backupProfileCache.get(new PlotCacheKey(plot), () -> new PlayerBackupProfile(plot.getOwnerAbs(), plot, this)); + } catch (ExecutionException e) { + final BackupProfile profile = new PlayerBackupProfile(plot.getOwnerAbs(), plot, this); + this.backupProfileCache.put(new PlotCacheKey(plot), profile); + return profile; + } + } + return new NullBackupProfile(); + } + + @Override public void automaticBackup(@Nullable PlotPlayer player, @NotNull final Plot plot, @NotNull Runnable whenDone) { + final BackupProfile profile; + if (!this.shouldAutomaticallyBackup() || (profile = getProfile(plot)) instanceof NullBackupProfile) { + whenDone.run(); + } else { + if (player != null) { + Captions.BACKUP_AUTOMATIC_STARTED.send(player); + } + profile.createBackup().whenComplete((backup, throwable) -> { + if (throwable != null) { + if (player != null) { + Captions.BACKUP_AUTOMATIC_FAILURE.send(player, throwable.getMessage()); + } + throwable.printStackTrace(); + } else { + if (player != null) { + Captions.BACKUP_AUTOMATIC_FINISHED.send(player); + TaskManager.runTaskAsync(whenDone); + } + } + }); + } + } + + @Override public boolean shouldAutomaticallyBackup() { + return this.automaticBackup; + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) private static final class PlotCacheKey { + + private final Plot plot; + + @Override public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final PlotCacheKey that = (PlotCacheKey) o; + return Objects.equals(plot.getArea(), that.plot.getArea()) + && Objects.equals(plot.getId(), that.plot.getId()) + && Objects.equals(plot.getOwnerAbs(), that.plot.getOwnerAbs()); + } + + @Override public int hashCode() { + return Objects.hash(plot.getArea(), plot.getId(), plot.getOwnerAbs()); + } + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/command/Backup.java b/Core/src/main/java/com/plotsquared/core/command/Backup.java new file mode 100644 index 000000000..9040735af --- /dev/null +++ b/Core/src/main/java/com/plotsquared/core/command/Backup.java @@ -0,0 +1,251 @@ +/* + * _____ _ _ _____ _ + * | __ \| | | | / ____| | | + * | |__) | | ___ | |_| (___ __ _ _ _ __ _ _ __ ___ __| | + * | ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` | + * | | | | (_) | |_ ____) | (_| | |_| | (_| | | | __/ (_| | + * |_| |_|\___/ \__|_____/ \__, |\__,_|\__,_|_| \___|\__,_| + * | | + * |_| + * PlotSquared plot management system for Minecraft + * Copyright (C) 2020 IntellectualSites + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.plotsquared.core.command; + +import com.plotsquared.core.PlotSquared; +import com.plotsquared.core.backup.BackupProfile; +import com.plotsquared.core.backup.NullBackupProfile; +import com.plotsquared.core.backup.PlayerBackupProfile; +import com.plotsquared.core.configuration.Captions; +import com.plotsquared.core.player.PlotPlayer; +import com.plotsquared.core.plot.Plot; +import com.plotsquared.core.util.Permissions; +import com.plotsquared.core.util.task.RunnableVal2; +import com.plotsquared.core.util.task.RunnableVal3; + +import java.nio.file.Files; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +@CommandDeclaration(command = "backup", +usage = "/plot backup ", +description = "Manage plot backups", +category = CommandCategory.SETTINGS, +requiredType = RequiredType.PLAYER, +permission = "plots.backup") +public final class Backup extends Command { + + public Backup() { + super(MainCommand.getInstance(), true); + } + + private static boolean sendMessage(PlotPlayer player, Captions message, Object... args) { + message.send(player, args); + return true; + } + + @Override public CompletableFuture execute(PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) throws CommandException { + if (args.length == 0 || !Arrays.asList("save", "list", "load") + .contains(args[0].toLowerCase(Locale.ENGLISH))) { + return CompletableFuture.completedFuture(sendMessage(player, Captions.BACKUP_USAGE)); + } + return super.execute(player, args, confirm, whenDone); + } + + @Override public Collection tab(PlotPlayer player, String[] args, boolean space) { + if (args.length == 1) { + return Stream.of("save", "list", "load") + .filter(value -> value.startsWith(args[0].toLowerCase(Locale.ENGLISH))) + .map(value -> new Command(null, false, value, "", RequiredType.NONE, null) {}) + .collect(Collectors.toList()); + } else if (args[0].equalsIgnoreCase("load")) { + + final Plot plot = player.getCurrentPlot(); + if (plot != null) { + final BackupProfile backupProfile = Objects.requireNonNull(PlotSquared.imp()).getBackupManager().getProfile(plot); + if (backupProfile instanceof PlayerBackupProfile) { + final CompletableFuture> backupList = backupProfile.listBackups(); + if (backupList.isDone()) { + final List backups = backupList.getNow(new ArrayList<>()); + if (backups.isEmpty()) { + return new ArrayList<>(); + } + return IntStream.range(1, 1 + backups.size()).mapToObj(i -> new Command(null, false, Integer.toString(i), + "", RequiredType.NONE, null) {}).collect(Collectors.toList()); + + } + } + } + } + return tabOf(player, args, space); + } + + @CommandDeclaration(command = "save", + usage = "/plot backup save", + description = "Create a plot backup", + category = CommandCategory.SETTINGS, + requiredType = RequiredType.PLAYER, + permission = "plots.backup.save") + public void save(final Command command, final PlotPlayer player, final String[] args, + final RunnableVal3 confirm, + final RunnableVal2 whenDone) { + final Plot plot = player.getCurrentPlot(); + if (plot == null) { + sendMessage(player, Captions.NOT_IN_PLOT); + } else if (!plot.hasOwner()) { + sendMessage(player, Captions.BACKUP_IMPOSSIBLE, Captions.GENERIC_UNOWNED.getTranslated()); + } else if (plot.isMerged()) { + sendMessage(player, Captions.BACKUP_IMPOSSIBLE, Captions.GENERIC_MERGED.getTranslated()); + } else if (!plot.isOwner(player.getUUID()) && !Permissions.hasPermission(player, Captions.PERMISSION_ADMIN_BACKUP_OTHER)) { + sendMessage(player, Captions.NO_PERMISSION, Captions.PERMISSION_ADMIN_BACKUP_OTHER); + } else { + final BackupProfile backupProfile = Objects.requireNonNull(PlotSquared.imp()).getBackupManager().getProfile(plot); + if (backupProfile instanceof NullBackupProfile) { + sendMessage(player, Captions.BACKUP_IMPOSSIBLE, Captions.GENERIC_OTHER.getTranslated()); + } else { + backupProfile.createBackup().whenComplete((backup, throwable) -> { + if (throwable != null) { + sendMessage(player, Captions.BACKUP_SAVE_FAILED, throwable.getMessage()); + throwable.printStackTrace(); + } else { + sendMessage(player, Captions.BACKUP_SAVE_SUCCESS); + } + }); + } + } + } + + @CommandDeclaration(command = "list", + usage = "/plot backup list", + description = "List available plot backups", + category = CommandCategory.SETTINGS, + requiredType = RequiredType.PLAYER, + permission = "plots.backup.list") + public void list(final Command command, final PlotPlayer player, final String[] args, + final RunnableVal3 confirm, + final RunnableVal2 whenDone) { + final Plot plot = player.getCurrentPlot(); + if (plot == null) { + sendMessage(player, Captions.NOT_IN_PLOT); + } else if (!plot.hasOwner()) { + sendMessage(player, Captions.BACKUP_IMPOSSIBLE, Captions.GENERIC_UNOWNED.getTranslated()); + } else if (plot.isMerged()) { + sendMessage(player, Captions.BACKUP_IMPOSSIBLE, Captions.GENERIC_MERGED.getTranslated()); + } else if (!plot.isOwner(player.getUUID()) && !Permissions.hasPermission(player, Captions.PERMISSION_ADMIN_BACKUP_OTHER)) { + sendMessage(player, Captions.NO_PERMISSION, Captions.PERMISSION_ADMIN_BACKUP_OTHER); + } else { + final BackupProfile backupProfile = Objects.requireNonNull(PlotSquared.imp()).getBackupManager().getProfile(plot); + if (backupProfile instanceof NullBackupProfile) { + sendMessage(player, Captions.BACKUP_IMPOSSIBLE, Captions.GENERIC_OTHER.getTranslated()); + } else { + backupProfile.listBackups().whenComplete((backups, throwable) -> { + if (throwable != null) { + sendMessage(player, Captions.BACKUP_LIST_FAILED, throwable.getMessage()); + throwable.printStackTrace(); + } else { + sendMessage(player, Captions.BACKUP_LIST_HEADER, plot.getId().toCommaSeparatedString()); + try { + for (int i = 0; i < backups.size(); i++) { + sendMessage(player, Captions.BACKUP_LIST_ENTRY, Integer.toString(i + 1), + DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime + .ofInstant(Instant.ofEpochMilli(backups.get(i).getCreationTime()), ZoneId + .systemDefault()))); + } + } catch (final Exception e) { + e.printStackTrace(); + } + } + }); + } + } + } + + @CommandDeclaration(command = "load", + usage = "/plot backup load <#>", + description = "Restore a plot backup", + category = CommandCategory.SETTINGS, + requiredType = RequiredType.PLAYER, + permission = "plots.backup.load") + public void load(final Command command, final PlotPlayer player, final String[] args, + final RunnableVal3 confirm, + final RunnableVal2 whenDone) { + final Plot plot = player.getCurrentPlot(); + if (plot == null) { + sendMessage(player, Captions.NOT_IN_PLOT); + } else if (!plot.hasOwner()) { + sendMessage(player, Captions.BACKUP_IMPOSSIBLE, Captions.GENERIC_UNOWNED.getTranslated()); + } else if (plot.isMerged()) { + sendMessage(player, Captions.BACKUP_IMPOSSIBLE, Captions.GENERIC_MERGED.getTranslated()); + sendMessage(player, Captions.BACKUP_IMPOSSIBLE, "merged"); + } else if (!plot.isOwner(player.getUUID()) && !Permissions.hasPermission(player, Captions.PERMISSION_ADMIN_BACKUP_OTHER)) { + sendMessage(player, Captions.NO_PERMISSION, Captions.PERMISSION_ADMIN_BACKUP_OTHER); + } else if (args.length == 0) { + sendMessage(player, Captions.BACKUP_LOAD_USAGE); + } else { + final int number; + try { + number = Integer.parseInt(args[0]); + } catch (final Exception e) { + sendMessage(player, Captions.NOT_A_NUMBER, args[0]); + return; + } + final BackupProfile backupProfile = Objects.requireNonNull(PlotSquared.imp()).getBackupManager().getProfile(plot); + if (backupProfile instanceof NullBackupProfile) { + sendMessage(player, Captions.BACKUP_IMPOSSIBLE, Captions.GENERIC_OTHER.getTranslated()); + } else { + backupProfile.listBackups().whenComplete((backups, throwable) -> { + if (throwable != null) { + sendMessage(player, Captions.BACKUP_LOAD_FAILURE, throwable.getMessage()); + throwable.printStackTrace(); + } else { + if (number < 1 || number > backups.size()) { + sendMessage(player, Captions.BACKUP_LOAD_FAILURE, Captions.GENERIC_INVALID_CHOICE.getTranslated()); + } else { + final com.plotsquared.core.backup.Backup backup = backups.get(number - 1); + if (backup == null || backup.getFile() == null || !Files.exists(backup.getFile())) { + sendMessage(player, Captions.BACKUP_LOAD_FAILURE, Captions.GENERIC_INVALID_CHOICE.getTranslated()); + } else { + CmdConfirm.addPending(player, "/plot backup load " + number, () -> + backupProfile.restoreBackup(backup).whenComplete((n, error) -> { + if (error != null) { + sendMessage(player, Captions.BACKUP_LOAD_FAILURE, error.getMessage()); + } else { + sendMessage(player, Captions.BACKUP_LOAD_SUCCESS); + } + })); + } + } + } + }); + } + } + } + +} diff --git a/Core/src/main/java/com/plotsquared/core/command/Clear.java b/Core/src/main/java/com/plotsquared/core/command/Clear.java index cc8ab64ab..2af1c4ed5 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Clear.java +++ b/Core/src/main/java/com/plotsquared/core/command/Clear.java @@ -26,6 +26,7 @@ package com.plotsquared.core.command; import com.plotsquared.core.PlotSquared; +import com.plotsquared.core.backup.BackupManager; import com.plotsquared.core.configuration.Captions; import com.plotsquared.core.configuration.Settings; import com.plotsquared.core.events.PlotFlagRemoveEvent; @@ -82,38 +83,40 @@ public class Clear extends Command { checkTrue(force || !Settings.Done.RESTRICT_BUILDING || !DoneFlag.isDone(plot) || Permissions .hasPermission(player, Captions.PERMISSION_CONTINUE), Captions.DONE_ALREADY_DONE); confirm.run(this, () -> { - final long start = System.currentTimeMillis(); - boolean result = plot.clear(true, false, () -> { - plot.unlink(); - GlobalBlockQueue.IMP.addEmptyTask(() -> { - plot.removeRunning(); - // If the state changes, then mark it as no longer done - if (DoneFlag.isDone(plot)) { - PlotFlag plotFlag = plot.getFlagContainer().getFlag(DoneFlag.class); - PlotFlagRemoveEvent event = - PlotSquared.get().getEventDispatcher().callFlagRemove(plotFlag, plot); - if (event.getEventResult() != Result.DENY) { - plot.removeFlag(event.getFlag()); + BackupManager.backup(player, plot, () -> { + final long start = System.currentTimeMillis(); + boolean result = plot.clear(true, false, () -> { + plot.unlink(); + GlobalBlockQueue.IMP.addEmptyTask(() -> { + plot.removeRunning(); + // If the state changes, then mark it as no longer done + if (DoneFlag.isDone(plot)) { + PlotFlag plotFlag = plot.getFlagContainer().getFlag(DoneFlag.class); + PlotFlagRemoveEvent event = + PlotSquared.get().getEventDispatcher().callFlagRemove(plotFlag, plot); + if (event.getEventResult() != Result.DENY) { + plot.removeFlag(event.getFlag()); + } } - } - if (!plot.getFlag(AnalysisFlag.class).isEmpty()) { - PlotFlag plotFlag = - plot.getFlagContainer().getFlag(AnalysisFlag.class); - PlotFlagRemoveEvent event = - PlotSquared.get().getEventDispatcher().callFlagRemove(plotFlag, plot); - if (event.getEventResult() != Result.DENY) { - plot.removeFlag(event.getFlag()); + if (!plot.getFlag(AnalysisFlag.class).isEmpty()) { + PlotFlag plotFlag = + plot.getFlagContainer().getFlag(AnalysisFlag.class); + PlotFlagRemoveEvent event = + PlotSquared.get().getEventDispatcher().callFlagRemove(plotFlag, plot); + if (event.getEventResult() != Result.DENY) { + plot.removeFlag(event.getFlag()); + } } - } - MainUtil.sendMessage(player, Captions.CLEARING_DONE, - "" + (System.currentTimeMillis() - start)); + MainUtil.sendMessage(player, Captions.CLEARING_DONE, + "" + (System.currentTimeMillis() - start)); + }); }); + if (!result) { + MainUtil.sendMessage(player, Captions.WAIT_FOR_TIMER); + } else { + plot.addRunning(); + } }); - if (!result) { - MainUtil.sendMessage(player, Captions.WAIT_FOR_TIMER); - } else { - plot.addRunning(); - } }, null); return CompletableFuture.completedFuture(true); } diff --git a/Core/src/main/java/com/plotsquared/core/command/MainCommand.java b/Core/src/main/java/com/plotsquared/core/command/MainCommand.java index fb1dc68ed..03be061ca 100644 --- a/Core/src/main/java/com/plotsquared/core/command/MainCommand.java +++ b/Core/src/main/java/com/plotsquared/core/command/MainCommand.java @@ -127,6 +127,7 @@ public class MainCommand extends Command { new SetHome(); new Cluster(); new DebugImportWorlds(); + new Backup(); if (Settings.Ratings.USE_LIKES) { new Like(); diff --git a/Core/src/main/java/com/plotsquared/core/command/Save.java b/Core/src/main/java/com/plotsquared/core/command/Save.java index 207ce9cbc..6196f08b7 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Save.java +++ b/Core/src/main/java/com/plotsquared/core/command/Save.java @@ -43,7 +43,6 @@ import java.util.List; import java.util.UUID; @CommandDeclaration(command = "save", - aliases = {"backup"}, description = "Save your plot", category = CommandCategory.SCHEMATIC, requiredType = RequiredType.NONE, diff --git a/Core/src/main/java/com/plotsquared/core/command/Set.java b/Core/src/main/java/com/plotsquared/core/command/Set.java index fe3884e75..c6c2b67f3 100644 --- a/Core/src/main/java/com/plotsquared/core/command/Set.java +++ b/Core/src/main/java/com/plotsquared/core/command/Set.java @@ -25,6 +25,7 @@ */ package com.plotsquared.core.command; +import com.plotsquared.core.backup.BackupManager; import com.plotsquared.core.configuration.CaptionUtility; import com.plotsquared.core.configuration.Captions; import com.plotsquared.core.configuration.Settings; @@ -130,12 +131,15 @@ public class Set extends SubCommand { MainUtil.sendMessage(player, Captions.WAIT_FOR_TIMER); return false; } - plot.addRunning(); - for (Plot current : plot.getConnectedPlots()) { - current.setComponent(component, pattern); - } - MainUtil.sendMessage(player, Captions.GENERATING_COMPONENT); - GlobalBlockQueue.IMP.addEmptyTask(plot::removeRunning); + + BackupManager.backup(player, plot, () -> { + plot.addRunning(); + for (Plot current : plot.getConnectedPlots()) { + current.setComponent(component, pattern); + } + MainUtil.sendMessage(player, Captions.GENERATING_COMPONENT); + GlobalBlockQueue.IMP.addEmptyTask(plot::removeRunning); + }); return true; } } diff --git a/Core/src/main/java/com/plotsquared/core/configuration/Captions.java b/Core/src/main/java/com/plotsquared/core/configuration/Captions.java index a37839a30..1e0e50080 100644 --- a/Core/src/main/java/com/plotsquared/core/configuration/Captions.java +++ b/Core/src/main/java/com/plotsquared/core/configuration/Captions.java @@ -184,6 +184,11 @@ public enum Captions implements Caption { PERMISSION_ALIAS_SET("plots.alias.set", "static.permissions"), PERMISSION_ALIAS_REMOVE("plots.alias.remove", "static.permissions"), PERMISSION_ADMIN_CHAT_BYPASS("plots.admin.chat.bypass", "static.permissions"), + PERMISSION_BACKUP("plots.backup", "static.permissions"), + PERMISSION_BACKUP_SAVE("plots.backup.save", "static.permissions"), + PERMISSION_BACKUP_LIST("plots.backup.list", "static.permissions"), + PERMISSION_BACKUP_LOAD("plots.backup.load", "static.permissions"), + PERMISSION_ADMIN_BACKUP_OTHER("plots.admin.backup.other", "static.permissions"), PERMISSION_ADMIN_ALLOW_UNSAFE("plots.admin.unsafe", "static.permissions"), // // @@ -746,6 +751,29 @@ public enum Captions implements Caption { false, "Info"), // + // + BACKUP_USAGE("$1Usage: $2/plot backup save/list/load", "Backups"), + BACKUP_IMPOSSIBLE("$2Backups are not enabled for this plot: %s", "Backups"), + BACKUP_SAVE_SUCCESS("$1The backup was created successfully", "Backups"), + BACKUP_SAVE_FAILED("$2The backup could not be created: %s", "Backups"), + BACKUP_LOAD_SUCCESS("$1The backup was restored successfully", "Backups"), + BACKUP_LOAD_FAILURE("$2The backup could not be restored: %s", "Backups"), + BACKUP_LOAD_USAGE("$1Usage: $2/plot backup load [#]", "Backups"), + BACKUP_LIST_HEADER("$1Available backups for plot $2%s", "Backups"), + BACKUP_LIST_ENTRY("$3- $1#%s0 $2%s1", "Backups"), + BACKUP_LIST_FAILED("$2Backup listing failed: %s", "Backups"), + BACKUP_AUTOMATIC_STARTED("$1Backing up the plot...", "Backups"), + BACKUP_AUTOMATIC_FINISHED("$1The automatic backup process finished successfully!", "Backups"), + BACKUP_AUTOMATIC_FAILURE("$2The automatic backup process failed. Your pending action has been canceled. Reason: %s", "Backups"), + // + + // + GENERIC_OTHER("other", "Generic"), + GENERIC_MERGED("merged", "Generic"), + GENERIC_UNOWNED("unowned", "Generic"), + GENERIC_INVALID_CHOICE("invalid choice", "Generic"), + // + /** * Legacy Configuration Conversion */ diff --git a/Core/src/main/java/com/plotsquared/core/configuration/Settings.java b/Core/src/main/java/com/plotsquared/core/configuration/Settings.java index ca0fd9985..aeaf72e7e 100644 --- a/Core/src/main/java/com/plotsquared/core/configuration/Settings.java +++ b/Core/src/main/java/com/plotsquared/core/configuration/Settings.java @@ -502,4 +502,15 @@ public class Settings extends Config { @Comment("Try to guess plot owners from sign data. This may decrease server performance") public static boolean GUESS_PLOT_OWNER = false; } + + @Comment("Backup related settings") + public static final class Backup { + @Comment("Automatically backup plots when destructive commands are performed") + public static boolean AUTOMATIC_BACKUPS = true; + @Comment("Maximum amount of backups associated with a plot") + public static int BACKUP_LIMIT = 3; + @Comment("Whether or not backups should be deleted when the plot is unclaimed") + public static boolean DELETE_ON_UNCLAIM = true; + } + } diff --git a/Core/src/main/java/com/plotsquared/core/plot/Plot.java b/Core/src/main/java/com/plotsquared/core/plot/Plot.java index 975c9becf..e77861b94 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/Plot.java +++ b/Core/src/main/java/com/plotsquared/core/plot/Plot.java @@ -93,6 +93,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -1358,6 +1359,12 @@ public class Plot { for (PlotPlayer pp : players) { PlotListener.plotExit(pp, current); } + + if (Settings.Backup.DELETE_ON_UNCLAIM) { + // Destroy all backups when the plot is unclaimed + Objects.requireNonNull(PlotSquared.imp()).getBackupManager().getProfile(current).destroy(); + } + getArea().removePlot(getId()); DBFunc.delete(current); current.setOwnerAbs(null); diff --git a/Core/src/main/java/com/plotsquared/core/plot/PlotId.java b/Core/src/main/java/com/plotsquared/core/plot/PlotId.java index 3421de833..41016da1f 100644 --- a/Core/src/main/java/com/plotsquared/core/plot/PlotId.java +++ b/Core/src/main/java/com/plotsquared/core/plot/PlotId.java @@ -198,7 +198,9 @@ public class PlotId { return this.x + "," + this.y; } - + public String toDashSeparatedString() { + return this.x + "-" + this.y; + } /** * The PlotId object caches the hashcode for faster mapping/fetching/sorting