diff --git a/pandalib-core/pom.xml b/pandalib-core/pom.xml index 52daa81..54a5a31 100644 --- a/pandalib-core/pom.xml +++ b/pandalib-core/pom.xml @@ -36,5 +36,12 @@ pandalib-chat ${project.version} + + + + ch.eitchnet + cron + 1.6.2 + diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/BackupCleaner.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/BackupCleaner.java similarity index 96% rename from pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/BackupCleaner.java rename to pandalib-core/src/main/java/fr/pandacube/lib/core/backup/BackupCleaner.java index b02b3f7..3504a77 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/BackupCleaner.java +++ b/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/BackupCleaner.java @@ -1,8 +1,8 @@ -package fr.pandacube.lib.paper.modules.backup; +package fr.pandacube.lib.core.backup; import fr.pandacube.lib.chat.Chat; import fr.pandacube.lib.util.Log; -import org.bukkit.ChatColor; +import net.md_5.bungee.api.ChatColor; import java.io.File; import java.time.LocalDateTime; @@ -89,7 +89,7 @@ public abstract class BackupCleaner implements UnaryOperator backupQueue = new ArrayList<>(); + + /* package */ final AtomicReference runningBackup = new AtomicReference<>(); + + private final Timer schedulerTimer = new Timer(); + + public BackupManager(File backupDirectory) { + this.backupDirectory = backupDirectory; + persist = new Persist(this); + + + long nextMinute = ZonedDateTime.now().plusMinutes(1).withSecond(0).withNano(0) + .toInstant().toEpochMilli(); + schedulerTimer.scheduleAtFixedRate(this, new Date(nextMinute), 60_000); + } + + + protected void addProcess(BackupProcess process) { + process.displayNextSchedule(); + backupQueue.add(process); + } + + + public File getBackupDirectory() { + return backupDirectory; + } + + public synchronized void run() { + BackupProcess tmp; + if ((tmp = runningBackup.get()) != null) { + tmp.logProgress(); + } + else { + backupQueue.sort(null); + for (BackupProcess process : backupQueue) { + if (System.currentTimeMillis() >= process.getNext() && process.couldRunNow()) { + process.run(); + return; + } + } + } + } + + + + public synchronized void onDisable() { + + schedulerTimer.cancel(); + + if (runningBackup.get() != null) { + Log.warning("[Backup] Waiting after the end of a backup..."); + BackupProcess tmp; + while ((tmp = runningBackup.get()) != null) { + try { + tmp.logProgress(); + // wait 5 seconds between each progress log + // but check if the process has ended each .5 seconds + for (int i = 0; i < 10; i++) { + if (runningBackup.get() == null) + break; + Thread.sleep(500); + } + } catch (Throwable e) { // could occur because of synchronization errors/interruption/... + break; + } + } + } + + persist.save(); + } + + + + + + + + + + +} diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/BackupProcess.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/BackupProcess.java new file mode 100644 index 0000000..b1bb865 --- /dev/null +++ b/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/BackupProcess.java @@ -0,0 +1,270 @@ +package fr.pandacube.lib.core.backup; + +import fc.cron.CronExpression; +import fr.pandacube.lib.util.FileUtils; +import fr.pandacube.lib.util.Log; +import net.md_5.bungee.api.ChatColor; + +import java.io.File; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiPredicate; + +public abstract class BackupProcess implements Comparable, Runnable { + private final BackupManager backupManager; + + public final String identifier; + + + protected ZipCompressor compressor = null; + + + private boolean enabled = true; + private String scheduling = "0 2 * * *"; // cron format, here is everyday at 2am + private BackupCleaner backupCleaner = null; + private List ignoreList = new ArrayList<>(); + + + protected BackupProcess(BackupManager bm, final String n) { + backupManager = bm; + identifier = n; + } + + public BackupManager getBackupManager() { + return backupManager; + } + + public String getIdentifier() { + return identifier; + } + + protected String getDisplayName() { + return getIdentifier(); + } + + + + @Override + public int compareTo(final BackupProcess process) { + return Long.compare(getNext(), process.getNext()); + } + + + + + + public abstract BiPredicate getFilenameFilter(); + + public abstract File getSourceDir(); + + protected abstract File getTargetDir(); + + protected abstract void onBackupStart(); + + protected abstract void onBackupEnd(boolean success); + + + + + + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getScheduling() { + return scheduling; + } + + public void setScheduling(String scheduling) { + this.scheduling = scheduling; + } + + public BackupCleaner getBackupCleaner() { + return backupCleaner; + } + + public void setBackupCleaner(BackupCleaner backupCleaner) { + this.backupCleaner = backupCleaner; + } + + + + + + + + + + @Override + public void run() { + getBackupManager().runningBackup.set(this); + + try { + BiPredicate filter = getFilenameFilter(); + File sourceDir = getSourceDir(); + + if (!sourceDir.exists()) { + Log.warning("[Backup] Unable to compress " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + ": source directory " + sourceDir + " doesn’t exist"); + return; + } + + File targetDir = getTargetDir(); + File target = new File(targetDir, getDateFileName() + ".zip"); + + onBackupStart(); + + new Thread(() -> { + Log.info("[Backup] Starting for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " ..."); + + compressor = new ZipCompressor(sourceDir, target, 9, filter); + + boolean success = false; + try { + compressor.compress(); + + success = true; + + Log.info("[Backup] Finished for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET); + + try { + BackupCleaner cleaner = getBackupCleaner(); + if (cleaner != null) + cleaner.cleanupArchives(targetDir, getDisplayName()); + } catch (Exception e) { + Log.severe(e); + } + } + catch (final Exception e) { + Log.severe("[Backup] Failed: " + sourceDir + " -> " + target, e); + + FileUtils.delete(target); + if (target.exists()) + Log.warning("unable to delete: " + target); + } finally { + + getBackupManager().runningBackup.set(null); + + onBackupEnd(success); + + displayNextSchedule(); + + } + }, "Backup Thread " + identifier).start(); + } catch (Throwable t) { + getBackupManager().runningBackup.set(null); + throw t; + } + + } + + + + + + + + + + + public abstract void displayNextSchedule(); + + + public static final DateTimeFormatter dateFileNameFormatter = new DateTimeFormatterBuilder() + .appendValue(ChronoField.YEAR, 4) + .appendValue(ChronoField.MONTH_OF_YEAR, 2) + .appendValue(ChronoField.DAY_OF_MONTH, 2) + .appendLiteral('-') + .appendValue(ChronoField.HOUR_OF_DAY, 2) + .appendValue(ChronoField.MINUTE_OF_HOUR, 2) + .appendValue(ChronoField.SECOND_OF_MINUTE, 2) + .toFormatter(); + + + private String getDateFileName() { + return dateFileNameFormatter.format(ZonedDateTime.now()); + } + + + public void logProgress() { + if (compressor == null) + return; + Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + ": " + compressor.getState().getLegacyText()); + } + + + + + + + public boolean couldRunNow() { + if (!isEnabled()) + return false; + if (!isDirty()) + return false; + if (getNext() > System.currentTimeMillis()) + return false; + return true; + } + + + + + public long getNext() { + if (!hasNextScheduled()) + return Long.MAX_VALUE; + return getNextCompress(backupManager.persist.isDirtySince(identifier)); + } + + public boolean hasNextScheduled() { + return isEnabled() && isDirty(); + } + + public boolean isDirty() { + return backupManager.persist.isDirty(identifier); + } + + public void setDirtySinceNow() { + backupManager.persist.setDirtySinceNow(identifier); + } + + public void setNotDirty() { + backupManager.persist.setNotDirty(identifier); + } + + + + + + /** + * get the timestamp (in ms) of when the next compress will run, depending on since when the files to compress are dirty. + * @param dirtySince the timestamp in ms since the files are dirty + * @return the timestamp in ms when the next compress of the files should be run, or 0 if it is not yet scheduled + */ + public long getNextCompress(long dirtySince) { + if (dirtySince == -1) + return 0; + + CronExpression parsedScheduling; + try { + parsedScheduling = new CronExpression(getScheduling(), false); + } catch (IllegalArgumentException e) { + Log.severe("Invalid backup scheduling configuration '" + getScheduling() + "'.", e); + return 0; + } + + return parsedScheduling.nextTimeAfter(ZonedDateTime.ofInstant(Instant.ofEpochMilli(dirtySince), ZoneId.systemDefault())) + .toInstant() + .toEpochMilli(); + } +} diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/Persist.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/Persist.java new file mode 100644 index 0000000..1fed96d --- /dev/null +++ b/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/Persist.java @@ -0,0 +1,89 @@ +package fr.pandacube.lib.core.backup; + +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; +import fr.pandacube.lib.core.json.Json; +import fr.pandacube.lib.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class Persist { + protected final BackupManager backupManager; + + private Map dirtySince = new HashMap<>(); + + private final File file; + + // private final Set dirtyWorldsSave = new HashSet<>(); + + public Persist(BackupManager bm) { + backupManager = bm; + file = new File(bm.getBackupDirectory(), "source-dirty-since.yml"); + load(); + } + + public void reload() { + load(); + } + + protected void load() { + boolean loaded = false; + try { + dirtySince = Json.gson.fromJson(new FileReader(file), new TypeToken>(){}.getType()); + loaded = true; + } + catch (final FileNotFoundException ignored) { } + catch (final JsonParseException e) { + Log.severe("cannot load " + file, e); + } + + if (!loaded) { + save(); + } + } + + public void save() { + try { + Json.gsonPrettyPrinting.toJson(dirtySince, new FileWriter(file, false)); + } + catch (final FileNotFoundException ignored) { } + catch (final JsonParseException | IOException e) { + Log.severe("could not save " + file, e); + } + } + + + + + + public void setDirtySinceNow(String id) { + dirtySince.put(id, System.currentTimeMillis()); + save(); + } + + public void setNotDirty(String id) { + dirtySince.put(id, -1L); + save(); + } + + + public boolean isDirty(String id) { + return isDirtySince(id) != -1; + } + + public long isDirtySince(String id) { + if (!dirtySince.containsKey(id)) + setDirtySinceNow(id); + return dirtySince.get(id); + } + + + + +} \ No newline at end of file diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/RotatedLogsBackupProcess.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/RotatedLogsBackupProcess.java new file mode 100644 index 0000000..c4d4ac0 --- /dev/null +++ b/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/RotatedLogsBackupProcess.java @@ -0,0 +1,125 @@ +package fr.pandacube.lib.core.backup; + +import com.google.common.io.Files; +import fr.pandacube.lib.util.Log; +import net.md_5.bungee.api.ChatColor; + +import java.io.File; +import java.io.IOException; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.function.BiPredicate; + +public class RotatedLogsBackupProcess extends BackupProcess { + final String logFileRegexPattern; + final File sourceLogDirectory; + final boolean inNewThread; + + public RotatedLogsBackupProcess(BackupManager bm, boolean inNewThread, File sourceLogDir, String logFileRegexPattern) { + super(bm, "logs"); + this.logFileRegexPattern = logFileRegexPattern; + sourceLogDirectory = sourceLogDir; + this.inNewThread = inNewThread; + super.setScheduling("0 1 * * *"); // do this every day at 1 am, by default + } + + @Override + public void run() { + // do not call super. We override the zip archive process, we just want to copy log files, here + if (inNewThread) { + new Thread(this::actuallyRun, "Backup Thread " + identifier).start(); + } + else { + actuallyRun(); + } + + } + + + private void actuallyRun() { + + Log.info("[Backup] Starting for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " ..."); + + try { + // wait a little after the log message above, in case the log file rotation has to be performed. + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + onBackupStart(); + + boolean success = false; + + File targetDir = getTargetDir(); + + try { + List filesToMove = getFilesToMove(); + + for (File source : filesToMove) { + try { + Files.move(source, new File(targetDir, source.getName())); + } catch (IOException e) { + Log.severe("Unable to move file " + source + " into " + targetDir); + } + } + + success = true; + + Log.info("[Backup] Finished for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET); + } catch (final Exception e) { + Log.severe("[Backup] Failed for : " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET, e); + } finally { + onBackupEnd(success); + + displayNextSchedule(); + } + } + + + + public List getFilesToMove() { + List ret = new ArrayList<>(); + for (File f : getSourceDir().listFiles()) { + if (f.getName().matches(logFileRegexPattern)) + ret.add(f); + } + return ret; + } + + + + + + @Override + public BiPredicate getFilenameFilter() { + return null; + } + + @Override + public File getSourceDir() { + return sourceLogDirectory; + } + + @Override + protected File getTargetDir() { + return new File(getBackupManager().getBackupDirectory(), "logs"); + } + + @Override + protected void onBackupStart() { + } + + @Override + protected void onBackupEnd(boolean success) { + setDirtySinceNow(); + } + + @Override + public void displayNextSchedule() { + Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " next backup on " + + DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date(getNext()))); + } +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/ZipCompressor.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/ZipCompressor.java similarity index 99% rename from pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/ZipCompressor.java rename to pandalib-core/src/main/java/fr/pandacube/lib/core/backup/ZipCompressor.java index 405d079..0c10437 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/ZipCompressor.java +++ b/pandalib-core/src/main/java/fr/pandacube/lib/core/backup/ZipCompressor.java @@ -1,4 +1,4 @@ -package fr.pandacube.lib.paper.modules.backup; +package fr.pandacube.lib.core.backup; import java.io.BufferedOutputStream; import java.io.File; diff --git a/pandalib-paper/pom.xml b/pandalib-paper/pom.xml index c18faff..e2f59a9 100644 --- a/pandalib-paper/pom.xml +++ b/pandalib-paper/pom.xml @@ -59,6 +59,12 @@ ${project.version} + + fr.pandacube.lib + pandalib-core + ${project.version} + + io.papermc.paper @@ -70,13 +76,6 @@ paper-mojangapi ${paper.version}-SNAPSHOT - - - - ch.eitchnet - cron - 1.6.2 - \ No newline at end of file diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/BackupConfig.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/BackupConfig.java similarity index 74% rename from pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/BackupConfig.java rename to pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/BackupConfig.java index 5e61cd0..c24bc46 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/BackupConfig.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/BackupConfig.java @@ -1,4 +1,6 @@ -package fr.pandacube.lib.paper.modules.backup; +package fr.pandacube.lib.paper.backup; + +import fr.pandacube.lib.core.backup.BackupCleaner; import java.io.File; import java.util.ArrayList; @@ -7,7 +9,8 @@ import java.util.List; public class BackupConfig { public boolean worldBackupEnabled = true; public boolean workdirBackupEnabled = true; - public String scheduling = "0 2 * * 1"; // cron format, here is everyday at 2am + 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)); diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupManager.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupManager.java new file mode 100644 index 0000000..a2c6c97 --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupManager.java @@ -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 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 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()); + } + + +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupProcess.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupProcess.java new file mode 100644 index 0000000..df8a466 --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperBackupProcess.java @@ -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); + } + + +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorkdirProcess.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorkdirProcess.java new file mode 100644 index 0000000..4454cb8 --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorkdirProcess.java @@ -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 getFilenameFilter() { + return new BiPredicate() { + + @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()))); + } +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorldProcess.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorldProcess.java new file mode 100644 index 0000000..e4d3a2d --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/backup/PaperWorldProcess.java @@ -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 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())) + ); + } + } +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/BackupManager.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/BackupManager.java deleted file mode 100644 index cead704..0000000 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/BackupManager.java +++ /dev/null @@ -1,196 +0,0 @@ -package fr.pandacube.lib.paper.modules.backup; - -import fc.cron.CronExpression; -import fr.pandacube.lib.paper.PandaLibPaper; -import fr.pandacube.lib.util.Log; -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.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; - -public class BackupManager implements Runnable, Listener { - - - - Persist persist; - - private final List compressQueue = new ArrayList<>(); - - private final Set compressWorlds = new HashSet<>(); - - /* package */ AtomicReference compressRunning = new AtomicReference<>(); - - private final Set dirtyForSave = new HashSet<>(); - - BackupConfig config; - - public BackupManager(BackupConfig config) { - setConfig(config); - persist = new Persist(this); - - - for (final World world : Bukkit.getWorlds()) { - initCompressProcess(Type.WORLDS, world.getName()); - } - - initCompressProcess(Type.WORKDIR, null); - - Bukkit.getServer().getScheduler().runTaskTimer(PandaLibPaper.getPlugin(), this, (60 - Calendar.getInstance().get(Calendar.SECOND)) * 20L, 60 * 20L); - - Bukkit.getServer().getPluginManager().registerEvents(this, PandaLibPaper.getPlugin()); - - } - - public void setConfig(BackupConfig config) { - this.config = config; - } - - - public void onDisable() { - - if (compressRunning.get() != null) { - Log.warning("[Backup] Waiting after the end of a backup..."); - CompressProcess tmp; - while ((tmp = compressRunning.get()) != null) { - try { - tmp.logProgress(); - // wait 5 seconds between each progress log - // but check if the process has ended each .5 seconds - for (int i = 0; i < 10; i++) { - if (compressRunning.get() == null) - break; - Thread.sleep(500); - } - } catch (Throwable e) { // could occur because of synchronization errors/interruption/... - break; - } - } - } - - // save dirty status of worlds - for (String wName : dirtyForSave) { - World w = Bukkit.getWorld(wName); - if (w != null) - persist.updateDirtyStatusAfterSave(w); - } - - persist.save(); - } - - private void initCompressProcess(final Type type, final String worldName) { - if (!type.backupEnabled(config)) - return; - if (type == Type.WORLDS) { - if (compressWorlds.contains(worldName)) - return; - compressWorlds.add(worldName); - } - CompressProcess process = type == Type.WORLDS ? new CompressWorldProcess(this, worldName) : new CompressWorkdirProcess(this); - process.displayDirtynessStatus(); - compressQueue.add(process); - } - - @Override - public void run() { - CompressProcess tmp; - if ((tmp = compressRunning.get()) != null) { - tmp.logProgress(); - } - else { - compressQueue.sort(null); - for (CompressProcess process : compressQueue) { - if (System.currentTimeMillis() >= process.getNext() && process.couldRunNow()) { - process.run(); - return; - } - } - } - } - - - - - - /** - * get the timestamp (in ms) of when the next compress will run, depending on since when the files to compress are dirty. - * @param dirtySince the timestamp in ms since the files are dirty - * @return the timestamp in ms when the next compress of the files should be run, or 0 if it is not yet scheduled - */ - /* package */ long getNextCompress(long dirtySince) { - if (dirtySince == -1) - return 0; - - CronExpression parsedScheduling; - try { - parsedScheduling = new CronExpression(config.scheduling, false); - } catch (IllegalArgumentException e) { - Log.severe("Invalid backup scheduling configuration '" + config.scheduling + "'.", e); - return 0; - } - - return parsedScheduling.nextTimeAfter(ZonedDateTime.ofInstant(Instant.ofEpochMilli(dirtySince), ZoneId.systemDefault())) - .toInstant() - .toEpochMilli(); - } - - - - - - - - - - @EventHandler(priority = EventPriority.MONITOR) - public void onWorldLoad(WorldLoadEvent event) { - initCompressProcess(Type.WORLDS, event.getWorld().getName()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onWorldSave(WorldSaveEvent event) { - if (event.getWorld().getLoadedChunks().length > 0 - || dirtyForSave.contains(event.getWorld().getName())) { - persist.updateDirtyStatusAfterSave(event.getWorld()); - 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()); - } - - - -} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/CompressProcess.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/CompressProcess.java deleted file mode 100644 index 741a082..0000000 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/CompressProcess.java +++ /dev/null @@ -1,207 +0,0 @@ -package fr.pandacube.lib.paper.modules.backup; - -import fr.pandacube.lib.chat.Chat; -import fr.pandacube.lib.paper.PandaLibPaper; -import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager; -import fr.pandacube.lib.paper.util.AutoUpdatedBossBar; -import fr.pandacube.lib.util.FileUtils; -import fr.pandacube.lib.util.Log; -import net.kyori.adventure.bossbar.BossBar; -import net.kyori.adventure.bossbar.BossBar.Color; -import net.kyori.adventure.bossbar.BossBar.Overlay; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; - -import java.io.File; -import java.text.DateFormat; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.temporal.ChronoField; -import java.util.Date; -import java.util.function.BiPredicate; - -public abstract class CompressProcess implements Comparable, Runnable { - protected final BackupManager backupManager; - public final Type type; - public final String name; - - - - private ZipCompressor compressor = null; - - protected CompressProcess(BackupManager bm, final Type t, final String n) { - backupManager = bm; - type = t; - name = n; - } - - @Override - public int compareTo(final CompressProcess process) { - return Long.compare(getNext(), process.getNext()); - } - - - public abstract BiPredicate getFilenameFilter(); - - public abstract File getSourceDir(); - - protected abstract void onCompressStart(); - - protected abstract void onCompressEnd(boolean success); - - protected abstract File getTargetDir(); - - protected abstract String getDisplayName(); - - @Override - public void run() { - backupManager.compressRunning.set(this); - - try { - BiPredicate filter = getFilenameFilter(); - File sourceDir = getSourceDir(); - - if (!sourceDir.exists()) { - Log.warning("[Backup] Unable to compress " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + ": source directory " + sourceDir + " doesn’t exist"); - return; - } - - File targetDir = getTargetDir(); - File target = new File(targetDir, getDateFileName() + ".zip"); - - - BossBar 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); - - onCompressStart(); - - Bukkit.getScheduler().runTaskAsynchronously(PandaLibPaper.getPlugin(), () -> { - Log.info("[Backup] Starting for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " ..."); - - compressor = new ZipCompressor(sourceDir, target, 9, filter); - - PerformanceAnalysisManager.getInstance().addBossBar(bossBar); - - boolean success = false; - try { - compressor.compress(); - - success = true; - - Log.info("[Backup] Finished for " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET); - - backupManager.persist.updateDirtyStatusAfterCompress(type, name); - - displayDirtynessStatus(); - - try { - type.backupCleaner(backupManager.config).cleanupArchives(targetDir, getDisplayName()); - } catch (Exception e) { - Log.severe(e); - } - } - catch (final Exception e) { - Log.severe("[Backup] Failed: " + sourceDir + " -> " + target, e); - - FileUtils.delete(target); - if (target.exists()) - Log.warning("unable to delete: " + target); - } finally { - - backupManager.compressRunning.set(null); - boolean successF = success; - Bukkit.getScheduler().runTask(PandaLibPaper.getPlugin(), () -> onCompressEnd(successF)); - - try { - Thread.sleep(2000); - } catch(InterruptedException e) { - Thread.currentThread().interrupt(); - } - - PerformanceAnalysisManager.getInstance().removeBossBar(bossBar); - } - }); - } catch (Throwable t) { - backupManager.compressRunning.set(null); - throw t; - } - - } - - - - public void displayDirtynessStatus() { - if (hasNextScheduled() && type == Type.WORLDS) { - Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " is dirty. Next backup on " - + DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG).format(new Date(getNext()))); - } - else if (hasNextScheduled()) { - Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + " 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."); - } - } - - - static DateTimeFormatter dateFileNameFormatter = new DateTimeFormatterBuilder() - .appendValue(ChronoField.YEAR, 4) - .appendValue(ChronoField.MONTH_OF_YEAR, 2) - .appendValue(ChronoField.DAY_OF_MONTH, 2) - .appendLiteral('-') - .appendValue(ChronoField.HOUR_OF_DAY, 2) - .appendValue(ChronoField.MINUTE_OF_HOUR, 2) - .appendValue(ChronoField.SECOND_OF_MINUTE, 2) - .toFormatter(); - - - private String getDateFileName() { - return dateFileNameFormatter.format(ZonedDateTime.now()); - } - - - public void logProgress() { - if (compressor == null) - return; - Log.info("[Backup] " + ChatColor.GRAY + getDisplayName() + ChatColor.RESET + ": " + compressor.getState().getLegacyText()); - } - - - - public boolean couldRunNow() { - if (!type.backupEnabled(backupManager.config)) - return false; - if (!backupManager.persist.isDirty(type, name)) - return false; - if (getNext() > System.currentTimeMillis()) - return false; - return true; - } - - - - - public long getNext() { - if (!hasNextScheduled()) - return Long.MAX_VALUE; - return backupManager.getNextCompress(backupManager.persist.isDirtySince(type, name)); - } - - public boolean hasNextScheduled() { - return backupManager.persist.isDirty(type, name); - } - -} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/CompressWorkdirProcess.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/CompressWorkdirProcess.java deleted file mode 100644 index fa36ce5..0000000 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/CompressWorkdirProcess.java +++ /dev/null @@ -1,74 +0,0 @@ -package fr.pandacube.lib.paper.modules.backup; - -import java.io.File; -import java.util.function.BiPredicate; - -public class CompressWorkdirProcess extends CompressProcess { - - protected CompressWorkdirProcess(BackupManager bm) { - super(bm, Type.WORKDIR, Type.WORKDIR.toString()); - } - - - public BiPredicate getFilenameFilter() { - return new SourceFileFilter(); - } - - - - private class SourceFileFilter implements BiPredicate { - - - @Override - public boolean test(File file, String path) { - if (globalExcluded(file, path)) - return false; - for (String exclude : backupManager.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 onCompressStart() { } - - @Override - protected void onCompressEnd(boolean success) { } - - @Override - protected File getTargetDir() { - return new File(backupManager.config.backupDirectory, type.toString()); - } - - @Override - protected String getDisplayName() { - return type.toString(); - } -} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/CompressWorldProcess.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/CompressWorldProcess.java deleted file mode 100644 index ade800a..0000000 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/CompressWorldProcess.java +++ /dev/null @@ -1,59 +0,0 @@ -package fr.pandacube.lib.paper.modules.backup; - -import fr.pandacube.lib.paper.util.WorldUtil; -import org.bukkit.Bukkit; -import org.bukkit.World; - -import java.io.File; -import java.util.function.BiPredicate; - -public class CompressWorldProcess extends CompressProcess { - - private boolean autoSave = true; - - protected CompressWorldProcess(BackupManager bm, final String n) { - super(bm, Type.WORLDS, n); - } - - private World getWorld() { - return Bukkit.getWorld(name); - } - - - public BiPredicate getFilenameFilter() { - return (f, s) -> true; - } - - - @Override - public File getSourceDir() { - return WorldUtil.worldDir(name); - } - - @Override - protected void onCompressStart() { - World w = getWorld(); - if (w == null) - return; - autoSave = w.isAutoSave(); - w.setAutoSave(false); - } - - @Override - protected void onCompressEnd(boolean success) { - World w = getWorld(); - if (w == null) - return; - w.setAutoSave(autoSave); - } - - @Override - protected File getTargetDir() { - return new File(backupManager.config.backupDirectory, type + "/" + name); - } - - @Override - protected String getDisplayName() { - return type + "/" + name; - } -} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/Persist.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/Persist.java deleted file mode 100644 index 24a156e..0000000 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/Persist.java +++ /dev/null @@ -1,134 +0,0 @@ -package fr.pandacube.lib.paper.modules.backup; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.text.DateFormat; -import java.util.Date; - -import org.bukkit.ChatColor; -import org.bukkit.World; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; -import org.yaml.snakeyaml.error.YAMLException; - -import fr.pandacube.lib.paper.PandaLibPaper; -import fr.pandacube.lib.util.Log; - -public class Persist extends YamlConfiguration { - protected final BackupManager backupManager; - - private final File file; - - // private final Set dirtyWorldsSave = new HashSet<>(); - - public Persist(BackupManager bm) { - file = new File(PandaLibPaper.getPlugin().getDataFolder(), "backup_persist.yml"); - backupManager = bm; - load(); - } - - public void reload() { - load(); - } - - protected void load() { - boolean loaded = false; - try { - load(file); - loaded = true; - } - catch (final FileNotFoundException ignored) { } - catch (final IOException e) { - Log.severe("cannot load " + file, e); - } - catch (final InvalidConfigurationException e) { - if (e.getCause() instanceof YAMLException) Log.severe("Config file " + file + " isn't valid!", e); - else if (e.getCause() == null || e.getCause() instanceof ClassCastException) Log.severe("Config file " + file + " isn't valid!"); - else Log.severe("cannot load " + file, e); - } - - if (!loaded) { - options().copyDefaults(true); - save(); - } - } - - public void save() { - try { - save(file); - } - catch (final IOException e) { - Log.severe("could not save " + file, e); - } - } - - - - - - /** - * Make the specified world dirty for compress. Also makes the specified world clean for saving if nobody is connected there. - */ - public void updateDirtyStatusAfterSave(final World world) { - if (world == null) - return; - if (!isDirty(Type.WORLDS, world.getName())) { // don't set dirty if it is already - setDirtySinceNow(Type.WORLDS, world.getName()); - Log.info("[Backup] " + ChatColor.GRAY + Type.WORLDS + "/" + world.getName() + ChatColor.RESET + " was saved and is now dirty. Next backup on " - + DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG) - .format(new Date(backupManager.getNextCompress(System.currentTimeMillis()))) - ); - } - } - - /** - * Update the dirty status after the specified compress process is done. - * @param t the type of process - * @param n the name of the process (for instance, the name of the world) - */ - public void updateDirtyStatusAfterCompress(Type t, String n) { - if (t == Type.WORLDS) - setNotDirty(t, n); - else - setDirtySinceNow(t, n); - } - - - private void setDirtySinceNow(Type t, String n) { - set(t + "." + n + ".dirty_since", System.currentTimeMillis()); - save(); - } - - private void setNotDirty(Type t, String n) { - if (t == Type.WORKDIR) - return; // WORKDIR are always considered dirty - set(t + "." + n + ".dirty_since", -1); - save(); - } - - - public boolean isDirty(Type t, String n) { - if (t == Type.WORKDIR) - return true; - return isDirtySince(t, n) != -1; - } - - public long isDirtySince(Type t, String n) { - if (!contains(t + "." + n + ".dirty_since")) - setDirtySinceNow(t, n); - return getLong(t + "." + n + ".dirty_since"); - } - - /* - * - * type: // (worlds|others) - * name: // (root|plugin|) - * dirty_since: (true|false) - * - */ - - - - -} \ No newline at end of file diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/Type.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/Type.java deleted file mode 100644 index f08da41..0000000 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/modules/backup/Type.java +++ /dev/null @@ -1,26 +0,0 @@ -package fr.pandacube.lib.paper.modules.backup; - -public enum Type { - WORLDS, - WORKDIR; - - @Override - public String toString() { - return name().toLowerCase(); - } - - public boolean backupEnabled(BackupConfig cfg) { - return switch (this) { - case WORLDS -> cfg.worldBackupEnabled; - case WORKDIR -> cfg.workdirBackupEnabled; - }; - } - - public BackupCleaner backupCleaner(BackupConfig cfg) { - return switch (this) { - case WORLDS -> cfg.worldBackupCleaner; - case WORKDIR -> cfg.workdirBackupCleaner; - }; - } - -}