performance: server simplified replay pages to web crawlers

This commit is contained in:
Thibault Duplessis 2015-05-13 13:00:18 +02:00
parent 89bba41886
commit c7434ebafa
10 changed files with 226 additions and 17 deletions

View file

@ -52,15 +52,13 @@ object Analyse extends LilaController {
}
def replay(pov: Pov, userTv: Option[lila.user.User])(implicit ctx: Context) =
GameRepo initialFen pov.game.id flatMap { initialFen =>
if (HTTPRequest isBot ctx.req) replayBot(pov)
else GameRepo initialFen pov.game.id flatMap { initialFen =>
(env.analyser get pov.game.id) zip
(pov.game.tournamentId ?? lila.tournament.TournamentRepo.byId) zip
(pov.game.simulId ?? Env.simul.repo.find) zip
Env.game.crosstableApi(pov.game) flatMap {
case (((analysis, tour), simul), crosstable) =>
val division =
if (HTTPRequest.isBot(ctx.req)) divider.empty
else divider(pov.game, initialFen)
val pgn = Env.game.pgnDump(pov.game, initialFen)
Env.api.roundApi.watcher(pov, lila.api.Mobile.Api.currentVersion,
tv = none,
@ -71,6 +69,7 @@ object Analyse extends LilaController {
Ok(html.analyse.replay(
pov,
data,
initialFen,
Env.analyse.annotator(pgn, analysis, pov.game.opening, pov.game.winnerColor, pov.game.status, pov.game.clock).toString,
analysis,
analysis filter (_.done) map { a => AdvantageChart(a.infoAdvices, pov.game.pgnMoves) },
@ -79,8 +78,27 @@ object Analyse extends LilaController {
new TimeChart(pov.game, pov.game.pgnMoves),
crosstable,
userTv,
division))
divider(pov.game, initialFen)))
}
}
}
private def replayBot(pov: Pov)(implicit ctx: Context) =
GameRepo initialFen pov.game.id flatMap { initialFen =>
(env.analyser get pov.game.id) zip
(pov.game.tournamentId ?? lila.tournament.TournamentRepo.byId) zip
(pov.game.simulId ?? Env.simul.repo.find) zip
Env.game.crosstableApi(pov.game) map {
case (((analysis, tour), simul), crosstable) =>
val pgn = Env.game.pgnDump(pov.game, initialFen)
Ok(html.analyse.replayBot(
pov,
initialFen,
Env.analyse.annotator(pgn, analysis, pov.game.opening, pov.game.winnerColor, pov.game.status, pov.game.clock).toString,
analysis,
tour,
simul,
crosstable))
}
}
}

View file

@ -8,6 +8,7 @@ import play.twirl.api.Html
import lila.api.Context
import lila.app._
import lila.common.HTTPRequest
import lila.game.{ Pov, PlayerRef, GameRepo, Game => GameModel }
import lila.hub.actorApi.map.Tell
import lila.round.actorApi.round._
@ -141,7 +142,7 @@ object Round extends LilaController with TheftPrevention {
else if (pov.game.joinable) join(pov)
else ctx.userId.flatMap(pov.game.playerByUserId) ifTrue pov.game.playable match {
case Some(player) => renderPlayer(pov withColor player.color)
case None =>
case None if HTTPRequest.isHuman(ctx.req) =>
(pov.game.tournamentId ?? TournamentRepo.byId) zip
(pov.game.simulId ?? Env.simul.repo.find) zip
Env.game.crosstableApi(pov.game) zip
@ -149,6 +150,13 @@ object Round extends LilaController with TheftPrevention {
case (((tour, simul), crosstable), data) =>
Ok(html.round.watcher(pov, data, tour, simul, crosstable, userTv = userTv))
}
case _ => // web crawlers don't need the full thing
GameRepo.initialFen(pov.game.id) zip
Env.game.crosstableApi(pov.game) map {
case (initialFen, crosstable) =>
val pgn = Env.game.pgnDump(pov.game, initialFen)
Ok(html.round.watcherBot(pov, initialFen, pgn, crosstable))
}
},
api = apiVersion => Env.api.roundApi.watcher(pov, apiVersion, tv = none) map { Ok(_) }
)

View file

@ -19,17 +19,21 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
def mandatorySecondsToMove = lila.game.Env.current.MandatorySecondsToMove
def povOpenGraph(pov: Pov) = {
val speed = chess.Speed(pov.game.clock).name
val variant = pov.game.variant.exotic ?? s" ${pov.game.variant.name}"
Map(
'type -> "website",
'image -> cdnUrl(routes.Export.png(pov.game.id).url),
'title -> s"$speed$variant Chess • ${playerText(pov.game.whitePlayer)} vs ${playerText(pov.game.blackPlayer)}",
'title -> titlePov(pov),
'site_name -> "lichess.org",
'url -> s"$netBaseUrl${routes.Round.watcher(pov.game.id, pov.color.name).url}",
'description -> describePov(pov))
}
def titlePov(pov: Pov) = {
val speed = chess.Speed(pov.game.clock).name
val variant = pov.game.variant.exotic ?? s" ${pov.game.variant.name}"
s"$speed$variant Chess • ${playerText(pov.game.whitePlayer)} vs ${playerText(pov.game.blackPlayer)}"
}
def describePov(pov: Pov) = {
import pov._
val p1 = playerText(player, withRating = true)

View file

@ -1,4 +1,4 @@
@(title: String, side: Option[Html] = None, chat: Option[Html] = None, underchat: Option[Html] = None, moreCss: Html = Html(""), moreJs: Html = Html(""), openGraph: Map[Symbol, String] = Map.empty)(body: Html)(implicit ctx: Context)
@(title: String, side: Option[Html] = None, chat: Option[Html] = None, underchat: Option[Html] = None, moreCss: Html = Html(""), moreJs: Html = Html(""), openGraph: Map[Symbol, String] = Map.empty, chessground: Boolean)(body: Html)(implicit ctx: Context)
@base.layout(
title = title,
@ -8,4 +8,4 @@ underchat = underchat,
moreCss = moreCss,
moreJs = moreJs,
openGraph = openGraph,
chessground = false)(body)
chessground = chessground)(body)

View file

@ -1,6 +1,4 @@
@import lila.evaluation.PlayerAssessments
@(pov: Pov, data: play.api.libs.json.JsObject, pgn: String, analysis: Option[lila.analyse.Analysis], advantageChart: Option[String], tour: Option[lila.tournament.Tournament], simul: Option[lila.simul.Simul], timeChart: lila.analyse.TimeChart, cross: Option[lila.game.Crosstable], userTv: Option[User], division: chess.Division)(implicit ctx: Context)
@(pov: Pov, data: play.api.libs.json.JsObject, initialFen: Option[String], pgn: String, analysis: Option[lila.analyse.Analysis], advantageChart: Option[String], tour: Option[lila.tournament.Tournament], simul: Option[lila.simul.Simul], timeChart: lila.analyse.TimeChart, cross: Option[lila.game.Crosstable], userTv: Option[User], division: chess.Division)(implicit ctx: Context)
@import pov._
@ -47,12 +45,13 @@ userId: @Html(ctx.userId.fold("null")(id => s""""$id""""))
@analyse.layout(
title = title,
side = views.html.game.side(pov, (data\"game"\"initialFen").asOpt[String], tour, withTourStanding = false, simul = simul, userTv = userTv).some,
side = views.html.game.side(pov, initialFen, tour, withTourStanding = false, simul = simul, userTv = userTv).some,
chat = base.chatDom(trans.spectatorRoom.str(), ctx.isAuth).some,
underchat = underchat.some,
moreCss = moreCss,
moreJs = moreJs,
openGraph = povOpenGraph(pov)) {
openGraph = povOpenGraph(pov),
chessground = false) {
<div class="analyse cg-512">@miniBoardContent</div>
<div class="advice_summary" style="display:none">
@analysis.filter(_.done).map { a =>

View file

@ -0,0 +1,127 @@
@(pov: Pov, initialFen: Option[String], pgn: String, analysis: Option[lila.analyse.Analysis], tour: Option[lila.tournament.Tournament], simul: Option[lila.simul.Simul], cross: Option[lila.game.Crosstable])(implicit ctx: Context)
@import pov._
@title = @{ s"${playerText(pov.player)} vs ${playerText(pov.opponent)} in $gameId : ${game.opening.fold(trans.analysis.str())(_.fullName)}" }
@moreJs = {
@embedJs {
Chessground(document.querySelector('#lichess .analyse .lichess_board'), {
viewOnly: true,
fen: "@{chess.format.Forsyth.>>(pov.game.toChess)}",
orientation: "@pov.color.name"
});
}
}
@moreCss = {
@cssTag("analyse.css")
}
@underchat = {
@views.html.game.watchers()
<div class="shortcuts">
<p class="title text" data-icon="u">Keyboard Shortcuts</p>
<div class="inner">
<ul>
<li><strong>h</strong>/<strong>l</strong> or <strong></strong>/<strong></strong> move backward/forward</li>
<li><strong>j</strong>/<strong>k</strong> or <strong></strong>/<strong></strong> go to start/end</li>
<li><strong>c</strong> show/hide comments</li>
<li><strong>shift</strong> + <strong>h</strong>/<strong>l</strong> or <strong></strong>/<strong></strong> enter/exit variation</li>
</ul>
Press shift+click or right-click to draw circles and arrows on the board!<br />
You can also scroll over the board to move in the game.
</div>
</div>
}
@analyse.layout(
title = title,
side = views.html.game.side(pov, initialFen, tour, withTourStanding = false, simul = simul).some,
chat = base.chatDom(trans.spectatorRoom.str(), ctx.isAuth).some,
underchat = underchat.some,
moreJs = moreJs,
moreCss = moreCss,
openGraph = povOpenGraph(pov),
chessground = true) {
<div class="analyse cg-512">
<div class="top">
<div class="lichess_game">
<div class="lichess_board_wrap">
<div class="lichess_board">
<div class="cg-board-wrap">
<div class="cg-board"></div>
</div>
</div>
</div>
<div class="lichess_ground for_bot">
<h1>@titlePov(pov)</h1>
<p>@describePov(pov)</p>
<p>@pov.game.opening.map(_.fullName)</p>
<div class="pgn">@Html(nl2br(escape(pgn.toString)))</div>
</div>
</div>
</div>
</div>
<div class="advice_summary" style="display:none">
@analysis.filter(_.done).map { a =>
<table>
@for((color, pairs) <- a.summary) {
<thead>
<tr>
<td>
<span class="is color-icon @color"></span>
</td>
<th>@playerLink(pov.game.player(color), withOnline = false)</th>
</tr>
</thead>
<tbody>
@for((nag, nb) <- pairs) {
<tr>
<td><strong>@nb</strong></td>
<th>@nagName(nag)</th>
</tr>
}
<tr>
<td><strong>@lila.analyse.Accuracy(pov.withColor(color), a)</strong></td>
<th>Average centipawn loss</th>
</tr>
<tr><td class="spacerlol" colspan=2></td></tr>
</tbody>
}
</table>
}
</div>
<div class="underboard_content" style="display:none">
<div class="analysis_panels">
<div class="panel fen_pgn">
<p><strong>FEN</strong><input type="input" readonly="true" spellcheck="false" class="copyable fen" /></p>
<p><strong>PGN</strong>
<a data-icon="x" rel="nofollow" href="@routes.Export.pgn(game.id)"> Download annotated</a>
@if(analysis.isDefined) {
/
<a data-icon="x" rel="nofollow" href="@routes.Export.pgn(game.id)?as=raw"> Download raw</a>
}
@if(game.isPgnImport) {
/
<a data-icon="x" rel="nofollow" href="@routes.Export.pgn(game.id)?as=imported"> Download imported</a>
}
/
<a data-icon="x" target="_blank" rel="nofollow" href="@cdnUrl(routes.Export.pdf(game.id).url)"> Print-friendly PDF</a>
</p>
<div class="pgn">@Html(nl2br(escape(pgn)))</div>
</div>
@cross.map { c =>
<div class="panel crosstable">
@views.html.game.crosstable(pov.player.userId.fold(c)(c.fromPov), pov.gameId.some)
</div>
}
</div>
<div class="analysis_menu">
@if(cross.isDefined) {
<a data-panel="crosstable" class="crosstable">Crosstable</a>
}
<a data-panel="fen_pgn" class="fen_pgn">FEN &amp; PGN</a>
</div>
</div>
}

View file

@ -0,0 +1,45 @@
@(pov: Pov, initialFen: Option[String], pgn: chess.format.pgn.Pgn, cross: Option[lila.game.Crosstable])(implicit ctx: Context)
@title = @{ s"${playerText(pov.player)} vs ${playerText(pov.opponent)} in ${pov.gameId}" }
@moreJs = {
@embedJs {
Chessground(document.querySelector('#lichess .round .lichess_board'), {
viewOnly: true,
fen: "@{chess.format.Forsyth.>>(pov.game.toChess)}",
orientation: "@pov.color.name"
});
}
}
@round.layout(
title = title,
side = views.html.game.side(pov, initialFen, none, withTourStanding = false, simul = none, userTv = none),
chat = base.chatDom(trans.spectatorRoom.str()).some,
underchat = views.html.game.watchers().some,
moreJs = moreJs,
openGraph = povOpenGraph(pov)) {
<div class="round cg-512">
<div class="top">
<div class="lichess_game">
<div class="lichess_board_wrap">
<div class="lichess_board">
<div class="cg-board-wrap">
<div class="cg-board"></div>
</div>
</div>
</div>
<div class="lichess_ground for_bot">
<h1>@titlePov(pov)</h1>
<p>@describePov(pov)</p>
<div class="pgn">@Html(nl2br(escape(pgn.toString)))</div>
</div>
</div>
</div>
</div>
<div class="crosstable" style="display:none">
@cross.map { c =>
@views.html.game.crosstable(c, pov.gameId.some)
}
</div>
}

@ -1 +1 @@
Subproject commit c472c3449d8fe9b7f288b6c52ea8808703975a87
Subproject commit f2af8bcb5dc2105ed383932a128d948f53e0fdc0

View file

@ -35,6 +35,8 @@ object HTTPRequest {
isBotPattern.matcher(ua).matches
}
def isHuman(req: RequestHeader) = !isBot(req)
def isFacebookBot(req: RequestHeader) = userAgent(req) ?? (_ contains "facebookexternalhit")
private val fileExtensionPattern = """.+\.[a-z0-9]{2,4}$""".r.pattern

View file

@ -214,6 +214,12 @@ div.lichess_game div.lichess_ground {
height: 512px;
width: 242px;
}
div.lichess_game div.lichess_ground.for_bot h1 {
font-size: 2em;
}
div.lichess_game div.lichess_ground.for_bot p {
margin: 1em 0;
}
div.lichess_board {
position: relative;
}