more broadcast insanity

pull/4672/head
Thibault Duplessis 2018-10-17 11:55:10 +02:00
parent fd5a55ae6c
commit 379bd5b7c9
3 changed files with 75 additions and 13 deletions

View File

@ -1,7 +1,7 @@
package lila.relay
import chess.format.pgn.Tags
import lila.study.{ Node, PgnImport }
import lila.study.{ Chapter, Node, PgnImport }
case class RelayGame(
index: Int,
@ -11,9 +11,10 @@ case class RelayGame(
end: Option[PgnImport.End]
) {
def staticTagsMatch(chapterTags: Tags) = List("white", "black", "round", "event") forall { name =>
def staticTagsMatch(chapterTags: Tags): Boolean = RelayGame.staticTags forall { name =>
chapterTags(name) == tags(name)
}
def staticTagsMatch(chapter: Chapter): Boolean = staticTagsMatch(chapter.tags)
def started = root.children.nodes.nonEmpty
@ -21,3 +22,8 @@ case class RelayGame(
def isEmpty = tags.value.isEmpty && root.children.nodes.isEmpty
}
private object RelayGame {
val staticTags = List("white", "black", "round", "event")
}

View File

@ -0,0 +1,41 @@
package lila.relay
import lila.study._
/* Try and detect variant ways for the input to be wrong */
private object RelayInputSanity {
sealed abstract class Fail(val msg: String)
case class Missing(pos: Int) extends Fail(s"Missing game for Chapter ${pos + 1}")
case class Misplaced(gamePos: Int, chapterPos: Int) extends Fail(s"Game ${gamePos + 1} matches with Chapter ${chapterPos + 1}")
def apply(chapters: List[Chapter], games: RelayGames): Option[Fail] =
if (chapters.isEmpty) none
else if (isValidTCEC(chapters, games)) none
else {
val relayChapters: List[RelayChapter] = chapters.flatMap { chapter =>
chapter.relay map chapter.->
}
detectMissingOrMisplaced(relayChapters, games.toVector)
}
private def detectMissingOrMisplaced(chapters: List[RelayChapter], games: Vector[RelayGame]): Option[Fail] =
chapters flatMap {
case (chapter, relay) => games.lift(relay.index) match {
case None => Missing(relay.index).some
case Some(game) if !game.staticTagsMatch(chapter) =>
games.zipWithIndex collectFirst {
case (otherGame, otherPos) if otherGame staticTagsMatch chapter => Misplaced(otherPos, relay.index)
}
case _ => None
}
} headOption
// TCEC style has one game per file, and reuses the file for all games
private def isValidTCEC(chapters: List[Chapter], games: RelayGames) = games match {
case List(onlyGame) => chapters.lastOption.exists { c => onlyGame staticTagsMatch c.tags }
case _ => false
}
private type RelayChapter = (Chapter, Chapter.Relay)
}

View File

@ -16,16 +16,19 @@ private final class RelaySync(
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 {
case Some(chapter) => updateChapter(study, chapter, game)
case None => createChapter(study, game) flatMap { chapter =>
chapters.find(_.isEmptyInitial).ifTrue(chapter.order == 2).?? { initial =>
studyApi.deleteChapter(study.ownerId, study.id, initial.id, socketUid)
} inject chapter.root.mainline.size
RelayInputSanity(chapters, games) match {
case Some(fail) => fufail(SyncResult.SourceFail(fail.msg))
case None => lila.common.Future.traverseSequentially(games) { game =>
findCorrespondingChapter(game, chapters, games.size) match {
case Some(chapter) => updateChapter(study, chapter, game)
case None => createChapter(study, game) flatMap { chapter =>
chapters.find(_.isEmptyInitial).ifTrue(chapter.order == 2).?? { initial =>
studyApi.deleteChapter(study.ownerId, study.id, initial.id, socketUid)
} inject chapter.root.mainline.size
}
}
}
} map { _.foldLeft(0)(_ + _) } map { SyncResult.Ok(_, games) }
} map { _.foldLeft(0)(_ + _) } map { SyncResult.Ok(_, games) }
}
}
}
@ -36,7 +39,7 @@ private final class RelaySync(
* 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)
if (nbGames == 1) chapters find game.staticTagsMatch
else chapters.find(_.relay.exists(_.index == game.index))
private def updateChapter(study: Study, chapter: Chapter, game: RelayGame): Fu[NbMoves] =
@ -64,6 +67,7 @@ private final class RelaySync(
} match {
case (path, newNode) =>
!Path.isMainline(chapter.root, path) ?? {
logger.info(s"Promote ${showSC(study, chapter)} $path")
studyApi.promote(
userId = chapter.ownerId,
studyId = study.id,
@ -106,6 +110,8 @@ private final class RelaySync(
case (chapterTags, tag) => PgnTags(chapterTags + tag)
}
(chapterNewTags != chapter.tags) ?? {
if (vs(chapterNewTags) != vs(chapter.tags))
logger.info(s"Update ${showSC(study, chapter)} tags '${vs(chapterNewTags)}' -> '${vs(chapter.tags)}'")
studyApi.setTags(
userId = chapter.ownerId,
studyId = study.id,
@ -166,6 +172,12 @@ private final class RelaySync(
)
private val socketUid = Uid("")
private def vs(tags: Tags) =
(tags(_.White) |@| tags(_.Black)).tupled map { case (w, b) => s"$w - $b" }
private def showSC(study: Study, chapter: Chapter) =
s"#${study.id} chapter[${chapter.relay.fold("?")(_.index.toString)}]"
}
sealed trait SyncResult {
@ -179,7 +191,10 @@ object SyncResult {
val reportKey = "timeout"
override def getMessage = "In progress..."
}
case class Error(msg: String) extends SyncResult {
case class Error(msg: String) extends Exception with SyncResult {
val reportKey = "error"
}
case class SourceFail(msg: String) extends Exception with SyncResult {
val reportKey = "error"
}
}