package lila.relay import org.joda.time.DateTime import lila.study.{ Chapter, Study } import lila.user.User case class RelayRound( _id: RelayRound.Id, tourId: RelayTour.Id, name: String, sync: RelayRound.Sync, /* When it's planned to start */ startsAt: Option[DateTime], /* When it actually starts */ startedAt: Option[DateTime], /* at least it *looks* finished... but maybe it's not * sync.nextAt is used for actually synchronising */ finished: Boolean, createdAt: DateTime ) { def id = _id def studyId = Study.Id(id.value) lazy val slug = { val s = lila.common.String slugify name if (s.isEmpty) "-" else s } def finish = copy( finished = true, sync = sync.pause ) def resume = copy( finished = false, sync = sync.play ) def ensureStarted = copy( startedAt = startedAt orElse DateTime.now.some ) def hasStarted = startedAt.isDefined def hasStartedEarly = hasStarted && startsAt.exists(_ isAfter DateTime.now) def shouldHaveStarted = hasStarted || startsAt.exists(_ isBefore DateTime.now) def shouldGiveUp = !hasStarted && (startsAt match { case Some(at) => at.isBefore(DateTime.now minusHours 3) case None => createdAt.isBefore(DateTime.now minusDays 1) }) def withSync(f: RelayRound.Sync => RelayRound.Sync) = copy(sync = f(sync)) def withTour(tour: RelayTour) = RelayRound.WithTour(this, tour) override def toString = s"""relay #$id "$name" $sync""" } object RelayRound { case class Id(value: String) extends AnyVal with StringValue def makeId = Id(lila.common.ThreadLocalRandom nextString 8) case class Sync( upstream: Option[Sync.Upstream], // if empty, needs a client to push PGN until: Option[DateTime], // sync until then; resets on move nextAt: Option[DateTime], // when to run next sync delay: Option[Int], // override time between two sync (rare) log: SyncLog ) { def hasUpstream = upstream.isDefined def renew = if (hasUpstream) copy(until = DateTime.now.plusHours(1).some) else pause def ongoing = until ?? DateTime.now.isBefore def play = if (hasUpstream) renew.copy(nextAt = nextAt orElse DateTime.now.plusSeconds(3).some) else pause def pause = copy( nextAt = none, until = none ) def seconds: Option[Int] = until map { u => (u.getSeconds - nowSeconds).toInt } filter (0 <) def playing = nextAt.isDefined def paused = !playing def addLog(event: SyncLog.Event) = copy(log = log add event) def clearLog = copy(log = SyncLog.empty) override def toString = upstream.toString } object Sync { sealed trait Upstream { def asUrl: Option[UpstreamUrl] = this match { case url: UpstreamUrl => url.some case _ => none } def local = asUrl.fold(true)(_.isLocal) } case class UpstreamUrl(url: String) extends Upstream { def isLocal = url.contains("://127.0.0.1") || url.contains("://localhost") def withRound = url.split(" ", 2) match { case Array(u, round) => UpstreamUrl.WithRound(u, round.toIntOption) case _ => UpstreamUrl.WithRound(url, none) } } object UpstreamUrl { case class WithRound(url: String, round: Option[Int]) val LccRegex = """.*view\.livechesscloud\.com/#?([0-9a-f\-]+)""".r } case class UpstreamIds(ids: List[lila.game.Game.ID]) extends Upstream } trait AndTour { val round: RelayRound val tour: RelayTour def fullName = s"${tour.name} • ${round.name}" def path: String = s"/broadcast/${tour.slug}/${if (round.slug == tour.slug) "-" else round.slug}/${round.id}" def path(chapterId: Chapter.Id): String = s"$path/$chapterId" } case class WithTour(round: RelayRound, tour: RelayTour) extends AndTour { def withStudy(study: Study) = WithTourAndStudy(round, tour, study) } case class WithTourAndStudy(relay: RelayRound, tour: RelayTour, study: Study) { def path = WithTour(relay, tour).path def fullName = WithTour(relay, tour).fullName } }