From 786d395045cdb4a36708a2dae0478fb81d8fe37e Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Wed, 20 Jun 2012 23:30:35 +0200 Subject: [PATCH] homepage realtime featured game --- app/controllers/Lobby.scala | 7 ++++- app/game/Featured.scala | 51 +++++++++++++++++++++++++++++++ app/game/GameEnv.scala | 3 ++ app/game/GameRepo.scala | 11 +++++-- app/lobby/Socket.scala | 5 ++- app/round/MoveNotifier.scala | 12 +++++--- app/round/RoundEnv.scala | 1 + app/site/Hub.scala | 15 --------- app/site/actorApi.scala | 8 ----- app/socket/HubActor.scala | 29 +++++++++++++----- app/socket/actorApi.scala | 7 +++++ app/views/lobby/home.scala.html | 34 ++++++++++++++++----- app/views/lobby/layout.scala.html | 11 ------- app/views/lobby/log.scala.html | 2 +- public/javascripts/ctrl.js | 2 +- public/javascripts/gamelist.js | 8 ++++- public/javascripts/socket.js | 3 -- public/stylesheets/board.css | 4 +-- public/stylesheets/dark.css | 2 +- public/stylesheets/gamelist.css | 2 +- public/stylesheets/opening.css | 8 +++++ 21 files changed, 159 insertions(+), 66 deletions(-) create mode 100644 app/game/Featured.scala delete mode 100644 app/views/lobby/layout.scala.html diff --git a/app/controllers/Lobby.scala b/app/controllers/Lobby.scala index bd60501309..fef3b515e5 100644 --- a/app/controllers/Lobby.scala +++ b/app/controllers/Lobby.scala @@ -18,6 +18,7 @@ object Lobby extends LilaController { def forumRecent = env.forum.recent def timelineRecent = env.timeline.entryRepo.recent def messageRepo = env.lobby.messageRepo + def featured = env.game.featured val home = Open { implicit ctx ⇒ renderHome(none).fold(identity, Ok(_)) @@ -36,7 +37,11 @@ object Lobby extends LilaController { timeline = timelineRecent ).unsafePerformIO.bimap( url ⇒ Redirect(url), - preload ⇒ html.lobby.home(toJson(preload), myHook, forumRecent(ctx.me)) + preload ⇒ html.lobby.home( + toJson(preload), + myHook, + forumRecent(ctx.me), + featured.one) ) def socket = WebSocket.async[JsValue] { implicit req ⇒ diff --git a/app/game/Featured.scala b/app/game/Featured.scala new file mode 100644 index 0000000000..832be8bc50 --- /dev/null +++ b/app/game/Featured.scala @@ -0,0 +1,51 @@ +package lila +package game + +import akka.actor._ +import akka.dispatch.{ Future, Await } +import akka.pattern.ask +import akka.util.Duration +import akka.util.duration._ +import akka.util.Timeout +import play.api.Play.current +import play.api.libs.concurrent._ +import scalaz.effects._ + +final class Featured( + gameRepo: GameRepo) { + + import Featured._ + + def one: Option[DbGame] = Await.result( + actor ? GetOne mapTo manifest[Option[DbGame]], + atMost) + + private val atMost = 2.second + private implicit val timeout = Timeout(atMost) + + private val actor = Akka.system.actorOf(Props(new Actor { + + private var oneId = none[String] + + def receive = { + case GetOne ⇒ sender ! getOne + } + + private def getOne = oneId flatMap fetch filter valid orElse { + feature ~ { o ⇒ oneId = o map (_.id) } + } + + private def fetch(id: String): Option[DbGame] = + gameRepo.game(id).unsafePerformIO + + private def valid(game: DbGame) = true + + private def feature: Option[DbGame] = + gameRepo.featuredCandidates.unsafePerformIO.headOption + })) +} + +object Featured { + + case object GetOne +} diff --git a/app/game/GameEnv.scala b/app/game/GameEnv.scala index 778f606ba6..6016789ec6 100644 --- a/app/game/GameEnv.scala +++ b/app/game/GameEnv.scala @@ -22,6 +22,9 @@ final class GameEnv( cached = cached, maxPerPage = GamePaginatorMaxPerPage) + lazy val featured = new Featured( + gameRepo = gameRepo) + lazy val export = Export(gameRepo) _ lazy val listMenu = ListMenu(cached) _ diff --git a/app/game/GameRepo.scala b/app/game/GameRepo.scala index f23210e945..49e7d233b1 100644 --- a/app/game/GameRepo.scala +++ b/app/game/GameRepo.scala @@ -111,11 +111,11 @@ class GameRepo(collection: MongoCollection) } def denormalizeStarted(game: DbGame): IO[Unit] = io { - update(idSelector(game), + update(idSelector(game), $set("userIds" -> game.players.map(_.userId).flatten)) update(idSelector(game), game.mode.rated.fold( $set("isRated" -> true), $unset("isRated"))) - if (game.variant.exotic) update(idSelector(game), + if (game.variant.exotic) update(idSelector(game), $set("initialFen" -> (Forsyth >> game.toChess))) } @@ -155,6 +155,13 @@ class GameRepo(collection: MongoCollection) ).toList.map(_.decode).flatten } + def featuredCandidates: IO[List[DbGame]] = io { + find(Query.playable ++ + Query.clock(true) ++ + ("createdAt" $gt (DateTime.now - 30.minutes)) + ).toList.map(_.decode).flatten + } + def count(query: DBObject): IO[Int] = io { super.count(query).toInt } diff --git a/app/lobby/Socket.scala b/app/lobby/Socket.scala index 99742d6ceb..7716aeee07 100644 --- a/app/lobby/Socket.scala +++ b/app/lobby/Socket.scala @@ -11,7 +11,7 @@ import play.api.libs.concurrent._ import scalaz.effects._ import implicits.RichJs._ -import socket.{ Util, PingVersion, Quit } +import socket.{ Util, PingVersion, Quit, LiveGames } import timeline.Entry import game.DbGame import security.Flood @@ -41,6 +41,9 @@ final class Socket(hub: ActorRef, flood: Flood) { case Some("p") ⇒ e int "v" foreach { v ⇒ hub ! PingVersion(uid, v) } + case Some("liveGames") ⇒ e str "d" foreach { ids ⇒ + hub ! LiveGames(uid, ids.split(' ').toList) + } case _ ⇒ } } mapDone { _ ⇒ diff --git a/app/round/MoveNotifier.scala b/app/round/MoveNotifier.scala index 0963211e08..7a8a0fe9e2 100644 --- a/app/round/MoveNotifier.scala +++ b/app/round/MoveNotifier.scala @@ -11,13 +11,17 @@ import play.api.libs.concurrent._ import play.api.Play.current final class MoveNotifier( - siteHubName: String, - countMove: () => Unit) { + siteHubName: String, + lobbyHubName: String, + countMove: () ⇒ Unit) { - lazy val siteHubRef = Akka.system.actorFor("/user/" + siteHubName) + lazy val hubRefs = List(siteHubName, lobbyHubName) map { name ⇒ + Akka.system.actorFor("/user/" + name) + } def apply(gameId: String, fen: String): IO[Unit] = io { countMove() - siteHubRef ! Fen(gameId, fen) + val message = Fen(gameId, fen) + hubRefs foreach (_ ! message) } } diff --git a/app/round/RoundEnv.scala b/app/round/RoundEnv.scala index 9e2dfbc7b6..3c59b0df7f 100644 --- a/app/round/RoundEnv.scala +++ b/app/round/RoundEnv.scala @@ -41,6 +41,7 @@ final class RoundEnv( lazy val moveNotifier = new MoveNotifier( siteHubName = ActorSiteHub, + lobbyHubName = ActorLobbyHub, countMove = countMove) lazy val socket = new Socket( diff --git a/app/site/Hub.scala b/app/site/Hub.scala index 8b807f0a8e..3109a10f5f 100644 --- a/app/site/Hub.scala +++ b/app/site/Hub.scala @@ -16,20 +16,5 @@ final class Hub(timeout: Int) extends HubActor[Member](timeout) { addMember(uid, Member(channel, username)) sender ! Connected(enumerator, channel) } - - case Fen(gameId, fen) ⇒ notifyFen(gameId, fen) - - case LiveGames(uid, gameIds) ⇒ registerLiveGames(uid, gameIds) - } - - def notifyFen(gameId: String, fen: String) { - val msg = makeMessage("fen", JsObject(Seq( - "id" -> JsString(gameId), - "fen" -> JsString(fen)))) - members.values filter (_ liveGames gameId) foreach (_.channel push msg) - } - - def registerLiveGames(uid: String, ids: List[String]) { - member(uid) foreach (_ addLiveGames ids) } } diff --git a/app/site/actorApi.scala b/app/site/actorApi.scala index d4226dcd11..3a004e9a77 100644 --- a/app/site/actorApi.scala +++ b/app/site/actorApi.scala @@ -3,17 +3,9 @@ package site import socket.SocketMember -import scala.collection.mutable - case class Member( channel: JsChannel, username: Option[String]) extends SocketMember { - - val liveGames = mutable.Set[String]() - - def addLiveGames(ids: List[String]) { - ids foreach liveGames.+= - } } case class Join( diff --git a/app/socket/HubActor.scala b/app/socket/HubActor.scala index f2bca06730..c5ecd36849 100644 --- a/app/socket/HubActor.scala +++ b/app/socket/HubActor.scala @@ -16,20 +16,24 @@ abstract class HubActor[M <: SocketMember](uidTimeout: Int) extends Actor { // generic message handler def receiveGeneric: Receive = { - case Ping(uid) ⇒ ping(uid) + case Ping(uid) ⇒ ping(uid) - case Broom ⇒ broom() + case Broom ⇒ broom() // when a member quits - case Quit(uid) ⇒ quit(uid) + case Quit(uid) ⇒ quit(uid) - case GetNbMembers ⇒ sender ! members.size + case GetNbMembers ⇒ sender ! members.size - case NbMembers(nb) ⇒ pong = makePong(nb) + case NbMembers(nb) ⇒ pong = makePong(nb) - case GetUsernames ⇒ sender ! usernames + case GetUsernames ⇒ sender ! usernames - case SendTo(userId, msg) ⇒ sendTo(userId, msg) + case LiveGames(uid, gameIds) ⇒ registerLiveGames(uid, gameIds) + + case Fen(gameId, fen) ⇒ notifyFen(gameId, fen) + + case SendTo(userId, msg) ⇒ sendTo(userId, msg) } def receive = receiveSpecific orElse receiveGeneric @@ -94,4 +98,15 @@ abstract class HubActor[M <: SocketMember](uidTimeout: Int) extends Actor { } def usernames: Iterable[String] = members.values.map(_.username).flatten + + def notifyFen(gameId: String, fen: String) { + val msg = makeMessage("fen", JsObject(Seq( + "id" -> JsString(gameId), + "fen" -> JsString(fen)))) + members.values filter (_ liveGames gameId) foreach (_.channel push msg) + } + + def registerLiveGames(uid: String, ids: List[String]) { + member(uid) foreach (_ addLiveGames ids) + } } diff --git a/app/socket/actorApi.scala b/app/socket/actorApi.scala index 7827075e42..a7700f2848 100644 --- a/app/socket/actorApi.scala +++ b/app/socket/actorApi.scala @@ -2,12 +2,19 @@ package lila package socket import play.api.libs.json.JsObject +import scala.collection.mutable trait SocketMember { val channel: JsChannel val username: Option[String] lazy val userId: Option[String] = username map (_.toLowerCase) + + val liveGames = mutable.Set[String]() + + def addLiveGames(ids: List[String]) { + ids foreach liveGames.+= + } } case object Close case object GetUsernames diff --git a/app/views/lobby/home.scala.html b/app/views/lobby/home.scala.html index adc2ae8fef..5d4fc19f44 100644 --- a/app/views/lobby/home.scala.html +++ b/app/views/lobby/home.scala.html @@ -1,15 +1,15 @@ -@(preload: String, myHook: Option[lila.lobby.Hook], forumRecent: List[lila.forum.PostView])(implicit ctx: Context) +@(preload: String, myHook: Option[lila.lobby.Hook], forumRecent: List[lila.forum.PostView], featured: Option[DbGame])(implicit ctx: Context) @chat = { @for(m <- ctx.me; if m.canChat) {
@trans.chatRoom() -
@@ -25,9 +25,29 @@ } } -@lobby.layout(title = "", chat = chat.some) { +@underchat = { +@featured.map { game => + +} +} + +@baseline = { +@trans.freeOnlineChess() +} + +@base.layout( +title = "", +baseline = baseline.some, +active = siteMenu.play.some, +chat = chat.some, +underchat = underchat.some) {
-@translationCall.map(i18n.callBox(_)) + @translationCall.map(i18n.callBox(_))
diff --git a/app/views/lobby/layout.scala.html b/app/views/lobby/layout.scala.html deleted file mode 100644 index f0f43f38ed..0000000000 --- a/app/views/lobby/layout.scala.html +++ /dev/null @@ -1,11 +0,0 @@ -@(title: String, chat: Option[Html] = None)(body: Html)(implicit ctx: Context) - -@baseline = { -@trans.freeOnlineChess() -} - -@base.layout( -title = title, -baseline = baseline.some, -active = siteMenu.play.some, -chat = chat)(body) diff --git a/app/views/lobby/log.scala.html b/app/views/lobby/log.scala.html index bbfb0ed242..0edfa105c6 100644 --- a/app/views/lobby/log.scala.html +++ b/app/views/lobby/log.scala.html @@ -2,7 +2,7 @@ @title = @{ "Chat logs" } -@lobby.layout(title = title) { +@base.layout(title = title) {