/* * Copyright (C) 2020 GeorgH93 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package at.pcgamingfreaks.Minepacks.Bukkit.Database; import at.pcgamingfreaks.Database.ConnectionProvider.ConnectionProvider; import at.pcgamingfreaks.Database.DBTools; import at.pcgamingfreaks.Minepacks.Bukkit.API.Callback; import at.pcgamingfreaks.Minepacks.Bukkit.Backpack; import at.pcgamingfreaks.Minepacks.Bukkit.Minepacks; import at.pcgamingfreaks.Utils; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.NotNull; import java.sql.*; public abstract class SQL extends Database { private final ConnectionProvider dataSource; protected String tablePlayers, tableBackpacks, tableCooldowns; // Table Names protected String fieldPlayerName, fieldPlayerID, fieldPlayerUUID, fieldBpOwner, fieldBpIts, fieldBpVersion, fieldBpLastUpdate, fieldCdPlayer, fieldCdTime; // Table Fields @Language("SQL") protected String queryUpdatePlayerAdd, queryGetPlayerID, queryInsertBp, queryUpdateBp, queryGetBP, queryDeleteOldBackpacks; // DB Querys @Language("SQL") protected String queryDeleteOldCooldowns, querySyncCooldown, queryGetCooldown; // DB Querys protected boolean syncCooldown; public SQL(@NotNull Minepacks plugin, @NotNull ConnectionProvider connectionProvider) { super(plugin); dataSource = connectionProvider; if(!dataSource.isAvailable()) throw new IllegalStateException("Failed to initialize database connection!"); loadSettings(); buildQuerys(); checkDB(); checkUUIDs(); // Check if there are user accounts without UUID // Delete old backpacks if(maxAge > 0) { try(Connection connection = getConnection(); Statement statement = connection.createStatement()) { statement.execute(queryDeleteOldBackpacks); } catch(SQLException e) { e.printStackTrace(); } } // Delete old cooldowns if(syncCooldown) { try(Connection connection = getConnection()) { DBTools.runStatement(connection, queryDeleteOldCooldowns, System.currentTimeMillis()); } catch(SQLException e) { e.printStackTrace(); } } } protected void loadSettings() { // Load table and field names tablePlayers = plugin.getConfiguration().getUserTable(); tableBackpacks = plugin.getConfiguration().getBackpackTable(); tableCooldowns = plugin.getConfiguration().getCooldownTable(); fieldPlayerID = plugin.getConfiguration().getDBFields("User.Player_ID", "id"); fieldPlayerName = plugin.getConfiguration().getDBFields("User.Name", "name"); fieldPlayerUUID = plugin.getConfiguration().getDBFields("User.UUID", "uuid"); fieldBpOwner = plugin.getConfiguration().getDBFields("Backpack.Owner_ID", "owner"); fieldBpIts = plugin.getConfiguration().getDBFields("Backpack.ItemStacks", "its"); fieldBpVersion = plugin.getConfiguration().getDBFields("Backpack.Version", "version"); fieldBpLastUpdate = plugin.getConfiguration().getDBFields("Backpack.LastUpdate", "lastUpdate"); fieldCdPlayer = plugin.getConfiguration().getDBFields("Cooldown.Player_ID", "id"); fieldCdTime = plugin.getConfiguration().getDBFields("Cooldown.Time", "time"); syncCooldown = plugin.getConfiguration().isCommandCooldownSyncEnabled(); } @Override public void close() { super.close(); Utils.blockThread(1); // Give the database some time to perform async operations dataSource.close(); } protected void checkUUIDs() { try(Connection connection = getConnection()) { DBTools.validateUUIDs(plugin.getLogger(), connection, tablePlayers, fieldPlayerName, fieldPlayerUUID, fieldPlayerID, useUUIDSeparators, onlineUUIDs); } catch(SQLException e) { e.printStackTrace(); } } public Connection getConnection() throws SQLException { return dataSource.getConnection(); } protected abstract void checkDB(); protected final void buildQuerys() { // Build the SQL querys with placeholders for the table and field names queryGetBP = "SELECT {FieldBPOwner},{FieldBPITS},{FieldBPVersion} FROM {TableBackpacks} INNER JOIN {TablePlayers} ON {TableBackpacks}.{FieldBPOwner}={TablePlayers}.{FieldPlayerID} WHERE {FieldUUID}=?;"; querySyncCooldown = "INSERT INTO {TableCooldowns} ({FieldCDPlayer},{FieldCDTime}) SELECT {FieldPlayerID},? FROM {TablePlayers} WHERE {FieldUUID}=? ON DUPLICATE KEY UPDATE {FieldCDTime}=?;"; queryUpdatePlayerAdd = "INSERT INTO {TablePlayers} ({FieldName},{FieldUUID}) VALUES (?,?) ON DUPLICATE KEY UPDATE {FieldName}=?;"; queryGetPlayerID = "SELECT {FieldPlayerID} FROM {TablePlayers} WHERE {FieldUUID}=?;"; queryGetCooldown = "SELECT * FROM {TableCooldowns} WHERE {FieldCDPlayer} IN (SELECT {FieldPlayerID} FROM {TablePlayers} WHERE {FieldUUID}=?);"; queryInsertBp = "REPLACE INTO {TableBackpacks} ({FieldBPOwner},{FieldBPITS},{FieldBPVersion}) VALUES (?,?,?);"; queryUpdateBp = "UPDATE {TableBackpacks} SET {FieldBPITS}=?,{FieldBPVersion}=?,{FieldBPLastUpdate}={NOW} WHERE {FieldBPOwner}=?;"; queryDeleteOldBackpacks = "DELETE FROM {TableBackpacks} WHERE {FieldBPLastUpdate} < DATE('now', '-{VarMaxAge} days')"; queryDeleteOldCooldowns = "DELETE FROM {TableCooldowns} WHERE {FieldCDTime} runStatement(query, args)); } protected void runStatement(final String query, final Object... args) { try(Connection connection = getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(query)) { for(int i = 0; args != null && i < args.length; i++) { preparedStatement.setObject(i + 1, args[i]); } preparedStatement.execute(); } catch(SQLException e) { plugin.getLogger().severe("Query: " + query); e.printStackTrace(); } } // Plugin Functions @Override public void updatePlayer(final Player player) { runStatementAsync(queryUpdatePlayerAdd, player.getName(), getPlayerFormattedUUID(player), player.getName()); } @Override public void saveBackpack(final Backpack backpack) { final byte[] data = itsSerializer.serialize(backpack.getInventory()); final int id = backpack.getOwnerID(), usedSerializer = itsSerializer.getUsedSerializer(); final String nameOrUUID = getPlayerFormattedUUID(backpack.getOwner()), name = backpack.getOwner().getName(); Runnable runnable = () -> { try(Connection connection = getConnection()) { if(id <= 0) { try(PreparedStatement ps = connection.prepareStatement(queryGetPlayerID)) { ps.setString(1, nameOrUUID); try(ResultSet rs = ps.executeQuery()) { if(rs.next()) { final int newID = rs.getInt(fieldPlayerID); DBTools.runStatement(connection, queryInsertBp, newID, data, usedSerializer); plugin.getServer().getScheduler().runTask(plugin, () -> backpack.setOwnerID(newID)); } else { plugin.getLogger().warning("Failed saving backpack for: " + name + " (Unable to get players ID from database)"); writeBackup(name, nameOrUUID, usedSerializer, data); } } } } else { DBTools.runStatement(connection, queryUpdateBp, data, usedSerializer, id); } } catch(SQLException e) { plugin.getLogger().warning("Failed to save backpack in database! Error: " + e.getMessage()); e.printStackTrace(); writeBackup(name, nameOrUUID, usedSerializer, data); } }; if(asyncSave) Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable); else runnable.run(); } @Override protected void loadBackpack(final OfflinePlayer player, final Callback callback) { plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { try(Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(queryGetBP)) { ps.setString(1, getPlayerFormattedUUID(player)); final int bpID, version; final byte[] data; try(ResultSet rs = ps.executeQuery()) { if(rs.next()) { bpID = rs.getInt(fieldBpOwner); version = rs.getInt(fieldBpVersion); data = rs.getBytes(fieldBpIts); } else { bpID = -1; version = 0; data = null; } } ItemStack[] its = itsSerializer.deserialize(data, version); final Backpack backpack = (its != null) ? new Backpack(player, its, bpID) : null; plugin.getServer().getScheduler().runTask(plugin, () -> { if(backpack != null) { callback.onResult(backpack); } else { callback.onFail(); } }); } catch(SQLException e) { e.printStackTrace(); plugin.getServer().getScheduler().runTask(plugin, callback::onFail); } }); } @Override public void syncCooldown(Player player, long cooldownTime) { Timestamp ts = new Timestamp(cooldownTime); runStatementAsync(querySyncCooldown, ts, getPlayerFormattedUUID(player), ts); } @Override public void getCooldown(final Player player, final Callback callback) { plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { try(Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(queryGetCooldown)) { ps.setString(1, getPlayerFormattedUUID(player)); try(ResultSet rs = ps.executeQuery()) { final long time = (rs.next()) ? rs.getTimestamp(fieldCdTime).getTime() : 0; plugin.getServer().getScheduler().runTask(plugin, () -> callback.onResult(time)); } } catch(SQLException e) { e.printStackTrace(); plugin.getServer().getScheduler().runTask(plugin, () -> callback.onResult(0L)); } }); } }