136 lines
4.2 KiB
Scala
136 lines
4.2 KiB
Scala
package lila.relay
|
|
|
|
import io.lemonlabs.uri._
|
|
import play.api.libs.json._
|
|
import play.api.libs.ws.StandaloneWSClient
|
|
import scala.concurrent.duration._
|
|
|
|
import lila.study.MultiPgn
|
|
import lila.memo.CacheApi
|
|
import lila.memo.CacheApi._
|
|
|
|
final private class RelayFormatApi(ws: StandaloneWSClient, cacheApi: CacheApi)(implicit
|
|
ec: scala.concurrent.ExecutionContext
|
|
) {
|
|
|
|
import RelayFormat._
|
|
import RelayRound.Sync.UpstreamUrl
|
|
|
|
private val cache = cacheApi[UpstreamUrl.WithRound, RelayFormat](8, "relay.format") {
|
|
_.refreshAfterWrite(10 minutes)
|
|
.expireAfterAccess(20 minutes)
|
|
.buildAsyncFuture(guessFormat)
|
|
}
|
|
|
|
def get(upstream: UpstreamUrl.WithRound): Fu[RelayFormat] = cache get upstream
|
|
|
|
def refresh(upstream: UpstreamUrl.WithRound): Unit = cache invalidate upstream
|
|
|
|
private def guessFormat(upstream: UpstreamUrl.WithRound): Fu[RelayFormat] = {
|
|
|
|
val originalUrl = Url parse upstream.url
|
|
|
|
// http://view.livechesscloud.com/ed5fb586-f549-4029-a470-d590f8e30c76
|
|
def guessLcc(url: Url): Fu[Option[RelayFormat]] =
|
|
url.toString match {
|
|
case UpstreamUrl.LccRegex(id) =>
|
|
guessManyFiles(
|
|
Url.parse(
|
|
s"http://1.pool.livechesscloud.com/get/$id/round-${upstream.round | 1}/index.json"
|
|
)
|
|
)
|
|
case _ => fuccess(none)
|
|
}
|
|
|
|
def guessSingleFile(url: Url): Fu[Option[RelayFormat]] =
|
|
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))
|
|
}
|
|
|
|
def guessManyFiles(url: Url): Fu[Option[RelayFormat]] =
|
|
lila.common.Future.find(
|
|
List(url) ::: mostCommonIndexNames.filterNot(url.path.parts.contains).map(addPart(url, _))
|
|
)(looksLikeJson) flatMap {
|
|
_ ?? { 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 {
|
|
ManyFiles(index, _)
|
|
}
|
|
}
|
|
}
|
|
|
|
guessLcc(originalUrl) orElse
|
|
guessSingleFile(originalUrl) orElse
|
|
guessManyFiles(originalUrl) orFail "No games found, check your source URL"
|
|
} addEffect { format =>
|
|
logger.info(s"guessed format of $upstream: $format")
|
|
}
|
|
|
|
private def httpGet(url: Url): Fu[Option[String]] =
|
|
ws.url(url.toString)
|
|
.withRequestTimeout(4.seconds)
|
|
.get()
|
|
.map {
|
|
case res if res.status == 200 => res.body.some
|
|
case _ => none
|
|
}
|
|
|
|
private def looksLikePgn(body: String): Boolean =
|
|
MultiPgn.split(body, 1).value.headOption ?? { pgn =>
|
|
lila.study.PgnImport(pgn, Nil).isValid
|
|
}
|
|
private def looksLikePgn(url: Url): Fu[Boolean] = httpGet(url).map { _ exists looksLikePgn }
|
|
|
|
private def looksLikeJson(body: String): Boolean =
|
|
try {
|
|
Json.parse(body) != JsNull
|
|
} catch {
|
|
case _: Exception => false
|
|
}
|
|
private def looksLikeJson(url: Url): Fu[Boolean] = httpGet(url).map { _ exists looksLikeJson }
|
|
}
|
|
|
|
sealed private trait RelayFormat
|
|
|
|
private object RelayFormat {
|
|
|
|
sealed trait DocFormat
|
|
object DocFormat {
|
|
case object Json extends DocFormat
|
|
case object Pgn extends DocFormat
|
|
}
|
|
|
|
case class Doc(url: Url, format: DocFormat)
|
|
|
|
def jsonDoc(url: Url) = Doc(url, DocFormat.Json)
|
|
def pgnDoc(url: Url) = Doc(url, DocFormat.Pgn)
|
|
|
|
case class SingleFile(doc: Doc) extends RelayFormat
|
|
|
|
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)
|
|
else
|
|
url.withPath {
|
|
url.path.withParts {
|
|
url.path.parts.init :+ withPart
|
|
}
|
|
}
|
|
|
|
val mostCommonSingleFileName = "games.pgn"
|
|
val mostCommonIndexNames = List("round.json", "index.json")
|
|
}
|