#1467 Create backup before migrating; output newly added message keys

- Extract logic for creating a backup timestamp into FileUtils
This commit is contained in:
ljacqu 2018-02-13 22:15:03 +01:00
parent ffeb04c0a2
commit 9dd4039fdd
6 changed files with 94 additions and 20 deletions

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.datasource; package fr.xephi.authme.datasource;
import com.google.common.io.Files;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings; import fr.xephi.authme.settings.properties.DatabaseSettings;
@ -8,14 +9,10 @@ import fr.xephi.authme.util.FileUtils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.file.Files;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DatabaseMetaData; import java.sql.DatabaseMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/** /**
* Migrates the SQLite database when necessary. * Migrates the SQLite database when necessary.
@ -70,11 +67,10 @@ class SqLiteMigrater {
File backupDirectory = new File(dataFolder, "backups"); File backupDirectory = new File(dataFolder, "backups");
FileUtils.createDirectory(backupDirectory); FileUtils.createDirectory(backupDirectory);
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm"); String backupName = "backup-" + databaseName + FileUtils.createCurrentTimeString() + ".db";
String backupName = "backup-" + databaseName + dateFormat.format(new Date()) + ".db";
File backup = new File(backupDirectory, backupName); File backup = new File(backupDirectory, backupName);
try { try {
Files.copy(sqLite.toPath(), backup.toPath()); Files.copy(sqLite, backup);
return backupName; return backupName;
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Failed to create SQLite backup before migration", e); throw new IllegalStateException("Failed to create SQLite backup before migration", e);

View File

@ -19,7 +19,7 @@ import java.util.Objects;
* Implementation of {@link PropertyReader} which can read a file or a stream with * Implementation of {@link PropertyReader} which can read a file or a stream with
* a specified charset. * a specified charset.
*/ */
public class MessageMigraterPropertyReader implements PropertyReader { public final class MessageMigraterPropertyReader implements PropertyReader {
public static final Charset CHARSET = StandardCharsets.UTF_8; public static final Charset CHARSET = StandardCharsets.UTF_8;
@ -31,6 +31,12 @@ public class MessageMigraterPropertyReader implements PropertyReader {
root = valuesMap; 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) { public static MessageMigraterPropertyReader loadFromFile(File file) {
Map<String, Object> valuesMap; Map<String, Object> valuesMap;
try (InputStream is = new FileInputStream(file)) { try (InputStream is = new FileInputStream(file)) {

View File

@ -7,12 +7,17 @@ import ch.jalu.configme.properties.Property;
import ch.jalu.configme.properties.StringProperty; import ch.jalu.configme.properties.StringProperty;
import ch.jalu.configme.resource.YamlFileResource; import ch.jalu.configme.resource.YamlFileResource;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.message.MessageKey; import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.util.FileUtils;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -60,6 +65,8 @@ public class MessageUpdater {
boolean addedMissingKeys = addMissingKeys(jarMessageSource, userResource); boolean addedMissingKeys = addMissingKeys(jarMessageSource, userResource);
if (movedOldKeys || addedMissingKeys) { if (movedOldKeys || addedMissingKeys) {
backupMessagesFile(userFile);
SettingsManager settingsManager = new SettingsManager(userResource, null, CONFIGURATION_DATA); SettingsManager settingsManager = new SettingsManager(userResource, null, CONFIGURATION_DATA);
settingsManager.save(); settingsManager.save();
ConsoleLogger.debug("Successfully saved {0}", userFile); ConsoleLogger.debug("Successfully saved {0}", userFile);
@ -77,20 +84,32 @@ public class MessageUpdater {
} }
private boolean addMissingKeys(JarMessageSource jarMessageSource, YamlFileResource userResource) { private boolean addMissingKeys(JarMessageSource jarMessageSource, YamlFileResource userResource) {
int addedKeys = 0; List<String> addedKeys = new ArrayList<>();
for (Property<?> property : CONFIGURATION_DATA.getProperties()) { for (Property<?> property : CONFIGURATION_DATA.getProperties()) {
if (userResource.getString(property.getPath()) == null) { final String key = property.getPath();
userResource.setValue(property.getPath(), jarMessageSource.getMessageFromJar(property)); if (userResource.getString(key) == null) {
++addedKeys; userResource.setValue(key, jarMessageSource.getMessageFromJar(property));
addedKeys.add(key);
} }
} }
if (addedKeys > 0) { if (!addedKeys.isEmpty()) {
ConsoleLogger.info("Added " + addedKeys + " missing keys to your messages_xx.yml file"); ConsoleLogger.info(
"Added " + addedKeys.size() + " missing keys to your messages_xx.yml file: " + addedKeys);
return true; return true;
} }
return false; 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. * Constructs the {@link ConfigurationData} for exporting a messages file in its entirety.
* *

View File

@ -16,8 +16,6 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; 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.logAndSendMessage;
import static fr.xephi.authme.util.Utils.logAndSendWarning; import static fr.xephi.authme.util.Utils.logAndSendWarning;
@ -27,7 +25,6 @@ import static fr.xephi.authme.util.Utils.logAndSendWarning;
*/ */
public class BackupService { public class BackupService {
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm");
private final File dataFolder; private final File dataFolder;
private final File backupFolder; private final File backupFolder;
private final Settings settings; private final Settings settings;
@ -181,7 +178,7 @@ public class BackupService {
* @return the file to back up the data to * @return the file to back up the data to
*/ */
private File constructBackupFile(String fileExtension) { private File constructBackupFile(String fileExtension) {
String dateString = dateFormat.format(new Date()); String dateString = FileUtils.createCurrentTimeString();
return new File(backupFolder, "backup" + dateString + "." + fileExtension); return new File(backupFolder, "backup" + dateString + "." + fileExtension);
} }

View File

@ -1,12 +1,14 @@
package fr.xephi.authme.util; package fr.xephi.authme.util;
import com.google.common.io.Files;
import fr.xephi.authme.AuthMe; import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.ConsoleLogger;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import static java.lang.String.format; import static java.lang.String.format;
@ -15,6 +17,9 @@ import static java.lang.String.format;
*/ */
public final class FileUtils { public final class FileUtils {
private static final DateTimeFormatter CURRENT_DATE_STRING_FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMdd_HHmm");
// Utility class // Utility class
private FileUtils() { private FileUtils() {
} }
@ -40,7 +45,7 @@ public final class FileUtils {
ConsoleLogger.warning(format("Cannot copy resource '%s' to file '%s': cannot load resource", ConsoleLogger.warning(format("Cannot copy resource '%s' to file '%s': cannot load resource",
resourcePath, destinationFile.getPath())); resourcePath, destinationFile.getPath()));
} else { } else {
Files.copy(is, destinationFile.toPath()); java.nio.file.Files.copy(is, destinationFile.toPath());
return true; return true;
} }
} catch (IOException e) { } catch (IOException e) {
@ -138,4 +143,28 @@ public final class FileUtils {
public static String makePath(String... elements) { public static String makePath(String... elements) {
return String.join(File.separator, 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);
}
} }

View File

@ -11,6 +11,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
@ -20,6 +21,9 @@ import static org.junit.Assert.assertThat;
*/ */
public class FileUtilsTest { public class FileUtilsTest {
/** Regex that matches timestamps such as 20180211_1048. */
private static final String BACKUP_TIMESTAMP_PATTERN = "20\\d{6}_\\d{4}";
@BeforeClass @BeforeClass
public static void initLogger() { public static void initLogger() {
TestHelper.setupLogger(); TestHelper.setupLogger();
@ -186,6 +190,29 @@ public class FileUtilsTest {
TestHelper.validateHasOnlyPrivateEmptyConstructor(FileUtils.class); 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 { private static void createFiles(File... files) throws IOException {
for (File file : files) { for (File file : files) {
boolean result = file.getParentFile().mkdirs() & file.createNewFile(); boolean result = file.getParentFile().mkdirs() & file.createNewFile();