9
0
mirror of https://github.com/Xiao-MoMi/Custom-Fishing.git synced 2025-12-23 08:59:34 +00:00

added export legacy command

This commit is contained in:
XiaoMoMi
2023-09-14 02:54:32 +08:00
parent 0286893529
commit efd31b7eb9
20 changed files with 403 additions and 53 deletions

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -27,5 +27,4 @@ public enum StorageType {
MariaDB,
MongoDB,
Redis
}

View File

@@ -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);

View File

@@ -52,6 +52,7 @@ public class CommandManagerImpl implements CommandManager {
getReloadCommand(),
getMarketCommand(),
getAboutCommand(),
DataCommand.INSTANCE.getDataCommand(),
CompetitionCommand.INSTANCE.getCompetitionCommand(),
ItemCommand.INSTANCE.getItemCommand(),
DebugCommand.INSTANCE.getDebugCommand(),

View File

@@ -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.");
});
});
}
}

View File

@@ -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;

View File

@@ -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"));

View File

@@ -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;

View File

@@ -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) {
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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(?, ?, ?)";

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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()));
}
}

View File

@@ -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);
}
}

View File

@@ -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%'