lila/modules/explorer/src/main/ExplorerIndexer.scala

160 lines
5.7 KiB
Scala
Raw Normal View History

2016-02-08 08:42:33 -07:00
package lila.explorer
2019-12-08 20:20:36 -07:00
import akka.stream.scaladsl._
import chess.format.pgn.Tag
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import scala.util.Random.nextFloat
import scala.util.{ Try, Success, Failure }
2016-02-08 08:42:33 -07:00
2019-12-03 14:30:53 -07:00
import lila.common.LilaStream
2016-04-01 11:50:57 -06:00
import lila.db.dsl._
2016-02-08 08:42:33 -07:00
import lila.game.{ Game, GameRepo, Query, PgnDump, Player }
import lila.user.{ User, UserRepo }
2016-02-08 08:42:33 -07:00
private final class ExplorerIndexer(
2019-12-03 14:30:53 -07:00
gameRepo: GameRepo,
userRepo: UserRepo,
getBotUserIds: lila.user.GetBotIds,
ws: play.api.libs.ws.WSClient,
internalEndpoint: InternalEndpoint
)(implicit mat: akka.stream.Materializer) {
2016-02-08 08:42:33 -07:00
private val separator = "\n\n\n"
2016-02-09 03:07:35 -07:00
private val datePattern = "yyyy-MM-dd"
2016-02-11 07:56:53 -07:00
private val dateFormatter = DateTimeFormat forPattern datePattern
private val pgnDateFormat = DateTimeFormat forPattern "yyyy.MM.dd";
private val internalEndPointUrl = s"$internalEndpoint/import/lichess"
2016-02-08 08:42:33 -07:00
2016-02-09 03:27:12 -07:00
private def parseDate(str: String): Option[DateTime] =
Try(dateFormatter parseDateTime str).toOption
2016-02-09 03:07:35 -07:00
def apply(sinceStr: String): Funit = getBotUserIds() flatMap { botUserIds =>
parseDate(sinceStr).fold(fufail[Unit](s"Invalid date $sinceStr")) { since =>
logger.info(s"Start indexing since $since")
2016-04-01 11:50:57 -06:00
val query =
2016-02-11 07:56:53 -07:00
Query.createdSince(since) ++
2016-02-09 03:07:35 -07:00
Query.rated ++
2016-02-08 08:42:33 -07:00
Query.finished ++
2018-01-25 08:10:01 -07:00
Query.turnsGt(8) ++
2016-02-09 20:41:21 -07:00
Query.noProvisional ++
2016-02-23 20:36:37 -07:00
Query.bothRatingsGreaterThan(1501)
2019-12-03 14:30:53 -07:00
gameRepo
.sortedCursor(query, Query.sortChronological)
.documentSource()
.via(LilaStream.logRate[Game]("fetch")(logger))
.mapAsyncUnordered(8) { makeFastPgn(_, botUserIds) }
.mapConcat(_.toList)
.via(LilaStream.logRate("index")(logger))
.grouped(50)
.map(_ mkString separator)
.mapAsyncUnordered(2) { pgn =>
ws.url(internalEndPointUrl).put(pgn).flatMap {
case res if res.status == 200 => funit
case res => fufail(s"Stop import because of status ${res.status}")
}
}
2019-12-08 20:20:36 -07:00
.toMat(Sink.ignore)(Keep.right)
.run
.void
2016-02-09 03:07:35 -07:00
}
}
def apply(game: Game): Funit = getBotUserIds() flatMap { botUserIds =>
makeFastPgn(game, botUserIds) map {
_ foreach flowBuffer.apply
}
}
private object flowBuffer {
2016-02-22 22:24:32 -07:00
private val max = 30
private val buf = scala.collection.mutable.ArrayBuffer.empty[String]
2017-10-21 14:06:14 -06:00
def apply(pgn: String): Unit = {
buf += pgn
2016-02-18 22:39:25 -07:00
val startAt = nowMillis
if (buf.size >= max) {
2019-12-03 14:30:53 -07:00
ws.url(internalEndPointUrl).put(buf mkString separator) andThen {
case Success(res) if res.status == 200 =>
2019-12-10 14:01:18 -07:00
lila.mon.explorer.index.time.record((nowMillis - startAt) / max)
lila.mon.explorer.index.count(true).increment(max)
2016-03-10 11:21:04 -07:00
case Success(res) =>
logger.warn(s"[${res.status}]")
2019-12-10 14:01:18 -07:00
lila.mon.explorer.index.count(false).increment(max)
2016-03-10 11:21:04 -07:00
case Failure(err) =>
logger.warn(s"$err", err)
2019-12-10 14:01:18 -07:00
lila.mon.explorer.index.count(false).increment(max)
}
buf.clear
}
}
}
private def valid(game: Game) =
game.finished &&
game.rated &&
game.turns >= 10 &&
game.variant != chess.variant.FromPosition &&
2016-09-04 11:07:21 -06:00
!Game.isOldHorde(game)
private def stableRating(player: Player) = player.rating ifFalse player.provisional
// probability of the game being indexed, between 0 and 1
private def probability(game: Game, rating: Int) = {
import lila.rating.PerfType._
game.perfType ?? {
case Correspondence => 1
2017-11-29 10:58:08 -07:00
case Rapid | Classical if rating >= 2000 => 1
case Rapid | Classical if rating >= 1800 => 2 / 5f
case Rapid | Classical => 1 / 8f
case Blitz if rating >= 2000 => 1
case Blitz if rating >= 1800 => 1 / 4f
2017-05-02 13:46:16 -06:00
case Blitz => 1 / 15f
case Bullet if rating >= 2300 => 1
case Bullet if rating >= 2200 => 4 / 5f
case Bullet if rating >= 2000 => 1 / 4f
case Bullet if rating >= 1800 => 1 / 7f
2017-05-02 13:46:16 -06:00
case Bullet => 1 / 20f
case _ if rating >= 1600 => 1 // variant games
case _ => 1 / 2f // noob variant games
}
}
private def makeFastPgn(game: Game, botUserIds: Set[User.ID]): Fu[Option[String]] = ~(for {
whiteRating <- stableRating(game.whitePlayer)
blackRating <- stableRating(game.blackPlayer)
minPlayerRating = if (game.variant.exotic) 1400 else 1500
minAverageRating = if (game.variant.exotic) 1520 else 1600
if whiteRating >= minPlayerRating
if blackRating >= minPlayerRating
averageRating = (whiteRating + blackRating) / 2
if averageRating >= minAverageRating
if probability(game, averageRating) > nextFloat
if !game.userIds.exists(botUserIds.contains)
if valid(game)
2019-12-03 14:30:53 -07:00
} yield gameRepo initialFen game flatMap { initialFen =>
userRepo.usernamesByIds(game.userIds) map { usernames =>
2016-02-11 07:56:53 -07:00
def username(color: chess.Color) = game.player(color).userId flatMap { id =>
usernames.find(_.toLowerCase == id)
} orElse game.player(color).userId getOrElse "?"
val fenTags = initialFen.?? { fen => List(s"[FEN $fen]") }
2017-11-03 12:37:33 -06:00
val timeControl = Tag.timeControl(game.clock.map(_.config)).value
2016-02-11 07:56:53 -07:00
val otherTags = List(
s"[LichessID ${game.id}]",
s"[Variant ${game.variant.name}]",
s"[TimeControl $timeControl]",
2016-02-11 07:56:53 -07:00
s"[White ${username(chess.White)}]",
s"[Black ${username(chess.Black)}]",
s"[WhiteElo $whiteRating]",
s"[BlackElo $blackRating]",
s"[Result ${PgnDump.result(game)}]",
s"[Date ${pgnDateFormat.print(game.createdAt)}]"
)
2016-02-11 07:56:53 -07:00
val allTags = fenTags ::: otherTags
s"${allTags.mkString("\n")}\n\n${game.pgnMoves.take(maxPlies).mkString(" ")}".some
2016-02-11 07:56:53 -07:00
}
})
2016-03-20 03:31:09 -06:00
private val logger = lila.log("explorer")
2016-02-08 08:42:33 -07:00
}