One tournament files directory per user for oauth
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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}"),
|
||||||
|
@@ -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"),
|
||||||
|
@@ -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"),
|
||||||
|
@@ -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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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 ->
|
||||||
|
@@ -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<*>)
|
|
||||||
}
|
|
@@ -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 {
|
||||||
|
@@ -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()
|
||||||
"memory" -> MemoryStore()
|
internal val _nextGameId = AtomicInteger()
|
||||||
"file" -> {
|
|
||||||
val filePath = WebappManager.properties.getProperty("store.file.path") ?: "."
|
val nextTournamentId get() = _nextTournamentId.incrementAndGet()
|
||||||
FileStore(filePath)
|
val nextPlayerId get() = _nextPlayerId.incrementAndGet()
|
||||||
}
|
val nextGameId get() = _nextGameId.incrementAndGet()
|
||||||
else -> throw Error("unknown store: $storeProperty")
|
|
||||||
}
|
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<*>)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Store: IStore by createStoreImplementation()
|
fun getStore(request: HttpServletRequest): Store {
|
||||||
|
val storeType = WebappManager.getMandatoryProperty("store")
|
||||||
|
return when (val auth = WebappManager.getMandatoryProperty("auth")) {
|
||||||
|
"none", "sesame" ->
|
||||||
|
when (storeType) {
|
||||||
|
"memory" -> MemoryStore()
|
||||||
|
"file" -> {
|
||||||
|
val filePath = WebappManager.properties.getProperty("store.file.path") ?: "."
|
||||||
|
FileStore(filePath)
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user