mirror of
https://github.com/Xiao-MoMi/Custom-Fishing.git
synced 2025-12-23 00:49:24 +00:00
added export legacy command
This commit is contained in:
@@ -21,6 +21,7 @@ import net.momirealms.customfishing.api.data.user.OfflineUser;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -31,9 +32,13 @@ public interface DataStorageInterface {
|
||||
|
||||
StorageType getStorageType();
|
||||
|
||||
CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean force);
|
||||
CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock);
|
||||
|
||||
CompletableFuture<Boolean> savePlayerData(UUID uuid, PlayerData playerData, boolean unlock);
|
||||
|
||||
void savePlayersData(Collection<? extends OfflineUser> users, boolean unlock);
|
||||
|
||||
void lockPlayerData(UUID uuid, boolean lock);
|
||||
|
||||
Set<UUID> getUniqueUsers(boolean legacy);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.momirealms.customfishing.api.data;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface LegacyDataStorageInterface extends DataStorageInterface {
|
||||
|
||||
CompletableFuture<Optional<PlayerData>> getLegacyPlayerData(UUID uuid);
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import java.util.Map;
|
||||
|
||||
public class StatisticData {
|
||||
|
||||
@SerializedName("stats")
|
||||
@SerializedName("map")
|
||||
public Map<String, Integer> statisticMap;
|
||||
|
||||
public StatisticData(@NotNull Map<String, Integer> data) {
|
||||
|
||||
@@ -27,5 +27,4 @@ public enum StorageType {
|
||||
MariaDB,
|
||||
MongoDB,
|
||||
Redis
|
||||
|
||||
}
|
||||
|
||||
@@ -44,14 +44,13 @@ public interface StorageManager {
|
||||
|
||||
/**
|
||||
* Get an offline user's data
|
||||
* force reading would ignore the database lock
|
||||
* Otherwise it would return Optional.empty() if data is locked
|
||||
* It an offline user never played the server, its name would equal "" (empty string)
|
||||
* @param uuid uuid
|
||||
* @param force force
|
||||
* @param lock lock
|
||||
* @return offline user data
|
||||
*/
|
||||
CompletableFuture<Optional<OfflineUser>> getOfflineUser(UUID uuid, boolean force);
|
||||
CompletableFuture<Optional<OfflineUser>> getOfflineUser(UUID uuid, boolean lock);
|
||||
|
||||
CompletableFuture<Boolean> saveUserData(OfflineUser offlineUser, boolean unlock);
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ public class CommandManagerImpl implements CommandManager {
|
||||
getReloadCommand(),
|
||||
getMarketCommand(),
|
||||
getAboutCommand(),
|
||||
DataCommand.INSTANCE.getDataCommand(),
|
||||
CompetitionCommand.INSTANCE.getCompetitionCommand(),
|
||||
ItemCommand.INSTANCE.getItemCommand(),
|
||||
DebugCommand.INSTANCE.getDebugCommand(),
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package net.momirealms.customfishing.command.sub;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import dev.jorel.commandapi.CommandAPICommand;
|
||||
import dev.jorel.commandapi.arguments.ArgumentSuggestions;
|
||||
import dev.jorel.commandapi.arguments.StringArgument;
|
||||
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
|
||||
import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.data.LegacyDataStorageInterface;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import net.momirealms.customfishing.storage.method.database.sql.MariaDBImpl;
|
||||
import net.momirealms.customfishing.storage.method.database.sql.MySQLImpl;
|
||||
import net.momirealms.customfishing.storage.method.file.YAMLImpl;
|
||||
import net.momirealms.customfishing.util.CompletableFutures;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
public class DataCommand {
|
||||
|
||||
public static DataCommand INSTANCE = new DataCommand();
|
||||
|
||||
public CommandAPICommand getDataCommand() {
|
||||
return new CommandAPICommand("data")
|
||||
.withSubcommands(
|
||||
getExportLegacyCommand()
|
||||
);
|
||||
}
|
||||
|
||||
public CommandAPICommand getExportLegacyCommand() {
|
||||
return new CommandAPICommand("export-legacy")
|
||||
.withArguments(new StringArgument("method")
|
||||
.replaceSuggestions(ArgumentSuggestions.strings("MySQL", "MariaDB", "YAML")))
|
||||
.executes((sender, args) -> {
|
||||
String arg = (String) args.get("method");
|
||||
if (arg == null) return;
|
||||
CustomFishingPlugin plugin = CustomFishingPlugin.get();
|
||||
plugin.getScheduler().runTaskAsync(() -> {
|
||||
LegacyDataStorageInterface dataStorageInterface;
|
||||
switch (arg) {
|
||||
case "MySQL" -> dataStorageInterface = new MySQLImpl(plugin);
|
||||
case "MariaDB" -> dataStorageInterface = new MariaDBImpl(plugin);
|
||||
case "YAML" -> dataStorageInterface = new YAMLImpl(plugin);
|
||||
default -> {
|
||||
AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, "No such legacy storage method.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dataStorageInterface.initialize();
|
||||
Set<UUID> uuids = dataStorageInterface.getUniqueUsers(true);
|
||||
Set<CompletableFuture<Void>> futures = new HashSet<>();
|
||||
AtomicInteger userCount = new AtomicInteger(0);
|
||||
Map<UUID, String> out = Collections.synchronizedMap(new TreeMap<>());
|
||||
|
||||
for (UUID uuid : uuids) {
|
||||
futures.add(dataStorageInterface.getLegacyPlayerData(uuid).thenAccept(it -> {
|
||||
if (it.isPresent()) {
|
||||
out.put(uuid, plugin.getStorageManager().toJson(it.get()));
|
||||
userCount.incrementAndGet();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
CompletableFuture<Void> overallFuture = CompletableFutures.allOf(futures);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
overallFuture.get(3, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
} catch (TimeoutException e) {
|
||||
LogUtils.info("Progress: " + userCount.get() + "/" + uuids.size());
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
JsonObject outJson = new JsonObject();
|
||||
for (Map.Entry<UUID, String> entry : out.entrySet()) {
|
||||
outJson.addProperty(entry.getKey().toString(), entry.getValue());
|
||||
}
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm");
|
||||
String formattedDate = formatter.format(new Date());
|
||||
File outFile = new File(plugin.getDataFolder(), "exported-" + formattedDate + ".json.gz");
|
||||
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(Files.newOutputStream(outFile.toPath())), StandardCharsets.UTF_8))) {
|
||||
new GsonBuilder().disableHtmlEscaping().create().toJson(outJson, writer);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
plugin.getScheduler().runTaskAsyncLater(dataStorageInterface::disable, 1, TimeUnit.SECONDS);
|
||||
|
||||
AdventureManagerImpl.getInstance().sendMessageWithPrefix(sender, "Finished.");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import dev.jorel.commandapi.arguments.UUIDArgument;
|
||||
import net.momirealms.customfishing.adventure.AdventureManagerImpl;
|
||||
import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.data.user.OfflineUser;
|
||||
import net.momirealms.customfishing.setting.CFConfig;
|
||||
import net.momirealms.customfishing.setting.CFLocale;
|
||||
import net.momirealms.customfishing.storage.user.OfflineUserImpl;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -79,7 +80,7 @@ public class FishingBagCommand {
|
||||
return;
|
||||
}
|
||||
}
|
||||
CustomFishingPlugin.get().getStorageManager().getOfflineUser(uuid, false).thenAccept(optional -> {
|
||||
CustomFishingPlugin.get().getStorageManager().getOfflineUser(uuid, CFConfig.lockData).thenAccept(optional -> {
|
||||
if (optional.isEmpty()) {
|
||||
AdventureManagerImpl.getInstance().sendMessageWithPrefix(player, CFLocale.MSG_Never_Played);
|
||||
return;
|
||||
|
||||
@@ -83,6 +83,8 @@ public class CFConfig {
|
||||
|
||||
// Data save interval
|
||||
public static int dataSaveInterval;
|
||||
// Lock data on join
|
||||
public static boolean lockData;
|
||||
|
||||
// Legacy color code support
|
||||
public static boolean legacyColorSupport;
|
||||
@@ -140,6 +142,7 @@ public class CFConfig {
|
||||
placeholderLimit = config.getInt("mechanics.competition.placeholder-limit", 3);
|
||||
|
||||
dataSaveInterval = config.getInt("other-settings.data-saving-interval", 600);
|
||||
lockData = config.getBoolean("other-settings.lock-data", true);
|
||||
legacyColorSupport = config.getBoolean("other-settings.legacy-color-code-support", false);
|
||||
|
||||
OffsetUtils.loadConfig(config.getConfigurationSection("other-settings.offset-characters"));
|
||||
|
||||
@@ -113,7 +113,7 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
this.timerSaveTask = this.plugin.getScheduler().runTaskAsyncTimer(
|
||||
() -> {
|
||||
long time1 = System.currentTimeMillis();
|
||||
this.dataSource.savePlayersData(this.onlineUserMap.values(), false);
|
||||
this.dataSource.savePlayersData(this.onlineUserMap.values(), !CFConfig.lockData);
|
||||
LogUtils.info("Data Saved for online players. Took " + (System.currentTimeMillis() - time1) + "ms.");
|
||||
},
|
||||
CFConfig.dataSaveInterval,
|
||||
@@ -143,8 +143,8 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<OfflineUser>> getOfflineUser(UUID uuid, boolean force) {
|
||||
var optionalDataFuture = dataSource.getPlayerData(uuid, force);
|
||||
public CompletableFuture<Optional<OfflineUser>> getOfflineUser(UUID uuid, boolean lock) {
|
||||
var optionalDataFuture = dataSource.getPlayerData(uuid, lock);
|
||||
return optionalDataFuture.thenCompose(optionalUser -> {
|
||||
if (optionalUser.isEmpty()) {
|
||||
// locked
|
||||
@@ -252,6 +252,7 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
if (optionalData.isPresent()) {
|
||||
putDataInCache(player, optionalData.get());
|
||||
task.cancel();
|
||||
if (CFConfig.lockData) dataSource.lockPlayerData(uuid, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -264,7 +265,7 @@ public class StorageManagerImpl implements StorageManager, Listener {
|
||||
var player = Bukkit.getPlayer(uuid);
|
||||
if (player == null || !player.isOnline() || times > 3)
|
||||
return;
|
||||
this.dataSource.getPlayerData(uuid, false).thenAccept(optionalData -> {
|
||||
this.dataSource.getPlayerData(uuid, CFConfig.lockData).thenAccept(optionalData -> {
|
||||
// should not be empty
|
||||
if (optionalData.isEmpty())
|
||||
return;
|
||||
|
||||
@@ -23,6 +23,7 @@ import net.momirealms.customfishing.api.data.user.OfflineUser;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class AbstractStorage implements DataStorageInterface {
|
||||
|
||||
@@ -52,4 +53,8 @@ public abstract class AbstractStorage implements DataStorageInterface {
|
||||
this.savePlayerData(user.getUUID(), user.getPlayerData(), unlock);
|
||||
}
|
||||
}
|
||||
|
||||
public void lockPlayerData(UUID uuid, boolean lock) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,25 +18,20 @@
|
||||
package net.momirealms.customfishing.storage.method.database.nosql;
|
||||
|
||||
import com.mongodb.*;
|
||||
import com.mongodb.bulk.BulkWriteResult;
|
||||
import com.mongodb.client.MongoClient;
|
||||
import com.mongodb.client.MongoClients;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.*;
|
||||
import com.mongodb.client.model.*;
|
||||
import com.mongodb.client.result.InsertOneResult;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.data.PlayerData;
|
||||
import net.momirealms.customfishing.api.data.StorageType;
|
||||
import net.momirealms.customfishing.api.data.user.OfflineUser;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import net.momirealms.customfishing.setting.CFConfig;
|
||||
import net.momirealms.customfishing.storage.method.AbstractStorage;
|
||||
import org.bson.Document;
|
||||
import org.bson.UuidRepresentation;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.Binary;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
@@ -109,23 +104,25 @@ public class MongoDBImpl extends AbstractStorage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean force) {
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
var future = new CompletableFuture<Optional<PlayerData>>();
|
||||
plugin.getScheduler().runTaskAsync(() -> {
|
||||
MongoCollection<Document> collection = database.getCollection(getCollectionName("data"));
|
||||
Document doc = collection.find(Filters.eq("uuid", uuid)).first();
|
||||
if (doc == null) {
|
||||
if (Bukkit.getPlayer(uuid) != null) {
|
||||
if (lock) lockPlayerData(uuid, true);
|
||||
future.complete(Optional.of(PlayerData.empty()));
|
||||
} else {
|
||||
future.complete(Optional.empty());
|
||||
}
|
||||
} else {
|
||||
if (!force && doc.getInteger("lock") != 0) {
|
||||
if (doc.getInteger("lock") != 0 && getCurrentSeconds() - CFConfig.dataSaveInterval <= doc.getInteger("lock")) {
|
||||
future.complete(Optional.of(PlayerData.LOCKED));
|
||||
return;
|
||||
}
|
||||
Binary binary = (Binary) doc.get("data");
|
||||
if (lock) lockPlayerData(uuid, true);
|
||||
future.complete(Optional.of(plugin.getStorageManager().fromBytes(binary.getData())));
|
||||
}
|
||||
});
|
||||
@@ -172,4 +169,35 @@ public class MongoDBImpl extends AbstractStorage {
|
||||
LogUtils.warn("Failed to update data for online players", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockPlayerData(UUID uuid, boolean lock) {
|
||||
MongoCollection<Document> collection = database.getCollection(getCollectionName("data"));
|
||||
try {
|
||||
Document query = new Document("uuid", uuid);
|
||||
Bson updates = Updates.combine(Updates.set("lock", !lock ? 0 : getCurrentSeconds()));
|
||||
UpdateOptions options = new UpdateOptions().upsert(true);
|
||||
collection.updateOne(query, updates, options);
|
||||
} catch (MongoException e) {
|
||||
LogUtils.warn("Failed to lock data for " + uuid, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers(boolean legacy) {
|
||||
// no legacy files
|
||||
Set<UUID> uuids = new HashSet<>();
|
||||
MongoCollection<Document> collection = database.getCollection(getCollectionName("data"));
|
||||
try {
|
||||
Bson projectionFields = Projections.fields(Projections.include("uuid"));
|
||||
try (MongoCursor<Document> cursor = collection.find().projection(projectionFields).iterator()) {
|
||||
while (cursor.hasNext()) {
|
||||
uuids.add(cursor.next().get("uuid", UUID.class));
|
||||
}
|
||||
}
|
||||
} catch (MongoException e) {
|
||||
LogUtils.warn("Failed to get unique data.", e);
|
||||
}
|
||||
return uuids;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ import redis.clients.jedis.resps.Tuple;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -226,7 +228,7 @@ public class RedisManager extends AbstractStorage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean ignore) {
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
var future = new CompletableFuture<Optional<PlayerData>>();
|
||||
plugin.getScheduler().runTaskAsync(() -> {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
@@ -268,6 +270,11 @@ public class RedisManager extends AbstractStorage {
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers(boolean legacy) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
private byte[] getRedisKey(String key, @NotNull UUID uuid) {
|
||||
return (key + ":" + uuid).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@@ -19,18 +19,20 @@ package net.momirealms.customfishing.storage.method.database.sql;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import net.momirealms.customfishing.api.CustomFishingPlugin;
|
||||
import net.momirealms.customfishing.api.data.StorageType;
|
||||
import net.momirealms.customfishing.api.data.*;
|
||||
import net.momirealms.customfishing.api.util.LogUtils;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class AbstractHikariDatabase extends AbstractSQLDatabase {
|
||||
public abstract class AbstractHikariDatabase extends AbstractSQLDatabase implements LegacyDataStorageInterface {
|
||||
|
||||
private HikariDataSource dataSource;
|
||||
private final String driverClass;
|
||||
@@ -117,4 +119,56 @@ public abstract class AbstractHikariDatabase extends AbstractSQLDatabase {
|
||||
public Connection getConnection() throws SQLException {
|
||||
return dataSource.getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getLegacyPlayerData(UUID uuid) {
|
||||
var future = new CompletableFuture<Optional<PlayerData>>();
|
||||
plugin.getScheduler().runTaskAsync(() -> {
|
||||
try (
|
||||
Connection connection = getConnection()
|
||||
) {
|
||||
var builder = new PlayerData.Builder().setName("");
|
||||
PreparedStatement statementOne = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("fishingbag")));
|
||||
statementOne.setString(1, uuid.toString());
|
||||
ResultSet rsOne = statementOne.executeQuery();
|
||||
if (rsOne.next()) {
|
||||
int size = rsOne.getInt("size");
|
||||
String contents = rsOne.getString("contents");
|
||||
builder.setBagData(new InventoryData(contents, size));
|
||||
} else {
|
||||
builder.setBagData(InventoryData.empty());
|
||||
}
|
||||
|
||||
PreparedStatement statementTwo = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("selldata")));
|
||||
statementTwo.setString(1, uuid.toString());
|
||||
ResultSet rsTwo = statementTwo.executeQuery();
|
||||
if (rsTwo.next()) {
|
||||
int date = rsTwo.getInt("date");
|
||||
double money = rsTwo.getInt("money");
|
||||
builder.setEarningData(new EarningData(money, date));
|
||||
} else {
|
||||
builder.setEarningData(EarningData.empty());
|
||||
}
|
||||
|
||||
PreparedStatement statementThree = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_BY_UUID, getTableName("statistics")));
|
||||
statementThree.setString(1, uuid.toString());
|
||||
ResultSet rsThree = statementThree.executeQuery();
|
||||
if (rsThree.next()) {
|
||||
String stats = rsThree.getString("stats");
|
||||
var amountMap = (Map<String, Integer>) Arrays.stream(stats.split(";"))
|
||||
.map(element -> element.split(":"))
|
||||
.filter(pair -> pair.length == 2)
|
||||
.collect(Collectors.toMap(pair -> pair[0], pair -> Integer.parseInt(pair[1])));
|
||||
builder.setStats(new StatisticData(amountMap));
|
||||
} else {
|
||||
builder.setStats(StatisticData.empty());
|
||||
}
|
||||
future.complete(Optional.of(builder.build()));
|
||||
} catch (SQLException e) {
|
||||
LogUtils.warn("Failed to get " + uuid + "'s data.", e);
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean force) {
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
var future = new CompletableFuture<Optional<PlayerData>>();
|
||||
plugin.getScheduler().runTaskAsync(() -> {
|
||||
try (
|
||||
@@ -89,10 +89,8 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
statement.setString(1, uuid.toString());
|
||||
ResultSet rs = statement.executeQuery();
|
||||
if (rs.next()) {
|
||||
int lock = rs.getInt(2);
|
||||
if (!force && (lock != 0 && getCurrentSeconds() - CFConfig.dataSaveInterval <= lock)) {
|
||||
statement.close();
|
||||
rs.close();
|
||||
int lockValue = rs.getInt(2);
|
||||
if (lockValue != 0 && getCurrentSeconds() - CFConfig.dataSaveInterval <= lockValue) {
|
||||
connection.close();
|
||||
future.complete(Optional.of(PlayerData.LOCKED));
|
||||
return;
|
||||
@@ -100,11 +98,11 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
final Blob blob = rs.getBlob("data");
|
||||
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
|
||||
blob.free();
|
||||
lockPlayerData(uuid);
|
||||
if (lock) lockPlayerData(uuid, true);
|
||||
future.complete(Optional.of(plugin.getStorageManager().fromBytes(dataByteArray)));
|
||||
} else if (Bukkit.getPlayer(uuid) != null) {
|
||||
var data = PlayerData.empty();
|
||||
insertPlayerData(uuid, data);
|
||||
insertPlayerData(uuid, data, lock);
|
||||
future.complete(Optional.of(data));
|
||||
} else {
|
||||
future.complete(Optional.empty());
|
||||
@@ -162,13 +160,13 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
}
|
||||
}
|
||||
|
||||
public void insertPlayerData(UUID uuid, PlayerData playerData) {
|
||||
public void insertPlayerData(UUID uuid, PlayerData playerData, boolean lock) {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_INSERT_DATA_BY_UUID, getTableName("data")))
|
||||
) {
|
||||
statement.setString(1, uuid.toString());
|
||||
statement.setInt(2, getCurrentSeconds());
|
||||
statement.setInt(2, lock ? getCurrentSeconds() : 0);
|
||||
statement.setBlob(3, new ByteArrayInputStream(plugin.getStorageManager().toBytes(playerData)));
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
@@ -176,12 +174,13 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
}
|
||||
}
|
||||
|
||||
public void lockPlayerData(UUID uuid) {
|
||||
@Override
|
||||
public void lockPlayerData(UUID uuid, boolean lock) {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_LOCK_BY_UUID, getTableName("data")))
|
||||
) {
|
||||
statement.setInt(1, getCurrentSeconds());
|
||||
statement.setInt(1, lock ? getCurrentSeconds() : 0);
|
||||
statement.setString(2, uuid.toString());
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
@@ -189,8 +188,26 @@ public abstract class AbstractSQLDatabase extends AbstractStorage {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers(boolean legacy) {
|
||||
Set<UUID> uuids = new HashSet<>();
|
||||
try (Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_SELECT_ALL_UUID, legacy ? getTableName("fishingbag") : getTableName("data")))) {
|
||||
try (ResultSet rs = statement.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
UUID uuid = UUID.fromString(rs.getString("uuid"));
|
||||
uuids.add(uuid);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LogUtils.warn("Failed to get unique data.", e);
|
||||
}
|
||||
return uuids;
|
||||
}
|
||||
|
||||
public static class SqlConstants {
|
||||
public static final String SQL_SELECT_BY_UUID = "SELECT * FROM `%s` WHERE `uuid` = ?";
|
||||
public static final String SQL_SELECT_ALL_UUID = "SELECT uuid FROM `%s`";
|
||||
public static final String SQL_UPDATE_BY_UUID = "UPDATE `%s` SET `lock` = ?, `data` = ? WHERE `uuid` = ?";
|
||||
public static final String SQL_LOCK_BY_UUID = "UPDATE `%s` SET `lock` = ? WHERE `uuid` = ?";
|
||||
public static final String SQL_INSERT_DATA_BY_UUID = "INSERT INTO `%s`(`uuid`, `lock`, `data`) VALUES(?, ?, ?)";
|
||||
|
||||
@@ -77,7 +77,7 @@ public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean force) {
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
var future = new CompletableFuture<Optional<PlayerData>>();
|
||||
plugin.getScheduler().runTaskAsync(() -> {
|
||||
try (
|
||||
@@ -87,20 +87,18 @@ public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
statement.setString(1, uuid.toString());
|
||||
ResultSet rs = statement.executeQuery();
|
||||
if (rs.next()) {
|
||||
int lock = rs.getInt(2);
|
||||
if (!force && (lock != 0 && getCurrentSeconds() - CFConfig.dataSaveInterval <= lock)) {
|
||||
statement.close();
|
||||
rs.close();
|
||||
int lockValue = rs.getInt(2);
|
||||
if (lockValue != 0 && getCurrentSeconds() - CFConfig.dataSaveInterval <= lockValue) {
|
||||
connection.close();
|
||||
future.complete(Optional.of(PlayerData.LOCKED));
|
||||
return;
|
||||
}
|
||||
final byte[] dataByteArray = rs.getBytes("data");
|
||||
lockPlayerData(uuid);
|
||||
if (lock) lockPlayerData(uuid, true);
|
||||
future.complete(Optional.of(plugin.getStorageManager().fromBytes(dataByteArray)));
|
||||
} else if (Bukkit.getPlayer(uuid) != null) {
|
||||
var data = PlayerData.empty();
|
||||
insertPlayerData(uuid, data);
|
||||
insertPlayerData(uuid, data, lock);
|
||||
future.complete(Optional.of(data));
|
||||
} else {
|
||||
future.complete(Optional.empty());
|
||||
@@ -158,13 +156,13 @@ public class SQLiteImpl extends AbstractSQLDatabase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertPlayerData(UUID uuid, PlayerData playerData) {
|
||||
public void insertPlayerData(UUID uuid, PlayerData playerData, boolean lock) {
|
||||
try (
|
||||
Connection connection = getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(String.format(SqlConstants.SQL_INSERT_DATA_BY_UUID, getTableName("data")))
|
||||
) {
|
||||
statement.setString(1, uuid.toString());
|
||||
statement.setInt(2, getCurrentSeconds());
|
||||
statement.setInt(2, lock ? getCurrentSeconds() : 0);
|
||||
statement.setBytes(3, plugin.getStorageManager().toBytes(playerData));
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
|
||||
@@ -29,12 +29,15 @@ import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class JsonImpl extends AbstractStorage {
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public JsonImpl(CustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
File folder = new File(plugin.getDataFolder(), "data");
|
||||
@@ -47,7 +50,7 @@ public class JsonImpl extends AbstractStorage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean ignore) {
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
File file = getPlayerDataFile(uuid);
|
||||
PlayerData playerData;
|
||||
if (file.exists()) {
|
||||
@@ -94,4 +97,20 @@ public class JsonImpl extends AbstractStorage {
|
||||
}
|
||||
return fileBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers(boolean legacy) {
|
||||
// No legacy files
|
||||
File folder = new File(plugin.getDataFolder(), "data");
|
||||
Set<UUID> uuids = new HashSet<>();
|
||||
if (folder.exists()) {
|
||||
File[] files = folder.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
uuids.add(UUID.fromString(file.getName().substring(file.getName().length() - 5)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return uuids;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +28,12 @@ import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class YAMLImpl extends AbstractStorage {
|
||||
public class YAMLImpl extends AbstractStorage implements LegacyDataStorageInterface {
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public YAMLImpl(CustomFishingPlugin plugin) {
|
||||
super(plugin);
|
||||
File folder = new File(plugin.getDataFolder(), "data");
|
||||
@@ -52,7 +50,7 @@ public class YAMLImpl extends AbstractStorage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean ignore) {
|
||||
public CompletableFuture<Optional<PlayerData>> getPlayerData(UUID uuid, boolean lock) {
|
||||
File dataFile = getPlayerDataFile(uuid);
|
||||
if (!dataFile.exists()) {
|
||||
if (Bukkit.getPlayer(uuid) != null) {
|
||||
@@ -91,6 +89,26 @@ public class YAMLImpl extends AbstractStorage {
|
||||
return CompletableFuture.completedFuture(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getUniqueUsers(boolean legacy) {
|
||||
File folder;
|
||||
if (legacy) {
|
||||
folder = new File(plugin.getDataFolder(), "data/fishingbag");
|
||||
} else {
|
||||
folder = new File(plugin.getDataFolder(), "data");
|
||||
}
|
||||
Set<UUID> uuids = new HashSet<>();
|
||||
if (folder.exists()) {
|
||||
File[] files = folder.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
uuids.add(UUID.fromString(file.getName().substring(0, file.getName().length() - 4)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return uuids;
|
||||
}
|
||||
|
||||
public StatisticData getStatistics(ConfigurationSection section) {
|
||||
if (section == null)
|
||||
return StatisticData.empty();
|
||||
@@ -102,4 +120,42 @@ public class YAMLImpl extends AbstractStorage {
|
||||
return new StatisticData(map);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<PlayerData>> getLegacyPlayerData(UUID uuid) {
|
||||
var builder = new PlayerData.Builder().setName("");
|
||||
File bagFile = new File(plugin.getDataFolder(), "data/fishingbag/" + uuid + ".yml");
|
||||
if (bagFile.exists()) {
|
||||
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(bagFile);
|
||||
String contents = yaml.getString("contents", "");
|
||||
int size = yaml.getInt("size", 9);
|
||||
builder.setBagData(new InventoryData(contents, size));
|
||||
} else {
|
||||
builder.setBagData(InventoryData.empty());
|
||||
}
|
||||
|
||||
File statFile = new File(plugin.getDataFolder(), "data/statistics/" + uuid + ".yml");
|
||||
if (statFile.exists()) {
|
||||
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(statFile);
|
||||
HashMap<String, Integer> map = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : yaml.getValues(false).entrySet()) {
|
||||
if (entry.getValue() instanceof Integer integer) {
|
||||
map.put(entry.getKey(), integer);
|
||||
}
|
||||
}
|
||||
builder.setStats(new StatisticData(map));
|
||||
} else {
|
||||
builder.setStats(StatisticData.empty());
|
||||
}
|
||||
|
||||
File sellFile = new File(plugin.getDataFolder(), "data/sell/" + uuid + ".yml");
|
||||
if (sellFile.exists()) {
|
||||
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(sellFile);
|
||||
builder.setEarningData(new EarningData(yaml.getDouble("earnings"), yaml.getInt("date")));
|
||||
} else {
|
||||
builder.setEarningData(EarningData.empty());
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(Optional.of(builder.build()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.momirealms.customfishing.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class CompletableFutures {
|
||||
private CompletableFutures() {}
|
||||
|
||||
public static <T extends CompletableFuture<?>> Collector<T, ImmutableList.Builder<T>, CompletableFuture<Void>> collector() {
|
||||
return Collector.of(
|
||||
ImmutableList.Builder::new,
|
||||
ImmutableList.Builder::add,
|
||||
(l, r) -> l.addAll(r.build()),
|
||||
builder -> allOf(builder.build())
|
||||
);
|
||||
}
|
||||
|
||||
public static CompletableFuture<Void> allOf(Stream<? extends CompletableFuture<?>> futures) {
|
||||
CompletableFuture<?>[] arr = futures.toArray(CompletableFuture[]::new);
|
||||
return CompletableFuture.allOf(arr);
|
||||
}
|
||||
|
||||
public static CompletableFuture<Void> allOf(Collection<? extends CompletableFuture<?>> futures) {
|
||||
CompletableFuture<?>[] arr = futures.toArray(new CompletableFuture[0]);
|
||||
return CompletableFuture.allOf(arr);
|
||||
}
|
||||
}
|
||||
@@ -149,6 +149,10 @@ other-settings:
|
||||
# set to -1 to disable
|
||||
data-saving-interval: 600
|
||||
|
||||
# Lock player's data if a player is playing on a server that connected to database
|
||||
# If you can ensure low database link latency and fast processing, you can consider disabling this option to save some performance
|
||||
lock-data: true
|
||||
|
||||
# Requires PlaceholderAPI to work
|
||||
placeholder-register:
|
||||
'{date}': '%server_time_yyyy-MM-dd-HH:mm:ss%'
|
||||
|
||||
Reference in New Issue
Block a user