Skip to content

Commit 5480f4c

Browse files
committed
move to database storage
1 parent a0e8d3f commit 5480f4c

16 files changed

+1048
-441
lines changed

src/main/kotlin/li/angu/challengeplugin/commands/CreateCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class CreateCommand(plugin: ChallengePluginPlugin) : BaseCommand(plugin) {
4545
}
4646

4747
private fun createChallengeWithName(player: Player, name: String) {
48-
val challenge = plugin.challengeManager.createChallenge(name)
48+
val challenge = plugin.challengeManager.createChallenge(name, player)
4949

5050
// Open settings GUI using the new SettingsInventoryManager
5151
plugin.settingsInventoryManager.openSettingsInventory(player, challenge)

src/main/kotlin/li/angu/challengeplugin/database/DatabaseDriver.kt

Lines changed: 135 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,74 +4,90 @@ import li.angu.challengeplugin.ChallengePluginPlugin
44
import java.io.File
55
import java.sql.Connection
66
import java.sql.DriverManager
7+
import java.sql.ResultSet
78
import java.sql.SQLException
89
import java.util.logging.Level
910

1011
class DatabaseDriver(private val plugin: ChallengePluginPlugin) {
11-
12+
1213
private val databaseFile = File(plugin.dataFolder, "challenges.db")
1314
private var connection: Connection? = null
1415
private val migrationManager = MigrationManager(this, plugin)
15-
16+
1617
/**
1718
* Initialize the database connection and run migrations
19+
* Returns false if initialization fails, which should abort plugin startup
1820
*/
1921
fun initialize(): Boolean {
2022
try {
2123
// Ensure data folder exists
2224
plugin.dataFolder.mkdirs()
23-
25+
26+
plugin.logger.info("Plugin data folder: ${plugin.dataFolder.absolutePath}")
27+
plugin.logger.info("Database file path: ${databaseFile.absolutePath}")
28+
plugin.logger.info("Database file exists: ${databaseFile.exists()}")
29+
if (databaseFile.exists()) {
30+
plugin.logger.info("Database file size: ${databaseFile.length()} bytes")
31+
}
32+
2433
// Load SQLite JDBC driver
2534
Class.forName("org.sqlite.JDBC")
26-
35+
2736
// Create connection
28-
connection = DriverManager.getConnection("jdbc:sqlite:${databaseFile.absolutePath}")
37+
val connectionString = "jdbc:sqlite:${databaseFile.absolutePath}"
38+
plugin.logger.info("Connecting to database with: $connectionString")
39+
connection = DriverManager.getConnection(connectionString)
2940

41+
plugin.logger.info("Database connection established: ${connection != null}")
42+
plugin.logger.info("Database connection valid: ${connection?.isValid(5)}")
43+
3044
// Enable foreign keys
31-
connection?.createStatement()?.execute("PRAGMA foreign_keys = ON")
32-
33-
// Run migrations
45+
connection?.createStatement()?.use { stmt ->
46+
stmt.execute("PRAGMA foreign_keys = ON")
47+
plugin.logger.info("Foreign keys enabled")
48+
49+
// Test if we can read basic SQLite info
50+
val result = stmt.executeQuery("PRAGMA database_list")
51+
while (result.next()) {
52+
plugin.logger.info("Database: name=${result.getString("name")}, file=${result.getString("file")}")
53+
}
54+
result.close()
55+
}
56+
57+
// Run migrations - this will throw an exception if any migration fails
3458
migrationManager.runMigrations()
35-
59+
3660
plugin.logger.info("Database initialized successfully at: ${databaseFile.absolutePath}")
3761
return true
38-
62+
3963
} catch (e: Exception) {
40-
plugin.logger.log(Level.SEVERE, "Failed to initialize database", e)
64+
plugin.logger.log(Level.SEVERE, "Failed to initialize database - plugin startup aborted", e)
65+
// Close connection if it was opened
66+
connection?.close()
67+
connection = null
4168
return false
4269
}
4370
}
44-
71+
72+
/**
73+
* Get the database file
74+
*/
75+
fun getDatabaseFile(): File = databaseFile
76+
4577
/**
4678
* Get the current database connection
4779
*/
4880
fun getConnection(): Connection? {
49-
try {
50-
// Check if connection is valid
51-
if (connection?.isValid(5) == true) {
52-
return connection
53-
}
54-
55-
// Reconnect if connection is invalid
56-
plugin.logger.warning("Database connection invalid, attempting to reconnect...")
57-
connection = DriverManager.getConnection("jdbc:sqlite:${databaseFile.absolutePath}")
58-
connection?.createStatement()?.execute("PRAGMA foreign_keys = ON")
59-
60-
return connection
61-
62-
} catch (e: SQLException) {
63-
plugin.logger.log(Level.SEVERE, "Failed to get database connection", e)
64-
return null
65-
}
81+
return connection
6682
}
67-
83+
6884
/**
69-
* Execute a query and return the result
85+
* Execute a query and process results immediately (SAFE - resources are properly managed)
7086
*/
71-
fun executeQuery(sql: String, vararg params: Any): QueryResult {
87+
fun <T> executeQuery(sql: String, vararg params: Any, processor: (ResultSet) -> T): T? {
7288
return try {
73-
val conn = getConnection() ?: return QueryResult.error("No database connection")
74-
89+
val conn = getConnection() ?: return null
90+
7591
conn.prepareStatement(sql).use { statement ->
7692
// Set parameters
7793
params.forEachIndexed { index, param ->
@@ -84,23 +100,24 @@ class DatabaseDriver(private val plugin: ChallengePluginPlugin) {
84100
else -> statement.setObject(index + 1, param)
85101
}
86102
}
87-
88-
val resultSet = statement.executeQuery()
89-
QueryResult.success(resultSet)
103+
104+
statement.executeQuery().use { resultSet ->
105+
processor(resultSet)
106+
}
90107
}
91108
} catch (e: SQLException) {
92109
plugin.logger.log(Level.SEVERE, "Failed to execute query: $sql", e)
93-
QueryResult.error(e.message ?: "Unknown SQL error")
110+
null
94111
}
95112
}
96-
113+
97114
/**
98115
* Execute an update (INSERT, UPDATE, DELETE) and return affected rows
99116
*/
100117
fun executeUpdate(sql: String, vararg params: Any): Int {
101118
return try {
102119
val conn = getConnection() ?: return -1
103-
120+
104121
conn.prepareStatement(sql).use { statement ->
105122
// Set parameters
106123
params.forEachIndexed { index, param ->
@@ -113,24 +130,24 @@ class DatabaseDriver(private val plugin: ChallengePluginPlugin) {
113130
else -> statement.setObject(index + 1, param)
114131
}
115132
}
116-
133+
117134
statement.executeUpdate()
118135
}
119136
} catch (e: SQLException) {
120137
plugin.logger.log(Level.SEVERE, "Failed to execute update: $sql", e)
121138
-1
122139
}
123140
}
124-
141+
125142
/**
126143
* Execute a batch of statements in a transaction
127144
*/
128145
fun executeBatch(statements: List<String>): Boolean {
129146
return try {
130147
val conn = getConnection() ?: return false
131-
148+
132149
conn.autoCommit = false
133-
150+
134151
try {
135152
statements.forEach { sql ->
136153
conn.createStatement().execute(sql)
@@ -148,35 +165,101 @@ class DatabaseDriver(private val plugin: ChallengePluginPlugin) {
148165
false
149166
}
150167
}
151-
168+
169+
/**
170+
* Execute a batch of parameterized statements in a transaction
171+
*/
172+
fun executeTransaction(operations: List<Pair<String, Array<Any?>>>): Boolean {
173+
return try {
174+
val conn = getConnection() ?: return false
175+
176+
plugin.logger.info("Starting database transaction with ${operations.size} operations")
177+
conn.autoCommit = false
178+
179+
try {
180+
operations.forEachIndexed { index, (sql, params) ->
181+
plugin.logger.info("Executing operation $index: $sql")
182+
plugin.logger.info("Parameters: ${params.joinToString(", ")}")
183+
184+
conn.prepareStatement(sql).use { statement ->
185+
params.forEachIndexed { paramIndex, param ->
186+
when (param) {
187+
null -> statement.setNull(paramIndex + 1, java.sql.Types.NULL)
188+
is String -> statement.setString(paramIndex + 1, param)
189+
is Int -> statement.setInt(paramIndex + 1, param)
190+
is Long -> statement.setLong(paramIndex + 1, param)
191+
is Boolean -> statement.setBoolean(paramIndex + 1, param)
192+
is Double -> statement.setDouble(paramIndex + 1, param)
193+
else -> statement.setObject(paramIndex + 1, param)
194+
}
195+
}
196+
val rowsAffected = statement.executeUpdate()
197+
plugin.logger.info("Operation $index affected $rowsAffected rows")
198+
}
199+
}
200+
201+
plugin.logger.info("Committing transaction")
202+
conn.commit()
203+
plugin.logger.info("Transaction committed successfully")
204+
true
205+
} catch (e: SQLException) {
206+
plugin.logger.severe("Transaction failed, rolling back: ${e.message}")
207+
conn.rollback()
208+
throw e
209+
} finally {
210+
conn.autoCommit = true
211+
}
212+
} catch (e: SQLException) {
213+
plugin.logger.log(Level.SEVERE, "Failed to execute parameterized batch", e)
214+
false
215+
}
216+
}
217+
152218
/**
153219
* Check if a table exists
154220
*/
155221
fun tableExists(tableName: String): Boolean {
156222
return try {
157-
val result = executeQuery(
223+
executeQuery(
158224
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
159225
tableName
160-
)
161-
162-
result.resultSet?.use { rs ->
226+
) { rs ->
163227
rs.next()
164228
} ?: false
165-
166229
} catch (e: Exception) {
167230
false
168231
}
169232
}
170-
233+
234+
/**
235+
* Force synchronization to disk
236+
*/
237+
fun sync() {
238+
try {
239+
plugin.logger.info("Database sync requested for file: ${databaseFile.absolutePath}")
240+
plugin.logger.info("Database file exists: ${databaseFile.exists()}, size: ${if (databaseFile.exists()) databaseFile.length() else 0} bytes")
241+
242+
// Execute PRAGMA synchronous to ensure data is written
243+
getConnection()?.createStatement()?.use { stmt ->
244+
stmt.execute("PRAGMA synchronous = FULL")
245+
stmt.execute("PRAGMA wal_checkpoint(FULL)")
246+
plugin.logger.info("Executed PRAGMA synchronous and WAL checkpoint")
247+
}
248+
} catch (e: Exception) {
249+
plugin.logger.warning("Error during database sync: ${e.message}")
250+
}
251+
}
252+
171253
/**
172254
* Close the database connection
173255
*/
174256
fun close() {
175257
try {
258+
sync() // Ensure everything is written to disk before closing
176259
connection?.close()
177260
plugin.logger.info("Database connection closed")
178261
} catch (e: SQLException) {
179262
plugin.logger.log(Level.WARNING, "Error closing database connection", e)
180263
}
181264
}
182-
}
265+
}

0 commit comments

Comments
 (0)