add storage migrate command to migrate from one storage type to another

This commit is contained in:
D3v1s0m 2024-08-18 22:52:09 +05:30
parent 304278edd8
commit 79ede2b586
No known key found for this signature in database
GPG key ID: FA1F770C7B1D40C1
5 changed files with 204 additions and 3 deletions

View file

@ -23,6 +23,7 @@ import lol.pyr.znpcsplus.commands.property.PropertyRemoveCommand;
import lol.pyr.znpcsplus.commands.property.PropertySetCommand;
import lol.pyr.znpcsplus.commands.storage.ImportCommand;
import lol.pyr.znpcsplus.commands.storage.LoadAllCommand;
import lol.pyr.znpcsplus.commands.storage.MigrateCommand;
import lol.pyr.znpcsplus.commands.storage.SaveAllCommand;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.conversion.DataImporterRegistry;
@ -39,6 +40,7 @@ import lol.pyr.znpcsplus.scheduling.SpigotScheduler;
import lol.pyr.znpcsplus.scheduling.TaskScheduler;
import lol.pyr.znpcsplus.skin.cache.MojangSkinCache;
import lol.pyr.znpcsplus.skin.cache.SkinCacheCleanTask;
import lol.pyr.znpcsplus.storage.NpcStorageType;
import lol.pyr.znpcsplus.tasks.HologramRefreshTask;
import lol.pyr.znpcsplus.tasks.NpcProcessorTask;
import lol.pyr.znpcsplus.tasks.ViewableHideOnLeaveListener;
@ -157,7 +159,7 @@ public class ZNpcsPlus {
pluginManager.registerEvents(new UserListener(userManager), bootstrap);
registerCommands(npcRegistry, skinCache, adventure, actionRegistry,
typeRegistry, propertyRegistry, importerRegistry, configManager);
typeRegistry, propertyRegistry, importerRegistry, configManager, packetFactory);
log(ChatColor.WHITE + " * Starting tasks...");
if (configManager.getConfig().checkForUpdates()) {
@ -243,7 +245,7 @@ public class ZNpcsPlus {
private void registerCommands(NpcRegistryImpl npcRegistry, MojangSkinCache skinCache, BukkitAudiences adventure,
ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry,
EntityPropertyRegistryImpl propertyRegistry, DataImporterRegistry importerRegistry,
ConfigManager configManager) {
ConfigManager configManager, PacketFactory packetFactory) {
Message<CommandContext> incorrectUsageMessage = context -> context.send(Component.text("Incorrect usage: /" + context.getUsage(), NamedTextColor.RED));
CommandManager manager = new CommandManager(bootstrap, adventure, incorrectUsageMessage);
@ -292,6 +294,7 @@ public class ZNpcsPlus {
registerEnumParser(manager, Sound.class, incorrectUsageMessage);
registerEnumParser(manager, ArmadilloState.class, incorrectUsageMessage);
registerEnumParser(manager, WoldVariant.class, incorrectUsageMessage);
registerEnumParser(manager, NpcStorageType.class, incorrectUsageMessage);
manager.registerCommand("npc", new MultiCommand(bootstrap.loadHelpMessage("root"))
.addSubcommand("center", new CenterCommand(npcRegistry))
@ -316,7 +319,8 @@ public class ZNpcsPlus {
.addSubcommand("storage", new MultiCommand(bootstrap.loadHelpMessage("storage"))
.addSubcommand("save", new SaveAllCommand(npcRegistry))
.addSubcommand("reload", new LoadAllCommand(npcRegistry))
.addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry)))
.addSubcommand("import", new ImportCommand(npcRegistry, importerRegistry))
.addSubcommand("migrate", new MigrateCommand(configManager, this, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer, npcRegistry.getStorage(), configManager.getConfig().storageType(), npcRegistry)))
.addSubcommand("holo", new MultiCommand(bootstrap.loadHelpMessage("holo"))
.addSubcommand("add", new HoloAddCommand(npcRegistry))
.addSubcommand("additem", new HoloAddItemCommand(npcRegistry))

View file

@ -0,0 +1,176 @@
package lol.pyr.znpcsplus.commands.storage;
import lol.pyr.director.adventure.command.CommandContext;
import lol.pyr.director.adventure.command.CommandHandler;
import lol.pyr.director.common.command.CommandExecutionException;
import lol.pyr.znpcsplus.ZNpcsPlus;
import lol.pyr.znpcsplus.config.ConfigManager;
import lol.pyr.znpcsplus.entity.EntityPropertyRegistryImpl;
import lol.pyr.znpcsplus.interaction.ActionRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcEntryImpl;
import lol.pyr.znpcsplus.npc.NpcRegistryImpl;
import lol.pyr.znpcsplus.npc.NpcTypeRegistryImpl;
import lol.pyr.znpcsplus.packets.PacketFactory;
import lol.pyr.znpcsplus.storage.NpcStorage;
import lol.pyr.znpcsplus.storage.NpcStorageType;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
public class MigrateCommand implements CommandHandler {
private final ConfigManager configManager;
private final ZNpcsPlus plugin;
private final PacketFactory packetFactory;
private final ActionRegistryImpl actionRegistry;
private final NpcTypeRegistryImpl typeRegistry;
private final EntityPropertyRegistryImpl propertyRegistry;
private final LegacyComponentSerializer textSerializer;
private final NpcStorage currentStorage;
private final NpcStorageType currentStorageType;
private final NpcRegistryImpl npcRegistry;
public MigrateCommand(ConfigManager configManager, ZNpcsPlus plugin, PacketFactory packetFactory, ActionRegistryImpl actionRegistry, NpcTypeRegistryImpl typeRegistry, EntityPropertyRegistryImpl propertyRegistry, LegacyComponentSerializer textSerializer, NpcStorage currentStorage, NpcStorageType currentStorageType, NpcRegistryImpl npcRegistry) {
this.configManager = configManager;
this.plugin = plugin;
this.packetFactory = packetFactory;
this.actionRegistry = actionRegistry;
this.typeRegistry = typeRegistry;
this.propertyRegistry = propertyRegistry;
this.textSerializer = textSerializer;
this.currentStorage = currentStorage;
this.currentStorageType = currentStorageType;
this.npcRegistry = npcRegistry;
}
@Override
public void run(CommandContext context) throws CommandExecutionException {
context.setUsage(context.getLabel() + " storage migrate <from> <to> [force]");
NpcStorageType from = context.parse(NpcStorageType.class);
NpcStorageType to = context.parse(NpcStorageType.class);
boolean force = context.argSize() > 2 && context.parse(Boolean.class);
if (from.equals(to)) {
context.halt(Component.text("The storage types must be different.", NamedTextColor.RED));
return;
}
NpcStorage fromStorage;
if (currentStorageType == from) {
fromStorage = currentStorage;
} else {
fromStorage = from.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
if (fromStorage == null) {
context.halt(Component.text("Failed to initialize the source storage. Please check the console for more information.", NamedTextColor.RED));
return;
}
}
Collection<NpcEntryImpl> entries;
try {
entries = fromStorage.loadNpcs();
} catch (Exception e) {
context.halt(Component.text("Failed to load NPCs from the source storage.", NamedTextColor.RED));
return;
}
if (entries.isEmpty()) {
context.send(Component.text("No NPCs to migrate.", NamedTextColor.YELLOW));
return;
}
NpcStorage toStorage;
if (currentStorageType == to) {
toStorage = currentStorage;
} else {
toStorage = to.create(configManager, plugin, packetFactory, actionRegistry, typeRegistry, propertyRegistry, textSerializer);
if (toStorage == null) {
context.halt(Component.text("Failed to initialize the destination storage. Please check the console for more information.", NamedTextColor.RED));
return;
}
}
Collection<NpcEntryImpl> existingEntries;
try {
existingEntries = toStorage.loadNpcs();
} catch (Exception e) {
context.halt(Component.text("Failed to load NPCs from the destination storage.", NamedTextColor.RED));
return;
}
if (existingEntries.isEmpty()) {
toStorage.saveNpcs(entries);
context.send(Component.text("Migrated " + entries.size() + " NPCs from the source storage (", NamedTextColor.GREEN)
.append(Component.text(from.name(), NamedTextColor.GOLD))
.append(Component.text(") to the destination storage (", NamedTextColor.GREEN))
.append(Component.text(to.name(), NamedTextColor.GOLD))
.append(Component.text(").", NamedTextColor.GREEN)));
if (currentStorageType == to) {
npcRegistry.reload();
} else {
toStorage.close();
}
return;
}
if (!force) {
Collection<NpcEntryImpl> toSave = entries.stream().filter(e -> existingEntries.stream().noneMatch(e2 -> e2.getId().equals(e.getId()))).collect(Collectors.toList());
Collection<NpcEntryImpl> idExists = entries.stream().filter(e -> existingEntries.stream().anyMatch(e2 -> e2.getId().equals(e.getId()))).collect(Collectors.toList());
if (toSave.isEmpty()) {
context.send(Component.text("No NPCs to migrate.", NamedTextColor.YELLOW));
if (currentStorageType != to) {
toStorage.close();
}
} else {
toStorage.saveNpcs(toSave);
context.send(Component.text("Migrated " + toSave.size() + " NPCs from the source storage (", NamedTextColor.GREEN)
.append(Component.text(from.name(), NamedTextColor.GOLD))
.append(Component.text(") to the destination storage (", NamedTextColor.GREEN))
.append(Component.text(to.name(), NamedTextColor.GOLD))
.append(Component.text(").", NamedTextColor.GREEN)));
if (currentStorageType == to) {
npcRegistry.reload();
} else {
toStorage.close();
}
}
if (!idExists.isEmpty()) {
AtomicReference<Component> component = new AtomicReference<>(Component.text("The following NPCs were not migrated because their IDs already exist in the destination storage:").color(NamedTextColor.YELLOW));
idExists.forEach(e -> {
component.set(component.get().append(Component.newline()).append(Component.text(e.getId(), NamedTextColor.RED)));
});
component.set(component.get().append(Component.newline())
.append(Component.text("Use the ", NamedTextColor.YELLOW))
.append(Component.text("force", NamedTextColor.GOLD))
.append(Component.text(" argument to overwrite them.", NamedTextColor.YELLOW)));
context.send(component.get());
}
} else {
toStorage.saveNpcs(entries);
context.send(Component.text("Force migrated " + entries.size() + " NPCs from the source storage (", NamedTextColor.GREEN)
.append(Component.text(from.name(), NamedTextColor.GOLD))
.append(Component.text(") to the destination storage (", NamedTextColor.GREEN))
.append(Component.text(to.name(), NamedTextColor.GOLD))
.append(Component.text(").", NamedTextColor.GREEN)));
if (currentStorageType == to) {
npcRegistry.reload();
} else {
toStorage.close();
}
}
}
@Override
public List<String> suggest(CommandContext context) throws CommandExecutionException {
if (context.argSize() == 1) {
return context.suggestEnum(NpcStorageType.values());
} else if (context.argSize() == 2) {
NpcStorageType from = context.suggestionParse(0, NpcStorageType.class);
if (from == null) return Collections.emptyList();
return context.suggestCollection(Arrays.stream(NpcStorageType.values())
.filter(t -> t != from).map(Enum::name).collect(Collectors.toList()));
} else if (context.argSize() == 3) {
return context.suggestLiteral("true");
}
return Collections.emptyList();
}
}

View file

@ -209,4 +209,8 @@ public class NpcRegistryImpl implements NpcRegistry {
npcList.forEach(npcEntry -> npcEntry.getNpc().delete());
storage.close();
}
public NpcStorage getStorage() {
return storage;
}
}

View file

@ -0,0 +1,16 @@
<gray>Usage <gold>» <yellow>/npc storage migrate <gold><from> <to> [force]
<gray>Storage Types:
<gold>* <yellow>YAML <gray>- Npcs are stored in yaml files
<gold>* <yellow>SQLite <gray>- Npcs are stored in a SQLite database
<gold>* <yellow>MySQL <gray>- Npcs are stored in a MySQL database
<gray>Command used to migrate npcs from one storage type to another.
This command will NOT delete the original storage files or database,
but will copy the npcs to the new storage type.
<gray>This will also not overwrite any existing npcs in the new storage
type, unless the <gold>force <gray>argument is set to <gold>true<gray>.
<red>Warning: <bold>force</bold> will overwrite any existing npcs with the same id
in the new storage type and CANNOT be undone.

View file

@ -5,4 +5,5 @@
<hover:show_text:'{@storage-hover/save}'><gold>* <yellow>/npc storage save</hover>
<hover:show_text:'{@storage-hover/reload}'><gold>* <yellow>/npc storage reload</hover>
<hover:show_text:'{@storage-hover/import}'><gold>* <yellow>/npc storage import <importer></hover>
<hover:show_text:'{@storage-hover/migrate}'><gold>* <yellow>/npc storage migrate <from> <to> [force]</hover>