2018-09-01 03:48:30 -06:00
|
|
|
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
|
2018-09-01 03:48:30 -06:00
|
|
|
import scala.concurrent.duration._
|
|
|
|
|
|
|
|
import lila.study.MultiPgn
|
2019-12-24 13:01:35 -07:00
|
|
|
import lila.memo.CacheApi
|
|
|
|
import lila.memo.CacheApi._
|
2018-09-01 03:48:30 -06:00
|
|
|
|
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
|
|
|
) {
|
2018-09-01 03:48:30 -06:00
|
|
|
|
|
|
|
import RelayFormat._
|
2021-04-23 13:22:10 -06:00
|
|
|
import RelayRound.Sync.UpstreamUrl
|
2018-09-01 03:48:30 -06:00
|
|
|
|
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
|
2018-09-01 03:48:30 -06:00
|
|
|
|
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] = {
|
2019-11-14 16:31:01 -07:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
2018-09-01 03:48:30 -06:00
|
|
|
|
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
|
2019-12-13 18:17:43 -07:00
|
|
|
)(looksLikePgn) dmap2 { (u: Url) =>
|
2018-09-01 03:48:30 -06:00
|
|
|
SingleFile(pgnDoc(u))
|
|
|
|
}
|
|
|
|
|
2019-11-09 05:43:22 -07:00
|
|
|
def guessManyFiles(url: Url): Fu[Option[RelayFormat]] =
|
2018-09-01 03:48:30 -06:00
|
|
|
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
|
2019-12-13 18:17:43 -07:00
|
|
|
looksLikePgn(pgnUrl(1).url).map(_ option pgnUrl) dmap2 {
|
2020-09-21 01:28:28 -06:00
|
|
|
ManyFiles(index, _)
|
|
|
|
}
|
2018-09-01 03:48:30 -06:00
|
|
|
}
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
2018-09-01 03:48:30 -06: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 =>
|
2019-11-14 16:31:01 -07:00
|
|
|
logger.info(s"guessed format of $upstream: $format")
|
2018-09-06 11:48:25 -06:00
|
|
|
}
|
2018-09-01 03:48:30 -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 }
|
2018-09-01 03:48:30 -06:00
|
|
|
}
|
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
sealed private trait RelayFormat
|
2018-09-01 03:48:30 -06:00
|
|
|
|
|
|
|
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
|
2018-09-01 03:48:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2018-09-01 03:48:30 -06:00
|
|
|
|
|
|
|
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)})"
|
|
|
|
}
|
2018-09-01 03:48:30 -06:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2018-09-01 03:48:30 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
val mostCommonSingleFileName = "games.pgn"
|
2019-12-13 07:30:20 -07:00
|
|
|
val mostCommonIndexNames = List("round.json", "index.json")
|
2018-09-01 03:48:30 -06:00
|
|
|
}
|