Skip to content

Commit

Permalink
Add JSON & YAML storage methods and improve storage type migration
Browse files Browse the repository at this point in the history
  • Loading branch information
srnyx committed Oct 3, 2024
1 parent e74dad0 commit ae11fa1
Show file tree
Hide file tree
Showing 22 changed files with 1,732 additions and 1,199 deletions.
146 changes: 10 additions & 136 deletions src/main/java/xyz/srnyx/annoyingapi/AnnoyingPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@
import xyz.srnyx.annoyingapi.data.storage.DataManager;
import xyz.srnyx.annoyingapi.data.ItemData;
import xyz.srnyx.annoyingapi.data.storage.StorageConfig;
import xyz.srnyx.annoyingapi.data.storage.dialects.SQLDialect;
import xyz.srnyx.annoyingapi.data.storage.dialects.sql.SQLDialect;
import xyz.srnyx.annoyingapi.dependency.AnnoyingDependency;
import xyz.srnyx.annoyingapi.dependency.AnnoyingDownload;
import xyz.srnyx.annoyingapi.events.AdvancedPlayerMoveEvent;
import xyz.srnyx.annoyingapi.events.PlayerDamageByPlayerEvent;
import xyz.srnyx.annoyingapi.file.AnnoyingFile;
import xyz.srnyx.annoyingapi.file.AnnoyingResource;
import xyz.srnyx.annoyingapi.options.*;
import xyz.srnyx.annoyingapi.parents.Registrable;
Expand All @@ -39,14 +38,8 @@
import xyz.srnyx.javautilities.MapGenerator;
import xyz.srnyx.javautilities.objects.SemanticVersion;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;
import java.util.function.Supplier;
Expand Down Expand Up @@ -181,7 +174,7 @@ public final void onEnable() {
*/
@Override
public final void onDisable() {
if (dataManager != null && dataManager.storageConfig.cache.saveOn.contains(StorageConfig.SaveOn.DISABLE)) dataManager.saveCache();
if (dataManager != null && dataManager.storageConfig.cache.saveOn.contains(StorageConfig.SaveOn.DISABLE)) dataManager.dialect.saveCache();
disable();
}

Expand Down Expand Up @@ -291,7 +284,7 @@ private void enablePlugin() {
}

// Start cache saving on interval if enabled
if (dataManager != null && dataManager.storageConfig.cache.saveOn.contains(StorageConfig.SaveOn.INTERVAL)) dataManager.startCacheSavingOnInterval(this, dataManager.storageConfig.cache.interval);
if (dataManager != null && dataManager.storageConfig.cache.saveOn.contains(StorageConfig.SaveOn.INTERVAL)) dataManager.startCacheSavingOnInterval();

// Custom onEnable
enable();
Expand Down Expand Up @@ -328,7 +321,7 @@ public void loadMessages() {
public void disablePlugin() {
new HashSet<>(registeredClasses).forEach(Registrable::unregister);
Bukkit.getScheduler().cancelTasks(this);
if (dataManager != null) dataManager.saveCache();
if (dataManager != null) dataManager.dialect.saveCache();
Bukkit.getPluginManager().disablePlugin(this);
}

Expand Down Expand Up @@ -376,7 +369,7 @@ public void checkUpdate() {

/**
* Attempts to load the {@link #dataManager}, catching any exceptions and logging them
* <br>If {@code storage-new.yml} exists, it will attempt to migrate the data from {@code storage.yml} to {@code storage-new.yml} using {@link #attemptDatabaseMigration(DataManager)}
* <br>If {@code storage-new.yml} exists, it will attempt to migrate the data from {@code storage.yml} to {@code storage-new.yml} using {@link DataManager#attemptDatabaseMigration()}
*
* @param saveCache whether to save the cache before loading the data manager
* <br><i>Data may be lost if {@code false}!</i>
Expand All @@ -385,10 +378,10 @@ public void loadDataManger(boolean saveCache) {
// Check if a manager is already loaded
if (dataManager != null) {
// Save cache
if (saveCache) dataManager.saveCache();
if (saveCache) dataManager.dialect.saveCache();
// Close previous connection
try {
dataManager.connection.close();
if (dataManager.dialect instanceof SQLDialect) try {
((SQLDialect) dataManager.dialect).connection.close();
} catch (final SQLException e) {
log(Level.SEVERE, "&cFailed to close the database connection, it's recommended to restart the server!", e);
}
Expand All @@ -410,129 +403,10 @@ public void loadDataManger(boolean saveCache) {
}

// Attempt database migration
dataManager = attemptDatabaseMigration(dataManager);
dataManager = dataManager.attemptDatabaseMigration();

// Create tables/columns
dataManager.createTablesColumns(options.dataOptions.tables);
}

/**
* Attempts to migrate data from {@code storage.yml} to {@code storage-new.yml}
*
* @param oldManager the old data manager
*
* @return the new data manager
*/
@NotNull
private DataManager attemptDatabaseMigration(@NotNull DataManager oldManager) {
// Check if migration is needed (storage-new.yml exists)
final File dataFolder = getDataFolder();
final File storageNew = new File(dataFolder, "storage-new.yml");
if (!storageNew.exists()) return oldManager;
log(Level.INFO, "&aFound &2storage-new.yml&a, attempting to migrate data from &2storage.yml&a to &2storage-new.yml&a...");

// NEW: Connect to new database
final AnnoyingFile<?> storageNewFile = new AnnoyingFile<>(this, storageNew, new AnnoyingFile.Options<>().canBeEmpty(false));
if (!storageNewFile.load()) return oldManager;
final DataManager newManager;
try {
newManager = new DataManager(storageNewFile);
} catch (final ConnectionException e) {
log(Level.SEVERE, "&4storage-new.yml &8|&c Failed to connect to database! URL: '&4" + e.url + "&c' Properties: &4" + e.getPropertiesRedacted(), e);
return oldManager;
}

// OLD: Get tables & columns
final Set<String> tables = new HashSet<>();
try (final PreparedStatement getTables = oldManager.dialect.getTables()) {
final ResultSet resultSet = getTables.executeQuery();
while (resultSet.next()) tables.add(resultSet.getString(1));
} catch (final SQLException e) {
log(Level.SEVERE, "&4" + newManager.storageConfig.getMigrationName() + " &8|&c Failed to get tables!", e);
return oldManager;
}

// OLD: Get values
final Map<String, Set<String>> tablesColumns = new HashMap<>(); // {Table, Columns}
final Map<String, Map<String, Map<String, String>>> values = new HashMap<>(); // {Table, {Target, {Column, Value}}}
final int oldPrefixLength = oldManager.tablePrefix.length();
for (final String table : tables) {
final String tableWithoutPrefix = table.substring(oldPrefixLength);
final Map<String, Map<String, String>> tableValues = new HashMap<>(); // {Target, {Column, Value}}
try (final PreparedStatement getValues = oldManager.dialect.getValues(table)) {
final ResultSet resultSet = getValues.executeQuery();

// Continue if table doesn't have target column
final Set<String> columns = new HashSet<>();
final ResultSetMetaData metaData = resultSet.getMetaData();
final int columnCount = metaData.getColumnCount();
if (columnCount == 0) {
log(Level.WARNING, "&4" + oldManager.storageConfig.getMigrationName() + " &8|&c Table &4" + table + "&c has no columns, skipping...");
continue;
}
for (int i = 1; i <= columnCount; i++) columns.add(metaData.getColumnName(i));
if (!columns.contains("target")) {
log(Level.WARNING, "&4" + oldManager.storageConfig.getMigrationName() + " &8|&c Table &4" + table + "&c doesn't have a '&4target&c' column, skipping...");
continue;
}
tablesColumns.put(tableWithoutPrefix, columns);

// Get values for each target
while (resultSet.next()) {
final String target = resultSet.getString("target");
if (target == null) continue;
final Map<String, String> columnValues = new HashMap<>(); // {Column, Value}
for (final String column : columns) if (!column.equals("target")) columnValues.put(column, resultSet.getString(column));
tableValues.put(target, columnValues);
}
} catch (final SQLException e) {
log(Level.SEVERE, "&4" + oldManager.storageConfig.getMigrationName() + " &8|&c Failed to get values for table &4" + table, e);
}
if (!tableValues.isEmpty()) values.put(newManager.getTableName(tableWithoutPrefix), tableValues);
}

if (!values.isEmpty()) {
// NEW: Create missing tables/columns
newManager.createTablesColumns(tablesColumns);

// NEW: Save values to new database
for (final SQLDialect.SetValueStatement statement : newManager.dialect.setValues(values)) try {
statement.statement.executeUpdate();
} catch (final SQLException e) {
log(Level.SEVERE, "&4" + newManager.storageConfig.getMigrationName() + " &8|&c Failed to migrate values for &4" + statement.target + "&c in table &4" + statement.table + "&c: &4" + statement.values, e);
}
} else {
log(Level.WARNING, "&4" + oldManager.storageConfig.getMigrationName() + " &8|&c Found no data to migrate! This may or may not be an error...");
}

// OLD: Close old connection
try {
oldManager.connection.close();
} catch (final SQLException e) {
log(Level.SEVERE, "&cFailed to close the old database connection, it's recommended to restart the server!", e);
}

// PREV: Delete storage-old.yml if it exists (from a previous migration)
final File storageOld = new File(dataFolder, "storage-old.yml");
if (storageOld.exists()) try {
Files.delete(storageOld.toPath());
} catch (final IOException e) {
log(Level.SEVERE, "&cFailed to delete previous &4storage-old.yml!");
}

// Rename files
final File storage = oldManager.storageConfig.file.file;
if (storage.renameTo(storageOld)) { // OLD: storage.yml -> storage-old.yml
if (!storageNew.renameTo(storage)) { // NEW: storage-new.yml -> storage.yml
log(Level.SEVERE, "Failed to rename &4storage-new.yml&c to &4storage.yml&c! You MUST rename &4storage-new.yml&c to &4storage.yml&c manually!");
}
} else {
log(Level.SEVERE, "Failed to rename &4storage.yml&c to &4storage-old.yml&c! You MUST rename &4storage.yml&c to &4storage-old.yml&c and &4storage-new.yml&c to &4storage.yml&c manually!");
}

// NEW: Use new storage
log(Level.INFO, "&aFinished migrating data from &2storage.yml&a to &2storage-new.yml&a!");
return newManager;
if (dataManager.dialect instanceof SQLDialect) ((SQLDialect) dataManager.dialect).createTablesKeys(options.dataOptions.tables);
}

/**
Expand Down
37 changes: 16 additions & 21 deletions src/main/java/xyz/srnyx/annoyingapi/data/EntityData.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package xyz.srnyx.annoyingapi.data;

import com.google.common.collect.ImmutableMap;

import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Entity;

Expand Down Expand Up @@ -60,7 +58,7 @@ public EntityData(@NotNull AnnoyingPlugin plugin, @NotNull Entity entity) {
@Nullable @SuppressWarnings("deprecation")
public Map<String, String> convertOldData(boolean onlyTryOnce, @Nullable Collection<String> keys) {
// 1.14+ (persistent data container)
if (NAMESPACED_KEY_CONSTRUCTOR != null && PERSISTENT_DATA_HOLDER_GET_PERSISTENT_DATA_CONTAINER_METHOD != null && PERSISTENT_DATA_CONTAINER_GET_METHOD != null && PERSISTENT_DATA_CONTAINER_SET_METHOD != null && PERSISTENT_DATA_CONTAINER_REMOVE_METHOD != null && PERSISTENT_DATA_TYPE_STRING != null && PERSISTENT_DATA_TYPE_BYTE != null) {
if (NAMESPACED_KEY_CONSTRUCTOR != null && PERSISTENT_DATA_HOLDER_GET_PERSISTENT_DATA_CONTAINER_METHOD != null && PERSISTENT_DATA_CONTAINER_GET_METHOD != null && PERSISTENT_DATA_CONTAINER_SET_METHOD != null && PERSISTENT_DATA_TYPE_STRING != null && PERSISTENT_DATA_TYPE_BYTE != null) {
final Object persistentDataContainer;
final Object convertedKey;
try {
Expand All @@ -76,8 +74,8 @@ public Map<String, String> convertOldData(boolean onlyTryOnce, @Nullable Collect

// Get keys
final Set<Map.Entry<String, ?>> namespacedKeys;
// 1.16.1+ (getKeys)
if (PERSISTENT_DATA_CONTAINER_GET_KEYS_METHOD != null && NAMESPACED_KEY_GET_NAMESPACE_METHOD != null && NAMESPACED_KEY_GET_KEY_METHOD != null) {
// 1.16.1+ (getKeys)
final String pluginName = plugin.getName().toLowerCase();
try {
namespacedKeys = ((Set<?>) PERSISTENT_DATA_CONTAINER_GET_KEYS_METHOD.invoke(persistentDataContainer)).stream()
Expand All @@ -94,8 +92,8 @@ public Map<String, String> convertOldData(boolean onlyTryOnce, @Nullable Collect
sendError("convert", e);
return null;
}
// 1.14.x (provided keys)
} else {
// 1.14.x (provided keys)
if (keys == null || keys.isEmpty()) return Collections.emptyMap();
namespacedKeys = keys.stream()
.map(key -> {
Expand All @@ -118,15 +116,12 @@ public Map<String, String> convertOldData(boolean onlyTryOnce, @Nullable Collect
final String value = (String) PERSISTENT_DATA_CONTAINER_GET_METHOD.invoke(persistentDataContainer, namespacedKey, PERSISTENT_DATA_TYPE_STRING);
if (value == null) continue;
final String key = entry.getKey();
if (!set(key, value)) {
failed.put(key, value);
if (!onlyTryOnce) continue;
}
PERSISTENT_DATA_CONTAINER_REMOVE_METHOD.invoke(persistentDataContainer, namespacedKey);
if (!set(key, value)) failed.put(key, value);
}
// Set converted key
if (failed.isEmpty() || onlyTryOnce) PERSISTENT_DATA_CONTAINER_SET_METHOD.invoke(persistentDataContainer, convertedKey, PERSISTENT_DATA_TYPE_BYTE, (byte) 1);
return ImmutableMap.copyOf(failed);
// Return failures
return failed;
} catch (final ReflectiveOperationException e) {
sendError("convert", e);
return null;
Expand All @@ -136,20 +131,20 @@ public Map<String, String> convertOldData(boolean onlyTryOnce, @Nullable Collect
// 1.13.2- (file)
final AnnoyingData file = new AnnoyingData(plugin, plugin.options.dataOptions.entities.path + "/" + target + ".yml", plugin.options.dataOptions.entities.fileOptions);
final ConfigurationSection section = file.getConfigurationSection(plugin.options.dataOptions.entities.section);
if (section == null) return Collections.emptyMap();
if (section == null || section.getBoolean("api_converted")) return Collections.emptyMap();
// Convert
final Map<String, String> failed = new HashMap<>();
for (final String key : section.getKeys(false)) {
final String value = section.getString(key);
if (value == null) continue;
if (!set(key, value)) {
failed.put(key, value);
continue;
}
section.set(key, null);
if (value != null && !set(key, value)) failed.put(key, value);
}
// Set converted key
if (failed.isEmpty() || onlyTryOnce) {
section.set("api_converted", true);
file.save();
}
if (section.getKeys(false).isEmpty()) file.set(plugin.options.dataOptions.entities.section, null);
file.save();
return ImmutableMap.copyOf(failed);
// Return failures
return failed;
}

/**
Expand Down
Loading

0 comments on commit ae11fa1

Please sign in to comment.