diff --git a/app/AppApi.scala b/app/AppApi.scala index 854c1c7e63..a7a1230f28 100644 --- a/app/AppApi.scala +++ b/app/AppApi.scala @@ -21,47 +21,62 @@ final class AppApi( messenger: Messenger, starter: Starter) { - private implicit val timeout = Timeout(200 millis) + private implicit val timeout = Timeout(300 millis) private implicit val executor = Akka.system.dispatcher - def show(fullId: String): Future[IO[Map[String, Any]]] = + def show(fullId: String): Future[IO[Valid[Map[String, Any]]]] = (gameHubMemo getFromFullId fullId) ? game.GetVersion map { case game.Version(version) ⇒ for { - pov ← gameRepo pov fullId - roomHtml ← messenger render pov.game.id - } yield Map( - "version" -> version, - "roomHtml" -> roomHtml, - "possibleMoves" -> { - if (pov.game playableBy pov.player) - pov.game.toChess.situation.destinations map { - case (from, dests) ⇒ from.key -> (dests.mkString) - } toMap - else null - } - ) + povOption ← gameRepo pov fullId + gameInfo ← povOption.fold( + pov ⇒ messenger render pov.game.id map { roomHtml ⇒ + Map( + "version" -> version, + "roomHtml" -> roomHtml, + "possibleMoves" -> { + if (pov.game playableBy pov.player) + pov.game.toChess.situation.destinations map { + case (from, dests) ⇒ from.key -> (dests.mkString) + } toMap + else null + } + ).success + }, + io(GameNotFound) + ) + } yield gameInfo } def join( fullId: String, url: String, messages: String, - entryData: String): IO[Unit] = for { - pov ← gameRepo pov fullId - p1 ← starter.start(pov.game, entryData) - p2 ← messenger.systemMessages(p1.game, messages) map { evts ⇒ - p1 + RedirectEvent(!pov.color, url) ++ evts - } - _ ← gameRepo save p2 - _ ← gameSocket send p2 - } yield () + entryData: String): IO[Valid[Unit]] = for { + povOption ← gameRepo pov fullId + op ← povOption.fold( + pov ⇒ for { + p1 ← starter.start(pov.game, entryData) + p2 ← messenger.systemMessages(p1.game, messages) map { evts ⇒ + p1 + RedirectEvent(!pov.color, url) ++ evts + } + _ ← gameRepo save p2 + _ ← gameSocket send p2 + } yield success(), + io(GameNotFound) + ) + } yield op - def start(gameId: String, entryData: String): IO[Unit] = for { - g1 ← gameRepo game gameId - progress ← starter.start(g1, entryData) - _ ← gameRepo save progress - _ ← gameSocket send progress - } yield () + def start(gameId: String, entryData: String): IO[Valid[Unit]] = + gameRepo game gameId flatMap { gameOption ⇒ + gameOption.fold( + g1 ⇒ for { + progress ← starter.start(g1, entryData) + _ ← gameRepo save progress + _ ← gameSocket send progress + } yield success(Unit), + io { !!("No such game") } + ) + } def rematchAccept( gameId: String, @@ -70,32 +85,47 @@ final class AppApi( whiteRedirect: String, blackRedirect: String, entryData: String, - messageString: String): IO[Unit] = for { + messageString: String): IO[Valid[Unit]] = for { color ← ioColor(colorName) - newGame ← gameRepo game newGameId - g1 ← gameRepo game gameId - progress = Progress(g1, List( - RedirectEvent(White, whiteRedirect), - RedirectEvent(Black, blackRedirect), - // to tell spectators to reload the table - ReloadTableEvent(White), - ReloadTableEvent(Black))) - _ ← gameRepo save progress - _ ← gameSocket send progress - newProgress ← starter.start(newGame, entryData) - newProgress2 ← messenger.systemMessages( - newProgress.game, messageString - ) map newProgress.++ - _ ← gameRepo save newProgress2 - _ ← gameSocket send newProgress2 - } yield () + newGameOption ← gameRepo game newGameId + g1Option ← gameRepo game gameId + result ← (newGameOption |@| g1Option).tupled.fold( + games ⇒ { + val (newGame, g1) = games + val progress = Progress(g1, List( + RedirectEvent(White, whiteRedirect), + RedirectEvent(Black, blackRedirect), + // tell spectators to reload the table + ReloadTableEvent(White), + ReloadTableEvent(Black))) + for { + _ ← gameRepo save progress + _ ← gameSocket send progress + newProgress ← starter.start(newGame, entryData) + newProgress2 ← messenger.systemMessages( + newProgress.game, messageString + ) map newProgress.++ + _ ← gameRepo save newProgress2 + _ ← gameSocket send newProgress2 + } yield success() + }, + io(GameNotFound) + ): IO[Valid[Unit]] + } yield result - def reloadTable(gameId: String): IO[Unit] = for { - g1 ← gameRepo game gameId - progress = Progress(g1, Color.all map ReloadTableEvent) - _ ← gameRepo save progress - _ ← gameSocket send progress - } yield () + def reloadTable(gameId: String): IO[Valid[Unit]] = for { + g1Option ← gameRepo game gameId + result ← g1Option.fold( + g1 ⇒ { + val progress = Progress(g1, Color.all map ReloadTableEvent) + for { + _ ← gameRepo save progress + _ ← gameSocket send progress + } yield success() + }, + io(GameNotFound) + ) + } yield result def gameVersion(gameId: String): Future[Int] = (gameHubMemo get gameId) ? game.GetVersion map { diff --git a/app/Cron.scala b/app/Cron.scala index 371cefad95..842ba72f4e 100644 --- a/app/Cron.scala +++ b/app/Cron.scala @@ -7,14 +7,9 @@ import akka.util.duration._ import akka.util.{ Duration, Timeout } import scalaz.effects._ -import socket.GetNbMembers -import site.{ NbMembers, WithUsernames } -import lobby.WithHooks -import RichDuration._ - final class Cron(env: SystemEnv)(implicit app: Application) { - implicit val timeout = Timeout(200 millis) + implicit val timeout = Timeout(500 millis) implicit val executor = Akka.system.dispatcher message(2 seconds) { @@ -22,11 +17,11 @@ final class Cron(env: SystemEnv)(implicit app: Application) { } message(1 second) { - env.lobbyHub -> WithHooks(env.hookMemo.putAll) + env.lobbyHub -> lobby.WithHooks(env.hookMemo.putAll) } message(2 seconds) { - env.siteHub -> NbMembers + env.siteHub -> site.NbMembers } effect(2 seconds) { @@ -38,7 +33,7 @@ final class Cron(env: SystemEnv)(implicit app: Application) { } message(3 seconds) { - env.siteHub -> WithUsernames(env.userRepo.updateOnlineUsernames) + env.siteHub -> site.WithUsernames(env.userRepo.updateOnlineUsernames) } effect(2 hours) { @@ -53,6 +48,8 @@ final class Cron(env: SystemEnv)(implicit app: Application) { env.remoteAi.diagnose } + import RichDuration._ + def effect(freq: Duration)(op: IO[_]) { Akka.system.scheduler.schedule(freq, freq.randomize()) { op.unsafePerformIO } } diff --git a/app/Finisher.scala b/app/Finisher.scala index 31e91c2d01..426d982ebf 100644 --- a/app/Finisher.scala +++ b/app/Finisher.scala @@ -104,6 +104,4 @@ final class Finisher( _ ← historyRepo.addEntry(blackUser.usernameCanonical, blackElo, game.id) } yield () } | io() - - private def !!(msg: String) = failure(msg.wrapNel) } diff --git a/app/Hand.scala b/app/Hand.scala index 02ec89296c..9a77a5a39c 100644 --- a/app/Hand.scala +++ b/app/Hand.scala @@ -129,11 +129,17 @@ final class Hand( action: Pov ⇒ Valid[IO[A]]): IO[Valid[A]] = fromPov(ref) { pov ⇒ action(pov).sequence } - private def fromPov[A](fullId: String)(op: Pov ⇒ IO[A]): IO[A] = - gameRepo pov fullId flatMap op + private def fromPov[A](ref: PovRef)(op: Pov ⇒ IO[Valid[A]]): IO[Valid[A]] = + fromPov(gameRepo pov ref)(op) - private def fromPov[A](ref: PovRef)(op: Pov ⇒ IO[A]): IO[A] = - gameRepo pov ref flatMap op + private def fromPov[A](fullId: String)(op: Pov ⇒ IO[Valid[A]]): IO[Valid[A]] = + fromPov(gameRepo pov fullId)(op) - private def !!(msg: String) = failure(msg.wrapNel) + private def fromPov[A](povIO: IO[Option[Pov]])(op: Pov ⇒ IO[Valid[A]]): IO[Valid[A]] = + povIO flatMap { povOption ⇒ + povOption.fold( + pov ⇒ op(pov), + io { "No such game".failNel } + ) + } } diff --git a/app/SystemEnv.scala b/app/SystemEnv.scala index dd6b02fc83..07e318ac68 100644 --- a/app/SystemEnv.scala +++ b/app/SystemEnv.scala @@ -32,7 +32,7 @@ final class SystemEnv(config: Config) { makeHistory = gameHistory) lazy val gameSocket = new game.Socket( - getGame = gameRepo.gameOption, + getGame = gameRepo.game, hand = hand, hubMemo = gameHubMemo, messenger = messenger) diff --git a/app/controllers/AppApiC.scala b/app/controllers/AppApiC.scala index b291fddba5..9a96c1f10c 100644 --- a/app/controllers/AppApiC.scala +++ b/app/controllers/AppApiC.scala @@ -12,12 +12,14 @@ object AppApiC extends LilaController { def show(fullId: String) = Action { Async { - (api show fullId).asPromise map JsonIOk + (api show fullId).asPromise map { op ⇒ + op.unsafePerformIO.fold(e ⇒ BadRequest(e.shows), JsonOk) + } } } def reloadTable(gameId: String) = Action { - IOk(api reloadTable gameId) + ValidIOk(api reloadTable gameId) } def start(gameId: String) = Action { implicit request ⇒ diff --git a/app/controllers/LilaController.scala b/app/controllers/LilaController.scala index f206e79bee..e0212be3c6 100644 --- a/app/controllers/LilaController.scala +++ b/app/controllers/LilaController.scala @@ -28,12 +28,18 @@ trait LilaController extends Controller with ContentTypes with RequestGetter { _ ⇒ Ok("ok") ) - def FormValidIOk[A](form: Form[A])(op: A ⇒ IO[Unit])(implicit request: Request[_]) = + def FormIOk[A](form: Form[A])(op: A ⇒ IO[Unit])(implicit request: Request[_]) = form.bindFromRequest.fold( form ⇒ BadRequest(form.errors mkString "\n"), data ⇒ IOk(op(data)) ) + def FormValidIOk[A](form: Form[A])(op: A ⇒ IO[Valid[Unit]])(implicit request: Request[_]) = + form.bindFromRequest.fold( + form ⇒ BadRequest(form.errors mkString "\n"), + data ⇒ ValidIOk(op(data)) + ) + def IOk(op: IO[Unit]) = Ok(op.unsafePerformIO) // I like Unit requests. diff --git a/app/db/GameRepo.scala b/app/db/GameRepo.scala index d3b7602c87..afb23bab8c 100644 --- a/app/db/GameRepo.scala +++ b/app/db/GameRepo.scala @@ -19,37 +19,30 @@ import scalaz.effects._ class GameRepo(collection: MongoCollection) extends SalatDAO[RawDbGame, String](collection) { - def game(gameId: String): IO[DbGame] = io { - if (gameId.size != gameIdSize) - throw new Exception("Invalid game id " + gameId) - findOneByID(gameId) flatMap decode err "No game found for id " + gameId - } - - def gameOption(gameId: String): IO[Option[DbGame]] = io { + def game(gameId: String): IO[Option[DbGame]] = io { if (gameId.size != gameIdSize) None else findOneByID(gameId) flatMap decode } - def pov(ref: PovRef): IO[Pov] = pov(ref.gameId, ref.color) - - def pov(gameId: String, color: Color): IO[Pov] = - game(gameId) map { g ⇒ Pov(g, g player color) } - - def player(gameId: String, color: Color): IO[DbPlayer] = - game(gameId) map { g ⇒ g player color } - - def pov(fullId: String): IO[Pov] = - game(fullId take gameIdSize) map { g ⇒ - val playerId = fullId drop gameIdSize - val player = g player playerId err "No player found for id " + fullId - Pov(g, player) + def player(gameId: String, color: Color): IO[Option[DbPlayer]] = + game(gameId) map { gameOption ⇒ + gameOption map { _ player color } } - def povOption(gameId: String, color: Color): IO[Option[Pov]] = - gameOption(gameId) map { gOption ⇒ - gOption map { g ⇒ Pov(g, g player color) } + def pov(gameId: String, color: Color): IO[Option[Pov]] = + game(gameId) map { gameOption ⇒ + gameOption map { g ⇒ Pov(g, g player color) } } + def pov(fullId: String): IO[Option[Pov]] = + game(fullId take gameIdSize) map { gameOption ⇒ + gameOption flatMap { g ⇒ + g player (fullId drop gameIdSize) map { Pov(g, _) } + } + } + + def pov(ref: PovRef): IO[Option[Pov]] = pov(ref.gameId, ref.color) + def save(game: DbGame): IO[Unit] = io { update(DBObject("_id" -> game.id), _grater asDBObject encode(game)) } diff --git a/app/db/MessageRepo.scala b/app/db/MessageRepo.scala index bb7e605fea..ea4b6f654a 100644 --- a/app/db/MessageRepo.scala +++ b/app/db/MessageRepo.scala @@ -36,6 +36,4 @@ extends CappedRepo[Message](collection, max) { def encode(obj: Message): DBObject = DBObject( "u" -> obj.username, "t" -> obj.text) - - private def !!(msg: String) = failure(msg.wrapNel) } diff --git a/app/lobby/Api.scala b/app/lobby/Api.scala index 1a50dddb59..088f6f7e0e 100644 --- a/app/lobby/Api.scala +++ b/app/lobby/Api.scala @@ -27,21 +27,28 @@ final class Api( entryData: String, messageString: String, hookOwnerId: String, - myHookOwnerId: Option[String]): IO[Unit] = for { + myHookOwnerId: Option[String]): IO[Valid[Unit]] = for { hook ← hookRepo ownedHook hookOwnerId - color ← ioColor(colorName) - game ← gameRepo game gameId - p1 ← starter.start(game, entryData) - p2 ← messenger.systemMessages(game, messageString) map p1.++ - _ ← gameRepo save p2 - _ ← gameSocket send p2 - _ ← hook.fold(h ⇒ fisherman.bite(h, p2.game), io()) - _ ← myHookOwnerId.fold( - ownerId ⇒ hookRepo ownedHook ownerId flatMap { myHook ⇒ - myHook.fold(fisherman.delete, io()) + gameOption ← gameRepo game gameId + result ← (Color(colorName) |@| gameOption).tupled.fold( + colorGame ⇒ { + val (color, game) = colorGame + for { + p1 ← starter.start(game, entryData) + p2 ← messenger.systemMessages(game, messageString) map p1.++ + _ ← gameRepo save p2 + _ ← gameSocket send p2 + _ ← hook.fold(h ⇒ fisherman.bite(h, p2.game), io()) + _ ← myHookOwnerId.fold( + ownerId ⇒ hookRepo ownedHook ownerId flatMap { myHook ⇒ + myHook.fold(fisherman.delete, io()) + }, + io()) + } yield success() }, - io()) - } yield () + io(GameNotFound) + ) + } yield result def create(hookOwnerId: String): IO[Unit] = for { hook ← hookRepo ownedHook hookOwnerId diff --git a/app/lobby/Preload.scala b/app/lobby/Preload.scala index b7924923ef..8f71d887b6 100644 --- a/app/lobby/Preload.scala +++ b/app/lobby/Preload.scala @@ -32,7 +32,7 @@ final class Preload( myHook: Option[Hook], std: () ⇒ IO[Response]): IO[Response] = myHook.fold( h ⇒ h.gameId.fold( - ref ⇒ gameRepo gameOption ref map { game ⇒ + ref ⇒ gameRepo game ref map { game ⇒ game.fold( g ⇒ redirect(g fullIdOf g.creatorColor), redirect() diff --git a/app/package.scala b/app/package.scala index 5e3deaea52..8f019e4321 100644 --- a/app/package.scala +++ b/app/package.scala @@ -31,6 +31,10 @@ package object lila override val typeHintStrategy = StringTypeHintStrategy(when = TypeHintFrequency.Never) } + def !!(msg: String) = msg.failNel + + val GameNotFound = !!("Game not found") + implicit def addPP[A](a: A) = new { def pp[A] = a ~ println } diff --git a/chess/src/main/scala/ReverseEngineering.scala b/chess/src/main/scala/ReverseEngineering.scala index 32ed555c6f..c7df3726d8 100644 --- a/chess/src/main/scala/ReverseEngineering.scala +++ b/chess/src/main/scala/ReverseEngineering.scala @@ -46,5 +46,5 @@ final class ReverseEngineering(fromGame: Game, to: Board) { } toList } - private def !!(msg: String) = failure(msg.wrapNel) + private def !!(msg: String) = msg.failNel }