2018-11-21 21:53:28 -07:00
|
|
|
package lila.study
|
|
|
|
|
2021-01-19 11:38:01 -07:00
|
|
|
import BSONHandlers._
|
|
|
|
import chess.Color
|
|
|
|
import chess.format.pgn.Tags
|
|
|
|
import chess.format.{ FEN, Uci }
|
2019-12-24 13:01:35 -07:00
|
|
|
import com.github.blemale.scaffeine.AsyncLoadingCache
|
2021-01-19 11:38:01 -07:00
|
|
|
import JsonView._
|
2018-11-21 21:53:28 -07:00
|
|
|
import play.api.libs.json._
|
2019-11-29 19:16:11 -07:00
|
|
|
import reactivemongo.api.bson._
|
2019-09-20 08:47:23 -06:00
|
|
|
import scala.concurrent.duration._
|
2018-11-21 21:53:28 -07:00
|
|
|
|
2019-12-03 17:55:45 -07:00
|
|
|
import lila.common.config.MaxPerPage
|
2021-01-19 11:38:01 -07:00
|
|
|
import lila.common.paginator.AdapterLike
|
2018-11-22 03:06:27 -07:00
|
|
|
import lila.common.paginator.{ Paginator, PaginatorJson }
|
2018-11-27 20:03:03 -07:00
|
|
|
import lila.db.dsl._
|
2018-11-21 21:53:28 -07:00
|
|
|
|
|
|
|
final class StudyMultiBoard(
|
2019-12-27 12:49:59 -07:00
|
|
|
chapterRepo: ChapterRepo,
|
|
|
|
cacheApi: lila.memo.CacheApi
|
2019-12-13 18:17:43 -07:00
|
|
|
)(implicit ec: scala.concurrent.ExecutionContext) {
|
2021-01-19 11:38:01 -07:00
|
|
|
|
2019-12-03 17:55:45 -07:00
|
|
|
private val maxPerPage = MaxPerPage(9)
|
2018-11-21 21:53:28 -07:00
|
|
|
|
|
|
|
import StudyMultiBoard._
|
2019-12-03 17:55:45 -07:00
|
|
|
import handlers._
|
2018-11-21 21:53:28 -07:00
|
|
|
|
2019-09-20 08:47:23 -06:00
|
|
|
def json(studyId: Study.Id, page: Int, playing: Boolean): Fu[JsObject] = {
|
|
|
|
if (page == 1 && !playing) firstPageCache.get(studyId)
|
|
|
|
else fetch(studyId, page, playing)
|
|
|
|
} map { PaginatorJson(_) }
|
2018-11-21 21:53:28 -07:00
|
|
|
|
2021-04-27 02:48:37 -06:00
|
|
|
def invalidate(studyId: Study.Id): Unit = firstPageCache.synchronous().invalidate(studyId)
|
|
|
|
|
2019-12-24 13:01:35 -07:00
|
|
|
private val firstPageCache: AsyncLoadingCache[Study.Id, Paginator[ChapterPreview]] =
|
2019-12-27 12:49:59 -07:00
|
|
|
cacheApi.scaffeine
|
2019-12-24 13:01:35 -07:00
|
|
|
.refreshAfterWrite(4 seconds)
|
|
|
|
.expireAfterAccess(10 minutes)
|
2020-08-16 06:42:29 -06:00
|
|
|
.buildAsyncFuture[Study.Id, Paginator[ChapterPreview]] { fetch(_, 1, playing = false) }
|
2018-11-26 00:05:05 -07:00
|
|
|
|
2021-05-15 01:30:52 -06:00
|
|
|
private val playingSelector = $doc("tags" -> "Result:*", "relay.path" $ne "")
|
2021-01-19 11:38:01 -07:00
|
|
|
|
2021-02-05 01:58:27 -07:00
|
|
|
private def fetch(studyId: Study.Id, page: Int, playing: Boolean): Fu[Paginator[ChapterPreview]] =
|
2021-01-19 11:38:01 -07:00
|
|
|
Paginator[ChapterPreview](
|
|
|
|
new ChapterPreviewAdapter(studyId, playing),
|
2018-11-27 20:03:03 -07:00
|
|
|
currentPage = page,
|
|
|
|
maxPerPage = maxPerPage
|
|
|
|
)
|
|
|
|
|
2021-01-19 11:38:01 -07:00
|
|
|
final private class ChapterPreviewAdapter(studyId: Study.Id, playing: Boolean)
|
|
|
|
extends AdapterLike[ChapterPreview] {
|
2018-11-26 20:35:12 -07:00
|
|
|
|
2021-01-19 11:38:01 -07:00
|
|
|
private val selector = $doc("studyId" -> studyId) ++ playing.??(playingSelector)
|
2019-12-03 17:55:45 -07:00
|
|
|
|
2021-02-04 13:02:50 -07:00
|
|
|
def nbResults: Fu[Int] = chapterRepo.coll(_.countSel(selector))
|
2021-01-19 11:38:01 -07:00
|
|
|
|
|
|
|
def slice(offset: Int, length: Int): Fu[Seq[ChapterPreview]] =
|
2021-02-02 13:27:42 -07:00
|
|
|
chapterRepo
|
|
|
|
.coll {
|
2021-02-04 13:02:50 -07:00
|
|
|
_.aggregateList(length, readPreference = readPref) { framework =>
|
2021-02-02 13:27:42 -07:00
|
|
|
import framework._
|
|
|
|
Match(selector) -> List(
|
|
|
|
Sort(Ascending("order")),
|
|
|
|
Skip(offset),
|
|
|
|
Limit(length),
|
|
|
|
Project(
|
|
|
|
$doc(
|
|
|
|
"comp" -> $doc(
|
2021-02-05 01:58:27 -07:00
|
|
|
"$function" -> $doc(
|
|
|
|
"lang" -> "js",
|
|
|
|
"args" -> $arr("$root", "$tags"),
|
|
|
|
"body" -> """function(root, tags) {
|
|
|
|
|tags = tags.filter(t => t.startsWith('White') || t.startsWith('Black') || t.startsWith('Result'));
|
2021-08-21 03:07:35 -06:00
|
|
|
|const node = tags.length ? Object.keys(root).reduce(([path, node], i) => (root[i].p > node.p && i.startsWith(path)) ? [i, root[i]] : [path, node], ['', root['_']])[1] : root['_'];
|
2021-02-05 01:58:27 -07:00
|
|
|
|return {node:{fen:node.f,uci:node.u},tags} }""".stripMargin
|
|
|
|
)
|
2021-02-02 10:50:35 -07:00
|
|
|
),
|
2021-02-02 13:27:42 -07:00
|
|
|
"orientation" -> "$setup.orientation",
|
|
|
|
"name" -> true
|
|
|
|
)
|
2021-01-19 11:38:01 -07:00
|
|
|
)
|
|
|
|
)
|
2021-02-02 13:27:42 -07:00
|
|
|
}
|
2021-01-19 11:38:01 -07:00
|
|
|
}
|
|
|
|
.map { r =>
|
|
|
|
for {
|
|
|
|
doc <- r
|
|
|
|
id <- doc.getAsOpt[Chapter.Id]("_id")
|
|
|
|
name <- doc.getAsOpt[Chapter.Name]("name")
|
|
|
|
comp <- doc.getAsOpt[Bdoc]("comp")
|
|
|
|
node <- comp.getAsOpt[Bdoc]("node")
|
|
|
|
fen <- node.getAsOpt[FEN]("fen")
|
|
|
|
lastMove = node.getAsOpt[Uci]("uci")
|
|
|
|
tags = comp.getAsOpt[Tags]("tags")
|
|
|
|
} yield ChapterPreview(
|
|
|
|
id = id,
|
|
|
|
name = name,
|
|
|
|
players = tags flatMap ChapterPreview.players,
|
|
|
|
orientation = doc.getAsOpt[Color]("orientation") | Color.White,
|
|
|
|
fen = fen,
|
|
|
|
lastMove = lastMove,
|
|
|
|
playing = lastMove.isDefined && tags.flatMap(_(_.Result)).has("*")
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private object handlers {
|
2018-11-21 21:53:28 -07:00
|
|
|
|
2021-11-20 01:51:26 -07:00
|
|
|
import lila.common.Json._
|
|
|
|
|
2019-12-03 17:55:45 -07:00
|
|
|
implicit val previewPlayerWriter: Writes[ChapterPreview.Player] = Writes[ChapterPreview.Player] { p =>
|
2019-12-13 07:30:20 -07:00
|
|
|
Json
|
|
|
|
.obj("name" -> p.name)
|
2019-12-03 17:55:45 -07:00
|
|
|
.add("title" -> p.title)
|
|
|
|
.add("rating" -> p.rating)
|
|
|
|
}
|
2018-11-21 21:53:28 -07:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
implicit val previewPlayersWriter: Writes[ChapterPreview.Players] = Writes[ChapterPreview.Players] {
|
|
|
|
players =>
|
|
|
|
Json.obj("white" -> players.white, "black" -> players.black)
|
2019-12-03 17:55:45 -07:00
|
|
|
}
|
2018-11-21 21:53:28 -07:00
|
|
|
|
2019-12-03 17:55:45 -07:00
|
|
|
implicit val previewWriter: Writes[ChapterPreview] = Json.writes[ChapterPreview]
|
|
|
|
}
|
2018-11-21 21:53:28 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
object StudyMultiBoard {
|
|
|
|
|
|
|
|
case class ChapterPreview(
|
|
|
|
id: Chapter.Id,
|
|
|
|
name: Chapter.Name,
|
|
|
|
players: Option[ChapterPreview.Players],
|
2018-11-21 22:21:54 -07:00
|
|
|
orientation: Color,
|
2018-11-22 00:30:54 -07:00
|
|
|
fen: FEN,
|
2018-11-22 05:47:19 -07:00
|
|
|
lastMove: Option[Uci],
|
|
|
|
playing: Boolean
|
2018-11-21 21:53:28 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
object ChapterPreview {
|
|
|
|
|
|
|
|
case class Player(name: String, title: Option[String], rating: Option[Int])
|
|
|
|
|
|
|
|
type Players = Color.Map[Player]
|
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def players(tags: Tags): Option[Players] =
|
|
|
|
for {
|
|
|
|
wName <- tags(_.White)
|
|
|
|
bName <- tags(_.Black)
|
|
|
|
} yield Color.Map(
|
|
|
|
white = Player(wName, tags(_.WhiteTitle), tags(_.WhiteElo) flatMap (_.toIntOption)),
|
|
|
|
black = Player(bName, tags(_.BlackTitle), tags(_.BlackElo) flatMap (_.toIntOption))
|
|
|
|
)
|
2018-11-21 21:53:28 -07:00
|
|
|
}
|
|
|
|
}
|