#450 Fix copying of JAR files to plugin folder

- Create SettingsMigrationService#copyFileFromResource (inspired from CustomSettings)
- Use new method to copy missing files in plugin folder from JAR
- Create YamlFileConfiguration inside NewSetting: FileConfiguration object provided by JavaPlugin#getConfig() sets default values from the JAR's config.yml :(
- Change ConsoleLogger to take logger from plugin (work in progress)
This commit is contained in:
ljacqu 2016-02-06 17:10:00 +01:00
parent 85868ca830
commit 99b7b80f1d
13 changed files with 156 additions and 86 deletions

View File

@ -97,6 +97,7 @@
<directory>src/main/resources/</directory>
<includes>
<include>email.html</include>
<include>welcome.txt</include>
</includes>
</resource>
<resource>

View File

@ -13,6 +13,7 @@ import java.util.logging.Logger;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import fr.xephi.authme.settings.SettingsMigrationService;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
@ -207,11 +208,21 @@ public class AuthMe extends JavaPlugin {
// Set various instances
server = getServer();
plugin = this;
ConsoleLogger.setLogger(getLogger());
setPluginInfos();
// Load settings and custom configurations, if it fails, stop the server due to security reasons.
newSettings = createNewSetting();
if (newSettings == null) {
ConsoleLogger.showError("Could not load configuration. Aborting.");
server.shutdown();
return;
}
ConsoleLogger.setLoggingOptions(newSettings.getProperty(SecuritySettings.USE_LOGGING),
new File(getDataFolder(), "authme.log"));
// Old settings manager
if (!loadSettings()) {
server.shutdown();
setEnabled(false);
@ -425,7 +436,6 @@ public class AuthMe extends JavaPlugin {
private boolean loadSettings() {
try {
settings = new Settings(this);
Settings.reload();
return true;
} catch (Exception e) {
ConsoleLogger.logException("Can't load the configuration file... Something went wrong. "
@ -436,8 +446,10 @@ public class AuthMe extends JavaPlugin {
}
private NewSetting createNewSetting() {
File configFile = new File(getDataFolder() + "config.yml");
return new NewSetting(getConfig(), configFile, getDataFolder());
File configFile = new File(getDataFolder(), "config.yml");
return SettingsMigrationService.copyFileFromResource(configFile, "config.yml")
? new NewSetting(configFile, getDataFolder())
: null;
}
/**

View File

@ -1,16 +1,16 @@
package fr.xephi.authme;
import com.google.common.base.Throwables;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.StringUtils;
import fr.xephi.authme.util.Wrapper;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Logger;
/**
* The plugin's static logger.
@ -19,21 +19,31 @@ public final class ConsoleLogger {
private static final String NEW_LINE = System.getProperty("line.separator");
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("[MM-dd HH:mm:ss]");
private static Wrapper wrapper = Wrapper.getInstance();
private static Logger logger;
private static boolean useLogging = false;
private static File logFile;
private ConsoleLogger() {
// Service class
}
public static void setLogger(Logger logger) {
ConsoleLogger.logger = logger;
}
public static void setLoggingOptions(boolean useLogging, File logFile) {
ConsoleLogger.useLogging = useLogging;
ConsoleLogger.logFile = logFile;
}
/**
* Print an info message.
*
* @param message String
*/
public static void info(String message) {
wrapper.getLogger().info(message);
if (Settings.useLogging) {
logger.info(message);
if (useLogging) {
writeLog(message);
}
}
@ -44,8 +54,8 @@ public final class ConsoleLogger {
* @param message String
*/
public static void showError(String message) {
wrapper.getLogger().warning(message);
if (Settings.useLogging) {
logger.warning(message);
if (useLogging) {
writeLog("ERROR: " + message);
}
}
@ -61,7 +71,7 @@ public final class ConsoleLogger {
dateTime = DATE_FORMAT.format(new Date());
}
try {
Files.write(Settings.LOG_FILE.toPath(), (dateTime + ": " + message + NEW_LINE).getBytes(),
Files.write(logFile.toPath(), (dateTime + ": " + message + NEW_LINE).getBytes(),
StandardOpenOption.APPEND,
StandardOpenOption.CREATE);
} catch (IOException ignored) {
@ -74,7 +84,7 @@ public final class ConsoleLogger {
* @param th The Throwable whose stack trace should be logged
*/
public static void writeStackTrace(Throwable th) {
if (Settings.useLogging) {
if (useLogging) {
writeLog(Throwables.getStackTraceAsString(th));
}
}

View File

@ -24,7 +24,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static fr.xephi.authme.util.StringUtils.makePath;
import static fr.xephi.authme.settings.SettingsMigrationService.copyFileFromResource;
/**
* The new settings manager.
@ -42,24 +42,14 @@ public class NewSetting {
/**
* Constructor. Checks the given {@link FileConfiguration} object for completeness.
*
* @param configuration The configuration to interact with
* @param configFile The configuration file
* @param pluginFolder The AuthMe plugin folder
*/
public NewSetting(FileConfiguration configuration, File configFile, File pluginFolder) {
this.configuration = configuration;
public NewSetting(File configFile, File pluginFolder) {
this.configuration = YamlConfiguration.loadConfiguration(configFile);
this.configFile = configFile;
this.pluginFolder = pluginFolder;
messagesFile = buildMessagesFile();
welcomeMessage = readWelcomeMessage();
emailMessage = readEmailMessage();
PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields();
if (SettingsMigrationService.checkAndMigrate(configuration, propertyMap, pluginFolder)) {
ConsoleLogger.info("Merged new config options");
ConsoleLogger.info("Please check your config.yml file for new settings!");
save(propertyMap);
}
validateAndLoadOptions();
}
/**
@ -131,6 +121,7 @@ public class NewSetting {
*/
public void reload() {
configuration = YamlConfiguration.loadConfiguration(configFile);
validateAndLoadOptions();
}
private void save(PropertyMap propertyMap) {
@ -185,6 +176,19 @@ public class NewSetting {
}
}
private void validateAndLoadOptions() {
PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields();
if (SettingsMigrationService.checkAndMigrate(configuration, propertyMap, pluginFolder)) {
ConsoleLogger.info("Merged new config options");
ConsoleLogger.info("Please check your config.yml file for new settings!");
save(propertyMap);
}
messagesFile = buildMessagesFile();
welcomeMessage = readWelcomeMessage();
emailMessage = readEmailMessage();
}
private <T> String toYaml(Property<T> property, int indent, Yaml simpleYaml, Yaml singleQuoteYaml) {
String representation = property.toYaml(configuration, simpleYaml, singleQuoteYaml);
return join("\n" + indent(indent), representation.split("\\n"));
@ -196,59 +200,45 @@ public class NewSetting {
if (messagesFile.exists()) {
return messagesFile;
}
return buildMessagesFileFromCode("en");
return copyFileFromResource(messagesFile, buildMessagesFilePathFromCode(languageCode))
? messagesFile
: buildMessagesFileFromCode("en");
}
private File buildMessagesFileFromCode(String language) {
return new File(pluginFolder,
makePath("messages", "messages_" + language + ".yml"));
return new File(pluginFolder, buildMessagesFilePathFromCode(language));
}
private static String buildMessagesFilePathFromCode(String language) {
return StringUtils.makePath("messages", "messages_" + language + ".yml");
}
private List<String> readWelcomeMessage() {
if (getProperty(RegistrationSettings.USE_WELCOME_MESSAGE)) {
final File welcomeFile = new File(pluginFolder, "welcome.txt");
final Charset charset = Charset.forName("UTF-8");
if (!welcomeFile.exists()) {
if (copyFileFromResource(welcomeFile, "welcome.txt")) {
try {
Files.write(
"Welcome {PLAYER} to {SERVER} server\n\nThis server uses AuthMe protection!",
welcomeFile, charset);
return Files.readLines(welcomeFile, charset);
} catch (IOException e) {
ConsoleLogger.showError("Failed to create file '" + welcomeFile.getPath() + "': "
+ StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
ConsoleLogger.logException("Failed to read file '" + welcomeFile.getPath() + "':", e);
}
}
try {
return Files.readLines(welcomeFile, charset);
} catch (IOException e) {
ConsoleLogger.showError("Failed to read file '" + welcomeFile.getPath() + "': " +
StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
}
}
return new ArrayList<>(0);
}
private String readEmailMessage() {
final File emailFile = new File(pluginFolder, "email.txt");
final File emailFile = new File(pluginFolder, "email.html");
final Charset charset = Charset.forName("UTF-8");
if (!emailFile.exists()) {
if (copyFileFromResource(emailFile, "email.html")) {
try {
Files.write("", emailFile, charset);
return StringUtils.join("", Files.readLines(emailFile, charset));
} catch (IOException e) {
ConsoleLogger.showError("Failed to create file '" + emailFile.getPath() + "': "
+ StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
ConsoleLogger.logException("Failed to read file '" + emailFile.getPath() + "':", e);
}
}
try {
return StringUtils.join("", Files.readLines(emailFile, charset));
} catch (IOException e) {
ConsoleLogger.showError("Failed to read file '" + emailFile.getPath() + "': " +
StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
}
return "";
}

View File

@ -6,7 +6,7 @@ import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.DataSource.DataSourceType;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.util.Wrapper;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.file.FileConfiguration;
import java.io.File;
import java.io.IOException;
@ -87,7 +87,7 @@ public final class Settings {
getmaxRegPerEmail, bCryptLog2Rounds, getPhpbbGroup,
antiBotSensibility, antiBotDuration, delayRecall, getMaxLoginPerIp,
getMaxJoinPerIp;
protected static YamlConfiguration configFile;
protected static FileConfiguration configFile;
private static AuthMe plugin;
private static Settings instance;
@ -99,25 +99,8 @@ public final class Settings {
public Settings(AuthMe pl) {
instance = this;
plugin = pl;
configFile = (YamlConfiguration) plugin.getConfig();
}
/**
* Method reload.
*
* @throws Exception if something went wrong
*/
public static void reload() throws Exception {
plugin.getLogger().info("Loading Configuration File...");
boolean exist = SETTINGS_FILE.exists();
if (!exist) {
plugin.saveDefaultConfig();
}
configFile.load(SETTINGS_FILE);
configFile = plugin.getConfig();
loadVariables();
if (exist) {
instance.saveDefaults();
}
}
public static void loadVariables() {

View File

@ -1,17 +1,20 @@
package fr.xephi.authme.settings;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import fr.xephi.authme.util.StringUtils;
import org.bukkit.configuration.file.FileConfiguration;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import static fr.xephi.authme.settings.properties.RestrictionSettings.ALLOWED_NICKNAME_CHARACTERS;
import static java.lang.String.format;
/**
* Service for verifying that the configuration is up-to-date.
@ -86,15 +89,51 @@ public final class SettingsMigrationService {
}
final File emailFile = new File(dataFolder, "email.html");
final String mailText = configuration.getString(oldSettingPath)
.replace("<playername>", "<playername />")
.replace("<servername>", "<servername />")
.replace("<generatedpass>", "<generatedpass />")
.replace("<image>", "<image />");
if (!emailFile.exists()) {
try (FileWriter fw = new FileWriter(emailFile)) {
fw.write(configuration.getString("Email.mailText"));
fw.write(mailText);
} catch (IOException e) {
ConsoleLogger.showError("Could not create email.html configuration file: "
+ StringUtils.formatException(e));
ConsoleLogger.logException("Could not create email.html configuration file:", e);
}
}
return true;
}
/**
* Copy a resource file (from the JAR) to the given file if it doesn't exist.
*
* @param destinationFile The file to check and copy to (outside of JAR)
* @param resourcePath Absolute path to the resource file (path to file within JAR)
* @return False if the file does not exist and could not be copied, true otherwise
*/
public static boolean copyFileFromResource(File destinationFile, String resourcePath) {
if (destinationFile.exists()) {
return true;
} else if (!destinationFile.getParentFile().exists() && !destinationFile.getParentFile().mkdirs()) {
ConsoleLogger.showError("Cannot create parent directories for '" + destinationFile + "'");
return false;
}
// ClassLoader#getResourceAsStream does not deal with the '\' path separator: replace to '/'
final String normalizedPath = resourcePath.replace("\\", "/");
try (InputStream is = AuthMe.class.getClassLoader().getResourceAsStream(normalizedPath)) {
if (is == null) {
ConsoleLogger.showError(format("Cannot copy resource '%s' to file '%s': cannot load resource",
resourcePath, destinationFile.getPath()));
} else {
Files.copy(is, destinationFile.toPath());
return true;
}
} catch (IOException e) {
ConsoleLogger.logException(format("Cannot copy resource '%s' to file '%s':",
resourcePath, destinationFile.getPath()), e);
}
return false;
}
}

View File

@ -0,0 +1,3 @@
Welcome {PLAYER} on {SERVER} server
This server uses AuthMeReloaded protection!

View File

@ -0,0 +1,20 @@
package fr.xephi.authme;
import org.mockito.Mockito;
import java.util.logging.Logger;
/**
* Test initializer for {@link ConsoleLogger}.
*/
public class ConsoleLoggerTestInitializer {
private ConsoleLoggerTestInitializer() {
}
public static Logger setupLogger() {
Logger logger = Mockito.mock(Logger.class);
ConsoleLogger.setLogger(logger);
return logger;
}
}

View File

@ -219,8 +219,8 @@ public class Log4JFilterTest {
* Mocks a {@link Message} object and makes it return the given formatted message.
*
* @param formattedMessage the formatted message the mock should return
* @return Message mock */
* @return Message mock
*/
private static Message mockMessage(String formattedMessage) {
Message message = Mockito.mock(Message.class);
when(message.getFormattedMessage()).thenReturn(formattedMessage);

View File

@ -1,9 +1,11 @@
package fr.xephi.authme.output;
import fr.xephi.authme.ConsoleLoggerTestInitializer;
import fr.xephi.authme.util.WrapperMock;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
@ -27,6 +29,12 @@ public class MessagesIntegrationTest {
private static final String YML_TEST_FILE = "messages_test.yml";
private Messages messages;
@BeforeClass
public static void setup() {
WrapperMock.createInstance();
ConsoleLoggerTestInitializer.setupLogger();
}
/**
* Loads the messages in the file {@code messages_test.yml} in the test resources folder.
* The file does not contain all messages defined in {@link MessageKey} and its contents
@ -34,8 +42,6 @@ public class MessagesIntegrationTest {
*/
@Before
public void setUpMessages() {
WrapperMock.createInstance();
URL url = getClass().getClassLoader().getResource(YML_TEST_FILE);
if (url == null) {
throw new RuntimeException("File '" + YML_TEST_FILE + "' could not be loaded");

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.ConsoleLoggerTestInitializer;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.WrapperMock;
import org.junit.BeforeClass;
@ -13,6 +14,7 @@ public class BcryptTest extends AbstractEncryptionMethodTest {
public static void setUpSettings() {
WrapperMock.createInstance();
Settings.bCryptLog2Rounds = 8;
ConsoleLoggerTestInitializer.setupLogger();
}
public BcryptTest() {

View File

@ -1,5 +1,6 @@
package fr.xephi.authme.security.crypts;
import fr.xephi.authme.ConsoleLoggerTestInitializer;
import fr.xephi.authme.util.WrapperMock;
import org.junit.BeforeClass;
@ -9,8 +10,9 @@ import org.junit.BeforeClass;
public class XFBCRYPTTest extends AbstractEncryptionMethodTest {
@BeforeClass
public static void setUpWrapper() {
public static void setup() {
WrapperMock.createInstance();
ConsoleLoggerTestInitializer.setupLogger();
}
public XFBCRYPTTest() {

View File

@ -1,6 +1,7 @@
package fr.xephi.authme.util;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLoggerTestInitializer;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.settings.NewSetting;
@ -39,6 +40,7 @@ public class UtilsTest {
public static void setUpMocks() {
WrapperMock wrapperMock = WrapperMock.createInstance();
authMeMock = wrapperMock.getAuthMe();
ConsoleLoggerTestInitializer.setupLogger();
}
@Before