mirror of
https://github.com/xHyroM/lighteco.git
synced 2024-11-21 22:41:06 +01:00
feat: sql storage provider
This commit is contained in:
parent
0578cdf3f4
commit
39b2211131
16 changed files with 232 additions and 34 deletions
|
@ -6,7 +6,9 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
import java.util.UUID;
|
||||
|
||||
public interface StorageProvider {
|
||||
void init() throws Exception;
|
||||
|
||||
@NonNull User loadUser(@NonNull UUID uniqueId) throws Exception;
|
||||
// todo: look at this
|
||||
void saveUser(@NonNull User user);
|
||||
void saveUser(@NonNull User user) throws Exception;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.bukkit.entity.Entity;
|
|||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
@ -31,12 +32,12 @@ public class BukkitLightEcoBootstrap implements LightEcoBootstrap, LoaderBootstr
|
|||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
plugin.load();
|
||||
this.plugin.load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
plugin.enable();
|
||||
this.plugin.enable();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -45,12 +46,12 @@ public class BukkitLightEcoBootstrap implements LightEcoBootstrap, LoaderBootstr
|
|||
}
|
||||
|
||||
public Server getServer() {
|
||||
return loader.getServer();
|
||||
return this.loader.getServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDataFolder() {
|
||||
return loader.getDataFolder();
|
||||
return this.loader.getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -59,4 +60,9 @@ public class BukkitLightEcoBootstrap implements LightEcoBootstrap, LoaderBootstr
|
|||
.map(Entity::getUniqueId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getResourceStream(String filename) {
|
||||
return this.loader.getResource(filename);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ dependencies {
|
|||
}
|
||||
api("net.kyori:adventure-text-minimessage:4.14.0")
|
||||
|
||||
api("org.xerial:sqlite-jdbc:3.40.0.0")
|
||||
|
||||
implementation("eu.okaeri:okaeri-configs-yaml-snakeyaml:5.0.0-beta.5")
|
||||
implementation("eu.okaeri:okaeri-configs-validator-okaeri:5.0.0-beta.5")
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package dev.xhyrom.lighteco.common.plugin.bootstrap;
|
|||
import dev.xhyrom.lighteco.common.plugin.logger.PluginLogger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
@ -12,4 +13,5 @@ public interface LightEcoBootstrap {
|
|||
PluginLogger getLogger();
|
||||
File getDataFolder();
|
||||
List<UUID> getOnlinePlayers();
|
||||
InputStream getResourceStream(String filename);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package dev.xhyrom.lighteco.common.storage;
|
|||
import dev.xhyrom.lighteco.common.model.user.User;
|
||||
import dev.xhyrom.lighteco.api.storage.StorageProvider;
|
||||
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
|
||||
import dev.xhyrom.lighteco.common.util.ThrowableRunnable;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
|
@ -32,8 +33,26 @@ public class Storage {
|
|||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> future(Runnable runnable) {
|
||||
return CompletableFuture.runAsync(runnable);
|
||||
private CompletableFuture<Void> future(ThrowableRunnable runnable) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
}
|
||||
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void init() {
|
||||
try {
|
||||
this.provider.init();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to initialize storage provider", e);
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<User> loadUser(UUID uniqueId) {
|
||||
|
|
|
@ -3,6 +3,8 @@ package dev.xhyrom.lighteco.common.storage;
|
|||
import dev.xhyrom.lighteco.api.storage.StorageProvider;
|
||||
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
|
||||
import dev.xhyrom.lighteco.common.storage.provider.memory.MemoryStorageProvider;
|
||||
import dev.xhyrom.lighteco.common.storage.provider.sql.SqlStorageProvider;
|
||||
import dev.xhyrom.lighteco.common.storage.provider.sql.connection.file.SqliteConnectionFactory;
|
||||
|
||||
public class StorageFactory {
|
||||
private final LightEcoPlugin plugin;
|
||||
|
@ -14,14 +16,22 @@ public class StorageFactory {
|
|||
public Storage get() {
|
||||
// todo: use config
|
||||
String provider = this.plugin.getConfig().storage.provider;
|
||||
Storage storage = new Storage(this.plugin, createProvider(provider));
|
||||
|
||||
return new Storage(this.plugin, createProvider(provider));
|
||||
storage.init();
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
private StorageProvider createProvider(String provider) {
|
||||
switch (provider.toLowerCase()) {
|
||||
case "memory":
|
||||
return new MemoryStorageProvider(this.plugin);
|
||||
case "sqlite":
|
||||
return new SqlStorageProvider(
|
||||
this.plugin,
|
||||
new SqliteConnectionFactory(this.plugin.getBootstrap().getDataFolder().toPath().resolve("data.db"))
|
||||
);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown storage provider: " + provider);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@ public class MemoryStorageProvider implements StorageProvider {
|
|||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {}
|
||||
|
||||
@Override
|
||||
public @NonNull User loadUser(@NonNull UUID uniqueId) {
|
||||
this.simulateSlowDatabaseQuery();
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// Implementation from LuckPerms
|
||||
// https://github.com/LuckPerms/LuckPerms/blob/master/common/src/main/java/me/lucko/luckperms/common/storage/implementation/sql/SchemaReader.java
|
||||
// Copyright (c) lucko (Luck) <lucko@lucko.me>
|
||||
// Copyright (c) contributors
|
||||
// Under MIT License
|
||||
|
||||
package dev.xhyrom.lighteco.common.storage.provider.sql;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@UtilityClass
|
||||
public final class SchemaReader {
|
||||
public static List<String> getStatements(InputStream is) throws IOException {
|
||||
List<String> queries = new LinkedList<>();
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("--") || line.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.append(line);
|
||||
|
||||
// check for end of declaration
|
||||
if (line.endsWith(";")) {
|
||||
sb.deleteCharAt(sb.length() - 1);
|
||||
|
||||
String result = sb.toString().trim();
|
||||
if (!result.isEmpty()) {
|
||||
queries.add(result);
|
||||
}
|
||||
|
||||
// reset
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return queries;
|
||||
}
|
||||
}
|
|
@ -7,16 +7,20 @@ import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
|
|||
import dev.xhyrom.lighteco.common.storage.provider.sql.connection.ConnectionFactory;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SqlStorageProvider implements StorageProvider {
|
||||
private static final String SAVE_USER_LOCAL_CURRENCY = "";
|
||||
private static final String SAVE_USER_GLOBAL_CURRENCY = "";
|
||||
private static final String SAVE_USER_LOCAL_CURRENCY_MYSQL = "INSERT INTO {prefix}_{context}_users (uuid, currency_identifier, balance) VALUES (?1, ?2, ?3) ON DUPLICATE KEY UPDATE balance=?3;";
|
||||
private static final String SAVE_USER_GLOBAL_CURRENCY_MYSQL = "INSERT INTO {prefix}_users (uuid, currency_identifier, balance) VALUES (?1, ?2, ?3) ON DUPLICATE KEY UPDATE balance=?3;";
|
||||
private static final String SAVE_USER_LOCAL_CURRENCY = "INSERT INTO {prefix}_users (uuid, currency_identifier, balance) VALUES (?1, ?2, ?3) ON CONFLICT (uuid, currency_identifier) DO UPDATE SET balance=?3;";
|
||||
private static final String SAVE_USER_GLOBAL_CURRENCY = "INSERT INTO {prefix}_users (uuid, currency_identifier, balance) VALUES (?1, ?2, ?3) ON CONFLICT (uuid, currency_identifier) DO UPDATE SET balance=?3;";
|
||||
|
||||
private static final String LOAD_WHOLE_USER = """
|
||||
SELECT currency_identifier, balance
|
||||
|
@ -46,15 +50,40 @@ public class SqlStorageProvider implements StorageProvider {
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
this.connectionFactory.init(this.plugin);
|
||||
|
||||
List<String> statements;
|
||||
String schemaFileName = "schema/" + this.connectionFactory.getImplementationName().toLowerCase() + ".sql";
|
||||
try (InputStream is = this.plugin.getBootstrap().getResourceStream(schemaFileName)) {
|
||||
if (is == null)
|
||||
throw new IOException("Failed to load schema file: " + schemaFileName);
|
||||
|
||||
statements = SchemaReader.getStatements(is).stream()
|
||||
.map(this.statementProcessor)
|
||||
.toList();
|
||||
}
|
||||
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
try (Statement s = c.createStatement()) {
|
||||
for (String statement : statements) {
|
||||
s.addBatch(statement);
|
||||
}
|
||||
|
||||
s.executeBatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull User loadUser(@NonNull UUID uniqueId) throws Exception {
|
||||
String uniqueIdString = uniqueId.toString();
|
||||
dev.xhyrom.lighteco.common.model.user.User user = this.plugin.getUserManager().getOrMake(uniqueId);
|
||||
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
try (PreparedStatement ps = c.prepareStatement(LOAD_WHOLE_USER)) {
|
||||
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(LOAD_WHOLE_USER))) {
|
||||
ps.setString(1, uniqueIdString);
|
||||
ps.setString(2, uniqueIdString);
|
||||
|
||||
ResultSet rs = ps.executeQuery();
|
||||
|
||||
|
@ -73,7 +102,53 @@ public class SqlStorageProvider implements StorageProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void saveUser(@NonNull User user) {
|
||||
public void saveUser(@NonNull User user) throws Exception {
|
||||
String uniqueIdString = user.getUniqueId().toString();
|
||||
|
||||
try (Connection c = this.connectionFactory.getConnection()) {
|
||||
try (PreparedStatement psGlobal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_GLOBAL_CURRENCY));
|
||||
PreparedStatement psLocal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_LOCAL_CURRENCY))) {
|
||||
|
||||
for (Currency currency : this.plugin.getCurrencyManager().getRegisteredCurrencies()) {
|
||||
BigDecimal balance = user.getBalance(currency.getProxy());
|
||||
|
||||
if (balance.compareTo(BigDecimal.ZERO) == 0) continue;
|
||||
|
||||
switch (currency.getType()) {
|
||||
case GLOBAL -> {
|
||||
psGlobal.setString(1, uniqueIdString);
|
||||
psGlobal.setString(2, currency.getIdentifier());
|
||||
psGlobal.setBigDecimal(3, balance);
|
||||
|
||||
psGlobal.addBatch();
|
||||
}
|
||||
case LOCAL -> {
|
||||
psLocal.setString(1, uniqueIdString);
|
||||
psLocal.setString(2, currency.getIdentifier());
|
||||
psLocal.setBigDecimal(3, balance);
|
||||
|
||||
psLocal.addBatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println(psGlobal.toString());
|
||||
System.out.println(psLocal.toString());
|
||||
psGlobal.executeBatch();
|
||||
psLocal.executeBatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean doesTableExists(Connection c, String table) throws SQLException {
|
||||
try (ResultSet rs = c.getMetaData().getTables(c.getCatalog(), null, "%s", null)) {
|
||||
while (rs.next()) {
|
||||
if (rs.getString(3).equalsIgnoreCase(table)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import java.sql.Connection;
|
|||
import java.util.function.Function;
|
||||
|
||||
public interface ConnectionFactory {
|
||||
String getImplementationName();
|
||||
|
||||
void init(LightEcoPlugin plugin);
|
||||
void shutdown() throws Exception;
|
||||
|
||||
|
|
|
@ -3,19 +3,20 @@ package dev.xhyrom.lighteco.common.storage.provider.sql.connection.file;
|
|||
import dev.xhyrom.lighteco.common.storage.provider.sql.connection.ConnectionFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.function.Function;
|
||||
|
||||
abstract class FileConnectionFactory implements ConnectionFactory {
|
||||
private Connection connection;
|
||||
private final File file;
|
||||
private final Path file;
|
||||
|
||||
public FileConnectionFactory(File file) {
|
||||
public FileConnectionFactory(Path file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
protected abstract Connection createConnection(File file) throws SQLException;
|
||||
protected abstract Connection createConnection(Path file) throws SQLException;
|
||||
|
||||
@Override
|
||||
public void shutdown() throws Exception {
|
||||
|
|
|
@ -4,6 +4,7 @@ import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
|
|||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
|
@ -12,10 +13,15 @@ import java.util.function.Function;
|
|||
public class H2ConnectionFactory extends FileConnectionFactory {
|
||||
private Constructor<?> connectionConstructor;
|
||||
|
||||
public H2ConnectionFactory(File file) {
|
||||
public H2ConnectionFactory(Path file) {
|
||||
super(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImplementationName() {
|
||||
return "h2";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(LightEcoPlugin plugin) {
|
||||
// TODO: implement
|
||||
|
@ -23,10 +29,10 @@ public class H2ConnectionFactory extends FileConnectionFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Connection createConnection(File file) throws SQLException {
|
||||
protected Connection createConnection(Path file) throws SQLException {
|
||||
try {
|
||||
return (Connection) this.connectionConstructor.newInstance(
|
||||
"jdbc:h2:" + file.getAbsolutePath(),
|
||||
"jdbc:h2:" + file,
|
||||
new Properties(),
|
||||
null, null, false
|
||||
);
|
||||
|
|
|
@ -2,33 +2,45 @@ package dev.xhyrom.lighteco.common.storage.provider.sql.connection.file;
|
|||
|
||||
import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SqliteConnectionFactory extends FileConnectionFactory {
|
||||
private Constructor<?> connectionConstructor;
|
||||
|
||||
public SqliteConnectionFactory(File file) {
|
||||
public SqliteConnectionFactory(Path file) {
|
||||
super(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(LightEcoPlugin plugin) {
|
||||
// TODO: implement
|
||||
public String getImplementationName() {
|
||||
return "sqlite";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Connection createConnection(File file) throws SQLException {
|
||||
public void init(LightEcoPlugin plugin) {
|
||||
/*ClassLoader classLoader = ClassLoader.getSystemClassLoader();
|
||||
|
||||
try {
|
||||
return (Connection) this.connectionConstructor.newInstance(
|
||||
"jdbc:sqlite:" + file.getAbsolutePath(),
|
||||
Class<?> connectionClass = classLoader.loadClass("org.sqlite.jdbc4.JDBC4Connection");
|
||||
this.connectionConstructor = connectionClass.getConstructor(String.class, String.class, Properties.class);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}*/
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Connection createConnection(Path file) throws SQLException {
|
||||
try {
|
||||
/*return (Connection) this.connectionConstructor.newInstance(
|
||||
"jdbc:sqlite:" + file,
|
||||
new Properties(),
|
||||
null, null, false
|
||||
);
|
||||
);*/
|
||||
return DriverManager.getConnection("jdbc:sqlite:" + file);
|
||||
} catch (Exception e) {
|
||||
if (e.getCause() instanceof SQLException) {
|
||||
throw (SQLException) e.getCause();
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package dev.xhyrom.lighteco.common.util;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ThrowableRunnable {
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
|
@ -3,11 +3,11 @@ CREATE TABLE IF NOT EXISTS `{prefix}_users` (
|
|||
`currency_identifier` VARCHAR(255) NOT NULL,
|
||||
`balance` DECIMAL(10, 2) NOT NULL,
|
||||
PRIMARY KEY (`uuid`, `currency_identifier`)
|
||||
) DEFAULT CHARSET = utf8mb4;
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `{prefix}_{context}_users` (
|
||||
`uuid` VARCHAR(36) NOT NULL,
|
||||
`currency_identifier` VARCHAR(255) NOT NULL,
|
||||
`balance` DECIMAL(10, 2) NOT NULL,
|
||||
PRIMARY KEY (`uuid`, `currency_identifier`)
|
||||
) DEFAULT CHARSET = utf8mb4;
|
||||
);
|
Loading…
Reference in a new issue