feat: sql data storage and retrieve

This commit is contained in:
aro 2023-02-01 01:06:40 +01:00
parent b63e60e83d
commit 13d2b6f497
14 changed files with 144 additions and 49 deletions

View file

@ -1,6 +1,7 @@
package net.artelnatif.nicko.bukkit.event; package net.artelnatif.nicko.bukkit.event;
import net.artelnatif.nicko.bukkit.NickoBukkit; import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.disguise.ActionResult;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@ -10,6 +11,9 @@ public class PlayerQuitListener implements Listener {
@EventHandler @EventHandler
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
final Player player = event.getPlayer(); final Player player = event.getPlayer();
NickoBukkit.getInstance().getNicko().getDataStore().saveData(player); final ActionResult<Void> result = NickoBukkit.getInstance().getNicko().getDataStore().saveData(player);
if (result.isError()) {
NickoBukkit.getInstance().getLogger().warning("Failed to save data for " + player.getName());
}
} }
} }

View file

@ -30,7 +30,7 @@ public class CacheDetailledGUI {
final List<String> loadedSkins = skins.entrySet().stream() final List<String> loadedSkins = skins.entrySet().stream()
.filter(entry -> entry.getValue().isPresent()) .filter(entry -> entry.getValue().isPresent())
.map(Map.Entry::getKey) .map(Map.Entry::getKey)
.toList(); .collect(Collectors.toList());
final List<Item> items = loadedSkins.stream() final List<Item> items = loadedSkins.stream()
.map(SkinPlaceholder::new) .map(SkinPlaceholder::new)

View file

@ -16,7 +16,7 @@ public class I18N {
final NickoBukkit instance = NickoBukkit.getInstance(); final NickoBukkit instance = NickoBukkit.getInstance();
try { try {
final Optional<NickoProfile> profile = instance.getNicko().getDataStore().getData(player.getUniqueId()); final Optional<NickoProfile> profile = instance.getNicko().getDataStore().getData(player.getUniqueId());
return profile.isEmpty() ? Locale.FALLBACK_LOCALE : profile.get().getLocale(); return !profile.isPresent() ? Locale.FALLBACK_LOCALE : profile.get().getLocale();
} catch (IllegalArgumentException exception) { } catch (IllegalArgumentException exception) {
instance.getLogger().severe("Invalid locale provided by " + player.getName() + ", defaulting to " + Locale.FALLBACK_LOCALE.getCode() + "."); instance.getLogger().severe("Invalid locale provided by " + player.getName() + ", defaulting to " + Locale.FALLBACK_LOCALE.getCode() + ".");
return Locale.FALLBACK_LOCALE; return Locale.FALLBACK_LOCALE;

View file

@ -5,7 +5,7 @@ import java.io.Serializable;
public enum Locale implements Serializable { public enum Locale implements Serializable {
ENGLISH("en", "English"), ENGLISH("en", "English"),
FRENCH("fr", "Français"), FRENCH("fr", "Français"),
CUSTOM("custom", "Server Custom"); CUSTOM("cm", "Server Custom");
public static final Locale FALLBACK_LOCALE = ENGLISH; public static final Locale FALLBACK_LOCALE = ENGLISH;
@ -17,6 +17,13 @@ public enum Locale implements Serializable {
this.name = name; this.name = name;
} }
public static Locale fromCode(String code) {
for (Locale value : values()) {
if (code.equals(value.code)) return value;
}
return ENGLISH;
}
public String getCode() { public String getCode() {
return code; return code;
} }

View file

@ -7,16 +7,18 @@ public class ActionResult<R> {
private boolean error = false; private boolean error = false;
private R result; private R result;
public ActionResult(I18NDict errorMessage) {
this.error = true;
this.errorMessage = errorMessage;
}
public ActionResult() { public ActionResult() {
this.errorMessage = null; this.errorMessage = null;
} }
public void setResult(R result) { public ActionResult(I18NDict errorMessage) {
this.errorMessage = errorMessage;
this.error = true;
this.result = null;
}
public ActionResult(R result) {
this.errorMessage = null;
this.result = result; this.result = result;
} }

View file

@ -26,13 +26,7 @@ public interface Internals {
final Optional<String> uuid = mojang.getUUID(profile.getSkin()); final Optional<String> uuid = mojang.getUUID(profile.getSkin());
if (uuid.isPresent()) { if (uuid.isPresent()) {
skin = (reset ? mojang.getSkinWithoutCaching(uuid.get()) : mojang.getSkin(uuid.get())); skin = (reset ? mojang.getSkinWithoutCaching(uuid.get()) : mojang.getSkin(uuid.get()));
if (skin.isEmpty()) { return skin.map(ActionResult::new).orElseGet(() -> new ActionResult<>(I18NDict.Error.SKIN_FAIL_MOJANG));
return new ActionResult<>(I18NDict.Error.SKIN_FAIL_MOJANG);
}
final ActionResult<MojangSkin> actionResult = new ActionResult<>();
actionResult.setResult(skin.get());
return actionResult;
} }
return new ActionResult<>(I18NDict.Error.NAME_FAIL_MOJANG); return new ActionResult<>(I18NDict.Error.NAME_FAIL_MOJANG);
} catch (ExecutionException e) { } catch (ExecutionException e) {

View file

@ -1,7 +1,9 @@
package net.artelnatif.nicko.impl; package net.artelnatif.nicko.impl;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
public class InternalsProvider { public class InternalsProvider {
@ -12,7 +14,15 @@ public class InternalsProvider {
final String packageName = Internals.class.getPackage().getName(); final String packageName = Internals.class.getPackage().getName();
final String bukkitVersion = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; final String bukkitVersion = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
final String fullClassName = packageName + "." + bukkitVersion; final String fullClassName = packageName + "." + bukkitVersion;
internals = (Internals) Class.forName(fullClassName).getConstructors()[0].newInstance(); final Class<?> clazz = Class.forName(fullClassName);
internals = (Internals) clazz.getConstructors()[0].newInstance();
for (Annotation annotation : clazz.getDeclaredAnnotations()) {
if (annotation instanceof Unstable) {
NickoBukkit.getInstance().getLogger().warning("Version " + Bukkit.getVersion() + " has been marked as unstable.");
NickoBukkit.getInstance().getLogger().warning("Nicko might not work at all until the version has been tested extensively.");
NickoBukkit.getInstance().getLogger().warning("Proceed with caution!");
}
}
} catch (InvocationTargetException | ClassNotFoundException | InstantiationException | IllegalAccessException | } catch (InvocationTargetException | ClassNotFoundException | InstantiationException | IllegalAccessException |
ClassCastException exception) { ClassCastException exception) {
internals = null; internals = null;

View file

@ -0,0 +1,7 @@
package net.artelnatif.nicko.impl;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
public @interface Unstable { }

View file

@ -1,6 +1,8 @@
package net.artelnatif.nicko.storage; package net.artelnatif.nicko.storage;
import net.artelnatif.nicko.Nicko; import net.artelnatif.nicko.Nicko;
import net.artelnatif.nicko.bukkit.i18n.I18NDict;
import net.artelnatif.nicko.disguise.ActionResult;
import net.artelnatif.nicko.disguise.NickoProfile; import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.mojang.MojangUtils; import net.artelnatif.nicko.mojang.MojangUtils;
import net.artelnatif.nicko.storage.json.JSONStorage; import net.artelnatif.nicko.storage.json.JSONStorage;
@ -76,12 +78,13 @@ public class PlayerDataStore {
} }
} }
public void saveData(Player player) { public ActionResult<Void> saveData(Player player) {
if (storage.isError()) { return; } if (storage.isError()) { return new ActionResult<>(I18NDict.Error.UNEXPECTED_ERROR); }
if (!profiles.containsKey(player.getUniqueId())) { return; } if (!profiles.containsKey(player.getUniqueId())) { return new ActionResult<>(I18NDict.Error.UNEXPECTED_ERROR); }
storage.store(player.getUniqueId(), profiles.get(player.getUniqueId())); final ActionResult<Void> store = storage.store(player.getUniqueId(), profiles.get(player.getUniqueId()));
profiles.remove(player.getUniqueId()); profiles.remove(player.getUniqueId());
return store;
} }
public Storage getStorage() { public Storage getStorage() {

View file

@ -1,18 +1,17 @@
package net.artelnatif.nicko.storage.sql; package net.artelnatif.nicko.storage.sql;
import com.google.common.io.ByteStreams;
import net.artelnatif.nicko.Nicko; import net.artelnatif.nicko.Nicko;
import net.artelnatif.nicko.bukkit.i18n.I18NDict; import net.artelnatif.nicko.bukkit.i18n.I18NDict;
import net.artelnatif.nicko.bukkit.i18n.Locale;
import net.artelnatif.nicko.disguise.ActionResult; import net.artelnatif.nicko.disguise.ActionResult;
import net.artelnatif.nicko.disguise.NickoProfile; import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.storage.Storage; import net.artelnatif.nicko.storage.Storage;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -40,13 +39,14 @@ public class SQLStorage extends Storage {
if (connection == null) return new ActionResult<>(I18NDict.Error.SQL_ERROR); if (connection == null) return new ActionResult<>(I18NDict.Error.SQL_ERROR);
try { try {
final String sql = "INSERT IGNORE INTO nicko.DATA (`uuid`, `name`, `skin`, `bungeecord`) VALUES (?, ?, ?, ?)"; final String sql = "INSERT IGNORE INTO nicko.DATA (`uuid`, `name`, `skin`, `locale`, `bungeecord`) VALUES (?, ?, ?, ?, ?)";
final PreparedStatement statement = connection.prepareStatement(sql); final PreparedStatement statement = connection.prepareStatement(sql);
statement.setBinaryStream(1, uuidToBin(uuid)); statement.setBinaryStream(1, uuidToBin(uuid));
statement.setString(2, profile.getName()); statement.setString(2, profile.getName());
statement.setString(3, profile.getSkin()); statement.setString(3, profile.getSkin());
statement.setBoolean(4, profile.isBungeecordTransfer()); statement.setString(4, profile.getLocale().getCode());
statement.setBoolean(5, profile.isBungeecordTransfer());
statement.executeUpdate(); statement.executeUpdate();
return new ActionResult<>(); return new ActionResult<>();
} catch (SQLException e) { } catch (SQLException e) {
@ -57,12 +57,52 @@ public class SQLStorage extends Storage {
@Override @Override
public boolean isStored(UUID uuid) { public boolean isStored(UUID uuid) {
return false; final Connection connection = getProvider().getConnection();
if (connection == null) return false;
try {
final String sql = "SELECT * FROM nicko.DATA WHERE uuid = ?";
final PreparedStatement statement = connection.prepareStatement(sql);
statement.setBinaryStream(1, uuidToBin(uuid));
final ResultSet resultSet = statement.executeQuery();
return resultSet.next();
} catch (SQLException e) {
nicko.getLogger().warning("Couldn't check if data is present: " + e.getMessage());
return false;
}
} }
@Override @Override
public Optional<NickoProfile> retrieve(UUID uuid) { public Optional<NickoProfile> retrieve(UUID uuid) {
return Optional.empty(); final Connection connection = getProvider().getConnection();
if (connection == null) return Optional.empty();
try {
final String sql = "SELECT * FROM nicko.DATA WHERE uuid = ?";
final PreparedStatement statement = connection.prepareStatement(sql);
statement.setBinaryStream(1, uuidToBin(uuid));
final ResultSet resultSet = statement.executeQuery();
String name = "";
String skin = "";
String locale = "";
boolean bungeecord = false;
while(resultSet.next()) {
name = resultSet.getString("name");
skin = resultSet.getString("skin");
locale = resultSet.getString("locale");
bungeecord = resultSet.getBoolean("bungeecord");
}
final NickoProfile profile = new NickoProfile(name, skin, Locale.fromCode(locale), bungeecord);
return Optional.of(profile);
} catch (SQLException e) {
nicko.getLogger().warning("Couldn't fetch profile: " + e.getMessage());
return Optional.empty();
}
} }
private ByteArrayInputStream uuidToBin(UUID uuid) { private ByteArrayInputStream uuidToBin(UUID uuid) {
@ -72,13 +112,4 @@ public class SQLStorage extends Storage {
.putLong(uuid.getLeastSignificantBits()); .putLong(uuid.getLeastSignificantBits());
return new ByteArrayInputStream(bytes); return new ByteArrayInputStream(bytes);
} }
private UUID binToUUID(InputStream stream) {
final ByteBuffer buffer = ByteBuffer.allocate(16);
try {
buffer.put(ByteStreams.toByteArray(stream));
buffer.flip();
return new UUID(buffer.getLong(), buffer.getLong());
} catch (IOException ignored) { return null; }
}
} }

View file

@ -55,7 +55,13 @@ public class SQLStorageProvider implements StorageProvider {
private void createTable() throws SQLException { private void createTable() throws SQLException {
final Connection connection = getConnection(); final Connection connection = getConnection();
String query = "CREATE TABLE IF NOT EXISTS %s.DATA (uuid binary(16) NOT NULL,name varchar(16) NOT NULL,skin varchar(16) NOT NULL,bungeecord boolean NOT NULL,PRIMARY KEY (UUID))"; String query = "CREATE TABLE IF NOT EXISTS %s.DATA " +
"(uuid binary(16) NOT NULL," +
"name varchar(16) NOT NULL," +
"skin varchar(16) NOT NULL," +
"locale char(2) NOT NULL," +
"bungeecord boolean NOT NULL," +
"PRIMARY KEY (UUID))";
query = query.replace("%s", schemaName); query = query.replace("%s", schemaName);
final PreparedStatement statement = connection.prepareStatement(query); final PreparedStatement statement = connection.prepareStatement(query);

View file

@ -7,12 +7,14 @@ import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.config.Configuration; import net.artelnatif.nicko.config.Configuration;
import net.artelnatif.nicko.disguise.ActionResult; import net.artelnatif.nicko.disguise.ActionResult;
import net.artelnatif.nicko.disguise.NickoProfile; import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.bukkit.i18n.Locale;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import java.util.Optional;
public class BrokenSQLTest { public class BrokenSQLTest {
private static ServerMock server; private static ServerMock server;
private static NickoBukkit plugin; private static NickoBukkit plugin;
private static PlayerMock player;
@BeforeAll @BeforeAll
public static void setup() { public static void setup() {
@ -26,6 +28,7 @@ public class BrokenSQLTest {
false); false);
server = MockBukkit.mock(); server = MockBukkit.mock();
plugin = MockBukkit.load(NickoBukkit.class, config); plugin = MockBukkit.load(NickoBukkit.class, config);
player = server.addPlayer();
} }
@Test @Test
@ -37,10 +40,17 @@ public class BrokenSQLTest {
@Test @Test
@DisplayName("Fail to Store Player Via SQL") @DisplayName("Fail to Store Player Via SQL")
public void storePlayer() { public void storePlayer() {
final PlayerMock playerMock = server.addPlayer(); final Optional<NickoProfile> optionalProfile = plugin.getNicko().getDataStore().getData(player.getUniqueId());
final NickoProfile profile = new NickoProfile("Notch", "Notch", Locale.ENGLISH, true); Assertions.assertFalse(optionalProfile.isPresent());
final ActionResult<Void> storeAction = plugin.getNicko().getDataStore().getStorage().store(playerMock.getUniqueId(), profile); ActionResult<Void> result = plugin.getNicko().getDataStore().saveData(player);
Assertions.assertTrue(storeAction.isError()); Assertions.assertTrue(result.isError());
}
@Test
@DisplayName("Fail to Retrieve Player Via SQL")
public void retrievePlayer() {
final Optional<NickoProfile> storeAction = plugin.getNicko().getDataStore().getData(player.getUniqueId());
Assertions.assertFalse(storeAction.isPresent());
} }
@AfterAll @AfterAll

View file

@ -10,9 +10,12 @@ import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.bukkit.i18n.Locale; import net.artelnatif.nicko.bukkit.i18n.Locale;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import java.util.Optional;
public class SQLStorageTest { public class SQLStorageTest {
private static ServerMock server; private static ServerMock server;
private static NickoBukkit plugin; private static NickoBukkit plugin;
private static PlayerMock player;
@BeforeAll @BeforeAll
public static void setup() { public static void setup() {
@ -26,6 +29,7 @@ public class SQLStorageTest {
false); false);
server = MockBukkit.mock(); server = MockBukkit.mock();
plugin = MockBukkit.load(NickoBukkit.class, config); plugin = MockBukkit.load(NickoBukkit.class, config);
player = server.addPlayer();
} }
@Test @Test
@ -37,14 +41,30 @@ public class SQLStorageTest {
@Test @Test
@DisplayName("Store Player Via SQL") @DisplayName("Store Player Via SQL")
public void storePlayer() { public void storePlayer() {
final PlayerMock playerMock = server.addPlayer(); final Optional<NickoProfile> optionalProfile = plugin.getNicko().getDataStore().getData(player.getUniqueId());
final NickoProfile profile = new NickoProfile("Notch", "Notch", Locale.ENGLISH, true); Assertions.assertTrue(optionalProfile.isPresent());
final ActionResult<Void> storeAction = plugin.getNicko().getDataStore().getStorage().store(playerMock.getUniqueId(), profile);
Assertions.assertFalse(storeAction.isError()); final NickoProfile profile = optionalProfile.get();
profile.setName("Notch");
profile.setSkin("Notch");
profile.setLocale(Locale.ENGLISH);
profile.setBungeecordTransfer(true);
ActionResult<Void> result = plugin.getNicko().getDataStore().saveData(player);
Assertions.assertFalse(result.isError());
}
@Test
@DisplayName("Retrieve Player Via SQL")
public void retrievePlayer() {
final Optional<NickoProfile> storeAction = plugin.getNicko().getDataStore().getData(player.getUniqueId());
Assertions.assertTrue(storeAction.isPresent());
final NickoProfile profile = storeAction.get();
Assertions.assertEquals("Notch", profile.getName());
Assertions.assertEquals(Locale.ENGLISH, profile.getLocale());
} }
@AfterAll @AfterAll
public static void shutdown() { public static void shutdown() {
MockBukkit.unmock(); MockBukkit.unmock();
} }
} }

View file

@ -26,6 +26,7 @@ import org.bukkit.event.player.PlayerTeleportEvent;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.*; import java.util.*;
@Unstable
public class v1_19_R2 implements Internals { public class v1_19_R2 implements Internals {
@Override @Override
public void updateSelf(Player player) { public void updateSelf(Player player) {