From 790e3d1531870db7de0d9bf5da32736ce1cb870e Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Thu, 14 Aug 2025 00:23:22 +0200 Subject: [PATCH] Adds a migrate command for fixing book names and text -> yml --- .../BooksWithoutBorders.java | 2 + .../command/CommandMigrate.java | 149 ++++++++++++++++++ .../config/BooksWithoutBordersConfig.java | 2 +- .../config/BwBCommand.java | 5 + .../listener/SignEventListener.java | 2 +- .../utility/BookFileHelper.java | 18 +++ .../utility/BookHelper.java | 16 +- .../utility/BookToFromTextHelper.java | 2 +- src/main/resources/plugin.yml | 4 + 9 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 src/main/java/net/knarcraft/bookswithoutborders/command/CommandMigrate.java diff --git a/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java b/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java index 4a0e6ea..d0a6de7 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java @@ -15,6 +15,7 @@ import net.knarcraft.bookswithoutborders.command.CommandGivePublic; import net.knarcraft.bookswithoutborders.command.CommandGroupEncrypt; import net.knarcraft.bookswithoutborders.command.CommandLoad; import net.knarcraft.bookswithoutborders.command.CommandLoadPublic; +import net.knarcraft.bookswithoutborders.command.CommandMigrate; import net.knarcraft.bookswithoutborders.command.CommandReload; import net.knarcraft.bookswithoutborders.command.CommandSave; import net.knarcraft.bookswithoutborders.command.CommandSavePublic; @@ -260,6 +261,7 @@ public class BooksWithoutBorders extends JavaPlugin { registerCommand(BwBCommand.SET_BOOKSHELF_DATA.toString(), new CommandSetBookshelfData()); registerCommand(BwBCommand.ADD_TITLE_PAGE.toString(), new CommandAddTitlePage()); registerCommand(BwBCommand.DELETE_PAGE.toString(), new CommandDeletePage()); + registerCommand(BwBCommand.MIGRATE.toString(), new CommandMigrate()); } /** diff --git a/src/main/java/net/knarcraft/bookswithoutborders/command/CommandMigrate.java b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandMigrate.java new file mode 100644 index 0000000..8e1b00e --- /dev/null +++ b/src/main/java/net/knarcraft/bookswithoutborders/command/CommandMigrate.java @@ -0,0 +1,149 @@ +package net.knarcraft.bookswithoutborders.command; + +import net.knarcraft.bookswithoutborders.BooksWithoutBorders; +import net.knarcraft.bookswithoutborders.config.Translatable; +import net.knarcraft.bookswithoutborders.utility.BookFileHelper; +import net.knarcraft.bookswithoutborders.utility.BookHelper; +import net.knarcraft.bookswithoutborders.utility.BookToFromTextHelper; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.inventory.meta.BookMeta; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; + +/** + * Command executor for the migrate command + */ +public class CommandMigrate implements TabExecutor { + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String s, + @NotNull String[] arguments) { + if (!(sender instanceof Player player)) { + BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY); + return false; + } + File bookDirectory = new File(BooksWithoutBorders.getConfiguration().getBookFolder()); + boolean success = migrateFiles(bookDirectory, player); + if (success) { + BooksWithoutBorders.sendSuccessMessage(player, "Successfully migrated all books!"); + } else { + BooksWithoutBorders.sendErrorMessage(player, "Failed to migrate all books!"); + } + return true; + } + + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, + @NotNull String[] arguments) { + return List.of(); + } + + /** + * Migrates the given folder recursively + * + * @param folder

The folder to migrate files for

+ * @param player

The player causing this code to be executed

+ * @return

If all migrations were successful

+ */ + private boolean migrateFiles(@NotNull File folder, @NotNull Player player) { + File[] files = folder.listFiles(); + if (files == null) { + BooksWithoutBorders.log(Level.WARNING, "Unable to access directory " + folder.getName() + " !"); + return false; + } + boolean success = true; + for (File file : files) { + if (file.isDirectory()) { + success = success & migrateFiles(file, player); + } else if (file.isFile()) { + success = success & migrateFile(file, player); + } + } + return success; + } + + /** + * Migrates a single book file + * + * @param file

The file to migrate

+ * @param player

The player causing this code to be executed

+ * @return

True if the migration completed successfully

+ */ + private boolean migrateFile(@NotNull File file, @NotNull Player player) { + BookMeta bookMeta = (BookMeta) BooksWithoutBorders.getItemFactory().getItemMeta(Material.WRITTEN_BOOK); + if (bookMeta == null) { + return false; + } + BookMeta loadedBook; + String extension = BookFileHelper.getExtensionFromPath(file.getName()); + if (extension.equalsIgnoreCase("yml")) { + loadedBook = BookToFromTextHelper.encryptedBookFromYml(file, bookMeta, "", true); + } else if (extension.equalsIgnoreCase("txt")) { + loadedBook = BookToFromTextHelper.bookFromFile(file, bookMeta); + } else { + BooksWithoutBorders.log(Level.WARNING, "File with unexpected extension " + extension + " encountered!"); + return true; + } + + if (loadedBook == null) { + BooksWithoutBorders.log(Level.SEVERE, "Unable to load book: " + file.getName()); + return false; + } + + // Attempt to retain UUID naming + boolean isPublic = true; + OfflinePlayer author = player; + try { + UUID authorId = UUID.fromString(file.getParentFile().getName()); + author = Bukkit.getOfflinePlayer(authorId); + isPublic = false; + } catch (IllegalArgumentException ignored) { + } + + try { + String newName = BookHelper.getBookFile(loadedBook, author, isPublic); + return saveBook(file.getParentFile(), newName, loadedBook, file); + } catch (IllegalArgumentException exception) { + BooksWithoutBorders.sendErrorMessage(player, "Failed to migrate book: " + file.getName() + " Cause:"); + BooksWithoutBorders.sendErrorMessage(player, exception.getMessage()); + return false; + } + } + + /** + * Saves a migrated book + * + * @param parent

The parent folder the file belongs to

+ * @param newName

The new name of the file

+ * @param bookMeta

The metadata of the book to migrate

+ * @param oldFile

The old file path, in case it should be deleted

+ * @return

True if successfully saved

+ */ + private boolean saveBook(@NotNull File parent, @NotNull String newName, @NotNull BookMeta bookMeta, + @NotNull File oldFile) { + try { + BookToFromTextHelper.bookToYml(parent.getAbsolutePath(), newName, bookMeta); + if (!oldFile.getAbsolutePath().equalsIgnoreCase(new File(parent, newName + ".yml").getAbsolutePath())) { + return oldFile.delete(); + } + return true; + } catch (IOException exception) { + BooksWithoutBorders.log(Level.SEVERE, "Failed to save migrated book: " + newName); + return false; + } + } + +} diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/BooksWithoutBordersConfig.java b/src/main/java/net/knarcraft/bookswithoutborders/config/BooksWithoutBordersConfig.java index a2abea6..d5c664d 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/BooksWithoutBordersConfig.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/BooksWithoutBordersConfig.java @@ -26,7 +26,7 @@ public class BooksWithoutBordersConfig { private final ChatColor commandColor = ChatColor.YELLOW; private final String SLASH = FileSystems.getDefault().getSeparator(); private boolean isInitialized; - public String bookFolder; + private final String bookFolder; private int bookDuplicateLimit; private String titleAuthorSeparator; diff --git a/src/main/java/net/knarcraft/bookswithoutborders/config/BwBCommand.java b/src/main/java/net/knarcraft/bookswithoutborders/config/BwBCommand.java index 541c513..67c5348 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/config/BwBCommand.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/config/BwBCommand.java @@ -138,6 +138,11 @@ public enum BwBCommand { * Deletes a page from the held book */ DELETE_PAGE("deleteBookPage", true), + + /** + * Migrates all books, fixing any problems with their names, and converts txt books to yml + */ + MIGRATE("migrateBooks", true), ; private final @NotNull String commandName; diff --git a/src/main/java/net/knarcraft/bookswithoutborders/listener/SignEventListener.java b/src/main/java/net/knarcraft/bookswithoutborders/listener/SignEventListener.java index e26ef95..9462642 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/listener/SignEventListener.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/listener/SignEventListener.java @@ -276,7 +276,7 @@ public class SignEventListener implements Listener { if (file == null) { file = BookFileHelper.findBookFile(encryptedFolder, oldBook); if (file == null) { - file = BookFileHelper.findBookFile(config.bookFolder, oldBook); + file = BookFileHelper.findBookFile(config.getBookFolder(), oldBook); if (file == null) { BooksWithoutBorders.sendErrorMessage(player, "Unable to find encrypted book"); return; diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookFileHelper.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookFileHelper.java index d9bab26..ad89561 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookFileHelper.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookFileHelper.java @@ -220,6 +220,24 @@ public final class BookFileHelper { } } + /** + * Strips the extension from the given path + * + * @param path

The path to strip the extension from

+ * @return

The input with the extension stripped

+ */ + @NotNull + public static String getExtensionFromPath(@NotNull String path) { + int dotIndex = path.lastIndexOf("."); + if (dotIndex > 0) { + String separator = BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator(); + if (path.lastIndexOf(separator) < dotIndex && (path.length() - dotIndex == 4)) { + return path.substring(dotIndex + 1); + } + } + return path; + } + /** * Strips the extension from the given path * diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookHelper.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookHelper.java index 3c5da3a..47e4e4e 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookHelper.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookHelper.java @@ -6,6 +6,7 @@ import net.knarcraft.bookswithoutborders.config.Translatable; import net.knarcraft.bookswithoutborders.state.BookDirectory; import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -159,8 +160,8 @@ public final class BookHelper { * @throws IllegalArgumentException

If the book title or author contains the title author separator

*/ @NotNull - public static String getBookFile(@NotNull BookMeta book, @NotNull Player player, boolean isPublic) throws IllegalArgumentException { - String titleAuthorSeparator = BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator(); + public static String getBookFile(@NotNull BookMeta book, @NotNull OfflinePlayer player, boolean isPublic) throws IllegalArgumentException { + String separator = BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator(); String bookName; if (book.hasTitle()) { bookName = book.getTitle(); @@ -171,8 +172,9 @@ public final class BookHelper { bookName = "Untitled"; } + String playerName = player.getName() == null ? player.getUniqueId().toString() : player.getName(); String authorName; - if ((!book.hasAuthor() || isAuthor(player.getName(), book.getAuthor())) && !isPublic) { + if ((!book.hasAuthor() || isAuthor(playerName, book.getAuthor())) && !isPublic) { //Store as unique id to account for name changes authorName = player.getUniqueId().toString(); } else if (!book.hasAuthor()) { @@ -184,12 +186,14 @@ public final class BookHelper { } } - if (bookName.contains(titleAuthorSeparator) || authorName.contains(titleAuthorSeparator)) { - throw new IllegalArgumentException("The author or title contains the title author separator. Saving this " + + if (InputCleaningHelper.cleanString(bookName).contains(separator) || + InputCleaningHelper.cleanString(authorName).contains(separator)) { + throw new IllegalArgumentException("The author; " + authorName + " or title; " + bookName + + " contains the title author separator (" + separator + "). Saving this " + "book would lead to unexpected problems."); } - return InputCleaningHelper.cleanString(bookName + titleAuthorSeparator + authorName); + return InputCleaningHelper.cleanString(bookName + separator + authorName); } /** diff --git a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookToFromTextHelper.java b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookToFromTextHelper.java index df65342..7924d41 100644 --- a/src/main/java/net/knarcraft/bookswithoutborders/utility/BookToFromTextHelper.java +++ b/src/main/java/net/knarcraft/bookswithoutborders/utility/BookToFromTextHelper.java @@ -39,7 +39,7 @@ public final class BookToFromTextHelper { */ public static void bookToYml(@NotNull String path, @NotNull String fileName, @NotNull BookMeta bookMetadata) throws IOException { FileConfiguration bookYml = getBookConfiguration(bookMetadata); - bookYml.save(path + fileName + ".yml"); + bookYml.save(new File(path, fileName + ".yml")); } /** diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index b8aa604..2d79141 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -109,6 +109,10 @@ commands: description: Deletes one page from a book usage: / permission: bookswithoutborders.deletepage + migrateBooks: + description: Migrates all txt books to yml, and fixes any incorrect filenames + usage: / + permission: bookswithoutborders.admin permissions: bookswithoutborders.*: description: Grants all permissions