mirror of
				https://github.com/IntellectualSites/PlotSquared.git
				synced 2025-11-02 18:33:42 +01:00 
			
		
		
		
	Merge pull request #2816 from IntellectualSites/backups
Plot backup system
This commit is contained in:
		@@ -52,6 +52,9 @@ import com.plotsquared.bukkit.util.uuid.OfflineUUIDWrapper;
 | 
				
			|||||||
import com.plotsquared.bukkit.util.uuid.SQLUUIDHandler;
 | 
					import com.plotsquared.bukkit.util.uuid.SQLUUIDHandler;
 | 
				
			||||||
import com.plotsquared.core.IPlotMain;
 | 
					import com.plotsquared.core.IPlotMain;
 | 
				
			||||||
import com.plotsquared.core.PlotSquared;
 | 
					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.Captions;
 | 
				
			||||||
import com.plotsquared.core.configuration.ChatFormatter;
 | 
					import com.plotsquared.core.configuration.ChatFormatter;
 | 
				
			||||||
import com.plotsquared.core.configuration.ConfigurationNode;
 | 
					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 {
 | 
					public final class BukkitMain extends JavaPlugin implements Listener, IPlotMain {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final int BSTATS_ID = 1404;
 | 
				
			||||||
    @Getter private static WorldEdit worldEdit;
 | 
					    @Getter private static WorldEdit worldEdit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static {
 | 
					    static {
 | 
				
			||||||
@@ -151,7 +155,7 @@ public final class BukkitMain extends JavaPlugin implements Listener, IPlotMain
 | 
				
			|||||||
    private Method methodUnloadChunk0;
 | 
					    private Method methodUnloadChunk0;
 | 
				
			||||||
    private boolean methodUnloadSetup = false;
 | 
					    private boolean methodUnloadSetup = false;
 | 
				
			||||||
    private boolean metricsStarted;
 | 
					    private boolean metricsStarted;
 | 
				
			||||||
    private static final int BSTATS_ID = 1404;
 | 
					    @Getter private BackupManager backupManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override public int[] getServerVersion() {
 | 
					    @Override public int[] getServerVersion() {
 | 
				
			||||||
        if (this.version == null) {
 | 
					        if (this.version == null) {
 | 
				
			||||||
@@ -229,6 +233,15 @@ public final class BukkitMain extends JavaPlugin implements Listener, IPlotMain
 | 
				
			|||||||
                e.printStackTrace();
 | 
					                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() {
 | 
					    private void unload() {
 | 
				
			||||||
@@ -886,4 +899,5 @@ public final class BukkitMain extends JavaPlugin implements Listener, IPlotMain
 | 
				
			|||||||
            ((WorldEditPlugin) Bukkit.getPluginManager().getPlugin("WorldEdit"));
 | 
					            ((WorldEditPlugin) Bukkit.getPluginManager().getPlugin("WorldEdit"));
 | 
				
			||||||
        return wePlugin.wrapCommandSender(console);
 | 
					        return wePlugin.wrapCommandSender(console);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,7 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
package com.plotsquared.core;
 | 
					package com.plotsquared.core;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.plotsquared.core.backup.BackupManager;
 | 
				
			||||||
import com.plotsquared.core.generator.GeneratorWrapper;
 | 
					import com.plotsquared.core.generator.GeneratorWrapper;
 | 
				
			||||||
import com.plotsquared.core.generator.HybridUtils;
 | 
					import com.plotsquared.core.generator.HybridUtils;
 | 
				
			||||||
import com.plotsquared.core.generator.IndependentPlotGenerator;
 | 
					import com.plotsquared.core.generator.IndependentPlotGenerator;
 | 
				
			||||||
@@ -262,4 +263,12 @@ public interface IPlotMain extends ILogger {
 | 
				
			|||||||
    List<Map.Entry<Map.Entry<String, String>, Boolean>> getPluginIds();
 | 
					    List<Map.Entry<Map.Entry<String, String>, Boolean>> getPluginIds();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Actor getConsole();
 | 
					    Actor getConsole();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the backup manager instance
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return Backup manager
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    BackupManager getBackupManager();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										60
									
								
								Core/src/main/java/com/plotsquared/core/backup/Backup.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								Core/src/main/java/com/plotsquared/core/backup/Backup.java
									
									
									
									
									
										Normal file
									
								
							@@ -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 <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					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();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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 <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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 <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					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<List<Backup>> 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<Backup> createBackup();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Restore a backup
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param backup Backup to restore
 | 
				
			||||||
 | 
					     * @return Future that completes when the backup has finished
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @NotNull CompletableFuture<Void> restoreBackup(@NotNull final Backup backup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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 <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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 <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					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<List<Backup>> listBackups() {
 | 
				
			||||||
 | 
					        return CompletableFuture.completedFuture(Collections.emptyList());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override public void destroy(){
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override @NotNull public Path getBackupDirectory() {
 | 
				
			||||||
 | 
					        return new File(".").toPath();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override @NotNull public CompletableFuture<Backup> createBackup() {
 | 
				
			||||||
 | 
					        throw new UnsupportedOperationException("Cannot create backup of an unowned plot");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override @NotNull public CompletableFuture<Void> restoreBackup(@NotNull final Backup backup) {
 | 
				
			||||||
 | 
					        return CompletableFuture.completedFuture(null);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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 <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					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<Backup> 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<List<Backup>> 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<Backup> 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<Backup> createBackup() {
 | 
				
			||||||
 | 
					        final CompletableFuture<Backup> future = new CompletableFuture<>();
 | 
				
			||||||
 | 
					        this.listBackups().thenAcceptAsync(backups -> {
 | 
				
			||||||
 | 
					            synchronized (this.backupLock) {
 | 
				
			||||||
 | 
					                if (backups.size() == backupManager.getBackupLimit()) {
 | 
				
			||||||
 | 
					                    backups.get(backups.size() - 1).delete();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                final List<Plot> 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<Void> restoreBackup(@NotNull final Backup backup) {
 | 
				
			||||||
 | 
					        final CompletableFuture<Void> 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<Boolean>() {
 | 
				
			||||||
 | 
					                        @Override public void run(Boolean value) {
 | 
				
			||||||
 | 
					                            if (value) {
 | 
				
			||||||
 | 
					                                future.complete(null);
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                                future.completeExceptionally(new RuntimeException(Captions.SCHEMATIC_PASTE_FAILED.toString()));
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return future;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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 <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					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<PlotCacheKey, BackupProfile> 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());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										251
									
								
								Core/src/main/java/com/plotsquared/core/command/Backup.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								Core/src/main/java/com/plotsquared/core/command/Backup.java
									
									
									
									
									
										Normal file
									
								
							@@ -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 <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					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 <save|list|load>",
 | 
				
			||||||
 | 
					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<Boolean> execute(PlotPlayer player, String[] args,
 | 
				
			||||||
 | 
					        RunnableVal3<Command, Runnable, Runnable> confirm,
 | 
				
			||||||
 | 
					        RunnableVal2<Command, CommandResult> 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<Command> 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<List<com.plotsquared.core.backup.Backup>> backupList = backupProfile.listBackups();
 | 
				
			||||||
 | 
					                    if (backupList.isDone()) {
 | 
				
			||||||
 | 
					                        final List<com.plotsquared.core.backup.Backup> 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<Command, Runnable, Runnable> confirm,
 | 
				
			||||||
 | 
					        final RunnableVal2<Command, CommandResult> 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<Command, Runnable, Runnable> confirm,
 | 
				
			||||||
 | 
					        final RunnableVal2<Command, CommandResult> 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<Command, Runnable, Runnable> confirm,
 | 
				
			||||||
 | 
					        final RunnableVal2<Command, CommandResult> 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);
 | 
				
			||||||
 | 
					                                   }
 | 
				
			||||||
 | 
					                                }));
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -26,6 +26,7 @@
 | 
				
			|||||||
package com.plotsquared.core.command;
 | 
					package com.plotsquared.core.command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.plotsquared.core.PlotSquared;
 | 
					import com.plotsquared.core.PlotSquared;
 | 
				
			||||||
 | 
					import com.plotsquared.core.backup.BackupManager;
 | 
				
			||||||
import com.plotsquared.core.configuration.Captions;
 | 
					import com.plotsquared.core.configuration.Captions;
 | 
				
			||||||
import com.plotsquared.core.configuration.Settings;
 | 
					import com.plotsquared.core.configuration.Settings;
 | 
				
			||||||
import com.plotsquared.core.events.PlotFlagRemoveEvent;
 | 
					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
 | 
					        checkTrue(force || !Settings.Done.RESTRICT_BUILDING || !DoneFlag.isDone(plot) || Permissions
 | 
				
			||||||
            .hasPermission(player, Captions.PERMISSION_CONTINUE), Captions.DONE_ALREADY_DONE);
 | 
					            .hasPermission(player, Captions.PERMISSION_CONTINUE), Captions.DONE_ALREADY_DONE);
 | 
				
			||||||
        confirm.run(this, () -> {
 | 
					        confirm.run(this, () -> {
 | 
				
			||||||
            final long start = System.currentTimeMillis();
 | 
					            BackupManager.backup(player, plot, () -> {
 | 
				
			||||||
            boolean result = plot.clear(true, false, () -> {
 | 
					                final long start = System.currentTimeMillis();
 | 
				
			||||||
                plot.unlink();
 | 
					                boolean result = plot.clear(true, false, () -> {
 | 
				
			||||||
                GlobalBlockQueue.IMP.addEmptyTask(() -> {
 | 
					                    plot.unlink();
 | 
				
			||||||
                    plot.removeRunning();
 | 
					                    GlobalBlockQueue.IMP.addEmptyTask(() -> {
 | 
				
			||||||
                    // If the state changes, then mark it as no longer done
 | 
					                        plot.removeRunning();
 | 
				
			||||||
                    if (DoneFlag.isDone(plot)) {
 | 
					                        // If the state changes, then mark it as no longer done
 | 
				
			||||||
                        PlotFlag<?, ?> plotFlag = plot.getFlagContainer().getFlag(DoneFlag.class);
 | 
					                        if (DoneFlag.isDone(plot)) {
 | 
				
			||||||
                        PlotFlagRemoveEvent event =
 | 
					                            PlotFlag<?, ?> plotFlag = plot.getFlagContainer().getFlag(DoneFlag.class);
 | 
				
			||||||
                            PlotSquared.get().getEventDispatcher().callFlagRemove(plotFlag, plot);
 | 
					                            PlotFlagRemoveEvent event =
 | 
				
			||||||
                        if (event.getEventResult() != Result.DENY) {
 | 
					                                PlotSquared.get().getEventDispatcher().callFlagRemove(plotFlag, plot);
 | 
				
			||||||
                            plot.removeFlag(event.getFlag());
 | 
					                            if (event.getEventResult() != Result.DENY) {
 | 
				
			||||||
 | 
					                                plot.removeFlag(event.getFlag());
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                        if (!plot.getFlag(AnalysisFlag.class).isEmpty()) {
 | 
				
			||||||
                    if (!plot.getFlag(AnalysisFlag.class).isEmpty()) {
 | 
					                            PlotFlag<?, ?> plotFlag =
 | 
				
			||||||
                        PlotFlag<?, ?> plotFlag =
 | 
					                                plot.getFlagContainer().getFlag(AnalysisFlag.class);
 | 
				
			||||||
                            plot.getFlagContainer().getFlag(AnalysisFlag.class);
 | 
					                            PlotFlagRemoveEvent event =
 | 
				
			||||||
                        PlotFlagRemoveEvent event =
 | 
					                                PlotSquared.get().getEventDispatcher().callFlagRemove(plotFlag, plot);
 | 
				
			||||||
                            PlotSquared.get().getEventDispatcher().callFlagRemove(plotFlag, plot);
 | 
					                            if (event.getEventResult() != Result.DENY) {
 | 
				
			||||||
                        if (event.getEventResult() != Result.DENY) {
 | 
					                                plot.removeFlag(event.getFlag());
 | 
				
			||||||
                            plot.removeFlag(event.getFlag());
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                        MainUtil.sendMessage(player, Captions.CLEARING_DONE,
 | 
				
			||||||
                    MainUtil.sendMessage(player, Captions.CLEARING_DONE,
 | 
					                            "" + (System.currentTimeMillis() - start));
 | 
				
			||||||
                        "" + (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);
 | 
					        }, null);
 | 
				
			||||||
        return CompletableFuture.completedFuture(true);
 | 
					        return CompletableFuture.completedFuture(true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -127,6 +127,7 @@ public class MainCommand extends Command {
 | 
				
			|||||||
            new SetHome();
 | 
					            new SetHome();
 | 
				
			||||||
            new Cluster();
 | 
					            new Cluster();
 | 
				
			||||||
            new DebugImportWorlds();
 | 
					            new DebugImportWorlds();
 | 
				
			||||||
 | 
					            new Backup();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (Settings.Ratings.USE_LIKES) {
 | 
					            if (Settings.Ratings.USE_LIKES) {
 | 
				
			||||||
                new Like();
 | 
					                new Like();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,6 @@ import java.util.List;
 | 
				
			|||||||
import java.util.UUID;
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@CommandDeclaration(command = "save",
 | 
					@CommandDeclaration(command = "save",
 | 
				
			||||||
    aliases = {"backup"},
 | 
					 | 
				
			||||||
    description = "Save your plot",
 | 
					    description = "Save your plot",
 | 
				
			||||||
    category = CommandCategory.SCHEMATIC,
 | 
					    category = CommandCategory.SCHEMATIC,
 | 
				
			||||||
    requiredType = RequiredType.NONE,
 | 
					    requiredType = RequiredType.NONE,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,7 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
package com.plotsquared.core.command;
 | 
					package com.plotsquared.core.command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.plotsquared.core.backup.BackupManager;
 | 
				
			||||||
import com.plotsquared.core.configuration.CaptionUtility;
 | 
					import com.plotsquared.core.configuration.CaptionUtility;
 | 
				
			||||||
import com.plotsquared.core.configuration.Captions;
 | 
					import com.plotsquared.core.configuration.Captions;
 | 
				
			||||||
import com.plotsquared.core.configuration.Settings;
 | 
					import com.plotsquared.core.configuration.Settings;
 | 
				
			||||||
@@ -130,12 +131,15 @@ public class Set extends SubCommand {
 | 
				
			|||||||
                            MainUtil.sendMessage(player, Captions.WAIT_FOR_TIMER);
 | 
					                            MainUtil.sendMessage(player, Captions.WAIT_FOR_TIMER);
 | 
				
			||||||
                            return false;
 | 
					                            return false;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        plot.addRunning();
 | 
					
 | 
				
			||||||
                        for (Plot current : plot.getConnectedPlots()) {
 | 
					                        BackupManager.backup(player, plot, () -> {
 | 
				
			||||||
                            current.setComponent(component, pattern);
 | 
					                            plot.addRunning();
 | 
				
			||||||
                        }
 | 
					                            for (Plot current : plot.getConnectedPlots()) {
 | 
				
			||||||
                        MainUtil.sendMessage(player, Captions.GENERATING_COMPONENT);
 | 
					                                current.setComponent(component, pattern);
 | 
				
			||||||
                        GlobalBlockQueue.IMP.addEmptyTask(plot::removeRunning);
 | 
					                            }
 | 
				
			||||||
 | 
					                            MainUtil.sendMessage(player, Captions.GENERATING_COMPONENT);
 | 
				
			||||||
 | 
					                            GlobalBlockQueue.IMP.addEmptyTask(plot::removeRunning);
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
                        return true;
 | 
					                        return true;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -184,6 +184,11 @@ public enum Captions implements Caption {
 | 
				
			|||||||
    PERMISSION_ALIAS_SET("plots.alias.set", "static.permissions"),
 | 
					    PERMISSION_ALIAS_SET("plots.alias.set", "static.permissions"),
 | 
				
			||||||
    PERMISSION_ALIAS_REMOVE("plots.alias.remove", "static.permissions"),
 | 
					    PERMISSION_ALIAS_REMOVE("plots.alias.remove", "static.permissions"),
 | 
				
			||||||
    PERMISSION_ADMIN_CHAT_BYPASS("plots.admin.chat.bypass", "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"),
 | 
					    PERMISSION_ADMIN_ALLOW_UNSAFE("plots.admin.unsafe", "static.permissions"),
 | 
				
			||||||
    //</editor-fold>
 | 
					    //</editor-fold>
 | 
				
			||||||
    //<editor-fold desc="Confirm">
 | 
					    //<editor-fold desc="Confirm">
 | 
				
			||||||
@@ -746,6 +751,29 @@ public enum Captions implements Caption {
 | 
				
			|||||||
        false, "Info"),
 | 
					        false, "Info"),
 | 
				
			||||||
    //</editor-fold>
 | 
					    //</editor-fold>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //<editor-fold desc="Backups">
 | 
				
			||||||
 | 
					    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"),
 | 
				
			||||||
 | 
					    //</editor-fold>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //<editor-fold desc="Generic">
 | 
				
			||||||
 | 
					    GENERIC_OTHER("other", "Generic"),
 | 
				
			||||||
 | 
					    GENERIC_MERGED("merged", "Generic"),
 | 
				
			||||||
 | 
					    GENERIC_UNOWNED("unowned", "Generic"),
 | 
				
			||||||
 | 
					    GENERIC_INVALID_CHOICE("invalid choice", "Generic"),
 | 
				
			||||||
 | 
					    //</editor-fold>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Legacy Configuration Conversion
 | 
					     * Legacy Configuration Conversion
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -502,4 +502,15 @@ public class Settings extends Config {
 | 
				
			|||||||
        @Comment("Try to guess plot owners from sign data. This may decrease server performance")
 | 
					        @Comment("Try to guess plot owners from sign data. This may decrease server performance")
 | 
				
			||||||
        public static boolean GUESS_PLOT_OWNER = false;
 | 
					        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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -93,6 +93,7 @@ import java.util.Iterator;
 | 
				
			|||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
import java.util.Map.Entry;
 | 
					import java.util.Map.Entry;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
import java.util.Set;
 | 
					import java.util.Set;
 | 
				
			||||||
import java.util.UUID;
 | 
					import java.util.UUID;
 | 
				
			||||||
import java.util.concurrent.CompletableFuture;
 | 
					import java.util.concurrent.CompletableFuture;
 | 
				
			||||||
@@ -1358,6 +1359,12 @@ public class Plot {
 | 
				
			|||||||
            for (PlotPlayer pp : players) {
 | 
					            for (PlotPlayer pp : players) {
 | 
				
			||||||
                PlotListener.plotExit(pp, current);
 | 
					                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());
 | 
					            getArea().removePlot(getId());
 | 
				
			||||||
            DBFunc.delete(current);
 | 
					            DBFunc.delete(current);
 | 
				
			||||||
            current.setOwnerAbs(null);
 | 
					            current.setOwnerAbs(null);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -198,7 +198,9 @@ public class PlotId {
 | 
				
			|||||||
        return this.x + "," + this.y;
 | 
					        return this.x + "," + this.y;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public String toDashSeparatedString() {
 | 
				
			||||||
 | 
					        return this.x + "-" + this.y;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The PlotId object caches the hashcode for faster mapping/fetching/sorting<br>
 | 
					     * The PlotId object caches the hashcode for faster mapping/fetching/sorting<br>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user