more broadcast insanity
parent
fd5a55ae6c
commit
379bd5b7c9
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue