diff --git a/src/main/java/mc/unraveled/reforged/banning/LocalizedBanList.java b/src/main/java/mc/unraveled/reforged/banning/BanManager.java similarity index 94% rename from src/main/java/mc/unraveled/reforged/banning/LocalizedBanList.java rename to src/main/java/mc/unraveled/reforged/banning/BanManager.java index 62c856a..73d6435 100644 --- a/src/main/java/mc/unraveled/reforged/banning/LocalizedBanList.java +++ b/src/main/java/mc/unraveled/reforged/banning/BanManager.java @@ -10,14 +10,14 @@ import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; -public class LocalizedBanList implements Locker, Baker { +public final class BanManager implements Locker, Baker { private final Traverse plugin; private Set storedBans; // This should only be reassigned by the baker. private boolean baked = false; // This should only be reassigned by the baker. @SneakyThrows - public LocalizedBanList(Traverse plugin) { + public BanManager(Traverse plugin) { this.storedBans = new HashSet<>(); this.plugin = plugin; diff --git a/src/main/java/mc/unraveled/reforged/data/DataManager.java b/src/main/java/mc/unraveled/reforged/data/DataManager.java new file mode 100644 index 0000000..b5db73a --- /dev/null +++ b/src/main/java/mc/unraveled/reforged/data/DataManager.java @@ -0,0 +1,101 @@ +package mc.unraveled.reforged.data; + +import lombok.SneakyThrows; +import mc.unraveled.reforged.api.Baker; +import mc.unraveled.reforged.api.Locker; +import mc.unraveled.reforged.plugin.Traverse; +import mc.unraveled.reforged.storage.DBUser; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +public final class DataManager implements Baker, Locker { + private final Traverse plugin; + private Set playerDataCache = new HashSet<>(); // Should only be set by the baker. + private boolean baked = false; // Should only be set by the baker. + + public DataManager(Traverse plugin) { + this.plugin = plugin; + DBUser user = new DBUser(plugin.getSQLManager().establish()); + user.createTable(); + playerDataCache.addAll(user.all()); + user.close(); + bake(); + } + + @Nullable + public PlayerData getPlayerData(String playerName) { + return playerDataCache.stream() + .filter(data -> data.getUserName().equals(playerName)) + .findFirst() + .orElse(null); + } + + @Nullable + public PlayerData getPlayerData(UUID uuid) { + return playerDataCache.stream() + .filter(data -> data.getUuid().equals(uuid)) + .findFirst() + .orElse(null); + } + + public void addPlayerData(PlayerData data) { + if (baked) throw new IllegalStateException("Cannot add player data while the data manager is baked."); + + playerDataCache.add(data); + } + + public void removePlayerData(PlayerData data) { + if (baked) throw new IllegalStateException("Cannot remove player data while the data manager is baked."); + + playerDataCache.remove(data); + } + + public void removePlayerData(UUID uuid) { + if (baked) throw new IllegalStateException("Cannot remove player data while the data manager is baked."); + + playerDataCache.removeIf(data -> data.getUuid().equals(uuid)); + } + + public void removePlayerData(String playerName) { + if (baked) throw new IllegalStateException("Cannot remove player data while the data manager is baked."); + + playerDataCache.removeIf(data -> data.getUserName().equals(playerName)); + } + + public void saveData(PlayerData data) { + DBUser user = new DBUser(plugin.getSQLManager().establish()); + user.insert(data); + } + + @SneakyThrows + @Override + public void bake() { + if (baked) return; + + synchronized (lock()) { + this.playerDataCache = playerDataCache.stream().collect(Collectors.toUnmodifiableSet()); + lock().wait(1000); + } + + this.baked = true; + lock().notify(); + } + + @SneakyThrows + @Override + public void unbake() { + if (!baked) return; + + synchronized (lock()) { + this.playerDataCache = new HashSet<>(playerDataCache); + lock().wait(1000); + } + + this.baked = false; + lock().notify(); + } +} diff --git a/src/main/java/mc/unraveled/reforged/data/PlayerData.java b/src/main/java/mc/unraveled/reforged/data/PlayerData.java new file mode 100644 index 0000000..a18c507 --- /dev/null +++ b/src/main/java/mc/unraveled/reforged/data/PlayerData.java @@ -0,0 +1,19 @@ +package mc.unraveled.reforged.data; + +import lombok.AllArgsConstructor; +import lombok.Data; +import mc.unraveled.reforged.permission.Rank; + +import java.util.Date; +import java.util.UUID; + +@Data +@AllArgsConstructor +public class PlayerData { + private UUID uuid; + private String userName; + private Rank rank; + private long playtime; + private int coins; + private Date lastLogin; +} diff --git a/src/main/java/mc/unraveled/reforged/data/PlayerDataListener.java b/src/main/java/mc/unraveled/reforged/data/PlayerDataListener.java new file mode 100644 index 0000000..b056592 --- /dev/null +++ b/src/main/java/mc/unraveled/reforged/data/PlayerDataListener.java @@ -0,0 +1,33 @@ +package mc.unraveled.reforged.data; + +import mc.unraveled.reforged.permission.Rank; +import mc.unraveled.reforged.plugin.Traverse; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +import java.time.Instant; +import java.util.Date; + +public class PlayerDataListener implements Listener { + private final Traverse plugin; + + public PlayerDataListener(Traverse plugin) { + plugin.getServer().getPluginManager().registerEvents(this, plugin); + this.plugin = plugin; + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + PlayerData data = plugin.getDataManager().getPlayerData(player.getUniqueId()); + + if (data == null) { + data = new PlayerData(player.getUniqueId(), player.getName(), Rank.NON_OP, 0L, 0, Date.from(Instant.now())); + plugin.getDataManager().addPlayerData(data); + } + + data.setLastLogin(Date.from(Instant.now())); + } +} diff --git a/src/main/java/mc/unraveled/reforged/permission/Group.java b/src/main/java/mc/unraveled/reforged/permission/Group.java new file mode 100644 index 0000000..cf77f46 --- /dev/null +++ b/src/main/java/mc/unraveled/reforged/permission/Group.java @@ -0,0 +1,63 @@ +package mc.unraveled.reforged.permission; + +import lombok.Data; +import mc.unraveled.reforged.api.Baker; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +@Data +public class Group implements Baker { + private final Rank rank; + private Set permissions = new HashSet<>(); + private Set players = new HashSet<>(); + private boolean baked = false; + + public Group(Rank rank) { + this.rank = rank; + } + + public void insert(String permission) { + if (baked) throw new IllegalStateException("Cannot modify a baked group."); + permissions.add(permission); + } + + public void insert(OfflinePlayer player) { + if (baked) throw new IllegalStateException("Cannot modify a baked group."); + players.add(player); + } + + public void eject(String permission) { + if (baked) throw new IllegalStateException("Cannot modify a baked group."); + permissions.remove(permission); + } + + public void eject(OfflinePlayer player) { + if (baked) throw new IllegalStateException("Cannot modify a baked group."); + players.remove(player); + } + + @Override + public void bake() { + if (baked) return; + + this.permissions = permissions.stream().collect(Collectors.toUnmodifiableSet()); + this.players = players.stream().collect(Collectors.toUnmodifiableSet()); + + this.baked = true; + } + + @Override + public void unbake() { + if (!baked) return; + + this.permissions = new HashSet<>(permissions); + this.players = new HashSet<>(players); + + this.baked = false; + + } +} diff --git a/src/main/java/mc/unraveled/reforged/permission/RankManager.java b/src/main/java/mc/unraveled/reforged/permission/RankManager.java new file mode 100644 index 0000000..60c3283 --- /dev/null +++ b/src/main/java/mc/unraveled/reforged/permission/RankManager.java @@ -0,0 +1,104 @@ +package mc.unraveled.reforged.permission; + +import lombok.Getter; +import mc.unraveled.reforged.api.Baker; +import mc.unraveled.reforged.api.Locker; +import mc.unraveled.reforged.plugin.Traverse; +import mc.unraveled.reforged.storage.DBGroup; +import mc.unraveled.reforged.util.Tuple; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class RankManager implements Baker, Locker { + @Getter + private final Traverse plugin; + private List, List>> rankQueue = new ArrayList<>(); + private List bakedGroups = new ArrayList<>(); + private boolean baked = false; + + public RankManager(@NotNull Traverse plugin) { + this.plugin = plugin; + + lock().notify(); + + DBGroup groupHandler = new DBGroup(plugin.getSQLManager().establish()); + Arrays.stream(Rank.values()).forEach(groupHandler::createTable); + Rank[] ranks = Rank.values(); + + for (Rank rank : ranks) { + Tuple, List> rankTuple; + List playerList; + List permissionList; + + playerList = groupHandler.getPlayers(rank); + permissionList = groupHandler.getPermissions(rank); + + rankTuple = new Tuple<>(rank, playerList, permissionList); + rankQueue.add(rankTuple); + } + + groupHandler.close(); + bake(); + } + + public void insert(Rank rank, List player, List args) { + rankQueue.add(new Tuple<>(rank, player, args)); + } + + public void eject(Rank rank, List player, List args) { + rankQueue.remove(new Tuple<>(rank, player, args)); + } + + public void insertPermission(Rank rank, String permission) { + rankQueue.stream() + .filter(tuple -> tuple.getFirst().equals(rank)) + .forEach(tuple -> tuple.getThird().add(permission)); + } + + public void ejectPermission(Rank rank, String permission) { + rankQueue.stream() + .filter(tuple -> tuple.getFirst().equals(rank)) + .forEach(tuple -> tuple.getThird().remove(permission)); + } + + @Override + public void bake() { + if (baked) return; + + synchronized (lock()) { + rankQueue.forEach(tuple -> { + Rank rank = tuple.getFirst(); + List players = tuple.getSecond(); + List permissions = tuple.getThird(); + + Group group = new Group(rank); + for (OfflinePlayer p : players) { + group.insert(p); + } + for (String arg : permissions) { + group.insert(arg); + } + bakedGroups.add(group); + }); + } + + baked = true; + lock().notify(); + } + + @Override + public void unbake() { + if (!baked) return; + + synchronized (lock()) { + bakedGroups.clear(); + } + + baked = false; + lock().notify(); + } +} diff --git a/src/main/java/mc/unraveled/reforged/plugin/Traverse.java b/src/main/java/mc/unraveled/reforged/plugin/Traverse.java index 1dd6bc8..9f7cf42 100644 --- a/src/main/java/mc/unraveled/reforged/plugin/Traverse.java +++ b/src/main/java/mc/unraveled/reforged/plugin/Traverse.java @@ -1,37 +1,41 @@ package mc.unraveled.reforged.plugin; +import lombok.Getter; import lombok.SneakyThrows; -import mc.unraveled.reforged.permission.Rank; +import mc.unraveled.reforged.banning.BanManager; +import mc.unraveled.reforged.command.base.CommandLoader; +import mc.unraveled.reforged.data.DataManager; +import mc.unraveled.reforged.permission.RankManager; import mc.unraveled.reforged.storage.DBConnectionHandler; -import mc.unraveled.reforged.storage.DBGroup; import mc.unraveled.reforged.storage.DBProperties; import org.bukkit.plugin.java.JavaPlugin; -import java.util.Arrays; - public final class Traverse extends JavaPlugin { - private DBConnectionHandler handler; + // Primary variable declaration. + @Getter + private DBConnectionHandler SQLManager; + @Getter + private DataManager dataManager; + @Getter + private CommandLoader commandLoader; + @Getter + private BanManager banManager; + @Getter + private RankManager rankManager; @Override + @SneakyThrows public void onEnable() { - initDatabaseGroups(); + this.SQLManager = new DBConnectionHandler(new DBProperties("db.properties")); + this.dataManager = new DataManager(this); + this.commandLoader = new CommandLoader(this, "TRAVERSE"); + this.banManager = new BanManager(this); + this.rankManager = new RankManager(this); } @Override public void onDisable() { // Plugin shutdown logic } - - @SneakyThrows - private void initDatabaseGroups() { - handler = new DBConnectionHandler(new DBProperties("groups.properties")); - DBGroup groupHandler = new DBGroup(handler.establish()); - Arrays.stream(Rank.values()).forEach(groupHandler::createTable); - groupHandler.close(); - } - - public DBConnectionHandler getSQLManager() { - return handler; - } } diff --git a/src/main/java/mc/unraveled/reforged/storage/DBBan.java b/src/main/java/mc/unraveled/reforged/storage/DBBan.java index 3806220..9a17e1d 100644 --- a/src/main/java/mc/unraveled/reforged/storage/DBBan.java +++ b/src/main/java/mc/unraveled/reforged/storage/DBBan.java @@ -30,7 +30,7 @@ public class DBBan { } @SneakyThrows - public void addBan(AbstractBan ban) { + public void insert(@NotNull AbstractBan ban) { PreparedStatement statement = getConnection().prepareStatement("INSERT INTO bans (uuid, ip, reason, banned_by, banned_at, expires_at, active) VALUES (?, ?, ?, ?, ?, ?, ?);"); statement.setString(1, ban.getUuid()); statement.setString(2, ban.getIp()); @@ -78,10 +78,37 @@ public class DBBan { } @SneakyThrows - public void close() { - getConnection().close(); + public void update(@NotNull AbstractBan ban) { + PreparedStatement statement = getConnection().prepareStatement("UPDATE bans SET reason = ?, banned_by = ?, banned_at = ?, expires_at = ?, active = ? WHERE uuid = ?;"); + statement.setString(1, ban.getReason()); + statement.setString(2, ban.getSource()); + statement.setLong(3, ban.getPropogated()); + statement.setLong(4, ban.getExpiry()); + statement.setBoolean(5, ban.isActive()); + statement.setString(6, ban.getUuid()); + statement.executeUpdate(); } + @SneakyThrows + public void delete(@NotNull AbstractBan ban) { + PreparedStatement statement = getConnection().prepareStatement("DELETE FROM bans WHERE uuid = ?;"); + statement.setString(1, ban.getUuid()); + statement.executeUpdate(); + } + @SneakyThrows + public void deleteAll() { + PreparedStatement statement = getConnection().prepareStatement("DELETE FROM bans;"); + statement.executeUpdate(); + } + @SneakyThrows + public void close() { + getConnection().close(); + this.connection = null; + } + + public void open(Connection connection) { + this.connection = connection; + } } diff --git a/src/main/java/mc/unraveled/reforged/storage/DBUser.java b/src/main/java/mc/unraveled/reforged/storage/DBUser.java new file mode 100644 index 0000000..b84b7a7 --- /dev/null +++ b/src/main/java/mc/unraveled/reforged/storage/DBUser.java @@ -0,0 +1,88 @@ +package mc.unraveled.reforged.storage; + +import lombok.Getter; +import lombok.SneakyThrows; +import mc.unraveled.reforged.data.PlayerData; +import mc.unraveled.reforged.permission.Rank; +import org.jetbrains.annotations.NotNull; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +public class DBUser { + @Getter + private Connection connection; + + public DBUser(Connection connection) { + this.connection = connection; + } + + @SneakyThrows + public void createTable() { + PreparedStatement statement = getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS users (uuid VARCHAR(36), username VARCHAR(16), rank VARCHAR(64), play_time BIGINT, coins BIGINT, last_login BIGINT, PRIMARY KEY (uuid));"); + statement.executeUpdate(); + } + + @SneakyThrows + public void insert(@NotNull PlayerData playerData) { + PreparedStatement statement = getConnection().prepareStatement("IF NOT EXISTS (SELECT 1 FROM users WHERE uuid = ?) " + + "BEGIN INSERT INTO users (uuid, username, rank, play_time, coins, last_login) VALUES (?, ?, ?, ?, ?, ?) END " + + "ELSE BEGIN (UPDATE users SET username = ?, rank = ?, play_time = ?, coins = ?, last_login = ? WHERE uuid = ?) END;"); + + statement.setString(1, playerData.getUuid().toString()); + statement.setString(2, playerData.getUuid().toString()); + statement.setString(3, playerData.getUserName()); + statement.setString(4, playerData.getRank().name()); + statement.setLong(5, playerData.getPlaytime()); + statement.setInt(6, playerData.getCoins()); + statement.setLong(7, playerData.getLastLogin().getTime()); + statement.setString(8, playerData.getUserName()); + statement.setString(9, playerData.getRank().name()); + statement.setLong(10, playerData.getPlaytime()); + statement.setInt(11, playerData.getCoins()); + statement.setLong(12, playerData.getLastLogin().getTime()); + statement.setString(13, playerData.getUuid().toString()); + + statement.executeUpdate(); + } + + @SneakyThrows + public void delete(@NotNull PlayerData playerData) { + PreparedStatement statement = getConnection().prepareStatement("DELETE FROM users WHERE uuid = ?;"); + statement.setString(1, playerData.getUuid().toString()); + statement.executeUpdate(); + } + + @SneakyThrows + public void deleteAll() { + PreparedStatement statement = getConnection().prepareStatement("DELETE FROM users;"); + statement.executeUpdate(); + } + + @SneakyThrows + public List all() { + PreparedStatement statement = getConnection().prepareStatement("SELECT * FROM users;"); + ResultSet resultSet = statement.executeQuery(); + List dataList = new ArrayList<>(); + while (resultSet.next()) { + PlayerData playerData = new PlayerData(UUID.fromString(resultSet.getString("uuid")), + resultSet.getString("username"), + Rank.valueOf(resultSet.getString("rank")), + resultSet.getLong("play_time"), + resultSet.getInt("coins"), + new Date(resultSet.getLong("last_login"))); + dataList.add(playerData); + } + return dataList; + } + + @SneakyThrows + public void close() { + getConnection().close(); + } +} diff --git a/src/main/resources/groups.properties b/src/main/resources/db.properties similarity index 85% rename from src/main/resources/groups.properties rename to src/main/resources/db.properties index 1a92cab..5ba6a08 100644 --- a/src/main/resources/groups.properties +++ b/src/main/resources/db.properties @@ -7,7 +7,7 @@ host=localhost # The database port. port=5432 # The database file name. -databaseFile=groups.db +databaseFile=database.db # The database username. username=admin # The database password.