diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/AbstractDataSourceHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/AbstractDataSourceHandler.java index ec6c50f85..5b425a72f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/AbstractDataSourceHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/AbstractDataSourceHandler.java @@ -80,11 +80,6 @@ public abstract class AbstractDataSourceHandler { this.level = level; this.saveDir = (saveDirOverride == null) ? saveStructure.getFullDataFolder(level.getLevelWrapper()) : saveDirOverride; - if (!this.saveDir.exists() && !this.saveDir.mkdirs()) - { - LOGGER.warn("Unable to create full data folder, file saving may fail."); - } - this.repo = this.createRepo(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV1.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV1.java index 4c1e443fe..7081bea13 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV1.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV1.java @@ -64,7 +64,7 @@ public class FullDataSourceProviderV1 { try { - return new FullDataSourceV1Repo("jdbc:sqlite", this.saveDir.getPath() + "/" + AbstractSaveStructure.DATABASE_NAME); + return new FullDataSourceV1Repo("jdbc:sqlite", new File(this.saveDir.getPath() + File.pathSeparator + AbstractSaveStructure.DATABASE_NAME)); } catch (SQLException e) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV2.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV2.java index e19751fc9..7a45f8a1e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV2.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV2.java @@ -142,7 +142,7 @@ public class FullDataSourceProviderV2 { try { - return new FullDataSourceV2Repo("jdbc:sqlite", this.saveDir.getPath() + "/" + AbstractSaveStructure.DATABASE_NAME); + return new FullDataSourceV2Repo("jdbc:sqlite", new File(this.saveDir.getPath() + File.pathSeparator + AbstractSaveStructure.DATABASE_NAME)); } catch (SQLException e) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java index 5a7e3f5b3..6cb44e31e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java @@ -33,6 +33,7 @@ import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; +import java.io.File; import java.sql.SQLException; import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; @@ -59,12 +60,12 @@ public abstract class AbstractDhLevel implements IDhLevel protected AbstractDhLevel() { this.chunkToLodBuilder = new ChunkToLodBuilder(); } - protected void createAndSetChunkHashRepo(String databaseFilePath) + protected void createAndSetChunkHashRepo(File databaseFile) { ChunkHashRepo newChunkHashRepo = null; try { - newChunkHashRepo = new ChunkHashRepo("jdbc:sqlite", databaseFilePath); + newChunkHashRepo = new ChunkHashRepo("jdbc:sqlite", databaseFile); } catch (SQLException e) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java index 42a9d5746..f3fa99055 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java @@ -35,7 +35,6 @@ import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; import java.io.File; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -63,7 +62,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel this.dataFileHandler = new RemoteFullDataSourceProvider(this, saveStructure, fullDataSaveDirOverride); this.clientside = new ClientLevelModule(this); - this.createAndSetChunkHashRepo(this.dataFileHandler.repo.databaseLocation); + this.createAndSetChunkHashRepo(this.dataFileHandler.repo.databaseFile); if (enableRendering) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java index 42b204e21..5a210bf08 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java @@ -39,7 +39,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapp import org.apache.logging.log4j.Logger; import java.awt.*; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -69,7 +68,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev this.serverLevelWrapper = serverLevelWrapper; this.serverside = new ServerLevelModule(this, saveStructure); this.clientside = new ClientLevelModule(this); - this.createAndSetChunkHashRepo(this.serverside.fullDataFileHandler.repo.databaseLocation); + this.createAndSetChunkHashRepo(this.serverside.fullDataFileHandler.repo.databaseFile); LOGGER.info("Started " + DhClientServerLevel.class.getSimpleName() + " for " + serverLevelWrapper + " with saves at " + saveStructure); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index 142c1ade1..13a520ff8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -28,7 +28,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import org.apache.logging.log4j.Logger; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -48,7 +47,7 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel } this.serverLevelWrapper = serverLevelWrapper; this.serverside = new ServerLevelModule(this, saveStructure); - this.createAndSetChunkHashRepo(this.serverside.fullDataFileHandler.repo.databaseLocation); + this.createAndSetChunkHashRepo(this.serverside.fullDataFileHandler.repo.databaseFile); LOGGER.info("Started DHLevel for {} with saves at {}", serverLevelWrapper, saveStructure); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/DatabaseUpdater.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/DatabaseUpdater.java index 6d5c70b61..b3696d065 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/sql/DatabaseUpdater.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/DatabaseUpdater.java @@ -97,7 +97,7 @@ public class DatabaseUpdater Map scriptAlreadyRunResult = repo.queryDictionaryFirst("SELECT EXISTS(SELECT 1 FROM "+SCHEMA_TABLE_NAME+" WHERE ScriptName='"+resource.name+"') as 'existingCount';"); if (scriptAlreadyRunResult != null && (int) scriptAlreadyRunResult.get("existingCount") == 0) { - LOGGER.info("Running SQL update script: ["+resource.name+"], for repo: ["+repo.databaseLocation+"]"); + LOGGER.info("Running SQL update script: ["+resource.name+"], for repo: ["+repo.databaseFile +"]"); int sqlIndex = 0; @@ -147,7 +147,7 @@ public class DatabaseUpdater catch (RuntimeException e) { // updating needs to stop to prevent data corruption - LOGGER.error("Unexpected error running database update script ["+resource.name+"] on database ["+repo.databaseLocation+"], stopping database update. Database reading/writing may fail if you continue. \n" + + LOGGER.error("Unexpected error running database update script ["+resource.name+"] on database ["+repo.databaseFile +"], stopping database update. Database reading/writing may fail if you continue. \n" + "Error: ["+e.getMessage()+"]. \n" + "Sql Script:["+resource.queryString+"]", e); throw e; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/AbstractDhRepo.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/AbstractDhRepo.java index 9d9b548e4..b4377a85d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/AbstractDhRepo.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/AbstractDhRepo.java @@ -26,6 +26,8 @@ import com.seibel.distanthorizons.core.sql.dto.IBaseDTO; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; +import java.io.File; +import java.io.IOException; import java.sql.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -49,7 +51,7 @@ public abstract class AbstractDhRepo> implemen private final Connection connection; public final String databaseType; - public final String databaseLocation; + public final File databaseFile; public final Class dtoClass; @@ -59,16 +61,15 @@ public abstract class AbstractDhRepo> implemen - //=============// // constructor // //=============// /** @throws SQLException if the repo is unable to access the database or has trouble updating said database. */ - public AbstractDhRepo(String databaseType, String databaseLocation, Class dtoClass) throws SQLException + public AbstractDhRepo(String databaseType, File databaseFile, Class dtoClass) throws SQLException { this.databaseType = databaseType; - this.databaseLocation = databaseLocation; + this.databaseFile = databaseFile; this.dtoClass = dtoClass; @@ -93,9 +94,55 @@ public abstract class AbstractDhRepo> implemen } + + //==========================// + // database file validation // + //==========================// + + // check that the database file exists + if (!databaseFile.exists()) + { + // check that the parent folder exists + File parentFolder = databaseFile.getParentFile(); + if (parentFolder != null && !parentFolder.exists()) + { + if (!parentFolder.mkdirs()) + { + throw new RuntimeException("Unable to create the necessary parent folders for the database file at location ["+databaseFile.getPath()+"]."); + } + } + + if (!databaseFile.exists()) + { + try + { + boolean fileCreated = databaseFile.createNewFile(); + } + catch (IOException e) + { + throw new RuntimeException("Unable to create database file at location ["+databaseFile.getPath()+"] due to error: ["+e.getMessage()+"]", e); + } + } + } + + if (!databaseFile.canRead()) + { + throw new RuntimeException("Unable to read database file at location ["+databaseFile.getPath()+"], please make sure the folder and file has the correct permissions."); + } + if (!databaseFile.canWrite()) + { + throw new RuntimeException("Unable to write database file at location ["+databaseFile.getPath()+"], please make sure the folder and file aren't set to read-only."); + } + + + + //==================// + // connection setup // + //==================// + // get or create the connection, // reusing existing connections reduces the chance of locking the database during trivial queries - this.connectionString = this.databaseType+":"+this.databaseLocation; + this.connectionString = this.databaseType+":"+this.databaseFile.getPath(); this.connection = CONNECTIONS_BY_CONNECTION_STRING.computeIfAbsent(this.connectionString, (connectionString) -> diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/ChunkHashRepo.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/ChunkHashRepo.java index ad9152886..4fc545b6a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/ChunkHashRepo.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/ChunkHashRepo.java @@ -26,6 +26,7 @@ import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import org.apache.logging.log4j.Logger; +import java.io.File; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Map; @@ -40,9 +41,9 @@ public class ChunkHashRepo extends AbstractDhRepo // constructor // //=============// - public ChunkHashRepo(String databaseType, String databaseLocation) throws SQLException + public ChunkHashRepo(String databaseType, File databaseFile) throws SQLException { - super(databaseType, databaseLocation, ChunkHashDTO.class); + super(databaseType, databaseFile, ChunkHashDTO.class); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/FullDataSourceV1Repo.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/FullDataSourceV1Repo.java index 0c7c444b7..a61bc22ab 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/FullDataSourceV1Repo.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/FullDataSourceV1Repo.java @@ -26,6 +26,7 @@ import com.seibel.distanthorizons.coreapi.util.StringUtil; import it.unimi.dsi.fastutil.longs.LongArrayList; import org.jetbrains.annotations.Nullable; +import java.io.File; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; @@ -42,9 +43,9 @@ public class FullDataSourceV1Repo extends AbstractDhRepo { - public TestCompoundKeyRepo(String databaseType, String databaseLocation) throws SQLException + public TestCompoundKeyRepo(String databaseType, File databaseFile) throws SQLException { - super(databaseType, databaseLocation, TestCompoundKeyDto.class); + super(databaseType, databaseFile, TestCompoundKeyDto.class); // note: this should only ever be done with the test repo. // All long term tables should be created using a sql Script. diff --git a/core/src/test/java/testItems/sql/TestPrimaryKeyRepo.java b/core/src/test/java/testItems/sql/TestPrimaryKeyRepo.java index 347e6d686..9df6a16c8 100644 --- a/core/src/test/java/testItems/sql/TestPrimaryKeyRepo.java +++ b/core/src/test/java/testItems/sql/TestPrimaryKeyRepo.java @@ -21,6 +21,7 @@ package testItems.sql; import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo; +import java.io.File; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Map; @@ -28,9 +29,9 @@ import java.util.Map; public class TestPrimaryKeyRepo extends AbstractDhRepo { - public TestPrimaryKeyRepo(String databaseType, String databaseLocation) throws SQLException + public TestPrimaryKeyRepo(String databaseType, File databaseFile) throws SQLException { - super(databaseType, databaseLocation, TestSingleKeyDto.class); + super(databaseType, databaseFile, TestSingleKeyDto.class); // note: this should only ever be done with the test repo. // All long term tables should be created using a sql Script. diff --git a/core/src/test/java/tests/CompressionTest.java b/core/src/test/java/tests/CompressionTest.java index d871b58ac..f17af721a 100644 --- a/core/src/test/java/tests/CompressionTest.java +++ b/core/src/test/java/tests/CompressionTest.java @@ -259,7 +259,7 @@ public class CompressionTest File uncompressedDatabaseFile = new File(uncompressedDatabaseFilePath); Assert.assertTrue(uncompressedDatabaseFile.exists()); - FullDataSourceV2Repo uncompressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFilePath); + FullDataSourceV2Repo uncompressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile); String compressedDatabaseFilePath = TEST_DIR + "/output/" + DB_FILE_NAME_PREFIX + "_" + compressorName + ".sqlite"; @@ -267,7 +267,7 @@ public class CompressionTest compressedDatabaseFile.mkdirs(); compressedDatabaseFile.delete(); Assert.assertTrue(!compressedDatabaseFile.exists()); - FullDataSourceV2Repo compressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", compressedDatabaseFilePath); + FullDataSourceV2Repo compressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile); diff --git a/core/src/test/java/tests/DhRepoSqliteTest.java b/core/src/test/java/tests/DhRepoSqliteTest.java index 066106110..bb9707b6c 100644 --- a/core/src/test/java/tests/DhRepoSqliteTest.java +++ b/core/src/test/java/tests/DhRepoSqliteTest.java @@ -62,7 +62,7 @@ public class DhRepoSqliteTest TestPrimaryKeyRepo primaryKeyRepo = null; try { - primaryKeyRepo = new TestPrimaryKeyRepo(DATABASE_TYPE, DB_FILE_NAME); + primaryKeyRepo = new TestPrimaryKeyRepo(DATABASE_TYPE, new File(DB_FILE_NAME)); @@ -78,8 +78,8 @@ public class DhRepoSqliteTest } // check that the update scripts aren't run multiple times - TestPrimaryKeyRepo altDataRepoOne = new TestPrimaryKeyRepo(DATABASE_TYPE, DB_FILE_NAME); - TestPrimaryKeyRepo altDataRepoTwo = new TestPrimaryKeyRepo(DATABASE_TYPE, DB_FILE_NAME); + TestPrimaryKeyRepo altDataRepoOne = new TestPrimaryKeyRepo(DATABASE_TYPE, new File(DB_FILE_NAME)); + TestPrimaryKeyRepo altDataRepoTwo = new TestPrimaryKeyRepo(DATABASE_TYPE, new File(DB_FILE_NAME)); @@ -142,7 +142,7 @@ public class DhRepoSqliteTest TestCompoundKeyRepo compoundKeyRepo = null; try { - compoundKeyRepo = new TestCompoundKeyRepo(DATABASE_TYPE, DB_FILE_NAME); + compoundKeyRepo = new TestCompoundKeyRepo(DATABASE_TYPE, new File(DB_FILE_NAME));