Prevents book migration from locking up the server thread
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
This commit is contained in:
@@ -30,6 +30,7 @@ import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
|
||||
import net.knarcraft.bookswithoutborders.config.BwBCommand;
|
||||
import net.knarcraft.bookswithoutborders.config.StaticMessage;
|
||||
import net.knarcraft.bookswithoutborders.config.Translatable;
|
||||
import net.knarcraft.bookswithoutborders.container.MigrationRequest;
|
||||
import net.knarcraft.bookswithoutborders.handler.BookshelfHandler;
|
||||
import net.knarcraft.bookswithoutborders.listener.BookEventListener;
|
||||
import net.knarcraft.bookswithoutborders.listener.BookshelfListener;
|
||||
@@ -56,9 +57,11 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -67,15 +70,17 @@ import java.util.logging.Level;
|
||||
*/
|
||||
public class BooksWithoutBorders extends JavaPlugin {
|
||||
|
||||
private static ItemFactory itemFactory;
|
||||
private static @NotNull Map<UUID, List<String>> playerBooksList = new HashMap<>();
|
||||
private static @NotNull List<String> publicBooksList = new ArrayList<>();
|
||||
private static Map<Character, Integer> publicLetterIndex;
|
||||
private static Map<UUID, Map<Character, Integer>> playerLetterIndex;
|
||||
private static BooksWithoutBorders booksWithoutBorders;
|
||||
private static BookshelfHandler bookshelfHandler;
|
||||
private static StringFormatter stringFormatter;
|
||||
private static BooksWithoutBordersConfig booksWithoutBordersConfig;
|
||||
|
||||
private ItemFactory itemFactory;
|
||||
private @NotNull Map<UUID, List<String>> playerBooksList = new HashMap<>();
|
||||
private @NotNull List<String> publicBooksList = new ArrayList<>();
|
||||
private Map<Character, Integer> publicLetterIndex;
|
||||
private Map<UUID, Map<Character, Integer>> playerLetterIndex;
|
||||
private BookshelfHandler bookshelfHandler;
|
||||
private StringFormatter stringFormatter;
|
||||
private BooksWithoutBordersConfig booksWithoutBordersConfig;
|
||||
private final Queue<MigrationRequest> migrationQueue = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Logs a message to the console
|
||||
@@ -93,7 +98,7 @@ public class BooksWithoutBorders extends JavaPlugin {
|
||||
* @return <p>The BwB configuration</p>
|
||||
*/
|
||||
public static BooksWithoutBordersConfig getConfiguration() {
|
||||
return booksWithoutBordersConfig;
|
||||
return getInstance().booksWithoutBordersConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +107,16 @@ public class BooksWithoutBorders extends JavaPlugin {
|
||||
* @return <p>The string formatter</p>
|
||||
*/
|
||||
public static StringFormatter getStringFormatter() {
|
||||
return stringFormatter;
|
||||
return getInstance().stringFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the migration queue
|
||||
*
|
||||
* @return <p>The migration queue</p>
|
||||
*/
|
||||
public static Queue<MigrationRequest> getMigrationQueue() {
|
||||
return getInstance().migrationQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,17 +129,17 @@ public class BooksWithoutBorders extends JavaPlugin {
|
||||
@NotNull
|
||||
public static List<String> getAvailableBooks(@NotNull CommandSender sender, boolean getPublic) {
|
||||
if (getPublic) {
|
||||
return new ArrayList<>(publicBooksList);
|
||||
return new ArrayList<>(getInstance().publicBooksList);
|
||||
} else if (sender instanceof Player player) {
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
if (!playerBooksList.containsKey(playerUUID)) {
|
||||
if (!getInstance().playerBooksList.containsKey(playerUUID)) {
|
||||
List<String> newFiles = BookFileHelper.listFiles(sender, false);
|
||||
if (newFiles != null) {
|
||||
playerBooksList.put(playerUUID, newFiles);
|
||||
playerLetterIndex.put(playerUUID, BookFileHelper.populateLetterIndices(newFiles));
|
||||
getInstance().playerBooksList.put(playerUUID, newFiles);
|
||||
getInstance().playerLetterIndex.put(playerUUID, BookFileHelper.populateLetterIndices(newFiles));
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(playerBooksList.get(playerUUID));
|
||||
return new ArrayList<>(getInstance().playerBooksList.get(playerUUID));
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
@@ -140,9 +154,9 @@ public class BooksWithoutBorders extends JavaPlugin {
|
||||
@NotNull
|
||||
public static Map<Character, Integer> getLetterIndex(@Nullable UUID playerIndex) {
|
||||
if (playerIndex == null) {
|
||||
return publicLetterIndex;
|
||||
return getInstance().publicLetterIndex;
|
||||
} else {
|
||||
Map<Character, Integer> letterIndex = playerLetterIndex.get(playerIndex);
|
||||
Map<Character, Integer> letterIndex = getInstance().playerLetterIndex.get(playerIndex);
|
||||
return Objects.requireNonNullElseGet(letterIndex, HashMap::new);
|
||||
}
|
||||
}
|
||||
@@ -159,11 +173,11 @@ public class BooksWithoutBorders extends JavaPlugin {
|
||||
return;
|
||||
}
|
||||
if (updatePublic) {
|
||||
publicBooksList = newFiles;
|
||||
publicLetterIndex = BookFileHelper.populateLetterIndices(newFiles);
|
||||
getInstance().publicBooksList = newFiles;
|
||||
getInstance().publicLetterIndex = BookFileHelper.populateLetterIndices(newFiles);
|
||||
} else if (sender instanceof Player player) {
|
||||
playerBooksList.put(player.getUniqueId(), newFiles);
|
||||
playerLetterIndex.put(player.getUniqueId(), BookFileHelper.populateLetterIndices(newFiles));
|
||||
getInstance().playerBooksList.put(player.getUniqueId(), newFiles);
|
||||
getInstance().playerLetterIndex.put(player.getUniqueId(), BookFileHelper.populateLetterIndices(newFiles));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,8 +185,8 @@ public class BooksWithoutBorders extends JavaPlugin {
|
||||
* Clears book data such as per-player lists and per-player character indexes
|
||||
*/
|
||||
public static void clearBookData() {
|
||||
playerBooksList = new HashMap<>();
|
||||
playerLetterIndex = new HashMap<>();
|
||||
getInstance().playerBooksList = new HashMap<>();
|
||||
getInstance().playerLetterIndex = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
@@ -308,7 +322,7 @@ public class BooksWithoutBorders extends JavaPlugin {
|
||||
* @return <p>The bookshelf handler</p>
|
||||
*/
|
||||
public static BookshelfHandler getBookshelfHandler() {
|
||||
return bookshelfHandler;
|
||||
return getInstance().bookshelfHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,7 +332,7 @@ public class BooksWithoutBorders extends JavaPlugin {
|
||||
*/
|
||||
@NotNull
|
||||
public static ItemFactory getItemFactory() {
|
||||
return itemFactory;
|
||||
return getInstance().itemFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -2,24 +2,19 @@ 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 net.knarcraft.bookswithoutborders.container.MigrationRequest;
|
||||
import net.knarcraft.bookswithoutborders.thread.MigrationQueueThread;
|
||||
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.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.Queue;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
@@ -35,12 +30,15 @@ public class CommandMigrate implements TabExecutor {
|
||||
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!");
|
||||
}
|
||||
BooksWithoutBorders.sendSuccessMessage(player, "Starting book migration...");
|
||||
Queue<MigrationRequest> filesToMigrate = new LinkedList<>();
|
||||
findFilesToMigrate(bookDirectory, filesToMigrate, player);
|
||||
BooksWithoutBorders.getMigrationQueue().addAll(filesToMigrate);
|
||||
|
||||
BooksWithoutBorders instance = BooksWithoutBorders.getInstance();
|
||||
MigrationQueueThread queueThread = new MigrationQueueThread();
|
||||
int taskId = instance.getServer().getScheduler().runTaskTimer(instance, queueThread, 1L, 1L).getTaskId();
|
||||
queueThread.setTaskId(taskId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -52,98 +50,25 @@ public class CommandMigrate implements TabExecutor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the given folder recursively
|
||||
* Finds all books that should be migrated
|
||||
*
|
||||
* @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>
|
||||
* @param folder <p>The folder to search for books</p>
|
||||
* @param queue <p>The list to append found files to</p>
|
||||
* @param player <p>The player that initiated the migration</p>
|
||||
*/
|
||||
private boolean migrateFiles(@NotNull File folder, @NotNull Player player) {
|
||||
private void findFilesToMigrate(@NotNull File folder, @NotNull Queue<MigrationRequest> queue, @NotNull Player player) {
|
||||
File[] files = folder.listFiles();
|
||||
if (files == null) {
|
||||
BooksWithoutBorders.log(Level.WARNING, "Unable to access directory " + folder.getName() + " !");
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
boolean success = true;
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
success = success & migrateFiles(file, player);
|
||||
findFilesToMigrate(file, queue, player);
|
||||
} else if (file.isFile()) {
|
||||
success = success & migrateFile(file, player);
|
||||
queue.add(new MigrationRequest(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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,15 @@
|
||||
package net.knarcraft.bookswithoutborders.container;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* A request for migrating a book
|
||||
*
|
||||
* @param file <p>The file to migrate</p>
|
||||
* @param player <p>The player that initiated the migration</p>
|
||||
*/
|
||||
public record MigrationRequest(@NotNull File file, @NotNull Player player) {
|
||||
}
|
@@ -0,0 +1,150 @@
|
||||
package net.knarcraft.bookswithoutborders.thread;
|
||||
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.container.MigrationRequest;
|
||||
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.entity.Player;
|
||||
import org.bukkit.inventory.meta.BookMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* A thread for doing book migrations without locking up the main thread
|
||||
*/
|
||||
public class MigrationQueueThread implements Runnable {
|
||||
|
||||
private Boolean success = null;
|
||||
private int taskId;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long systemTime = System.nanoTime();
|
||||
//Repeat for at most 0.025 seconds
|
||||
while (System.nanoTime() - systemTime < 25000000) {
|
||||
if (pollQueue()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the task id used for stopping this task
|
||||
*
|
||||
* @param taskId <p>The id of this task</p>
|
||||
*/
|
||||
public void setTaskId(int taskId) {
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls the migration queue for any waiting requests
|
||||
*
|
||||
* @return <p>True if the queue is empty and it's safe to quit</p>
|
||||
*/
|
||||
public boolean pollQueue() {
|
||||
MigrationRequest migrationRequest = BooksWithoutBorders.getMigrationQueue().poll();
|
||||
if (migrationRequest == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (success == null) {
|
||||
success = true;
|
||||
}
|
||||
|
||||
success = success & migrateFile(migrationRequest.file(), migrationRequest.player());
|
||||
|
||||
if (BooksWithoutBorders.getMigrationQueue().peek() == null) {
|
||||
Player player = migrationRequest.player();
|
||||
if (success) {
|
||||
BooksWithoutBorders.sendSuccessMessage(player, "Successfully migrated all books");
|
||||
} else {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Failed to migrate all books");
|
||||
}
|
||||
BooksWithoutBorders.getInstance().getServer().getScheduler().cancelTask(this.taskId);
|
||||
success = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user