#477 Change lastlogin column from bigint to timestamp (work in progress)

- Alter column type
- Create migration for MySQL
- Unrelated: move DataSource enum to its own file
This commit is contained in:
ljacqu 2016-02-07 14:27:03 +01:00
parent 9cf7405f63
commit db4d4a7cce
13 changed files with 106 additions and 119 deletions

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.datasource.DataSourceType;
import fr.xephi.authme.settings.SettingsMigrationService;
import org.apache.logging.log4j.LogManager;
@ -534,7 +535,7 @@ public class AuthMe extends JavaPlugin {
});
}
if (Settings.getDataSource == DataSource.DataSourceType.FILE) {
if (Settings.getDataSource == DataSourceType.FILE) {
ConsoleLogger.showError("FlatFile backend has been detected and is now deprecated, it will be changed " +
"to SQLite... Connection will be impossible until conversion is done!");
ForceFlatToSqlite converter = new ForceFlatToSqlite(database, newSettings);

View File

@ -1,6 +1,6 @@
package fr.xephi.authme;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.DataSourceType;
import fr.xephi.authme.settings.NewSetting;
import fr.xephi.authme.settings.properties.BackupSettings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
@ -80,7 +80,7 @@ public class PerformBackup {
}
public boolean doBackup() {
DataSource.DataSourceType dataSourceType = settings.getProperty(DatabaseSettings.BACKEND);
DataSourceType dataSourceType = settings.getProperty(DatabaseSettings.BACKEND);
switch (dataSourceType) {
case FILE:
return fileBackup("auths.db");
@ -112,14 +112,10 @@ public class PerformBackup {
ConsoleLogger.info("Backup created successfully.");
return true;
} else {
ConsoleLogger.showError("Could not create the backup!");
ConsoleLogger.showError("Could not create the backup! (Windows)");
}
} catch (IOException e) {
ConsoleLogger.showError("Error during backup: " + StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
} catch (InterruptedException e) {
ConsoleLogger.showError("Backup was interrupted: " + StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
} catch (IOException | InterruptedException e) {
ConsoleLogger.logException("Error during Windows backup:", e);
}
} else {
String executeCmd = "mysqldump -u " + dbUserName + " -p" + dbPassword + " " + dbName + " --tables " + tblname + " -r " + path + ".sql";
@ -133,12 +129,8 @@ public class PerformBackup {
} else {
ConsoleLogger.showError("Could not create the backup!");
}
} catch (IOException e) {
ConsoleLogger.showError("Error during backup: " + StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
} catch (InterruptedException e) {
ConsoleLogger.showError("Backup was interrupted: " + StringUtils.formatException(e));
ConsoleLogger.writeStackTrace(e);
} catch (IOException | InterruptedException e) {
ConsoleLogger.logException("Error during backup:", e);
}
}
return false;

View File

@ -3,6 +3,7 @@ package fr.xephi.authme.converter;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.DataSourceType;
import fr.xephi.authme.datasource.SQLite;
import fr.xephi.authme.settings.NewSetting;
import fr.xephi.authme.settings.properties.DatabaseSettings;
@ -30,7 +31,7 @@ public class ForceFlatToSqlite {
auth.setRealName("Player");
sqlite.saveAuth(auth);
}
settings.setProperty(DatabaseSettings.BACKEND, DataSource.DataSourceType.SQLITE);
settings.setProperty(DatabaseSettings.BACKEND, DataSourceType.SQLITE);
settings.save();
ConsoleLogger.info("Database successfully converted to sqlite!");
return sqlite;

View File

@ -5,7 +5,7 @@ import org.bukkit.command.CommandSender;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.datasource.DataSource.DataSourceType;
import fr.xephi.authme.datasource.DataSourceType;
import fr.xephi.authme.datasource.SQLite;
import fr.xephi.authme.output.MessageKey;

View File

@ -41,6 +41,7 @@ public class CacheDataSource implements DataSource {
})
.build(
new CacheLoader<String, Optional<PlayerAuth>>() {
@Override
public Optional<PlayerAuth> load(String key) {
return Optional.fromNullable(source.getAuth(key));
}
@ -62,15 +63,6 @@ public class CacheDataSource implements DataSource {
return source.getPassword(user);
}
/**
* Method getAuth.
*
* @param user String
*
* @return PlayerAuth
*
* @see fr.xephi.authme.datasource.DataSource#getAuth(String)
*/
@Override
public synchronized PlayerAuth getAuth(String user) {
user = user.toLowerCase();

View File

@ -220,9 +220,4 @@ public interface DataSource {
boolean isEmailStored(String email);
enum DataSourceType {
MYSQL,
FILE,
SQLITE
}
}

View File

@ -0,0 +1,14 @@
package fr.xephi.authme.datasource;
/**
* DataSource type.
*/
public enum DataSourceType {
MYSQL,
FILE,
SQLITE
}

View File

@ -18,6 +18,8 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
@ -149,7 +151,7 @@ public class MySQL implements DataSource {
+ columnRealName + " VARCHAR(255) NOT NULL,"
+ columnPassword + " VARCHAR(255) NOT NULL,"
+ columnIp + " VARCHAR(40) NOT NULL DEFAULT '127.0.0.1',"
+ columnLastLogin + " BIGINT NOT NULL DEFAULT '" + System.currentTimeMillis() + "',"
+ columnLastLogin + " TIMESTAMP NOT NULL DEFAULT current_timestamp,"
+ lastlocX + " DOUBLE NOT NULL DEFAULT '0.0',"
+ lastlocY + " DOUBLE NOT NULL DEFAULT '0.0',"
+ lastlocZ + " DOUBLE NOT NULL DEFAULT '0.0',"
@ -200,7 +202,9 @@ public class MySQL implements DataSource {
rs = md.getColumns(null, null, tableName, columnLastLogin);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName
+ " ADD COLUMN " + columnLastLogin + " BIGINT;");
+ " ADD COLUMN " + columnLastLogin + " TIMESTAMP NOT NULL DEFAULT current_timestamp;");
} else {
migrateLastLoginColumnToTimestamp(con, rs);
}
rs.close();
@ -245,7 +249,7 @@ public class MySQL implements DataSource {
st.close();
}
ConsoleLogger.info("MySQL Setup finished");
ConsoleLogger.info("MySQL setup finished");
}
@Override
@ -291,22 +295,8 @@ public class MySQL implements DataSource {
if (!rs.next()) {
return null;
}
String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null;
int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1;
int id = rs.getInt(columnID);
pAuth = PlayerAuth.builder()
.name(rs.getString(columnName))
.realName(rs.getString(columnRealName))
.password(rs.getString(columnPassword), salt)
.lastLogin(rs.getLong(columnLastLogin))
.ip(rs.getString(columnIp))
.locWorld(rs.getString(lastlocWorld))
.locX(rs.getDouble(lastlocX))
.locY(rs.getDouble(lastlocY))
.locZ(rs.getDouble(lastlocZ))
.email(rs.getString(columnEmail))
.groupId(group)
.build();
pAuth = buildAuthFromResultSet(rs);
rs.close();
pst.close();
if (Settings.getPasswordHash == HashAlgorithm.XFBCRYPT) {
@ -344,7 +334,7 @@ public class MySQL implements DataSource {
pst.setString(1, auth.getNickname());
pst.setString(2, auth.getPassword().getHash());
pst.setString(3, auth.getIp());
pst.setLong(4, auth.getLastLogin());
pst.setTimestamp(4, new Timestamp(auth.getLastLogin()));
pst.setString(5, auth.getRealName());
pst.setString(6, auth.getEmail());
if (useSalt) {
@ -923,22 +913,7 @@ public class MySQL implements DataSource {
ResultSet rs = st.executeQuery("SELECT * FROM " + tableName);
PreparedStatement pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;");
while (rs.next()) {
String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null;
int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1;
PlayerAuth pAuth = PlayerAuth.builder()
.name(rs.getString(columnName))
.realName(rs.getString(columnRealName))
.password(rs.getString(columnPassword), salt)
.lastLogin(rs.getLong(columnLastLogin))
.ip(rs.getString(columnIp))
.locWorld(rs.getString(lastlocWorld))
.locX(rs.getDouble(lastlocX))
.locY(rs.getDouble(lastlocY))
.locZ(rs.getDouble(lastlocZ))
.email(rs.getString(columnEmail))
.groupId(group)
.build();
PlayerAuth pAuth = buildAuthFromResultSet(rs);
if (Settings.getPasswordHash == HashAlgorithm.XFBCRYPT) {
int id = rs.getInt(columnID);
pst.setInt(1, id);
@ -969,22 +944,7 @@ public class MySQL implements DataSource {
ResultSet rs = st.executeQuery("SELECT * FROM " + tableName + " WHERE " + columnLogged + "=1;");
PreparedStatement pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;");
while (rs.next()) {
String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null;
int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1;
PlayerAuth pAuth = PlayerAuth.builder()
.name(rs.getString(columnName))
.realName(rs.getString(columnRealName))
.password(rs.getString(columnPassword), salt)
.lastLogin(rs.getLong(columnLastLogin))
.ip(rs.getString(columnIp))
.locWorld(rs.getString(lastlocWorld))
.locX(rs.getDouble(lastlocX))
.locY(rs.getDouble(lastlocY))
.locZ(rs.getDouble(lastlocZ))
.email(rs.getString(columnEmail))
.groupId(group)
.build();
PlayerAuth pAuth = buildAuthFromResultSet(rs);
if (Settings.getPasswordHash == HashAlgorithm.XFBCRYPT) {
int id = rs.getInt(columnID);
pst.setInt(1, id);
@ -1018,6 +978,55 @@ public class MySQL implements DataSource {
return false;
}
private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException {
String salt = columnSalt.isEmpty() ? null : row.getString(columnSalt);
int group = columnGroup.isEmpty() ? -1 : row.getInt(columnGroup);
return PlayerAuth.builder()
.name(row.getString(columnName))
.realName(row.getString(columnRealName))
.password(row.getString(columnPassword), salt)
.lastLogin(row.getTimestamp(columnLastLogin).getTime())
.ip(row.getString(columnIp))
.locWorld(row.getString(lastlocWorld))
.locX(row.getDouble(lastlocX))
.locY(row.getDouble(lastlocY))
.locZ(row.getDouble(lastlocZ))
.email(row.getString(columnEmail))
.groupId(group)
.build();
}
private void migrateLastLoginColumnToTimestamp(Connection con, ResultSet rs) throws SQLException {
final int columnType = rs.getInt("DATA_TYPE");
if (columnType == Types.BIGINT) {
ConsoleLogger.info("Migrating lastlogin column from bigint to timestamp");
final String lastLoginOld = columnLastLogin + "_old";
// Rename lastlogin to lastlogin_old
String sql = String.format("ALTER TABLE %s CHANGE COLUMN %s %s BIGINT",
tableName, columnLastLogin, lastLoginOld);
PreparedStatement pst = con.prepareStatement(sql);
pst.execute();
// Create lastlogin column
sql = String.format("ALTER TABLE %s ADD COLUMN %s " +
"TIMESTAMP NOT NULL DEFAULT current_timestamp AFTER %s",
tableName, columnLastLogin, columnIp);
con.prepareStatement(sql).execute();
// Set values of lastlogin based on lastlogin_old
sql = String.format("UPDATE %s SET %s = FROM_UNIXTIME(%s)",
tableName, columnLastLogin, lastLoginOld);
con.prepareStatement(sql).execute();
// Drop lastlogin_old
sql = String.format("ALTER TABLE %s DROP COLUMN %s",
tableName, lastLoginOld);
con.prepareStatement(sql).execute();
ConsoleLogger.info("Finished migration of lastlogin (bigint to timestamp)");
}
}
private static void logSqlException(SQLException e) {
ConsoleLogger.logException("Error during SQL operation:", e);
}

View File

@ -64,9 +64,9 @@ public class SQLite implements DataSource {
try {
this.connect();
this.setup();
} catch (ClassNotFoundException | SQLException cnf) {
ConsoleLogger.showError("Can't use SQLITE... !");
throw cnf;
} catch (ClassNotFoundException | SQLException ex) {
ConsoleLogger.logException("Error during SQLite initialization:", ex);
throw ex;
}
}
@ -102,7 +102,7 @@ public class SQLite implements DataSource {
rs.close();
rs = con.getMetaData().getColumns(null, null, tableName, columnLastLogin);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnLastLogin + " BIGINT DEFAULT '0';");
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnLastLogin + " TIMESTAMP DEFAULT current_timestamp;");
}
rs.close();
rs = con.getMetaData().getColumns(null, null, tableName, lastlocX);
@ -124,7 +124,7 @@ public class SQLite implements DataSource {
rs.close();
rs = con.getMetaData().getColumns(null, null, tableName, columnLogged);
if (!rs.next()) {
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnLogged + " BIGINT DEFAULT '0';");
st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnLogged + " INT DEFAULT '0';");
}
rs.close();
rs = con.getMetaData().getColumns(null, null, tableName, columnRealName);
@ -178,13 +178,6 @@ public class SQLite implements DataSource {
return null;
}
/**
* Method getAuth.
*
* @param user String
*
* @return PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getAuth(String)
*/
@Override
public synchronized PlayerAuth getAuth(String user) {
PreparedStatement pst = null;

View File

@ -63,7 +63,7 @@ public class AsynchronousLogin {
this.settings = settings;
}
protected boolean needsCaptcha() {
private boolean needsCaptcha() {
if (Settings.useCaptcha) {
if (!plugin.captcha.containsKey(name)) {
plugin.captcha.putIfAbsent(name, 1);
@ -87,7 +87,7 @@ public class AsynchronousLogin {
*
* @return PlayerAuth
*/
protected PlayerAuth preAuth() {
private PlayerAuth preAuth() {
if (PlayerCache.getInstance().isAuthenticated(name)) {
m.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR);
return null;
@ -153,7 +153,6 @@ public class AsynchronousLogin {
.name(name)
.realName(realName)
.ip(ip)
.lastLogin(new Date().getTime())
.email(email)
.password(pAuth.getPassword())
.build();
@ -221,14 +220,12 @@ public class AsynchronousLogin {
}
public void displayOtherAccounts(PlayerAuth auth) {
if (!Settings.displayOtherAccounts) {
return;
}
if (auth == null) {
if (!Settings.displayOtherAccounts || auth == null) {
return;
}
List<String> auths = this.database.getAllAuthsByName(auth);
if (auths.isEmpty() || auths.size() == 1) {
if (auths.size() < 2) {
return;
}
String message = "[AuthMe] " + StringUtils.join(", ", auths) + ".";

View File

@ -43,7 +43,7 @@ public class AsyncRegister {
this.settings = settings;
}
private boolean preRegisterCheck() throws Exception {
private boolean preRegisterCheck() {
String passLow = password.toLowerCase();
if (PlayerCache.getInstance().isAuthenticated(name)) {
m.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR);
@ -86,18 +86,12 @@ public class AsyncRegister {
}
public void process() {
try {
if (!preRegisterCheck()) {
return;
}
if (preRegisterCheck()) {
if (email != null && !email.isEmpty()) {
emailRegister();
} else {
passwordRegister();
}
} catch (Exception e) {
ConsoleLogger.logException("Error during async register process", e);
m.send(player, MessageKey.ERROR);
}
}
@ -130,7 +124,7 @@ public class AsyncRegister {
}
private void passwordRegister() throws Exception {
private void passwordRegister() {
final HashedPassword hashedPassword = plugin.getPasswordSecurity().computeHash(password, name);
PlayerAuth auth = PlayerAuth.builder()
.name(name)

View File

@ -2,8 +2,7 @@ package fr.xephi.authme.settings;
import fr.xephi.authme.AuthMe;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.DataSource.DataSourceType;
import fr.xephi.authme.datasource.DataSourceType;
import fr.xephi.authme.security.HashAlgorithm;
import fr.xephi.authme.util.Wrapper;
import org.bukkit.configuration.file.FileConfiguration;
@ -281,10 +280,10 @@ public final class Settings {
private static DataSourceType getDataSource() {
String key = "DataSource.backend";
try {
return DataSource.DataSourceType.valueOf(configFile.getString(key, "sqlite").toUpperCase());
return DataSourceType.valueOf(configFile.getString(key, "sqlite").toUpperCase());
} catch (IllegalArgumentException ex) {
ConsoleLogger.showError("Unknown database backend; defaulting to SQLite database");
return DataSource.DataSourceType.SQLITE;
return DataSourceType.SQLITE;
}
}

View File

@ -1,6 +1,6 @@
package fr.xephi.authme.settings.properties;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.datasource.DataSourceType;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
@ -11,8 +11,8 @@ public class DatabaseSettings implements SettingsClass {
@Comment({"What type of database do you want to use?",
"Valid values: sqlite, mysql"})
public static final Property<DataSource.DataSourceType> BACKEND =
newProperty(DataSource.DataSourceType.class, "DataSource.backend", DataSource.DataSourceType.SQLITE);
public static final Property<DataSourceType> BACKEND =
newProperty(DataSourceType.class, "DataSource.backend", DataSourceType.SQLITE);
@Comment("Enable database caching, should improve database performance")
public static final Property<Boolean> USE_CACHING =