diff --git a/docs/commands.md b/docs/commands.md
index b9e127f85..3d978090d 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -1,5 +1,5 @@
-
+
## AuthMe Commands
You can use the following commands to use the features of AuthMe. Mandatory arguments are marked with `< >`
@@ -32,8 +32,10 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
Requires `authme.admin.firstspawn`
- **/authme setfirstspawn**: Change the first player's spawn to your current position.
Requires `authme.admin.setfirstspawn`
-- **/authme purge** <days> [all]: Purge old AuthMeReloaded data longer than the specified amount of days ago.
+- **/authme purge** <days> [all]: Purge old AuthMeReloaded data longer than the specified number of days ago.
Requires `authme.admin.purge`
+- **/authme backup**: Creates a backup of the registered users.
+
Requires `authme.admin.backup`
- **/authme resetpos** <player/*>: Purge the last know position of the specified player or all of them.
Requires `authme.admin.purgelastpos`
- **/authme purgebannedplayers**: Purge all AuthMeReloaded data for banned players.
@@ -85,4 +87,4 @@ brackets; optional arguments are enclosed in square brackets (`[ ]`).
---
-This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Fri Apr 14 18:52:29 CEST 2017
+This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Apr 23 19:29:43 CEST 2017
diff --git a/docs/config.md b/docs/config.md
index 9c8a52bd7..8da66c1a4 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -1,5 +1,5 @@
-
+
## AuthMe Configuration
The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder,
@@ -481,13 +481,13 @@ limbo:
# See above for a description of the values.
restoreWalkSpeed: 'MAX_RESTORE'
BackupSystem:
- # Enable or disable automatic backup
+ # General configuration for backups: if false, no backups are possible
ActivateBackup: false
- # Set backup at every start of server
+ # Create backup at every start of server
OnServerStart: false
- # Set backup at every stop of server
+ # Create backup at every stop of server
OnServerStop: true
- # Windows only mysql installation Path
+ # Windows only: MySQL installation path
MysqlWindowsPath: 'C:\Program Files\MySQL\MySQL Server 5.1\'
Converter:
Rakamak:
@@ -507,4 +507,4 @@ To change settings on a running server, save your changes to config.yml and use
---
-This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Tue Mar 28 21:48:52 CEST 2017
+This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Apr 23 19:30:08 CEST 2017
diff --git a/docs/permission_nodes.md b/docs/permission_nodes.md
index ef9df8d8f..4bf2c0118 100644
--- a/docs/permission_nodes.md
+++ b/docs/permission_nodes.md
@@ -1,5 +1,5 @@
-
+
## AuthMe Permission Nodes
The following are the permission nodes that are currently supported by the latest dev builds.
@@ -7,6 +7,7 @@ The following are the permission nodes that are currently supported by the lates
- **authme.admin.*** – Give access to all admin commands.
- **authme.admin.accounts** – Administrator command to see all accounts associated with a user.
- **authme.admin.antibotmessages** – Permission to see Antibot messages.
+- **authme.admin.backup** – Allows to use the backup command.
- **authme.admin.changemail** – Administrator command to set or change the email address of a user.
- **authme.admin.changepassword** – Administrator command to change the password of a user.
- **authme.admin.converter** – Administrator command to convert old or other data to AuthMe data.
@@ -59,4 +60,4 @@ The following are the permission nodes that are currently supported by the lates
---
-This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Fri Apr 14 18:52:32 CEST 2017
+This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sun Apr 23 19:32:06 CEST 2017
diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java
index 1c9dbdcfd..2a4612a34 100644
--- a/src/main/java/fr/xephi/authme/AuthMe.java
+++ b/src/main/java/fr/xephi/authme/AuthMe.java
@@ -73,6 +73,7 @@ public class AuthMe extends JavaPlugin {
private DataSource database;
private BukkitService bukkitService;
private Injector injector;
+ private BackupService backupService;
/**
* Constructor.
@@ -155,7 +156,7 @@ public class AuthMe extends JavaPlugin {
}
// Do a backup on start
- new BackupService(this, settings).doBackup(BackupService.BackupCause.START);
+ backupService.doBackup(BackupService.BackupCause.START);
// Set up Metrics
OnStartupTasks.sendMetrics(this, settings);
@@ -262,6 +263,7 @@ public class AuthMe extends JavaPlugin {
permsMan = injector.getSingleton(PermissionsManager.class);
bukkitService = injector.getSingleton(BukkitService.class);
commandHandler = injector.getSingleton(CommandHandler.class);
+ backupService = injector.getSingleton(BackupService.class);
// Trigger construction of API classes; they will keep track of the singleton
injector.getSingleton(fr.xephi.authme.api.v3.AuthMeApi.class);
@@ -357,8 +359,8 @@ public class AuthMe extends JavaPlugin {
}
// Do backup on stop if enabled
- if (settings != null) {
- new BackupService(this, settings).doBackup(BackupService.BackupCause.STOP);
+ if (backupService != null) {
+ backupService.doBackup(BackupService.BackupCause.STOP);
}
// Wait for tasks and close data source
diff --git a/src/main/java/fr/xephi/authme/command/CommandInitializer.java b/src/main/java/fr/xephi/authme/command/CommandInitializer.java
index 841cdf4d8..70dfe734e 100644
--- a/src/main/java/fr/xephi/authme/command/CommandInitializer.java
+++ b/src/main/java/fr/xephi/authme/command/CommandInitializer.java
@@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList;
import fr.xephi.authme.command.executable.HelpCommand;
import fr.xephi.authme.command.executable.authme.AccountsCommand;
import fr.xephi.authme.command.executable.authme.AuthMeCommand;
+import fr.xephi.authme.command.executable.authme.BackupCommand;
import fr.xephi.authme.command.executable.authme.ChangePasswordAdminCommand;
import fr.xephi.authme.command.executable.authme.ConverterCommand;
import fr.xephi.authme.command.executable.authme.FirstSpawnCommand;
@@ -226,13 +227,23 @@ public class CommandInitializer {
.parent(AUTHME_BASE)
.labels("purge", "delete")
.description("Purge old data")
- .detailedDescription("Purge old AuthMeReloaded data longer than the specified amount of days ago.")
+ .detailedDescription("Purge old AuthMeReloaded data longer than the specified number of days ago.")
.withArgument("days", "Number of days", false)
.withArgument("all", "Add 'all' at the end to also purge players with lastlogin = 0", true)
.permission(AdminPermission.PURGE)
.executableCommand(PurgeCommand.class)
.register();
+ // Backup command
+ CommandDescription.builder()
+ .parent(AUTHME_BASE)
+ .labels("backup")
+ .description("Perform a backup")
+ .detailedDescription("Creates a backup of the registered users.")
+ .permission(AdminPermission.BACKUP)
+ .executableCommand(BackupCommand.class)
+ .register();
+
// Register the purgelastposition command
CommandDescription.builder()
.parent(AUTHME_BASE)
diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/BackupCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/BackupCommand.java
new file mode 100644
index 000000000..0e72fb600
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/command/executable/authme/BackupCommand.java
@@ -0,0 +1,23 @@
+package fr.xephi.authme.command.executable.authme;
+
+import fr.xephi.authme.command.ExecutableCommand;
+import fr.xephi.authme.service.BackupService;
+import fr.xephi.authme.service.BackupService.BackupCause;
+import org.bukkit.command.CommandSender;
+
+import javax.inject.Inject;
+import java.util.List;
+
+/**
+ * Command to perform a backup.
+ */
+public class BackupCommand implements ExecutableCommand {
+
+ @Inject
+ private BackupService backupService;
+
+ @Override
+ public void executeCommand(CommandSender sender, List arguments) {
+ backupService.doBackup(BackupCause.COMMAND, sender);
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/permission/AdminPermission.java b/src/main/java/fr/xephi/authme/permission/AdminPermission.java
index 46d58072a..4f0957a0a 100644
--- a/src/main/java/fr/xephi/authme/permission/AdminPermission.java
+++ b/src/main/java/fr/xephi/authme/permission/AdminPermission.java
@@ -113,7 +113,12 @@ public enum AdminPermission implements PermissionNode {
/**
* Permission to see the other accounts of the players that log in.
*/
- SEE_OTHER_ACCOUNTS("authme.admin.seeotheraccounts");
+ SEE_OTHER_ACCOUNTS("authme.admin.seeotheraccounts"),
+
+ /**
+ * Allows to use the backup command.
+ */
+ BACKUP("authme.admin.backup");
/**
* The permission node.
diff --git a/src/main/java/fr/xephi/authme/service/BackupService.java b/src/main/java/fr/xephi/authme/service/BackupService.java
index 846e34415..0141e8f6e 100644
--- a/src/main/java/fr/xephi/authme/service/BackupService.java
+++ b/src/main/java/fr/xephi/authme/service/BackupService.java
@@ -1,12 +1,15 @@
package fr.xephi.authme.service;
-import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSourceType;
+import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.BackupSettings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
+import fr.xephi.authme.util.FileUtils;
+import org.bukkit.command.CommandSender;
+import javax.inject.Inject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -16,80 +19,80 @@ import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
+import static fr.xephi.authme.util.Utils.logAndSendMessage;
+import static fr.xephi.authme.util.Utils.logAndSendWarning;
+
/**
- * The backup management class
- *
- * @author stefano
+ * Performs a backup of the data source.
*/
public class BackupService {
- private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH-mm");
-
- private final String dbName;
- private final String dbUserName;
- private final String dbPassword;
- private final String tblname;
- private final String path;
+ private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm");
private final File dataFolder;
+ private final File backupFolder;
private final Settings settings;
/**
- * Constructor for PerformBackup.
+ * Constructor.
*
- * @param instance AuthMe
- * @param settings The plugin settings
+ * @param dataFolder the data folder
+ * @param settings the plugin settings
*/
- public BackupService(AuthMe instance, Settings settings) {
- this.dataFolder = instance.getDataFolder();
- this.settings = settings;
- this.dbName = settings.getProperty(DatabaseSettings.MYSQL_DATABASE);
- this.dbUserName = settings.getProperty(DatabaseSettings.MYSQL_USERNAME);
- this.dbPassword = settings.getProperty(DatabaseSettings.MYSQL_PASSWORD);
- this.tblname = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
-
- String dateString = DATE_FORMAT.format(new Date());
- this.path = String.join(File.separator,
- instance.getDataFolder().getPath(), "backups", "backup" + dateString);
+ @Inject
+ public BackupService(@DataFolder File dataFolder, Settings settings) {
+ this.dataFolder = dataFolder;
+ this.backupFolder = new File(dataFolder, "backups");
+ this.settings = settings;
}
/**
- * Perform a backup with the given reason.
+ * Performs a backup for the given reason.
*
- * @param cause The cause of the backup.
+ * @param cause backup reason
*/
public void doBackup(BackupCause cause) {
+ doBackup(cause, null);
+ }
+
+ /**
+ * Performs a backup for the given reason.
+ *
+ * @param cause backup reason
+ * @param sender the command sender (nullable)
+ */
+ public void doBackup(BackupCause cause, CommandSender sender) {
if (!settings.getProperty(BackupSettings.ENABLED)) {
// Print a warning if the backup was requested via command or by another plugin
if (cause == BackupCause.COMMAND || cause == BackupCause.OTHER) {
- ConsoleLogger.warning("Can't perform a Backup: disabled in configuration. Cause of the Backup: "
- + cause.name());
+ logAndSendWarning(sender,
+ "Can't perform a backup: disabled in configuration. Cause of the backup: " + cause.name());
}
return;
- }
-
- // Check whether a backup should be made at the specified point in time
- if (BackupCause.START.equals(cause) && !settings.getProperty(BackupSettings.ON_SERVER_START)
- || BackupCause.STOP.equals(cause) && !settings.getProperty(BackupSettings.ON_SERVER_STOP)) {
+ } else if (BackupCause.START == cause && !settings.getProperty(BackupSettings.ON_SERVER_START)
+ || BackupCause.STOP == cause && !settings.getProperty(BackupSettings.ON_SERVER_STOP)) {
+ // Don't perform backup on start or stop if so configured
return;
}
// Do backup and check return value!
if (doBackup()) {
- ConsoleLogger.info("A backup has been performed successfully. Cause of the Backup: " + cause.name());
+ logAndSendMessage(sender,
+ "A backup has been performed successfully. Cause of the backup: " + cause.name());
} else {
- ConsoleLogger.warning("Error while performing a backup! Cause of the Backup: " + cause.name());
+ logAndSendWarning(sender, "Error while performing a backup! Cause of the backup: " + cause.name());
}
}
- public boolean doBackup() {
+ private boolean doBackup() {
DataSourceType dataSourceType = settings.getProperty(DatabaseSettings.BACKEND);
switch (dataSourceType) {
case FILE:
- return fileBackup("auths.db");
+ return performFileBackup("auths.db");
case MYSQL:
- return mySqlBackup();
+ return performMySqlBackup();
case SQLITE:
- return fileBackup(dbName + ".db");
+ String dbName = settings.getProperty(DatabaseSettings.MYSQL_DATABASE);
+ return performFileBackup(dbName + ".db");
default:
ConsoleLogger.warning("Unknown data source type '" + dataSourceType + "' for backup");
}
@@ -97,17 +100,15 @@ public class BackupService {
return false;
}
- private boolean mySqlBackup() {
- File dirBackup = new File(dataFolder + File.separator + "backups");
+ private boolean performMySqlBackup() {
+ FileUtils.createDirectory(backupFolder);
+ File sqlBackupFile = constructBackupFile("sql");
- if (!dirBackup.exists()) {
- dirBackup.mkdir();
- }
String backupWindowsPath = settings.getProperty(BackupSettings.MYSQL_WINDOWS_PATH);
- boolean isUsingWindows = checkWindows(backupWindowsPath);
+ boolean isUsingWindows = useWindowsCommand(backupWindowsPath);
String backupCommand = isUsingWindows
- ? backupWindowsPath + "\\bin\\mysqldump.exe" + buildMysqlDumpArguments()
- : "mysqldump" + buildMysqlDumpArguments();
+ ? backupWindowsPath + "\\bin\\mysqldump.exe" + buildMysqlDumpArguments(sqlBackupFile)
+ : "mysqldump" + buildMysqlDumpArguments(sqlBackupFile);
try {
Process runtimeProcess = Runtime.getRuntime().exec(backupCommand);
@@ -124,14 +125,12 @@ public class BackupService {
return false;
}
- private boolean fileBackup(String backend) {
- File dirBackup = new File(dataFolder + File.separator + "backups");
-
- if (!dirBackup.exists())
- dirBackup.mkdir();
+ private boolean performFileBackup(String filename) {
+ FileUtils.createDirectory(backupFolder);
+ File backupFile = constructBackupFile("db");
try {
- copy("plugins" + File.separator + "AuthMe" + File.separator + backend, path + ".db");
+ copy(new File(dataFolder, filename), backupFile);
return true;
} catch (IOException ex) {
ConsoleLogger.logException("Encountered an error during file backup:", ex);
@@ -146,7 +145,7 @@ public class BackupService {
* @param windowsPath The path to check
* @return True if the path is correct, false if it is incorrect or the OS is not Windows
*/
- private static boolean checkWindows(String windowsPath) {
+ private static boolean useWindowsCommand(String windowsPath) {
String isWin = System.getProperty("os.name").toLowerCase();
if (isWin.contains("win")) {
if (new File(windowsPath + "\\bin\\mysqldump.exe").exists()) {
@@ -162,28 +161,42 @@ public class BackupService {
/**
* Builds the command line arguments to pass along when running the {@code mysqldump} command.
*
+ * @param sqlBackupFile the file to back up to
* @return the mysqldump command line arguments
*/
- private String buildMysqlDumpArguments() {
- return " -u " + dbUserName + " -p" + dbPassword + " " + dbName
- + " --tables " + tblname + " -r " + path + ".sql";
+ private String buildMysqlDumpArguments(File sqlBackupFile) {
+ String dbUsername = settings.getProperty(DatabaseSettings.MYSQL_USERNAME);
+ String dbPassword = settings.getProperty(DatabaseSettings.MYSQL_PASSWORD);
+ String dbName = settings.getProperty(DatabaseSettings.MYSQL_DATABASE);
+ String tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
+
+ return " -u " + dbUsername + " -p" + dbPassword + " " + dbName
+ + " --tables " + tableName + " -r " + sqlBackupFile.getPath() + ".sql";
}
- private static void copy(String src, String dst) throws IOException {
- InputStream in = new FileInputStream(src);
- OutputStream out = new FileOutputStream(dst);
+ /**
+ * Constructs the file name to back up the data source to.
+ *
+ * @param fileExtension the file extension to use (e.g. sql)
+ * @return the file to back up the data to
+ */
+ private File constructBackupFile(String fileExtension) {
+ String dateString = dateFormat.format(new Date());
+ return new File(backupFolder, "backup" + dateString + "." + fileExtension);
+ }
- // Transfer bytes from in to out
- byte[] buf = new byte[1024];
- int len;
- while ((len = in.read(buf)) > 0) {
- out.write(buf, 0, len);
+ private static void copy(File src, File dst) throws IOException {
+ try (InputStream in = new FileInputStream(src);
+ OutputStream out = new FileOutputStream(dst)) {
+ // Transfer bytes from in to out
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = in.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
}
- in.close();
- out.close();
}
-
/**
* Possible backup causes.
*/
diff --git a/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java b/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java
index 57bb5941f..190c5b7da 100644
--- a/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java
+++ b/src/main/java/fr/xephi/authme/settings/properties/BackupSettings.java
@@ -8,19 +8,19 @@ import static ch.jalu.configme.properties.PropertyInitializer.newProperty;
public final class BackupSettings implements SettingsHolder {
- @Comment("Enable or disable automatic backup")
+ @Comment("General configuration for backups: if false, no backups are possible")
public static final Property ENABLED =
newProperty("BackupSystem.ActivateBackup", false);
- @Comment("Set backup at every start of server")
+ @Comment("Create backup at every start of server")
public static final Property ON_SERVER_START =
newProperty("BackupSystem.OnServerStart", false);
- @Comment("Set backup at every stop of server")
+ @Comment("Create backup at every stop of server")
public static final Property ON_SERVER_STOP =
newProperty("BackupSystem.OnServerStop", true);
- @Comment("Windows only mysql installation Path")
+ @Comment("Windows only: MySQL installation path")
public static final Property MYSQL_WINDOWS_PATH =
newProperty("BackupSystem.MysqlWindowsPath", "C:\\Program Files\\MySQL\\MySQL Server 5.1\\");
diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java
index 4d6983df5..9d557e58e 100644
--- a/src/main/java/fr/xephi/authme/util/Utils.java
+++ b/src/main/java/fr/xephi/authme/util/Utils.java
@@ -1,6 +1,7 @@
package fr.xephi.authme.util;
import fr.xephi.authme.ConsoleLogger;
+import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
@@ -69,6 +70,22 @@ public final class Utils {
}
}
+ /**
+ * Sends a warning to the given sender (null safe), and logs the warning to the console.
+ * This method is aware that the command sender might be the console sender and avoids
+ * displaying the message twice in this case.
+ *
+ * @param sender the sender to inform
+ * @param message the warning to log and send
+ */
+ public static void logAndSendWarning(CommandSender sender, String message) {
+ ConsoleLogger.warning(message);
+ // Make sure sender is not console user, which will see the message from ConsoleLogger already
+ if (sender != null && !(sender instanceof ConsoleCommandSender)) {
+ sender.sendMessage(ChatColor.RED + message);
+ }
+ }
+
/**
* Null-safe way to check whether a collection is empty or not.
*
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 3ca000fdf..8629a4535 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -17,7 +17,7 @@ softdepend:
commands:
authme:
description: AuthMe op commands
- usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|debug
+ usage: /authme register|unregister|forcelogin|password|lastlogin|accounts|email|setemail|getip|spawn|setspawn|firstspawn|setfirstspawn|purge|backup|resetpos|purgebannedplayers|switchantibot|reload|version|converter|messages|debug
login:
description: Login command
usage: /login
@@ -55,6 +55,7 @@ permissions:
children:
authme.admin.accounts: true
authme.admin.antibotmessages: true
+ authme.admin.backup: true
authme.admin.changemail: true
authme.admin.changepassword: true
authme.admin.converter: true
@@ -81,6 +82,9 @@ permissions:
authme.admin.antibotmessages:
description: Permission to see Antibot messages.
default: op
+ authme.admin.backup:
+ description: Allows to use the backup command.
+ default: op
authme.admin.changemail:
description: Administrator command to set or change the email address of a user.
default: op