9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-19 15:09:25 +00:00
- Auto migrator
- Backup system
This commit is contained in:
Taiyou06
2025-07-20 19:25:37 +02:00
parent e32588174c
commit c1d4ac877b
7 changed files with 494 additions and 77 deletions

View File

@@ -0,0 +1,100 @@
package org.dreeam.leaf.config;
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class ConfigMigrator {
private static final String BACKUP_PREFIX = "backup-migration-";
public void migrate() throws Exception {
File oldConfigFile = new File(LeafConfig.I_CONFIG_FOLDER, LeafConfig.I_GLOBAL_CONFIG_FILE);
if (!oldConfigFile.exists()) {
return;
}
// Ensure config/leaf directory exists
LeafConfig.createDirectory(LeafConfig.I_LEAF_CONFIG_FOLDER);
// Load the old config
ConfigFile oldConfig = ConfigFile.loadConfig(oldConfigFile);
// Create backup
createBackup(oldConfigFile);
// Create separate config files for each category in config/leaf folder
Map<EnumConfigCategory, ConfigFile> newConfigs = new HashMap<>();
for (EnumConfigCategory category : EnumConfigCategory.getCategoryValues()) {
File newConfigFile = new File(LeafConfig.I_LEAF_CONFIG_FOLDER, category.getFileName());
ConfigFile newConfig = ConfigFile.loadConfig(newConfigFile);
newConfigs.put(category, newConfig);
// Set up basic structure
newConfig.set("config-version", "3.0");
newConfig.addComments("config-version", String.format("Leaf %s Config\nGitHub Repo: https://github.com/Winds-Studio/Leaf", category.name()));
}
// Migrate data from old config to new configs
migrateConfigData(oldConfig, newConfigs);
// Save all new config files
for (ConfigFile newConfig : newConfigs.values()) {
newConfig.save();
}
LeafConfig.LOGGER.info("Config migration completed. Old config backed up.");
LeafConfig.LOGGER.info("New config files created in config/leaf/ folder.");
}
private void createBackup(File oldConfigFile) throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss");
String timestamp = dateFormat.format(new Date());
String backupName = BACKUP_PREFIX + timestamp + "-" + oldConfigFile.getName();
Path backupPath = oldConfigFile.toPath().resolveSibling(backupName);
Files.copy(oldConfigFile.toPath(), backupPath, StandardCopyOption.REPLACE_EXISTING);
LeafConfig.LOGGER.info("Created backup: {}", backupName);
}
private void migrateConfigData(ConfigFile oldConfig, Map<EnumConfigCategory, ConfigFile> newConfigs) {
// Iterate through each category and migrate relevant sections
for (EnumConfigCategory category : EnumConfigCategory.getCategoryValues()) {
ConfigFile newConfig = newConfigs.get(category);
String basePath = category.getBaseKeyName();
// Get the section from old config
ConfigSection oldSection = oldConfig.getConfigSection(basePath);
if (oldSection != null) {
// Copy all values from the old section to the new config
copySection(oldSection, newConfig, basePath);
LeafConfig.LOGGER.debug("Migrated {} section to {}", basePath, category.getFileName());
}
}
}
private void copySection(ConfigSection source, ConfigFile target, String basePath) {
// Get all keys from the source section (shallow keys only)
for (String key : source.getKeys(false)) {
String fullPath = basePath + "." + key;
Object value = source.get(key);
if (value instanceof ConfigSection) {
// Recursively copy subsections
copySection((ConfigSection) value, target, fullPath);
} else {
// Copy the value
target.set(fullPath, value);
}
}
}
}

View File

@@ -15,10 +15,32 @@ public abstract class ConfigModules extends LeafConfig {
private static final Set<ConfigModules> MODULES = new HashSet<>();
public LeafGlobalConfig config;
public LeafCategoryConfig config;
private EnumConfigCategory category;
public ConfigModules() {
this.config = LeafConfig.config();
// Determine category from the module's base path
this.category = determineCategory();
this.config = LeafConfig.config(this.category);
}
// Abstract method that modules must implement to specify their base path
public abstract String getBasePath();
// Determine category from base path
private EnumConfigCategory determineCategory() {
String basePath = getBasePath();
for (EnumConfigCategory cat : EnumConfigCategory.getCategoryValues()) {
if (basePath.startsWith(cat.getBaseKeyName())) {
return cat;
}
}
// Fallback to MISC if no category matches
return EnumConfigCategory.MISC;
}
public EnumConfigCategory getCategory() {
return category;
}
public static void initModules() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
@@ -38,7 +60,8 @@ public abstract class ConfigModules extends LeafConfig {
}
if (!enabledExperimentalModules.isEmpty()) {
LeafConfig.LOGGER.warn("You have following experimental module(s) enabled: {}, please proceed with caution!", enabledExperimentalModules.stream().map(f -> f.getDeclaringClass().getSimpleName() + "." + f.getName()).toList());
LeafConfig.LOGGER.warn("You have following experimental module(s) enabled: {}, please proceed with caution!",
enabledExperimentalModules.stream().map(f -> f.getDeclaringClass().getSimpleName() + "." + f.getName()).toList());
}
}
@@ -47,11 +70,13 @@ public abstract class ConfigModules extends LeafConfig {
module.onPostLoaded();
}
// Save config to disk
// Save all config files to disk
for (EnumConfigCategory category : EnumConfigCategory.getCategoryValues()) {
try {
LeafConfig.config().saveConfig();
LeafConfig.config(category).saveConfig();
} catch (Exception e) {
LeafConfig.LOGGER.error("Failed to save config file!", e);
LeafConfig.LOGGER.error("Failed to save config file for category {}!", category.name(), e);
}
}
}

View File

@@ -0,0 +1,39 @@
package org.dreeam.leaf.config;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
public class ConfigStatus {
public static Map<String, Object> getStatus() {
Map<String, Object> status = new HashMap<>();
File oldConfigFile = new File(LeafConfig.I_CONFIG_FOLDER, LeafConfig.I_GLOBAL_CONFIG_FILE);
status.put("oldConfigExists", oldConfigFile.exists());
status.put("oldConfigFile", LeafConfig.I_GLOBAL_CONFIG_FILE);
Map<String, Boolean> categoryFiles = new HashMap<>();
for (EnumConfigCategory category : EnumConfigCategory.getCategoryValues()) {
File categoryFile = new File(LeafConfig.I_CONFIG_FOLDER, category.getFileName());
categoryFiles.put(category.getFileName(), categoryFile.exists());
}
status.put("categoryFiles", categoryFiles);
boolean needsMigration = oldConfigFile.exists() && !allCategoryFilesExist();
status.put("needsMigration", needsMigration);
status.put("multiFileSystemReady", allCategoryFilesExist());
return status;
}
private static boolean allCategoryFilesExist() {
for (EnumConfigCategory category : EnumConfigCategory.getCategoryValues()) {
File categoryFile = new File(LeafConfig.I_CONFIG_FOLDER, category.getFileName());
if (!categoryFile.exists()) {
return false;
}
}
return true;
}
}

View File

@@ -1,25 +1,40 @@
package org.dreeam.leaf.config;
public enum EnumConfigCategory {
ASYNC("async"),
PERF("performance"),
FIXES("fixes"),
GAMEPLAY("gameplay-mechanisms"),
NETWORK("network"),
MISC("misc");
ASYNC("async", "leaf-async.yml"),
PERF("performance", "leaf-performance.yml"),
FIXES("fixes", "leaf-fixes.yml"),
GAMEPLAY("gameplay-mechanisms", "leaf-gameplay.yml"),
NETWORK("network", "leaf-network.yml"),
MISC("misc", "leaf-misc.yml");
private final String baseKeyName;
private final String fileName;
private static final EnumConfigCategory[] VALUES = EnumConfigCategory.values();
EnumConfigCategory(String baseKeyName) {
EnumConfigCategory(String baseKeyName, String fileName) {
this.baseKeyName = baseKeyName;
this.fileName = fileName;
}
public String getBaseKeyName() {
return this.baseKeyName;
}
public String getFileName() {
return this.fileName;
}
public static EnumConfigCategory[] getCategoryValues() {
return VALUES;
}
public static EnumConfigCategory fromBaseKeyName(String baseKeyName) {
for (EnumConfigCategory category : VALUES) {
if (category.baseKeyName.equals(baseKeyName)) {
return category;
}
}
return null;
}
}

View File

@@ -0,0 +1,211 @@
package org.dreeam.leaf.config;
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
import java.io.File;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class LeafCategoryConfig {
private static final String CURRENT_VERSION = "3.0";
private static final String CURRENT_REGION = Locale.getDefault().getCountry().toUpperCase(Locale.ROOT);
private static final boolean isCN = CURRENT_REGION.equals("CN");
private final EnumConfigCategory category;
private ConfigFile configFile;
public LeafCategoryConfig(EnumConfigCategory category, boolean init) throws Exception {
this.category = category;
this.configFile = ConfigFile.loadConfig(new File(LeafConfig.I_LEAF_CONFIG_FOLDER, category.getFileName()));
// Add version and header comments
configFile.set("config-version", CURRENT_VERSION);
configFile.addComments("config-version", pickStringRegionBased(
String.format("Leaf %s Config\nGitHub Repo: https://github.com/Winds-Studio/Leaf\nDiscord: https://discord.com/invite/gfgAwdSEuM",
category.name()),
String.format("Leaf %s Config\nGitHub Repo: https://github.com/Winds-Studio/Leaf\nQQ Group: 619278377",
category.name())
));
// Pre-structure the config
structureConfig();
}
protected void structureConfig() {
createTitledSection(category.name(), category.getBaseKeyName());
}
public void saveConfig() throws Exception {
configFile.save();
}
public EnumConfigCategory getCategory() {
return category;
}
// Config Utilities
public void createTitledSection(String title, String path) {
configFile.addSection(title);
configFile.addDefault(path, null);
}
public boolean getBoolean(String path, boolean def, String comment) {
configFile.addDefault(path, def, comment);
return configFile.getBoolean(path, def);
}
public boolean getBoolean(String path, boolean def) {
configFile.addDefault(path, def);
return configFile.getBoolean(path, def);
}
public String getString(String path, String def, String comment) {
configFile.addDefault(path, def, comment);
return configFile.getString(path, def);
}
public String getString(String path, String def) {
configFile.addDefault(path, def);
return configFile.getString(path, def);
}
public double getDouble(String path, double def, String comment) {
configFile.addDefault(path, def, comment);
return configFile.getDouble(path, def);
}
public double getDouble(String path, double def) {
configFile.addDefault(path, def);
return configFile.getDouble(path, def);
}
public int getInt(String path, int def, String comment) {
configFile.addDefault(path, def, comment);
return configFile.getInteger(path, def);
}
public int getInt(String path, int def) {
configFile.addDefault(path, def);
return configFile.getInteger(path, def);
}
public long getLong(String path, long def, String comment) {
configFile.addDefault(path, def, comment);
return configFile.getLong(path, def);
}
public long getLong(String path, long def) {
configFile.addDefault(path, def);
return configFile.getLong(path, def);
}
public List<String> getList(String path, List<String> def, String comment) {
configFile.addDefault(path, def, comment);
return configFile.getStringList(path);
}
public List<String> getList(String path, List<String> def) {
configFile.addDefault(path, def);
return configFile.getStringList(path);
}
public ConfigSection getConfigSection(String path, Map<String, Object> defaultKeyValue, String comment) {
configFile.addDefault(path, null, comment);
configFile.makeSectionLenient(path);
defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object));
return configFile.getConfigSection(path);
}
public ConfigSection getConfigSection(String path, Map<String, Object> defaultKeyValue) {
configFile.addDefault(path, null);
configFile.makeSectionLenient(path);
defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object));
return configFile.getConfigSection(path);
}
// Getter methods
public Boolean getBoolean(String path) {
String value = configFile.getString(path, null);
return value == null ? null : Boolean.parseBoolean(value);
}
public String getString(String path) {
return configFile.getString(path, null);
}
public Double getDouble(String path) {
String value = configFile.getString(path, null);
if (value == null) {
return null;
}
try {
return Double.parseDouble(value);
} catch (NumberFormatException e) {
LeafConfig.LOGGER.warn("{} is not a valid number, skipped! Please check your configuration.", path, e);
return null;
}
}
public Integer getInt(String path) {
String value = configFile.getString(path, null);
if (value == null) {
return null;
}
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
LeafConfig.LOGGER.warn("{} is not a valid number, skipped! Please check your configuration.", path, e);
return null;
}
}
public Long getLong(String path) {
String value = configFile.getString(path, null);
if (value == null) {
return null;
}
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
LeafConfig.LOGGER.warn("{} is not a valid number, skipped! Please check your configuration.", path, e);
return null;
}
}
public List<String> getList(String path) {
return configFile.getList(path, null);
}
public ConfigSection getConfigSection(String path) {
configFile.addDefault(path, null);
configFile.makeSectionLenient(path);
return configFile.getConfigSection(path);
}
public void addComment(String path, String comment) {
configFile.addComment(path, comment);
}
public void addCommentIfCN(String path, String comment) {
if (isCN) {
configFile.addComment(path, comment);
}
}
public void addCommentIfNonCN(String path, String comment) {
if (!isCN) {
configFile.addComment(path, comment);
}
}
public void addCommentRegionBased(String path, String en, String cn) {
configFile.addComment(path, isCN ? cn : en);
}
public String pickStringRegionBased(String en, String cn) {
return isCN ? cn : en;
}
}

View File

@@ -26,13 +26,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@@ -40,21 +34,18 @@ import java.util.jar.JarFile;
/*
* Yoinked from: https://github.com/xGinko/AnarchyExploitFixes/ & https://github.com/LuminolMC/Luminol
* @author: @xGinko & @MrHua269
*
* Rewritten by @Taiyou
*/
public class LeafConfig {
public static final Logger LOGGER = LogManager.getLogger(LeafConfig.class.getSimpleName());
protected static final File I_CONFIG_FOLDER = new File("config");
protected static final File I_LEAF_CONFIG_FOLDER = new File(I_CONFIG_FOLDER, "leaf");
protected static final String I_CONFIG_PKG = "org.dreeam.leaf.config.modules";
protected static final String I_GLOBAL_CONFIG_FILE = "leaf-global.yml";
protected static final String I_LEVEL_CONFIG_FILE = "leaf-world-defaults.yml"; // Leaf TODO - Per level config
private static LeafGlobalConfig leafGlobalConfig;
//private static int preMajorVer;
private static int preMinorVer;
//private static int currMajorVer;
private static int currMinorVer;
private static Map<EnumConfigCategory, LeafCategoryConfig> categoryConfigs = new HashMap<>();
/* Load & Reload */
@@ -84,6 +75,10 @@ public class LeafConfig {
LOGGER.info("Loading config...");
purgeOutdated();
// Auto-migrate if old config exists
autoMigrateIfNeeded();
loadConfig(true);
LOGGER.info("Successfully loaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000);
@@ -95,17 +90,58 @@ public class LeafConfig {
/* Load Global Config */
private static void loadConfig(boolean init) throws Exception {
// Create config folder
createDirectory(I_CONFIG_FOLDER);
// Create leaf config folder
createDirectory(I_LEAF_CONFIG_FOLDER);
leafGlobalConfig = new LeafGlobalConfig(init);
// Load all category configs
categoryConfigs.clear();
for (EnumConfigCategory category : EnumConfigCategory.getCategoryValues()) {
categoryConfigs.put(category, new LeafCategoryConfig(category, init));
}
// Load config modules
ConfigModules.initModules();
}
public static LeafGlobalConfig config() {
return leafGlobalConfig;
public static LeafCategoryConfig config(EnumConfigCategory category) {
return categoryConfigs.get(category);
}
// For backward compatibility - returns misc config
@Deprecated
public static LeafCategoryConfig config() {
return categoryConfigs.get(EnumConfigCategory.MISC);
}
/* Auto-migration logic */
private static void autoMigrateIfNeeded() {
File oldConfigFile = new File(I_CONFIG_FOLDER, I_GLOBAL_CONFIG_FILE);
if (!oldConfigFile.exists()) {
return; // No old config to migrate
}
// Check if any category files are missing
boolean needsMigration = false;
for (EnumConfigCategory category : EnumConfigCategory.getCategoryValues()) {
File categoryFile = new File(I_LEAF_CONFIG_FOLDER, category.getFileName());
if (!categoryFile.exists()) {
needsMigration = true;
break;
}
}
if (needsMigration) {
LOGGER.info("Detected old config file. Automatically migrating to multi-file system...");
try {
ConfigMigrator migrator = new ConfigMigrator();
migrator.migrate();
LOGGER.info("Migration completed successfully!");
} catch (Exception e) {
LOGGER.error("Auto-migration failed!", e);
throw new RuntimeException("Failed to migrate config", e);
}
}
}
/* Create config folder */
@@ -203,22 +239,19 @@ public class LeafConfig {
}
}
// TODO
public static void loadConfigVersion(String preVer, String currVer) {
int currMinor;
int preMinor;
// First time user
if (preVer == null) {
}
}
/* Register Spark profiler extra server configurations */
private static List<String> buildSparkExtraConfigs() {
List<String> extraConfigs = new ArrayList<>(Arrays.asList(
"config/leaf-global.yml",
public static void regSparkExtraConfig() {
if (GlobalConfiguration.get().spark.enabled || Bukkit.getServer().getPluginManager().getPlugin("spark") != null) {
List<String> extraConfigs = new ArrayList<>();
// Add all category config files from config/leaf folder (individual files)
for (EnumConfigCategory category : EnumConfigCategory.getCategoryValues()) {
extraConfigs.add("config/leaf/" + category.getFileName());
}
// Add other configs
extraConfigs.addAll(Arrays.asList(
"config/gale-global.yml",
"config/gale-world-defaults.yml"
));
@@ -236,7 +269,12 @@ public class LeafConfig {
extraConfigs.add(galeWorldFolder.toString().replace("\\", "/").replace("./", "")); // Gale world config
}
return extraConfigs;
String extraConfigsStr = String.join(",", extraConfigs);
String hiddenPaths = String.join(",", buildSparkHiddenPaths());
System.setProperty("spark.serverconfigs.extra", extraConfigsStr);
System.setProperty("spark.serverconfigs.hiddenpaths", hiddenPaths);
}
}
private static List<String> buildSparkHiddenPaths() {
@@ -249,16 +287,6 @@ public class LeafConfig {
return extraHidden;
}
public static void regSparkExtraConfig() {
if (GlobalConfiguration.get().spark.enabled || Bukkit.getServer().getPluginManager().getPlugin("spark") != null) {
String extraConfigs = String.join(",", buildSparkExtraConfigs());
String hiddenPaths = String.join(",", buildSparkHiddenPaths());
System.setProperty("spark.serverconfigs.extra", extraConfigs);
System.setProperty("spark.serverconfigs.hiddenpaths", hiddenPaths);
}
}
/* Purge and backup old Leaf config & Pufferfish config */
private static void purgeOutdated() {
@@ -295,7 +323,7 @@ public class LeafConfig {
if (foundLegacy) {
LOGGER.warn("Found legacy Leaf config files, move to backup directory: {}", backupDir);
LOGGER.warn("New Leaf config located at config/ folder, You need to transfer config to the new one manually and restart the server!");
LOGGER.warn("New Leaf config located at config/leaf/ folder, You need to transfer config to the new one manually and restart the server!");
}
} catch (IOException e) {
LOGGER.error("Failed to purge old configs.", e);

View File

@@ -10,7 +10,7 @@ import java.util.Map;
public class LeafGlobalConfig {
private static final String CURRENT_VERSION = "3.0";
private static final String CURRENT_VERSION = "4.0";
private static final String CURRENT_REGION = Locale.getDefault().getCountry().toUpperCase(Locale.ROOT); // It will be in uppercase by default, just make sure
private static final boolean isCN = CURRENT_REGION.equals("CN");
@@ -19,7 +19,6 @@ public class LeafGlobalConfig {
public LeafGlobalConfig(boolean init) throws Exception {
configFile = ConfigFile.loadConfig(new File(LeafConfig.I_CONFIG_FOLDER, LeafConfig.I_GLOBAL_CONFIG_FILE));
LeafConfig.loadConfigVersion(getString("config-version"), CURRENT_VERSION);
configFile.set("config-version", CURRENT_VERSION);
configFile.addComments("config-version", pickStringRegionBased("""