diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/AGARatingsHandler.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/AGARatingsHandler.kt index 541c8cf..96c1d82 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/AGARatingsHandler.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/AGARatingsHandler.kt @@ -2,13 +2,17 @@ package org.jeudego.pairgoth.ratings import com.republicate.kson.Json import java.net.URL +import java.time.LocalDate object AGARatingsHandler: RatingsHandler(RatingsManager.Ratings.AGA) { override val defaultURL: URL by lazy { - throw Error("No URL for AGA...") + throw Error("No functional URL for AGA...") } override val active = false - override fun parsePayload(payload: String): Json.Array { - return Json.Array() + override fun parsePayload(payload: String): Pair { + return Pair( + LocalDate.MIN, + Json.Array() + ) } } diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/EGFRatingsHandler.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/EGFRatingsHandler.kt index 4532d73..8c5141c 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/EGFRatingsHandler.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/EGFRatingsHandler.kt @@ -2,33 +2,41 @@ package org.jeudego.pairgoth.ratings import com.republicate.kson.Json import java.net.URL +import java.time.LocalDate +import java.time.format.DateTimeFormatter object EGFRatingsHandler: RatingsHandler(RatingsManager.Ratings.EGF) { + val ratingsDateFormatter = DateTimeFormatter.ofPattern("dd MMM yyyy") override val defaultURL = URL("https://www.europeangodatabase.eu/EGD/EGD_2_0/downloads/allworld_lp.html") - override fun parsePayload(payload: String): Json.Array { - return payload.lines().filter { - it.matches(Regex("\\s+\\d+(?!.*\\(undefined\\)|Anonymous).*")) - }.mapNotNullTo(Json.MutableArray()) { - val match = linePattern.matchEntire(it) - if (match == null) { - logger.error("could not parse line: $it") - null - } else { - val pairs = groups.map { - Pair(it, match.groups[it]?.value) - }.toTypedArray() - Json.MutableObject(*pairs).also { player -> - player["origin"] = "EGF" - // override rank with rating equivalent - player["rating"]?.toString()?.toIntOrNull()?.let { rating -> - player["rank"] = ((rating - 2050)/100).let { if (it < 0) "${-it+1}k" else "${it+1}d" } - } - if ("UK" == player.getString("country")) { - player["country"] = "GB" + override fun parsePayload(payload: String): Pair { + val ratingsDateString = payload.lines().filter { it.startsWith("(") }.first().trim().removeSurrounding("(", ")") + val ratingsDate = LocalDate.parse(ratingsDateString, ratingsDateFormatter) + return Pair( + ratingsDate, + payload.lines().filter { + it.matches(Regex("\\s+\\d+(?!.*\\(undefined\\)|Anonymous).*")) + }.mapNotNullTo(Json.MutableArray()) { + val match = linePattern.matchEntire(it) + if (match == null) { + logger.error("could not parse line: $it") + null + } else { + val pairs = groups.map { + Pair(it, match.groups[it]?.value) + }.toTypedArray() + Json.MutableObject(*pairs).also { player -> + player["origin"] = "EGF" + // override rank with rating equivalent + player["rating"]?.toString()?.toIntOrNull()?.let { rating -> + player["rank"] = ((rating - 2050)/100).let { if (it < 0) "${-it+1}k" else "${it+1}d" } + } + if ("UK" == player.getString("country")) { + player["country"] = "GB" + } } } } - } + ) } // 19574643 Abad Jahin FR 38GJ 20k -- 15 2 T200202B var linePattern = diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/FFGRatingsHandler.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/FFGRatingsHandler.kt index 1fba88f..f8bc26d 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/FFGRatingsHandler.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/FFGRatingsHandler.kt @@ -1,36 +1,40 @@ package org.jeudego.pairgoth.ratings import com.republicate.kson.Json -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Request import java.net.URL -import java.nio.charset.Charset import java.nio.charset.StandardCharsets +import java.time.LocalDate +import java.time.format.DateTimeFormatter object FFGRatingsHandler: RatingsHandler(RatingsManager.Ratings.FFG) { + val ratingsDateFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy") override val defaultURL = URL("https://ffg.jeudego.org/echelle/echtxt/ech_ffg_V3.txt") - override fun parsePayload(payload: String): Json.Array { - return payload.lines().mapNotNullTo(Json.MutableArray()) { line -> - val match = linePattern.matchEntire(line) - if (match == null) { - logger.error("could not parse line: $line") - null - } else { - val pairs = groups.map { - Pair(it, match.groups[it]?.value) - }.toTypedArray() - Json.MutableObject(*pairs).also { - it["origin"] = "FFG" - val rating = it["rating"]?.toString()?.toIntOrNull() - if (rating != null) { - it["rank"] = (rating/100).let { if (rating < 0) "${-it+1}k" else "${it+1}d" } - // then adjust to match EGF ratings - it["rating"] = rating + 2050 + override fun parsePayload(payload: String): Pair { + val ratingsDateString = payload.lineSequence().first().substringAfter("#Echelle au ").substringBefore(" ") + val ratingsDate = LocalDate.parse(ratingsDateString, ratingsDateFormatter) + return Pair( + ratingsDate, + payload.lines().mapNotNullTo(Json.MutableArray()) { line -> + val match = linePattern.matchEntire(line) + if (match == null) { + logger.error("could not parse line: $line") + null + } else { + val pairs = groups.map { + Pair(it, match.groups[it]?.value) + }.toTypedArray() + Json.MutableObject(*pairs).also { + it["origin"] = "FFG" + val rating = it["rating"]?.toString()?.toIntOrNull() + if (rating != null) { + it["rank"] = (rating/100).let { if (rating < 0) "${-it+1}k" else "${it+1}d" } + // then adjust to match EGF ratings + it["rating"] = rating + 2050 + } } } } - } + ) } override fun defaultCharset() = StandardCharsets.ISO_8859_1 diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsHandler.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsHandler.kt index 262b06d..05e1a07 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsHandler.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsHandler.kt @@ -6,18 +6,27 @@ import okhttp3.OkHttpClient import okhttp3.Request import org.jeudego.pairgoth.web.WebappManager import org.slf4j.LoggerFactory +import java.io.File import java.net.URL import java.nio.charset.StandardCharsets +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.format.DateTimeFormatter import java.util.* import java.util.concurrent.TimeUnit +import kotlin.io.path.name +import kotlin.io.path.useDirectoryEntries abstract class RatingsHandler(val origin: RatingsManager.Ratings) { - - private val delay = TimeUnit.HOURS.toMillis(1L) + companion object { + private val delay = TimeUnit.HOURS.toMillis(1L) + private val ymd = DateTimeFormatter.ofPattern("yyyyMMdd") + } private val client = OkHttpClient() abstract val defaultURL: URL open val active = true - val cacheFile = RatingsManager.path.resolve("${origin.name}.json").toFile() + // val cacheFile = RatingsManager.path.resolve("${origin.name}.json").toFile() lateinit var players: Json.Array private var updated = false @@ -25,23 +34,38 @@ abstract class RatingsHandler(val origin: RatingsManager.Ratings) { WebappManager.properties.getProperty("ratings.${origin.name.lowercase(Locale.ROOT)}")?.let { URL(it) } ?: defaultURL } + private fun getRatingsFiles() = RatingsManager.path.useDirectoryEntries("${origin.name}-*.json") { entries -> + entries.sortedBy { it.fileName.name }.map { + it.toFile() + }.toList() + } + + private fun getLatestRatingsFile() = getRatingsFiles().lastOrNull() + + private fun initIfNeeded(ratingsFile: File): Boolean { + return if (!this::players.isInitialized) { + players = Json.parse(ratingsFile.readText())?.asArray() ?: Json.Array() + true + } else false + } + fun updateIfNeeded(): Boolean { - return if (Date().time - cacheFile.lastModified() > delay) { - RatingsManager.logger.info("Updating $origin cache from $url") - val payload = fetchPayload() - players = parsePayload(payload).also { - val cachePayload = it.toString() - cacheFile.printWriter().use { out -> - out.println(cachePayload) - } - } - true - } else if (!this::players.isInitialized) { - players = Json.parse(cacheFile.readText())?.asArray() ?: Json.Array() - true - } else { - false + val latestRatingsFile = getLatestRatingsFile() + if (latestRatingsFile != null && Date().time - latestRatingsFile.lastModified() < delay) { + return initIfNeeded(latestRatingsFile) } + val payload = fetchPayload() + val (lastUpdated, lastPlayers) = parsePayload(payload) + val targetRatingsFilename = "${origin.name}-${ymd.format(lastUpdated)}.json" + if (latestRatingsFile != null && latestRatingsFile.name == targetRatingsFilename) { + return initIfNeeded(latestRatingsFile) + } + RatingsManager.logger.info("Updating $origin cache from $url") + RatingsManager.path.resolve(targetRatingsFilename).toFile().printWriter().use { out -> + out.println(lastPlayers.toString()) + } + players = lastPlayers + return true } fun fetchPlayers(): Json.Array { @@ -62,7 +86,7 @@ abstract class RatingsHandler(val origin: RatingsManager.Ratings) { } open fun defaultCharset() = StandardCharsets.UTF_8 fun updated() = updated - abstract fun parsePayload(payload: String): Json.Array + abstract fun parsePayload(payload: String): Pair val logger = LoggerFactory.getLogger(origin.name) val atom = "[-._`'a-zA-ZÀ-ÿ]" }