forked from SteamWar/SteamWar
Remove deprecated world management commands and associated SQL logic
Signed-off-by: Chaoscaot <max@maxsp.de>
This commit is contained in:
+1
-11
@@ -11,23 +11,13 @@ import de.steamwar.commands.profiler.ProfilerCommand
|
||||
import de.steamwar.commands.user.UserCommand
|
||||
import de.steamwar.commands.user.UserInfoCommand
|
||||
import de.steamwar.commands.user.UserSearchCommand
|
||||
import de.steamwar.commands.world.MigrationCommand
|
||||
import de.steamwar.commands.world.SaveToStorageCommand
|
||||
import de.steamwar.commands.world.TemplateCommand
|
||||
import de.steamwar.commands.world.TemplateCreateCommand
|
||||
import de.steamwar.commands.world.WorldCommand
|
||||
|
||||
fun main(args: Array<String>) =
|
||||
SteamWar()
|
||||
.subcommands(
|
||||
DatabaseCommand().subcommands(InfoCommand(), ResetCommand()),
|
||||
UserCommand().subcommands(UserInfoCommand(), UserSearchCommand()),
|
||||
WorldCommand().subcommands(
|
||||
MigrationCommand(),
|
||||
SaveToStorageCommand(),
|
||||
TemplateCommand().subcommands(TemplateCreateCommand())
|
||||
),
|
||||
DevCommand(),
|
||||
ProfilerCommand()
|
||||
)
|
||||
.main(args)
|
||||
.main(args)
|
||||
@@ -1,243 +0,0 @@
|
||||
package de.steamwar.commands.world
|
||||
|
||||
import com.github.ajalt.clikt.core.CliktCommand
|
||||
import de.steamwar.db.Database
|
||||
import de.steamwar.sql.SteamwarUser
|
||||
import de.steamwar.sql.SteamwarWorld
|
||||
import de.steamwar.sql.UserConfig
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import java.io.File
|
||||
import java.sql.SQLException
|
||||
import java.util.UUID
|
||||
|
||||
class MigrationCommand : CliktCommand(name = "migrate") {
|
||||
private val userWorldPattern = Regex("""userworlds(\d+)""")
|
||||
private val builderWorldPattern = Regex("""builder(\d+)""")
|
||||
private val trailingVersionPattern = Regex("""(\d+)$""")
|
||||
|
||||
override fun run() {
|
||||
Database.ensureConnected()
|
||||
|
||||
val bau = migrateBauWorlds()
|
||||
val builder = migrateBuilderWorlds()
|
||||
val arena = migrateArenaWorlds()
|
||||
migrateBauLockConfig()
|
||||
migrateBauweltMembers()
|
||||
|
||||
echo("Imported ${bau.imported} build worlds, skipped ${bau.skipped}, missing users ${bau.missingOwner}.")
|
||||
echo("Imported ${builder.imported} builder worlds, skipped ${builder.skipped}.")
|
||||
echo("Imported ${arena.imported} arena worlds, skipped ${arena.skipped}.")
|
||||
echo("World migration completed.")
|
||||
}
|
||||
|
||||
private fun migrateBauWorlds(): MigrationStats {
|
||||
val stats = MigrationStats()
|
||||
val worldsRoot = File("/worlds")
|
||||
worldsRoot.listFiles { file -> file.isDirectory && userWorldPattern.matches(file.name) }
|
||||
?.forEach { versionFolder ->
|
||||
val version = userWorldPattern.matchEntire(versionFolder.name)!!.groupValues[1].toInt()
|
||||
versionFolder.listFiles { file -> file.isDirectory && isWorldDirectory(file) }
|
||||
?.sortedBy { it.name }
|
||||
?.forEach { legacyWorld ->
|
||||
val owner = legacyWorld.ownerUser()
|
||||
if (owner == null) {
|
||||
stats.missingOwner++
|
||||
echo("Skipped build world ${legacyWorld.path}: no matching user.")
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val lockState = UserConfig.getConfig(owner.id.value, BAU_LOCK_CONFIG_NAME)
|
||||
val world = SteamwarWorld.getOrCreateBauWorld(owner, owner.userName, version, legacyWorld)
|
||||
if (world.lockState == null && lockState != null) {
|
||||
world.changeLockState(lockState)
|
||||
}
|
||||
stats.imported++
|
||||
}
|
||||
} ?: run {
|
||||
stats.skipped++
|
||||
echo("Skipped build worlds: /worlds does not exist.")
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
private fun migrateBuilderWorlds(): MigrationStats {
|
||||
val stats = MigrationStats()
|
||||
val worldsRoot = File("/worlds")
|
||||
worldsRoot.listFiles { file -> file.isDirectory && builderWorldPattern.matches(file.name) }
|
||||
?.forEach { versionFolder ->
|
||||
val version = builderWorldPattern.matchEntire(versionFolder.name)!!.groupValues[1].toInt()
|
||||
versionFolder.listFiles { file -> file.isDirectory && isWorldDirectory(file) }
|
||||
?.sortedBy { it.name }
|
||||
?.forEach { legacyWorld ->
|
||||
SteamwarWorld.getOrCreateBuilderWorld(legacyWorld.name, version, legacyWorld)
|
||||
stats.imported++
|
||||
}
|
||||
} ?: run {
|
||||
stats.skipped++
|
||||
echo("Skipped builder worlds: /worlds does not exist.")
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
private fun migrateArenaWorlds(): MigrationStats {
|
||||
val stats = MigrationStats()
|
||||
val configs = parseGameModeConfigs(File("/configs/GameModes"))
|
||||
if (configs.isEmpty()) {
|
||||
stats.skipped++
|
||||
echo("Skipped arena worlds: no game mode configs found in /configs/GameModes.")
|
||||
return stats
|
||||
}
|
||||
|
||||
for (config in configs) {
|
||||
val version = trailingVersionPattern.find(config.serverFolder)?.value?.toIntOrNull()
|
||||
if (version == null) {
|
||||
stats.skipped++
|
||||
echo("Skipped arena mode ${config.modeName}: server folder '${config.serverFolder}' has no version suffix.")
|
||||
continue
|
||||
}
|
||||
|
||||
val arenaRoot = File("/servers/${config.serverFolder}/arenas")
|
||||
for (map in config.maps) {
|
||||
val legacyWorld = File(arenaRoot, map)
|
||||
if (!isWorldDirectory(legacyWorld)) {
|
||||
stats.skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
SteamwarWorld.getOrCreateArenaWorld(config.modeName, map, version, legacyWorld)
|
||||
stats.imported++
|
||||
}
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
private fun migrateBauLockConfig() {
|
||||
transaction(Database.db) {
|
||||
exec(
|
||||
"""
|
||||
UPDATE `world` w
|
||||
JOIN `UserConfig` uc ON uc.`User` = w.`Owner` AND uc.`Config` = 'baulockstate'
|
||||
SET w.`LockState` = uc.`Value`
|
||||
WHERE w.`Owner` IS NOT NULL AND w.`LockState` IS NULL
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateBauweltMembers() {
|
||||
if (!columnExists("BauweltMember", "WorldID") || !columnExists("BauweltMember", "BauweltID")) {
|
||||
echo("Skipped BauweltMember data migration: expected WorldID and BauweltID columns are not both present.")
|
||||
return
|
||||
}
|
||||
|
||||
execIgnore(
|
||||
"""
|
||||
INSERT IGNORE INTO `BauweltMember` (`WorldID`, `MemberID`, `Build`, `WorldEdit`, `World`)
|
||||
SELECT w.`id`, bm.`MemberID`, bm.`Build`, bm.`WorldEdit`, bm.`World`
|
||||
FROM `BauweltMember` bm
|
||||
JOIN `world` w ON w.`Owner` = bm.`BauweltID` AND w.`Deleted` = 0
|
||||
""".trimIndent(),
|
||||
"Skipped BauweltMember data migration"
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseGameModeConfigs(configRoot: File): List<ArenaMigrationConfig> {
|
||||
if (!configRoot.isDirectory) return emptyList()
|
||||
|
||||
return configRoot.listFiles { file -> file.isFile && file.extension == "yml" && !file.name.endsWith(".kits.yml") }
|
||||
?.mapNotNull { file ->
|
||||
val server = parseServerBlock(file)
|
||||
val folder = server.folder ?: return@mapNotNull null
|
||||
ArenaMigrationConfig(file.nameWithoutExtension, folder, server.maps)
|
||||
}
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
private fun parseServerBlock(file: File): ParsedServerBlock {
|
||||
var inServer = false
|
||||
var inMaps = false
|
||||
var folder: String? = null
|
||||
val maps = mutableListOf<String>()
|
||||
|
||||
file.readLines().forEach { line ->
|
||||
val trimmed = line.trim()
|
||||
if (trimmed.isEmpty() || trimmed.startsWith("#")) return@forEach
|
||||
|
||||
val indent = line.indexOfFirst { !it.isWhitespace() }.let { if (it == -1) 0 else it }
|
||||
if (indent == 0) {
|
||||
inServer = trimmed == "Server:"
|
||||
inMaps = false
|
||||
return@forEach
|
||||
}
|
||||
|
||||
if (!inServer) return@forEach
|
||||
|
||||
when {
|
||||
indent <= 2 && trimmed.startsWith("Folder:") -> folder = trimmed.substringAfter(":").cleanYamlValue()
|
||||
indent <= 2 && trimmed.startsWith("Maps:") -> {
|
||||
val value = trimmed.substringAfter(":").trim()
|
||||
inMaps = true
|
||||
if (value.startsWith("[") && value.endsWith("]")) {
|
||||
maps += value.removePrefix("[").removeSuffix("]")
|
||||
.split(",")
|
||||
.map { it.cleanYamlValue() }
|
||||
.filter { it.isNotEmpty() }
|
||||
inMaps = false
|
||||
}
|
||||
}
|
||||
inMaps && trimmed.startsWith("-") -> maps += trimmed.removePrefix("-").cleanYamlValue()
|
||||
inMaps && indent <= 2 -> inMaps = false
|
||||
}
|
||||
}
|
||||
|
||||
return ParsedServerBlock(folder, maps)
|
||||
}
|
||||
|
||||
private fun File.ownerUser(): SteamwarUser? {
|
||||
name.toIntOrNull()?.let { return SteamwarUser.byId(it) }
|
||||
return runCatching { SteamwarUser.get(UUID.fromString(name)) }.getOrNull()
|
||||
}
|
||||
|
||||
private fun isWorldDirectory(file: File): Boolean =
|
||||
file.isDirectory && (File(file, "level.dat").isFile || File(file, "region").isDirectory)
|
||||
|
||||
private fun String.cleanYamlValue(): String =
|
||||
trim().trim('"').trim('\'')
|
||||
|
||||
private fun columnExists(table: String, column: String): Boolean =
|
||||
transaction(Database.db) {
|
||||
exec("SHOW COLUMNS FROM `$table` LIKE '$column'") { result -> result.next() } ?: false
|
||||
}
|
||||
|
||||
private fun execIgnore(sql: String, prefix: String) {
|
||||
try {
|
||||
transaction(Database.db) {
|
||||
exec(sql)
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
echo("$prefix: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private data class MigrationStats(
|
||||
var imported: Int = 0,
|
||||
var skipped: Int = 0,
|
||||
var missingOwner: Int = 0,
|
||||
)
|
||||
|
||||
private data class ArenaMigrationConfig(
|
||||
val modeName: String,
|
||||
val serverFolder: String,
|
||||
val maps: List<String>,
|
||||
)
|
||||
|
||||
private data class ParsedServerBlock(
|
||||
val folder: String?,
|
||||
val maps: List<String>,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
private const val BAU_LOCK_CONFIG_NAME = "baulockstate"
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package de.steamwar.commands.world
|
||||
|
||||
import com.github.ajalt.clikt.core.CliktCommand
|
||||
import de.steamwar.db.Database
|
||||
import de.steamwar.sql.SteamwarWorld
|
||||
import java.time.LocalTime
|
||||
|
||||
class SaveToStorageCommand : CliktCommand(name = "saveToStorage") {
|
||||
override fun run() {
|
||||
Database.ensureConnected()
|
||||
|
||||
var archived = 0
|
||||
for (world in SteamwarWorld.getWorldsToArchive()) {
|
||||
if (!beforeCutoff()) {
|
||||
echo("Stopping before 04:00. Archived $archived worlds.")
|
||||
return
|
||||
}
|
||||
|
||||
if (world.archiveIfNeeded()) {
|
||||
archived++
|
||||
echo("Archived ${world.uuid} (${world.type}/${world.name}).")
|
||||
}
|
||||
}
|
||||
|
||||
echo("Storage save completed. Archived $archived worlds.")
|
||||
}
|
||||
|
||||
private fun beforeCutoff(): Boolean =
|
||||
LocalTime.now().isBefore(LocalTime.of(4, 0))
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package de.steamwar.commands.world
|
||||
|
||||
import com.github.ajalt.clikt.core.CliktCommand
|
||||
import com.github.ajalt.clikt.parameters.arguments.argument
|
||||
import com.github.ajalt.clikt.parameters.types.int
|
||||
import com.github.ajalt.clikt.parameters.types.path
|
||||
import de.steamwar.db.Database
|
||||
import de.steamwar.sql.SteamwarWorld
|
||||
|
||||
class TemplateCommand : CliktCommand(name = "template") {
|
||||
override fun run() = Unit
|
||||
}
|
||||
|
||||
class TemplateCreateCommand : CliktCommand(name = "create") {
|
||||
private val name by argument()
|
||||
private val version by argument().int()
|
||||
private val source by argument().path(canBeFile = false, mustExist = true)
|
||||
|
||||
override fun run() {
|
||||
Database.ensureConnected()
|
||||
SteamwarWorld.getOrCreateTemplateWorld(name, version, source.toFile())
|
||||
echo("Template world '$name' created.")
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package de.steamwar.commands.world
|
||||
|
||||
import com.github.ajalt.clikt.core.CliktCommand
|
||||
|
||||
class WorldCommand : CliktCommand(name = "world") {
|
||||
override fun run() = Unit
|
||||
}
|
||||
Reference in New Issue
Block a user