2018-09-01 03:48:30 -06:00
|
|
|
package lila.relay
|
|
|
|
|
|
|
|
import io.lemonlabs.uri._
|
|
|
|
import play.api.libs.json._
|
2019-12-03 21:58:09 -07:00
|
|
|
import play.api.libs.ws.WSClient
|
2018-09-01 03:48:30 -06:00
|
|
|
import scala.concurrent.duration._
|
|
|
|
|
2019-12-21 10:01:43 -07:00
|
|
|
import com.github.blemale.scaffeine.{ LoadingCache, Scaffeine }
|
2018-09-01 03:48:30 -06:00
|
|
|
import lila.study.MultiPgn
|
|
|
|
|
2019-12-21 10:01:43 -07:00
|
|
|
final private class RelayFormatApi(ws: WSClient)(implicit ec: scala.concurrent.ExecutionContext) {
|
2018-09-01 03:48:30 -06:00
|
|
|
|
|
|
|
import RelayFormat._
|
2019-11-14 16:31:01 -07:00
|
|
|
import Relay.Sync.UpstreamWithRound
|
2018-09-01 03:48:30 -06:00
|
|
|
|
2019-12-21 10:01:43 -07:00
|
|
|
private val cache: LoadingCache[UpstreamWithRound, Fu[RelayFormat]] = Scaffeine()
|
|
|
|
.expireAfterWrite(10 minutes)
|
|
|
|
.build { (upstream: UpstreamWithRound) =>
|
|
|
|
guessFormat(upstream) addFailureEffect { _ =>
|
|
|
|
cache.invalidate(upstream)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-14 16:31:01 -07:00
|
|
|
def get(upstream: UpstreamWithRound): Fu[RelayFormat] = cache get upstream
|
2018-09-01 03:48:30 -06:00
|
|
|
|
2019-12-21 10:01:43 -07:00
|
|
|
def refresh(upstream: UpstreamWithRound): Unit = cache invalidate upstream
|
2018-09-06 10:32:35 -06:00
|
|
|
|
2019-11-14 16:31:01 -07:00
|
|
|
private def guessFormat(upstream: UpstreamWithRound): 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
|
|
|
|
def guessLcc(url: Url): Fu[Option[RelayFormat]] = url.toString match {
|
2019-12-13 07:30:20 -07:00
|
|
|
case Relay.Sync.LccRegex(id) =>
|
|
|
|
guessManyFiles(
|
|
|
|
Url.parse(
|
|
|
|
s"http://1.pool.livechesscloud.com/get/$id/round-${upstream.round | 1}/index.json"
|
|
|
|
)
|
|
|
|
)
|
2019-11-09 05:43:22 -07: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 {
|
|
|
|
ManyFiles(index, _)
|
2018-09-06 10:32:35 -06:00
|
|
|
}
|
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-21 19:29:13 -07:00
|
|
|
guessManyFiles(originalUrl) orFail "Cannot find any game there"
|
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-10 03:54:04 -06:00
|
|
|
} addFailureEffect { err =>
|
2019-11-14 16:31:01 -07:00
|
|
|
logger.info(s"can't guess format of $upstream: $err")
|
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
|
|
|
}
|
|
|
|
|
|
|
|
private def looksLikePgn(body: String): Boolean = MultiPgn.split(body, 1).value.headOption ?? { pgn =>
|
|
|
|
lila.study.PgnImport(pgn, Nil).isSuccess
|
|
|
|
}
|
|
|
|
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
|
|
|
}
|