From 955944b4228bbf212ba2e2cd92b10499cc11d91c Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Thu, 2 May 2024 20:04:46 -0400 Subject: [PATCH 01/26] Start working on more stable claim data storage system --- .../com/cjburkey/claimchunk/ClaimChunk.java | 3 +- .../claimchunk/chunk/ChunkHandler.java | 36 +--- .../claimchunk/chunk/ChunkOutlineHandler.java | 6 +- .../cjburkey/claimchunk/chunk/ChunkPos.java | 46 +---- .../cjburkey/claimchunk/cmd/MainHandler.java | 10 +- .../data/journaled/ClaimRegion.java | 74 ++++++++ .../data/journaled/JournaledDataHandler.java | 172 ++++++++++++++++++ .../data/journaled/RegionInnerPos.java | 50 +++++ .../claimchunk/data/journaled/RegionPos.java | 44 +++++ .../data/newdata/MySQLDataHandler.java | 71 ++++---- .../event/WorldProfileEventHandler.java | 2 +- .../sub/admin/AdminUnclaimAllCmd.java | 4 +- .../smartcommand/sub/ply/ListCmd.java | 4 +- .../smartcommand/sub/ply/ShowClaimedCmd.java | 4 +- .../smartcommand/sub/ply/UnclaimAllCmd.java | 8 +- 15 files changed, 402 insertions(+), 132 deletions(-) create mode 100644 src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java create mode 100644 src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java create mode 100644 src/main/java/com/cjburkey/claimchunk/data/journaled/RegionInnerPos.java create mode 100644 src/main/java/com/cjburkey/claimchunk/data/journaled/RegionPos.java diff --git a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java index d12dafed..494ff5f9 100644 --- a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java @@ -483,7 +483,7 @@ private void handleAutoUnclaim() { // Unclaim all of the player's chunks for (ChunkPos chunk : claimedChunks) { chunkHandler.unclaimChunk( - getServer().getWorld(chunk.getWorld()), chunk.getX(), chunk.getZ()); + getServer().getWorld(chunk.world()), chunk.x(), chunk.z()); } Utils.log( @@ -635,6 +635,7 @@ public void onDisable() { dataHandler.exit(); Utils.debug("Cleaned up."); } catch (Exception e) { + Utils.err("Failed to clean up data handler!"); //noinspection CallToPrintStackTrace e.printStackTrace(); } diff --git a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkHandler.java b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkHandler.java index d0f39889..0d302183 100644 --- a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkHandler.java @@ -69,10 +69,9 @@ private void claimAll(Collection chunks, UUID player) { * @param x The chunk x-coord. * @param z The chunk z-coord. * @param player The player for whom to claim the chunk. - * @param floodfill Whether or not flood filling should be attempted * @return The chunk position variable or {@code null} if the chunk is already claimed */ - public ChunkPos claimChunk(String world, int x, int z, UUID player, boolean floodfill) { + public ChunkPos claimChunk(String world, int x, int z, UUID player) { if (isClaimed(world, x, z)) { // If the chunk is already claimed, return null return null; @@ -211,21 +210,6 @@ private Map.Entry, FloodClaimResult> fillClaim( return new AbstractMap.SimpleEntry<>(positions, result); } - /** - * Claims a specific chunk for a player if that chunk is not already owned. This method doesn't - * do any checks other than previous ownership. It is not generally safe to use this method. - * Other public API methods should be used to claim chunks. Does not perform flood filling. - * - * @param world The current world. - * @param x The chunk x-coord. - * @param z The chunk z-coord. - * @param player The player for whom to claim the chunk. - * @return The chunk position variable or {@code null} if the chunk is already claimed - */ - public ChunkPos claimChunk(String world, int x, int z, UUID player) { - return claimChunk(world, x, z, player, false); - } - /** * Claims a specific chunk for a player if that chunk is not already owned. This method doesn't * do any checks other than previous ownership. It is not generally safe to use this method. @@ -235,22 +219,6 @@ public ChunkPos claimChunk(String world, int x, int z, UUID player) { * @param x The chunk x-coord. * @param z The chunk z-coord. * @param player The player for whom to claim the chunk. - * @param floodfill Whether or not flood filling should be attempted - * @return The chunk position variable or {@code null} if the chunk is already claimed - */ - public ChunkPos claimChunk(World world, int x, int z, UUID player, boolean floodfill) { - return claimChunk(world.getName(), x, z, player, floodfill); - } - - /** - * Claims a specific chunk for a player if that chunk is not already owned. This method doesn't - * do any checks other than previous ownership. It is not generally safe to use this method. - * Other public API methods should be used to claim chunks. Does not perform flood filling. - * - * @param world The current world. - * @param x The chunk x-coord. - * @param z The chunk z-coord. - * @param player The player for whom to claim the chunk. * @return The chunk position variable or {@code null} if the chunk is already claimed */ public ChunkPos claimChunk(World world, int x, int z, UUID player) { @@ -502,9 +470,11 @@ public boolean toggleTnt(Chunk chunk) { /** * Checks whether TNT is enabled in the provided chunk. * + * @deprecated Must make use of new * @param chunk The Spigot chunk position. * @return Whether TNT is currently enabled in this chunk. */ + @Deprecated public boolean isTntEnabled(Chunk chunk) { return dataHandler.isTntEnabled(new ChunkPos(chunk)); } diff --git a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkOutlineHandler.java b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkOutlineHandler.java index 2a0bd771..9ad8c93e 100644 --- a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkOutlineHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkOutlineHandler.java @@ -100,10 +100,10 @@ private class ChunkOutlineEntry { void onParticle() { // Loop through `yHeight*2+1` y-levels for (var y = plyY - yHeight; y <= plyY + yHeight; y++) { - var zAt = chunkPos.getZ() << 4; + var zAt = chunkPos.z() << 4; // Spawn particles along the x-axis if (sidesShown.north | sidesShown.south) { - for (var x = chunkPos.getX() << 4; x < ((chunkPos.getX() + 1) << 4); x++) { + for (var x = chunkPos.x() << 4; x < ((chunkPos.x() + 1) << 4); x++) { if (sidesShown.north) spawnParticle(player, x, y, zAt); if (sidesShown.south) spawnParticle(player, x, y, zAt + 15); } @@ -113,7 +113,7 @@ void onParticle() { if (sidesShown.east | sidesShown.west) { // TODO: Ignore these offsets for now, overdraw doesn't concern me very much lol for (var z = zAt /* + 1*/; z < zAt + 16 /* - 1*/; z++) { - var xAt = chunkPos.getX() << 4; + var xAt = chunkPos.x() << 4; if (sidesShown.east) spawnParticle(player, xAt, y, z); if (sidesShown.west) spawnParticle(player, xAt + 15, y, z); } diff --git a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPos.java b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPos.java index b37df07a..3b7f9649 100644 --- a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPos.java +++ b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPos.java @@ -4,24 +4,7 @@ import java.util.Objects; -public final class ChunkPos { - - private final String world; - private final int x; - private final int z; - - /** - * Create an instance of a chunk position from raw data. - * - * @param world The name of the world that this chunk is in. - * @param x The x-coordinate of this chunk (in chunk coordinates). - * @param z The y-coordinate of this chunk (in chunk coordinates). - */ - public ChunkPos(String world, int x, int z) { - this.world = world; - this.x = x; - this.z = z; - } +public record ChunkPos(String world, int x, int z) { /** * Create an instance of a chunk position from Spigot's chunk position representation. @@ -32,33 +15,6 @@ public ChunkPos(Chunk chunk) { this(chunk.getWorld().getName(), chunk.getX(), chunk.getZ()); } - /** - * Get the name of the world that this chunk is in. - * - * @return The world name of this chunk. - */ - public String getWorld() { - return world; - } - - /** - * Get the x-coordinate of this chunk. - * - * @return The x-coordinate of this chunk (in chunk coordinates). - */ - public int getX() { - return x; - } - - /** - * Get the y-coordinate of this chunk. - * - * @return The y-coordinate of this chunk (in chunk coordinates). - */ - public int getZ() { - return z; - } - /** * Helper method to get a chunk north, relative to this one. * diff --git a/src/main/java/com/cjburkey/claimchunk/cmd/MainHandler.java b/src/main/java/com/cjburkey/claimchunk/cmd/MainHandler.java index 05f18a43..ad93aaee 100644 --- a/src/main/java/com/cjburkey/claimchunk/cmd/MainHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/cmd/MainHandler.java @@ -56,7 +56,7 @@ public void outlineChunk(ChunkPos chunk, Player showTo, int timeToShow) { List particleLocations = new ArrayList<>(); // The current world - World world = claimChunk.getServer().getWorld(chunk.getWorld()); + World world = claimChunk.getServer().getWorld(chunk.world()); // Make sure the world is valid if (world == null) { return; @@ -66,8 +66,8 @@ public void outlineChunk(ChunkPos chunk, Player showTo, int timeToShow) { int showTimeInSeconds = Utils.clamp(timeToShow, 1, 60); // Get the start position in world coordinates - int xStart = chunk.getX() << 4; - int zStart = chunk.getZ() << 4; + int xStart = chunk.x() << 4; + int zStart = chunk.z() << 4; int yStart = (int) showTo.getLocation().getY() - 1; // The particle effects with be three blocks tall @@ -138,8 +138,8 @@ public void claimChunk(Player p, Chunk loc) { loc.getWorld(), loc.getX(), loc.getZ(), - p.getUniqueId(), - true); + p.getUniqueId() + ); // Error check, though it *shouldn't* occur if (pos == null) { diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java new file mode 100644 index 00000000..c4ad7efd --- /dev/null +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java @@ -0,0 +1,74 @@ +package com.cjburkey.claimchunk.data.journaled; + +import com.cjburkey.claimchunk.Utils; +import com.cjburkey.claimchunk.chunk.ChunkPos; +import com.cjburkey.claimchunk.chunk.DataChunk; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A region of chunk claiming data within the world. Each region is a 16x16 array of claim data for + * each chunk within it. + * + * @since 0.0.24 + */ +public class ClaimRegion { + + public static final int CLAIM_REGION_WIDTH = 16; + + public final RegionPos regionPos; + private final DataChunk[] claims; + + public ClaimRegion(@NotNull RegionPos regionPos) { + this.regionPos = regionPos; + this.claims = new DataChunk[CLAIM_REGION_WIDTH * CLAIM_REGION_WIDTH]; + } + + /** + * Retrieve the claim information for a given chunk within this region. + * + * @param posInRegion The position within the region. + * @return The chunk for the given position, or {@code null} if no data exists for it. + */ + public @Nullable DataChunk getClaimInfo(RegionInnerPos posInRegion) { + return claims[posInRegion.index()]; + } + + /** + * Assign the chunk data at the given position within this region to the provided new chunk + * data. + * + * @param newChunk The ClaimChunk information to insert into this region. + * @return The previous chunk, or {@code null} if no information exists for the given claim OR + * the provided chunk does not exist within this region, i.e. a chunk is being inserted into + * the wrong region, given its position within the world. + */ + public @Nullable DataChunk setClaimInfo(@NotNull DataChunk newChunk) { + ChunkPos chunkPos = newChunk.chunk; + // Return null if the provided chunk is outside of this region. + if (!regionPos.equals(new RegionPos(chunkPos))) { + Utils.warn("Uh oh, the provided chunk at "); + return null; + } + return setClaimInfo(new RegionInnerPos(chunkPos), newChunk); + } + + // Unsafe version of above methods. + // May be exposed later if necessary, but everything should be possible through the safer API + private @Nullable DataChunk setClaimInfo( + @NotNull RegionInnerPos chunkPos, @Nullable DataChunk newChunk) { + int chunkIndex = chunkPos.index(); + DataChunk previousChunk = claims[chunkIndex]; + claims[chunkIndex] = newChunk; + return previousChunk; + } + + /** + * @param chunkPos The position of the chunks whose data we need to remove. + * @return The previous data at this position, or {@code null} if no data existed. + */ + public @Nullable DataChunk removeClaimInfo(@NotNull RegionInnerPos chunkPos) { + return setClaimInfo(chunkPos, null); + } +} diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java new file mode 100644 index 00000000..e9a242e6 --- /dev/null +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java @@ -0,0 +1,172 @@ +package com.cjburkey.claimchunk.data.journaled; + +import com.cjburkey.claimchunk.chunk.ChunkPlayerPermissions; +import com.cjburkey.claimchunk.chunk.ChunkPos; +import com.cjburkey.claimchunk.chunk.DataChunk; +import com.cjburkey.claimchunk.data.newdata.IClaimChunkDataHandler; +import com.cjburkey.claimchunk.player.FullPlayerData; +import com.cjburkey.claimchunk.player.SimplePlayerData; + +import lombok.Getter; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.util.Collection; +import java.util.Map; +import java.util.UUID; + +/** + * The SHINY, NEW........data handler that tries to fix the data loss issues by which this project + * has been plagued since its conception. + * + * @since 0.0.24 + */ +public class JournaledDataHandler implements IClaimChunkDataHandler { + + private boolean init = false; + + @Getter private final File playerDataFile; + @Getter private final File claimRegionsDir; + + public JournaledDataHandler(@NotNull File playerDataFile, @NotNull File claimRegionsDir) { + this.playerDataFile = playerDataFile; + this.claimRegionsDir = claimRegionsDir; + } + + @Override + public void init() { + init = true; + } + + @Override + public boolean getHasInit() { + return init; + } + + @Override + public void exit() {} + + @Override + public void save() throws Exception {} + + @Override + public void load() throws Exception {} + + @Override + public void addClaimedChunk(ChunkPos pos, UUID player) {} + + @Override + public void addClaimedChunks(DataChunk[] chunks) {} + + @Override + public void removeClaimedChunk(ChunkPos pos) {} + + @Override + public boolean isChunkClaimed(ChunkPos pos) { + return false; + } + + @Override + public @Nullable UUID getChunkOwner(ChunkPos pos) { + return null; + } + + @Override + public DataChunk[] getClaimedChunks() { + return new DataChunk[0]; + } + + @Override + public boolean toggleTnt(ChunkPos pos) { + return false; + } + + @Override + public boolean isTntEnabled(ChunkPos pos) { + return false; + } + + @Override + public void addPlayer( + UUID player, + String lastIgn, + @Nullable String chunkName, + long lastOnlineTime, + boolean alerts, + int maxClaims) {} + + @Override + public void addPlayers(FullPlayerData[] players) {} + + @Override + public @Nullable String getPlayerUsername(UUID player) { + return null; + } + + @Override + public @Nullable UUID getPlayerUUID(String username) { + return null; + } + + @Override + public void setPlayerLastOnline(UUID player, long time) {} + + @Override + public void setPlayerChunkName(UUID player, @Nullable String name) {} + + @Override + public @Nullable String getPlayerChunkName(UUID player) { + return null; + } + + @Override + public void setPlayerReceiveAlerts(UUID player, boolean alerts) {} + + @Override + public boolean getPlayerReceiveAlerts(UUID player) { + return false; + } + + @Override + public void setPlayerExtraMaxClaims(UUID player, int maxClaims) {} + + @Override + public void addPlayerExtraMaxClaims(UUID player, int numToAdd) {} + + @Override + public void takePlayerExtraMaxClaims(UUID player, int numToTake) {} + + @Override + public int getPlayerExtraMaxClaims(UUID player) { + return 0; + } + + @Override + public boolean hasPlayer(UUID player) { + return false; + } + + @Override + public Collection getPlayers() { + return null; + } + + @Override + public FullPlayerData[] getFullPlayerData() { + return new FullPlayerData[0]; + } + + @Override + public void givePlayerAccess( + ChunkPos chunk, UUID accessor, ChunkPlayerPermissions permissions) {} + + @Override + public void takePlayerAccess(ChunkPos chunk, UUID accessor) {} + + @Override + public Map getPlayersWithAccess(ChunkPos chunk) { + return null; + } +} diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionInnerPos.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionInnerPos.java new file mode 100644 index 00000000..f505030e --- /dev/null +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionInnerPos.java @@ -0,0 +1,50 @@ +package com.cjburkey.claimchunk.data.journaled; + +import static com.cjburkey.claimchunk.data.journaled.ClaimRegion.CLAIM_REGION_WIDTH; + +import com.cjburkey.claimchunk.chunk.ChunkPos; + +import java.util.Objects; + +/** + * The position of a region, guaranteed to be within the 0 < p < {@code CLAIM_REGION_WIDTH}. + * + * @since 0.0.24 + */ +public class RegionInnerPos { + + /** The X position of the region. */ + public final int x; + + /** The Y position of the region. */ + public final int z; + + public RegionInnerPos(int chunkX, int chunkZ) { + this.x = Math.floorMod(chunkX, CLAIM_REGION_WIDTH); + this.z = Math.floorMod(chunkZ, CLAIM_REGION_WIDTH); + } + + public RegionInnerPos(ChunkPos chunkPos) { + this(chunkPos.x(), chunkPos.z()); + } + + /** + * @return the unique index for this position in a {@code CLAIM_REGION_WIDTH}^2 sized array. + */ + public int index() { + return x * CLAIM_REGION_WIDTH + z; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RegionInnerPos that = (RegionInnerPos) o; + return x == that.x && z == that.z; + } + + @Override + public int hashCode() { + return Objects.hash(x, z); + } +} diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionPos.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionPos.java new file mode 100644 index 00000000..07f03b47 --- /dev/null +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionPos.java @@ -0,0 +1,44 @@ +package com.cjburkey.claimchunk.data.journaled; + +import static com.cjburkey.claimchunk.data.journaled.ClaimRegion.CLAIM_REGION_WIDTH; + +import com.cjburkey.claimchunk.chunk.ChunkPos; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * The position of a claim data region within a world. + * + * @since 0.0.24 + */ +public record RegionPos(String worldName, int x, int y) { + + public RegionPos(@NotNull ChunkPos chunkPos) { + this( + chunkPos.world(), + Math.floorDiv(chunkPos.x(), CLAIM_REGION_WIDTH), + Math.floorDiv(chunkPos.z(), CLAIM_REGION_WIDTH)); + } + + @Override + public String toString() { + return String.format("%s, %s in %s", x, y, worldName); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RegionPos regionPos = (RegionPos) o; + return x == regionPos.x + && y == regionPos.y + && Objects.equals(worldName, regionPos.worldName); + } + + @Override + public int hashCode() { + return Objects.hash(worldName, x, y); + } +} diff --git a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java index 469d89f1..4b578984 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java @@ -155,9 +155,9 @@ public void addClaimedChunk(ChunkPos pos, UUID player) { CLAIMED_CHUNKS_Z, CLAIMED_CHUNKS_OWNER); try (PreparedStatement statement = prep(claimChunk, connection, sql)) { - statement.setString(1, pos.getWorld()); - statement.setInt(2, pos.getX()); - statement.setInt(3, pos.getZ()); + statement.setString(1, pos.world()); + statement.setInt(2, pos.x()); + statement.setInt(3, pos.z()); statement.setString(4, player.toString()); statement.execute(); } catch (Exception e) { @@ -187,9 +187,9 @@ public void addClaimedChunks(DataChunk[] chunks) { try (PreparedStatement statement = prep(claimChunk, connection, sql.toString())) { int i = 0; for (DataChunk chunk : chunks) { - statement.setString(4 * i + 1, chunk.chunk.getWorld()); - statement.setInt(4 * i + 2, chunk.chunk.getX()); - statement.setInt(4 * i + 3, chunk.chunk.getZ()); + statement.setString(4 * i + 1, chunk.chunk.world()); + statement.setInt(4 * i + 2, chunk.chunk.x()); + statement.setInt(4 * i + 3, chunk.chunk.z()); statement.setString(4 * i + 4, chunk.player.toString()); i++; } @@ -212,9 +212,9 @@ public void removeClaimedChunk(ChunkPos pos) { CLAIMED_CHUNKS_X, CLAIMED_CHUNKS_Z); try (PreparedStatement statement = prep(claimChunk, connection, sql)) { - statement.setString(1, pos.getWorld()); - statement.setInt(2, pos.getX()); - statement.setInt(3, pos.getZ()); + statement.setString(1, pos.world()); + statement.setInt(2, pos.x()); + statement.setInt(3, pos.z()); statement.execute(); } catch (Exception e) { Utils.err("Failed to unclaim chunk: %s", e.getMessage()); @@ -233,9 +233,9 @@ public boolean isChunkClaimed(ChunkPos pos) { CLAIMED_CHUNKS_X, CLAIMED_CHUNKS_Z); try (PreparedStatement statement = prep(claimChunk, connection, sql)) { - statement.setString(1, pos.getWorld()); - statement.setInt(2, pos.getX()); - statement.setInt(3, pos.getZ()); + statement.setString(1, pos.world()); + statement.setInt(2, pos.x()); + statement.setInt(3, pos.z()); try (ResultSet result = statement.executeQuery()) { if (result.next()) return result.getInt(1) > 0; } @@ -259,9 +259,9 @@ public UUID getChunkOwner(ChunkPos pos) { CLAIMED_CHUNKS_X, CLAIMED_CHUNKS_Z); try (PreparedStatement statement = prep(claimChunk, connection, sql)) { - statement.setString(1, pos.getWorld()); - statement.setInt(2, pos.getX()); - statement.setInt(3, pos.getZ()); + statement.setString(1, pos.world()); + statement.setInt(2, pos.x()); + statement.setInt(3, pos.z()); try (ResultSet result = statement.executeQuery()) { if (result.next()) return UUID.fromString(result.getString(1)); } @@ -321,9 +321,9 @@ public boolean toggleTnt(ChunkPos pos) { CLAIMED_CHUNKS_Z); try (PreparedStatement statement = prep(claimChunk, connection, sql)) { statement.setBoolean(1, !current); - statement.setString(2, pos.getWorld()); - statement.setInt(3, pos.getX()); - statement.setInt(4, pos.getZ()); + statement.setString(2, pos.world()); + statement.setInt(3, pos.x()); + statement.setInt(4, pos.z()); statement.execute(); return !current; } catch (Exception e) { @@ -345,9 +345,9 @@ public boolean isTntEnabled(ChunkPos pos) { CLAIMED_CHUNKS_X, CLAIMED_CHUNKS_Z); try (PreparedStatement statement = prep(claimChunk, connection, sql)) { - statement.setString(1, pos.getWorld()); - statement.setInt(2, pos.getX()); - statement.setInt(3, pos.getZ()); + statement.setString(1, pos.world()); + statement.setInt(2, pos.x()); + statement.setInt(3, pos.z()); try (ResultSet result = statement.executeQuery()) { if (result.next()) return result.getBoolean(1); } @@ -370,7 +370,7 @@ public void addPlayer( String sql = String.format( "INSERT INTO `%s` (`%s`, `%s`, `%s`, `%s`, `%s`, `%s`) VALUES (?, ?, ?, ?," - + " ?)", + + " ?)", PLAYERS_TABLE_NAME, PLAYERS_UUID, PLAYERS_IGN, @@ -393,6 +393,7 @@ public void addPlayer( } } + @SuppressWarnings("ExtractMethodRecommender") @Override public void addPlayers(FullPlayerData[] players) { if (players.length == 0) return; @@ -579,6 +580,7 @@ public void setPlayerExtraMaxClaims(UUID player, int extraMaxClaims) { } } + @SuppressWarnings("DuplicatedCode") @Override public void addPlayerExtraMaxClaims(UUID player, int numToAdd) { String sql = @@ -596,6 +598,7 @@ public void addPlayerExtraMaxClaims(UUID player, int numToAdd) { } } + @SuppressWarnings("DuplicatedCode") @Override public void takePlayerExtraMaxClaims(UUID player, int numToTake) { // Ugly but idk how to do this in sql :( @@ -724,9 +727,9 @@ public void givePlayerAccess( CLAIMED_CHUNKS_X, CLAIMED_CHUNKS_Z); try (PreparedStatement chunkIdStatement = prep(claimChunk, connection, getChunkIdSql)) { - chunkIdStatement.setString(1, chunk.getWorld()); - chunkIdStatement.setInt(2, chunk.getX()); - chunkIdStatement.setInt(3, chunk.getZ()); + chunkIdStatement.setString(1, chunk.world()); + chunkIdStatement.setInt(2, chunk.x()); + chunkIdStatement.setInt(3, chunk.z()); try (ResultSet result = chunkIdStatement.executeQuery()) { if (result.next()) { @@ -835,9 +838,9 @@ public void writeAccessAssociationsBulk(DataChunk[] chunks) { for (DataChunk c : chunks) { for (Map.Entry entry : c.playerPermissions.entrySet()) { - statement.setString(6 * i + 1, c.chunk.getWorld()); - statement.setInt(6 * i + 2, c.chunk.getX()); - statement.setInt(6 * i + 3, c.chunk.getZ()); + statement.setString(6 * i + 1, c.chunk.world()); + statement.setInt(6 * i + 2, c.chunk.x()); + statement.setInt(6 * i + 3, c.chunk.z()); statement.setString(6 * i + 4, c.player.toString()); statement.setString(6 * i + 5, entry.getKey().toString()); statement.setInt(6 * i + 6, entry.getValue().getPermissionFlags()); @@ -867,9 +870,9 @@ public void takePlayerAccess(ChunkPos chunk, UUID accessor) { CLAIMED_CHUNKS_Z); try (PreparedStatement chunkIdStatement = prep(claimChunk, connection, getChunkIdSql)) { - chunkIdStatement.setString(1, chunk.getWorld()); - chunkIdStatement.setInt(2, chunk.getX()); - chunkIdStatement.setInt(3, chunk.getZ()); + chunkIdStatement.setString(1, chunk.world()); + chunkIdStatement.setInt(2, chunk.x()); + chunkIdStatement.setInt(3, chunk.z()); try (ResultSet chunkIdResult = chunkIdStatement.executeQuery()) { if (chunkIdResult.next()) { @@ -914,9 +917,9 @@ public Map getPlayersWithAccess(ChunkPos chunk) { CLAIMED_CHUNKS_Z); try (PreparedStatement statement = prep(claimChunk, connection, getPlayerPermsSql)) { - statement.setString(1, chunk.getWorld()); - statement.setInt(2, chunk.getX()); - statement.setInt(3, chunk.getZ()); + statement.setString(1, chunk.world()); + statement.setInt(2, chunk.x()); + statement.setInt(3, chunk.z()); try (ResultSet result = statement.executeQuery()) { Map playerPermissions = new HashMap<>(); diff --git a/src/main/java/com/cjburkey/claimchunk/event/WorldProfileEventHandler.java b/src/main/java/com/cjburkey/claimchunk/event/WorldProfileEventHandler.java index db0ef5ec..4bf8d864 100644 --- a/src/main/java/com/cjburkey/claimchunk/event/WorldProfileEventHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/event/WorldProfileEventHandler.java @@ -148,7 +148,7 @@ public void onBlockPlace(BlockPlaceEvent event) { BlockAccess.BlockAccessType.PLACE); } - /** Event handler for when a player right clicks on a block. */ + /** Event handler for when a player right-clicks on a block. */ @EventHandler public void onBlockInteraction(PlayerInteractEvent event) { if (event != null diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/admin/AdminUnclaimAllCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/admin/AdminUnclaimAllCmd.java index 8f9335bc..ffaf2b79 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/admin/AdminUnclaimAllCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/admin/AdminUnclaimAllCmd.java @@ -52,8 +52,8 @@ public boolean onCall(@NotNull String cmdUsed, @NotNull CommandSender executor, var claimedChunks = chunkHandler.getClaimedChunks(ply); int unclaimed = 0; for (var chunk : claimedChunks) { - if (allWorlds || player.getWorld().getName().equals(chunk.getWorld())) { - chunkHandler.unclaimChunk(chunk.getWorld(), chunk.getX(), chunk.getZ()); + if (allWorlds || player.getWorld().getName().equals(chunk.world())) { + chunkHandler.unclaimChunk(chunk.world(), chunk.x(), chunk.z()); unclaimed++; } } diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ListCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ListCmd.java index 88aca86e..e945c790 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ListCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ListCmd.java @@ -91,8 +91,8 @@ public boolean onCall(@NotNull String cmdUsed, @NotNull CommandSender executor, + claimChunk .getMessages() .claimsChunk - .replace("%%X%%", "" + (chunks[i].getX() << 4)) - .replace("%%Z%%", "" + (chunks[i].getZ() << 4))); + .replace("%%X%%", "" + (chunks[i].x() << 4)) + .replace("%%Z%%", "" + (chunks[i].z() << 4))); } return true; } diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ShowClaimedCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ShowClaimedCmd.java index 9415064d..038612d0 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ShowClaimedCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/ShowClaimedCmd.java @@ -69,8 +69,8 @@ public boolean onCall(@NotNull String cmdUsed, @NotNull CommandSender executor, // Create a set of this player's claimed chunks within the given radius HashSet claimedChunks = new HashSet<>(); - for (var x = chunkPos.getX() - radius; x <= chunkPos.getX() + radius; x++) { - for (var z = chunkPos.getZ() - radius; z <= chunkPos.getZ() + radius; z++) { + for (var x = chunkPos.x() - radius; x <= chunkPos.x() + radius; x++) { + for (var z = chunkPos.z() - radius; z <= chunkPos.z() + radius; z++) { if (claimChunk .getChunkHandler() .isOwner(player.getWorld(), x, z, player.getUniqueId())) { diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/UnclaimAllCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/UnclaimAllCmd.java index 7a643ed9..26c77563 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/UnclaimAllCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/UnclaimAllCmd.java @@ -42,16 +42,16 @@ public boolean onCall(@NotNull String cmdUsed, @NotNull CommandSender executor, var claimedChunks = chunkHandler.getClaimedChunks(player.getUniqueId()); int unclaimed = 0; for (var chunk : claimedChunks) { - if ((allWorlds || player.getWorld().getName().equals(chunk.getWorld())) + if ((allWorlds || player.getWorld().getName().equals(chunk.world())) && claimChunk .getMainHandler() .unclaimChunk( false, true, player, - chunk.getWorld(), - chunk.getX(), - chunk.getZ())) { + chunk.world(), + chunk.x(), + chunk.z())) { unclaimed++; } } From d5933507e36541b0dfdd69713ac8fe14f749f7aa Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Fri, 3 May 2024 14:09:09 -0400 Subject: [PATCH 02/26] Minor cleanup --- .../chunk/ChunkPlayerPermissions.java | 119 ++++++++---------- .../data/newdata/IClaimChunkDataHandler.java | 2 +- .../claimchunk/player/PlayerHandler.java | 2 +- 3 files changed, 51 insertions(+), 72 deletions(-) diff --git a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java index 218e16a7..9b9f3976 100644 --- a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java +++ b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java @@ -1,16 +1,29 @@ package com.cjburkey.claimchunk.chunk; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; public class ChunkPlayerPermissions { + public static final class Masks { + public static int BREAK = 1; + public static int PLACE = 1 << 1; + public static int DOOR = 1 << 2; + public static int REDSTONE = 1 << 3; + public static int VEHICLE = 1 << 4; + public static int INTERACT_ENTITY = 1 << 5; + public static int INTERACT_BLOCK = 1 << 6; + public static int CONTAINERS = 1 << 7; + } + /** * The flags for each permission. These are combined into a single integer to save space in the * file. Format (from Rightmost (i.e. least significant) bit to leftmost): Break, Place, Doors, * Redstone, Ride Boats/Minecarts, interact with entities, interact with blocks, open containers */ - private int permissionFlags; + public int permissionFlags; public ChunkPlayerPermissions() { permissionFlags = 0; @@ -20,104 +33,69 @@ public ChunkPlayerPermissions(final int permissionFlags) { this.permissionFlags = permissionFlags; } - public boolean canBreak() { - return (permissionFlags & 1) == 1; + public boolean checkMask(int mask) { + return (permissionFlags & mask) == mask; } + private void setAllow(int mask, boolean allow) { + if (allow) permissionFlags |= mask; + else permissionFlags &= ~mask; + } + + public boolean canBreak() { + return checkMask(Masks.BREAK); + } public void allowBreak(final boolean allow) { - if (allow) { - permissionFlags |= 1; - } else { - permissionFlags &= ~1; - } + setAllow(Masks.BREAK, allow); } public boolean canPlace() { - return (permissionFlags & 2) == 2; + return checkMask(Masks.PLACE); } - public void allowPlace(final boolean allow) { - if (allow) { - permissionFlags |= 2; - } else { - permissionFlags &= ~2; - } + setAllow(Masks.PLACE, allow); } public boolean canUseDoors() { - return (permissionFlags & 4) == 4; + return checkMask(Masks.DOOR); } - public void allowUseDoors(final boolean allow) { - if (allow) { - permissionFlags |= 4; - } else { - permissionFlags &= ~4; - } + setAllow(Masks.DOOR, allow); } public boolean canUseRedstone() { - return (permissionFlags & 8) == 8; + return checkMask(Masks.REDSTONE); } - public void allowUseRedstone(final boolean allow) { - if (allow) { - permissionFlags |= 8; - } else { - permissionFlags &= ~8; - } + setAllow(Masks.REDSTONE, allow); } public boolean canUseVehicles() { - return (permissionFlags & 16) == 16; + return checkMask(Masks.VEHICLE); } - public void allowUseVehicles(final boolean allow) { - if (allow) { - permissionFlags |= 16; - } else { - permissionFlags &= ~16; - } + setAllow(Masks.VEHICLE, allow); } public boolean canInteractEntities() { - return (permissionFlags & 32) == 32; + return checkMask(Masks.INTERACT_ENTITY); } - public void allowInteractEntities(final boolean allow) { - if (allow) { - permissionFlags |= 32; - } else { - permissionFlags &= ~32; - } + setAllow(Masks.INTERACT_ENTITY, allow); } public boolean canInteractBlocks() { - return (permissionFlags & 64) == 64; + return checkMask(Masks.INTERACT_BLOCK); } - public void allowInteractBlocks(final boolean allow) { - if (allow) { - permissionFlags |= 64; - } else { - permissionFlags &= ~64; - } + setAllow(Masks.INTERACT_BLOCK, allow); } public boolean canUseContainers() { - return (permissionFlags & 128) == 128; + return checkMask(Masks.CONTAINERS); } - public void allowUseContainers(final boolean allow) { - if (allow) { - permissionFlags |= 128; - } else { - permissionFlags &= ~128; - } - } - - public int getPermissionFlags() { - return permissionFlags; + setAllow(Masks.CONTAINERS, allow); } public Map toPermissionsMap() { @@ -135,21 +113,22 @@ public Map toPermissionsMap() { return permissionsMap; } - public static ChunkPlayerPermissions fromPermissionsMap(Map permissions) { + public static @NotNull ChunkPlayerPermissions fromPermissionsMap(@NotNull Map permissions) { ChunkPlayerPermissions chunkPlayerPermissions = new ChunkPlayerPermissions(); for (Map.Entry perm : permissions.entrySet()) { + boolean permVal = perm.getValue(); switch (perm.getKey()) { - case "break" -> chunkPlayerPermissions.allowBreak(perm.getValue()); - case "place" -> chunkPlayerPermissions.allowPlace(perm.getValue()); - case "doors" -> chunkPlayerPermissions.allowUseDoors(perm.getValue()); - case "redstone" -> chunkPlayerPermissions.allowUseRedstone(perm.getValue()); - case "interactVehicles" -> chunkPlayerPermissions.allowUseVehicles(perm.getValue()); + case "break" -> chunkPlayerPermissions.allowBreak(permVal); + case "place" -> chunkPlayerPermissions.allowPlace(permVal); + case "doors" -> chunkPlayerPermissions.allowUseDoors(permVal); + case "redstone" -> chunkPlayerPermissions.allowUseRedstone(permVal); + case "interactVehicles" -> chunkPlayerPermissions.allowUseVehicles(permVal); case "interactEntities" -> - chunkPlayerPermissions.allowInteractEntities(perm.getValue()); + chunkPlayerPermissions.allowInteractEntities(permVal); case "interactBlocks" -> - chunkPlayerPermissions.allowInteractBlocks(perm.getValue()); - case "useContainers" -> chunkPlayerPermissions.allowUseContainers(perm.getValue()); + chunkPlayerPermissions.allowInteractBlocks(permVal); + case "useContainers" -> chunkPlayerPermissions.allowUseContainers(permVal); } } 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 06790014..840e1b9a 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/IClaimChunkDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/IClaimChunkDataHandler.java @@ -289,7 +289,7 @@ default void addPlayer(UUID player, String lastIgn, boolean alerts, int defaultM * Clamp to 0 * * @param player The player's UUID - * @param numToAdd Number of claims to add + * @param numToTake Number of claims to add * @since 0.0.24 */ void takePlayerExtraMaxClaims(UUID player, int numToTake); diff --git a/src/main/java/com/cjburkey/claimchunk/player/PlayerHandler.java b/src/main/java/com/cjburkey/claimchunk/player/PlayerHandler.java index 64ed899f..0c722d86 100644 --- a/src/main/java/com/cjburkey/claimchunk/player/PlayerHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/player/PlayerHandler.java @@ -73,7 +73,7 @@ public Map> getAllPlayerPermissions(ChunkPos chunk) { } public void changePermissions(ChunkPos chunk, UUID accessor, Map permissions) { - if (permissions.values().stream().allMatch(v -> v == false)) { + if (permissions.values().stream().noneMatch(v -> v)) { // All permissions are false, so remove the accessor's access entirely dataHandler.takePlayerAccess(chunk, accessor); } else { From 2b48b5e17fbeb21f2d1c2834d273b5b290e1121a Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Fri, 3 May 2024 15:28:26 -0400 Subject: [PATCH 03/26] Quick fix --- .../chunk/ChunkPlayerPermissions.java | 17 ++++++++++++----- .../cjburkey/claimchunk/cmd/MainHandler.java | 3 +-- .../data/newdata/MySQLDataHandler.java | 8 ++++---- .../smartcommand/sub/ply/UnclaimAllCmd.java | 7 +------ 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java index 9b9f3976..7d75b089 100644 --- a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java +++ b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java @@ -45,6 +45,7 @@ private void setAllow(int mask, boolean allow) { public boolean canBreak() { return checkMask(Masks.BREAK); } + public void allowBreak(final boolean allow) { setAllow(Masks.BREAK, allow); } @@ -52,6 +53,7 @@ public void allowBreak(final boolean allow) { public boolean canPlace() { return checkMask(Masks.PLACE); } + public void allowPlace(final boolean allow) { setAllow(Masks.PLACE, allow); } @@ -59,6 +61,7 @@ public void allowPlace(final boolean allow) { public boolean canUseDoors() { return checkMask(Masks.DOOR); } + public void allowUseDoors(final boolean allow) { setAllow(Masks.DOOR, allow); } @@ -66,6 +69,7 @@ public void allowUseDoors(final boolean allow) { public boolean canUseRedstone() { return checkMask(Masks.REDSTONE); } + public void allowUseRedstone(final boolean allow) { setAllow(Masks.REDSTONE, allow); } @@ -73,6 +77,7 @@ public void allowUseRedstone(final boolean allow) { public boolean canUseVehicles() { return checkMask(Masks.VEHICLE); } + public void allowUseVehicles(final boolean allow) { setAllow(Masks.VEHICLE, allow); } @@ -80,6 +85,7 @@ public void allowUseVehicles(final boolean allow) { public boolean canInteractEntities() { return checkMask(Masks.INTERACT_ENTITY); } + public void allowInteractEntities(final boolean allow) { setAllow(Masks.INTERACT_ENTITY, allow); } @@ -87,6 +93,7 @@ public void allowInteractEntities(final boolean allow) { public boolean canInteractBlocks() { return checkMask(Masks.INTERACT_BLOCK); } + public void allowInteractBlocks(final boolean allow) { setAllow(Masks.INTERACT_BLOCK, allow); } @@ -94,6 +101,7 @@ public void allowInteractBlocks(final boolean allow) { public boolean canUseContainers() { return checkMask(Masks.CONTAINERS); } + public void allowUseContainers(final boolean allow) { setAllow(Masks.CONTAINERS, allow); } @@ -113,7 +121,8 @@ public Map toPermissionsMap() { return permissionsMap; } - public static @NotNull ChunkPlayerPermissions fromPermissionsMap(@NotNull Map permissions) { + public static @NotNull ChunkPlayerPermissions fromPermissionsMap( + @NotNull Map permissions) { ChunkPlayerPermissions chunkPlayerPermissions = new ChunkPlayerPermissions(); for (Map.Entry perm : permissions.entrySet()) { @@ -124,10 +133,8 @@ public Map toPermissionsMap() { case "doors" -> chunkPlayerPermissions.allowUseDoors(permVal); case "redstone" -> chunkPlayerPermissions.allowUseRedstone(permVal); case "interactVehicles" -> chunkPlayerPermissions.allowUseVehicles(permVal); - case "interactEntities" -> - chunkPlayerPermissions.allowInteractEntities(permVal); - case "interactBlocks" -> - chunkPlayerPermissions.allowInteractBlocks(permVal); + case "interactEntities" -> chunkPlayerPermissions.allowInteractEntities(permVal); + case "interactBlocks" -> chunkPlayerPermissions.allowInteractBlocks(permVal); case "useContainers" -> chunkPlayerPermissions.allowUseContainers(permVal); } } diff --git a/src/main/java/com/cjburkey/claimchunk/cmd/MainHandler.java b/src/main/java/com/cjburkey/claimchunk/cmd/MainHandler.java index ad93aaee..b7d2ddb1 100644 --- a/src/main/java/com/cjburkey/claimchunk/cmd/MainHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/cmd/MainHandler.java @@ -138,8 +138,7 @@ public void claimChunk(Player p, Chunk loc) { loc.getWorld(), loc.getX(), loc.getZ(), - p.getUniqueId() - ); + p.getUniqueId()); // Error check, though it *shouldn't* occur if (pos == null) { diff --git a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java index 4b578984..df0a0513 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java @@ -760,7 +760,7 @@ public void givePlayerAccess( ACCESS_ACCESS_ID); try (PreparedStatement updateStatement = prep(claimChunk, connection, updateStatementSql)) { - updateStatement.setInt(1, permissions.getPermissionFlags()); + updateStatement.setInt(1, permissions.permissionFlags); updateStatement.setInt(2, result.getInt(1)); updateStatement.execute(); @@ -782,7 +782,7 @@ public void givePlayerAccess( insertStatement.setInt(1, chunkId); insertStatement.setString(2, chunkOwner); insertStatement.setString(3, accessor.toString()); - insertStatement.setInt(4, permissions.getPermissionFlags()); + insertStatement.setInt(4, permissions.permissionFlags); insertStatement.execute(); } @@ -843,8 +843,8 @@ public void writeAccessAssociationsBulk(DataChunk[] chunks) { statement.setInt(6 * i + 3, c.chunk.z()); statement.setString(6 * i + 4, c.player.toString()); statement.setString(6 * i + 5, entry.getKey().toString()); - statement.setInt(6 * i + 6, entry.getValue().getPermissionFlags()); - statement.setInt(6 * i + 6, entry.getValue().getPermissionFlags()); + statement.setInt(6 * i + 6, entry.getValue().permissionFlags); + statement.setInt(6 * i + 6, entry.getValue().permissionFlags); i++; } } diff --git a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/UnclaimAllCmd.java b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/UnclaimAllCmd.java index 26c77563..2e7edbda 100644 --- a/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/UnclaimAllCmd.java +++ b/src/main/java/com/cjburkey/claimchunk/smartcommand/sub/ply/UnclaimAllCmd.java @@ -46,12 +46,7 @@ public boolean onCall(@NotNull String cmdUsed, @NotNull CommandSender executor, && claimChunk .getMainHandler() .unclaimChunk( - false, - true, - player, - chunk.world(), - chunk.x(), - chunk.z())) { + false, true, player, chunk.world(), chunk.x(), chunk.z())) { unclaimed++; } } From 5a514c036a080a9a67cc7af63cffe9358de319bf Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Fri, 3 May 2024 15:31:05 -0400 Subject: [PATCH 04/26] Update to new chunk pos record field accessors --- .../java/com/cjburkey/claimchunk/chunk/ChunkHandler.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkHandler.java b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkHandler.java index 55e670f8..cef9bc57 100644 --- a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkHandler.java @@ -10,7 +10,6 @@ import org.bukkit.entity.Player; import java.util.*; -import java.util.stream.Collectors; // TODO: MOVE FLOOD FILL TO MAIN HANDLER AND REQUIRE RECIPIENT TO BE ONLINE TO // GUARANTEE QUOTA ISN'T EXCEEDED. @@ -300,9 +299,9 @@ public int deleteAllWorldClaims(String worldName) { List chunks = Arrays.stream(dataHandler.getClaimedChunks()) .map(c -> c.chunk) - .filter(pos -> pos.getWorld().equals(worldName)) - .collect(Collectors.toList()); - chunks.forEach(pos -> unclaimChunk(worldName, pos.getX(), pos.getZ())); + .filter(pos -> pos.world().equals(worldName)) + .toList(); + chunks.forEach(pos -> unclaimChunk(worldName, pos.x(), pos.z())); return chunks.size(); } From b429fde45cb3acbd6fa09000dbd67c8ca35fdbc2 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Tue, 7 May 2024 01:58:24 -0400 Subject: [PATCH 05/26] Increment version --- .../claimchunk/data/journaled/ClaimRegion.java | 4 ++-- .../data/journaled/JournaledDataHandler.java | 17 +++++++++++------ .../data/journaled/RegionInnerPos.java | 2 +- .../claimchunk/data/journaled/RegionPos.java | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java index c4ad7efd..2b110188 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java @@ -11,7 +11,7 @@ * A region of chunk claiming data within the world. Each region is a 16x16 array of claim data for * each chunk within it. * - * @since 0.0.24 + * @since 0.0.25 */ public class ClaimRegion { @@ -48,7 +48,7 @@ public ClaimRegion(@NotNull RegionPos regionPos) { ChunkPos chunkPos = newChunk.chunk; // Return null if the provided chunk is outside of this region. if (!regionPos.equals(new RegionPos(chunkPos))) { - Utils.warn("Uh oh, the provided chunk at "); + Utils.warn("Uh oh, the provided chunk at %s isn't within the region %s".formatted(chunkPos, regionPos)); return null; } return setClaimInfo(new RegionInnerPos(chunkPos), newChunk); 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 e9a242e6..8160d8b5 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java @@ -20,19 +20,24 @@ /** * The SHINY, NEW........data handler that tries to fix the data loss issues by which this project * has been plagued since its conception. + *

+ * I've actually just decided that we're gonna do it this way: + * - SQLite backing database similar to current MySQL integration (which will + * be removed and automatically converted). + * - Keep some regions in memory and unload when no players are within them + * for a minute or two. + * I hope this is better :) * - * @since 0.0.24 + * @since 0.0.25 */ public class JournaledDataHandler implements IClaimChunkDataHandler { private boolean init = false; - @Getter private final File playerDataFile; - @Getter private final File claimRegionsDir; + @Getter private final File claimChunkDb; - public JournaledDataHandler(@NotNull File playerDataFile, @NotNull File claimRegionsDir) { - this.playerDataFile = playerDataFile; - this.claimRegionsDir = claimRegionsDir; + public JournaledDataHandler(@NotNull File claimChunkDb) { + this.claimChunkDb = claimChunkDb; } @Override diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionInnerPos.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionInnerPos.java index f505030e..3f6b81f8 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionInnerPos.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionInnerPos.java @@ -9,7 +9,7 @@ /** * The position of a region, guaranteed to be within the 0 < p < {@code CLAIM_REGION_WIDTH}. * - * @since 0.0.24 + * @since 0.0.25 */ public class RegionInnerPos { diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionPos.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionPos.java index 07f03b47..ec6cec54 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionPos.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionPos.java @@ -11,7 +11,7 @@ /** * The position of a claim data region within a world. * - * @since 0.0.24 + * @since 0.0.25 */ public record RegionPos(String worldName, int x, int y) { From d6669c981e03139be86d30f541c96ba7273be412 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Tue, 7 May 2024 02:01:01 -0400 Subject: [PATCH 06/26] Change 0.0.24 to 0.0.25-DEV as 0.0.25 is the new 0.0.24 yay --- README.md | 2 +- build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2b14a69a..32ffd80e 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Here are some plugins that either have addons for or integrations with ClaimChun Building -------- [![Automatic Build](https://img.shields.io/github/actions/workflow/status/cjburkey01/ClaimChunk/gradle.yml?branch=main&style=for-the-badge)](https://claimchunk.cjburkey.com/server/Downloads.html#snapshot-downloads) -[![Version Info](https://img.shields.io/static/v1?label=Repository%20Version&message=0.0.24&color=ff5555&style=for-the-badge)](https://github.com/cjburkey01/ClaimChunk/archive/main.zip) +[![Version Info](https://img.shields.io/static/v1?label=Repository%20Version&message=0.0.25-DEV&color=ff5555&style=for-the-badge)](https://github.com/cjburkey01/ClaimChunk/archive/main.zip) If you want to obtain a version of the plugin that isn't available yet (like a snapshot), you can do so by asking on the Discord or building it yourself. Here's how to build it yourself: diff --git a/build.gradle.kts b/build.gradle.kts index c6eb74c5..08de0882 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,7 @@ object DepData { const val JAVA_VERSION = 17; const val LIVE_VERSION = "0.0.24"; - const val THIS_VERSION = "0.0.24"; + const val THIS_VERSION = "0.0.25-DEV"; const val PLUGIN_NAME = "ClaimChunk"; const val ARCHIVES_BASE_NAME = "claimchunk"; const val MAIN_CLASS = "com.cjburkey.claimchunk.ClaimChunk"; From e65d60c1d8bf5ebf4e3c76d81ab937bc690e2557 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Tue, 7 May 2024 23:53:16 -0400 Subject: [PATCH 07/26] Progress --- build.gradle.kts | 4 - .../data/journaled/JournaledDataHandler.java | 110 +++++++++++++----- .../data/journaled/SqLiteWrapper.java | 14 +++ 3 files changed, 97 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java diff --git a/build.gradle.kts b/build.gradle.kts index 08de0882..611ead02 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,10 +38,6 @@ object DepData { const val JETBRAINS_ANNOTATIONS_VERSION = "23.0.0"; const val JUNIT_VERSION = "5.10.2"; const val JUNIT_LAUNCHER_VERSION = "1.10.2"; - // Goldmensch's SmartCommandDispatcher. Thank you!! - // const val SMART_COMMAND_DISPATCHER_VERSION = "2.0.1"; - // And internationalization library! - // const val JALL_I18N_VERSION = "1.0.2" // Directories const val TEST_SERVER_DIR = "run"; 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 8160d8b5..6e6af2b7 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java @@ -13,28 +13,33 @@ import org.jetbrains.annotations.Nullable; import java.io.File; -import java.util.Collection; -import java.util.Map; -import java.util.UUID; +import java.util.*; + +/* + * I've actually just decided that we're gonna do it this way: + * - SQLite backing database *file* similar to current MySQL integration (which will + * be removed and automatically converted). + * - Have some intermediary layer that can + * - Keep some regions in memory and unload when no players are within them + * for a minute or two. + * - Respond immediately and asynchronously update database. + */ /** * The SHINY, NEW........data handler that tries to fix the data loss issues by which this project * has been plagued since its conception. *

- * I've actually just decided that we're gonna do it this way: - * - SQLite backing database similar to current MySQL integration (which will - * be removed and automatically converted). - * - Keep some regions in memory and unload when no players are within them - * for a minute or two. * I hope this is better :) * * @since 0.0.25 */ public class JournaledDataHandler implements IClaimChunkDataHandler { - private boolean init = false; - @Getter private final File claimChunkDb; + private boolean init = false; + private HashMap joinedPlayers; + private HashMap claimRegions; + private SqLiteWrapper sqLiteWrapper; public JournaledDataHandler(@NotNull File claimChunkDb) { this.claimChunkDb = claimChunkDb; @@ -42,6 +47,10 @@ public JournaledDataHandler(@NotNull File claimChunkDb) { @Override public void init() { + joinedPlayers = new HashMap<>(); + claimRegions = new HashMap<>(); + sqLiteWrapper = new SqLiteWrapper(claimChunkDb); + init = true; } @@ -54,19 +63,29 @@ public boolean getHasInit() { public void exit() {} @Override - public void save() throws Exception {} + public void save() { + // Don't do anything, async handler should have us safe + } @Override - public void load() throws Exception {} + public void load() throws Exception { + // TODO: THIS + } @Override - public void addClaimedChunk(ChunkPos pos, UUID player) {} + public void addClaimedChunk(ChunkPos pos, UUID player) { + // TODO: mutating methods must call sqLiteWrapper methods + } @Override - public void addClaimedChunks(DataChunk[] chunks) {} + public void addClaimedChunks(DataChunk[] chunks) { + // TODO: mutating methods must call sqLiteWrapper methods + } @Override - public void removeClaimedChunk(ChunkPos pos) {} + public void removeClaimedChunk(ChunkPos pos) { + // TODO: mutating methods must call sqLiteWrapper methods + } @Override public boolean isChunkClaimed(ChunkPos pos) { @@ -100,34 +119,61 @@ public void addPlayer( @Nullable String chunkName, long lastOnlineTime, boolean alerts, - int maxClaims) {} + int extraMaxClaims) { + joinedPlayers.put( + player, + new FullPlayerData( + player, lastIgn, chunkName, lastOnlineTime, alerts, extraMaxClaims)); + + // TODO: mutating methods must call sqLiteWrapper methods + } @Override - public void addPlayers(FullPlayerData[] players) {} + public void addPlayers(FullPlayerData[] players) { + Arrays.stream(players).forEach(player -> joinedPlayers.put(player.player, player)); + + // TODO: mutating methods must call sqLiteWrapper methods + } @Override public @Nullable String getPlayerUsername(UUID player) { - return null; + FullPlayerData ply = joinedPlayers.get(player); + return ply == null ? null : ply.lastIgn; } @Override public @Nullable UUID getPlayerUUID(String username) { + // TODO: THIS + return null; } @Override - public void setPlayerLastOnline(UUID player, long time) {} + 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 + } @Override - public void setPlayerChunkName(UUID player, @Nullable String name) {} + 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 + } @Override public @Nullable String getPlayerChunkName(UUID player) { - return null; + FullPlayerData ply = joinedPlayers.get(player); + return ply == null ? null : ply.chunkName; } @Override - public void setPlayerReceiveAlerts(UUID player, boolean alerts) {} + public void setPlayerReceiveAlerts(UUID player, boolean alerts) { + // TODO: mutating methods must call sqLiteWrapper methods + } @Override public boolean getPlayerReceiveAlerts(UUID player) { @@ -135,13 +181,19 @@ public boolean getPlayerReceiveAlerts(UUID player) { } @Override - public void setPlayerExtraMaxClaims(UUID player, int maxClaims) {} + public void setPlayerExtraMaxClaims(UUID player, int maxClaims) { + // TODO: mutating methods must call sqLiteWrapper methods + } @Override - public void addPlayerExtraMaxClaims(UUID player, int numToAdd) {} + public void addPlayerExtraMaxClaims(UUID player, int numToAdd) { + // TODO: mutating methods must call sqLiteWrapper methods + } @Override - public void takePlayerExtraMaxClaims(UUID player, int numToTake) {} + public void takePlayerExtraMaxClaims(UUID player, int numToTake) { + // TODO: mutating methods must call sqLiteWrapper methods + } @Override public int getPlayerExtraMaxClaims(UUID player) { @@ -165,10 +217,14 @@ public FullPlayerData[] getFullPlayerData() { @Override public void givePlayerAccess( - ChunkPos chunk, UUID accessor, ChunkPlayerPermissions permissions) {} + ChunkPos chunk, UUID accessor, ChunkPlayerPermissions permissions) { + // TODO: mutating methods must call sqLiteWrapper methods + } @Override - public void takePlayerAccess(ChunkPos chunk, UUID accessor) {} + public void takePlayerAccess(ChunkPos chunk, UUID accessor) { + // TODO: mutating methods must call sqLiteWrapper methods + } @Override public Map getPlayersWithAccess(ChunkPos chunk) { diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java new file mode 100644 index 00000000..74b5c0ad --- /dev/null +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java @@ -0,0 +1,14 @@ +package com.cjburkey.claimchunk.data.journaled; + +import lombok.Getter; + +import java.io.File; + +public class SqLiteWrapper { + + private final File dbFile; + + public SqLiteWrapper(File dbFile) { + this.dbFile = dbFile; + } +} From 6e622677e5c9cd63a0c76c24ba3fc64edcb9ab53 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Sat, 11 May 2024 01:25:32 -0400 Subject: [PATCH 08/26] Add untested SQLite database table initialization --- .../data/journaled/ClaimRegion.java | 4 +- .../data/journaled/JournaledDataHandler.java | 4 +- .../data/journaled/SqLiteWrapper.java | 52 +++++++- .../data/journaled/TableMigrationManager.java | 117 ++++++++++++++++++ 4 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java index 2b110188..bc7791f4 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java @@ -48,7 +48,9 @@ public ClaimRegion(@NotNull RegionPos regionPos) { ChunkPos chunkPos = newChunk.chunk; // Return null if the provided chunk is outside of this region. if (!regionPos.equals(new RegionPos(chunkPos))) { - Utils.warn("Uh oh, the provided chunk at %s isn't within the region %s".formatted(chunkPos, regionPos)); + Utils.warn( + "Uh oh, the provided chunk at %s isn't within the region %s" + .formatted(chunkPos, regionPos)); return null; } return setClaimInfo(new RegionInnerPos(chunkPos), newChunk); 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 6e6af2b7..4db4811a 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java @@ -28,8 +28,8 @@ /** * The SHINY, NEW........data handler that tries to fix the data loss issues by which this project * has been plagued since its conception. - *

- * I hope this is better :) + * + *

I hope this is better :) * * @since 0.0.25 */ 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 74b5c0ad..85930140 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java @@ -1,14 +1,62 @@ package com.cjburkey.claimchunk.data.journaled; -import lombok.Getter; +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; public class SqLiteWrapper { private final File dbFile; + private Connection connection; - public SqLiteWrapper(File dbFile) { + public SqLiteWrapper(@NotNull File dbFile) { this.dbFile = dbFile; + + try { + TableMigrationManager.go(this::connectionOrDie); + } catch (SQLException e) { + throw new RuntimeException("Failed to initialize tables!", e); + } + } + + public @NotNull Connection ensureConnection() throws RuntimeException, SQLException { + if (connection != null && !connection.isClosed()) { + return connection; + } + try { + if (!dbFile.exists()) { + //noinspection ResultOfMethodCallIgnored + dbFile.createNewFile(); + } + + Class.forName("org.sqlite.JDBC"); + connection = DriverManager.getConnection("jdbc:sqlite:" + dbFile); + return connection; + } catch (IOException e) { + throw new RuntimeException("Failed to create new file " + dbFile, e); + } catch (ClassNotFoundException e) { + throw new RuntimeException( + "Cannot find SQLite JDBC class? Not sure how this can happen. Please submit an" + + " issue on GitHub", + e); + } + } + + public @NotNull Connection connectionOrDie() { + try { + return ensureConnection(); + } catch (SQLException e) { + throw new RuntimeException("SQL Exception", e); + } + } + + @SuppressWarnings("unused") + public @Nullable Connection getOpenConnection() { + return connection; } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java new file mode 100644 index 00000000..1a2c9d35 --- /dev/null +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java @@ -0,0 +1,117 @@ +package com.cjburkey.claimchunk.data.journaled; + +import org.jetbrains.annotations.NotNull; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.function.Supplier; + +/** This class is responsible for creating, loading, and upgrading the database file. */ +public class TableMigrationManager { + + public static void go(Supplier connectionSupplier) + throws RuntimeException, SQLException { + try (Connection connection = ensureConnection(connectionSupplier)) { + // Make tables if they don't exist + initializeTables(connection); + + // Call migration check methods here. + } + } + + private static @NotNull Connection ensureConnection(Supplier connectionSupplier) + throws RuntimeException { + Connection connection = connectionSupplier.get(); + try { + if (connection != null && !connection.isClosed()) { + return connection; + } else { + throw new RuntimeException("Connection provided was not valid."); + } + } catch (SQLException e) { + throw new RuntimeException( + "Failed to create connection to ClaimChunk SQLite database file", e); + } + } + + private static void initializeTables(Connection connection) throws SQLException { + tryCreateTables(connection); + + // Call migration methods here. + // Add table column exist checks inside each method to make this method + // cleaner. + } + + // TODO: CHECK THESE WORK! + private static void tryCreateTables(Connection connection) throws SQLException { + // Player data table + connection + .prepareCall( + """ + CREATE TABLE IF NOT EXISTS player_data ( + player_id INTEGER PRIMARY KEY, + player_uuid VARCHAR(36) UNIQUE NOT NULL, + last_ign VARCHAR(32) NOT NULL, + chunk_name VARCHAR(32), + last_online_time INTEGER NOT NULL, + alerts_enabled INTEGER NOT NULL, + extra_max_claims INTEGER NOT NULL, + ) STRICT + """) + .execute(); + + // Chunk data table + connection + .prepareCall( + """ + CREATE TABLE IF NOT EXISTS chunk_data ( + chunk_id INTEGER PRIMARY KEY, + chunk_world VARCHAR(32) NOT NULL, + chunk_x INTEGER NOT NULL, + chunk_z INTEGER NOT NULL, + owner_id INTEGER NOT NULL, + + FOREIGN KEY(owner_id) REFERENCES player_data(player_id) + ) STRICT + """) + .execute(); + + // Granular chunk player permission table + connection + .prepareCall( + """ + CREATE TABLE IF NOT EXISTS chunk_permissions ( + chunk_id INTEGER NOT NULL, + other_player_id INTEGER NOT NULL, + permission_bits INTEGER NOT NULL, + + FOREIGN KEY(chunk_id) REFERENCES chunk_data(chunk_id) + FOREIGN KEY(other_player_id) REFERENCES player_data(player_id) + ) STRICT + """) + .execute(); + } + + // Use this method to determine if a column exists in a table to perform migrations + // TODO: MAYBE CHECK IF THIS WORKS + @SuppressWarnings("unused") + private static boolean columnExists(Connection connection, String tableName, String columnName) + throws SQLException { + PreparedStatement statement = + connection.prepareCall( + """ + SELECT COUNT(*) FROM pragma_table_info(?) WHERE name=? + """); + statement.setString(1, tableName); + statement.setString(2, columnName); + ResultSet resultSet = statement.executeQuery(); + int count = resultSet.getInt(1); + return count > 0; + } + + // Whenever a column is added or moved or transformed or whatever, add a + // method here to perform that transformation and call it in initialize_tables. + +} From 6cc12a4701113d52b804dc0e757e5e83e5d269a7 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Sat, 11 May 2024 13:54:45 -0400 Subject: [PATCH 09/26] Format --- .../com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java | 2 +- .../claimchunk/data/journaled/TableMigrationManager.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 85930140..ce0fe02b 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java @@ -42,7 +42,7 @@ public SqLiteWrapper(@NotNull File dbFile) { } catch (ClassNotFoundException e) { throw new RuntimeException( "Cannot find SQLite JDBC class? Not sure how this can happen. Please submit an" - + " issue on GitHub", + + " issue on GitHub", e); } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java index 1a2c9d35..d36cba32 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java @@ -72,7 +72,7 @@ chunk_world VARCHAR(32) NOT NULL, chunk_x INTEGER NOT NULL, chunk_z INTEGER NOT NULL, owner_id INTEGER NOT NULL, - + FOREIGN KEY(owner_id) REFERENCES player_data(player_id) ) STRICT """) @@ -86,7 +86,7 @@ CREATE TABLE IF NOT EXISTS chunk_permissions ( chunk_id INTEGER NOT NULL, other_player_id INTEGER NOT NULL, permission_bits INTEGER NOT NULL, - + FOREIGN KEY(chunk_id) REFERENCES chunk_data(chunk_id) FOREIGN KEY(other_player_id) REFERENCES player_data(player_id) ) STRICT From 9cca6f91530eb9701f8dc2c7c65e740f7b8fc498 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Sat, 11 May 2024 14:26:53 -0400 Subject: [PATCH 10/26] Create sqlite tables --- .../com/cjburkey/claimchunk/ClaimChunk.java | 11 ++++++++++- .../data/journaled/SqLiteWrapper.java | 4 +++- .../data/journaled/TableMigrationManager.java | 19 +++++++++---------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java index 494ff5f9..02cb265a 100644 --- a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java @@ -6,6 +6,7 @@ import com.cjburkey.claimchunk.cmd.*; import com.cjburkey.claimchunk.config.ClaimChunkWorldProfileHandler; import com.cjburkey.claimchunk.config.ccconfig.*; +import com.cjburkey.claimchunk.data.journaled.JournaledDataHandler; import com.cjburkey.claimchunk.data.newdata.*; import com.cjburkey.claimchunk.event.*; import com.cjburkey.claimchunk.i18n.V2JsonMessages; @@ -365,9 +366,10 @@ private void initAnonymousData() { } } + @SuppressWarnings("CommentedOutCode") private boolean initDataHandler() { // Initialize the data handler if another plugin hasn't substituted one already - if (dataHandler == null) { + /*if (dataHandler == null) { // The ternary operator is great // But it's ugly sometimes // Yuck! @@ -383,6 +385,11 @@ private boolean initDataHandler() { this::createJsonDataHandler, JsonDataHandler::deleteFiles)) : createJsonDataHandler(); + }*/ + if (dataHandler == null) { + dataHandler = + new JournaledDataHandler( + new File(getDataFolder(), "/data/claimAndPlayerData.sqlite")); } Utils.debug("Using data handler \"%s\"", dataHandler.getClass().getName()); try { @@ -400,6 +407,7 @@ private boolean initDataHandler() { "Please double check your config and make sure it's set to the correct data" + " information to ensure ClaimChunk can operate normally"); } + System.exit(-1); return false; } @@ -448,6 +456,7 @@ private void initEcon() { Utils.log("Economy not enabled."); } + @SuppressWarnings("unused") private JsonDataHandler createJsonDataHandler() { // Create the basic JSON data handler return new JsonDataHandler( 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 ce0fe02b..f095c91a 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java @@ -1,5 +1,7 @@ package com.cjburkey.claimchunk.data.journaled; +import lombok.Getter; + import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -11,7 +13,7 @@ public class SqLiteWrapper { - private final File dbFile; + @Getter private final File dbFile; private Connection connection; public SqLiteWrapper(@NotNull File dbFile) { diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java index d36cba32..c229ac4e 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java @@ -44,31 +44,30 @@ private static void initializeTables(Connection connection) throws SQLException // cleaner. } - // TODO: CHECK THESE WORK! private static void tryCreateTables(Connection connection) throws SQLException { // Player data table connection - .prepareCall( + .prepareStatement( """ CREATE TABLE IF NOT EXISTS player_data ( player_id INTEGER PRIMARY KEY, - player_uuid VARCHAR(36) UNIQUE NOT NULL, - last_ign VARCHAR(32) NOT NULL, - chunk_name VARCHAR(32), + player_uuid TEXT UNIQUE NOT NULL, + last_ign TEXT NOT NULL, + chunk_name TEXT, last_online_time INTEGER NOT NULL, alerts_enabled INTEGER NOT NULL, - extra_max_claims INTEGER NOT NULL, + extra_max_claims INTEGER NOT NULL ) STRICT """) .execute(); // Chunk data table connection - .prepareCall( + .prepareStatement( """ CREATE TABLE IF NOT EXISTS chunk_data ( chunk_id INTEGER PRIMARY KEY, - chunk_world VARCHAR(32) NOT NULL, + chunk_world TEXT NOT NULL, chunk_x INTEGER NOT NULL, chunk_z INTEGER NOT NULL, owner_id INTEGER NOT NULL, @@ -80,14 +79,14 @@ FOREIGN KEY(owner_id) REFERENCES player_data(player_id) // Granular chunk player permission table connection - .prepareCall( + .prepareStatement( """ CREATE TABLE IF NOT EXISTS chunk_permissions ( chunk_id INTEGER NOT NULL, other_player_id INTEGER NOT NULL, permission_bits INTEGER NOT NULL, - FOREIGN KEY(chunk_id) REFERENCES chunk_data(chunk_id) + FOREIGN KEY(chunk_id) REFERENCES chunk_data(chunk_id), FOREIGN KEY(other_player_id) REFERENCES player_data(player_id) ) STRICT """) From acb2d19a264369a55e07004c1bc48b266fd3957c Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Sat, 11 May 2024 14:28:40 -0400 Subject: [PATCH 11/26] Use .sqlite3 extension for db file to be specific --- src/main/java/com/cjburkey/claimchunk/ClaimChunk.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java index 02cb265a..2d42633f 100644 --- a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java @@ -389,7 +389,7 @@ private boolean initDataHandler() { if (dataHandler == null) { dataHandler = new JournaledDataHandler( - new File(getDataFolder(), "/data/claimAndPlayerData.sqlite")); + new File(getDataFolder(), "/data/claimAndPlayerData.sqlite3")); } Utils.debug("Using data handler \"%s\"", dataHandler.getClass().getName()); try { From 8eb8392e6a643318e82f437ce617f2fecdab634b Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Mon, 13 May 2024 16:01:00 -0400 Subject: [PATCH 12/26] Implement almost all of it? Except for data loading --- .../data/journaled/JournaledDataHandler.java | 104 ++++-- .../data/journaled/SqLiteWrapper.java | 312 ++++++++++++++++-- .../data/newdata/IClaimChunkDataHandler.java | 6 +- .../event/PlayerConnectionHandler.java | 1 + 4 files changed, 368 insertions(+), 55 deletions(-) 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 4db4811a..db6b7914 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 f095c91a..d7978686 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 840e1b9a..5c60befa 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 4908ffc5..74d04f33 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()); } } From fcafaf5d523097c1e6118e86c6092bb40333e25f Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Wed, 15 May 2024 18:39:16 -0400 Subject: [PATCH 13/26] Clean up and make sure player online time is saved at login and logoff --- .../data/journaled/ClaimRegion.java | 76 ------------------- .../data/journaled/JournaledDataHandler.java | 3 - .../data/journaled/RegionInnerPos.java | 50 ------------ .../claimchunk/data/journaled/RegionPos.java | 44 ----------- .../data/journaled/SqLiteWrapper.java | 65 +++++++++------- .../event/PlayerConnectionHandler.java | 4 +- .../claimchunk/player/PlayerHandler.java | 7 +- 7 files changed, 44 insertions(+), 205 deletions(-) delete mode 100644 src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java delete mode 100644 src/main/java/com/cjburkey/claimchunk/data/journaled/RegionInnerPos.java delete mode 100644 src/main/java/com/cjburkey/claimchunk/data/journaled/RegionPos.java diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java deleted file mode 100644 index bc7791f4..00000000 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/ClaimRegion.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.cjburkey.claimchunk.data.journaled; - -import com.cjburkey.claimchunk.Utils; -import com.cjburkey.claimchunk.chunk.ChunkPos; -import com.cjburkey.claimchunk.chunk.DataChunk; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * A region of chunk claiming data within the world. Each region is a 16x16 array of claim data for - * each chunk within it. - * - * @since 0.0.25 - */ -public class ClaimRegion { - - public static final int CLAIM_REGION_WIDTH = 16; - - public final RegionPos regionPos; - private final DataChunk[] claims; - - public ClaimRegion(@NotNull RegionPos regionPos) { - this.regionPos = regionPos; - this.claims = new DataChunk[CLAIM_REGION_WIDTH * CLAIM_REGION_WIDTH]; - } - - /** - * Retrieve the claim information for a given chunk within this region. - * - * @param posInRegion The position within the region. - * @return The chunk for the given position, or {@code null} if no data exists for it. - */ - public @Nullable DataChunk getClaimInfo(RegionInnerPos posInRegion) { - return claims[posInRegion.index()]; - } - - /** - * Assign the chunk data at the given position within this region to the provided new chunk - * data. - * - * @param newChunk The ClaimChunk information to insert into this region. - * @return The previous chunk, or {@code null} if no information exists for the given claim OR - * the provided chunk does not exist within this region, i.e. a chunk is being inserted into - * the wrong region, given its position within the world. - */ - public @Nullable DataChunk setClaimInfo(@NotNull DataChunk newChunk) { - ChunkPos chunkPos = newChunk.chunk; - // Return null if the provided chunk is outside of this region. - if (!regionPos.equals(new RegionPos(chunkPos))) { - Utils.warn( - "Uh oh, the provided chunk at %s isn't within the region %s" - .formatted(chunkPos, regionPos)); - return null; - } - return setClaimInfo(new RegionInnerPos(chunkPos), newChunk); - } - - // Unsafe version of above methods. - // May be exposed later if necessary, but everything should be possible through the safer API - private @Nullable DataChunk setClaimInfo( - @NotNull RegionInnerPos chunkPos, @Nullable DataChunk newChunk) { - int chunkIndex = chunkPos.index(); - DataChunk previousChunk = claims[chunkIndex]; - claims[chunkIndex] = newChunk; - return previousChunk; - } - - /** - * @param chunkPos The position of the chunks whose data we need to remove. - * @return The previous data at this position, or {@code null} if no data existed. - */ - public @Nullable DataChunk removeClaimInfo(@NotNull RegionInnerPos chunkPos) { - return setClaimInfo(chunkPos, null); - } -} 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 db6b7914..0efd2fc7 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java @@ -42,9 +42,6 @@ public class JournaledDataHandler implements IClaimChunkDataHandler { private HashMap joinedPlayers; private SqLiteWrapper sqLiteWrapper; - // Don't need this shit actually - // private HashMap claimRegions; - public JournaledDataHandler(@NotNull File claimChunkDb) { this.claimChunkDb = claimChunkDb; } diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionInnerPos.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionInnerPos.java deleted file mode 100644 index 3f6b81f8..00000000 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionInnerPos.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.cjburkey.claimchunk.data.journaled; - -import static com.cjburkey.claimchunk.data.journaled.ClaimRegion.CLAIM_REGION_WIDTH; - -import com.cjburkey.claimchunk.chunk.ChunkPos; - -import java.util.Objects; - -/** - * The position of a region, guaranteed to be within the 0 < p < {@code CLAIM_REGION_WIDTH}. - * - * @since 0.0.25 - */ -public class RegionInnerPos { - - /** The X position of the region. */ - public final int x; - - /** The Y position of the region. */ - public final int z; - - public RegionInnerPos(int chunkX, int chunkZ) { - this.x = Math.floorMod(chunkX, CLAIM_REGION_WIDTH); - this.z = Math.floorMod(chunkZ, CLAIM_REGION_WIDTH); - } - - public RegionInnerPos(ChunkPos chunkPos) { - this(chunkPos.x(), chunkPos.z()); - } - - /** - * @return the unique index for this position in a {@code CLAIM_REGION_WIDTH}^2 sized array. - */ - public int index() { - return x * CLAIM_REGION_WIDTH + z; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - RegionInnerPos that = (RegionInnerPos) o; - return x == that.x && z == that.z; - } - - @Override - public int hashCode() { - return Objects.hash(x, z); - } -} diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionPos.java b/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionPos.java deleted file mode 100644 index ec6cec54..00000000 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/RegionPos.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.cjburkey.claimchunk.data.journaled; - -import static com.cjburkey.claimchunk.data.journaled.ClaimRegion.CLAIM_REGION_WIDTH; - -import com.cjburkey.claimchunk.chunk.ChunkPos; - -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -/** - * The position of a claim data region within a world. - * - * @since 0.0.25 - */ -public record RegionPos(String worldName, int x, int y) { - - public RegionPos(@NotNull ChunkPos chunkPos) { - this( - chunkPos.world(), - Math.floorDiv(chunkPos.x(), CLAIM_REGION_WIDTH), - Math.floorDiv(chunkPos.z(), CLAIM_REGION_WIDTH)); - } - - @Override - public String toString() { - return String.format("%s, %s in %s", x, y, worldName); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - RegionPos regionPos = (RegionPos) o; - return x == regionPos.x - && y == regionPos.y - && Objects.equals(worldName, regionPos.worldName); - } - - @Override - public int hashCode() { - return Objects.hash(worldName, x, y); - } -} 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 d7978686..acb1f54f 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java +++ b/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java @@ -12,22 +12,35 @@ import java.sql.*; import java.util.UUID; -public record SqLiteWrapper(File dbFile) { +public class SqLiteWrapper { - public SqLiteWrapper(@NotNull File dbFile) { + public final File dbFile; + private Connection liveConnection; + + public SqLiteWrapper(@NotNull File dbFile) throws RuntimeException { this.dbFile = dbFile; try { - TableMigrationManager.go(this::connectionOrDie); + // Make sure the SQLite driver exists and get it in the classpath + // for the DriverManager to search. + Class.forName("org.sqlite.JDBC"); + + // Initialize the tables and perform any changes to them + TableMigrationManager.go(this::connectionOrException); + } catch (ClassNotFoundException e) { + throw new RuntimeException( + "Cannot find SQLite JDBC class? Not sure how this can happen. Please submit an" + + " issue on GitHub", + e); } catch (SQLException e) { - throw new RuntimeException("Failed to initialize tables!", e); + throw new RuntimeException("Failed to initialize tables! This is fatal!", e); } } // -- DATABASE INTEGRATIONS! -- // public void addClaimedChunk(DataChunk chunk) { - try (Connection connection = ensureConnection()) { + try (Connection connection = connectionOrException()) { // Use the nested select query to get the user's row ID as the // owner's id try (PreparedStatement statement = @@ -55,7 +68,7 @@ INSERT INTO chunk_data ( } public void removeClaimedChunk(ChunkPos chunk) { - try (Connection connection = ensureConnection()) { + try (Connection connection = connectionOrException()) { // Get chunk ID final int chunkId; try (PreparedStatement statement = @@ -99,7 +112,7 @@ public void removeClaimedChunk(ChunkPos chunk) { // TODO: TEST public void addPlayer(FullPlayerData playerData) { - try (Connection connection = ensureConnection()) { + try (Connection connection = connectionOrException()) { try (PreparedStatement statement = connection.prepareStatement( """ @@ -126,7 +139,7 @@ INSERT INTO player_data ( } public void setPlayerLastOnline(UUID player, long time) { - try (Connection connection = ensureConnection()) { + try (Connection connection = connectionOrException()) { // Use the nested select query to get the user's row ID as the // owner's id try (PreparedStatement statement = @@ -146,7 +159,7 @@ public void setPlayerLastOnline(UUID player, long time) { } public void setPlayerChunkName(UUID player, String chunkName) { - try (Connection connection = ensureConnection()) { + try (Connection connection = connectionOrException()) { // Use the nested select query to get the user's row ID as the // owner's id try (PreparedStatement statement = @@ -166,7 +179,7 @@ public void setPlayerChunkName(UUID player, String chunkName) { } public void setPlayerReceiveAlerts(UUID player, boolean receiveAlerts) { - try (Connection connection = ensureConnection()) { + try (Connection connection = connectionOrException()) { // Use the nested select query to get the user's row ID as the // owner's id try (PreparedStatement statement = @@ -186,7 +199,7 @@ public void setPlayerReceiveAlerts(UUID player, boolean receiveAlerts) { } public void setPlayerExtraMaxClaims(UUID player, int extraMaxClaims) { - try (Connection connection = ensureConnection()) { + try (Connection connection = connectionOrException()) { // Use the nested select query to get the user's row ID as the // owner's id try (PreparedStatement statement = @@ -206,7 +219,7 @@ public void setPlayerExtraMaxClaims(UUID player, int extraMaxClaims) { } public void addPlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlags) { - try (Connection connection = ensureConnection()) { + try (Connection connection = connectionOrException()) { try (PreparedStatement statement = connection.prepareStatement( """ @@ -241,7 +254,7 @@ INSERT INTO chunk_permissions ( } public void updatePlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlags) { - try (Connection connection = ensureConnection()) { + try (Connection connection = connectionOrException()) { try (PreparedStatement statement = connection.prepareStatement( """ @@ -273,7 +286,7 @@ public void updatePlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlag } public void removePlayerAccess(ChunkPos chunk, UUID accessor) { - try (Connection connection = ensureConnection()) { + try (Connection connection = connectionOrException()) { try (PreparedStatement statement = connection.prepareStatement( """ @@ -304,29 +317,23 @@ public void removePlayerAccess(ChunkPos chunk, UUID accessor) { // -- Connection stuff -- // - public @NotNull Connection ensureConnection() throws RuntimeException, SQLException { - try { + public @NotNull Connection connection() throws SQLException, IOException { + if (liveConnection == null || liveConnection.isClosed()) { if (!dbFile.exists() && dbFile.createNewFile()) { Utils.warn("Created empty database file"); } - - Class.forName("org.sqlite.JDBC"); - return DriverManager.getConnection("jdbc:sqlite:" + dbFile); - } catch (IOException e) { - throw new RuntimeException("Failed to create new file " + dbFile, e); - } catch (ClassNotFoundException e) { - throw new RuntimeException( - "Cannot find SQLite JDBC class? Not sure how this can happen. Please submit an" - + " issue on GitHub", - e); + liveConnection = DriverManager.getConnection("jdbc:sqlite:" + dbFile); } + return liveConnection; } - public @NotNull Connection connectionOrDie() { + public @NotNull Connection connectionOrException() throws RuntimeException { try { - return ensureConnection(); + return connection(); } catch (SQLException e) { - throw new RuntimeException("SQL Exception", e); + throw new RuntimeException("SQLException on connection creation", e); + } catch (IOException e) { + throw new RuntimeException("Failed to create new file " + dbFile, e); } } } diff --git a/src/main/java/com/cjburkey/claimchunk/event/PlayerConnectionHandler.java b/src/main/java/com/cjburkey/claimchunk/event/PlayerConnectionHandler.java index 74d04f33..e740c5e9 100644 --- a/src/main/java/com/cjburkey/claimchunk/event/PlayerConnectionHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/event/PlayerConnectionHandler.java @@ -59,6 +59,8 @@ 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()); + claimChunk + .getPlayerHandler() + .setLastJoinedTime(e.getPlayer().getUniqueId(), System.currentTimeMillis()); } } diff --git a/src/main/java/com/cjburkey/claimchunk/player/PlayerHandler.java b/src/main/java/com/cjburkey/claimchunk/player/PlayerHandler.java index 0c722d86..5bd77b25 100644 --- a/src/main/java/com/cjburkey/claimchunk/player/PlayerHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/player/PlayerHandler.java @@ -127,9 +127,12 @@ public int getMaxClaims(UUID player) { } public void onJoin(Player ply) { - if (!dataHandler.hasPlayer(ply.getUniqueId())) { + UUID uuid = ply.getUniqueId(); + if (dataHandler.hasPlayer(uuid)) { + dataHandler.setPlayerLastOnline(uuid, System.currentTimeMillis()); + } else { dataHandler.addPlayer( - ply.getUniqueId(), + uuid, ply.getName(), claimChunk.getConfigHandler().getDefaultSendAlertsToOwner(), 0); From 2b78d730952dd819b89b51e6c47e21a7a1760395 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Wed, 15 May 2024 21:38:46 -0400 Subject: [PATCH 14/26] Start working on loading --- .../com/cjburkey/claimchunk/ClaimChunk.java | 4 +- .../claimchunk/chunk/ChunkHandler.java | 49 +++++++++--------- .../data/newdata/BulkMySQLDataHandler.java | 10 ---- .../data/newdata/IClaimChunkDataHandler.java | 22 ++++---- .../data/newdata/JsonDataHandler.java | 23 ++------- .../data/newdata/MySQLDataHandler.java | 51 ------------------- .../SqLiteDataHandler.java} | 32 +++++------- .../SqLiteTableMigrationManager.java} | 4 +- .../{journaled => sqlite}/SqLiteWrapper.java | 43 ++++++++++++++-- .../layer/PlaceholderInitLayer.java | 2 + 10 files changed, 98 insertions(+), 142 deletions(-) rename src/main/java/com/cjburkey/claimchunk/data/{journaled/JournaledDataHandler.java => sqlite/SqLiteDataHandler.java} (93%) rename src/main/java/com/cjburkey/claimchunk/data/{journaled/TableMigrationManager.java => sqlite/SqLiteTableMigrationManager.java} (98%) rename src/main/java/com/cjburkey/claimchunk/data/{journaled => sqlite}/SqLiteWrapper.java (88%) diff --git a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java index 2d42633f..73a1f5cf 100644 --- a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java @@ -6,8 +6,8 @@ import com.cjburkey.claimchunk.cmd.*; import com.cjburkey.claimchunk.config.ClaimChunkWorldProfileHandler; import com.cjburkey.claimchunk.config.ccconfig.*; -import com.cjburkey.claimchunk.data.journaled.JournaledDataHandler; import com.cjburkey.claimchunk.data.newdata.*; +import com.cjburkey.claimchunk.data.sqlite.SqLiteDataHandler; import com.cjburkey.claimchunk.event.*; import com.cjburkey.claimchunk.i18n.V2JsonMessages; import com.cjburkey.claimchunk.layer.PlaceholderInitLayer; @@ -388,7 +388,7 @@ private boolean initDataHandler() { }*/ if (dataHandler == null) { dataHandler = - new JournaledDataHandler( + new SqLiteDataHandler( new File(getDataFolder(), "/data/claimAndPlayerData.sqlite3")); } Utils.debug("Using data handler \"%s\"", dataHandler.getClass().getName()); diff --git a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkHandler.java b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkHandler.java index cef9bc57..0ef83699 100644 --- a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkHandler.java @@ -28,13 +28,13 @@ public ChunkHandler(IClaimChunkDataHandler dataHandler, ClaimChunk claimChunk) { * Result returned by the {@link #fillClaimInto(String, int, int, int, int, UUID, Collection)} * and {@link #fillClaim(String, int, int, int, UUID)} methods. */ - public static enum FloodClaimResult { + public enum FloodClaimResult { /** The method completed without issues. */ - SUCCESSFULL, + SUCCESSFUL, /** The method recursed too many times and aborted due to that. */ - TOO_MANY_RECUSIONS, + TOO_MANY_RECURSIONS, /** The collection got too big and the method aborted due to that. */ COLLECTION_TOO_BIG, @@ -43,13 +43,13 @@ public static enum FloodClaimResult { * The algorithm hit a claimed chunk that did not belong to the player and aborted due to * this */ - HIT_NONPLAYER_CLAIM; + HIT_NONPLAYER_CLAIM, } /** - * Claims several chunks at once for a player. This method is very unsafe at it's own as it does + * Claims several chunks at once for a player. This method is very unsafe at its own as it does * not test whether the player can actually claim that chunk. This means that ownership can be - * overridden. And the player can go over it's quota as well + * overridden. And the player can go over its quota as well * * @param chunks The chunks to claim * @param player The player that claims these chunks @@ -110,19 +110,19 @@ public ChunkPos claimChunk(String world, int x, int z, UUID player) { - getClaimed(player)); Map.Entry, FloodClaimResult> result = fillClaim(world, x - 1, z, maxArea, player); - if (result.getValue() == FloodClaimResult.SUCCESSFULL) { + if (result.getValue() == FloodClaimResult.SUCCESSFUL) { claimAll(result.getKey(), player); } else { result = fillClaim(world, x + 1, z, maxArea, player); - if (result.getValue() == FloodClaimResult.SUCCESSFULL) { + if (result.getValue() == FloodClaimResult.SUCCESSFUL) { claimAll(result.getKey(), player); } else { result = fillClaim(world, x, z - 1, maxArea, player); - if (result.getValue() == FloodClaimResult.SUCCESSFULL) { + if (result.getValue() == FloodClaimResult.SUCCESSFUL) { claimAll(result.getKey(), player); } else { result = fillClaim(world, x, z + 1, maxArea, player); - if (result.getValue() == FloodClaimResult.SUCCESSFULL) { + if (result.getValue() == FloodClaimResult.SUCCESSFUL) { claimAll(result.getKey(), player); } } @@ -161,19 +161,19 @@ private FloodClaimResult fillClaimInto( UUID player, Collection collector) { if (recursions == 0) { - return FloodClaimResult.TOO_MANY_RECUSIONS; + return FloodClaimResult.TOO_MANY_RECURSIONS; } if (collector.size() > maxSize) { return FloodClaimResult.COLLECTION_TOO_BIG; } ChunkPos claimingPosition = new ChunkPos(world, x, z); if (collector.contains(claimingPosition)) { - return FloodClaimResult.SUCCESSFULL; + return FloodClaimResult.SUCCESSFUL; } UUID owner = getOwner(claimingPosition); if (owner != null) { if (owner.equals(player)) { - return FloodClaimResult.SUCCESSFULL; // Hit player claim, do not claim it + return FloodClaimResult.SUCCESSFUL; // Hit player claim, do not claim it } else { return FloodClaimResult.HIT_NONPLAYER_CLAIM; // Hit player claim, do not claim it } @@ -181,15 +181,15 @@ private FloodClaimResult fillClaimInto( collector.add(claimingPosition); FloodClaimResult result = fillClaimInto(world, x - 1, z, --recursions, maxSize, player, collector); - if (result != FloodClaimResult.SUCCESSFULL) { + if (result != FloodClaimResult.SUCCESSFUL) { return result; } result = fillClaimInto(world, x + 1, z, recursions, maxSize, player, collector); - if (result != FloodClaimResult.SUCCESSFULL) { + if (result != FloodClaimResult.SUCCESSFUL) { return result; } result = fillClaimInto(world, x, z - 1, recursions, maxSize, player, collector); - if (result != FloodClaimResult.SUCCESSFULL) { + if (result != FloodClaimResult.SUCCESSFUL) { return result; } return fillClaimInto(world, x, z + 1, recursions, maxSize, player, collector); @@ -418,6 +418,7 @@ public boolean isOwner(World world, int x, int z, Player ply) { * @param ply The UUID of the player. * @return Whether this player owns this chunk. */ + @SuppressWarnings("unused") public boolean isOwner(Chunk chunk, UUID ply) { return isOwner(chunk.getWorld(), chunk.getX(), chunk.getZ(), ply); } @@ -470,22 +471,24 @@ public UUID getOwner(ChunkPos pos) { /** * Toggles whether TNT is enabled in the provided chunk. * - * @param chunk The Spigot chunk position. + * @param ignoredChunk The Spigot chunk position. * @return Whether TNT is now (after the toggle) enabled in this chunk. + * @deprecated DOES NOTHING! */ - public boolean toggleTnt(Chunk chunk) { - return dataHandler.toggleTnt(new ChunkPos(chunk)); + @Deprecated + public boolean toggleTnt(Chunk ignoredChunk) { + return false; } /** * Checks whether TNT is enabled in the provided chunk. * - * @deprecated Must make use of new - * @param chunk The Spigot chunk position. + * @param ignoredChunk The Spigot chunk position. * @return Whether TNT is currently enabled in this chunk. + * @deprecated DOES NOTHING! */ @Deprecated - public boolean isTntEnabled(Chunk chunk) { - return dataHandler.isTntEnabled(new ChunkPos(chunk)); + public boolean isTntEnabled(Chunk ignoredChunk) { + return false; } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/newdata/BulkMySQLDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/newdata/BulkMySQLDataHandler.java index b4d1461b..b557d632 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/BulkMySQLDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/BulkMySQLDataHandler.java @@ -161,16 +161,6 @@ public DataChunk[] getClaimedChunks() { return dataHandler.getClaimedChunks(); } - @Override - public boolean toggleTnt(ChunkPos pos) { - return dataHandler.toggleTnt(pos); - } - - @Override - public boolean isTntEnabled(ChunkPos pos) { - return dataHandler.isTntEnabled(pos); - } - @Override public void addPlayer( UUID player, 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 5c60befa..015a0b0c 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/IClaimChunkDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/IClaimChunkDataHandler.java @@ -113,7 +113,7 @@ public interface IClaimChunkDataHandler { UUID getChunkOwner(ChunkPos pos); /** - * Retrives all claimed chunks and their owners across all worlds. + * Retrieves all claimed chunks and their owners across all worlds. * * @return An array of all claimed chunks * @since 0.0.13 @@ -123,25 +123,29 @@ public interface IClaimChunkDataHandler { /** * Toggles whether TNT can explode in the given chunk. * - * @param pos The position of the chunk + * @param ignoredPos 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); + default boolean toggleTnt(ChunkPos ignoredPos) { + return false; + } /** * Retrieves whether TNT can explode in the given chunk (regardless of whether TNT is disabled * in the config). * - * @param pos The position of the chunk + * @param ignoredPos 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); + default boolean isTntEnabled(ChunkPos ignoredPos) { + return false; + } // -- PLAYERS -- // @@ -151,7 +155,7 @@ public interface IClaimChunkDataHandler { * @param player The UUID of the player * @param lastIgn The in-game name of the player * @param chunkName The display name for this player's chunks - * @param lastOnlineTime The last time (in ms since January 1, 1970 UTC) that the player was + * @param lastOnlineTime The last time (in ms since January 1, 1970, UTC) that the player was * online * @param alerts Whether to send this player alerts when someone enters their chunks * @since 0.0.24 @@ -221,7 +225,7 @@ default void addPlayer(UUID player, String lastIgn, boolean alerts, int defaultM UUID getPlayerUUID(String username); /** - * Set the last time (in ms since January 1, 1970 UTC) that the player was online. + * Set the last time (in ms since January 1, 1970, UTC) that the player was online. * * @param player The player whose time should be updated * @param time The new time since the player was last online @@ -233,7 +237,7 @@ default void addPlayer(UUID player, String lastIgn, boolean alerts, int defaultM * Sets the given player's chunk's display name. * * @param player The player whose chunks should have the new display name - * @param name The new display name for this players chunks or {@code null} to clear it + * @param name The new display name for this player's chunks or {@code null} to clear it * @since 0.0.13 */ void setPlayerChunkName(UUID player, @Nullable String name); @@ -242,7 +246,7 @@ default void addPlayer(UUID player, String lastIgn, boolean alerts, int defaultM * Retrieves the given player's chunk's display name. * * @param player The player whose chunks' name to check - * @return The new display name for this players chunks or {@code null} if the player has not + * @return The new display name for this player's chunks or {@code null} if the player has not * joined or the chunks are unnamed * @since 0.0.13 */ diff --git a/src/main/java/com/cjburkey/claimchunk/data/newdata/JsonDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/newdata/JsonDataHandler.java index 1b12ab54..dbc31ac6 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/JsonDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/JsonDataHandler.java @@ -90,13 +90,6 @@ public void load() throws Exception { } } - public void deleteFiles() { - if (claimedChunksFile != null && !claimedChunksFile.delete()) - Utils.err("Failed to delete claimed chunks file"); - if (joinedPlayersFile != null && !joinedPlayersFile.delete()) - Utils.err("Failed to delete joined players file"); - } - void clearData() { claimedChunks.clear(); joinedPlayers.clear(); @@ -150,18 +143,6 @@ public DataChunk[] getClaimedChunks() { .toArray(DataChunk[]::new); } - @Override - public boolean toggleTnt(ChunkPos pos) { - DataChunk chunk = claimedChunks.get(pos); - if (chunk == null) return false; - return (chunk.tnt = !chunk.tnt); - } - - @Override - public boolean isTntEnabled(ChunkPos pos) { - return claimedChunks.containsKey(pos) && claimedChunks.get(pos).tnt; - } - @Override public void addPlayer( UUID player, @@ -446,7 +427,9 @@ private void loadPre0024Data() throws Exception { // Backup existing files doBackup(joinedPlayersFile); - doBackup(claimedChunksFile); + if (claimedChunksFile != null && claimedChunksFile.exists()) { + doBackup(claimedChunksFile); + } } } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java index df0a0513..b1415bba 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java @@ -308,57 +308,6 @@ public DataChunk[] getClaimedChunks() { return chunks.toArray(new DataChunk[0]); } - @Override - public boolean toggleTnt(ChunkPos pos) { - boolean current = isTntEnabled(pos); - String sql = - String.format( - "UPDATE `%s` SET `%s`=? WHERE (`%s`=?) AND (`%s`=?) AND (`%s`=?)", - CLAIMED_CHUNKS_TABLE_NAME, - CLAIMED_CHUNKS_TNT, - CLAIMED_CHUNKS_WORLD, - CLAIMED_CHUNKS_X, - CLAIMED_CHUNKS_Z); - try (PreparedStatement statement = prep(claimChunk, connection, sql)) { - statement.setBoolean(1, !current); - statement.setString(2, pos.world()); - statement.setInt(3, pos.x()); - statement.setInt(4, pos.z()); - statement.execute(); - return !current; - } catch (Exception e) { - Utils.err("Failed to update tnt enabled in chunk: %s", e.getMessage()); - //noinspection CallToPrintStackTrace - e.printStackTrace(); - } - return current; - } - - @Override - public boolean isTntEnabled(ChunkPos pos) { - String sql = - String.format( - "SELECT `%s` FROM `%s` WHERE (`%s`=?) AND (`%s`=?) AND (`%s`=?)", - CLAIMED_CHUNKS_TNT, - CLAIMED_CHUNKS_TABLE_NAME, - CLAIMED_CHUNKS_WORLD, - CLAIMED_CHUNKS_X, - CLAIMED_CHUNKS_Z); - try (PreparedStatement statement = prep(claimChunk, connection, sql)) { - statement.setString(1, pos.world()); - statement.setInt(2, pos.x()); - statement.setInt(3, pos.z()); - try (ResultSet result = statement.executeQuery()) { - if (result.next()) return result.getBoolean(1); - } - } catch (Exception e) { - Utils.err("Failed to retrieve tnt enabled in chunk: %s", e.getMessage()); - //noinspection CallToPrintStackTrace - e.printStackTrace(); - } - return false; - } - @Override public void addPlayer( UUID player, diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java similarity index 93% rename from src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java rename to src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java index 0efd2fc7..22a44c84 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/JournaledDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java @@ -1,4 +1,4 @@ -package com.cjburkey.claimchunk.data.journaled; +package com.cjburkey.claimchunk.data.sqlite; import com.cjburkey.claimchunk.chunk.ChunkPlayerPermissions; import com.cjburkey.claimchunk.chunk.ChunkPos; @@ -34,7 +34,7 @@ * * @since 0.0.25 */ -public class JournaledDataHandler implements IClaimChunkDataHandler { +public class SqLiteDataHandler implements IClaimChunkDataHandler { @Getter private final File claimChunkDb; private boolean init = false; @@ -42,14 +42,13 @@ public class JournaledDataHandler implements IClaimChunkDataHandler { private HashMap joinedPlayers; private SqLiteWrapper sqLiteWrapper; - public JournaledDataHandler(@NotNull File claimChunkDb) { + public SqLiteDataHandler(@NotNull File claimChunkDb) { this.claimChunkDb = claimChunkDb; } @Override public void init() { joinedPlayers = new HashMap<>(); - // claimRegions = new HashMap<>(); claimedChunks = new HashMap<>(); sqLiteWrapper = new SqLiteWrapper(claimChunkDb); @@ -66,12 +65,19 @@ public void exit() {} @Override public void save() { - // Don't do anything, async handler should have us safe + // Don't do anything, we save as we go } + @SuppressWarnings("StatementWithEmptyBody") @Override public void load() throws Exception { - // TODO: THIS + for (FullPlayerData player : sqLiteWrapper.getAllPlayers()) { + // TODO: THIS + } + + for (DataChunk chunk : sqLiteWrapper.getAllChunks()) { + // TODO: THIS + } } @Override @@ -108,20 +114,6 @@ public DataChunk[] getClaimedChunks() { return claimedChunks.values().toArray(new DataChunk[0]); } - // TODO: REMOVE - - @Override - public boolean toggleTnt(ChunkPos pos) { - return false; - } - - @Override - public boolean isTntEnabled(ChunkPos pos) { - return false; - } - - // END TODO - @Override public void addPlayer(FullPlayerData playerData) { joinedPlayers.put(playerData.player, playerData); diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java similarity index 98% rename from src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java rename to src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java index c229ac4e..ae0c05c3 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/TableMigrationManager.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java @@ -1,4 +1,4 @@ -package com.cjburkey.claimchunk.data.journaled; +package com.cjburkey.claimchunk.data.sqlite; import org.jetbrains.annotations.NotNull; @@ -9,7 +9,7 @@ import java.util.function.Supplier; /** This class is responsible for creating, loading, and upgrading the database file. */ -public class TableMigrationManager { +public class SqLiteTableMigrationManager { public static void go(Supplier connectionSupplier) throws RuntimeException, SQLException { diff --git a/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java similarity index 88% rename from src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java rename to src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java index acb1f54f..88f79c93 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/journaled/SqLiteWrapper.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java @@ -1,4 +1,4 @@ -package com.cjburkey.claimchunk.data.journaled; +package com.cjburkey.claimchunk.data.sqlite; import com.cjburkey.claimchunk.Utils; import com.cjburkey.claimchunk.chunk.ChunkPos; @@ -10,6 +10,8 @@ import java.io.File; import java.io.IOException; import java.sql.*; +import java.util.ArrayList; +import java.util.Collection; import java.util.UUID; public class SqLiteWrapper { @@ -26,7 +28,7 @@ public SqLiteWrapper(@NotNull File dbFile) throws RuntimeException { Class.forName("org.sqlite.JDBC"); // Initialize the tables and perform any changes to them - TableMigrationManager.go(this::connectionOrException); + SqLiteTableMigrationManager.go(this::connectionOrException); } catch (ClassNotFoundException e) { throw new RuntimeException( "Cannot find SQLite JDBC class? Not sure how this can happen. Please submit an" @@ -46,7 +48,7 @@ public void addClaimedChunk(DataChunk chunk) { try (PreparedStatement statement = connection.prepareStatement( """ - INSERT INTO chunk_data ( + INSERT OR IGNORE INTO chunk_data ( chunk_world, chunk_x, chunk_z, @@ -81,6 +83,7 @@ public void removeClaimedChunk(ChunkPos chunk) { statement.setInt(2, chunk.x()); statement.setInt(3, chunk.z()); ResultSet results = statement.executeQuery(); + if (!results.next()) return; chunkId = results.getInt(1); } @@ -116,7 +119,7 @@ public void addPlayer(FullPlayerData playerData) { try (PreparedStatement statement = connection.prepareStatement( """ - INSERT INTO player_data ( + INSERT OR IGNORE INTO player_data ( player_uuid, last_ign, chunk_name, @@ -223,7 +226,7 @@ public void addPlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlags) try (PreparedStatement statement = connection.prepareStatement( """ - INSERT INTO chunk_permissions ( + INSERT OR IGNORE INTO chunk_permissions ( chunk_id, other_player_id, permission_bits @@ -315,6 +318,36 @@ public void removePlayerAccess(ChunkPos chunk, UUID accessor) { } } + // -- Loading stuff -- // + + public Collection getAllPlayers() { + ArrayList players = new ArrayList<>(); + try (Connection connection = connectionOrException()) { + try (PreparedStatement statement = + connection.prepareStatement( + "SELECT * FROM player_data")) { + ResultSet resultSet = statement.executeQuery(); + while (resultSet.next()) { + UUID player = UUID.fromString(resultSet.getString("player_uuid")); + String lastIgn = resultSet.getString("last_ign"); + String chunkName = resultSet.getString("chunk_name"); + long lastOnlineTime = resultSet.getLong("last_online_time"); + boolean alert = resultSet.getBoolean("alerts_enabled"); + int extraMaxClaims = resultSet.getInt("extra_max_claims"); + players.add(new FullPlayerData(player, lastIgn, chunkName, lastOnlineTime, alert, extraMaxClaims)); + } + } + } catch (SQLException e) { + throw new RuntimeException("Failed to remove player access!", e); + } + return players; + } + + public Collection getAllChunks() { + // TODO: THIS + return null; + } + // -- Connection stuff -- // public @NotNull Connection connection() throws SQLException, IOException { diff --git a/src/main/java/com/cjburkey/claimchunk/layer/PlaceholderInitLayer.java b/src/main/java/com/cjburkey/claimchunk/layer/PlaceholderInitLayer.java index fab9977e..bace4d39 100644 --- a/src/main/java/com/cjburkey/claimchunk/layer/PlaceholderInitLayer.java +++ b/src/main/java/com/cjburkey/claimchunk/layer/PlaceholderInitLayer.java @@ -7,6 +7,7 @@ import lombok.Getter; +@SuppressWarnings("LombokGetterMayBeUsed") public class PlaceholderInitLayer implements IClaimChunkLayer { @Getter private ClaimChunkPlaceholders placeholders; @@ -33,6 +34,7 @@ public boolean onEnable(IClaimChunkPlugin claimChunk) { "An error occurred while trying to enable the PlaceholderAPI expansion for" + " claimchunk placeholders!"); Utils.err("Here is the error for reference:"); + //noinspection CallToPrintStackTrace e.printStackTrace(); } From 6dcc33a65ff8b48c513d388729fdb74d59bfba60 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Thu, 16 May 2024 13:54:02 -0400 Subject: [PATCH 15/26] Work on new data handler, switch player data to UUID instead of id --- .../data/newdata/BulkMySQLDataHandler.java | 2 + .../data/newdata/MySQLDataHandler.java | 43 +++++-- .../data/sqlite/SqLiteDataHandler.java | 8 +- .../sqlite/SqLiteTableMigrationManager.java | 13 +- .../claimchunk/data/sqlite/SqLiteWrapper.java | 116 +++++++++++++----- 5 files changed, 133 insertions(+), 49 deletions(-) diff --git a/src/main/java/com/cjburkey/claimchunk/data/newdata/BulkMySQLDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/newdata/BulkMySQLDataHandler.java index b557d632..40d62ce0 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/BulkMySQLDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/BulkMySQLDataHandler.java @@ -143,6 +143,7 @@ public void addClaimedChunks(DataChunk[] chunks) { @Override public void removeClaimedChunk(ChunkPos pos) { dataHandler.removeClaimedChunk(pos); + super.removeClaimedChunk(pos); } @Override @@ -214,6 +215,7 @@ public void givePlayerAccess( @Override public void takePlayerAccess(ChunkPos chunk, UUID accessor) { dataHandler.takePlayerAccess(chunk, accessor); + super.takePlayerAccess(chunk, accessor); } @Override diff --git a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java index b1415bba..ded1b73e 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java @@ -202,19 +202,46 @@ public void addClaimedChunks(DataChunk[] chunks) { } } + @SuppressWarnings("DuplicatedCode") @Override public void removeClaimedChunk(ChunkPos pos) { - String sql = - String.format( - "DELETE FROM `%s` WHERE `%s`=? AND `%s`=? AND `%s`=?", - CLAIMED_CHUNKS_TABLE_NAME, - CLAIMED_CHUNKS_WORLD, - CLAIMED_CHUNKS_X, - CLAIMED_CHUNKS_Z); - try (PreparedStatement statement = prep(claimChunk, connection, sql)) { + // Get the chunk ID + int chunkId; + try(PreparedStatement statement = prep(claimChunk, connection, String.format("SELECT `%s` FROM `%s` WHERE `%s`=? AND `%s`=? AND `%s`=?", CLAIMED_CHUNKS_ID, CLAIMED_CHUNKS_TABLE_NAME, + CLAIMED_CHUNKS_WORLD, + CLAIMED_CHUNKS_X, + CLAIMED_CHUNKS_Z))) { statement.setString(1, pos.world()); statement.setInt(2, pos.x()); statement.setInt(3, pos.z()); + + ResultSet resultSet = statement.executeQuery(); + if (!resultSet.next()) return; + chunkId = resultSet.getInt(1); + } catch (Exception e) { + Utils.err("Failed to get chunk id: %s", e.getMessage()); + //noinspection CallToPrintStackTrace + e.printStackTrace(); + return; + } + + // Remove chunk accesses + try (PreparedStatement statement = prep(claimChunk, connection, String.format( + "DELETE FROM `%s` WHERE `%s`=?", ACCESS_TABLE_NAME, ACCESS_CHUNK_ID))) { + statement.setInt(1, chunkId); + statement.execute(); + } catch (Exception e) { + Utils.err("Failed to unclaim chunk: %s", e.getMessage()); + //noinspection CallToPrintStackTrace + e.printStackTrace(); + } + + // Delete the chunk + try (PreparedStatement statement = prep(claimChunk, connection, String.format( + "DELETE FROM `%s` WHERE `%s`=?", + CLAIMED_CHUNKS_TABLE_NAME, + CLAIMED_CHUNKS_ID))) { + statement.setInt(1, chunkId); statement.execute(); } catch (Exception e) { Utils.err("Failed to unclaim chunk: %s", e.getMessage()); diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java index 22a44c84..2b912672 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java @@ -32,6 +32,8 @@ * *

I hope this is better :) * + *

Mutations are written to disk immediately, but data is kept in memory + * * @since 0.0.25 */ public class SqLiteDataHandler implements IClaimChunkDataHandler { @@ -68,15 +70,13 @@ public void save() { // Don't do anything, we save as we go } - @SuppressWarnings("StatementWithEmptyBody") @Override public void load() throws Exception { for (FullPlayerData player : sqLiteWrapper.getAllPlayers()) { - // TODO: THIS + joinedPlayers.putIfAbsent(player.player, player); } - for (DataChunk chunk : sqLiteWrapper.getAllChunks()) { - // TODO: THIS + claimedChunks.putIfAbsent(chunk.chunk, chunk); } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java index ae0c05c3..e5f11e1f 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java @@ -50,8 +50,7 @@ private static void tryCreateTables(Connection connection) throws SQLException { .prepareStatement( """ CREATE TABLE IF NOT EXISTS player_data ( - player_id INTEGER PRIMARY KEY, - player_uuid TEXT UNIQUE NOT NULL, + player_uuid TEXT PRIMARY KEY NOT NULL, last_ign TEXT NOT NULL, chunk_name TEXT, last_online_time INTEGER NOT NULL, @@ -70,9 +69,9 @@ CREATE TABLE IF NOT EXISTS chunk_data ( chunk_world TEXT NOT NULL, chunk_x INTEGER NOT NULL, chunk_z INTEGER NOT NULL, - owner_id INTEGER NOT NULL, + owner_uuid TEXT NOT NULL, - FOREIGN KEY(owner_id) REFERENCES player_data(player_id) + FOREIGN KEY(owner_uuid) REFERENCES player_data(player_uuid) ) STRICT """) .execute(); @@ -83,11 +82,11 @@ FOREIGN KEY(owner_id) REFERENCES player_data(player_id) """ CREATE TABLE IF NOT EXISTS chunk_permissions ( chunk_id INTEGER NOT NULL, - other_player_id INTEGER NOT NULL, + other_player_uuid TEXT NOT NULL, permission_bits INTEGER NOT NULL, FOREIGN KEY(chunk_id) REFERENCES chunk_data(chunk_id), - FOREIGN KEY(other_player_id) REFERENCES player_data(player_id) + FOREIGN KEY(other_player_uuid) REFERENCES player_data(player_uuid) ) STRICT """) .execute(); @@ -106,7 +105,7 @@ SELECT COUNT(*) FROM pragma_table_info(?) WHERE name=? statement.setString(1, tableName); statement.setString(2, columnName); ResultSet resultSet = statement.executeQuery(); - int count = resultSet.getInt(1); + int count = resultSet.next() ? resultSet.getInt(1) : 0; return count > 0; } diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java index 88f79c93..627392d7 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java @@ -1,6 +1,7 @@ package com.cjburkey.claimchunk.data.sqlite; import com.cjburkey.claimchunk.Utils; +import com.cjburkey.claimchunk.chunk.ChunkPlayerPermissions; import com.cjburkey.claimchunk.chunk.ChunkPos; import com.cjburkey.claimchunk.chunk.DataChunk; import com.cjburkey.claimchunk.player.FullPlayerData; @@ -12,7 +13,9 @@ import java.sql.*; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.UUID; +import java.util.stream.Collectors; public class SqLiteWrapper { @@ -52,11 +55,8 @@ INSERT OR IGNORE INTO chunk_data ( chunk_world, chunk_x, chunk_z, - owner_id - ) VALUES ( - ?, ?, ?, - (SELECT player_id FROM player_data WHERE player_uuid=?) - ) + owner_uuid + ) VALUES (?, ?, ?, ?) """)) { statement.setString(1, chunk.chunk.world()); statement.setInt(2, chunk.chunk.x()); @@ -228,7 +228,7 @@ public void addPlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlags) """ INSERT OR IGNORE INTO chunk_permissions ( chunk_id, - other_player_id, + other_player_uuid, permission_bits ) VALUES ( ( @@ -236,12 +236,7 @@ INSERT OR IGNORE INTO chunk_permissions ( 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()); @@ -269,12 +264,7 @@ public void updatePlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlag 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=? - ) + AND other_player_uuid=? """)) { statement.setInt(1, permissionFlags); statement.setString(2, chunk.world()); @@ -300,12 +290,7 @@ public void removePlayerAccess(ChunkPos chunk, UUID accessor) { 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=? - ) + AND other_player_uuid=? """)) { statement.setString(1, chunk.world()); statement.setInt(2, chunk.x()); @@ -322,10 +307,10 @@ public void removePlayerAccess(ChunkPos chunk, UUID accessor) { public Collection getAllPlayers() { ArrayList players = new ArrayList<>(); + try (Connection connection = connectionOrException()) { try (PreparedStatement statement = - connection.prepareStatement( - "SELECT * FROM player_data")) { + connection.prepareStatement("SELECT * FROM player_data")) { ResultSet resultSet = statement.executeQuery(); while (resultSet.next()) { UUID player = UUID.fromString(resultSet.getString("player_uuid")); @@ -334,18 +319,89 @@ public Collection getAllPlayers() { long lastOnlineTime = resultSet.getLong("last_online_time"); boolean alert = resultSet.getBoolean("alerts_enabled"); int extraMaxClaims = resultSet.getInt("extra_max_claims"); - players.add(new FullPlayerData(player, lastIgn, chunkName, lastOnlineTime, alert, extraMaxClaims)); + + players.add( + new FullPlayerData( + player, + lastIgn, + chunkName, + lastOnlineTime, + alert, + extraMaxClaims)); } } } catch (SQLException e) { - throw new RuntimeException("Failed to remove player access!", e); + throw new RuntimeException("Failed to load all players from SQLite database!", e); } + return players; } public Collection getAllChunks() { - // TODO: THIS - return null; + HashMap> permissions = new HashMap<>(); + HashMap owners = new HashMap<>(); + + try (Connection connection = connectionOrException()) { + // Get the permissions first + try (PreparedStatement statement = + connection.prepareStatement( + """ + SELECT chunk_world, chunk_x, chunk_z, owner_uuid, + other_player_uuid, permission_bits + FROM chunk_permissions + RIGHT JOIN chunk_data + ON chunk_permissions.chunk_id=chunk_data.chunk_id + """)) { + ResultSet resultSet = statement.executeQuery(); + while (resultSet.next()) { + String world = resultSet.getString("chunk_world"); + int chunk_x = resultSet.getInt("chunk_x"); + int chunk_z = resultSet.getInt("chunk_z"); + ChunkPos pos = new ChunkPos(world, chunk_x, chunk_z); + UUID owner = UUID.fromString(resultSet.getString("owner_uuid")); + UUID otherPlayer = UUID.fromString(resultSet.getString("other_player_uuid")); + ChunkPlayerPermissions chunkPerms = + new ChunkPlayerPermissions(resultSet.getInt("permission_bits")); + + permissions + .computeIfAbsent(pos, ignoredPos -> new HashMap<>()) + .put(otherPlayer, chunkPerms); + + owners.putIfAbsent(pos, owner); + } + } + + // Then the chunks, for chunks with no permissions granted + try (PreparedStatement statement = + connection.prepareStatement( + """ + SELECT chunk_world, chunk_x, chunk_z, owner_uuid + FROM chunk_data + """)) { + ResultSet resultSet = statement.executeQuery(); + while (resultSet.next()) { + String world = resultSet.getString("chunk_world"); + int chunk_x = resultSet.getInt("chunk_x"); + int chunk_z = resultSet.getInt("chunk_z"); + ChunkPos pos = new ChunkPos(world, chunk_x, chunk_z); + UUID owner = UUID.fromString(resultSet.getString("owner_uuid")); + + owners.putIfAbsent(pos, owner); + } + } + } catch (SQLException e) { + throw new RuntimeException("Failed to load all players from SQLite database!", e); + } + + return owners.entrySet().stream() + .map( + entry -> + new DataChunk( + entry.getKey(), + entry.getValue(), + permissions.getOrDefault(entry.getKey(), new HashMap<>()), + false)) + .collect(Collectors.toList()); } // -- Connection stuff -- // From 60e84e4b5f483b6ccee16063f3b5ca7c8aa6257b Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Thu, 16 May 2024 14:48:28 -0400 Subject: [PATCH 16/26] Conversion work --- .../com/cjburkey/claimchunk/ClaimChunk.java | 83 ++++++++++++++----- .../data/newdata/MySQLDataHandler.java | 35 +++++--- 2 files changed, 87 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java index 73a1f5cf..2bcb641d 100644 --- a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java @@ -6,6 +6,7 @@ import com.cjburkey.claimchunk.cmd.*; import com.cjburkey.claimchunk.config.ClaimChunkWorldProfileHandler; import com.cjburkey.claimchunk.config.ccconfig.*; +import com.cjburkey.claimchunk.data.conversion.IDataConverter; import com.cjburkey.claimchunk.data.newdata.*; import com.cjburkey.claimchunk.data.sqlite.SqLiteDataHandler; import com.cjburkey.claimchunk.event.*; @@ -32,6 +33,7 @@ import org.jetbrains.annotations.Nullable; import java.io.*; +import java.nio.file.Files; // TODO: Split this plugin up into services that users can use // Services: @@ -65,10 +67,14 @@ public final class ClaimChunk extends JavaPlugin implements IClaimChunkPlugin { - // The global instance of ClaimChunk on this server - // A plugin can only exist in one instance on any given server so it's ok to have a static - // instance - private static ClaimChunk instance; + /** + * External quick access to the main ClaimChunk class. + * + *

A plugin can only exist in one instance on any given server so it's ok to have a static + * instance I think. We don't actually internally use this + */ + @Getter private static ClaimChunk instance; + // Set once ClaimChunk has registered the `chunk-claim` flag with WorldGuard. private static boolean worldGuardRegisteredFlag = false; @@ -387,14 +393,62 @@ private boolean initDataHandler() { : createJsonDataHandler(); }*/ if (dataHandler == null) { - dataHandler = - new SqLiteDataHandler( - new File(getDataFolder(), "/data/claimAndPlayerData.sqlite3")); + File sqliteFile = new File(getDataFolder(), "/data/claimAndPlayerData.sqlite3"); + + IClaimChunkDataHandler oldDataHandler = null; + // UGLY HACK! + // RANKS ARE INITIALIZED AFTER DATA HANDLER, SO IF RANK FILE + // DOESN'T EXIST, WE CAN ASSUME THIS IS A NEW INSTALL RATHER THAN + // CONVERSION! + // AFTER 0.0.25 releases, 0.0.26 doesn't need to include this (but + // WILL require players to install 0.0.25 FIRST to upgrade from + // pre-0.0.25 if, say, 0.0.26 comes out while they're on 0.0.24). + if (!sqliteFile.exists() && new File(getDataFolder(), "/ranks.json").exists()) { + oldDataHandler = + (config.getUseDatabase()) + ? ((config.getGroupRequests()) + ? new BulkMySQLDataHandler<>( + this, + this::createJsonDataHandler, + ignored -> {} /*JsonDataHandler::deleteFiles*/) + : new MySQLDataHandler<>( + this, + this::createJsonDataHandler, + ignored -> {} /*JsonDataHandler::deleteFiles*/)) + : createJsonDataHandler(); + } + + dataHandler = new SqLiteDataHandler(sqliteFile); + + if (oldDataHandler != null) { + try { + IDataConverter.copyConvert(oldDataHandler, dataHandler); + oldDataHandler.exit(); + + File dataFolder = new File(getDataFolder(), "/data"); + File oldClaimedFile = new File(dataFolder, "/claimedChunks.json"); + File oldPlayerFile = new File(dataFolder, "/playerData.json"); + if (oldClaimedFile.exists()) { + Files.move( + oldClaimedFile.toPath(), + new File(dataFolder, "/OLD_claimedChunks.json").toPath()); + } + if (oldPlayerFile.exists()) { + Files.move( + oldClaimedFile.toPath(), + new File(dataFolder, "/OLD_playerData.json").toPath()); + } + } catch (Exception e) { + throw new RuntimeException( + "Failed to initialize previous data handler to convert old data!"); + } + } } + Utils.debug("Using data handler \"%s\"", dataHandler.getClass().getName()); try { // Initialize the data handler - dataHandler.init(); + if (!dataHandler.getHasInit()) dataHandler.init(); return true; } catch (Exception e) { Utils.err( @@ -669,19 +723,6 @@ public void onDisable() { Utils.log("Finished disable."); } - /** - * External quick access to the main ClaimChunk class. - * - * @return The current instance of ClaimChunk - * @see org.bukkit.plugin.PluginManager#getPlugin(String) - * @deprecated It is recommended to use {@code (ClaimChunk) - * Bukkit.getServer().getPluginManager().getPlugin("ClaimChunk")} - */ - @Deprecated - public static ClaimChunk getInstance() { - return instance; - } - public static class DataHandlerAlreadySetException extends Exception { @Serial private static final long serialVersionUID = 49857948732L; diff --git a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java index ded1b73e..ebf57dea 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java @@ -207,10 +207,17 @@ public void addClaimedChunks(DataChunk[] chunks) { public void removeClaimedChunk(ChunkPos pos) { // Get the chunk ID int chunkId; - try(PreparedStatement statement = prep(claimChunk, connection, String.format("SELECT `%s` FROM `%s` WHERE `%s`=? AND `%s`=? AND `%s`=?", CLAIMED_CHUNKS_ID, CLAIMED_CHUNKS_TABLE_NAME, - CLAIMED_CHUNKS_WORLD, - CLAIMED_CHUNKS_X, - CLAIMED_CHUNKS_Z))) { + try (PreparedStatement statement = + prep( + claimChunk, + connection, + String.format( + "SELECT `%s` FROM `%s` WHERE `%s`=? AND `%s`=? AND `%s`=?", + CLAIMED_CHUNKS_ID, + CLAIMED_CHUNKS_TABLE_NAME, + CLAIMED_CHUNKS_WORLD, + CLAIMED_CHUNKS_X, + CLAIMED_CHUNKS_Z))) { statement.setString(1, pos.world()); statement.setInt(2, pos.x()); statement.setInt(3, pos.z()); @@ -226,8 +233,13 @@ public void removeClaimedChunk(ChunkPos pos) { } // Remove chunk accesses - try (PreparedStatement statement = prep(claimChunk, connection, String.format( - "DELETE FROM `%s` WHERE `%s`=?", ACCESS_TABLE_NAME, ACCESS_CHUNK_ID))) { + try (PreparedStatement statement = + prep( + claimChunk, + connection, + String.format( + "DELETE FROM `%s` WHERE `%s`=?", + ACCESS_TABLE_NAME, ACCESS_CHUNK_ID))) { statement.setInt(1, chunkId); statement.execute(); } catch (Exception e) { @@ -237,10 +249,13 @@ public void removeClaimedChunk(ChunkPos pos) { } // Delete the chunk - try (PreparedStatement statement = prep(claimChunk, connection, String.format( - "DELETE FROM `%s` WHERE `%s`=?", - CLAIMED_CHUNKS_TABLE_NAME, - CLAIMED_CHUNKS_ID))) { + try (PreparedStatement statement = + prep( + claimChunk, + connection, + String.format( + "DELETE FROM `%s` WHERE `%s`=?", + CLAIMED_CHUNKS_TABLE_NAME, CLAIMED_CHUNKS_ID))) { statement.setInt(1, chunkId); statement.execute(); } catch (Exception e) { From d927e7422a780bc3117ec7ae55153a4730875bba Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Sun, 19 May 2024 16:36:46 -0400 Subject: [PATCH 17/26] Stop clearing stuff for a second --- .../com/cjburkey/claimchunk/data/newdata/JsonDataHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cjburkey/claimchunk/data/newdata/JsonDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/newdata/JsonDataHandler.java index dbc31ac6..f6d723f2 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/JsonDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/JsonDataHandler.java @@ -82,8 +82,8 @@ public void load() throws Exception { if (claimedChunks.values().stream().allMatch(c -> c.playerPermissions == null)) { // If all playerPermissions are null, then the JSON files are in the pre 0.0.24 format loadPre0024Data(); + Utils.log("Performing 0.0.23 to 0.0.24+ player data format"); } else if (joinedPlayersFile != null && joinedPlayersFile.exists()) { - joinedPlayers.clear(); for (FullPlayerData player : loadJsonFile(joinedPlayersFile, FullPlayerData[].class)) { joinedPlayers.put(player.player, player); } From 29e72cc9f58b8d258864715628ad9a7132d98656 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Mon, 20 May 2024 14:17:07 -0400 Subject: [PATCH 18/26] Fix version :P --- README.md | 2 +- build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c6aa9988..84b05f47 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ implementation("com.cjburkey.claimchunk:claimchunk:0.0.24-RC1") Building -------- [![Automatic Build](https://img.shields.io/github/actions/workflow/status/cjburkey01/ClaimChunk/gradle.yml?branch=main&style=for-the-badge)](https://claimchunk.cjburkey.com/server/Downloads.html#snapshot-downloads) -[![Version Info](https://img.shields.io/static/v1?label=Repository%20Version&message=0.0.24-RC2&color=ff5555&style=for-the-badge)](https://github.com/cjburkey01/ClaimChunk/archive/main.zip) +[![Version Info](https://img.shields.io/static/v1?label=Repository%20Version&message=0.0.25&color=ff5555&style=for-the-badge)](https://github.com/cjburkey01/ClaimChunk/archive/main.zip) If you want to obtain a version of the plugin that isn't available yet (like a snapshot), you can do so by asking on the Discord or building it yourself. Here's how to build it yourself: diff --git a/build.gradle.kts b/build.gradle.kts index 40e5807e..d58a601f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,7 +19,7 @@ object DepData { const val JAVA_VERSION = 17; const val LIVE_VERSION = "0.0.24-RC1"; - const val THIS_VERSION = "0.0.24-RC2"; + const val THIS_VERSION = "0.0.25"; const val PLUGIN_NAME = "ClaimChunk"; const val ARCHIVES_BASE_NAME = "claimchunk"; const val MAIN_CLASS = "com.cjburkey.claimchunk.ClaimChunk"; From 9e7f67560caa0c6374ff30f7d249c45bce7cbb15 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Mon, 20 May 2024 14:51:11 -0400 Subject: [PATCH 19/26] Add new dependency! --- .github/workflows/gradle.yml | 2 +- build.gradle.kts | 254 +++++++++++++++++------------------ 2 files changed, 128 insertions(+), 128 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index af0b2c96..294aa4b9 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -46,6 +46,6 @@ jobs: # Artifact name name: ClaimChunk # Upload plugin jar - path: OUT/*.jar + path: OUT/*-plugin.jar # Fail if the output file isn't found if-no-files-found: error diff --git a/build.gradle.kts b/build.gradle.kts index d58a601f..ec9999dc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,53 +1,51 @@ -// I NEED THESE PLEASE -@file:Suppress("RedundantSemicolon") - import com.vanniktech.maven.publish.SonatypeHost -import org.apache.tools.ant.filters.ReplaceTokens; -import de.undercouch.gradle.tasks.download.Download; +import org.apache.tools.ant.filters.ReplaceTokens +import de.undercouch.gradle.tasks.download.Download plugins { - java; + java - id("de.undercouch.download") version "5.6.0"; - id("io.freefair.lombok") version "8.6"; + id("de.undercouch.download") version "5.6.0" + id("io.freefair.lombok") version "8.6" // Including dependencies in final jar - //id("com.github.johnrengelman.shadow") version "8.1.1"; - id("com.vanniktech.maven.publish") version "0.28.0"; + id("com.github.johnrengelman.shadow") version "8.1.1" + id("com.vanniktech.maven.publish") version "0.28.0" } object DepData { - const val JAVA_VERSION = 17; + const val JAVA_VERSION = 17 - const val LIVE_VERSION = "0.0.24-RC1"; - const val THIS_VERSION = "0.0.25"; - const val PLUGIN_NAME = "ClaimChunk"; - const val ARCHIVES_BASE_NAME = "claimchunk"; - const val MAIN_CLASS = "com.cjburkey.claimchunk.ClaimChunk"; + const val LIVE_VERSION = "0.0.24-RC1" + const val THIS_VERSION = "0.0.25" + const val PLUGIN_NAME = "ClaimChunk" + const val ARCHIVES_BASE_NAME = "claimchunk" + const val MAIN_CLASS = "com.cjburkey.claimchunk.ClaimChunk" // Only used if you run `gradlew installSpigot` const val SPIGOT_BUILD_TOOLS_URL - = "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar"; - const val SPIGOT_REV = "1.20.4"; + = "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar" + const val SPIGOT_REV = "1.20.4" // Dependency versions - const val BUKKIT_VERSION = "1.20.4-R0.1-SNAPSHOT"; - const val SPIGOT_VERSION = "1.20.4-R0.1-SNAPSHOT"; - const val LATEST_MC_VERSION = "1.20.6"; - const val VAULT_API_VERSION = "1.7"; - const val WORLD_EDIT_CORE_VERSION = "7.2.9"; - const val WORLD_GUARD_BUKKIT_VERSION = "7.0.7"; - const val PLACEHOLDER_API_VERSION = "2.11.1"; - const val JETBRAINS_ANNOTATIONS_VERSION = "23.0.0"; - const val JUNIT_VERSION = "5.10.2"; - const val JUNIT_LAUNCHER_VERSION = "1.10.2"; + const val BUKKIT_VERSION = "1.20.4-R0.1-SNAPSHOT" + const val SPIGOT_VERSION = "1.20.4-R0.1-SNAPSHOT" + const val LATEST_MC_VERSION = "1.20.6" + const val VAULT_API_VERSION = "1.7" + const val WORLD_EDIT_CORE_VERSION = "7.2.9" + const val WORLD_GUARD_BUKKIT_VERSION = "7.0.7" + const val PLACEHOLDER_API_VERSION = "2.11.1" + const val JETBRAINS_ANNOTATIONS_VERSION = "23.0.0" + const val JUNIT_VERSION = "5.10.2" + const val JUNIT_LAUNCHER_VERSION = "1.10.2" + const val SANS_ORM_VERSION = "3.7" // Directories - const val TEST_SERVER_DIR = "run"; - const val OUTPUT_DIR = "OUT"; + const val TEST_SERVER_DIR = "run" + const val OUTPUT_DIR = "OUT" // Readme locations - const val README_IN = "unbuilt_readme.md"; - const val README_OUT = "README.md"; + const val README_IN = "unbuilt_readme.md" + const val README_OUT = "README.md" } // Tokens to replace within files @@ -61,17 +59,17 @@ val replaceTokens = mapOf( "SPIGOT_VERSION" to DepData.SPIGOT_VERSION.substring(0, DepData.SPIGOT_VERSION.indexOf("-")), "LATEST_MC_VERSION" to DepData.LATEST_MC_VERSION ) -); +) // Plugin information -group = "com.cjburkey"; -version = DepData.THIS_VERSION; +group = "com.cjburkey.claimchunk" +version = DepData.THIS_VERSION -val mainDir = layout.projectDirectory; +val mainDir = layout.projectDirectory java { toolchain { - languageVersion = JavaLanguageVersion.of(DepData.JAVA_VERSION); + languageVersion = JavaLanguageVersion.of(DepData.JAVA_VERSION) } } @@ -79,27 +77,26 @@ tasks { compileJava { // Disable incremental compilation (module system bs and spigot no mesh // well) - options.isIncremental = false; + options.isIncremental = false // Enable all compiler warnings for cleaner (hopefully) code - options.isWarnings = true; - options.isDeprecation = true; - options.compilerArgs.addAll(arrayOf("-Xlint:all", "-Xmaxwarns", "200")); - options.encoding = "UTF-8"; + options.isWarnings = true + options.isDeprecation = true + options.compilerArgs.addAll(arrayOf("-Xlint:all", "-Xmaxwarns", "200")) + options.encoding = "UTF-8" } // We don't actually include any other libraries now // (except smartcommanddispatcher, but we do that manually) - /*shadowJar { + shadowJar { + mustRunAfter("updateReadme") + // Set the jar name and version - archiveBaseName.set(DepData.ARCHIVES_BASE_NAME); - archiveClassifier.set("plugin"); - archiveVersion.set(project.version.toString()); + archiveBaseName.set(DepData.ARCHIVES_BASE_NAME) + archiveClassifier.set("plugin") + archiveVersion.set(project.version.toString()) - dependencies { - // Exclude annotations from output jar - exclude(dependency("org.jetbrains:annotations:.*")); - } + relocate("com.zaxxer.sansorm", "claimchunk.dependency.com.zaxxer.sansorm") // Move SmartCommandDispatcher to a unique package to avoid clashes with // any other plugins that might include it in their jar files. @@ -108,27 +105,27 @@ tasks { // Do the same with JALL // relocate("io.github.goldmensch.jall", // "claimchunk.dependency.io.github.goldmensch.jall"); - }*/ + } test { - useJUnitPlatform(); + useJUnitPlatform() systemProperties = mapOf( "junit.jupiter.conditions.deactivate" to "*", "junit.jupiter.extensions.autodetection.enabled" to "true", "junit.jupiter.testinstance.lifecycle.default" to "per_class" - ); + ) } clean { // Delete old build(s) - project.delete(files(DepData.OUTPUT_DIR)); + project.delete(files(DepData.OUTPUT_DIR)) // Delete old build(s) from test server plugin dir project.delete( fileTree(mainDir.dir("${DepData.TEST_SERVER_DIR}/plugins")) .include("claimchunk**.jar") - .include("ClaimChunk**.jar")); + .include("ClaimChunk**.jar")) } build { @@ -138,124 +135,124 @@ tasks { // (Also rebuild the README file) finalizedBy("updateReadme", "copyClaimChunkToOutputDir" - ); + ) } // Replace placeholders with values in source and resource files processResources { - filter(replaceTokens); + filter(replaceTokens) } // Fill in readme placeholders register("updateReadme") { - mustRunAfter("build"); - description = "Expands tokens in the unbuilt readme file into the main readme file"; + mustRunAfter("build") + description = "Expands tokens in the unbuilt readme file into the main readme file" - val inf = mainDir.file(DepData.README_IN); - val ouf = mainDir.file(DepData.README_OUT); + val inf = mainDir.file(DepData.README_IN) + val ouf = mainDir.file(DepData.README_OUT) doFirst { closureOf { inputs.file(ouf) - delete(ouf); + delete(ouf) } } // Set the inputs and outputs for the operation - inputs.file(inf); - outputs.file(ouf); + inputs.file(inf) + outputs.file(ouf) // Copy the new readme, rename it, and expand tokens - from(inf); - into(mainDir); - filter(replaceTokens); + from(inf) + into(mainDir) + filter(replaceTokens) rename(DepData.README_IN, DepData.README_OUT) } // Clear out old Spigot versions from test server directory register("deleteOldSpigotInstalls") { - description = "Deletes (any) old Spigot server jars from the test server directory"; + description = "Deletes (any) old Spigot server jars from the test server directory" - delete(fileTree(mainDir.dir(DepData.TEST_SERVER_DIR)).include("spigot-*.jar")); + delete(fileTree(mainDir.dir(DepData.TEST_SERVER_DIR)).include("spigot-*.jar")) } register("downloadSpigotBuildTools") { - description = "Downloads the latest version of the Spigot BuildTools into TEST_SERVER_DIR/TEMP"; + description = "Downloads the latest version of the Spigot BuildTools into TEST_SERVER_DIR/TEMP" - src(DepData.SPIGOT_BUILD_TOOLS_URL); - dest(mainDir.dir(DepData.TEST_SERVER_DIR).dir("TEMP").file("BuildTools.jar")); - overwrite(true); + src(DepData.SPIGOT_BUILD_TOOLS_URL) + dest(mainDir.dir(DepData.TEST_SERVER_DIR).dir("TEMP").file("BuildTools.jar")) + overwrite(true) } // Download and run Spigot BuildTools to generate a Spigot server jar in the spigot `testServerDir` register("installSpigot") { - description = "Downloads and executes the Spigot build tools to generate a server jar in the test server directory."; + description = "Downloads and executes the Spigot build tools to generate a server jar in the test server directory." // Delete old Spigot jar(s) and download BuildTools first - dependsOn("deleteOldSpigotInstalls", "downloadSpigotBuildTools"); + dependsOn("deleteOldSpigotInstalls", "downloadSpigotBuildTools") - val testServerDir = mainDir.dir(DepData.TEST_SERVER_DIR); - val tmpDir = testServerDir.dir("TEMP"); - val tmpServerJar = tmpDir.file("spigot-${DepData.SPIGOT_REV}.jar"); + val testServerDir = mainDir.dir(DepData.TEST_SERVER_DIR) + val tmpDir = testServerDir.dir("TEMP") + val tmpServerJar = tmpDir.file("spigot-${DepData.SPIGOT_REV}.jar") // Run the build tools jar (the manifest main class) - mainClass.set("-jar"); - workingDir(tmpDir); - args("BuildTools.jar", "--nogui", "--rev", DepData.SPIGOT_REV); + mainClass.set("-jar") + workingDir(tmpDir) + args("BuildTools.jar", "--nogui", "--rev", DepData.SPIGOT_REV) doLast { - println("Cleaning up Spigot build"); - tmpServerJar.asFile.copyTo(testServerDir.file("spigot-${DepData.SPIGOT_REV}.jar").asFile, true); - tmpDir.asFile.deleteRecursively(); + println("Cleaning up Spigot build") + tmpServerJar.asFile.copyTo(testServerDir.file("spigot-${DepData.SPIGOT_REV}.jar").asFile, true) + tmpDir.asFile.deleteRecursively() } } // Copy from the libs dir to the plugins directory in the testServerDir register("copyClaimChunkToPluginsDir") { - mustRunAfter("copyClaimChunkToOutputDir"); - description = "Copies ClaimChunk from the build directory to the test server plugin directory."; + mustRunAfter("copyClaimChunkToOutputDir") + description = "Copies ClaimChunk from the build directory to the test server plugin directory." - from(jar); - into(mainDir.dir("${DepData.TEST_SERVER_DIR}/plugins")); + from(shadowJar) + into(mainDir.dir("${DepData.TEST_SERVER_DIR}/plugins")) } register("copyClaimChunkToOutputDir") { - mustRunAfter("updateReadme"); - description = "Copies ClaimChunk from the build directory to the output directory."; + mustRunAfter("updateReadme") + description = "Copies ClaimChunk from the build directory to the output directory." - from(jar); - into(mainDir.dir(DepData.OUTPUT_DIR)); + from(shadowJar) + into(mainDir.dir(DepData.OUTPUT_DIR)) } register("googleFormat") { - description = "Attempts to format source files for ClaimChunk to unify programming style."; + description = "Attempts to format source files for ClaimChunk to unify programming style." // For now, this file is just included with the project for the sake of // ease of use. - val execJarFile = mainDir.file("req/google-java-format-1.22.0-all-deps.jar"); + val execJarFile = mainDir.file("req/google-java-format-1.22.0-all-deps.jar") // Include all source Java files // (I don't think there's a case where I would want to avoid formatting // a file, but be it necessary, this is where it would be implemented) val includedFiles = fileTree("src") { include("**/*.java") - }.files; - inputs.files(includedFiles); - outputs.files(includedFiles); + }.files + inputs.files(includedFiles) + outputs.files(includedFiles) // Run the build tools jar (the manifest main class) // The JVM args are required because of Java's Project Jigsaw - mainClass.set("-jar"); - workingDir(mainDir); - jvmArgs("--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"); - jvmArgs("--add-exports", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED"); - jvmArgs("--add-exports", "jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED"); - jvmArgs("--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED"); - jvmArgs("--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"); - args(execJarFile); - args("--replace"); - args("--aosp"); - args(includedFiles); + mainClass.set("-jar") + workingDir(mainDir) + jvmArgs("--add-exports", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED") + jvmArgs("--add-exports", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED") + jvmArgs("--add-exports", "jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED") + jvmArgs("--add-exports", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED") + jvmArgs("--add-exports", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED") + args(execJarFile) + args("--replace") + args("--aosp") + args(includedFiles) } } @@ -265,33 +262,36 @@ tasks { // Extra repos for Bukkit/Spigot stuff repositories { - mavenCentral(); - maven("https://oss.sonatype.org/content/repositories/snapshots/"); - maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/"); - maven("https://maven.enginehub.org/repo/"); - maven("https://repo.mikeprimm.com"); - maven("https://papermc.io/repo/repository/maven-public/"); - maven("https://repo.extendedclip.com/content/repositories/placeholderapi/"); - maven("https://eldonexus.de/repository/maven-public"); + mavenCentral() + maven("https://oss.sonatype.org/content/repositories/snapshots/") + maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") + maven("https://maven.enginehub.org/repo/") + maven("https://repo.mikeprimm.com") + maven("https://papermc.io/repo/repository/maven-public/") + maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") + maven("https://eldonexus.de/repository/maven-public") // Why do you have to be special, huh? maven { - url = uri("http://nexus.hc.to/content/repositories/pub_releases/"); - isAllowInsecureProtocol = true; + url = uri("http://nexus.hc.to/content/repositories/pub_releases/") + isAllowInsecureProtocol = true } } dependencies { // Things needed to compile the plugin - compileOnly("org.jetbrains:annotations:${DepData.JETBRAINS_ANNOTATIONS_VERSION}"); - compileOnly("org.spigotmc:spigot-api:${DepData.SPIGOT_VERSION}"); - compileOnly("net.milkbowl.vault:VaultAPI:${DepData.VAULT_API_VERSION}"); - compileOnly("com.sk89q.worldedit:worldedit-core:${DepData.WORLD_EDIT_CORE_VERSION}"); - compileOnly("com.sk89q.worldguard:worldguard-bukkit:${DepData.WORLD_GUARD_BUKKIT_VERSION}"); - compileOnly("me.clip:placeholderapi:${DepData.PLACEHOLDER_API_VERSION}"); - - testImplementation("org.junit.jupiter:junit-jupiter:${DepData.JUNIT_VERSION}"); - testRuntimeOnly("org.junit.platform:junit-platform-launcher:${DepData.JUNIT_LAUNCHER_VERSION}"); + compileOnly("org.jetbrains:annotations:${DepData.JETBRAINS_ANNOTATIONS_VERSION}") + compileOnly("org.spigotmc:spigot-api:${DepData.SPIGOT_VERSION}") + compileOnly("net.milkbowl.vault:VaultAPI:${DepData.VAULT_API_VERSION}") + compileOnly("com.sk89q.worldedit:worldedit-core:${DepData.WORLD_EDIT_CORE_VERSION}") + compileOnly("com.sk89q.worldguard:worldguard-bukkit:${DepData.WORLD_GUARD_BUKKIT_VERSION}") + compileOnly("me.clip:placeholderapi:${DepData.PLACEHOLDER_API_VERSION}") + + // We need to shade this! + implementation("com.zaxxer:sansorm:${DepData.SANS_ORM_VERSION}") + + testImplementation("org.junit.jupiter:junit-jupiter:${DepData.JUNIT_VERSION}") + testRuntimeOnly("org.junit.platform:junit-platform-launcher:${DepData.JUNIT_LAUNCHER_VERSION}") } From b091558d896b31252652f70266853353e16740d8 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Tue, 21 May 2024 01:29:52 -0400 Subject: [PATCH 20/26] Switch to library but now it doesn't work --- build.gradle.kts | 32 +- .../data/sqlite/SqLiteDataHandler.java | 15 +- .../sqlite/SqLiteTableMigrationManager.java | 116 ++-- .../claimchunk/data/sqlite/SqLiteWrapper.java | 553 +++++++----------- .../claimchunk/data/sqlite/SqlDataChunk.java | 36 ++ .../data/sqlite/SqlDataChunkPermission.java | 34 ++ .../claimchunk/data/sqlite/SqlDataPlayer.java | 42 ++ .../claimchunk/player/FullPlayerData.java | 12 + 8 files changed, 399 insertions(+), 441 deletions(-) create mode 100644 src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java create mode 100644 src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunkPermission.java create mode 100644 src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataPlayer.java diff --git a/build.gradle.kts b/build.gradle.kts index ec9999dc..fc3b5e3c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import com.vanniktech.maven.publish.SonatypeHost import org.apache.tools.ant.filters.ReplaceTokens import de.undercouch.gradle.tasks.download.Download @@ -37,7 +38,10 @@ object DepData { const val JETBRAINS_ANNOTATIONS_VERSION = "23.0.0" const val JUNIT_VERSION = "5.10.2" const val JUNIT_LAUNCHER_VERSION = "1.10.2" - const val SANS_ORM_VERSION = "3.7" + const val SQLITE_JDBC_VERSION = "3.42.0.1" + const val JAVAX_PERSISTENCE_VERSION = "2.1.0" + const val JAVAX_TRANSACTION_VERSION = "1.1" + const val SANS_ORM_VERSION = "3.17" // Directories const val TEST_SERVER_DIR = "run" @@ -75,6 +79,8 @@ java { tasks { compileJava { + mustRunAfter("googleFormat") + // Disable incremental compilation (module system bs and spigot no mesh // well) options.isIncremental = false @@ -96,15 +102,16 @@ tasks { archiveClassifier.set("plugin") archiveVersion.set(project.version.toString()) - relocate("com.zaxxer.sansorm", "claimchunk.dependency.com.zaxxer.sansorm") + dependencies { + exclude(dependency("org.slf4j:slf4j-api")) + exclude(dependency("org.xerial:sqlite-jdbc")) + } - // Move SmartCommandDispatcher to a unique package to avoid clashes with - // any other plugins that might include it in their jar files. - // relocate("de.goldmensch.commanddispatcher", - // "claimchunk.dependency.de.goldmensch.commanddispatcher"); - // Do the same with JALL - // relocate("io.github.goldmensch.jall", - // "claimchunk.dependency.io.github.goldmensch.jall"); + relocate("com.zaxxer", "claimchunk.dependency.com.zaxxer") + relocate("javax.persistence", "claimchunk.dependency.javax.persistence") + relocate("javax.transaction", "claimchunk.dependency.javax.transaction") + relocate("org.eclipse", "claimchunk.dependency.org.eclipse") + relocate("org.osgi", "claimchunk.dependency.org.osgi") } test { @@ -287,8 +294,11 @@ dependencies { compileOnly("com.sk89q.worldguard:worldguard-bukkit:${DepData.WORLD_GUARD_BUKKIT_VERSION}") compileOnly("me.clip:placeholderapi:${DepData.PLACEHOLDER_API_VERSION}") - // We need to shade this! - implementation("com.zaxxer:sansorm:${DepData.SANS_ORM_VERSION}") + // We need these during runtime! + implementation("org.xerial:sqlite-jdbc:${DepData.SQLITE_JDBC_VERSION}") + implementation("org.eclipse.persistence:javax.persistence:${DepData.JAVAX_PERSISTENCE_VERSION}") + implementation("javax.transaction:transaction-api:${DepData.JAVAX_TRANSACTION_VERSION}") + implementation("com.github.h-thurow:q2o:${DepData.SANS_ORM_VERSION}") testImplementation("org.junit.jupiter:junit-jupiter:${DepData.JUNIT_VERSION}") testRuntimeOnly("org.junit.platform:junit-platform-launcher:${DepData.JUNIT_LAUNCHER_VERSION}") diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java index 2b912672..5dcca7b5 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java @@ -63,7 +63,9 @@ public boolean getHasInit() { } @Override - public void exit() {} + public void exit() { + sqLiteWrapper.close(); + } @Override public void save() { @@ -234,15 +236,8 @@ public void givePlayerAccess( ChunkPos chunk, UUID accessor, ChunkPlayerPermissions permissions) { 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); - } + chunkData.playerPermissions.put(accessor, permissions); + sqLiteWrapper.updateOrInsertPlayerAccess(chunk, accessor, permissions.permissionFlags); } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java index e5f11e1f..a62e111b 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java @@ -1,95 +1,63 @@ package com.cjburkey.claimchunk.data.sqlite; -import org.jetbrains.annotations.NotNull; +import com.zaxxer.q2o.Q2Sql; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.function.Supplier; /** This class is responsible for creating, loading, and upgrading the database file. */ public class SqLiteTableMigrationManager { - public static void go(Supplier connectionSupplier) - throws RuntimeException, SQLException { - try (Connection connection = ensureConnection(connectionSupplier)) { - // Make tables if they don't exist - initializeTables(connection); + public static void go() { + // Make tables if they don't exist + tryCreateTables(); - // Call migration check methods here. - } + // Call migration check methods here. } - private static @NotNull Connection ensureConnection(Supplier connectionSupplier) - throws RuntimeException { - Connection connection = connectionSupplier.get(); - try { - if (connection != null && !connection.isClosed()) { - return connection; - } else { - throw new RuntimeException("Connection provided was not valid."); - } - } catch (SQLException e) { - throw new RuntimeException( - "Failed to create connection to ClaimChunk SQLite database file", e); - } - } - - private static void initializeTables(Connection connection) throws SQLException { - tryCreateTables(connection); - - // Call migration methods here. - // Add table column exist checks inside each method to make this method - // cleaner. - } - - private static void tryCreateTables(Connection connection) throws SQLException { - // Player data table - connection - .prepareStatement( - """ - CREATE TABLE IF NOT EXISTS player_data ( - player_uuid TEXT PRIMARY KEY NOT NULL, - last_ign TEXT NOT NULL, - chunk_name TEXT, - last_online_time INTEGER NOT NULL, - alerts_enabled INTEGER NOT NULL, - extra_max_claims INTEGER NOT NULL - ) STRICT - """) - .execute(); + private static void tryCreateTables() { + // Create player data table + Q2Sql.executeUpdate( + """ + CREATE TABLE IF NOT EXISTS player_data ( + player_uuid TEXT PRIMARY KEY NOT NULL, + last_ign TEXT NOT NULL, + chunk_name TEXT, + last_online_time INTEGER NOT NULL, + alerts_enabled INTEGER NOT NULL, + extra_max_claims INTEGER NOT NULL + ) STRICT + """); // Chunk data table - connection - .prepareStatement( - """ - CREATE TABLE IF NOT EXISTS chunk_data ( - chunk_id INTEGER PRIMARY KEY, - chunk_world TEXT NOT NULL, - chunk_x INTEGER NOT NULL, - chunk_z INTEGER NOT NULL, - owner_uuid TEXT NOT NULL, - - FOREIGN KEY(owner_uuid) REFERENCES player_data(player_uuid) - ) STRICT - """) - .execute(); + Q2Sql.executeUpdate( + """ + CREATE TABLE IF NOT EXISTS chunk_data ( + chunk_id INTEGER PRIMARY KEY, + chunk_world TEXT NOT NULL, + chunk_x INTEGER NOT NULL, + chunk_z INTEGER NOT NULL, + owner_uuid TEXT NOT NULL, + + FOREIGN KEY(owner_uuid) REFERENCES player_data(player_uuid) + ) STRICT + """); // Granular chunk player permission table - connection - .prepareStatement( - """ - CREATE TABLE IF NOT EXISTS chunk_permissions ( - chunk_id INTEGER NOT NULL, - other_player_uuid TEXT NOT NULL, - permission_bits INTEGER NOT NULL, - - FOREIGN KEY(chunk_id) REFERENCES chunk_data(chunk_id), - FOREIGN KEY(other_player_uuid) REFERENCES player_data(player_uuid) - ) STRICT - """) - .execute(); + Q2Sql.executeUpdate( + """ + CREATE TABLE IF NOT EXISTS chunk_permissions ( + perm_id INTEGER PRIMARY KEY, + chunk_id INTEGER NOT NULL, + other_player_uuid TEXT NOT NULL, + permission_bits INTEGER NOT NULL, + + FOREIGN KEY(chunk_id) REFERENCES chunk_data(chunk_id), + FOREIGN KEY(other_player_uuid) REFERENCES player_data(player_uuid) + ) STRICT + """); } // Use this method to determine if a column exists in a table to perform migrations diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java index 627392d7..f8076aa6 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java @@ -5,392 +5,275 @@ import com.cjburkey.claimchunk.chunk.ChunkPos; import com.cjburkey.claimchunk.chunk.DataChunk; import com.cjburkey.claimchunk.player.FullPlayerData; +import com.zaxxer.q2o.*; import org.jetbrains.annotations.NotNull; +import org.sqlite.SQLiteDataSource; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.sql.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; -public class SqLiteWrapper { +public record SqLiteWrapper(File dbFile) implements Closeable { - public final File dbFile; - private Connection liveConnection; + private static final String SELECT_CHUNK_ID_SQL = + """ + ( + SELECT chunk_id + FROM chunk_data + WHERE chunk_world=? AND chunk_x=? AND chunk_z=? + ) + """; - public SqLiteWrapper(@NotNull File dbFile) throws RuntimeException { + public SqLiteWrapper(@NotNull File dbFile) { this.dbFile = dbFile; try { - // Make sure the SQLite driver exists and get it in the classpath - // for the DriverManager to search. - Class.forName("org.sqlite.JDBC"); - - // Initialize the tables and perform any changes to them - SqLiteTableMigrationManager.go(this::connectionOrException); - } catch (ClassNotFoundException e) { + if (!dbFile.exists() && dbFile.createNewFile()) { + Utils.warn("Created empty database file"); + } + } catch (IOException e) { throw new RuntimeException( - "Cannot find SQLite JDBC class? Not sure how this can happen. Please submit an" - + " issue on GitHub", - e); - } catch (SQLException e) { - throw new RuntimeException("Failed to initialize tables! This is fatal!", e); + "Failed to create new database file even though it didn't exist!", e); } + + // Make sure the SQLite driver exists and get it in the classpath + // for the DriverManager to search. + SQLiteDataSource dataSource = new SQLiteDataSource(); + dataSource.setUrl("jdbc:sqlite:" + dbFile); + //q2o.initializeTxSimple(dataSource); + q2o.initializeTxNone(dataSource); + + // Initialize the tables and perform any changes to them + SqLiteTableMigrationManager.go(); + + // TODO: WHY DOESN'T PARAMETER SUBSTITUTION WORK?!?!?! + // HELP ME PLEASE + Q2Sql.executeUpdate( + """ + INSERT INTO player_data ( + player_uuid, + last_ign, + last_online_time, + alerts_enabled, + extra_max_claims + ) VALUES ( + ?, + "CJBurkey", + 72468, + TRUE, + 0 + ) + """, + "8da30070-a1df-4e47-a913-f12424aabf6a"); + } + + @Override + public void close() { + q2o.deinitialize(); } // -- DATABASE INTEGRATIONS! -- // public void addClaimedChunk(DataChunk chunk) { - try (Connection connection = connectionOrException()) { - // Use the nested select query to get the user's row ID as the - // owner's id - try (PreparedStatement statement = - connection.prepareStatement( - """ - INSERT OR IGNORE INTO chunk_data ( - chunk_world, - chunk_x, - chunk_z, - owner_uuid - ) VALUES (?, ?, ?, ?) - """)) { - 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); - } + Q2Obj.insert(new SqlDataChunk(chunk)); } public void removeClaimedChunk(ChunkPos chunk) { - try (Connection connection = connectionOrException()) { - // 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(); - if (!results.next()) return; - 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); - } + int chunkId = + SqlClosure.sqlExecute( + connection -> { + // Get chunk ID + ResultSet resultSet = + Q2Sql.executeQuery( + connection, + """ + SELECT chunk_id FROM chunk_data + WHERE chunk_world=? AND chunk_x=? AND chunk_z=? + """, + chunk.world(), + chunk.x(), + chunk.z()); + return resultSet.next() ? resultSet.getInt(1) : -1; + }); + if (chunkId < 0) return; + + // Remove permissions + Q2Sql.executeUpdate( + """ + DELETE FROM chunk_permissions + WHERE chunk_id=? + """, + chunkId); + + // Remove chunks + Q2Sql.executeUpdate( + """ + DELETE FROM chunk_data + WHERE chunk_id=? + """, + chunkId); } - // TODO: TEST public void addPlayer(FullPlayerData playerData) { - try (Connection connection = connectionOrException()) { - try (PreparedStatement statement = - connection.prepareStatement( - """ - INSERT OR IGNORE 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); - } + Q2Obj.insert(new SqlDataPlayer(playerData)); } public void setPlayerLastOnline(UUID player, long time) { - try (Connection connection = connectionOrException()) { - // 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); - } + Q2Sql.executeUpdate( + """ + UPDATE player_data + SET last_online_time=? + WHERE player_uuid=? + """, + time, + player.toString()); } public void setPlayerChunkName(UUID player, String chunkName) { - try (Connection connection = connectionOrException()) { - // 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); - } + Q2Sql.executeUpdate( + """ + UPDATE player_data + SET chunk_name=? + WHERE player_uuid=? + """, + chunkName, + player.toString()); } public void setPlayerReceiveAlerts(UUID player, boolean receiveAlerts) { - try (Connection connection = connectionOrException()) { - // 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); - } + Q2Sql.executeUpdate( + """ + UPDATE player_data + SET receiveAlerts=? + WHERE player_uuid=? + """, + receiveAlerts, + player.toString()); } public void setPlayerExtraMaxClaims(UUID player, int extraMaxClaims) { - try (Connection connection = connectionOrException()) { - // 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); - } + Q2Sql.executeUpdate( + """ + UPDATE player_data + SET extra_max_claims=? + WHERE player_uuid=? + """, + extraMaxClaims, + player.toString()); } - public void addPlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlags) { - try (Connection connection = connectionOrException()) { - try (PreparedStatement statement = - connection.prepareStatement( + public void updateOrInsertPlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlags) { + // Check if the access already exists + // If so, update the permission bits + if (Q2Obj.countFromClause( + SqlDataChunkPermission.class, + "other_player_uuid=? AND chunk_id=" + SELECT_CHUNK_ID_SQL, + accessor.toString(), + chunk.world(), + chunk.x(), + chunk.z()) + > 0) { + Q2Sql.executeUpdate( + String.format( + """ + UPDATE chunk_permissions + SET permission_bits=? + WHERE chunk_id=%s + AND other_player_uuid=? + """, + SELECT_CHUNK_ID_SQL), + permissionFlags, + chunk.world(), + chunk.x(), + chunk.z(), + accessor.toString()); + } else { + Q2Sql.executeUpdate( + String.format( """ - INSERT OR IGNORE INTO chunk_permissions ( + INSERT INTO chunk_permissions ( chunk_id, other_player_uuid, permission_bits ) VALUES ( - ( - SELECT chunk_id - FROM chunk_data - WHERE chunk_world=? AND chunk_x=? AND chunk_z=? - ), - ?, ? + %s, ?, ? ) - """)) { - 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 = connectionOrException()) { - 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_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); + """, + SELECT_CHUNK_ID_SQL), + chunk.world(), + chunk.x(), + chunk.z(), + accessor.toString(), + permissionFlags); } } public void removePlayerAccess(ChunkPos chunk, UUID accessor) { - try (Connection connection = connectionOrException()) { - 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_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); - } + Q2Sql.executeUpdate( + String.format( + """ + DELETE FROM chunk_permissions + WHERE chunk_id=%s, + AND other_player_uuid=? + """, + SELECT_CHUNK_ID_SQL), + chunk.world(), + chunk.x(), + chunk.z(), + accessor.toString()); } // -- Loading stuff -- // - public Collection getAllPlayers() { - ArrayList players = new ArrayList<>(); - - try (Connection connection = connectionOrException()) { - try (PreparedStatement statement = - connection.prepareStatement("SELECT * FROM player_data")) { - ResultSet resultSet = statement.executeQuery(); - while (resultSet.next()) { - UUID player = UUID.fromString(resultSet.getString("player_uuid")); - String lastIgn = resultSet.getString("last_ign"); - String chunkName = resultSet.getString("chunk_name"); - long lastOnlineTime = resultSet.getLong("last_online_time"); - boolean alert = resultSet.getBoolean("alerts_enabled"); - int extraMaxClaims = resultSet.getInt("extra_max_claims"); - - players.add( - new FullPlayerData( - player, - lastIgn, - chunkName, - lastOnlineTime, - alert, - extraMaxClaims)); - } - } - } catch (SQLException e) { - throw new RuntimeException("Failed to load all players from SQLite database!", e); - } - - return players; + public List getAllPlayers() { + return Q2ObjList.fromClause(SqlDataPlayer.class, null).stream() + .map(FullPlayerData::new) + .toList(); } public Collection getAllChunks() { HashMap> permissions = new HashMap<>(); HashMap owners = new HashMap<>(); - try (Connection connection = connectionOrException()) { - // Get the permissions first - try (PreparedStatement statement = - connection.prepareStatement( - """ - SELECT chunk_world, chunk_x, chunk_z, owner_uuid, - other_player_uuid, permission_bits - FROM chunk_permissions - RIGHT JOIN chunk_data - ON chunk_permissions.chunk_id=chunk_data.chunk_id - """)) { - ResultSet resultSet = statement.executeQuery(); - while (resultSet.next()) { - String world = resultSet.getString("chunk_world"); - int chunk_x = resultSet.getInt("chunk_x"); - int chunk_z = resultSet.getInt("chunk_z"); - ChunkPos pos = new ChunkPos(world, chunk_x, chunk_z); - UUID owner = UUID.fromString(resultSet.getString("owner_uuid")); - UUID otherPlayer = UUID.fromString(resultSet.getString("other_player_uuid")); - ChunkPlayerPermissions chunkPerms = - new ChunkPlayerPermissions(resultSet.getInt("permission_bits")); - - permissions - .computeIfAbsent(pos, ignoredPos -> new HashMap<>()) - .put(otherPlayer, chunkPerms); - - owners.putIfAbsent(pos, owner); - } + try (ResultSet resultSet = + SqlClosure.sqlExecute( + connection -> + Q2Sql.executeQuery( + connection, + """ + SELECT chunk_world, chunk_x, chunk_z, owner_uuid, + other_player_uuid, permission_bits + FROM chunk_permissions + RIGHT JOIN chunk_data + ON chunk_permissions.chunk_id=chunk_data.chunk_id + """))) { + while (resultSet.next()) { + String world = resultSet.getString("chunk_world"); + int chunk_x = resultSet.getInt("chunk_x"); + int chunk_z = resultSet.getInt("chunk_z"); + ChunkPos pos = new ChunkPos(world, chunk_x, chunk_z); + UUID owner = UUID.fromString(resultSet.getString("owner_uuid")); + UUID otherPlayer = UUID.fromString(resultSet.getString("other_player_uuid")); + ChunkPlayerPermissions chunkPerms = + new ChunkPlayerPermissions(resultSet.getInt("permission_bits")); + + permissions + .computeIfAbsent(pos, ignoredPos -> new HashMap<>()) + .put(otherPlayer, chunkPerms); + + owners.putIfAbsent(pos, owner); } + } catch (Exception e) { + throw new RuntimeException("Failed to load chunk data!", e); + } - // Then the chunks, for chunks with no permissions granted - try (PreparedStatement statement = - connection.prepareStatement( - """ - SELECT chunk_world, chunk_x, chunk_z, owner_uuid - FROM chunk_data - """)) { - ResultSet resultSet = statement.executeQuery(); - while (resultSet.next()) { - String world = resultSet.getString("chunk_world"); - int chunk_x = resultSet.getInt("chunk_x"); - int chunk_z = resultSet.getInt("chunk_z"); - ChunkPos pos = new ChunkPos(world, chunk_x, chunk_z); - UUID owner = UUID.fromString(resultSet.getString("owner_uuid")); - - owners.putIfAbsent(pos, owner); - } - } - } catch (SQLException e) { - throw new RuntimeException("Failed to load all players from SQLite database!", e); + for (SqlDataChunk chunk : Q2ObjList.fromClause(SqlDataChunk.class, null)) { + owners.putIfAbsent( + new ChunkPos(chunk.world, chunk.x, chunk.z), UUID.fromString(chunk.uuid)); } return owners.entrySet().stream() @@ -403,26 +286,4 @@ public Collection getAllChunks() { false)) .collect(Collectors.toList()); } - - // -- Connection stuff -- // - - public @NotNull Connection connection() throws SQLException, IOException { - if (liveConnection == null || liveConnection.isClosed()) { - if (!dbFile.exists() && dbFile.createNewFile()) { - Utils.warn("Created empty database file"); - } - liveConnection = DriverManager.getConnection("jdbc:sqlite:" + dbFile); - } - return liveConnection; - } - - public @NotNull Connection connectionOrException() throws RuntimeException { - try { - return connection(); - } catch (SQLException e) { - throw new RuntimeException("SQLException on connection creation", e); - } catch (IOException e) { - throw new RuntimeException("Failed to create new file " + dbFile, e); - } - } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java new file mode 100644 index 00000000..585d73d4 --- /dev/null +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java @@ -0,0 +1,36 @@ +package com.cjburkey.claimchunk.data.sqlite; + +import com.cjburkey.claimchunk.chunk.DataChunk; + +import javax.persistence.*; + +@Table(name = "chunk_data") +public class SqlDataChunk { + + @Id + @Column(name = "chunk_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + public int chunkId; + + @Column(name = "chunk_world") + public String world; + + @Column(name = "chunk_x") + public int x; + + @Column(name = "chunk_z") + public int z; + + @Column(name = "owner_uuid") + public String uuid; + + @SuppressWarnings("unused") + public SqlDataChunk() {} + + public SqlDataChunk(DataChunk chunk) { + this.world = chunk.chunk.world(); + this.x = chunk.chunk.x(); + this.z = chunk.chunk.z(); + this.uuid = chunk.player.toString(); + } +} diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunkPermission.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunkPermission.java new file mode 100644 index 00000000..a11aa497 --- /dev/null +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunkPermission.java @@ -0,0 +1,34 @@ +package com.cjburkey.claimchunk.data.sqlite; + +import com.cjburkey.claimchunk.chunk.ChunkPlayerPermissions; + +import java.util.UUID; + +import javax.persistence.*; + +@Table(name = "chunk_permissions") +public class SqlDataChunkPermission { + + @Id + @Column(name = "perm_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + public int permissionId; + + @Column(name = "chunk_id") + public int chunkId; + + @Column(name = "other_player_uuid") + public String otherPlayerUuid; + + @Column(name = "permission_bits") + public int permissionBits; + + @SuppressWarnings("unused") + public SqlDataChunkPermission() {} + + public SqlDataChunkPermission(UUID otherPlayer, ChunkPlayerPermissions permissions) { + this.chunkId = -1; + this.otherPlayerUuid = otherPlayer.toString(); + this.permissionBits = permissions.permissionFlags; + } +} diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataPlayer.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataPlayer.java new file mode 100644 index 00000000..c8bd2999 --- /dev/null +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataPlayer.java @@ -0,0 +1,42 @@ +package com.cjburkey.claimchunk.data.sqlite; + +import com.cjburkey.claimchunk.player.FullPlayerData; + +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.Table; + +@Table(name = "player_data") +public class SqlDataPlayer { + + @Id + @Column(name = "player_uuid") + public String uuid; + + @Column(name = "last_ign") + public String lastIgn; + + @Column(name = "chunk_name") + public String chunkName; + + @Column(name = "last_online_time") + public long lastOnlineTime; + + @Column(name = "alerts_enabled") + public boolean alert; + + @Column(name = "extra_max_claims") + public int extraMaxClaims; + + @SuppressWarnings("unused") + public SqlDataPlayer() {} + + public SqlDataPlayer(FullPlayerData player) { + this.uuid = player.player.toString(); + this.lastIgn = player.lastIgn; + this.chunkName = player.chunkName; + this.lastOnlineTime = player.lastOnlineTime; + this.alert = player.alert; + this.extraMaxClaims = player.extraMaxClaims; + } +} diff --git a/src/main/java/com/cjburkey/claimchunk/player/FullPlayerData.java b/src/main/java/com/cjburkey/claimchunk/player/FullPlayerData.java index 578d2868..27ef0381 100644 --- a/src/main/java/com/cjburkey/claimchunk/player/FullPlayerData.java +++ b/src/main/java/com/cjburkey/claimchunk/player/FullPlayerData.java @@ -1,5 +1,7 @@ package com.cjburkey.claimchunk.player; +import com.cjburkey.claimchunk.data.sqlite.SqlDataPlayer; + import java.util.UUID; public class FullPlayerData implements Cloneable { @@ -26,6 +28,16 @@ public FullPlayerData( this.extraMaxClaims = extraMaxClaims; } + public FullPlayerData(SqlDataPlayer player) { + this( + UUID.fromString(player.uuid), + player.lastIgn, + player.chunkName, + player.lastOnlineTime, + player.alert, + player.extraMaxClaims); + } + private FullPlayerData(FullPlayerData clone) { this( clone.player, From 99526452d2dbba5d9b9f9f4ed21abba54a30390c Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Thu, 23 May 2024 00:37:54 -0400 Subject: [PATCH 21/26] Remove problematic Q2O calls :/ --- .../data/sqlite/SqLiteDataHandler.java | 2 +- .../sqlite/SqLiteTableMigrationManager.java | 30 +- .../claimchunk/data/sqlite/SqLiteWrapper.java | 452 +++++++++++------- .../cjburkey/claimchunk/TestSQLPlease.java | 53 ++ 4 files changed, 341 insertions(+), 196 deletions(-) create mode 100644 src/test/java/com/cjburkey/claimchunk/TestSQLPlease.java diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java index 5dcca7b5..a9386043 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java @@ -52,7 +52,7 @@ public SqLiteDataHandler(@NotNull File claimChunkDb) { public void init() { joinedPlayers = new HashMap<>(); claimedChunks = new HashMap<>(); - sqLiteWrapper = new SqLiteWrapper(claimChunkDb); + sqLiteWrapper = new SqLiteWrapper(claimChunkDb, false); init = true; } diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java index a62e111b..cf907c4d 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java @@ -1,11 +1,10 @@ package com.cjburkey.claimchunk.data.sqlite; import com.zaxxer.q2o.Q2Sql; +import com.zaxxer.q2o.SqlClosure; -import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.SQLException; /** This class is responsible for creating, loading, and upgrading the database file. */ public class SqLiteTableMigrationManager { @@ -63,18 +62,21 @@ FOREIGN KEY(other_player_uuid) REFERENCES player_data(player_uuid) // Use this method to determine if a column exists in a table to perform migrations // TODO: MAYBE CHECK IF THIS WORKS @SuppressWarnings("unused") - private static boolean columnExists(Connection connection, String tableName, String columnName) - throws SQLException { - PreparedStatement statement = - connection.prepareCall( - """ - SELECT COUNT(*) FROM pragma_table_info(?) WHERE name=? - """); - statement.setString(1, tableName); - statement.setString(2, columnName); - ResultSet resultSet = statement.executeQuery(); - int count = resultSet.next() ? resultSet.getInt(1) : 0; - return count > 0; + public static boolean columnExists(String tableName, String columnName) { + return SqlClosure.sqlExecute( + connection -> { + try (PreparedStatement statement = + connection.prepareStatement( + """ + SELECT COUNT(*) FROM pragma_table_info(?) WHERE name=? + """)) { + statement.setString(1, tableName); + statement.setString(2, columnName); + ResultSet resultSet = statement.executeQuery(); + int count = resultSet.next() ? resultSet.getInt(1) : 0; + return count > 0; + } + }); } // Whenever a column is added or moved or transformed or whatever, add a diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java index f8076aa6..e81c93fc 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java @@ -1,6 +1,5 @@ package com.cjburkey.claimchunk.data.sqlite; -import com.cjburkey.claimchunk.Utils; import com.cjburkey.claimchunk.chunk.ChunkPlayerPermissions; import com.cjburkey.claimchunk.chunk.ChunkPos; import com.cjburkey.claimchunk.chunk.DataChunk; @@ -13,11 +12,17 @@ import java.io.Closeable; import java.io.File; import java.io.IOException; -import java.sql.*; -import java.util.*; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import java.util.regex.Pattern; import java.util.stream.Collectors; -public record SqLiteWrapper(File dbFile) implements Closeable { +public record SqLiteWrapper(File dbFile, boolean usesTransactionManager) implements Closeable { private static final String SELECT_CHUNK_ID_SQL = """ @@ -27,14 +32,16 @@ public record SqLiteWrapper(File dbFile) implements Closeable { WHERE chunk_world=? AND chunk_x=? AND chunk_z=? ) """; + private static final Pattern SELECT_CHUNK_ID_SQL_PATTERN = + Pattern.compile(Pattern.quote("%%SELECT_CHUNK_ID_SQL%%")); - public SqLiteWrapper(@NotNull File dbFile) { + public SqLiteWrapper(@NotNull File dbFile, boolean usesTransactionManager) { this.dbFile = dbFile; + this.usesTransactionManager = usesTransactionManager; try { - if (!dbFile.exists() && dbFile.createNewFile()) { - Utils.warn("Created empty database file"); - } + //noinspection ResultOfMethodCallIgnored + dbFile.createNewFile(); } catch (IOException e) { throw new RuntimeException( "Failed to create new database file even though it didn't exist!", e); @@ -44,31 +51,11 @@ public SqLiteWrapper(@NotNull File dbFile) { // for the DriverManager to search. SQLiteDataSource dataSource = new SQLiteDataSource(); dataSource.setUrl("jdbc:sqlite:" + dbFile); - //q2o.initializeTxSimple(dataSource); - q2o.initializeTxNone(dataSource); + if (usesTransactionManager) q2o.initializeTxSimple(dataSource); + else q2o.initializeTxNone(dataSource); // Initialize the tables and perform any changes to them SqLiteTableMigrationManager.go(); - - // TODO: WHY DOESN'T PARAMETER SUBSTITUTION WORK?!?!?! - // HELP ME PLEASE - Q2Sql.executeUpdate( - """ - INSERT INTO player_data ( - player_uuid, - last_ign, - last_online_time, - alerts_enabled, - extra_max_claims - ) VALUES ( - ?, - "CJBurkey", - 72468, - TRUE, - 0 - ) - """, - "8da30070-a1df-4e47-a913-f12424aabf6a"); } @Override @@ -79,152 +66,233 @@ public void close() { // -- DATABASE INTEGRATIONS! -- // public void addClaimedChunk(DataChunk chunk) { - Q2Obj.insert(new SqlDataChunk(chunk)); + SqlClosure.sqlExecute( + connection -> { + try (PreparedStatement statement = + connection.prepareStatement( + """ + INSERT INTO chunk_data ( + chunk_world, + chunk_x, + chunk_z, + owner_uuid + ) VALUES ( + ?, ?, ?, ? + ) + """)) { + int next = setChunkPosParams(statement, 1, chunk.chunk); + statement.setString(next, chunk.player.toString()); + statement.execute(); + return null; + } + }); } public void removeClaimedChunk(ChunkPos chunk) { - int chunkId = - SqlClosure.sqlExecute( - connection -> { - // Get chunk ID - ResultSet resultSet = - Q2Sql.executeQuery( - connection, + SqlClosure.sqlExecute( + connection -> { + // Remove all granted permissions for the chunk + try (PreparedStatement statement = + connection.prepareStatement( + replaceChunkIdQuery( """ - SELECT chunk_id FROM chunk_data - WHERE chunk_world=? AND chunk_x=? AND chunk_z=? - """, - chunk.world(), - chunk.x(), - chunk.z()); - return resultSet.next() ? resultSet.getInt(1) : -1; - }); - if (chunkId < 0) return; - - // Remove permissions - Q2Sql.executeUpdate( - """ - DELETE FROM chunk_permissions - WHERE chunk_id=? - """, - chunkId); - - // Remove chunks - Q2Sql.executeUpdate( - """ - DELETE FROM chunk_data - WHERE chunk_id=? - """, - chunkId); + DELETE FROM chunk_permissions + WHERE chunk_id=%%SELECT_CHUNK_ID_SQL%% + """))) { + setChunkPosParams(statement, 1, chunk); + statement.execute(); + } + + // Remove chunk + try (PreparedStatement statement = + connection.prepareStatement( + """ + DELETE FROM chunk_data + WHERE chunk_world=? AND chunk_x=? AND chunk_z=? + """)) { + setChunkPosParams(statement, 1, chunk); + statement.execute(); + } + + return null; + }); } public void addPlayer(FullPlayerData playerData) { - Q2Obj.insert(new SqlDataPlayer(playerData)); + SqlClosure.sqlExecute( + connection -> { + 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(); + return null; + } + }); } public void setPlayerLastOnline(UUID player, long time) { - Q2Sql.executeUpdate( - """ - UPDATE player_data - SET last_online_time=? - WHERE player_uuid=? - """, - time, - player.toString()); + SqlClosure.sqlExecute( + connection -> { + 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(); + return null; + } + }); } public void setPlayerChunkName(UUID player, String chunkName) { - Q2Sql.executeUpdate( - """ - UPDATE player_data - SET chunk_name=? - WHERE player_uuid=? - """, - chunkName, - player.toString()); + SqlClosure.sqlExecute( + connection -> { + 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(); + return null; + } + }); } public void setPlayerReceiveAlerts(UUID player, boolean receiveAlerts) { - Q2Sql.executeUpdate( - """ - UPDATE player_data - SET receiveAlerts=? - WHERE player_uuid=? - """, - receiveAlerts, - player.toString()); + SqlClosure.sqlExecute( + connection -> { + try (PreparedStatement statement = + connection.prepareStatement( + """ + UPDATE player_data + SET receiveAlerts=? + WHERE player_uuid=? + """)) { + statement.setBoolean(1, receiveAlerts); + statement.setString(2, player.toString()); + statement.execute(); + return null; + } + }); } public void setPlayerExtraMaxClaims(UUID player, int extraMaxClaims) { - Q2Sql.executeUpdate( - """ - UPDATE player_data - SET extra_max_claims=? - WHERE player_uuid=? - """, - extraMaxClaims, - player.toString()); + SqlClosure.sqlExecute( + connection -> { + 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(); + return null; + } + }); } public void updateOrInsertPlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlags) { // Check if the access already exists - // If so, update the permission bits - if (Q2Obj.countFromClause( - SqlDataChunkPermission.class, - "other_player_uuid=? AND chunk_id=" + SELECT_CHUNK_ID_SQL, - accessor.toString(), - chunk.world(), - chunk.x(), - chunk.z()) - > 0) { - Q2Sql.executeUpdate( - String.format( - """ - UPDATE chunk_permissions - SET permission_bits=? - WHERE chunk_id=%s - AND other_player_uuid=? - """, - SELECT_CHUNK_ID_SQL), - permissionFlags, - chunk.world(), - chunk.x(), - chunk.z(), - accessor.toString()); - } else { - Q2Sql.executeUpdate( - String.format( - """ - INSERT INTO chunk_permissions ( - chunk_id, - other_player_uuid, - permission_bits - ) VALUES ( - %s, ?, ? - ) - """, - SELECT_CHUNK_ID_SQL), - chunk.world(), - chunk.x(), - chunk.z(), - accessor.toString(), - permissionFlags); - } + SqlClosure.sqlExecute( + connection -> { + final boolean accessExists; + try (PreparedStatement statement = + connection.prepareStatement( + replaceChunkIdQuery( + """ +SELECT COUNT(*) FROM chunk_permissions +WHERE other_player_uuid=? AND chunk_id=%%SELECT_CHUNK_ID_SQL%% +"""))) { + statement.setString(1, accessor.toString()); + setChunkPosParams(statement, 2, chunk); + ResultSet resultSet = statement.executeQuery(); + accessExists = resultSet.next() && resultSet.getInt(1) > 0; + } + + // If the entry already exists, update the permission bits + if (accessExists) { + try (PreparedStatement statement = + connection.prepareStatement( + replaceChunkIdQuery( + """ + UPDATE chunk_permissions + SET permission_bits=? + WHERE chunk_id=%%SELECT_CHUNK_ID_SQL%% + AND other_player_uuid=? + """))) { + statement.setInt(1, permissionFlags); + int next = setChunkPosParams(statement, 2, chunk); + statement.setString(next, accessor.toString()); + statement.execute(); + } + } else { + try (PreparedStatement statement = + connection.prepareStatement( + replaceChunkIdQuery( + """ + INSERT INTO chunk_permissions ( + chunk_id, + other_player_uuid, + permission_bits + ) VALUES ( + %%SELECT_CHUNK_ID_SQL%%, ?, ? + ) + """))) { + int next = setChunkPosParams(statement, 1, chunk); + statement.setString(next, accessor.toString()); + statement.setInt(next + 1, permissionFlags); + statement.execute(); + } + } + + return null; + }); } public void removePlayerAccess(ChunkPos chunk, UUID accessor) { - Q2Sql.executeUpdate( - String.format( - """ - DELETE FROM chunk_permissions - WHERE chunk_id=%s, - AND other_player_uuid=? - """, - SELECT_CHUNK_ID_SQL), - chunk.world(), - chunk.x(), - chunk.z(), - accessor.toString()); + SqlClosure.sqlExecute( + connection -> { + try (PreparedStatement statement = + connection.prepareStatement( + replaceChunkIdQuery( + """ + DELETE FROM chunk_permissions + WHERE chunk_id=%%SELECT_CHUNK_ID_SQL%%, + AND other_player_uuid=? + """))) { + int next = setChunkPosParams(statement, 1, chunk); + statement.setString(next, accessor.toString()); + return null; + } + }); } // -- Loading stuff -- // @@ -239,37 +307,43 @@ public Collection getAllChunks() { HashMap> permissions = new HashMap<>(); HashMap owners = new HashMap<>(); - try (ResultSet resultSet = - SqlClosure.sqlExecute( - connection -> - Q2Sql.executeQuery( - connection, - """ - SELECT chunk_world, chunk_x, chunk_z, owner_uuid, - other_player_uuid, permission_bits - FROM chunk_permissions - RIGHT JOIN chunk_data - ON chunk_permissions.chunk_id=chunk_data.chunk_id - """))) { - while (resultSet.next()) { - String world = resultSet.getString("chunk_world"); - int chunk_x = resultSet.getInt("chunk_x"); - int chunk_z = resultSet.getInt("chunk_z"); - ChunkPos pos = new ChunkPos(world, chunk_x, chunk_z); - UUID owner = UUID.fromString(resultSet.getString("owner_uuid")); - UUID otherPlayer = UUID.fromString(resultSet.getString("other_player_uuid")); - ChunkPlayerPermissions chunkPerms = - new ChunkPlayerPermissions(resultSet.getInt("permission_bits")); - - permissions - .computeIfAbsent(pos, ignoredPos -> new HashMap<>()) - .put(otherPlayer, chunkPerms); - - owners.putIfAbsent(pos, owner); - } - } catch (Exception e) { - throw new RuntimeException("Failed to load chunk data!", e); - } + SqlClosure.sqlExecute( + connection -> { + try (PreparedStatement statement = + connection.prepareStatement( + """ + SELECT chunk_world, chunk_x, chunk_z, owner_uuid, + other_player_uuid, permission_bits + FROM chunk_permissions + RIGHT JOIN chunk_data + ON chunk_permissions.chunk_id=chunk_data.chunk_id + """)) { + ResultSet resultSet = statement.executeQuery(); + while (resultSet.next()) { + String world = resultSet.getString("chunk_world"); + int chunk_x = resultSet.getInt("chunk_x"); + int chunk_z = resultSet.getInt("chunk_z"); + ChunkPos pos = new ChunkPos(world, chunk_x, chunk_z); + + String otherUuid = resultSet.getString("other_player_uuid"); + if (otherUuid != null) { + UUID otherPlayer = + UUID.fromString(otherUuid); + ChunkPlayerPermissions chunkPerms = + new ChunkPlayerPermissions(resultSet.getInt("permission_bits")); + + permissions + .computeIfAbsent(pos, ignoredPos -> new HashMap<>()) + .put(otherPlayer, chunkPerms); + } + + UUID owner = UUID.fromString(resultSet.getString("owner_uuid")); + owners.putIfAbsent(pos, owner); + } + } + + return null; + }); for (SqlDataChunk chunk : Q2ObjList.fromClause(SqlDataChunk.class, null)) { owners.putIfAbsent( @@ -286,4 +360,20 @@ public Collection getAllChunks() { false)) .collect(Collectors.toList()); } + + // -- Queries -- // + + // Returns the index of the next parameter! + private int setChunkPosParams( + PreparedStatement statement, int worldParameterNum, ChunkPos chunkPos) + throws SQLException { + statement.setString(worldParameterNum, chunkPos.world()); + statement.setInt(worldParameterNum + 1, chunkPos.x()); + statement.setInt(worldParameterNum + 2, chunkPos.z()); + return worldParameterNum + 3; + } + + private String replaceChunkIdQuery(String sql) { + return SELECT_CHUNK_ID_SQL_PATTERN.matcher(sql).replaceAll(SELECT_CHUNK_ID_SQL); + } } diff --git a/src/test/java/com/cjburkey/claimchunk/TestSQLPlease.java b/src/test/java/com/cjburkey/claimchunk/TestSQLPlease.java new file mode 100644 index 00000000..758843e6 --- /dev/null +++ b/src/test/java/com/cjburkey/claimchunk/TestSQLPlease.java @@ -0,0 +1,53 @@ +package com.cjburkey.claimchunk; + +import com.cjburkey.claimchunk.chunk.ChunkPos; +import com.cjburkey.claimchunk.chunk.DataChunk; +import com.cjburkey.claimchunk.data.sqlite.SqLiteTableMigrationManager; +import com.cjburkey.claimchunk.data.sqlite.SqLiteWrapper; +import com.cjburkey.claimchunk.player.FullPlayerData; + +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.HashMap; +import java.util.UUID; + +class TestSQLPlease { + + @Test + void ensureSomeColumnsExistsAfterInitializing() { + File dbFile = randomDbFile(); + + try (SqLiteWrapper ignoredWrapper = new SqLiteWrapper(dbFile, false)) { + // Make sure that instantiating SqLiteWrapper created the tables + assert SqLiteTableMigrationManager.columnExists("player_data", "player_uuid"); + assert SqLiteTableMigrationManager.columnExists("chunk_data", "owner_uuid"); + assert SqLiteTableMigrationManager.columnExists("chunk_permissions", "permission_bits"); + assert !SqLiteTableMigrationManager.columnExists("chunk_hell", "permission_bits"); + assert !SqLiteTableMigrationManager.columnExists("player_data", "fake_col"); + + // Add a random player + UUID plyUuid = UUID.randomUUID(); + ignoredWrapper.addPlayer( + new FullPlayerData( + plyUuid, "SomeGuysName", null, System.currentTimeMillis(), true, 0)); + + // Add a chunk to the player + ChunkPos chunkPos = new ChunkPos("world", 10, -3); + ignoredWrapper.addClaimedChunk( + new DataChunk(chunkPos, plyUuid, new HashMap<>(), false)); + + // Make sure the chunk exists when we load from the database + assert ignoredWrapper.getAllChunks().stream() + .anyMatch( + chunk -> chunk.player.equals(plyUuid) && chunk.chunk.equals(chunkPos)); + } + + //noinspection ResultOfMethodCallIgnored + dbFile.delete(); + } + + protected static File randomDbFile() { + return new File(UUID.randomUUID() + ".tmp.sqlite3"); + } +} From 194d7be2bcd1f5aa665f64d05b177cda28818ac0 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Thu, 23 May 2024 12:15:23 -0400 Subject: [PATCH 22/26] Get conversions from old data formats working --- .gitignore | 5 +- .../com/cjburkey/claimchunk/ClaimChunk.java | 33 ++++------- .../chunk/ChunkPlayerPermissions.java | 14 +++++ .../sqlite/SqLiteTableMigrationManager.java | 1 - .../claimchunk/data/sqlite/SqLiteWrapper.java | 51 ++++++++++++++--- .../claimchunk/data/sqlite/SqlDataChunk.java | 9 --- .../data/sqlite/SqlDataChunkPermission.java | 34 ------------ .../claimchunk/data/sqlite/SqlDataPlayer.java | 11 ---- .../cjburkey/claimchunk/TestSQLPlease.java | 55 +++++++++++++------ 9 files changed, 112 insertions(+), 101 deletions(-) delete mode 100644 src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunkPermission.java diff --git a/.gitignore b/.gitignore index e8f169ba..8656ef44 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,7 @@ OUT # Gource my_captions.txt source_visual.ppm -source_visual.mp4 \ No newline at end of file +source_visual.mp4 + +# Leftover from tests +*.tmp.sqlite3 diff --git a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java index 2bcb641d..3a50b367 100644 --- a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java @@ -393,28 +393,22 @@ private boolean initDataHandler() { : createJsonDataHandler(); }*/ if (dataHandler == null) { - File sqliteFile = new File(getDataFolder(), "/data/claimAndPlayerData.sqlite3"); + File dataFolder = new File(getDataFolder(), "/data"); + File sqliteFile = new File(dataFolder, "/claimAndPlayerData.sqlite3"); + File oldClaimedFile = new File(dataFolder, "/claimedChunks.json"); + File oldPlayerFile = new File(dataFolder, "/playerData.json"); + boolean oldUseDb = config.getUseDatabase(); IClaimChunkDataHandler oldDataHandler = null; - // UGLY HACK! - // RANKS ARE INITIALIZED AFTER DATA HANDLER, SO IF RANK FILE - // DOESN'T EXIST, WE CAN ASSUME THIS IS A NEW INSTALL RATHER THAN - // CONVERSION! - // AFTER 0.0.25 releases, 0.0.26 doesn't need to include this (but - // WILL require players to install 0.0.25 FIRST to upgrade from - // pre-0.0.25 if, say, 0.0.26 comes out while they're on 0.0.24). - if (!sqliteFile.exists() && new File(getDataFolder(), "/ranks.json").exists()) { + if (!sqliteFile.exists() + && (oldUseDb || (oldClaimedFile.exists() && oldPlayerFile.exists()))) { oldDataHandler = - (config.getUseDatabase()) + oldUseDb ? ((config.getGroupRequests()) ? new BulkMySQLDataHandler<>( - this, - this::createJsonDataHandler, - ignored -> {} /*JsonDataHandler::deleteFiles*/) + this, this::createJsonDataHandler, ignored -> {}) : new MySQLDataHandler<>( - this, - this::createJsonDataHandler, - ignored -> {} /*JsonDataHandler::deleteFiles*/)) + this, this::createJsonDataHandler, ignored -> {})) : createJsonDataHandler(); } @@ -425,9 +419,6 @@ private boolean initDataHandler() { IDataConverter.copyConvert(oldDataHandler, dataHandler); oldDataHandler.exit(); - File dataFolder = new File(getDataFolder(), "/data"); - File oldClaimedFile = new File(dataFolder, "/claimedChunks.json"); - File oldPlayerFile = new File(dataFolder, "/playerData.json"); if (oldClaimedFile.exists()) { Files.move( oldClaimedFile.toPath(), @@ -435,12 +426,12 @@ private boolean initDataHandler() { } if (oldPlayerFile.exists()) { Files.move( - oldClaimedFile.toPath(), + oldPlayerFile.toPath(), new File(dataFolder, "/OLD_playerData.json").toPath()); } } catch (Exception e) { throw new RuntimeException( - "Failed to initialize previous data handler to convert old data!"); + "Failed to initialize previous data handler to convert old data!", e); } } } diff --git a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java index 7d75b089..506db81c 100644 --- a/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java +++ b/src/main/java/com/cjburkey/claimchunk/chunk/ChunkPlayerPermissions.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; public class ChunkPlayerPermissions { @@ -141,4 +142,17 @@ public Map toPermissionsMap() { return chunkPlayerPermissions; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ChunkPlayerPermissions that = (ChunkPlayerPermissions) o; + return permissionFlags == that.permissionFlags; + } + + @Override + public int hashCode() { + return Objects.hash(permissionFlags); + } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java index cf907c4d..915ae516 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java @@ -60,7 +60,6 @@ FOREIGN KEY(other_player_uuid) REFERENCES player_data(player_uuid) } // Use this method to determine if a column exists in a table to perform migrations - // TODO: MAYBE CHECK IF THIS WORKS @SuppressWarnings("unused") public static boolean columnExists(String tableName, String columnName) { return SqlClosure.sqlExecute( diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java index e81c93fc..b9f7716a 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java @@ -15,10 +15,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -68,6 +65,7 @@ public void close() { public void addClaimedChunk(DataChunk chunk) { SqlClosure.sqlExecute( connection -> { + // Add the chunk try (PreparedStatement statement = connection.prepareStatement( """ @@ -83,8 +81,45 @@ INSERT INTO chunk_data ( int next = setChunkPosParams(statement, 1, chunk.chunk); statement.setString(next, chunk.player.toString()); statement.execute(); - return null; } + + // Add the player permissions + if (!chunk.playerPermissions.isEmpty()) { + String permsInsertPrefixSql = + """ + INSERT INTO chunk_permissions ( + chunk_id, + other_player_uuid, + permission_bits + ) VALUES + """; + + // Better way to do this? + String permsValsSql = + chunk.playerPermissions.entrySet().stream() + .map( + ignored -> + replaceChunkIdQuery( + """ + (%%SELECT_CHUNK_ID_SQL%%, ?, ?) + """)) + .collect(Collectors.joining(",")); + + try (PreparedStatement statement = + connection.prepareStatement(permsInsertPrefixSql + permsValsSql)) { + int currentParam = 1; + for (Map.Entry entry : + chunk.playerPermissions.entrySet()) { + currentParam = + setChunkPosParams(statement, currentParam, chunk.chunk); + statement.setString(currentParam++, entry.getKey().toString()); + statement.setInt(currentParam++, entry.getValue().permissionFlags); + } + statement.execute(); + } + } + + return null; }); } @@ -327,10 +362,10 @@ public Collection getAllChunks() { String otherUuid = resultSet.getString("other_player_uuid"); if (otherUuid != null) { - UUID otherPlayer = - UUID.fromString(otherUuid); + UUID otherPlayer = UUID.fromString(otherUuid); ChunkPlayerPermissions chunkPerms = - new ChunkPlayerPermissions(resultSet.getInt("permission_bits")); + new ChunkPlayerPermissions( + resultSet.getInt("permission_bits")); permissions .computeIfAbsent(pos, ignoredPos -> new HashMap<>()) diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java index 585d73d4..302bc4d4 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java @@ -1,7 +1,5 @@ package com.cjburkey.claimchunk.data.sqlite; -import com.cjburkey.claimchunk.chunk.DataChunk; - import javax.persistence.*; @Table(name = "chunk_data") @@ -26,11 +24,4 @@ public class SqlDataChunk { @SuppressWarnings("unused") public SqlDataChunk() {} - - public SqlDataChunk(DataChunk chunk) { - this.world = chunk.chunk.world(); - this.x = chunk.chunk.x(); - this.z = chunk.chunk.z(); - this.uuid = chunk.player.toString(); - } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunkPermission.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunkPermission.java deleted file mode 100644 index a11aa497..00000000 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunkPermission.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.cjburkey.claimchunk.data.sqlite; - -import com.cjburkey.claimchunk.chunk.ChunkPlayerPermissions; - -import java.util.UUID; - -import javax.persistence.*; - -@Table(name = "chunk_permissions") -public class SqlDataChunkPermission { - - @Id - @Column(name = "perm_id") - @GeneratedValue(strategy = GenerationType.IDENTITY) - public int permissionId; - - @Column(name = "chunk_id") - public int chunkId; - - @Column(name = "other_player_uuid") - public String otherPlayerUuid; - - @Column(name = "permission_bits") - public int permissionBits; - - @SuppressWarnings("unused") - public SqlDataChunkPermission() {} - - public SqlDataChunkPermission(UUID otherPlayer, ChunkPlayerPermissions permissions) { - this.chunkId = -1; - this.otherPlayerUuid = otherPlayer.toString(); - this.permissionBits = permissions.permissionFlags; - } -} diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataPlayer.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataPlayer.java index c8bd2999..14df833c 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataPlayer.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataPlayer.java @@ -1,7 +1,5 @@ package com.cjburkey.claimchunk.data.sqlite; -import com.cjburkey.claimchunk.player.FullPlayerData; - import javax.persistence.Column; import javax.persistence.Id; import javax.persistence.Table; @@ -30,13 +28,4 @@ public class SqlDataPlayer { @SuppressWarnings("unused") public SqlDataPlayer() {} - - public SqlDataPlayer(FullPlayerData player) { - this.uuid = player.player.toString(); - this.lastIgn = player.lastIgn; - this.chunkName = player.chunkName; - this.lastOnlineTime = player.lastOnlineTime; - this.alert = player.alert; - this.extraMaxClaims = player.extraMaxClaims; - } } diff --git a/src/test/java/com/cjburkey/claimchunk/TestSQLPlease.java b/src/test/java/com/cjburkey/claimchunk/TestSQLPlease.java index 758843e6..a6d83a43 100644 --- a/src/test/java/com/cjburkey/claimchunk/TestSQLPlease.java +++ b/src/test/java/com/cjburkey/claimchunk/TestSQLPlease.java @@ -1,5 +1,6 @@ package com.cjburkey.claimchunk; +import com.cjburkey.claimchunk.chunk.ChunkPlayerPermissions; import com.cjburkey.claimchunk.chunk.ChunkPos; import com.cjburkey.claimchunk.chunk.DataChunk; import com.cjburkey.claimchunk.data.sqlite.SqLiteTableMigrationManager; @@ -9,42 +10,64 @@ import org.junit.jupiter.api.Test; import java.io.File; +import java.util.Collection; import java.util.HashMap; +import java.util.Objects; import java.util.UUID; class TestSQLPlease { @Test - void ensureSomeColumnsExistsAfterInitializing() { + void ensureNoDataLoss() { File dbFile = randomDbFile(); + dbFile.deleteOnExit(); try (SqLiteWrapper ignoredWrapper = new SqLiteWrapper(dbFile, false)) { - // Make sure that instantiating SqLiteWrapper created the tables - assert SqLiteTableMigrationManager.columnExists("player_data", "player_uuid"); - assert SqLiteTableMigrationManager.columnExists("chunk_data", "owner_uuid"); - assert SqLiteTableMigrationManager.columnExists("chunk_permissions", "permission_bits"); - assert !SqLiteTableMigrationManager.columnExists("chunk_hell", "permission_bits"); - assert !SqLiteTableMigrationManager.columnExists("player_data", "fake_col"); - // Add a random player UUID plyUuid = UUID.randomUUID(); ignoredWrapper.addPlayer( new FullPlayerData( plyUuid, "SomeGuysName", null, System.currentTimeMillis(), true, 0)); - // Add a chunk to the player + // Make fake accessors and permissions + UUID accessorUuid1 = UUID.randomUUID(); + UUID accessorUuid2 = UUID.randomUUID(); + ChunkPlayerPermissions permissions1 = new ChunkPlayerPermissions(0b11111111); + ChunkPlayerPermissions permissions2 = new ChunkPlayerPermissions(0b10101101); + + // Add a chunk to the player and give the permissions to the other players ChunkPos chunkPos = new ChunkPos("world", 10, -3); - ignoredWrapper.addClaimedChunk( - new DataChunk(chunkPos, plyUuid, new HashMap<>(), false)); + DataChunk chunkData = new DataChunk(chunkPos, plyUuid, new HashMap<>(), false); + chunkData.playerPermissions.put(accessorUuid1, permissions1); + chunkData.playerPermissions.put(accessorUuid2, permissions2); + ignoredWrapper.addClaimedChunk(chunkData); + + // Load the chunk after adding it + Collection loadedChunks = ignoredWrapper.getAllChunks(); + DataChunk loadedChunk = loadedChunks.iterator().next(); + Objects.requireNonNull(loadedChunk); // Make sure the chunk exists when we load from the database - assert ignoredWrapper.getAllChunks().stream() - .anyMatch( - chunk -> chunk.player.equals(plyUuid) && chunk.chunk.equals(chunkPos)); + assert loadedChunk.player.equals(plyUuid) && loadedChunk.chunk.equals(chunkPos); + // Make sure the chunk permission got loaded correctly + assert Objects.equals(permissions1, loadedChunk.playerPermissions.get(accessorUuid1)); + assert Objects.equals(permissions2, loadedChunk.playerPermissions.get(accessorUuid2)); } + } - //noinspection ResultOfMethodCallIgnored - dbFile.delete(); + @Test + void ensureSomeColumnsExistsAfterInitializing() { + File dbFile = randomDbFile(); + dbFile.deleteOnExit(); + + try (SqLiteWrapper ignoredWrapper = new SqLiteWrapper(dbFile, false)) { + // Make sure that instantiating SqLiteWrapper created the tables + assert SqLiteTableMigrationManager.columnExists("player_data", "player_uuid"); + assert SqLiteTableMigrationManager.columnExists("chunk_data", "owner_uuid"); + assert SqLiteTableMigrationManager.columnExists("chunk_permissions", "permission_bits"); + assert !SqLiteTableMigrationManager.columnExists("chunk_hell", "permission_bits"); + assert !SqLiteTableMigrationManager.columnExists("player_data", "fake_col"); + } } protected static File randomDbFile() { From 965c57b093b33687154e5cb3b0e7ad8b622b6a42 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Thu, 23 May 2024 12:22:10 -0400 Subject: [PATCH 23/26] Don't include jetbrains annotations in shaded jar --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index fc3b5e3c..aa0c43ce 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -105,6 +105,7 @@ tasks { dependencies { exclude(dependency("org.slf4j:slf4j-api")) exclude(dependency("org.xerial:sqlite-jdbc")) + exclude(dependency("org.jetbrains:annotations")) } relocate("com.zaxxer", "claimchunk.dependency.com.zaxxer") From afd1958520567b4d1bae3e6cd3116bcb869843bb Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Thu, 23 May 2024 13:33:48 -0400 Subject: [PATCH 24/26] Organize and cleanup --- build.gradle.kts | 2 + .../com/cjburkey/claimchunk/ClaimChunk.java | 2 +- .../data/{conversion => }/IDataConverter.java | 2 +- .../data/conversion/ConvertJsonToMySQL.java | 30 ---- .../data/conversion/ConvertMySQLToJson.java | 38 ----- .../data/newdata/MySQLDataHandler.java | 2 +- .../data/sqlite/SqLiteDataHandler.java | 2 +- .../sqlite/SqLiteTableMigrationManager.java | 2 +- .../claimchunk/data/sqlite/SqLiteWrapper.java | 93 ++++------ .../claimchunk/data/sqlite/SqlDataChunk.java | 5 - .../cjburkey/claimchunk/TestSQLPlease.java | 161 +++++++++++++++--- 11 files changed, 172 insertions(+), 167 deletions(-) rename src/main/java/com/cjburkey/claimchunk/data/{conversion => }/IDataConverter.java (97%) delete mode 100644 src/main/java/com/cjburkey/claimchunk/data/conversion/ConvertJsonToMySQL.java delete mode 100644 src/main/java/com/cjburkey/claimchunk/data/conversion/ConvertMySQLToJson.java diff --git a/build.gradle.kts b/build.gradle.kts index aa0c43ce..276bc604 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,6 +42,7 @@ object DepData { const val JAVAX_PERSISTENCE_VERSION = "2.1.0" const val JAVAX_TRANSACTION_VERSION = "1.1" const val SANS_ORM_VERSION = "3.17" + const val SLF4J_VERSION = "1.7.25" // Directories const val TEST_SERVER_DIR = "run" @@ -301,6 +302,7 @@ dependencies { implementation("javax.transaction:transaction-api:${DepData.JAVAX_TRANSACTION_VERSION}") implementation("com.github.h-thurow:q2o:${DepData.SANS_ORM_VERSION}") + testImplementation("org.slf4j:slf4j-simple:${DepData.SLF4J_VERSION}") testImplementation("org.junit.jupiter:junit-jupiter:${DepData.JUNIT_VERSION}") testRuntimeOnly("org.junit.platform:junit-platform-launcher:${DepData.JUNIT_LAUNCHER_VERSION}") } diff --git a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java index 3a50b367..be35503e 100644 --- a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java @@ -6,7 +6,7 @@ import com.cjburkey.claimchunk.cmd.*; import com.cjburkey.claimchunk.config.ClaimChunkWorldProfileHandler; import com.cjburkey.claimchunk.config.ccconfig.*; -import com.cjburkey.claimchunk.data.conversion.IDataConverter; +import com.cjburkey.claimchunk.data.IDataConverter; import com.cjburkey.claimchunk.data.newdata.*; import com.cjburkey.claimchunk.data.sqlite.SqLiteDataHandler; import com.cjburkey.claimchunk.event.*; diff --git a/src/main/java/com/cjburkey/claimchunk/data/conversion/IDataConverter.java b/src/main/java/com/cjburkey/claimchunk/data/IDataConverter.java similarity index 97% rename from src/main/java/com/cjburkey/claimchunk/data/conversion/IDataConverter.java rename to src/main/java/com/cjburkey/claimchunk/data/IDataConverter.java index 28a6afef..ea7cb898 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/conversion/IDataConverter.java +++ b/src/main/java/com/cjburkey/claimchunk/data/IDataConverter.java @@ -1,4 +1,4 @@ -package com.cjburkey.claimchunk.data.conversion; +package com.cjburkey.claimchunk.data; import com.cjburkey.claimchunk.data.newdata.IClaimChunkDataHandler; diff --git a/src/main/java/com/cjburkey/claimchunk/data/conversion/ConvertJsonToMySQL.java b/src/main/java/com/cjburkey/claimchunk/data/conversion/ConvertJsonToMySQL.java deleted file mode 100644 index e55a6e7b..00000000 --- a/src/main/java/com/cjburkey/claimchunk/data/conversion/ConvertJsonToMySQL.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.cjburkey.claimchunk.data.conversion; - -import com.cjburkey.claimchunk.ClaimChunk; -import com.cjburkey.claimchunk.data.newdata.JsonDataHandler; -import com.cjburkey.claimchunk.data.newdata.MySQLDataHandler; - -@SuppressWarnings("unused") -public class ConvertJsonToMySQL implements IDataConverter> { - - private final ClaimChunk claimChunk; - - public ConvertJsonToMySQL(ClaimChunk claimChunk) { - this.claimChunk = claimChunk; - } - - @Override - public MySQLDataHandler convert(JsonDataHandler oldDataHandler) throws Exception { - // Create and a new MySQL data handler - MySQLDataHandler newDataHandler = new MySQLDataHandler<>(claimChunk, null, null); - - // Initialize the new data handler - newDataHandler.init(); - - // Convert from the old data handler to the new one - IDataConverter.copyConvert(oldDataHandler, newDataHandler); - - // Return the new data handler - return newDataHandler; - } -} diff --git a/src/main/java/com/cjburkey/claimchunk/data/conversion/ConvertMySQLToJson.java b/src/main/java/com/cjburkey/claimchunk/data/conversion/ConvertMySQLToJson.java deleted file mode 100644 index 2f05d8c0..00000000 --- a/src/main/java/com/cjburkey/claimchunk/data/conversion/ConvertMySQLToJson.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.cjburkey.claimchunk.data.conversion; - -import com.cjburkey.claimchunk.ClaimChunk; -import com.cjburkey.claimchunk.data.newdata.JsonDataHandler; -import com.cjburkey.claimchunk.data.newdata.MySQLDataHandler; - -import java.io.File; - -@SuppressWarnings("unused") -public class ConvertMySQLToJson implements IDataConverter, JsonDataHandler> { - - private final ClaimChunk claimChunk; - private final File claimedChunksFile; - private final File joinedPlayersFile; - - public ConvertMySQLToJson( - ClaimChunk claimChunk, File claimedChunksFile, File joinedPlayersFile) { - this.claimChunk = claimChunk; - this.claimedChunksFile = claimedChunksFile; - this.joinedPlayersFile = joinedPlayersFile; - } - - @Override - public JsonDataHandler convert(MySQLDataHandler oldDataHandler) throws Exception { - // Create and a new MySQL data handler - JsonDataHandler newDataHandler = - new JsonDataHandler(claimChunk, claimedChunksFile, joinedPlayersFile); - - // Initialize the new data handler - newDataHandler.init(); - - // Convert from the old data handler to the new one - IDataConverter.copyConvert(oldDataHandler, newDataHandler); - - // Return the new data handler - return newDataHandler; - } -} diff --git a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java index ebf57dea..205c9fad 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java @@ -7,7 +7,7 @@ import com.cjburkey.claimchunk.chunk.ChunkPlayerPermissions; import com.cjburkey.claimchunk.chunk.ChunkPos; import com.cjburkey.claimchunk.chunk.DataChunk; -import com.cjburkey.claimchunk.data.conversion.IDataConverter; +import com.cjburkey.claimchunk.data.IDataConverter; import com.cjburkey.claimchunk.player.FullPlayerData; import com.cjburkey.claimchunk.player.SimplePlayerData; diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java index a9386043..d206fc19 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java @@ -237,7 +237,7 @@ public void givePlayerAccess( DataChunk chunkData = claimedChunks.get(chunk); if (chunkData != null) { chunkData.playerPermissions.put(accessor, permissions); - sqLiteWrapper.updateOrInsertPlayerAccess(chunk, accessor, permissions.permissionFlags); + sqLiteWrapper.setPlayerAccess(chunk, accessor, permissions.permissionFlags); } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java index 915ae516..50b7e895 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteTableMigrationManager.java @@ -48,11 +48,11 @@ FOREIGN KEY(owner_uuid) REFERENCES player_data(player_uuid) Q2Sql.executeUpdate( """ CREATE TABLE IF NOT EXISTS chunk_permissions ( - perm_id INTEGER PRIMARY KEY, chunk_id INTEGER NOT NULL, other_player_uuid TEXT NOT NULL, permission_bits INTEGER NOT NULL, + PRIMARY KEY(chunk_id, other_player_uuid) FOREIGN KEY(chunk_id) REFERENCES chunk_data(chunk_id), FOREIGN KEY(other_player_uuid) REFERENCES player_data(player_uuid) ) STRICT diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java index b9f7716a..466d35a9 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java @@ -95,18 +95,14 @@ INSERT INTO chunk_permissions ( """; // Better way to do this? - String permsValsSql = - chunk.playerPermissions.entrySet().stream() - .map( - ignored -> - replaceChunkIdQuery( - """ - (%%SELECT_CHUNK_ID_SQL%%, ?, ?) - """)) - .collect(Collectors.joining(",")); - - try (PreparedStatement statement = - connection.prepareStatement(permsInsertPrefixSql + permsValsSql)) { + ArrayList params = new ArrayList<>(); + for (int i = 0; i < chunk.playerPermissions.size(); i++) { + params.add("(%%SELECT_CHUNK_ID_SQL%%, ?, ?)"); + } + String finalSql = + chunkIdQuery(permsInsertPrefixSql + String.join(",", params)); + + try (PreparedStatement statement = connection.prepareStatement(finalSql)) { int currentParam = 1; for (Map.Entry entry : chunk.playerPermissions.entrySet()) { @@ -129,7 +125,7 @@ public void removeClaimedChunk(ChunkPos chunk) { // Remove all granted permissions for the chunk try (PreparedStatement statement = connection.prepareStatement( - replaceChunkIdQuery( + chunkIdQuery( """ DELETE FROM chunk_permissions WHERE chunk_id=%%SELECT_CHUNK_ID_SQL%% @@ -254,60 +250,28 @@ public void setPlayerExtraMaxClaims(UUID player, int extraMaxClaims) { }); } - public void updateOrInsertPlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlags) { - // Check if the access already exists + public void setPlayerAccess(ChunkPos chunk, UUID accessor, int permissionFlags) { SqlClosure.sqlExecute( connection -> { - final boolean accessExists; try (PreparedStatement statement = connection.prepareStatement( - replaceChunkIdQuery( + chunkIdQuery( """ -SELECT COUNT(*) FROM chunk_permissions -WHERE other_player_uuid=? AND chunk_id=%%SELECT_CHUNK_ID_SQL%% -"""))) { - statement.setString(1, accessor.toString()); - setChunkPosParams(statement, 2, chunk); - ResultSet resultSet = statement.executeQuery(); - accessExists = resultSet.next() && resultSet.getInt(1) > 0; - } - - // If the entry already exists, update the permission bits - if (accessExists) { - try (PreparedStatement statement = - connection.prepareStatement( - replaceChunkIdQuery( - """ - UPDATE chunk_permissions - SET permission_bits=? - WHERE chunk_id=%%SELECT_CHUNK_ID_SQL%% - AND other_player_uuid=? - """))) { - statement.setInt(1, permissionFlags); - int next = setChunkPosParams(statement, 2, chunk); - statement.setString(next, accessor.toString()); - statement.execute(); - } - } else { - try (PreparedStatement statement = - connection.prepareStatement( - replaceChunkIdQuery( - """ - INSERT INTO chunk_permissions ( - chunk_id, - other_player_uuid, - permission_bits - ) VALUES ( - %%SELECT_CHUNK_ID_SQL%%, ?, ? - ) - """))) { - int next = setChunkPosParams(statement, 1, chunk); - statement.setString(next, accessor.toString()); - statement.setInt(next + 1, permissionFlags); - statement.execute(); - } + INSERT INTO chunk_permissions ( + chunk_id, + other_player_uuid, + permission_bits + ) VALUES ( + %%SELECT_CHUNK_ID_SQL%%, ?, ? + ) + ON CONFLICT(chunk_id, other_player_uuid) DO + UPDATE SET permission_bits=excluded.permission_bits + """))) { + int next = setChunkPosParams(statement, 1, chunk); + statement.setString(next, accessor.toString()); + statement.setInt(next + 1, permissionFlags); + statement.execute(); } - return null; }); } @@ -317,14 +281,15 @@ public void removePlayerAccess(ChunkPos chunk, UUID accessor) { connection -> { try (PreparedStatement statement = connection.prepareStatement( - replaceChunkIdQuery( + chunkIdQuery( """ DELETE FROM chunk_permissions - WHERE chunk_id=%%SELECT_CHUNK_ID_SQL%%, + WHERE chunk_id=%%SELECT_CHUNK_ID_SQL%% AND other_player_uuid=? """))) { int next = setChunkPosParams(statement, 1, chunk); statement.setString(next, accessor.toString()); + statement.execute(); return null; } }); @@ -408,7 +373,7 @@ private int setChunkPosParams( return worldParameterNum + 3; } - private String replaceChunkIdQuery(String sql) { + private String chunkIdQuery(String sql) { return SELECT_CHUNK_ID_SQL_PATTERN.matcher(sql).replaceAll(SELECT_CHUNK_ID_SQL); } } diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java index 302bc4d4..de6b20f8 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java @@ -5,11 +5,6 @@ @Table(name = "chunk_data") public class SqlDataChunk { - @Id - @Column(name = "chunk_id") - @GeneratedValue(strategy = GenerationType.IDENTITY) - public int chunkId; - @Column(name = "chunk_world") public String world; diff --git a/src/test/java/com/cjburkey/claimchunk/TestSQLPlease.java b/src/test/java/com/cjburkey/claimchunk/TestSQLPlease.java index a6d83a43..6162204b 100644 --- a/src/test/java/com/cjburkey/claimchunk/TestSQLPlease.java +++ b/src/test/java/com/cjburkey/claimchunk/TestSQLPlease.java @@ -1,5 +1,8 @@ package com.cjburkey.claimchunk; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import com.cjburkey.claimchunk.chunk.ChunkPlayerPermissions; import com.cjburkey.claimchunk.chunk.ChunkPos; import com.cjburkey.claimchunk.chunk.DataChunk; @@ -12,22 +15,41 @@ import java.io.File; import java.util.Collection; import java.util.HashMap; -import java.util.Objects; +import java.util.Map; import java.util.UUID; class TestSQLPlease { @Test - void ensureNoDataLoss() { - File dbFile = randomDbFile(); - dbFile.deleteOnExit(); + void ensureColumnExistsMethodWorks() { + // Must create the wrapper to initialize (and deinitialize) connection + try (TestQlWrap ignoredWrapper = new TestQlWrap()) { + // Make sure that instantiating SqLiteWrapper created the tables + assert SqLiteTableMigrationManager.columnExists("player_data", "player_uuid"); + assert SqLiteTableMigrationManager.columnExists("chunk_data", "owner_uuid"); + assert SqLiteTableMigrationManager.columnExists("chunk_permissions", "permission_bits"); + assert !SqLiteTableMigrationManager.columnExists("chunk_hell", "permission_bits"); + assert !SqLiteTableMigrationManager.columnExists("player_data", "fake_col"); + } + } - try (SqLiteWrapper ignoredWrapper = new SqLiteWrapper(dbFile, false)) { + @Test + void ensureNoDataLoss() { + try (TestQlWrap wrapper = new TestQlWrap()) { // Add a random player - UUID plyUuid = UUID.randomUUID(); - ignoredWrapper.addPlayer( + UUID ply1Uuid = UUID.randomUUID(); + UUID ply2Uuid = UUID.randomUUID(); + wrapper.sql.addPlayer( + new FullPlayerData( + ply1Uuid, "SomeGuysName", null, System.currentTimeMillis(), true, 0)); + wrapper.sql.addPlayer( new FullPlayerData( - plyUuid, "SomeGuysName", null, System.currentTimeMillis(), true, 0)); + ply2Uuid, + "OtherPersonsName", + "queenshit", + System.currentTimeMillis(), + false, + 0)); // Make fake accessors and permissions UUID accessorUuid1 = UUID.randomUUID(); @@ -37,40 +59,129 @@ void ensureNoDataLoss() { // Add a chunk to the player and give the permissions to the other players ChunkPos chunkPos = new ChunkPos("world", 10, -3); - DataChunk chunkData = new DataChunk(chunkPos, plyUuid, new HashMap<>(), false); + DataChunk chunkData = new DataChunk(chunkPos, ply1Uuid, new HashMap<>(), false); chunkData.playerPermissions.put(accessorUuid1, permissions1); chunkData.playerPermissions.put(accessorUuid2, permissions2); - ignoredWrapper.addClaimedChunk(chunkData); + wrapper.sql.addClaimedChunk(chunkData); + + // Make sure both players get loaded + Collection players = wrapper.sql.getAllPlayers(); + assertEquals(2, players.size()); + assert players.stream() + .allMatch(ply -> ply.player.equals(ply1Uuid) || ply.player.equals(ply2Uuid)); + assert players.stream().anyMatch(ply -> "queenshit".equals(ply.chunkName)); // Load the chunk after adding it - Collection loadedChunks = ignoredWrapper.getAllChunks(); + Collection loadedChunks = wrapper.sql.getAllChunks(); DataChunk loadedChunk = loadedChunks.iterator().next(); - Objects.requireNonNull(loadedChunk); + assertNotNull(loadedChunk); // Make sure the chunk exists when we load from the database - assert loadedChunk.player.equals(plyUuid) && loadedChunk.chunk.equals(chunkPos); + assert loadedChunk.player.equals(ply1Uuid) && loadedChunk.chunk.equals(chunkPos); // Make sure the chunk permission got loaded correctly - assert Objects.equals(permissions1, loadedChunk.playerPermissions.get(accessorUuid1)); - assert Objects.equals(permissions2, loadedChunk.playerPermissions.get(accessorUuid2)); + assertEquals(permissions1, loadedChunk.playerPermissions.get(accessorUuid1)); + assertEquals(permissions2, loadedChunk.playerPermissions.get(accessorUuid2)); } } @Test - void ensureSomeColumnsExistsAfterInitializing() { - File dbFile = randomDbFile(); - dbFile.deleteOnExit(); + void multiplePermissions() { + try (TestQlWrap wrapper = new TestQlWrap()) { + UUID owner = UUID.randomUUID(); + UUID accessor1 = UUID.randomUUID(); + UUID accessor2 = UUID.randomUUID(); + ChunkPos chunk = new ChunkPos("world", 824, -29); + DataChunk chunkData = new DataChunk(chunk, owner, new HashMap<>(), false); + chunkData.playerPermissions.put(accessor1, new ChunkPlayerPermissions(0b01)); + chunkData.playerPermissions.put(accessor2, new ChunkPlayerPermissions(0b10)); - try (SqLiteWrapper ignoredWrapper = new SqLiteWrapper(dbFile, false)) { - // Make sure that instantiating SqLiteWrapper created the tables - assert SqLiteTableMigrationManager.columnExists("player_data", "player_uuid"); - assert SqLiteTableMigrationManager.columnExists("chunk_data", "owner_uuid"); - assert SqLiteTableMigrationManager.columnExists("chunk_permissions", "permission_bits"); - assert !SqLiteTableMigrationManager.columnExists("chunk_hell", "permission_bits"); - assert !SqLiteTableMigrationManager.columnExists("player_data", "fake_col"); + // Add the players + wrapper.sql.addPlayer( + new FullPlayerData( + owner, "PersonHere", null, System.currentTimeMillis(), true, 0)); + wrapper.sql.addPlayer( + new FullPlayerData( + accessor1, "PersonThere", null, System.currentTimeMillis(), true, 0)); + wrapper.sql.addPlayer( + new FullPlayerData( + accessor2, "AnotherOne", null, System.currentTimeMillis(), true, 0)); + + // Add the chunk + wrapper.sql.addClaimedChunk(chunkData); + + // Load the chunk and make sure it contains both accessors + Map loadedPerms = + wrapper.sql.getAllChunks().iterator().next().playerPermissions; + assert loadedPerms.containsKey(accessor1); + assert loadedPerms.containsKey(accessor2); + } + } + + @Test + void insertOrUpdatePermission() { + try (TestQlWrap wrapper = new TestQlWrap()) { + UUID owner = UUID.randomUUID(); + UUID accessor = UUID.randomUUID(); + ChunkPos chunk = new ChunkPos("world", 824, -29); + int flags1 = 0b10101001; + int flags2 = 0b01010100; + + // Add the players and the chunk + wrapper.sql.addPlayer( + new FullPlayerData( + owner, "PersonHere", null, System.currentTimeMillis(), true, 0)); + wrapper.sql.addPlayer( + new FullPlayerData( + accessor, "PersonThere", null, System.currentTimeMillis(), true, 0)); + wrapper.sql.addClaimedChunk(new DataChunk(chunk, owner, new HashMap<>(), false)); + + // Insert the permission and check it + wrapper.sql.setPlayerAccess(chunk, accessor, flags1); + assertEquals( + flags1, + wrapper.sql + .getAllChunks() + .iterator() + .next() + .playerPermissions + .get(accessor) + .permissionFlags); + + // Update the permission and check it + wrapper.sql.setPlayerAccess(chunk, accessor, flags2); + assertEquals( + flags2, + wrapper.sql + .getAllChunks() + .iterator() + .next() + .playerPermissions + .get(accessor) + .permissionFlags); + + // Remove the permission and make sure there aren't any permissions now + wrapper.sql.removePlayerAccess(chunk, accessor); + assert wrapper.sql.getAllChunks().iterator().next().playerPermissions.isEmpty(); } } protected static File randomDbFile() { return new File(UUID.randomUUID() + ".tmp.sqlite3"); } + + static class TestQlWrap implements AutoCloseable { + SqLiteWrapper sql; + File dbFile; + + TestQlWrap() { + dbFile = randomDbFile(); + sql = new SqLiteWrapper(dbFile, false); + dbFile.deleteOnExit(); + } + + @Override + public void close() { + sql.close(); + } + } } From 1742869b93ce5f9c8e81e10c2b84f6a7e0c7ef56 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Thu, 23 May 2024 13:41:45 -0400 Subject: [PATCH 25/26] Turn SemVer class into a record --- .../com/cjburkey/claimchunk/ClaimChunk.java | 2 +- .../cjburkey/claimchunk/update/SemVer.java | 35 ++++++------------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java index be35503e..1bd39e0a 100644 --- a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java @@ -146,7 +146,7 @@ public void onLoad() { // Get the current plugin version version = SemVer.fromString(getDescription().getVersion()); - if (version.marker != null) { + if (version.marker() != null) { Utils.debug("Plugin version is nonstandard release %s", version); } diff --git a/src/main/java/com/cjburkey/claimchunk/update/SemVer.java b/src/main/java/com/cjburkey/claimchunk/update/SemVer.java index ac5196d9..050427b8 100644 --- a/src/main/java/com/cjburkey/claimchunk/update/SemVer.java +++ b/src/main/java/com/cjburkey/claimchunk/update/SemVer.java @@ -1,6 +1,6 @@ package com.cjburkey.claimchunk.update; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -8,28 +8,10 @@ * A nice little semantic versioning class. Feel free to use this in your own projects if you want * :) */ -public class SemVer implements Comparable { +public record SemVer(int major, int minor, int patch, String marker) implements Comparable { - @SuppressWarnings("WeakerAccess") - public final int major; - - @SuppressWarnings("WeakerAccess") - public final int minor; - - @SuppressWarnings("WeakerAccess") - public final int patch; - - @SuppressWarnings("WeakerAccess") - public final String marker; - - private SemVer(int major, int minor, int patch, @Nullable String marker) { - this.major = major; - this.minor = minor; - this.patch = patch; - this.marker = ((marker == null) ? null : marker.toUpperCase()); - } - - public static SemVer fromString(final String version) { + public static @NotNull SemVer fromString(@NotNull String version) + throws IllegalArgumentException { try { final String[] split = version.trim().split("\\."); if (split.length != 3) { @@ -43,10 +25,11 @@ public static SemVer fromString(final String version) { final int major = Integer.parseInt(split[0].trim()); final int minor = Integer.parseInt(split[1].trim()); final int patch = Integer.parseInt(patchMarker[0].trim()); - final String marker = ((patchMarker.length == 2) ? patchMarker[1].trim() : null); + final String marker = + ((patchMarker.length == 2) ? patchMarker[1].trim().toUpperCase() : null); return new SemVer(major, minor, patch, marker); } catch (Exception e) { - throw INVALID_SEMVER(version); + throw INVALID_SEMVER(version, e); } } @@ -54,6 +37,10 @@ private static IllegalArgumentException INVALID_SEMVER(String input) { return new IllegalArgumentException("Invalid SemVer format: " + input); } + private static IllegalArgumentException INVALID_SEMVER(String input, Throwable cause) { + return new IllegalArgumentException("Invalid SemVer format: " + input, cause); + } + @Override public int compareTo(SemVer o) { if (o.major > major) return -1; From 8f8afc014a5859aa2b5c75e943ec0a7da26cada7 Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Fri, 24 May 2024 15:55:43 -0400 Subject: [PATCH 26/26] Finish up! --- .../com/cjburkey/claimchunk/ClaimChunk.java | 4 +- .../{IDataConverter.java => DataConvert.java} | 37 +++++++------------ .../data/newdata/MySQLDataHandler.java | 4 +- .../data/sqlite/SqLiteDataHandler.java | 5 +-- .../claimchunk/data/sqlite/SqLiteWrapper.java | 25 ++++++++++++- .../claimchunk/data/sqlite/SqlDataChunk.java | 11 ++---- .../claimchunk/data/sqlite/SqlDataPlayer.java | 3 -- 7 files changed, 46 insertions(+), 43 deletions(-) rename src/main/java/com/cjburkey/claimchunk/data/{IDataConverter.java => DataConvert.java} (50%) diff --git a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java index 1bd39e0a..47c8fd75 100644 --- a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java @@ -6,7 +6,7 @@ import com.cjburkey.claimchunk.cmd.*; import com.cjburkey.claimchunk.config.ClaimChunkWorldProfileHandler; import com.cjburkey.claimchunk.config.ccconfig.*; -import com.cjburkey.claimchunk.data.IDataConverter; +import com.cjburkey.claimchunk.data.DataConvert; import com.cjburkey.claimchunk.data.newdata.*; import com.cjburkey.claimchunk.data.sqlite.SqLiteDataHandler; import com.cjburkey.claimchunk.event.*; @@ -416,7 +416,7 @@ private boolean initDataHandler() { if (oldDataHandler != null) { try { - IDataConverter.copyConvert(oldDataHandler, dataHandler); + DataConvert.copyConvert(oldDataHandler, dataHandler); oldDataHandler.exit(); if (oldClaimedFile.exists()) { diff --git a/src/main/java/com/cjburkey/claimchunk/data/IDataConverter.java b/src/main/java/com/cjburkey/claimchunk/data/DataConvert.java similarity index 50% rename from src/main/java/com/cjburkey/claimchunk/data/IDataConverter.java rename to src/main/java/com/cjburkey/claimchunk/data/DataConvert.java index ea7cb898..0782ee78 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/IDataConverter.java +++ b/src/main/java/com/cjburkey/claimchunk/data/DataConvert.java @@ -5,25 +5,23 @@ /** * Represents a class that may act as a converter between two different data systems. * - * @param The type from which the conversion may occur - * @param The type into which the provided handler will be converted * @since 0.0.13 */ -public interface IDataConverter< - From extends IClaimChunkDataHandler, To extends IClaimChunkDataHandler> { +public class DataConvert { + + private DataConvert() {} /** * Copies the data from the provided old data handler into the provided new data handler. This * does not update the old data handler. * - * @param oldDataHandler The old handler - * @param newDataHandler The new handler - * @param The type of the old data handler - * @param The type of the new data handler + * @param oldDataHandler The old handler, may or may not be initialized + * @param newDataHandler The new handler, may or may not be initialized * @since 0.0.13 */ - static void copyConvert( - A oldDataHandler, B newDataHandler) throws Exception { + public static void copyConvert( + IClaimChunkDataHandler oldDataHandler, IClaimChunkDataHandler newDataHandler) + throws Exception { // Initialize the old data handler if it hasn't been initialized yet if (!oldDataHandler.getHasInit()) oldDataHandler.init(); @@ -33,21 +31,12 @@ static void // Initialize the new data handler if it hasn't been initialized yet if (!newDataHandler.getHasInit()) newDataHandler.init(); + // Copy the player data from the old data handler to the new data handler. + // Make sure we do this before players! The SQLite data handler will make dummy players if + // there aren't proper players in the player data table already. + newDataHandler.addPlayers(oldDataHandler.getFullPlayerData()); + // Copy the chunks from the old data handler to the new data handler newDataHandler.addClaimedChunks(oldDataHandler.getClaimedChunks()); - - // Copy the player data from the old data handler to the new data handler - newDataHandler.addPlayers(oldDataHandler.getFullPlayerData()); } - - /** - * Converts one kind of data handler into the other kind. - * - * @param oldDataHandler The old data handler - * @return A new data handler containing the old data handler's data - * @throws Exception Any error that may occur during any phase of data conversion - * @since 0.0.13 - */ - @SuppressWarnings("unused") - To convert(From oldDataHandler) throws Exception; } diff --git a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java index 205c9fad..797e1250 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/newdata/MySQLDataHandler.java @@ -7,7 +7,7 @@ import com.cjburkey.claimchunk.chunk.ChunkPlayerPermissions; import com.cjburkey.claimchunk.chunk.ChunkPos; import com.cjburkey.claimchunk.chunk.DataChunk; -import com.cjburkey.claimchunk.data.IDataConverter; +import com.cjburkey.claimchunk.data.DataConvert; import com.cjburkey.claimchunk.player.FullPlayerData; import com.cjburkey.claimchunk.player.SimplePlayerData; @@ -116,7 +116,7 @@ public void init() throws Exception { } if (oldDataHandler != null && claimChunk.getConfigHandler().getConvertOldData()) { - IDataConverter.copyConvert(oldDataHandler, this); + DataConvert.copyConvert(oldDataHandler, this); oldDataHandler.exit(); if (onCleanOld != null) { onCleanOld.accept(oldDataHandler); diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java index d206fc19..1459adf7 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteDataHandler.java @@ -20,10 +20,7 @@ * I've actually just decided that we're gonna do it this way: * - SQLite backing database *file* similar to current MySQL integration (which will * be removed and automatically converted). - * - Have some intermediary layer that can - * - Keep some regions in memory and unload when no players are within them - * for a minute or two. - * - Respond immediately and asynchronously update database. + * - Have some intermediary layer that can Respond immediately and asynchronously update database. */ /** diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java index 466d35a9..4b0283d6 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqLiteWrapper.java @@ -65,6 +65,28 @@ public void close() { public void addClaimedChunk(DataChunk chunk) { SqlClosure.sqlExecute( connection -> { + // Make sure the player already exists! + // If there isn't a player with their UUID as a primary key, I'm pretty sure + // inserting into the chunk data would fail. This only really matters during + // loading, as there could be a chance the player is missing, somehow? + try (PreparedStatement statement = + connection.prepareStatement( + """ + INSERT OR IGNORE INTO player_data ( + player_uuid, + last_ign, + chunk_name, + last_online_time, + alerts_enabled, + extra_max_claims + ) VALUES ( + ?, "", NULL, 0, TRUE, 0 + ) + """)) { + statement.setString(1, chunk.player.toString()); + statement.execute(); + } + // Add the chunk try (PreparedStatement statement = connection.prepareStatement( @@ -149,13 +171,14 @@ public void removeClaimedChunk(ChunkPos chunk) { }); } + // The provided player data will replace an existing row public void addPlayer(FullPlayerData playerData) { SqlClosure.sqlExecute( connection -> { try (PreparedStatement statement = connection.prepareStatement( """ - INSERT INTO player_data ( + INSERT OR REPLACE INTO player_data ( player_uuid, last_ign, chunk_name, diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java index de6b20f8..dfb4af3f 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataChunk.java @@ -6,17 +6,14 @@ public class SqlDataChunk { @Column(name = "chunk_world") - public String world; + String world; @Column(name = "chunk_x") - public int x; + int x; @Column(name = "chunk_z") - public int z; + int z; @Column(name = "owner_uuid") - public String uuid; - - @SuppressWarnings("unused") - public SqlDataChunk() {} + String uuid; } diff --git a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataPlayer.java b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataPlayer.java index 14df833c..2a3892bf 100644 --- a/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataPlayer.java +++ b/src/main/java/com/cjburkey/claimchunk/data/sqlite/SqlDataPlayer.java @@ -25,7 +25,4 @@ public class SqlDataPlayer { @Column(name = "extra_max_claims") public int extraMaxClaims; - - @SuppressWarnings("unused") - public SqlDataPlayer() {} }