From 9dd4039fdd667b913992e20aef45e60ec4589112 Mon Sep 17 00:00:00 2001 From: ljacqu Date: Tue, 13 Feb 2018 22:15:03 +0100 Subject: [PATCH] #1467 Create backup before migrating; output newly added message keys - Extract logic for creating a backup timestamp into FileUtils --- .../authme/datasource/SqLiteMigrater.java | 10 ++---- .../MessageMigraterPropertyReader.java | 8 ++++- .../message/updater/MessageUpdater.java | 31 +++++++++++++---- .../xephi/authme/service/BackupService.java | 5 +-- .../java/fr/xephi/authme/util/FileUtils.java | 33 +++++++++++++++++-- .../fr/xephi/authme/util/FileUtilsTest.java | 27 +++++++++++++++ 6 files changed, 94 insertions(+), 20 deletions(-) diff --git a/src/main/java/fr/xephi/authme/datasource/SqLiteMigrater.java b/src/main/java/fr/xephi/authme/datasource/SqLiteMigrater.java index 6f8f2a371..aab79f187 100644 --- a/src/main/java/fr/xephi/authme/datasource/SqLiteMigrater.java +++ b/src/main/java/fr/xephi/authme/datasource/SqLiteMigrater.java @@ -1,5 +1,6 @@ package fr.xephi.authme.datasource; +import com.google.common.io.Files; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.properties.DatabaseSettings; @@ -8,14 +9,10 @@ import fr.xephi.authme.util.FileUtils; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; -import java.nio.file.Files; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.sql.Statement; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; /** * Migrates the SQLite database when necessary. @@ -70,11 +67,10 @@ class SqLiteMigrater { File backupDirectory = new File(dataFolder, "backups"); FileUtils.createDirectory(backupDirectory); - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm"); - String backupName = "backup-" + databaseName + dateFormat.format(new Date()) + ".db"; + String backupName = "backup-" + databaseName + FileUtils.createCurrentTimeString() + ".db"; File backup = new File(backupDirectory, backupName); try { - Files.copy(sqLite.toPath(), backup.toPath()); + Files.copy(sqLite, backup); return backupName; } catch (IOException e) { throw new IllegalStateException("Failed to create SQLite backup before migration", e); diff --git a/src/main/java/fr/xephi/authme/message/updater/MessageMigraterPropertyReader.java b/src/main/java/fr/xephi/authme/message/updater/MessageMigraterPropertyReader.java index 1174ba043..ab9f8d3b8 100644 --- a/src/main/java/fr/xephi/authme/message/updater/MessageMigraterPropertyReader.java +++ b/src/main/java/fr/xephi/authme/message/updater/MessageMigraterPropertyReader.java @@ -19,7 +19,7 @@ import java.util.Objects; * Implementation of {@link PropertyReader} which can read a file or a stream with * a specified charset. */ -public class MessageMigraterPropertyReader implements PropertyReader { +public final class MessageMigraterPropertyReader implements PropertyReader { public static final Charset CHARSET = StandardCharsets.UTF_8; @@ -31,6 +31,12 @@ public class MessageMigraterPropertyReader implements PropertyReader { root = valuesMap; } + /** + * Creates a new property reader for the given file. + * + * @param file the file to load + * @return the created property reader + */ public static MessageMigraterPropertyReader loadFromFile(File file) { Map valuesMap; try (InputStream is = new FileInputStream(file)) { diff --git a/src/main/java/fr/xephi/authme/message/updater/MessageUpdater.java b/src/main/java/fr/xephi/authme/message/updater/MessageUpdater.java index 9e06bafff..3b429106c 100644 --- a/src/main/java/fr/xephi/authme/message/updater/MessageUpdater.java +++ b/src/main/java/fr/xephi/authme/message/updater/MessageUpdater.java @@ -7,12 +7,17 @@ import ch.jalu.configme.properties.Property; import ch.jalu.configme.properties.StringProperty; import ch.jalu.configme.resource.YamlFileResource; import com.google.common.collect.ImmutableMap; +import com.google.common.io.Files; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.message.MessageKey; +import fr.xephi.authme.util.FileUtils; import java.io.File; +import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -60,6 +65,8 @@ public class MessageUpdater { boolean addedMissingKeys = addMissingKeys(jarMessageSource, userResource); if (movedOldKeys || addedMissingKeys) { + backupMessagesFile(userFile); + SettingsManager settingsManager = new SettingsManager(userResource, null, CONFIGURATION_DATA); settingsManager.save(); ConsoleLogger.debug("Successfully saved {0}", userFile); @@ -77,20 +84,32 @@ public class MessageUpdater { } private boolean addMissingKeys(JarMessageSource jarMessageSource, YamlFileResource userResource) { - int addedKeys = 0; + List addedKeys = new ArrayList<>(); for (Property property : CONFIGURATION_DATA.getProperties()) { - if (userResource.getString(property.getPath()) == null) { - userResource.setValue(property.getPath(), jarMessageSource.getMessageFromJar(property)); - ++addedKeys; + final String key = property.getPath(); + if (userResource.getString(key) == null) { + userResource.setValue(key, jarMessageSource.getMessageFromJar(property)); + addedKeys.add(key); } } - if (addedKeys > 0) { - ConsoleLogger.info("Added " + addedKeys + " missing keys to your messages_xx.yml file"); + if (!addedKeys.isEmpty()) { + ConsoleLogger.info( + "Added " + addedKeys.size() + " missing keys to your messages_xx.yml file: " + addedKeys); return true; } return false; } + private static void backupMessagesFile(File messagesFile) { + String backupName = FileUtils.createBackupFilePath(messagesFile); + File backupFile = new File(backupName); + try { + Files.copy(messagesFile, backupFile); + } catch (IOException e) { + throw new IllegalStateException("Could not back up '" + messagesFile + "' to '" + backupFile + "'", e); + } + } + /** * Constructs the {@link ConfigurationData} for exporting a messages file in its entirety. * diff --git a/src/main/java/fr/xephi/authme/service/BackupService.java b/src/main/java/fr/xephi/authme/service/BackupService.java index 0141e8f6e..b85002eaa 100644 --- a/src/main/java/fr/xephi/authme/service/BackupService.java +++ b/src/main/java/fr/xephi/authme/service/BackupService.java @@ -16,8 +16,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; 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; @@ -27,7 +25,6 @@ import static fr.xephi.authme.util.Utils.logAndSendWarning; */ public class BackupService { - private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm"); private final File dataFolder; private final File backupFolder; private final Settings settings; @@ -181,7 +178,7 @@ public class BackupService { * @return the file to back up the data to */ private File constructBackupFile(String fileExtension) { - String dateString = dateFormat.format(new Date()); + String dateString = FileUtils.createCurrentTimeString(); return new File(backupFolder, "backup" + dateString + "." + fileExtension); } diff --git a/src/main/java/fr/xephi/authme/util/FileUtils.java b/src/main/java/fr/xephi/authme/util/FileUtils.java index 759f55811..bc1bef41c 100644 --- a/src/main/java/fr/xephi/authme/util/FileUtils.java +++ b/src/main/java/fr/xephi/authme/util/FileUtils.java @@ -1,12 +1,14 @@ package fr.xephi.authme.util; +import com.google.common.io.Files; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import static java.lang.String.format; @@ -15,6 +17,9 @@ import static java.lang.String.format; */ public final class FileUtils { + private static final DateTimeFormatter CURRENT_DATE_STRING_FORMATTER = + DateTimeFormatter.ofPattern("yyyyMMdd_HHmm"); + // Utility class private FileUtils() { } @@ -40,7 +45,7 @@ public final class FileUtils { ConsoleLogger.warning(format("Cannot copy resource '%s' to file '%s': cannot load resource", resourcePath, destinationFile.getPath())); } else { - Files.copy(is, destinationFile.toPath()); + java.nio.file.Files.copy(is, destinationFile.toPath()); return true; } } catch (IOException e) { @@ -138,4 +143,28 @@ public final class FileUtils { public static String makePath(String... elements) { return String.join(File.separator, elements); } + + /** + * Creates a textual representation of the current time (including minutes), e.g. useful for + * automatically generated backup files. + * + * @return string of the current time for use in file names + */ + public static String createCurrentTimeString() { + return LocalDateTime.now().format(CURRENT_DATE_STRING_FORMATTER); + } + + /** + * Returns a path to a new file (which doesn't exist yet) with a timestamp in the name in the same + * folder as the given file and containing the given file's filename. + * + * @param file the file based on which a new file path should be created + * @return path to a file suitably named for storing a backup + */ + public static String createBackupFilePath(File file) { + String filename = "backup_" + Files.getNameWithoutExtension(file.getName()) + + "_" + createCurrentTimeString() + + "." + Files.getFileExtension(file.getName()); + return makePath(file.getParent(), filename); + } } diff --git a/src/test/java/fr/xephi/authme/util/FileUtilsTest.java b/src/test/java/fr/xephi/authme/util/FileUtilsTest.java index a20210bb9..91145cc8c 100644 --- a/src/test/java/fr/xephi/authme/util/FileUtilsTest.java +++ b/src/test/java/fr/xephi/authme/util/FileUtilsTest.java @@ -11,6 +11,7 @@ import java.io.File; import java.io.IOException; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.matchesPattern; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; @@ -20,6 +21,9 @@ import static org.junit.Assert.assertThat; */ public class FileUtilsTest { + /** Regex that matches timestamps such as 20180211_1048. */ + private static final String BACKUP_TIMESTAMP_PATTERN = "20\\d{6}_\\d{4}"; + @BeforeClass public static void initLogger() { TestHelper.setupLogger(); @@ -186,6 +190,29 @@ public class FileUtilsTest { TestHelper.validateHasOnlyPrivateEmptyConstructor(FileUtils.class); } + + @Test + public void shouldCreateCurrentTimestampString() { + // given / when + String currentTimeString = FileUtils.createCurrentTimeString(); + + // then + assertThat(currentTimeString, matchesPattern(BACKUP_TIMESTAMP_PATTERN)); + } + + @Test + public void shouldCreateBackupFile() { + // given + File file = new File("some/folders/config.yml"); + + // when + String backupFile = FileUtils.createBackupFilePath(file); + + // then + String folders = String.join(File.separator,"some", "folders", "").replace("\\", "\\\\"); + assertThat(backupFile, matchesPattern(folders + "backup_config_" + BACKUP_TIMESTAMP_PATTERN + "\\.yml")); + } + private static void createFiles(File... files) throws IOException { for (File file : files) { boolean result = file.getParentFile().mkdirs() & file.createNewFile();