One tournament files directory per user for oauth

This commit is contained in:
Claude Brisson
2024-03-04 09:08:11 +01:00
parent 5c0b763751
commit b7508a85f8
14 changed files with 112 additions and 73 deletions

View File

@@ -2,22 +2,21 @@ package org.jeudego.pairgoth.api
import com.republicate.kson.Json import com.republicate.kson.Json
import org.jeudego.pairgoth.model.Tournament import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.store.Store
import org.jeudego.pairgoth.server.Event import org.jeudego.pairgoth.server.Event
import org.jeudego.pairgoth.store.getStore
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
interface PairgothApiHandler: ApiHandler { interface PairgothApiHandler: ApiHandler {
fun getTournament(request: HttpServletRequest): Tournament<*> { fun getTournament(request: HttpServletRequest): Tournament<*> {
val tournamentId = getSelector(request)?.toIntOrNull() ?: ApiHandler.badRequest("invalid tournament id") val tournamentId = getSelector(request)?.toIntOrNull() ?: ApiHandler.badRequest("invalid tournament id")
return Store.getTournament(tournamentId) ?: ApiHandler.badRequest("unknown tournament id") return getStore(request).getTournament(tournamentId) ?: ApiHandler.badRequest("unknown tournament id")
} }
fun Tournament<*>.dispatchEvent(event: Event, data: Json? = null) { fun Tournament<*>.dispatchEvent(event: Event, request: HttpServletRequest, data: Json? = null) {
Event.dispatch(event, Json.Object("tournament" to id, "data" to data)) Event.dispatch(event, Json.Object("tournament" to id, "data" to data))
// when storage is not in memory, the tournament has to be persisted // when storage is not in memory, the tournament has to be persisted
if (event != Event.TournamentAdded && event != Event.TournamentDeleted) if (event != Event.TournamentAdded && event != Event.TournamentDeleted)
Store.replaceTournament(this) getStore(request).replaceTournament(this)
} }
} }

View File

@@ -58,7 +58,7 @@ object PairingHandler: PairgothApiHandler {
} }
val games = tournament.pair(round, pairables) val games = tournament.pair(round, pairables)
val ret = games.map { it.toJson() }.toJsonArray() val ret = games.map { it.toJson() }.toJsonArray()
tournament.dispatchEvent(GamesAdded, Json.Object("round" to round, "games" to ret)) tournament.dispatchEvent(GamesAdded, request, Json.Object("round" to round, "games" to ret))
return ret return ret
} }
@@ -98,7 +98,7 @@ object PairingHandler: PairgothApiHandler {
if (payload.containsKey("t")) { if (payload.containsKey("t")) {
game.table = payload.getString("t")?.toIntOrNull() ?: badRequest("invalid table number") game.table = payload.getString("t")?.toIntOrNull() ?: badRequest("invalid table number")
} }
tournament.dispatchEvent(GameUpdated, Json.Object("round" to round, "game" to game.toJson())) tournament.dispatchEvent(GameUpdated, request, Json.Object("round" to round, "game" to game.toJson()))
if (game.table != previousTable) { if (game.table != previousTable) {
val sortedPairables = tournament.getSortedPairables(round) val sortedPairables = tournament.getSortedPairables(round)
val sortedMap = sortedPairables.associateBy { val sortedMap = sortedPairables.associateBy {
@@ -113,7 +113,10 @@ object PairingHandler: PairgothApiHandler {
val games = tournament.games(round).values.sortedBy { val games = tournament.games(round).values.sortedBy {
if (it.table == 0) Int.MAX_VALUE else it.table if (it.table == 0) Int.MAX_VALUE else it.table
} }
tournament.dispatchEvent(TablesRenumbered, Json.Object("round" to round, "games" to games.map { it.toJson() }.toCollection(Json.MutableArray()))) tournament.dispatchEvent(
TablesRenumbered, request,
Json.Object("round" to round, "games" to games.map { it.toJson() }.toCollection(Json.MutableArray()))
)
} }
} }
return Json.Object("success" to true) return Json.Object("success" to true)
@@ -132,7 +135,10 @@ object PairingHandler: PairgothApiHandler {
val games = tournament.games(round).values.sortedBy { val games = tournament.games(round).values.sortedBy {
if (it.table == 0) Int.MAX_VALUE else it.table if (it.table == 0) Int.MAX_VALUE else it.table
} }
tournament.dispatchEvent(TablesRenumbered, Json.Object("round" to round, "games" to games.map { it.toJson() }.toCollection(Json.MutableArray()))) tournament.dispatchEvent(
TablesRenumbered, request,
Json.Object("round" to round, "games" to games.map { it.toJson() }.toCollection(Json.MutableArray()))
)
} }
return Json.Object("success" to true) return Json.Object("success" to true)
} }
@@ -161,7 +167,7 @@ object PairingHandler: PairgothApiHandler {
} }
} }
} }
tournament.dispatchEvent(GamesDeleted, Json.Object("round" to round, "games" to payload)) tournament.dispatchEvent(GamesDeleted, request, Json.Object("round" to round, "games" to payload))
return Json.Object("success" to true) return Json.Object("success" to true)
} }
} }

View File

@@ -24,7 +24,7 @@ object PlayerHandler: PairgothApiHandler {
val payload = getObjectPayload(request) val payload = getObjectPayload(request)
val player = Player.fromJson(payload) val player = Player.fromJson(payload)
tournament.players[player.id] = player tournament.players[player.id] = player
tournament.dispatchEvent(PlayerAdded, player.toJson()) tournament.dispatchEvent(PlayerAdded, request, player.toJson())
return Json.Object("success" to true, "id" to player.id) return Json.Object("success" to true, "id" to player.id)
} }
@@ -46,7 +46,7 @@ object PlayerHandler: PairgothApiHandler {
} }
} }
tournament.players[id] = updated tournament.players[id] = updated
tournament.dispatchEvent(PlayerUpdated, player.toJson()) tournament.dispatchEvent(PlayerUpdated, request, player.toJson())
return Json.Object("success" to true) return Json.Object("success" to true)
} }
@@ -59,7 +59,7 @@ object PlayerHandler: PairgothApiHandler {
badRequest("player is playing") badRequest("player is playing")
} }
tournament.players.remove(id) ?: badRequest("invalid player id") tournament.players.remove(id) ?: badRequest("invalid player id")
tournament.dispatchEvent(PlayerDeleted, Json.Object("id" to id)) tournament.dispatchEvent(PlayerDeleted, request, Json.Object("id" to id))
return Json.Object("success" to true) return Json.Object("success" to true)
} }
} }

View File

@@ -24,7 +24,7 @@ object ResultsHandler: PairgothApiHandler {
val payload = getObjectPayload(request) val payload = getObjectPayload(request)
val game = tournament.games(round)[payload.getInt("id")] ?: badRequest("invalid game id") val game = tournament.games(round)[payload.getInt("id")] ?: badRequest("invalid game id")
game.result = Game.Result.fromSymbol(payload.getChar("result") ?: badRequest("missing result")) game.result = Game.Result.fromSymbol(payload.getChar("result") ?: badRequest("missing result"))
tournament.dispatchEvent(Event.ResultUpdated, Json.Object("round" to round, "data" to game)) tournament.dispatchEvent(Event.ResultUpdated, request, Json.Object("round" to round, "data" to game))
return Json.Object("success" to true) return Json.Object("success" to true)
} }
} }

View File

@@ -25,7 +25,7 @@ object TeamHandler: PairgothApiHandler {
val payload = getObjectPayload(request) val payload = getObjectPayload(request)
val team = tournament.teamFromJson(payload) val team = tournament.teamFromJson(payload)
tournament.teams[team.id] = team tournament.teams[team.id] = team
tournament.dispatchEvent(TeamAdded, team.toJson()) tournament.dispatchEvent(TeamAdded, request, team.toJson())
return Json.Object("success" to true, "id" to team.id) return Json.Object("success" to true, "id" to team.id)
} }
@@ -37,7 +37,7 @@ object TeamHandler: PairgothApiHandler {
val payload = getObjectPayload(request) val payload = getObjectPayload(request)
val updated = tournament.teamFromJson(payload, team) val updated = tournament.teamFromJson(payload, team)
tournament.teams[updated.id] = updated tournament.teams[updated.id] = updated
tournament.dispatchEvent(TeamUpdated, team.toJson()) tournament.dispatchEvent(TeamUpdated, request, team.toJson())
return Json.Object("success" to true) return Json.Object("success" to true)
} }
@@ -46,7 +46,7 @@ object TeamHandler: PairgothApiHandler {
if (tournament !is TeamTournament) badRequest("tournament is not a team tournament") if (tournament !is TeamTournament) badRequest("tournament is not a team tournament")
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid team selector") val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid team selector")
tournament.teams.remove(id) ?: badRequest("invalid team id") tournament.teams.remove(id) ?: badRequest("invalid team id")
tournament.dispatchEvent(TeamDeleted, Json.Object("id" to id)) tournament.dispatchEvent(TeamDeleted, request, Json.Object("id" to id))
return Json.Object("success" to true) return Json.Object("success" to true)
} }
} }

View File

@@ -9,9 +9,9 @@ import org.jeudego.pairgoth.model.TeamTournament
import org.jeudego.pairgoth.model.Tournament import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.model.fromJson import org.jeudego.pairgoth.model.fromJson
import org.jeudego.pairgoth.model.toJson import org.jeudego.pairgoth.model.toJson
import org.jeudego.pairgoth.store.Store
import org.jeudego.pairgoth.server.ApiServlet import org.jeudego.pairgoth.server.ApiServlet
import org.jeudego.pairgoth.server.Event.* import org.jeudego.pairgoth.server.Event.*
import org.jeudego.pairgoth.store.getStore
import org.w3c.dom.Element import org.w3c.dom.Element
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse import javax.servlet.http.HttpServletResponse
@@ -21,12 +21,12 @@ object TournamentHandler: PairgothApiHandler {
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? { override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
val accept = request.getHeader("Accept") val accept = request.getHeader("Accept")
return when (val id = getSelector(request)?.toIntOrNull()) { return when (val id = getSelector(request)?.toIntOrNull()) {
null -> Store.getTournaments().toJsonObject() null -> getStore(request).getTournaments().toJsonObject()
else -> else ->
when { when {
ApiServlet.isJson(accept) -> Store.getTournament(id)?.toJson() ?: badRequest("no tournament with id #${id}") ApiServlet.isJson(accept) -> getStore(request).getTournament(id)?.toJson() ?: badRequest("no tournament with id #${id}")
ApiServlet.isXml(accept) -> { ApiServlet.isXml(accept) -> {
val export = Store.getTournament(id)?.let { OpenGotha.export(it) } ?: badRequest("no tournament with id #${id}") val export = getStore(request).getTournament(id)?.let { OpenGotha.export(it) } ?: badRequest("no tournament with id #${id}")
response.contentType = "application/xml; charset=UTF-8" response.contentType = "application/xml; charset=UTF-8"
response.writer.write(export) response.writer.write(export)
null // return null to indicate that we handled the response ourself null // return null to indicate that we handled the response ourself
@@ -42,8 +42,8 @@ object TournamentHandler: PairgothApiHandler {
is Element -> OpenGotha.import(payload) is Element -> OpenGotha.import(payload)
else -> badRequest("missing or invalid payload") else -> badRequest("missing or invalid payload")
} }
Store.addTournament(tournament) getStore(request).addTournament(tournament)
tournament.dispatchEvent(TournamentAdded, tournament.toJson()) tournament.dispatchEvent(TournamentAdded, request, tournament.toJson())
return Json.Object("success" to true, "id" to tournament.id) return Json.Object("success" to true, "id" to tournament.id)
} }
@@ -63,14 +63,14 @@ object TournamentHandler: PairgothApiHandler {
clear() clear()
putAll(tournament.games(round)) putAll(tournament.games(round))
} }
updated.dispatchEvent(TournamentUpdated, updated.toJson()) updated.dispatchEvent(TournamentUpdated, request, updated.toJson())
return Json.Object("success" to true) return Json.Object("success" to true)
} }
override fun delete(request: HttpServletRequest, response: HttpServletResponse): Json { override fun delete(request: HttpServletRequest, response: HttpServletResponse): Json {
val tournament = getTournament(request) val tournament = getTournament(request)
Store.deleteTournament(tournament) getStore(request).deleteTournament(tournament)
tournament.dispatchEvent(TournamentDeleted, Json.Object("id" to tournament.id)) tournament.dispatchEvent(TournamentDeleted, request, Json.Object("id" to tournament.id))
return Json.Object("success" to true) return Json.Object("success" to true)
} }
} }

View File

@@ -7,6 +7,9 @@ import org.jeudego.pairgoth.model.*
import org.jeudego.pairgoth.opengotha.TournamentType import org.jeudego.pairgoth.opengotha.TournamentType
import org.jeudego.pairgoth.opengotha.ObjectFactory import org.jeudego.pairgoth.opengotha.ObjectFactory
import org.jeudego.pairgoth.store.Store import org.jeudego.pairgoth.store.Store
import org.jeudego.pairgoth.store.nextGameId
import org.jeudego.pairgoth.store.nextPlayerId
import org.jeudego.pairgoth.store.nextTournamentId
import org.w3c.dom.Element import org.w3c.dom.Element
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@@ -118,7 +121,7 @@ object OpenGotha {
) )
val tournament = StandardTournament( val tournament = StandardTournament(
id = Store.nextTournamentId, id = nextTournamentId,
type = Tournament.Type.INDIVIDUAL, // CB for now, TODO type = Tournament.Type.INDIVIDUAL, // CB for now, TODO
name = genParams.name, name = genParams.name,
shortName = genParams.shortName, shortName = genParams.shortName,
@@ -153,7 +156,7 @@ object OpenGotha {
// import players // import players
ogTournament.players.player.map { player -> ogTournament.players.player.map { player ->
Player( Player(
id = Store.nextPlayerId, id = nextPlayerId,
name = player.name, name = player.name,
firstname = player.firstName, firstname = player.firstName,
rating = player.rating, rating = player.rating,
@@ -174,7 +177,7 @@ object OpenGotha {
}.entries.sortedBy { it.key }.map { }.entries.sortedBy { it.key }.map {
it.value.map { game -> it.value.map { game ->
Game( Game(
id = Store.nextGameId, id = nextGameId,
table = game.tableNumber, table = game.tableNumber,
black = canonicMap[game.blackPlayer] ?: throw Error("player not found: ${game.blackPlayer}"), black = canonicMap[game.blackPlayer] ?: throw Error("player not found: ${game.blackPlayer}"),
white = canonicMap[game.whitePlayer] ?: throw Error("player not found: ${game.whitePlayer}"), white = canonicMap[game.whitePlayer] ?: throw Error("player not found: ${game.whitePlayer}"),

View File

@@ -3,6 +3,7 @@ package org.jeudego.pairgoth.model
import com.republicate.kson.Json import com.republicate.kson.Json
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.store.Store import org.jeudego.pairgoth.store.Store
import org.jeudego.pairgoth.store.nextPlayerId
import java.util.* import java.util.*
// Pairable // Pairable
@@ -101,7 +102,7 @@ class Player(
} }
fun Player.Companion.fromJson(json: Json.Object, default: Player? = null) = Player( fun Player.Companion.fromJson(json: Json.Object, default: Player? = null) = Player(
id = json.getInt("id") ?: default?.id ?: Store.nextPlayerId, id = json.getInt("id") ?: default?.id ?: nextPlayerId,
name = json.getString("name") ?: default?.name ?: badRequest("missing name"), name = json.getString("name") ?: default?.name ?: badRequest("missing name"),
firstname = json.getString("firstname") ?: default?.firstname ?: badRequest("missing firstname"), firstname = json.getString("firstname") ?: default?.firstname ?: badRequest("missing firstname"),
rating = json.getInt("rating") ?: default?.rating ?: badRequest("missing rating"), rating = json.getInt("rating") ?: default?.rating ?: badRequest("missing rating"),

View File

@@ -9,6 +9,8 @@ import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
import org.jeudego.pairgoth.pairing.solver.SwissSolver import org.jeudego.pairgoth.pairing.solver.SwissSolver
import org.jeudego.pairgoth.store.Store import org.jeudego.pairgoth.store.Store
import org.jeudego.pairgoth.store.nextPlayerId
import org.jeudego.pairgoth.store.nextTournamentId
import kotlin.math.max import kotlin.math.max
import java.util.* import java.util.*
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -178,7 +180,7 @@ class TeamTournament(
} }
fun teamFromJson(json: Json.Object, default: TeamTournament.Team? = null) = Team( fun teamFromJson(json: Json.Object, default: TeamTournament.Team? = null) = Team(
id = json.getInt("id") ?: default?.id ?: Store.nextPlayerId, id = json.getInt("id") ?: default?.id ?: nextPlayerId,
name = json.getString("name") ?: default?.name ?: badRequest("missing name"), name = json.getString("name") ?: default?.name ?: badRequest("missing name"),
final = json.getBoolean("final") ?: default?.final ?: badRequest("missing final") final = json.getBoolean("final") ?: default?.final ?: badRequest("missing final")
).apply { ).apply {
@@ -199,7 +201,7 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
// No clean way to avoid this redundancy // No clean way to avoid this redundancy
val tournament = 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 ?: nextTournamentId,
type = type, type = type,
name = json.getString("name") ?: default?.name ?: badRequest("missing name"), name = json.getString("name") ?: default?.name ?: badRequest("missing name"),
shortName = json.getString("shortName") ?: default?.shortName ?: badRequest("missing shortName"), shortName = json.getString("shortName") ?: default?.shortName ?: badRequest("missing shortName"),
@@ -217,7 +219,7 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
) )
else else
TeamTournament( TeamTournament(
id = json.getInt("id") ?: default?.id ?: Store.nextTournamentId, id = json.getInt("id") ?: default?.id ?: nextTournamentId,
type = type, type = type,
name = json.getString("name") ?: default?.name ?: badRequest("missing name"), name = json.getString("name") ?: default?.name ?: badRequest("missing name"),
shortName = json.getString("shortName") ?: default?.shortName ?: badRequest("missing shortName"), shortName = json.getString("shortName") ?: default?.shortName ?: badRequest("missing shortName"),

View File

@@ -6,6 +6,7 @@ import org.jeudego.pairgoth.pairing.BasePairingHelper
import org.jeudego.pairgoth.pairing.detRandom import org.jeudego.pairgoth.pairing.detRandom
import org.jeudego.pairgoth.pairing.nonDetRandom import org.jeudego.pairgoth.pairing.nonDetRandom
import org.jeudego.pairgoth.store.Store import org.jeudego.pairgoth.store.Store
import org.jeudego.pairgoth.store.nextGameId
import org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching import org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching
import org.jgrapht.alg.matching.blossom.v5.ObjectiveSense import org.jgrapht.alg.matching.blossom.v5.ObjectiveSense
import org.jgrapht.graph.DefaultWeightedEdge import org.jgrapht.graph.DefaultWeightedEdge
@@ -118,7 +119,7 @@ sealed class BaseSolver(
var result = sorted.flatMap { games(white = it[0], black = it[1]) } var result = sorted.flatMap { games(white = it[0], black = it[1]) }
// add game for ByePlayer // add game for ByePlayer
if (chosenByePlayer != ByePlayer) result += Game(id = Store.nextGameId, table = 0, white = ByePlayer.id, black = chosenByePlayer.id, result = Game.Result.fromSymbol('b')) if (chosenByePlayer != ByePlayer) result += Game(id = nextGameId, table = 0, white = ByePlayer.id, black = chosenByePlayer.id, result = Game.Result.fromSymbol('b'))
val DEBUG_EXPORT_WEIGHT = false val DEBUG_EXPORT_WEIGHT = false
if (DEBUG_EXPORT_WEIGHT) { if (DEBUG_EXPORT_WEIGHT) {
@@ -554,6 +555,6 @@ sealed class BaseSolver(
// CB TODO team of individuals pairing // CB TODO team of individuals pairing
val table = if (black.id == 0 || white.id == 0) 0 else usedTables.nextClearBit(1) val table = if (black.id == 0 || white.id == 0) 0 else usedTables.nextClearBit(1)
usedTables.set(table) usedTables.set(table)
return listOf(Game(id = Store.nextGameId, table = table, black = black.id, white = white.id, handicap = hd(white = white, black = black), drawnUpDown = dudd(black, white))) return listOf(Game(id = nextGameId, table = table, black = black.id, white = white.id, handicap = hd(white = white, black = black), drawnUpDown = dudd(black, white)))
} }
} }

View File

@@ -10,23 +10,40 @@ import org.jeudego.pairgoth.model.fromJson
import org.jeudego.pairgoth.model.getID import org.jeudego.pairgoth.model.getID
import org.jeudego.pairgoth.model.toFullJson import org.jeudego.pairgoth.model.toFullJson
import org.jeudego.pairgoth.model.toID import org.jeudego.pairgoth.model.toID
import org.jeudego.pairgoth.server.WebappManager
import java.lang.Integer.max import java.lang.Integer.max
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.PathMatcher
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.readText import kotlin.io.path.readText
import kotlin.io.path.useDirectoryEntries import kotlin.io.path.useDirectoryEntries
import kotlin.io.path.walk
private const val LEFT_PAD = 6 // left padding of IDs with '0' in filename 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" private fun Tournament<*>.filename() = "${id.toString().padStart(LEFT_PAD, '0')}-${shortName}.tour"
class FileStore(pathStr: String): IStore { class FileStore(pathStr: String): Store {
companion object { companion object {
private val filenameRegex = Regex("^(\\d+)-(.*)\\.tour$") private val filenameRegex = Regex("^(\\d+)-(.*)\\.tour$")
private val displayFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") private val displayFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
private val timestampFormat: DateFormat = SimpleDateFormat("yyyyMMddHHmmss") private val timestampFormat: DateFormat = SimpleDateFormat("yyyyMMddHHmmss")
private val timestamp: String get() = timestampFormat.format(Date()) private val timestamp: String get() = timestampFormat.format(Date())
@OptIn(ExperimentalPathApi::class)
private fun getMaxID(): ID {
val rootPath = Path.of(WebappManager.properties.getProperty("store.file.path") ?: ".")
val globMatcher = PathMatcher { path -> path.fileName.toString().endsWith(".tour") }
return rootPath.walk().filter { path -> globMatcher.matches(path) }.mapNotNull { path ->
val match = filenameRegex.matchEntire(path.fileName.toString())
match?.let { it.groupValues[1].toID() }
}.maxOrNull() ?: 0
}
init {
_nextTournamentId.set(getMaxID())
}
} }
private val path = Path.of(pathStr).also { private val path = Path.of(pathStr).also {
@@ -34,13 +51,10 @@ class FileStore(pathStr: String): IStore {
if (!file.mkdirs() && !file.isDirectory) throw Error("Property pairgoth.store.file.path must be a directory") if (!file.mkdirs() && !file.isDirectory) throw Error("Property pairgoth.store.file.path must be a directory")
} }
init {
_nextTournamentId.set(getTournaments().keys.maxOrNull() ?: 0.toID())
}
private fun lastModified(path: Path) = displayFormat.format(Date(path.toFile().lastModified())) private fun lastModified(path: Path) = displayFormat.format(Date(path.toFile().lastModified()))
override fun getTournaments(): Map<ID, Map<String, String>> { override fun getTournaments(): Map<ID, Map<String, String>> {
return path.useDirectoryEntries("*.tour") { entries -> return path.useDirectoryEntries("*.tour") { entries ->
entries.mapNotNull { entry -> entries.mapNotNull { entry ->

View File

@@ -1,22 +0,0 @@
package org.jeudego.pairgoth.store
import org.jeudego.pairgoth.model.ID
import org.jeudego.pairgoth.model.Tournament
import java.util.concurrent.atomic.AtomicInteger
internal val _nextTournamentId = AtomicInteger()
internal val _nextPlayerId = AtomicInteger()
internal val _nextGameId = AtomicInteger()
interface IStore {
val nextTournamentId get() = _nextTournamentId.incrementAndGet()
val nextPlayerId get() = _nextPlayerId.incrementAndGet()
val nextGameId get() = _nextGameId.incrementAndGet()
fun getTournaments(): Map<ID, Map<String, String>>
fun addTournament(tournament: Tournament<*>)
fun getTournament(id: ID): Tournament<*>?
fun replaceTournament(tournament: Tournament<*>)
fun deleteTournament(tournament: Tournament<*>)
}

View File

@@ -3,7 +3,7 @@ package org.jeudego.pairgoth.store
import org.jeudego.pairgoth.model.ID import org.jeudego.pairgoth.model.ID
import org.jeudego.pairgoth.model.Tournament import org.jeudego.pairgoth.model.Tournament
class MemoryStore: IStore { class MemoryStore: Store {
private val tournaments = mutableMapOf<ID, Tournament<*>>() private val tournaments = mutableMapOf<ID, Tournament<*>>()
override fun getTournaments(): Map<ID, Map<String, String>> = tournaments.mapValues { override fun getTournaments(): Map<ID, Map<String, String>> = tournaments.mapValues {

View File

@@ -1,16 +1,51 @@
package org.jeudego.pairgoth.store package org.jeudego.pairgoth.store
import com.republicate.kson.Json
import org.jeudego.pairgoth.model.ID
import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.server.ApiServlet.Companion.USER_KEY
import org.jeudego.pairgoth.server.WebappManager import org.jeudego.pairgoth.server.WebappManager
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicInteger
import javax.servlet.http.HttpServletRequest
private fun createStoreImplementation(): IStore { internal val _nextTournamentId = AtomicInteger()
return when (val storeProperty = WebappManager.properties.getProperty("store") ?: "memory") { internal val _nextPlayerId = AtomicInteger()
internal val _nextGameId = AtomicInteger()
val nextTournamentId get() = _nextTournamentId.incrementAndGet()
val nextPlayerId get() = _nextPlayerId.incrementAndGet()
val nextGameId get() = _nextGameId.incrementAndGet()
interface Store {
fun getTournaments(): Map<ID, Map<String, String>>
fun addTournament(tournament: Tournament<*>)
fun getTournament(id: ID): Tournament<*>?
fun replaceTournament(tournament: Tournament<*>)
fun deleteTournament(tournament: Tournament<*>)
}
fun getStore(request: HttpServletRequest): Store {
val storeType = WebappManager.getMandatoryProperty("store")
return when (val auth = WebappManager.getMandatoryProperty("auth")) {
"none", "sesame" ->
when (storeType) {
"memory" -> MemoryStore() "memory" -> MemoryStore()
"file" -> { "file" -> {
val filePath = WebappManager.properties.getProperty("store.file.path") ?: "." val filePath = WebappManager.properties.getProperty("store.file.path") ?: "."
FileStore(filePath) FileStore(filePath)
} }
else -> throw Error("unknown store: $storeProperty") else -> throw Error("invalid store type: $storeType")
}
"oauth" -> {
if (storeType == "memory") throw Error("invalid store type for oauth: $storeType")
var rootPath = WebappManager.properties.getProperty("store.file.path") ?: "."
(request.getAttribute(USER_KEY) as Json.Object?)?.getString("email")?.also { email ->
rootPath = "$rootPath/$email"
Path.of(rootPath).toFile().mkdirs()
}
FileStore(rootPath)
}
else -> throw Error("invalid auth: $auth")
} }
} }
object Store: IStore by createStoreImplementation()