diff --git a/Common/Source/pom.xml b/Common/Source/pom.xml index 6384f17..4638a47 100644 --- a/Common/Source/pom.xml +++ b/Common/Source/pom.xml @@ -63,7 +63,7 @@ shade - true + false org.yaml.snakeyaml @@ -148,7 +148,7 @@ com.mysql mysql-connector-j - 9.1.0 + 9.3.0 jar provided @@ -182,7 +182,6 @@ 3.1.8 provided - org.mongodb mongo-java-driver diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java b/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java index 944b2f6..88b5347 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java @@ -57,10 +57,10 @@ import java.util.List; @MavenLibrary( groupId = "com.mysql", artifactId = "mysql-connector-j", - version = "9.1.0", + version = "9.3.0", relocations = { @Relocate(from = "com.my\\" + "sql.cj", to = "dev.brighten.antivpn.shaded.com.mysql.cj"), - @Relocate(from = "com.my\\" + "sql.jdbc", to = "dev.brighten.antivpn.shaded.com.mysql.jdbc") + @Relocate(from = "com.my\\" + "sql.jdbc", to = "dev.brighten.antivpn.shaded.com.mysql.jdbc"), } ) @MavenLibrary(groupId = "com.\\github\\.ben-manes\\.caffeine", artifactId = "caffeine", version = "3.1.8", diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java index 3235abb..2969feb 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java @@ -359,15 +359,37 @@ public class H2VPN implements VPNDatabase { public void backupDatabase() { File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases"); - if(!dataFolder.exists()) { + if(!dataFolder.exists() || MySQL.isClosed()) { return; } - List files = new ArrayList<>(List.of(Optional.ofNullable(dataFolder.listFiles()).orElse(new File[0]))); - files.sort(Comparator.comparingLong(File::lastModified)); + try { + var connection = Query.getConn(); + if (connection == null || connection.getMetaData() == null + || !connection.getMetaData().getDatabaseProductName().equalsIgnoreCase("H2")) { + return; + } + } catch (SQLException e) { + AntiVPN.getInstance().getExecutor().logException("Could not verify database type before H2 backup.", e); + return; + } - for (File file : files) { - MySQL.backupOldDB(file, dataFolder); + File backupDir = new File(dataFolder, "backups"); + if (!backupDir.exists() && !backupDir.mkdirs()) { + AntiVPN.getInstance().getExecutor().log("Could not create backup directory"); + return; + } + + File backupFile = new File(backupDir, "database.h2_backup_" + System.currentTimeMillis() + ".zip"); + String backupPath = backupFile.getAbsolutePath() + .replace("\\", "/") + .replace("'", "''"); + + try (var statement = Query.prepare("BACKUP TO '" + backupPath + "'")) { + statement.execute(); + AntiVPN.getInstance().getExecutor().log("Created H2 backup at " + backupFile.getName()); + } catch (SQLException e) { + AntiVPN.getInstance().getExecutor().logException("Could not create H2 backup before migration.", e); } } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/First.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/First.java index 4724ca2..8f7064a 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/First.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/First.java @@ -24,6 +24,7 @@ import dev.brighten.antivpn.database.sql.utils.Query; import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.utils.MiscUtils; +import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -51,11 +52,11 @@ public class First implements Version { .append(versionNumber())).execute(); AntiVPN.getInstance().getExecutor().log("Creating indexes..."); - closeOnEnd(Query.prepare("create index if not exists `uuid_1` on `whitelisted` (`uuid`)")).execute(); - closeOnEnd(Query.prepare("create index if not exists `ip_1` on `responses` (`ip`)")).execute(); - closeOnEnd(Query.prepare("create index if not exists `proxy_1` on `responses` (`proxy`)")).execute(); - closeOnEnd(Query.prepare("create index if not exists `inserted_1` on `responses` (`inserted`)")).execute(); - closeOnEnd(Query.prepare("create index if not exists `ip_1` on `whitelisted-ips` (`ip`)")).execute(); + createIndexIfAbsent("whitelisted", "uuid_1", "`uuid`"); + createIndexIfAbsent("responses", "ip_1", "`ip`"); + createIndexIfAbsent("responses", "proxy_1", "`proxy`"); + createIndexIfAbsent("responses", "inserted_1", "`inserted`"); + createIndexIfAbsent("whitelisted-ips", "ip_1", "`ip`"); } catch (SQLException e) { throw new DatabaseException("Failed to update database", e); } finally { @@ -69,6 +70,47 @@ public class First implements Version { return statement; } + protected void createIndexIfAbsent(String tableName, String indexName, String columnList) throws SQLException { + if (hasIndex(tableName, indexName)) { + return; + } + + closeOnEnd(Query.prepare(String.format( + "create index `%s` on `%s` (%s)", + indexName, + tableName, + columnList + ))).execute(); + } + + protected void dropIndexIfPresent(String tableName, String indexName) throws SQLException { + if (!hasIndex(tableName, indexName)) { + return; + } + + closeOnEnd(Query.prepare(String.format( + "drop index `%s` on `%s`", + indexName, + tableName + ))).execute(); + } + + protected boolean hasIndex(String tableName, String indexName) throws SQLException { + DatabaseMetaData metaData = Query.getConn().getMetaData(); + + try (ResultSet indexes = metaData.getIndexInfo(null, null, tableName, false, false)) { + while (indexes.next()) { + String existingIndexName = indexes.getString("INDEX_NAME"); + + if (existingIndexName != null && existingIndexName.equalsIgnoreCase(indexName)) { + return true; + } + } + } + + return false; + } + @Override public int versionNumber() { return 0; @@ -78,7 +120,7 @@ public class First implements Version { public boolean needsUpdate(VPNDatabase database) { try(var statement = Query.prepare("select * from `database_version` where version = 0")) { try(ResultSet set = statement.executeQuery()) { - return set.getFetchSize() == 0; + return !set.next(); } } catch (SQLException e) { return true; diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Second.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Second.java index cf151e8..55c9927 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Second.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Second.java @@ -20,6 +20,7 @@ import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.database.DatabaseException; import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.local.H2VPN; +import dev.brighten.antivpn.database.sql.MySqlVPN; import dev.brighten.antivpn.database.sql.utils.ExecutableStatement; import dev.brighten.antivpn.database.sql.utils.Query; import dev.brighten.antivpn.database.version.Version; @@ -31,12 +32,12 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -public class Second implements Version { +public class Second extends First implements Version { private final List toClose = new ArrayList<>(); @Override public void update(VPNDatabase database) throws DatabaseException { - if(database instanceof H2VPN h2VPN) { + if(database instanceof H2VPN h2VPN && !(database instanceof MySqlVPN)) { h2VPN.backupDatabase(); } List whitelistedIps = new ArrayList<>(); @@ -58,7 +59,7 @@ public class Second implements Version { "ip_start BIGINT NOT NULL, " + "ip_end BIGINT NOT NULL)")) .execute(); - closeOnEnd(Query.prepare("CREATE INDEX idx_ip_range ON `whitelisted-ranges` (ip_start, ip_end)")).execute(); + createIndexIfAbsent("whitelisted-ranges", "idx_ip_range", "ip_start, ip_end"); var cidrs = whitelistedIps.stream().map(ip -> { try { @@ -84,7 +85,7 @@ public class Second implements Version { } } - closeOnEnd(Query.prepare("DROP INDEX ip_1 on `whitelisted-ips`")).execute(); + dropIndexIfPresent("whitelisted-ips", "ip_1"); closeOnEnd(Query.prepare("DROP TABLE `whitelisted-ips`")).execute(); closeOnEnd(Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)").append(versionNumber())).execute(); } catch (Throwable e) { @@ -109,9 +110,7 @@ public class Second implements Version { private void rollback(List ipAddresses) throws SQLException { AntiVPN.getInstance().getExecutor().log("Rolling back to version 0..."); - try(var statement = Query.prepare("DROP INDEX idx_ip_range ON `whitelisted-ranges`")) { - statement.execute(); - } + dropIndexIfPresent("whitelisted-ranges", "idx_ip_range"); try(var statement = Query.prepare("DROP TABLE `whitelisted-ranges`")) { statement.execute(); } @@ -124,9 +123,7 @@ public class Second implements Version { statement.execute(); } - try(var statement = Query.prepare("create index if not exists `ip_1` on `whitelisted-ips` (`ip`)")) { - statement.execute(); - } + createIndexIfAbsent("whitelisted-ips", "ip_1", "`ip`"); try(var statement = Query.prepare("DELETE FROM `whitelisted-ips`")) { statement.execute(); @@ -151,7 +148,7 @@ public class Second implements Version { public boolean needsUpdate(VPNDatabase database) { try (var statement = Query.prepare("select * from `database_version` where version = 1")) { try(var set = statement.executeQuery()) { - return set.getFetchSize() == 0; + return !set.next(); } } catch (SQLException e) { return true; diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Third.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Third.java index a152cfd..4d19479 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Third.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Third.java @@ -106,7 +106,7 @@ public class Third implements Version { public boolean needsUpdate(VPNDatabase database) { try (var statement = Query.prepare("select * from `database_version` where version = 2")) { try(var set = statement.executeQuery()) { - return set.getFetchSize() == 0; + return !set.next(); } } catch (SQLException e) { return true; diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java index e2aef2e..21a433b 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java @@ -54,14 +54,11 @@ public class MySQL { } } - private static boolean didRetry = false; - public static void initH2() { - if(didRetry) { - AntiVPN.getInstance().getExecutor().log(Level.WARNING, - "Already attempted to retry H2 connection, skipping."); - return; - } + initH2(true); + } + + private static void initH2(boolean allowRetry) { File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases"); if (!dataFolder.exists() && dataFolder.mkdirs()) { AntiVPN.getInstance().getExecutor().log("Created database directory"); @@ -85,9 +82,13 @@ public class MySQL { AntiVPN.getInstance().getExecutor() .log("H2 database file is incompatible with this version of AntiVPN. " + "Backing up old database file..."); - backupOldDB(dbFile, dataFolder); - initH2(); - didRetry = true; + shutdown(); + if (allowRetry && backupOldDB(dbFile, dataFolder)) { + initH2(false); + } else { + AntiVPN.getInstance().getExecutor().log( + "Could not back up and remove the incompatible H2 database file automatically."); + } } else { AntiVPN.getInstance().getExecutor().logException("Failed to load H2 database: " + ex.getCause().toString(), ex); } @@ -97,31 +98,42 @@ public class MySQL { } } - public static void backupOldDB(File dbFile, File dataFolder) { - if (dbFile.exists()) { - try { - // Optional: Make backup first - File backupDir = new File(dataFolder, "backups"); - if(backupDir.mkdirs()) { - AntiVPN.getInstance().getExecutor().log("Created backup directory"); - } else { - AntiVPN.getInstance().getExecutor().log("Backup directory already exists"); - } - File backupFile = new File(backupDir, dbFile.getName() + ".backup_" + System.currentTimeMillis()); - Files.copy(dbFile.toPath(), backupFile.toPath()); - - // Actually delete the file - if (!dbFile.delete()) { - // If normal delete fails, try force delete on JVM exit - dbFile.deleteOnExit(); - AntiVPN.getInstance().getExecutor().log("Could not delete database file - will try again on shutdown"); - } else { - AntiVPN.getInstance().getExecutor().log("Successfully deleted incompatible database file"); - } - } catch (IOException ex) { - AntiVPN.getInstance().getExecutor().logException("Failed to handle database file", ex); - } + public static boolean backupOldDB(File dbFile, File dataFolder) { + if (!dbFile.exists()) { + return true; } + + if (!dbFile.isFile()) { + AntiVPN.getInstance().getExecutor().log("Skipping backup for non-file path: " + dbFile.getAbsolutePath()); + return false; + } + + try { + File backupDir = new File(dataFolder, "backups"); + if(backupDir.mkdirs()) { + AntiVPN.getInstance().getExecutor().log("Created backup directory"); + } else if (backupDir.exists()) { + AntiVPN.getInstance().getExecutor().log("Backup directory already exists"); + } else { + AntiVPN.getInstance().getExecutor().log("Could not create backup directory"); + return false; + } + File backupFile = new File(backupDir, dbFile.getName() + ".backup_" + System.currentTimeMillis()); + Files.copy(dbFile.toPath(), backupFile.toPath()); + + if (!dbFile.delete()) { + dbFile.deleteOnExit(); + AntiVPN.getInstance().getExecutor().log("Could not delete database file - will try again on shutdown"); + return false; + } + + AntiVPN.getInstance().getExecutor().log("Successfully deleted incompatible database file"); + return true; + } catch (IOException ex) { + AntiVPN.getInstance().getExecutor().logException("Failed to handle database file", ex); + } + + return false; } public static void use() {