Remove unsafe DB accesses to add monad crazyness

This commit is contained in:
Thibault Duplessis 2012-04-16 19:51:49 +02:00
parent 1dd772b5b3
commit e2379dbc22
13 changed files with 155 additions and 114 deletions

View file

@ -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 {

View file

@ -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 }
}

View file

@ -104,6 +104,4 @@ final class Finisher(
_ historyRepo.addEntry(blackUser.usernameCanonical, blackElo, game.id)
} yield ()
} | io()
private def !!(msg: String) = failure(msg.wrapNel)
}

View file

@ -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 }
)
}
}

View file

@ -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)

View file

@ -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

View file

@ -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.

View file

@ -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))
}

View file

@ -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)
}

View file

@ -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

View file

@ -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()

View file

@ -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
}

View file

@ -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
}