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() {