diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java index 4db4811..db6b791 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java @@ -14,6 +14,7 @@ import java.io.File; import java.util.*; +import java.util.stream.Collectors; /* * I've actually just decided that we're gonna do it this way: @@ -37,10 +38,13 @@ public class JournaledDataHandler implements IClaimChunkDataHandler { @Getter private final File claimChunkDb; private boolean init = false; + private HashMap claimedChunks; private HashMap joinedPlayers; - private HashMap claimRegions; private SqLiteWrapper sqLiteWrapper; + // Don't need this shit actually + // private HashMap claimRegions; + public JournaledDataHandler(@NotNull File claimChunkDb) { this.claimChunkDb = claimChunkDb; } @@ -48,7 +52,8 @@ public JournaledDataHandler(@NotNull File claimChunkDb) { @Override public void init() { joinedPlayers = new HashMap<>(); - claimRegions = new HashMap<>(); + // claimRegions = new HashMap<>(); + claimedChunks = new HashMap<>(); sqLiteWrapper = new SqLiteWrapper(claimChunkDb); init = true; @@ -74,34 +79,40 @@ public void load() throws Exception { @Override public void addClaimedChunk(ChunkPos pos, UUID player) { - // TODO: mutating methods must call sqLiteWrapper methods + DataChunk chunk = new DataChunk(pos, player, new HashMap<>(), false); + claimedChunks.put(pos, chunk); + sqLiteWrapper.addClaimedChunk(chunk); } @Override public void addClaimedChunks(DataChunk[] chunks) { - // TODO: mutating methods must call sqLiteWrapper methods + Arrays.stream(chunks).forEach(chunk -> addClaimedChunk(chunk.chunk, chunk.player)); } @Override public void removeClaimedChunk(ChunkPos pos) { - // TODO: mutating methods must call sqLiteWrapper methods + claimedChunks.remove(pos); + sqLiteWrapper.removeClaimedChunk(pos); } @Override public boolean isChunkClaimed(ChunkPos pos) { - return false; + return claimedChunks.containsKey(pos); } @Override public @Nullable UUID getChunkOwner(ChunkPos pos) { - return null; + DataChunk chunk = claimedChunks.get(pos); + return chunk == null ? null : chunk.player; } @Override public DataChunk[] getClaimedChunks() { - return new DataChunk[0]; + return claimedChunks.values().toArray(new DataChunk[0]); } + // TODO: REMOVE + @Override public boolean toggleTnt(ChunkPos pos) { return false; @@ -112,6 +123,14 @@ public boolean isTntEnabled(ChunkPos pos) { return false; } + // END TODO + + @Override + public void addPlayer(FullPlayerData playerData) { + joinedPlayers.put(playerData.player, playerData); + sqLiteWrapper.addPlayer(playerData); + } + @Override public void addPlayer( UUID player, @@ -120,19 +139,15 @@ public void addPlayer( long lastOnlineTime, boolean alerts, int extraMaxClaims) { - joinedPlayers.put( - player, + addPlayer( new FullPlayerData( player, lastIgn, chunkName, lastOnlineTime, alerts, extraMaxClaims)); - - // TODO: mutating methods must call sqLiteWrapper methods } @Override public void addPlayers(FullPlayerData[] players) { - Arrays.stream(players).forEach(player -> joinedPlayers.put(player.player, player)); - - // TODO: mutating methods must call sqLiteWrapper methods + // this::addPlayer calls SQLite mutation + Arrays.stream(players).forEach(this::addPlayer); } @Override @@ -143,8 +158,9 @@ public void addPlayers(FullPlayerData[] players) { @Override public @Nullable UUID getPlayerUUID(String username) { - // TODO: THIS - + for (FullPlayerData ply : joinedPlayers.values()) { + if (username.equals(ply.lastIgn)) return ply.player; + } return null; } @@ -152,16 +168,14 @@ public void addPlayers(FullPlayerData[] players) { public void setPlayerLastOnline(UUID player, long time) { FullPlayerData ply = joinedPlayers.get(player); if (ply != null) ply.lastOnlineTime = time; - - // TODO: mutating methods must call sqLiteWrapper methods + sqLiteWrapper.setPlayerLastOnline(player, time); } @Override public void setPlayerChunkName(UUID player, @Nullable String name) { FullPlayerData ply = joinedPlayers.get(player); if (ply != null) ply.chunkName = name; - - // TODO: mutating methods must call sqLiteWrapper methods + sqLiteWrapper.setPlayerChunkName(player, name); } @Override @@ -171,63 +185,89 @@ public void setPlayerChunkName(UUID player, @Nullable String name) { } @Override - public void setPlayerReceiveAlerts(UUID player, boolean alerts) { - // TODO: mutating methods must call sqLiteWrapper methods + public void setPlayerReceiveAlerts(UUID player, boolean receiveAlerts) { + FullPlayerData ply = joinedPlayers.get(player); + if (ply != null) ply.alert = receiveAlerts; + sqLiteWrapper.setPlayerReceiveAlerts(player, receiveAlerts); } @Override public boolean getPlayerReceiveAlerts(UUID player) { - return false; + FullPlayerData ply = joinedPlayers.get(player); + return ply != null && ply.alert; } @Override public void setPlayerExtraMaxClaims(UUID player, int maxClaims) { - // TODO: mutating methods must call sqLiteWrapper methods + FullPlayerData ply = joinedPlayers.get(player); + if (ply != null) ply.extraMaxClaims = maxClaims; + sqLiteWrapper.setPlayerExtraMaxClaims(player, maxClaims); } @Override public void addPlayerExtraMaxClaims(UUID player, int numToAdd) { - // TODO: mutating methods must call sqLiteWrapper methods + // This method executes database modification + setPlayerExtraMaxClaims(player, getPlayerExtraMaxClaims(player) + Math.abs(numToAdd)); } @Override public void takePlayerExtraMaxClaims(UUID player, int numToTake) { - // TODO: mutating methods must call sqLiteWrapper methods + // This method executes database modification + setPlayerExtraMaxClaims(player, Math.max(0, getPlayerExtraMaxClaims(player) - numToTake)); } @Override public int getPlayerExtraMaxClaims(UUID player) { + FullPlayerData ply = joinedPlayers.get(player); + if (ply != null) return ply.extraMaxClaims; return 0; } @Override public boolean hasPlayer(UUID player) { - return false; + return joinedPlayers.containsKey(player); } @Override public Collection getPlayers() { - return null; + return joinedPlayers.values().stream() + .map(FullPlayerData::toSimplePlayer) + .collect(Collectors.toCollection(ArrayList::new)); } @Override public FullPlayerData[] getFullPlayerData() { - return new FullPlayerData[0]; + return joinedPlayers.values().toArray(new FullPlayerData[0]); } @Override public void givePlayerAccess( ChunkPos chunk, UUID accessor, ChunkPlayerPermissions permissions) { - // TODO: mutating methods must call sqLiteWrapper methods + DataChunk chunkData = claimedChunks.get(chunk); + if (chunkData != null) { + ChunkPlayerPermissions previousPerms = + chunkData.playerPermissions.put(accessor, permissions); + if (previousPerms == null) { + // Player doesn't already have any access + sqLiteWrapper.addPlayerAccess(chunk, accessor, permissions.permissionFlags); + } else { + // Player has access, we're changing it + sqLiteWrapper.updatePlayerAccess(chunk, accessor, permissions.permissionFlags); + } + } } @Override public void takePlayerAccess(ChunkPos chunk, UUID accessor) { - // TODO: mutating methods must call sqLiteWrapper methods + DataChunk chunkData = claimedChunks.get(chunk); + if (chunkData != null) chunkData.playerPermissions.remove(accessor); + sqLiteWrapper.removePlayerAccess(chunk, accessor); } @Override public Map getPlayersWithAccess(ChunkPos chunk) { + DataChunk chunkData = claimedChunks.get(chunk); + if (chunkData != null) return chunkData.playerPermissions; return null; } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java index f095c91..d797868 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java @@ -1,20 +1,18 @@ package com.cjburkey.claimchunk.data.journaled; -import lombok.Getter; +import com.cjburkey.claimchunk.Utils; +import com.cjburkey.claimchunk.chunk.ChunkPos; +import com.cjburkey.claimchunk.chunk.DataChunk; +import com.cjburkey.claimchunk.player.FullPlayerData; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; +import java.sql.*; +import java.util.UUID; -public class SqLiteWrapper { - - @Getter private final File dbFile; - private Connection connection; +public record SqLiteWrapper(File dbFile) { public SqLiteWrapper(@NotNull File dbFile) { this.dbFile = dbFile; @@ -26,19 +24,294 @@ public SqLiteWrapper(@NotNull File dbFile) { } } - public @NotNull Connection ensureConnection() throws RuntimeException, SQLException { - if (connection != null && !connection.isClosed()) { - return connection; + // -- DATABASE INTEGRATIONS! -- // + + public void addClaimedChunk(DataChunk chunk) { + try (Connection connection = ensureConnection()) { + // Use the nested select query to get the user's row ID as the + // owner's id + try (PreparedStatement statement = + connection.prepareStatement( + """ + INSERT INTO chunk_data ( + chunk_world, + chunk_x, + chunk_z, + owner_id + ) VALUES ( + ?, ?, ?, + (SELECT player_id FROM player_data WHERE player_uuid=?) + ) + """)) { + statement.setString(1, chunk.chunk.world()); + statement.setInt(2, chunk.chunk.x()); + statement.setInt(3, chunk.chunk.z()); + statement.setString(4, chunk.player.toString()); + statement.execute(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to add claimed chunk!", e); + } + } + + public void removeClaimedChunk(ChunkPos chunk) { + try (Connection connection = ensureConnection()) { + // Get chunk ID + final int chunkId; + try (PreparedStatement statement = + connection.prepareStatement( + """ + SELECT chunk_id FROM chunk_data + WHERE chunk_world=? AND chunk_x=? AND chunk_y=? + """)) { + statement.setString(1, chunk.world()); + statement.setInt(2, chunk.x()); + statement.setInt(3, chunk.z()); + ResultSet results = statement.executeQuery(); + chunkId = results.getInt(1); + } + + // Remove granted permissions + try (PreparedStatement statement = + connection.prepareStatement( + """ + DELETE FROM chunk_permissions + WHERE chunk_id=? + """)) { + statement.setInt(1, chunkId); + statement.execute(); + } + + // Remove the chunk + try (PreparedStatement statement = + connection.prepareStatement( + """ + DELETE FROM chunk_data + WHERE chunk_id=? + """)) { + statement.setInt(1, chunkId); + statement.execute(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to remove claimed chunk!", e); + } + } + + // TODO: TEST + public void addPlayer(FullPlayerData playerData) { + try (Connection connection = ensureConnection()) { + try (PreparedStatement statement = + connection.prepareStatement( + """ + INSERT INTO player_data ( + player_uuid, + last_ign, + chunk_name, + last_online_time, + alerts_enabled, + extra_max_claims + ) VALUES (?, ?, ?, ?, ?, ?) + """)) { + statement.setString(1, playerData.player.toString()); + statement.setString(2, playerData.lastIgn); + statement.setString(3, playerData.chunkName); + statement.setLong(4, playerData.lastOnlineTime); + statement.setBoolean(5, playerData.alert); + statement.setInt(6, playerData.extraMaxClaims); + statement.execute(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to add player to data handler!", e); + } + } + + public void setPlayerLastOnline(UUID player, long time) { + try (Connection connection = ensureConnection()) { + // Use the nested select query to get the user's row ID as the + // owner's id + try (PreparedStatement statement = + connection.prepareStatement( + """ + UPDATE player_data + SET last_online_time=? + WHERE player_uuid=? + """)) { + statement.setLong(1, time); + statement.setString(2, player.toString()); + statement.execute(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to set player last online time!", e); + } + } + + public void setPlayerChunkName(UUID player, String chunkName) { + try (Connection connection = ensureConnection()) { + // Use the nested select query to get the user's row ID as the + // owner's id + try (PreparedStatement statement = + connection.prepareStatement( + """ + UPDATE player_data + SET chunk_name=? + WHERE player_uuid=? + """)) { + statement.setString(1, chunkName); + statement.setString(2, player.toString()); + statement.execute(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to set player chunk name!", e); + } + } + + public void setPlayerReceiveAlerts(UUID player, boolean receiveAlerts) { + try (Connection connection = ensureConnection()) { + // Use the nested select query to get the user's row ID as the + // owner's id + try (PreparedStatement statement = + connection.prepareStatement( + """ + UPDATE player_data + SET receiveAlerts=? + WHERE player_uuid=? + """)) { + statement.setBoolean(1, receiveAlerts); + statement.setString(2, player.toString()); + statement.execute(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to enable/disable player alerts!", e); + } + } + + public void setPlayerExtraMaxClaims(UUID player, int extraMaxClaims) { + try (Connection connection = ensureConnection()) { + // Use the nested select query to get the user's row ID as the + // owner's id + try (PreparedStatement statement = + connection.prepareStatement( + """ + UPDATE player_data + SET extra_max_claims=? + WHERE player_uuid=? + """)) { + statement.setInt(1, extraMaxClaims); + statement.setString(2, player.toString()); + statement.execute(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to set player extra max claims!", e); + } + } + + public void addPlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlags) { + try (Connection connection = ensureConnection()) { + try (PreparedStatement statement = + connection.prepareStatement( + """ + INSERT INTO chunk_permissions ( + chunk_id, + other_player_id, + permission_bits + ) VALUES ( + ( + SELECT chunk_id + FROM chunk_data + WHERE chunk_world=? AND chunk_x=? AND chunk_z=? + ), + ( + SELECT player_id + FROM player_data + WHERE player_uuid=? + ), + ? + ) + """)) { + statement.setString(1, chunk.world()); + statement.setInt(2, chunk.x()); + statement.setInt(3, chunk.z()); + statement.setString(4, accessor.toString()); + statement.setInt(5, permissionFlags); + statement.execute(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to add player access!", e); + } + } + + public void updatePlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlags) { + try (Connection connection = ensureConnection()) { + try (PreparedStatement statement = + connection.prepareStatement( + """ + UPDATE chunk_permissions + SET permission_bits=? + WHERE + chunk_id=( + SELECT chunk_id + FROM chunk_data + WHERE chunk_world=? AND chunk_x=? AND chunk_z=? + ) + AND + other_player_id=( + SELECT player_id + FROM player_data + WHERE player_uuid=? + ) + """)) { + statement.setInt(1, permissionFlags); + statement.setString(2, chunk.world()); + statement.setInt(3, chunk.x()); + statement.setInt(4, chunk.z()); + statement.setString(5, accessor.toString()); + statement.execute(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to update player access!", e); } + } + + public void removePlayerAccess(ChunkPos chunk, UUID accessor) { + try (Connection connection = ensureConnection()) { + try (PreparedStatement statement = + connection.prepareStatement( + """ + DELETE FROM chunk_permissions + WHERE + chunk_id=( + SELECT chunk_id + FROM chunk_data + WHERE chunk_world=? AND chunk_x=? AND chunk_z=? + ) + AND + other_player_id=( + SELECT player_id + FROM player_data + WHERE player_uuid=? + ) + """)) { + statement.setString(1, chunk.world()); + statement.setInt(2, chunk.x()); + statement.setInt(3, chunk.z()); + statement.setString(4, accessor.toString()); + statement.execute(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to remove player access!", e); + } + } + + // -- Connection stuff -- // + + public @NotNull Connection ensureConnection() throws RuntimeException, SQLException { try { - if (!dbFile.exists()) { - //noinspection ResultOfMethodCallIgnored - dbFile.createNewFile(); + if (!dbFile.exists() && dbFile.createNewFile()) { + Utils.warn("Created empty database file"); } Class.forName("org.sqlite.JDBC"); - connection = DriverManager.getConnection("jdbc:sqlite:" + dbFile); - return connection; + return DriverManager.getConnection("jdbc:sqlite:" + dbFile); } catch (IOException e) { throw new RuntimeException("Failed to create new file " + dbFile, e); } catch (ClassNotFoundException e) { @@ -56,9 +329,4 @@ public SqLiteWrapper(@NotNull File dbFile) { throw new RuntimeException("SQL Exception", e); } } - - @SuppressWarnings("unused") - public @Nullable Connection getOpenConnection() { - return connection; - } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/newdata/IClaimChunkDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/newdata/IClaimChunkDataHandler.java index 840e1b9..5c60bef 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/IClaimChunkDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/IClaimChunkDataHandler.java @@ -126,7 +126,9 @@ public interface IClaimChunkDataHandler { * @param pos The position of the chunk * @return Whether TNT is now enabled in the provided chunk * @since 0.0.16 + * @deprecated Unused. */ + @Deprecated boolean toggleTnt(ChunkPos pos); /** @@ -136,7 +138,9 @@ public interface IClaimChunkDataHandler { * @param pos The position of the chunk * @return Whether TNT is enabled in the provided chunk * @since 0.0.16 + * @deprecated Unused. */ + @Deprecated boolean isTntEnabled(ChunkPos pos); // -- PLAYERS -- // @@ -185,7 +189,7 @@ default void addPlayer(FullPlayerData playerData) { * @since 0.0.24 */ default void addPlayer(UUID player, String lastIgn, boolean alerts, int defaultMaxClaims) { - this.addPlayer(player, lastIgn, null, 0L, alerts, defaultMaxClaims); + this.addPlayer(player, lastIgn, null, System.currentTimeMillis(), alerts, defaultMaxClaims); } /** diff --git a/src/main/java/com/cjburkey/claimchunk/event/PlayerConnectionHandler.java b/src/main/java/com/cjburkey/claimchunk/event/PlayerConnectionHandler.java index 4908ffc..74d04f3 100644 --- a/src/main/java/com/cjburkey/claimchunk/event/PlayerConnectionHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/event/PlayerConnectionHandler.java @@ -59,5 +59,6 @@ public void onPlayerJoin(PlayerJoinEvent e) { public void onPlayerLeave(PlayerQuitEvent e) { claimChunk.getAdminOverrideHandler().remove(e.getPlayer().getUniqueId()); AutoClaimHandler.disable(e.getPlayer()); + claimChunk.getPlayerHandler().setLastJoinedTime(e.getPlayer().getUniqueId(), System.currentTimeMillis()); } }