lila/modules/relay/src/main/RelayFormat.scala

136 lines
4.2 KiB
Scala
Raw Permalink Normal View History

package lila.relay
import io.lemonlabs.uri._
import play.api.libs.json._
2020-08-07 08:22:26 -06:00
import play.api.libs.ws.StandaloneWSClient
import scala.concurrent.duration._
import lila.study.MultiPgn
2019-12-24 13:01:35 -07:00
import lila.memo.CacheApi
import lila.memo.CacheApi._
2020-08-07 08:22:26 -06:00
final private class RelayFormatApi(ws: StandaloneWSClient, cacheApi: CacheApi)(implicit
2020-05-05 22:11:15 -06:00
ec: scala.concurrent.ExecutionContext
2019-12-24 13:01:35 -07:00
) {
import RelayFormat._
2021-04-23 13:22:10 -06:00
import RelayRound.Sync.UpstreamUrl
2021-02-20 16:21:06 -07:00
private val cache = cacheApi[UpstreamUrl.WithRound, RelayFormat](8, "relay.format") {
2019-12-24 13:01:35 -07:00
_.refreshAfterWrite(10 minutes)
.expireAfterAccess(20 minutes)
.buildAsyncFuture(guessFormat)
}
2019-12-21 10:01:43 -07:00
2021-02-20 16:21:06 -07:00
def get(upstream: UpstreamUrl.WithRound): Fu[RelayFormat] = cache get upstream
2021-02-20 16:21:06 -07:00
def refresh(upstream: UpstreamUrl.WithRound): Unit = cache invalidate upstream
2018-09-06 10:32:35 -06:00
2021-02-20 16:21:06 -07:00
private def guessFormat(upstream: UpstreamUrl.WithRound): Fu[RelayFormat] = {
val originalUrl = Url parse upstream.url
2019-11-09 05:43:22 -07:00
// http://view.livechesscloud.com/ed5fb586-f549-4029-a470-d590f8e30c76
2020-05-05 22:11:15 -06:00
def guessLcc(url: Url): Fu[Option[RelayFormat]] =
url.toString match {
2021-02-20 16:21:06 -07:00
case UpstreamUrl.LccRegex(id) =>
2020-05-05 22:11:15 -06:00
guessManyFiles(
Url.parse(
s"http://1.pool.livechesscloud.com/get/$id/round-${upstream.round | 1}/index.json"
)
2019-12-13 07:30:20 -07:00
)
2020-05-05 22:11:15 -06:00
case _ => fuccess(none)
}
2019-11-09 05:43:22 -07:00
def guessSingleFile(url: Url): Fu[Option[RelayFormat]] =
2019-12-13 07:30:20 -07:00
lila.common.Future.find(
List(
url.some,
!url.path.parts.contains(mostCommonSingleFileName) option addPart(url, mostCommonSingleFileName)
).flatten.distinct
)(looksLikePgn) dmap2 { (u: Url) =>
SingleFile(pgnDoc(u))
}
2019-11-09 05:43:22 -07:00
def guessManyFiles(url: Url): Fu[Option[RelayFormat]] =
lila.common.Future.find(
List(url) ::: mostCommonIndexNames.filterNot(url.path.parts.contains).map(addPart(url, _))
2018-09-06 10:32:35 -06:00
)(looksLikeJson) flatMap {
2019-12-13 07:30:20 -07:00
_ ?? { index =>
val jsonUrl = (n: Int) => jsonDoc(replaceLastPart(index, s"game-$n.json"))
val pgnUrl = (n: Int) => pgnDoc(replaceLastPart(index, s"game-$n.pgn"))
looksLikeJson(jsonUrl(1).url).map(_ option jsonUrl) orElse
looksLikePgn(pgnUrl(1).url).map(_ option pgnUrl) dmap2 {
2020-09-21 01:28:28 -06:00
ManyFiles(index, _)
}
}
2019-12-13 07:30:20 -07:00
}
2019-11-09 05:43:22 -07:00
guessLcc(originalUrl) orElse
guessSingleFile(originalUrl) orElse
2019-12-31 06:47:35 -07:00
guessManyFiles(originalUrl) orFail "No games found, check your source URL"
2018-09-06 15:28:52 -06:00
} addEffect { format =>
logger.info(s"guessed format of $upstream: $format")
2018-09-06 11:48:25 -06:00
}
2019-11-09 05:27:29 -07:00
private def httpGet(url: Url): Fu[Option[String]] =
2019-12-03 21:58:09 -07:00
ws.url(url.toString)
2019-12-13 07:30:20 -07:00
.withRequestTimeout(4.seconds)
.get()
.map {
2019-11-09 05:27:29 -07:00
case res if res.status == 200 => res.body.some
2019-12-13 07:30:20 -07:00
case _ => none
2019-11-09 05:27:29 -07:00
}
2020-05-05 22:11:15 -06:00
private def looksLikePgn(body: String): Boolean =
MultiPgn.split(body, 1).value.headOption ?? { pgn =>
2020-08-12 00:53:51 -06:00
lila.study.PgnImport(pgn, Nil).isValid
2020-05-05 22:11:15 -06:00
}
2019-11-09 05:27:29 -07:00
private def looksLikePgn(url: Url): Fu[Boolean] = httpGet(url).map { _ exists looksLikePgn }
2019-12-13 07:30:20 -07:00
private def looksLikeJson(body: String): Boolean =
try {
Json.parse(body) != JsNull
} catch {
case _: Exception => false
}
2019-11-09 05:27:29 -07:00
private def looksLikeJson(url: Url): Fu[Boolean] = httpGet(url).map { _ exists looksLikeJson }
}
2019-12-13 07:30:20 -07:00
sealed private trait RelayFormat
private object RelayFormat {
sealed trait DocFormat
object DocFormat {
case object Json extends DocFormat
2019-12-13 07:30:20 -07:00
case object Pgn extends DocFormat
}
case class Doc(url: Url, format: DocFormat)
def jsonDoc(url: Url) = Doc(url, DocFormat.Json)
2019-12-13 07:30:20 -07:00
def pgnDoc(url: Url) = Doc(url, DocFormat.Pgn)
case class SingleFile(doc: Doc) extends RelayFormat
2018-09-06 10:32:35 -06:00
type GameNumberToDoc = Int => Doc
case class ManyFiles(jsonIndex: Url, game: GameNumberToDoc) extends RelayFormat {
override def toString = s"Manyfiles($jsonIndex, ${game(0)})"
}
def addPart(url: Url, part: String) = url.withPath(url.path addPart part)
def replaceLastPart(url: Url, withPart: String) =
if (url.path.isEmpty) addPart(url, withPart)
2019-12-13 07:30:20 -07:00
else
url.withPath {
url.path.withParts {
url.path.parts.init :+ withPart
}
}
val mostCommonSingleFileName = "games.pgn"
2019-12-13 07:30:20 -07:00
val mostCommonIndexNames = List("round.json", "index.json")
}