/*
* The MIT License (MIT)
*
* Copyright (c) 2015 games647 and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.flexiblelogin;
import com.github.games647.flexiblelogin.config.SQLConfiguration;
import com.github.games647.flexiblelogin.config.SQLType;
import com.google.common.collect.Maps;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Longs;
import java.io.File;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.UUID;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.service.sql.SqlService;
public class Database {
public static final String USERS_TABLE = "flexiblelogin_users";
private final FlexibleLogin plugin = FlexibleLogin.getInstance();
//this cache is thread-safe
private final Map<UUID, Account> cache = Maps.newConcurrentMap();
private final String jdbcUrl;
private final String username;
private final String password;
private SqlService sql;
public Database() {
SQLConfiguration sqlConfig = plugin.getConfigManager().getConfig().getSqlConfiguration();
if (sqlConfig.getType() == SQLType.MYSQL) {
this.username = sqlConfig.getUsername();
this.password = sqlConfig.getPassword();
} else {
//flat file drivers throw exception if you try to connect with a account
this.username = "";
this.password = "";
}
String storagePath = sqlConfig.getPath()
.replace("%DIR%", plugin.getConfigManager().getConfigDir().normalize().toString());
StringBuilder urlBuilder = new StringBuilder("jdbc:")
.append(sqlConfig.getType().name().toLowerCase()).append("://");
switch (sqlConfig.getType()) {
case SQLITE:
urlBuilder.append(storagePath).append(File.separatorChar).append("database.db");
break;
case MYSQL:
//jdbc:<engine>://[<username>[:<password>]@]<host>/<database> - copied from sponge doc
urlBuilder.append(username).append(':').append(password).append('@')
.append(sqlConfig.getPath())
.append(':')
.append(sqlConfig.getPort())
.append('/')
.append(sqlConfig.getDatabase())
.append("?useSSL").append('=').append(sqlConfig.isUseSSL());
break;
case H2:
default:
urlBuilder.append(storagePath).append(File.separatorChar).append("database");
break;
}
this.jdbcUrl = urlBuilder.toString();
}
public Connection getConnection() throws SQLException {
if (sql == null) {
//lazy binding
sql = plugin.getGame().getServiceManager().provideUnchecked(SqlService.class);
}
return sql.getDataSource(jdbcUrl).getConnection();
}
public Account getAccountIfPresent(Player player) {
return cache.get(player.getUniqueId());
}
public boolean isLoggedin(Player player) {
Account account = getAccountIfPresent(player);
return account != null && account.isLoggedIn();
}
public void createTable() {
try {
DatabaseMigration migration = new DatabaseMigration(plugin);
migration.migrateName();
migration.createTable();
} catch (SQLException sqlEx) {
plugin.getLogger().error("Error creating database table", sqlEx);
}
}
public boolean deleteAccount(String playerName) {
Connection conn = null;
try {
conn = getConnection();
PreparedStatement statement = conn.prepareStatement("DELETE FROM " + USERS_TABLE + " WHERE Username=?");
statement.setString(1, playerName);
int affectedRows = statement.executeUpdate();
//remove cache entry
cache.values().stream()
.filter(account -> account.getUsername().equals(playerName))
.map(Account::getUuid)
.forEach(cache::remove);
//min one account was found
return affectedRows > 0;
} catch (SQLException ex) {
plugin.getLogger().error("Error deleting user account", ex);
} finally {
closeQuietly(conn);
}
return false;
}
public boolean deleteAccount(UUID uuid) {
Connection conn = null;
try {
conn = getConnection();
PreparedStatement statement = conn.prepareStatement("DELETE FROM " + USERS_TABLE + " WHERE UUID=?");
byte[] mostBytes = Longs.toByteArray(uuid.getMostSignificantBits());
byte[] leastBytes = Longs.toByteArray(uuid.getLeastSignificantBits());
statement.setObject(1, Bytes.concat(mostBytes, leastBytes));
int affectedRows = statement.executeUpdate();
//removes the account from the cache
cache.remove(uuid);
//min one account was found
return affectedRows > 0;
} catch (SQLException sqlEx) {
plugin.getLogger().error("Error deleting user account", sqlEx);
} finally {
closeQuietly(conn);
}
return false;
}
public Account loadAccount(Player player) {
return loadAccount(player.getUniqueId());
}
public Account remove(Player player) {
return cache.remove(player.getUniqueId());
}
public Account loadAccount(UUID uuid) {
Account loadedAccount = null;
Connection conn = null;
try {
conn = getConnection();
PreparedStatement prepareStatement = conn.prepareStatement("SELECT * FROM " + USERS_TABLE
+ " WHERE UUID=?");
byte[] mostBytes = Longs.toByteArray(uuid.getMostSignificantBits());
byte[] leastBytes = Longs.toByteArray(uuid.getLeastSignificantBits());
prepareStatement.setObject(1, Bytes.concat(mostBytes, leastBytes));
ResultSet resultSet = prepareStatement.executeQuery();
if (resultSet.next()) {
loadedAccount = new Account(resultSet);
cache.put(uuid, loadedAccount);
}
} catch (SQLException sqlEx) {
plugin.getLogger().error("Error loading account", sqlEx);
} finally {
closeQuietly(conn);
}
return loadedAccount;
}
public Account loadAccount(String playerName) {
Connection conn = null;
try {
conn = getConnection();
PreparedStatement statement = conn.prepareStatement("SELECT * FROM " + USERS_TABLE + " WHERE Username=?");
statement.setString(1, playerName);
ResultSet resultSet = statement.executeQuery();
if (resultSet.next()) {
Account loadedAccount = new Account(resultSet);
return loadedAccount;
}
} catch (SQLException sqlEx) {
plugin.getLogger().error("Error loading account", sqlEx);
} finally {
closeQuietly(conn);
}
return null;
}
public int getRegistrationsCount(byte[] ip) {
Connection conn = null;
try {
conn = getConnection();
PreparedStatement statement = conn.prepareStatement("SELECT COUNT(*) FROM " + USERS_TABLE + " WHERE IP=?");
statement.setBytes(1, ip);
ResultSet resultSet = statement.executeQuery();
if (resultSet.next()) {
return resultSet.getInt(1);
}
} catch (SQLException sqlEx) {
plugin.getLogger().error("Error loading count of registrations", sqlEx);
} finally {
closeQuietly(conn);
}
return -1;
}
public Account createAccount(Player player, String password) {
Connection conn = null;
try {
conn = getConnection();
PreparedStatement prepareStatement = conn.prepareStatement("INSERT INTO " + USERS_TABLE
+ " (UUID, Username, Password, IP, Email, LastLogin) VALUES (?,?,?,?,?,?)");
UUID uuid = player.getUniqueId();
byte[] mostBytes = Longs.toByteArray(uuid.getMostSignificantBits());
byte[] leastBytes = Longs.toByteArray(uuid.getLeastSignificantBits());
byte[] ip = player.getConnection().getAddress().getAddress().getAddress();
Account account = new Account(uuid, player.getName(), password, ip);
prepareStatement.setObject(1, Bytes.concat(mostBytes, leastBytes));
prepareStatement.setString(2, player.getName());
prepareStatement.setString(3, password);
prepareStatement.setObject(4, ip);
prepareStatement.setString(5, account.getEmail());
prepareStatement.setTimestamp(6, account.getTimestamp());
prepareStatement.execute();
//if successfull
cache.put(uuid, account);
return account;
} catch (SQLException sqlEx) {
plugin.getLogger().error("Error registering account", sqlEx);
} finally {
closeQuietly(conn);
}
return null;
}
protected void closeQuietly(Connection conn) {
if (conn != null) {
try {
//this closes automatically the statement and resultset
conn.close();
} catch (SQLException ex) {
//ingore
}
}
}
public void flushLoginStatus(Account account, boolean loggedIn) {
Connection conn = null;
try {
conn = getConnection();
PreparedStatement prepareStatement = conn.prepareStatement("UPDATE " + USERS_TABLE
+ " SET LoggedIn=? WHERE UUID=?");
prepareStatement.setInt(1, loggedIn ? 1 : 0);
UUID uuid = account.getUuid();
byte[] mostBytes = Longs.toByteArray(uuid.getMostSignificantBits());
byte[] leastBytes = Longs.toByteArray(uuid.getLeastSignificantBits());
prepareStatement.setObject(2, Bytes.concat(mostBytes, leastBytes));
prepareStatement.execute();
} catch (SQLException ex) {
plugin.getLogger().error("Error updating login status", ex);
} finally {
closeQuietly(conn);
}
}
public void close() {
cache.clear();
Connection conn = null;
try {
conn = getConnection();
//set all player accounts existing in the database to unlogged
conn.createStatement().execute("UPDATE " + USERS_TABLE + " SET LoggedIn=0");
} catch (SQLException ex) {
plugin.getLogger().error("Error updating user account", ex);
} finally {
closeQuietly(conn);
}
}
public boolean save(Account account) {
Connection conn = null;
try {
conn = getConnection();
PreparedStatement statement = conn.prepareStatement("UPDATE " + USERS_TABLE
+ " SET Username=?, Password=?, IP=?, LastLogin=?, Email=? WHERE UUID=?");
//username is now changeable by Mojang - so keep it up to date
statement.setString(1, account.getUsername());
statement.setString(2, account.getPassword());
statement.setObject(3, account.getIp());
statement.setTimestamp(4, account.getTimestamp());
statement.setString(5, account.getEmail());
UUID uuid = account.getUuid();
byte[] mostBytes = Longs.toByteArray(uuid.getMostSignificantBits());
byte[] leastBytes = Longs.toByteArray(uuid.getLeastSignificantBits());
statement.setObject(6, Bytes.concat(mostBytes, leastBytes));
statement.execute();
cache.put(uuid, account);
return true;
} catch (SQLException ex) {
plugin.getLogger().error("Error updating user account", ex);
return false;
} finally {
closeQuietly(conn);
}
}
}