diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/CoordinatesCommand.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/CoordinatesCommand.java index a45abd5..246c020 100644 --- a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/CoordinatesCommand.java +++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/CoordinatesCommand.java @@ -21,6 +21,7 @@ public class CoordinatesCommand implements CommandExecutor{ case "add" -> new AddCoordinateCommandHandler().handleCommand(sender, args); case "map" -> new GetMapCommandHandler().handleCommand(sender, args); case "help" -> new HelpCommandHandler().handleCommand(sender, args); + case "remove" -> new RemoveCoordinateCommandHandler().handleCommand(sender, args); default -> false; }; } diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/GetCoordinatesCommandHandler.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/GetCoordinatesCommandHandler.java index 7441e2a..72a27a4 100644 --- a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/GetCoordinatesCommandHandler.java +++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/GetCoordinatesCommandHandler.java @@ -7,7 +7,7 @@ import org.bukkit.entity.Player; import nl.interestingcorner.coordinates.db.Coordinate; import nl.interestingcorner.coordinates.db.CoordinatesDatabaseManager; -import nl.interestingcorner.coordinates.gui.CoordinatesGUI; +import nl.interestingcorner.coordinates.gui.ShowCoordinatesGUI; public class GetCoordinatesCommandHandler implements CoordinatesCommandHandler { @@ -31,9 +31,10 @@ public class GetCoordinatesCommandHandler implements CoordinatesCommandHandler { return true; } - CoordinatesGUI.open(player, coords); + new ShowCoordinatesGUI().open(player, coords); } return true; + } catch (Exception e) { sender.sendMessage("An error occurred while getting coordinates: " + e.getMessage()); return false; diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/HelpCommandHandler.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/HelpCommandHandler.java index e8ff360..83cd941 100644 --- a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/HelpCommandHandler.java +++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/HelpCommandHandler.java @@ -14,6 +14,7 @@ public class HelpCommandHandler implements CoordinatesCommandHandler { player.sendMessage(MinecraftColor.AQUA.toColorCode() + "/ic-coords map" + MinecraftColor.WHITE.toColorCode() + " - Get a special map that you can use to teleport between coordinates"); player.sendMessage(MinecraftColor.AQUA.toColorCode() + "/ic-coords get [world]" + MinecraftColor.WHITE.toColorCode() + " - Show all coordinates. Add a world name to filter by world."); player.sendMessage(MinecraftColor.AQUA.toColorCode() + "/ic-coords add [color]" + MinecraftColor.WHITE.toColorCode() + " - Add a new coordinate at you current location with a name, description, and color. If no color is specified, the color will be white"); + player.sendMessage(MinecraftColor.AQUA.toColorCode() + "/ic-coords remove" + MinecraftColor.WHITE.toColorCode() + " - Remove a coordinate. This will show a GUI with all coordinates and you can click on one to remove it."); player.sendMessage(MinecraftColor.AQUA.toColorCode() + "/ic-coords help" + MinecraftColor.WHITE.toColorCode() + " - Show this help message"); return true; diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/RemoveCoordinateCommandHandler.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/RemoveCoordinateCommandHandler.java new file mode 100644 index 0000000..151f7a9 --- /dev/null +++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/commands/RemoveCoordinateCommandHandler.java @@ -0,0 +1,39 @@ +package nl.interestingcorner.coordinates.commands; + +import java.util.List; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import nl.interestingcorner.coordinates.db.Coordinate; +import nl.interestingcorner.coordinates.db.CoordinatesDatabaseManager; +import nl.interestingcorner.coordinates.gui.RemoveCoordinatesGUI; + +public class RemoveCoordinateCommandHandler implements CoordinatesCommandHandler { + + /** + * Removes a coordinate from the database. Usage: /ic-coords remove This + * command will show the GUI to select a coordinate and remove it when a + * coordinate is clicked. + */ + @Override + public boolean handleCommand(CommandSender sender, String[] args) { + if (sender instanceof Player player) { + List coords; + String world = player.getWorld().getName(); + coords = CoordinatesDatabaseManager.INSTANCE.getAllCoordinates(world); + + if (coords.isEmpty()) { + player.sendMessage("No coordinates found! Add some with the §3/ic-coords §5add §fcommand."); + return true; + } + + RemoveCoordinatesGUI gui = new RemoveCoordinatesGUI(); + gui.open(player, coords); + + return true; + } + return false; + } + +} diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/Coordinate.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/Coordinate.java index a3b936c..1af405d 100644 --- a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/Coordinate.java +++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/Coordinate.java @@ -88,7 +88,7 @@ public class Coordinate { public static String createTableStatement = """ CREATE TABLE IF NOT EXISTS coordinates ( - id INTEGER PRIMARY KEY, + id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(255) NOT NULL, description VARCHAR(255), x INT NOT NULL, diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/CoordinatesDatabaseManager.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/CoordinatesDatabaseManager.java index d157582..f4ae51f 100644 --- a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/CoordinatesDatabaseManager.java +++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/db/CoordinatesDatabaseManager.java @@ -1,5 +1,6 @@ package nl.interestingcorner.coordinates.db; +import java.io.File; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -14,7 +15,7 @@ import nl.interestingcorner.core.MinecraftColor; import nl.interestingcorner.core.db.DatabaseManager; import nl.interestingcorner.core.logging.Logger; -public enum CoordinatesDatabaseManager implements nl.interestingcorner.core.db.DatabaseInitializeListener{ +public enum CoordinatesDatabaseManager implements nl.interestingcorner.core.db.DatabaseInitializeListener { INSTANCE; @@ -32,7 +33,7 @@ public enum CoordinatesDatabaseManager implements nl.interestingcorner.core.db.D * Gets all coordinates from the database * * @return a list with all coordinates from the database. Empty if an error - * occurred. + * occurred. */ public List getAllCoordinates() { List result = new ArrayList<>(); @@ -59,7 +60,7 @@ public enum CoordinatesDatabaseManager implements nl.interestingcorner.core.db.D } catch (SQLException e) { this.app.getLogger().log(Level.SEVERE, "Could not get coordinates for world {0}! {1}", - new Object[] { world, e.getMessage() }); + new Object[]{world, e.getMessage()}); } return result; } @@ -91,16 +92,38 @@ public enum CoordinatesDatabaseManager implements nl.interestingcorner.core.db.D return coordinates; } + /** + * Removes the given coordinate from the database. + * + * @param coordinate the coordinate to remove + * @return true if the coordinate was removed successfully, false if an + * error occurred. + */ + public boolean removeCoordinate(Coordinate coordinate) { + String sql = "DELETE FROM coordinates WHERE id = ?"; + try { + PreparedStatement removeCoordinateStatement = this.connection.prepareStatement(sql); + removeCoordinateStatement.setInt(1, coordinate.id); + removeCoordinateStatement.executeUpdate(); + } catch (SQLException e) { + this.app.getLogger().log(Level.SEVERE, "Error removing coordinate from database: {0}", e.getMessage()); + return false; + } + + Logger.INSTANCE.info(TAG, "Removed coordinate '" + coordinate.name + "' (id: " + coordinate.id + ") from database."); + return true; + } + /** * Adds a new coordinate to the database with the given parameters. * - * @param name the name of the coordinate + * @param name the name of the coordinate * @param description a short description of the coordinate - * @param position the position of the coordinate where the player will - * spwan when they teleport to the coordinate - * @param nether if the coordinate is in the nether - * @param world the Multiverse world the coordinate belongs to - * @param color the color to display for the coordinate name + * @param position the position of the coordinate where the player will + * spwan when they teleport to the coordinate + * @param nether if the coordinate is in the nether + * @param world the Multiverse world the coordinate belongs to + * @param color the color to display for the coordinate name * @return true if the command was added successfully, false otherwise */ public boolean addCoordinate(String name, String description, Coordinate.Position position, boolean nether, @@ -132,6 +155,36 @@ public enum CoordinatesDatabaseManager implements nl.interestingcorner.core.db.D coordinate.world, coordinate.color); } + /** + * Tries to set the id of the given coordinate from the database. This is useful for when converting between ItemStacks and Coordinates, since the id is not stored in the ItemStack lore. + * NOTE: The coordinate is identified by all its parameters except for the id, so if there are multiple coordinates with the same parameters, this method may set the id of the wrong coordinate. + * @param coordinate the coordinate to set the id for + * @return true if the id was set successfully, false otherwise + */ + public boolean trySetIdFromDatabase(Coordinate coordinate) { + String sql = "SELECT id FROM coordinates WHERE name = ? AND description = ? AND x = ? AND y = ? AND z = ? AND nether = ? AND world = ?"; + try { + PreparedStatement statement = this.connection.prepareStatement(sql); + statement.setString(1, coordinate.name); + statement.setString(2, coordinate.description); + statement.setInt(3, coordinate.position.x()); + statement.setInt(4, coordinate.position.y()); + statement.setInt(5, coordinate.position.z()); + statement.setBoolean(6, coordinate.nether); + statement.setString(7, coordinate.world); + + ResultSet rs = statement.executeQuery(); + if (rs.next()) { + int id = rs.getInt("id"); + coordinate.id = id; + return true; + } + } catch (SQLException e) { + this.app.getLogger().log(Level.SEVERE, "Error getting id for coordinate: {0}", e.getMessage()); + } + return false; + } + /** * initializes the tables for the database. * @@ -160,10 +213,55 @@ public enum CoordinatesDatabaseManager implements nl.interestingcorner.core.db.D @Override public void initializeDatabaseTables(Connection connection) { this.connection = connection; + + migrateCoordinatesTableIfNeeded(); if (!initializeTables()) { this.app.getLogger().severe("Could not initialize coordinates database tables"); } this.app.getLogger().info("Coordinates database tables initialized"); } + private void migrateCoordinatesTableIfNeeded() { + try { + boolean needsMigration; + try (PreparedStatement checkTable = this.connection.prepareStatement( + "SELECT sql FROM sqlite_master WHERE type='table' AND name='coordinates';"); + ResultSet rs = checkTable.executeQuery()) { + needsMigration = false; + if (rs.next()) { + String createSql = rs.getString("sql"); + // Check if AUTOINCREMENT is missing + if (createSql != null && !createSql.contains("AUTOINCREMENT")) { + needsMigration = true; + } + } + } + if (needsMigration) { + // Backup database file before migration + File backup = DatabaseManager.INSTANCE.createBackupDatabase(); + if (backup != null) { + this.app.getLogger().log(Level.INFO, "Database backup created before migration: {0}", backup.getName()); + } else { + this.app.getLogger().log(Level.WARNING, "Failed to create database backup before migration."); + } + this.app.getLogger().log(Level.WARNING, "Migrating coordinates table to add AUTOINCREMENT..."); + + // Rename old table + this.connection.createStatement().executeUpdate("ALTER TABLE coordinates RENAME TO coordinates_old;"); + try (PreparedStatement createTableStatement = this.connection.prepareStatement(Coordinate.createTableStatement)) { + createTableStatement.executeUpdate(); + } + // Copy data (excluding id, so new ids are generated) + String copySql = "INSERT INTO coordinates (name, description, x, y, z, nether, color, world) " + + "SELECT name, description, x, y, z, nether, color, world FROM coordinates_old;"; + this.connection.createStatement().executeUpdate(copySql); + // Drop old table + this.connection.createStatement().executeUpdate("DROP TABLE coordinates_old;"); + this.app.getLogger().log(Level.INFO, "Migration complete. New ids assigned."); + } + } catch (SQLException e) { + this.app.getLogger().log(Level.SEVERE, "Migration error: {0}", e.getMessage()); + } + } + } diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/CoordinatesGUI.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/CoordinatesGUI.java index 4782afb..0e38ccc 100644 --- a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/CoordinatesGUI.java +++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/CoordinatesGUI.java @@ -1,26 +1,19 @@ package nl.interestingcorner.coordinates.gui; -import java.util.ArrayList; import java.util.List; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; import nl.interestingcorner.coordinates.db.Coordinate; -import nl.interestingcorner.core.gui.GUI; -public class CoordinatesGUI { - public static void open(Player player, List coords) { - List items = new ArrayList<>(); - - for (Coordinate coord : coords) { - ItemStack item = coord.toItem(); - items.add(item); - } - - GUI gui = new GUI("Coordinates", GUI.DEFAULT_PAGE_SIZE); - gui.addItemClickListener(new TeleportItemClickListener()); - gui.setItems(items); - gui.show(player); - } -} +/** + * Class responsible for showing a GUI to the player with coordinates. The implementation decides what is done with those coordinates when the player clicks them + */ +public abstract class CoordinatesGUI { + /** + * Opens the GUI for the player with the given coordinates. + * @param player the player to show the GUI to + * @param coords the coordinates to show in the GUI + */ + public abstract void open(Player player, List coords); +} \ No newline at end of file diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/RemoveCoordinatesGUI.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/RemoveCoordinatesGUI.java new file mode 100644 index 0000000..507480f --- /dev/null +++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/RemoveCoordinatesGUI.java @@ -0,0 +1,30 @@ +package nl.interestingcorner.coordinates.gui; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import nl.interestingcorner.coordinates.db.Coordinate; +import nl.interestingcorner.coordinates.gui.clicklisteners.RemoveItemClickListener; +import nl.interestingcorner.core.gui.GUI; + +public class RemoveCoordinatesGUI extends CoordinatesGUI{ + + @Override + public void open(Player player, List coords) { + List items = new ArrayList<>(); + + for (Coordinate coord : coords) { + ItemStack item = coord.toItem(); + items.add(item); + } + + GUI gui = new GUI("Click coordinate to remove", GUI.DEFAULT_PAGE_SIZE); + gui.addItemClickListener(new RemoveItemClickListener()); + gui.setItems(items); + gui.show(player); + } + +} diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/ShowCoordinatesGUI.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/ShowCoordinatesGUI.java new file mode 100644 index 0000000..6382e18 --- /dev/null +++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/ShowCoordinatesGUI.java @@ -0,0 +1,31 @@ +package nl.interestingcorner.coordinates.gui; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import nl.interestingcorner.coordinates.db.Coordinate; +import nl.interestingcorner.coordinates.gui.clicklisteners.TeleportItemClickListener; +import nl.interestingcorner.core.gui.GUI; + +/** + * Class to show the available coordinates to a player so they can teleport to a coordinate by clicking it. + */ +public class ShowCoordinatesGUI extends CoordinatesGUI { + @Override + public void open(Player player, List coords) { + List items = new ArrayList<>(); + + for (Coordinate coord : coords) { + ItemStack item = coord.toItem(); + items.add(item); + } + + GUI gui = new GUI("Coordinates", GUI.DEFAULT_PAGE_SIZE); + gui.addItemClickListener(new TeleportItemClickListener()); + gui.setItems(items); + gui.show(player); + } +} diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/clicklisteners/RemoveItemClickListener.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/clicklisteners/RemoveItemClickListener.java new file mode 100644 index 0000000..7d73943 --- /dev/null +++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/clicklisteners/RemoveItemClickListener.java @@ -0,0 +1,27 @@ +package nl.interestingcorner.coordinates.gui.clicklisteners; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import nl.interestingcorner.coordinates.db.Coordinate; +import nl.interestingcorner.coordinates.db.CoordinatesDatabaseManager; +import nl.interestingcorner.core.MinecraftColor; +import nl.interestingcorner.core.gui.GUI; +import nl.interestingcorner.core.gui.GUIItemClickListener; + +public class RemoveItemClickListener implements GUIItemClickListener{ + + @Override + public void onItemClick(Player player, GUI gui, ItemStack item, int slot) { + Coordinate coordinate = Coordinate.fromItem(item); + if (!CoordinatesDatabaseManager.INSTANCE.trySetIdFromDatabase(coordinate)) { + player.sendMessage(MinecraftColor.RED + "Error: Could not find coordinate in database. Contact Sem about this issue!"); + return; + } + CoordinatesDatabaseManager.INSTANCE.removeCoordinate(coordinate); + + player.closeInventory(); + player.sendMessage("Removed coordinate " + coordinate.name); + } + +} diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/TeleportItemClickListener.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/clicklisteners/TeleportItemClickListener.java similarity index 95% rename from ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/TeleportItemClickListener.java rename to ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/clicklisteners/TeleportItemClickListener.java index 0a3e7b2..080513c 100644 --- a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/TeleportItemClickListener.java +++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/gui/clicklisteners/TeleportItemClickListener.java @@ -1,4 +1,4 @@ -package nl.interestingcorner.coordinates.gui; +package nl.interestingcorner.coordinates.gui.clicklisteners; import org.bukkit.Location; import org.bukkit.entity.Player; diff --git a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/listeners/PlayerJoinListener.java b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/listeners/PlayerJoinListener.java index e9b2cd9..ce2a69a 100644 --- a/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/listeners/PlayerJoinListener.java +++ b/ic_plugin_coordinates/src/main/java/nl/interestingcorner/coordinates/listeners/PlayerJoinListener.java @@ -9,7 +9,6 @@ import nl.interestingcorner.core.MinecraftColor; public class PlayerJoinListener implements Listener{ @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { - // This is just an example of a listener. You can remove this if you don't need it. event.getPlayer().sendMessage(MinecraftColor.LIGHT_PURPLE.toColorCode() + "This server uses the IC-Coords plugin." + MinecraftColor.YELLOW.toColorCode() + " Use /ic-coords help to see the available commands."); } diff --git a/ic_plugin_coordinates/src/main/resources/plugin.yml b/ic_plugin_coordinates/src/main/resources/plugin.yml index ccb6dbd..301d85e 100644 --- a/ic_plugin_coordinates/src/main/resources/plugin.yml +++ b/ic_plugin_coordinates/src/main/resources/plugin.yml @@ -7,4 +7,4 @@ depend: [IC-core] commands: ic-coords: description: Main command for the coordinates - usage: /ic-coords get | add | map | help. To add a command, use /ic-coords add "name" "description" . \ No newline at end of file + usage: /ic-coords get | add | remove | map | help. To add a command, use /ic-coords add "name" "description" . \ No newline at end of file diff --git a/ic_plugin_core/src/main/java/nl/interestingcorner/core/db/DatabaseManager.java b/ic_plugin_core/src/main/java/nl/interestingcorner/core/db/DatabaseManager.java index 53f8f89..040eb68 100644 --- a/ic_plugin_core/src/main/java/nl/interestingcorner/core/db/DatabaseManager.java +++ b/ic_plugin_core/src/main/java/nl/interestingcorner/core/db/DatabaseManager.java @@ -28,7 +28,7 @@ public enum DatabaseManager { if (this.initialized) { return true; } - + this.app = app; if (!setupDatabase()) { @@ -58,6 +58,12 @@ public enum DatabaseManager { return true; } + /** + * Registers a listener that will be called when the database is + * initialized. This can be used to initialize the tables for the database. + * + * @param listener the listener to register + */ public void registerDatabaseInitializeListener(DatabaseInitializeListener listener) { this.databaseInitializeListeners.add(listener); this.app.getLogger().log(Level.INFO, "Registered database initialize listener: {0}", listener.getClass().getName()); @@ -93,7 +99,7 @@ public enum DatabaseManager { this.connection = DriverManager.getConnection(url); } catch (SQLException e) { this.app.getLogger().log(Level.SEVERE, "Could not connect to database file {0}: {1}", - new Object[] { dbFile, e.getMessage() }); + new Object[]{dbFile, e.getMessage()}); } this.app.getLogger().log(Level.INFO, "Connected to SQLite database: {0}", dbFile.getName()); @@ -102,6 +108,37 @@ public enum DatabaseManager { return true; } + /** + * Creates a backup of the current database file in the plugin data folder. + * The backup file will be named database_backup_TIMESTAMP.db + * + * @return the File object of the backup, or null if failed + */ + public File createBackupDatabase() { + if (this.app == null) { + return null; + } + File dbFile = new File(app.getDataFolder(), "database.db"); + if (!dbFile.exists()) { + return null; + } + String backupName = "database_backup_" + System.currentTimeMillis() + ".db"; + File backupFile = new File(app.getDataFolder(), backupName); + try ( + java.io.FileInputStream in = new java.io.FileInputStream(dbFile); java.io.FileOutputStream out = new java.io.FileOutputStream(backupFile)) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = in.read(buffer)) > 0) { + out.write(buffer, 0, bytesRead); + } + this.app.getLogger().log(Level.INFO, "Database backup created: {0}", backupFile.getName()); + return backupFile; + } catch (Exception e) { + this.app.getLogger().log(Level.SEVERE, "Failed to create database backup: {0}", e.getMessage()); + return null; + } + } + /** * initializes the tables for the database. */