File storage looks good

This commit is contained in:
Claude Brisson
2023-06-06 13:40:22 +02:00
parent 7c82dad7bc
commit 231b7d68dd
14 changed files with 47 additions and 31 deletions

9
.gitignore vendored
View File

@@ -1,6 +1,7 @@
target
docker/data
.idea
docker/.env
pairgoth.properties
/docker/data
/.idea
/docker/.env
/pairgoth.properties
*.err
/tournamentfiles

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -66,7 +66,7 @@ sealed class Tournament <P: Pairable>(
private val games = mutableListOf<MutableMap<ID, Game>>()
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<ID, Game>().also { games.add(it) }
fun lastRound() = games.size

View File

@@ -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"

View File

@@ -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<ID> {
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()

View File

@@ -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")

View File

@@ -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 {

View File

@@ -107,9 +107,12 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H
private val webServices: MutableMap<String?, Pair<Runnable, Thread?>> = 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") }