diff --git a/app/analyse/AnalyseEnv.scala b/app/analyse/AnalyseEnv.scala index ecf8e89fde..df3fd4f634 100644 --- a/app/analyse/AnalyseEnv.scala +++ b/app/analyse/AnalyseEnv.scala @@ -29,4 +29,14 @@ final class AnalyseEnv( analysisRepo = analysisRepo, gameRepo = gameRepo, generator = generator) + + lazy val paginator = new PaginatorBuilder( + analysisRepo = analysisRepo, + cached = cached, + gameRepo = gameRepo, + maxPerPage = GamePaginatorMaxPerPage) + + lazy val cached = new Cached( + analysisRepo = analysisRepo, + nbTtl = AnalyseCachedNbTtl) } diff --git a/app/analyse/AnalysisRepo.scala b/app/analyse/AnalysisRepo.scala index 0210accd7f..9e3da2bea5 100644 --- a/app/analyse/AnalysisRepo.scala +++ b/app/analyse/AnalysisRepo.scala @@ -7,7 +7,7 @@ import com.mongodb.casbah.query.Imports._ import scalaz.effects._ import org.joda.time.DateTime -final class AnalysisRepo(collection: MongoCollection) { +final class AnalysisRepo(val collection: MongoCollection) { def done(id: String, a: Analysis) = io { collection.update( diff --git a/app/analyse/Cached.scala b/app/analyse/Cached.scala new file mode 100644 index 0000000000..f9cc08aa31 --- /dev/null +++ b/app/analyse/Cached.scala @@ -0,0 +1,28 @@ +package lila +package analyse + +import akka.util.duration._ + +import memo.ActorMemo + +final class Cached( + analysisRepo: AnalysisRepo, + nbTtl: Int) { + + import Cached._ + + def nbAnalysis: Int = memo(NbAnalysis) + + private val memo = ActorMemo(loadFromDb, nbTtl, 5.seconds) + + private def loadFromDb(key: Key) = key match { + case NbAnalysis ⇒ analysisRepo.collection.count.toInt + } +} + +object Cached { + + sealed trait Key + + case object NbAnalysis extends Key +} diff --git a/app/analyse/PaginatorBuilder.scala b/app/analyse/PaginatorBuilder.scala new file mode 100644 index 0000000000..49fe3a6beb --- /dev/null +++ b/app/analyse/PaginatorBuilder.scala @@ -0,0 +1,43 @@ +package lila +package analyse + +import game.{ DbGame, GameRepo } + +import com.github.ornicar.paginator._ +import com.mongodb.casbah.Imports._ +import org.joda.time.DateTime + +final class PaginatorBuilder( + analysisRepo: AnalysisRepo, + cached: Cached, + gameRepo: GameRepo, + maxPerPage: Int) { + + def games(page: Int): Paginator[DbGame] = { + paginator(GameAdapter, page) + } + + private def paginator(adapter: Adapter[DbGame], page: Int): Paginator[DbGame] = + Paginator( + adapter, + currentPage = page, + maxPerPage = maxPerPage + ).fold(_ ⇒ paginator(adapter, 0), identity) + + private object GameAdapter extends Adapter[DbGame] { + + def nbResults: Int = cached.nbAnalysis + + def slice(offset: Int, length: Int): Seq[DbGame] = { + val ids = ((analysisRepo.collection.find(query, select) sort sort skip offset limit length).toList map { + _.getAs[String]("_id") + }).flatten + val games = (gameRepo games ids).unsafePerformIO + ids map { id ⇒ games find (_.id == id) } + } flatten + + private def query = DBObject("done" -> true) + private def select = DBObject("_id" -> true) + private def sort = DBObject("date" -> -1) + } +} diff --git a/app/controllers/Game.scala b/app/controllers/Game.scala index 349a42ccc3..027b45df3e 100644 --- a/app/controllers/Game.scala +++ b/app/controllers/Game.scala @@ -10,7 +10,9 @@ object Game extends LilaController { val gameRepo = env.game.gameRepo val paginator = env.game.paginator + val analysePaginator = env.analyse.paginator val cached = env.game.cached + val analyseCached = env.analyse.cached val bookmarkApi = env.bookmark.api val listMenu = env.game.listMenu @@ -47,6 +49,12 @@ object Game extends LilaController { } } + def analysed(page: Int) = Open { implicit ctx ⇒ + reasonable(page) { + Ok(html.game.analysed(analysePaginator games page, makeListMenu)) + } + } + def featuredJs(id: String) = Open { implicit ctx ⇒ IOk(gameRepo game id map { gameOption => html.game.featuredJs(gameOption) @@ -57,5 +65,5 @@ object Game extends LilaController { (page < maxPage).fold(result, BadRequest("too old")) private def makeListMenu(implicit ctx: Context) = - listMenu(bookmarkApi.countByUser, ctx.me) + listMenu(bookmarkApi.countByUser, analyseCached.nbAnalysis, ctx.me) } diff --git a/app/core/Settings.scala b/app/core/Settings.scala index af7eed1461..3f68586ded 100644 --- a/app/core/Settings.scala +++ b/app/core/Settings.scala @@ -28,6 +28,8 @@ final class Settings(config: Config) { val GameCachedNbTtl = millis("game.cached.nb.ttl") val GamePaginatorMaxPerPage = getInt("game.paginator.max_per_page") + val AnalyseCachedNbTtl = millis("analyse.cached.nb.ttl") + val UserPaginatorMaxPerPage = getInt("user.paginator.max_per_page") val UserEloUpdaterFloor = getInt("user.elo_updater.floor") val UserCachedNbTtl = millis("user.cached.nb.ttl") diff --git a/app/game/ListMenu.scala b/app/game/ListMenu.scala index db590edc9f..92b5634931 100644 --- a/app/game/ListMenu.scala +++ b/app/game/ListMenu.scala @@ -9,16 +9,18 @@ case class ListMenu( nbGames: Int, nbMates: Int, nbPopular: Int, - nbBookmarks: Option[Int]) + nbBookmarks: Option[Int], + nbAnalysed: Int) object ListMenu { type CountBookmarks = User ⇒ Int - def apply(cached: Cached)(countBookmarks: CountBookmarks, me: Option[User]): ListMenu = + def apply(cached: Cached)(countBookmarks: CountBookmarks, countAnalysed: ⇒ Int, me: Option[User]): ListMenu = new ListMenu( nbGames = cached.nbGames, nbMates = cached.nbMates, nbPopular = cached.nbPopular, - nbBookmarks = me map countBookmarks) + nbBookmarks = me map countBookmarks, + nbAnalysed = countAnalysed) } diff --git a/app/i18n/I18nKeys.scala b/app/i18n/I18nKeys.scala index a109ed841c..f9442aeca9 100644 --- a/app/i18n/I18nKeys.scala +++ b/app/i18n/I18nKeys.scala @@ -82,6 +82,7 @@ final class I18nKeys(translator: Translator) { val viewNbCheckmates = new Key("viewNbCheckmates") val nbBookmarks = new Key("nbBookmarks") val nbPopularGames = new Key("nbPopularGames") + val nbAnalysedGames = new Key("nbAnalysedGames") val bookmarkedByNbPlayers = new Key("bookmarkedByNbPlayers") val viewInFullSize = new Key("viewInFullSize") val logOut = new Key("logOut") @@ -162,5 +163,5 @@ final class I18nKeys(translator: Translator) { val toggleBackground = new Key("toggleBackground") val freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents = new Key("freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents") - def keys = List(playWithAFriend, inviteAFriendToPlayWithYou, playWithTheMachine, challengeTheArtificialIntelligence, toInviteSomeoneToPlayGiveThisUrl, gameOver, waitingForOpponent, waiting, yourTurn, aiNameLevelAiLevel, level, toggleTheChat, toggleSound, chat, resign, checkmate, stalemate, white, black, createAGame, noGameAvailableRightNowCreateOne, whiteIsVictorious, blackIsVictorious, playWithTheSameOpponentAgain, newOpponent, playWithAnotherOpponent, yourOpponentWantsToPlayANewGameWithYou, joinTheGame, whitePlays, blackPlays, theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim, makeYourOpponentResign, forceResignation, talkInChat, theFirstPersonToComeOnThisUrlWillPlayWithYou, whiteCreatesTheGame, blackCreatesTheGame, whiteJoinsTheGame, blackJoinsTheGame, whiteResigned, blackResigned, whiteLeftTheGame, blackLeftTheGame, shareThisUrlToLetSpectatorsSeeTheGame, youAreViewingThisGameAsASpectator, replayAndAnalyse, viewGameStats, flipBoard, threefoldRepetition, claimADraw, offerDraw, draw, nbConnectedPlayers, talkAboutChessAndDiscussLichessFeaturesInTheForum, seeTheGamesBeingPlayedInRealTime, gamesBeingPlayedRightNow, viewAllNbGames, viewNbCheckmates, nbBookmarks, nbPopularGames, bookmarkedByNbPlayers, viewInFullSize, logOut, signIn, signUp, people, games, forum, chessPlayers, minutesPerSide, variant, timeControl, start, username, password, haveAnAccount, allYouNeedIsAUsernameAndAPassword, learnMoreAboutLichess, rank, gamesPlayed, declineInvitation, cancel, timeOut, drawOfferSent, drawOfferDeclined, drawOfferAccepted, drawOfferCanceled, yourOpponentOffersADraw, accept, decline, playingRightNow, abortGame, gameAborted, standard, unlimited, mode, casual, rated, thisGameIsRated, rematch, rematchOfferSent, rematchOfferAccepted, rematchOfferCanceled, rematchOfferDeclined, cancelRematchOffer, viewRematch, play, inbox, chatRoom, spectatorRoom, composeMessage, sentMessages, incrementInSeconds, freeOnlineChess, spectators, nbWins, nbLosses, nbDraws, exportGames, color, eloRange, giveNbSeconds, searchAPlayer, whoIsOnline, allPlayers, namedPlayers, premoveEnabledClickAnywhereToCancel, thisPlayerUsesChessComputerAssistance, opening, takeback, proposeATakeback, takebackPropositionSent, takebackPropositionDeclined, takebackPropositionAccepted, takebackPropositionCanceled, yourOpponentProposesATakeback, bookmarkThisGame, toggleBackground, freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents) + def keys = List(playWithAFriend, inviteAFriendToPlayWithYou, playWithTheMachine, challengeTheArtificialIntelligence, toInviteSomeoneToPlayGiveThisUrl, gameOver, waitingForOpponent, waiting, yourTurn, aiNameLevelAiLevel, level, toggleTheChat, toggleSound, chat, resign, checkmate, stalemate, white, black, createAGame, noGameAvailableRightNowCreateOne, whiteIsVictorious, blackIsVictorious, playWithTheSameOpponentAgain, newOpponent, playWithAnotherOpponent, yourOpponentWantsToPlayANewGameWithYou, joinTheGame, whitePlays, blackPlays, theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim, makeYourOpponentResign, forceResignation, talkInChat, theFirstPersonToComeOnThisUrlWillPlayWithYou, whiteCreatesTheGame, blackCreatesTheGame, whiteJoinsTheGame, blackJoinsTheGame, whiteResigned, blackResigned, whiteLeftTheGame, blackLeftTheGame, shareThisUrlToLetSpectatorsSeeTheGame, youAreViewingThisGameAsASpectator, replayAndAnalyse, viewGameStats, flipBoard, threefoldRepetition, claimADraw, offerDraw, draw, nbConnectedPlayers, talkAboutChessAndDiscussLichessFeaturesInTheForum, seeTheGamesBeingPlayedInRealTime, gamesBeingPlayedRightNow, viewAllNbGames, viewNbCheckmates, nbBookmarks, nbPopularGames, nbAnalysedGames, bookmarkedByNbPlayers, viewInFullSize, logOut, signIn, signUp, people, games, forum, chessPlayers, minutesPerSide, variant, timeControl, start, username, password, haveAnAccount, allYouNeedIsAUsernameAndAPassword, learnMoreAboutLichess, rank, gamesPlayed, declineInvitation, cancel, timeOut, drawOfferSent, drawOfferDeclined, drawOfferAccepted, drawOfferCanceled, yourOpponentOffersADraw, accept, decline, playingRightNow, abortGame, gameAborted, standard, unlimited, mode, casual, rated, thisGameIsRated, rematch, rematchOfferSent, rematchOfferAccepted, rematchOfferCanceled, rematchOfferDeclined, cancelRematchOffer, viewRematch, play, inbox, chatRoom, spectatorRoom, composeMessage, sentMessages, incrementInSeconds, freeOnlineChess, spectators, nbWins, nbLosses, nbDraws, exportGames, color, eloRange, giveNbSeconds, searchAPlayer, whoIsOnline, allPlayers, namedPlayers, premoveEnabledClickAnywhereToCancel, thisPlayerUsesChessComputerAssistance, opening, takeback, proposeATakeback, takebackPropositionSent, takebackPropositionDeclined, takebackPropositionAccepted, takebackPropositionCanceled, yourOpponentProposesATakeback, bookmarkThisGame, toggleBackground, freeOnlineChessGamePlayChessNowInACleanInterfaceNoRegistrationNoAdsNoPluginRequiredPlayChessWithComputerFriendsOrRandomOpponents) } diff --git a/app/timeline/Entry.scala b/app/timeline/Entry.scala index d2521c3503..ce41f913a8 100644 --- a/app/timeline/Entry.scala +++ b/app/timeline/Entry.scala @@ -2,7 +2,6 @@ package lila package timeline import com.novus.salat.annotations._ -import com.mongodb.BasicDBList case class Entry( gameId: String, diff --git a/app/views/game/analysed.scala.html b/app/views/game/analysed.scala.html new file mode 100644 index 0000000000..e33c54935f --- /dev/null +++ b/app/views/game/analysed.scala.html @@ -0,0 +1,7 @@ +@(paginator: Paginator[DbGame], listMenu: lila.game.ListMenu)(implicit ctx: Context) + +@game.list( +name = trans.nbAnalysedGames.str(listMenu.nbAnalysed.localize), +paginator = paginator, +next = paginator.nextPage map { n => routes.Game.analysed(n) }, +menu = sideMenu(listMenu, "analysed")) diff --git a/app/views/game/sideMenu.scala.html b/app/views/game/sideMenu.scala.html index 0ea6ee8208..f756af61dd 100644 --- a/app/views/game/sideMenu.scala.html +++ b/app/views/game/sideMenu.scala.html @@ -17,3 +17,6 @@ @trans.nbPopularGames(listMenu.nbPopular.localize) + + @trans.nbAnalysedGames(listMenu.nbAnalysed.localize) + diff --git a/conf/application.conf b/conf/application.conf index a6b85886dd..7fb21bdbc0 100644 --- a/conf/application.conf +++ b/conf/application.conf @@ -50,11 +50,14 @@ game { message.lifetime = 30 seconds uid.timeout = 10 seconds hub.timeout = 2 minutes - player.timeout = 1 minute + player.timeout = 1 minutes animation.delay = 200 ms - cached.nb.ttl = 10 minute + cached.nb.ttl = 10 minutes paginator.max_per_page = 10 } +analyse { + cached.nb.ttl = 10 minutes +} forum { topic.max_per_page = 10 post.max_per_page = 10 @@ -136,7 +139,7 @@ logger { akka { loglevel = INFO - stdout-loglevel = DEBUG + stdout-loglevel = INFO log-config-on-start = off event-handlers = ["lila.core.AkkaLogger"] } diff --git a/conf/messages b/conf/messages index bda5203a4e..0f4833510e 100644 --- a/conf/messages +++ b/conf/messages @@ -58,6 +58,7 @@ viewAllNbGames=%s Games viewNbCheckmates=%s Checkmates nbBookmarks=%s Bookmarks nbPopularGames=%s Popular Games +nbAnalysedGames=%s Analysed Games bookmarkedByNbPlayers=Bookmarked by %s players viewInFullSize=View in full size logOut=Log out diff --git a/conf/routes b/conf/routes index 62f5cdb93d..8321b9b0ce 100644 --- a/conf/routes +++ b/conf/routes @@ -7,6 +7,7 @@ GET /games/all controllers.Game.all(page: Int ?= 1) GET /games/checkmate controllers.Game.checkmate(page: Int ?= 1) GET /games/bookmark controllers.Game.bookmark(page: Int ?= 1) GET /games/popular controllers.Game.popular(page: Int ?= 1) +GET /games/analysed controllers.Game.analysed(page: Int ?= 1) GET /games/featured/js controllers.Game.featuredJs(id: String ?= "") # Round diff --git a/todo b/todo index 54f3ec7dfb..9e80713f95 100644 --- a/todo +++ b/todo @@ -30,3 +30,8 @@ legend feedback: http://en.lichess.org/forum/lichess-feedback/a-few-points-bugss http://en.lichess.org/forum/lichess-feedback/problems-with-yin-yang#1 also translate websockets error message play with a friend possible bug http://en.lichess.org/forum/lichess-feedback/play-with-a-friend-bug + +next deploy +----------- +db.analysis.ensureIndex({done:1}) +db.analysis.ensureIndex({date:-1})