From 231b7d68dd9932ea3ecc8d9432d11a2d77ad9fff Mon Sep 17 00:00:00 2001 From: Claude Brisson Date: Tue, 6 Jun 2023 13:40:22 +0200 Subject: [PATCH] File storage looks good --- .gitignore | 9 +++++---- .../org/jeudego/pairgoth/api/PairgothApiHandler.kt | 10 +++++++++- .../kotlin/org/jeudego/pairgoth/api/PairingHandler.kt | 6 +++--- .../kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt | 6 +++--- .../kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt | 2 +- .../org/jeudego/pairgoth/api/StandingsHandler.kt | 2 +- .../kotlin/org/jeudego/pairgoth/api/TeamHandler.kt | 6 +++--- .../org/jeudego/pairgoth/api/TournamentHandler.kt | 6 +++--- .../kotlin/org/jeudego/pairgoth/model/Tournament.kt | 2 +- .../kotlin/org/jeudego/pairgoth/oauth/OAuthHelper.kt | 4 ++-- .../kotlin/org/jeudego/pairgoth/store/FileStore.kt | 8 ++++++-- .../main/kotlin/org/jeudego/pairgoth/store/Store.kt | 6 +++--- .../org/jeudego/pairgoth/store/StoreImplementation.kt | 6 +++--- .../kotlin/org/jeudego/pairgoth/web/WebappManager.kt | 5 ++++- 14 files changed, 47 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 7a0cdfb..7815b8a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ target -docker/data -.idea -docker/.env -pairgoth.properties +/docker/data +/.idea +/docker/.env +/pairgoth.properties *.err +/tournamentfiles diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairgothApiHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairgothApiHandler.kt index 45fa50f..d125d7f 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairgothApiHandler.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairgothApiHandler.kt @@ -1,7 +1,9 @@ package org.jeudego.pairgoth.api +import com.republicate.kson.Json import org.jeudego.pairgoth.model.Tournament import org.jeudego.pairgoth.store.Store +import org.jeudego.pairgoth.web.Event import javax.servlet.http.HttpServletRequest interface PairgothApiHandler: ApiHandler { @@ -11,5 +13,11 @@ interface PairgothApiHandler: ApiHandler { return Store.getTournament(tournamentId) ?: ApiHandler.badRequest("unknown tournament id") } + fun Tournament<*>.dispatchEvent(event: Event, data: Json? = null) { + Event.dispatch(event, Json.Object("tournament" to id, "data" to data)) + // when storage is not in memory, the tournament has to be persisted + if (event != Event.tournamentAdded && event != Event.tournamentDeleted && event != Event.gameUpdated) + Store.replaceTournament(this) + } -} \ No newline at end of file +} diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt index a4fb956..5b0c7b2 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt @@ -46,7 +46,7 @@ object PairingHandler: PairgothApiHandler { } val games = tournament.pair(round, pairables) val ret = games.map { it.toJson() }.toJsonArray() - Event.dispatch(gamesAdded, Json.Object("tournament" to tournament.id, "round" to round, "data" to ret)) + tournament.dispatchEvent(gamesAdded, Json.Object("round" to round, "games" to ret)) return ret } @@ -60,7 +60,7 @@ object PairingHandler: PairgothApiHandler { game.black = payload.getID("b") ?: badRequest("missing black player id") game.white = payload.getID("w") ?: badRequest("missing white player id") if (payload.containsKey("h")) game.handicap = payload.getString("h")?.toIntOrNull() ?: badRequest("invalid handicap") - Event.dispatch(gameUpdated, Json.Object("tournament" to tournament.id, "round" to round, "data" to game.toJson())) + tournament.dispatchEvent(gameUpdated, Json.Object("round" to round, "game" to game.toJson())) return Json.Object("success" to true) } @@ -79,7 +79,7 @@ object PairingHandler: PairgothApiHandler { tournament.games(round).remove(id) } } - Event.dispatch(gamesDeleted, Json.Object("tournament" to tournament.id, "round" to round, "data" to payload)) + tournament.dispatchEvent(gamesDeleted, Json.Object("round" to round, "games" to payload)) return Json.Object("success" to true) } } diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt index 5a68180..d6f8de8 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt @@ -25,7 +25,7 @@ object PlayerHandler: PairgothApiHandler { val payload = getObjectPayload(request) val player = Player.fromJson(payload) tournament.players[player.id] = player - Event.dispatch(playerAdded, Json.Object("tournament" to tournament.id, "data" to player.toJson())) + tournament.dispatchEvent(playerAdded, player.toJson()) return Json.Object("success" to true, "id" to player.id) } @@ -36,7 +36,7 @@ object PlayerHandler: PairgothApiHandler { val payload = getObjectPayload(request) val updated = Player.fromJson(payload, player) tournament.players[updated.id] = updated - Event.dispatch(playerUpdated, Json.Object("tournament" to tournament.id, "data" to player.toJson())) + tournament.dispatchEvent(playerUpdated, player.toJson()) return Json.Object("success" to true) } @@ -44,7 +44,7 @@ object PlayerHandler: PairgothApiHandler { val tournament = getTournament(request) val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector") tournament.players.remove(id) ?: badRequest("invalid player id") - Event.dispatch(playerDeleted, Json.Object("tournament" to tournament.id, "data" to id)) + tournament.dispatchEvent(playerDeleted, Json.Object("id" to id)) return Json.Object("success" to true) } } diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt index 724b691..a26f994 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt @@ -24,7 +24,7 @@ object ResultsHandler: PairgothApiHandler { val payload = getObjectPayload(request) val game = tournament.games(round)[payload.getInt("id")] ?: badRequest("invalid game id") game.result = Game.Result.fromSymbol(payload.getChar("result") ?: badRequest("missing result")) - Event.dispatch(Event.resultUpdated, Json.Object("tournament" to tournament.id, "round" to round, "data" to game)) + tournament.dispatchEvent(Event.resultUpdated, Json.Object("round" to round, "data" to game)) return Json.Object("success" to true) } } diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/StandingsHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/StandingsHandler.kt index d83dc20..ffbe742 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/StandingsHandler.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/StandingsHandler.kt @@ -1,4 +1,4 @@ package org.jeudego.pairgoth.api object StandingsHandler: PairgothApiHandler { -} \ No newline at end of file +} diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TeamHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TeamHandler.kt index e440a9d..ee39b57 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TeamHandler.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TeamHandler.kt @@ -26,7 +26,7 @@ object TeamHandler: PairgothApiHandler { val payload = getObjectPayload(request) val team = tournament.teamFromJson(payload) tournament.teams[team.id] = team - Event.dispatch(teamAdded, Json.Object("tournament" to tournament.id, "data" to team.toJson())) + tournament.dispatchEvent(teamAdded, team.toJson()) return Json.Object("success" to true, "id" to team.id) } @@ -38,7 +38,7 @@ object TeamHandler: PairgothApiHandler { val payload = getObjectPayload(request) val updated = tournament.teamFromJson(payload, team) tournament.teams[updated.id] = updated - Event.dispatch(teamUpdated, Json.Object("tournament" to tournament.id, "data" to team.toJson())) + tournament.dispatchEvent(teamUpdated, team.toJson()) return Json.Object("success" to true) } @@ -47,7 +47,7 @@ object TeamHandler: PairgothApiHandler { if (tournament !is TeamTournament) badRequest("tournament is not a team tournament") val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid team selector") tournament.teams.remove(id) ?: badRequest("invalid team id") - Event.dispatch(teamDeleted, Json.Object("tournament" to tournament.id, "data" to id)) + tournament.dispatchEvent(teamDeleted, Json.Object("id" to id)) return Json.Object("success" to true) } } diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt index 3dc6a02..d803114 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt @@ -43,7 +43,7 @@ object TournamentHandler: PairgothApiHandler { else -> badRequest("missing or invalid payload") } Store.addTournament(tournament) - Event.dispatch(tournamentAdded, tournament.toJson()) + tournament.dispatchEvent(tournamentAdded, tournament.toJson()) return Json.Object("success" to true, "id" to tournament.id) } @@ -65,14 +65,14 @@ object TournamentHandler: PairgothApiHandler { } updated.criteria.addAll(tournament.criteria) Store.replaceTournament(updated) - Event.dispatch(tournamentUpdated, tournament.toJson()) + tournament.dispatchEvent(tournamentUpdated, tournament.toJson()) return Json.Object("success" to true) } override fun delete(request: HttpServletRequest): Json { val tournament = getTournament(request) Store.deleteTournament(tournament) - Event.dispatch(tournamentDeleted, tournament.id) + tournament.dispatchEvent(tournamentDeleted, Json.Object("id" to tournament.id)) return Json.Object("success" to true) } } diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt index ebe853d..c6b0c0e 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt @@ -66,7 +66,7 @@ sealed class Tournament ( private val games = mutableListOf>() fun games(round: Int) = games.getOrNull(round - 1) ?: - if (round > games.size) throw Error("invalid round") + if (round > games.size + 1) throw Error("invalid round") else mutableMapOf().also { games.add(it) } fun lastRound() = games.size diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/OAuthHelper.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/OAuthHelper.kt index 9f92561..453a7a4 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/OAuthHelper.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/OAuthHelper.kt @@ -16,9 +16,9 @@ abstract class OAuthHelper { abstract val name: String abstract fun getLoginURL(sessionId: String?): String protected val clientId: String - protected get() = WebappManager.getProperty("oauth." + name + ".client_id") + protected get() = WebappManager.getMandatoryProperty("oauth." + name + ".client_id") protected val secret: String - protected get() = WebappManager.getProperty("oauth." + name + ".secret") + protected get() = WebappManager.getMandatoryProperty("oauth." + name + ".secret") protected val redirectURI: String? protected get() = try { val uri: String = WebappManager.Companion.getProperty("webapp.url") + "/oauth.html" diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/store/FileStore.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/store/FileStore.kt index f4d69cf..22fd6a2 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/store/FileStore.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/store/FileStore.kt @@ -32,6 +32,10 @@ class FileStore(pathStr: String): StoreImplementation { if (!file.mkdirs() && !file.isDirectory) throw Error("Property pairgoth.store.file.path must be a directory") } + init { + _nextTournamentId.set(getTournamentsIDs().maxOrNull() ?: 0.toID()) + } + override fun getTournamentsIDs(): Set { return path.useDirectoryEntries("*.tour") { entries -> entries.mapNotNull { entry -> @@ -59,8 +63,8 @@ class FileStore(pathStr: String): StoreImplementation { val file = path.useDirectoryEntries("${id.toString().padStart(LEFT_PAD, '0')}-*.tour") { entries -> entries.map { entry -> entry.fileName.toString() - } - }.firstOrNull() ?: throw Error("no such tournament") + }.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() diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/store/Store.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/store/Store.kt index dd582df..bbf1574 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/store/Store.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/store/Store.kt @@ -2,14 +2,14 @@ package org.jeudego.pairgoth.store import org.jeudego.pairgoth.model.ID import org.jeudego.pairgoth.model.Tournament +import org.jeudego.pairgoth.web.WebappManager import java.util.concurrent.atomic.AtomicInteger private fun createStoreImplementation(): StoreImplementation { - val storeProperty = System.getProperty("pairgoth.store") ?: "memory" - return when (storeProperty) { + return when (val storeProperty = WebappManager.getProperty("store") ?: "memory") { "memory" -> MemoryStore() "file" -> { - val filePath = System.getProperty("pairgoth.store.file.path") ?: "." + val filePath = WebappManager.getMandatoryProperty("store.file.path") ?: "." FileStore(filePath) } else -> throw Error("unknown store: $storeProperty") diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/store/StoreImplementation.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/store/StoreImplementation.kt index 5a00cb0..0948a47 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/store/StoreImplementation.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/store/StoreImplementation.kt @@ -4,9 +4,9 @@ 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() +internal val _nextTournamentId = AtomicInteger() +internal val _nextPlayerId = AtomicInteger() +internal val _nextGameId = AtomicInteger() interface StoreImplementation { diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt index 148fc00..1688dc9 100644 --- a/webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt +++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt @@ -107,9 +107,12 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H private val webServices: MutableMap> = TreeMap() var logger = LoggerFactory.getLogger(WebappManager::class.java) val properties = Properties() - fun getProperty(prop: String?): String { + fun getProperty(prop: String): String? { return properties.getProperty(prop) } + fun getMandatoryProperty(prop: String): String { + return properties.getProperty(prop) ?: throw Error("missing property: ${prop}") + } val webappURL by lazy { getProperty("webapp.url") }