File storage in progress
This commit is contained in:
@@ -5,6 +5,7 @@ import com.republicate.kson.Json
|
|||||||
typealias ID = Int
|
typealias ID = Int
|
||||||
|
|
||||||
fun String.toID() = toInt()
|
fun String.toID() = toInt()
|
||||||
|
fun String.toIDOrNull() = toIntOrNull()
|
||||||
fun Number.toID() = toInt()
|
fun Number.toID() = toInt()
|
||||||
fun Json.Object.getID(key: String) = getInt(key)
|
fun Json.Object.getID(key: String) = getInt(key)
|
||||||
fun Json.Array.getID(index: Int) = getInt(index)
|
fun Json.Array.getID(index: Int) = getInt(index)
|
@@ -11,6 +11,7 @@ data class Game(
|
|||||||
var handicap: Int = 0,
|
var handicap: Int = 0,
|
||||||
var result: Result = UNKNOWN
|
var result: Result = UNKNOWN
|
||||||
) {
|
) {
|
||||||
|
companion object {}
|
||||||
enum class Result(val symbol: Char) {
|
enum class Result(val symbol: Char) {
|
||||||
UNKNOWN('?'),
|
UNKNOWN('?'),
|
||||||
BLACK('b'),
|
BLACK('b'),
|
||||||
@@ -36,3 +37,11 @@ fun Game.toJson() = Json.Object(
|
|||||||
"h" to handicap,
|
"h" to handicap,
|
||||||
"r" to "${result.symbol}"
|
"r" to "${result.symbol}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun Game.Companion.fromJson(json: Json.Object) = Game(
|
||||||
|
id = json.getID("id") ?: throw Error("missing game id"),
|
||||||
|
white = json.getID("white") ?: throw Error("missing white player"),
|
||||||
|
black = json.getID("black") ?: throw Error("missing black player"),
|
||||||
|
handicap = json.getInt("handicap") ?: 0,
|
||||||
|
result = json.getChar("result")?.let { Game.Result.fromSymbol(it) } ?: UNKNOWN
|
||||||
|
)
|
||||||
|
@@ -65,7 +65,9 @@ sealed class Tournament <P: Pairable>(
|
|||||||
// games per id for each round
|
// games per id for each round
|
||||||
private val games = mutableListOf<MutableMap<ID, Game>>()
|
private val games = mutableListOf<MutableMap<ID, Game>>()
|
||||||
|
|
||||||
fun games(round: Int) = games.getOrNull(round - 1) ?: mutableMapOf()
|
fun games(round: Int) = games.getOrNull(round - 1) ?:
|
||||||
|
if (round > games.size) throw Error("invalid round")
|
||||||
|
else mutableMapOf<ID, Game>().also { games.add(it) }
|
||||||
fun lastRound() = games.size
|
fun lastRound() = games.size
|
||||||
|
|
||||||
// standings criteria
|
// standings criteria
|
||||||
@@ -153,7 +155,7 @@ class TeamTournament(
|
|||||||
fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = null): Tournament<*> {
|
fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = null): Tournament<*> {
|
||||||
val type = json.getString("type")?.uppercase()?.let { Tournament.Type.valueOf(it) } ?: default?.type ?: badRequest("missing type")
|
val type = json.getString("type")?.uppercase()?.let { Tournament.Type.valueOf(it) } ?: default?.type ?: badRequest("missing type")
|
||||||
// No clean way to avoid this redundancy
|
// No clean way to avoid this redundancy
|
||||||
return if (type.playersNumber == 1)
|
val tournament = if (type.playersNumber == 1)
|
||||||
StandardTournament(
|
StandardTournament(
|
||||||
id = json.getInt("id") ?: default?.id ?: Store.nextTournamentId,
|
id = json.getInt("id") ?: default?.id ?: Store.nextTournamentId,
|
||||||
type = type,
|
type = type,
|
||||||
@@ -189,6 +191,10 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
|
|||||||
rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
|
rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
|
||||||
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it) } ?: default?.pairing ?: badRequest("missing pairing")
|
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it) } ?: default?.pairing ?: badRequest("missing pairing")
|
||||||
)
|
)
|
||||||
|
json["pairables"]?.let { pairables ->
|
||||||
|
|
||||||
|
}
|
||||||
|
return tournament
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Tournament<*>.toJson() = Json.Object(
|
fun Tournament<*>.toJson() = Json.Object(
|
||||||
|
@@ -1,4 +1,111 @@
|
|||||||
package org.jeudego.pairgoth.store
|
package org.jeudego.pairgoth.store
|
||||||
|
|
||||||
class FileStore {
|
import com.republicate.kson.Json
|
||||||
|
import org.jeudego.pairgoth.model.Game
|
||||||
|
import org.jeudego.pairgoth.model.ID
|
||||||
|
import org.jeudego.pairgoth.model.Player
|
||||||
|
import org.jeudego.pairgoth.model.TeamTournament
|
||||||
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
|
import org.jeudego.pairgoth.model.fromJson
|
||||||
|
import org.jeudego.pairgoth.model.getID
|
||||||
|
import org.jeudego.pairgoth.model.toID
|
||||||
|
import org.jeudego.pairgoth.model.toJson
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.io.path.readText
|
||||||
|
import kotlin.io.path.useDirectoryEntries
|
||||||
|
|
||||||
|
private const val LEFT_PAD = 6 // left padding of IDs with '0' in filename
|
||||||
|
private fun Tournament<*>.filename() = "${id.toString().padStart(LEFT_PAD, '0')}-${shortName}.tour"
|
||||||
|
|
||||||
|
class FileStore(pathStr: String): StoreImplementation {
|
||||||
|
companion object {
|
||||||
|
private val filenameRegex = Regex("^(\\d+)-.*\\.tour$")
|
||||||
|
private val timestampFormat: DateFormat = SimpleDateFormat("yyyyMMddHHmmss")
|
||||||
|
private val timestamp: String get() = timestampFormat.format(Date())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val path = Path.of(pathStr).also {
|
||||||
|
val file = it.toFile()
|
||||||
|
if (!file.mkdirs() && !file.isDirectory) throw Error("Property pairgoth.store.file.path must be a directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTournamentsIDs(): Set<ID> {
|
||||||
|
return path.useDirectoryEntries("*.tour") { entries ->
|
||||||
|
entries.mapNotNull { entry ->
|
||||||
|
filenameRegex.matchEntire(entry.fileName.toString())?.groupValues?.get(1)?.toID()
|
||||||
|
}.toSet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addTournament(tournament: Tournament<*>) {
|
||||||
|
val filename = tournament.filename()
|
||||||
|
val file = path.resolve(filename).toFile()
|
||||||
|
if (file.exists()) throw Error("File $filename already exists")
|
||||||
|
val json = Json.MutableObject(tournament.toJson())
|
||||||
|
json["players"] = Json.Array(tournament.players.values.map { it.toJson() })
|
||||||
|
if (tournament is TeamTournament) {
|
||||||
|
json["teams"] = Json.Array(tournament.teams.values.map { it.toJson() })
|
||||||
|
}
|
||||||
|
json["games"] = Json.Array((1..tournament.lastRound()).map { round -> tournament.games(round).values.map { it.toJson() } });
|
||||||
|
file.printWriter().use { out ->
|
||||||
|
out.println(json.toPrettyString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTournament(id: ID): Tournament<*>? {
|
||||||
|
val file = path.useDirectoryEntries("${id.toString().padStart(LEFT_PAD, '0')}-*.tour") { entries ->
|
||||||
|
entries.map { entry ->
|
||||||
|
entry.fileName.toString()
|
||||||
|
}
|
||||||
|
}.firstOrNull() ?: throw Error("no such tournament")
|
||||||
|
val json = Json.parse(path.resolve(file).readText())?.asObject() ?: throw Error("could not read tournament")
|
||||||
|
val tournament = Tournament.fromJson(json)
|
||||||
|
val players = json["players"] as Json.Array? ?: Json.Array()
|
||||||
|
tournament.players.putAll(
|
||||||
|
players.associate {
|
||||||
|
(it as Json.Object).let { player ->
|
||||||
|
Pair(player.getID("id") ?: throw Error("invalid tournament file"), Player.fromJson(player))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (tournament is TeamTournament) {
|
||||||
|
val teams = json["teams"] as Json.Array? ?: Json.Array()
|
||||||
|
tournament.teams.putAll(
|
||||||
|
teams.associate {
|
||||||
|
(it as Json.Object).let { team ->
|
||||||
|
Pair(team.getID("id") ?: throw Error("invalid tournament file"), tournament.teamFromJson(team))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val games = json["games"] as Json.Array? ?: Json.Array()
|
||||||
|
(1..games.size).forEach { round ->
|
||||||
|
tournament.games(round).putAll(
|
||||||
|
games.associate {
|
||||||
|
(it as Json.Object).let { game ->
|
||||||
|
Pair(game.getID("id") ?: throw Error("invalid tournament file"), Game.fromJson(game))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return tournament
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceTournament(tournament: Tournament<*>) {
|
||||||
|
val filename = tournament.filename()
|
||||||
|
val file = path.resolve(filename).toFile()
|
||||||
|
if (!file.exists()) throw Error("File $filename does not exist")
|
||||||
|
file.renameTo(path.resolve(filename + "-${timestamp}").toFile())
|
||||||
|
addTournament(tournament)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteTournament(tournament: Tournament<*>) {
|
||||||
|
val filename = tournament.filename()
|
||||||
|
val file = path.resolve(filename).toFile()
|
||||||
|
if (!file.exists()) throw Error("File $filename does not exist")
|
||||||
|
file.renameTo(path.resolve(filename + "-${timestamp}").toFile())
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,28 @@
|
|||||||
package org.jeudego.pairgoth.store
|
package org.jeudego.pairgoth.store
|
||||||
|
|
||||||
class MemoryStore {
|
import org.jeudego.pairgoth.model.ID
|
||||||
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
|
|
||||||
|
class MemoryStore: StoreImplementation {
|
||||||
|
private val tournaments = mutableMapOf<ID, Tournament<*>>()
|
||||||
|
|
||||||
|
override fun getTournamentsIDs(): Set<ID> = tournaments.keys
|
||||||
|
|
||||||
|
override fun addTournament(tournament: Tournament<*>) {
|
||||||
|
if (tournaments.containsKey(tournament.id)) throw Error("tournament id #${tournament.id} already exists")
|
||||||
|
tournaments[tournament.id] = tournament
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTournament(id: ID) = tournaments[id]
|
||||||
|
|
||||||
|
override fun replaceTournament(tournament: Tournament<*>) {
|
||||||
|
if (!tournaments.containsKey(tournament.id)) throw Error("tournament id #${tournament.id} not known")
|
||||||
|
tournaments[tournament.id] = tournament
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteTournament(tournament: Tournament<*>) {
|
||||||
|
if (!tournaments.containsKey(tournament.id)) throw Error("tournament id #${tournament.id} not known")
|
||||||
|
tournaments.remove(tournament.id)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,38 +1,19 @@
|
|||||||
package org.jeudego.pairgoth.store
|
package org.jeudego.pairgoth.store
|
||||||
|
|
||||||
import org.jeudego.pairgoth.model.ID
|
import org.jeudego.pairgoth.model.ID
|
||||||
import org.jeudego.pairgoth.model.Player
|
|
||||||
import org.jeudego.pairgoth.model.Tournament
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import kotlin.math.E
|
|
||||||
|
|
||||||
object Store {
|
|
||||||
private val _nextTournamentId = AtomicInteger()
|
|
||||||
private val _nextPlayerId = AtomicInteger()
|
|
||||||
private val _nextGameId = AtomicInteger()
|
|
||||||
val nextTournamentId get() = _nextTournamentId.incrementAndGet()
|
|
||||||
val nextPlayerId get() = _nextPlayerId.incrementAndGet()
|
|
||||||
val nextGameId get() = _nextGameId.incrementAndGet()
|
|
||||||
|
|
||||||
private val tournaments = mutableMapOf<ID, Tournament<*>>()
|
|
||||||
|
|
||||||
fun addTournament(tournament: Tournament<*>) {
|
|
||||||
if (tournaments.containsKey(tournament.id)) throw Error("tournament id #${tournament.id} already exists")
|
|
||||||
tournaments[tournament.id] = tournament
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTournament(id: ID) = tournaments[id]
|
|
||||||
|
|
||||||
fun getTournamentsIDs(): Set<ID> = tournaments.keys
|
|
||||||
|
|
||||||
fun replaceTournament(tournament: Tournament<*>) {
|
|
||||||
if (!tournaments.containsKey(tournament.id)) throw Error("tournament id #${tournament.id} not known")
|
|
||||||
tournaments[tournament.id] = tournament
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteTournament(tournament: Tournament<*>) {
|
|
||||||
if (!tournaments.containsKey(tournament.id)) throw Error("tournament id #${tournament.id} not known")
|
|
||||||
tournaments.remove(tournament.id)
|
|
||||||
|
|
||||||
|
private fun createStoreImplementation(): StoreImplementation {
|
||||||
|
val storeProperty = System.getProperty("pairgoth.store") ?: "memory"
|
||||||
|
return when (storeProperty) {
|
||||||
|
"memory" -> MemoryStore()
|
||||||
|
"file" -> {
|
||||||
|
val filePath = System.getProperty("pairgoth.store.file.path") ?: "."
|
||||||
|
FileStore(filePath)
|
||||||
|
}
|
||||||
|
else -> throw Error("unknown store: $storeProperty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object Store: StoreImplementation by createStoreImplementation()
|
||||||
|
@@ -1,4 +1,22 @@
|
|||||||
package org.jeudego.pairgoth.store
|
package org.jeudego.pairgoth.store
|
||||||
|
|
||||||
|
import org.jeudego.pairgoth.model.ID
|
||||||
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
private val _nextTournamentId = AtomicInteger()
|
||||||
|
private val _nextPlayerId = AtomicInteger()
|
||||||
|
private val _nextGameId = AtomicInteger()
|
||||||
|
|
||||||
interface StoreImplementation {
|
interface StoreImplementation {
|
||||||
|
|
||||||
|
val nextTournamentId get() = _nextTournamentId.incrementAndGet()
|
||||||
|
val nextPlayerId get() = _nextPlayerId.incrementAndGet()
|
||||||
|
val nextGameId get() = _nextGameId.incrementAndGet()
|
||||||
|
|
||||||
|
fun getTournamentsIDs(): Set<ID>
|
||||||
|
fun addTournament(tournament: Tournament<*>)
|
||||||
|
fun getTournament(id: ID): Tournament<*>?
|
||||||
|
fun replaceTournament(tournament: Tournament<*>)
|
||||||
|
fun deleteTournament(tournament: Tournament<*>)
|
||||||
}
|
}
|
@@ -2,6 +2,10 @@
|
|||||||
webapp.env = dev
|
webapp.env = dev
|
||||||
webapp.url = http://localhost:8080
|
webapp.url = http://localhost:8080
|
||||||
|
|
||||||
|
# store
|
||||||
|
store = file
|
||||||
|
store.file.path = tournamentfiles
|
||||||
|
|
||||||
# smtp
|
# smtp
|
||||||
smtp.sender =
|
smtp.sender =
|
||||||
smtp.host =
|
smtp.host =
|
||||||
|
Reference in New Issue
Block a user