forked from SteamWar/SteamWar
Migrate builder and bau worlds to shared world records
- add SQL-backed world entities with archive and rename support - route builder, bau, deploy, and GDPR flows through world storage - keep legacy folder support for existing worlds
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2026 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.sql
|
||||
|
||||
import de.steamwar.sql.internal.useDb
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.dao.id.EntityID
|
||||
import org.jetbrains.exposed.v1.core.dao.id.UUIDTable
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.not
|
||||
import org.jetbrains.exposed.v1.dao.Entity
|
||||
import org.jetbrains.exposed.v1.dao.EntityClass
|
||||
import org.jetbrains.exposed.v1.javatime.CurrentTimestamp
|
||||
import org.jetbrains.exposed.v1.javatime.timestamp
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.UUID
|
||||
|
||||
object WorldTable : UUIDTable("world") {
|
||||
val name = varchar("Name", 512)
|
||||
val version = integer("Version")
|
||||
val type = enumeration<WorldType>("Type")
|
||||
val owner = reference("Owner", SteamwarUserTable).nullable()
|
||||
val deleted = bool("Deleted").default(false)
|
||||
val created = timestamp("Created").defaultExpression(CurrentTimestamp)
|
||||
val lastUsed = timestamp("LastUsed").defaultExpression(CurrentTimestamp)
|
||||
}
|
||||
|
||||
enum class WorldType {
|
||||
BAU,
|
||||
BUILDER,
|
||||
}
|
||||
|
||||
class SteamwarWorld(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
var name by WorldTable.name
|
||||
private set
|
||||
var version by WorldTable.version
|
||||
private set
|
||||
var type by WorldTable.type
|
||||
private set
|
||||
var owner by WorldTable.owner
|
||||
private set
|
||||
var deleted by WorldTable.deleted
|
||||
private set
|
||||
val created by WorldTable.created
|
||||
var lastUsed by WorldTable.lastUsed
|
||||
private set
|
||||
|
||||
val archived: Boolean
|
||||
get() = !storageDirectory.exists() && archiveFile.exists()
|
||||
|
||||
val shouldArchive: Boolean
|
||||
get() = !archived && (deleted || lastUsed.plus(7, ChronoUnit.DAYS).isBefore(Instant.now()))
|
||||
|
||||
val uuid: UUID
|
||||
get() = id.value
|
||||
|
||||
val storageDirectory: File
|
||||
get() = File(WORLD_STORAGE, id.value.toString())
|
||||
|
||||
private val archiveFile: File
|
||||
get() = File(ARCHIVE_WORLD_STORAGE, "${id.value}.zip")
|
||||
|
||||
@JvmOverloads
|
||||
fun setupAndGetStoragePath(prototype: File? = null): String {
|
||||
if (archived) {
|
||||
loadFromArchive()
|
||||
}
|
||||
|
||||
val needsInitialization = !storageDirectory.exists() || storageDirectory.list()?.isEmpty() == true
|
||||
if (needsInitialization) {
|
||||
if (prototype != null && prototype.exists()) {
|
||||
prototype.copyRecursively(storageDirectory, overwrite = true)
|
||||
} else {
|
||||
storageDirectory.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
useDb {
|
||||
lastUsed = Instant.now()
|
||||
}
|
||||
|
||||
return storageDirectory.path
|
||||
}
|
||||
|
||||
fun markDeleted() = useDb {
|
||||
deleted = true
|
||||
}
|
||||
|
||||
fun rename(newName: String) = useDb {
|
||||
name = newName
|
||||
}
|
||||
|
||||
private fun archiveWorld() = ProcessBuilder("zip", "-u9oymrqq", "$ARCHIVE_WORLD_STORAGE/${id.value}.zip", id.value.toString())
|
||||
.directory(File(WORLD_STORAGE))
|
||||
.inheritIO()
|
||||
.start()
|
||||
.waitFor()
|
||||
|
||||
|
||||
private fun loadFromArchive() = ProcessBuilder("unzip", "-qq", "-o", "$ARCHIVE_WORLD_STORAGE/${id.value}.zip", "-d", WORLD_STORAGE)
|
||||
.inheritIO()
|
||||
.start()
|
||||
.waitFor()
|
||||
|
||||
companion object : EntityClass<UUID, SteamwarWorld>(WorldTable) {
|
||||
const val WORLD_STORAGE = "/worlds/storage"
|
||||
const val ARCHIVE_WORLD_STORAGE = "/mnt/storage/worlds/storage"
|
||||
|
||||
@JvmStatic
|
||||
fun getBauWorld(user: SteamwarUser, version: Int) = useDb {
|
||||
find {
|
||||
(WorldTable.owner eq user.id) and
|
||||
(WorldTable.version eq version) and
|
||||
(WorldTable.type eq WorldType.BAU) and
|
||||
not(WorldTable.deleted)
|
||||
}.firstOrNull()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun getOrCreateBauWorld(user: SteamwarUser, version: Int, prototype: File? = null): SteamwarWorld =
|
||||
getBauWorld(user, version) ?: createWorld(user, user.userName, version, WorldType.BAU, prototype)
|
||||
|
||||
@JvmStatic
|
||||
fun getBuilderWorld(name: String, version: Int) = useDb {
|
||||
find {
|
||||
(WorldTable.name eq name) and
|
||||
(WorldTable.version eq version) and
|
||||
(WorldTable.type eq WorldType.BUILDER) and
|
||||
not(WorldTable.deleted)
|
||||
}.firstOrNull()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getBuilderWorlds(version: Int) = useDb {
|
||||
find {
|
||||
(WorldTable.version eq version) and
|
||||
(WorldTable.type eq WorldType.BUILDER) and
|
||||
not(WorldTable.deleted)
|
||||
}.toList()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun getOrCreateBuilderWorld(name: String, version: Int, prototype: File? = null): SteamwarWorld =
|
||||
getBuilderWorld(name, version) ?: createWorld(null, name, version, WorldType.BUILDER, prototype)
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun createWorld(user: SteamwarUser?, name: String, version: Int, type: WorldType, prototype: File? = null): SteamwarWorld {
|
||||
val world = useDb { new {
|
||||
this.name = name
|
||||
this.version = version
|
||||
this.type = type
|
||||
this.owner = user?.id
|
||||
} }
|
||||
|
||||
world.setupAndGetStoragePath(prototype)
|
||||
return world
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user