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