Made backup manager more generic
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package fr.pandacube.lib.paper.backup;
|
||||
|
||||
import fr.pandacube.lib.core.backup.BackupCleaner;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BackupConfig {
|
||||
public boolean worldBackupEnabled = true;
|
||||
public boolean workdirBackupEnabled = true;
|
||||
public boolean logsBackupEnabled = true;
|
||||
public String scheduling = "0 2 * * *"; // cron format, here is everyday at 2am
|
||||
public File backupDirectory = null;
|
||||
public BackupCleaner worldBackupCleaner = BackupCleaner.KEEPING_1_EVERY_N_MONTH(3).merge(BackupCleaner.KEEPING_N_LAST(5));
|
||||
public BackupCleaner workdirBackupCleaner = BackupCleaner.KEEPING_1_EVERY_N_MONTH(3).merge(BackupCleaner.KEEPING_N_LAST(5));
|
||||
public List<String> workdirIgnoreList = new ArrayList<>();
|
||||
}
|
@@ -0,0 +1,156 @@
|
||||
package fr.pandacube.lib.paper.backup;
|
||||
|
||||
import fr.pandacube.lib.core.backup.BackupManager;
|
||||
import fr.pandacube.lib.core.backup.BackupProcess;
|
||||
import fr.pandacube.lib.core.backup.RotatedLogsBackupProcess;
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
import fr.pandacube.lib.paper.scheduler.SchedulerUtil;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.bukkit.event.world.WorldSaveEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class PaperBackupManager extends BackupManager implements Listener {
|
||||
|
||||
private final Map<String, PaperWorldProcess> compressWorlds = new HashMap<>();
|
||||
|
||||
BackupConfig config;
|
||||
|
||||
public PaperBackupManager(BackupConfig config) {
|
||||
super(config.backupDirectory);
|
||||
setConfig(config);
|
||||
|
||||
|
||||
for (final World world : Bukkit.getWorlds()) {
|
||||
initWorldProcess(world.getName());
|
||||
}
|
||||
|
||||
initWorkdirProcess();
|
||||
|
||||
addProcess(new RotatedLogsBackupProcess(this, true, new File("logs"), "[0-9]{4}-[0-9]{2}-[0-9]{2}(-[0-9]+)?\\.log\\.gz"));
|
||||
|
||||
Bukkit.getServer().getPluginManager().registerEvents(this, PandaLibPaper.getPlugin());
|
||||
|
||||
}
|
||||
|
||||
public void setConfig(BackupConfig config) {
|
||||
this.config = config;
|
||||
for (BackupProcess process : backupQueue) {
|
||||
if (process instanceof PaperWorkdirProcess) {
|
||||
process.setEnabled(config.workdirBackupEnabled);
|
||||
process.setBackupCleaner(config.workdirBackupCleaner);
|
||||
process.setScheduling(config.scheduling);
|
||||
}
|
||||
else if (process instanceof PaperWorldProcess) {
|
||||
process.setEnabled(config.worldBackupEnabled);
|
||||
process.setBackupCleaner(config.worldBackupCleaner);
|
||||
process.setScheduling(config.scheduling);
|
||||
}
|
||||
else if (process instanceof RotatedLogsBackupProcess) {
|
||||
process.setEnabled(config.logsBackupEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
SchedulerUtil.runOnServerThreadAndWait(super::run);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onDisable() {
|
||||
|
||||
// save dirty status of worlds
|
||||
for (String wName : dirtyForSave) {
|
||||
World w = Bukkit.getWorld(wName);
|
||||
if (w != null)
|
||||
compressWorlds.get(w.getName()).setDirtyAfterSave();
|
||||
}
|
||||
|
||||
super.onDisable();
|
||||
}
|
||||
|
||||
private void initWorldProcess(final String worldName) {
|
||||
if (compressWorlds.containsKey(worldName))
|
||||
return;
|
||||
PaperWorldProcess process = new PaperWorldProcess(this, worldName);
|
||||
process.setEnabled(config.worldBackupEnabled);
|
||||
process.setBackupCleaner(config.worldBackupCleaner);
|
||||
process.setScheduling(config.scheduling);
|
||||
addProcess(process);
|
||||
compressWorlds.put(worldName, process);
|
||||
}
|
||||
|
||||
private void initWorkdirProcess() {
|
||||
PaperWorkdirProcess process = new PaperWorkdirProcess(this);
|
||||
process.setEnabled(config.workdirBackupEnabled);
|
||||
process.setBackupCleaner(config.workdirBackupCleaner);
|
||||
process.setScheduling(config.scheduling);
|
||||
addProcess(process);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private final Set<String> dirtyForSave = new HashSet<>();
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onWorldLoad(WorldLoadEvent event) {
|
||||
initWorldProcess(event.getWorld().getName());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onWorldSave(WorldSaveEvent event) {
|
||||
if (event.getWorld().getLoadedChunks().length > 0
|
||||
|| dirtyForSave.contains(event.getWorld().getName())) {
|
||||
compressWorlds.get(event.getWorld().getName()).setDirtyAfterSave();
|
||||
dirtyForSave.remove(event.getWorld().getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerChangeWorldEvent(PlayerChangedWorldEvent event) {
|
||||
dirtyForSave.add(event.getFrom().getName());
|
||||
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
dirtyForSave.add(event.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
package fr.pandacube.lib.paper.backup;
|
||||
|
||||
import fr.pandacube.lib.chat.Chat;
|
||||
import fr.pandacube.lib.core.backup.BackupProcess;
|
||||
import fr.pandacube.lib.paper.PandaLibPaper;
|
||||
import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager;
|
||||
import fr.pandacube.lib.paper.util.AutoUpdatedBossBar;
|
||||
import net.kyori.adventure.bossbar.BossBar;
|
||||
import net.kyori.adventure.bossbar.BossBar.Color;
|
||||
import net.kyori.adventure.bossbar.BossBar.Overlay;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
public abstract class PaperBackupProcess extends BackupProcess {
|
||||
|
||||
|
||||
private BossBar bossBar;
|
||||
|
||||
protected PaperBackupProcess(PaperBackupManager bm, String id) {
|
||||
super(bm, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaperBackupManager getBackupManager() {
|
||||
return (PaperBackupManager) super.getBackupManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBackupStart() {
|
||||
bossBar = BossBar.bossBar(Chat.text("Archivage"), 0, Color.YELLOW, Overlay.NOTCHED_20);
|
||||
AutoUpdatedBossBar auBossBar = new AutoUpdatedBossBar(bossBar, (bar) -> {
|
||||
bar.setTitle(Chat.infoText("Archivage ")
|
||||
.thenData(getDisplayName())
|
||||
.thenText(" : ")
|
||||
.then(compressor == null
|
||||
? Chat.text("Démarrage...")
|
||||
: compressor.getState()
|
||||
)
|
||||
);
|
||||
bar.setProgress(compressor == null ? 0 : compressor.getProgress());
|
||||
});
|
||||
auBossBar.scheduleUpdateTimeSyncThreadAsync(100, 100);
|
||||
PerformanceAnalysisManager.getInstance().addBossBar(bossBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBackupEnd(boolean success) {
|
||||
Bukkit.getScheduler().runTaskLater(PandaLibPaper.getPlugin(), () -> {
|
||||
PerformanceAnalysisManager.getInstance().removeBossBar(bossBar);
|
||||
bossBar = null;
|
||||
}, 40);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
package fr.pandacube.lib.paper.backup;
|
||||
|
||||
import fr.pandacube.lib.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
public class PaperWorkdirProcess extends PaperBackupProcess {
|
||||
|
||||
protected PaperWorkdirProcess(PaperBackupManager bm) {
|
||||
super(bm, "workdir");
|
||||
}
|
||||
|
||||
|
||||
public BiPredicate<File, String> getFilenameFilter() {
|
||||
return new BiPredicate<File, String>() {
|
||||
|
||||
@Override
|
||||
public boolean test(File file, String path) {
|
||||
if (globalExcluded(file, path))
|
||||
return false;
|
||||
for (String exclude : getBackupManager().config.workdirIgnoreList) {
|
||||
if (exclude.startsWith("/")) { // relative to source of workdir
|
||||
if (path.matches(exclude.substring(1)))
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
String name = path.substring(path.lastIndexOf("/") + 1);
|
||||
if (name.matches(exclude))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean globalExcluded(File file, String path) {
|
||||
if (file.isDirectory() && new File(file, "level.dat").exists())
|
||||
return true;
|
||||
if (new File(getSourceDir(), "logs").equals(file))
|
||||
return true;
|
||||
if (file.isFile() && file.getName().endsWith(".lck"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public File getSourceDir() {
|
||||
return new File(".");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBackupEnd(boolean success) {
|
||||
if (success)
|
||||
setDirtySinceNow();
|
||||
super.onBackupEnd(success);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File getTargetDir() {
|
||||
return new File(getBackupManager().getBackupDirectory(), "workdir");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDisplayName() {
|
||||
return "workdir";
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void displayNextSchedule() {
|
||||
Log.info("[Backup] " + net.md_5.bungee.api.ChatColor.GRAY + getDisplayName() + net.md_5.bungee.api.ChatColor.RESET + " next backup on "
|
||||
+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date(getNext())));
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
package fr.pandacube.lib.paper.backup;
|
||||
|
||||
import fr.pandacube.lib.paper.scheduler.SchedulerUtil;
|
||||
import fr.pandacube.lib.paper.util.WorldUtil;
|
||||
import fr.pandacube.lib.util.Log;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
public class PaperWorldProcess extends PaperBackupProcess {
|
||||
private final String worldName;
|
||||
|
||||
private boolean autoSave = true;
|
||||
|
||||
protected PaperWorldProcess(PaperBackupManager bm, final String n) {
|
||||
super(bm, "worlds/" + n);
|
||||
worldName = n;
|
||||
}
|
||||
|
||||
private World getWorld() {
|
||||
return Bukkit.getWorld(worldName);
|
||||
}
|
||||
|
||||
|
||||
public BiPredicate<File, String> getFilenameFilter() {
|
||||
return (f, s) -> true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public File getSourceDir() {
|
||||
return WorldUtil.worldDir(worldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBackupStart() {
|
||||
World w = getWorld();
|
||||
if (w == null)
|
||||
return;
|
||||
autoSave = w.isAutoSave();
|
||||
w.setAutoSave(false);
|
||||
super.onBackupStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBackupEnd(boolean success) {
|
||||
if (success)
|
||||
setNotDirty();
|
||||
SchedulerUtil.runOnServerThread(() -> {
|
||||
World w = getWorld();
|
||||
if (w == null)
|
||||
return;
|
||||
w.setAutoSave(autoSave);
|
||||
});
|
||||
super.onBackupEnd(success);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File getTargetDir() {
|
||||
return new File(getBackupManager().getBackupDirectory(), "worlds/" + worldName);
|
||||
}
|
||||
|
||||
|
||||
public void displayNextSchedule() {
|
||||
if (hasNextScheduled()) {
|
||||
Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " is dirty. Next backup on "
|
||||
+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date(getNext())));
|
||||
}
|
||||
else {
|
||||
Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " is clean. Next backup not scheduled.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Make the specified world dirty for compress. Also makes the specified world clean for saving if nobody is connected there.
|
||||
*/
|
||||
public void setDirtyAfterSave() {
|
||||
if (!isDirty()) { // don't set dirty if it is already
|
||||
setDirtySinceNow();
|
||||
Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " was saved and is now dirty. Next backup on "
|
||||
+ DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG)
|
||||
.format(new Date(getNext()))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user