Throw an exception if the file handler can't read/write to the DB

This commit is contained in:
James Seibel
2024-06-23 08:22:35 -05:00
parent 0d16c037f5
commit 30dda058fe
16 changed files with 83 additions and 38 deletions
@@ -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();
}
@@ -64,7 +64,7 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
{
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)
{
@@ -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)
{
@@ -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)
{
@@ -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)
{
@@ -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);
}
@@ -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);
}
@@ -97,7 +97,7 @@ public class DatabaseUpdater
Map<String, Object> 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;
@@ -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<TKey, TDTO extends IBaseDTO<TKey>> implemen
private final Connection connection;
public final String databaseType;
public final String databaseLocation;
public final File databaseFile;
public final Class<? extends TDTO> dtoClass;
@@ -59,16 +61,15 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> 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<? extends TDTO> dtoClass) throws SQLException
public AbstractDhRepo(String databaseType, File databaseFile, Class<? extends TDTO> dtoClass) throws SQLException
{
this.databaseType = databaseType;
this.databaseLocation = databaseLocation;
this.databaseFile = databaseFile;
this.dtoClass = dtoClass;
@@ -93,9 +94,55 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> 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) ->
@@ -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<DhChunkPos, ChunkHashDTO>
// 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);
}
@@ -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<Long, FullDataSourceV1D
// constructor //
//=============//
public FullDataSourceV1Repo(String databaseType, String databaseLocation) throws SQLException
public FullDataSourceV1Repo(String databaseType, File databaseFile) throws SQLException
{
super(databaseType, databaseLocation, FullDataSourceV1DTO.class);
super(databaseType, databaseFile, FullDataSourceV1DTO.class);
}
@@ -29,6 +29,7 @@ import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.Logger;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@@ -45,9 +46,9 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
// constructor //
//=============//
public FullDataSourceV2Repo(String databaseType, String databaseLocation) throws SQLException
public FullDataSourceV2Repo(String databaseType, File databaseFile) throws SQLException
{
super(databaseType, databaseLocation, FullDataSourceV2DTO.class);
super(databaseType, databaseFile, FullDataSourceV2DTO.class);
}
@@ -22,6 +22,7 @@ package testItems.sql;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import java.io.File;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Map;
@@ -29,9 +30,9 @@ import java.util.Map;
public class TestCompoundKeyRepo extends AbstractDhRepo<DhChunkPos, TestCompoundKeyDto>
{
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.
@@ -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<Integer, TestSingleKeyDto>
{
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.
@@ -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);
@@ -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));