Adds a migrate command for fixing book names and text -> yml

This commit is contained in:
2025-08-14 00:23:22 +02:00
parent 4243c484c4
commit 790e3d1531
9 changed files with 191 additions and 9 deletions

View File

@@ -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());
}
/**

View File

@@ -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<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
return List.of();
}
/**
* Migrates the given folder recursively
*
* @param folder <p>The folder to migrate files for</p>
* @param player <p>The player causing this code to be executed</p>
* @return <p>If all migrations were successful</p>
*/
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 <p>The file to migrate</p>
* @param player <p>The player causing this code to be executed</p>
* @return <p>True if the migration completed successfully</p>
*/
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 <p>The parent folder the file belongs to</p>
* @param newName <p>The new name of the file</p>
* @param bookMeta <p>The metadata of the book to migrate</p>
* @param oldFile <p>The old file path, in case it should be deleted</p>
* @return <p>True if successfully saved</p>
*/
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;
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -220,6 +220,24 @@ public final class BookFileHelper {
}
}
/**
* Strips the extension from the given path
*
* @param path <p>The path to strip the extension from</p>
* @return <p>The input with the extension stripped</p>
*/
@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
*

View File

@@ -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 <p>If the book title or author contains the title author separator</p>
*/
@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);
}
/**

View File

@@ -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"));
}
/**

View File

@@ -109,6 +109,10 @@ commands:
description: Deletes one page from a book
usage: /<command> <page>
permission: bookswithoutborders.deletepage
migrateBooks:
description: Migrates all txt books to yml, and fixes any incorrect filenames
usage: /<command>
permission: bookswithoutborders.admin
permissions:
bookswithoutborders.*:
description: Grants all permissions