From 8112b41d12adb9886c62222ef40627d029faa978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jozef=20Steinh=C3=BCbl?= Date: Thu, 31 Aug 2023 18:14:13 +0200 Subject: [PATCH] refactor: one currency, one table (#2) * refactor: rewrite how users are loaded * finish * feat: use \' instead ` in CREATE_TABLE * fix: remove record if user has zero balance * add housekeeper * feat: add housekeeper config --- .../lighteco/api/manager/CurrencyManager.java | 13 - .../lighteco/api/storage/StorageProvider.java | 4 +- .../bukkit/BukkitLightEcoBootstrap.java | 7 + .../lighteco/bukkittest/TestPlugin.java | 18 -- .../common/api/impl/ApiCurrencyManager.java | 9 - .../common/api/impl/ApiUserManager.java | 5 +- .../lighteco/common/cache/ExpiringSet.java | 33 +++ .../lighteco/common/cache/RedisBackedMap.java | 7 + .../xhyrom/lighteco/common/config/Config.java | 5 + .../config/housekeeper/HousekeeperConfig.java | 16 ++ .../manager/currency/CurrencyManager.java | 5 - .../currency/StandardCurrencyManager.java | 12 +- .../manager/user/StandardUserManager.java | 18 +- .../common/manager/user/UserHousekeeper.java | 67 +++++ .../common/manager/user/UserManager.java | 3 +- .../lighteco/common/model/user/User.java | 11 +- .../common/plugin/AbstractLightEcoPlugin.java | 4 +- .../plugin/bootstrap/LightEcoBootstrap.java | 1 + .../lighteco/common/storage/Storage.java | 18 +- .../memory/MemoryStorageProvider.java | 14 - .../storage/provider/sql/SchemaReader.java | 51 ---- .../storage/provider/sql/SqlStatements.java | 32 ++- .../provider/sql/SqlStorageProvider.java | 251 ++++++++++-------- .../file/FileConnectionFactory.java | 6 - .../file/SqliteConnectionFactory.java | 7 +- .../hikari/MariaDBConnectionFactory.java | 7 + .../hikari/MySQLConnectionFactory.java | 6 + .../hikari/PostgreSQLConnectionFactory.java | 2 +- .../lighteco/common/task/UserSaveTask.java | 8 +- common/src/main/resources/schema/h2.sql | 13 - common/src/main/resources/schema/mariadb.sql | 13 - common/src/main/resources/schema/mysql.sql | 13 - .../src/main/resources/schema/postgresql.sql | 13 - common/src/main/resources/schema/sqlite.sql | 13 - .../money/bukkit/hooks/vault/Vault.java | 3 - .../sponge/SpongeLightEcoBootstrap.java | 5 + 36 files changed, 367 insertions(+), 346 deletions(-) create mode 100644 common/src/main/java/dev/xhyrom/lighteco/common/cache/ExpiringSet.java create mode 100644 common/src/main/java/dev/xhyrom/lighteco/common/cache/RedisBackedMap.java create mode 100644 common/src/main/java/dev/xhyrom/lighteco/common/config/housekeeper/HousekeeperConfig.java create mode 100644 common/src/main/java/dev/xhyrom/lighteco/common/manager/user/UserHousekeeper.java delete mode 100644 common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SchemaReader.java delete mode 100644 common/src/main/resources/schema/h2.sql delete mode 100644 common/src/main/resources/schema/mariadb.sql delete mode 100644 common/src/main/resources/schema/mysql.sql delete mode 100644 common/src/main/resources/schema/postgresql.sql delete mode 100644 common/src/main/resources/schema/sqlite.sql diff --git a/api/src/main/java/dev/xhyrom/lighteco/api/manager/CurrencyManager.java b/api/src/main/java/dev/xhyrom/lighteco/api/manager/CurrencyManager.java index db35d2f..b9113e9 100644 --- a/api/src/main/java/dev/xhyrom/lighteco/api/manager/CurrencyManager.java +++ b/api/src/main/java/dev/xhyrom/lighteco/api/manager/CurrencyManager.java @@ -1,13 +1,10 @@ package dev.xhyrom.lighteco.api.manager; import dev.xhyrom.lighteco.api.model.currency.Currency; -import dev.xhyrom.lighteco.api.model.user.User; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Collection; -import java.util.List; -import java.util.concurrent.CompletableFuture; public interface CurrencyManager { /** @@ -31,14 +28,4 @@ public interface CurrencyManager { * @param currency the currency to register */ void registerCurrency(@NonNull Currency currency); - - /** - * Gets the top users for a currency. - * - * @implNote This method is not cached. It fetches the data from the database every time it is called. - * @param currency the currency to get the top users for - * @param length the length of the list - * @return a future that completes with the top users (sorted from highest to lowest balance) - */ - CompletableFuture> getTopUsers(@NonNull Currency currency, int length); } diff --git a/api/src/main/java/dev/xhyrom/lighteco/api/storage/StorageProvider.java b/api/src/main/java/dev/xhyrom/lighteco/api/storage/StorageProvider.java index fbc7732..e4c199b 100644 --- a/api/src/main/java/dev/xhyrom/lighteco/api/storage/StorageProvider.java +++ b/api/src/main/java/dev/xhyrom/lighteco/api/storage/StorageProvider.java @@ -5,7 +5,6 @@ import dev.xhyrom.lighteco.api.model.user.User; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.List; import java.util.UUID; public interface StorageProvider { @@ -23,9 +22,8 @@ public interface StorageProvider { */ void shutdown() throws Exception; + default void registerCurrency(@NonNull Currency currency) throws Exception {} @NonNull User loadUser(@NonNull UUID uniqueId, @Nullable String username) throws Exception; void saveUser(@NonNull User user) throws Exception; void saveUsers(@NonNull User... users) throws Exception; - - @NonNull List getTopUsers(Currency currency, int length) throws Exception; } diff --git a/bukkit/src/main/java/dev/xhyrom/lighteco/bukkit/BukkitLightEcoBootstrap.java b/bukkit/src/main/java/dev/xhyrom/lighteco/bukkit/BukkitLightEcoBootstrap.java index 743379c..eafc476 100644 --- a/bukkit/src/main/java/dev/xhyrom/lighteco/bukkit/BukkitLightEcoBootstrap.java +++ b/bukkit/src/main/java/dev/xhyrom/lighteco/bukkit/BukkitLightEcoBootstrap.java @@ -9,6 +9,7 @@ import dev.xhyrom.lighteco.common.plugin.logger.PluginLogger; import dev.xhyrom.lighteco.common.plugin.scheduler.SchedulerAdapter; import lombok.Getter; import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import java.io.InputStream; @@ -58,6 +59,12 @@ public class BukkitLightEcoBootstrap implements LightEcoBootstrap, LoaderBootstr return this.loader.getDataFolder().toPath(); } + @Override + public boolean isPlayerOnline(UUID uniqueId) { + Player player = this.loader.getServer().getPlayer(uniqueId); + return player != null && player.isOnline(); + } + @Override public List getOnlinePlayers() { return this.loader.getServer().getOnlinePlayers().stream() diff --git a/bukkittest/src/main/java/dev/xhyrom/lighteco/bukkittest/TestPlugin.java b/bukkittest/src/main/java/dev/xhyrom/lighteco/bukkittest/TestPlugin.java index a012e8e..26170e1 100644 --- a/bukkittest/src/main/java/dev/xhyrom/lighteco/bukkittest/TestPlugin.java +++ b/bukkittest/src/main/java/dev/xhyrom/lighteco/bukkittest/TestPlugin.java @@ -3,15 +3,9 @@ package dev.xhyrom.lighteco.bukkittest; import dev.xhyrom.lighteco.api.LightEco; import dev.xhyrom.lighteco.api.LightEcoProvider; import dev.xhyrom.lighteco.api.manager.CurrencyManager; -import dev.xhyrom.lighteco.api.model.user.User; -import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.plugin.java.JavaPlugin; -import java.util.List; -import java.util.concurrent.CompletableFuture; - public class TestPlugin extends JavaPlugin implements Listener { @Override public void onEnable() { @@ -22,7 +16,6 @@ public class TestPlugin extends JavaPlugin implements Listener { currencyManager.registerCurrency(new TestCurrency()); currencyManager.registerCurrency(new TestCurrency2()); - getServer().getPluginManager().registerEvents(this, this); getLogger().info("TestCurrency registered!"); @@ -31,15 +24,4 @@ public class TestPlugin extends JavaPlugin implements Listener { provider.getCommandManager().registerCurrencyCommand(currencyManager.getCurrency("test")); provider.getCommandManager().registerCurrencyCommand(currencyManager.getCurrency("test2")); } - - @EventHandler - public void onChat(AsyncPlayerChatEvent event) { - LightEco provider = LightEcoProvider.get(); - CurrencyManager currencyManager = provider.getCurrencyManager(); - CompletableFuture> topusers = currencyManager.getTopUsers(currencyManager.getCurrency("money"), 5); - - for (User user : topusers.join()) { - event.getPlayer().sendMessage(user.getUniqueId() + " ("+ user.getUsername() +") " + ": " + user.getBalance(currencyManager.getCurrency("money"))); - } - } } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/api/impl/ApiCurrencyManager.java b/common/src/main/java/dev/xhyrom/lighteco/common/api/impl/ApiCurrencyManager.java index 860c35b..00cedf7 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/api/impl/ApiCurrencyManager.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/api/impl/ApiCurrencyManager.java @@ -2,13 +2,10 @@ package dev.xhyrom.lighteco.common.api.impl; import dev.xhyrom.lighteco.api.manager.CurrencyManager; import dev.xhyrom.lighteco.api.model.currency.Currency; -import dev.xhyrom.lighteco.api.model.user.User; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import org.checkerframework.checker.nullness.qual.NonNull; import java.util.Collection; -import java.util.List; -import java.util.concurrent.CompletableFuture; public class ApiCurrencyManager extends ApiAbstractManager implements CurrencyManager { public ApiCurrencyManager(LightEcoPlugin plugin, dev.xhyrom.lighteco.common.manager.currency.CurrencyManager handler) { @@ -36,10 +33,4 @@ public class ApiCurrencyManager extends ApiAbstractManager> getTopUsers(@NonNull Currency currency, int length) { - dev.xhyrom.lighteco.common.model.currency.Currency internal = this.handler.getIfLoaded(currency.getIdentifier()); - return this.handler.getTopUsers(internal, length); - } } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/api/impl/ApiUserManager.java b/common/src/main/java/dev/xhyrom/lighteco/common/api/impl/ApiUserManager.java index 91915d2..89098d2 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/api/impl/ApiUserManager.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/api/impl/ApiUserManager.java @@ -15,7 +15,8 @@ public class ApiUserManager extends ApiAbstractManager loadUser(@NonNull UUID uniqueId, String username) { return this.plugin.getStorage().loadUser(uniqueId, username) - .thenApply(ApiUserManager::wrap); + .thenApply(this::wrap); } @Override diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/cache/ExpiringSet.java b/common/src/main/java/dev/xhyrom/lighteco/common/cache/ExpiringSet.java new file mode 100644 index 0000000..45b40b3 --- /dev/null +++ b/common/src/main/java/dev/xhyrom/lighteco/common/cache/ExpiringSet.java @@ -0,0 +1,33 @@ +package dev.xhyrom.lighteco.common.cache; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import java.util.concurrent.TimeUnit; + +public class ExpiringSet { + private final Cache cache; + private final long lifetime; + + public ExpiringSet(long duration, TimeUnit unit) { + this.cache = CacheBuilder.newBuilder() + .expireAfterWrite(duration, unit) + .build(); + this.lifetime = duration; + } + + public boolean add(T item) { + boolean present = contains(item); + this.cache.put(item, System.currentTimeMillis() + this.lifetime); + return !present; + } + + public boolean contains(T item) { + Long timeout = this.cache.getIfPresent(item); + return timeout != null && timeout > System.currentTimeMillis(); + } + + public void remove(T item) { + this.cache.invalidate(item); + } +} diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/cache/RedisBackedMap.java b/common/src/main/java/dev/xhyrom/lighteco/common/cache/RedisBackedMap.java new file mode 100644 index 0000000..7fd2df8 --- /dev/null +++ b/common/src/main/java/dev/xhyrom/lighteco/common/cache/RedisBackedMap.java @@ -0,0 +1,7 @@ +package dev.xhyrom.lighteco.common.cache; + +import java.util.HashMap; + +public class RedisBackedMap extends HashMap { + +} diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/config/Config.java b/common/src/main/java/dev/xhyrom/lighteco/common/config/Config.java index d30c733..7f1e85a 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/config/Config.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/config/Config.java @@ -1,5 +1,6 @@ package dev.xhyrom.lighteco.common.config; +import dev.xhyrom.lighteco.common.config.housekeeper.HousekeeperConfig; import dev.xhyrom.lighteco.common.config.message.MessageConfig; import dev.xhyrom.lighteco.common.config.storage.StorageConfig; import eu.okaeri.configs.OkaeriConfig; @@ -23,6 +24,10 @@ public class Config extends OkaeriConfig { @Comment("Messages") public MessageConfig messages = new MessageConfig(); + @Comment("Housekeeper") + @Comment("Task that runs periodically to clean up the cache.") + public HousekeeperConfig housekeeper = new HousekeeperConfig(); + @Comment("Debug mode") @Comment("Prints additional information to the console.") public boolean debug = false; diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/config/housekeeper/HousekeeperConfig.java b/common/src/main/java/dev/xhyrom/lighteco/common/config/housekeeper/HousekeeperConfig.java new file mode 100644 index 0000000..f15e82f --- /dev/null +++ b/common/src/main/java/dev/xhyrom/lighteco/common/config/housekeeper/HousekeeperConfig.java @@ -0,0 +1,16 @@ +package dev.xhyrom.lighteco.common.config.housekeeper; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; + +import java.util.concurrent.TimeUnit; + +public class HousekeeperConfig extends OkaeriConfig { + @Comment("How long should the cache be kept after the last write") + public int expireAfterWrite = 300; + public TimeUnit expireAfterWriteUnit = TimeUnit.SECONDS; + + @Comment("How often should housekeeper run") + public int runInterval = 60; + public TimeUnit runIntervalUnit = TimeUnit.SECONDS; +} diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/manager/currency/CurrencyManager.java b/common/src/main/java/dev/xhyrom/lighteco/common/manager/currency/CurrencyManager.java index 350669f..c2675f9 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/manager/currency/CurrencyManager.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/manager/currency/CurrencyManager.java @@ -1,18 +1,13 @@ package dev.xhyrom.lighteco.common.manager.currency; -import dev.xhyrom.lighteco.api.model.user.User; import dev.xhyrom.lighteco.common.manager.Manager; import dev.xhyrom.lighteco.common.model.currency.Currency; import org.checkerframework.checker.nullness.qual.NonNull; import java.util.Collection; -import java.util.List; -import java.util.concurrent.CompletableFuture; public interface CurrencyManager extends Manager { @NonNull Collection getRegisteredCurrencies(); void registerCurrency(@NonNull Currency currency); - - CompletableFuture> getTopUsers(@NonNull Currency currency, int length); } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/manager/currency/StandardCurrencyManager.java b/common/src/main/java/dev/xhyrom/lighteco/common/manager/currency/StandardCurrencyManager.java index 1577d11..f15f5d8 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/manager/currency/StandardCurrencyManager.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/manager/currency/StandardCurrencyManager.java @@ -1,14 +1,11 @@ package dev.xhyrom.lighteco.common.manager.currency; -import dev.xhyrom.lighteco.api.model.user.User; import dev.xhyrom.lighteco.common.manager.SingleManager; import dev.xhyrom.lighteco.common.model.currency.Currency; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import org.checkerframework.checker.nullness.qual.NonNull; import java.util.Collection; -import java.util.List; -import java.util.concurrent.CompletableFuture; public class StandardCurrencyManager extends SingleManager implements CurrencyManager { private final LightEcoPlugin plugin; @@ -37,11 +34,10 @@ public class StandardCurrencyManager extends SingleManager imp if (this.isLoaded(currency.getIdentifier())) throw new IllegalArgumentException("Currency with identifier " + currency.getIdentifier() + " already registered"); + if (this.plugin.getConfig().debug) + this.plugin.getBootstrap().getLogger().info("Registering currency " + currency.getIdentifier()); + + this.plugin.getStorage().registerCurrencySync(currency.getProxy()); this.map.put(currency.getIdentifier(), currency); } - - @Override - public CompletableFuture> getTopUsers(@NonNull Currency currency, int length) { - return this.plugin.getStorage().getTopUsers(currency.getProxy(), length); - } } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/manager/user/StandardUserManager.java b/common/src/main/java/dev/xhyrom/lighteco/common/manager/user/StandardUserManager.java index cf8b43d..deff464 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/manager/user/StandardUserManager.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/manager/user/StandardUserManager.java @@ -3,17 +3,29 @@ package dev.xhyrom.lighteco.common.manager.user; import dev.xhyrom.lighteco.common.manager.ConcurrentManager; import dev.xhyrom.lighteco.common.model.user.User; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; +import lombok.Getter; import java.util.Arrays; -import java.util.Collection; import java.util.UUID; import java.util.concurrent.CompletableFuture; public class StandardUserManager extends ConcurrentManager implements UserManager { private final LightEcoPlugin plugin; + @Getter + private final UserHousekeeper housekeeper; public StandardUserManager(LightEcoPlugin plugin) { this.plugin = plugin; + this.housekeeper = new UserHousekeeper(plugin, this, UserHousekeeper.timeoutSettings( + this.plugin.getConfig().housekeeper.expireAfterWrite, + this.plugin.getConfig().housekeeper.expireAfterWriteUnit + )); + + this.plugin.getBootstrap().getScheduler().asyncRepeating( + this.housekeeper, + this.plugin.getConfig().housekeeper.runInterval, + this.plugin.getConfig().housekeeper.runIntervalUnit + ); } @Override @@ -23,11 +35,15 @@ public class StandardUserManager extends ConcurrentManager implement @Override public CompletableFuture loadUser(UUID uniqueId) { + this.plugin.getUserManager().getHousekeeper().registerUsage(uniqueId); + return loadUser(uniqueId, null); } @Override public CompletableFuture loadUser(UUID uniqueId, String username) { + this.plugin.getUserManager().getHousekeeper().registerUsage(uniqueId); + return this.plugin.getStorage().loadUser(uniqueId, username); } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/manager/user/UserHousekeeper.java b/common/src/main/java/dev/xhyrom/lighteco/common/manager/user/UserHousekeeper.java new file mode 100644 index 0000000..3b5c080 --- /dev/null +++ b/common/src/main/java/dev/xhyrom/lighteco/common/manager/user/UserHousekeeper.java @@ -0,0 +1,67 @@ +package dev.xhyrom.lighteco.common.manager.user; + +import dev.xhyrom.lighteco.common.cache.ExpiringSet; +import dev.xhyrom.lighteco.common.model.user.User; +import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public class UserHousekeeper implements Runnable { + private final LightEcoPlugin plugin; + private final UserManager userManager; + + private final ExpiringSet recentlyUsed; + + public UserHousekeeper(LightEcoPlugin plugin, UserManager userManager, TimeoutSettings timeoutSettings) { + this.plugin = plugin; + this.userManager = userManager; + this.recentlyUsed = new ExpiringSet<>(timeoutSettings.duration, timeoutSettings.unit); + } + + public void registerUsage(UUID uuid) { + this.recentlyUsed.add(uuid); + } + + @Override + public void run() { + for (UUID entry : this.userManager.keys()) { + cleanup(entry); + } + } + + public void cleanup(UUID uuid) { + if (this.recentlyUsed.contains(uuid) || this.plugin.getBootstrap().isPlayerOnline(uuid)) { + return; + } + + User user = this.userManager.getIfLoaded(uuid); + if (user == null) { + return; + } + + // If the user is dirty (has unsaved changes), don't unload + if (user.isDirty()) + return; + + if (this.plugin.getConfig().debug) { + this.plugin.getBootstrap().getLogger().info("Unloading data for " + uuid); + } + + this.userManager.unload(uuid); + } + + public static TimeoutSettings timeoutSettings(long duration, TimeUnit unit) { + return new TimeoutSettings(duration, unit); + } + + public static final class TimeoutSettings { + private final long duration; + private final TimeUnit unit; + + TimeoutSettings(long duration, TimeUnit unit) { + this.duration = duration; + this.unit = unit; + } + } +} \ No newline at end of file diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/manager/user/UserManager.java b/common/src/main/java/dev/xhyrom/lighteco/common/manager/user/UserManager.java index ec3ec86..3d50fcc 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/manager/user/UserManager.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/manager/user/UserManager.java @@ -3,11 +3,12 @@ package dev.xhyrom.lighteco.common.manager.user; import dev.xhyrom.lighteco.common.manager.Manager; import dev.xhyrom.lighteco.common.model.user.User; -import java.util.Collection; import java.util.UUID; import java.util.concurrent.CompletableFuture; public interface UserManager extends Manager { + UserHousekeeper getHousekeeper(); + CompletableFuture saveUser(User user); CompletableFuture saveUsers(User... users); diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/model/user/User.java b/common/src/main/java/dev/xhyrom/lighteco/common/model/user/User.java index 275c1f7..bf0744b 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/model/user/User.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/model/user/User.java @@ -1,6 +1,7 @@ package dev.xhyrom.lighteco.common.model.user; import dev.xhyrom.lighteco.common.api.impl.ApiUser; +import dev.xhyrom.lighteco.common.cache.RedisBackedMap; import dev.xhyrom.lighteco.common.model.currency.Currency; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import lombok.Getter; @@ -15,6 +16,7 @@ import java.util.UUID; @Getter public class User { private final LightEcoPlugin plugin; + @Getter private final ApiUser proxy = new ApiUser(this); @@ -28,7 +30,7 @@ public class User { @Setter private String username; - private final HashMap balances = new HashMap<>(); + private final HashMap balances = new RedisBackedMap<>(); public User(LightEcoPlugin plugin, UUID uniqueId) { this(plugin, uniqueId, null); @@ -45,6 +47,10 @@ public class User { } public void setBalance(@NonNull Currency currency, @NonNull BigDecimal balance) { + this.setBalance(currency, balance, false); + } + + public void setBalance(@NonNull Currency currency, @NonNull BigDecimal balance, boolean force) { if (balance.compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException("Balance cannot be negative"); } @@ -52,7 +58,8 @@ public class User { balance = balance.setScale(currency.fractionalDigits(), RoundingMode.DOWN); balances.put(currency, balance); - this.setDirty(true); + if (!force) + this.setDirty(true); } public void deposit(@NonNull Currency currency, @NonNull BigDecimal amount) throws IllegalArgumentException { diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/plugin/AbstractLightEcoPlugin.java b/common/src/main/java/dev/xhyrom/lighteco/common/plugin/AbstractLightEcoPlugin.java index 62530aa..4908b9c 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/plugin/AbstractLightEcoPlugin.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/plugin/AbstractLightEcoPlugin.java @@ -28,8 +28,6 @@ public abstract class AbstractLightEcoPlugin implements LightEcoPlugin { private UserSaveTask userSaveTask; public final void load() { - this.dependencyManager = new DependencyManagerImpl(this); - this.config = ConfigManager.create(Config.class, it -> { it.withConfigurer(new YamlSnakeYamlConfigurer()); it.withBindFile(this.getBootstrap().getDataDirectory().resolve("config.yml")); @@ -37,6 +35,8 @@ public abstract class AbstractLightEcoPlugin implements LightEcoPlugin { it.saveDefaults(); it.load(true); }); + + this.dependencyManager = new DependencyManagerImpl(this); } public final void enable() { diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/plugin/bootstrap/LightEcoBootstrap.java b/common/src/main/java/dev/xhyrom/lighteco/common/plugin/bootstrap/LightEcoBootstrap.java index f3069c3..ac16413 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/plugin/bootstrap/LightEcoBootstrap.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/plugin/bootstrap/LightEcoBootstrap.java @@ -13,6 +13,7 @@ public interface LightEcoBootstrap { PluginLogger getLogger(); SchedulerAdapter getScheduler(); Path getDataDirectory(); + boolean isPlayerOnline(UUID uniqueId); List getOnlinePlayers(); InputStream getResourceStream(String filename); } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/Storage.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/Storage.java index 661e7c3..8faae9a 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/Storage.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/Storage.java @@ -1,11 +1,11 @@ package dev.xhyrom.lighteco.common.storage; -import dev.xhyrom.lighteco.common.model.user.User; +import dev.xhyrom.lighteco.api.model.currency.Currency; import dev.xhyrom.lighteco.api.storage.StorageProvider; +import dev.xhyrom.lighteco.common.model.user.User; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; import dev.xhyrom.lighteco.common.util.ThrowableRunnable; -import java.util.List; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; @@ -86,6 +86,14 @@ public class Storage { return future(() -> this.provider.saveUsers(users)); } + public void registerCurrencySync(Currency currency) { + try { + this.provider.registerCurrency(currency); + } catch (Exception e) { + throw new RuntimeException("Failed to register currency", e); + } + } + public void saveUsersSync(dev.xhyrom.lighteco.api.model.user.User... users) { try { this.provider.saveUsers(users); @@ -93,10 +101,4 @@ public class Storage { throw new RuntimeException("Failed to save users", e); } } - - // Return ApiUser instead of User - // We don't do anything with this - public CompletableFuture> getTopUsers(dev.xhyrom.lighteco.api.model.currency.Currency currency, int length) { - return future(() -> this.provider.getTopUsers(currency, length)); - } } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/memory/MemoryStorageProvider.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/memory/MemoryStorageProvider.java index 607c99b..bdea3e5 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/memory/MemoryStorageProvider.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/memory/MemoryStorageProvider.java @@ -1,6 +1,5 @@ package dev.xhyrom.lighteco.common.storage.provider.memory; -import dev.xhyrom.lighteco.api.model.currency.Currency; import dev.xhyrom.lighteco.api.model.user.User; import dev.xhyrom.lighteco.api.storage.StorageProvider; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; @@ -8,10 +7,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.jetbrains.annotations.NotNull; -import java.math.BigDecimal; import java.util.HashMap; -import java.util.List; -import java.util.Set; import java.util.UUID; public class MemoryStorageProvider implements StorageProvider { @@ -53,16 +49,6 @@ public class MemoryStorageProvider implements StorageProvider { } } - @Override - public @NonNull List getTopUsers(Currency currency, int length) throws Exception { - return userDatabase.values().stream().sorted((user1, user2) -> { - BigDecimal balance1 = user1.getBalance(currency); - BigDecimal balance2 = user2.getBalance(currency); - - return balance1.compareTo(balance2); - }).limit(length).toList(); - } - private User createUser(UUID uniqueId, String username, User data) { dev.xhyrom.lighteco.common.model.user.User user = this.plugin.getUserManager().getOrMake(uniqueId); if (username != null) diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SchemaReader.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SchemaReader.java deleted file mode 100644 index f7d83f4..0000000 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SchemaReader.java +++ /dev/null @@ -1,51 +0,0 @@ -// 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) -// 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 getStatements(InputStream is) throws IOException { - List 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; - } -} \ No newline at end of file diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SqlStatements.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SqlStatements.java index 572221a..1577d8c 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SqlStatements.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SqlStatements.java @@ -4,22 +4,28 @@ import dev.xhyrom.lighteco.common.storage.StorageType; public enum SqlStatements { SAVE_USER_LOCAL_CURRENCY( - "INSERT INTO {prefix}_{context}_users (uuid, currency_identifier, balance) VALUES (?1, ?2, ?3) ON CONFLICT (uuid, currency_identifier) DO UPDATE SET balance=?3;", - "INSERT INTO {prefix}_{context}_users (uuid, currency_identifier, balance) VALUES (?1, ?2, ?3) ON DUPLICATE KEY UPDATE balance=?3;", - "INSERT INTO {prefix}_{context}_users (uuid, currency_identifier, balance) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE balance=?;", - "INSERT INTO {prefix}_{context}_users (uuid, currency_identifier, balance) VALUES (?, ?, ?) ON CONFLICT (uuid, currency_identifier) DO UPDATE SET balance=?;" + "INSERT INTO '{prefix}_local_{context}_{currency}_users' (uuid, balance) VALUES (?1, ?2) ON CONFLICT (uuid) DO UPDATE SET balance=?2;", + "INSERT INTO '{prefix}_local_{context}_{currency}_users' (uuid, balance) VALUES (?1, ?2) ON DUPLICATE KEY UPDATE balance=?2;", + "INSERT INTO '{prefix}_local_{context}_{currency}_users' (uuid, balance) VALUES (?, ?) ON DUPLICATE KEY UPDATE balance=?;", + "INSERT INTO '{prefix}_local_{context}_{currency}_users' (uuid, balance) VALUES (?1, ?2) ON CONFLICT (uuid) DO UPDATE SET balance=?2;" ), 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;", - "INSERT INTO {prefix}_users (uuid, currency_identifier, balance) VALUES (?1, ?2, ?3) ON DUPLICATE KEY UPDATE balance=?3;", - "INSERT INTO {prefix}_users (uuid, currency_identifier, balance) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE balance=?;", - "INSERT INTO {prefix}_users (uuid, currency_identifier, balance) VALUES (?, ?, ?) ON CONFLICT (uuid, currency_identifier) DO UPDATE SET balance=?;" + "INSERT INTO '{prefix}_global_{currency}_users' (uuid, balance) VALUES (?1, ?2) ON CONFLICT (uuid) DO UPDATE SET balance=?2;", + "INSERT INTO '{prefix}_global_{currency}_users' (uuid, balance) VALUES (?1, ?2) ON DUPLICATE KEY UPDATE balance=?2;", + "INSERT INTO '{prefix}_global_{currency}_users' (uuid, balance) VALUES (?, ?) ON DUPLICATE KEY UPDATE balance=?;", + "INSERT INTO '{prefix}_global_{currency}_users' (uuid, balance) VALUES (?1, ?2) ON CONFLICT (uuid) DO UPDATE SET balance=?2;" ), - LOAD_WHOLE_USER( - "SELECT currency_identifier, balance FROM ( SELECT currency_identifier, balance FROM '{prefix}_users' WHERE uuid = ?1 UNION ALL SELECT currency_identifier, balance FROM '{prefix}_{context}_users' WHERE uuid = ?1 ) AS combined_currencies;", - "SELECT currency_identifier, balance FROM ( SELECT currency_identifier, balance FROM '{prefix}_users' WHERE uuid = ?1 UNION ALL SELECT currency_identifier, balance FROM '{prefix}_{context}_users' WHERE uuid = ?1 ) AS combined_currencies;", - "SELECT currency_identifier, balance FROM ( SELECT currency_identifier, balance FROM '{prefix}_users' WHERE uuid = ? UNION ALL SELECT currency_identifier, balance FROM '{prefix}_{context}_users' WHERE uuid = ? ) AS combined_currencies;", - null // same as mariadb + LOAD_LOCAL_CURRENCY_USER( + "SELECT {identifier} AS name, balance FROM '{prefix}_local_{context}_{currency}_users' WHERE uuid = ?1", + "SELECT {identifier} AS name, balance FROM '{prefix}_local_{context}_{currency}_users' WHERE uuid = ?1", + "SELECT {identifier} AS name, balance FROM '{prefix}_local_{context}_{currency}_users' WHERE uuid = ?", + "SELECT {identifier} AS name, balance FROM '{prefix}_local_{context}_{currency}_users' WHERE uuid = ?1" + ), + LOAD_GLOBAL_CURRENCY_USER( + "SELECT {identifier} AS name, balance FROM '{prefix}_global_{currency}_users' WHERE uuid = ?1", + "SELECT {identifier} AS name, balance FROM '{prefix}_global_{currency}_users' WHERE uuid = ?1", + "SELECT {identifier} AS name, balance FROM '{prefix}_global_{currency}_users' WHERE uuid = ?", + "SELECT {identifier} AS name, balance FROM '{prefix}_global_{currency}_users' WHERE uuid = ?1" ); public final String sqlite; diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SqlStorageProvider.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SqlStorageProvider.java index aa73b80..94bbb89 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SqlStorageProvider.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SqlStorageProvider.java @@ -10,11 +10,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.jetbrains.annotations.NotNull; -import java.io.IOException; -import java.io.InputStream; import java.math.BigDecimal; -import java.sql.*; -import java.util.ArrayList; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.List; import java.util.UUID; import java.util.function.Function; @@ -22,11 +22,19 @@ import java.util.function.Function; public class SqlStorageProvider implements StorageProvider { private static String SAVE_USER_LOCAL_CURRENCY; private static String SAVE_USER_GLOBAL_CURRENCY; - private static String LOAD_WHOLE_USER; - private static final String GET_TOP_X_USERS_LOCAL = "SELECT uuid, balance FROM {prefix}_{context}_users WHERE currency_identifier = ? ORDER BY balance DESC LIMIT ?;"; - private static final String GET_TOP_X_USERS_GLOBAL = "SELECT uuid, balance FROM {prefix}_users WHERE currency_identifier = ? ORDER BY balance DESC LIMIT ?;"; - private static final String DELETE_GLOBAL_USER_IF_BALANCE = "DELETE FROM {prefix}_{context}_users WHERE uuid = ? AND currency_identifier = ? AND balance = ?;"; - private static final String DELETE_LOCAL_USER_IF_BALANCE = "DELETE FROM {prefix}_{context}_users WHERE uuid = ? AND currency_identifier = ? AND balance = ?;"; + + private static String LOAD_LOCAL_CURRENCY_USER; + private static String LOAD_GLOBAL_CRRENCY_USER; + + private static final String DELETE_LOCAL_USER = "DELETE FROM {prefix}_local_{context}_{currency}_users WHERE uuid = ?;"; + private static final String DELETE_GLOBAL_USER = "DELETE FROM {prefix}_global_{currency}_users WHERE uuid = ?;"; + private static final String CREATE_TABLE = """ + CREATE TABLE IF NOT EXISTS '{prefix}_{table}' ( + 'uuid' VARCHAR(36) NOT NULL, + 'balance' DECIMAL(10, 2) NOT NULL, + PRIMARY KEY (`uuid`) + ); + """.trim(); private final LightEcoPlugin plugin; private final ConnectionFactory connectionFactory; @@ -44,33 +52,13 @@ public class SqlStorageProvider implements StorageProvider { final StorageType implementationName = this.connectionFactory.getImplementationName(); SAVE_USER_LOCAL_CURRENCY = SqlStatements.SAVE_USER_LOCAL_CURRENCY.get(implementationName); SAVE_USER_GLOBAL_CURRENCY = SqlStatements.SAVE_USER_GLOBAL_CURRENCY.get(implementationName); - LOAD_WHOLE_USER = SqlStatements.LOAD_WHOLE_USER.get(implementationName); + LOAD_LOCAL_CURRENCY_USER = SqlStatements.LOAD_LOCAL_CURRENCY_USER.get(implementationName); + LOAD_GLOBAL_CRRENCY_USER = SqlStatements.LOAD_GLOBAL_CURRENCY_USER.get(implementationName); } @Override - public void init() throws Exception { + public void init() { this.connectionFactory.init(this.plugin); - - List statements; - String schemaFileName = "schema/" + this.connectionFactory.getImplementationName().name().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 @@ -78,6 +66,31 @@ public class SqlStorageProvider implements StorageProvider { this.connectionFactory.shutdown(); } + @Override + public void registerCurrency(dev.xhyrom.lighteco.api.model.currency.@NonNull Currency currency) throws Exception { + StringBuilder tableName = new StringBuilder(); + + if (currency.getType() == dev.xhyrom.lighteco.api.model.currency.Currency.Type.LOCAL) { + tableName.append("local_{context}_"); + tableName.append(currency.getIdentifier()); + } else { + tableName.append("global_"); + tableName.append(currency.getIdentifier()); + } + + tableName.append("_users"); + + try (Connection c = this.connectionFactory.getConnection()) { + try (PreparedStatement ps = c.prepareStatement( + this.statementProcessor.apply(CREATE_TABLE + .replace("{table}", tableName.toString()) + ) + )) { + ps.execute(); + } + } + } + @Override public @NonNull User loadUser(@NonNull UUID uniqueId, @Nullable String username) throws Exception { String uniqueIdString = uniqueId.toString(); @@ -85,21 +98,50 @@ public class SqlStorageProvider implements StorageProvider { if (username != null) user.setUsername(username); + StringBuilder query = new StringBuilder(); + List currencies = this.plugin.getCurrencyManager().getRegisteredCurrencies().stream().toList(); + int size = this.plugin.getCurrencyManager().getRegisteredCurrencies().size(); + + for (int i = 0; i < size; i++) { + Currency currency = currencies.get(i); + + switch (currency.getType()) { + case GLOBAL -> query.append( + this.statementProcessor.apply(LOAD_GLOBAL_CRRENCY_USER + .replace("{currency}", currency.getIdentifier()) + ).replace("{identifier}", "'"+currency.getIdentifier()+"'") + ); + case LOCAL -> query.append( + this.statementProcessor.apply(LOAD_LOCAL_CURRENCY_USER + .replace("{currency}", currency.getIdentifier()) + ).replace("{identifier}", "'"+currency.getIdentifier()+"'") + ); + } + + if (i != size - 1) { + query.append(" UNION ALL "); + } + } + try (Connection c = this.connectionFactory.getConnection()) { - try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(LOAD_WHOLE_USER))) { - ps.setString(1, uniqueIdString); - if (SqlStatements.mustDuplicateParameters(this.connectionFactory.getImplementationName())) - ps.setString(2, uniqueIdString); + try (PreparedStatement ps = c.prepareStatement(query.toString())) { + if (SqlStatements.mustDuplicateParameters(this.connectionFactory.getImplementationName())) { + for (int i = 0; i < size; i++) { + ps.setString(i + 1, uniqueIdString); + } + } else { + ps.setString(1, uniqueIdString); + } ResultSet rs = ps.executeQuery(); while (rs.next()) { - String currencyIdentifier = rs.getString("currency_identifier"); - Currency currency = this.plugin.getCurrencyManager().getIfLoaded(currencyIdentifier); + String identifier = rs.getString("name"); + Currency currency = this.plugin.getCurrencyManager().getIfLoaded(identifier); BigDecimal balance = rs.getBigDecimal("balance"); - user.setBalance(currency, balance); + user.setBalance(currency, balance, true); } } } @@ -130,7 +172,7 @@ public class SqlStorageProvider implements StorageProvider { for (User user : users) { String uniqueIdString = user.getUniqueId().toString(); - saveBalances(c, user, uniqueIdString); + saveBalances(c, user, uniqueIdString, false); } c.commit(); @@ -141,96 +183,73 @@ public class SqlStorageProvider implements StorageProvider { } } - @Override - public @NonNull List getTopUsers(dev.xhyrom.lighteco.api.model.currency.Currency apiCurrency, int length) throws Exception { - Currency currency = this.plugin.getCurrencyManager().getIfLoaded(apiCurrency.getIdentifier()); - String statement = currency.getType() == dev.xhyrom.lighteco.api.model.currency.Currency.Type.GLOBAL - ? GET_TOP_X_USERS_GLOBAL - : GET_TOP_X_USERS_LOCAL; - - try (Connection c = this.connectionFactory.getConnection()) { - try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(statement))) { - ps.setString(1, currency.getIdentifier()); - ps.setInt(2, length); - - ResultSet rs = ps.executeQuery(); - - List users = new ArrayList<>(); - while (rs.next()) { - String uniqueIdString = rs.getString("uuid"); - UUID uniqueId = UUID.fromString(uniqueIdString); - - BigDecimal balance = rs.getBigDecimal("balance"); - - dev.xhyrom.lighteco.common.model.user.User user = this.plugin.getUserManager().getOrMake(uniqueId); - user.setBalance(currency, balance); - - users.add(user.getProxy()); - } - - return users; - } - } + private void saveBalances(Connection c, User user, String uniqueIdString) throws SQLException { + saveBalances(c, user, uniqueIdString, true); } - private void saveBalances(Connection c, User user, String uniqueIdString) throws SQLException { - try (PreparedStatement psGlobal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_GLOBAL_CURRENCY)); - PreparedStatement psLocal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_LOCAL_CURRENCY)); - PreparedStatement psDeleteGlobal = c.prepareStatement(this.statementProcessor.apply(DELETE_GLOBAL_USER_IF_BALANCE)); - PreparedStatement psDeleteLocal = c.prepareStatement(this.statementProcessor.apply(DELETE_LOCAL_USER_IF_BALANCE))) { + private void saveBalances(Connection c, User user, String uniqueIdString, boolean transactions) throws SQLException { + if (transactions) + c.setAutoCommit(false); - for (Currency currency : this.plugin.getCurrencyManager().getRegisteredCurrencies()) { - BigDecimal balance = user.getBalance(currency.getProxy()); - - if (balance.compareTo(BigDecimal.ZERO) == 0) { - switch (currency.getType()) { - case GLOBAL -> { - psDeleteGlobal.setString(1, uniqueIdString); - psDeleteGlobal.setString(2, currency.getIdentifier()); - psDeleteGlobal.setBigDecimal(3, balance); - - psDeleteGlobal.addBatch(); - } - case LOCAL -> { - psDeleteLocal.setString(1, uniqueIdString); - psDeleteLocal.setString(2, currency.getIdentifier()); - psDeleteLocal.setBigDecimal(3, balance); - - psDeleteLocal.addBatch(); - } - } - - continue; - } + for (Currency currency : this.plugin.getCurrencyManager().getRegisteredCurrencies()) { + BigDecimal balance = user.getBalance(currency.getProxy()); + if (balance.compareTo(BigDecimal.ZERO) == 0) { switch (currency.getType()) { case GLOBAL -> { - psGlobal.setString(1, uniqueIdString); - psGlobal.setString(2, currency.getIdentifier()); - psGlobal.setBigDecimal(3, balance); - if (SqlStatements.mustDuplicateParameters(this.connectionFactory.getImplementationName())) - psGlobal.setBigDecimal(4, balance); + try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(DELETE_GLOBAL_USER + .replace("{currency}", currency.getIdentifier())))) { + ps.setString(1, uniqueIdString); - psGlobal.addBatch(); + ps.execute(); + } } case LOCAL -> { - psLocal.setString(1, uniqueIdString); - psLocal.setString(2, currency.getIdentifier()); - psLocal.setBigDecimal(3, balance); - if (SqlStatements.mustDuplicateParameters(this.connectionFactory.getImplementationName())) - psLocal.setBigDecimal(4, balance); + try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(DELETE_LOCAL_USER + .replace("{currency}", currency.getIdentifier())))) { + ps.setString(1, uniqueIdString); - psLocal.addBatch(); + ps.execute(); + } + } + } + + continue; + } + + switch (currency.getType()) { + case GLOBAL -> { + try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_GLOBAL_CURRENCY + .replace("{currency}", currency.getIdentifier())))) { + ps.setString(1, uniqueIdString); + ps.setBigDecimal(2, balance); + if (SqlStatements.mustDuplicateParameters(this.connectionFactory.getImplementationName())) + ps.setBigDecimal(3, balance); + + ps.execute(); + } + } + case LOCAL -> { + try (PreparedStatement psLocal = c.prepareStatement(this.statementProcessor.apply(SAVE_USER_LOCAL_CURRENCY + .replace("{currency}", currency.getIdentifier())))) { + psLocal.setString(1, uniqueIdString); + psLocal.setBigDecimal(2, balance); + if (SqlStatements.mustDuplicateParameters(this.connectionFactory.getImplementationName())) + psLocal.setBigDecimal(3, balance); + + psLocal.execute(); } } } + } - psGlobal.executeBatch(); - psLocal.executeBatch(); - psDeleteGlobal.executeBatch(); - psDeleteLocal.executeBatch(); - } catch (SQLException e) { - throw new SQLException("Failed to save user " + user.getUniqueId(), e); + if (transactions) { + try { + c.commit(); + } catch (SQLException e) { + c.rollback(); + throw new SQLException("Failed to save user " + user.getUniqueId(), e); + } } } } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/file/FileConnectionFactory.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/file/FileConnectionFactory.java index aa7db9c..5aa8817 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/file/FileConnectionFactory.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/file/FileConnectionFactory.java @@ -5,7 +5,6 @@ import dev.xhyrom.lighteco.common.storage.provider.sql.connection.ConnectionFact 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; @@ -34,9 +33,4 @@ abstract class FileConnectionFactory implements ConnectionFactory { return connection; } - - @Override - public Function getStatementProcessor() { - return s -> s.replace('\'', '`'); - } } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/file/SqliteConnectionFactory.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/file/SqliteConnectionFactory.java index 08619b3..10fa8ba 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/file/SqliteConnectionFactory.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/file/SqliteConnectionFactory.java @@ -7,10 +7,10 @@ import dev.xhyrom.lighteco.common.storage.StorageType; 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.EnumSet; import java.util.Properties; +import java.util.function.Function; public class SqliteConnectionFactory extends FileConnectionFactory { private Constructor connectionConstructor; @@ -52,4 +52,9 @@ public class SqliteConnectionFactory extends FileConnectionFactory { throw new SQLException("Failed to create connection", e); } } + + @Override + public Function getStatementProcessor() { + return s -> s.replace('\'', '`'); + } } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MariaDBConnectionFactory.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MariaDBConnectionFactory.java index f951bd4..89671bc 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MariaDBConnectionFactory.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MariaDBConnectionFactory.java @@ -3,6 +3,8 @@ package dev.xhyrom.lighteco.common.storage.provider.sql.connection.hikari; import dev.xhyrom.lighteco.common.config.storage.StorageDataConfig; import dev.xhyrom.lighteco.common.storage.StorageType; +import java.util.function.Function; + public class MariaDBConnectionFactory extends DriverBasedHikariConnectionFactory { public MariaDBConnectionFactory(StorageDataConfig configuration) { super(configuration); @@ -27,4 +29,9 @@ public class MariaDBConnectionFactory extends DriverBasedHikariConnectionFactory protected String driverJdbcIdentifier() { return "mariadb"; } + + @Override + public Function getStatementProcessor() { + return s -> s.replace('\'', '`'); + } } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MySQLConnectionFactory.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MySQLConnectionFactory.java index d5f8d0b..ed6b733 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MySQLConnectionFactory.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MySQLConnectionFactory.java @@ -4,6 +4,7 @@ import dev.xhyrom.lighteco.common.config.storage.StorageDataConfig; import dev.xhyrom.lighteco.common.storage.StorageType; import java.util.Map; +import java.util.function.Function; public class MySQLConnectionFactory extends DriverBasedHikariConnectionFactory { public MySQLConnectionFactory(StorageDataConfig configuration) { @@ -51,4 +52,9 @@ public class MySQLConnectionFactory extends DriverBasedHikariConnectionFactory { // data types in any of our schemas/queries. properties.putIfAbsent("serverTimezone", "UTC"); } + + @Override + public Function getStatementProcessor() { + return s -> s.replace('\'', '`'); + } } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/PostgreSQLConnectionFactory.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/PostgreSQLConnectionFactory.java index 4d344fb..781ae6c 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/PostgreSQLConnectionFactory.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/PostgreSQLConnectionFactory.java @@ -42,6 +42,6 @@ public class PostgreSQLConnectionFactory extends DriverBasedHikariConnectionFact @Override public Function getStatementProcessor() { - return s -> s.replace("\'", "\""); + return s -> s.replace('\'', '`'); } } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/task/UserSaveTask.java b/common/src/main/java/dev/xhyrom/lighteco/common/task/UserSaveTask.java index b0b6968..ad99adc 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/task/UserSaveTask.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/task/UserSaveTask.java @@ -26,15 +26,15 @@ public class UserSaveTask implements Runnable { } try { + for (User user : users) { + user.setDirty(false); + } + this.plugin.getStorage().saveUsersSync( Arrays.stream(users) .map(User::getProxy) .toArray(dev.xhyrom.lighteco.api.model.user.User[]::new) ); - - for (User user : users) { - user.setDirty(false); - } } catch (RuntimeException e) { this.plugin.getBootstrap().getLogger().error("Failed to save users", e); } diff --git a/common/src/main/resources/schema/h2.sql b/common/src/main/resources/schema/h2.sql deleted file mode 100644 index 8e45fcf..0000000 --- a/common/src/main/resources/schema/h2.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS `{prefix}_users` ( - `uuid` VARCHAR(36) NOT NULL, - `currency_identifier` VARCHAR(255) NOT NULL, - `balance` DECIMAL(10, 2) NOT NULL, - PRIMARY KEY (`uuid`, `currency_identifier`) -); - -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`) -); \ No newline at end of file diff --git a/common/src/main/resources/schema/mariadb.sql b/common/src/main/resources/schema/mariadb.sql deleted file mode 100644 index 8e45fcf..0000000 --- a/common/src/main/resources/schema/mariadb.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS `{prefix}_users` ( - `uuid` VARCHAR(36) NOT NULL, - `currency_identifier` VARCHAR(255) NOT NULL, - `balance` DECIMAL(10, 2) NOT NULL, - PRIMARY KEY (`uuid`, `currency_identifier`) -); - -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`) -); \ No newline at end of file diff --git a/common/src/main/resources/schema/mysql.sql b/common/src/main/resources/schema/mysql.sql deleted file mode 100644 index 8e45fcf..0000000 --- a/common/src/main/resources/schema/mysql.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS `{prefix}_users` ( - `uuid` VARCHAR(36) NOT NULL, - `currency_identifier` VARCHAR(255) NOT NULL, - `balance` DECIMAL(10, 2) NOT NULL, - PRIMARY KEY (`uuid`, `currency_identifier`) -); - -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`) -); \ No newline at end of file diff --git a/common/src/main/resources/schema/postgresql.sql b/common/src/main/resources/schema/postgresql.sql deleted file mode 100644 index 1af0acf..0000000 --- a/common/src/main/resources/schema/postgresql.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS "{prefix}_users" ( - "uuid" VARCHAR(36) NOT NULL, - "currency_identifier" VARCHAR(255) NOT NULL, - "balance" DECIMAL(10, 2) NOT NULL, - PRIMARY KEY ("uuid", "currency_identifier") -); - -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") -); \ No newline at end of file diff --git a/common/src/main/resources/schema/sqlite.sql b/common/src/main/resources/schema/sqlite.sql deleted file mode 100644 index 8e45fcf..0000000 --- a/common/src/main/resources/schema/sqlite.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS `{prefix}_users` ( - `uuid` VARCHAR(36) NOT NULL, - `currency_identifier` VARCHAR(255) NOT NULL, - `balance` DECIMAL(10, 2) NOT NULL, - PRIMARY KEY (`uuid`, `currency_identifier`) -); - -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`) -); \ No newline at end of file diff --git a/currency-money/src/main/java/dev/xhyrom/lighteco/currency/money/bukkit/hooks/vault/Vault.java b/currency-money/src/main/java/dev/xhyrom/lighteco/currency/money/bukkit/hooks/vault/Vault.java index 4e9d46f..1c19bd0 100644 --- a/currency-money/src/main/java/dev/xhyrom/lighteco/currency/money/bukkit/hooks/vault/Vault.java +++ b/currency-money/src/main/java/dev/xhyrom/lighteco/currency/money/bukkit/hooks/vault/Vault.java @@ -123,9 +123,6 @@ public class Vault extends AbstractEconomy { ); } - // Can happen on background - this.provider.getUserManager().saveUser(user); - return new EconomyResponse( amount, bigDecimalToDouble(user.getBalance(currency)), diff --git a/sponge-8/src/main/java/dev/xhyrom/lighteco/sponge/SpongeLightEcoBootstrap.java b/sponge-8/src/main/java/dev/xhyrom/lighteco/sponge/SpongeLightEcoBootstrap.java index 9fe84f7..469ab9f 100644 --- a/sponge-8/src/main/java/dev/xhyrom/lighteco/sponge/SpongeLightEcoBootstrap.java +++ b/sponge-8/src/main/java/dev/xhyrom/lighteco/sponge/SpongeLightEcoBootstrap.java @@ -59,6 +59,11 @@ public class SpongeLightEcoBootstrap implements LightEcoBootstrap, LoaderBootstr return null; } + @Override + public boolean isPlayerOnline(UUID uniqueId) { + return false; + } + @Override public List getOnlinePlayers() { return null;