Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion modules/relay/src/main/BSONHandlers.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package lila.relay

import reactivemongo.api.bson.{ BSONDocumentHandler, BSONHandler, Macros }
import reactivemongo.api.bson.*

import lila.db.BSON
import lila.db.dsl.{ *, given }
Expand Down Expand Up @@ -38,6 +38,19 @@ object BSONHandlers:

given BSONDocumentHandler[Sync] = Macros.handler

import RelayRound.Starts
val startsAfterPrevious = "afterPrevious"
given BSONHandler[Starts] = quickHandler[Starts](
{
case v: BSONDateTime => Starts.At(millisToInstant(v.value))
case BSONString("afterPrevious") => Starts.AfterPrevious
},
{
case Starts.At(time) => BSONDateTime(time.toMillis)
case Starts.AfterPrevious => BSONString("afterPrevious")
}
)

given BSONDocumentHandler[RelayRound] = Macros.handler

given BSONDocumentHandler[RelayPinnedStream] = Macros.handler
Expand Down
2 changes: 1 addition & 1 deletion modules/relay/src/main/JsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ object JsonView:
)
.add("finished" -> r.finished)
.add("ongoing" -> (r.hasStarted && !r.finished))
.add("startsAt" -> r.startsAt.orElse(r.startedAt))
.add("startsAt" -> r.startsAtTime.orElse(r.startedAt))

given OWrites[RelayStats.RoundStats] = OWrites: r =>
Json.obj(
Expand Down
7 changes: 5 additions & 2 deletions modules/relay/src/main/RelayApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,9 @@ final class RelayApi(
_ <- roundRepo.coll.update.one($id(round.id), round).void
_ <- (round.sync.playing != from.sync.playing)
.so(sendToContributors(round.id, "relaySync", jsonView.sync(round)))
_ <- denormalizeTour(round.tourId)
_ <- denormalizeTour(round.tourId)
nextRoundToStart <- round.finished.so(nextRoundThatStartsAfterThisOneCompletes(round))
_ <- nextRoundToStart.so(next => requestPlay(next.id, v = true))
yield
round.sync.log.events.lastOption
.ifTrue(round.sync.log != from.sync.log)
Expand Down Expand Up @@ -429,6 +431,7 @@ final class RelayApi(
.take(max.fold(9999)(_.value))

export tourRepo.{ isSubscribed, setSubscribed as subscribe }
export roundRepo.nextRoundThatStartsAfterThisOneCompletes

object image:
def rel(rt: RelayTour, tag: Option[String]) =
Expand Down Expand Up @@ -465,7 +468,7 @@ final class RelayApi(
.flatMap:
_.sequentiallyVoid: relay =>
val earlyMinutes = Math.min(60, 30 + relay.sync.delay.so(_.value / 60))
relay.startsAt
relay.startsAtTime
.exists(_.isBefore(nowInstant.plusMinutes(earlyMinutes)))
.so:
logger.info(s"Automatically start $relay")
Expand Down
6 changes: 3 additions & 3 deletions modules/relay/src/main/RelayFetch.scala
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ final private class RelayFetch(
games = res.plan.input.games
_ <- orphanNotifier.inspectPlan(rt, res.plan)
allGamesHaveOutcome = games.nonEmpty && games.forall(_.outcome.isDefined)
// TODO only if the next round starts when this one completes?
noMoreGamesSelected = games.isEmpty && allGamesInSource.nonEmpty && rt.round.startedAt.isDefined
nextRoundToStart <- noMoreGamesSelected.so(api.nextRoundThatStartsAfterThisOneCompletes(rt.round))
yield res -> updating:
_.withSync(_.addLog(SyncLog.event(res.nbMoves, none)))
.copy(finished = allGamesHaveOutcome || noMoreGamesSelected)
.copy(finished = allGamesHaveOutcome || nextRoundToStart.isDefined)
syncFu
.recover:
case e: Exception =>
Expand Down Expand Up @@ -278,7 +278,7 @@ final private class RelayFetch(
case RelayFormat.LccWithGames(lcc) =>
httpGetJson[RoundJson](lcc.indexUrl).flatMap: round =>
val lookForStart: Boolean =
rt.round.startsAt
rt.round.startsAtTime
.map(_.minusSeconds(rt.round.sync.delay.so(_.value) + 5 * 60))
.forall(_.isBeforeNow)
round.pairings
Expand Down
16 changes: 8 additions & 8 deletions modules/relay/src/main/RelayListing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,10 @@ final class RelayListing(
yield (tour, round, group)
sorted = tours.sortBy: (tour, round, _) =>
(
!round.hasStarted, // ongoing tournaments first
0 - ~tour.tier, // then by tier
0 - ~round.crowd, // then by viewers
round.startsAt.fold(Long.MaxValue)(_.toMillis) // then by next round date
!round.hasStarted, // ongoing tournaments first
0 - ~tour.tier, // then by tier
0 - ~round.crowd, // then by viewers
round.startsAtTime.fold(Long.MaxValue)(_.toMillis) // then by next round date
)
active <- sorted.parallel: (tour, round, group) =>
defaultRoundToShow
Expand All @@ -147,7 +147,7 @@ final class RelayListing(
.filter(_.tour.spotlight.exists(_.enabled))
.filterNot(_.display.finished)
.filter: tr =>
tr.display.hasStarted || tr.display.startsAt.exists(_.isBefore(nowInstant.plusMinutes(30)))
tr.display.hasStarted || tr.display.startsAtTime.exists(_.isBefore(nowInstant.plusMinutes(30)))
active

val upcoming = cacheApi.unit[List[RelayTour.WithLastRound]]:
Expand Down Expand Up @@ -186,8 +186,8 @@ final class RelayListing(
.map:
_.sortBy: rt =>
(
0 - ~rt.tour.tier, // tier sort
rt.round.startsAt.fold(Long.MaxValue)(_.toMillis) // then by next round date
0 - ~rt.tour.tier, // tier sort
rt.round.startsAtTime.fold(Long.MaxValue)(_.toMillis) // then by next round date
)

val defaultRoundToShow = cacheApi[RelayTourId, Option[RelayRound]](32, "relay.lastAndNextRounds"):
Expand All @@ -209,7 +209,7 @@ final class RelayListing(
.one[RelayRound]
case (Some(last), Some(next)) => // show the next one if it's less than an hour away
fuccess:
if next.startsAt.exists(_.isBefore(nowInstant.plusHours(1)))
if next.startsAtTime.exists(_.isBefore(nowInstant.plusHours(1)))
then next.some
else last.some
case (Some(last), None) =>
Expand Down
17 changes: 13 additions & 4 deletions modules/relay/src/main/RelayRound.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ case class RelayRound(
caption: Option[RelayRound.Caption],
sync: RelayRound.Sync,
/* When it's planned to start */
startsAt: Option[Instant],
startsAt: Option[RelayRound.Starts],
/* When it actually starts */
startedAt: Option[Instant],
/* at least it *looks* finished... but maybe it's not
Expand All @@ -30,6 +30,11 @@ case class RelayRound(
val s = scalalib.StringOps.slug(name.value)
if s.isEmpty then "-" else s

def startsAtTime = startsAt.flatMap:
case RelayRound.Starts.At(at) => at.some
case _ => none
def startsAfterPrevious = startsAt.contains(RelayRound.Starts.AfterPrevious)

def finish =
copy(
finished = true,
Expand All @@ -44,11 +49,11 @@ case class RelayRound(

def ensureStarted = copy(startedAt = startedAt.orElse(nowInstant.some))
def hasStarted = startedAt.isDefined
def hasStartedEarly = startedAt.exists(at => startsAt.exists(_.isAfter(at)))
def shouldHaveStarted = hasStarted || startsAt.exists(_.isBefore(nowInstant))
def hasStartedEarly = startedAt.exists(at => startsAtTime.exists(_.isAfter(at)))
def shouldHaveStarted = hasStarted || startsAtTime.exists(_.isBefore(nowInstant))

def shouldGiveUp =
!hasStarted && startsAt.match
!hasStarted && startsAtTime.match
case Some(at) => at.isBefore(nowInstant.minusHours(3))
case None => createdAt.isBefore(nowInstant.minusDays(1))

Expand All @@ -68,6 +73,10 @@ object RelayRound:
opaque type Caption = String
object Caption extends OpaqueString[Caption]

enum Starts:
case At(at: Instant)
case AfterPrevious

case class Sync(
upstream: Option[Sync.Upstream], // if empty, needs a client to push PGN
until: Option[Instant], // sync until then; resets on move
Expand Down
36 changes: 22 additions & 14 deletions modules/relay/src/main/RelayRoundForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,14 @@ final class RelayRoundForm(using mode: Mode):
u => !u.url.host.toString.endsWith("lichess.org") || Granter(_.Relay)
)
),
"syncUrls" -> optional(of[Upstream.Urls]),
"syncIds" -> optional(of[Upstream.Ids]),
"startsAt" -> optional(ISOInstantOrTimestamp.mapping),
"finished" -> optional(boolean),
"period" -> optional(number(min = 2, max = 60).into[Seconds]),
"delay" -> optional(number(min = 0, max = RelayDelay.maxSeconds.value).into[Seconds]),
"onlyRound" -> optional(number(min = 1, max = 999)),
"syncUrls" -> optional(of[Upstream.Urls]),
"syncIds" -> optional(of[Upstream.Ids]),
"startsAt" -> optional(ISOInstantOrTimestamp.mapping),
"startsAfterPrevious" -> optional(boolean),
"finished" -> optional(boolean),
"period" -> optional(number(min = 2, max = 60).into[Seconds]),
"delay" -> optional(number(min = 0, max = RelayDelay.maxSeconds.value).into[Seconds]),
"onlyRound" -> optional(number(min = 1, max = 999)),
"slices" -> optional:
nonEmptyText
.transform[List[RelayGame.Slice]](RelayGame.Slices.parse, RelayGame.Slices.show)
Expand Down Expand Up @@ -118,10 +119,10 @@ object RelayRoundForm:
roundNumberIn(old.name.value).contains(n - 1)
p <- prev
yield replaceRoundNumber(p.name.value, nextNumber)
val guessDate = for
val guessStartsAtTime = for
(prev, old) <- prevs
prevDate <- prev.startsAt
oldDate <- old.startsAt
prevDate <- prev.startsAtTime
oldDate <- old.startsAtTime
delta = prevDate.toEpochMilli - oldDate.toEpochMilli
yield prevDate.plusMillis(delta)
val nextUrl: Option[URL] = for
Expand All @@ -136,7 +137,8 @@ object RelayRoundForm:
caption = prev.flatMap(_.caption),
syncSource = prev.map(Data.make).flatMap(_.syncSource),
syncUrl = nextUrl.map(Upstream.Url.apply),
startsAt = guessDate,
startsAt = guessStartsAtTime,
startsAfterPrevious = prev.exists(_.startsAfterPrevious).option(true),
period = prev.flatMap(_.sync.period),
delay = prev.flatMap(_.sync.delay),
onlyRound = prev.flatMap(_.sync.onlyRound).map(_ + 1),
Expand Down Expand Up @@ -192,6 +194,7 @@ object RelayRoundForm:
syncUrls: Option[Upstream.Urls] = None,
syncIds: Option[Upstream.Ids] = None,
startsAt: Option[Instant] = None,
startsAfterPrevious: Option[Boolean] = None,
finished: Option[Boolean] = None,
period: Option[Seconds] = None,
delay: Option[Seconds] = None,
Expand All @@ -205,13 +208,17 @@ object RelayRoundForm:
case Some("ids") => syncIds
case _ => None

private def relayStartsAt: Option[RelayRound.Starts] = startsAt
.map(RelayRound.Starts.At(_))
.orElse((~startsAfterPrevious).option(RelayRound.Starts.AfterPrevious))

def update(official: Boolean)(relay: RelayRound)(using me: Me)(using mode: Mode) =
val sync = makeSync(me)
relay.copy(
name = name,
caption = caption,
sync = if relay.sync.playing then sync.play(official) else sync,
startsAt = startsAt,
startsAt = relayStartsAt,
finished = ~finished
)

Expand All @@ -237,7 +244,7 @@ object RelayRoundForm:
createdAt = nowInstant,
crowd = none,
finished = ~finished,
startsAt = startsAt,
startsAt = relayStartsAt,
startedAt = none
)

Expand All @@ -260,7 +267,8 @@ object RelayRoundForm:
case urls: Upstream.Urls => urls,
syncIds = relay.sync.upstream.collect:
case ids: Upstream.Ids => ids,
startsAt = relay.startsAt,
startsAt = relay.startsAtTime,
startsAfterPrevious = relay.startsAfterPrevious.option(true),
finished = relay.finished.option(true),
period = relay.sync.period,
onlyRound = relay.sync.onlyRound,
Expand Down
14 changes: 14 additions & 0 deletions modules/relay/src/main/RelayRoundRepo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ final private class RelayRoundRepo(val coll: Coll)(using Executor):
)
.void

def nextRoundThatStartsAfterThisOneCompletes(round: RelayRound): Fu[Option[RelayRound]] =
coll
.find(
$doc(
"tourId" -> round.tourId,
"finished" -> false,
"startsAt" -> BSONHandlers.startsAfterPrevious,
"createdAt".$gt(round.createdAt)
)
)
.sort($doc("createdAt" -> 1))
.cursor[RelayRound]()
.uno

private object RelayRoundRepo:

object sort:
Expand Down
32 changes: 22 additions & 10 deletions modules/relay/src/main/ui/FormUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,7 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi):
)
,
form3.globalError(form),
form3.split(
form3.group(form("name"), trb.roundName(), half = true)(form3.input(_)(autofocus)),
form3.group(
form("startsAt"),
trb.startDate(),
help = trb.startDateHelp().some,
half = true
)(form3.flatpickr(_, minDate = None))
),
form3.group(form("name"), trb.roundName())(form3.input(_)(autofocus)),
form3.fieldset("Source", toggle = true.some)(cls := "box-pad")(
form3.group(
form("syncSource"),
Expand All @@ -222,7 +214,9 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi):
"s",
br,
"Start: ",
source.round.startedAt.orElse(source.round.startsAt).fold(frag("unscheduled"))(momentFromNow),
source.round.startedAt
.orElse(source.round.startsAtTime)
.fold(frag("unscheduled"))(momentFromNow),
br,
"Last sync: ",
source.round.sync.log.events.lastOption.map: event =>
Expand Down Expand Up @@ -295,6 +289,24 @@ final class FormUi(helpers: Helpers, ui: RelayUi, tourUi: RelayTourUi):
)(form3.input(_))
)
),
form3.fieldset("When does it start", toggle = true.some)(cls := "box-pad")(
form3.split(
form3.group(
form("startsAt"),
trb.startDate(),
help = trb.startDateHelp().some,
half = true
)(form3.flatpickr(_, minDate = None)),
form3.checkbox(
form("startsAfterPrevious"),
"When the previous round completes",
help = frag(
"The start date is unknown, and the round will start automatically when the previous round completes."
).some,
half = true
)
)
),
form3.fieldset("Advanced", toggle = nav.round.exists(r => r.sync.delay.isDefined).some)(
form3.split(
form3.group(
Expand Down
2 changes: 1 addition & 1 deletion modules/relay/src/main/ui/RelayTourUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ final class RelayTourUi(helpers: Helpers, ui: RelayUi):
.map: nb =>
span(cls := "relay-card__crowd text", dataIcon := Icon.User)(nb.localize)
)
else tr.display.startedAt.orElse(tr.display.startsAt).map(momentFromNow(_))
else tr.display.startedAt.orElse(tr.display.startsAtTime).map(momentFromNow(_))
),
h3(cls := "relay-card__title")(tr.group.fold(tr.tour.name.value)(_.value)),
if errors.nonEmpty
Expand Down
2 changes: 1 addition & 1 deletion modules/relay/src/main/ui/RelayUi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ final class RelayUi(helpers: Helpers)(
" • ",
if tr.display.hasStarted
then trans.site.eventInProgress()
else tr.display.startsAt.map(momentFromNow(_)) | "Soon"
else tr.display.startsAtTime.map(momentFromNow(_)) | "Soon"
)
)
)
Expand Down