Rework blueprint and blueprint bundle names. (#2026)

* Fixes issue when blueprint clipboard was stuck after saving.

The issue was that Map#putIfAbsent still creates a new task, even if object is already in map. The usage requires to use Map#computeIfAbsent.

* Rework blueprint and blueprint bundle names.

There was an issue with using non-english characters in blueprint names. It was not possible, as all chars for names were converted to lower cased english letters. It included display names.

I reworked it a bit and now it should be possible to set non-english names for bundles and blueprints.

Fixes #1954
This commit is contained in:
BONNe 2022-09-29 18:44:07 +03:00 committed by GitHub
parent 12926f9ee7
commit 35ce1a7d81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 479 additions and 277 deletions

View File

@ -62,20 +62,30 @@ public class AdminBlueprintCommand extends ConfirmableCommand {
return clipboards;
}
protected void showClipboard(User user) {
displayClipboards.putIfAbsent(user, Bukkit.getScheduler().scheduleSyncRepeatingTask(getPlugin(), () -> {
if (!user.isPlayer() || !user.getPlayer().isOnline()) {
hideClipboard(user);
}
if (clipboards.containsKey(user.getUniqueId())) {
BlueprintClipboard clipboard = clipboards.get(user.getUniqueId());
paintAxis(user, clipboard);
}
/**
* This method shows clipboard for requested user.
* @param user User who need to see clipboard.
*/
protected void showClipboard(User user)
{
this.displayClipboards.computeIfAbsent(user,
key -> Bukkit.getScheduler().scheduleSyncRepeatingTask(this.getPlugin(), () ->
{
if (!key.isPlayer() || !key.getPlayer().isOnline())
{
this.hideClipboard(key);
}
}, 20, 20));
if (this.clipboards.containsKey(key.getUniqueId()))
{
BlueprintClipboard clipboard = this.clipboards.get(key.getUniqueId());
this.paintAxis(key, clipboard);
}
}, 20, 20));
}
private void paintAxis(User user, BlueprintClipboard clipboard) {
if (clipboard.getPos1() == null || clipboard.getPos2() == null) {
return;

View File

@ -8,49 +8,63 @@ import java.util.Optional;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.util.Util;
/**
* Command that deletes a Blueprint.
* @author Poslovitch
* @since 1.9.0
*/
public class AdminBlueprintDeleteCommand extends ConfirmableCommand {
public AdminBlueprintDeleteCommand(AdminBlueprintCommand parent) {
public class AdminBlueprintDeleteCommand extends ConfirmableCommand
{
public AdminBlueprintDeleteCommand(AdminBlueprintCommand parent)
{
super(parent, "delete", "remove");
}
@Override
public void setup() {
inheritPermission();
setParametersHelp("commands.admin.blueprint.delete.parameters");
setDescription("commands.admin.blueprint.delete.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() != 1) {
showHelp(this, user);
public void setup()
{
this.inheritPermission();
this.setParametersHelp("commands.admin.blueprint.delete.parameters");
this.setDescription("commands.admin.blueprint.delete.description");
}
@Override
public boolean execute(User user, String label, List<String> args)
{
if (args.size() != 1)
{
this.showHelp(this, user);
return false;
}
String blueprintName = args.get(0).toLowerCase(Locale.ENGLISH);
String blueprintName = Util.sanitizeInput(args.get(0));
// Check if blueprint exist
if (getPlugin().getBlueprintsManager().getBlueprints(getAddon()).containsKey(blueprintName)) {
askConfirmation(user, user.getTranslation("commands.admin.blueprint.delete.confirmation"), () -> {
getPlugin().getBlueprintsManager().deleteBlueprint(getAddon(), blueprintName);
user.sendMessage("commands.admin.blueprint.delete.success", TextVariables.NAME, blueprintName);
});
if (this.getPlugin().getBlueprintsManager().getBlueprints(this.getAddon()).containsKey(blueprintName))
{
this.askConfirmation(user, user.getTranslation("commands.admin.blueprint.delete.confirmation"),
() -> {
this.getPlugin().getBlueprintsManager().deleteBlueprint(this.getAddon(), blueprintName);
user.sendMessage("commands.admin.blueprint.delete.success", TextVariables.NAME, blueprintName);
});
return true;
} else {
}
else
{
user.sendMessage("commands.admin.blueprint.delete.no-blueprint", TextVariables.NAME, blueprintName);
return false;
}
}
@Override
public Optional<List<String>> tabComplete(User user, String alias, List<String> args) {
return Optional.of(new LinkedList<>(getPlugin().getBlueprintsManager().getBlueprints(getAddon()).keySet()));
public Optional<List<String>> tabComplete(User user, String alias, List<String> args)
{
return Optional.of(new LinkedList<>(this.getPlugin().getBlueprintsManager().getBlueprints(this.getAddon()).keySet()));
}
}
}

View File

@ -5,51 +5,66 @@ import java.io.FilenameFilter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.managers.BlueprintsManager;
public class AdminBlueprintListCommand extends CompositeCommand {
public class AdminBlueprintListCommand extends CompositeCommand
{
public AdminBlueprintListCommand(AdminBlueprintCommand parent) {
public AdminBlueprintListCommand(AdminBlueprintCommand parent)
{
super(parent, "list");
}
@Override
public void setup() {
inheritPermission();
setDescription("commands.admin.blueprint.list.description");
public void setup()
{
this.inheritPermission();
this.setDescription("commands.admin.blueprint.list.description");
}
@Override
public boolean canExecute(User user, String label, List<String> args) {
if (!args.isEmpty()) {
showHelp(this, user);
public boolean canExecute(User user, String label, List<String> args)
{
if (!args.isEmpty())
{
this.showHelp(this, user);
return false;
}
return true;
}
@Override
public boolean execute(User user, String label, List<String> args) {
File blueprints = new File(getAddon().getDataFolder(), BlueprintsManager.FOLDER_NAME);
if (!blueprints.exists()) {
public boolean execute(User user, String label, List<String> args)
{
File blueprints = new File(this.getAddon().getDataFolder(), BlueprintsManager.FOLDER_NAME);
if (!blueprints.exists())
{
user.sendMessage("commands.admin.blueprint.list.no-blueprints");
return false;
}
FilenameFilter blueprintFilter = (File dir, String name) -> name.toLowerCase(java.util.Locale.ENGLISH).endsWith(BlueprintsManager.BLUEPRINT_SUFFIX);
List<String> blueprintList = Arrays.stream(Objects.requireNonNull(blueprints.list(blueprintFilter))).map(name -> name.substring(0, name.length() - BlueprintsManager.BLUEPRINT_SUFFIX.length())).collect(Collectors.toList());
if (blueprintList.isEmpty()) {
FilenameFilter blueprintFilter = (File dir, String name) -> name.endsWith(BlueprintsManager.BLUEPRINT_SUFFIX);
List<String> blueprintList = Arrays.stream(Objects.requireNonNull(blueprints.list(blueprintFilter))).
map(name -> name.substring(0, name.length() - BlueprintsManager.BLUEPRINT_SUFFIX.length())).
toList();
if (blueprintList.isEmpty())
{
user.sendMessage("commands.admin.blueprint.list.no-blueprints");
return false;
}
user.sendMessage("commands.admin.blueprint.list.available-blueprints");
blueprintList.forEach(user::sendRawMessage);
return true;
}
}

View File

@ -32,7 +32,7 @@ public class AdminBlueprintLoadCommand extends CompositeCommand {
AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent();
BlueprintClipboardManager bp = new BlueprintClipboardManager(getPlugin(), parent.getBlueprintsFolder());
if (bp.load(user, args.get(0))) {
if (bp.load(user, Util.sanitizeInput(args.get(0)))) {
parent.getClipboards().put(user.getUniqueId(), bp.getClipboard());
return true;
}

View File

@ -8,66 +8,107 @@ import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.blueprints.BlueprintClipboard;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.util.Util;
/**
* Renames an existing blueprint.
* @author Poslovitch
* @since 1.10.0
*/
public class AdminBlueprintRenameCommand extends ConfirmableCommand {
public AdminBlueprintRenameCommand(AdminBlueprintCommand parent) {
public class AdminBlueprintRenameCommand extends ConfirmableCommand
{
public AdminBlueprintRenameCommand(AdminBlueprintCommand parent)
{
super(parent, "rename");
}
@Override
public void setup() {
inheritPermission();
setParametersHelp("commands.admin.blueprint.rename.parameters");
setDescription("commands.admin.blueprint.rename.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() != 2) {
showHelp(this, user);
public void setup()
{
this.inheritPermission();
this.setParametersHelp("commands.admin.blueprint.rename.parameters");
this.setDescription("commands.admin.blueprint.rename.description");
}
@Override
public boolean canExecute(User user, String label, List<String> args)
{
if (args.size() != 2)
{
// Blueprint must have a name.
this.showHelp(this, user);
return false;
}
AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent();
String from = Util.sanitizeInput(args.get(0));
String to = Util.sanitizeInput(args.get(1));
// Check if the names are the same
String from = args.get(0).toLowerCase(Locale.ENGLISH);
String to = args.get(1).toLowerCase(Locale.ENGLISH);
if (from.equals(to)) {
// Check if name is changed.
if (from.equals(to))
{
user.sendMessage("commands.admin.blueprint.rename.pick-different-name");
return false;
}
// Check if the 'from' file exists
AdminBlueprintCommand parent = (AdminBlueprintCommand) this.getParent();
File fromFile = new File(parent.getBlueprintsFolder(), from + BlueprintsManager.BLUEPRINT_SUFFIX);
if (!fromFile.exists()) {
if (!fromFile.exists())
{
user.sendMessage("commands.admin.blueprint.no-such-file");
return false;
}
// Check if the 'to' file exists
File toFile = new File(parent.getBlueprintsFolder(), to + BlueprintsManager.BLUEPRINT_SUFFIX);
if (toFile.exists()) {
// Ask for confirmation to overwrite
askConfirmation(user, user.getTranslation("commands.admin.blueprint.file-exists"), () -> rename(user, from, to));
} else {
askConfirmation(user, () -> rename(user, from, to));
}
return true;
}
private void rename(User user, String blueprintName, String newName) {
Blueprint blueprint = getPlugin().getBlueprintsManager().getBlueprints(getAddon()).get(blueprintName);
getPlugin().getBlueprintsManager().renameBlueprint(getAddon(), blueprint, newName);
user.sendMessage("commands.admin.blueprint.rename.success", "[old]", blueprintName, TextVariables.NAME, blueprint.getName());
@Override
public boolean execute(User user, String label, List<String> args)
{
AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent();
// Check if the names are the same
String from = Util.sanitizeInput(args.get(0));
String to = Util.sanitizeInput(args.get(1));
// Check if the 'to' file exists
File toFile = new File(parent.getBlueprintsFolder(), to + BlueprintsManager.BLUEPRINT_SUFFIX);
if (toFile.exists())
{
// Ask for confirmation to overwrite
this.askConfirmation(user,
user.getTranslation("commands.admin.blueprint.file-exists"),
() -> this.rename(user, from, to, args.get(1)));
}
else
{
this.askConfirmation(user, () -> this.rename(user, from, to, args.get(1)));
}
return true;
}
private void rename(User user, String blueprintName, String fileName, String displayName)
{
Blueprint blueprint = this.getPlugin().getBlueprintsManager().getBlueprints(this.getAddon()).get(blueprintName);
this.getPlugin().getBlueprintsManager().renameBlueprint(this.getAddon(), blueprint, fileName, displayName);
user.sendMessage("commands.admin.blueprint.rename.success",
"[old]",
blueprintName,
TextVariables.NAME,
blueprint.getName(),
"[display]",
blueprint.getDisplayName());
}
}

View File

@ -2,62 +2,115 @@ package world.bentobox.bentobox.api.commands.admin.blueprints;
import java.io.File;
import java.util.List;
import java.util.Locale;
import world.bentobox.bentobox.api.commands.ConfirmableCommand;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.BlueprintClipboard;
import world.bentobox.bentobox.managers.BlueprintClipboardManager;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.util.Util;
public class AdminBlueprintSaveCommand extends ConfirmableCommand {
public AdminBlueprintSaveCommand(AdminBlueprintCommand parent) {
/**
* This method allows to save blueprint from the clipboard.
*/
public class AdminBlueprintSaveCommand extends ConfirmableCommand
{
public AdminBlueprintSaveCommand(AdminBlueprintCommand parent)
{
super(parent, "save");
}
@Override
public void setup() {
inheritPermission();
setParametersHelp("commands.admin.blueprint.save.parameters");
setDescription("commands.admin.blueprint.save.description");
}
@Override
public boolean execute(User user, String label, List<String> args) {
if (args.size() != 1) {
showHelp(this, user);
public void setup()
{
this.inheritPermission();
this.setParametersHelp("commands.admin.blueprint.save.parameters");
this.setDescription("commands.admin.blueprint.save.description");
}
@Override
public boolean canExecute(User user, String label, List<String> args)
{
if (args.size() != 1)
{
// Blueprint must have a name.
this.showHelp(this, user);
return false;
}
AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent();
BlueprintClipboard clipboard = parent.getClipboards().computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard());
String fileName = args.get(0).toLowerCase(Locale.ENGLISH);
if (clipboard.isFull()) {
// Check if blueprint had bedrock
if (clipboard.getBlueprint().getBedrock() == null) {
user.sendMessage("commands.admin.blueprint.bedrock-required");
return false;
}
// Check if file exists
File newFile = new File(parent.getBlueprintsFolder(), fileName + BlueprintsManager.BLUEPRINT_SUFFIX);
if (newFile.exists()) {
this.askConfirmation(user, user.getTranslation("commands.admin.blueprint.file-exists"), () -> hideAndSave(user, parent, clipboard, fileName));
return false;
}
return hideAndSave(user, parent, clipboard, fileName);
} else {
BlueprintClipboard clipboard = ((AdminBlueprintCommand) this.getParent()).getClipboards().
computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard());
if (!clipboard.isFull())
{
// Clipboard is not set up.
user.sendMessage("commands.admin.blueprint.copy-first");
return false;
}
if (clipboard.getBlueprint().getBedrock() == null)
{
// Bedrock is required for all blueprints.
user.sendMessage("commands.admin.blueprint.bedrock-required");
return false;
}
return true;
}
private boolean hideAndSave(User user, AdminBlueprintCommand parent, BlueprintClipboard clipboard, String name) {
parent.hideClipboard(user);
boolean result = new BlueprintClipboardManager(getPlugin(), parent.getBlueprintsFolder(), clipboard).save(user, name);
if (result && clipboard.getBlueprint() != null) {
getPlugin().getBlueprintsManager().addBlueprint(getAddon(), clipboard.getBlueprint());
@Override
public boolean execute(User user, String label, List<String> args)
{
AdminBlueprintCommand parent = (AdminBlueprintCommand) this.getParent();
BlueprintClipboard clipboard = parent.getClipboards().
computeIfAbsent(user.getUniqueId(), v -> new BlueprintClipboard());
String fileName = Util.sanitizeInput(args.get(0));
// Check if file exists
File newFile = new File(parent.getBlueprintsFolder(), fileName + BlueprintsManager.BLUEPRINT_SUFFIX);
if (newFile.exists())
{
this.askConfirmation(user,
user.getTranslation("commands.admin.blueprint.file-exists"),
() -> this.hideAndSave(user, parent, clipboard, fileName, args.get(0)));
return false;
}
return this.hideAndSave(user, parent, clipboard, fileName, args.get(0));
}
/**
* This method saves given blueprint.
* @param user User that triggers blueprint save.
* @param parent Parent command that contains clipboard.
* @param clipboard Active clipboard.
* @param name Filename for the blueprint
* @param displayName Display name for the blueprint.
* @return {@code true} if blueprint is saved, {@code false} otherwise.
*/
private boolean hideAndSave(User user,
AdminBlueprintCommand parent,
BlueprintClipboard clipboard,
String name,
String displayName)
{
parent.hideClipboard(user);
boolean result = new BlueprintClipboardManager(this.getPlugin(),
parent.getBlueprintsFolder(), clipboard).
save(user, name, displayName);
if (result && clipboard.isFull())
{
this.getPlugin().getBlueprintsManager().addBlueprint(this.getAddon(), clipboard.getBlueprint());
}
return result;
}
}

View File

@ -12,6 +12,8 @@ import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.managers.island.NewIsland;
import world.bentobox.bentobox.panels.IslandCreationPanel;
import world.bentobox.bentobox.util.Util;
/**
* /island create - Create an island.
@ -63,13 +65,13 @@ public class IslandCreateCommand extends CompositeCommand {
public boolean execute(User user, String label, List<String> args) {
// Permission check if the name is not the default one
if (!args.isEmpty()) {
String name = getPlugin().getBlueprintsManager().validate(getAddon(), args.get(0).toLowerCase(java.util.Locale.ENGLISH));
String name = getPlugin().getBlueprintsManager().validate(getAddon(), Util.sanitizeInput(args.get(0)));
if (name == null) {
// The blueprint name is not valid.
user.sendMessage("commands.island.create.unknown-blueprint");
return false;
}
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, args.get(0))) {
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, Util.sanitizeInput(args.get(0)))) {
return false;
}
// Make island

View File

@ -17,6 +17,8 @@ import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.managers.island.NewIsland;
import world.bentobox.bentobox.managers.island.NewIsland.Builder;
import world.bentobox.bentobox.panels.IslandCreationPanel;
import world.bentobox.bentobox.util.Util;
/**
* @author tastybento
@ -78,13 +80,13 @@ public class IslandResetCommand extends ConfirmableCommand {
public boolean execute(User user, String label, List<String> args) {
// Permission check if the name is not the default one
if (!args.isEmpty()) {
String name = getPlugin().getBlueprintsManager().validate(getAddon(), args.get(0).toLowerCase(java.util.Locale.ENGLISH));
String name = getPlugin().getBlueprintsManager().validate(getAddon(), Util.sanitizeInput(args.get(0)));
if (name == null || name.isEmpty()) {
// The blueprint name is not valid.
user.sendMessage("commands.island.create.unknown-blueprint");
return false;
}
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, args.get(0))) {
if (!getPlugin().getBlueprintsManager().checkPerm(getAddon(), user, Util.sanitizeInput(args.get(0)))) {
return false;
}
return resetIsland(user, name);

View File

@ -53,14 +53,14 @@ public class Blueprint {
public String getName() {
if (name == null) name = "unnamed";
// Force lower case
return name.toLowerCase(Locale.ENGLISH);
return name;
}
/**
* @param name the name to set
*/
public Blueprint setName(@NonNull String name) {
// Force lowercase
this.name = name.toLowerCase(Locale.ENGLISH);
this.name = name;
return this;
}
/**

View File

@ -1,7 +1,5 @@
package world.bentobox.bentobox.blueprints.conversation;
import java.util.Locale;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.Prompt;
import org.bukkit.conversations.StringPrompt;
@ -18,67 +16,74 @@ import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.util.Util;
public class NamePrompt extends StringPrompt {
public class NamePrompt extends StringPrompt
{
private final GameModeAddon addon;
@Nullable
private final BlueprintBundle bb;
@Nullable
private Blueprint bp;
public NamePrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb) {
public NamePrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb)
{
this.addon = addon;
this.bb = bb;
}
public NamePrompt(@NonNull GameModeAddon addon, @Nullable Blueprint bp, @Nullable BlueprintBundle bb) {
public NamePrompt(@NonNull GameModeAddon addon, @Nullable Blueprint bp, @Nullable BlueprintBundle bb)
{
this.addon = addon;
this.bp = bp;
this.bb = bb;
}
@Override
public @NonNull String getPromptText(ConversationContext context) {
User user = User.getInstance((Player)context.getForWhom());
public @NonNull String getPromptText(ConversationContext context)
{
User user = User.getInstance((Player) context.getForWhom());
return user.getTranslation("commands.admin.blueprint.management.name.prompt");
}
@Override
public Prompt acceptInput(ConversationContext context, String input) {
User user = User.getInstance((Player)context.getForWhom());
// Convert color codes
input = Util.translateColorCodes(input);
if (ChatColor.stripColor(input).length() > 32) {
context.getForWhom().sendRawMessage("Too long");
return this;
/*Check if unique name contains chars not supported in regex expression
Cannot start, contain, or end with special char, cannot contain any numbers.
Can only contain - for word separation*/
}else if (!ChatColor.stripColor(input).matches("^[a-zA-Z]+(?:-[a-zA-Z]+)*$")) {
context.getForWhom().sendRawMessage(user.getTranslation("commands.admin.blueprint.management.name.invalid-char-in-unique-name"));
@Override
public Prompt acceptInput(ConversationContext context, String input)
{
User user = User.getInstance((Player) context.getForWhom());
String uniqueId = Util.sanitizeInput(input);
// Convert color codes
if (ChatColor.stripColor(Util.translateColorCodes(input)).length() > 32)
{
context.getForWhom().sendRawMessage(
user.getTranslation("commands.admin.blueprint.management.name.too-long"));
return this;
}
if (bb == null || !bb.getUniqueId().equals(BlueprintsManager.DEFAULT_BUNDLE_NAME)) {
// Make a uniqueid
StringBuilder uniqueId = new StringBuilder(ChatColor.stripColor(input).toLowerCase(Locale.ENGLISH).replace(" ", "_"));
if (this.bb == null || !this.bb.getUniqueId().equals(BlueprintsManager.DEFAULT_BUNDLE_NAME))
{
// Check if this name is unique
int max = 0;
while (max++ < 32 && addon.getPlugin().getBlueprintsManager().getBlueprintBundles(addon).containsKey(uniqueId.toString())) {
uniqueId.append("x");
}
if (max == 32) {
context.getForWhom().sendRawMessage(user.getTranslation("commands.admin.blueprint.management.name.pick-a-unique-name"));
if (this.addon.getPlugin().getBlueprintsManager().getBlueprintBundles(this.addon).containsKey(uniqueId))
{
context.getForWhom().sendRawMessage(
user.getTranslation("commands.admin.blueprint.management.name.pick-a-unique-name"));
return this;
}
context.setSessionData("uniqueId", uniqueId.toString());
} else {
// Default stays as default
context.setSessionData("uniqueId", bb.getUniqueId());
context.setSessionData("uniqueId", uniqueId);
}
else
{
// Default stays as default
context.setSessionData("uniqueId", this.bb.getUniqueId());
}
context.setSessionData("name", input);
return new NameSuccessPrompt(addon, bb, bp);
return new NameSuccessPrompt(this.addon, this.bb, this.bp);
}
}
}

View File

@ -15,58 +15,76 @@ import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBundle;
import world.bentobox.bentobox.panels.BlueprintManagementPanel;
public class NameSuccessPrompt extends MessagePrompt {
public class NameSuccessPrompt extends MessagePrompt
{
private final GameModeAddon addon;
private BlueprintBundle bb;
private final Blueprint bp;
/**
* Handles the name processing
*
* @param addon - Game Mode addon
* @param bb - Blueprint Bundle
* @param bp - blueprint
*/
public NameSuccessPrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb, @Nullable Blueprint bp) {
public NameSuccessPrompt(@NonNull GameModeAddon addon, @Nullable BlueprintBundle bb, @Nullable Blueprint bp)
{
this.addon = addon;
this.bb = bb;
this.bp = bp;
}
@Override
public @NonNull String getPromptText(ConversationContext context) {
public @NonNull String getPromptText(ConversationContext context)
{
String name = (String) context.getSessionData("name");
String uniqueId = (String) context.getSessionData("uniqueId");
User user = User.getInstance((Player)context.getForWhom());
// Rename blueprint
if (bp != null) {
BentoBox.getInstance().getBlueprintsManager().renameBlueprint(addon, bp, name);
new BlueprintManagementPanel(BentoBox.getInstance(), user, addon).openBB(bb);
} else {
// Blueprint Bundle
if (bb == null) {
// New Blueprint bundle
bb = new BlueprintBundle();
bb.setIcon(Material.RED_WOOL);
} else {
// Rename - remove old named file
BentoBox.getInstance().getBlueprintsManager().deleteBlueprintBundle(addon, bb);
}
bb.setDisplayName(name);
bb.setUniqueId(uniqueId);
BentoBox.getInstance().getBlueprintsManager().addBlueprintBundle(addon, bb);
BentoBox.getInstance().getBlueprintsManager().saveBlueprintBundle(addon, bb);
new BlueprintManagementPanel(BentoBox.getInstance(), user, addon).openPanel();
// Set the name
// if successfully
User user = User.getInstance((Player) context.getForWhom());
// Rename blueprint
if (this.bp != null)
{
this.addon.getPlugin().getBlueprintsManager().renameBlueprint(this.addon, this.bp, uniqueId, name);
new BlueprintManagementPanel(this.addon.getPlugin(), user, this.addon).openBB(this.bb);
}
else
{
// Blueprint Bundle
if (this.bb == null)
{
// New Blueprint bundle
this.bb = new BlueprintBundle();
this.bb.setIcon(Material.RED_WOOL);
}
else
{
// Rename - remove old named file
this.addon.getPlugin().getBlueprintsManager().deleteBlueprintBundle(this.addon, this.bb);
}
this.bb.setDisplayName(name);
this.bb.setUniqueId(uniqueId);
this.addon.getPlugin().getBlueprintsManager().addBlueprintBundle(this.addon, this.bb);
this.addon.getPlugin().getBlueprintsManager().saveBlueprintBundle(this.addon, this.bb);
new BlueprintManagementPanel(this.addon.getPlugin(), user, this.addon).openPanel();
// Set the name
}
return user.getTranslation("commands.admin.blueprint.management.description.success");
}
@Override
protected Prompt getNextPrompt(@NonNull ConversationContext context) {
protected Prompt getNextPrompt(@NonNull ConversationContext context)
{
return Prompt.END_OF_CONVERSATION;
}
}

View File

@ -69,7 +69,7 @@ public class BlueprintBundle implements DataObject {
*/
@Override
public String getUniqueId() {
return uniqueId.toLowerCase(Locale.ENGLISH);
return uniqueId;
}
/**
* @param uniqueId the uniqueId to set

View File

@ -7,6 +7,7 @@ import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -86,24 +87,24 @@ public class BlueprintClipboardManager {
/**
* Loads a blueprint
* @param fileName - the filename without the suffix
* @param fileName - the sanitized filename without the suffix
* @return the blueprint
* @throws IOException exception if there's an issue loading or unzipping
*/
public Blueprint loadBlueprint(String fileName) throws IOException {
File zipFile = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX);
File zipFile = new File(blueprintFolder, fileName + BlueprintsManager.BLUEPRINT_SUFFIX);
if (!zipFile.exists()) {
plugin.logError(LOAD_ERROR + zipFile.getName());
throw new IOException(LOAD_ERROR + zipFile.getName());
}
unzip(zipFile.getCanonicalPath());
File file = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(fileName));
File file = new File(blueprintFolder, fileName);
if (!file.exists()) {
plugin.logError(LOAD_ERROR + file.getName());
throw new IOException(LOAD_ERROR + file.getName() + " temp file");
}
Blueprint bp;
try (FileReader fr = new FileReader(file)) {
try (FileReader fr = new FileReader(file, StandardCharsets.UTF_8)) {
bp = gson.fromJson(fr, Blueprint.class);
} catch (Exception e) {
plugin.logError("Blueprint has JSON error: " + zipFile.getName());
@ -114,7 +115,7 @@ public class BlueprintClipboardManager {
if (bp.getBedrock() == null) {
bp.setBedrock(new Vector(bp.getxSize() / 2, bp.getySize() / 2, bp.getzSize() / 2));
bp.getBlocks().put(bp.getBedrock(), new BlueprintBlock(Material.BEDROCK.createBlockData().getAsString()));
plugin.logWarning("Blueprint " + BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX + " had no bedrock block in it so one was added automatically in the center. You should check it.");
plugin.logWarning("Blueprint " + fileName + BlueprintsManager.BLUEPRINT_SUFFIX + " had no bedrock block in it so one was added automatically in the center. You should check it.");
}
return bp;
}
@ -130,7 +131,7 @@ public class BlueprintClipboardManager {
load(fileName);
} catch (IOException e1) {
user.sendMessage("commands.admin.blueprint.could-not-load");
plugin.logError("Could not load blueprint file: " + BlueprintsManager.sanitizeFileName(fileName) + BlueprintsManager.BLUEPRINT_SUFFIX + " " + e1.getMessage());
plugin.logError("Could not load blueprint file: " + fileName + BlueprintsManager.BLUEPRINT_SUFFIX + " " + e1.getMessage());
return false;
}
user.sendMessage("general.success");
@ -143,14 +144,20 @@ public class BlueprintClipboardManager {
* @param newName - new name of this blueprint
* @return - true if successful, false if error
*/
public boolean save(User user, String newName) {
if (clipboard.getBlueprint() != null) {
clipboard.getBlueprint().setName(newName);
if (saveBlueprint(clipboard.getBlueprint())) {
public boolean save(User user, String newName, String displayName)
{
if (this.clipboard.isFull())
{
this.clipboard.getBlueprint().setName(newName);
this.clipboard.getBlueprint().setDisplayName(displayName);
if (this.saveBlueprint(this.clipboard.getBlueprint()))
{
user.sendMessage("general.success");
return true;
}
}
user.sendMessage("commands.admin.blueprint.could-not-save", "[message]", "Could not save temp blueprint file.");
return false;
}
@ -165,9 +172,9 @@ public class BlueprintClipboardManager {
plugin.logError("Blueprint name was empty - could not save it");
return false;
}
File file = new File(blueprintFolder, BlueprintsManager.sanitizeFileName(blueprint.getName()));
File file = new File(blueprintFolder, blueprint.getName());
String toStore = gson.toJson(blueprint, Blueprint.class);
try (FileWriter fileWriter = new FileWriter(file)) {
try (FileWriter fileWriter = new FileWriter(file, StandardCharsets.UTF_8)) {
fileWriter.write(toStore);
} catch (IOException e) {
plugin.logError("Could not save temporary blueprint file: " + file.getName());

View File

@ -1,11 +1,9 @@
package world.bentobox.bentobox.managers;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@ -207,13 +205,24 @@ public class BlueprintsManager {
bpf.mkdirs();
}
boolean loaded = false;
File[] bundles = bpf.listFiles((dir, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(BLUEPRINT_BUNDLE_SUFFIX));
File[] bundles = bpf.listFiles((dir, name) -> name.endsWith(BLUEPRINT_BUNDLE_SUFFIX));
if (bundles == null || bundles.length == 0) {
return false;
}
for (File file : bundles) {
try {
BlueprintBundle bb = gson.fromJson(new FileReader(file), BlueprintBundle.class);
try (FileReader fileReader = new FileReader(file, StandardCharsets.UTF_8))
{
if (!file.getName().equals(Util.sanitizeInput(file.getName())))
{
// fail on all blueprints with incorrect names.
throw new InputMismatchException(file.getName());
}
BlueprintBundle bb = gson.fromJson(fileReader, BlueprintBundle.class);
if (bb != null) {
// Make sure there is no existing bundle with the same uniqueId
if (blueprintBundles.get(addon).stream().noneMatch(bundle -> bundle.getUniqueId().equals(bb.getUniqueId()))) {
@ -290,13 +299,17 @@ public class BlueprintsManager {
plugin.logError("There is no blueprint folder for addon " + addon.getDescription().getName());
bpf.mkdirs();
}
File[] bps = bpf.listFiles((dir, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(BLUEPRINT_SUFFIX));
File[] bps = bpf.listFiles((dir, name) -> name.endsWith(BLUEPRINT_SUFFIX));
if (bps == null || bps.length == 0) {
plugin.logError("No blueprints found for " + addon.getDescription().getName());
return;
}
for (File file : bps) {
String fileName = file.getName().substring(0, file.getName().length() - BLUEPRINT_SUFFIX.length());
// Input sanitization is required for weirdos that edit files manually.
String fileName = Util.sanitizeInput(file.getName().substring(0, file.getName().length() - BLUEPRINT_SUFFIX.length()));
try {
Blueprint bp = new BlueprintClipboardManager(plugin, bpf).loadBlueprint(fileName);
bp.setName(fileName);
@ -345,9 +358,9 @@ public class BlueprintsManager {
if (!bpf.exists()) {
bpf.mkdirs();
}
File fileName = new File(bpf, sanitizeFileName(bb.getUniqueId()) + BLUEPRINT_BUNDLE_SUFFIX);
File fileName = new File(bpf, bb.getUniqueId() + BLUEPRINT_BUNDLE_SUFFIX);
String toStore = gson.toJson(bb, BlueprintBundle.class);
try (FileWriter fileWriter = new FileWriter(fileName)) {
try (FileWriter fileWriter = new FileWriter(fileName, StandardCharsets.UTF_8)) {
fileWriter.write(toStore);
} catch (IOException e) {
plugin.logError("Could not save blueprint bundle file: " + e.getMessage());
@ -355,20 +368,6 @@ public class BlueprintsManager {
});
}
/**
* Sanitizes a filename as much as possible retaining the original name
* @param name - filename to sanitize
* @return sanitized name
*/
public static String sanitizeFileName(String name) {
return name
.chars()
.mapToObj(i -> (char) i)
.map(c -> Character.isWhitespace(c) ? '_' : c)
.filter(c -> Character.isLetterOrDigit(c) || c == '-' || c == '_')
.map(String::valueOf)
.collect(Collectors.joining());
}
/**
* Saves all the blueprint bundles
@ -396,26 +395,35 @@ public class BlueprintsManager {
* @param name name of the Blueprint to delete
* @since 1.9.0
*/
public void deleteBlueprint(GameModeAddon addon, String name) {
List<Blueprint> addonBlueprints = blueprints.get(addon);
public void deleteBlueprint(GameModeAddon addon, String name)
{
List<Blueprint> addonBlueprints = this.blueprints.get(addon);
Iterator<Blueprint> it = addonBlueprints.iterator();
while (it.hasNext()) {
Blueprint b = it.next();
if (b.getName().equalsIgnoreCase(name)) {
it.remove();
blueprints.put(addon, addonBlueprints);
File file = new File(getBlueprintsFolder(addon), b.getName() + BLUEPRINT_SUFFIX);
while (it.hasNext())
{
Blueprint b = it.next();
if (b.getName().equalsIgnoreCase(name))
{
it.remove();
File file = new File(this.getBlueprintsFolder(addon), b.getName() + BLUEPRINT_SUFFIX);
// Delete the file
try {
try
{
Files.deleteIfExists(file.toPath());
} catch (IOException e) {
plugin.logError("Could not delete Blueprint " + e.getLocalizedMessage());
}
catch (IOException e)
{
this.plugin.logError("Could not delete Blueprint " + e.getLocalizedMessage());
}
}
}
}
/**
* Paste the islands to world
*
@ -441,7 +449,7 @@ public class BlueprintsManager {
plugin.logError("Tried to paste '" + name + "' but the bundle is not loaded!");
return false;
}
BlueprintBundle bb = getBlueprintBundles(addon).get(name.toLowerCase(Locale.ENGLISH));
BlueprintBundle bb = getBlueprintBundles(addon).get(name.toLowerCase());
if (!blueprints.containsKey(addon) || blueprints.get(addon).isEmpty()) {
plugin.logError("No blueprints loaded for bundle '" + name + "'!");
return false;
@ -514,7 +522,7 @@ public class BlueprintsManager {
if (name == null) {
return null;
}
if (blueprintBundles.containsKey(addon) && getBlueprintBundles(addon).containsKey(name.toLowerCase(Locale.ENGLISH))) {
if (blueprintBundles.containsKey(addon) && getBlueprintBundles(addon).containsKey(name)) {
return name;
}
return null;
@ -546,7 +554,7 @@ public class BlueprintsManager {
// Permission
String permission = addon.getPermissionPrefix() + "island.create." + name;
// Get Blueprint bundle
BlueprintBundle bb = getBlueprintBundles((GameModeAddon) addon).get(name.toLowerCase(Locale.ENGLISH));
BlueprintBundle bb = getBlueprintBundles((GameModeAddon) addon).get(name.toLowerCase());
if (bb == null || (bb.isRequirePermission() && !name.equals(DEFAULT_BUNDLE_NAME) && !user.hasPermission(permission))) {
user.sendMessage("general.errors.no-permission", TextVariables.PERMISSION, permission);
return false;
@ -565,7 +573,7 @@ public class BlueprintsManager {
blueprintBundles.get(addon).removeIf(k -> k.getUniqueId().equals(bb.getUniqueId()));
}
File bpf = getBlueprintsFolder(addon);
File fileName = new File(bpf, sanitizeFileName(bb.getUniqueId()) + BLUEPRINT_BUNDLE_SUFFIX);
File fileName = new File(bpf, bb.getUniqueId() + BLUEPRINT_BUNDLE_SUFFIX);
try {
Files.deleteIfExists(fileName.toPath());
} catch (IOException e) {
@ -579,25 +587,39 @@ public class BlueprintsManager {
* @param addon - Game Mode Addon
* @param bp - blueprint
* @param name - new name
* @param displayName - display name for blueprint
*/
public void renameBlueprint(GameModeAddon addon, Blueprint bp, String name) {
if (bp.getName().equalsIgnoreCase(name)) {
public void renameBlueprint(GameModeAddon addon, Blueprint bp, String name, String displayName)
{
if (bp.getName().equalsIgnoreCase(name))
{
// If the name is the same, do not do anything
return;
}
File bpf = getBlueprintsFolder(addon);
// Get the filename
File fileName = new File(bpf, sanitizeFileName(bp.getName()) + BLUEPRINT_SUFFIX);
// Delete the old file
try {
Files.deleteIfExists(fileName.toPath());
} catch (IOException e) {
plugin.logError("Could not delete old Blueprint " + e.getLocalizedMessage());
}
// Set new name
bp.setName(name.toLowerCase(Locale.ENGLISH));
// Save it
saveBlueprint(addon, bp);
}
File bpf = this.getBlueprintsFolder(addon);
// Get the filename
File fileName = new File(bpf, bp.getName() + BLUEPRINT_SUFFIX);
// Delete the old file
try
{
Files.deleteIfExists(fileName.toPath());
}
catch (IOException e)
{
this.plugin.logError("Could not delete old Blueprint " + e.getLocalizedMessage());
}
// Remove blueprint from the blueprints.
this.blueprints.get(addon).remove(bp);
// Set new name
bp.setName(name);
bp.setDisplayName(displayName);
// Save it
this.saveBlueprint(addon, bp);
this.addBlueprint(addon, bp);
}
}

View File

@ -2,11 +2,7 @@ package world.bentobox.bentobox.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@ -761,4 +757,19 @@ public class Util {
}
return count;
}
/**
* This method removes all special characters that are not allowed in filenames (windows).
* It also includes any white-spaces, as for some reason, I do like it more without them.
* Also, all cases are lower cased for easier blueprint mapping.
* @param input Input that need to be sanitized.
* @return A sanitized input without illegal characters in names.
*/
public static String sanitizeInput(String input)
{
return ChatColor.stripColor(
Util.translateColorCodes(input.replaceAll("[\\\\/:*?\"<>|\s]", "_"))).
toLowerCase();
}
}

View File

@ -332,7 +332,7 @@ commands:
rename:
parameters: "<blueprint name> <new name>"
description: "rename a blueprint"
success: "&a Blueprint &b [old] &a has been successfully renamed to &b [name]&a."
success: "&a Blueprint &b [old] &a has been successfully renamed to &b [display]&a. Filename now is &b [name]&a."
pick-different-name: "&c Please specify a name that is different from the blueprint's current name."
management:
back: "Back"
@ -366,9 +366,9 @@ commands:
name:
quit: "quit"
prompt: "Enter a name, or 'quit' to quit"
too-long: "&c Too long"
too-long: "&c Too long name. Only 32 chars are allowed."
pick-a-unique-name: "Please pick a more unique name"
invalid-char-in-unique-name: "Unique name cannot contain, start, or end with special characters, neither contain number! "
stripped-char-in-unique-name: "&c Some chars were removed because they are not allowed. &a New ID will be &b [name]&a."
success: "Success!"
conversation-prefix: ">"
description:

View File

@ -39,6 +39,8 @@ import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.Blueprint;
import world.bentobox.bentobox.blueprints.BlueprintClipboard;
import world.bentobox.bentobox.util.Util;
/**
* @author tastybento
@ -345,7 +347,7 @@ public class BlueprintClipboardManagerTest {
}
/**
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}.
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}.
* @throws IOException
*/
@Test
@ -361,14 +363,14 @@ public class BlueprintClipboardManagerTest {
BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder);
bcm.load(BLUEPRINT);
User user = mock(User.class);
assertTrue(bcm.save(user, "test1234"));
assertTrue(bcm.save(user, "test1234", ""));
File bp = new File(blueprintFolder, "test1234.blu");
assertTrue(bp.exists());
verify(user).sendMessage("general.success");
}
/**
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}.
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}.
* @throws IOException
*/
@Test
@ -384,14 +386,14 @@ public class BlueprintClipboardManagerTest {
BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder);
bcm.load(BLUEPRINT);
User user = mock(User.class);
assertTrue(bcm.save(user, "test.1234/../../film"));
File bp = new File(blueprintFolder, "test1234film.blu");
assertTrue(bcm.save(user, Util.sanitizeInput("test.1234/../../film"), ""));
File bp = new File(blueprintFolder, "test.1234_.._.._film.blu");
assertTrue(bp.exists());
verify(user).sendMessage("general.success");
}
/**
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}.
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}.
* @throws IOException
*/
@Test
@ -407,14 +409,14 @@ public class BlueprintClipboardManagerTest {
BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder);
bcm.load(BLUEPRINT);
User user = mock(User.class);
assertTrue(bcm.save(user, "日本語の言葉"));
assertTrue(bcm.save(user, "日本語の言葉", ""));
File bp = new File(blueprintFolder, "日本語の言葉.blu");
assertTrue(bp.exists());
verify(user).sendMessage("general.success");
}
/**
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String)}.
* Test method for {@link world.bentobox.bentobox.managers.BlueprintClipboardManager#save(world.bentobox.bentobox.api.user.User, java.lang.String, java.lang.String)}.
* @throws IOException
*/
@Test
@ -430,8 +432,9 @@ public class BlueprintClipboardManagerTest {
BlueprintClipboardManager bcm = new BlueprintClipboardManager(plugin, blueprintFolder);
bcm.load(BLUEPRINT);
User user = mock(User.class);
assertTrue(bcm.save(user, "日本語の言葉/../../../config"));
File bp = new File(blueprintFolder, "日本語の言葉config.blu");
assertTrue(bcm.save(user, Util.sanitizeInput("日本語の言葉/../../../config"), ""));
File bp = new File(blueprintFolder, "日本語の言葉_.._.._.._config.blu");
assertTrue(bp.exists());
verify(user).sendMessage("general.success");
}

View File

@ -526,8 +526,6 @@ public class BlueprintsManagerTest {
BlueprintsManager bpm = new BlueprintsManager(plugin);
bpm.addBlueprintBundle(addon, bb);
assertEquals("bundle", bpm.validate(addon, "bundle"));
// Mixed case
assertEquals("buNdle", bpm.validate(addon, "buNdle"));
// Not there
assertNull(bpm.validate(addon, "buNdle2"));
}
@ -651,18 +649,19 @@ public class BlueprintsManagerTest {
}
/**
* Test method for {@link world.bentobox.bentobox.managers.BlueprintsManager#renameBlueprint(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.blueprints.Blueprint, java.lang.String)}.
* Test method for {@link world.bentobox.bentobox.managers.BlueprintsManager#renameBlueprint(world.bentobox.bentobox.api.addons.GameModeAddon, world.bentobox.bentobox.blueprints.Blueprint, java.lang.String, java.lang.String)}.
*/
@Test
public void testRenameBlueprint() {
// Save it
BlueprintsManager bpm = new BlueprintsManager(plugin);
bpm.saveBlueprint(addon, defaultBp);
bpm.addBlueprint(addon, defaultBp);
File blueprints = new File(dataFolder, BlueprintsManager.FOLDER_NAME);
File d = new File(blueprints, "bedrock.blu");
assertTrue(d.exists());
// Rename it
bpm.renameBlueprint(addon, defaultBp, "bedrock2");
bpm.renameBlueprint(addon, defaultBp, "bedrock2", "");
assertFalse(d.exists());
d = new File(blueprints, "bedrock2.blu");
assertTrue(d.exists());