lila/modules/relay/src/main/RelaySync.scala

168 lines
5.8 KiB
Scala
Raw Normal View History

2017-09-19 20:24:59 -06:00
package lila.relay
2017-10-05 15:48:14 -06:00
import org.joda.time.DateTime
2017-10-06 17:33:33 -06:00
import chess.format.pgn.{ Tag, Tags }
2017-10-07 19:45:50 -06:00
import lila.base.LilaException
import lila.common.Chronometer
2017-09-20 13:35:28 -06:00
import lila.socket.Socket.Uid
import lila.study._
2017-09-19 20:24:59 -06:00
private final class RelaySync(
2017-09-20 13:35:28 -06:00
studyApi: StudyApi,
chapterRepo: ChapterRepo
) {
2017-09-19 20:24:59 -06:00
2017-10-04 23:59:35 -06:00
private type NbMoves = Int
def apply(relay: Relay, games: RelayGames): Fu[SyncResult.Ok] =
studyApi byId relay.studyId flatten "Missing relay study!" flatMap { study =>
chapterRepo orderedByStudy study.id flatMap { chapters =>
lila.common.Future.traverseSequentially(games) { game =>
findCorrespondingChapter(game, chapters, games.size) match {
2017-09-20 13:35:28 -06:00
case Some(chapter) => updateChapter(study, chapter, game)
case None => createChapter(study, game) flatMap { chapter =>
2017-10-04 23:59:35 -06:00
chapters.find(_.isEmptyInitial).ifTrue(chapter.order == 2).?? { initial =>
studyApi.deleteChapter(study.ownerId, study.id, initial.id, socketUid)
2017-10-04 23:59:35 -06:00
} inject chapter.root.mainline.size
}
2017-09-20 13:35:28 -06:00
}
} map { _.foldLeft(0)(_ + _) } map { SyncResult.Ok(_, games) }
}
2017-09-20 13:35:28 -06:00
}
2017-09-19 20:24:59 -06:00
/*
* If the source contains several games, use their index to match them with the study chapter.
* If the source contains only one game, use the player tags to match with the study chapter.
* So the TCEC style - one game per file, reusing the file for all games - is supported.
* lichess will create a new chapter when the game player tags differ.
*/
private def findCorrespondingChapter(game: RelayGame, chapters: List[Chapter], nbGames: Int): Option[Chapter] =
if (nbGames == 1) chapters.find(c => game staticTagsMatch c.tags)
else chapters.find(_.relay.exists(_.index == game.index))
private def updateChapter(study: Study, chapter: Chapter, game: RelayGame): Fu[NbMoves] =
2017-10-06 17:33:33 -06:00
updateChapterTags(study, chapter, game) >>
updateChapterTree(study, chapter, game)
private def updateChapterTree(study: Study, chapter: Chapter, game: RelayGame): Fu[NbMoves] =
2017-09-20 13:35:28 -06:00
game.root.mainline.foldLeft(Path.root -> none[Node]) {
case ((parentPath, None), gameNode) =>
val path = parentPath + gameNode
chapter.root.nodeAt(path) match {
case None => parentPath -> gameNode.some
case Some(existing) =>
gameNode.clock.filter(c => !existing.clock.has(c)) ?? { c =>
2017-10-09 12:36:36 -06:00
studyApi.setClock(
userId = chapter.ownerId,
2017-10-09 12:36:36 -06:00
studyId = study.id,
position = Position(chapter, path).ref,
clock = c.some,
uid = socketUid
)
}
path -> none
}
2017-09-20 13:35:28 -06:00
case (found, _) => found
} match {
case (path, newNode) =>
!Path.isMainline(chapter.root, path) ?? {
studyApi.promote(
userId = chapter.ownerId,
studyId = study.id,
position = Position(chapter, path).ref,
toMainline = true,
uid = socketUid
)
} >> newNode.?? { node =>
lila.common.Future.fold(node.mainline)(Position(chapter, path).ref) {
case (position, n) => studyApi.addNode(
userId = chapter.ownerId,
studyId = study.id,
position = position,
node = n,
uid = socketUid,
opts = moveOpts.copy(clock = n.clock),
relay = Chapter.Relay(
index = game.index,
path = position.path + n,
lastMoveAt = DateTime.now
).some
) inject position + n
} inject node.mainline.size
}
2017-09-20 13:35:28 -06:00
}
2017-10-06 17:33:33 -06:00
private def updateChapterTags(study: Study, chapter: Chapter, game: RelayGame): Funit = {
val gameTags = game.tags.value.foldLeft(Tags(Nil)) {
case (newTags, tag) =>
2017-10-10 15:08:48 -06:00
if (!chapter.tags.value.exists(tag ==)) newTags + tag
2017-10-06 17:33:33 -06:00
else newTags
}
val tags = game.end
.ifFalse(gameTags(_.Result).isDefined)
.filterNot(end => chapter.tags(_.Result).??(end.resultText ==))
.fold(gameTags) { end =>
gameTags + Tag(_.Result, end.resultText)
}
lila.common.Future.traverseSequentially(tags.value) { tag =>
studyApi.setTag(
userId = chapter.ownerId,
studyId = study.id,
lila.study.actorApi.SetTag(chapter.id, tag.name.name, tag.value),
uid = socketUid
)
}.void
}
private def createChapter(study: Study, game: RelayGame): Fu[Chapter] =
2017-09-20 13:35:28 -06:00
chapterRepo.nextOrderByStudy(study.id) flatMap { order =>
val name = {
for {
w <- game.tags(_.White)
b <- game.tags(_.Black)
} yield s"$w - $b"
} orElse game.tags("board") getOrElse "?"
2017-09-20 13:35:28 -06:00
val chapter = Chapter.make(
studyId = study.id,
name = Chapter.Name(name),
2017-09-20 13:35:28 -06:00
setup = Chapter.Setup(
none,
game.tags.variant | chess.variant.Variant.default,
chess.Color.White
),
root = game.root,
2017-10-05 11:28:07 -06:00
tags = game.tags,
2017-09-20 13:35:28 -06:00
order = order,
ownerId = study.ownerId,
practice = false,
gamebook = false,
2017-10-07 06:14:25 -06:00
conceal = none,
relay = Chapter.Relay(
index = game.index,
2017-10-07 06:14:25 -06:00
path = game.root.mainlinePath,
lastMoveAt = DateTime.now
2017-10-07 06:14:25 -06:00
).some
2017-09-20 13:35:28 -06:00
)
studyApi.doAddChapter(study, chapter, sticky = false, uid = socketUid) inject chapter
2017-09-20 13:35:28 -06:00
}
private val moveOpts = MoveOpts(
write = true,
sticky = false,
promoteToMainline = true,
clock = none
)
private val socketUid = Uid("")
2017-09-19 20:24:59 -06:00
}
2017-10-18 11:53:30 -06:00
sealed abstract class SyncResult(val reportKey: String)
object SyncResult {
2017-10-18 11:53:30 -06:00
case class Ok(moves: Int, games: RelayGames) extends SyncResult("ok")
case object Timeout extends Exception with SyncResult("timeout") {
override def getMessage = "In progress..."
}
2017-10-18 11:53:30 -06:00
case class Error(msg: String) extends SyncResult("error")
}