package eu.mhsl.minecraft.pixelblocks; import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlock; import eu.mhsl.minecraft.pixelblocks.utils.Direction; import org.bukkit.Bukkit; import org.bukkit.Location; import java.sql.*; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.logging.Level; public class PixelBlockDatabase { private final Connection db; // Alle Statement-Zugriffe nach dem Startup laufen ausschließlich auf diesem Thread, // da die geteilten PreparedStatements nicht threadsicher sind. private final ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "PixelBlocks-Database")); private final PreparedStatement getAllPixelBlocks; private final PreparedStatement deletePixelBlock; private final PreparedStatement insertOrReplacePixelBlock; private record PixelBlockRow( String uuid, String owner, String worldName, double x, double y, double z, String entryWorldName, double entryX, double entryY, double entryZ, String direction ) { } public PixelBlockDatabase(String url) { try { Class.forName("org.sqlite.JDBC"); this.db = DriverManager.getConnection(url); this.db.createStatement().execute( "CREATE TABLE IF NOT EXISTS pixelblocks (" + "uuid CHAR(36) PRIMARY KEY, " + "owner CHAR(36), " + "locationWorldName CHAR(36), " + "locationX DOUBLE, " + "locationY DOUBLE, " + "locationZ DOUBLE, " + "entryLocationWorldName CHAR(36), " + "entryLocationX DOUBLE, " + "entryLocationY DOUBLE, " + "entryLocationZ DOUBLE, " + "direction CHAR(36)" + ")" ); this.deletePixelBlock = this.db.prepareStatement("DELETE FROM pixelblocks WHERE uuid = ?"); this.getAllPixelBlocks = this.db.prepareStatement("SELECT * FROM pixelblocks"); this.insertOrReplacePixelBlock = this.db.prepareStatement( "INSERT OR REPLACE INTO pixelblocks(uuid, owner, " + "locationWorldName, locationX, locationY, locationZ, " + "entryLocationWorldName, entryLocationX, entryLocationY, entryLocationZ, direction) " + "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" ); } catch(SQLException | RuntimeException | ClassNotFoundException e) { throw new RuntimeException("Error while initializing database", e); } } public void close() { this.executor.shutdown(); try { if(!this.executor.awaitTermination(10, TimeUnit.SECONDS)) { Main.logger().warning("Database executor did not terminate in time, forcing shutdown"); this.executor.shutdownNow(); } } catch(InterruptedException e) { Thread.currentThread().interrupt(); this.executor.shutdownNow(); } try { this.deletePixelBlock.close(); this.getAllPixelBlocks.close(); this.insertOrReplacePixelBlock.close(); this.db.close(); } catch(SQLException e) { Main.logger().log(Level.SEVERE, "Failed closing the database", e); } } public void deletePixelBlock(PixelBlock pixelBlock) { String uuid = pixelBlock.getBlockUUID().toString(); this.executor.execute(() -> { try { this.deletePixelBlock.setString(1, uuid); this.deletePixelBlock.executeUpdate(); } catch(SQLException e) { Main.logger().log(Level.SEVERE, String.format("Failed to delete PixelBlock '%s' from the database", uuid), e); } }); } public void savePixelBlock(PixelBlock pixelBlock) { Location blockLocation = pixelBlock.getPixelBlockLocation(); Location entryLocation = pixelBlock.hasLastEntryLocation() ? pixelBlock.getLastEntryLocation() : blockLocation; PixelBlockRow row = new PixelBlockRow( pixelBlock.getBlockUUID().toString(), pixelBlock.getOwnerUUID().toString(), blockLocation.getWorld().getName(), blockLocation.getX(), blockLocation.getY(), blockLocation.getZ(), entryLocation.getWorld().getName(), entryLocation.getX(), entryLocation.getY(), entryLocation.getZ(), pixelBlock.getFacingDirection().toString() ); this.executor.execute(() -> { try { this.insertOrReplacePixelBlock.setString(1, row.uuid()); this.insertOrReplacePixelBlock.setString(2, row.owner()); this.insertOrReplacePixelBlock.setString(3, row.worldName()); this.insertOrReplacePixelBlock.setDouble(4, row.x()); this.insertOrReplacePixelBlock.setDouble(5, row.y()); this.insertOrReplacePixelBlock.setDouble(6, row.z()); this.insertOrReplacePixelBlock.setString(7, row.entryWorldName()); this.insertOrReplacePixelBlock.setDouble(8, row.entryX()); this.insertOrReplacePixelBlock.setDouble(9, row.entryY()); this.insertOrReplacePixelBlock.setDouble(10, row.entryZ()); this.insertOrReplacePixelBlock.setString(11, row.direction()); this.insertOrReplacePixelBlock.executeUpdate(); } catch(SQLException e) { Main.logger().log(Level.SEVERE, String.format("Failed to create or update PixelBlock '%s' in the database", row.uuid()), e); } }); } public void loadPixelBlocks() { try { ResultSet allPixelBlocks = this.getAllPixelBlocks.executeQuery(); while(allPixelBlocks.next()) { String uuid = allPixelBlocks.getString("uuid"); try { Location blockLocation = new Location( Bukkit.getWorld(allPixelBlocks.getString("locationWorldName")), allPixelBlocks.getDouble("locationX"), allPixelBlocks.getDouble("locationY"), allPixelBlocks.getDouble("locationZ") ); Location entryLocation = new Location( Bukkit.getWorld(allPixelBlocks.getString("entryLocationWorldName")), allPixelBlocks.getDouble("entryLocationX"), allPixelBlocks.getDouble("entryLocationY"), allPixelBlocks.getDouble("entryLocationZ") ); Main.pixelBlocks.add(PixelBlock.fromExisting( UUID.fromString(uuid), UUID.fromString(allPixelBlocks.getString("owner")), blockLocation, Direction.valueOf(allPixelBlocks.getString("direction")), entryLocation )); } catch(Exception e) { Main.logger().log(Level.SEVERE, String.format("Failed initializing existing pixelblock '%s'", uuid), e); } } } catch(SQLException e) { throw new RuntimeException("Failed loading PixelBlocks from the database", e); } } }