Lot of work just got done
parent
75f416f6aa
commit
9dac236e3d
|
@ -15,6 +15,17 @@ final class Cron(env: SystemEnv)(implicit app: Application) {
|
|||
env.userRepo updateOnlineUsernames env.usernameMemo.keys
|
||||
}
|
||||
|
||||
spawn("hook_cleanup_dead") { env ⇒
|
||||
for {
|
||||
hasRemoved ← env.hookRepo keepOnlyIds env.hookMemo.keys
|
||||
_ ← if (hasRemoved) env.lobbyMemo++ else io()
|
||||
} yield ()
|
||||
}
|
||||
|
||||
spawn("hook_cleanup_old") { env ⇒
|
||||
env.hookRepo.cleanupOld
|
||||
}
|
||||
|
||||
def spawn(name: String)(f: SystemEnv ⇒ IO[Unit]) = {
|
||||
val freq = env.getMilliseconds("cron.online_username.frequency") millis
|
||||
val actor = Akka.system.actorOf(Props(new Actor {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package lila.http
|
||||
|
||||
import lila.system.model._
|
||||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
|
||||
|
@ -21,16 +22,20 @@ object DataForm {
|
|||
"message" -> nonEmptyText
|
||||
))
|
||||
|
||||
type JoinData = (String, String)
|
||||
val entryGameForm = Form(entryGameMapping)
|
||||
|
||||
type JoinData = (String, String, EntryGame)
|
||||
val joinForm = Form(tuple(
|
||||
"redirect" -> nonEmptyText,
|
||||
"messages" -> nonEmptyText
|
||||
"messages" -> nonEmptyText,
|
||||
"entry" -> entryGameMapping
|
||||
))
|
||||
|
||||
type RematchData = (String, String)
|
||||
type RematchData = (String, String, EntryGame)
|
||||
val rematchForm = Form(tuple(
|
||||
"whiteRedirect" -> nonEmptyText,
|
||||
"blackRedirect" -> nonEmptyText
|
||||
"blackRedirect" -> nonEmptyText,
|
||||
"entry" -> entryGameMapping
|
||||
))
|
||||
|
||||
private type MessagesData = String
|
||||
|
@ -43,4 +48,15 @@ object DataForm {
|
|||
|
||||
type DrawData = MessagesData
|
||||
val drawForm = messagesForm
|
||||
|
||||
private val entryGameMapping = mapping(
|
||||
"id" -> nonEmptyText,
|
||||
"players" -> list(mapping(
|
||||
"u" -> optional(nonEmptyText),
|
||||
"ue" -> nonEmptyText
|
||||
)(EntryPlayer.apply)(EntryPlayer.unapply)),
|
||||
"variant" -> nonEmptyText,
|
||||
"rated" -> boolean,
|
||||
"clock" -> list(number)
|
||||
)(EntryGame.apply)(EntryGame.unapply)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package controllers
|
||||
|
||||
import lila.system.model.EntryGame
|
||||
import lila.http._
|
||||
import DataForm._
|
||||
|
||||
|
@ -26,11 +27,11 @@ object AppApiC extends LilaController {
|
|||
IOk(api.alive(gameId, color))
|
||||
}
|
||||
|
||||
def draw(gameId: String, color: String) = Action { implicit request =>
|
||||
def draw(gameId: String, color: String) = Action { implicit request ⇒
|
||||
ValidIOk[String](drawForm)(msgs ⇒ api.draw(gameId, color, msgs))
|
||||
}
|
||||
|
||||
def drawAccept(gameId: String, color: String) = Action { implicit request =>
|
||||
def drawAccept(gameId: String, color: String) = Action { implicit request ⇒
|
||||
ValidIOk[String](drawForm)(msgs ⇒ api.drawAccept(gameId, color, msgs))
|
||||
}
|
||||
|
||||
|
@ -38,16 +39,22 @@ object AppApiC extends LilaController {
|
|||
ValidIOk[String](endForm)(msgs ⇒ api.end(gameId, msgs))
|
||||
}
|
||||
|
||||
def start = Action { implicit request =>
|
||||
ValidIOk[EntryGame](entryGameForm)(entryGame ⇒ api.start(entryGame))
|
||||
}
|
||||
|
||||
def join(fullId: String) = Action { implicit request ⇒
|
||||
ValidIOk[JoinData](joinForm)(join ⇒ api.join(fullId, join._1, join._2))
|
||||
ValidIOk[JoinData](joinForm) { join ⇒
|
||||
api.join(fullId, join._1, join._2, join._3)
|
||||
}
|
||||
}
|
||||
|
||||
def activity(gameId: String, color: String) = Action {
|
||||
Ok(api.activity(gameId, color).toString)
|
||||
}
|
||||
|
||||
def acceptRematch(gameId: String, color: String, newGameId: String) = Action { implicit request ⇒
|
||||
ValidIOk[RematchData](rematchForm)(rematch ⇒
|
||||
api.acceptRematch(gameId, newGameId, color, rematch._1, rematch._2))
|
||||
def rematchAccept(gameId: String, color: String, newGameId: String) = Action { implicit request ⇒
|
||||
ValidIOk[RematchData](rematchForm)(r ⇒
|
||||
api.acceptRematch(gameId, newGameId, color, r._1, r._2, r._3))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package controllers
|
||||
|
||||
import lila.system.model.{ Hook, EntryGame }
|
||||
import lila.http._
|
||||
import lila.system.model.Hook
|
||||
import DataForm._
|
||||
|
||||
import play.api._
|
||||
|
@ -11,12 +11,8 @@ object LobbyApiC extends LilaController {
|
|||
|
||||
private val api = env.lobbyApi
|
||||
|
||||
def join(gameId: String, color: String) = Action {
|
||||
IOk(api.join(gameId, color))
|
||||
}
|
||||
|
||||
def inc = Action {
|
||||
IOk(api.inc)
|
||||
def join(gameId: String, color: String) = Action { implicit request ⇒
|
||||
ValidIOk[EntryGame](entryGameForm)(ec ⇒ api.join(gameId, color, ec))
|
||||
}
|
||||
|
||||
def create(hookOwnerId: String) = Action {
|
||||
|
@ -26,4 +22,8 @@ object LobbyApiC extends LilaController {
|
|||
def remove(hookId: String) = Action {
|
||||
IOk(api.remove(hookId))
|
||||
}
|
||||
|
||||
def alive(hookOwnerId: String) = Action {
|
||||
IOk(api.alive(hookOwnerId))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@ object LobbyXhrC extends LilaController {
|
|||
JsonOk(syncer.sync(
|
||||
hookId,
|
||||
getIntOr("auth", 0) == 1,
|
||||
getIntOr("state", 0)
|
||||
getIntOr("state", 0),
|
||||
getIntOr("entryId", 0)
|
||||
).unsafePerformIO)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ mongo {
|
|||
game = game2
|
||||
user = user
|
||||
hook = hook
|
||||
entry = lobby_entry
|
||||
}
|
||||
}
|
||||
redis {
|
||||
|
@ -19,7 +20,9 @@ sync {
|
|||
lobby {
|
||||
sync {
|
||||
duration = 10 seconds
|
||||
sleep = 300 milliseconds
|
||||
#duration = 2 seconds
|
||||
sleep = 250 milliseconds
|
||||
max_entries = 12
|
||||
}
|
||||
}
|
||||
memo {
|
||||
|
@ -36,6 +39,8 @@ crafty {
|
|||
}
|
||||
cron {
|
||||
online_username.frequency = 2 seconds
|
||||
hook_cleanup_dead.frequency = 2 seconds
|
||||
hook_cleanup_old.frequency = 21 seconds
|
||||
}
|
||||
akka {
|
||||
debug.receive = on
|
||||
|
|
|
@ -3,19 +3,20 @@
|
|||
# ~~~~
|
||||
|
||||
# App XHR
|
||||
POST /move/:fullId controllers.AppXhrC.move(fullId: String)
|
||||
GET /ping controllers.AppXhrC.ping
|
||||
GET /sync/:gameId/:color/:version controllers.AppXhrC.syncPublic(gameId: String, color: String, version: Int)
|
||||
GET /sync/:gameId/:color/:version/:fullId controllers.AppXhrC.sync(gameId: String, color: String, version: Int, fullId: String)
|
||||
GET /ping controllers.AppXhrC.ping
|
||||
GET /how-many-players-now controllers.AppXhrC.nbPlayers
|
||||
POST /move/:fullId controllers.AppXhrC.move(fullId: String)
|
||||
|
||||
# App Private API
|
||||
POST /api/update-version/:gameId controllers.AppApiC.updateVersion(gameId: String)
|
||||
POST /api/start controllers.AppApiC.start
|
||||
POST /api/end/:gameId controllers.AppApiC.end(gameId: String)
|
||||
POST /api/talk/:fullId controllers.AppApiC.talk(fullId: String)
|
||||
POST /api/join/:fullId controllers.AppApiC.join(fullId: String)
|
||||
POST /api/reload-table/:gameId controllers.AppApiC.reloadTable(gameId: String)
|
||||
POST /api/accept-rematch/:gameId/:color/:newGameId controllers.AppApiC.acceptRematch(gameId: String, color: String, newGameId: String)
|
||||
POST /api/rematch-accept/:gameId/:color/:newGameId controllers.AppApiC.rematchAccept(gameId: String, color: String, newGameId: String)
|
||||
POST /api/alive/:gameId/:color controllers.AppApiC.alive(gameId: String, color: String)
|
||||
POST /api/draw/:gameId/:color controllers.AppApiC.draw(gameId: String, color: String)
|
||||
POST /api/draw-accept/:gameId/:color controllers.AppApiC.drawAccept(gameId: String, color: String)
|
||||
|
@ -30,9 +31,9 @@ GET /lobby/sync controllers.LobbyXhrC.syncWithoutHook
|
|||
POST /api/lobby/join/:gameId/:color controllers.LobbyApiC.join(gameId: String, color: String)
|
||||
GET /api/lobby/preload/:hookId controllers.LobbyXhrC.syncWithHook(hookId: String)
|
||||
GET /api/lobby/preload controllers.LobbyXhrC.syncWithoutHook
|
||||
POST /api/lobby/inc controllers.LobbyApiC.inc
|
||||
POST /api/lobby/create/:hookOwnerId controllers.LobbyApiC.create(hookOwnerId: String)
|
||||
POST /api/lobby/remove/:hookId controllers.LobbyApiC.remove(hookId: String)
|
||||
POST /api/lobby/alive/:hookOwnerId controllers.LobbyApiC.alive(hookOwnerId: String)
|
||||
|
||||
# Useless, but play2 needs it
|
||||
GET /assets/*file controllers.Assets.at(path="/public", file)
|
||||
|
|
|
@ -7,7 +7,6 @@ trait Resolvers {
|
|||
val typesafe = "typesafe.com" at "http://repo.typesafe.com/typesafe/releases/"
|
||||
val iliaz = "iliaz.com" at "http://scala.iliaz.com/"
|
||||
val sonatype = "sonatype" at "http://oss.sonatype.org/content/repositories/releases"
|
||||
val novusS = "repo.novus snaps" at "http://repo.novus.com/snapshots/"
|
||||
}
|
||||
|
||||
trait Dependencies {
|
||||
|
@ -21,6 +20,9 @@ trait Dependencies {
|
|||
val json = "com.codahale" %% "jerkson" % "0.5.0"
|
||||
val guava = "com.google.guava" % "guava" % "11.0.2"
|
||||
val apache = "org.apache.commons" % "commons-lang3" % "3.1"
|
||||
val jodaTime = "joda-time" % "joda-time" % "2.0"
|
||||
val jodaConvert = "org.joda" % "joda-convert" % "1.2"
|
||||
val scalaTime = "org.scala-tools.time" %% "time" % "0.5"
|
||||
|
||||
// benchmark
|
||||
val instrumenter = "com.google.code.java-allocation-instrumenter" % "java-allocation-instrumenter" % "2.0"
|
||||
|
@ -33,7 +35,7 @@ object ApplicationBuild extends Build with Resolvers with Dependencies {
|
|||
organization := "com.github.ornicar",
|
||||
version := "0.1",
|
||||
scalaVersion := "2.9.1",
|
||||
resolvers := Seq(iliaz, codahale, sonatype, novusS, typesafe),
|
||||
resolvers := Seq(iliaz, codahale, sonatype, typesafe),
|
||||
libraryDependencies := Seq(scalalib),
|
||||
libraryDependencies in test := Seq(specs2),
|
||||
shellPrompt := {
|
||||
|
@ -54,7 +56,7 @@ object ApplicationBuild extends Build with Resolvers with Dependencies {
|
|||
) dependsOn (system)
|
||||
|
||||
lazy val system = Project("system", file("system"), settings = buildSettings).settings(
|
||||
libraryDependencies ++= Seq(scalaz, config, json, casbah, salat, guava, apache)
|
||||
libraryDependencies ++= Seq(scalaz, config, json, casbah, salat, guava, apache, jodaTime, jodaConvert, scalaTime)
|
||||
) dependsOn (chess)
|
||||
|
||||
lazy val chess = Project("chess", file("chess"), settings = buildSettings).settings(
|
||||
|
|
|
@ -9,14 +9,20 @@ import scalaz.effects._
|
|||
case class AppApi(
|
||||
gameRepo: GameRepo,
|
||||
versionMemo: VersionMemo,
|
||||
aliveMemo: AliveMemo) extends IOTools {
|
||||
aliveMemo: AliveMemo,
|
||||
addEntry: EntryGame ⇒ IO[Unit]) extends IOTools {
|
||||
|
||||
def join(fullId: String, url: String, messages: String): IO[Unit] = for {
|
||||
def join(
|
||||
fullId: String,
|
||||
url: String,
|
||||
messages: String,
|
||||
entryGame: EntryGame): IO[Unit] = for {
|
||||
gameAndPlayer ← gameRepo player fullId
|
||||
(g1, player) = gameAndPlayer
|
||||
g2 = g1 withEvents decodeMessages(messages)
|
||||
g3 = g2.withEvents(g2.opponent(player).color, List(RedirectEvent(url)))
|
||||
_ ← save(g1, g3)
|
||||
_ ← addEntry(entryGame)
|
||||
} yield ()
|
||||
|
||||
def talk(gameId: String, author: String, message: String): IO[Unit] = for {
|
||||
|
@ -31,12 +37,15 @@ case class AppApi(
|
|||
_ ← save(g1, g2)
|
||||
} yield ()
|
||||
|
||||
def start(entryGame: EntryGame): IO[Unit] = addEntry(entryGame)
|
||||
|
||||
def acceptRematch(
|
||||
gameId: String,
|
||||
newGameId: String,
|
||||
colorName: String,
|
||||
whiteRedirect: String,
|
||||
blackRedirect: String): IO[Unit] = for {
|
||||
blackRedirect: String,
|
||||
entryGame: EntryGame): IO[Unit] = for {
|
||||
color ← ioColor(colorName)
|
||||
g1 ← gameRepo game gameId
|
||||
g2 = g1.withEvents(
|
||||
|
@ -45,6 +54,7 @@ case class AppApi(
|
|||
_ ← save(g1, g2)
|
||||
_ ← aliveMemo.put(newGameId, !color)
|
||||
_ ← aliveMemo.transfer(gameId, !color, newGameId, color)
|
||||
_ ← addEntry(entryGame)
|
||||
} yield ()
|
||||
|
||||
def updateVersion(gameId: String): IO[Unit] =
|
||||
|
|
|
@ -2,34 +2,47 @@ package lila.system
|
|||
|
||||
import model._
|
||||
import memo._
|
||||
import db.{ GameRepo, HookRepo }
|
||||
import db._
|
||||
import scalaz.effects._
|
||||
|
||||
case class LobbyApi(
|
||||
hookRepo: HookRepo,
|
||||
lobbyMemo: LobbyMemo,
|
||||
versionMemo: VersionMemo,
|
||||
gameRepo: GameRepo,
|
||||
entryRepo: EntryRepo,
|
||||
lobbyMemo: LobbyMemo,
|
||||
entryMemo: EntryMemo,
|
||||
versionMemo: VersionMemo,
|
||||
aliveMemo: AliveMemo,
|
||||
hookMemo: HookMemo) extends IOTools {
|
||||
|
||||
def join(gameId: String, colorName: String): IO[Unit] = for {
|
||||
def join(
|
||||
gameId: String,
|
||||
colorName: String,
|
||||
entryGame: EntryGame): IO[Unit] = for {
|
||||
color ← ioColor(colorName)
|
||||
g1 ← gameRepo game gameId
|
||||
_ ← aliveMemo.put(gameId, color)
|
||||
_ ← aliveMemo.put(gameId, !color)
|
||||
_ ← (lobbyMemo++)
|
||||
_ ← versionInc
|
||||
_ ← addEntry(entryGame)
|
||||
} yield ()
|
||||
|
||||
def inc: IO[Unit] = lobbyMemo++
|
||||
|
||||
def create(hookOwnerId: String): IO[Unit] = for {
|
||||
_ ← (lobbyMemo++)
|
||||
_ ← versionInc
|
||||
_ ← hookMemo put hookOwnerId
|
||||
} yield ()
|
||||
|
||||
def remove(hookId: String): IO[Unit] = for {
|
||||
_ ← hookRepo removeId hookId
|
||||
_ ← (lobbyMemo++)
|
||||
_ ← versionInc
|
||||
} yield ()
|
||||
|
||||
def alive(hookOwnerId: String): IO[Unit] = hookMemo put hookOwnerId
|
||||
|
||||
private[system] def versionInc: IO[Int] = lobbyMemo++
|
||||
|
||||
private[system] def addEntry(entryGame: EntryGame): IO[Unit] = for {
|
||||
nextId ← (entryMemo++)
|
||||
_ ← io { entryRepo insert Entry(nextId, entryGame) }
|
||||
} yield ()
|
||||
}
|
||||
|
|
|
@ -4,38 +4,45 @@ import model._
|
|||
import memo._
|
||||
import db._
|
||||
import scalaz.effects._
|
||||
import scalaz.NonEmptyList
|
||||
import scala.annotation.tailrec
|
||||
import scala.math.max
|
||||
|
||||
final class LobbySyncer(
|
||||
hookRepo: HookRepo,
|
||||
gameRepo: GameRepo,
|
||||
entryRepo: EntryRepo,
|
||||
lobbyMemo: LobbyMemo,
|
||||
hookMemo: HookMemo,
|
||||
entryMemo: EntryMemo,
|
||||
duration: Int,
|
||||
sleep: Int) {
|
||||
sleep: Int,
|
||||
maxEntries: Int) {
|
||||
|
||||
type Response = Map[String, Any]
|
||||
|
||||
def sync(
|
||||
myHookId: Option[String],
|
||||
auth: Boolean,
|
||||
version: Int): IO[Response] = for {
|
||||
newVersion ← versionWait(version)
|
||||
version: Int,
|
||||
entryId: Int): IO[Response] = for {
|
||||
_ ← myHookId.fold(hookMemo.put, io())
|
||||
newVersion ← wait(version, entryId)
|
||||
hooks ← if (auth) hookRepo.allOpen else hookRepo.allOpenCasual
|
||||
response ← {
|
||||
val response = () ⇒ stdResponse(newVersion, hooks, myHookId)
|
||||
myHookId some { hookResponse(_, response) } none io { response() }
|
||||
res ← {
|
||||
val response = () ⇒ stdResponse(newVersion, hooks, myHookId, entryId)
|
||||
myHookId some { hookResponse(_, response) } none response()
|
||||
}
|
||||
} yield response
|
||||
} yield res
|
||||
|
||||
def hookResponse(myHookId: String, response: () ⇒ Response): IO[Response] =
|
||||
def hookResponse(myHookId: String, response: () ⇒ IO[Response]): IO[Response] =
|
||||
hookRepo ownedHook myHookId flatMap { hookOption ⇒
|
||||
hookOption.fold(
|
||||
hook ⇒ hook.game.fold(
|
||||
ref ⇒ gameRepo game ref.getId.toString.pp map { game ⇒
|
||||
Map("redirect" -> (game fullIdOf game.creatorColor))
|
||||
},
|
||||
io { response() }
|
||||
response()
|
||||
),
|
||||
io { Map("redirect" -> "") }
|
||||
)
|
||||
|
@ -44,30 +51,56 @@ final class LobbySyncer(
|
|||
def stdResponse(
|
||||
version: Int,
|
||||
hooks: List[Hook],
|
||||
myHookId: Option[String]): Response = Map(
|
||||
myHookId: Option[String],
|
||||
entryId: Int): IO[Response] = for {
|
||||
entries ←
|
||||
if (entryId == 0) entryRepo recent maxEntries
|
||||
else entryRepo since max(entryMemo.id - maxEntries, entryId)
|
||||
} yield Map(
|
||||
"state" -> version,
|
||||
"pool" -> {
|
||||
if (hooks.nonEmpty) Map("hooks" -> renderHooks(hooks, myHookId).toMap)
|
||||
else Map("message" -> "No game available right now, create one!")
|
||||
},
|
||||
"chat" -> null,
|
||||
"timeline" -> ""
|
||||
"timeline" -> entries.toNel.fold(
|
||||
renderTimeline,
|
||||
Map("id" -> entryId, "entries" -> Nil)
|
||||
)
|
||||
)
|
||||
|
||||
private def renderTimeline(entries: NonEmptyList[Entry]) = Map(
|
||||
"id" -> entries.head.id,
|
||||
"entries" -> (entries.list.reverse map { entry =>
|
||||
"<td>%s</td><td>%s</td><td class='trans_me'>%s</td><td>%s</td><td class='trans_me'>%s</td>".format(
|
||||
"<a class='watch' href='/%s'></a>" format entry.data.id,
|
||||
entry.data.players map { p ⇒
|
||||
p.u.fold(
|
||||
username ⇒ "<a class='user_link' href='/@/%s'>%s</a>".format(username, p.ue),
|
||||
p.ue)
|
||||
} mkString " vs ",
|
||||
entry.data.variant,
|
||||
entry.data.rated ? "Rated" | "Casual",
|
||||
entry.data.clock |> { c ⇒ if (c.empty) "Unlimited" else c mkString " + " }
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
private def renderHooks(hooks: List[Hook], myHookId: Option[String]) = for {
|
||||
hook ← hooks
|
||||
} yield hook.id -> {
|
||||
hook.render ++ {
|
||||
if (myHookId == Some(hook.ownerId))
|
||||
Map("action" -> "cancel", "id" -> myHookId)
|
||||
if (myHookId == Some(hook.ownerId)) Map("action" -> "cancel", "id" -> myHookId)
|
||||
else Map("action" -> "join", "id" -> hook.id)
|
||||
}
|
||||
}
|
||||
|
||||
private def versionWait(version: Int): IO[Int] = io {
|
||||
private def wait(version: Int, entryId: Int): IO[Int] = io {
|
||||
@tailrec
|
||||
def wait(loop: Int): Int = {
|
||||
if (loop == 0 || lobbyMemo.version != version) lobbyMemo.version
|
||||
if (loop == 0 ||
|
||||
lobbyMemo.version != version ||
|
||||
entryMemo.id != entryId) lobbyMemo.version
|
||||
else { Thread sleep sleep; wait(loop - 1) }
|
||||
}
|
||||
wait(max(1, duration / sleep))
|
||||
|
|
|
@ -18,7 +18,8 @@ final class SystemEnv(config: Config) {
|
|||
lazy val appApi = new AppApi(
|
||||
gameRepo = gameRepo,
|
||||
versionMemo = versionMemo,
|
||||
aliveMemo = aliveMemo)
|
||||
aliveMemo = aliveMemo,
|
||||
addEntry = lobbyApi.addEntry)
|
||||
|
||||
lazy val appSyncer = new AppSyncer(
|
||||
gameRepo = gameRepo,
|
||||
|
@ -34,18 +35,24 @@ final class SystemEnv(config: Config) {
|
|||
|
||||
lazy val lobbyApi = new LobbyApi(
|
||||
hookRepo = hookRepo,
|
||||
gameRepo = gameRepo,
|
||||
entryRepo = entryRepo,
|
||||
versionMemo = versionMemo,
|
||||
lobbyMemo = lobbyMemo,
|
||||
gameRepo = gameRepo,
|
||||
entryMemo = entryMemo,
|
||||
aliveMemo = aliveMemo,
|
||||
hookMemo = hookMemo)
|
||||
|
||||
lazy val lobbySyncer = new LobbySyncer(
|
||||
hookRepo = hookRepo,
|
||||
gameRepo = gameRepo,
|
||||
entryRepo = entryRepo,
|
||||
lobbyMemo = lobbyMemo,
|
||||
hookMemo = hookMemo,
|
||||
entryMemo = entryMemo,
|
||||
duration = getMilliseconds("lobby.sync.duration"),
|
||||
sleep = getMilliseconds("lobby.sync.sleep"))
|
||||
sleep = getMilliseconds("lobby.sync.sleep"),
|
||||
maxEntries = config getInt "lobby.sync.max_entries")
|
||||
|
||||
lazy val pinger = new Pinger(
|
||||
aliveMemo = aliveMemo,
|
||||
|
@ -58,7 +65,6 @@ final class SystemEnv(config: Config) {
|
|||
execPath = config getString "crafty.exec_path",
|
||||
bookPath = Some(config getString "crafty.book_path") filter ("" !=))
|
||||
|
||||
|
||||
lazy val gameRepo = new GameRepo(
|
||||
mongodb(config getString "mongo.collection.game"))
|
||||
|
||||
|
@ -68,6 +74,9 @@ final class SystemEnv(config: Config) {
|
|||
lazy val hookRepo = new HookRepo(
|
||||
mongodb(config getString "mongo.collection.hook"))
|
||||
|
||||
lazy val entryRepo = new EntryRepo(
|
||||
mongodb(config getString "mongo.collection.entry"))
|
||||
|
||||
lazy val mongodb = MongoConnection(
|
||||
config getString "mongo.host",
|
||||
config getInt "mongo.port"
|
||||
|
@ -92,6 +101,9 @@ final class SystemEnv(config: Config) {
|
|||
lazy val hookMemo = new HookMemo(
|
||||
timeout = getMilliseconds("memo.hook.timeout"))
|
||||
|
||||
lazy val entryMemo = new EntryMemo(
|
||||
getId = entryRepo.lastId)
|
||||
|
||||
def getMilliseconds(name: String): Int = (config getMilliseconds name).toInt
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package lila.system
|
||||
package db
|
||||
|
||||
import model.Entry
|
||||
|
||||
import com.novus.salat._
|
||||
import com.novus.salat.dao._
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
import com.mongodb.casbah.Imports._
|
||||
import scalaz.effects._
|
||||
|
||||
class EntryRepo(collection: MongoCollection)
|
||||
extends SalatDAO[Entry, String](collection) {
|
||||
|
||||
private val idSelector = DBObject("_id" -> true)
|
||||
private val idSorter = DBObject("_id" -> -1)
|
||||
|
||||
val lastId: () ⇒ IO[Option[Int]] = () ⇒ io {
|
||||
collection.find(DBObject(), idSelector)
|
||||
.sort(idSorter)
|
||||
.limit(1)
|
||||
.next()
|
||||
.getAs[Int]("_id")
|
||||
}
|
||||
|
||||
def recent(max: Int) = io {
|
||||
find(DBObject()).sort(idSorter).limit(max).toList
|
||||
}
|
||||
|
||||
def since(id: Int): IO[List[Entry]] = io {
|
||||
find("_id" $gt id).sort(idSorter).toList
|
||||
}
|
||||
}
|
|
@ -8,6 +8,8 @@ import com.novus.salat.dao._
|
|||
import com.mongodb.casbah.MongoCollection
|
||||
import com.mongodb.casbah.Imports._
|
||||
import scalaz.effects._
|
||||
import org.joda.time.DateTime
|
||||
import org.scala_tools.time.Imports._
|
||||
|
||||
class HookRepo(collection: MongoCollection)
|
||||
extends SalatDAO[Hook, String](collection) {
|
||||
|
@ -36,4 +38,38 @@ class HookRepo(collection: MongoCollection)
|
|||
def removeId(id: String): IO[Unit] = io {
|
||||
remove(DBObject("id" -> id))
|
||||
}
|
||||
|
||||
def keepOnlyIds(ids: Iterable[String]): IO[Boolean] = io {
|
||||
val removableIds = collection.find(
|
||||
("_id" $nin ids) ++ ("match" -> false),
|
||||
DBObject("_id" -> true)
|
||||
).toList
|
||||
if (removableIds.nonEmpty) {
|
||||
remove("_id" $in removableIds)
|
||||
true
|
||||
}
|
||||
else false
|
||||
}
|
||||
|
||||
def cleanupOld: IO[Unit] = io {
|
||||
remove("createdAt" $lt (DateTime.now - 1.hour))
|
||||
}
|
||||
|
||||
//public function removeDeadHooks()
|
||||
//{
|
||||
//if (0 == time()%10) {
|
||||
//$this->hookRepository->removeOldHooks();
|
||||
//}
|
||||
//$hooks = $this->hookRepository->findAllOpen();
|
||||
//$removed = false;
|
||||
//foreach ($hooks as $hook) {
|
||||
//if (!$this->memory->isAlive($hook)) {
|
||||
//$this->hookRepository->getDocumentManager()->remove($hook);
|
||||
//$removed = true;
|
||||
//}
|
||||
//}
|
||||
//if ($removed) {
|
||||
//$this->memory->incrementState();
|
||||
//}
|
||||
//}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package lila.system
|
||||
package memo
|
||||
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
import com.mongodb.casbah.Imports._
|
||||
import scalaz.effects._
|
||||
|
||||
final class EntryMemo(getId: () => IO[Option[Int]]) {
|
||||
|
||||
private var privateId: Int = _
|
||||
|
||||
refresh.unsafePerformIO
|
||||
|
||||
def refresh = for {
|
||||
idOption ← getId()
|
||||
} yield {
|
||||
privateId = idOption err "No last entry found"
|
||||
}
|
||||
|
||||
def ++ : IO[Int] = io {
|
||||
privateId = privateId + 1
|
||||
privateId
|
||||
}
|
||||
|
||||
def id: Int = privateId
|
||||
}
|
|
@ -9,7 +9,8 @@ final class LobbyMemo {
|
|||
|
||||
def version: Int = privateVersion
|
||||
|
||||
def ++ : IO[Unit] = io {
|
||||
def ++ : IO[Int] = io {
|
||||
privateVersion = privateVersion + 1
|
||||
privateVersion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package lila.system
|
||||
package model
|
||||
|
||||
import com.novus.salat.annotations._
|
||||
|
||||
case class Entry(
|
||||
@Key("_id") id: Int,
|
||||
data: EntryGame) {
|
||||
}
|
||||
|
||||
case class EntryPlayer(u: Option[String], ue: String)
|
||||
|
||||
case class EntryGame(
|
||||
id: String,
|
||||
players: List[EntryPlayer],
|
||||
variant: String,
|
||||
rated: Boolean,
|
||||
clock: List[Int] = Nil)
|
|
@ -3,14 +3,18 @@ package lila
|
|||
import ornicar.scalalib._
|
||||
|
||||
import com.novus.salat._
|
||||
import com.mongodb.casbah.commons.conversions.scala.RegisterJodaTimeConversionHelpers
|
||||
|
||||
package object system
|
||||
extends OrnicarValidation
|
||||
with OrnicarCommon
|
||||
with scalaz.NonEmptyLists
|
||||
with scalaz.Strings
|
||||
with scalaz.Lists
|
||||
with scalaz.Booleans {
|
||||
|
||||
RegisterJodaTimeConversionHelpers()
|
||||
|
||||
// custom salat context
|
||||
implicit val ctx = new Context {
|
||||
val name = "Lila System Context"
|
||||
|
|
Loading…
Reference in New Issue