diff --git a/app/controllers/RelayTour.scala b/app/controllers/RelayTour.scala index 2a9cd6fa3b..7bdf5b0248 100644 --- a/app/controllers/RelayTour.scala +++ b/app/controllers/RelayTour.scala @@ -4,11 +4,13 @@ import play.api.data.Form import play.api.mvc._ import scala.annotation.nowarn import scala.concurrent.duration._ +import scala.util.chaining._ import views._ import lila.api.Context import lila.app._ import lila.common.config.MaxPerSecond +import lila.common.{ HTTPRequest, IpAddress } import lila.relay.{ RelayRound => RoundModel, RelayTour => TourModel } import lila.user.{ User => UserModel } @@ -124,6 +126,21 @@ final class RelayTour(env: Env, apiC: => Api) extends LilaController(env) { } } + def pgn(id: String) = + Action.async { req => + env.relay.api tourById TourModel.Id(id) map { + _ ?? { tour => + apiC.GlobalConcurrencyLimitPerIP(HTTPRequest ipAddress req)( + env.relay.pgnStream(tour) + ) { source => + asAttachmentStream(s"${env.relay.pgnStream filename tour}.pgn")( + Ok chunked source as pgnContentType + ) + } + } + } + } + def apiIndex = Action.async { implicit req => apiC.jsonStream { @@ -162,7 +179,7 @@ final class RelayTour(env: Env, apiC: => Api) extends LilaController(env) { key = "broadcast.tournament.user" ) - private val CreateLimitPerIP = new lila.memo.RateLimit[lila.common.IpAddress]( + private val CreateLimitPerIP = new lila.memo.RateLimit[IpAddress]( credits = 10 * 10, duration = 24.hour, key = "broadcast.tournament.ip" @@ -180,7 +197,7 @@ final class RelayTour(env: Env, apiC: => Api) extends LilaController(env) { else if (me.hasTitle || me.isVerified) 5 else 10 CreateLimitPerUser(me.id, cost = cost) { - CreateLimitPerIP(lila.common.HTTPRequest ipAddress req, cost = cost) { + CreateLimitPerIP(HTTPRequest ipAddress req, cost = cost) { create }(fail.fuccess) }(fail.fuccess) diff --git a/conf/routes b/conf/routes index f8fc340473..516152c78b 100644 --- a/conf/routes +++ b/conf/routes @@ -200,6 +200,7 @@ GET /broadcast controllers.RelayTour.index(page: GET /broadcast/new controllers.RelayTour.form POST /broadcast/new controllers.RelayTour.create GET /broadcast/:rs/$anyId<\w{8}> controllers.RelayTour.redirectOrApiTour(rs: String, anyId: String) +GET /broadcast/$tourId<\w{8}>.pgn controllers.RelayTour.pgn(tourId: String) GET /broadcast/$tourId<\w{8}>/edit controllers.RelayTour.edit(tourId: String) POST /broadcast/$tourId<\w{8}>/edit controllers.RelayTour.update(tourId: String) GET /broadcast/$tourId<\w{8}>/new controllers.RelayRound.form(tourId: String) diff --git a/modules/relay/src/main/Env.scala b/modules/relay/src/main/Env.scala index 31aa0c8ad8..1f8b9157e9 100644 --- a/modules/relay/src/main/Env.scala +++ b/modules/relay/src/main/Env.scala @@ -14,6 +14,7 @@ final class Env( multiboard: lila.study.StudyMultiBoard, studyRepo: lila.study.StudyRepo, chapterRepo: lila.study.ChapterRepo, + studyPgnDump: lila.study.PgnDump, gameRepo: lila.game.GameRepo, pgnDump: lila.game.PgnDump, gameProxy: lila.round.GameProxyRepo, @@ -43,6 +44,8 @@ final class Env( lazy val markup = wire[RelayMarkup] + lazy val pgnStream = wire[RelayPgnStream] + private lazy val sync = wire[RelaySync] private lazy val formatApi = wire[RelayFormatApi] diff --git a/modules/relay/src/main/RelayPgnStream.scala b/modules/relay/src/main/RelayPgnStream.scala new file mode 100644 index 0000000000..47675e35d4 --- /dev/null +++ b/modules/relay/src/main/RelayPgnStream.scala @@ -0,0 +1,33 @@ +package lila.relay + +import akka.stream.scaladsl._ +import org.joda.time.format.DateTimeFormat +import scala.concurrent.duration._ +import scala.concurrent.ExecutionContext + +import lila.study.{ PgnDump, Study, StudyRepo } + +final class RelayPgnStream( + roundRepo: RelayRoundRepo, + studyRepo: StudyRepo, + studyPgnDump: PgnDump +)(implicit ec: ExecutionContext) { + + def apply(tour: RelayTour): Source[String, _] = + Source futureSource { + roundRepo.idsByTourOrdered(tour) flatMap { ids => + studyRepo.byOrderedIds(ids.map(_.value).map(Study.Id)) map { studies => + Source(studies).flatMapConcat { studyPgnDump(_, flags) } + } + } + } + + private val flags = PgnDump.WithFlags(comments = false, variations = false, clocks = true) + private val fileR = """[\s,]""".r + private val dateFormat = DateTimeFormat forPattern "yyyy.MM.dd" + + def filename(tour: RelayTour): String = { + val date = dateFormat.print(tour.syncedAt | tour.createdAt) + fileR.replaceAllIn(s"lichess_broadcast_${tour.slug}_${tour.id}_$date", "") + } +} diff --git a/modules/relay/src/main/RelayRoundRepo.scala b/modules/relay/src/main/RelayRoundRepo.scala index 63d37a3b95..59415f0c9f 100644 --- a/modules/relay/src/main/RelayRoundRepo.scala +++ b/modules/relay/src/main/RelayRoundRepo.scala @@ -18,6 +18,14 @@ final private class RelayRoundRepo(val coll: Coll)(implicit ec: scala.concurrent .cursor[RelayRound]() .list(RelayTour.maxRelays) + def idsByTourOrdered(tour: RelayTour): Fu[List[RelayRound.Id]] = + coll + .find(selectors.tour(tour.id), $id(true).some) + .sort(sort.chrono) + .cursor[Bdoc]() + .list(RelayTour.maxRelays) + .map(_.flatMap(_.getAsOpt[RelayRound.Id]("_id"))) + def lastByTour(tour: RelayTour): Fu[Option[RelayRound]] = coll .find(selectors tour tour.id)