Lobby timeline and messages, ping hook id
parent
9dac236e3d
commit
875516529b
|
@ -4,8 +4,6 @@ import lila.system.model._
|
|||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
|
||||
import lila.system.model.Hook
|
||||
|
||||
object DataForm {
|
||||
|
||||
type MoveData = (String, String, Option[String], Option[Int])
|
||||
|
@ -22,20 +20,23 @@ object DataForm {
|
|||
"message" -> nonEmptyText
|
||||
))
|
||||
|
||||
val entryGameForm = Form(entryGameMapping)
|
||||
type EntryData = String
|
||||
val entryForm = Form(single(
|
||||
"entry" -> nonEmptyText
|
||||
))
|
||||
|
||||
type JoinData = (String, String, EntryGame)
|
||||
type JoinData = (String, String, EntryData)
|
||||
val joinForm = Form(tuple(
|
||||
"redirect" -> nonEmptyText,
|
||||
"messages" -> nonEmptyText,
|
||||
"entry" -> entryGameMapping
|
||||
"entry" -> nonEmptyText
|
||||
))
|
||||
|
||||
type RematchData = (String, String, EntryGame)
|
||||
type RematchData = (String, String, EntryData)
|
||||
val rematchForm = Form(tuple(
|
||||
"whiteRedirect" -> nonEmptyText,
|
||||
"blackRedirect" -> nonEmptyText,
|
||||
"entry" -> entryGameMapping
|
||||
"entry" -> nonEmptyText
|
||||
))
|
||||
|
||||
private type MessagesData = String
|
||||
|
@ -48,15 +49,4 @@ 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,6 +1,5 @@
|
|||
package controllers
|
||||
|
||||
import lila.system.model.EntryGame
|
||||
import lila.http._
|
||||
import DataForm._
|
||||
|
||||
|
@ -39,8 +38,8 @@ 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 start(gameId: String) = Action { implicit request =>
|
||||
ValidIOk[EntryData](entryForm)(entryData ⇒ api.start(gameId, entryData))
|
||||
}
|
||||
|
||||
def join(fullId: String) = Action { implicit request ⇒
|
||||
|
|
|
@ -27,10 +27,11 @@ object AppXhrC extends LilaController {
|
|||
|
||||
def ping() = Action { implicit request =>
|
||||
JsonOk(env.pinger.ping(
|
||||
get("username"),
|
||||
get("player_key"),
|
||||
get("watcher"),
|
||||
get("get_nb_watchers")
|
||||
username = get("username"),
|
||||
playerKey = get("player_key"),
|
||||
watcherKey = get("watcher"),
|
||||
getNbWatchers = get("get_nb_watchers"),
|
||||
hookId = get("hook_id")
|
||||
).unsafePerformIO)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package controllers
|
||||
|
||||
import lila.system.model.{ Hook, EntryGame }
|
||||
import lila.system.model.Hook
|
||||
import lila.http._
|
||||
import DataForm._
|
||||
|
||||
|
@ -12,7 +12,7 @@ object LobbyApiC extends LilaController {
|
|||
private val api = env.lobbyApi
|
||||
|
||||
def join(gameId: String, color: String) = Action { implicit request ⇒
|
||||
ValidIOk[EntryGame](entryGameForm)(ec ⇒ api.join(gameId, color, ec))
|
||||
ValidIOk[EntryData](entryForm)(entry ⇒ api.join(gameId, color, entry))
|
||||
}
|
||||
|
||||
def create(hookOwnerId: String) = Action {
|
||||
|
@ -26,4 +26,8 @@ object LobbyApiC extends LilaController {
|
|||
def alive(hookOwnerId: String) = Action {
|
||||
IOk(api.alive(hookOwnerId))
|
||||
}
|
||||
|
||||
def message = Action {
|
||||
IOk(api.messageRefresh)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ object LobbyXhrC extends LilaController {
|
|||
hookId,
|
||||
getIntOr("auth", 0) == 1,
|
||||
getIntOr("state", 0),
|
||||
getIntOr("messageId", 0),
|
||||
getIntOr("entryId", 0)
|
||||
).unsafePerformIO)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ sealed trait Clock {
|
|||
|
||||
def limitInMinutes = limitInSeconds / 60
|
||||
|
||||
def incrementInSeconds = increment / 1000
|
||||
|
||||
def estimateTotalTime = limit + 30 * increment
|
||||
|
||||
def step: RunningClock
|
||||
|
|
|
@ -7,6 +7,7 @@ mongo {
|
|||
user = user
|
||||
hook = hook
|
||||
entry = lobby_entry
|
||||
message = lobby_message
|
||||
}
|
||||
}
|
||||
redis {
|
||||
|
@ -22,8 +23,9 @@ lobby {
|
|||
duration = 10 seconds
|
||||
#duration = 2 seconds
|
||||
sleep = 250 milliseconds
|
||||
max_entries = 12
|
||||
}
|
||||
message.max = 30
|
||||
entry.max = 12
|
||||
}
|
||||
memo {
|
||||
version.timeout = 30 minutes
|
||||
|
|
|
@ -11,7 +11,7 @@ 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/start/:gameId controllers.AppApiC.start(gameId: String)
|
||||
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)
|
||||
|
@ -34,6 +34,7 @@ GET /api/lobby/preload controllers.LobbyXhrC.syncWithoutHook
|
|||
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)
|
||||
POST /api/lobby/message controllers.LobbyApiC.message
|
||||
|
||||
# Useless, but play2 needs it
|
||||
GET /assets/*file controllers.Assets.at(path="/public", file)
|
||||
|
|
|
@ -10,19 +10,19 @@ case class AppApi(
|
|||
gameRepo: GameRepo,
|
||||
versionMemo: VersionMemo,
|
||||
aliveMemo: AliveMemo,
|
||||
addEntry: EntryGame ⇒ IO[Unit]) extends IOTools {
|
||||
addEntry: (DbGame, String) ⇒ IO[Unit]) extends IOTools {
|
||||
|
||||
def join(
|
||||
fullId: String,
|
||||
url: String,
|
||||
messages: String,
|
||||
entryGame: EntryGame): IO[Unit] = for {
|
||||
entryData: String): 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)
|
||||
_ ← addEntry(g3, entryData)
|
||||
} yield ()
|
||||
|
||||
def talk(gameId: String, author: String, message: String): IO[Unit] = for {
|
||||
|
@ -37,7 +37,10 @@ case class AppApi(
|
|||
_ ← save(g1, g2)
|
||||
} yield ()
|
||||
|
||||
def start(entryGame: EntryGame): IO[Unit] = addEntry(entryGame)
|
||||
def start(gameId: String, entryData: String): IO[Unit] = for {
|
||||
game ← gameRepo game gameId
|
||||
_ ← addEntry(game, entryData)
|
||||
} yield ()
|
||||
|
||||
def acceptRematch(
|
||||
gameId: String,
|
||||
|
@ -45,7 +48,7 @@ case class AppApi(
|
|||
colorName: String,
|
||||
whiteRedirect: String,
|
||||
blackRedirect: String,
|
||||
entryGame: EntryGame): IO[Unit] = for {
|
||||
entryData: String): IO[Unit] = for {
|
||||
color ← ioColor(colorName)
|
||||
g1 ← gameRepo game gameId
|
||||
g2 = g1.withEvents(
|
||||
|
@ -54,7 +57,7 @@ case class AppApi(
|
|||
_ ← save(g1, g2)
|
||||
_ ← aliveMemo.put(newGameId, !color)
|
||||
_ ← aliveMemo.transfer(gameId, !color, newGameId, color)
|
||||
_ ← addEntry(entryGame)
|
||||
_ ← addEntry(g2, entryData)
|
||||
} yield ()
|
||||
|
||||
def updateVersion(gameId: String): IO[Unit] =
|
||||
|
|
|
@ -42,7 +42,7 @@ final class AppSyncer(
|
|||
) filterValues (null !=)
|
||||
} getOrElse failMap
|
||||
}
|
||||
} except (e ⇒ {println(e.getMessage);io(failMap)})
|
||||
} except (e ⇒ io(failMap))
|
||||
|
||||
private def renderEvents(events: List[Event], isPrivate: Boolean) =
|
||||
if (isPrivate) events map {
|
||||
|
|
|
@ -10,6 +10,7 @@ case class LobbyApi(
|
|||
gameRepo: GameRepo,
|
||||
entryRepo: EntryRepo,
|
||||
lobbyMemo: LobbyMemo,
|
||||
messageMemo: MessageMemo,
|
||||
entryMemo: EntryMemo,
|
||||
versionMemo: VersionMemo,
|
||||
aliveMemo: AliveMemo,
|
||||
|
@ -18,13 +19,13 @@ case class LobbyApi(
|
|||
def join(
|
||||
gameId: String,
|
||||
colorName: String,
|
||||
entryGame: EntryGame): IO[Unit] = for {
|
||||
entryData: String): IO[Unit] = for {
|
||||
color ← ioColor(colorName)
|
||||
g1 ← gameRepo game gameId
|
||||
game ← gameRepo game gameId
|
||||
_ ← aliveMemo.put(gameId, color)
|
||||
_ ← aliveMemo.put(gameId, !color)
|
||||
_ ← versionInc
|
||||
_ ← addEntry(entryGame)
|
||||
_ ← addEntry(game, entryData)
|
||||
} yield ()
|
||||
|
||||
def create(hookOwnerId: String): IO[Unit] = for {
|
||||
|
@ -39,10 +40,13 @@ case class LobbyApi(
|
|||
|
||||
def alive(hookOwnerId: String): IO[Unit] = hookMemo put hookOwnerId
|
||||
|
||||
def messageRefresh: IO[Unit] = messageMemo.refresh
|
||||
|
||||
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 ()
|
||||
private[system] def addEntry(game: DbGame, data: String): IO[Unit] =
|
||||
Entry.build(game, data).fold(
|
||||
f ⇒ (entryMemo++) map (id ⇒ entryRepo insert f(id)),
|
||||
io()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,17 +7,19 @@ import scalaz.effects._
|
|||
import scalaz.NonEmptyList
|
||||
import scala.annotation.tailrec
|
||||
import scala.math.max
|
||||
import org.apache.commons.lang3.StringEscapeUtils.escapeXml
|
||||
|
||||
final class LobbySyncer(
|
||||
hookRepo: HookRepo,
|
||||
gameRepo: GameRepo,
|
||||
messageRepo: MessageRepo,
|
||||
entryRepo: EntryRepo,
|
||||
lobbyMemo: LobbyMemo,
|
||||
hookMemo: HookMemo,
|
||||
messageMemo: MessageMemo,
|
||||
entryMemo: EntryMemo,
|
||||
duration: Int,
|
||||
sleep: Int,
|
||||
maxEntries: Int) {
|
||||
sleep: Int) {
|
||||
|
||||
type Response = Map[String, Any]
|
||||
|
||||
|
@ -25,12 +27,13 @@ final class LobbySyncer(
|
|||
myHookId: Option[String],
|
||||
auth: Boolean,
|
||||
version: Int,
|
||||
messageId: Int,
|
||||
entryId: Int): IO[Response] = for {
|
||||
_ ← myHookId.fold(hookMemo.put, io())
|
||||
newVersion ← wait(version, entryId)
|
||||
newVersion ← wait(version, messageId, entryId)
|
||||
hooks ← if (auth) hookRepo.allOpen else hookRepo.allOpenCasual
|
||||
res ← {
|
||||
val response = () ⇒ stdResponse(newVersion, hooks, myHookId, entryId)
|
||||
val response = () ⇒ stdResponse(newVersion, hooks, myHookId, messageId, entryId)
|
||||
myHookId some { hookResponse(_, response) } none response()
|
||||
}
|
||||
} yield res
|
||||
|
@ -39,7 +42,7 @@ final class LobbySyncer(
|
|||
hookRepo ownedHook myHookId flatMap { hookOption ⇒
|
||||
hookOption.fold(
|
||||
hook ⇒ hook.game.fold(
|
||||
ref ⇒ gameRepo game ref.getId.toString.pp map { game ⇒
|
||||
ref ⇒ gameRepo game ref.getId.toString map { game ⇒
|
||||
Map("redirect" -> (game fullIdOf game.creatorColor))
|
||||
},
|
||||
response()
|
||||
|
@ -52,27 +55,43 @@ final class LobbySyncer(
|
|||
version: Int,
|
||||
hooks: List[Hook],
|
||||
myHookId: Option[String],
|
||||
messageId: Int,
|
||||
entryId: Int): IO[Response] = for {
|
||||
entries ←
|
||||
if (entryId == 0) entryRepo recent maxEntries
|
||||
else entryRepo since max(entryMemo.id - maxEntries, entryId)
|
||||
messages ← if (messageId == 0) messageRepo.recent
|
||||
else messageRepo since max(messageMemo.id - messageRepo.max, messageId)
|
||||
entries ← if (entryId == 0) entryRepo.recent
|
||||
else entryRepo since max(entryMemo.id - entryRepo.max, 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,
|
||||
"chat" -> messages.toNel.fold(
|
||||
renderMessages,
|
||||
Map("id" -> messageId, "messages" -> Nil)
|
||||
),
|
||||
"timeline" -> entries.toNel.fold(
|
||||
renderTimeline,
|
||||
renderEntries,
|
||||
Map("id" -> entryId, "entries" -> Nil)
|
||||
)
|
||||
)
|
||||
|
||||
private def renderTimeline(entries: NonEmptyList[Entry]) = Map(
|
||||
private def renderMessages(messages: NonEmptyList[Message]) = Map(
|
||||
"id" -> messages.head.id,
|
||||
"messages" -> (messages.list.reverse map { message ⇒
|
||||
Map(
|
||||
"id" -> message.id,
|
||||
"u" -> message.username,
|
||||
"m" -> escapeXml(message.message)
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
private def renderEntries(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(
|
||||
"entries" -> (entries.list.reverse map { entry ⇒
|
||||
"<td>%s</td><td>%s</td><td class='trans_me'>%s</td><td class='trans_me'>%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(
|
||||
|
@ -95,11 +114,12 @@ final class LobbySyncer(
|
|||
}
|
||||
}
|
||||
|
||||
private def wait(version: Int, entryId: Int): IO[Int] = io {
|
||||
private def wait(version: Int, messageId: Int, entryId: Int): IO[Int] = io {
|
||||
@tailrec
|
||||
def wait(loop: Int): Int = {
|
||||
if (loop == 0 ||
|
||||
lobbyMemo.version != version ||
|
||||
messageMemo.id != messageId ||
|
||||
entryMemo.id != entryId) lobbyMemo.version
|
||||
else { Thread sleep sleep; wait(loop - 1) }
|
||||
}
|
||||
|
|
|
@ -7,16 +7,19 @@ import scalaz.effects._
|
|||
final class Pinger(
|
||||
aliveMemo: AliveMemo,
|
||||
usernameMemo: UsernameMemo,
|
||||
watcherMemo: WatcherMemo) {
|
||||
watcherMemo: WatcherMemo,
|
||||
hookMemo: HookMemo) {
|
||||
|
||||
def ping(
|
||||
username: Option[String],
|
||||
playerKey: Option[String],
|
||||
watcherKey: Option[String],
|
||||
getNbWatchers: Option[String]): IO[Map[String, Any]] = for {
|
||||
getNbWatchers: Option[String],
|
||||
hookId: Option[String]): IO[Map[String, Any]] = for {
|
||||
_ ← optionIO(playerKey, aliveMemo.put)
|
||||
_ ← optionIO(username, usernameMemo.put)
|
||||
_ ← optionIO(watcherKey, watcherMemo.put)
|
||||
_ ← optionIO(hookId, hookMemo.put)
|
||||
} yield flatten(Map(
|
||||
"nbp" -> Some(aliveMemo.count),
|
||||
"nbw" -> (getNbWatchers map watcherMemo.count)
|
||||
|
|
|
@ -39,6 +39,7 @@ final class SystemEnv(config: Config) {
|
|||
entryRepo = entryRepo,
|
||||
versionMemo = versionMemo,
|
||||
lobbyMemo = lobbyMemo,
|
||||
messageMemo = messageMemo,
|
||||
entryMemo = entryMemo,
|
||||
aliveMemo = aliveMemo,
|
||||
hookMemo = hookMemo)
|
||||
|
@ -46,18 +47,20 @@ final class SystemEnv(config: Config) {
|
|||
lazy val lobbySyncer = new LobbySyncer(
|
||||
hookRepo = hookRepo,
|
||||
gameRepo = gameRepo,
|
||||
messageRepo = messageRepo,
|
||||
entryRepo = entryRepo,
|
||||
lobbyMemo = lobbyMemo,
|
||||
hookMemo = hookMemo,
|
||||
messageMemo = messageMemo,
|
||||
entryMemo = entryMemo,
|
||||
duration = getMilliseconds("lobby.sync.duration"),
|
||||
sleep = getMilliseconds("lobby.sync.sleep"),
|
||||
maxEntries = config getInt "lobby.sync.max_entries")
|
||||
sleep = getMilliseconds("lobby.sync.sleep"))
|
||||
|
||||
lazy val pinger = new Pinger(
|
||||
aliveMemo = aliveMemo,
|
||||
usernameMemo = usernameMemo,
|
||||
watcherMemo = watcherMemo)
|
||||
watcherMemo = watcherMemo,
|
||||
hookMemo = hookMemo)
|
||||
|
||||
lazy val ai: Ai = craftyAi
|
||||
|
||||
|
@ -75,7 +78,12 @@ final class SystemEnv(config: Config) {
|
|||
mongodb(config getString "mongo.collection.hook"))
|
||||
|
||||
lazy val entryRepo = new EntryRepo(
|
||||
mongodb(config getString "mongo.collection.entry"))
|
||||
collection = mongodb(config getString "mongo.collection.entry"),
|
||||
max = config getInt "lobby.entry.max")
|
||||
|
||||
lazy val messageRepo = new MessageRepo(
|
||||
collection = mongodb(config getString "mongo.collection.message"),
|
||||
max = config getInt "lobby.message.max")
|
||||
|
||||
lazy val mongodb = MongoConnection(
|
||||
config getString "mongo.host",
|
||||
|
@ -104,6 +112,9 @@ final class SystemEnv(config: Config) {
|
|||
lazy val entryMemo = new EntryMemo(
|
||||
getId = entryRepo.lastId)
|
||||
|
||||
lazy val messageMemo = new MessageMemo(
|
||||
getId = messageRepo.lastId)
|
||||
|
||||
def getMilliseconds(name: String): Int = (config getMilliseconds name).toInt
|
||||
}
|
||||
|
||||
|
|
|
@ -3,31 +3,7 @@ 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
|
||||
}
|
||||
}
|
||||
class EntryRepo(collection: MongoCollection, val max: Int)
|
||||
extends TimelineRepo[Entry](collection, max)
|
||||
|
|
|
@ -70,7 +70,7 @@ class GameRepo(collection: MongoCollection)
|
|||
d("clock.timer", _.clock.get.timer)
|
||||
}
|
||||
|
||||
MongoDBObject("$set" -> builder.result.pp)
|
||||
MongoDBObject("$set" -> builder.result)
|
||||
}
|
||||
|
||||
def insert(game: DbGame): IO[Option[String]] = io {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package lila.system
|
||||
package db
|
||||
|
||||
import model.Message
|
||||
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
|
||||
class MessageRepo(collection: MongoCollection, val max: Int)
|
||||
extends TimelineRepo[Message](collection, max)
|
|
@ -0,0 +1,32 @@
|
|||
package lila.system
|
||||
package db
|
||||
|
||||
import model.Message
|
||||
|
||||
import com.novus.salat._
|
||||
import com.novus.salat.dao._
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
import com.mongodb.casbah.Imports._
|
||||
import scalaz.effects._
|
||||
|
||||
abstract class TimelineRepo[A <: AnyRef](collection: MongoCollection, max: Int)(implicit m: Manifest[A]) extends SalatDAO[A, Int](collection) {
|
||||
|
||||
val idSelector = DBObject("_id" -> true)
|
||||
val idSorter = DBObject("_id" -> -1)
|
||||
|
||||
val lastId: () ⇒ IO[Option[Int]] = () ⇒ io {
|
||||
collection.find(DBObject(), idSelector)
|
||||
.sort(idSorter)
|
||||
.limit(1)
|
||||
.next()
|
||||
.getAs[Int]("_id")
|
||||
}
|
||||
|
||||
val recent = io {
|
||||
find(DBObject()).sort(idSorter).limit(max).toList
|
||||
}
|
||||
|
||||
def since(id: Int): IO[List[A]] = io {
|
||||
find("_id" $gt id).sort(idSorter).toList
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ import com.mongodb.casbah.MongoCollection
|
|||
import com.mongodb.casbah.Imports._
|
||||
import scalaz.effects._
|
||||
|
||||
final class EntryMemo(getId: () => IO[Option[Int]]) {
|
||||
final class EntryMemo(getId: () ⇒ IO[Option[Int]]) {
|
||||
|
||||
private var privateId: Int = _
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package lila.system
|
||||
package memo
|
||||
|
||||
import com.mongodb.casbah.MongoCollection
|
||||
import com.mongodb.casbah.Imports._
|
||||
import scalaz.effects._
|
||||
|
||||
final class MessageMemo(getId: () ⇒ IO[Option[Int]]) {
|
||||
|
||||
private var privateId: Int = _
|
||||
|
||||
refresh.unsafePerformIO
|
||||
|
||||
def refresh = for {
|
||||
idOption ← getId()
|
||||
} yield {
|
||||
privateId = idOption err "No last message found"
|
||||
}
|
||||
|
||||
def ++ : IO[Int] = io {
|
||||
privateId = privateId + 1
|
||||
privateId
|
||||
}
|
||||
|
||||
def id: Int = privateId
|
||||
}
|
|
@ -15,4 +15,36 @@ case class EntryGame(
|
|||
players: List[EntryPlayer],
|
||||
variant: String,
|
||||
rated: Boolean,
|
||||
clock: List[Int] = Nil)
|
||||
clock: Option[List[Int]])
|
||||
|
||||
object Entry {
|
||||
|
||||
def build(game: DbGame, encodedData: String): Option[Int ⇒ Entry] =
|
||||
encodedData.split('$').toList match {
|
||||
case wu :: wue :: bu :: bue :: Nil ⇒ Some(
|
||||
(id: Int) ⇒
|
||||
new Entry(
|
||||
id = id,
|
||||
EntryGame(
|
||||
id = game.id,
|
||||
players = List(
|
||||
EntryPlayer(
|
||||
u = wu.some filterNot (_.isEmpty),
|
||||
ue = wue
|
||||
),
|
||||
EntryPlayer(
|
||||
u = bu.some filterNot (_.isEmpty),
|
||||
ue = bue
|
||||
)
|
||||
),
|
||||
variant = game.variant.name,
|
||||
rated = game.isRated,
|
||||
clock = game.clock map { c ⇒
|
||||
List(c.limitInMinutes, c.incrementInSeconds)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
case _ ⇒ None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package lila.system
|
||||
package model
|
||||
|
||||
import com.novus.salat.annotations._
|
||||
|
||||
case class Message(
|
||||
@Key("_id") id: Int,
|
||||
username: String,
|
||||
message: String) {
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
package lila.system
|
||||
package model
|
||||
|
||||
sealed abstract class Variant(val id: Int)
|
||||
sealed abstract class Variant(val id: Int) {
|
||||
|
||||
lazy val name = toString.toLowerCase
|
||||
}
|
||||
|
||||
case object Standard extends Variant(1)
|
||||
case object Chess960 extends Variant(2)
|
||||
|
|
Loading…
Reference in New Issue