From 8b714372641a92ae4843abddfe78d8999152b52d Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Mon, 11 May 2020 11:55:31 -0400 Subject: [PATCH] Improve backup functionality (#3258) Waits for an ongoing backup task to complete in onDisable (and yells at users for `/reload`ing), and adds a `backup.always-run` option to enable always running backups even when no users have logged in since the last backup. Fixes #3257 and closes #2646. --- .../src/com/earth2me/essentials/Backup.java | 23 ++++++++++++++++--- .../com/earth2me/essentials/Essentials.java | 12 +++++++++- .../com/earth2me/essentials/ISettings.java | 2 ++ .../src/com/earth2me/essentials/Settings.java | 5 ++++ Essentials/src/config.yml | 2 ++ Essentials/src/messages.properties | 2 ++ 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/Essentials/src/com/earth2me/essentials/Backup.java b/Essentials/src/com/earth2me/essentials/Backup.java index 6f4020acc..ada804d0e 100644 --- a/Essentials/src/com/earth2me/essentials/Backup.java +++ b/Essentials/src/com/earth2me/essentials/Backup.java @@ -7,6 +7,8 @@ import org.bukkit.command.CommandSender; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -20,11 +22,13 @@ public class Backup implements Runnable { private transient boolean running = false; private transient int taskId = -1; private transient boolean active = false; + private final AtomicBoolean pendingShutdown = new AtomicBoolean(false); + private transient CompletableFuture taskLock = null; public Backup(final IEssentials ess) { this.ess = ess; server = ess.getServer(); - if (!ess.getOnlinePlayers().isEmpty()) { + if (!ess.getOnlinePlayers().isEmpty() || ess.getSettings().isAlwaysRunBackup()) { ess.runTaskAsynchronously(this::startTask); } } @@ -52,12 +56,21 @@ public class Backup implements Runnable { } } + public CompletableFuture getTaskLock() { + return taskLock; + } + + public void setPendingShutdown(boolean shutdown) { + pendingShutdown.set(shutdown); + } + @Override public void run() { if (active) { return; } active = true; + taskLock = new CompletableFuture<>(); final String command = ess.getSettings().getBackupCommand(); if (command == null || "".equals(command)) { return; @@ -66,6 +79,7 @@ public class Backup implements Runnable { final CommandSender cs = server.getConsoleSender(); server.dispatchCommand(cs, "save-all"); active = false; + taskLock.complete(new Object()); return; } LOGGER.log(Level.INFO, tl("backupStarted")); @@ -102,14 +116,17 @@ public class Backup implements Runnable { @Override public void run() { server.dispatchCommand(cs, "save-on"); - if (ess.getOnlinePlayers().isEmpty()) { + if (!ess.getSettings().isAlwaysRunBackup() && ess.getOnlinePlayers().isEmpty()) { stopTask(); } active = false; + taskLock.complete(new Object()); LOGGER.log(Level.INFO, tl("backupFinished")); } } - ess.scheduleSyncDelayedTask(new BackupEnableSaveTask()); + if (!pendingShutdown.get()) { + ess.scheduleSyncDelayedTask(new BackupEnableSaveTask()); + } } }); } diff --git a/Essentials/src/com/earth2me/essentials/Essentials.java b/Essentials/src/com/earth2me/essentials/Essentials.java index d2428ee3d..bb8b96c7c 100644 --- a/Essentials/src/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/com/earth2me/essentials/Essentials.java @@ -314,6 +314,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { handleCrash(ex); throw ex; } + getBackup().setPendingShutdown(false); } @Override @@ -363,12 +364,17 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { @Override public void onDisable() { + boolean stopping = ServerState.isStopping(); + if (!stopping) { + LOGGER.log(Level.SEVERE, tl("serverReloading")); + } + getBackup().setPendingShutdown(true); for (User user : getOnlineUsers()) { if (user.isVanished()) { user.setVanished(false); user.sendMessage(tl("unvanishedReload")); } - if (ServerState.isStopping()) { + if (stopping) { user.setLastLocation(); if (!user.isHidden()) { user.setLastLogout(System.currentTimeMillis()); @@ -379,6 +385,10 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { } } cleanupOpenInventories(); + if (getBackup().getTaskLock() != null && !getBackup().getTaskLock().isDone()) { + LOGGER.log(Level.SEVERE, tl("backupInProgress")); + getBackup().getTaskLock().join(); + } if (i18n != null) { i18n.onDisable(); } diff --git a/Essentials/src/com/earth2me/essentials/ISettings.java b/Essentials/src/com/earth2me/essentials/ISettings.java index a5393f0f2..7a91de7e8 100644 --- a/Essentials/src/com/earth2me/essentials/ISettings.java +++ b/Essentials/src/com/earth2me/essentials/ISettings.java @@ -32,6 +32,8 @@ public interface ISettings extends IConf { long getBackupInterval(); + boolean isAlwaysRunBackup(); + String getChatFormat(String group); int getChatRadius(); diff --git a/Essentials/src/com/earth2me/essentials/Settings.java b/Essentials/src/com/earth2me/essentials/Settings.java index 487b1bc9e..5f1551685 100644 --- a/Essentials/src/com/earth2me/essentials/Settings.java +++ b/Essentials/src/com/earth2me/essentials/Settings.java @@ -425,6 +425,11 @@ public class Settings implements net.ess3.api.ISettings { return config.getString("backup.command", null); } + @Override + public boolean isAlwaysRunBackup() { + return config.getBoolean("backup.always-run", false); + } + private final Map chatFormats = Collections.synchronizedMap(new HashMap<>()); @Override diff --git a/Essentials/src/config.yml b/Essentials/src/config.yml index 31a699d90..e07130415 100644 --- a/Essentials/src/config.yml +++ b/Essentials/src/config.yml @@ -371,6 +371,8 @@ unprotected-sign-names: backup: # Interval in minutes. interval: 30 + # If true, the backup task will run even if there are no players online. + always-run: false # Unless you add a valid backup command or script here, this feature will be useless. # Use 'save-all' to simply force regular world saving without backup. #command: 'rdiff-backup World1 backups/World1' diff --git a/Essentials/src/messages.properties b/Essentials/src/messages.properties index e1ac8b799..94238a8a6 100644 --- a/Essentials/src/messages.properties +++ b/Essentials/src/messages.properties @@ -27,6 +27,7 @@ backOther=\u00a76Returned\u00a7c {0}\u00a76 to previous location. backupDisabled=\u00a74An external backup script has not been configured. backupFinished=\u00a76Backup finished. backupStarted=\u00a76Backup started. +backupInProgress=\u00a76An external backup script is currently in progress! Halting plugin disable until finished. backUsageMsg=\u00a76Returning to previous location. balance=\u00a7aBalance\:\u00a7c {0} balanceOther=\u00a7aBalance of {0}\u00a7a\:\u00a7c {1} @@ -484,6 +485,7 @@ seenOnline=\u00a76Player\u00a7c {0} \u00a76has been \u00a7aonline\u00a76 since \ sellBulkPermission=\u00a76You do not have permission to bulk sell. sellHandPermission=\u00a76You do not have permission to hand sell. serverFull=Server is full\! +serverReloading=There's a good chance you're reloading your server right now. If that's the case, why do you hate yourself? Expect no support from the EssentialsX team when using /reload. serverTotal=\u00a76Server Total\:\u00a7c {0} serverUnsupported=You are running an unsupported server version! setBal=\u00a7aYour balance was set to {0}.