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;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.disguise.ActionResult;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@ -10,6 +11,9 @@ public class PlayerQuitListener implements Listener {
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
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()
.filter(entry -> entry.getValue().isPresent())
.map(Map.Entry::getKey)
.toList();
.collect(Collectors.toList());
final List<Item> items = loadedSkins.stream()
.map(SkinPlaceholder::new)

View file

@ -16,7 +16,7 @@ public class I18N {
final NickoBukkit instance = NickoBukkit.getInstance();
try {
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) {
instance.getLogger().severe("Invalid locale provided by " + player.getName() + ", defaulting to " + Locale.FALLBACK_LOCALE.getCode() + ".");
return Locale.FALLBACK_LOCALE;

View file

@ -5,7 +5,7 @@ import java.io.Serializable;
public enum Locale implements Serializable {
ENGLISH("en", "English"),
FRENCH("fr", "Français"),
CUSTOM("custom", "Server Custom");
CUSTOM("cm", "Server Custom");
public static final Locale FALLBACK_LOCALE = ENGLISH;
@ -17,6 +17,13 @@ public enum Locale implements Serializable {
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() {
return code;
}

View file

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

View file

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

View file

@ -1,7 +1,9 @@
package net.artelnatif.nicko.impl;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import org.bukkit.Bukkit;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
public class InternalsProvider {
@ -12,7 +14,15 @@ public class InternalsProvider {
final String packageName = Internals.class.getPackage().getName();
final String bukkitVersion = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
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 |
ClassCastException exception) {
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;
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.mojang.MojangUtils;
import net.artelnatif.nicko.storage.json.JSONStorage;
@ -76,12 +78,13 @@ public class PlayerDataStore {
}
}
public void saveData(Player player) {
if (storage.isError()) { return; }
if (!profiles.containsKey(player.getUniqueId())) { return; }
public ActionResult<Void> saveData(Player player) {
if (storage.isError()) { return new ActionResult<>(I18NDict.Error.UNEXPECTED_ERROR); }
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());
return store;
}
public Storage getStorage() {

View file

@ -1,18 +1,17 @@
package net.artelnatif.nicko.storage.sql;
import com.google.common.io.ByteStreams;
import net.artelnatif.nicko.Nicko;
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.NickoProfile;
import net.artelnatif.nicko.storage.Storage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;
import java.util.UUID;
@ -40,13 +39,14 @@ public class SQLStorage extends Storage {
if (connection == null) return new ActionResult<>(I18NDict.Error.SQL_ERROR);
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);
statement.setBinaryStream(1, uuidToBin(uuid));
statement.setString(2, profile.getName());
statement.setString(3, profile.getSkin());
statement.setBoolean(4, profile.isBungeecordTransfer());
statement.setString(4, profile.getLocale().getCode());
statement.setBoolean(5, profile.isBungeecordTransfer());
statement.executeUpdate();
return new ActionResult<>();
} catch (SQLException e) {
@ -57,13 +57,53 @@ public class SQLStorage extends Storage {
@Override
public boolean isStored(UUID uuid) {
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
public Optional<NickoProfile> retrieve(UUID uuid) {
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) {
byte[] bytes = new byte[16];
@ -72,13 +112,4 @@ public class SQLStorage extends Storage {
.putLong(uuid.getLeastSignificantBits());
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 {
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);
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.disguise.ActionResult;
import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.bukkit.i18n.Locale;
import org.junit.jupiter.api.*;
import java.util.Optional;
public class BrokenSQLTest {
private static ServerMock server;
private static NickoBukkit plugin;
private static PlayerMock player;
@BeforeAll
public static void setup() {
@ -26,6 +28,7 @@ public class BrokenSQLTest {
false);
server = MockBukkit.mock();
plugin = MockBukkit.load(NickoBukkit.class, config);
player = server.addPlayer();
}
@Test
@ -37,10 +40,17 @@ public class BrokenSQLTest {
@Test
@DisplayName("Fail to Store Player Via SQL")
public void storePlayer() {
final PlayerMock playerMock = server.addPlayer();
final NickoProfile profile = new NickoProfile("Notch", "Notch", Locale.ENGLISH, true);
final ActionResult<Void> storeAction = plugin.getNicko().getDataStore().getStorage().store(playerMock.getUniqueId(), profile);
Assertions.assertTrue(storeAction.isError());
final Optional<NickoProfile> optionalProfile = plugin.getNicko().getDataStore().getData(player.getUniqueId());
Assertions.assertFalse(optionalProfile.isPresent());
ActionResult<Void> result = plugin.getNicko().getDataStore().saveData(player);
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

View file

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

View file

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